Polytraders Dev Guide
internal
v3 spine Phase 1 · Shared contracts 9 demo-wired · 0 shadow-ready · 0 production-live · 100 pending · 109 total 15/33 infra tasks the plan status board
HomeBy LayerStrategy3.5 Bregman-Projection Arb

3.5 Bregman-Projection Arb

Strategy Alpha Strategy Trade BETA Limited live capital · Direct P8 · Additional strategies pending stub

Bregman-Projection Arb detects when the joint probability distribution implied by a neg-risk multi-outcome market's order book diverges from the closest valid (simplex-constrained) distribution by more than kl_divergence_threshold nats. It uses a Frank-Wolfe iterative projection to find the nearest valid distribution, measures the KL-divergence of the observed book from that projection, and emits a set of up to max_legs_per_trade OrderIntents to exploit the divergence. This is a user-controlled execution tool for multi-outcome neg-risk markets. It never trades standard 2-outcome binary markets (those are handled by Sum-to-One Arb). No performance claims are made; the tool automates the mechanical detection and sizing of Bregman-projection violations.

v3 readiness

Docs27/27
donehow scored
Impl0/15
pendinghow scored
Backtest0/4
pendinghow scored
Runtime0/8
pendinghow scored

A bot is done when all four scores are. What does done mean?

1. Bot Identity

LayerStrategy  Strategy
Bot classAlpha Strategy
AuthorityTrade
StatusBETA
ReadinessLimited live
Runs beforeRisk guardrail pipeline
Runs afterMarket scanner / opportunity feed
Applies toNegative-risk multi-outcome CLOB markets where the joint order-book distribution deviates from the nearest Bregman-projection-valid simplex distribution by more than kl_divergence_threshold
Default modelimited_live
User-visibleAdvanced details only
Developer ownerPolytraders core — Strategy pod

2. Purpose

Bregman-Projection Arb detects when the joint probability distribution implied by a neg-risk multi-outcome market's order book diverges from the closest valid (simplex-constrained) distribution by more than kl_divergence_threshold nats. It uses a Frank-Wolfe iterative projection to find the nearest valid distribution, measures the KL-divergence of the observed book from that projection, and emits a set of up to max_legs_per_trade OrderIntents to exploit the divergence. This is a user-controlled execution tool for multi-outcome neg-risk markets. It never trades standard 2-outcome binary markets (those are handled by Sum-to-One Arb). No performance claims are made; the tool automates the mechanical detection and sizing of Bregman-projection violations.

3. Why This Bot Matters

  • KL-divergence computed on stale snapshots

    The divergence measurement reflects an order book that no longer exists. Emitting OrderIntents based on stale data incurs slippage on all legs and may produce a net loss.

  • Frank-Wolfe projection does not converge within frank_wolfe_iters

    The nearest valid distribution is not found; the measured divergence is an upper bound, not the true value. The bot may under-size or skip genuinely profitable trades.

  • Leg count exceeds max_legs_per_trade while liquidity is thin

    In a 10-outcome market, all legs may be needed to fully capture the divergence. Capping legs leaves residual exposure that does not hedge cleanly.

  • NegRisk market settled via DVM while legs are open

    If a disputed NegRisk outcome enters a 24-48h UMA DVM vote, open positions cannot be settled immediately. The bot must detect the dispute flag and halt new legs on affected markets.

  • feeRateBps present on signed order (V1 pattern)

    CTFExchangeV2 rejects orders with feeRateBps. Fees are operator-set at match time. No leg intent may contain this field.

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

InputSourceRequired?Use
Full order book for all outcome tokens in the neg-risk marketws_market (CLOB WebSocket)YesBuild observed joint distribution from best-ask prices across all outcome legs.
Market condition ID, outcome token IDs, negRisk flag, NegRiskAdapter addressclob_public / onchainYesConfirm market uses NegRiskAdapter and identify all outcome token IDs for the projection.
Top-of-book depth for each outcome legclob_publicYesSize each leg to min(depth_available, liquidity_cap_usd / n_legs).
Market open/closed/resolved/dispute statusclob_publicYesSkip markets in UMA DVM dispute or resolution; halt new legs immediately.
Historical co-movement matrix (internal feed)internalNoOptional prior for initialising the Frank-Wolfe projection; speeds up convergence.

5. Required Internal Inputs

