Add inferred dark evidence drawer and synthetic bursts

This commit is contained in:
dirtydishes 2026-01-04 19:05:20 -05:00
parent ea61c3b013
commit 8fc8361390
4 changed files with 517 additions and 28 deletions

View file

@ -186,7 +186,8 @@ h1 {
.card-flow, .card-flow,
.card-alerts, .card-alerts,
.card-classifiers { .card-classifiers,
.card-dark {
grid-column: span 2; grid-column: span 2;
} }
@ -857,7 +858,8 @@ h1 {
.card-equities, .card-equities,
.card-flow, .card-flow,
.card-alerts, .card-alerts,
.card-classifiers { .card-classifiers,
.card-dark {
grid-column: span 2; grid-column: span 2;
} }
} }
@ -871,31 +873,36 @@ h1 {
.card-equities, .card-equities,
.card-flow, .card-flow,
.card-alerts, .card-alerts,
.card-classifiers { .card-classifiers,
.card-dark {
grid-column: span 1; grid-column: span 1;
} }
} }
.card-flow .row, .card-flow .row,
.card-alerts .row, .card-alerts .row,
.card-classifiers .row { .card-classifiers .row,
.card-dark .row {
padding: 12px 14px; padding: 12px 14px;
} }
.card-flow .meta, .card-flow .meta,
.card-alerts .meta, .card-alerts .meta,
.card-classifiers .meta { .card-classifiers .meta,
.card-dark .meta {
font-size: 0.78rem; font-size: 0.78rem;
} }
.card-flow .note, .card-flow .note,
.card-alerts .note, .card-alerts .note,
.card-classifiers .note { .card-classifiers .note,
.card-dark .note {
font-size: 0.72rem; font-size: 0.72rem;
} }
.card-flow, .card-flow,
.card-alerts, .card-alerts,
.card-classifiers { .card-classifiers,
.card-dark {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 960px; height: 960px;
@ -919,7 +926,8 @@ h1 {
@media (max-width: 720px) { @media (max-width: 720px) {
.card-flow, .card-flow,
.card-alerts, .card-alerts,
.card-classifiers { .card-classifiers,
.card-dark {
height: 780px; height: 780px;
} }
} }

View file

@ -5,7 +5,9 @@ import type {
AlertEvent, AlertEvent,
ClassifierHitEvent, ClassifierHitEvent,
EquityPrint, EquityPrint,
EquityPrintJoin,
FlowPacket, FlowPacket,
InferredDarkEvent,
OptionNBBO, OptionNBBO,
OptionPrint OptionPrint
} from "@islandflow/types"; } from "@islandflow/types";
@ -24,7 +26,9 @@ type MessageType =
| "option-print" | "option-print"
| "option-nbbo" | "option-nbbo"
| "equity-print" | "equity-print"
| "equity-join"
| "flow-packet" | "flow-packet"
| "inferred-dark"
| "classifier-hit" | "classifier-hit"
| "alert"; | "alert";
@ -223,6 +227,46 @@ const extractUnderlying = (contractId: string): string => {
return contractId.split("-")[0]?.toUpperCase() ?? contractId.toUpperCase(); return contractId.split("-")[0]?.toUpperCase() ?? contractId.toUpperCase();
}; };
const extractEquityTraceFromJoin = (joinId: string): string | null => {
const match = joinId.match(/^equityjoin:(.+)$/);
return match?.[1] ?? null;
};
const inferDarkUnderlying = (
event: InferredDarkEvent,
equityPrints: Map<string, EquityPrint>,
equityJoins: Map<string, EquityPrintJoin>
): string | null => {
for (const ref of event.evidence_refs) {
const join = equityJoins.get(ref);
if (!join) {
continue;
}
const underlying = join.features.underlying_id;
if (typeof underlying === "string" && underlying.length > 0) {
return underlying.toUpperCase();
}
}
const match = event.trace_id.match(/^dark:(?:stealth_accumulation|distribution):([^:]+):/);
if (match?.[1]) {
return match[1].toUpperCase();
}
for (const ref of event.evidence_refs) {
const traceId = extractEquityTraceFromJoin(ref);
if (!traceId) {
continue;
}
const print = equityPrints.get(traceId);
if (print) {
return print.underlying_id.toUpperCase();
}
}
return null;
};
const parseNumber = (value: unknown, fallback: number): number => { const parseNumber = (value: unknown, fallback: number): number => {
if (typeof value === "number" && Number.isFinite(value)) { if (typeof value === "number" && Number.isFinite(value)) {
return value; return value;
@ -238,6 +282,38 @@ const parseNumber = (value: unknown, fallback: number): number => {
return fallback; return fallback;
}; };
const parseBoolean = (value: unknown, fallback = false): boolean => {
if (typeof value === "boolean") {
return value;
}
if (typeof value === "number") {
return value !== 0;
}
if (typeof value === "string") {
const normalized = value.trim().toLowerCase();
if (["true", "1", "yes", "on"].includes(normalized)) {
return true;
}
if (["false", "0", "no", "off"].includes(normalized)) {
return false;
}
}
return fallback;
};
const getJoinString = (join: EquityPrintJoin, key: string): string | null => {
const value = join.features[key];
return typeof value === "string" ? value : null;
};
const getJoinNumber = (join: EquityPrintJoin, key: string, fallback = Number.NaN): number => {
return parseNumber(join.features[key], fallback);
};
const getJoinBoolean = (join: EquityPrintJoin, key: string): boolean => {
return parseBoolean(join.features[key], false);
};
type NbboSide = "AA" | "A" | "B" | "BB"; type NbboSide = "AA" | "A" | "B" | "BB";
const classifyNbboSide = (price: number, quote: OptionNBBO | null | undefined): NbboSide | null => { const classifyNbboSide = (price: number, quote: OptionNBBO | null | undefined): NbboSide | null => {
@ -445,14 +521,18 @@ type TapeConfig<T> = {
pollMs?: number; pollMs?: number;
captureScroll?: () => void; captureScroll?: () => void;
onNewItems?: (count: number) => void; onNewItems?: (count: number) => void;
getItemTs?: (item: T) => number;
getReplayKey?: (item: T) => string | null;
}; };
const useTape = <T extends { ts: number; seq: number }>( const useTape = <T extends SortableItem & { seq: number }>(
config: TapeConfig<T> config: TapeConfig<T>
): TapeState<T> => { ): TapeState<T> => {
const { mode, wsPath, replayPath, expectedType, latestPath, onNewItems, captureScroll } = config; const { mode, wsPath, replayPath, expectedType, latestPath, onNewItems, captureScroll } = config;
const batchSize = config.batchSize ?? 40; const batchSize = config.batchSize ?? 40;
const pollMs = config.pollMs ?? 1000; const pollMs = config.pollMs ?? 1000;
const getItemTs = config.getItemTs ?? extractSortTs;
const getReplayKey = config.getReplayKey ?? extractTracePrefix;
const [status, setStatus] = useState<WsStatus>("connecting"); const [status, setStatus] = useState<WsStatus>("connecting");
const [items, setItems] = useState<T[]>([]); const [items, setItems] = useState<T[]>([]);
const [lastUpdate, setLastUpdate] = useState<number | null>(null); const [lastUpdate, setLastUpdate] = useState<number | null>(null);
@ -561,7 +641,7 @@ const useTape = <T extends { ts: number; seq: number }>(
const payload = (await response.json()) as { data?: T[] }; const payload = (await response.json()) as { data?: T[] };
const latest = payload.data?.[0]; const latest = payload.data?.[0];
if (active && latest) { if (active && latest) {
replayEndRef.current = latest.ts; replayEndRef.current = getItemTs(latest);
} }
} catch (error) { } catch (error) {
console.warn("Failed to load replay end cursor", error); console.warn("Failed to load replay end cursor", error);
@ -573,7 +653,7 @@ const useTape = <T extends { ts: number; seq: number }>(
return () => { return () => {
active = false; active = false;
}; };
}, [mode, latestPath]); }, [mode, latestPath, getItemTs]);
useEffect(() => { useEffect(() => {
if (mode !== "live") { if (mode !== "live") {
@ -703,21 +783,21 @@ const useTape = <T extends { ts: number; seq: number }>(
let sourcePrefix = replaySourceRef.current; let sourcePrefix = replaySourceRef.current;
if (!sourcePrefix) { if (!sourcePrefix) {
const firstWithTrace = payload.data.find((item) => extractTracePrefix(item)); const firstWithTrace = payload.data.find((item) => getReplayKey(item));
if (firstWithTrace) { if (firstWithTrace) {
sourcePrefix = extractTracePrefix(firstWithTrace); sourcePrefix = getReplayKey(firstWithTrace);
replaySourceRef.current = sourcePrefix ?? null; replaySourceRef.current = sourcePrefix ?? null;
} }
} }
const filtered = sourcePrefix const filtered = sourcePrefix
? payload.data.filter((item) => extractTracePrefix(item) === sourcePrefix) ? payload.data.filter((item) => getReplayKey(item) === sourcePrefix)
: payload.data; : payload.data;
const hasForeign = const hasForeign =
sourcePrefix && sourcePrefix &&
payload.data.some((item) => { payload.data.some((item) => {
const prefix = extractTracePrefix(item); const prefix = getReplayKey(item);
return prefix !== null && prefix !== sourcePrefix; return prefix !== null && prefix !== sourcePrefix;
}); });
@ -728,9 +808,10 @@ const useTape = <T extends { ts: number; seq: number }>(
scheduleFlush(); scheduleFlush();
const last = filtered.at(-1); const last = filtered.at(-1);
if (last) { if (last) {
setReplayTime(last.ts); const lastTs = getItemTs(last);
if (replayEnd !== null && last.ts >= replayEnd) { setReplayTime(lastTs);
cursorRef.current = { ts: last.ts, seq: last.seq }; if (replayEnd !== null && lastTs >= replayEnd) {
cursorRef.current = { ts: lastTs, seq: last.seq };
replayCompleteRef.current = true; replayCompleteRef.current = true;
setReplayComplete(true); setReplayComplete(true);
setStatus("disconnected"); setStatus("disconnected");
@ -781,7 +862,7 @@ const useTape = <T extends { ts: number; seq: number }>(
window.clearInterval(interval); window.clearInterval(interval);
cancelFlush(); cancelFlush();
}; };
}, [mode, replayPath, batchSize, pollMs, scheduleFlush, cancelFlush]); }, [mode, replayPath, batchSize, pollMs, scheduleFlush, cancelFlush, getItemTs, getReplayKey]);
return { return {
status, status,
@ -1179,6 +1260,10 @@ type EvidenceItem =
| { kind: "print"; id: string; print: OptionPrint } | { kind: "print"; id: string; print: OptionPrint }
| { kind: "unknown"; id: string }; | { kind: "unknown"; id: string };
type DarkEvidenceItem =
| { kind: "join"; id: string; join: EquityPrintJoin }
| { kind: "unknown"; id: string };
type AlertDrawerProps = { type AlertDrawerProps = {
alert: AlertEvent; alert: AlertEvent;
flowPacket: FlowPacket | null; flowPacket: FlowPacket | null;
@ -1293,6 +1378,118 @@ const AlertDrawer = ({ alert, flowPacket, evidence, onClose }: AlertDrawerProps)
); );
}; };
type DarkDrawerProps = {
event: InferredDarkEvent;
evidence: DarkEvidenceItem[];
underlying: string | null;
onClose: () => void;
};
const DarkDrawer = ({ event, evidence, underlying, onClose }: DarkDrawerProps) => {
const joinEvidence = evidence.filter(
(item): item is { kind: "join"; id: string; join: EquityPrintJoin } => item.kind === "join"
);
const unknownCount = evidence.filter((item) => item.kind === "unknown").length;
const traceRefs = event.evidence_refs.slice(0, 6);
const extraRefs = Math.max(0, event.evidence_refs.length - traceRefs.length);
return (
<aside className="drawer">
<div className="drawer-header">
<div>
<p className="drawer-eyebrow">Inferred dark</p>
<h3>{humanizeClassifierId(event.type)}</h3>
<p className="drawer-subtitle">{formatDateTime(event.source_ts)}</p>
</div>
<button className="drawer-close" type="button" onClick={onClose}>
Close
</button>
</div>
<div className="drawer-meta">
<span className="drawer-chip">Confidence {formatConfidence(event.confidence)}</span>
{underlying ? <span className="drawer-chip">{underlying}</span> : null}
<span className="drawer-chip">Evidence {event.evidence_refs.length}</span>
</div>
<div className="drawer-section">
<h4>Trace path</h4>
<div className="drawer-row">
<div className="drawer-row-title">Event trace</div>
<p className="drawer-note">{event.trace_id}</p>
</div>
{traceRefs.length === 0 ? (
<p className="drawer-empty">No evidence references attached.</p>
) : (
<div className="drawer-list">
{traceRefs.map((ref) => (
<div className="drawer-row" key={ref}>
<div className="drawer-row-title">Evidence ref</div>
<p className="drawer-note">{ref}</p>
</div>
))}
</div>
)}
{extraRefs > 0 ? <p className="drawer-empty">+{extraRefs} more evidence refs.</p> : null}
</div>
<div className="drawer-section">
<h4>Evidence joins</h4>
{joinEvidence.length === 0 ? (
<p className="drawer-empty">No evidence joins in the current cache.</p>
) : (
<div className="drawer-list">
{joinEvidence.slice(0, 6).map((item) => {
const joinUnderlying = getJoinString(item.join, "underlying_id") ?? "Unknown";
const price = getJoinNumber(item.join, "price");
const size = getJoinNumber(item.join, "size");
const placement = getJoinString(item.join, "quote_placement") ?? "MISSING";
const offExchange = getJoinBoolean(item.join, "off_exchange_flag");
const bid = getJoinNumber(item.join, "quote_bid");
const ask = getJoinNumber(item.join, "quote_ask");
const mid = getJoinNumber(item.join, "quote_mid");
const spread = getJoinNumber(item.join, "quote_spread");
const quoteAge = parseNumber(item.join.join_quality.quote_age_ms, Number.NaN);
const quoteStale = parseNumber(item.join.join_quality.quote_stale, 0) > 0;
const quoteMissing = parseNumber(item.join.join_quality.quote_missing, 0) > 0;
return (
<div className="drawer-row" key={item.id}>
<div className="drawer-row-title">{joinUnderlying}</div>
<div className="drawer-row-meta">
{Number.isFinite(price) ? <span>${formatPrice(price)}</span> : null}
{Number.isFinite(size) ? <span>{formatSize(size)}x</span> : null}
<span className="pill">{placement}</span>
{offExchange ? (
<span className="flag">Off-Ex</span>
) : (
<span className="flag flag-muted">Lit</span>
)}
{Number.isFinite(quoteAge) ? <span>{Math.round(quoteAge)}ms</span> : null}
{quoteStale ? <span className="pill nbbo-stale">Quote stale</span> : null}
{quoteMissing ? <span className="pill nbbo-missing">Quote missing</span> : null}
</div>
<p className="drawer-note">{item.join.trace_id}</p>
{Number.isFinite(bid) && Number.isFinite(ask) ? (
<p className="drawer-note">
Quote ${formatPrice(bid)} x ${formatPrice(ask)}
{Number.isFinite(mid) ? ` · Mid ${formatPrice(mid)}` : ""}
{Number.isFinite(spread) ? ` · Spr ${formatPrice(spread)}` : ""}
</p>
) : null}
</div>
);
})}
</div>
)}
{unknownCount > 0 ? (
<p className="drawer-empty">+{unknownCount} evidence refs not in cache.</p>
) : null}
</div>
</aside>
);
};
const formatFlowMetric = (value: number, suffix?: string): string => { const formatFlowMetric = (value: number, suffix?: string): string => {
if (suffix) { if (suffix) {
return `${value}${suffix}`; return `${value}${suffix}`;
@ -1304,21 +1501,25 @@ const formatFlowMetric = (value: number, suffix?: string): string => {
export default function HomePage() { export default function HomePage() {
const [mode, setMode] = useState<TapeMode>("live"); const [mode, setMode] = useState<TapeMode>("live");
const [selectedAlert, setSelectedAlert] = useState<AlertEvent | null>(null); const [selectedAlert, setSelectedAlert] = useState<AlertEvent | null>(null);
const [selectedDarkEvent, setSelectedDarkEvent] = useState<InferredDarkEvent | null>(null);
const [filterInput, setFilterInput] = useState<string>(""); const [filterInput, setFilterInput] = useState<string>("");
const optionsScroll = useListScroll(); const optionsScroll = useListScroll();
const equitiesScroll = useListScroll(); const equitiesScroll = useListScroll();
const flowScroll = useListScroll(); const flowScroll = useListScroll();
const darkScroll = useListScroll();
const alertsScroll = useListScroll(); const alertsScroll = useListScroll();
const classifierScroll = useListScroll(); const classifierScroll = useListScroll();
const optionsAnchor = useScrollAnchor(optionsScroll.listRef, optionsScroll.isAtTopRef); const optionsAnchor = useScrollAnchor(optionsScroll.listRef, optionsScroll.isAtTopRef);
const equitiesAnchor = useScrollAnchor(equitiesScroll.listRef, equitiesScroll.isAtTopRef); const equitiesAnchor = useScrollAnchor(equitiesScroll.listRef, equitiesScroll.isAtTopRef);
const flowAnchor = useScrollAnchor(flowScroll.listRef, flowScroll.isAtTopRef); const flowAnchor = useScrollAnchor(flowScroll.listRef, flowScroll.isAtTopRef);
const darkAnchor = useScrollAnchor(darkScroll.listRef, darkScroll.isAtTopRef);
const alertsAnchor = useScrollAnchor(alertsScroll.listRef, alertsScroll.isAtTopRef); const alertsAnchor = useScrollAnchor(alertsScroll.listRef, alertsScroll.isAtTopRef);
const classifierAnchor = useScrollAnchor( const classifierAnchor = useScrollAnchor(
classifierScroll.listRef, classifierScroll.listRef,
classifierScroll.isAtTopRef classifierScroll.isAtTopRef
); );
const disableReplayGrouping = useCallback(() => null, []);
const options = useTape<OptionPrint>({ const options = useTape<OptionPrint>({
mode, mode,
@ -1344,6 +1545,17 @@ export default function HomePage() {
onNewItems: equitiesScroll.onNewItems onNewItems: equitiesScroll.onNewItems
}); });
const equityJoins = useTape<EquityPrintJoin>({
mode,
wsPath: "/ws/equity-joins",
replayPath: "/replay/equity-joins",
latestPath: "/joins/equities",
expectedType: "equity-join",
batchSize: mode === "replay" ? 120 : undefined,
pollMs: mode === "replay" ? 200 : undefined,
getReplayKey: disableReplayGrouping
});
const nbbo = useTape<OptionNBBO>({ const nbbo = useTape<OptionNBBO>({
mode, mode,
wsPath: "/ws/options-nbbo", wsPath: "/ws/options-nbbo",
@ -1354,6 +1566,19 @@ export default function HomePage() {
pollMs: mode === "replay" ? 200 : undefined pollMs: mode === "replay" ? 200 : undefined
}); });
const inferredDark = useTape<InferredDarkEvent>({
mode,
wsPath: "/ws/inferred-dark",
replayPath: "/replay/inferred-dark",
latestPath: "/dark/inferred",
expectedType: "inferred-dark",
batchSize: mode === "replay" ? 120 : undefined,
pollMs: mode === "replay" ? 200 : undefined,
captureScroll: darkAnchor.capture,
onNewItems: darkScroll.onNewItems,
getReplayKey: disableReplayGrouping
});
const flowHold = useCallback(() => !flowScroll.isAtTopRef.current, [flowScroll.isAtTopRef]); const flowHold = useCallback(() => !flowScroll.isAtTopRef.current, [flowScroll.isAtTopRef]);
const flow = useFlowStream( const flow = useFlowStream(
mode === "live", mode === "live",
@ -1389,6 +1614,10 @@ export default function HomePage() {
flowAnchor.apply(); flowAnchor.apply();
}, [flow.items, flowAnchor.apply]); }, [flow.items, flowAnchor.apply]);
useLayoutEffect(() => {
darkAnchor.apply();
}, [inferredDark.items, darkAnchor.apply]);
useLayoutEffect(() => { useLayoutEffect(() => {
alertsAnchor.apply(); alertsAnchor.apply();
}, [alerts.items, alertsAnchor.apply]); }, [alerts.items, alertsAnchor.apply]);
@ -1432,6 +1661,24 @@ export default function HomePage() {
return map; return map;
}, [options.items]); }, [options.items]);
const equityPrintMap = useMemo(() => {
const map = new Map<string, EquityPrint>();
for (const print of equities.items) {
if (print.trace_id) {
map.set(print.trace_id, print);
}
}
return map;
}, [equities.items]);
const equityJoinMap = useMemo(() => {
const map = new Map<string, EquityPrintJoin>();
for (const join of equityJoins.items) {
map.set(join.id, join);
}
return map;
}, [equityJoins.items]);
const flowPacketMap = useMemo(() => { const flowPacketMap = useMemo(() => {
const map = new Map<string, FlowPacket>(); const map = new Map<string, FlowPacket>();
for (const packet of flow.items) { for (const packet of flow.items) {
@ -1466,10 +1713,32 @@ export default function HomePage() {
return packetId ? flowPacketMap.get(packetId) ?? null : null; return packetId ? flowPacketMap.get(packetId) ?? null : null;
}, [selectedAlert, flowPacketMap]); }, [selectedAlert, flowPacketMap]);
const selectedDarkEvidence = useMemo((): DarkEvidenceItem[] => {
if (!selectedDarkEvent) {
return [];
}
return selectedDarkEvent.evidence_refs.map((id) => {
const join = equityJoinMap.get(id);
if (join) {
return { kind: "join", id, join };
}
return { kind: "unknown", id };
});
}, [selectedDarkEvent, equityJoinMap]);
const selectedDarkUnderlying = useMemo(() => {
if (!selectedDarkEvent) {
return null;
}
return inferDarkUnderlying(selectedDarkEvent, equityPrintMap, equityJoinMap);
}, [selectedDarkEvent, equityJoinMap, equityPrintMap]);
useEffect(() => { useEffect(() => {
if (mode !== "live") { if (mode !== "live") {
setSelectedAlert(null); setSelectedAlert(null);
} }
setSelectedDarkEvent(null);
}, [mode]); }, [mode]);
const extractPacketContract = useCallback((packet: FlowPacket): string => { const extractPacketContract = useCallback((packet: FlowPacket): string => {
@ -1545,6 +1814,16 @@ export default function HomePage() {
return equities.items.filter((print) => matchesTicker(print.underlying_id)); return equities.items.filter((print) => matchesTicker(print.underlying_id));
}, [equities.items, matchesTicker, tickerSet]); }, [equities.items, matchesTicker, tickerSet]);
const filteredInferredDark = useMemo(() => {
if (tickerSet.size === 0) {
return inferredDark.items;
}
return inferredDark.items.filter((event) => {
const underlying = inferDarkUnderlying(event, equityPrintMap, equityJoinMap);
return matchesTicker(underlying);
});
}, [equityJoinMap, equityPrintMap, inferredDark.items, matchesTicker, tickerSet]);
const filteredFlow = useMemo(() => { const filteredFlow = useMemo(() => {
if (tickerSet.size === 0) { if (tickerSet.size === 0) {
return flow.items; return flow.items;
@ -1575,6 +1854,7 @@ export default function HomePage() {
return [ return [
options.lastUpdate, options.lastUpdate,
equities.lastUpdate, equities.lastUpdate,
inferredDark.lastUpdate,
flow.lastUpdate, flow.lastUpdate,
alerts.lastUpdate, alerts.lastUpdate,
classifierHits.lastUpdate classifierHits.lastUpdate
@ -1584,6 +1864,7 @@ export default function HomePage() {
}, [ }, [
options.lastUpdate, options.lastUpdate,
equities.lastUpdate, equities.lastUpdate,
inferredDark.lastUpdate,
flow.lastUpdate, flow.lastUpdate,
alerts.lastUpdate, alerts.lastUpdate,
classifierHits.lastUpdate classifierHits.lastUpdate
@ -1975,7 +2256,10 @@ export default function HomePage() {
className="row row-button" className="row row-button"
key={`${alert.trace_id}-${alert.seq}`} key={`${alert.trace_id}-${alert.seq}`}
type="button" type="button"
onClick={() => setSelectedAlert(alert)} onClick={() => {
setSelectedDarkEvent(null);
setSelectedAlert(alert);
}}
> >
<div> <div>
<div className="contract"> <div className="contract">
@ -2060,6 +2344,75 @@ export default function HomePage() {
</div> </div>
</div> </div>
</section> </section>
<section className="card card-dark">
<div className="card-header">
<div>
<h2>Inferred Dark</h2>
<p className="card-subtitle">Off-exchange patterns inferred from equity joins.</p>
</div>
</div>
<div className="card-controls">
<TapeStatus
status={inferredDark.status}
lastUpdate={inferredDark.lastUpdate}
replayTime={inferredDark.replayTime}
replayComplete={inferredDark.replayComplete}
paused={inferredDark.paused}
dropped={inferredDark.dropped}
mode={mode}
onTogglePause={inferredDark.togglePause}
/>
<TapeControls
isAtTop={darkScroll.isAtTop}
missed={darkScroll.missed}
onJump={darkScroll.jumpToTop}
/>
</div>
<div className="card-body">
<div className="list" ref={darkScroll.listRef}>
{filteredInferredDark.length === 0 ? (
<div className="empty">
{tickerSet.size > 0
? "No inferred dark events match the current filter."
: mode === "live"
? "No inferred dark events yet. Start compute."
: "Replay queue empty. Ensure ClickHouse has data."}
</div>
) : (
filteredInferredDark.map((event) => {
const underlying = inferDarkUnderlying(event, equityPrintMap, equityJoinMap);
const evidenceCount = event.evidence_refs.length;
return (
<button
className="row row-button"
key={`${event.trace_id}-${event.seq}`}
type="button"
onClick={() => {
setSelectedAlert(null);
setSelectedDarkEvent(event);
}}
>
<div>
<div className="contract">{humanizeClassifierId(event.type)}</div>
<div className="meta">
{underlying ? <span>{underlying}</span> : <span className="pill">Unknown</span>}
<span>Confidence {formatConfidence(event.confidence)}</span>
<span>Evidence {evidenceCount}</span>
</div>
{underlying ? null : (
<div className="note">Underlying not in current equity cache.</div>
)}
</div>
<div className="time">{formatTime(event.source_ts)}</div>
</button>
);
})
)}
</div>
</div>
</section>
</div> </div>
{selectedAlert ? ( {selectedAlert ? (
@ -2070,6 +2423,15 @@ export default function HomePage() {
onClose={() => setSelectedAlert(null)} onClose={() => setSelectedAlert(null)}
/> />
) : null} ) : null}
{selectedDarkEvent ? (
<DarkDrawer
event={selectedDarkEvent}
evidence={selectedDarkEvidence}
underlying={selectedDarkUnderlying}
onClose={() => setSelectedDarkEvent(null)}
/>
) : null}
</main> </main>
); );
} }

View file

@ -30,6 +30,11 @@ const spawnChild = ({ name, cmd, cwd }: ChildSpec): void => {
const exitCode = code ?? 0; const exitCode = code ?? 0;
const statusLabel = exitCode === 0 ? "exited" : "failed"; const statusLabel = exitCode === 0 ? "exited" : "failed";
console.error(`[dev] ${name} ${statusLabel} (${exitCode})`); console.error(`[dev] ${name} ${statusLabel} (${exitCode})`);
if (name === "infra" && exitCode !== 0) {
console.error(
"[dev] Infra failed. Ensure Docker is installed and the daemon is running (OrbStack or Docker Desktop), then retry."
);
}
shutdown(exitCode); shutdown(exitCode);
}); });
}; };

View file

@ -6,6 +6,22 @@ type SyntheticEquitiesAdapterConfig = {
}; };
const EXCHANGES = ["NYSE", "NASDAQ", "ARCA", "BATS", "IEX", "TEST"]; const EXCHANGES = ["NYSE", "NASDAQ", "ARCA", "BATS", "IEX", "TEST"];
const DARK_EXCHANGE = "OTC";
type PricePlacement = "MID" | "A" | "AA" | "B" | "BB";
type DarkScenario = "block" | "buy" | "sell";
const DARK_SEQUENCE: DarkScenario[] = [
"block",
"buy",
"buy",
"buy",
"buy",
"sell",
"sell",
"sell",
"sell"
];
const hashSymbol = (value: string): number => { const hashSymbol = (value: string): number => {
let hash = 0; let hash = 0;
@ -57,6 +73,50 @@ const buildSyntheticQuote = (
}; };
}; };
const formatPrice = (value: number): number => {
return Number(value.toFixed(2));
};
const buildQuoteFromMid = (mid: number) => {
const spread = Math.max(0.05, Number((mid * 0.002).toFixed(2)));
const half = spread / 2;
const bid = formatPrice(Math.max(0.01, mid - half));
const ask = formatPrice(Math.max(bid + 0.01, mid + half));
const epsilon = Math.max(0.01, spread * 0.05);
return { bid, ask, spread, epsilon };
};
const priceForPlacement = (
mid: number,
quote: { bid: number; ask: number; epsilon: number },
placement: PricePlacement
): number => {
const { bid, ask, epsilon } = quote;
let price = mid;
switch (placement) {
case "AA":
price = ask + epsilon * 1.5;
break;
case "A":
price = ask;
break;
case "BB":
price = bid - epsilon * 1.5;
break;
case "B":
price = bid;
break;
case "MID":
default:
price = mid;
break;
}
return formatPrice(Math.max(0.01, price));
};
export const createSyntheticEquitiesAdapter = ( export const createSyntheticEquitiesAdapter = (
config: SyntheticEquitiesAdapterConfig config: SyntheticEquitiesAdapterConfig
): EquityIngestAdapter => { ): EquityIngestAdapter => {
@ -65,6 +125,8 @@ export const createSyntheticEquitiesAdapter = (
start: (handlers: EquityIngestHandlers) => { start: (handlers: EquityIngestHandlers) => {
let seq = 0; let seq = 0;
let quoteSeq = 0; let quoteSeq = 0;
let darkStep = 0;
let darkSymbolIndex = 0;
let timer: ReturnType<typeof setInterval> | null = null; let timer: ReturnType<typeof setInterval> | null = null;
let stopped = false; let stopped = false;
@ -76,27 +138,79 @@ export const createSyntheticEquitiesAdapter = (
const now = Date.now(); const now = Date.now();
const batchSize = 3; const batchSize = 3;
const darkSymbol = SP500_SYMBOLS[darkSymbolIndex % SP500_SYMBOLS.length];
const darkHash = hashSymbol(darkSymbol);
const darkBase = 25 + (darkHash % 475);
const darkDrift = ((darkStep % 24) - 12) * 0.08;
const darkMid = formatPrice(darkBase + darkDrift);
const darkQuote = buildQuoteFromMid(darkMid);
const scenario = DARK_SEQUENCE[darkStep % DARK_SEQUENCE.length];
const darkTs = now;
if (handlers.onQuote) {
quoteSeq += 1;
const quoteEvent = buildSyntheticQuote(
quoteSeq,
darkTs - 2,
darkSymbol,
darkQuote.bid,
darkQuote.ask
);
void handlers.onQuote(quoteEvent);
}
seq += 1;
let darkPlacement: PricePlacement = "MID";
let darkSize = 2600;
if (scenario === "buy") {
darkPlacement = darkStep % 2 === 0 ? "A" : "AA";
darkSize = 800;
} else if (scenario === "sell") {
darkPlacement = darkStep % 2 === 0 ? "B" : "BB";
darkSize = 800;
}
const darkPrice = priceForPlacement(darkMid, darkQuote, darkPlacement);
const darkPrint = buildSyntheticPrint(
seq,
darkTs,
darkSymbol,
darkPrice,
darkSize,
DARK_EXCHANGE,
true
);
void handlers.onTrade(darkPrint);
darkStep += 1;
if (darkStep >= DARK_SEQUENCE.length) {
darkStep = 0;
darkSymbolIndex += 1;
}
for (let i = 0; i < batchSize; i += 1) { for (let i = 0; i < batchSize; i += 1) {
seq += 1; seq += 1;
const symbol = SP500_SYMBOLS[(seq + i) % SP500_SYMBOLS.length]; const symbol = SP500_SYMBOLS[(seq + i) % SP500_SYMBOLS.length];
const symbolHash = hashSymbol(symbol); const symbolHash = hashSymbol(symbol);
const basePrice = 25 + (symbolHash % 475); const basePrice = 25 + (symbolHash % 475);
const price = Number((basePrice + ((seq % 40) - 20) * 0.05).toFixed(2)); const mid = formatPrice(basePrice + ((seq % 40) - 20) * 0.05);
const quote = buildQuoteFromMid(mid);
const placement: PricePlacement =
seq % 11 === 0 ? "A" : seq % 13 === 0 ? "B" : "MID";
const price = priceForPlacement(mid, quote, placement);
const size = 10 + (seq % 600); const size = 10 + (seq % 600);
const exchange = EXCHANGES[(seq + symbolHash) % EXCHANGES.length]; const exchange = EXCHANGES[(seq + symbolHash) % EXCHANGES.length];
const offExchangeFlag = (seq + i) % 6 === 0; const offExchangeFlag = (seq + i) % 6 === 0;
const eventTs = now + i * 4; const eventTs = now + i * 4;
const print = buildSyntheticPrint(seq, eventTs, symbol, price, size, exchange, offExchangeFlag);
void handlers.onTrade(print);
if (handlers.onQuote) { if (handlers.onQuote) {
quoteSeq += 1; quoteSeq += 1;
const spread = Math.max(0.02, Number((price * 0.002).toFixed(2))); const quoteEventTs = eventTs - 2;
const bid = Math.max(0.01, Number((price - spread / 2).toFixed(2))); const quoteEvent = buildSyntheticQuote(quoteSeq, quoteEventTs, symbol, quote.bid, quote.ask);
const ask = Math.max(bid + 0.01, Number((price + spread / 2).toFixed(2))); void handlers.onQuote(quoteEvent);
const quote = buildSyntheticQuote(quoteSeq, eventTs, symbol, bid, ask);
void handlers.onQuote(quote);
} }
const print = buildSyntheticPrint(seq, eventTs, symbol, price, size, exchange, offExchangeFlag);
void handlers.onTrade(print);
} }
}; };