implement durable options tape history

This commit is contained in:
dirtydishes 2026-05-16 17:27:02 -04:00
parent e3940eb0a6
commit bd60d0d5d5
9 changed files with 423 additions and 56 deletions

View file

@ -606,6 +606,13 @@ h3 {
text-transform: uppercase;
}
.flow-filter-section-copy {
margin: -2px 0 0;
color: var(--text-muted);
font-size: 0.78rem;
line-height: 1.35;
}
.flow-filter-checkbox-grid,
.flow-filter-chip-grid {
display: grid;
@ -617,6 +624,10 @@ h3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.flow-filter-chip-grid-two {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.flow-filter-check {
display: inline-flex;
align-items: center;

View file

@ -17,7 +17,6 @@ import {
getEffectiveOptionPrintFilters,
getAlertWindowAnchorTs,
getHotChannelFeedStatus,
getScopedLiveAutoHydrationChannels,
getLiveHistoryRetentionCap,
getOptionTableSnapshot,
getOptionScope,
@ -298,6 +297,24 @@ describe("contract-focused option helpers", () => {
});
});
it("includes the selected options view in tape query params", () => {
expect(
buildOptionTapeQueryParams(
{
...buildDefaultFlowFilters(),
view: "raw",
securityTypes: undefined,
nbboSides: undefined,
optionTypes: undefined
},
{ underlying_ids: ["AAPL"] }
)
).toEqual({
view: "raw",
underlying_ids: "AAPL"
});
});
it("keeps the focus seed until the matching scoped subscription has loaded it", () => {
const seedItem = makeOptionPrint({
trace_id: "focused-seed",
@ -652,32 +669,6 @@ describe("live tape history helpers", () => {
).toBe(0);
});
it("does not auto-hydrate scoped live history before the scroll gate is reached", () => {
const manifest = getLiveManifest(
"/tape",
"AAPL",
60000,
buildDefaultFlowFilters(),
{
underlying_ids: ["AAPL"],
option_contract_id: "AAPL-2025-01-17-200-C"
},
{ underlying_ids: ["AAPL"] }
);
const historyCursors = Object.fromEntries(
manifest.map((subscription) => [getLiveSubscriptionKey(subscription), { ts: 1, seq: 1 }])
);
expect(
getScopedLiveAutoHydrationChannels(true, "/tape", manifest, historyCursors, {})
).toEqual([]);
expect(
getScopedLiveAutoHydrationChannels(true, "/tape", manifest, historyCursors, {
[getLiveSubscriptionKey(manifest.find((subscription) => subscription.channel === "options")!)]: true
})
).toEqual([]);
});
it("restores the same anchor key after live insertions at the top", () => {
const nextKeys = ["new-1", "new-2", "anchor", "after-1", "after-2"];
expect(findAnchorRestoreIndex(nextKeys, "anchor", ["anchor", "after-1", "after-2"])).toBe(2);
@ -806,6 +797,7 @@ describe("flow filter popup helpers", () => {
expect(countActiveFlowFilterGroups(defaults)).toBe(0);
expect(countActiveFlowFilterGroups(next)).toBe(3);
expect(countActiveFlowFilterGroups({ ...defaults, view: "raw" })).toBe(1);
expect(buildDefaultFlowFilters()).toEqual(defaults);
});
});

View file

@ -34,6 +34,7 @@ import type {
LiveHotChannelHealthMap,
LiveSubscription,
OptionFlowFilters,
OptionFlowView,
OptionNbboSide,
OptionSecurityType,
OptionType,
@ -853,21 +854,6 @@ export const getLiveHistoryRetentionCap = (subscription: LiveSubscription): numb
}
};
export const getScopedLiveAutoHydrationChannels = (
enabled: boolean,
pathname: string,
manifest: LiveSubscription[],
historyCursors: Partial<Record<string, Cursor | null>>,
historyLoading: Partial<Record<string, boolean>>
): Array<Extract<LiveSubscription["channel"], "options" | "equities">> => {
void enabled;
void pathname;
void manifest;
void historyCursors;
void historyLoading;
return [];
};
export const getLiveFeedStatus = (
sourceStatus: WsStatus,
freshestTs: number | null,
@ -1436,6 +1422,9 @@ export const countActiveFlowFilterGroups = (filters: OptionFlowFilters): number
if ((filters.minNotional ?? undefined) !== (defaults.minNotional ?? undefined)) {
count += 1;
}
if ((filters.view ?? defaults.view) !== defaults.view) {
count += 1;
}
return count;
};
@ -3684,18 +3673,6 @@ const useLiveSession = (
[enabled, manifest, historyCursors, historyLoading]
);
useEffect(() => {
for (const channel of getScopedLiveAutoHydrationChannels(
enabled,
pathname,
manifest,
historyCursors,
historyLoading
)) {
void loadOlder(channel);
}
}, [enabled, pathname, manifest, historyCursors, historyLoading, loadOlder]);
return {
status,
connectedAt,
@ -6904,6 +6881,17 @@ export const FlowFilterPopover = ({ filters, onChange }: FlowFilterPopoverProps)
}));
};
const applyView = (view: OptionFlowView) => {
onChange((prev) => ({
...prev,
view,
securityTypes: view === "raw" ? undefined : prev.securityTypes ?? DEFAULT_FLOW_SECURITY_TYPES,
nbboSides: view === "raw" ? undefined : prev.nbboSides,
optionTypes: view === "raw" ? undefined : prev.optionTypes,
minNotional: view === "raw" ? undefined : prev.minNotional
}));
};
useEffect(() => {
if (!open) {
return;
@ -6968,6 +6956,27 @@ export const FlowFilterPopover = ({ filters, onChange }: FlowFilterPopoverProps)
</div>
<div className="flow-filter-popover-body">
<FlowFilterSection title="Options View">
<div className="flow-filter-chip-grid flow-filter-chip-grid-two">
{[
{ label: "Signal", value: "signal" as const },
{ label: "All prints", value: "raw" as const }
].map((preset) => (
<button
className={`filter-chip ${filters.view === preset.value ? "is-active" : ""}`}
key={preset.value}
type="button"
onClick={() => applyView(preset.value)}
>
{preset.label}
</button>
))}
</div>
<p className="flow-filter-section-copy">
Signal keeps classifier-ready prints. All prints includes raw option tape rows.
</p>
</FlowFilterSection>
<FlowFilterSection title="Security">
<div className="flow-filter-checkbox-grid">
{(["stock", "etf"] as OptionSecurityType[]).map((value) => (