⚠ INTERNAL — Operator UX mock · Design preview of V2 development path · demo-wired ≠ production-live · back to dev guide
Strategies/Templates/Click-through
Polygon · pUSD 10
📓 Developer Guide 📋 Reason Codes 📈 Shadow Mode
JD John Doe · Admin
OperatorReady-made rule sets you can copy and change, instead of starting from a blank page.When: When you need a fast-start for a new market type.
Details
What this page is forBrowse pre-built strategy templates ready to clone and customize.
When to use itWhen you need a fast-start for a new market type.
Strategies · Templates · worked example

Maker-Tight Election, click by click

Every screen a user touches between choosing a template and arming it, built out rather than described. Click the steps. Step 3 has a screen per bot — that is the part worth arguing about.

This is a design spec, not a working builder: the controls are positioned, not wired. What matters is the shape — which questions get asked, in what order, with what consequence shown alongside.

Step 1 of 5

Pick a template

The user has not chosen a market, a size, or a bot yet. They are choosing an intention. Every card leads with what the strategy is trying to do, and is honest about how much it will ask of them.

polytraders.com/strategies/new

Maker-Tight Election — what this template has already decided for you

Shown so the user knows what they are inheriting. These are not editable in the builder; changing them means cloning the template.

Enginestrat.maker_tight — quotes both sides, never takes a directional view
Always-on guardsrisk.killswitch, risk.compliancegate, risk.markethaltguard — you cannot switch these off
Requote triggersOn mid move and on depth change. Not on time.
Settlement assetpUSD only. A market that settles in anything else is skipped, with DISC_ASSET_MISMATCH.

Note what is missing: no code editor, no file upload, no “paste your strategy” box.

for developers

The card copy is not marketing text — it is the template’s summary field and it is required to be one sentence with no jargon. Review rejects templates whose summary needs a second sentence, on the grounds that a strategy you cannot state in one line is a strategy the user cannot supervise.

“What this template has already decided for you” renders every manifest entry marked locked: true. Locked entries are shown, never hidden. Hiding them is how you get a user who believes their strategy is doing something it is not.

The decision count on each card is computed, not authored: it is the number of manifest entries with locked: false. If a template drifts past eight, CI fails it.

Step 2 of 5

Pick your markets

The template arrived bound to a cluster. The user narrows it. The two unselected rows are not hidden — they are shown greyed with the reason, because “why isn’t my market here” is the single most common support question on every trading product ever built.

polytraders.com/strategies/new · markets
US_ELECT_2028_A SPORTS_NBA CRYPTO_Q2 FED_RATES + add by hand
MarketClusterBid / ask24h vol
Will the Democratic nominee win Pennsylvania?US_ELECT_2028_A41¢ / 43¢18.2k
Will the Republican nominee win Pennsylvania?US_ELECT_2028_A57¢ / 59¢17.9k
Will turnout exceed 62%?US_ELECT_2028_A33¢ / 36¢4.1k
Will there be a recount in any state?US_ELECT_2028_A12¢ / 17¢0.9k
Will the Senate flip? spread too wideUS_ELECT_2028_A48¢ / 52¢0.3k
Will a third-party candidate exceed 5%? spread too wideUS_ELECT_2028_A7¢ / 19¢0.1k
4markets selected
240envelopes / day, est.
46of your 50 cap left
2skipped, with a reason

Recount has traded 0.9k in 24h. Below 1k the quality guard will pull your quotes most of the day. Keep it if you want, but expect RISK_SPREAD_WIDE in the log.

The estimate updates as rows are ticked, so “select all 50” visibly turns into “3,000 envelopes a day” before it turns into a bill.

for developers

Ineligible markets stay in the table, greyed, carrying the reason code that excluded them. This costs a row of vertical space and saves a support queue — “why isn’t my market here” is the most common question on every trading product ever shipped.

The envelope estimate comes from the template’s measured envelopes-per-market-per-day multiplied by the selection, not from a formula in the front end. When we change engine behaviour the estimate moves on its own.

