Implement options snapshot tape table

This commit is contained in:
dirtydishes 2026-05-04 01:14:52 -04:00
parent 6abfff30d3
commit e78387130a
15 changed files with 904 additions and 128 deletions

View file

@ -873,6 +873,89 @@ h3 {
background: linear-gradient(180deg, rgba(245, 166, 35, 0.07), rgba(255, 255, 255, 0.018));
}
.options-table-wrap {
min-height: 0;
overflow: auto;
}
.options-table {
min-width: 1040px;
}
.options-table-head,
.options-table-row {
display: grid;
grid-template-columns: 88px 72px 76px 72px 44px 76px 130px 70px 82px 64px 56px minmax(150px, 1fr);
align-items: center;
column-gap: 10px;
}
.options-table-head {
position: sticky;
top: 0;
z-index: 2;
height: 30px;
padding: 0 10px;
border-bottom: 1px solid var(--border);
background: rgba(8, 11, 16, 0.98);
color: var(--muted);
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.08em;
}
.options-table-row {
width: 100%;
min-height: 34px;
padding: 0 10px;
border: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.055);
background:
linear-gradient(90deg, rgba(var(--classifier-rgb, 192, 200, 210), calc(0.02 + var(--classifier-intensity, 0) * 0.12)), transparent 62%),
rgba(255, 255, 255, 0.012);
color: inherit;
font: inherit;
text-align: left;
}
.options-table-row:hover,
.options-table-row:focus-visible {
outline: none;
background:
linear-gradient(90deg, rgba(var(--classifier-rgb, 192, 200, 210), calc(0.04 + var(--classifier-intensity, 0) * 0.18)), transparent 68%),
rgba(255, 255, 255, 0.03);
}
.options-table-row.is-classified {
cursor: pointer;
border-left: 3px solid rgba(var(--classifier-rgb), calc(0.35 + var(--classifier-intensity) * 0.45));
padding-left: 7px;
}
.options-table-row > span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.72rem;
}
.mono {
font-variant-numeric: tabular-nums;
}
.classifier-green { --classifier-rgb: 37, 193, 122; }
.classifier-red { --classifier-rgb: 255, 107, 95; }
.classifier-amber { --classifier-rgb: 245, 166, 35; }
.classifier-copper { --classifier-rgb: 198, 122, 75; }
.classifier-blue { --classifier-rgb: 77, 163, 255; }
.classifier-teal { --classifier-rgb: 64, 210, 190; }
.classifier-yellowgreen { --classifier-rgb: 174, 210, 78; }
.classifier-violet { --classifier-rgb: 170, 130, 255; }
.classifier-cyan { --classifier-rgb: 94, 214, 255; }
.classifier-magenta { --classifier-rgb: 255, 92, 205; }
.classifier-neutral { --classifier-rgb: 192, 200, 210; }
.contract,
.drawer-row-title {
margin-bottom: 6px;

View file

@ -1,12 +1,14 @@
import { describe, expect, it } from "bun:test";
import {
buildDefaultFlowFilters,
classifierToneForFamily,
deriveAlertDirection,
countActiveFlowFilterGroups,
formatCompactUsd,
formatOptionContractLabel,
flushPausableTapeData,
getAlertWindowAnchorTs,
getOptionTableSnapshot,
getLiveFeedStatus,
normalizeAlertSeverity,
nextFlowFilterPopoverState,
@ -14,6 +16,7 @@ import {
reducePausableTapeData,
shouldRetainLiveSnapshotHistory,
shouldShowEquitiesSilentFeedWarning,
selectPrimaryClassifierHit,
statusLabel,
toggleFilterValue
} from "./terminal";
@ -171,6 +174,54 @@ describe("options display formatters", () => {
expect(formatCompactUsd(1_250_000)).toBe("1.3M");
expect(formatCompactUsd(Number.NaN)).toBe("0.00");
});
it("renders options table snapshot values from preserved spot and IV", () => {
expect(
getOptionTableSnapshot({
price: 1.25,
size: 10,
notional: 12_500,
execution_nbbo_side: "A",
execution_underlying_spot: 450.05,
execution_iv: 0.42
})
).toEqual({
spot: "450.05",
iv: "42%",
side: "A",
details: "10@1.25_A",
value: "12.5K"
});
});
it("renders legacy options table snapshot spot and IV as dashes", () => {
const snapshot = getOptionTableSnapshot({
price: 1,
size: 2
});
expect(snapshot.spot).toBe("--");
expect(snapshot.iv).toBe("--");
});
});
describe("classifier row decoration helpers", () => {
it("maps classifier families to row tones", () => {
expect(classifierToneForFamily("large_bullish_call_sweep")).toBe("green");
expect(classifierToneForFamily("large_bearish_put_sweep")).toBe("red");
expect(classifierToneForFamily("straddle")).toBe("blue");
expect(classifierToneForFamily("unknown_family")).toBe("neutral");
});
it("selects primary hits by confidence, source timestamp, then seq", () => {
const hit = selectPrimaryClassifierHit([
{ ...makeAlert({ classifier_id: "old", confidence: 0.9, source_ts: 1_000, seq: 1 }), direction: "bullish", explanations: [] },
{ ...makeAlert({ classifier_id: "new", confidence: 0.9, source_ts: 2_000, seq: 1 }), direction: "bullish", explanations: [] },
{ ...makeAlert({ classifier_id: "low", confidence: 0.5, source_ts: 3_000, seq: 9 }), direction: "bullish", explanations: [] }
]);
expect(hit?.classifier_id).toBe("new");
});
});
describe("flow filter popup helpers", () => {

View file

@ -11,6 +11,7 @@ import {
useMemo,
useRef,
useState,
type CSSProperties,
type Dispatch,
type ReactNode,
type SetStateAction
@ -982,7 +983,8 @@ const LIVE_SNAPSHOT_HISTORY_CHANNELS = new Set<LiveSubscription["channel"]>([
"options",
"nbbo",
"equities",
"flow"
"flow",
"classifier-hits"
]);
export const shouldRetainLiveSnapshotHistory = (
@ -1027,6 +1029,80 @@ const classifyNbboSide = (price: number, quote: OptionNBBO | null | undefined):
return price >= mid ? "A" : "B";
};
type ClassifierDecor = {
hit: ClassifierHitEvent;
family: string;
tone: string;
intensity: number;
};
const CLASSIFIER_FAMILY_TONES: Record<string, string> = {
large_bullish_call_sweep: "green",
large_bearish_put_sweep: "red",
unusual_contract_spike: "amber",
large_call_sell_overwrite: "copper",
large_put_sell_write: "copper",
straddle: "blue",
strangle: "blue",
vertical_spread: "teal",
ladder_accumulation: "yellowgreen",
roll_up_down_out: "violet",
far_dated_conviction: "cyan",
zero_dte_gamma_punch: "magenta"
};
export const selectPrimaryClassifierHit = (
hits: readonly ClassifierHitEvent[]
): ClassifierHitEvent | null => {
if (hits.length === 0) {
return null;
}
return [...hits].sort((a, b) => {
const confidenceDelta = b.confidence - a.confidence;
if (confidenceDelta !== 0) {
return confidenceDelta;
}
const tsDelta = b.source_ts - a.source_ts;
if (tsDelta !== 0) {
return tsDelta;
}
return b.seq - a.seq;
})[0];
};
export const classifierToneForFamily = (classifierId: string): string =>
CLASSIFIER_FAMILY_TONES[classifierId] ?? "neutral";
const buildClassifierDecor = (hit: ClassifierHitEvent): ClassifierDecor => ({
hit,
family: hit.classifier_id,
tone: classifierToneForFamily(hit.classifier_id),
intensity: clamp(hit.confidence, 0.25, 1)
});
export const getOptionTableSnapshot = (
print: Pick<
OptionPrint,
| "price"
| "size"
| "notional"
| "nbbo_side"
| "execution_nbbo_side"
| "execution_underlying_spot"
| "execution_iv"
>,
fallbackSide: OptionNbboSide | null = null
): { spot: string; iv: string; side: string; details: string; value: string } => {
const side = print.execution_nbbo_side ?? print.nbbo_side ?? fallbackSide ?? "--";
return {
spot: typeof print.execution_underlying_spot === "number" ? formatPrice(print.execution_underlying_spot) : "--",
iv: typeof print.execution_iv === "number" ? formatPct(print.execution_iv) : "--",
side,
details: `${formatSize(print.size)}@${formatPrice(print.price)}_${side}`,
value: formatCompactUsd(print.notional ?? print.price * print.size * 100)
};
};
type ListScrollState = {
listRef: React.RefObject<HTMLDivElement>;
isAtTop: boolean;
@ -2125,7 +2201,8 @@ const getLiveManifest = (
{ channel: "options", filters: flowFilters },
{ channel: "nbbo" },
{ channel: "equities" },
{ channel: "flow", filters: flowFilters }
{ channel: "flow", filters: flowFilters },
{ channel: "classifier-hits" }
];
}
@ -4157,6 +4234,39 @@ const useTerminalState = () => {
return traceId.slice(idx);
}, []);
const classifierHitsByPacketId = useMemo(() => {
const map = new Map<string, ClassifierHitEvent[]>();
for (const hit of classifierHitsFeed.items) {
const packetId = extractPacketIdFromClassifierHitTrace(hit.trace_id);
if (!packetId) {
continue;
}
map.set(packetId, [...(map.get(packetId) ?? []), hit]);
}
return map;
}, [classifierHitsFeed.items, extractPacketIdFromClassifierHitTrace]);
const packetIdByOptionTraceId = useMemo(() => {
const map = new Map<string, string>();
for (const packet of flowFeed.items) {
for (const member of packet.members) {
map.set(member, packet.id);
}
}
return map;
}, [flowFeed.items]);
const classifierDecorByOptionTraceId = useMemo(() => {
const map = new Map<string, ClassifierDecor>();
for (const [traceId, packetId] of packetIdByOptionTraceId) {
const primary = selectPrimaryClassifierHit(classifierHitsByPacketId.get(packetId) ?? []);
if (primary) {
map.set(traceId, buildClassifierDecor(primary));
}
}
return map;
}, [classifierHitsByPacketId, packetIdByOptionTraceId]);
const selectedClassifierPacketId = useMemo(() => {
if (!selectedClassifierHit) {
return null;
@ -4632,6 +4742,9 @@ const useTerminalState = () => {
equityPrintMap,
equityJoinMap: resolvedEquityJoinMap,
flowPacketMap: resolvedFlowPacketMap,
classifierHitsByPacketId,
packetIdByOptionTraceId,
classifierDecorByOptionTraceId,
selectedEvidence,
selectedFlowPacket,
selectedDarkEvidence,
@ -5002,7 +5115,7 @@ type OptionsPaneProps = {
const OptionsPane = ({ limit }: OptionsPaneProps) => {
const state = useTerminal();
const items = limit ? state.filteredOptions.slice(0, limit) : state.filteredOptions;
const virtual = useVirtualList(items, state.optionsScroll.listRef, !limit, 96);
const virtual = useVirtualList(items, state.optionsScroll.listRef, !limit, 34);
return (
<Pane
@ -5028,7 +5141,7 @@ const OptionsPane = ({ limit }: OptionsPaneProps) => {
/>
}
>
<div className="list terminal-list" ref={state.optionsScroll.listRef}>
<div className="options-table-wrap" ref={state.optionsScroll.listRef}>
{items.length === 0 ? (
<div className="empty">
{state.tickerSet.size > 0
@ -5040,103 +5153,92 @@ const OptionsPane = ({ limit }: OptionsPaneProps) => {
: "Replay queue empty. Ensure ClickHouse has data."}
</div>
) : (
<>
<div className="options-table" role="table" aria-label="Options tape">
<div className="options-table-head" role="row">
<span>TIME</span>
<span>SYM</span>
<span>EXP</span>
<span>STRIKE</span>
<span>C/P</span>
<span>SPOT</span>
<span>DETAILS</span>
<span>TYPE</span>
<span>VALUE</span>
<span>SIDE</span>
<span>IV</span>
<span>CLASSIFIER</span>
</div>
{virtual.topSpacerHeight > 0 ? (
<div style={{ height: `${virtual.topSpacerHeight}px` }} aria-hidden />
) : null}
{virtual.visibleItems.map((print) => {
const contractId = normalizeContractId(print.option_contract_id);
const contractDisplay = formatOptionContractLabel(contractId);
const quote = state.nbboMap.get(contractId);
const nbboAge = quote ? Math.abs(print.ts - quote.ts) : null;
const nbboStale = nbboAge !== null && nbboAge > NBBO_MAX_AGE_MS_SAFE;
const nbboMid = quote ? (quote.bid + quote.ask) / 2 : null;
const nbboSide = print.nbbo_side ?? classifyNbboSide(print.price, quote);
const notional = print.notional ?? print.price * print.size * 100;
return (
<div className="row" key={`${print.trace_id}-${print.seq}`}>
<div>
<div className={`contract${contractDisplay ? " option-contract" : ""}`}>
{contractDisplay ? (
<>
<span>{contractDisplay.ticker}</span>
<span>{contractDisplay.strike}</span>
<span>{contractDisplay.expiration}</span>
</>
const contractId = normalizeContractId(print.option_contract_id);
const parsed = parseOptionContractId(contractId);
const contractDisplay = formatOptionContractLabel(contractId);
const quote = state.nbboMap.get(contractId);
const hasPreservedNbbo = typeof print.execution_nbbo_side === "string";
const nbboSide =
print.execution_nbbo_side ??
print.nbbo_side ??
(!hasPreservedNbbo ? classifyNbboSide(print.price, quote) : null);
const notional = print.notional ?? print.price * print.size * 100;
const spot = print.execution_underlying_spot;
const iv = print.execution_iv;
const decor = state.classifierDecorByOptionTraceId.get(print.trace_id);
const commonProps = {
className: `options-table-row${decor ? ` is-classified classifier-${decor.tone}` : ""}`,
style: decor ? ({ "--classifier-intensity": decor.intensity } as CSSProperties) : undefined
};
const cells = (
<>
<span className="mono">{formatTime(print.ts)}</span>
<span>{contractDisplay?.ticker ?? parsed?.root ?? formatContractLabel(contractId)}</span>
<span>{contractDisplay?.expiration ?? parsed?.expiry ?? "--"}</span>
<span>{contractDisplay?.strike.replace(/[CP]$/, "") ?? "--"}</span>
<span>{parsed?.right ?? contractDisplay?.strike.slice(-1) ?? "--"}</span>
<span>{typeof spot === "number" ? formatPrice(spot) : "--"}</span>
<span className="mono">
{formatSize(print.size)}@{formatPrice(print.price)}_{nbboSide ?? "--"}
</span>
<span>{print.option_type ?? "--"}</span>
<span className="notional-emphasis">${formatCompactUsd(notional)}</span>
<span>
{nbboSide ? (
<span className={`nbbo-tag nbbo-tag-${nbboSide.toLowerCase()}`}>{nbboSide}</span>
) : (
formatContractLabel(contractId)
"--"
)}
</div>
<div className="meta">
<span>${formatPrice(print.price)}</span>
<span>{formatSize(print.size)}x</span>
<span>{print.exchange}</span>
<span className="notional-emphasis">Notional ${formatCompactUsd(notional)}</span>
{print.conditions?.map((condition) => {
const normalized = condition.toUpperCase();
const tone =
normalized === "SWEEP"
? "condition-sweep"
: normalized === "ISO"
? "condition-iso"
: "condition-neutral";
return (
<span className={`condition-chip ${tone}`} key={`${print.trace_id}-${condition}`}>
{normalized}
</span>
);
})}
</div>
{quote ? (
<div className="meta nbbo-meta">
<span>Bid ${formatPrice(quote.bid)}</span>
<span>Ask ${formatPrice(quote.ask)}</span>
<span>Mid ${formatPrice(nbboMid ?? 0)}</span>
<span>{Math.round(nbboAge ?? 0)}ms</span>
{nbboSide ? (
<span className="nbbo-side" tabIndex={0} aria-label="NBBO side legend">
<span className={`nbbo-tag nbbo-tag-${nbboSide.toLowerCase()}`}>
{nbboSide}
</span>
<span className="nbbo-tooltip" role="tooltip">
<span className="nbbo-tooltip-row">
<span className="nbbo-tag nbbo-tag-a">A</span>
<span>Ask</span>
</span>
<span className="nbbo-tooltip-row">
<span className="nbbo-tag nbbo-tag-aa">AA</span>
<span>Above Ask</span>
</span>
<span className="nbbo-tooltip-row">
<span className="nbbo-tag nbbo-tag-b">B</span>
<span>Bid</span>
</span>
<span className="nbbo-tooltip-row">
<span className="nbbo-tag nbbo-tag-bb">BB</span>
<span>Below Bid</span>
</span>
</span>
</span>
) : null}
{print.nbbo_side === "STALE" || nbboStale ? <span className="pill nbbo-stale">Stale</span> : null}
</div>
) : (
<div className="meta nbbo-meta">
<span className="pill nbbo-missing">
{print.nbbo_side === "STALE" ? "NBBO stale" : "NBBO missing"}
</span>
</div>
)}
</span>
<span>{typeof iv === "number" ? formatPct(iv) : "--"}</span>
<span>{decor ? humanizeClassifierId(decor.family) : "--"}</span>
</>
);
return decor ? (
<button
type="button"
{...commonProps}
key={`${print.trace_id}-${print.seq}`}
onClick={() => state.openFromClassifierHit(decor.hit)}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
state.openFromClassifierHit(decor.hit);
}
}}
>
{cells}
</button>
) : (
<div {...commonProps} key={`${print.trace_id}-${print.seq}`}>
{cells}
</div>
<div className="time">{formatTime(print.ts)}</div>
</div>
);
);
})}
{virtual.bottomSpacerHeight > 0 ? (
<div style={{ height: `${virtual.bottomSpacerHeight}px` }} aria-hidden />
) : null}
</>
</div>
)}
</div>
</Pane>