Details
Election Momentum
This paragraph is generated from the same decision manifest the builder renders — it is not written by hand and it is not stored. Each highlighted value is bound to a parameter path, so it reflects live state and links back to the control that owns it.
If you add a parameter to a manifest and forget its say_template, the build fails. That is deliberate: a parameter a user can set but that we cannot describe in one clause is a parameter we should not have exposed.
Market by market
What it is offering, what it is holding, what it made. A market that is not trading says so on its own card, with the reason — not in a log somewhere else.
Not quoting. The book is 12¢/17¢ — 5¢ wide. Your quality guard pulls quotes above 3¢. It has been like this since 06:00. RISK_SPREAD_WIDE
Each card reads one market_state envelope. “Stood down” is not a separate flag — it is derived from the most recent REJECT reason code still inside its cooldown window. One source of truth, so the card, the attention queue and the audit log physically cannot disagree about why a market is quiet.
Cards render optimistically from the last envelope and grey out after 2 × expected_interval with no update, rather than showing stale numbers as though they were current. A number with no timestamp is a bug report waiting to happen.
Why it did what it did
The last six decisions, as sentences. Reason codes are still here, but as the small print rather than the headline. Expect this to be the most-read part of the page after the header.
Somebody sold you 200 pUSD of Democratic nominee wins Pennsylvania at 41¢. That is exactly the price you were offering.
You wanted to buy another 200 pUSD of Democratic nominee wins Pennsylvania, but you already hold 320 and your own limit is 500. Buying was refused. You are still offering to sell.
You sold 200 pUSD of Republican nominee wins Pennsylvania at 59¢, having bought it at 57¢ eleven minutes earlier. That is 4 pUSD of spread earned.
The book on Recount in any state went 5¢ wide, so you stopped quoting it. You will start again automatically when it tightens below 3¢.
Somebody bought 200 pUSD of Turnout exceeds 62% from you at 36¢.
The price feed for Turnout exceeds 62% went quiet for 17 seconds, so you pulled your quotes rather than trade on a price that had stopped moving. Back 4 seconds later.
Every row is one envelope. The sentence comes from a per-reason-code narrative template with the envelope’s own fields interpolated. There is no model in this path and there must not be — this is the record a user will quote back at us in a dispute, so it has to be reproducible from the envelope alone.
Colour lives in the dot and the dot is decorative: the state is also stated in words (“filled”, “refused by your own limit”). Never encode meaning in colour alone.
Note the split the copy makes between refused by your own limit and blocked by the platform. Users forgive the first and escalate the second, so the phrasing is chosen by reason-code origin, not by severity: guards the user configured say “your”, always-on guards say “the platform”.
What is stopping it, and how much room is left
Six guards are attached: four you chose, two that are always on. Each shows its current headroom rather than its configured threshold, because a threshold on its own tells you nothing about whether you are near it.
The most you can hold at once
Fired 3 times today, all on the Pennsylvania Democratic market between 14:00 and 14:41.
Markets too thin to quote safely
Holding Recount out since 06:04.
Prices that stopped updating
Fired once today, for 21 seconds at 13:58.
When you stop for the day
Quiet today.
Stops everything, instantly
Quiet today.
Jurisdiction and builderCode
Quiet today.
A guard exposes headroom() returning a 0–1 float plus a plain current-state string. Guards with no meaningful headroom — kill switch, compliance gate — return None and render without a bar, rather than a bar sitting at 0%. An empty bar reads as “safe” when it actually means “not applicable”.
Platform invariant. Every guard here can refuse an opening order and none of them can refuse a closing one. allow_reduce is not a per-guard setting a user can switch off. If you are writing a new guard and it needs to block a reduce, it is not a guard — it is a halt, and it belongs behind the kill switch with an operator in the loop.
Change something
The same six controls from the builder, in place, on a running strategy. No raw parameter names. Changes take effect at the next requote, which is at most ten seconds away.
How tight do you want to quote?
This is the only decision that makes you money. Quote tight and you trade a lot for a thin margin. Quote wide and you trade rarely for a fat one. There is no right answer, so we show the consequence instead of the number.
Pick a starting point
Then drag the handles if you want something in between. Most users never drag.
Or set it by hand
The market is currently trading at 42¢. Drag either handle.
You will offer to buy at 41¢ and sell at 43¢, in 200 pUSD clips, and you will not change your mind more than once every 10 seconds.
How much per clip?
The size of each individual offer. Not your total exposure — the guard on the next screen handles that.
At 200 pUSD a clip and a 500 pUSD cap per market, you can be filled 2 and a half times on one market before you stop quoting that side.
Raise this above 400 and a single fill uses most of your per-market room. The engine will spend more of the day flat than quoting.
What this screen writes into the bundle
engine strat.maker_tight { half_spread_bps = 10 // from the handles: 1c on a 42c mid quote_size_pusd = 200 // from the slider cooldown_s = 10 // preset-derived, not user-facing requote_on = [mid, depth] // fixed by the template }
How much are you willing to have riding at once?
A guard has no opinion about the market. It only ever says no. So its screen is not a form — it is a sentence the user completes, with a picture of what that sentence means against the money they actually have.
Complete the sentence
This is the whole configuration for this bot. Two numbers.
or more than pUSD across .
Once you hold 500 pUSD of one market, new buy orders on that market are rejected with RISK_EXPOSURE_CAP_HIT. Sells still go through — a guard never traps you in a position.
What should happen when you hit the ceiling?
Keep quoting the side that gets me out
You stop adding, but you keep offering to sell. This is how the position unwinds on its own.
If off: you go completely silent on that market and wait for it to resolve.
Put it in my attention queue
First time it happens each day. Not every rejection — that would be hundreds.
If off: it is still in the log, you just will not be told.
Pause the whole strategy, not just that market
Blunt. Use it if hitting a cap at all means your assumptions were wrong.
Off is the default. Most people want the other three markets to keep working.
What this screen writes into the bundle
guard risk.exposureguard { max_pos_market = 500 max_pos_cluster = 5000 scope = cluster:"US_ELECT_2028_A" on_breach = { allow_reduce: true, notify: once_daily, halt: false } }
When you do trade, what matters more?
Routing has about fourteen tunable parameters. Thirteen of them are trade-offs along one axis: speed versus price. So we expose the axis, not the parameters. An advanced user can open the drawer and set all fourteen.
Pick one
On last week’s data, Balanced on these four markets would have filled 131 of 140 intents, and the nine misses were all in the last hour before close.
Where are you allowed to trade?
Polymarket CLOB
The main order book. Deepest, and where the template expects to work.
CTFExchangeV2
The V2 contract. Occasionally a better price. Adds about 40ms.
If off: you give up roughly 0.2¢ a fill on this cluster.
All fourteen routing parameters (advanced)
Opening this drawer switches the strategy to manual routing and marks it for review before it can leave shadow. The three cards above are the reviewed paths.
route exec.smartrouter { objective = balanced // set by the card max_latency_ms = 200 rest_first_ms = 200 cross_after_ms = 200 venues = [clob, ctfv2] max_child_orders = 3 min_child_pusd = 50 cancel_on_reject = true // + 6 more, all derived from `objective` }
When should this stop for the day?
One number. The screen’s job is to make the number feel like money rather than a percentage, and to say out loud what happens at the moment it trips.
Stop for the day if this strategy is down
On a 5,000 pUSD ceiling that is 400 pUSD. In the last 90 days this strategy would have stopped 3 times, and on 2 of those days it would have finished green if left alone.
Below 4% you will stop on ordinary noise. The election cluster moves 3–5% intraday without anything happening.
When it trips
Cancel everything resting
Pull your quotes immediately. Takes about 300ms.
Also sell out of what I am holding
Off by default. Selling into the move that just hurt you usually makes it worse.
If off: you keep the position and it resolves on its own.
Start again automatically tomorrow
At 00:00 UTC. If off, it waits for you to press resume.
What this screen writes into the bundle
guard risk.drawdownguard { daily_stop_pct = 8 scope = strategy on_trip = { cancel_resting: true, flatten: false, auto_resume: "00:00Z" } }
Before you save, this is what changes — in English
A diff nobody has to parse. If the sentence does not change, the behaviour does not either, and the button stays disabled.
You will offer to buy at 41¢ and sell at 43¢, instead of 40¢ and 44¢. Expect roughly 3× as many fills and about half the margin on each one.
Editing a running strategy writes a new immutable bundle version and swaps the pointer; the engine picks it up on its next requote tick. No in-place mutation, no partial application — either the whole new bundle is live or the previous one still is.
The English diff is produced by rendering both bundles through the same say_template set and diffing the sentences, not the JSON. Two bundles that produce identical sentences are a no-op and the save button disables itself. That single rule removes the “I changed something and nothing happened” ticket class entirely.
Loosening a guard — raising a cap, widening a stop — requires re-confirmation. Tightening one does not. Asymmetric friction, on purpose.
Where this sits on the ladder
It is running, but on fake money. Real money is three rungs away and none of them can be skipped.
Spec frozen, schema validated, reason codes registered.
You are here. 19 fills today against a simulated book.
48 hours of shadow. Not started.
Proves the plumbing end to end without risking anything.
Needs an admin signature on the shadow report.
Rung is a property of the bundle, not of the UI, and it is computed as the minimum rung across every bot the bundle references. A strategy can never be more live than its least promoted component, which is why there is no way to hand-set this field.
The two unchecked rows are rendered from the same gate definitions the pipeline page uses, so a gate cannot be described one way here and another way there.
The raw bundle, for anyone who wants it
Read-only here. Editing happens through the controls above, because that is the only path that produces a reviewable English diff. Download it, diff it, attach it to a ticket — but the builder stays the writer.
// election-momentum.bundle · v7 · live on demo · since 06:00Z bind cluster "US_ELECT_2028_A" markets [pa-dem, pa-rep, turnout-62, recount-any] engine strat.maker_tight { half_spread_bps = 10, quote_size_pusd = 200, cooldown_s = 10 } guard risk.exposureguard { max_pos_market = 500, max_pos_cluster = 5000, allow_reduce = true } guard risk.drawdownguard { daily_stop_pct = 8, flatten = false } guard risk.marketqualityguard { max_spread_c = 3 } guard risk.stalebookguard { max_age_s = 15 } route exec.smartrouter { objective = balanced, venues = [clob, ctfv2] } always risk.killswitch, risk.compliancegate, risk.markethaltguard rung demo-wired // = min(rung) across all referenced bots