select a match
Odds — both players
Y
scale
gap cut
Position P&L
P&L src
What this is. Every match the bot monitored — bet AND watched-only — charted from its ~25 s tick ledger. On the bot's machine (directly or via loopback) you see full data; the public view is a sanitized bundle: stakes, order ids and P&L never leave the local machine.
Ranks beside the names. Every place a player or team is named — the match list, the head title, the legend pills, the chart's end-of-line labels and the click readout — prints that side's world rank right after the name (Diana Martynov 461 vs Federica Urgesi 356), in a smaller muted figure so the names stay scannable. It is the rank the board recorded for the match when it was picked up. Unranked players simply have no number — low-tier ITF draws are full of them, and an empty rank is never printed as a blank badge. Ranks are public roster data, so the sanitized public bundles carry them too.
Opponent line. Newer ledgers record the opponent's real book odds (opp_dec). Older files don't — there the line is derived as 1/(1−fav price) and drawn dashed.
TP fav vs MKT fav. Each line is labelled at its right end with the player and their role. TP fav = the algorithm's pick (the side the bot bets). MKT fav = the side the market priced above 50¢ pre-game (median implied price of the ticks before game start). They are often not the same — the bot frequently backs the market underdog; when they are the same that line reads TP+MKT fav and the other reads underdog.
BET and WON tags. BET = the side pmtrade actually placed money on (only when a fill exists — an armed ladder that never filled is not a bet; the bot only ever buys its own pick). WON = the decided match's winner, by authority: (1) the recorded resolution pmtrade stores per match (market resolution cross-checked against the winner-only on-chain redeem) — a recorded void/unresolved means no winner tag at all; (2) the bot's own settled won/lost ledger result; (3) only if neither exists, inference from the tape (game start past and the price pinned at a settlement print — 0¢ or 100¢, not a merely near-certain 98¢ — for at least a minute, cross-checked against the opponent's quote). Inference is last because a fallback price feed can invert a tail. Hover a label to see which of the three decided it. A stop-loss or early close says nothing about who won — undecided matches get no WON tag rather than a guess.
Markers — two families. Hollow △/▽ = RULE TARGET (the tick where the rung threshold / exit rule first triggered, inside the shaded legal entry window). Solid ▲/▼ = ACTUAL execution (confirmed fill / close). ✕ = attempt that failed (unfilled / buy-pending / void). The dashed amber connector + "+Ns" label between a target and its execution is the LATENCY GAP. Rungs are labeled R0/R1/R2 in ladder order. Watched matches show would-have-entered targets from that match's armed ladder; no ladder was ever armed → no targets invented.
What each trade flag says. A buy flag prints what it spent — the dollars on the bot's machine, the same buy's share of everything staked in the public view. A sell flag prints what fraction of the inventory it sold (its shares over every share the position ever bought), then its profit in dollars and percent, coloured green or red. Locally the share count rides along too. Every one of those figures is read straight off the results table's own row — the markers, the table and the Telegram accounting are three renderings of one calculation, so they cannot disagree.
Sell tiers 1-2, and why numbers can look skipped. The sell ladder fires lowest tier first, one per cycle. A tranche worth less than the exchange's minimum order size cannot rest on its own, so its fraction rolls forward into the next tier that does clear the minimum, and the resulting sell is tagged with the highest tier it discharged. On a small book that legitimately leaves gaps — a 3.79-share position sells tiers 1+2 together, then 3+4 together. Nothing is missing and nothing is unrecorded: the flag, the table's words and the notes column all print the whole run (S·t1-2, “tiers 1-2 together”), 1-based everywhere, so a coalesced sell can never read as a lost trade.
Slope-stop floor, not a stop-loss. The lower dashed line on the P&L pane (labelled slope-stop floor) is the slope-stop's arming floor from that match's config — the bot does not sell there. The rule fires only when all four hold together: no live score feed for the match, the blended bid at least that far underwater, the position still above the salvage line, and the odds worsening by ≥0.5 points per minute. It is a no-score fallback; matches with score data are handled by the game-state stop instead. The hollow ▽slope-stop marker is just the first crossing of the floor, never an exit.
Game start & H/L marks. The teal dashed vertical line is the game start (tagged start; ledgers with no game time fall back to the pregame anchor lock, tagged anchor). The dots labelled H/L are each side's highest and lowest traded price with its decimal odds. H/L marks exclude the last 10min before settlement (prices pin to 0/1 there) and the odds blow up to 100–2000x, so extremes printed there would be meaningless; a still-live match is searched to the newest tick instead of being trimmed. Values also appear in the click/hover readout.
Running P&L line. The lower pane's second line is this position's running P&L on its own right-hand axis in dollars: cash banked − cash staked + whatever is still held, marked at the tape price at every tick. Zero is shared with the open-P&L% axis on the left, so one break-even line serves both. It is local-only — the public board carries no P&L series unless the push flag is turned on, and then only as % of stake.
MAX $1 — the ceiling line. The dashed gold line on the P&L pane is the most this position could possibly show at each moment: everything already banked, minus everything staked, plus every share still held valued at $1.00 (a winning share redeems at $1). It steps up at every buy — more shares, more upside — and down at every sell, because shares banked below $1 shrink the ceiling toward what is already realized; once everything is sold or redeemed the two lines meet. The vertical gap between it and the P&L line is exactly how much of the maximum was not captured. The max button (and its legend pill) hides it, and the Y-axis then re-fits to the remaining lines — a ceiling 6× above the profit line would otherwise flatten it. Public view carries it as % of stake only.
Where the P&L numbers come from. The bot's positions ledger owns the timing of every buy and sell it booked. The wallet's on-chain totals (state/accounting.json) own the amounts. When a fill settles after the bot has stopped tracking a market the ledger is left short a booking — it still shows shares “riding” that the chain says were sold. The line runs on the ledger and takes the chain's totals from one labelled step (chain recon), so it ends at the true final number. P&L src switches between the reconciled and the raw ledger path.
Results table — every buy & sell, with the money. One row per executed action, in order, carrying shares AND dollars side by side so nothing has to be multiplied: what changed hands, at what price, for how much, the position and its dollar value after that row, the blended cost basis, the profit that row realized in $ and %, and the running cumulative in $ and %. Every column sorts; the money columns carry a per-column heat wash so a column reads as a shape before it reads as figures. Percent denominators differ by row type on purpose and every header says which: a buy's % is its cost as a share of everything staked (the buys sum to 100%), a sell's % is its price against the blended basis at that moment, and cum % is against the cash staked up to and including that row — not the final stake. The P&L src switch above re-drives the whole table: ledger = the bot's own bookings, chain = those plus the on-chain fills it never booked.
Unsold — the money that never came back. The summary block's coloured band is the shares this position bought and never sold: how many, what they cost, and what they actually recovered. If the pick lost they settled at 0¢ and the whole line is dead money (red); if it won they redeemed at 100¢ (green); if the match is still live they are marked at the last price (amber). It also prices what the standing sells that never triggered would have fetched (each tier's own limit price × the shares it offered, cheapest first) and how close the tape came — the best price printed after the first fill, against the nearest standing sell, with the gap in cents, in percent, and in points of blended profit. The bot's Telegram near-miss and exit-quality alerts run the same arithmetic, so the board and the feed never disagree.
Public view = percentages and prices, never dollars. On the bot's own machine the results table is the full money view. Everywhere else it carries percentages, prices and event counts only — no dollar amounts and no share counts, with the position's size shown as % of the shares bought rather than a count. The exporter builds that view by allowlist (a new field can never leak by being forgotten) and re-scans every staged bundle before upload: a single $ figure, share count or order id aborts the whole upload.
Settlement. Once pmtrade has a recorded resolution, anything still held is valued at its redemption price — $1 a share if the pick won, $0 if it lost — instead of its last quote, so a match that ran on after the exit is not marked at a dead price. The redemption is marked × on the line.
Start countdown. The head chip counts down to the first ball, then counts how long the match has been running, then shows the final state — ticking every second, with the start time printed in your timezone (the ledger records UTC).
Finding a match. The search box takes either player in any order — rows read “the bot's pick vs the opponent” and the pick is often the market underdog, so the name you think of first is frequently the second one on the row. Accents and punctuation are ignored. Polymarket sometimes lists one real pairing as two markets; those are tagged dup and always shown next to each other, never hidden.
Controls. Wheel = x-zoom. Drag = pan. Shift+drag = brush zoom. Shift+wheel or the Y slider (or dragging the y-axis gutter) = data Y-stretch. Double-click = reset. Both charts share one x-axis.
Downsampling & clip. Series over 3000 ticks are bucketed server-side keeping first/last/min/max per bucket — spikes survive; bet rows are always kept (—). The odds axis is clipped at 20x (4¢ floor in ¢ mode); a dead game's 100–2000x tail pins at the cap with a "clipped" hint instead of flattening the tradeable range.
Times. All timestamps are rendered in your local timezone (—); the ledger records UTC.
Live test. While the bot is running a live acceptance test of the sell architecture, the matches under test carry a magenta TEST badge in the list and in the match head, and a Live test section appears under the charts: the plan's function-coverage checklist, every wire action counted as kind × outcome (placed / stood / filled / rejected / refused / ignored / failed / unbooked) with a per-kind time strip, and every anomaly with its log excerpt. It disappears the moment the test stops. It carries no money — statuses, kinds and counts only; stakes, share counts and order ids never leave the local machine, and the exporter refuses to upload the file if a single one appears.