3.2 Maker-Wide
Maker-Wide is a passive market-making strategy designed for thin, illiquid binary markets where the natural spread is wider than Maker-Tight's target. It posts resting maker quotes at a wider edge (80–200 bps from mid), a smaller clip size, and with a longer expected holding period. The stale-book detector (time since last fill > stale_book_minutes) prevents continuous requoting on markets with no real two-way flow. Volatility of mid over the last hour determines the spread width dynamically: higher vol → wider edge. This is a user-controlled liquidity-provision tool. Maker rebates (20–25% of platform fees, paid in pUSD) contribute to the return profile. No performance claims are made.
v3 readiness
A bot is done when all four scores are. What does done mean?
1. Bot Identity
| Layer | Strategy Strategy |
|---|---|
| Bot class | Alpha Strategy |
| Authority | Trade |
| Status | BETA |
| Readiness | Limited live |
| Runs before | Risk guardrail pipeline |
| Runs after | Market scanner / opportunity feed |
| Applies to | Thin-book binary markets where 24h volume < 250,000 pUSD, spread > 2 * edge_bps, and no stale-book condition is detected (time since last fill < stale_book_minutes) |
| Default mode | limited_live |
| User-visible | Advanced details only |
| Developer owner | Polytraders core — Strategy pod |
2. Purpose
Maker-Wide is a passive market-making strategy designed for thin, illiquid binary markets where the natural spread is wider than Maker-Tight's target. It posts resting maker quotes at a wider edge (80–200 bps from mid), a smaller clip size, and with a longer expected holding period. The stale-book detector (time since last fill > stale_book_minutes) prevents continuous requoting on markets with no real two-way flow. Volatility of mid over the last hour determines the spread width dynamically: higher vol → wider edge. This is a user-controlled liquidity-provision tool. Maker rebates (20–25% of platform fees, paid in pUSD) contribute to the return profile. No performance claims are made.
3. Why This Bot Matters
Quoting on a stale book (no fills for > stale_book_minutes)
Resting orders sit on a market with no real two-way activity. If news arrives and moves the market, the stale quotes are adversely selected at a wide loss.
Edge too narrow for wide-spread illiquid markets
In a thin book, the spread can move by 100–200 bps in a single fill. A narrow edge does not cover this adverse-selection risk.
Inventory accumulates without rebalancing
On a thin market with one-sided flow, the bot can accumulate a large directional inventory before the inventory skew mechanism has time to work. Hard inventory cap prevents runaway accumulation.
feeRateBps present on signed maker order (V1 pattern)
CTFExchangeV2 rejects orders with feeRateBps. Maker fees are operator-set at match time; the signed order must not contain this field. Maker fee_bps is capped at 50 bps.
No worked examples on this bot yet. Worked examples are optional but strongly recommended — they turn an abstract failure mode into something a developer can verify in a fixture.
4. Required Polymarket Inputs
| Input | Source | Required? | Use |
|---|---|---|---|
| Live order book — best bid, best ask, mid-price | ws_market (CLOB WebSocket) | Yes | Compute current spread and mid-price; derive dynamic edge width from mid-volatility. |
| Time since last fill on the market | clob_public (fills endpoint) | Yes | Stale-book detector: if time since last fill > stale_book_minutes, skip requoting. |
| Hourly mid-price volatility | ws_market (trade tape, 60-min rolling) | Yes | Set dynamic edge: edge_bps = base_edge_bps * (1 + vol_multiplier * hourly_vol). |
| Running inventory position on each market | clob_auth (open positions) | Yes | Compute inventory ratio; apply hard inventory cap; skew quotes to reduce open inventory. |
5. Required Internal Inputs
| Input | Source | Required? | Use |
|---|---|---|---|
| KillSwitch active flag | KillSwitch | Yes | Cancel all open maker quotes and emit no new OrderIntents if KillSwitch is active. |
| Builder code bytes32 | internal config | Yes | Injected into builder field on every maker OrderIntent. Maker fee_bps <= 50. |
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|---|---|---|---|
| edge_bps | 120 | 60 | 30 | Base edge in basis points from mid-price at which the bot will post a wide-spread quote. Actual edge is scaled upward by hourly mid volatility. |
| clip_size_usd | 50 | 100 | 200 | Size in pUSD of each individual wide maker quote (bid or ask). Kept small to limit adverse-selection exposure on thin books. |
| hard_inventory_cap_usd | 300 | 200 | 500 | Maximum total pUSD inventory on a single market. If inventory exceeds this, the bot stops quoting until inventory reduces via natural fill. |
| stale_book_minutes | 15 | 30 | 60 | Maximum time in minutes since the last fill before the book is considered stale and quoting is paused. |
7. Detailed Parameter Instructions
edge_bps
What it means
Base edge in basis points from mid-price at which the bot will post a wide-spread quote. Actual edge is scaled upward by hourly mid volatility.
Default
{ "edge_bps": 120 }
Why this default matters
120 bps covers expected adverse selection on thin illiquid markets where the spread can move 100+ bps on a single fill. Below 60 bps the edge is unlikely to persist; below 30 bps the bot will not quote regardless.
Threshold logic
| Condition | Action |
|---|---|
| >= 120 bps | Post wide quotes at mid +/- edge_bps/2 |
| 60–120 bps | WARN MAKER_WIDE_EDGE_MARGINAL; post at 50% clip size |
| < 30 bps (hard floor) | SKIP — MAKER_WIDE_SPREAD_TOO_TIGHT |
Developer check
if edge_bps < params.hard: return skip('MAKER_WIDE_SPREAD_TOO_TIGHT')
User-facing English
The market spread is too narrow for wide-spread maker quoting. Quoting was skipped for this cycle.
clip_size_usd
What it means
Size in pUSD of each individual wide maker quote (bid or ask). Kept small to limit adverse-selection exposure on thin books.
Default
{ "clip_size_usd": 50 }
Why this default matters
50 pUSD per side is modest for thin markets where a single fill can move the book significantly. 200 pUSD is the hard cap to prevent over-concentration in illiquid positions.
Threshold logic
| Condition | Action |
|---|---|
| <= 50 pUSD | Normal small clip quote |
| 50–200 pUSD | WARN; confirm thin-book depth supports this size |
| > 200 pUSD | Reject config — PARAMETER_CHANGE_REQUIRES_APPROVAL |
Developer check
if params.clip_size_usd > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')
User-facing English
Quote was sized to keep risk small on this low-activity market.
hard_inventory_cap_usd
What it means
Maximum total pUSD inventory on a single market. If inventory exceeds this, the bot stops quoting until inventory reduces via natural fill.
Default
{ "hard_inventory_cap_usd": 300 }
Why this default matters
300 pUSD on a thin market represents meaningful one-sided exposure. The hard cap prevents runaway accumulation in the absence of two-way flow.
Threshold logic
| Condition | Action |
|---|---|
| <= 300 pUSD | Normal quoting |
| 200–300 pUSD | WARN MAKER_WIDE_HIGH_INVENTORY; reduce clip to minimum |
| > 500 pUSD | Hard cap; stop quoting on market until inventory falls below warning threshold |
Developer check
if inventory_usd >= params.hard_inventory_cap_usd: return skip('MAKER_WIDE_INVENTORY_CAP_HIT')
User-facing English
This market's current position is at the maximum allowed size. No new quotes were placed until the position reduces.
stale_book_minutes
What it means
Maximum time in minutes since the last fill before the book is considered stale and quoting is paused.
Default
{ "stale_book_minutes": 15 }
Why this default matters
15 minutes without a fill on a thin market indicates negligible real two-way interest. Quoting into a completely inactive book has no maker rebate benefit and high adverse-selection risk when interest returns.
Threshold logic
| Condition | Action |
|---|---|
| < 15 min since last fill | Active book; quote normally |
| 15–60 min | WARN MAKER_WIDE_STALE_BOOK; continue quoting with reduced clip |
| > 60 min (hard floor) | SKIP — MAKER_WIDE_BOOK_TOO_STALE; cancel open quotes |
Developer check
if time_since_last_fill_min > params.hard: return skip('MAKER_WIDE_BOOK_TOO_STALE')
User-facing English
This market hasn't had any recent trading activity. Quoting was paused to avoid posting into a stale book.
8. Default Configuration
{
"bot_id": "strat.maker_wide",
"version": "2.1.0",
"mode": "limited_live",
"defaults": {
"edge_bps": 120,
"clip_size_usd": 50,
"hard_inventory_cap_usd": 300,
"stale_book_minutes": 15
},
"locked": {
"edge_bps": {
"min": 30
},
"clip_size_usd": {
"max": 200
},
"hard_inventory_cap_usd": {
"max": 500
},
"stale_book_minutes": {
"max": 60
}
}
}9. Implementation Flow
- Check KillSwitch active flag; if active, cancel all open maker quotes and emit no new OrderIntents.
- Subscribe to ws_market book and trade-tape streams for all eligible thin-book markets.
- Filter markets: 24h volume < 250,000 pUSD (thin-book regime). Markets with > 250K volume are handled by Maker-Tight.
- On each book tick: compute mid = (best_bid + best_ask) / 2; spread_bps = (best_ask - best_bid) / mid * 10000.
- Check stale-book condition: fetch time since last fill from clob_public. If > stale_book_minutes hard (60 min), skip — MAKER_WIDE_BOOK_TOO_STALE; cancel open quotes.
- If time_since_last_fill > 15 min (warning), WARN MAKER_WIDE_STALE_BOOK; reduce clip to 50%.
- Compute hourly mid volatility from trade tape. Set dynamic_edge_bps = edge_bps * (1 + 0.5 * hourly_vol / 0.01).
- If spread_bps < 2 * dynamic_edge_bps hard floor (30 bps): skip — MAKER_WIDE_SPREAD_TOO_TIGHT (sampled 1/100).
- Fetch running inventory from clob_auth. If inventory_usd >= hard_inventory_cap_usd: skip — MAKER_WIDE_INVENTORY_CAP_HIT.
- Compute inventory skew: bidPrice = mid - dynamic_edge_bps/2/10000 - skew * inventory_ratio; askPrice symmetric.
- Emit OrderIntent YES (post_only=true, side=buy, price=bidPrice, tif=GTC, builder={code, fee_bps:30}, size_pUSD=clip_size_usd).
- Emit OrderIntent NO (post_only=true, side=buy, price=1-askPrice, tif=GTC, builder={code, fee_bps:30}, size_pUSD=clip_size_usd).
- Note: fees are operator-set at match time in V2 — feeRateBps is NOT on the signed order. Maker fee_bps <= 50.
- Emit DecisionReport with intent_emitted=true, dynamic_edge_bps, time_since_last_fill_min, reason MAKER_WIDE_QUOTING.
10. Reference Implementation
Subscribes to CLOB WebSocket book and trade-tape streams for thin-book markets, checks spread, stale-book condition, and inventory cap, then emits GTC post_only maker OrderIntents inside the touch at dynamically widened edge.
Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.
FUNCTION onBookTick(market_id, bookTick):
// --- 0. KillSwitch gate ---
ks = FETCH internal.killswitch.status
IF ks.active:
CANCEL_OPEN_QUOTES(market_id)
RETURN
// --- 1. Stale-book check ---
fills = FETCH clob_public.GET('/markets/' + market_id + '/fills?limit=1')
timeSinceFillMin = (now_ms() - fills[0].timestamp_ms) / 60000
IF timeSinceFillMin > params.stale_book_minutes_hard: // 60 min
CANCEL_OPEN_QUOTES(market_id)
EMIT DecisionReport(intent_emitted=false, reason='MAKER_WIDE_BOOK_TOO_STALE')
RETURN
// --- 2. Inventory cap check ---
position = FETCH clob_auth.GET('/positions?market=' + market_id)
IF position.notional_usd >= params.hard_inventory_cap_usd:
EMIT DecisionReport(intent_emitted=false, reason='MAKER_WIDE_INVENTORY_CAP_HIT')
RETURN
// --- 3. Dynamic edge from volatility ---
tape = FETCH ws_market.tradeTape(market_id, window_min=60)
hourlyVol = stddev(tape.mid_prices)
dynamicEdgeBps = params.edge_bps * (1 + 0.5 * hourlyVol / 0.01)
IF dynamicEdgeBps < params.edge_bps_hard: // 30 bps
IF random() < 0.01:
EMIT DecisionReport(intent_emitted=false, reason='MAKER_WIDE_SPREAD_TOO_TIGHT')
RETURN
// --- 4. Warning thresholds ---
clipSize = toPusdUnits(params.clip_size_usd)
IF dynamicEdgeBps < 60 OR timeSinceFillMin > 15:
WARN('MAKER_WIDE_EDGE_MARGINAL' OR 'MAKER_WIDE_STALE_BOOK')
clipSize = toPusdUnits(params.clip_size_usd * 0.5)
// --- 5. Inventory skew ---
inventoryRatio = position.notional_usd / params.hard_inventory_cap_usd
skew = 0.3 * inventoryRatio
mid = (bookTick.best_bid + bookTick.best_ask) / 2
edgeHalf = dynamicEdgeBps / 2 / 10000
bidPrice = round(mid - edgeHalf - skew, 4)
askPrice = round(mid + edgeHalf - skew, 4)
// --- 6. Emit wide maker OrderIntents (V2: no feeRateBps; post_only=true) ---
EMIT OrderIntent(
market_id = market_id, outcome = 'YES', side = 'buy',
price = bidPrice, size_pUSD = clipSize, tif = 'GTC', post_only = true,
builder = {code: internal.builder_code, fee_bps: 30}
)
EMIT OrderIntent(
market_id = market_id, outcome = 'NO', side = 'buy',
price = round(1 - askPrice, 4), size_pUSD = clipSize, tif = 'GTC', post_only = true,
builder = {code: internal.builder_code, fee_bps: 30}
)
EMIT DecisionReport(intent_emitted=true, dynamic_edge_bps=dynamicEdgeBps,
time_since_last_fill_min=timeSinceFillMin, reason='MAKER_WIDE_QUOTING')
SDK calls used
ws_market.subscribe('book', [market_id])ws_market.subscribe('trade_tape', [market_id])fetchClobPublic('/markets/' + market_id + '/fills?limit=1')clob_auth.GET('/positions?market=' + market_id)toPusdUnits(rawFloat)buildOrderTypedData(orderParams, { name: 'CTFExchange', version: '2', chainId: 137 })internal.killswitch.status()internal.builder_code
Complexity: O(1) per market book tick
11. Wire Examples
Input — what arrives on the wire
WebSocket book tick — thin market, spread 250 bps, fresh book — ws_market
{
"market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
"best_bid": "0.570",
"best_ask": "0.606",
"mid": "0.588",
"spread_bps": "61.2",
"volume_24h_pusd": "42000",
"time_since_last_fill_min": "4.2",
"hourly_vol": "0.018",
"received_at_ms": 1746790400000
}
Output — what the bot emits
OrderIntent — wide maker bid (YES buy, post_only, GTC)
{
"intent_id": "oi_01HXMKW0000W01A",
"trace_id": "tr_01HXMKW0000WTR1",
"market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
"outcome": "YES",
"side": "buy",
"price": "0.582",
"size_pUSD": "50.00",
"tif": "GTC",
"post_only": true,
"builder": {
"code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"fee_bps": 30
},
"negrisk_aware": false,
"decision": {
"dynamic_edge_bps": 132.0,
"time_since_last_fill_min": 4.2,
"inventory_ratio": 0.08,
"skew_applied": true,
"reasons": [
"MAKER_WIDE_QUOTING"
]
},
"comment": "fees are operator-set at match time in V2 — feeRateBps is NOT on the signed order"
}
DecisionReport — skipped (book too stale), sampled 1/100
{
"report_id": "dr_01HXMKW999ZZZZ",
"bot_id": "strat.maker_wide",
"market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
"intent_emitted": false,
"time_since_last_fill_min": 82.0,
"reasons": [
"MAKER_WIDE_BOOK_TOO_STALE"
],
"sampled": true,
"evaluated_at_ms": 1746790401000
}12. Decision Logic
APPROVE
spread_bps > 2 * dynamic_edge_bps, time_since_last_fill < stale_book_minutes, inventory_usd < hard_inventory_cap_usd, KillSwitch inactive. Emit bid + ask OrderIntents as GTC post_only maker orders.
RESHAPE_REQUIRED
Not applicable — strat bots emit OrderIntents; reshaping is handled downstream by the Risk guardrail pipeline.
REJECT
spread too tight; stale book (> 60 min); inventory cap hit; KillSwitch active; stale feed. Cancel open quotes and emit DecisionReport intent_emitted=false.
WARNING_ONLY
edge_bps between 30 and 60 bps, or time_since_last_fill between 15 and 60 min, triggers warning and 50% size reduction.
13. Standard Decision Output
This bot returns a OrderIntent object. See OrderIntent schema.
{
"intent_id": "oi_01HXMKW0000W01A",
"trace_id": "tr_01HXMKW0000WTR1",
"market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
"outcome": "YES",
"side": "buy",
"price": "0.582",
"size_pUSD": "50.00",
"tif": "GTC",
"post_only": true,
"builder": {
"code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"fee_bps": 30
},
"negrisk_aware": false,
"decision": {
"dynamic_edge_bps": 132.0,
"time_since_last_fill_min": 4.2,
"inventory_ratio": 0.08,
"skew_applied": true,
"reasons": [
"MAKER_WIDE_QUOTING"
]
},
"comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
}14. Reason Codes
| Code | Severity | Meaning | Action | User-facing message |
|---|---|---|---|---|
MAKER_WIDE_QUOTING | INFO | Spread is adequate, book is fresh, inventory below cap. Wide maker bid+ask OrderIntents emitted. | Emit two GTC post_only OrderIntents. | Wide maker quotes were posted inside the market spread. |
MAKER_WIDE_SPREAD_TOO_TIGHT | INFO | Market spread is narrower than 2 * dynamic_edge_bps hard floor. Wide quoting is not viable. | Skip; emit sampled DecisionReport. | The market spread was too tight for wide maker quoting. |
MAKER_WIDE_BOOK_TOO_STALE | WARN | Time since last fill exceeds stale_book_minutes hard limit (60 min). Book is considered inactive. | Cancel open quotes; emit DecisionReport intent_emitted=false. | This market has had no recent trading activity. Quotes were paused. |
MAKER_WIDE_STALE_BOOK | WARN | Time since last fill is between 15 and 60 minutes (warning threshold). Book activity is low. | Reduce clip to 50%; continue quoting; log warning. | This market has had limited recent activity. Quote sizes were reduced. |
MAKER_WIDE_INVENTORY_CAP_HIT | WARN | Inventory on this market has reached hard_inventory_cap_usd. Quoting is suspended. | Skip; no new OrderIntents; emit DecisionReport. | The maximum position size for this market has been reached. No new quotes were placed. |
MAKER_WIDE_EDGE_MARGINAL | WARN | Dynamic edge is between 30 and 60 bps (warning threshold). Quoting at reduced clip size. | Emit OrderIntents at 50% clip size; log warning. | Market conditions are borderline. Maker quotes were posted at reduced size. |
MAKER_WIDE_HIGH_INVENTORY | WARN | Inventory is between the warning (200 pUSD) and hard cap (300 pUSD). Accumulation risk elevated. | Continue quoting; reduce clip to minimum; log warning. | The current position on this market is large. Quote sizes were reduced. |
STALE_MARKET_DATA | HARD_REJECT | Book snapshot older than 5s or position data unavailable. | Cancel open quotes; no new OrderIntents. | Market data was too old. Quotes were cancelled. |
KILL_SWITCH_ACTIVE | HARD_REJECT | Global kill switch is active. | Cancel all open quotes; no new OrderIntents. | Trading is currently paused. |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|---|---|---|---|
polytraders_strat_makerwide_decisions_total | counter | count | verdict, reason_code | Total evaluation cycles by intent_emitted and reason code. |
polytraders_strat_makerwide_dynamic_edge_bps | histogram | basis_points | market_id | Distribution of dynamic edge in bps at each quoting cycle. |
polytraders_strat_makerwide_inventory_usd | gauge | pusd | market_id | Current inventory in pUSD per quoted market. |
polytraders_strat_makerwide_intents_emitted_total | counter | count | side | Total wide maker OrderIntents emitted by side (bid/ask). |
polytraders_strat_makerwide_stale_book_skips_total | counter | count | market_id | Quoting cycles skipped due to stale book per market. |
polytraders_strat_makerwide_eval_latency_ms | histogram | milliseconds | Wall-clock latency from book tick to OrderIntent emit. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|---|---|---|
MakerWideHighInventory | polytraders_strat_makerwide_inventory_usd > 250 | warn | #runbook-makerwide-inventory |
MakerWideHighStaleBookRate | rate(polytraders_strat_makerwide_stale_book_skips_total[10m]) > 5 | warn | #runbook-makerwide-stale-book |
MakerWideStaleFeed | rate(polytraders_strat_makerwide_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1 | warn | #runbook-makerwide-stale-feed |
MakerWideKillSwitchBlocking | rate(polytraders_strat_makerwide_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0 | page | #runbook-killswitch |
Dashboards
- Grafana — Strategy / MakerWide dynamic edge and stale-book rate
- Grafana — Strategy / MakerWide inventory per market
16. Developer Reporting
{
"bot_id": "strat.maker_wide",
"market_id": "0xmakerwide000000000000000000000000000000000000000000000000000001a",
"mid": 0.588,
"best_bid": 0.57,
"best_ask": 0.606,
"spread_bps": 61.2,
"base_edge_bps": 120,
"hourly_vol": 0.018,
"dynamic_edge_bps": 132.0,
"time_since_last_fill_min": 4.2,
"inventory_usd": 24.0,
"inventory_ratio": 0.08,
"skew_applied": true,
"clip_size_pusd": 50.0,
"intent_emitted": true,
"reason": "MAKER_WIDE_QUOTING",
"emitted_at_ms": 1746790400000
}17. Plain-English Reporting
| Situation | User-facing explanation |
|---|---|
| Wide maker quote posted | A resting order was placed at a wider spread on this low-activity market to provide liquidity. The quote will fill if a taker crosses the price. |
| Quote skipped — stale book | This market hasn't had any trades recently. Quoting was paused to avoid posting into a potentially stale book. |
| Quote skipped — inventory cap reached | The current position on this market is at the maximum allowed size. No new quotes were placed until the position reduces via natural trading. |
| Spread dynamically widened | Recent price volatility on this market caused the quote spread to widen automatically to provide more protection against adverse selection. |
18. Failure-Mode Block
| main_failure_mode | Adverse selection on news arrival: stale wide-spread quotes get hit by informed takers when an event moves the market sharply, producing a loss larger than the accumulated maker rebates. |
|---|---|
| false_positive_risk | Stale-book detector is too sensitive, blocking quoting on thin markets that do have valid two-way interest at infrequent intervals. |
| false_negative_risk | stale_book_minutes set too high causes the bot to continue quoting on truly inactive markets, accumulating positions without offsetting natural flow. |
| safe_fallback | If book data is stale (last_seen > 5s) or clob_auth position read fails, cancel all open quotes and emit no new OrderIntents. Hard inventory cap prevents runaway accumulation. |
| required_dependencies | ws_market book and trade-tape stream, clob_public fills endpoint (time since last fill), clob_auth open positions, KillSwitch active flag, internal builder code |
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|---|---|---|
STALE_WS_FEED | Pause ws_market WebSocket; let last_seen age beyond 5s | Automatic on WebSocket reconnect; quotes re-posted on next clean tick. | |
STALE_BOOK_HARD_LIMIT | Set mock last fill timestamp to now() - 75 minutes | Automatic when a fill occurs on the market and stale timer resets. | |
INVENTORY_CAP_HIT | Set mock position.notional_usd=310 on target market (> hard cap 300) | Automatic when inventory falls below warning threshold via natural fills. | |
KILL_SWITCH_ON | Set killswitch.active=true | Automatic on manual KillSwitch reset. | |
DYNAMIC_EDGE_COLLAPSE | Set mock hourly_vol=0 and spread_bps=50bps (below 2*30bps hard floor edge) | Automatic when spread widens above threshold. |
20. State & Persistence
Cold-start recovery
On cold start, open quote IDs re-fetched from clob_auth. Inventory re-read from clob_auth. Book state rebuilt from first ws_market tick.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|---|
| Execution model | actor-per-market |
| Max in-flight | 30 |
| Idempotency key | intent_id |
| Per-call timeout (ms) | 200 |
| Backpressure strategy | drop oldest pending tick per market_id when queue depth > 3 |
| Locking / mutual exclusion | per-market_id mutex for inventory state read/write and open quote tracking |
22. Dependencies
Depends on (must run first)
| Bot | Why | Contract |
|---|---|---|
| risk.kill_switch | Checked first; cancels all open quotes and blocks new intent emission when active. |
Emits to (downstream consumers)
| Bot | Why | Contract |
|---|---|---|
| risk.portfolio_guard | ||
| gov.builder_attribution |
Sibling bots (same OrderIntent)
| Bot | Why | Contract |
|---|---|---|
| strat.maker_tight |
External services
| Service | Endpoint | SLA assumed | On failure |
|---|---|---|---|
| Polymarket CLOB WebSocket (ws_market) | best-effort | ||
| Polymarket CLOB auth API (positions) | 99.95% | ||
| Polymarket CLOB public API (fills) | 99.9% |
23. Security Surfaces
On-chain contract calls
| Contract | Method | Network | Effect |
|---|---|---|---|
CTFExchangeV2 | | polygon |
Abuse vectors considered
- Adversary floods thin book with cancels to advance stale_book timer, then exploits resumed quoting with informed order
- Quote stuffing on thin market to repeatedly trigger stale-book detection and prevent Maker-Wide from providing liquidity
- Inventory manipulation via spoofed position to pin inventory near hard cap and halt quoting
Mitigations
- Stale-book timer is based on last actual fill timestamp, not last quote or cancel
- Inventory read from clob_auth (authenticated), not from ws_market (public feed)
- KillSwitch cancels all open quotes immediately on activation
- V2 order timestamp(ms) prevents replay of old signed maker orders
- post_only=true prevents inadvertent taker fills on latency spikes
24. Polymarket V2 Compatibility
| Aspect | Value |
|---|---|
| CLOB version | v2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | yes |
| Aware of negative-risk markets | no |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | All maker orders use post_only=true to qualify for maker rebates (20-25% of platform fees, paid in pUSD). builder.fee_bps is capped at 30 bps, within the V2 maker maximum of 50 bps. feeRateBps is not present on any signed order — operator-set at match time. |
API surfaces declared
Networks supported
25. Versioning & Migration
| Field | Value |
|---|---|
| spec | 2.0.0 |
| implementation | 2.1.0 |
| schema | 2 |
| released | 2026-04-28 |
Migration history
| Date | From | To | Reason | Action taken |
|---|---|---|---|---|
| 2026-04-28 | v1 (USDC.e, feeRateBps on signed order) | v2 (pUSD, fees operator-set at match time, maker fee_bps <= 50) | CLOB V2 cutover | Switched to py-clob-client-v2. Removed feeRateBps from all signed maker order construction. Updated collateral denomination to pUSD. Injected builder field (bytes32) on every OrderIntent. Confirmed maker fee_bps cap of 50 bps. EIP-712 Exchange domain version updated from '1' to '2'. Stale-book detection extended to use minute-level resolution for thin books. |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|---|---|
| Post bid+ask when spread > 2*dynamic_edge_bps and book is fresh | spread=250bps, base_edge=120bps, time_since_fill=4min, inventory=0 | Two OrderIntents (bid + ask) emitted with post_only=true, GTC |
| Skip when time_since_last_fill > hard limit (60 min) | time_since_last_fill_min=75 | No OrderIntents; open quotes cancelled; reason=MAKER_WIDE_BOOK_TOO_STALE |
| Skip when inventory_usd >= hard_inventory_cap_usd | inventory_usd=310, hard_inventory_cap_usd=300 | No OrderIntents; reason=MAKER_WIDE_INVENTORY_CAP_HIT |
| Dynamic edge widens with high volatility | base_edge_bps=120, hourly_vol=0.03 | dynamic_edge_bps > 120; quotes placed at wider spread |
| Reduce clip 50% when edge_bps marginal (50 bps) | edge_bps=50, clip_size_usd=50 | OrderIntents emitted with size=25; WARN MAKER_WIDE_EDGE_MARGINAL |
| Skip when KillSwitch active; cancel open quotes | killswitch.active=true | No new OrderIntents; cancellation of open quotes emitted |
Integration Tests
| Test | Expected result |
|---|---|
| Full cycle: ws_market tick → stale-book check → dynamic edge → two signed V2 post_only GTC OrderIntents | Both orders have builder.code (bytes32), no feeRateBps, post_only=true, EIP-712 domain version '2' |
| Stale book detection cancels open quotes and emits MAKER_WIDE_BOOK_TOO_STALE | Open quote IDs cancelled; DecisionReport intent_emitted=false |
Property Tests
| Property | Required behaviour |
|---|---|
| post_only=true on every maker OrderIntent; bot never submits taker orders | Always true |
| feeRateBps never present on any signed OrderIntent | Always true — V2 fees are operator-set at match time |
| Inventory never exceeds hard_inventory_cap_usd on any single market | Always true |
27. Operational Runbook
Maker-Wide incidents are typically stale-book triggers (no fills for 60+ min), inventory cap breaches on thin markets, or stale feeds. Stale feeds resolve automatically; inventory cap breaches require monitoring until natural fills reduce the position.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|---|---|---|---|
MakerWideHighInventory | ||||
MakerWideHighStaleBookRate | ||||
MakerWideStaleFeed | ||||
MakerWideKillSwitchBlocking |
Manual overrides
——
Healthcheck
GET /internal/health/maker-wide -> 200 if ws_market feed last_seen < 5s, clob_auth reachable, KillSwitch inactive, and at least one thin-book market quoted in last 5 min.28. Promotion Gates
A bot does not advance to the next readiness state until every gate below is green. Gates are observable from production data — no subjective sign-off.
Promote to Shadow
| Gate | How measured | Threshold |
|---|---|---|
| All unit tests pass including post_only invariant, stale-book detection, and inventory cap | CI test run | 100% pass |
| feeRateBps absence verified; maker fee_bps <= 50 verified in integration test | Integration test asserting V2 order schema | Pass |
Promote to Limited live
| Gate | How measured | Threshold |
|---|---|---|
| p99 eval latency < 200ms over 24h | polytraders_strat_makerwide_eval_latency_ms histogram | p99 < 200ms |
| Inventory stays below hard_inventory_cap_usd on all markets over 48h shadow run | polytraders_strat_makerwide_inventory_usd gauge | max < 300 pUSD |
Promote to General live
| Gate | How measured | Threshold |
|---|---|---|
| E2E: book tick → stale-book check → two signed V2 post_only GTC OrderIntents submitted and resting on CLOB testnet | E2E test | Pass |
| Stale-book cancellation verified: open quotes cancelled on hard-limit trigger | Integration test | Pass |
29. Developer Checklist
Ready-to-ship score: 27/27 sections complete · 100%
| Requirement | Status |
|---|---|
| Purpose defined | ✓ done |
| Required inputs listed | ✓ done |
| Parameters defined | ✓ done |
| Defaults defined | ✓ done |
| Warning thresholds defined | ✓ done |
| Hard thresholds defined | ✓ done |
| Safe fallback defined | ✓ done |
| Structured output defined | ✓ done |
| Developer log defined | ✓ done |
| Plain-English explanation | ✓ done |
| Unit tests defined | ✓ done |
| Integration tests defined | ✓ done |
| Property tests defined | ✓ done |
| Failure-mode block complete | ✓ done |
| Reference implementation pseudocode | ✓ done |
| Wire examples (input + output) | ✓ done |
| Reason codes listed | ✓ done |
| Metrics & logs defined | ✓ done |
| State & persistence defined | ✓ done |
| Concurrency & idempotency defined | ✓ done |
| Dependencies declared | ✓ done |
| Security surfaces declared | ✓ done |
| Polymarket V2 compatibility declared | ✓ done |
| Version & migration history declared | ✓ done |
| Operational runbook defined | ✓ done |
| Promotion gates defined | ✓ done |
| Failure-injection recipes defined | ✓ done |