Polytraders Dev Guide
Internal
Phase 1 · operator surface Where this lands: the operator app, after the rails are runtime-live. See the plan →
HomeDevelopers › Operator UX

Operator UX

Operator surface Spec Frontend Phase 1 — 2 weeks

A frozen spec for the next iteration of the operator surface. The signal tab and TradingView-style overlay Artem demoed are the right foundation. This page documents how to evolve it from a viewer of bot signals into a console that supervises bots. Everything below uses data we already have, surfaced differently.

0.A note to the team #

The current signal tab does the hardest part of this problem: it makes invisible bot behaviour visible. Plotting bot signals on the same canvas as price action is the right instinct. The strategy filtering, date filtering, and status workflow are also a strong base — the underlying data model is clearly there, which is the part that actually takes time.

What follows is not a redesign. It is a sketch of where the existing foundation should evolve next, with a single goal: move from viewer of bot signals to trading-ops console that supervises bots. Almost every suggestion below uses data we already have. The work is mostly assembly and UX, not new data infrastructure.

1.Why this model representation #

Six reasons the proposed layout matches what Polytraders actually does.

1

It matches the job, not the metaphor

TradingView is built for humans clicking buy and sell. Our users are supervising autonomous agents. Different job, different screen.

2

It serves three audiences at once

Quant wants why did it fire. Ops wants are we within limits. Engineer wants is latency healthy. Each gets a home zone.

3

It encodes a visual hierarchy of importance

Top strip = survival. Left rail = portfolio. Centre = supervision. Bottom = plumbing. Matches how a desk actually scans during a live event.

4

It makes bot decisions auditable

The current view shows that something happened. The decision drawer shows why — model probability, top features, risk checks, counterfactual P&L.

5

It uses our one differentiating asset

Real-world event timelines. Pistol won, bomb planted, MVP clutch, state called. This is the moat — no generic trading dashboard has it.

6

It is honest about uncertainty

Binary resolution markets are not continuous price series. A probability line with a confidence band speaks the right language. Candles stay as a secondary view.

2.Jobs to be done #

Two columns. One is what the current screen optimises for. The other is what we need.

What a manual trader needsWhat a bot supervisor needs
Price action and indicatorsIs my bot alive? What did it just do? Why?
An order ticketRisk limits, kill switch, exposure caps
Drawing toolsStrategy attribution and audit trail per fill
One market at a timePortfolio-wide health at a glance
Charting customisationReplay mode, decision context, slippage truth

3.The proposed layout, annotated #

Six zones, in order of importance. Each one uses data we already have.

● Monitoring layout · v1 sketch
Polytraders Monitor · Strategies · Markets · Fills · Research · Risk ⏻ KILL ALL
MarketSashi vs 9INE · Map 2
Status● LIVE
Position+320 YES
Unrealised+$48.20
Resolves in2h 14m
Strategies · 7
CS2 Momentum+$524
Pinnacle Drift+$311
Eco Fader+$202
Book MM+$94
YES probability — Sashi wins Map 2 5m candles · live
Why did it trade
▲ BUY 120 YES @ $0.408
Model prob46.2% Market mid42.1% Edge+4.1¢ Kelly used0.22
Top features
round14_eco
+0.18
pistol_won
+0.11
pinnacle_drift
+0.07
A

Persistent header strip — market, status, position, P&L, edge, resolves-in. Always visible.

B

Kill switch top-right — two-click confirm, audit-logged. Never navigate to halt.

C

Strategy leaderboard — live P&L, Sharpe, win %. Click to isolate on chart.

D

Probability line + band — default view for binary markets. Candles secondary.

E

Fills as glyphs — shape by order type, size by stake, colour by realised outcome.

F

Match-clock event ribbon — pistols, bomb plants, timeouts mapped to price. The moat.

G

Decision drawer — model prob, top features, risk checks, counterfactual P&L per fill.

4.Nine changes, in order of impact #

In order. Earlier items unlock later ones.

1

Persistent header strip. Market, status, position, P&L, edge, resolves-in, always visible.

2

Match-clock event ribbon. Real-world events mapped to price. Single biggest moat.

3

Fills as first-class glyphs. Shape by order type, size by stake, colour by realised outcome, clickable.

4

Strategy leaderboard. Left rail — live P&L, Sharpe, win %. Click to isolate.

5

"Why did it trade" drawer. Model probability, top features, risk checks, counterfactual P&L per fill.

6

Latency + microstructure pane. Signal→fill ms, p50/p95, spread, slippage vs intended price.

7

Risk + kill switch always-on. Global P&L, drawdown vs limit, exposure vs cap, halt button.

8

Replay mode + ⌘K palette. Scrub the match at 4× / 16×, fuzzy-search across markets and fills.

9

Two layouts, one toggle. Monitoring mode (default) vs Chart-first mode for deep investigation.

5.How to build it #

Three phases. Almost everything reuses data we already have.

Architectural assumption. Postgres or Timescale for signals/fills/positions, a Python or Node backend, a React frontend, a websocket per page multiplexed by market_id and strategy_id. The chart must render on a <canvas>, not through React reconciliation — React on every tick will not survive contact with production.

5.1 Phase 1 — Two weeks, zero new data #

demo-wired → shadow-ready ~2 weeks · 1 frontend, 1 backend

