Merge pull request #37 from dirtydishes/synthetic-prints-done-right

Add hosted synthetic market control surface
This commit is contained in:
dirtydishes 2026-05-14 03:18:17 -04:00 committed by GitHub
commit 424d67271d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 5235 additions and 772 deletions

View file

@ -1,3 +1,5 @@
{"_type":"issue","id":"islandflow-9nd","title":"Hosted synthetic tape redesign with internal control surface","description":"Implement hosted synthetic market redesign with shared deterministic regime engine, internal JetStream KV control plane, ingest coupling across options and equities, and an internal bottom-right synthetic-control drawer with Next proxy routes. Preserve the six public smart-money categories while adding hidden subtype families, soft coverage accounting, and backend-only admin endpoints.\n","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T01:25:02Z","created_by":"dirtydishes","updated_at":"2026-05-14T02:10:03Z","started_at":"2026-05-14T01:25:09Z","closed_at":"2026-05-14T02:10:03Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-9dz","title":"Tune synthetic smart-money scenario coverage","description":"Redesign synthetic smart-money option prints so the emitted scenarios trigger each classifier category more consistently while staying directionally plausible. Focus on scenario mix, DTE/moneyness, price placement, and event/structure context so the Electron demo reliably shows institutional directional, retail whale, event-driven, vol seller, arbitrage, and hedge reactive hits.\n","status":"in_progress","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-13T21:36:37Z","created_by":"dirtydishes","updated_at":"2026-05-13T21:36:41Z","started_at":"2026-05-13T21:36:41Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-zuf","title":"Fix Home to Tape tab navigation freeze","description":"Home-to-Tape navigation becomes unresponsive because TerminalAppShell enters a live-mode rerender loop. The pinned-evidence prune effect writes new Map instances even when contents are unchanged, which can retrigger state updates indefinitely on the Home route where alert evidence prefetch is active. Make pruning idempotent and add regression coverage.\n","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-13T15:05:56Z","created_by":"dirtydishes","updated_at":"2026-05-13T15:08:01Z","started_at":"2026-05-13T15:06:06Z","closed_at":"2026-05-13T15:08:01Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-9ug","title":"Electron desktop shell for hosted Islandflow","description":"Build a macOS-first Electron desktop shell workspace that loads hosted Islandflow in a locked-down BrowserWindow, adds Bun-first dev/package scripts, documents the workflow, and preserves the existing remote API/WS contract.\n","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-13T13:11:40Z","created_by":"dirtydishes","updated_at":"2026-05-13T13:20:57Z","started_at":"2026-05-13T13:12:03Z","closed_at":"2026-05-13T13:20:57Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-sh1","title":"Fix live websocket stale lag and reconnect loop","description":"Investigate and fix API live consumer lag causing stale timestamps, feed-behind status, and reconnect loops. Optimize live cache persistence path, add lag telemetry/alerts, and validate in runtime.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-04T17:04:34Z","created_by":"dirtydishes","updated_at":"2026-05-04T17:09:44Z","started_at":"2026-05-04T17:04:38Z","closed_at":"2026-05-04T17:09:44Z","close_reason":"Completed: optimized live cache persistence path, added lag telemetry, deployed api via docker compose on di, verified ws freshness and low hotFeedLagMs","dependency_count":0,"dependent_count":0,"comment_count":0}
@ -6,6 +8,7 @@
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-a50","title":"Add HTML plan docs for synthetic tape redesign","description":"Create two HTML planning docs under plans/: one straightforward end-user readable version and one more polished impeccable-style version, both covering the hosted synthetic tape redesign with summary, scope, affected services, UI notes, rollout, tests, and the full detailed implementation plan.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-14T02:47:44Z","created_by":"dirtydishes","updated_at":"2026-05-14T02:53:11Z","started_at":"2026-05-14T02:47:48Z","closed_at":"2026-05-14T02:53:11Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-932","title":"Desktop follow-up native features","description":"Track deferred native desktop features after the thin hosted-wrapper v1 lands: notifications, keyboard shortcuts, local preferences storage, remembered window state, signed/notarized macOS distribution, auto-update evaluation, and optional local frontend bundling.\n","status":"open","priority":2,"issue_type":"task","owner":"dishes@dpdrm.com","created_at":"2026-05-13T13:20:12Z","created_by":"dirtydishes","updated_at":"2026-05-13T13:20:12Z","dependencies":[{"issue_id":"islandflow-932","depends_on_id":"islandflow-9ug","type":"discovered-from","created_at":"2026-05-13T09:20:12Z","created_by":"dirtydishes","metadata":"{}"}],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-vbk","title":"Remove deprecated Alpaca key-pair auth","description":"Remove legacy Alpaca key-pair authentication support and keep ALPACA_API_KEY as the only supported auth method across options/equities ingest and docs.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:19:51Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:21:10Z","started_at":"2026-05-05T07:19:54Z","closed_at":"2026-05-05T07:21:10Z","close_reason":"Removed key-pair auth and kept ALPACA_API_KEY only","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"islandflow-h47","title":"Support single-token Alpaca auth","description":"Support single-token Alpaca authentication across ingest adapters using ALPACA_API_KEY with fallback to ALPACA_KEY_ID/ALPACA_SECRET_KEY, and document env usage.\n","status":"closed","priority":2,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-05T07:12:22Z","created_by":"dirtydishes","updated_at":"2026-05-05T07:13:54Z","started_at":"2026-05-05T07:12:25Z","closed_at":"2026-05-05T07:13:54Z","close_reason":"Added ALPACA_API_KEY support with key-pair fallback","dependency_count":0,"dependent_count":0,"comment_count":0}

View file

@ -0,0 +1,19 @@
import { proxySyntheticAdminRequest } from "../shared";
export const dynamic = "force-dynamic";
export async function GET(): Promise<Response> {
return proxySyntheticAdminRequest("/admin/synthetic/control", {
method: "GET"
});
}
export async function PUT(req: Request): Promise<Response> {
return proxySyntheticAdminRequest(
"/admin/synthetic/control",
{
method: "PUT",
body: await req.text()
}
);
}

View file

@ -0,0 +1,61 @@
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
import {
getSyntheticAdminProxyConfig,
isSyntheticAdminFeatureEnabled
} from "./shared";
const originalFetch = globalThis.fetch;
describe("synthetic admin proxy helpers", () => {
beforeEach(() => {
process.env.NEXT_PUBLIC_SYNTHETIC_ADMIN = "1";
process.env.NEXT_PUBLIC_API_URL = "http://127.0.0.1:4000";
process.env.SYNTHETIC_ADMIN_TOKEN = "secret-token";
});
afterEach(() => {
globalThis.fetch = originalFetch;
});
it("gates visibility on the public env flag", () => {
expect(isSyntheticAdminFeatureEnabled("1")).toBe(true);
expect(isSyntheticAdminFeatureEnabled("0")).toBe(false);
});
it("reads the proxy config from server env only", () => {
expect(getSyntheticAdminProxyConfig()).toEqual({
apiBaseUrl: "http://127.0.0.1:4000",
token: "secret-token"
});
});
it("proxies status requests with the backend admin token", async () => {
const fetchMock = mock(async (input: string | URL, init?: RequestInit) => {
expect(String(input)).toBe("http://127.0.0.1:4000/admin/synthetic/status");
expect(new Headers(init?.headers).get("authorization")).toBe("Bearer secret-token");
return new Response(JSON.stringify({ enabled: true }), {
status: 200,
headers: {
"content-type": "application/json"
}
});
});
globalThis.fetch = fetchMock as typeof fetch;
const route = await import("./status/route");
const response = await route.GET();
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ enabled: true });
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it("returns 404 from proxy routes when the internal UI flag is off", async () => {
process.env.NEXT_PUBLIC_SYNTHETIC_ADMIN = "0";
const route = await import("./control/route");
const response = await route.GET();
expect(response.status).toBe(404);
});
});

View file

@ -0,0 +1,63 @@
const jsonResponse = (body: unknown, status = 200): Response => {
return new Response(JSON.stringify(body), {
status,
headers: {
"content-type": "application/json"
}
});
};
export const isSyntheticAdminFeatureEnabled = (
value = process.env.NEXT_PUBLIC_SYNTHETIC_ADMIN
): boolean => value === "1";
export const getSyntheticAdminProxyConfig = (
env: Record<string, string | undefined> = process.env
): { apiBaseUrl: string; token: string } | null => {
const apiBaseUrl = env.NEXT_PUBLIC_API_URL?.trim();
const token = env.SYNTHETIC_ADMIN_TOKEN?.trim();
if (!apiBaseUrl || !token) {
return null;
}
return { apiBaseUrl, token };
};
export const proxySyntheticAdminRequest = async (
path: string,
init: RequestInit = {},
env: Record<string, string | undefined> = process.env
): Promise<Response> => {
if (!isSyntheticAdminFeatureEnabled(env.NEXT_PUBLIC_SYNTHETIC_ADMIN)) {
return jsonResponse({ error: "not found" }, 404);
}
const config = getSyntheticAdminProxyConfig(env);
if (!config) {
return jsonResponse(
{
error: "synthetic admin proxy misconfigured"
},
500
);
}
const url = new URL(path, config.apiBaseUrl);
const headers = new Headers(init.headers);
headers.set("authorization", `Bearer ${config.token}`);
if (!headers.has("content-type") && init.body) {
headers.set("content-type", "application/json");
}
const response = await fetch(url.toString(), {
...init,
cache: "no-store",
headers
});
return new Response(response.body, {
status: response.status,
headers: {
"content-type": response.headers.get("content-type") ?? "application/json"
}
});
};

View file

@ -0,0 +1,9 @@
import { proxySyntheticAdminRequest } from "../shared";
export const dynamic = "force-dynamic";
export async function GET(): Promise<Response> {
return proxySyntheticAdminRequest("/admin/synthetic/status", {
method: "GET"
});
}

View file

@ -1507,6 +1507,196 @@ h3 {
z-index: 40;
}
.synthetic-control-gear {
position: fixed;
right: 22px;
bottom: 22px;
width: 42px;
height: 42px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(245, 166, 35, 0.24);
border-radius: 12px;
background: rgba(9, 13, 18, 0.96);
color: var(--accent);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.38);
z-index: 45;
transition: transform 0.16s ease, border-color 0.16s ease, background 0.16s ease;
}
.synthetic-control-gear:hover,
.synthetic-control-gear.is-open {
transform: translateY(-1px);
border-color: rgba(245, 166, 35, 0.4);
background: rgba(12, 18, 24, 0.98);
}
.synthetic-control-gear-mark {
display: inline-flex;
font-size: 1.05rem;
line-height: 1;
transform: rotate(45deg);
}
.synthetic-control-drawer {
position: fixed;
top: 84px;
right: 0;
bottom: 0;
width: min(388px, calc(100vw - 20px));
padding: 18px 18px 24px;
display: grid;
align-content: start;
gap: 16px;
overflow: auto;
border-left: 1px solid rgba(245, 166, 35, 0.18);
background:
linear-gradient(180deg, rgba(245, 166, 35, 0.04), transparent 18%),
rgba(6, 9, 13, 0.98);
box-shadow: -18px 0 50px rgba(0, 0, 0, 0.34);
z-index: 42;
}
.synthetic-control-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.synthetic-control-header h3 {
margin: 0;
font-size: 1rem;
letter-spacing: 0.04em;
}
.synthetic-control-kicker {
margin: 0 0 6px;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.16em;
font-size: 0.64rem;
}
.synthetic-control-section {
display: grid;
gap: 10px;
padding: 14px 14px 0;
border-top: 1px solid var(--border);
}
.synthetic-control-section-head {
display: flex;
justify-content: space-between;
gap: 12px;
color: var(--text-faint);
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 0.68rem;
}
.synthetic-control-select select,
.synthetic-segment,
.synthetic-control-toggle {
font: inherit;
}
.synthetic-control-select select {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 10px;
background: rgba(255, 255, 255, 0.03);
color: var(--text);
}
.synthetic-control-toggle {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--text-dim);
}
.synthetic-control-toggle input {
accent-color: var(--accent);
}
.synthetic-segment-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.synthetic-segment {
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: 999px;
background: rgba(255, 255, 255, 0.02);
color: var(--text-dim);
}
.synthetic-segment.is-active {
border-color: rgba(245, 166, 35, 0.44);
background: rgba(245, 166, 35, 0.12);
color: var(--text);
}
.synthetic-profile-grid,
.synthetic-hit-list {
display: grid;
gap: 12px;
}
.synthetic-profile-row,
.synthetic-hit-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.synthetic-profile-row > span,
.synthetic-hit-row > span,
.synthetic-status-grid span {
color: var(--text-dim);
font-size: 0.84rem;
}
.synthetic-status-grid {
display: grid;
gap: 10px;
}
.synthetic-status-grid strong,
.synthetic-hit-row strong {
font-family: var(--font-mono), monospace;
font-size: 0.86rem;
}
.synthetic-control-disabled {
display: grid;
gap: 8px;
padding: 14px 14px 0;
border-top: 1px solid var(--border);
}
.synthetic-control-disabled p,
.synthetic-control-disabled span {
margin: 0;
}
.synthetic-control-disabled-label {
color: var(--accent);
text-transform: uppercase;
letter-spacing: 0.14em;
font-size: 0.68rem;
}
.synthetic-control-error {
color: var(--red);
}
.drawer-header {
display: flex;
align-items: flex-start;
@ -1732,4 +1922,19 @@ h3 {
max-height: none;
margin-top: 14px;
}
.synthetic-control-gear {
right: 14px;
bottom: 14px;
}
.synthetic-control-drawer {
top: auto;
left: 14px;
right: 14px;
bottom: 68px;
width: auto;
border: 1px solid rgba(245, 166, 35, 0.16);
border-radius: 14px;
}
}

View file

