Plan · Standard HTML Version

# Hosted Synthetic Tape Redesign

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.

Main outcome More believable synthetic options, equities, quotes, and smart-money events, with softer coverage guarantees and stronger cross-asset coupling.
User-facing change An internal-only bottom-right gear opens a compact synthetic-control drawer for operators.
Public API impact No change to existing smart-money event types or public smart-money endpoints.
Why it matters The current tape reaches the categories, but it looks too templated and too clean in ways that weaken the demo.

## Simplified Summary

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.

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.

The public smart-money taxonomy stays the same: `institutional_directional`, `retail_whale`, `event_driven`, `vol_seller`, `arbitrage`, and `hedge_reactive`.

## Scope

In scope

- Hosted synthetic market regime engine
- Options and equities synthetic generator redesign
- Hidden subtype scenario families
- Soft coverage logic
- Internal control API and UI
- Documentation and tests for the new behavior

Out of scope

- Changing public smart-money profile IDs
- General settings page work
- User profile or token-spend features
- Live market feed changes
- Public-facing simulator controls

## Services Affected

Area | Primary files | Role in the redesign
--- | --- | ---
Shared types | `packages/types/src/synthetic-market.ts`, `packages/types/src/events.ts` | Add the shared regime model and internal synthetic-control schemas.
Hosted API | `services/api/src/index.ts` | Add internal control endpoints and expose hosted simulator status.
Options ingest | `services/ingest-options/src/index.ts`, `services/ingest-options/src/adapters/synthetic.ts` | Adopt the new regime engine, scenario families, and soft coverage logic.
Equities ingest | `services/ingest-equities/src/index.ts`, `services/ingest-equities/src/adapters/synthetic.ts` | Synchronize synthetic quotes and prints with the same latent market regime.
Web and Electron shell | `apps/web/app/terminal.tsx`, `apps/web/app/api/admin/synthetic/*` | Add the internal gear-triggered control drawer and server-side proxy routes.
Tests | `services/ingest-options/tests/synthetic.test.ts`, web tests, API tests | Protect classification alignment, determinism, coverage behavior, and control-plane behavior.

## Product Decisions Locked

- Keep the current six public smart-money categories.
- Add richer hidden sub-scenarios beneath them.
- Use soft coverage guarantees, not hard forced sequencing.
- Prioritize cross-asset coupling first.
- Controls affect the hosted synthetic backend.
- Controls are internal-only.
- Do not build a general settings page, user-info work, or token-spend work in this effort.
- Use a bottom-right gear that opens a synthetic-control drawer.

## Architecture

### 1. Replace the simple burst pulse with a shared regime engine

Expand `packages/types/src/synthetic-market.ts` into the shared deterministic market-state engine used by both ingest services.

Shared functions:

- `getSyntheticSessionState(ts, control)`
- `getSyntheticUnderlyingState(symbol, ts, control, sessionState)`
- `getSyntheticScenarioWeights(symbol, ts, control, sessionState)`
- `getSyntheticCoverageBoost(profileId, coverageState, control)`

`sessionState` includes:

- `session_phase`: `open | midday | power_hour | after_event`
- `regime`: `trend_up | trend_down | mean_revert | retail_chase | event_ramp | dealer_gamma | arb_calm`
- `volatility_level`
- `liquidity_level`
- `quote_cleanliness`
- `focus_symbols`
- `event_symbols`
- `seed_bucket`

### 2. Add hosted synthetic control state

Add internal control schemas in `packages/types`:

- `SyntheticControlPresetId`
- `SyntheticControlState`
- `SyntheticProfileWeightMap`
- `SyntheticCoverageConfig`
- `SyntheticDerivedStatus`

    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;
    };

Defaults:

- `preset_id: balanced_demo`
- `coverage_assist: true`
- `coverage_window_minutes: 20`
- All profile weights `1.0`

### 3. Persist and distribute control state through NATS

- Use JetStream KV bucket `synthetic_control`
- Use key `global`
- `services/api` reads and writes the KV entry
- `services/ingest-options` loads on boot and watches for updates
- `services/ingest-equities` does the same

### 4. Rebuild options scenarios as hidden subtype families

Keep public profiles the same, but generate them through richer hidden subtype families.

- `institutional_directional`: `call_sweep`, `put_sweep`, `ask_lift_accumulation`, `far_dated_conviction`
- `retail_whale`: `0dte_call_chase`, `short_dated_put_panic`, `attention_contract_spike`
- `event_driven`: `earnings_vol_probe`, `pre_event_directional_ramp`, `post_gap_followthrough`
- `vol_seller`: `covered_call_overwrite`, `cash_secured_put_write`, `short_straddle_harvest`
- `arbitrage`: `parity_vertical`, `conversion_reversal`, `box_spread`
- `hedge_reactive`: `gamma_pinch_call_hedge`, `reactive_put_wall`, `dealer_unwind`
- `neutral_noise`: `single_print_mid`, `two_sided_scalp`, `stale_quote_noise`

### 5. Make equities and options react to the same latent state

Equities changes:

- Remove the fixed dark-sequence loop
- Make lit versus dark balance regime-dependent
- Make spread, quote cleanliness, off-exchange frequency, and clustering regime-dependent
- Use shared focus symbols
- During `event_ramp` and `retail_chase`, create modest trend and wider quotes
- During `dealer_gamma`, create choppier reversals and denser quote changes
- During `arb_calm`, create quieter underlying motion and more neutral execution context

Options changes:

- Replace hardcoded coverage forcing with weighted family selection plus coverage debt
- Make venue count, placement, stale or missing quote probability, and structure prevalence regime-sensitive
- Derive `execution_iv_shock`, `underlying_move_bps`, and `nbbo_spread_z` from shared state
- Generate event-driven timestamps and symbols from shared regime state

