105 lines
7.3 KiB
Markdown
105 lines
7.3 KiB
Markdown
|
|
# Mini-PRD — Quick-Trade: a hotkey execution interface for 5-minute binaries
|
|||
|
|
|
|||
|
|
> *Written retrospectively: this documents the product decisions behind a tool I built and used during the study. The tool itself belongs to the full (private) system — no live-trading code ships in this repo.*
|
|||
|
|
|
|||
|
|
## Problem
|
|||
|
|
|
|||
|
|
A 5-minute binary market prices in new information within seconds; the actionable moment for any late-window signal is the **final 10–40 seconds**. Polymarket's native web UI needs multiple clicks and page-state changes to place one order, gives no feedback on how long an order actually took, and is unusable for building intuition about how the book behaves under time pressure.
|
|||
|
|
|
|||
|
|
If Pillar 3 of the study asks *"can a human capture the edge at all?"*, the native UI answers "no" for the wrong reason — **tool latency, not market latency**. To make the test fair, execution overhead had to be driven to its floor and *measured*, so that whatever latency remained was attributable to the market, not the mouse.
|
|||
|
|
|
|||
|
|
## User
|
|||
|
|
|
|||
|
|
One power user: me. (n = 1 by design — this is a research instrument, not a consumer product. That constraint drives most scope cuts below.)
|
|||
|
|
|
|||
|
|
## Goals & success metrics
|
|||
|
|
|
|||
|
|
| Goal | Metric |
|
|||
|
|
|---|---|
|
|||
|
|
| Minimal click-to-submit path | **One keystroke** = one signed, posted order (no Enter, no confirmation dialog) |
|
|||
|
|
| Latency visible, not assumed | Every order prints its **measured sign-and-submit round-trip in ms** (`create_order` + `post_order` → API ack) |
|
|||
|
|
| Fill outcome reported, not assumed | After the order: **filled or not, matched size, fill price, and PnL** — read from the order's `size_matched` |
|
|||
|
|
| Zero per-click network overhead beyond the order itself | Price display and order parameters require **0 extra HTTP calls** at trade time |
|
|||
|
|
| Usable across windows without operator work | New 5-minute market auto-discovered and re-warmed in the background |
|
|||
|
|
|
|||
|
|
## Non-goals (explicit cuts)
|
|||
|
|
|
|||
|
|
- **No variable order size** — fixed 10 shares per keystroke. Fewer keys to think about, and it caps fat-finger damage.
|
|||
|
|
- **No order management** — no cancel/modify UI. GTD expiry does the cleanup.
|
|||
|
|
- **No position dashboard, no charts** — the separate watch screen owns market display; this tool owns the trigger.
|
|||
|
|
- **No multi-market support** — current window only.
|
|||
|
|
|
|||
|
|
## Functional requirements (the entire keyboard surface)
|
|||
|
|
|
|||
|
|
| Key | Action |
|
|||
|
|
|---|---|
|
|||
|
|
| `u` / `d` | Buy UP / DOWN, 10 shares, at market (ask + $0.02 buffer, capped at $0.99) |
|
|||
|
|
| `Shift`+`1`–`6` | Resting limit "lottery" bids at $0.01 / $0.02 / $0.03 — cheap maker orders (`1`–`3` = UP, `4`–`6` = DOWN) |
|
|||
|
|
| `i` / `o` | Sell UP / DOWN at best bid, most expensive lot first |
|
|||
|
|
| `b` / `r` / `w` | Balance check · redeem settled tokens · force window refresh |
|
|||
|
|
| `q` | Quit (restores terminal state) |
|
|||
|
|
|
|||
|
|
## Component structure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
① PRICE FEED — always live in the background. THIS is why a click needs no network:
|
|||
|
|
|
|||
|
|
Polymarket WebSocket ──► collector ──► snapshots.jsonl ──► tail -F ──► Price panel
|
|||
|
|
(chainlink price + (local file, the (live bid/ask,
|
|||
|
|
CLOB book, real-time) collector streams it) countdown)
|
|||
|
|
|
|||
|
|
② HOT PATH — fires on ONE keystroke; it reads the current ask from the Price panel
|
|||
|
|
above (a local read), so no network call is needed to price the order:
|
|||
|
|
|
|||
|
|
keystroke (raw tty, no Enter)
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Key listener (select() loop)
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Order router u/d = market · cheap limits · i/o = sell
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Preflight cache tick_size + neg_risk, warmed once per window
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Order builder + signer client-side signing; HTTP/1.1 keep-alive
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
REST post_order (GTD) ──► Polymarket CLOB
|
|||
|
|
|
|||
|
|
③ REPORTING — every order writes one line to a JSONL journal (the study's dataset):
|
|||
|
|
|
|||
|
|
• on submit: sign → ack latency (ms)
|
|||
|
|
• at settle: poll order status (REST) ──► size_matched · fill price · PnL
|
|||
|
|
|
|||
|
|
Background: a window manager auto-detects each new 5-minute market, resolves its
|
|||
|
|
token IDs (one REST call), and re-warms the Preflight cache before the window opens.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Key decisions & tradeoffs
|
|||
|
|
|
|||
|
|
| Decision | Why | Cost accepted |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Single keystroke, no confirmation | The confirmation dialog *is* the latency problem | Fat-finger risk — mitigated by fixed small size |
|
|||
|
|
| Fixed 10-share size | Removes a decision (and keystrokes) from the hot path | No sizing flexibility |
|
|||
|
|
| Prices from a local file tail, not REST | The collector already streams every tick to disk; reading it costs ~0ms and no rate limits | Depends on the collector running |
|
|||
|
|
| Warm order params once per window | `tick_size`/`neg_risk` lookups are per-token constants; fetching them per order wastes a round trip inside the hot path | Stale cache on rare market config change |
|
|||
|
|
| Market buys pay ask + $0.02 | In the last seconds, a missed fill costs more than 2¢ of price improvement | Slightly worse average entry |
|
|||
|
|
| Sell most-expensive-lot-first | Realizes the best exit first when unwinding under time pressure | — |
|
|||
|
|
| Terminal UI, not web | tty raw mode delivers a keypress in microseconds; a browser adds an event loop you don't control | No visual polish |
|
|||
|
|
|
|||
|
|
## The real cost center: the fill lifecycle
|
|||
|
|
|
|||
|
|
The interface optimizes the *submit* path — but a keystroke doesn't buy anything; it opens an order lifecycle:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
quote (best ask) → post → { full fill | partial fill | no fill } → retry / cancel
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This is deliberately **not** managed in the tool (fixed size, GTD auto-expiry does the cleanup — see Non-goals), and that scoping choice is exactly where the honesty lives: **order management is the pain, not a feature I could polish away.** On a thin binary book, partial fills and no-fills are routine. Each retry chases an ask the maker has already moved, so you cross to a worse price (slippage) or miss entirely. The exit runs the same lifecycle as a ladder — post the ask → step to the bid → cross at market — and a latency circuit breaker halts trading when the round trip degrades. The full loop regularly takes longer than the edge exists. **Latency inside this lifecycle plus thin-book slippage consumes essentially the entire margin** — which is why the fast-submit interface, though necessary, was never going to be sufficient.
|
|||
|
|
|
|||
|
|
## Outcome
|
|||
|
|
|
|||
|
|
The interface doubled as the study's **measurement instrument**: every order writes one line to a JSONL journal — sign-and-submit latency, whether it filled, matched size, fill price, PnL. That log is the raw dataset behind Pillar 3, turning "execution is slow" from a hunch into distributions you can read. Order latency became a **measured number printed after every trade** instead of a guess, and — together with the fill-lifecycle cost above — that is what makes Pillar 3's conclusion honest: even with the human reduced to a single keystroke and the software path stripped to its floor, the remaining round-trip is orders of magnitude slower than the market makers' millisecond repricing. The bottleneck was never the UI; it's the speed of light plus someone else's colo. **Knowing that changed the product question** — from "how do I trade this faster?" to "what game can retail actually win?" (see the study's conclusion in the [README](../README.md)).
|