3.1 Maker-Tight
Maker-Tight is a passive market-making strategy that posts resting maker orders inside the current touch when market conditions are benign (low volatility, adequate volume, clean order flow). It quotes both YES and NO sides at a configurable edge (edge_bps) from the mid-price, sized to clip_size_usd. When spread is too tight to earn the edge, or when order-flow imbalance signals toxic flow, the bot skips the cycle and cancels any open quotes. This is a user-controlled execution tool for posting maker liquidity on predictive-event markets — not a directional betting strategy. Maker rebates (20–25% of platform fees, paid in pUSD) contribute to the overall return profile.
v3 readiness
A bot is done when all four scores are. What does done mean?
strat.maker_tight
Reference market-making strategy. Six SEARCH_SPACE parameters. Reference optimizer run pending.
Source: @polytraders/bots · src/strategy/maker_tight.js · Impl 12/15 · Backtest 3/4
1. Bot Identity
| Layer | Strategy Strategy |
|---|---|
| Bot class | Alpha Strategy |
| Authority | Trade |
| Status | LIVE |
| Readiness | General live |
| Runs before | Risk guardrail pipeline |
| Runs after | Market scanner / opportunity feed |
| Applies to | Liquid binary markets where 24h volume ≥ min_volume_24h_usd, spread > min_spread_bps, and no toxic order flow is detected |
| Default mode | general_live |
| User-visible | Advanced details only |
| Developer owner | Polytraders core — Strategy pod |
2. Purpose
Maker-Tight is a passive market-making strategy that posts resting maker orders inside the current touch when market conditions are benign (low volatility, adequate volume, clean order flow). It quotes both YES and NO sides at a configurable edge (edge_bps) from the mid-price, sized to clip_size_usd. When spread is too tight to earn the edge, or when order-flow imbalance signals toxic flow, the bot skips the cycle and cancels any open quotes. This is a user-controlled execution tool for posting maker liquidity on predictive-event markets — not a directional betting strategy. Maker rebates (20–25% of platform fees, paid in pUSD) contribute to the overall return profile.
3. Why This Bot Matters
Quoting into toxic flow without detection
Informed takers repeatedly fill the maker side immediately after posting, resulting in adverse selection losses that erode the quoted spread over time.
Posting quotes when spread < min_spread_bps
Quoting inside an already-tight spread earns no edge; the maker rebate does not cover the risk of being adversely selected on a fast-moving market.
Inventory skew not applied
Without skewing quotes toward the short side as inventory builds, the bot accumulates a directional position that violates the passive-maker intent and increases settlement risk.
feeRateBps hardcoded on signed maker order (V1 pattern)
CLOB V2 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.
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 (ask - bid) and mid-price to determine quoting levels. |
| Order-flow imbalance over last N seconds | ws_market (trade tape) | Yes | Detect toxic flow: if taker-initiated buy volume >> sell volume, the market is directional and maker posting is paused. |
| 24h trading volume in pUSD | clob_public | Yes | Only post on markets meeting min_volume_24h_usd; illiquid markets have wide spreads that attract toxic flow. |
| Running inventory position on each market | clob_auth (open positions) | Yes | Compute inventory skew factor; shift bid and ask prices toward the direction that reduces 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 for attribution. Maker fee_bps ≤ 50. |
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|---|---|---|---|
| edge_bps | 10 | 6 | 3 | Minimum edge in basis points from mid-price at which the bot will post a quote. Quotes are placed at mid ± edge_bps/2. |
| clip_size_usd | 200 | 400 | 600 | Size in pUSD of each individual maker quote (bid or ask). Both sides are quoted at this size unless inventory skew applies. |
| inventory_skew_factor | 0.3 | 0.6 | 0.9 | Coefficient applied to shift quote prices toward reducing open inventory. At 0.3, a long YES position shifts the YES ask down and the YES bid up proportionally. |
| min_volume_24h_usd | 250000 | 150000 | 50000 | Minimum 24h trading volume (in pUSD) required for this strategy to post on a market. Below this threshold the spread is likely structural rather than temporary. |
7. Detailed Parameter Instructions
edge_bps
What it means
Minimum edge in basis points from mid-price at which the bot will post a quote. Quotes are placed at mid ± edge_bps/2.
Default
{ "edge_bps": 10 }
Why this default matters
10 bps covers expected adverse selection on benign low-vol markets. Below 6 bps the edge is unlikely to persist long enough to fill; below 3 bps (hard floor) the bot will not quote regardless of config.
Threshold logic
| Condition | Action |
|---|---|
| edge_bps ≥ 10 | Post quotes at mid ± edge_bps/2 |
| 6 ≤ edge_bps < 10 | WARN MAKER_TIGHT_EDGE_MARGINAL; post at reduced clip size (50%) |
| edge_bps < 3 (hard floor) | SKIP — MAKER_TIGHT_SPREAD_TOO_TIGHT |
Developer check
if edge_bps < params.hard: return skip('MAKER_TIGHT_SPREAD_TOO_TIGHT')
User-facing English
The market spread is too narrow to post competitively. Quoting was skipped for this cycle.
clip_size_usd
What it means
Size in pUSD of each individual maker quote (bid or ask). Both sides are quoted at this size unless inventory skew applies.
Default
{ "clip_size_usd": 200 }
Why this default matters
200 pUSD per side keeps individual quote impact small on typical Polymarket binary books. The Risk guardrail pipeline may further reduce this.
Threshold logic
| Condition | Action |
|---|---|
| ≤ 200 pUSD | Normal single-side quote |
| 200–600 pUSD | WARN; risk guardrail will reshape if above portfolio budget |
| > 600 pUSD | Reject config change — PARAMETER_CHANGE_REQUIRES_APPROVAL |
Developer check
if params.clip_size_usd > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')
User-facing English
Each quote was sized to keep the order visible but not dominant on the market.
inventory_skew_factor
What it means
Coefficient applied to shift quote prices toward reducing open inventory. At 0.3, a long YES position shifts the YES ask down and the YES bid up proportionally.
Default
{ "inventory_skew_factor": 0.3 }
Why this default matters
0.3 provides a moderate skew that discourages further inventory accumulation without widening quotes so aggressively that they never fill.
Threshold logic
| Condition | Action |
|---|---|
| ≤ 0.3 | Normal skew |
| 0.3–0.6 | WARN MAKER_TIGHT_HIGH_SKEW; inventory is being aggressively managed |
| > 0.9 | Hard ceiling; config rejected |
Developer check
bidPrice = mid - edge_half - skew * inventory_ratio; askPrice = mid + edge_half - skew * inventory_ratio
User-facing English
Quote prices were adjusted to help balance the current position.
min_volume_24h_usd
What it means
Minimum 24h trading volume (in pUSD) required for this strategy to post on a market. Below this threshold the spread is likely structural rather than temporary.
Default
{ "min_volume_24h_usd": 250000 }
Why this default matters
Markets with < $250K daily volume tend to have wide, illiquid spreads that attract informed takers rather than uninformed flow. Posting on them is adverse-selection-prone.
Threshold logic
| Condition | Action |
|---|---|
| volume_24h ≥ 250,000 pUSD | Eligible to post |
| 150,000–250,000 pUSD | WARN MAKER_TIGHT_LOW_VOLUME; post at 50% clip size |
| < 50,000 pUSD (hard floor) | SKIP — MAKER_TIGHT_SPREAD_TOO_TIGHT (volume too low) |
Developer check
if volume_24h < params.hard: return skip('MAKER_TIGHT_SPREAD_TOO_TIGHT')
User-facing English
This market does not have enough daily trading activity for safe maker quoting.
8. Default Configuration
{
"bot_id": "strat.maker_tight",
"version": "2.1.0",
"mode": "general_live",
"defaults": {
"edge_bps": 10,
"clip_size_usd": 200,
"inventory_skew_factor": 0.3,
"min_volume_24h_usd": 250000
},
"locked": {
"edge_bps": {
"min": 3
},
"clip_size_usd": {
"max": 600
},
"inventory_skew_factor": {
"max": 0.9
}
}
}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 markets.
- Filter markets: 24h volume ≥ min_volume_24h_usd (clob_public). Skip low-volume markets.
- On each book tick: compute mid = (best_bid + best_ask) / 2; spread_bps = (best_ask - best_bid) / mid * 10000.
- If spread_bps < 2 * edge_bps: skip cycle — spread too tight to earn edge; emit DecisionReport intent_emitted=false MAKER_TIGHT_SPREAD_TOO_TIGHT (sampled 1/100).
- Compute order-flow imbalance over last 30s from trade tape. If taker-buy_volume / total_volume > toxic_flow_threshold: skip cycle — MAKER_TIGHT_TOXIC_FLOW_DETECTED.
- Fetch open YES/NO position from clob_auth to compute inventory_ratio = current_position / max_inventory.
- Compute skewed bid/ask prices: bidPrice = mid - edge_bps/2/10000 - skew * inventory_ratio; askPrice = mid + edge_bps/2/10000 - skew * inventory_ratio.
- Apply edge_bps warning threshold: if edge_bps < 6, reduce clip to 50%.
- Emit OrderIntent YES (post_only=true, side=buy, price=bidPrice, tif=GTC, builder={code, fee_bps: 30}).
- Emit OrderIntent NO (post_only=true, side=buy, price=ask_of_NO=1-bidPrice, tif=GTC, builder={code, fee_bps: 30}).
- 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, edge_bps, inventory_ratio, skew applied.
10. Reference Implementation
Subscribes to CLOB WebSocket book and trade-tape streams, checks spread and flow conditions, and emits GTC post_only maker OrderIntents inside the touch when conditions are benign.
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. Volume filter ---
vol24h = FETCH clob_public.GET('/markets/' + market_id + '/volume')
IF vol24h < params.min_volume_24h_usd_hard: // 50,000 pUSD
SKIP 'MAKER_TIGHT_SPREAD_TOO_TIGHT' (volume)
RETURN
// --- 2. Spread check ---
mid = (bookTick.best_bid + bookTick.best_ask) / 2
spread_bps = (bookTick.best_ask - bookTick.best_bid) / mid * 10000
IF spread_bps < 2 * params.edge_bps_hard: // 3 bps hard floor
// sample 1/100 for observability
IF random() < 0.01:
EMIT DecisionReport(intent_emitted=false, reason='MAKER_TIGHT_SPREAD_TOO_TIGHT')
RETURN
// --- 3. Toxic flow detection ---
tape = FETCH ws_market.tradeTape(market_id, window_s=30)
takerBuyRatio = tape.taker_buy_volume / (tape.total_volume + 1e-9)
IF takerBuyRatio > 0.75:
EMIT DecisionReport(intent_emitted=false, reason='MAKER_TIGHT_TOXIC_FLOW_DETECTED')
RETURN
// --- 4. Inventory skew ---
position = FETCH clob_auth.GET('/positions?market=' + market_id)
inventoryRatio = position.yes_notional / params.max_inventory_usd
skew = params.inventory_skew_factor * inventoryRatio
edgeHalf = params.edge_bps / 2 / 10000
bidPrice = round(mid - edgeHalf - skew, 4)
askPrice = round(mid + edgeHalf - skew, 4)
// --- 5. Warning threshold ---
clipSize = toPusdUnits(params.clip_size_usd)
IF params.edge_bps < 6:
WARN('MAKER_TIGHT_EDGE_MARGINAL')
clipSize = toPusdUnits(params.clip_size_usd * 0.5)
// --- 6. Emit maker OrderIntents (V2: no feeRateBps; builder field; post_only=true) ---
// Maker fee_bps <= 50 (V2 maker cap)
EMIT OrderIntent(
market_id = market_id,
outcome = 'YES',
side = 'buy',
price = bidPrice,
size_pUSD = clipSize,
tif = 'GTC',
post_only = true,
builder = { code: config.builder_code, fee_bps: 30 },
negrisk_aware = false
)
EMIT OrderIntent(
market_id = market_id,
outcome = 'YES',
side = 'sell',
price = askPrice,
size_pUSD = clipSize,
tif = 'GTC',
post_only = true,
builder = { code: config.builder_code, fee_bps: 30 },
negrisk_aware = false
)
EMIT DecisionReport(
intent_emitted = true,
edge_bps = params.edge_bps,
inventory_ratio = inventoryRatio,
skew_applied = (skew != 0),
reasons = ['MAKER_TIGHT_QUOTING']
)
SDK calls used
ws_market.subscribe('book', [market_id])ws_market.subscribe('trade_tape', [market_id])fetchClobPublic('/markets/' + market_id + '/volume')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 — benign market, spread 16 bps — ws_market
{
"market_id": "0xcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890cd",
"best_bid": "0.619",
"best_ask": "0.629",
"mid": "0.624",
"spread_bps": "16.0",
"taker_buy_ratio_30s": "0.52",
"volume_24h_pusd": "480000",
"received_at_ms": 1746789800000
}
Output — what the bot emits
OrderIntent — maker bid (YES buy, post_only, GTC)
{
"intent_id": "oi_01HX9MKRT1A4Z1B",
"trace_id": "tr_01HX9MKRT1A4VR5",
"market_id": "0xcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890cd",
"outcome": "YES",
"side": "buy",
"price": "0.619",
"size_pUSD": "200.00",
"tif": "GTC",
"post_only": true,
"builder": {
"code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"fee_bps": 30
},
"negrisk_aware": false,
"decision": {
"edge_bps": 10.0,
"inventory_ratio": 0.12,
"skew_applied": true,
"reasons": [
"MAKER_TIGHT_QUOTING"
]
},
"comment": "fees are operator-set at match time in V2 — feeRateBps is NOT on the signed order"
}
DecisionReport — skipped (spread too tight), sampled 1/100
{
"report_id": "dr_01HX9MKRT1BZZZ",
"bot_id": "strat.maker_tight",
"market_id": "0xcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890cd",
"intent_emitted": false,
"spread_bps": 3.2,
"reasons": [
"MAKER_TIGHT_SPREAD_TOO_TIGHT"
],
"sampled": true,
"evaluated_at_ms": 1746789801000
}12. Decision Logic
APPROVE
spread_bps > 2 * edge_bps, volume_24h ≥ min_volume_24h_usd, no toxic flow detected, KillSwitch inactive, inventory_ratio < 1.0. 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_bps < 2 * edge_bps hard floor; volume too low; toxic flow detected; KillSwitch active; stale feed. Cancel open quotes and emit DecisionReport intent_emitted=false.
WARNING_ONLY
edge_bps between 3 and 6, or volume between 50K and 150K pUSD, triggers warning and 50% size reduction before emitting.
13. Standard Decision Output
This bot returns a OrderIntent object. See OrderIntent schema.
{
"intent_id": "oi_01HX9MKRT1A4Z1B",
"trace_id": "tr_01HX9MKRT1A4VR5",
"market_id": "0xcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890cd",
"outcome": "YES",
"side": "buy",
"price": "0.619",
"size_pUSD": "200.00",
"tif": "GTC",
"post_only": true,
"builder": {
"code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"fee_bps": 30
},
"negrisk_aware": false,
"decision": {
"edge_bps": 10.0,
"inventory_ratio": 0.12,
"skew_applied": true,
"reasons": [
"MAKER_TIGHT_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_TIGHT_QUOTING | INFO | Spread is adequate, volume passes filter, no toxic flow detected. Maker bid+ask OrderIntents emitted. | Emit two GTC post_only OrderIntents. | Maker quotes were posted inside the market spread. |
MAKER_TIGHT_SPREAD_TOO_TIGHT | INFO | Market spread is narrower than 2 * edge_bps hard floor, or volume is below min_volume_24h_usd hard floor. Quoting is not viable. | Skip; emit DecisionReport intent_emitted=false (sampled 1/100). | The market spread was too tight to post a competitive quote. |
MAKER_TIGHT_TOXIC_FLOW_DETECTED | WARN | Taker-initiated buy volume over the last 30s exceeds the toxic flow threshold (0.75 ratio). Directional flow detected. | Skip; cancel open quotes; emit DecisionReport intent_emitted=false. | Strong directional trading was detected. Maker quotes were paused to avoid adverse selection. |
MAKER_TIGHT_EDGE_MARGINAL | WARN | edge_bps is between the warning threshold (6) and the hard floor (3). 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_TIGHT_HIGH_SKEW | WARN | inventory_skew_factor is above the warning threshold (0.6). Inventory is being aggressively managed. | Continue quoting; log warning; monitor inventory_ratio. | Quote prices were significantly adjusted to help balance the current position. |
MAKER_TIGHT_LOW_VOLUME | WARN | 24h volume is between the warning (150K) and hard floor (50K). Market is less liquid than preferred. | Emit at 50% clip size; log warning. | This market has lower than usual activity. 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_makertight_decisions_total | counter | count | verdict, reason_code | Total evaluation cycles by intent_emitted (true/false) and reason code. |
polytraders_strat_makertight_spread_bps | histogram | basis_points | market_id | Distribution of observed market spread in bps at each evaluation, including skipped cycles. |
polytraders_strat_makertight_inventory_ratio | gauge | ratio | market_id | Current inventory ratio (yes_notional / max_inventory) per market. |
polytraders_strat_makertight_intents_emitted_total | counter | count | side | Total maker OrderIntents emitted by side (buy bid / sell ask). |
polytraders_strat_makertight_toxic_flow_skips_total | counter | count | market_id | Number of cycles skipped due to toxic flow detection per market. |
polytraders_strat_makertight_eval_latency_ms | histogram | milliseconds | Wall-clock latency from book tick to OrderIntent emit. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|---|---|---|
MakerTightHighToxicFlowRate | rate(polytraders_strat_makertight_toxic_flow_skips_total[10m]) > 5 | warn | #runbook-makertight-toxic-flow |
MakerTightHighInventory | polytraders_strat_makertight_inventory_ratio > 0.8 | warn | #runbook-makertight-inventory |
MakerTightStaleFeed | rate(polytraders_strat_makertight_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1 | warn | #runbook-makertight-stale-feed |
MakerTightKillSwitchBlocking | rate(polytraders_strat_makertight_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0 | page | #runbook-killswitch |
Dashboards
- Grafana — Strategy / MakerTight spread distribution and quote throughput
- Grafana — Strategy / MakerTight inventory ratio and toxic flow skip rate
16. Developer Reporting
{
"bot_id": "strat.maker_tight",
"market_id": "0xcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890cd",
"mid": 0.624,
"best_bid": 0.619,
"best_ask": 0.629,
"spread_bps": 16.0,
"edge_bps": 10.0,
"inventory_ratio": 0.12,
"skew_applied": true,
"bid_price": 0.619,
"ask_price": 0.629,
"clip_size_pusd": 200.0,
"toxic_flow": false,
"intent_emitted": true,
"reason": "MAKER_TIGHT_QUOTING",
"emitted_at_ms": 1746789800000
}17. Plain-English Reporting
| Situation | User-facing explanation |
|---|---|
| Maker quote posted | A resting order was placed inside the market's current spread to provide liquidity. It will fill if a taker crosses the price. |
| Quote skipped — spread too tight | The market's current bid-ask spread is too narrow to post a quote at a worthwhile price. Quoting was skipped for this cycle. |
| Quote skipped — directional flow detected | Recent trading activity showed a strong directional pattern. Posting a maker order in these conditions increases the risk of being traded against by better-informed participants. |
| Quotes adjusted for inventory balance | Quote prices were shifted slightly to encourage trades that bring the current position back toward neutral. |
18. Failure-Mode Block
| main_failure_mode | Adverse selection: quoted maker orders fill repeatedly immediately after posting because of undetected informed flow, resulting in a directional inventory that cannot be profitably unwound. |
|---|---|
| false_positive_risk | Toxic flow detector is overly sensitive, triggering MAKER_TIGHT_TOXIC_FLOW_DETECTED on normal short-term imbalances and causing the bot to skip valid quoting opportunities. |
| false_negative_risk | Order-flow imbalance window too long (e.g. 60s) causes the bot to miss a brief but high-intensity informed-flow episode, posting quotes that fill immediately at a loss. |
| 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. Fail closed on missing inventory state. |
| required_dependencies | ws_market book and trade-tape stream, clob_public 24h volume, 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. | |
TOXIC_FLOW_SPIKE | Inject mock trade tape with taker_buy_ratio=0.85 for 30s | Automatic when taker_buy_ratio falls below 0.75 over next 30s window. | |
SPREAD_COLLAPSE | Set mock best_bid=0.623, best_ask=0.624 (spread = 1.6 bps < 2*edge_bps=20 bps) | Automatic when spread widens above threshold. | |
KILL_SWITCH_ON | Set killswitch.active=true | Automatic on manual KillSwitch reset. | |
POSITION_READ_FAIL | Block clob_auth GET /positions | Automatic on clob_auth reconnect. |
20. State & Persistence
Cold-start recovery
On cold start, open quote IDs are re-fetched from clob_auth. Inventory is re-read from clob_auth. Book state is rebuilt from first ws_market tick.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|---|
| Execution model | actor-per-market |
| Max in-flight | 40 |
| Idempotency key | intent_id |
| Per-call timeout (ms) | 100 |
| Backpressure strategy | drop oldest pending tick per market_id when queue depth > 5 |
| 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 |
External services
| Service | Endpoint | SLA assumed | On failure |
|---|---|---|---|
| Polymarket CLOB WebSocket (ws_market) | best-effort | ||
| Polymarket CLOB auth API (positions) | 99.95% |
23. Security Surfaces
On-chain contract calls
| Contract | Method | Network | Effect |
|---|---|---|---|
CTFExchangeV2 | | polygon |
Abuse vectors considered
- Quote stuffing: adversary places and cancels orders to artificially move the mid-price seen by Maker-Tight
- Inventory manipulation: feeding a spoofed position to inflate inventory_ratio and widen quotes
- Open maker orders left live after KillSwitch activation
Mitigations
- Book tick staleness check (> 5s) cancels quotes before accepting an adversarial price update
- Inventory ratio is 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, well 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 — fees are operator-set at match time by CTFExchangeV2. 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'. Verified maker rebate flow (20–25% of platform fee, paid in pUSD). |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|---|---|
| Post bid+ask when spread > 2*edge_bps and volume > min_volume | spread=16bps, edge=10bps, volume_24h=500000, toxic_flow=false | Two OrderIntents (bid + ask) emitted with post_only=true |
| Skip when spread < 2*edge_bps hard floor (spread = 4bps, edge = 3bps) | spread=4bps, edge=3bps | No OrderIntents; DecisionReport intent_emitted=false, reason=MAKER_TIGHT_SPREAD_TOO_TIGHT |
| Skip when toxic flow detected | taker_buy_ratio=0.82 (> 0.75 threshold) | No OrderIntents; reason=MAKER_TIGHT_TOXIC_FLOW_DETECTED |
| Apply inventory skew correctly | mid=0.624, edge_bps=10, inventory_ratio=0.4, skew_factor=0.3 | bidPrice = 0.624 - 0.005 - 0.3*0.4*0.001 (skew reduces bid); askPrice shifted symmetrically |
| Reduce clip size when edge is marginal (6 bps warning threshold) | edge_bps=6, clip_size_usd=200 | OrderIntents emitted with size=100; WARN MAKER_TIGHT_EDGE_MARGINAL |
| Skip when KillSwitch active; cancel open quotes | killswitch.active=true, open_quotes=[quote1, quote2] | No new OrderIntents; cancellation of open quotes emitted |
Integration Tests
| Test | Expected result |
|---|---|
| Full cycle: ws_market tick → spread check → two signed V2 maker OrderIntents submitted to CLOB as post_only=true | Both orders contain builder.code (bytes32), no feeRateBps, post_only=true, EIP-712 domain version '2' |
| Sampled 1/100 skip events generate DecisionReport with intent_emitted=false | Exactly ~1% of SPREAD_TOO_TIGHT skips emit a DecisionReport |
Property Tests
| Property | Required behaviour |
|---|---|
| post_only=true on every maker OrderIntent; bot never submits taker orders | Always true |
| feeRateBps field is never present on any emitted OrderIntent | Always true — V2 fees are operator-set at match time |
| Builder fee_bps is always ≤ 50 (maker max) | Always true |
27. Operational Runbook
Maker-Tight incidents are typically elevated toxic flow detection (causing missed quoting) or high inventory ratios requiring manual review. Quote failures due to stale feeds should resolve automatically.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|---|---|---|---|
MakerTightHighToxicFlowRate | ||||
MakerTightHighInventory | ||||
MakerTightStaleFeed | ||||
MakerTightKillSwitchBlocking |
Manual overrides
——
Healthcheck
GET /internal/health/maker-tight -> 200 if ws_market feed last_seen < 5s, clob_auth reachable, KillSwitch inactive, and at least one market quoted in last 60s.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 and toxic flow detection | 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 < 100ms over 24h | polytraders_strat_makertight_eval_latency_ms histogram | p99 < 100ms |
| Inventory_ratio stays below 0.7 over 48h shadow run | polytraders_strat_makertight_inventory_ratio gauge | max < 0.7 |
Promote to General live
| Gate | How measured | Threshold |
|---|---|---|
| E2E: book tick → two signed V2 post_only GTC OrderIntents submitted and resting on CLOB testnet | E2E test | Pass |
| Sampled 1/100 DecisionReport for skipped cycles verified in integration test | 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 |