### 6. Add soft coverage accounting

- Track rolling coverage debt per public profile inside each ingest service
- Maintain a rolling counter across the selected `coverage_window_minutes`
- Only public profiles count toward coverage
- Missing profiles get a bounded weight boost
- Noise and low-key scenarios continue to appear between labeled bursts

### 7. Add internal hosted control endpoints

Add routes in `services/api/src/index.ts`:

- `GET /admin/synthetic/status`
- `GET /admin/synthetic/control`
- `PUT /admin/synthetic/control`

    {
      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<SmartMoneyProfileId, number>;
        coverage_window_minutes: number;
      } | null;
      disabled_reason?: string;
    }

Behavior:

- Return `404` when admin mode is disabled
- Return `409` when hosted adapters are not synthetic
- Validate full payloads on `PUT`
- Keep all existing public smart-money, history, replay, and websocket endpoints unchanged

### 8. Keep secrets out of the browser with Next.js proxy routes

Add server-side proxy routes:

- `apps/web/app/api/admin/synthetic/status/route.ts`
- `apps/web/app/api/admin/synthetic/control/route.ts`

Proxy behavior:

- Read server-only `SYNTHETIC_ADMIN_TOKEN`
- Forward to backend admin endpoints at `NEXT_PUBLIC_API_URL`
- Return `404` when the internal UI flag is off
- Never send the token to the browser

### 9. Add an internal control surface

UI surface:

- A small floating gear button in the bottom-right corner
- Opens a right-edge non-modal drawer
- Internal-only visibility

Drawer sections:

- `Preset`
- `Coverage`
- `Profile Bias`
- `Live Status`

Controls:

- Preset dropdown: `Balanced Demo`, `Event Day`, `Dealer Day`, `Retail Chase`, `Quiet Range`
- Coverage assist toggle
- Coverage window selector: `10m`, `20m`, `30m`
- Six profile weight controls with `Low`, `Normal`, `High`

## Public and Internal Interfaces

### Public contracts unchanged

- `SmartMoneyProfileId`
- `SmartMoneyEvent`
- `/flow/smart-money`
- `/history/smart-money`
- `/replay/smart-money`
- `/ws/smart-money`

### New internal contracts

- `SyntheticControlState`
- `SyntheticControlPresetId`
- `SyntheticDerivedStatus`

### New internal endpoints

- `GET /admin/synthetic/status`
- `GET /admin/synthetic/control`
- `PUT /admin/synthetic/control`

### New environment variables

Backend:

- `SYNTHETIC_CONTROL_ENABLED=0|1`
- `SYNTHETIC_ADMIN_TOKEN=...`

Web:

- `NEXT_PUBLIC_SYNTHETIC_ADMIN=0|1`
- `SYNTHETIC_ADMIN_TOKEN=...` for the Next server proxy only

## Implementation Sequence

1. Phase 1. Shared types and regime engine

   Touch `packages/types/src/synthetic-market.ts` and related exports and tests. Deliver control schemas, preset definitions, deterministic session and regime functions, and coverage boost helpers.

2. Phase 2. Hosted control plane

   Touch `services/api/src/index.ts` and NATS or KV helpers as needed. Deliver admin endpoints, KV persistence, status payloads, and disabled or error behavior.

3. Phase 3. Ingest service coupling

   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.

4. Phase 4. Internal control UI

   Touch `apps/web/app/terminal.tsx` and the internal admin proxy routes. Deliver the floating gear, non-modal drawer, polling, optimistic updates, and disabled state.

5. Phase 5. Regression and realism tests

   Deliver determinism tests, control API tests, scenario coverage tests, UI visibility tests, and classifier-alignment tests for hidden subtype families.

## Test Cases and Scenarios

### Shared engine

- Same `timestamp + control snapshot + seed` yields the same regime and focus symbols in both ingest services.
- Presets materially change regime weights without breaking determinism.
- `balanced_demo` yields mixed regimes over a session.
- `quiet_range` yields lower volatility, tighter spreads, and fewer labeled events than `retail_chase`.

### Cross-asset coupling

- `event_ramp` produces event-aligned option scenarios and synchronized underlying drift and spread behavior.
- `dealer_gamma` produces short-dated ATM-heavy options plus choppier underlying reversals.
- `arb_calm` increases neutral multi-leg structures without strong directional underlying moves.
- `retail_chase` increases short-dated OTM call behavior, IV shock, and louder underlying momentum.

### Coverage behavior

- With default controls, every public smart-money profile appears at least once in a 20-minute synthetic session sample.
- With `coverage_assist=false`, there is no forced coverage logic.
- Raising one profile to `High` increases its frequency without starving all other categories.
- The quiet preset still emits noise and occasional signals rather than a dead tape.

### Classification alignment

- Each hidden subtype family still classifies primarily into its intended public profile.
- Neutral noise remains below the smart-money emission threshold.
- Nearby wrong profiles stay below threshold in subtype template tests.

### Admin API and UI

- Disabled admin mode returns `404`.
- Non-synthetic hosted mode returns `409` with a useful reason.
- Valid `PUT` persists to KV and becomes visible to both ingest services.
- The floating gear is hidden when `NEXT_PUBLIC_SYNTHETIC_ADMIN` is off.
- The browser client never receives the backend admin token.

## Assumptions and Defaults

- Hosted synthetic control applies only when both options and equities ingest adapters are synthetic.
- No general settings page, user-info work, or token-spend work is in scope here.
- Hidden subtype labels remain internal and test-only and never attach to emitted prints.
- The first pass uses polling for admin status rather than a new admin websocket.
- The default operator experience is `Balanced Demo` with soft coverage on and a 20-minute window.
- The repo currently lacks local `PRODUCT.md`, `DESIGN.md`, 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.