The biggest perceived UX jump for the smallest engineering cost. Every input already exists.

  1. Top header strip. Source: existing positions table + market metadata + open P&L calc. Lift the websocket stream that already feeds the chart. Payload {position, avg_price, mid, mark_pnl, edge_vs_mid, resolves_at}.
  2. Strategy leaderboard. Aggregate the existing fills table grouped by strategy_id into a materialised view that refreshes every minute. Click row → frontend state filters the chart. No backend change for the click behaviour.
  3. Fills as glyphs. Already plotted as signal markers. Change three things: shape by order_type, radius ∝ sqrt(notional) capped, colour by realised outcome via fills LEFT JOIN positions.
  4. Kill switch. Extend the existing per-strategy pause toggle to a global pause-all mutation. Two-click confirm. Audit-logged in control_actions.

5.2 Phase 2 — Three to four weeks, the differentiating features #

shadow-ready → runtime-live ~3–4 weeks · 1 frontend, 1 backend, 1 data eng

The things competitors will not have. Step 2 (decision persistence) is the single highest-leverage data change in this whole spec.

  1. Match-clock event ribbon. Subscribe to the same odds feed the bots already consume (CS2, football, tennis all expose discrete in-game events). New table match_events, one row per event. Render as vertical lines on the chart plus a ribbon strip below. Hover tooltip: price before / price after / bot action within 30s.
  2. Decision persistence. Every time a bot fires a signal, persist the feature vector and decision context alongside the order. Without this, the decision drawer is fake. Schema in section 6.
  3. Latency + microstructure strip. Rolling 5-minute p50/p95 of signal_fired_at → fill_confirmed_at server-side. Spread from Polymarket orderbook websocket. Slippage as (filled_price − intended_price) × side per fill. One small endpoint, frontend polls every 2s.
  4. Probability view as default. Use existing mid line with a confidence band from the model's predictive variance — or historical residual std as fallback. Candles remain as a toggle for users who prefer them.

5.3 Phase 3 — Polish, the things that compound #

runtime-live → production-live ongoing

Each item below is small in isolation and large in cumulative effect.

  1. Replay mode. Already have all timestamped data — signals, fills, match events, prices. Build a scrubber with 1× / 4× / 16× speeds, re-emit through the same render path. Best onboarding tool we can build: new hire watches yesterday's NBA Finals market at 16× and understands the system in five minutes.
  2. Command palette (⌘K). Use cmdk by Paco Coursey. Index: markets, strategies, last 7 days of fills, team members, settings. Fuzzy search client-side.
  3. Two layouts, one toggle. Monitoring mode = this spec. Chart-first mode = current Polytraders layout for deep investigation. Persist preference, one keyboard shortcut, one URL param to deep-link.

6.Data model additions #

Two new tables. The first is required for Phase 1's ribbon, the second is the unlock for Phase 2's decision drawer.

match_events
create table match_events (
  event_id      bigserial primary key,
  market_id     text not null,
  ts            timestamptz not null,
  event_type    text not null,   -- pistol_won, bomb_plant, timeout, mvp, goal, state_called ...
  label         text not null,   -- human label for the ribbon tooltip
  impact_side   text,            -- YES | NO | null
  raw           jsonb            -- full provider payload, kept for replay
);
create index on match_events (market_id, ts);
signal_decisions
create table signal_decisions (
  signal_id            uuid primary key,
  strategy_id          text not null,
  market_id            text not null,
  fired_at             timestamptz not null,
  model_prob           numeric(6,4) not null,
  market_implied_prob  numeric(6,4) not null,
  edge_cents           numeric(6,2) not null,
  kelly_fraction       numeric(6,4),
  top_features         jsonb not null,    -- [{name, value, contribution}, ...]
  risk_checks          jsonb not null,    -- [{name, passed, threshold, actual}, ...]
  suggested_size       integer,
  actual_size          integer,
  counterfactual_pnl   numeric(12,4),     -- backfilled at resolution
  order_id             uuid references orders(order_id)
);
create index on signal_decisions (market_id, fired_at desc);
create index on signal_decisions (strategy_id, fired_at desc);

On feature attribution. For a linear or logistic model, write feature_name × coefficient × value for the top contributors. For a gradient-boosted model use SHAP. For a neural net, integrated gradients or input-perturbation attribution. The drawer never displays more than the top five.

Backfill is your friend. When signal_decisions lands, ship a backfill job that reconstructs the last 30 days from logs. The drawer has something to show on day one instead of waiting for new fills to accumulate.

7.Engineering practicalities #

7.1 State management #

The dashboard trap is re-rendering the entire React tree on every tick. Use a fine-grained reactive store (Zustand, Valtio, or signals) keyed by market_id, so the header strip re-renders independently of the leaderboard, which re-renders independently of the chart.

7.2 The chart is not React #

Use a single <canvas> with imperative draws. React's reconciliation cost is too high for sub-second tick updates with hundreds of marks. The mockup deliberately uses canvas — keep that pattern.

7.3 Websocket discipline #

One socket per page, multiplexed by market_id and strategy_id on the server side, dispatched to subscribers on the client. Never one socket per widget.

7.4 Materialised views for leaderboard stats #

Do not compute Sharpe in the request path. Pre-aggregate every minute, refresh on demand if you want immediacy.

7.5 Honest readiness states #

The five-state taxonomy still applies: docs-complete → demo-wired → shadow-ready → runtime-live → production-live. A dashboard surface that displays decision counterfactuals is shadow-ready, not production-live, until backfill, replay, and the kill switch have all run hot for two weeks without a regression. See Start here.

8.If you only have one sprint #

Build the header strip + leaderboard + fill glyphs + kill switch — items 1–4 of Phase 1. That alone takes the screen from viewer to console and reuses 100% of the data we already have. Everything else is upside.