@ -28,6 +28,7 @@ import {
mergeNewestWithOverflow,
normalizeAlertSeverity,
nextFlowFilterPopoverState,
isSyntheticAdminVisible,
prunePinnedEntries,
projectPausableTapeState,
reducePausableTapeData,
@ -407,6 +408,13 @@ describe("terminal navigation", () => {
});
});
describe("synthetic admin visibility", () => {
it("shows the internal control rail only when the public admin flag is enabled", () => {
expect(isSyntheticAdminVisible("1")).toBe(true);
expect(isSyntheticAdminVisible("0")).toBe(false);
});
});
describe("live tape pausable helpers", () => {
it("queues new items while paused and flushes them on resume", () => {
let state = reducePausableTapeData(

View file

@ -39,7 +39,11 @@ import type {
OptionNBBO,
OptionPrint,
SmartMoneyEvent,
SmartMoneyProfileId
SmartMoneyProfileId,
SyntheticControlState,
SyntheticCoverageWindowMinutes,
SyntheticDerivedStatus,
SyntheticProfileWeightValue
} from "@islandflow/types";
import {
getSubscriptionKey as getLiveSubscriptionKey,
@ -988,6 +992,96 @@ const buildApiUrl = (path: string): string => {
return `${httpProtocol}://${host}${path}`;
};
export const isSyntheticAdminVisible = (
value = process.env.NEXT_PUBLIC_SYNTHETIC_ADMIN
): boolean => value === "1";
type SyntheticAdminStatusResponse = {
enabled: boolean;
backend_mode: "synthetic" | "mixed" | "live";
adapters: {
options: string;
equities: string;
};
control: SyntheticControlState | null;
derived: SyntheticDerivedStatus | null;
disabled_reason?: string;
};
type SyntheticAdminControlResponse = {
control: SyntheticControlState;
derived?: SyntheticDerivedStatus | null;
};
const SYNTHETIC_ADMIN_PROXY_PATHS = {
status: "/api/admin/synthetic/status",
control: "/api/admin/synthetic/control"
} as const;
const SYNTHETIC_PROFILE_ORDER: Array<keyof SyntheticControlState["profile_weights"]> = [
"institutional_directional",
"retail_whale",
"event_driven",
"vol_seller",
"arbitrage",
"hedge_reactive"
];
const SYNTHETIC_PROFILE_LABELS: Record<
keyof SyntheticControlState["profile_weights"],
string
> = {
institutional_directional: "Institutional Directional",
retail_whale: "Retail Whale",
event_driven: "Event Driven",
vol_seller: "Vol Seller",
arbitrage: "Arbitrage",
hedge_reactive: "Hedge Reactive"
};
const SYNTHETIC_PRESET_LABELS: Record<SyntheticControlState["preset_id"], string> = {
balanced_demo: "Balanced Demo",
event_day: "Event Day",
dealer_day: "Dealer Day",
retail_chase: "Retail Chase",
quiet_range: "Quiet Range"
};
const buildDefaultSyntheticControl = (): SyntheticControlState => ({
preset_id: "balanced_demo",
coverage_assist: true,
coverage_window_minutes: 20,
shared_seed: 11,
profile_weights: {
institutional_directional: 1.0,
retail_whale: 1.0,
event_driven: 1.0,
vol_seller: 1.0,
arbitrage: 1.0,
hedge_reactive: 1.0
},
updated_at: 0,
updated_by: "internal-ui"
});
type SyntheticControlPatch = Omit<Partial<SyntheticControlState>, "profile_weights"> & {
profile_weights?: Partial<SyntheticControlState["profile_weights"]>;
};
const createSyntheticControlDraft = (
current: SyntheticControlState,
patch: SyntheticControlPatch
): SyntheticControlState => ({
...current,
...patch,
profile_weights: {
...current.profile_weights,
...(patch.profile_weights ?? {})
},
updated_at: Date.now(),
updated_by: "internal-ui"
});
const formatPrice = (price: number): string => {
if (!Number.isFinite(price)) {
return "0.00";
@ -7926,6 +8020,331 @@ const ReplayConsole = memo(({ state }: { state: TerminalState }) => {
);
});
function SyntheticControlDock() {
const visible = isSyntheticAdminVisible();
const [open, setOpen] = useState(false);
const [status, setStatus] = useState<SyntheticAdminStatusResponse | null>(null);
const [draft, setDraft] = useState<SyntheticControlState | null>(null);
const [saved, setSaved] = useState<SyntheticControlState | null>(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const dirtyRef = useRef(false);
const savedRef = useRef<SyntheticControlState | null>(null);
useEffect(() => {
if (!visible) {
return;
}
let cancelled = false;
const load = async () => {
try {
const response = await fetch(SYNTHETIC_ADMIN_PROXY_PATHS.status, {
cache: "no-store"
});
if (cancelled) {
return;
}
if (response.status === 404) {
setStatus({
enabled: false,
backend_mode: "live",
adapters: { options: "unknown", equities: "unknown" },
control: null,
derived: null,
disabled_reason: "Synthetic admin backend is disabled."
});
setLoading(false);
return;
}
const nextStatus = (await response.json()) as SyntheticAdminStatusResponse;
setStatus(nextStatus);
if (!dirtyRef.current) {
const nextControl = nextStatus.control ?? buildDefaultSyntheticControl();
setDraft(nextControl);
setSaved(nextControl);
savedRef.current = nextControl;
}
} catch (loadError) {
if (!cancelled) {
setError(loadError instanceof Error ? loadError.message : String(loadError));
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
};
void load();
const timer = setInterval(() => {
void load();
}, 5_000);
return () => {
cancelled = true;
clearInterval(timer);
};
}, [visible]);
useEffect(() => {
if (!visible || !status?.enabled || !draft || !dirtyRef.current) {
return;
}
const timeout = setTimeout(() => {
const nextDraft = draft;
setSaving(true);
setError(null);
void fetch(SYNTHETIC_ADMIN_PROXY_PATHS.control, {
method: "PUT",
headers: {
"content-type": "application/json"
},
body: JSON.stringify(nextDraft)
})
.then(async (response) => {
if (!response.ok) {
const body = await response.json().catch(() => null);
throw new Error(body?.detail ?? body?.error ?? "Synthetic control update failed");
}
return (await response.json()) as SyntheticAdminControlResponse;
})
.then((payload) => {
dirtyRef.current = false;
savedRef.current = payload.control;
setSaved(payload.control);
setDraft(payload.control);
setStatus((current) =>
current
? {
...current,
control: payload.control,
derived: payload.derived ?? current.derived
}
: current
);
})
.catch((updateError) => {
dirtyRef.current = false;
setError(updateError instanceof Error ? updateError.message : String(updateError));
setDraft(savedRef.current);
})
.finally(() => {
setSaving(false);
});
}, 250);
return () => {
clearTimeout(timeout);
};
}, [draft, status?.enabled, visible]);
if (!visible) {
return null;
}
const currentControl = draft ?? saved ?? buildDefaultSyntheticControl();
const disabled = !status?.enabled;
const derived = status?.derived;
const updateControl = (
patch: SyntheticControlPatch
) => {
dirtyRef.current = true;
setDraft((current) =>
createSyntheticControlDraft(current ?? buildDefaultSyntheticControl(), patch)
);
};
const updateProfileWeight = (
profileId: keyof SyntheticControlState["profile_weights"],
value: SyntheticProfileWeightValue
) => {
updateControl({
profile_weights: {
[profileId]: value
} as Partial<SyntheticControlState["profile_weights"]>
});
};
return (
<>
<button
aria-expanded={open}
aria-label="Synthetic control"
className={`synthetic-control-gear${open ? " is-open" : ""}`}
onClick={() => setOpen((current) => !current)}
type="button"
>
<span className="synthetic-control-gear-mark">+</span>
</button>
{open ? (
<aside className="synthetic-control-drawer" aria-label="Synthetic control drawer">
<div className="synthetic-control-header">
<div>
<p className="synthetic-control-kicker">Synthetic Control</p>
<h3>Hosted tape operator rail</h3>
</div>
<button className="drawer-close" onClick={() => setOpen(false)} type="button">
Close
</button>
</div>
{loading ? (
<p className="drawer-note">Loading hosted synthetic status</p>
) : disabled ? (
<div className="synthetic-control-disabled">
<p className="synthetic-control-disabled-label">Unavailable</p>
<p>{status?.disabled_reason ?? "Synthetic control is currently unavailable."}</p>
<span>
Backend: {status?.backend_mode ?? "unknown"} · Options:{" "}
{status?.adapters.options ?? "unknown"} · Equities:{" "}
{status?.adapters.equities ?? "unknown"}
</span>
</div>
) : (
<>
<section className="synthetic-control-section">
<div className="synthetic-control-section-head">
<span>Preset</span>
<span>{saving ? "Saving…" : "Live"}</span>
</div>
<label className="synthetic-control-select">
<select
onChange={(event) =>
updateControl({
preset_id: event.target.value as SyntheticControlState["preset_id"]
})
}
value={currentControl.preset_id}
>
{Object.entries(SYNTHETIC_PRESET_LABELS).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</label>
</section>
<section className="synthetic-control-section">
<div className="synthetic-control-section-head">
<span>Coverage</span>
<span>{currentControl.coverage_window_minutes}m window</span>
</div>
<label className="synthetic-control-toggle">
<input
checked={currentControl.coverage_assist}
onChange={(event) =>
updateControl({ coverage_assist: event.target.checked })
}
type="checkbox"
/>
<span>Coverage assist</span>
</label>
<div className="synthetic-segment-row">
{[10, 20, 30].map((minutes) => (
<button
className={`synthetic-segment${currentControl.coverage_window_minutes === minutes ? " is-active" : ""}`}
key={minutes}
onClick={() =>
updateControl({
coverage_window_minutes:
minutes as SyntheticCoverageWindowMinutes
})
}
type="button"
>
{minutes}m
</button>
))}
</div>
</section>
<section className="synthetic-control-section">
<div className="synthetic-control-section-head">
<span>Profile Bias</span>
<span>Low · Normal · High</span>
</div>
<div className="synthetic-profile-grid">
{SYNTHETIC_PROFILE_ORDER.map((profileId) => (
<div className="synthetic-profile-row" key={profileId}>
<span>{SYNTHETIC_PROFILE_LABELS[profileId]}</span>
<div className="synthetic-segment-row">
{[
{ label: "Low", value: 0.6 },
{ label: "Normal", value: 1.0 },
{ label: "High", value: 1.6 }
].map((option) => (
<button
className={`synthetic-segment${currentControl.profile_weights[profileId] === option.value ? " is-active" : ""}`}
key={option.label}
onClick={() =>
updateProfileWeight(
profileId,
option.value as SyntheticProfileWeightValue
)
}
type="button"
>
{option.label}
</button>
))}
</div>
</div>
))}
</div>
</section>
<section className="synthetic-control-section">
<div className="synthetic-control-section-head">
<span>Live Status</span>
<span>{status?.backend_mode ?? "unknown"}</span>
</div>
<div className="synthetic-status-grid">
<div>
<span>Regime</span>
<strong>{derived?.regime ?? "—"}</strong>
</div>
<div>
<span>Session</span>
<strong>{derived?.session_phase ?? "—"}</strong>
</div>
<div>
<span>Focus</span>
<strong>
{derived?.focus_symbols?.length
? derived.focus_symbols.join(", ")
: "—"}
</strong>
</div>
<div>
<span>Backend</span>
<strong>{status?.enabled ? "Enabled" : "Disabled"}</strong>
</div>
</div>
<div className="synthetic-hit-list">
{SYNTHETIC_PROFILE_ORDER.map((profileId) => (
<div className="synthetic-hit-row" key={profileId}>
<span>{SYNTHETIC_PROFILE_LABELS[profileId]}</span>
<strong>{derived?.profile_hit_counts?.[profileId] ?? 0}</strong>
</div>
))}
</div>
</section>
{error ? <p className="drawer-note synthetic-control-error">{error}</p> : null}
</>
)}
</aside>
) : null}
</>
);
}
export function TerminalAppShell({ children }: { children: ReactNode }) {
const state = useTerminalState();
const pathname = usePathname();
@ -8003,6 +8422,8 @@ export function TerminalAppShell({ children }: { children: ReactNode }) {
<main className="terminal-content">{children}</main>
</div>
<SyntheticControlDock />
{state.selectedAlert ? (
<AlertDrawer
alert={state.selectedAlert}

View file

@ -6,6 +6,7 @@
".": "./src/index.ts"
},
"dependencies": {
"@islandflow/types": "workspace:*",
"nats": "^2.24.0"
}
}

View file

@ -1,3 +1,4 @@
export * from "./jetstream";
export * from "./streams";
export * from "./subjects";
export * from "./synthetic-control";

View file

@ -0,0 +1,100 @@
import {
DEFAULT_SYNTHETIC_CONTROL_STATE,
SyntheticControlStateSchema,
normalizeSyntheticControlState,
type SyntheticControlState
} from "@islandflow/types";
import { JSONCodec, type JetStreamClient, type KV, type KvEntry } from "nats";
export const SYNTHETIC_CONTROL_BUCKET = "synthetic_control";
export const SYNTHETIC_CONTROL_GLOBAL_KEY = "global";
const codec = JSONCodec<SyntheticControlState>();
const decodeSyntheticControlEntry = (
entry: KvEntry | null | undefined
): SyntheticControlState => {
if (!entry || entry.operation !== "PUT") {
return DEFAULT_SYNTHETIC_CONTROL_STATE;
}
return SyntheticControlStateSchema.parse(entry.json());
};
export const openSyntheticControlKv = async (
js: JetStreamClient
): Promise<KV> => {
return js.views.kv(SYNTHETIC_CONTROL_BUCKET, {
description: "Hosted synthetic market internal control state",
history: 8
});
};
export const readSyntheticControlState = async (
kv: KV
): Promise<SyntheticControlState> => {
return decodeSyntheticControlEntry(
await kv.get(SYNTHETIC_CONTROL_GLOBAL_KEY)
);
};
export const ensureSyntheticControlState = async (
kv: KV
): Promise<SyntheticControlState> => {
const current = await kv.get(SYNTHETIC_CONTROL_GLOBAL_KEY);
if (current && current.operation === "PUT") {
return SyntheticControlStateSchema.parse(current.json());
}
await kv.put(
SYNTHETIC_CONTROL_GLOBAL_KEY,
codec.encode(DEFAULT_SYNTHETIC_CONTROL_STATE)
);
return DEFAULT_SYNTHETIC_CONTROL_STATE;
};
export const writeSyntheticControlState = async (
kv: KV,
control: Partial<SyntheticControlState>
): Promise<SyntheticControlState> => {
const normalized = normalizeSyntheticControlState(control);
await kv.put(
SYNTHETIC_CONTROL_GLOBAL_KEY,
codec.encode(normalized)
);
return normalized;
};
export const watchSyntheticControlState = async (
kv: KV,
onUpdate: (control: SyntheticControlState) => void,
onError?: (error: unknown) => void
): Promise<() => Promise<void>> => {
const iterator = await kv.watch({
key: SYNTHETIC_CONTROL_GLOBAL_KEY,
ignoreDeletes: true
});
let stopped = false;
const task = (async () => {
try {
for await (const entry of iterator) {
if (stopped || entry.operation !== "PUT") {
continue;
}
onUpdate(SyntheticControlStateSchema.parse(entry.json()));
}
} catch (error) {
if (!stopped) {
onError?.(error);
}
}
})();
return async () => {
if (stopped) {
return;
}
stopped = true;
iterator.stop();
await task;
};
};

View file

@ -2,3 +2,4 @@ export * from "./events";
export * from "./live";
export * from "./options-flow";
export * from "./sp500";
export * from "./synthetic-market";

View file

@ -0,0 +1,834 @@
import { z } from "zod";
import type { SmartMoneyProfileId } from "./events";
import type { SyntheticMarketMode } from "./options-flow";
import { SP500_SYMBOLS } from "./sp500";
const SYNTHETIC_PROFILE_WEIGHT_VALUES = [0.6, 1.0, 1.6] as const;
const SYNTHETIC_COVERAGE_WINDOW_VALUES = [10, 20, 30] as const;
const SYNTHETIC_SYMBOLS = ["SPY", ...(SP500_SYMBOLS as readonly string[])];
const EVENT_SYMBOL_POOL = [
"AAPL",
"MSFT",
"NVDA",
"META",
"AMZN",
"TSLA",
"GOOGL",
"NFLX",
"AMD",
"AVGO"
] as const;
const SMART_MONEY_PROFILE_IDS = [
"institutional_directional",
"retail_whale",
"event_driven",
"vol_seller",
"arbitrage",
"hedge_reactive"
] as const satisfies readonly SmartMoneyProfileId[];
const SYNTHETIC_SCENARIO_FAMILY_IDS = [
...SMART_MONEY_PROFILE_IDS,
"neutral_noise"
] as const;
const REGIME_IDS = [
"trend_up",
"trend_down",
"mean_revert",
"retail_chase",
"event_ramp",
"dealer_gamma",
"arb_calm"
] as const;
export const SyntheticControlPresetIdSchema = z.enum([
"balanced_demo",
"event_day",
"dealer_day",
"retail_chase",
"quiet_range"
]);
export type SyntheticControlPresetId = z.infer<typeof SyntheticControlPresetIdSchema>;
export const SyntheticCoverageWindowMinutesSchema = z.union([
z.literal(10),
z.literal(20),
z.literal(30)
]);
export type SyntheticCoverageWindowMinutes = z.infer<
typeof SyntheticCoverageWindowMinutesSchema
>;
export const SyntheticProfileWeightValueSchema = z.union([
z.literal(0.6),
z.literal(1.0),
z.literal(1.6)
]);
export type SyntheticProfileWeightValue = z.infer<
typeof SyntheticProfileWeightValueSchema
>;
export const SyntheticProfileWeightMapSchema = z
.object({
institutional_directional: SyntheticProfileWeightValueSchema,
retail_whale: SyntheticProfileWeightValueSchema,
event_driven: SyntheticProfileWeightValueSchema,
vol_seller: SyntheticProfileWeightValueSchema,
arbitrage: SyntheticProfileWeightValueSchema,
hedge_reactive: SyntheticProfileWeightValueSchema
})
.strict();
export type SyntheticProfileWeightMap = z.infer<
typeof SyntheticProfileWeightMapSchema
>;
export const SyntheticControlStateSchema = z
.object({
preset_id: SyntheticControlPresetIdSchema,
coverage_assist: z.boolean(),
coverage_window_minutes: SyntheticCoverageWindowMinutesSchema,
shared_seed: z.number().int(),
profile_weights: SyntheticProfileWeightMapSchema,
updated_at: z.number().int().nonnegative(),
updated_by: z.string().trim().min(1)
})
.strict();
export type SyntheticControlState = z.infer<typeof SyntheticControlStateSchema>;
export const SyntheticSessionPhaseSchema = z.enum([
"open",
"midday",
"power_hour",
"after_event"
]);
export type SyntheticSessionPhase = z.infer<typeof SyntheticSessionPhaseSchema>;
export const SyntheticRegimeSchema = z.enum(REGIME_IDS);
export type SyntheticRegime = z.infer<typeof SyntheticRegimeSchema>;
export const SyntheticScenarioFamilyIdSchema = z.enum(
SYNTHETIC_SCENARIO_FAMILY_IDS
);
export type SyntheticScenarioFamilyId = z.infer<
typeof SyntheticScenarioFamilyIdSchema
>;
export const SyntheticCoverageConfigSchema = z
.object({
coverage_assist: z.boolean(),
coverage_window_minutes: SyntheticCoverageWindowMinutesSchema
})
.strict();
export type SyntheticCoverageConfig = z.infer<
typeof SyntheticCoverageConfigSchema
>;
export const SyntheticDerivedStatusSchema = z
.object({
session_phase: SyntheticSessionPhaseSchema,
regime: SyntheticRegimeSchema,
focus_symbols: z.array(z.string()),
profile_hit_counts: z.record(z.number().int().nonnegative()),
coverage_window_minutes: SyntheticCoverageWindowMinutesSchema
})
.strict();
export type SyntheticDerivedStatus = z.infer<
typeof SyntheticDerivedStatusSchema
>;
export type SyntheticSessionState = {
session_phase: SyntheticSessionPhase;
regime: SyntheticRegime;
volatility_level: number;
liquidity_level: number;
quote_cleanliness: number;
focus_symbols: string[];
event_symbols: string[];
seed_bucket: number;
};
export type SyntheticUnderlyingState = {
mid: number;
bid: number;
ask: number;
spread: number;
driftBps: number;
shockBps: number;
sessionVolatility: number;
liquiditySkew: number;
quoteCleanliness: number;
clusteringScore: number;
offExchangeBias: number;
};
export type SyntheticScenarioWeightMap = Record<
SyntheticScenarioFamilyId,
number
>;
export type SyntheticCoverageState = {
profile_hit_counts: Record<SmartMoneyProfileId, number>;
};
export type SyntheticBurstPulse = {
active: boolean;
intensity: number;
focusSymbols: string[];
bucket: number;
};
const DEFAULT_PROFILE_WEIGHTS: SyntheticProfileWeightMap = {
institutional_directional: 1.0,
retail_whale: 1.0,
event_driven: 1.0,
vol_seller: 1.0,
arbitrage: 1.0,
hedge_reactive: 1.0
};
export const DEFAULT_SYNTHETIC_CONTROL_STATE: SyntheticControlState = {
preset_id: "balanced_demo",
coverage_assist: true,
coverage_window_minutes: 20,
shared_seed: 11,
profile_weights: DEFAULT_PROFILE_WEIGHTS,
updated_at: 0,
updated_by: "system"
};
const PRESET_REGIME_BIAS: Record<
SyntheticControlPresetId,
Record<SyntheticRegime, number>
> = {
balanced_demo: {
trend_up: 1.0,
trend_down: 0.95,
mean_revert: 1.05,
retail_chase: 0.95,
event_ramp: 0.85,
dealer_gamma: 0.95,
arb_calm: 0.95
},
event_day: {
trend_up: 0.9,
trend_down: 0.9,
mean_revert: 0.75,
retail_chase: 0.95,
event_ramp: 1.9,
dealer_gamma: 1.0,
arb_calm: 0.55
},
dealer_day: {
trend_up: 0.85,
trend_down: 0.85,
mean_revert: 0.9,
retail_chase: 0.85,
event_ramp: 0.7,
dealer_gamma: 1.95,
arb_calm: 0.8
},
retail_chase: {
trend_up: 1.1,
trend_down: 0.7,
mean_revert: 0.6,
retail_chase: 2.0,
event_ramp: 0.95,
dealer_gamma: 0.95,
arb_calm: 0.45
},
quiet_range: {
trend_up: 0.7,
trend_down: 0.7,
mean_revert: 1.35,
retail_chase: 0.45,
event_ramp: 0.5,
dealer_gamma: 0.75,
arb_calm: 1.8
}
};
const PRESET_ACTIVITY_BIAS: Record<
SyntheticControlPresetId,
{ focusCount: number; eventCount: number; amplitude: number }
> = {
balanced_demo: { focusCount: 3, eventCount: 2, amplitude: 1.0 },
event_day: { focusCount: 4, eventCount: 3, amplitude: 1.28 },
dealer_day: { focusCount: 3, eventCount: 1, amplitude: 1.12 },
retail_chase: { focusCount: 4, eventCount: 1, amplitude: 1.25 },
quiet_range: { focusCount: 2, eventCount: 1, amplitude: 0.72 }
};
const REGIME_PROFILE_BIAS: Record<
SyntheticRegime,
SyntheticScenarioWeightMap
> = {
trend_up: {
institutional_directional: 1.35,
retail_whale: 1.05,
event_driven: 0.9,
vol_seller: 0.78,
arbitrage: 0.72,
hedge_reactive: 0.82,
neutral_noise: 0.82
},
trend_down: {
institutional_directional: 1.2,
retail_whale: 0.82,
event_driven: 0.88,
vol_seller: 0.8,
arbitrage: 0.78,
hedge_reactive: 1.22,
neutral_noise: 0.85
},
mean_revert: {
institutional_directional: 0.92,
retail_whale: 0.78,
event_driven: 0.8,
vol_seller: 1.18,
arbitrage: 1.28,
hedge_reactive: 0.92,
neutral_noise: 1.2
},
retail_chase: {
institutional_directional: 1.04,
retail_whale: 1.72,
event_driven: 0.9,
vol_seller: 0.7,
arbitrage: 0.58,
hedge_reactive: 0.98,
neutral_noise: 0.72
},
event_ramp: {
institutional_directional: 1.08,
retail_whale: 0.96,
event_driven: 1.95,
vol_seller: 0.74,
arbitrage: 0.62,
hedge_reactive: 1.04,
neutral_noise: 0.58
},
dealer_gamma: {
institutional_directional: 0.94,
retail_whale: 1.02,
event_driven: 0.78,
vol_seller: 0.84,
arbitrage: 0.92,
hedge_reactive: 1.74,
neutral_noise: 0.76
},
arb_calm: {
institutional_directional: 0.68,
retail_whale: 0.58,
event_driven: 0.62,
vol_seller: 1.28,
arbitrage: 1.78,
hedge_reactive: 0.72,
neutral_noise: 1.34
}
};
const REGIME_STATE_BASE: Record<
SyntheticRegime,
{
volatility: number;
liquidity: number;
quoteCleanliness: number;
offExchangeBias: number;
}
> = {
trend_up: {
volatility: 0.72,
liquidity: 0.72,
quoteCleanliness: 0.64,
offExchangeBias: 0.46
},
trend_down: {
volatility: 0.78,
liquidity: 0.66,
quoteCleanliness: 0.58,
offExchangeBias: 0.52
},
mean_revert: {
volatility: 0.5,
liquidity: 0.84,
quoteCleanliness: 0.8,
offExchangeBias: 0.34
},
retail_chase: {
volatility: 0.88,
liquidity: 0.62,
quoteCleanliness: 0.5,
offExchangeBias: 0.58
},
event_ramp: {
volatility: 0.92,
liquidity: 0.56,
quoteCleanliness: 0.42,
offExchangeBias: 0.54
},
dealer_gamma: {
volatility: 0.82,
liquidity: 0.66,
quoteCleanliness: 0.48,
offExchangeBias: 0.5
},
arb_calm: {
volatility: 0.34,
liquidity: 0.9,
quoteCleanliness: 0.88,
offExchangeBias: 0.3
}
};
const clamp = (value: number, min: number, max: number): number => {
if (!Number.isFinite(value)) {
return min;
}
return Math.max(min, Math.min(max, value));
};
const roundTo = (value: number, digits = 4): number => {
if (!Number.isFinite(value)) {
return 0;
}
return Number(value.toFixed(digits));
};
const signedNoise = (seed: number): number => {
const raw = Math.sin(seed * 12.9898) * 43_758.5453;
return (raw - Math.floor(raw)) * 2 - 1;
};
const positiveNoise = (seed: number): number => {
return (signedNoise(seed) + 1) / 2;
};
const mixSeed = (...parts: number[]): number => {
let seed = 0x811c9dc5;
for (const part of parts) {
seed ^= Math.floor(part) >>> 0;
seed = Math.imul(seed, 0x01000193) >>> 0;
}
return seed >>> 0;
};
const pick = <T,>(items: readonly T[], seed: number): T => {
const index = Math.abs(seed) % items.length;
return items[index]!;
};
const pickManyUnique = <T,>(
items: readonly T[],
count: number,
seed: number
): T[] => {
const pool = [...items];
const output: T[] = [];
let cursor = seed;
while (pool.length > 0 && output.length < count) {
const index = Math.abs(cursor) % pool.length;
output.push(pool.splice(index, 1)[0]!);
cursor = mixSeed(cursor, output.length * 17 + 3);
}
return output;
};
const weightedPick = <T extends string>(
weights: Record<T, number>,
seed: number
): T => {
const entries = Object.entries(weights) as Array<[T, number]>;
const total = entries.reduce((sum, [, weight]) => sum + Math.max(0.0001, weight), 0);
let target = positiveNoise(seed) * total;
for (const [value, weight] of entries) {
target -= Math.max(0.0001, weight);
if (target <= 0) {
return value;
}
}
return entries[entries.length - 1]![0];
};
const getSessionMinute = (ts: number): number => {
const minute = Math.floor(ts / 60_000);
return ((minute % 390) + 390) % 390;
};
export const hashSyntheticSymbol = (value: string): number => {
let hash = 0;
for (let i = 0; i < value.length; i += 1) {
hash = (hash * 31 + value.charCodeAt(i)) >>> 0;
}
return hash;
};
export const buildEmptySyntheticProfileHitCounts = (): Record<
SmartMoneyProfileId,
number
> => ({
institutional_directional: 0,
retail_whale: 0,
event_driven: 0,
vol_seller: 0,
arbitrage: 0,
hedge_reactive: 0
});
export const normalizeSyntheticControlState = (
control: Partial<SyntheticControlState> | null | undefined
): SyntheticControlState => {
const merged: SyntheticControlState = {
...DEFAULT_SYNTHETIC_CONTROL_STATE,
...control,
profile_weights: {
...DEFAULT_SYNTHETIC_CONTROL_STATE.profile_weights,
...(control?.profile_weights ?? {})
}
};
return SyntheticControlStateSchema.parse(merged);
};
const resolvePhaseBias = (
phase: SyntheticSessionPhase,
regime: SyntheticRegime
): number => {
if (phase === "open") {
return regime === "event_ramp" ? 1.08 : 1.02;
}
if (phase === "power_hour") {
return regime === "retail_chase" || regime === "dealer_gamma" ? 1.08 : 1.03;
}
if (phase === "after_event") {
return regime === "event_ramp" ? 1.24 : 1.0;
}
return 1.0;
};
const resolveSessionPhase = (
minuteOfSession: number,
eventActive: boolean,
eventOffset: number
): SyntheticSessionPhase => {
if (eventActive && eventOffset > 0.58) {
return "after_event";
}
if (minuteOfSession < 60) {
return "open";
}
if (minuteOfSession >= 300) {
return "power_hour";
}
return "midday";
};
export const getSyntheticSessionState = (
ts: number,
control: Partial<SyntheticControlState> | null | undefined = DEFAULT_SYNTHETIC_CONTROL_STATE
): SyntheticSessionState => {
const normalized = normalizeSyntheticControlState(control);
const minuteOfSession = getSessionMinute(ts);
const bucketMs = 5 * 60_000;
const seedBucket = Math.floor(ts / bucketMs);
const presetBias = PRESET_REGIME_BIAS[normalized.preset_id];
const eventSeed = mixSeed(normalized.shared_seed, seedBucket, normalized.updated_at);
const eventBucketOffset = positiveNoise(eventSeed + 41);
const eventActive =
normalized.preset_id === "event_day" ||
eventBucketOffset > (normalized.preset_id === "balanced_demo" ? 0.72 : 0.6);
const prePhase = resolveSessionPhase(minuteOfSession, eventActive, eventBucketOffset);
const regimeWeights = REGIME_IDS.reduce(
(acc, regime) => {
const drift = 0.82 + positiveNoise(mixSeed(eventSeed, regime.length * 29)) * 0.38;
acc[regime] = presetBias[regime] * drift * resolvePhaseBias(prePhase, regime);
return acc;
},
{} as Record<SyntheticRegime, number>
);
const regime = weightedPick(regimeWeights, mixSeed(eventSeed, 97));
const phase = resolveSessionPhase(
minuteOfSession,
eventActive || regime === "event_ramp",
eventBucketOffset
);
const presetActivity = PRESET_ACTIVITY_BIAS[normalized.preset_id];
const stateBase = REGIME_STATE_BASE[regime];
const activitySeed = mixSeed(eventSeed, minuteOfSession, regime.length * 13);
const eventCount =
regime === "event_ramp" || phase === "after_event"
? Math.max(2, presetActivity.eventCount)
: presetActivity.eventCount;
const focusCount =
regime === "retail_chase" || regime === "event_ramp"
? presetActivity.focusCount + 1
: presetActivity.focusCount;
const event_symbols: string[] = pickManyUnique(
EVENT_SYMBOL_POOL,
eventCount,
mixSeed(activitySeed, 211)
);
const focus_symbols: string[] = pickManyUnique(
[
...event_symbols,
...SYNTHETIC_SYMBOLS.filter((symbol) => !event_symbols.includes(symbol))
],
focusCount,
mixSeed(activitySeed, 389)
);
const amplitude = presetActivity.amplitude;
return {
session_phase: phase,
regime,
volatility_level: roundTo(
clamp(
stateBase.volatility * amplitude + signedNoise(activitySeed + 3) * 0.08,
0.18,
1.2
)
),
liquidity_level: roundTo(
clamp(
stateBase.liquidity - (amplitude - 1) * 0.08 + signedNoise(activitySeed + 5) * 0.06,
0.2,
1.1
)
),
quote_cleanliness: roundTo(
clamp(
stateBase.quoteCleanliness - (amplitude - 1) * 0.1 + signedNoise(activitySeed + 7) * 0.06,
0.18,
0.96
)
),
focus_symbols,
event_symbols,
seed_bucket: seedBucket
};
};
const isModeString = (
value: Partial<SyntheticControlState> | SyntheticMarketMode | null | undefined
): value is SyntheticMarketMode => {
return value === "realistic" || value === "active" || value === "firehose";
};
export const getSyntheticUnderlyingState = (
symbol: string,
ts: number,
controlOrMode:
| Partial<SyntheticControlState>
| SyntheticMarketMode
| null
| undefined = DEFAULT_SYNTHETIC_CONTROL_STATE,
sessionState?: SyntheticSessionState
): SyntheticUnderlyingState => {
const control = isModeString(controlOrMode)
? DEFAULT_SYNTHETIC_CONTROL_STATE
: normalizeSyntheticControlState(controlOrMode);
const session = sessionState ?? getSyntheticSessionState(ts, control);
const hash = hashSyntheticSymbol(symbol);
const minuteOfSession = getSessionMinute(ts);
const base = 25 + (hash % 475);
const isFocus = session.focus_symbols.includes(symbol);
const isEvent = session.event_symbols.includes(symbol);
const regimeDirection =
session.regime === "trend_up" || session.regime === "retail_chase"
? 1
: session.regime === "trend_down"
? -1
: 0;
const trendWave =
Math.sin((minuteOfSession + (hash % 71) + session.seed_bucket) / 29) * 0.55 +
Math.cos((minuteOfSession + (hash % 37) + session.seed_bucket) / 17) * 0.28;
const meanRevertWave =
Math.sin((minuteOfSession + (hash % 19)) / 6) * 0.42 -
Math.sin((minuteOfSession + (hash % 13)) / 19) * 0.24;
const eventDrift =
isEvent && (session.regime === "event_ramp" || session.session_phase === "after_event")
? 1.25
: 0;
const focusBoost = isFocus ? 1.18 : 0.92;
const directionBps =
regimeDirection * (14 + session.volatility_level * 36) * focusBoost +
trendWave * 22 * focusBoost +
eventDrift * 18;
const reversionBps =
session.regime === "mean_revert" || session.regime === "arb_calm"
? -meanRevertWave * (12 + session.liquidity_level * 10)
: meanRevertWave * 6;
const gammaChop =
session.regime === "dealer_gamma"
? Math.sin((minuteOfSession + (hash % 11)) / 2.8) * 16
: 0;
const noiseBps =
signedNoise(mixSeed(hash, session.seed_bucket, control.shared_seed)) *
(6 + session.volatility_level * 18);
const driftBps = directionBps + reversionBps + gammaChop;
const shockBps = noiseBps + (isFocus ? signedNoise(hash + minuteOfSession) * 6 : 0);
const totalBps = driftBps + shockBps;
const mid = Math.max(0.01, Number((base * (1 + totalBps / 10_000)).toFixed(2)));
const spreadBps =
4 +
session.volatility_level * 14 +
(1 - session.liquidity_level) * 10 +
(1 - session.quote_cleanliness) * 12 +
(session.session_phase === "open" ? 3 : 0) +
(session.session_phase === "power_hour" ? 2 : 0);
const spread = Math.max(0.01, Number((mid * (spreadBps / 10_000)).toFixed(2)));
const halfSpread = spread / 2;
const bid = Number(Math.max(0.01, mid - halfSpread).toFixed(2));
const ask = Number(Math.max(bid + 0.01, mid + halfSpread).toFixed(2));
const clusteringScore = clamp(
(isFocus ? 0.34 : 0.12) +
(session.regime === "dealer_gamma" ? 0.28 : 0) +
(session.regime === "retail_chase" ? 0.16 : 0),
0,
1
);
return {
mid,
bid,
ask,
spread: Number((ask - bid).toFixed(2)),
driftBps: roundTo(driftBps),
shockBps: roundTo(shockBps),
sessionVolatility: roundTo(session.volatility_level),
liquiditySkew: roundTo(session.liquidity_level),
quoteCleanliness: roundTo(session.quote_cleanliness),
clusteringScore: roundTo(clusteringScore),
offExchangeBias: roundTo(
clamp(
REGIME_STATE_BASE[session.regime].offExchangeBias +
(isFocus ? 0.08 : 0) +
(isEvent ? 0.05 : 0),
0.08,
0.92
)
)
};
};
export const getSyntheticScenarioWeights = (
symbol: string,
ts: number,
control: Partial<SyntheticControlState> | null | undefined = DEFAULT_SYNTHETIC_CONTROL_STATE,
sessionState?: SyntheticSessionState
): SyntheticScenarioWeightMap => {
const normalized = normalizeSyntheticControlState(control);
const session = sessionState ?? getSyntheticSessionState(ts, normalized);
const base = REGIME_PROFILE_BIAS[session.regime];
const isFocus = session.focus_symbols.includes(symbol);
const isEvent = session.event_symbols.includes(symbol);
const isPower = session.session_phase === "open" || session.session_phase === "power_hour";
const weights: SyntheticScenarioWeightMap = {
institutional_directional: base.institutional_directional,
retail_whale: base.retail_whale,
event_driven: base.event_driven,
vol_seller: base.vol_seller,
arbitrage: base.arbitrage,
hedge_reactive: base.hedge_reactive,
neutral_noise: base.neutral_noise
};
for (const profileId of SMART_MONEY_PROFILE_IDS) {
weights[profileId] = roundTo(
weights[profileId] * normalized.profile_weights[profileId],
4
);
}
if (isFocus) {
weights.institutional_directional = roundTo(weights.institutional_directional * 1.08, 4);
weights.retail_whale = roundTo(weights.retail_whale * 1.14, 4);
weights.hedge_reactive = roundTo(weights.hedge_reactive * 1.08, 4);
weights.neutral_noise = roundTo(weights.neutral_noise * 0.92, 4);
}
if (isEvent) {
weights.event_driven = roundTo(weights.event_driven * 1.36, 4);
weights.institutional_directional = roundTo(
weights.institutional_directional * 1.04,
4
);
weights.neutral_noise = roundTo(weights.neutral_noise * 0.8, 4);
}
if (isPower) {
weights.retail_whale = roundTo(weights.retail_whale * 1.08, 4);
weights.hedge_reactive = roundTo(weights.hedge_reactive * 1.06, 4);
}
if (normalized.preset_id === "quiet_range") {
weights.neutral_noise = roundTo(weights.neutral_noise * 1.18, 4);
}
return weights;
};
export const getSyntheticCoverageBoost = (
profileId: SmartMoneyProfileId,
coverageState: SyntheticCoverageState,
control: Pick<
SyntheticControlState,
"coverage_assist" | "coverage_window_minutes"
>
): number => {
if (!control.coverage_assist) {
return 1;
}
const counts = SMART_MONEY_PROFILE_IDS.map(
(candidate) => coverageState.profile_hit_counts[candidate] ?? 0
);
const targetCount = coverageState.profile_hit_counts[profileId] ?? 0;
const maxCount = Math.max(...counts);
const averageCount =
counts.reduce((sum, value) => sum + value, 0) / SMART_MONEY_PROFILE_IDS.length;
if (maxCount <= 0) {
return 1;
}
const imbalance = clamp((maxCount - targetCount) / Math.max(1, maxCount), 0, 1);
const averageDebt = clamp(averageCount - targetCount, 0, 3);
const zeroBoost = targetCount === 0 ? 0.22 : 0;
const windowFactor =
control.coverage_window_minutes === 10
? 1.12
: control.coverage_window_minutes === 30
? 0.94
: 1.0;
return roundTo(
clamp(1 + (imbalance * 0.56 + averageDebt * 0.14 + zeroBoost) * windowFactor, 1, 1.86)
);
};
export const getSyntheticBurstPulse = (
ts: number,
controlOrMode:
| Partial<SyntheticControlState>
| SyntheticMarketMode
| null
| undefined = DEFAULT_SYNTHETIC_CONTROL_STATE
): SyntheticBurstPulse => {
const control = isModeString(controlOrMode)
? DEFAULT_SYNTHETIC_CONTROL_STATE
: normalizeSyntheticControlState(controlOrMode);
const session = getSyntheticSessionState(ts, control);
return {
active: session.regime !== "arb_calm" || session.focus_symbols.length > 1,
intensity: roundTo(
clamp(
session.volatility_level * 0.72 +
session.focus_symbols.length * 0.06 -
session.quote_cleanliness * 0.08,
0.12,
1
)
),
focusSymbols: [...session.focus_symbols],
bucket: session.seed_bucket
};
};
export const SYNTHETIC_CONTROL_METADATA = {
profileWeightValues: SYNTHETIC_PROFILE_WEIGHT_VALUES,
coverageWindowValues: SYNTHETIC_COVERAGE_WINDOW_VALUES,
smartMoneyProfileIds: SMART_MONEY_PROFILE_IDS
} as const;

View file

@ -0,0 +1,104 @@
import { describe, expect, it } from "bun:test";
import {
DEFAULT_SYNTHETIC_CONTROL_STATE,
buildEmptySyntheticProfileHitCounts,
getSyntheticCoverageBoost,
getSyntheticScenarioWeights,
getSyntheticSessionState,
getSyntheticUnderlyingState
} from "../src/synthetic-market";
describe("synthetic market regime engine", () => {
it("is deterministic for the same timestamp, control, and seed", () => {
const ts = Date.parse("2026-01-14T15:25:00Z");
const sessionA = getSyntheticSessionState(ts, DEFAULT_SYNTHETIC_CONTROL_STATE);
const sessionB = getSyntheticSessionState(ts, DEFAULT_SYNTHETIC_CONTROL_STATE);
const underlyingA = getSyntheticUnderlyingState(
"NVDA",
ts,
DEFAULT_SYNTHETIC_CONTROL_STATE,
sessionA
);
const underlyingB = getSyntheticUnderlyingState(
"NVDA",
ts,
DEFAULT_SYNTHETIC_CONTROL_STATE,
sessionB
);
expect(sessionA).toEqual(sessionB);
expect(underlyingA).toEqual(underlyingB);
});
it("makes quiet range calmer than retail chase", () => {
const ts = Date.parse("2026-01-14T17:10:00Z");
const quietControl = {
...DEFAULT_SYNTHETIC_CONTROL_STATE,
preset_id: "quiet_range" as const
};
const chaseControl = {
...DEFAULT_SYNTHETIC_CONTROL_STATE,
preset_id: "retail_chase" as const
};
const quietSession = getSyntheticSessionState(ts, quietControl);
const chaseSession = getSyntheticSessionState(ts, chaseControl);
const quietState = getSyntheticUnderlyingState("AAPL", ts, quietControl, quietSession);
const chaseState = getSyntheticUnderlyingState("AAPL", ts, chaseControl, chaseSession);
expect(quietSession.volatility_level).toBeLessThan(chaseSession.volatility_level);
expect(quietState.spread).toBeLessThanOrEqual(chaseState.spread);
expect(quietState.sessionVolatility).toBeLessThan(chaseState.sessionVolatility);
});
it("materially tilts family weights by preset and regime", () => {
const ts = Date.parse("2026-01-14T19:40:00Z");
const eventControl = {
...DEFAULT_SYNTHETIC_CONTROL_STATE,
preset_id: "event_day" as const
};
const quietControl = {
...DEFAULT_SYNTHETIC_CONTROL_STATE,
preset_id: "quiet_range" as const
};
const eventSession = getSyntheticSessionState(ts, eventControl);
const quietSession = getSyntheticSessionState(ts, quietControl);
const eventWeights = getSyntheticScenarioWeights("AAPL", ts, eventControl, eventSession);
const quietWeights = getSyntheticScenarioWeights("AAPL", ts, quietControl, quietSession);
expect(eventWeights.event_driven).toBeGreaterThan(quietWeights.event_driven);
expect(quietWeights.neutral_noise).toBeGreaterThan(eventWeights.neutral_noise);
});
});
describe("synthetic coverage assist", () => {
it("boosts under-hit profiles without forcing when enabled", () => {
const counts = buildEmptySyntheticProfileHitCounts();
counts.institutional_directional = 3;
counts.arbitrage = 2;
const boost = getSyntheticCoverageBoost(
"event_driven",
{ profile_hit_counts: counts },
DEFAULT_SYNTHETIC_CONTROL_STATE
);
expect(boost).toBeGreaterThan(1);
expect(boost).toBeLessThanOrEqual(1.86);
});
it("returns neutral boost when coverage assist is disabled", () => {
const counts = buildEmptySyntheticProfileHitCounts();
counts.institutional_directional = 4;
expect(
getSyntheticCoverageBoost(
"event_driven",
{ profile_hit_counts: counts },
{
coverage_assist: false,
coverage_window_minutes: 20
}
)
).toBe(1);
});
});

View file

@ -0,0 +1,816 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hosted Synthetic Tape Redesign · Impeccable Version</title>
<style>
:root {
color-scheme: dark;
--bg: #0b0f13;
--bg-soft: #11161d;
--surface: rgba(14, 20, 27, 0.86);
--surface-strong: rgba(17, 24, 32, 0.96);
--line: rgba(151, 168, 186, 0.18);
--line-strong: rgba(151, 168, 186, 0.32);
--text: #ebf0f6;
--muted: #a0adbb;
--sage: #7cccb2;
--teal: #59b7c9;
--gold: #d5b26e;
--rose: #c78497;
--ink: #0a0d11;
--shadow: 0 24px 80px rgba(0, 0, 0, 0.34);
--radius-xl: 28px;
--radius-lg: 18px;
--radius-md: 14px;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
font-family: "SF Pro Text", "Inter", "Segoe UI", sans-serif;
background:
radial-gradient(circle at 14% 12%, rgba(124, 204, 178, 0.12), transparent 24rem),
radial-gradient(circle at 86% 8%, rgba(89, 183, 201, 0.13), transparent 28rem),
linear-gradient(180deg, #091017, var(--bg) 32%, #0d1117 100%);
color: var(--text);
}
.shell {
width: min(1220px, calc(100vw - 1.5rem));
margin: 0 auto;
padding: 1.25rem 0 4rem;
}
.hero {
position: relative;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 36px;
background:
linear-gradient(135deg, rgba(124, 204, 178, 0.1), transparent 26%),
linear-gradient(180deg, rgba(15, 21, 29, 0.95), rgba(10, 14, 19, 0.92));
box-shadow: var(--shadow);
padding: 2rem;
}
.hero::after {
content: "";
position: absolute;
inset: auto -6rem -7rem auto;
width: 18rem;
height: 18rem;
border-radius: 50%;
background: radial-gradient(circle, rgba(89, 183, 201, 0.18), transparent 70%);
pointer-events: none;
}
.hero-top {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
margin-bottom: 1rem;
}
.pill {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.42rem 0.82rem;
border-radius: 999px;
border: 1px solid rgba(124, 204, 178, 0.22);
background: rgba(124, 204, 178, 0.08);
color: var(--sage);
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hero-grid {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.9fr);
gap: 1.25rem;
}
h1,
h2,
h3 {
margin: 0 0 0.7rem;
line-height: 1.05;
}
h1 {
max-width: 11ch;
font-size: clamp(2.4rem, 7vw, 4.6rem);
letter-spacing: -0.06em;
text-wrap: balance;
}
h2 {
font-size: 1.35rem;
letter-spacing: -0.03em;
}
h3 {
font-size: 0.98rem;
text-transform: uppercase;
letter-spacing: 0.09em;
color: var(--sage);
}
p,
li {
max-width: 74ch;
color: var(--text);
line-height: 1.6;
}
.lede {
font-size: 1.12rem;
color: #d8e1ea;
}
.hero-notes {
display: grid;
gap: 0.8rem;
align-content: start;
}
.note {
background: rgba(16, 23, 31, 0.72);
border: 1px solid var(--line);
border-radius: var(--radius-lg);
padding: 1rem;
}
.note strong {
display: block;
margin-bottom: 0.32rem;
color: #f7fbff;
}
.deck {
display: grid;
grid-template-columns: 260px minmax(0, 1fr);
gap: 1rem;
align-items: start;
margin-top: 1rem;
}
nav {
position: sticky;
top: 1rem;
border: 1px solid var(--line);
border-radius: 24px;
background: rgba(13, 19, 25, 0.86);
box-shadow: var(--shadow);
padding: 1rem;
}
nav h2 {
font-size: 0.95rem;
text-transform: uppercase;
letter-spacing: 0.09em;
color: var(--muted);
}
nav ol {
list-style: none;
padding: 0;
margin: 0;
}
nav li + li {
margin-top: 0.5rem;
}
nav a {
color: #d5e3ee;
text-decoration: none;
}
nav a:hover {
color: var(--sage);
}
article {
display: grid;
gap: 1rem;
}
section {
border: 1px solid var(--line);
border-radius: var(--radius-xl);
background: var(--surface);
box-shadow: var(--shadow);
padding: 1.4rem;
}
.overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 0.85rem;
}
.metric {
padding: 1rem;
border-radius: var(--radius-lg);
border: 1px solid var(--line);
background:
linear-gradient(180deg, rgba(124, 204, 178, 0.06), transparent 60%),
rgba(18, 24, 32, 0.76);
}
.metric-label {
display: block;
margin-bottom: 0.4rem;
color: var(--muted);
text-transform: uppercase;
font-size: 0.76rem;
letter-spacing: 0.09em;
}
.metric strong {
display: block;
margin-bottom: 0.35rem;
font-size: 1rem;
}
.split {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.9rem;
}
.panel {
border: 1px solid var(--line);
border-radius: var(--radius-lg);
background: var(--surface-strong);
padding: 1rem;
}
ul,
ol {
padding-left: 1.15rem;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 0.75rem;
}
th,
td {
padding: 0.85rem 0.65rem;
border-bottom: 1px solid var(--line);
text-align: left;
vertical-align: top;
}
th {
color: var(--teal);
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.09em;
}
pre {
overflow-x: auto;
margin: 0.85rem 0 0;
padding: 1rem;
border-radius: var(--radius-lg);
border: 1px solid var(--line);
background: var(--ink);
color: #dce7f0;
}
code,
pre {
font-family: "SF Mono", "JetBrains Mono", "Cascadia Code", monospace;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 0.55rem;
}
.chip {
display: inline-flex;
padding: 0.45rem 0.72rem;
border-radius: 999px;
border: 1px solid var(--line);
background: rgba(17, 24, 32, 0.9);
color: #dae4ec;
font-size: 0.86rem;
}
.accent-sage {
color: var(--sage);
}
.accent-gold {
color: var(--gold);
}
.accent-rose {
color: var(--rose);
}
.subdued {
color: var(--muted);
}
@media (max-width: 900px) {
.hero-grid,
.deck,
.split {
grid-template-columns: 1fr;
}
nav {
position: static;
}
h1 {
max-width: none;
}
}
</style>
</head>
<body>
<div class="shell">
<header class="hero">
<div class="hero-top">
<span class="pill">Plan · Impeccable HTML Version</span>
<span class="pill">Internal Control Surface</span>
<span class="pill">Hosted Synthetic Backend</span>
</div>
<div class="hero-grid">
<div>
<h1>Make the tape feel alive, not scheduled.</h1>
<p class="lede">
The hosted synthetic market already reaches the smart-money categories, but it often reaches them too cleanly. This redesign makes the demo feel more like a market session: one shared regime drives options, equities, quotes, event context, and coverage pressure, while operators keep a compact internal handle on the simulator through a bottom-right gear.
</p>
<div class="chips">
<span class="chip">Public smart-money taxonomy stays stable</span>
<span class="chip">Cross-asset coupling is the first priority</span>
<span class="chip">Internal-only controls, not a public settings page</span>
</div>
</div>
<div class="hero-notes">
<div class="note">
<strong>What changes for a viewer</strong>
The tape looks less templated, more coherent, and more educational because the surrounding market conditions finally support the category hits.
</div>
<div class="note">
<strong>What changes for an operator</strong>
A small bottom-right gear opens a non-modal synthetic-control drawer for the hosted backend.
</div>
<div class="note">
<strong>What does not change</strong>
Existing public smart-money event types, endpoints, and surface labels remain intact.
</div>
</div>
</div>
</header>
<div class="deck">
<nav>
<h2>Contents</h2>
<ol>
<li><a href="#overview">Simplified overview</a></li>
<li><a href="#scope">Scope</a></li>
<li><a href="#affected">Services affected</a></li>
<li><a href="#locked">Locked decisions</a></li>
<li><a href="#architecture">Full architecture</a></li>
<li><a href="#interfaces">Interfaces and env</a></li>
<li><a href="#sequence">Implementation phases</a></li>
<li><a href="#tests">Tests</a></li>
<li><a href="#assumptions">Assumptions</a></li>
</ol>
</nav>
<article>
<section id="overview">
<h2>Simplified Overview</h2>
<p>
The current synthetic system is strong at coverage and weaker at realism. It can produce the categories, but the tape often reveals the machinery: option bursts appear on a rhythm, quotes are too consistently clean, equities and options are only loosely related, and the market context around a labeled event is not convincing enough.
</p>
<p>
The redesign introduces a shared market regime engine. Instead of “emit a category-shaped burst now,” the system will model a believable session state first, then let both options and equities express that state. That keeps the smart-money demo behavior while making the experience feel more grounded.
</p>
<div class="overview">
<div class="metric">
<span class="metric-label">Audience</span>
<strong>Internal operators and demo owners</strong>
<span class="subdued">This is about making hosted synthetic sessions more convincing during demos and product evaluation.</span>
</div>
<div class="metric">
<span class="metric-label">Primary outcome</span>
<strong>Higher realism with preserved category coverage</strong>
<span class="subdued">The demo should still surface every smart-money category, but not in a visibly scripted way.</span>
</div>
<div class="metric">
<span class="metric-label">UI entry point</span>
<strong>Bottom-right gear, not a settings page</strong>
<span class="subdued">The operator control surface should stay compact and contextual.</span>
</div>
<div class="metric">
<span class="metric-label">Compatibility</span>
<strong>Public contracts remain stable</strong>
<span class="subdued">No public API break for smart-money consumers.</span>
</div>
</div>
</section>
<section id="scope">
<h2>Scope</h2>
<div class="split">
<div class="panel">
<h3>In scope</h3>
<ul>
<li>Hosted synthetic regime engine</li>
<li>Options and equities generator redesign</li>
<li>Hidden subtype scenario families</li>
<li>Soft coverage logic</li>
<li>Internal control API</li>
<li>Internal control drawer in the terminal shell</li>
<li>Regression and realism tests</li>
</ul>
</div>
<div class="panel">
<h3>Out of scope</h3>
<ul>
<li>Changing public smart-money categories</li>
<li>General settings-page work</li>
<li>User profile or token-spend UI</li>
<li>Public simulator controls</li>
<li>Live-feed product changes</li>
</ul>
</div>
</div>
</section>
<section id="affected">
<h2>Services Affected</h2>
<table>
<thead>
<tr>
<th>Area</th>
<th>Files</th>
<th>Why they change</th>
</tr>
</thead>
<tbody>
<tr>
<td>Shared types and regime model</td>
<td><code>packages/types/src/synthetic-market.ts</code>, <code>packages/types/src/events.ts</code></td>
<td>Introduce the control-state model and the deterministic shared market regime.</td>
</tr>
<tr>
<td>Hosted API</td>
<td><code>services/api/src/index.ts</code></td>
<td>Add internal synthetic-control status and mutation endpoints.</td>
</tr>
<tr>
<td>Options ingest</td>
<td><code>services/ingest-options/src/index.ts</code>, <code>services/ingest-options/src/adapters/synthetic.ts</code></td>
<td>Swap burst scheduling for regime-driven scenario selection and coverage debt.</td>
</tr>
<tr>
<td>Equities ingest</td>
<td><code>services/ingest-equities/src/index.ts</code>, <code>services/ingest-equities/src/adapters/synthetic.ts</code></td>
<td>Make equity prints and quotes react to the same latent regime as the options side.</td>
</tr>
<tr>
<td>Web and Electron shell</td>
<td><code>apps/web/app/terminal.tsx</code>, <code>apps/web/app/api/admin/synthetic/*</code></td>
<td>Add the internal-only gear trigger, drawer, and secure proxy layer.</td>
</tr>
<tr>
<td>Tests</td>
<td>Options tests, API tests, web tests</td>
<td>Protect determinism, realism, UI visibility rules, and classification alignment.</td>
</tr>
</tbody>
</table>
</section>
<section id="locked">
<h2>Locked Decisions</h2>
<div class="chips">
<span class="chip">Keep the six public smart-money categories</span>
<span class="chip">Add hidden subtype families</span>
<span class="chip">Use soft coverage guarantees</span>
<span class="chip">Prioritize cross-asset coupling first</span>
<span class="chip">Target the hosted synthetic backend</span>
<span class="chip">Internal-only control surface</span>
<span class="chip">No general settings page in this effort</span>
<span class="chip">Bottom-right gear opens a drawer</span>
</div>
</section>
<section id="architecture">
<h2>Full Architecture</h2>
<h3 class="accent-sage">1. Replace the burst pulse with a shared regime engine</h3>
<p>Expand <code>packages/types/src/synthetic-market.ts</code> into the shared deterministic engine used by both ingest services.</p>
<p>Shared functions:</p>
<ul>
<li><code>getSyntheticSessionState(ts, control)</code></li>
<li><code>getSyntheticUnderlyingState(symbol, ts, control, sessionState)</code></li>
<li><code>getSyntheticScenarioWeights(symbol, ts, control, sessionState)</code></li>
<li><code>getSyntheticCoverageBoost(profileId, coverageState, control)</code></li>
</ul>
<p><code>sessionState</code> includes:</p>
<ul>
<li><code>session_phase</code>: <code>open | midday | power_hour | after_event</code></li>
<li><code>regime</code>: <code>trend_up | trend_down | mean_revert | retail_chase | event_ramp | dealer_gamma | arb_calm</code></li>
<li><code>volatility_level</code></li>
<li><code>liquidity_level</code></li>
<li><code>quote_cleanliness</code></li>
<li><code>focus_symbols</code></li>
<li><code>event_symbols</code></li>
<li><code>seed_bucket</code></li>
</ul>
<h3 class="accent-teal">2. Add hosted synthetic control state</h3>
<p>Add internal control schemas in <code>packages/types</code>:</p>
<ul>
<li><code>SyntheticControlPresetId</code></li>
<li><code>SyntheticControlState</code></li>
<li><code>SyntheticProfileWeightMap</code></li>
<li><code>SyntheticCoverageConfig</code></li>
<li><code>SyntheticDerivedStatus</code></li>
</ul>
<pre><code>type SyntheticControlState = {
preset_id: "balanced_demo" | "event_day" | "dealer_day" | "retail_chase" | "quiet_range";
coverage_assist: boolean;
coverage_window_minutes: 10 | 20 | 30;
shared_seed: number;
profile_weights: {
institutional_directional: 0.6 | 1.0 | 1.6;
retail_whale: 0.6 | 1.0 | 1.6;
event_driven: 0.6 | 1.0 | 1.6;
vol_seller: 0.6 | 1.0 | 1.6;
arbitrage: 0.6 | 1.0 | 1.6;
hedge_reactive: 0.6 | 1.0 | 1.6;
};
updated_at: number;
updated_by: string;
};</code></pre>
<p>Defaults: <code>preset_id: balanced_demo</code>, <code>coverage_assist: true</code>, <code>coverage_window_minutes: 20</code>, all profile weights <code>1.0</code>.</p>
<h3 class="accent-gold">3. Persist and distribute control state through NATS</h3>
<ul>
<li>Use JetStream KV bucket <code>synthetic_control</code></li>
<li>Use key <code>global</code></li>
<li><code>services/api</code> reads and writes the KV entry</li>
<li><code>services/ingest-options</code> loads on boot and watches for updates</li>
<li><code>services/ingest-equities</code> does the same</li>
</ul>
<h3 class="accent-rose">4. Rebuild options scenarios as hidden subtype families</h3>
<ul>
<li><strong><code>institutional_directional</code></strong>: <code>call_sweep</code>, <code>put_sweep</code>, <code>ask_lift_accumulation</code>, <code>far_dated_conviction</code></li>
<li><strong><code>retail_whale</code></strong>: <code>0dte_call_chase</code>, <code>short_dated_put_panic</code>, <code>attention_contract_spike</code></li>
<li><strong><code>event_driven</code></strong>: <code>earnings_vol_probe</code>, <code>pre_event_directional_ramp</code>, <code>post_gap_followthrough</code></li>
<li><strong><code>vol_seller</code></strong>: <code>covered_call_overwrite</code>, <code>cash_secured_put_write</code>, <code>short_straddle_harvest</code></li>
<li><strong><code>arbitrage</code></strong>: <code>parity_vertical</code>, <code>conversion_reversal</code>, <code>box_spread</code></li>
<li><strong><code>hedge_reactive</code></strong>: <code>gamma_pinch_call_hedge</code>, <code>reactive_put_wall</code>, <code>dealer_unwind</code></li>
<li><strong><code>neutral_noise</code></strong>: <code>single_print_mid</code>, <code>two_sided_scalp</code>, <code>stale_quote_noise</code></li>
</ul>
<p>Hidden subtype labels remain internal and test-only. They should never appear on emitted option prints or public smart-money events.</p>
<h3>5. Make equities and options react to the same latent state</h3>
<div class="split">
<div class="panel">
<h3>Equities changes</h3>
<ul>
<li>Remove the fixed dark-sequence loop</li>
<li>Make lit versus dark balance regime-dependent</li>
<li>Make spread, quote cleanliness, off-exchange frequency, and clustering regime-dependent</li>
<li>Use shared focus symbols</li>
<li>Make <code>event_ramp</code> and <code>retail_chase</code> show modest trend and wider quotes</li>
<li>Make <code>dealer_gamma</code> show choppier reversals and denser quote changes</li>
<li>Make <code>arb_calm</code> quieter and more neutral</li>
</ul>
</div>
<div class="panel">
<h3>Options changes</h3>
<ul>
<li>Replace hardcoded coverage forcing with weighted family selection plus coverage debt</li>
<li>Make venue count, placement, stale or missing quote probability, and structure prevalence regime-sensitive</li>
<li>Derive <code>execution_iv_shock</code>, <code>underlying_move_bps</code>, and <code>nbbo_spread_z</code> from shared state</li>
<li>Generate event-driven timestamps and symbols from shared regime state</li>
</ul>
</div>
</div>
<h3>6. Add soft coverage accounting</h3>
<ul>
<li>Track rolling coverage debt per public profile inside each ingest service</li>
<li>Maintain a rolling counter across the selected <code>coverage_window_minutes</code></li>
<li>Only public profiles count toward coverage</li>
<li>Missing profiles get a bounded weight boost</li>
<li>Noise and low-key scenarios continue to appear between labeled bursts</li>
</ul>
<h3>7. Add internal hosted control endpoints</h3>
<p>Add routes in <code>services/api/src/index.ts</code>:</p>
<ul>
<li><code>GET /admin/synthetic/status</code></li>
<li><code>GET /admin/synthetic/control</code></li>
<li><code>PUT /admin/synthetic/control</code></li>
</ul>
<pre><code>{
enabled: boolean;
backend_mode: "synthetic" | "mixed" | "live";
adapters: {
options: string;
equities: string;
};
control: SyntheticControlState | null;
derived: {
session_phase: string;
regime: string;
focus_symbols: string[];
profile_hit_counts: Record&lt;SmartMoneyProfileId, number&gt;;
coverage_window_minutes: number;
} | null;
disabled_reason?: string;
}</code></pre>
<p>Behavior: return <code>404</code> when admin mode is disabled, return <code>409</code> when hosted adapters are not synthetic, validate full payloads on <code>PUT</code>, and keep public smart-money interfaces unchanged.</p>
<h3>8. Keep secrets out of the browser with Next.js proxy routes</h3>
<ul>
<li><code>apps/web/app/api/admin/synthetic/status/route.ts</code></li>
<li><code>apps/web/app/api/admin/synthetic/control/route.ts</code></li>
</ul>
<p>The proxy reads server-only <code>SYNTHETIC_ADMIN_TOKEN</code>, forwards to <code>NEXT_PUBLIC_API_URL</code>, returns <code>404</code> when the internal UI flag is off, and never exposes the token client-side.</p>
<h3>9. Add an internal control surface</h3>
<p>UI rules for the first pass:</p>
<ul>
<li>Small floating gear in the bottom-right corner</li>
<li>Opens a right-edge non-modal drawer</li>
<li>Internal-only visibility</li>
<li>Preset dropdown: <code>Balanced Demo</code>, <code>Event Day</code>, <code>Dealer Day</code>, <code>Retail Chase</code>, <code>Quiet Range</code></li>
<li>Coverage assist toggle</li>
<li>Coverage window selector: <code>10m</code>, <code>20m</code>, <code>30m</code></li>
<li>Six profile-weight controls: <code>Low</code>, <code>Normal</code>, <code>High</code></li>
<li>Read-only live status: regime, session phase, focus symbols, rolling hit counts, backend state</li>
<li>Optimistic updates with rollback on error</li>
<li>Debounced writes at <code>250ms</code></li>
<li>Status polling every <code>5s</code>, no admin websocket in v1</li>
</ul>
</section>
<section id="interfaces">
<h2>Interfaces and Environment</h2>
<div class="split">
<div class="panel">
<h3>Public contracts unchanged</h3>
<ul>
<li><code>SmartMoneyProfileId</code></li>
<li><code>SmartMoneyEvent</code></li>
<li><code>/flow/smart-money</code></li>
<li><code>/history/smart-money</code></li>
<li><code>/replay/smart-money</code></li>
<li><code>/ws/smart-money</code></li>
</ul>
</div>
<div class="panel">
<h3>New internal contracts</h3>
<ul>
<li><code>SyntheticControlState</code></li>
<li><code>SyntheticControlPresetId</code></li>
<li><code>SyntheticDerivedStatus</code></li>
</ul>
</div>
</div>
<div class="split">
<div class="panel">
<h3>New internal endpoints</h3>
<ul>
<li><code>GET /admin/synthetic/status</code></li>
<li><code>GET /admin/synthetic/control</code></li>
<li><code>PUT /admin/synthetic/control</code></li>
</ul>
</div>
<div class="panel">
<h3>New env vars</h3>
<p class="subdued">Backend</p>
<ul>
<li><code>SYNTHETIC_CONTROL_ENABLED=0|1</code></li>
<li><code>SYNTHETIC_ADMIN_TOKEN=...</code></li>
</ul>
<p class="subdued">Web</p>
<ul>
<li><code>NEXT_PUBLIC_SYNTHETIC_ADMIN=0|1</code></li>
<li><code>SYNTHETIC_ADMIN_TOKEN=...</code> for the Next server proxy only</li>
</ul>
</div>
</div>
</section>
<section id="sequence">
<h2>Implementation Phases</h2>
<ol>
<li>
<strong>Phase 1. Shared types and regime engine</strong>
<p class="subdued">Touch <code>packages/types/src/synthetic-market.ts</code> and related exports and tests. Deliver control schemas, preset definitions, deterministic session and regime functions, and coverage boost helpers.</p>
</li>
<li>
<strong>Phase 2. Hosted control plane</strong>
<p class="subdued">Touch <code>services/api/src/index.ts</code> and NATS or KV helpers as needed. Deliver admin endpoints, KV persistence, status payloads, and disabled or error behavior.</p>
</li>
<li>
<strong>Phase 3. Ingest service coupling</strong>
<p class="subdued">Touch both ingest services and their synthetic adapters. Deliver boot-time control loading, KV watch updates, shared regime-driven generation, and removal of visibly scripted fixed sequences.</p>
</li>
<li>
<strong>Phase 4. Internal control UI</strong>
<p class="subdued">Touch <code>apps/web/app/terminal.tsx</code> and the internal admin proxy routes. Deliver the floating gear, non-modal drawer, polling, optimistic updates, and disabled state.</p>
</li>
<li>
<strong>Phase 5. Regression and realism tests</strong>
<p class="subdued">Deliver determinism tests, control API tests, scenario coverage tests, UI visibility tests, and classifier-alignment tests for hidden subtype families.</p>
</li>
</ol>
</section>
<section id="tests">
<h2>Tests and Acceptance</h2>
<div class="split">
<div class="panel">
<h3>Shared engine</h3>
<ul>
<li>Same <code>timestamp + control snapshot + seed</code> yields the same regime and focus symbols in both ingest services.</li>
<li>Presets materially change regime weights without breaking determinism.</li>
<li><code>balanced_demo</code> yields mixed regimes over a session.</li>
<li><code>quiet_range</code> yields lower volatility, tighter spreads, and fewer labeled events than <code>retail_chase</code>.</li>
</ul>
</div>
<div class="panel">
<h3>Cross-asset coupling</h3>
<ul>
<li><code>event_ramp</code> produces event-aligned option scenarios and synchronized underlying drift and spread behavior.</li>
<li><code>dealer_gamma</code> produces short-dated ATM-heavy options plus choppier underlying reversals.</li>
<li><code>arb_calm</code> increases neutral multi-leg structures without strong directional underlying moves.</li>
<li><code>retail_chase</code> increases short-dated OTM call behavior, IV shock, and louder underlying momentum.</li>
</ul>
</div>
</div>
<div class="split">
<div class="panel">
<h3>Coverage and classification</h3>
<ul>
<li>With default controls, every public smart-money profile appears at least once in a 20-minute synthetic session sample.</li>
<li>With <code>coverage_assist=false</code>, there is no forced coverage logic.</li>
<li>Raising one profile to <code>High</code> increases its frequency without starving other categories.</li>
<li>Neutral noise remains below the smart-money emission threshold.</li>
<li>Each hidden subtype family still classifies into the intended public profile.</li>
</ul>
</div>
<div class="panel">
<h3>Admin API and UI</h3>
<ul>
<li>Disabled admin mode returns <code>404</code>.</li>
<li>Non-synthetic hosted mode returns <code>409</code> with a useful reason.</li>
<li>Valid <code>PUT</code> persists to KV and becomes visible to both ingest services.</li>
<li>The floating gear is hidden when <code>NEXT_PUBLIC_SYNTHETIC_ADMIN</code> is off.</li>
<li>The browser client never receives the backend admin token.</li>
</ul>
</div>
</div>
</section>
<section id="assumptions">
<h2>Assumptions and Defaults</h2>
<ul>
<li>Hosted synthetic control applies only when both options and equities ingest adapters are synthetic.</li>
<li>No general settings page, user-info work, or token-spend work is in scope here.</li>
<li>Hidden subtype labels remain internal and test-only and never attach to emitted prints.</li>
<li>The first pass uses polling for admin status rather than a new admin websocket.</li>
<li>The default operator experience is <code>Balanced Demo</code> with soft coverage on and a 20-minute window.</li>
<li>The repo currently lacks local <code>PRODUCT.md</code>, <code>DESIGN.md</code>, and the local impeccable loader path. This version therefore follows the spirit of the terminal shell and impeccable product-UI principles rather than project-specific design-context files.</li>
</ul>
</section>
</article>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,620 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hosted Synthetic Tape Redesign Plan</title>
<style>
:root {
color-scheme: dark;
--bg: #0f1216;
--panel: #151a20;
--panel-soft: #1b2129;
--border: #2a3440;
--text: #e4e9ef;
--muted: #a4afbc;
--accent: #69c3a5;
--accent-soft: rgba(105, 195, 165, 0.14);
--warn: #e0b36c;
--code: #10151a;
--shadow: 0 18px 44px rgba(0, 0, 0, 0.28);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "SF Pro Text", "Inter", "Segoe UI", sans-serif;
background:
radial-gradient(circle at top, rgba(105, 195, 165, 0.08), transparent 28rem),
linear-gradient(180deg, #0c0f13, var(--bg));
color: var(--text);
line-height: 1.55;
}
main {
width: min(1100px, calc(100vw - 2rem));
margin: 0 auto;
padding: 2rem 0 4rem;
}
header,
section {
background: rgba(21, 26, 32, 0.92);
border: 1px solid var(--border);
border-radius: 18px;
box-shadow: var(--shadow);
padding: 1.5rem;
margin-bottom: 1rem;
}
header {
padding: 1.8rem;
}
h1,
h2,
h3 {
margin: 0 0 0.75rem;
line-height: 1.15;
}
h1 {
font-size: clamp(2rem, 5vw, 3rem);
letter-spacing: -0.04em;
}
h2 {
font-size: 1.3rem;
color: var(--accent);
}
h3 {
font-size: 1rem;
color: #f1f5f9;
}
p,
li {
max-width: 76ch;
color: var(--text);
}
.lede {
font-size: 1.05rem;
color: #d7dde5;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 0.45rem;
margin-bottom: 0.75rem;
padding: 0.35rem 0.75rem;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent);
font-size: 0.8rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.summary-grid,
.scope-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 0.85rem;
margin-top: 1rem;
}
.summary-item,
.scope-item {
background: var(--panel-soft);
border: 1px solid var(--border);
border-radius: 14px;
padding: 1rem;
}
.summary-item strong,
.scope-item strong {
display: block;
margin-bottom: 0.35rem;
}
ul,
ol {
padding-left: 1.2rem;
}
code,
pre {
font-family: "SF Mono", "JetBrains Mono", "Cascadia Code", monospace;
}
pre {
overflow-x: auto;
background: var(--code);
border: 1px solid var(--border);
border-radius: 14px;
padding: 1rem;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 0.75rem;
}
th,
td {
border-bottom: 1px solid var(--border);
text-align: left;
vertical-align: top;
padding: 0.8rem 0.65rem;
}
th {
color: var(--accent);
font-size: 0.86rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.muted {
color: var(--muted);
}
.callout {
padding: 0.95rem 1rem;
background: rgba(224, 179, 108, 0.12);
border: 1px solid rgba(224, 179, 108, 0.28);
border-radius: 14px;
color: #f6debb;
}
.toc a {
color: var(--accent);
text-decoration: none;
}
.toc li {
margin-bottom: 0.35rem;
}
.small {
font-size: 0.92rem;
}
</style>
</head>
<body>
<main>
<header>
<div class="eyebrow">Plan · Standard HTML Version</div>
<h1>Hosted Synthetic Tape Redesign</h1>
<p class="lede">
This plan redesigns the hosted synthetic market so the tape feels more like a real market session while still surfacing all six smart-money categories during a demo window. It keeps the public labels stable, adds richer hidden scenario families underneath them, and introduces an internal control surface for shaping the hosted simulator.
</p>
<div class="summary-grid">
<div class="summary-item">
<strong>Main outcome</strong>
<span>More believable synthetic options, equities, quotes, and smart-money events, with softer coverage guarantees and stronger cross-asset coupling.</span>
</div>
<div class="summary-item">
<strong>User-facing change</strong>
<span>An internal-only bottom-right gear opens a compact synthetic-control drawer for operators.</span>
</div>
<div class="summary-item">
<strong>Public API impact</strong>
<span>No change to existing smart-money event types or public smart-money endpoints.</span>
</div>
<div class="summary-item">
<strong>Why it matters</strong>
<span>The current tape reaches the categories, but it looks too templated and too clean in ways that weaken the demo.</span>
</div>
</div>
</header>
<section>
<h2>Simplified Summary</h2>
<p>
Today the simulator does the important part mechanically: it hits the categories. The problem is that the surrounding market behavior does not always look convincing. Options bursts, equity prints, quote quality, and event timing can feel loosely stitched together instead of driven by one believable market state.
</p>
<p>
The redesign fixes that by introducing a shared regime engine. Both synthetic options and synthetic equities will respond to the same session conditions, such as event ramps, dealer-gamma chop, retail chase, quiet range trading, and neutral arbitrage-heavy periods. The result should be a tape that still teaches the product, but no longer feels obviously scripted.
</p>
<div class="callout small">
The public smart-money taxonomy stays the same: <code>institutional_directional</code>, <code>retail_whale</code>, <code>event_driven</code>, <code>vol_seller</code>, <code>arbitrage</code>, and <code>hedge_reactive</code>.
</div>
</section>
<section>
<h2>Scope</h2>
<div class="scope-grid">
<div class="scope-item">
<strong>In scope</strong>
<ul>
<li>Hosted synthetic market regime engine</li>
<li>Options and equities synthetic generator redesign</li>
<li>Hidden subtype scenario families</li>
<li>Soft coverage logic</li>
<li>Internal control API and UI</li>
<li>Documentation and tests for the new behavior</li>
</ul>
</div>
<div class="scope-item">
<strong>Out of scope</strong>
<ul>
<li>Changing public smart-money profile IDs</li>
<li>General settings page work</li>
<li>User profile or token-spend features</li>
<li>Live market feed changes</li>
<li>Public-facing simulator controls</li>
</ul>
</div>
</div>
</section>
<section>
<h2>Services Affected</h2>
<table>
<thead>
<tr>
<th>Area</th>
<th>Primary files</th>
<th>Role in the redesign</th>
</tr>
</thead>
<tbody>
<tr>
<td>Shared types</td>
<td><code>packages/types/src/synthetic-market.ts</code>, <code>packages/types/src/events.ts</code></td>
<td>Add the shared regime model and internal synthetic-control schemas.</td>
</tr>
<tr>
<td>Hosted API</td>
<td><code>services/api/src/index.ts</code></td>
<td>Add internal control endpoints and expose hosted simulator status.</td>
</tr>
<tr>
<td>Options ingest</td>
<td><code>services/ingest-options/src/index.ts</code>, <code>services/ingest-options/src/adapters/synthetic.ts</code></td>
<td>Adopt the new regime engine, scenario families, and soft coverage logic.</td>
</tr>
<tr>
<td>Equities ingest</td>
<td><code>services/ingest-equities/src/index.ts</code>, <code>services/ingest-equities/src/adapters/synthetic.ts</code></td>
<td>Synchronize synthetic quotes and prints with the same latent market regime.</td>
</tr>
<tr>
<td>Web and Electron shell</td>
<td><code>apps/web/app/terminal.tsx</code>, <code>apps/web/app/api/admin/synthetic/*</code></td>
<td>Add the internal gear-triggered control drawer and server-side proxy routes.</td>
</tr>
<tr>
<td>Tests</td>
<td><code>services/ingest-options/tests/synthetic.test.ts</code>, web tests, API tests</td>
<td>Protect classification alignment, determinism, coverage behavior, and control-plane behavior.</td>
</tr>
</tbody>
</table>
</section>
<section class="toc">
<h2>Full Plan Contents</h2>
<ol>
<li><a href="#locked">Product decisions locked</a></li>
<li><a href="#architecture">Architecture</a></li>
<li><a href="#interfaces">Public and internal interfaces</a></li>
<li><a href="#sequence">Implementation sequence</a></li>
<li><a href="#tests">Test cases and scenarios</a></li>
<li><a href="#assumptions">Assumptions and defaults</a></li>
</ol>
</section>
<section id="locked">
<h2>Product Decisions Locked</h2>
<ul>
<li>Keep the current six public smart-money categories.</li>
<li>Add richer hidden sub-scenarios beneath them.</li>
<li>Use soft coverage guarantees, not hard forced sequencing.</li>
<li>Prioritize cross-asset coupling first.</li>
<li>Controls affect the hosted synthetic backend.</li>
<li>Controls are internal-only.</li>
<li>Do not build a general settings page, user-info work, or token-spend work in this effort.</li>
<li>Use a bottom-right gear that opens a synthetic-control drawer.</li>
</ul>
</section>
<section id="architecture">
<h2>Architecture</h2>
<h3>1. Replace the simple burst pulse with a shared regime engine</h3>
<p>Expand <code>packages/types/src/synthetic-market.ts</code> into the shared deterministic market-state engine used by both ingest services.</p>
<p>Shared functions:</p>
<ul>
<li><code>getSyntheticSessionState(ts, control)</code></li>
<li><code>getSyntheticUnderlyingState(symbol, ts, control, sessionState)</code></li>
<li><code>getSyntheticScenarioWeights(symbol, ts, control, sessionState)</code></li>
<li><code>getSyntheticCoverageBoost(profileId, coverageState, control)</code></li>
</ul>
<p><code>sessionState</code> includes:</p>
<ul>
<li><code>session_phase</code>: <code>open | midday | power_hour | after_event</code></li>
<li><code>regime</code>: <code>trend_up | trend_down | mean_revert | retail_chase | event_ramp | dealer_gamma | arb_calm</code></li>
<li><code>volatility_level</code></li>
<li><code>liquidity_level</code></li>
<li><code>quote_cleanliness</code></li>
<li><code>focus_symbols</code></li>
<li><code>event_symbols</code></li>
<li><code>seed_bucket</code></li>
</ul>
<h3>2. Add hosted synthetic control state</h3>
<p>Add internal control schemas in <code>packages/types</code>:</p>
<ul>
<li><code>SyntheticControlPresetId</code></li>
<li><code>SyntheticControlState</code></li>
<li><code>SyntheticProfileWeightMap</code></li>
<li><code>SyntheticCoverageConfig</code></li>
<li><code>SyntheticDerivedStatus</code></li>
</ul>
<pre><code>type SyntheticControlState = {
preset_id: "balanced_demo" | "event_day" | "dealer_day" | "retail_chase" | "quiet_range";
coverage_assist: boolean;
coverage_window_minutes: 10 | 20 | 30;
shared_seed: number;
profile_weights: {
institutional_directional: 0.6 | 1.0 | 1.6;
retail_whale: 0.6 | 1.0 | 1.6;
event_driven: 0.6 | 1.0 | 1.6;
vol_seller: 0.6 | 1.0 | 1.6;
arbitrage: 0.6 | 1.0 | 1.6;
hedge_reactive: 0.6 | 1.0 | 1.6;
};
updated_at: number;
updated_by: string;
};</code></pre>
<p>Defaults:</p>
<ul>
<li><code>preset_id: balanced_demo</code></li>
<li><code>coverage_assist: true</code></li>
<li><code>coverage_window_minutes: 20</code></li>
<li>All profile weights <code>1.0</code></li>
</ul>
<h3>3. Persist and distribute control state through NATS</h3>
<ul>
<li>Use JetStream KV bucket <code>synthetic_control</code></li>
<li>Use key <code>global</code></li>
<li><code>services/api</code> reads and writes the KV entry</li>
<li><code>services/ingest-options</code> loads on boot and watches for updates</li>
<li><code>services/ingest-equities</code> does the same</li>
</ul>
<h3>4. Rebuild options scenarios as hidden subtype families</h3>
<p>Keep public profiles the same, but generate them through richer hidden subtype families.</p>
<ul>
<li><strong><code>institutional_directional</code></strong>: <code>call_sweep</code>, <code>put_sweep</code>, <code>ask_lift_accumulation</code>, <code>far_dated_conviction</code></li>
<li><strong><code>retail_whale</code></strong>: <code>0dte_call_chase</code>, <code>short_dated_put_panic</code>, <code>attention_contract_spike</code></li>
<li><strong><code>event_driven</code></strong>: <code>earnings_vol_probe</code>, <code>pre_event_directional_ramp</code>, <code>post_gap_followthrough</code></li>
<li><strong><code>vol_seller</code></strong>: <code>covered_call_overwrite</code>, <code>cash_secured_put_write</code>, <code>short_straddle_harvest</code></li>
<li><strong><code>arbitrage</code></strong>: <code>parity_vertical</code>, <code>conversion_reversal</code>, <code>box_spread</code></li>
<li><strong><code>hedge_reactive</code></strong>: <code>gamma_pinch_call_hedge</code>, <code>reactive_put_wall</code>, <code>dealer_unwind</code></li>
<li><strong><code>neutral_noise</code></strong>: <code>single_print_mid</code>, <code>two_sided_scalp</code>, <code>stale_quote_noise</code></li>
</ul>
<h3>5. Make equities and options react to the same latent state</h3>
<p>Equities changes:</p>
<ul>
<li>Remove the fixed dark-sequence loop</li>
<li>Make lit versus dark balance regime-dependent</li>
<li>Make spread, quote cleanliness, off-exchange frequency, and clustering regime-dependent</li>
<li>Use shared focus symbols</li>
<li>During <code>event_ramp</code> and <code>retail_chase</code>, create modest trend and wider quotes</li>
<li>During <code>dealer_gamma</code>, create choppier reversals and denser quote changes</li>
<li>During <code>arb_calm</code>, create quieter underlying motion and more neutral execution context</li>
</ul>
<p>Options changes:</p>
<ul>
<li>Replace hardcoded coverage forcing with weighted family selection plus coverage debt</li>
<li>Make venue count, placement, stale or missing quote probability, and structure prevalence regime-sensitive</li>
<li>Derive <code>execution_iv_shock</code>, <code>underlying_move_bps</code>, and <code>nbbo_spread_z</code> from shared state</li>
<li>Generate event-driven timestamps and symbols from shared regime state</li>
</ul>
<h3>6. Add soft coverage accounting</h3>
<ul>
<li>Track rolling coverage debt per public profile inside each ingest service</li>
<li>Maintain a rolling counter across the selected <code>coverage_window_minutes</code></li>
<li>Only public profiles count toward coverage</li>
<li>Missing profiles get a bounded weight boost</li>
<li>Noise and low-key scenarios continue to appear between labeled bursts</li>
</ul>
<h3>7. Add internal hosted control endpoints</h3>
<p>Add routes in <code>services/api/src/index.ts</code>:</p>
<ul>
<li><code>GET /admin/synthetic/status</code></li>
<li><code>GET /admin/synthetic/control</code></li>
<li><code>PUT /admin/synthetic/control</code></li>
</ul>
<pre><code>{
enabled: boolean;
backend_mode: "synthetic" | "mixed" | "live";
adapters: {
options: string;
equities: string;
};
control: SyntheticControlState | null;
derived: {
session_phase: string;
regime: string;
focus_symbols: string[];
profile_hit_counts: Record&lt;SmartMoneyProfileId, number&gt;;
coverage_window_minutes: number;
} | null;
disabled_reason?: string;
}</code></pre>
<p>Behavior:</p>
<ul>
<li>Return <code>404</code> when admin mode is disabled</li>
<li>Return <code>409</code> when hosted adapters are not synthetic</li>
<li>Validate full payloads on <code>PUT</code></li>
<li>Keep all existing public smart-money, history, replay, and websocket endpoints unchanged</li>
</ul>
<h3>8. Keep secrets out of the browser with Next.js proxy routes</h3>
<p>Add server-side proxy routes:</p>
<ul>
<li><code>apps/web/app/api/admin/synthetic/status/route.ts</code></li>
<li><code>apps/web/app/api/admin/synthetic/control/route.ts</code></li>
</ul>
<p>Proxy behavior:</p>
<ul>
<li>Read server-only <code>SYNTHETIC_ADMIN_TOKEN</code></li>
<li>Forward to backend admin endpoints at <code>NEXT_PUBLIC_API_URL</code></li>
<li>Return <code>404</code> when the internal UI flag is off</li>
<li>Never send the token to the browser</li>
</ul>
<h3>9. Add an internal control surface</h3>
<p>UI surface:</p>
<ul>
<li>A small floating gear button in the bottom-right corner</li>
<li>Opens a right-edge non-modal drawer</li>
<li>Internal-only visibility</li>
</ul>
<p>Drawer sections:</p>
<ul>
<li><code>Preset</code></li>
<li><code>Coverage</code></li>
<li><code>Profile Bias</code></li>
<li><code>Live Status</code></li>
</ul>
<p>Controls:</p>
<ul>
<li>Preset dropdown: <code>Balanced Demo</code>, <code>Event Day</code>, <code>Dealer Day</code>, <code>Retail Chase</code>, <code>Quiet Range</code></li>
<li>Coverage assist toggle</li>
<li>Coverage window selector: <code>10m</code>, <code>20m</code>, <code>30m</code></li>
<li>Six profile weight controls with <code>Low</code>, <code>Normal</code>, <code>High</code></li>
</ul>
</section>
<section id="interfaces">
<h2>Public and Internal Interfaces</h2>
<h3>Public contracts unchanged</h3>
<ul>
<li><code>SmartMoneyProfileId</code></li>
<li><code>SmartMoneyEvent</code></li>
<li><code>/flow/smart-money</code></li>
<li><code>/history/smart-money</code></li>
<li><code>/replay/smart-money</code></li>
<li><code>/ws/smart-money</code></li>
</ul>
<h3>New internal contracts</h3>
<ul>
<li><code>SyntheticControlState</code></li>
<li><code>SyntheticControlPresetId</code></li>
<li><code>SyntheticDerivedStatus</code></li>
</ul>
<h3>New internal endpoints</h3>
<ul>
<li><code>GET /admin/synthetic/status</code></li>
<li><code>GET /admin/synthetic/control</code></li>
<li><code>PUT /admin/synthetic/control</code></li>
</ul>
<h3>New environment variables</h3>
<p>Backend:</p>
<ul>
<li><code>SYNTHETIC_CONTROL_ENABLED=0|1</code></li>
<li><code>SYNTHETIC_ADMIN_TOKEN=...</code></li>
</ul>
<p>Web:</p>
<ul>
<li><code>NEXT_PUBLIC_SYNTHETIC_ADMIN=0|1</code></li>
<li><code>SYNTHETIC_ADMIN_TOKEN=...</code> for the Next server proxy only</li>
</ul>
</section>
<section id="sequence">
<h2>Implementation Sequence</h2>
<ol>
<li>
<strong>Phase 1. Shared types and regime engine</strong>
<p class="muted small">Touch <code>packages/types/src/synthetic-market.ts</code> and related exports and tests. Deliver control schemas, preset definitions, deterministic session and regime functions, and coverage boost helpers.</p>
</li>
<li>
<strong>Phase 2. Hosted control plane</strong>
<p class="muted small">Touch <code>services/api/src/index.ts</code> and NATS or KV helpers as needed. Deliver admin endpoints, KV persistence, status payloads, and disabled or error behavior.</p>
</li>
<li>
<strong>Phase 3. Ingest service coupling</strong>
<p class="muted small">Touch both ingest services and their synthetic adapters. Deliver boot-time control loading, KV watch updates, shared regime-driven generation, and removal of visibly scripted fixed sequences.</p>
</li>
<li>
<strong>Phase 4. Internal control UI</strong>
<p class="muted small">Touch <code>apps/web/app/terminal.tsx</code> and the internal admin proxy routes. Deliver the floating gear, non-modal drawer, polling, optimistic updates, and disabled state.</p>
</li>
<li>
<strong>Phase 5. Regression and realism tests</strong>
<p class="muted small">Deliver determinism tests, control API tests, scenario coverage tests, UI visibility tests, and classifier-alignment tests for hidden subtype families.</p>
</li>
</ol>
</section>
<section id="tests">
<h2>Test Cases and Scenarios</h2>
<h3>Shared engine</h3>
<ul>
<li>Same <code>timestamp + control snapshot + seed</code> yields the same regime and focus symbols in both ingest services.</li>
<li>Presets materially change regime weights without breaking determinism.</li>
<li><code>balanced_demo</code> yields mixed regimes over a session.</li>
<li><code>quiet_range</code> yields lower volatility, tighter spreads, and fewer labeled events than <code>retail_chase</code>.</li>
</ul>
<h3>Cross-asset coupling</h3>
<ul>
<li><code>event_ramp</code> produces event-aligned option scenarios and synchronized underlying drift and spread behavior.</li>
<li><code>dealer_gamma</code> produces short-dated ATM-heavy options plus choppier underlying reversals.</li>
<li><code>arb_calm</code> increases neutral multi-leg structures without strong directional underlying moves.</li>
<li><code>retail_chase</code> increases short-dated OTM call behavior, IV shock, and louder underlying momentum.</li>
</ul>
<h3>Coverage behavior</h3>
<ul>
<li>With default controls, every public smart-money profile appears at least once in a 20-minute synthetic session sample.</li>
<li>With <code>coverage_assist=false</code>, there is no forced coverage logic.</li>
<li>Raising one profile to <code>High</code> increases its frequency without starving all other categories.</li>
<li>The quiet preset still emits noise and occasional signals rather than a dead tape.</li>
</ul>
<h3>Classification alignment</h3>
<ul>
<li>Each hidden subtype family still classifies primarily into its intended public profile.</li>
<li>Neutral noise remains below the smart-money emission threshold.</li>
<li>Nearby wrong profiles stay below threshold in subtype template tests.</li>
</ul>
<h3>Admin API and UI</h3>
<ul>
<li>Disabled admin mode returns <code>404</code>.</li>
<li>Non-synthetic hosted mode returns <code>409</code> with a useful reason.</li>
<li>Valid <code>PUT</code> persists to KV and becomes visible to both ingest services.</li>
<li>The floating gear is hidden when <code>NEXT_PUBLIC_SYNTHETIC_ADMIN</code> is off.</li>
<li>The browser client never receives the backend admin token.</li>
</ul>
</section>
<section id="assumptions">
<h2>Assumptions and Defaults</h2>
<ul>
<li>Hosted synthetic control applies only when both options and equities ingest adapters are synthetic.</li>
<li>No general settings page, user-info work, or token-spend work is in scope here.</li>
<li>Hidden subtype labels remain internal and test-only and never attach to emitted prints.</li>
<li>The first pass uses polling for admin status rather than a new admin websocket.</li>
<li>The default operator experience is <code>Balanced Demo</code> with soft coverage on and a 20-minute window.</li>
<li>The repo currently lacks local <code>PRODUCT.md</code>, <code>DESIGN.md</code>, and the local impeccable loader path, so UI implementation should use the existing terminal shell as the visual source of truth unless those design-context files are added later.</li>
</ul>
</section>
</main>
</body>
</html>

View file

@ -25,8 +25,12 @@ import {
STREAM_OPTION_SIGNAL_PRINTS,
buildDurableConsumer,
connectJetStreamWithRetry,
ensureSyntheticControlState,
ensureKnownStreams,
subscribeJson
openSyntheticControlKv,
subscribeJson,
watchSyntheticControlState,
writeSyntheticControlState
} from "@islandflow/bus";
import {
createClickHouseClient,
@ -100,6 +104,7 @@ import {
matchesFlowPacketFilters,
matchesOptionPrintFilters,
FlowPacketSchema,
SyntheticControlStateSchema,
SmartMoneyEventSchema,
OptionNBBOSchema,
OptionPrintSchema,
@ -114,6 +119,13 @@ import {
shouldFanoutLiveEvent
} from "./live";
import { parseOptionPrintQuery } from "./option-queries";
import {
buildSyntheticDerivedStatus,
createRollingSyntheticProfileHits,
getSyntheticBackendDisabledReason,
recordSyntheticProfileHit,
resolveSyntheticBackendMode
} from "./synthetic-control";
const service = "api";
const logger = createLogger({ service });
@ -127,10 +139,27 @@ const envSchema = z.object({
CLICKHOUSE_URL: z.string().default("http://127.0.0.1:8123"),
CLICKHOUSE_DATABASE: z.string().default("default"),
REDIS_URL: z.string().default("redis://127.0.0.1:6379"),
OPTIONS_INGEST_ADAPTER: z.string().min(1).default("synthetic"),
EQUITIES_INGEST_ADAPTER: z.string().min(1).default("synthetic"),
REST_DEFAULT_LIMIT: z.coerce.number().int().positive().default(200),
API_DELIVER_POLICY: DeliverPolicySchema.default("new"),
API_CONSUMER_RESET: z.coerce.boolean().default(false),
LIVE_LAG_WARN_MS: z.coerce.number().int().positive().default(120_000)
LIVE_LAG_WARN_MS: z.coerce.number().int().positive().default(120_000),
SYNTHETIC_CONTROL_ENABLED: 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(false),
SYNTHETIC_ADMIN_TOKEN: z.string().default("")
});
const env = readEnv(envSchema);
@ -283,6 +312,14 @@ const readJsonBody = async (req: Request): Promise<unknown> => {
return JSON.parse(text);
};
const getBearerToken = (req: Request): string => {
const authorization = req.headers.get("authorization") ?? "";
if (authorization.toLowerCase().startsWith("bearer ")) {
return authorization.slice(7).trim();
}
return req.headers.get("x-synthetic-admin-token")?.trim() ?? "";
};
const optionsSupportLookupSchema = z.object({
trace_ids: z.array(z.string().min(1)).default([]),
nbbo_context: z
@ -641,6 +678,27 @@ const run = async () => {
{ logger }
);
const syntheticBackendMode = resolveSyntheticBackendMode(
env.OPTIONS_INGEST_ADAPTER,
env.EQUITIES_INGEST_ADAPTER
);
const syntheticBackendDisabledReason =
getSyntheticBackendDisabledReason(syntheticBackendMode);
const syntheticControlKv = await openSyntheticControlKv(js);
let syntheticControl = await ensureSyntheticControlState(syntheticControlKv);
const syntheticProfileHits = createRollingSyntheticProfileHits();
const stopSyntheticControlWatch = await watchSyntheticControlState(
syntheticControlKv,
(nextControl) => {
syntheticControl = nextControl;
},
(error) => {
logger.warn("synthetic control watch failed", {
error: getErrorMessage(error)
});
}
);
const clickhouse = createClickHouseClient({
url: env.CLICKHOUSE_URL,
database: env.CLICKHOUSE_DATABASE
@ -1146,6 +1204,7 @@ const run = async () => {
for await (const msg of smartMoneySubscription.messages) {
try {
const payload = SmartMoneyEventSchema.parse(smartMoneySubscription.decode(msg));
recordSyntheticProfileHit(syntheticProfileHits, payload);
broadcast(smartMoneySockets, { type: "smart-money", payload });
await fanoutLive({ channel: "smart-money" }, payload, "smart-money");
msg.ack();
@ -1202,6 +1261,54 @@ const run = async () => {
void pumpClassifierHits();
void pumpAlerts();
const buildSyntheticStatusBody = () => {
const derived =
syntheticBackendMode === "synthetic"
? buildSyntheticDerivedStatus(Date.now(), syntheticControl, syntheticProfileHits)
: null;
return {
enabled: env.SYNTHETIC_CONTROL_ENABLED && syntheticBackendMode === "synthetic",
backend_mode: syntheticBackendMode,
adapters: {
options: env.OPTIONS_INGEST_ADAPTER,
equities: env.EQUITIES_INGEST_ADAPTER
},
control: syntheticBackendMode === "synthetic" ? syntheticControl : null,
derived,
...(syntheticBackendDisabledReason
? { disabled_reason: syntheticBackendDisabledReason }
: {})
};
};
const authenticateSyntheticAdminRequest = (req: Request): Response | null => {
if (!env.SYNTHETIC_CONTROL_ENABLED) {
return jsonResponse({ error: "not found" }, 404);
}
if (!env.SYNTHETIC_ADMIN_TOKEN) {
return jsonResponse(
{
error: "synthetic admin misconfigured",
detail: "SYNTHETIC_ADMIN_TOKEN is required when synthetic control is enabled."
},
500
);
}
if (getBearerToken(req) !== env.SYNTHETIC_ADMIN_TOKEN) {
return jsonResponse({ error: "unauthorized" }, 401);
}
if (syntheticBackendMode !== "synthetic") {
return jsonResponse(
{
error: "synthetic backend unavailable",
...buildSyntheticStatusBody()
},
409
);
}
return null;
};
const server = Bun.serve<WsData | LiveWsData>({
port: env.API_PORT,
fetch: async (req: Request, serverRef: any) => {
@ -1211,6 +1318,49 @@ const run = async () => {
return jsonResponse({ status: "ok" });
}
if (req.method === "GET" && url.pathname === "/admin/synthetic/status") {
const authError = authenticateSyntheticAdminRequest(req);
if (authError) {
return authError;
}
return jsonResponse(buildSyntheticStatusBody());
}
if (req.method === "GET" && url.pathname === "/admin/synthetic/control") {
const authError = authenticateSyntheticAdminRequest(req);
if (authError) {
return authError;
}
return jsonResponse({ control: syntheticControl });
}
if (req.method === "PUT" && url.pathname === "/admin/synthetic/control") {
const authError = authenticateSyntheticAdminRequest(req);
if (authError) {
return authError;
}
try {
const payload = SyntheticControlStateSchema.parse(await readJsonBody(req));
syntheticControl = await writeSyntheticControlState(syntheticControlKv, payload);
return jsonResponse({
control: syntheticControl,
derived: buildSyntheticDerivedStatus(
Date.now(),
syntheticControl,
syntheticProfileHits
)
});
} catch (error) {
return jsonResponse(
{
error: "invalid synthetic control payload",
detail: getErrorMessage(error)
},
400
);
}
}
if (req.method === "GET" && url.pathname === "/prints/options") {
try {
const limit = parseLimit(url.searchParams.get("limit"));
@ -1824,6 +1974,7 @@ const run = async () => {
logger.info("service stopping", { signal });
server.stop();
clearInterval(liveStateMetricsTimer);
await stopSyntheticControlWatch();
await liveState.close();
if (redis && redis.isOpen) {

View file

@ -0,0 +1,93 @@
import {
SyntheticDerivedStatusSchema,
buildEmptySyntheticProfileHitCounts,
getSyntheticSessionState,
type SmartMoneyEvent,
type SmartMoneyProfileId,
type SyntheticControlState,
type SyntheticDerivedStatus
} from "@islandflow/types";
export type SyntheticBackendMode = "synthetic" | "mixed" | "live";
export type RollingSyntheticProfileHits = Record<SmartMoneyProfileId, number[]>;
export const createRollingSyntheticProfileHits = (): RollingSyntheticProfileHits => ({
institutional_directional: [],
retail_whale: [],
event_driven: [],
vol_seller: [],
arbitrage: [],
hedge_reactive: []
});
export const resolveSyntheticBackendMode = (
optionsAdapter: string,
equitiesAdapter: string
): SyntheticBackendMode => {
const optionsSynthetic = optionsAdapter === "synthetic";
const equitiesSynthetic = equitiesAdapter === "synthetic";
if (optionsSynthetic && equitiesSynthetic) {
return "synthetic";
}
if (optionsSynthetic || equitiesSynthetic) {
return "mixed";
}
return "live";
};
export const getSyntheticBackendDisabledReason = (
mode: SyntheticBackendMode
): string | undefined => {
if (mode === "synthetic") {
return undefined;
}
if (mode === "mixed") {
return "Synthetic control requires both hosted ingest adapters to run in synthetic mode.";
}
return "Hosted ingest adapters are not synthetic, so the internal synthetic control surface is unavailable.";
};
export const recordSyntheticProfileHit = (
state: RollingSyntheticProfileHits,
event: Pick<SmartMoneyEvent, "primary_profile_id" | "source_ts">
): void => {
if (!event.primary_profile_id) {
return;
}
state[event.primary_profile_id].push(event.source_ts);
};
export const getSyntheticProfileHitCounts = (
state: RollingSyntheticProfileHits,
now: number,
coverageWindowMinutes: number
): Record<SmartMoneyProfileId, number> => {
const floorTs = now - coverageWindowMinutes * 60_000;
const counts = buildEmptySyntheticProfileHitCounts();
for (const profileId of Object.keys(state) as SmartMoneyProfileId[]) {
const retained = state[profileId].filter((ts) => ts >= floorTs);
state[profileId] = retained;
counts[profileId] = retained.length;
}
return counts;
};
export const buildSyntheticDerivedStatus = (
now: number,
control: SyntheticControlState,
state: RollingSyntheticProfileHits
): SyntheticDerivedStatus => {
const session = getSyntheticSessionState(now, control);
return SyntheticDerivedStatusSchema.parse({
session_phase: session.session_phase,
regime: session.regime,
focus_symbols: session.focus_symbols,
profile_hit_counts: getSyntheticProfileHitCounts(
state,
now,
control.coverage_window_minutes
),
coverage_window_minutes: control.coverage_window_minutes
});
};

View file

@ -0,0 +1,69 @@
import { describe, expect, it } from "bun:test";
import { DEFAULT_SYNTHETIC_CONTROL_STATE } from "@islandflow/types";
import {
buildSyntheticDerivedStatus,
createRollingSyntheticProfileHits,
getSyntheticBackendDisabledReason,
getSyntheticProfileHitCounts,
recordSyntheticProfileHit,
resolveSyntheticBackendMode
} from "../src/synthetic-control";
describe("synthetic control backend mode", () => {
it("detects synthetic, mixed, and live hosted modes", () => {
expect(resolveSyntheticBackendMode("synthetic", "synthetic")).toBe("synthetic");
expect(resolveSyntheticBackendMode("synthetic", "alpaca")).toBe("mixed");
expect(resolveSyntheticBackendMode("alpaca", "alpaca")).toBe("live");
});
it("provides a useful disabled reason for non-synthetic modes", () => {
expect(getSyntheticBackendDisabledReason("mixed")).toContain("both hosted ingest adapters");
expect(getSyntheticBackendDisabledReason("live")).toContain("not synthetic");
});
});
describe("synthetic control rolling status", () => {
it("tracks public-profile hits inside the rolling coverage window", () => {
const hits = createRollingSyntheticProfileHits();
recordSyntheticProfileHit(hits, {
primary_profile_id: "event_driven",
source_ts: 1_000
});
recordSyntheticProfileHit(hits, {
primary_profile_id: "event_driven",
source_ts: 60_000
});
recordSyntheticProfileHit(hits, {
primary_profile_id: "arbitrage",
source_ts: 70_000
});
expect(getSyntheticProfileHitCounts(hits, 11 * 60_000, 10)).toEqual({
institutional_directional: 0,
retail_whale: 0,
event_driven: 1,
vol_seller: 0,
arbitrage: 1,
hedge_reactive: 0
});
});
it("builds derived status from the shared session engine", () => {
const hits = createRollingSyntheticProfileHits();
recordSyntheticProfileHit(hits, {
primary_profile_id: "hedge_reactive",
source_ts: Date.parse("2026-01-14T18:00:00Z")
});
const derived = buildSyntheticDerivedStatus(
Date.parse("2026-01-14T18:05:00Z"),
DEFAULT_SYNTHETIC_CONTROL_STATE,
hits
);
expect(derived.coverage_window_minutes).toBe(20);
expect(derived.focus_symbols.length).toBeGreaterThan(0);
expect(derived.profile_hit_counts.hedge_reactive).toBe(1);
});
});

View file

@ -271,6 +271,14 @@ type ClusterState = {
totalPremium: number;
firstPrice: number;
lastPrice: number;
conditions: Set<string>;
specialPrintCount: number;
firstExecutionIv: number | null;
lastExecutionIv: number | null;
minExecutionIv: number | null;
maxExecutionIv: number | null;
firstUnderlyingMid: number | null;
lastUnderlyingMid: number | null;
placements: NbboPlacementCounts;
flushed: boolean;
};
@ -329,6 +337,29 @@ const createPlacementCounts = (): NbboPlacementCounts => ({
stale: 0
});
const SPECIAL_PRINT_CONDITIONS = new Set(["AUCTION", "CROSS", "OPENING", "CLOSING", "COMPLEX", "SPREAD"]);
const SYNTHETIC_EVENT_CONDITION_RE = /^EVENT_(\d+)D$/i;
const normalizeConditions = (conditions: readonly string[] | undefined): string[] =>
(conditions ?? []).map((condition) => condition.trim().toUpperCase()).filter(Boolean);
const hasSpecialCondition = (conditions: readonly string[] | undefined): boolean =>
normalizeConditions(conditions).some((condition) => SPECIAL_PRINT_CONDITIONS.has(condition));
const parseSyntheticEventOffsetDays = (conditions: Iterable<string>): number | null => {
for (const condition of conditions) {
const match = SYNTHETIC_EVENT_CONDITION_RE.exec(condition);
if (!match) {
continue;
}
const days = Number(match[1]);
if (Number.isFinite(days) && days > 0) {
return days;
}
}
return null;
};
const recordPlacement = (counts: NbboPlacementCounts, placement: NbboPlacement): void => {
switch (placement) {
case "AA":
@ -569,6 +600,12 @@ const applyDeliverPolicy = (
const buildCluster = (print: OptionPrint): ClusterState => {
const placements = createPlacementCounts();
const normalizedConditions = normalizeConditions(print.conditions);
const executionIv = typeof print.execution_iv === "number" && Number.isFinite(print.execution_iv) ? print.execution_iv : null;
const executionUnderlyingMid =
typeof print.execution_underlying_mid === "number" && Number.isFinite(print.execution_underlying_mid)
? print.execution_underlying_mid
: null;
recordPlacement(placements, classifyPlacement(print.price, selectNbbo(print.option_contract_id, print.ts)));
return {
contractId: print.option_contract_id,
@ -585,6 +622,14 @@ const buildCluster = (print: OptionPrint): ClusterState => {
totalPremium: print.price * print.size,
firstPrice: print.price,
lastPrice: print.price,
conditions: new Set(normalizedConditions),
specialPrintCount: hasSpecialCondition(print.conditions) ? 1 : 0,
firstExecutionIv: executionIv,
lastExecutionIv: executionIv,
minExecutionIv: executionIv,
maxExecutionIv: executionIv,
firstUnderlyingMid: executionUnderlyingMid,
lastUnderlyingMid: executionUnderlyingMid,
placements,
flushed: false
};
@ -607,6 +652,25 @@ const updateCluster = (cluster: ClusterState, print: OptionPrint): ClusterState
cluster.totalSize += print.size;
cluster.totalPremium += print.price * print.size;
cluster.lastPrice = print.price;
for (const condition of normalizeConditions(print.conditions)) {
cluster.conditions.add(condition);
}
if (hasSpecialCondition(print.conditions)) {
cluster.specialPrintCount += 1;
}
if (typeof print.execution_iv === "number" && Number.isFinite(print.execution_iv)) {
cluster.lastExecutionIv = print.execution_iv;
cluster.minExecutionIv =
cluster.minExecutionIv === null ? print.execution_iv : Math.min(cluster.minExecutionIv, print.execution_iv);
cluster.maxExecutionIv =
cluster.maxExecutionIv === null ? print.execution_iv : Math.max(cluster.maxExecutionIv, print.execution_iv);
}
if (typeof print.execution_underlying_mid === "number" && Number.isFinite(print.execution_underlying_mid)) {
if (cluster.firstUnderlyingMid === null) {
cluster.firstUnderlyingMid = print.execution_underlying_mid;
}
cluster.lastUnderlyingMid = print.execution_underlying_mid;
}
recordPlacement(
cluster.placements,
classifyPlacement(print.price, selectNbbo(print.option_contract_id, print.ts))
@ -836,6 +900,27 @@ const flushCluster = async (
if (cluster.isEtf !== null) {
features.is_etf = cluster.isEtf;
}
if (cluster.conditions.size > 0) {
features.conditions = Array.from(cluster.conditions).sort().join(",");
}
if (cluster.specialPrintCount > 0) {
features.special_print_count = cluster.specialPrintCount;
}
if (cluster.minExecutionIv !== null && cluster.maxExecutionIv !== null) {
features.execution_iv_shock = roundTo(Math.max(0, cluster.maxExecutionIv - cluster.minExecutionIv));
}
if (
cluster.firstUnderlyingMid !== null &&
cluster.lastUnderlyingMid !== null &&
cluster.firstUnderlyingMid > 0
) {
const moveBps = ((cluster.lastUnderlyingMid - cluster.firstUnderlyingMid) / cluster.firstUnderlyingMid) * 10_000;
features.underlying_move_bps = roundTo(moveBps);
}
const syntheticEventOffsetDays = parseSyntheticEventOffsetDays(cluster.conditions);
if (syntheticEventOffsetDays !== null) {
features.corporate_event_ts = cluster.endTs + syntheticEventOffsetDays * 86_400_000;
}
const placementTotal =
cluster.placements.aa +

View file

@ -46,6 +46,7 @@ export type StructurePacketPlan = {
nbboAggressiveBuyRatio: number;
nbboAggressiveSellRatio: number;
nbboAggressiveRatio: number;
sameSizeLegSymmetry: number;
source_ts: number;
ingest_ts: number;
seq: number;
@ -132,6 +133,19 @@ const dayDiff = (from: string | null, to: string | null): number | null => {
return Math.round(diffMs / 86_400_000);
};
const sameSizeLegSymmetry = (legs: LegEvidence[]): number => {
const sizes = legs.map((leg) => leg.totalSize).filter((value) => Number.isFinite(value) && value > 0);
if (sizes.length < 2) {
return 0;
}
const min = Math.min(...sizes);
const max = Math.max(...sizes);
if (!Number.isFinite(min) || !Number.isFinite(max) || max <= 0) {
return 0;
}
return min / max;
};
export const shouldEmitStructurePacket = (legs: LegEvidence[], currentLegContractId: string): boolean => {
if (legs.length < 2) {
return false;
@ -250,6 +264,7 @@ export const planStructurePacket = (
nbboAggressiveBuyRatio,
nbboAggressiveSellRatio,
nbboAggressiveRatio,
sameSizeLegSymmetry: roundTo(sameSizeLegSymmetry(legs)),
source_ts: Number.isFinite(source_ts) ? source_ts : 0,
ingest_ts,
seq
@ -320,6 +335,7 @@ export const buildStructureFlowPacket = (
features.nbbo_aggressive_buy_ratio = roundTo(plan.nbboAggressiveBuyRatio);
features.nbbo_aggressive_sell_ratio = roundTo(plan.nbboAggressiveSellRatio);
features.nbbo_aggressive_ratio = roundTo(plan.nbboAggressiveRatio);
features.same_size_leg_symmetry = roundTo(plan.sameSizeLegSymmetry);
const join_quality: Record<string, number> = {
nbbo_coverage_ratio: roundTo(plan.nbboCoverageRatio)

View file

@ -130,6 +130,7 @@ describe("structure packet planning", () => {
expect(packet.features.nbbo_bb_count).toBe(1);
expect(packet.features.nbbo_mid_count).toBe(1);
expect(packet.features.nbbo_coverage_ratio).toBeCloseTo(1, 6);
expect(packet.features.same_size_leg_symmetry).toBeCloseTo(0.5, 4);
// 2 aggressive (AA + BB) out of 3 classified (AA + BB + MID)
expect(packet.features.nbbo_aggressive_ratio).toBeCloseTo(2 / 3, 4);

View file

@ -1,7 +1,10 @@
import {
SP500_SYMBOLS,
getSyntheticSessionState,
getSyntheticUnderlyingState,
type EquityPrint,
type EquityQuote,
type SyntheticControlState,
type SyntheticMarketMode
} from "@islandflow/types";
import type { EquityIngestAdapter, EquityIngestHandlers } from "./types";
@ -9,34 +12,14 @@ import type { EquityIngestAdapter, EquityIngestHandlers } from "./types";
type SyntheticEquitiesAdapterConfig = {
emitIntervalMs: number;
mode: SyntheticMarketMode;
getControl: () => SyntheticControlState;
};
const EXCHANGES = ["NYSE", "NASDAQ", "ARCA", "BATS", "IEX", "TEST"];
const EXCHANGES = ["NYSE", "NASDAQ", "ARCA", "BATS", "IEX", "MEMX"];
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 SYNTHETIC_SYMBOLS = ["SPY", ...(SP500_SYMBOLS as readonly string[])];
const hashSymbol = (value: string): number => {
let hash = 0;
for (let i = 0; i < value.length; i += 1) {
hash = (hash * 31 + value.charCodeAt(i)) >>> 0;
}
return hash;
};
type PricePlacement = "MID" | "A" | "AA" | "B" | "BB";
const buildSyntheticPrint = (
seq: number,
@ -46,20 +29,18 @@ const buildSyntheticPrint = (
size: number,
exchange: string,
offExchangeFlag: boolean
): EquityPrint => {
return {
source_ts: now,
ingest_ts: now,
seq,
trace_id: `synthetic-equities-${seq}`,
ts: now,
underlying_id: symbol,
price,
size,
exchange,
offExchangeFlag
};
};
): EquityPrint => ({
source_ts: now,
ingest_ts: now,
seq,
trace_id: `synthetic-equities-${seq}`,
ts: now,
underlying_id: symbol,
price,
size,
exchange,
offExchangeFlag
});
const buildSyntheticQuote = (
seq: number,
@ -67,32 +48,18 @@ const buildSyntheticQuote = (
symbol: string,
bid: number,
ask: number
): EquityQuote => {
return {
source_ts: now,
ingest_ts: now,
seq,
trace_id: `synthetic-equity-quote-${seq}`,
ts: now,
underlying_id: symbol,
bid,
ask
};
};
): EquityQuote => ({
source_ts: now,
ingest_ts: now,
seq,
trace_id: `synthetic-equity-quote-${seq}`,
ts: now,
underlying_id: symbol,
bid,
ask
});
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 formatPrice = (value: number): number => Number(value.toFixed(2));
const priceForPlacement = (
mid: number,
@ -100,7 +67,6 @@ const priceForPlacement = (
placement: PricePlacement
): number => {
const { bid, ask, epsilon } = quote;
let price = mid;
switch (placement) {
case "AA":
@ -120,44 +86,83 @@ const priceForPlacement = (
price = mid;
break;
}
return formatPrice(Math.max(0.01, price));
};
const buildQuoteContext = (
symbol: string,
now: number,
control: SyntheticControlState
) => {
const session = getSyntheticSessionState(now, control);
const state = getSyntheticUnderlyingState(symbol, now, control, session);
return {
session,
state,
mid: state.mid,
bid: formatPrice(state.bid),
ask: formatPrice(state.ask),
spread: state.spread,
epsilon: Math.max(0.01, state.spread * 0.08)
};
};
const pickPrimaryPlacement = (
driftBps: number,
regime: ReturnType<typeof getSyntheticSessionState>["regime"],
seq: number
): PricePlacement => {
if (regime === "dealer_gamma") {
return seq % 4 === 0 ? "A" : seq % 3 === 0 ? "B" : "MID";
}
if (regime === "arb_calm" || regime === "mean_revert") {
return seq % 11 === 0 ? "A" : seq % 13 === 0 ? "B" : "MID";
}
if (regime === "event_ramp" || regime === "retail_chase") {
if (driftBps >= 0) {
return seq % 3 === 0 ? "AA" : "A";
}
return seq % 3 === 0 ? "BB" : "B";
}
if (driftBps >= 0) {
return seq % 5 === 0 ? "A" : "MID";
}
return seq % 5 === 0 ? "B" : "MID";
};
const pickDarkPlacement = (
driftBps: number,
regime: ReturnType<typeof getSyntheticSessionState>["regime"],
seq: number
): PricePlacement => {
if (regime === "dealer_gamma") {
return seq % 2 === 0 ? "A" : "B";
}
if (regime === "arb_calm" || regime === "mean_revert") {
return "MID";
}
if (regime === "event_ramp" || regime === "retail_chase") {
return driftBps >= 0 ? (seq % 2 === 0 ? "A" : "AA") : seq % 2 === 0 ? "B" : "BB";
}
return driftBps >= 0 ? "A" : "B";
};
export const createSyntheticEquitiesAdapter = (
config: SyntheticEquitiesAdapterConfig
): EquityIngestAdapter => {
const profile =
const throughput =
config.mode === "firehose"
? {
batchSize: 10,
darkEvery: true,
offExchangeMod: 2,
litSizeBase: 40,
litSizeRange: 1400
}
? { batchSize: 10, litSizeBase: 48, litSizeRange: 1800, darkSizeBase: 2800 }
: config.mode === "active"
? {
batchSize: 5,
darkEvery: true,
offExchangeMod: 4,
litSizeBase: 20,
litSizeRange: 900
}
: {
batchSize: 2,
darkEvery: false,
offExchangeMod: 8,
litSizeBase: 10,
litSizeRange: 300
};
? { batchSize: 5, litSizeBase: 22, litSizeRange: 980, darkSizeBase: 1800 }
: { batchSize: 2, litSizeBase: 12, litSizeRange: 340, darkSizeBase: 900 };
return {
name: "synthetic",
start: (handlers: EquityIngestHandlers) => {
let seq = 0;
let quoteSeq = 0;
let darkStep = 0;
let darkSymbolIndex = 0;
let symbolCursor = 0;
let timer: ReturnType<typeof setInterval> | null = null;
let stopped = false;
@ -167,84 +172,113 @@ export const createSyntheticEquitiesAdapter = (
}
const now = Date.now();
const batchSize = profile.batchSize;
const control = config.getControl();
const session = getSyntheticSessionState(now, control);
const focusSymbols =
session.focus_symbols.length > 0 ? session.focus_symbols : SYNTHETIC_SYMBOLS.slice(0, 3);
const focusSet = new Set(focusSymbols);
const allowDark =
config.mode !== "realistic" ||
session.regime === "event_ramp" ||
session.regime === "dealer_gamma" ||
session.regime === "retail_chase";
const darkSymbol = SYNTHETIC_SYMBOLS[darkSymbolIndex % SYNTHETIC_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 (profile.darkEvery) {
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 = config.mode === "firehose" ? 4000 : 2600;
if (scenario === "buy") {
darkPlacement = darkStep % 2 === 0 ? "A" : "AA";
darkSize = config.mode === "firehose" ? 1500 : 800;
} else if (scenario === "sell") {
darkPlacement = darkStep % 2 === 0 ? "B" : "BB";
darkSize = config.mode === "firehose" ? 1500 : 800;
}
const darkPrice = priceForPlacement(darkMid, darkQuote, darkPlacement);
const darkPrint = buildSyntheticPrint(
seq,
darkTs,
darkSymbol,
darkPrice,
darkSize,
DARK_EXCHANGE,
true
if (allowDark) {
const darkSymbol = focusSymbols[seq % focusSymbols.length] ?? SYNTHETIC_SYMBOLS[symbolCursor % SYNTHETIC_SYMBOLS.length]!;
const darkQuote = buildQuoteContext(darkSymbol, now, control);
const darkPlacement = pickDarkPlacement(
darkQuote.state.driftBps,
session.regime,
seq + 1
);
const darkBias = darkQuote.state.offExchangeBias;
const darkSize = Math.max(
250,
Math.round(
throughput.darkSizeBase *
(0.65 + darkBias * 0.9 + darkQuote.state.sessionVolatility * 0.2)
)
);
void handlers.onTrade(darkPrint);
darkStep += 1;
if (darkStep >= DARK_SEQUENCE.length) {
darkStep = 0;
darkSymbolIndex += 1;
}
}
for (let i = 0; i < batchSize; i += 1) {
seq += 1;
const symbol = SYNTHETIC_SYMBOLS[(seq + i) % SYNTHETIC_SYMBOLS.length];
const symbolHash = hashSymbol(symbol);
const basePrice = 25 + (symbolHash % 475);
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 = profile.litSizeBase + (seq % profile.litSizeRange);
const exchange = EXCHANGES[(seq + symbolHash) % EXCHANGES.length];
const offExchangeFlag = (seq + i) % profile.offExchangeMod === 0;
const eventTs = now + i * 4;
if (handlers.onQuote) {
quoteSeq += 1;
const quoteEventTs = eventTs - 2;
const quoteEvent = buildSyntheticQuote(quoteSeq, quoteEventTs, symbol, quote.bid, quote.ask);
void handlers.onQuote(quoteEvent);
void handlers.onQuote(
buildSyntheticQuote(
quoteSeq,
now - 2,
darkSymbol,
darkQuote.bid,
darkQuote.ask
)
);
}
const print = buildSyntheticPrint(seq, eventTs, symbol, price, size, exchange, offExchangeFlag);
void handlers.onTrade(print);
seq += 1;
void handlers.onTrade(
buildSyntheticPrint(
seq,
now,
darkSymbol,
priceForPlacement(darkQuote.mid, darkQuote, darkPlacement),
darkSize,
DARK_EXCHANGE,
true
)
);
}
for (let i = 0; i < throughput.batchSize; i += 1) {
seq += 1;
const symbol =
i < focusSymbols.length
? focusSymbols[i]!
: SYNTHETIC_SYMBOLS[(symbolCursor + i) % SYNTHETIC_SYMBOLS.length]!;
const eventTs = now + i * 4;
const quote = buildQuoteContext(symbol, eventTs, control);
const clustered = focusSet.has(symbol);
const placement = pickPrimaryPlacement(
quote.state.driftBps,
session.regime,
seq + i
);
const exchange = EXCHANGES[(seq + symbol.charCodeAt(0) + i) % EXCHANGES.length]!;
const baseSize =
throughput.litSizeBase +
((seq + i) % throughput.litSizeRange) +
Math.round(quote.state.sessionVolatility * 140);
const size = clustered
? Math.round(baseSize * (1 + quote.state.clusteringScore * 0.35))
: baseSize;
const offExchangeFlag =
((seq + i * 3) % 10) / 10 < quote.state.offExchangeBias * (clustered ? 1.12 : 0.86);
if (handlers.onQuote) {
quoteSeq += 1;
void handlers.onQuote(
buildSyntheticQuote(
quoteSeq,
eventTs - 2,
symbol,
quote.bid,
quote.ask
)
);
}
void handlers.onTrade(
buildSyntheticPrint(
seq,
eventTs,
symbol,
priceForPlacement(quote.mid, quote, placement),
size,
exchange,
offExchangeFlag
)
);
}
symbolCursor = (symbolCursor + throughput.batchSize) % SYNTHETIC_SYMBOLS.length;
};
timer = setInterval(emit, config.emitIntervalMs);

View file

@ -6,7 +6,10 @@ import {
STREAM_EQUITY_PRINTS,
STREAM_EQUITY_QUOTES,
connectJetStreamWithRetry,
ensureSyntheticControlState,
ensureKnownStreams,
openSyntheticControlKv,
watchSyntheticControlState,
publishJson
} from "@islandflow/bus";
import {
@ -19,9 +22,11 @@ import {
import {
EquityPrintSchema,
EquityQuoteSchema,
DEFAULT_SYNTHETIC_CONTROL_STATE,
resolveSyntheticMarketModes,
type EquityPrint,
type EquityQuote
type EquityQuote,
type SyntheticControlState
} from "@islandflow/types";
import { createAlpacaEquitiesAdapter } from "./adapters/alpaca";
import { createSyntheticEquitiesAdapter } from "./adapters/synthetic";
@ -157,11 +162,15 @@ const parseSymbolList = (value: string): string[] => {
.filter(Boolean);
};
const selectAdapter = (name: string): EquityIngestAdapter => {
const selectAdapter = (
name: string,
getSyntheticControl: () => SyntheticControlState
): EquityIngestAdapter => {
if (name === "synthetic") {
return createSyntheticEquitiesAdapter({
emitIntervalMs: env.EMIT_INTERVAL_MS,
mode: syntheticModes.equities
mode: syntheticModes.equities,
getControl: getSyntheticControl
});
}
@ -196,6 +205,24 @@ const run = async () => {
await ensureKnownStreams(jsm, [STREAM_EQUITY_PRINTS, STREAM_EQUITY_QUOTES], { logger });
let syntheticControl = DEFAULT_SYNTHETIC_CONTROL_STATE;
let stopSyntheticControlWatch = async () => {};
if (env.EQUITIES_INGEST_ADAPTER === "synthetic") {
const syntheticControlKv = await openSyntheticControlKv(js);
syntheticControl = await ensureSyntheticControlState(syntheticControlKv);
stopSyntheticControlWatch = await watchSyntheticControlState(
syntheticControlKv,
(nextControl) => {
syntheticControl = nextControl;
},
(error) => {
logger.warn("synthetic control watch failed", {
error: getErrorMessage(error)
});
}
);
}
const clickhouse = createClickHouseClient({
url: env.CLICKHOUSE_URL,
database: env.CLICKHOUSE_DATABASE
@ -206,7 +233,10 @@ const run = async () => {
await ensureEquityQuotesTable(clickhouse);
});
const adapter = selectAdapter(env.EQUITIES_INGEST_ADAPTER);
const adapter = selectAdapter(
env.EQUITIES_INGEST_ADAPTER,
() => syntheticControl
);
logger.info("ingest adapter selected", { adapter: adapter.name });
const allowPublish = buildThrottle(env.TESTING_MODE, env.TESTING_THROTTLE_MS);
const allowQuotePublish = buildThrottle(env.TESTING_MODE, env.TESTING_THROTTLE_MS);
@ -274,6 +304,7 @@ const run = async () => {
state.shuttingDown = true;
state.shutdownPromise = (async () => {
logger.info("service stopping", { signal });
await stopSyntheticControlWatch();
await stopAdapter();
try {

File diff suppressed because it is too large Load diff

View file

@ -11,9 +11,12 @@ import {
STREAM_OPTION_SIGNAL_PRINTS,
buildDurableConsumer,
connectJetStreamWithRetry,
ensureSyntheticControlState,
ensureKnownStreams,
openSyntheticControlKv,
publishJson,
subscribeJson
subscribeJson,
watchSyntheticControlState
} from "@islandflow/bus";
import {
createClickHouseClient,
@ -26,12 +29,14 @@ import {
OptionNBBOSchema,
OptionPrintSchema,
EquityQuoteSchema,
DEFAULT_SYNTHETIC_CONTROL_STATE,
deriveOptionPrintMetadata,
resolveSyntheticMarketModes,
type EquityQuote,
type OptionNBBO,
type OptionPrint,
type OptionsSignalConfig
type OptionsSignalConfig,
type SyntheticControlState
} from "@islandflow/types";
import { createAlpacaOptionsAdapter } from "./adapters/alpaca";
import { createDatabentoOptionsAdapter } from "./adapters/databento";
@ -259,11 +264,15 @@ const retry = async <T>(
throw lastError ?? new Error(`${label} failed after retries`);
};
const selectAdapter = (name: string): OptionIngestAdapter => {
const selectAdapter = (
name: string,
getSyntheticControl: () => SyntheticControlState
): OptionIngestAdapter => {
if (name === "synthetic") {
return createSyntheticOptionsAdapter({
emitIntervalMs: env.EMIT_INTERVAL_MS,
mode: syntheticModes.options
mode: syntheticModes.options,
getControl: getSyntheticControl
});
}
@ -351,6 +360,24 @@ const run = async () => {
{ logger }
);
let syntheticControl = DEFAULT_SYNTHETIC_CONTROL_STATE;
let stopSyntheticControlWatch = async () => {};
if (env.OPTIONS_INGEST_ADAPTER === "synthetic") {
const syntheticControlKv = await openSyntheticControlKv(js);
syntheticControl = await ensureSyntheticControlState(syntheticControlKv);
stopSyntheticControlWatch = await watchSyntheticControlState(
syntheticControlKv,
(nextControl) => {
syntheticControl = nextControl;
},
(error) => {
logger.warn("synthetic control watch failed", {
error: getErrorMessage(error)
});
}
);
}
const clickhouse = createClickHouseClient({
url: env.CLICKHOUSE_URL,
database: env.CLICKHOUSE_DATABASE
@ -361,7 +388,10 @@ const run = async () => {
await ensureOptionNBBOTable(clickhouse);
});
const adapter = selectAdapter(env.OPTIONS_INGEST_ADAPTER);
const adapter = selectAdapter(
env.OPTIONS_INGEST_ADAPTER,
() => syntheticControl
);
logger.info("ingest adapter selected", { adapter: adapter.name });
const allowPublish = buildThrottle(env.TESTING_MODE, env.TESTING_THROTTLE_MS);
const allowNbboPublish = buildThrottle(env.TESTING_MODE, env.TESTING_THROTTLE_MS);
@ -482,6 +512,7 @@ const run = async () => {
state.shutdownPromise = (async () => {
logger.info("service stopping", { signal });
clearInterval(pruneTimer);
await stopSyntheticControlWatch();
await stopAdapter();
try {

View file

@ -10,26 +10,43 @@ import {
} from "../src/adapters/synthetic";
const totalBurstNotional = (burst: {
basePrice: number;
baseSize: number;
printCount: number;
}): number => burst.basePrice * burst.baseSize * burst.printCount * 100;
legs: Array<{
basePrice: number;
baseSize: number;
}>;
cycles: number;
}): number =>
burst.legs.reduce((sum, leg) => sum + leg.basePrice * leg.baseSize * burst.cycles * 100, 0);
const findBurst = (
mode: "realistic" | "active",
scenarioId: string,
now = Date.UTC(2026, 0, 2)
) => {
for (let i = 1; i <= 360; i += 1) {
const burst = buildSyntheticBurstForTest(i, now + i * 1_000, mode);
if (burst.scenarioId === scenarioId) {
return burst;
}
}
throw new Error(`Unable to find synthetic scenario ${scenarioId} in mode ${mode}`);
};
describe("synthetic options burst sizing", () => {
it("keeps realistic-mode ask lifts inside the configured notional band", () => {
const burst = buildSyntheticBurstForTest(2, Date.UTC(2026, 0, 2), "realistic");
it("keeps realistic-mode ask-lift accumulation inside the configured notional band", () => {
const burst = findBurst("realistic", "ask_lift_accumulation");
expect(burst.scenarioId).toBe("ask_lift");
expect(totalBurstNotional(burst)).toBeGreaterThanOrEqual(9_000);
expect(totalBurstNotional(burst)).toBeLessThanOrEqual(35_000);
expect(burst.scenarioId).toBe("ask_lift_accumulation");
expect(totalBurstNotional(burst)).toBeGreaterThanOrEqual(12_000);
expect(totalBurstNotional(burst)).toBeLessThanOrEqual(90_000);
});
it("keeps active-mode sweeps inside the configured notional band", () => {
const burst = buildSyntheticBurstForTest(1, Date.UTC(2026, 0, 2), "active");
it("keeps active-mode call sweeps inside the configured notional band", () => {
const burst = findBurst("active", "call_sweep");
expect(burst.scenarioId).toBe("bearish_sweep");
expect(totalBurstNotional(burst)).toBeGreaterThanOrEqual(120_000);
expect(totalBurstNotional(burst)).toBeLessThanOrEqual(240_000);
expect(burst.scenarioId).toBe("call_sweep");
expect(totalBurstNotional(burst)).toBeGreaterThanOrEqual(70_000);
expect(totalBurstNotional(burst)).toBeLessThanOrEqual(420_000);
});
});
@ -114,7 +131,7 @@ describe("synthetic smart-money scenarios", () => {
it("scores each labeled scenario as its intended primary profile", () => {
const now = Date.parse("2026-01-02T15:00:00Z");
const scenarios = listSyntheticSmartMoneyScenariosForTest().filter(
(scenario) => scenario.hiddenLabel !== "neutral_noise"
(scenario) => scenario.label !== "neutral_noise"
);
for (const scenario of scenarios) {
@ -122,17 +139,62 @@ describe("synthetic smart-money scenarios", () => {
const event = buildSmartMoneyEventFromPacket(packet);
const winningScore = event.profile_scores[0];
const nearbyWrongScores = event.profile_scores.filter(
(score) => score.profile_id !== hiddenLabel && score.probability >= 0.5
(score) => score.profile_id !== scenario.label && score.probability >= 0.5
);
expect(event.abstained, scenario.id).toBe(false);
expect(event.primary_profile_id, scenario.id).toBe(hiddenLabel);
expect(winningScore?.profile_id, scenario.id).toBe(hiddenLabel);
expect(event.primary_profile_id, scenario.id).toBe(scenario.label);
expect(winningScore?.profile_id, scenario.id).toBe(scenario.label);
expect(winningScore?.probability ?? 0, scenario.id).toBeGreaterThanOrEqual(0.5);
expect(hiddenLabel.length, scenario.id).toBeGreaterThan(0);
expect(nearbyWrongScores, scenario.id).toEqual([]);
}
});
it("covers every smart-money label in active runtime mode over a deterministic sample", () => {
const seen = new Set<string>();
const now = Date.parse("2026-01-02T15:00:00Z");
for (let i = 1; i <= 120; i += 1) {
const burst = buildSyntheticBurstForTest(i, now + i * 1_000, "active");
seen.add(burst.label);
}
expect(seen).toEqual(
new Set([
"institutional_directional",
"retail_whale",
"event_driven",
"vol_seller",
"arbitrage",
"hedge_reactive",
"neutral_noise"
])
);
});
it("covers every smart-money label in realistic mode within a default twenty-minute window", () => {
const seen = new Set<string>();
const now = Date.parse("2026-01-02T15:00:00Z");
for (let i = 1; i <= 120; i += 1) {
const burst = buildSyntheticBurstForTest(i, now + i * 10_000, "realistic");
seen.add(burst.label);
}
expect(seen).toEqual(
new Set([
"institutional_directional",
"retail_whale",
"event_driven",
"vol_seller",
"arbitrage",
"hedge_reactive",
"neutral_noise"
])
);
});
it("keeps neutral background noise below the emission threshold", () => {
const { packet } = buildSyntheticFlowPacketForTest(
"neutral_noise",