The 50-market cap is enforced in the manifest schema, not in the UI. The counter is a courtesy; the schema is the rule. Never let a limit live only in a form.

Step 3 of 5 · the important one

Tune each bot, on a screen built for that bot

This is where a generic form would have put twelve number fields in two columns. Instead every bot gets its own screen, and the controls are chosen to match what the bot actually decides. An engine that picks prices gets a price ladder. A guard that refuses things gets a sentence and a capacity bar. A router that trades speed against price gets three named choices, not fourteen sliders.

Click through all four. The pattern is deliberately identical every time: the question in plain words, the control, then a green line saying what the answer means, then an amber line if the answer is risky. Developers can build any new bot’s screen from that skeleton.

the engine · decides what to offer

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.

mid 42¢
buy 41¢
sell 43¢
38¢40¢42¢44¢46¢

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.

200pUSD
501,000

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
}
a guard · decides what to refuse

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.

Never let me hold more than pUSD on ,
or more than pUSD across .
Your wallet: 12,400 pUSDThis strategy could use up to 5,000 (40%)
your ceiling

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 }
}
execution · decides how to get it done

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`
}
a guard · decides when to stop everything

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

8%
1% — twitchy25% — barely a stop

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

Four bots, six questions between them. The other 23 parameters in the bundle are template defaults, shown read-only in the review step.

for developers

The builder has no per-bot code. It renders whatever the bot’s decision manifest declares, using the control vocabulary. If you find yourself wanting a special case in the builder for your bot, the manifest is missing an expression — add it to the vocabulary and everyone benefits.

Every control declares a consequence expression (the green line) and optionally a risk_when predicate with its own copy (the amber line). Both are evaluated client-side against the current draft, so they update as the user drags. A control with no consequence expression will not pass review.

Numbers shown in consequence lines that come from measurement — fills per day, average slippage, “fired 9 times last week” — are read from the last completed replay for that cluster, cached daily. They are labelled as observations, never as predictions, and when no replay exists the line is omitted rather than guessed.

The escape-hatch drawer at the bottom of each screen is generated by serialising exactly the fields this screen owns. It is not a hand-written sample, so it cannot drift from what the screen actually writes.

Step 4 of 5

Read it back in English

Before anyone is allowed to run anything, the machine says the whole strategy back as prose. If the user cannot recognise their own intention in this paragraph, the builder failed. Every highlighted value links back to the control that produced it.

On the 4 Pennsylvania election markets you picked, you will continuously offer to buy at 1¢ below and sell at 1¢ above whatever the market is trading at, in 200 pUSD pieces, updating no more than once every 10 seconds. strat.maker_tight

You will never hold more than 500 pUSD of any single market, or more than 5,000 pUSD across all four together. When you hit either ceiling you stop buying but keep offering to sell, so the position can unwind. risk.exposureguard

When you do need to trade, you will rest the order for 200ms and then cross the spread if nobody has taken it, using both venues. exec.smartrouter

If this strategy is down 8% — about 400 pUSD — on any day, you pull all your quotes, keep whatever you are holding, and start again at midnight UTC. risk.drawdownguard

Three guards you did not configure are always on and cannot be turned off: the kill switch, the compliance gate, and the market-halt guard. always-on

for developers

The readback is a hard gate, not a nicety. Every manifest entry must supply a say_template, and the build fails without one. That constraint is doing real design work: it makes it impossible to expose a parameter we cannot explain, which is the failure mode that produced the old strategy-detail page.

Sentences are grouped by owning bot and tagged with the bot slug, so a user who wants to know which component is responsible for a clause can see it without us adding a legend.

The always-on paragraph is rendered from the platform policy set rather than the bundle, and it is not suppressible. Users should never be surprised by a guard they did not know existed.

The full bundle, all 29 parameters

The six you chose are marked. The rest come from the template. This is also the download link, the git diff, and what gets attached to the review ticket — the DSL never went away, it just stopped being something a person has to type.

// maker-tight-election.bundle  ·  draft  ·  never executed
bind   cluster "US_ELECT_2028_A"
markets [pa-dem, pa-rep, turnout-62, recount-any]   // <- you

engine strat.maker_tight {
  half_spread_bps = 10     // <- you (Balanced)
  quote_size_pusd = 200    // <- you
  cooldown_s      = 10
  requote_on      = [mid, depth]
  min_book_depth  = 1000
  skew_on_inv     = true
}
guard risk.exposureguard { max_pos_market = 500   // <- you
                          max_pos_cluster = 5000 // <- you
                          allow_reduce = true }
guard risk.drawdownguard  { daily_stop_pct = 8     // <- you
                          flatten = false, auto_resume = "00:00Z" }
route exec.smartrouter  { objective = balanced   // <- you
                          venues = [clob, ctfv2], max_latency_ms = 200 }

always risk.killswitch, risk.compliancegate, risk.markethaltguard
rung   demo-wired
builder_code "0xPT…a91c"
Step 5 of 5

Dry run, then arm — if you are allowed to

The strategy is replayed against the last seven days of real recorded market data. Nothing is placed. The user sees what it would have done and, more importantly, every time a guard would have said no.

It ran the whole week without breaking anything

140 intents, 131 filled, 9 rejected by guards you configured. No always-on guard ever fired, which means the strategy never tried to do anything structurally forbidden. Modelled result: +218 pUSD on a 5,000 pUSD ceiling — but see the caveat below.

140intentsWhat it wanted to do
131would have filled94% — matches the Balanced estimate
9guard rejectionsAll from your exposure cap
−3.1%worst dayYour stop is at −8%
Reason codeCountWhat happenedWhat we’d do
RISK_EXPOSURE_CAP_HIT14You hit the 500 pUSD per-market lid on pa-dem four days running, always in the last hour.Expected. Raise the cap or accept the flat hour.
RISK_SPREAD_WIDE61The recount-any market was too illiquid to quote for most of the week.Consider dropping that market.
EXEC_ROUTE_CTFV2_ACCEPTED3827% of fills went through V2 rather than the main book.Working as intended.
INTEL_DEPTH_THIN9Top-of-book depth fell below the quoting threshold nine times.Normal for overnight hours.
RISK_DRAWDOWN_TRIGGERED0Never fired. The worst day was −3.1%.Your 8% stop was never close.

The caveat, shown at the same size as the result. A replay assumes your order would have been filled at the recorded price without moving it. On thin markets that is generous. Treat +218 as the ceiling of what was possible, not a forecast.

To actually arm this, four things have to be true
The dry run completed

Seven days, no engine errors.

Every bot in it is demo-wired or higher

All four are demo-wired. A template containing a frozen bot cannot be armed at all.

The strategy has run 48h in shadow

Not started. Shadow means it watches real markets and places nothing. This is rung 3 of the ladder and there is no way to skip it.

An admin has signed off

Requires the shadow report. You are Admin, so you can sign your own — but not before the 48h.

Start 48h shadow run → Arm with real money disabled — needs the shadow run first Where this sits on the ladder →

The disabled button always says why it is disabled. A greyed control with no explanation is a support ticket.

for developers

The replay runs the real engine against recorded envelopes — same binary, same guards, replayed clock. It is not a separate simulator, because a separate simulator eventually disagrees with production and then nobody trusts either one.

The caveat is rendered at the same weight as the headline number and cannot be dismissed. Fill assumptions on thin books are optimistic, and a product that quietly implies otherwise is mis-selling. If you are tempted to shrink that box, you are building the wrong thing.

The four gate rows come from the same gate definitions the pipeline page renders. A gate cannot be described one way in the builder and another way on the ladder — there is one definition and both surfaces read it.

The arm button is disabled by the gate evaluator returning an unmet-requirement list, and the adjacent text is that list’s first entry. There is no code path that disables the button without producing a reason.

← All templates Control vocabulary for every bot → The ladder this feeds →
Polytraders Operator UX Mock · INTERNAL · demo-wired ≠ production-live · how this works · words we use · the plan · reason codes · shadow-mode pipeline
⚙ Page Configuration
Current page settings
Loading configuration…