InputSourceRequired?Use
KillSwitch active flagKillSwitchYesAbort all intent emission immediately if KillSwitch is active.
Builder code bytes32internal configYesInjected into builder field on every signed V2 OrderIntent for attribution.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
kl_divergence_threshold0.0150.0080.003Minimum KL-divergence (in nats) from the nearest valid simplex distribution required before emitting OrderIntents.
frank_wolfe_iters2008030Maximum iterations for the Frank-Wolfe simplex projection algorithm. Higher values give a more precise projection but increase latency.
max_legs_per_trade6912Maximum number of outcome-token legs emitted in a single trade cycle. Limits exposure on high-outcome-count markets.
liquidity_cap_usd400600800Total pUSD budget divided evenly across emitted legs. Each leg is capped at liquidity_cap_usd / n_legs, further bounded by available depth.

7. Detailed Parameter Instructions

kl_divergence_threshold

What it means

Minimum KL-divergence (in nats) from the nearest valid simplex distribution required before emitting OrderIntents.

Default

{ "kl_divergence_threshold": 0.015 }

Why this default matters

0.015 nats provides meaningful edge after fee drag on typical neg-risk books. Below 0.008 the divergence is marginal; below 0.003 the bot will not fire regardless of config.

Threshold logic

ConditionAction
kl_div >= 0.015EMIT multi-leg OrderIntents
0.008 <= kl_div < 0.015WARN BREGMAN_ARB_DIVERGENCE_MARGINAL; emit at 50% leg size
kl_div < 0.003 (hard floor)SKIP — BREGMAN_ARB_NO_EDGE; do not emit

Developer check

if kl_div < params.hard: return skip('BREGMAN_ARB_NO_EDGE')

User-facing English

The multi-outcome pricing distribution was not far enough from the valid range to justify a trade after fees.

frank_wolfe_iters

What it means

Maximum iterations for the Frank-Wolfe simplex projection algorithm. Higher values give a more precise projection but increase latency.

Default

{ "frank_wolfe_iters": 200 }

Why this default matters

200 iterations converges to within 1e-6 nats on markets with up to 20 outcomes. Below 80 the projection may not converge on large markets.

Threshold logic

ConditionAction
>= 200Full convergence expected
80–200WARN BREGMAN_ARB_PROJECTION_MARGINAL; acceptable for small markets (≤ 5 outcomes)
< 30Hard floor; config rejected

Developer check

assert params.frank_wolfe_iters >= params.hard

User-facing English

— not yet authored —

max_legs_per_trade

What it means

Maximum number of outcome-token legs emitted in a single trade cycle. Limits exposure on high-outcome-count markets.

Default

{ "max_legs_per_trade": 6 }

Why this default matters

6 legs covers most neg-risk markets cleanly. Beyond 9 legs the tail positions have tiny notional and the incremental edge is smaller than combined fee drag.

Threshold logic

ConditionAction
<= 6Normal; all legs emitted
7–12WARN; each additional leg has diminishing edge
> 12Reject config — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

if params.max_legs_per_trade > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')

User-facing English

Trade was sized across the most mispriced outcome legs.

liquidity_cap_usd

What it means

Total pUSD budget divided evenly across emitted legs. Each leg is capped at liquidity_cap_usd / n_legs, further bounded by available depth.

Default

{ "liquidity_cap_usd": 400 }

Why this default matters

400 pUSD total keeps per-leg sizes modest (< 70 pUSD each for 6 legs) and within typical neg-risk top-of-book depth.

Threshold logic

ConditionAction
<= 400 pUSDNormal leg sizing
400–800 pUSDWARN; confirm depth covers each leg
> 800 pUSDReject config — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

legSize = min(depthAvail, liquidity_cap_usd / n_legs)

User-facing English

Each leg of the multi-outcome trade was sized to fit within available market liquidity.

8. Default Configuration

{
  "bot_id": "strat.bregman_projection_arb",
  "version": "2.1.0",
  "mode": "limited_live",
  "defaults": {
    "kl_divergence_threshold": 0.015,
    "frank_wolfe_iters": 200,
    "max_legs_per_trade": 6,
    "liquidity_cap_usd": 400
  },
  "locked": {
    "kl_divergence_threshold": {
      "min": 0.003
    },
    "frank_wolfe_iters": {
      "min": 30
    },
    "max_legs_per_trade": {
      "max": 12
    },
    "liquidity_cap_usd": {
      "max": 800
    }
  }
}

9. Implementation Flow

  1. Check KillSwitch active flag; if active, emit no OrderIntents.
  2. Subscribe to ws_market book updates for all active neg-risk multi-outcome markets.
  3. On each book snapshot: validate freshness (last_seen < 3s); else emit STALE_MARKET_DATA and skip.
  4. Confirm market negRisk=true and not closed/resolved/in-dispute via clob_public.
  5. Build observed probability vector p_obs from best-ask prices across all outcome token IDs (normalise to sum-to-one).
  6. Run Frank-Wolfe projection: initialise q = uniform; iterate up to frank_wolfe_iters; converge to nearest simplex distribution q*.
  7. Compute KL(p_obs || q*). If < kl_divergence_threshold hard floor (0.003), emit DecisionReport intent_emitted=false BREGMAN_ARB_NO_EDGE (sampled 1/100); RETURN.
  8. If kl_div < warning threshold (0.015), WARN BREGMAN_ARB_DIVERGENCE_MARGINAL; reduce all leg sizes by 50%.
  9. Sort outcome legs by |p_obs_i - q*_i| descending; select top min(max_legs_per_trade, n_outcomes) legs.
  10. Fetch top-of-book depth for each selected leg from clob_public; set legSize = min(depth, liquidity_cap_usd / n_legs).
  11. Emit OrderIntent for each selected leg: market_id, outcome_token_id, side=buy (if p_obs_i > q*_i: NO/short; else YES/long), price=best_ask, size_pUSD=legSize, tif=FOK, builder={code, fee_bps:25}.
  12. Note: fees are operator-set at match time in V2 — feeRateBps is NOT on any signed order.
  13. Emit DecisionReport with intent_emitted=true, kl_divergence, n_legs, reason BREGMAN_ARB_EDGE_DETECTED.

10. Reference Implementation

Subscribes to CLOB WebSocket book snapshots for neg-risk markets, runs Frank-Wolfe simplex projection to find the nearest valid distribution, measures KL-divergence, and emits per-leg FOK OrderIntents when divergence exceeds threshold.

Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. Translate to TS/Python/Go/Rust.

FUNCTION evaluateNegRiskMarket(market_id, bookSnapshot):
  // --- 0. KillSwitch gate ---
  ks = FETCH internal.killswitch.status
  IF ks.active: RETURN

  // --- 1. Staleness check ---
  IF isStale(bookSnapshot, maxAgeS=3):
    EMIT DecisionReport(intent_emitted=false, reason='STALE_MARKET_DATA')
    RETURN

  // --- 2. NegRisk market validation ---
  market = FETCH clob_public.GET('/markets/' + market_id)
  IF NOT market.neg_risk OR market.closed OR market.resolved OR market.dispute_active:
    EMIT DecisionReport(intent_emitted=false, reason='MARKET_CLOSED')
    RETURN

  // --- 3. Build observed probability vector ---
  outcomes = bookSnapshot.outcomes  // list of {token_id, best_ask, depth_pusd}
  p_obs = [o.best_ask for o in outcomes]
  p_obs = normalise(p_obs)  // sum-to-one on simplex

  // --- 4. Frank-Wolfe projection to nearest valid distribution ---
  q = uniform(len(outcomes))  // init
  FOR iter IN range(params.frank_wolfe_iters):
    grad = KL_gradient(p_obs, q)  // ∇KL(p||q) = -p/q
    s = argmin_simplex(grad)      // Frank-Wolfe linear minimisation step
    gamma = 2.0 / (iter + 2)     // step size
    q_new = (1 - gamma) * q + gamma * s
    IF norm(q_new - q) < 1e-8: BREAK
    q = q_new
  q_star = q

  // --- 5. KL-divergence check ---
  kl_div = KL(p_obs, q_star)
  IF kl_div < params.kl_divergence_threshold_hard:  // 0.003
    IF random() < 0.01:
      EMIT DecisionReport(intent_emitted=false, reason='BREGMAN_ARB_NO_EDGE', kl_div=kl_div)
    RETURN

  // --- 6. Warning threshold ---
  sizeMultiplier = 1.0
  IF kl_div < params.kl_divergence_threshold:  // 0.015
    WARN('BREGMAN_ARB_DIVERGENCE_MARGINAL')
    sizeMultiplier = 0.5

  // --- 7. Select legs by divergence magnitude ---
  divergences = [(i, abs(p_obs[i] - q_star[i])) for i in range(len(outcomes))]
  divergences.sort(by=magnitude, descending=true)
  selected = divergences[:params.max_legs_per_trade]

  // --- 8. Size and emit legs ---
  legBudget = params.liquidity_cap_usd / len(selected)
  FOR (i, _) IN selected:
    legSize = toPusdUnits(min(outcomes[i].depth_pusd, legBudget) * sizeMultiplier)
    side = 'buy' IF p_obs[i] < q_star[i] ELSE 'sell'
    EMIT OrderIntent(
      market_id        = market_id,
      outcome_token_id = outcomes[i].token_id,
      side             = side,
      price            = outcomes[i].best_ask,
      size_pUSD        = legSize,
      tif              = 'FOK',
      post_only        = false,
      negrisk_aware    = true,
      builder          = {code: internal.builder_code, fee_bps: 25}
      // V2: no feeRateBps on signed order
    )

  EMIT DecisionReport(intent_emitted=true, kl_divergence=kl_div,
                      n_legs=len(selected), reason='BREGMAN_ARB_EDGE_DETECTED')

SDK calls used

  • fetchClobPublic('/markets/' + market_id)
  • ws_market.subscribe('book', outcome_token_ids)
  • toPusdUnits(rawFloat)
  • buildOrderTypedData(orderParams, { name: 'CTFExchange', version: '2', chainId: 137 })
  • internal.killswitch.status()
  • internal.builder_code

Complexity: O(k * frank_wolfe_iters) per book snapshot, where k = number of outcomes

11. Wire Examples

Input — what arrives on the wire

NegRisk market book snapshot — 8 outcomes, divergence detectedws_market

{
  "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
  "neg_risk": true,
  "outcomes": [
    {
      "token_id": "0xbrg_tk_0",
      "best_ask": "0.310",
      "depth_pusd": "180.00"
    },
    {
      "token_id": "0xbrg_tk_1",
      "best_ask": "0.185",
      "depth_pusd": "220.00"
    },
    {
      "token_id": "0xbrg_tk_2",
      "best_ask": "0.112",
      "depth_pusd": "90.00"
    },
    {
      "token_id": "0xbrg_tk_3",
      "best_ask": "0.098",
      "depth_pusd": "75.00"
    },
    {
      "token_id": "0xbrg_tk_4",
      "best_ask": "0.095",
      "depth_pusd": "60.00"
    },
    {
      "token_id": "0xbrg_tk_5",
      "best_ask": "0.074",
      "depth_pusd": "50.00"
    },
    {
      "token_id": "0xbrg_tk_6",
      "best_ask": "0.068",
      "depth_pusd": "45.00"
    },
    {
      "token_id": "0xbrg_tk_7",
      "best_ask": "0.058",
      "depth_pusd": "40.00"
    }
  ],
  "received_at_ms": 1746790000000
}

Output — what the bot emits

OrderIntent — leg 2 (buy outcome 2, FOK, builder-attributed)

{
  "intent_id": "oi_01HXB9PARZ0001A",
  "trace_id": "tr_01HXB9PARZ000TR",
  "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
  "outcome_token_id": "0xbrg_tk_2",
  "side": "buy",
  "price": "0.112",
  "size_pUSD": "66.00",
  "tif": "FOK",
  "post_only": false,
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "negrisk_aware": true,
  "decision": {
    "kl_divergence": 0.022,
    "n_legs": 6,
    "leg_index": 2,
    "reasons": [
      "BREGMAN_ARB_EDGE_DETECTED"
    ]
  },
  "comment": "fees are operator-set at match time in V2 — feeRateBps is NOT on the signed order"
}

DecisionReport — skipped (no edge), sampled 1/100

{
  "report_id": "dr_01HXB9PARZ999Z",
  "bot_id": "strat.bregman_projection_arb",
  "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
  "intent_emitted": false,
  "kl_divergence": 0.001,
  "reasons": [
    "BREGMAN_ARB_NO_EDGE"
  ],
  "sampled": true,
  "evaluated_at_ms": 1746790001000
}

12. Decision Logic

APPROVE

kl_div >= kl_divergence_threshold, market is neg-risk and open, all legs have depth, KillSwitch inactive. Emit per-leg OrderIntents (FOK).

RESHAPE_REQUIRED

Not applicable — strat bots emit OrderIntents; reshaping is handled by the downstream Risk guardrail pipeline.

REJECT

