Add NBBO persistence, API/WS streaming, and UI context
This commit is contained in:
parent
15fce370ef
commit
fc7065792f
12 changed files with 768 additions and 46 deletions
|
|
@ -4,6 +4,7 @@ import {
|
|||
ClassifierHitEventSchema,
|
||||
EquityPrintSchema,
|
||||
FlowPacketSchema,
|
||||
OptionNBBOSchema,
|
||||
OptionPrintSchema
|
||||
} from "@islandflow/types";
|
||||
import type {
|
||||
|
|
@ -11,6 +12,7 @@ import type {
|
|||
ClassifierHitEvent,
|
||||
EquityPrint,
|
||||
FlowPacket,
|
||||
OptionNBBO,
|
||||
OptionPrint
|
||||
} from "@islandflow/types";
|
||||
import {
|
||||
|
|
@ -18,6 +20,7 @@ import {
|
|||
optionPrintsTableDDL,
|
||||
OPTION_PRINTS_TABLE
|
||||
} from "./option-prints";
|
||||
import { normalizeOptionNBBO, optionNBBOTableDDL, OPTION_NBBO_TABLE } from "./option-nbbo";
|
||||
import {
|
||||
equityPrintsTableDDL,
|
||||
EQUITY_PRINTS_TABLE,
|
||||
|
|
@ -69,6 +72,14 @@ export const ensureOptionPrintsTable = async (
|
|||
});
|
||||
};
|
||||
|
||||
export const ensureOptionNBBOTable = async (
|
||||
client: ClickHouseClient
|
||||
): Promise<void> => {
|
||||
await client.exec({
|
||||
query: optionNBBOTableDDL()
|
||||
});
|
||||
};
|
||||
|
||||
export const ensureEquityPrintsTable = async (
|
||||
client: ClickHouseClient
|
||||
): Promise<void> => {
|
||||
|
|
@ -111,6 +122,18 @@ export const insertOptionPrint = async (
|
|||
});
|
||||
};
|
||||
|
||||
export const insertOptionNBBO = async (
|
||||
client: ClickHouseClient,
|
||||
nbbo: OptionNBBO
|
||||
): Promise<void> => {
|
||||
const record = normalizeOptionNBBO(nbbo);
|
||||
await client.insert({
|
||||
table: OPTION_NBBO_TABLE,
|
||||
values: [record],
|
||||
format: "JSONEachRow"
|
||||
});
|
||||
};
|
||||
|
||||
export const insertEquityPrint = async (
|
||||
client: ClickHouseClient,
|
||||
print: EquityPrint
|
||||
|
|
@ -213,6 +236,24 @@ const normalizeOptionRow = (row: unknown): unknown => {
|
|||
return row;
|
||||
};
|
||||
|
||||
const normalizeOptionNbboRow = (row: unknown): unknown => {
|
||||
if (row && typeof row === "object") {
|
||||
return normalizeNumericFields(row as Record<string, unknown>, [
|
||||
"source_ts",
|
||||
"ingest_ts",
|
||||
"seq",
|
||||
"ts",
|
||||
"bid",
|
||||
"ask",
|
||||
"bidSize",
|
||||
"askSize"
|
||||
]);
|
||||
}
|
||||
|
||||
return row;
|
||||
};
|
||||
|
||||
|
||||
const normalizeEquityRow = (row: unknown): unknown => {
|
||||
if (row && typeof row === "object") {
|
||||
const record = normalizeNumericFields(row as Record<string, unknown>, [
|
||||
|
|
@ -307,6 +348,20 @@ export const fetchRecentOptionPrints = async (
|
|||
return OptionPrintSchema.array().parse(rows.map(normalizeOptionRow));
|
||||
};
|
||||
|
||||
export const fetchRecentOptionNBBO = async (
|
||||
client: ClickHouseClient,
|
||||
limit: number
|
||||
): Promise<OptionNBBO[]> => {
|
||||
const safeLimit = clampLimit(limit);
|
||||
const result = await client.query({
|
||||
query: `SELECT * FROM ${OPTION_NBBO_TABLE} ORDER BY ts DESC, seq DESC LIMIT ${safeLimit}`,
|
||||
format: "JSONEachRow"
|
||||
});
|
||||
|
||||
const rows = await result.json<unknown[]>();
|
||||
return OptionNBBOSchema.array().parse(rows.map(normalizeOptionNbboRow));
|
||||
};
|
||||
|
||||
export const fetchRecentEquityPrints = async (
|
||||
client: ClickHouseClient,
|
||||
limit: number
|
||||
|
|
@ -394,6 +449,25 @@ export const fetchOptionPrintsAfter = async (
|
|||
return OptionPrintSchema.array().parse(rows.map(normalizeOptionRow));
|
||||
};
|
||||
|
||||
export const fetchOptionNBBOAfter = async (
|
||||
client: ClickHouseClient,
|
||||
afterTs: number,
|
||||
afterSeq: number,
|
||||
limit: number
|
||||
): Promise<OptionNBBO[]> => {
|
||||
const safeLimit = clampLimit(limit);
|
||||
const safeAfterTs = clampCursor(afterTs);
|
||||
const safeAfterSeq = clampCursor(afterSeq);
|
||||
|
||||
const result = await client.query({
|
||||
query: `SELECT * FROM ${OPTION_NBBO_TABLE} WHERE (ts, seq) > (${safeAfterTs}, ${safeAfterSeq}) ORDER BY ts ASC, seq ASC LIMIT ${safeLimit}`,
|
||||
format: "JSONEachRow"
|
||||
});
|
||||
|
||||
const rows = await result.json<unknown[]>();
|
||||
return OptionNBBOSchema.array().parse(rows.map(normalizeOptionNbboRow));
|
||||
};
|
||||
|
||||
export const fetchEquityPrintsAfter = async (
|
||||
client: ClickHouseClient,
|
||||
afterTs: number,
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@ export * from "./alerts";
|
|||
export * from "./flow-packets";
|
||||
export * from "./equity-prints";
|
||||
export * from "./option-prints";
|
||||
export * from "./option-nbbo";
|
||||
|
|
|
|||
26
packages/storage/src/option-nbbo.ts
Normal file
26
packages/storage/src/option-nbbo.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import type { OptionNBBO } from "@islandflow/types";
|
||||
|
||||
export const OPTION_NBBO_TABLE = "option_nbbo";
|
||||
|
||||
export const optionNBBOTableDDL = (): string => {
|
||||
return `
|
||||
CREATE TABLE IF NOT EXISTS ${OPTION_NBBO_TABLE} (
|
||||
source_ts UInt64,
|
||||
ingest_ts UInt64,
|
||||
seq UInt64,
|
||||
trace_id String,
|
||||
ts UInt64,
|
||||
option_contract_id String,
|
||||
bid Float64,
|
||||
ask Float64,
|
||||
bidSize UInt32,
|
||||
askSize UInt32
|
||||
)
|
||||
ENGINE = MergeTree
|
||||
ORDER BY (ts, option_contract_id)
|
||||
`;
|
||||
};
|
||||
|
||||
export const normalizeOptionNBBO = (nbbo: OptionNBBO): OptionNBBO => {
|
||||
return nbbo;
|
||||
};
|
||||
28
packages/storage/tests/option-nbbo.test.ts
Normal file
28
packages/storage/tests/option-nbbo.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { describe, expect, it } from "bun:test";
|
||||
import { normalizeOptionNBBO, optionNBBOTableDDL, OPTION_NBBO_TABLE } from "../src/option-nbbo";
|
||||
|
||||
const baseNbbo = {
|
||||
source_ts: 100,
|
||||
ingest_ts: 200,
|
||||
seq: 1,
|
||||
trace_id: "trace-1",
|
||||
ts: 100,
|
||||
option_contract_id: "SPY-2025-01-17-450-C",
|
||||
bid: 1.2,
|
||||
ask: 1.3,
|
||||
bidSize: 10,
|
||||
askSize: 12
|
||||
};
|
||||
|
||||
describe("option-nbbo storage helpers", () => {
|
||||
it("keeps required fields intact", () => {
|
||||
const normalized = normalizeOptionNBBO(baseNbbo);
|
||||
expect(normalized).toEqual(baseNbbo);
|
||||
});
|
||||
|
||||
it("includes the correct table name in the DDL", () => {
|
||||
const ddl = optionNBBOTableDDL();
|
||||
expect(ddl).toContain(OPTION_NBBO_TABLE);
|
||||
expect(ddl).toContain("CREATE TABLE IF NOT EXISTS");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue