gate option print clickhouse persistence by signal pass
This commit is contained in:
parent
828c81bcc6
commit
955eccce3c
7 changed files with 352 additions and 5 deletions
|
|
@ -44,6 +44,7 @@ import { createIbkrOptionsAdapter } from "./adapters/ibkr";
|
|||
import { createSyntheticOptionsAdapter } from "./adapters/synthetic";
|
||||
import type { OptionIngestAdapter, StopHandler } from "./adapters/types";
|
||||
import { enrichOptionPrint, rememberContext, selectAtOrBefore, type ContextHistory } from "./enrichment";
|
||||
import { processOptionTrade } from "./trade-pipeline";
|
||||
import { z } from "zod";
|
||||
|
||||
const service = "ingest-options";
|
||||
|
|
@ -104,6 +105,20 @@ const envSchema = z.object({
|
|||
OPTIONS_SIGNAL_ETF_UNDERLYINGS: z
|
||||
.string()
|
||||
.default("SPY,QQQ,IWM,DIA,TLT,GLD,SLV,XLF,XLE,XLV,XLI,XLP,XLU,XLY,SMH,ARKK"),
|
||||
OPTIONS_PERSIST_SIGNAL_ONLY: z
|
||||
.preprocess((value) => {
|
||||
if (typeof value === "string") {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (["1", "true", "yes", "on"].includes(normalized)) {
|
||||
return true;
|
||||
}
|
||||
if (["0", "false", "no", "off"].includes(normalized)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}, z.boolean())
|
||||
.default(true),
|
||||
TESTING_MODE: z
|
||||
.preprocess((value) => {
|
||||
if (typeof value === "string") {
|
||||
|
|
@ -400,6 +415,9 @@ const run = async () => {
|
|||
() => syntheticControl
|
||||
);
|
||||
logger.info("ingest adapter selected", { adapter: adapter.name });
|
||||
logger.info("option print clickhouse persistence mode", {
|
||||
signal_only: env.OPTIONS_PERSIST_SIGNAL_ONLY
|
||||
});
|
||||
const allowPublish = buildThrottle(env.TESTING_MODE, env.TESTING_THROTTLE_MS);
|
||||
const allowNbboPublish = buildThrottle(env.TESTING_MODE, env.TESTING_THROTTLE_MS);
|
||||
|
||||
|
|
@ -426,11 +444,12 @@ const run = async () => {
|
|||
const print = enrichOptionPrint(rawPrint, optionQuote, equityQuote, optionsSignalConfig);
|
||||
|
||||
try {
|
||||
await insertOptionPrint(clickhouse, print);
|
||||
await publishJson(js, SUBJECT_OPTION_PRINTS, print);
|
||||
if (print.signal_pass) {
|
||||
await publishJson(js, SUBJECT_OPTION_SIGNAL_PRINTS, print);
|
||||
}
|
||||
await processOptionTrade(print, {
|
||||
persistSignalOnly: env.OPTIONS_PERSIST_SIGNAL_ONLY,
|
||||
persist: async (value) => insertOptionPrint(clickhouse, value),
|
||||
publishRaw: async (value) => publishJson(js, SUBJECT_OPTION_PRINTS, value),
|
||||
publishSignal: async (value) => publishJson(js, SUBJECT_OPTION_SIGNAL_PRINTS, value)
|
||||
});
|
||||
} catch (error) {
|
||||
if (isExpectedShutdownError(error)) {
|
||||
return;
|
||||
|
|
|
|||
24
services/ingest-options/src/trade-pipeline.ts
Normal file
24
services/ingest-options/src/trade-pipeline.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import type { OptionPrint } from "@islandflow/types";
|
||||
|
||||
export type ProcessOptionTradeDeps = {
|
||||
persistSignalOnly: boolean;
|
||||
persist: (print: OptionPrint) => Promise<void>;
|
||||
publishRaw: (print: OptionPrint) => Promise<void>;
|
||||
publishSignal: (print: OptionPrint) => Promise<void>;
|
||||
};
|
||||
|
||||
export const shouldPersistOptionPrint = (print: Pick<OptionPrint, "signal_pass">, persistSignalOnly: boolean): boolean => {
|
||||
return !persistSignalOnly || print.signal_pass === true;
|
||||
};
|
||||
|
||||
export const processOptionTrade = async (print: OptionPrint, deps: ProcessOptionTradeDeps): Promise<void> => {
|
||||
if (shouldPersistOptionPrint(print, deps.persistSignalOnly)) {
|
||||
await deps.persist(print);
|
||||
}
|
||||
|
||||
await deps.publishRaw(print);
|
||||
|
||||
if (print.signal_pass) {
|
||||
await deps.publishSignal(print);
|
||||
}
|
||||
};
|
||||
101
services/ingest-options/tests/trade-pipeline.test.ts
Normal file
101
services/ingest-options/tests/trade-pipeline.test.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { describe, expect, it } from "bun:test";
|
||||
import type { OptionPrint } from "@islandflow/types";
|
||||
import { processOptionTrade, shouldPersistOptionPrint } from "../src/trade-pipeline";
|
||||
|
||||
const makePrint = (signalPass: boolean): OptionPrint => ({
|
||||
source_ts: 1_000,
|
||||
ingest_ts: 1_001,
|
||||
seq: 1,
|
||||
trace_id: `print-${signalPass ? "pass" : "fail"}`,
|
||||
ts: 1_000,
|
||||
option_contract_id: "SPY-2025-01-17-450-C",
|
||||
price: 1.25,
|
||||
size: 100,
|
||||
exchange: "TEST",
|
||||
signal_pass: signalPass
|
||||
});
|
||||
|
||||
describe("option trade persistence gating", () => {
|
||||
it("does not persist failing prints when signal-only persistence is enabled", async () => {
|
||||
const persisted: string[] = [];
|
||||
const rawPublished: string[] = [];
|
||||
const signalPublished: string[] = [];
|
||||
|
||||
await processOptionTrade(makePrint(false), {
|
||||
persistSignalOnly: true,
|
||||
persist: async (print) => {
|
||||
persisted.push(print.trace_id);
|
||||
},
|
||||
publishRaw: async (print) => {
|
||||
rawPublished.push(print.trace_id);
|
||||
},
|
||||
publishSignal: async (print) => {
|
||||
signalPublished.push(print.trace_id);
|
||||
}
|
||||
});
|
||||
|
||||
expect(persisted).toEqual([]);
|
||||
expect(rawPublished).toEqual(["print-fail"]);
|
||||
expect(signalPublished).toEqual([]);
|
||||
});
|
||||
|
||||
it("persists and publishes passing prints when signal-only persistence is enabled", async () => {
|
||||
const persisted: string[] = [];
|
||||
const rawPublished: string[] = [];
|
||||
const signalPublished: string[] = [];
|
||||
|
||||
await processOptionTrade(makePrint(true), {
|
||||
persistSignalOnly: true,
|
||||
persist: async (print) => {
|
||||
persisted.push(print.trace_id);
|
||||
},
|
||||
publishRaw: async (print) => {
|
||||
rawPublished.push(print.trace_id);
|
||||
},
|
||||
publishSignal: async (print) => {
|
||||
signalPublished.push(print.trace_id);
|
||||
}
|
||||
});
|
||||
|
||||
expect(persisted).toEqual(["print-pass"]);
|
||||
expect(rawPublished).toEqual(["print-pass"]);
|
||||
expect(signalPublished).toEqual(["print-pass"]);
|
||||
});
|
||||
|
||||
it("persists failing prints when signal-only persistence is disabled", async () => {
|
||||
const persisted: string[] = [];
|
||||
const rawPublished: string[] = [];
|
||||
const signalPublished: string[] = [];
|
||||
|
||||
await processOptionTrade(makePrint(false), {
|
||||
persistSignalOnly: false,
|
||||
persist: async (print) => {
|
||||
persisted.push(print.trace_id);
|
||||
},
|
||||
publishRaw: async (print) => {
|
||||
rawPublished.push(print.trace_id);
|
||||
},
|
||||
publishSignal: async (print) => {
|
||||
signalPublished.push(print.trace_id);
|
||||
}
|
||||
});
|
||||
|
||||
expect(persisted).toEqual(["print-fail"]);
|
||||
expect(rawPublished).toEqual(["print-fail"]);
|
||||
expect(signalPublished).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldPersistOptionPrint", () => {
|
||||
it("returns true for passing prints in signal-only mode", () => {
|
||||
expect(shouldPersistOptionPrint({ signal_pass: true }, true)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for failing prints in signal-only mode", () => {
|
||||
expect(shouldPersistOptionPrint({ signal_pass: false }, true)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true for failing prints when signal-only mode is disabled", () => {
|
||||
expect(shouldPersistOptionPrint({ signal_pass: false }, false)).toBe(true);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue