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.6 Cross-Market Arb

3.6 Cross-Market Arb

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

Cross-Market Arb detects logically constrained relationships between pairs of Polymarket binary markets — e.g. 'Candidate A wins primary' should price at or below 'Candidate A wins general', or 'Team X advances to semi-final' should price at or below 'Team X wins tournament'. When the logical constraint is violated by more than tolerance_bps (after fee buffer), the bot emits a pair of OrderIntents (one per market) to exploit the mispricing. Market pairs must pass manual review before the bot will trade them (require_manual_pair_review=true). This is a user-controlled execution tool; it does not copy trades or make performance claims.

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 toManually reviewed pairs of binary markets with logical price constraints (e.g. nominee ≤ winner, team advances ≤ tournament winner) where the constraint is violated by more than tolerance_bps
Default modelimited_live
User-visibleAdvanced details only
Developer ownerPolytraders core — Strategy pod

2. Purpose

Cross-Market Arb detects logically constrained relationships between pairs of Polymarket binary markets — e.g. 'Candidate A wins primary' should price at or below 'Candidate A wins general', or 'Team X advances to semi-final' should price at or below 'Team X wins tournament'. When the logical constraint is violated by more than tolerance_bps (after fee buffer), the bot emits a pair of OrderIntents (one per market) to exploit the mispricing. Market pairs must pass manual review before the bot will trade them (require_manual_pair_review=true). This is a user-controlled execution tool; it does not copy trades or make performance claims.

3. Why This Bot Matters

  • Logically distinct markets paired incorrectly

    A pair that looks logically constrained but has different resolution conditions may diverge legitimately. Trading a false constraint produces losses when both markets resolve consistently with their actual rules.

  • One market resolves while the paired order is still resting

    If market A resolves YES and the paired market B order has not filled, the bot holds a one-sided position with no offsetting settlement path.

  • Tolerance_bps too tight on high-spread markets

    The bot may fire on transient spread noise rather than genuine constraint violations, incurring fee costs with no structural edge.

  • 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
Best bid/ask for each market in registered pairsws_market (CLOB WebSocket)YesMonitor for constraint violations: p_market_A > p_market_B + tolerance_bps when A implies B.
Market metadata: condition ID, outcome token IDs, resolution rules, categorygammaYesValidate that pair logic and resolution conditions align before registering a market pair.
Market open/closed/resolved statusclob_publicYesSkip pairs where either market is closed or resolved; cancel resting legs immediately.
Top-of-book depth on both marketsclob_publicYesSize each leg to min(depth, per-leg budget from liquidity budget).
Embedding-based similarity score (e5-large-v2 or similar)internalNoCross-check that proposed pair markets are semantically related; flag low-similarity pairs for human review.

5. Required Internal Inputs

InputSourceRequired?Use
KillSwitch active flagKillSwitchYesAbort all intent emission immediately if KillSwitch is active.
Registered market-pair registry (manually reviewed)internal configYesBot only evaluates pairs in the approved registry. Pairs require human review before entry.
Builder code bytes32internal configYesInjected into builder field on every signed V2 OrderIntent.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
tolerance_bps30155Minimum basis-point violation of the logical constraint (after fee buffer) required before emitting OrderIntents.
min_similarity_score0.820.70.6Minimum embedding-based similarity score (0–1) between the two market questions before the pair is eligible for auto-registration.
require_manual_pair_reviewTrueNoneNoneWhen true, every new market pair must be manually reviewed and approved before the bot will trade it. Cannot be set to false in production.
max_legs234Maximum number of simultaneous legs per detected constraint violation. Standard cross-market arb requires exactly 2 legs.

7. Detailed Parameter Instructions

tolerance_bps

What it means

Minimum basis-point violation of the logical constraint (after fee buffer) required before emitting OrderIntents.

Default

{ "tolerance_bps": 30 }

Why this default matters

30 bps covers fee drag and expected slippage on a typical Polymarket binary pair. Below 15 bps the violation may be transient noise; below 5 bps the bot will not fire.

Threshold logic

ConditionAction
>= 30 bpsEMIT pair OrderIntents
15–30 bpsWARN CROSS_MARKET_ARB_VIOLATION_MARGINAL; emit at 50% size
< 5 bps (hard floor)SKIP — CROSS_MARKET_ARB_NO_EDGE

Developer check

if violation_bps < params.hard: return skip('CROSS_MARKET_ARB_NO_EDGE')

User-facing English

The pricing inconsistency between linked markets was too small to trade profitably after fees.

min_similarity_score

What it means

Minimum embedding-based similarity score (0–1) between the two market questions before the pair is eligible for auto-registration.

Default

{ "min_similarity_score": 0.82 }

Why this default matters

0.82 ensures markets are semantically related enough to have a meaningful logical constraint. Below 0.70 pairs are likely comparing unrelated events.

Threshold logic

ConditionAction
>= 0.82Pair eligible; still requires manual review
0.70–0.82WARN CROSS_MARKET_ARB_LOW_SIMILARITY; manual review mandatory before any trade
< 0.60Hard floor; pair rejected from registry

Developer check

if similarity < params.hard: raise ConfigError('CROSS_MARKET_ARB_LOW_SIMILARITY')

User-facing English

— not yet authored —

require_manual_pair_review

What it means

When true, every new market pair must be manually reviewed and approved before the bot will trade it. Cannot be set to false in production.

Default

{ "require_manual_pair_review": true }

Why this default matters

Prevents the bot from auto-discovering and trading logically incorrect pairs that could produce losses.

Threshold logic

ConditionAction
trueNormal; all pairs require manual approval
falseNot permitted in production; config rejected

Developer check

assert params.require_manual_pair_review == True

User-facing English

— not yet authored —

max_legs

What it means

Maximum number of simultaneous legs per detected constraint violation. Standard cross-market arb requires exactly 2 legs.

Default

{ "max_legs": 2 }

Why this default matters

2 legs covers the standard binary pair. Values > 2 are only valid for chained logical constraints (A implies B implies C).

Threshold logic

ConditionAction
== 2Normal paired trade
3–4WARN; chained constraint mode; each additional leg increases execution risk
> 4Reject config — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

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

User-facing English

The trade was placed across the two linked markets simultaneously.

8. Default Configuration

{
  "bot_id": "strat.cross_market_arb",
  "version": "2.1.0",
  "mode": "limited_live",
  "defaults": {
    "tolerance_bps": 30,
    "min_similarity_score": 0.82,
    "require_manual_pair_review": true,
    "max_legs": 2
  },
  "locked": {
    "tolerance_bps": {
      "min": 5
    },
    "min_similarity_score": {
      "min": 0.6
    },
    "require_manual_pair_review": {
      "value": true
    },
    "max_legs": {
      "max": 4
    }
  }
}

9. Implementation Flow

  1. Check KillSwitch active flag; if active, emit no OrderIntents.
  2. Load registered market-pair registry (manually reviewed pairs only).
  3. Subscribe to ws_market book updates for all markets in active pairs.
  4. On each book tick: retrieve the latest best-ask for each market in the pair.
  5. Validate book freshness (last_seen < 5s); if stale, emit STALE_MARKET_DATA and skip.
  6. Confirm both markets in the pair are open and not resolved (clob_public).
  7. Compute violation_bps = (p_market_A - p_market_B) * 10000 for pairs where A logically implies B.
  8. If violation_bps < tolerance_bps hard floor (5 bps), emit sampled DecisionReport CROSS_MARKET_ARB_NO_EDGE and skip.
  9. If violation_bps < warning threshold (30 bps), WARN CROSS_MARKET_ARB_VIOLATION_MARGINAL; reduce leg sizes by 50%.
  10. Fetch top-of-book depth for both markets; set legSize = min(depth_A, depth_B, per-leg budget).
  11. Emit OrderIntent for market A (sell YES / buy NO — the overpriced side).
  12. Emit OrderIntent for market B (buy YES — the underpriced side).
  13. Note: fees are operator-set at match time in V2 — feeRateBps is NOT on any signed order.
  14. Emit DecisionReport with intent_emitted=true, violation_bps, pair_id, reason CROSS_MARKET_ARB_EDGE_DETECTED.

10. Reference Implementation

Subscribes to CLOB WebSocket book updates for registered market pairs, checks for logical-constraint violations above tolerance_bps, and emits FOK OrderIntents on both legs when a violation is confirmed.

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

FUNCTION evaluatePair(pair_id, bookTick):
  // --- 0. KillSwitch gate ---
  ks = FETCH internal.killswitch.status
  IF ks.active: RETURN

  // --- 1. Registry check ---
  pair = FETCH internal.pairRegistry.get(pair_id)
  IF NOT pair OR NOT pair.manually_approved:
    EMIT DecisionReport(intent_emitted=false, reason='CROSS_MARKET_ARB_PAIR_NOT_APPROVED')
    RETURN

  // --- 2. Staleness check ---
  IF isStale(bookTick, maxAgeS=5):
    EMIT DecisionReport(intent_emitted=false, reason='STALE_MARKET_DATA')
    RETURN

  // --- 3. Market status check ---
  mktA = FETCH clob_public.GET('/markets/' + pair.market_id_A)
  mktB = FETCH clob_public.GET('/markets/' + pair.market_id_B)
  IF mktA.closed OR mktA.resolved OR mktB.closed OR mktB.resolved:
    CANCEL_RESTING_LEGS(pair_id)
    EMIT DecisionReport(intent_emitted=false, reason='MARKET_CLOSED')
    RETURN

  // --- 4. Constraint violation check (A implies B, so p_A should <= p_B) ---
  p_A = bookTick.market_A.best_ask
  p_B = bookTick.market_B.best_ask
  violation_bps = (p_A - p_B) * 10000

  // --- 5. Hard floor ---
  IF violation_bps < params.tolerance_bps_hard:  // 5 bps
    IF random() < 0.01:
      EMIT DecisionReport(intent_emitted=false, reason='CROSS_MARKET_ARB_NO_EDGE', violation_bps=violation_bps)
    RETURN

  // --- 6. Warning threshold ---
  sizeMultiplier = 1.0
  IF violation_bps < params.tolerance_bps:  // 30 bps default
    WARN('CROSS_MARKET_ARB_VIOLATION_MARGINAL')
    sizeMultiplier = 0.5

  // --- 7. Size and emit legs ---
  depthA = FETCH clob_public.depth(pair.market_id_A)
  depthB = FETCH clob_public.depth(pair.market_id_B)
  legSize = toPusdUnits(min(depthA, depthB, legBudget) * sizeMultiplier)

  // Sell the overpriced side (market A)
  EMIT OrderIntent(market_id=pair.market_id_A, outcome='YES', side='sell',
                   price=p_A, size_pUSD=legSize, tif='FOK',
                   builder={code: internal.builder_code, fee_bps: 25})
  // Buy the underpriced side (market B)
  EMIT OrderIntent(market_id=pair.market_id_B, outcome='YES', side='buy',
                   price=p_B, size_pUSD=legSize, tif='FOK',
                   builder={code: internal.builder_code, fee_bps: 25})

  EMIT DecisionReport(intent_emitted=true, violation_bps=violation_bps,
                      pair_id=pair_id, reason='CROSS_MARKET_ARB_EDGE_DETECTED')

SDK calls used

  • fetchClobPublic('/markets/' + market_id)
  • ws_market.subscribe('book', [market_id_A, market_id_B])
  • toPusdUnits(rawFloat)
  • buildOrderTypedData(orderParams, { name: 'CTFExchange', version: '2', chainId: 137 })
  • internal.killswitch.status()
  • internal.pairRegistry.get(pair_id)
  • internal.builder_code

Complexity: O(1) per market pair per book tick

11. Wire Examples

Input — what arrives on the wire

Pair book tick — constraint violation detected (45 bps)ws_market

{
  "pair_id": "pair_nominee_vs_winner_0042",
  "market_id_A": "0xcrossma000000000000000000000000000000000000000000000000000000001b",
  "market_id_B": "0xcrossma000000000000000000000000000000000000000000000000000000001a",
  "p_A_best_ask": "0.455",
  "p_B_best_ask": "0.410",
  "violation_bps": "45.0",
  "depth_A_pusd": "380.00",
  "depth_B_pusd": "290.00",
  "received_at_ms": 1746790200000
}

Output — what the bot emits

OrderIntent — market B leg (buy, FOK, builder-attributed)

{
  "intent_id": "oi_01HXCMA00001AA",
  "trace_id": "tr_01HXCMA0000TR1",
  "market_id": "0xcrossma000000000000000000000000000000000000000000000000000000001a",
  "outcome": "YES",
  "side": "buy",
  "price": "0.410",
  "size_pUSD": "250.00",
  "tif": "FOK",
  "post_only": false,
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "negrisk_aware": false,
  "decision": {
    "pair_id": "pair_nominee_vs_winner_0042",
    "violation_bps": 45.0,
    "leg": "underpriced_B",
    "reasons": [
      "CROSS_MARKET_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_01HXCMA999ZZZ",
  "bot_id": "strat.cross_market_arb",
  "pair_id": "pair_nominee_vs_winner_0042",
  "intent_emitted": false,
  "violation_bps": 2.1,
  "reasons": [
    "CROSS_MARKET_ARB_NO_EDGE"
  ],
  "sampled": true,
  "evaluated_at_ms": 1746790201000
}

12. Decision Logic

APPROVE

violation_bps >= tolerance_bps, both markets open, both legs have depth, pair is manually approved, KillSwitch inactive. Emit two paired OrderIntents (FOK).

RESHAPE_REQUIRED

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

REJECT

violation_bps < 5 bps hard floor; either market closed/resolved; pair not in approved registry; KillSwitch active; stale feed. Emit DecisionReport intent_emitted=false.

WARNING_ONLY

violation_bps between 5 and 30 bps triggers CROSS_MARKET_ARB_VIOLATION_MARGINAL and 50% size reduction.

13. Standard Decision Output

This bot returns a OrderIntent object. See OrderIntent schema.

{
  "intent_id": "oi_01HXCMA00001AA",
  "trace_id": "tr_01HXCMA0000TR1",
  "market_id": "0xcrossma000000000000000000000000000000000000000000000000000000001a",
  "outcome": "YES",
  "side": "buy",
  "price": "0.410",
  "size_pUSD": "250.00",
  "tif": "FOK",
  "post_only": false,
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "negrisk_aware": false,
  "decision": {
    "pair_id": "pair_nominee_vs_winner_0042",
    "violation_bps": 45.0,
    "leg": "underpriced_B",
    "reasons": [
      "CROSS_MARKET_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
CROSS_MARKET_ARB_EDGE_DETECTEDINFOLogical constraint violation exceeds tolerance_bps after fee buffer. Paired OrderIntents emitted.Emit two FOK OrderIntents (one per market in the pair).A pricing inconsistency between two related markets was detected and orders were placed to capture it.
CROSS_MARKET_ARB_NO_EDGEINFOViolation is below the 5 bps hard floor. No trade opportunity exists after fees.Skip; emit sampled DecisionReport.The pricing gap between linked markets was too small to trade profitably after fees.
CROSS_MARKET_ARB_VIOLATION_MARGINALWARNViolation is between 5 and 30 bps (warning threshold). Trade is marginal.Emit OrderIntents at 50% leg size; log warning.A small pricing inconsistency was detected. Order sizes were reduced.
CROSS_MARKET_ARB_PAIR_NOT_APPROVEDHARD_REJECTMarket pair is not in the manually reviewed approved registry.Skip; no OrderIntents emitted.
CROSS_MARKET_ARB_LOW_SIMILARITYWARNEmbedding similarity score for the proposed pair is below min_similarity_score warning threshold.Block pair auto-registration; require additional manual review.
STALE_MARKET_DATAHARD_REJECTBook snapshot older than 5s for either market in the pair.Skip; no OrderIntents; cancel resting legs if any.Market data was too old to act on safely.
MARKET_CLOSEDHARD_REJECTEither market in the pair is closed or resolved.Skip; cancel resting legs; deactivate pair until re-reviewed.One of the linked markets has closed. No new orders will be placed.
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch is active.Skip all pairs; 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_crossmarketarb_decisions_totalcountercountverdict, reason_codeTotal evaluation cycles by intent_emitted (true/false) and reason code.
polytraders_strat_crossmarketarb_violation_bpshistogrambasis_pointspair_idDistribution of measured constraint violations in bps per pair.
polytraders_strat_crossmarketarb_intents_emitted_totalcountercountpair_id, legTotal OrderIntents emitted by pair and leg (A/B).
polytraders_strat_crossmarketarb_eval_latency_mshistogrammillisecondsWall-clock time from pair book tick to OrderIntent emit.
polytraders_strat_crossmarketarb_stale_feed_totalcountercountEvaluation cycles skipped due to stale feed on either market in a pair.
polytraders_strat_crossmarketarb_active_pairsgaugecountNumber of currently active (manually approved) market pairs.

Alerts

AlertConditionSeverityRunbook
CrossMarketArbHighStaleFeedrate(polytraders_strat_crossmarketarb_stale_feed_total[5m]) > 0.1warn#runbook-crossmarketarb-stale-feed
CrossMarketArbNoEdgeStreakrate(polytraders_strat_crossmarketarb_decisions_total{verdict='false'}[15m]) / rate(polytraders_strat_crossmarketarb_decisions_total[15m]) > 0.99warn#runbook-crossmarketarb-no-edge
CrossMarketArbHighLatencyhistogram_quantile(0.99, rate(polytraders_strat_crossmarketarb_eval_latency_ms_bucket[5m])) > 200warn#runbook-crossmarketarb-latency
CrossMarketArbKillSwitchBlockingrate(polytraders_strat_crossmarketarb_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0page#runbook-killswitch

Dashboards

  • Grafana — Strategy / CrossMarketArb violation distribution per pair
  • Grafana — Strategy / CrossMarketArb active pairs and intent throughput

16. Developer Reporting

{
  "bot_id": "strat.cross_market_arb",
  "pair_id": "pair_nominee_vs_winner_0042",
  "market_A": "0xcrossma000000000000000000000000000000000000000000000000000000001b",
  "market_B": "0xcrossma000000000000000000000000000000000000000000000000000000001a",
  "p_A": 0.455,
  "p_B": 0.41,
  "violation_bps": 45.0,
  "leg_size_pusd": 250.0,
  "intent_emitted": true,
  "reason": "CROSS_MARKET_ARB_EDGE_DETECTED",
  "emitted_at_ms": 1746790200000
}

17. Plain-English Reporting

SituationUser-facing explanation
Cross-market arb trade initiatedTwo related markets were priced inconsistently: one was priced higher than the other in a way that contradicts their logical relationship. Orders were placed to exploit the pricing gap.
No violation — no tradeThe pricing between the two linked markets was consistent after accounting for fees. No order was placed.
Violation marginal — reduced sizeA small pricing inconsistency between related markets was detected. Order sizes were halved to limit exposure given the thin margin.
Market resolved — pair deactivatedOne of the two linked markets has resolved. The pair has been deactivated and no new orders will be placed.

18. Failure-Mode Block

main_failure_modeOne market in the pair resolves while the second leg order is resting, leaving a one-sided position with no offsetting settlement path.
false_positive_riskStale WebSocket book data shows a violation that has already corrected, causing the bot to attempt an arb that no longer exists and incurring fee costs with no edge.
false_negative_risktolerance_bps set too conservatively on low-fee market pairs causes valid constraint violations to be skipped.
safe_fallbackIf either market's book data is stale (last_seen > 5s) or either market resolves, emit STALE_MARKET_DATA DecisionReport and skip. Cancel any resting legs on resolved markets immediately.
required_dependenciesws_market book stream (both market IDs in each active pair), clob_public market endpoint (status + depth), internal market-pair registry (manually reviewed), KillSwitch active flag, internal builder code

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
STALE_WS_FEEDPause ws_market WebSocket for market_A; let last_seen age beyond 5sAutomatic on WebSocket reconnect.
MARKET_RESOLVES_MID_TRADESet mock market_B.resolved=true after market_A leg is emittedManual pair re-review required before re-activation.
VIOLATION_BELOW_HARD_FLOORSet p_A=0.413, p_B=0.411 (violation=2 bps)Automatic when violation widens.
KILL_SWITCH_ONSet killswitch.active=trueAutomatic on manual KillSwitch reset.
UNAPPROVED_PAIR_INJECTIONAdd a pair directly to registry without setting manually_approved=trueManual approval required.

20. State & Persistence

Cold-start recovery

On cold start, resting leg IDs re-fetched from clob_auth. Book state rebuilt from first ws_market tick per market.

21. Concurrency & Idempotency

AspectSpecification
Execution modelactor-per-market
Max in-flight20
Idempotency keyintent_id
Per-call timeout (ms)200
Backpressure strategydrop oldest pending tick per pair_id when queue depth > 5
Locking / mutual exclusionper-pair_id mutex for Redis state write and resting leg tracking

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
Polymarket Gamma API99.9% (Polymarket-published)

23. Security Surfaces

On-chain contract calls

ContractMethodNetworkEffect
CTFExchangeV2polygon

Abuse vectors considered

  • Adversary manufactures a pair violation by moving one market via a large spoof order
  • Replaying a signed OrderIntent after the constraint violation has closed
  • Injecting a new pair into the registry by bypassing the manual review gate

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
  • Pair registry mutations require operator authentication and manual approval flag
  • Book staleness check (5s) prevents acting on adversarially manipulated prices

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldyes
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesOperates on linked binary market pairs. feeRateBps is not present on any signed order; fees are operator-set at match time. Market-pair relationships are reviewed and registered manually before deployment.

API surfaces declared

gammaclob_publicclob_authws_marketinternal

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-market order construction. Updated collateral denomination to pUSD. Injected builder field (bytes32) on every OrderIntent. EIP-712 Exchange domain version updated from '1' to '2'. Market-pair registry updated to use Gamma API for metadata and condition-ID resolution.

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Emit pair when violation_bps=45 (above threshold 30)p_A=0.455, p_B=0.410, tolerance_bps=30, pair manually approvedTwo OrderIntents emitted; DecisionReport intent_emitted=true, reason=CROSS_MARKET_ARB_EDGE_DETECTED
Skip when violation_bps=3 (below hard floor 5)p_A=0.502, p_B=0.499No OrderIntents; sampled DecisionReport reason=CROSS_MARKET_ARB_NO_EDGE
Reduce sizes 50% when violation is marginal (18 bps)violation_bps=18, tolerance_bps=30OrderIntents emitted at 50% size; WARN CROSS_MARKET_ARB_VIOLATION_MARGINAL
Reject unreviewed pairpair not in approved registryNo OrderIntents; CROSS_MARKET_ARB_PAIR_NOT_APPROVED
Skip when either market closedmarket_B.status=resolvedNo OrderIntents; reason=MARKET_CLOSED; resting leg on market_A cancelled
Skip when KillSwitch activekillswitch.active=trueNo OrderIntents emitted

Integration Tests

TestExpected result
Full cycle: ws_market pair tick → violation detected → two signed V2 FOK OrderIntents submittedBoth orders have builder.code (bytes32), no feeRateBps, EIP-712 domain version '2'
Market resolution mid-trade triggers immediate leg cancellationResting leg cancelled; MARKET_CLOSED DecisionReport emitted; no new intents for pair

Property Tests

PropertyRequired behaviour
Bot never trades a pair not in the manually reviewed approved registryAlways true
feeRateBps is never present on any signed OrderIntentAlways true — V2 fees are operator-set at match time
require_manual_pair_review is always true in production configAlways true

27. Operational Runbook

Cross-Market Arb incidents are typically stale feeds, market resolutions mid-trade (requiring manual pair review), or false constraint violations from thin-book manipulation. Stale feed incidents resolve automatically; market resolution events require ops review.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
CrossMarketArbHighStaleFeed
CrossMarketArbNoEdgeStreak
CrossMarketArbHighLatency
CrossMarketArbKillSwitchBlocking

Manual overrides

Healthcheck

GET /internal/health/cross-market-arb -> 200 if ws_market feed last_seen < 5s for all active pair markets, Redis reachable, KillSwitch inactive, at least one pair 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 pair-approval invariant and FOK-only propertyCI test run100% pass
feeRateBps absence verified; require_manual_pair_review=true enforced in integration testIntegration test asserting V2 order schemaPass

Promote to Limited live

GateHow measuredThreshold
p99 eval latency < 200ms over 24hpolytraders_strat_crossmarketarb_eval_latency_ms histogramp99 < 200ms
Zero unreviewed pair trades in 48h shadow runPair approval audit log0 incidents

Promote to General live

GateHow measuredThreshold
E2E: pair violation detected → two signed V2 FOK OrderIntents submitted on Polygon testnetE2E testPass
Market resolution mid-trade correctly triggers leg cancellation in integration testIntegration testPass

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