kl_div < 0.003 hard floor; market closed/resolved/disputed; KillSwitch active; stale snapshot. Emit DecisionReport intent_emitted=false.

WARNING_ONLY

kl_div between 0.003 and 0.015 triggers BREGMAN_ARB_DIVERGENCE_MARGINAL warn and 50% size reduction.

13. Standard Decision Output

This bot returns a OrderIntent object. See OrderIntent schema.

{
  "intent_id": "oi_01HXB9PARZ0001A",
  "trace_id": "tr_01HXB9PARZ000TR",
  "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
  "outcome_token_id": "0xbregman_tk_outcome_3",
  "side": "buy",
  "price": "0.112",
  "size_pUSD": "66.00",
  "tif": "FOK",
  "post_only": false,
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "negrisk_aware": true,
  "decision": {
    "kl_divergence": 0.022,
    "n_legs": 6,
    "leg_index": 2,
    "reasons": [
      "BREGMAN_ARB_EDGE_DETECTED"
    ]
  },
  "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
BREGMAN_ARB_EDGE_DETECTEDINFOKL-divergence from the nearest valid simplex distribution exceeds kl_divergence_threshold. Multi-leg OrderIntents emitted.Emit per-leg FOK OrderIntents.A pricing inconsistency across multiple outcomes was detected and orders were placed to capture it.
BREGMAN_ARB_NO_EDGEINFOKL-divergence is below the 0.003 nats absolute hard floor. No trade opportunity exists after fees.Skip; emit DecisionReport intent_emitted=false (sampled 1/100).The multi-outcome pricing was within normal bounds. No order was placed.
BREGMAN_ARB_DIVERGENCE_MARGINALWARNKL-divergence is between the hard floor (0.003) and the warning threshold (0.015). Trade is marginal.Emit OrderIntents at 50% leg size; log warning.A small pricing inconsistency was detected. Leg sizes were reduced.
BREGMAN_ARB_PROJECTION_MARGINALWARNFrank-Wolfe iteration count is below the warning threshold; projection may not have fully converged.Emit with caution; log warning for ops review.
BREGMAN_ARB_DEPTH_INSUFFICIENTWARNTop-of-book depth on one or more selected legs is below minimum viable trade size (5 pUSD).Skip affected leg; reduce n_legs; emit DecisionReport.Not enough liquidity on some outcome legs to place the full trade.
STALE_MARKET_DATAHARD_REJECTBook snapshot is older than 3 seconds or market is in UMA DVM dispute.Skip; no OrderIntent emitted.Market data was too old to act on safely.
MARKET_CLOSEDHARD_REJECTMarket is closed, resolved, disputed, or is not a neg-risk market.Skip immediately; no OrderIntent emitted.This market is no longer open for trading.
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch is active.Skip all markets; no OrderIntents emitted.Trading is currently paused.
PARAMETER_CHANGE_REQUIRES_APPROVALHARD_REJECTA config change would push a parameter past its locked hard limit.Reject config change; do not apply.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_strat_bregmanarb_decisions_totalcountercountverdict, reason_codeTotal evaluation cycles by intent_emitted (true/false) and reason code.
polytraders_strat_bregmanarb_kl_divergencehistogramnatsDistribution of measured KL-divergence across all evaluated neg-risk markets.
polytraders_strat_bregmanarb_legs_emitted_totalcountercountoutcome_indexTotal OrderIntents emitted per outcome leg index.
polytraders_strat_bregmanarb_projection_itershistogramiterationsFrank-Wolfe iteration count at convergence per evaluation.
polytraders_strat_bregmanarb_eval_latency_mshistogrammillisecondsWall-clock time from book snapshot receipt to last OrderIntent emit.
polytraders_strat_bregmanarb_stale_feed_totalcountercountEvaluation cycles skipped due to stale ws_market feed.

Alerts

AlertConditionSeverityRunbook
BregmanArbHighStaleFeedrate(polytraders_strat_bregmanarb_stale_feed_total[5m]) > 0.1warn#runbook-bregmanarb-stale-feed
BregmanArbHighLatencyhistogram_quantile(0.99, rate(polytraders_strat_bregmanarb_eval_latency_ms_bucket[5m])) > 300warn#runbook-bregmanarb-latency
BregmanArbNoEdgeStreakrate(polytraders_strat_bregmanarb_decisions_total{verdict='false'}[15m]) / rate(polytraders_strat_bregmanarb_decisions_total[15m]) > 0.99warn#runbook-bregmanarb-no-edge
BregmanArbKillSwitchBlockingrate(polytraders_strat_bregmanarb_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0page#runbook-killswitch

Dashboards

  • Grafana — Strategy / BregmanArb KL-divergence distribution
  • Grafana — Strategy / BregmanArb leg throughput and projection convergence

16. Developer Reporting

{
  "bot_id": "strat.bregman_projection_arb",
  "market_id": "0xbregman0000000000000000000000000000000000000000000000000000000001",
  "n_outcomes": 8,
  "kl_divergence": 0.022,
  "frank_wolfe_iters_used": 147,
  "n_legs_selected": 6,
  "liquidity_cap_usd": 400,
  "per_leg_size_pusd": 66.0,
  "intent_emitted": true,
  "reason": "BREGMAN_ARB_EDGE_DETECTED",
  "emitted_at_ms": 1746790000000
}

17. Plain-English Reporting

SituationUser-facing explanation
Multi-outcome arb trade initiatedThe combined pricing across several outcomes of this multi-choice market deviated from a mathematically consistent distribution. Orders were placed across the mispriced outcomes to capture the discrepancy.
No divergence — no tradeThe multi-outcome pricing was within normal bounds after fees. No order was placed.
Divergence marginal — reduced sizeA small pricing inconsistency was detected. Leg sizes were halved to limit exposure given the thin margin.
Market in disputeThis market's outcome is under review. No new orders were placed until the dispute resolves.

18. Failure-Mode Block

main_failure_modeFrank-Wolfe projection does not converge within frank_wolfe_iters: the KL-divergence measurement is imprecise, and the bot may misidentify which legs to trade or over-size positions.
false_positive_riskStale or illiquid book data produces a spurious divergence; the bot emits legs that no longer have edge by the time they reach the CLOB.
false_negative_riskkl_divergence_threshold set too conservatively misses genuine divergences on smaller-outcome-count markets where even 0.010 nats represents meaningful pricing error.
safe_fallbackIf book snapshot is stale (last_seen > 3s) or market is in UMA DVM dispute, emit STALE_MARKET_DATA DecisionReport and skip without emitting any OrderIntent.
required_dependenciesws_market book stream (all outcome token IDs for neg-risk market), clob_public market endpoint (status + depth + negRisk flag), KillSwitch active flag, internal builder code, Historical co-movement matrix (optional; improves projection init)

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
STALE_WS_FEEDPause ws_market WebSocket; let last_seen age beyond 3sAutomatic on WebSocket reconnect within one evaluation cycle.
PROJECTION_NON_CONVERGENCESet frank_wolfe_iters=1 on a 15-outcome marketIncrease frank_wolfe_iters in config; ops review required.
DISPUTE_ACTIVESet market.dispute_active=true on the test marketAutomatic when dispute resolves and market returns to open status.
KILL_SWITCH_ONSet killswitch.active=trueAutomatic on manual KillSwitch reset.
DEPTH_INSUFFICIENT_ON_LEGSet mock depth on outcome leg 3 to 2 pUSD (below 5 pUSD floor)Automatic when depth replenishes.

20. State & Persistence

Cold-start recovery

On cold start, state is empty; first book snapshot triggers fresh Frank-Wolfe projection without stale-state comparison.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight30
Idempotency keyintent_id
Per-call timeout (ms)400
Backpressure strategydrop oldest pending snapshot per market_id when queue depth > 3
Locking / mutual exclusionper-market_id mutex for Redis state write during projection

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchChecked first; blocks all intent emission when active.

Emits to (downstream consumers)

External services

ServiceEndpointSLA assumedOn failure
Polymarket CLOB v2 (public)99.9% (Polymarket-published)
Polymarket CLOB WebSocket (ws_market)best-effort

23. Security Surfaces

On-chain contract calls

ContractMethodNetworkEffect
CTFExchangeV2polygon

Abuse vectors considered

  • Adversary spams thin neg-risk legs to force spurious KL-divergence signals
  • Replaying a signed OrderIntent after the projected divergence closes
  • Injecting a modified builder.code to redirect attribution fees

Mitigations

  • All legs use FOK; partial fills are rejected rather than leaving naked positions
  • V2 order timestamp(ms) invalidates replays outside the exchange acceptance window
  • Builder code is read from immutable internal config; not user-supplied
  • Book snapshot staleness check (3s) prevents acting on stale divergence signals

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldyes
Aware of negative-risk marketsyes
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesOperates exclusively on negative-risk multi-outcome markets via NegRiskAdapter. Builder code is injected on each leg intent. feeRateBps is not present on any signed order.

API surfaces declared

clob_publicclob_authws_marketonchaininternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation2.1.0
schema2
released2026-04-28

Migration history

DateFromToReasonAction taken
2026-04-28v1 (USDC.e, feeRateBps on signed order)v2 (pUSD, fees operator-set at match time)CLOB V2 cutoverSwitched to py-clob-client-v2. Removed feeRateBps from all signed multi-leg order construction. Updated collateral denomination to pUSD. Injected builder field (bytes32) on every OrderIntent. EIP-712 Exchange domain version updated from '1' to '2'. NegRisk market handling now uses NegRiskAdapter for multi-outcome CLOB V2 settlement.

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Emit 6 legs when kl_div=0.022 (above threshold 0.015)8-outcome market; p_obs deviates from projection by 0.022 nats; depth=200 pUSD eachSix OrderIntents emitted; DecisionReport intent_emitted=true, reason=BREGMAN_ARB_EDGE_DETECTED
Skip when kl_div=0.002 (below hard floor 0.003)p_obs near-valid distribution; kl_div=0.002No OrderIntents; sampled DecisionReport reason=BREGMAN_ARB_NO_EDGE
Reduce leg sizes 50% when kl_div marginal (0.010)kl_div=0.010, liquidity_cap_usd=400, n_legs=6Six OrderIntents at 33 pUSD each (50% of 66); WARN BREGMAN_ARB_DIVERGENCE_MARGINAL
Skip when market in UMA DVM disputemarket.dispute_active=trueNo OrderIntents; reason=MARKET_CLOSED
Skip when KillSwitch activekillswitch.active=trueNo OrderIntents emitted
Frank-Wolfe iteration cap enforcedfrank_wolfe_iters=200; convergence at iter 147Projection completes; developer_log.frank_wolfe_iters_used <= 200

Integration Tests

TestExpected result
Full cycle: neg-risk ws_market snapshot → KL computed → 6 signed V2 FOK OrderIntents submittedAll intents have builder.code (bytes32), no feeRateBps, negrisk_aware=true, EIP-712 domain version '2'
Stale ws_market feed triggers STALE_MARKET_DATA skipDecisionReport intent_emitted=false, reason=STALE_MARKET_DATA after 3s feed gap

Property Tests

PropertyRequired behaviour
All emitted leg OrderIntents are FOK; bot never posts resting maker ordersAlways true
n_legs emitted never exceeds max_legs_per_tradeAlways true
feeRateBps never present on any signed OrderIntentAlways true — V2 fees are operator-set at match time

27. Operational Runbook

Bregman-Projection Arb incidents are usually stale feed, Frank-Wolfe non-convergence on high-outcome markets, or UMA DVM dispute halts. Non-convergence requires a config fix; dispute halts resolve automatically.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
BregmanArbHighStaleFeed
BregmanArbHighLatency
BregmanArbNoEdgeStreak
BregmanArbKillSwitchBlocking

Manual overrides

Healthcheck

GET /internal/health/bregman-projection-arb -> 200 if ws_market feed last_seen < 3s, Redis reachable, KillSwitch inactive, at least one market evaluated 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

GateHow measuredThreshold
All unit tests pass including FOK-only invariant and projection convergenceCI test run100% pass
feeRateBps absence verified; negrisk_aware=true on all OrderIntentsIntegration test asserting V2 order schemaPass

Promote to Limited live

GateHow measuredThreshold
p99 eval latency < 400ms over 24h (includes Frank-Wolfe projection time)polytraders_strat_bregmanarb_eval_latency_ms histogramp99 < 400ms
Zero partial-leg fills (all FOK either fully fill or reject) in 48h shadow runFill reconciliation report0 partial fills

Promote to General live

GateHow measuredThreshold
E2E: neg-risk book snapshot → KL computed → N signed V2 FOK OrderIntents submitted on Polygon testnetE2E testPass
Frank-Wolfe projection convergence verified across 10 synthetic market distributionsUnit test suiteAll converge within frank_wolfe_iters

29. Developer Checklist

Ready-to-ship score: 27/27 sections complete · 100%

RequirementStatus
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