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.12 Mean-Reversion Sniper

3.12 Mean-Reversion Sniper

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

Mean-Reversion Sniper fades price momentum in markets that have overreacted to recent information. It monitors markets where the best-ask for YES tokens exceeds price_threshold (default 0.80), computes a rolling z-score of the price change over a lookback window, and checks for order-book signals of a momentum reversal (aggressor side flips and large cancels on the heavy side). When z_score >= z_score_min and no active news cycle is detected by the NewsIngest event-density feed, the bot emits an IOC OrderIntent to sell YES tokens at or above price_threshold. An automatic time exit (time_exit_s) and stop-loss (stop_bps) control downside risk. This is a user-controlled execution tool; it does not predict outcomes 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 + NewsIngest event-density feed
Applies toStandard binary markets where price has moved above price_threshold (default 0.80) and z-score of last-N price change exceeds z_score_min, with no active news cycle on the market
Default modelimited_live
User-visibleAdvanced details only
Developer ownerPolytraders core — Strategy pod

2. Purpose

Mean-Reversion Sniper fades price momentum in markets that have overreacted to recent information. It monitors markets where the best-ask for YES tokens exceeds price_threshold (default 0.80), computes a rolling z-score of the price change over a lookback window, and checks for order-book signals of a momentum reversal (aggressor side flips and large cancels on the heavy side). When z_score >= z_score_min and no active news cycle is detected by the NewsIngest event-density feed, the bot emits an IOC OrderIntent to sell YES tokens at or above price_threshold. An automatic time exit (time_exit_s) and stop-loss (stop_bps) control downside risk. This is a user-controlled execution tool; it does not predict outcomes or make performance claims.

3. Why This Bot Matters

  • Fading during an active news cycle

    If a material news event is causing the price to move permanently higher, fading it produces a directional loss rather than a mean-reversion gain. NewsIngest event-density gate must be respected.

  • z-score computed on too short a lookback

    Short lookbacks produce noisy z-scores that trigger on normal intraday price variation, leading to repeated small losses from false fades.

  • Stop-loss not configured or too wide

    Without a stop-loss, an overreaction that continues (e.g., genuine resolution signal) produces an uncapped loss on the short YES position.

  • feeRateBps present on signed order (V1 pattern)

    CTFExchangeV2 rejects orders with feeRateBps. Fees are operator-set at match time. The signed order must not 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
Price and z-score of last-N price change on target marketws_market (CLOB WebSocket trade tape)YesConfirm price >= price_threshold and z_score >= z_score_min before emitting fade intent.
Aggressor side flips and large cancel eventsws_market (order book updates)YesDetect reversal signals: momentum fading when aggressor flips from buy-dominant to sell-dominant.
Top-of-book depth on YES sideclob_publicYesSize the IOC order to min(available_depth, max_position_usd).
Market open/closed/resolved statusclob_publicYesSkip markets that are closed, resolved, or approaching resolution (< 2h to close).

5. Required Internal Inputs

InputSourceRequired?Use
KillSwitch active flagKillSwitchYesAbort all intent emission immediately if KillSwitch is active.
NewsIngest event-density window (don't fade during active news cycles)internal (NewsIngest feed)YesBlock fade intent if NewsIngest signals an active material news cycle on the target market's entity.
Builder code bytes32internal configYesInjected into builder field on every signed V2 OrderIntent.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
price_threshold0.80.90.95Minimum YES token price (0–1 pUSD) above which the bot considers a fade. Below this level the market is not overextended enough to fade.
z_score_min2.51.51.0Minimum z-score of the recent price move (normalised against rolling price volatility) required before emitting a fade intent.
stop_bps150250400Basis points above the fade entry price at which the bot automatically closes the position (stop-loss). If price continues up by stop_bps, the bot buys to cover.
time_exit_s120200300Maximum seconds the bot will hold a fade position before exiting regardless of price. Acts as a time-based stop to prevent open positions persisting into market resolution.

7. Detailed Parameter Instructions

price_threshold

What it means

Minimum YES token price (0–1 pUSD) above which the bot considers a fade. Below this level the market is not overextended enough to fade.

Default

{ "price_threshold": 0.8 }

Why this default matters

0.80 captures markets where the crowd has pushed probability high enough that a reversion is plausible. Above 0.90 prices reflect near-certain resolution; fading near 0.95 hard cap is almost never correct.

Threshold logic

ConditionAction
>= 0.80Eligible for fade evaluation; check z_score
0.90–0.95WARN MEAN_REVERSION_HIGH_PRICE_THRESHOLD; market near resolution; fade only with high z_score
>= 0.95 (hard floor)SKIP — MEAN_REVERSION_PRICE_TOO_HIGH; do not fade near resolution

Developer check

if price >= params.hard: return skip('MEAN_REVERSION_PRICE_TOO_HIGH')

User-facing English

The market price was too close to certain resolution to fade safely.

z_score_min

What it means

Minimum z-score of the recent price move (normalised against rolling price volatility) required before emitting a fade intent.

Default

{ "z_score_min": 2.5 }

Why this default matters

2.5 sigma corresponds to a statistically unusual price move that is likely to partially revert. Below 1.5 sigma the move may be within normal daily variation; below 1.0 the bot will not fire.

Threshold logic

ConditionAction
>= 2.5EMIT fade OrderIntent
1.5–2.5WARN MEAN_REVERSION_Z_MARGINAL; emit at 50% size
< 1.0 (hard floor)SKIP — MEAN_REVERSION_Z_TOO_LOW

Developer check

if z_score < params.hard: return skip('MEAN_REVERSION_Z_TOO_LOW')

User-facing English

The recent price move was not statistically extreme enough to fade.

stop_bps

What it means

Basis points above the fade entry price at which the bot automatically closes the position (stop-loss). If price continues up by stop_bps, the bot buys to cover.

Default

{ "stop_bps": 150 }

Why this default matters

150 bps provides meaningful downside protection while allowing normal post-fade price noise. Above 250 bps the stop is too wide for a reversion trade; above 400 bps it is rejected.

Threshold logic

ConditionAction
<= 150 bpsNormal stop-loss
150–400 bpsWARN MEAN_REVERSION_WIDE_STOP; elevated risk per trade
> 400 bpsReject config — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

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

User-facing English

An automatic stop-loss is in place to limit losses if the price continues rising.

time_exit_s

What it means

Maximum seconds the bot will hold a fade position before exiting regardless of price. Acts as a time-based stop to prevent open positions persisting into market resolution.

Default

{ "time_exit_s": 120 }

Why this default matters

120 seconds limits exposure to a 2-minute window. Reversion trades either work quickly or not at all; holding longer on a market near resolution adds settlement risk.

Threshold logic

ConditionAction
<= 120sNormal time exit
120–300sWARN MEAN_REVERSION_LONG_TIME_EXIT; risk of open position at resolution
> 300s (hard ceiling)Reject config — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

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

User-facing English

The position was automatically closed after the maximum holding time to avoid any unresolved exposure.

8. Default Configuration

{
  "bot_id": "strat.mean_reversion_sniper",
  "version": "2.1.0",
  "mode": "limited_live",
  "defaults": {
    "price_threshold": 0.8,
    "z_score_min": 2.5,
    "stop_bps": 150,
    "time_exit_s": 120
  },
  "locked": {
    "price_threshold": {
      "max": 0.95
    },
    "z_score_min": {
      "min": 1.0
    },
    "stop_bps": {
      "max": 400
    },
    "time_exit_s": {
      "max": 300
    }
  }
}

9. Implementation Flow

  1. Check KillSwitch active flag; if active, emit no OrderIntents; close any open fade positions.
  2. Subscribe to ws_market book and trade-tape streams for markets with best_ask_YES >= price_threshold.
  3. On each price tick: compute rolling z-score of price change over lookback window (default 20 ticks).
  4. Gate 1 — Price threshold: if best_ask_YES >= 0.95 (hard): skip MEAN_REVERSION_PRICE_TOO_HIGH.
  5. Gate 2 — NewsIngest: if active news cycle on market entity: skip MEAN_REVERSION_NEWS_ACTIVE.
  6. Gate 3 — Z-score: if z_score < 1.0 (hard): skip MEAN_REVERSION_Z_TOO_LOW (sampled 1/100).
  7. Gate 4 — Reversal signal: check aggressor side flip (taker-sell ratio rising) and large cancels on bid side.
  8. If z_score < 2.5 (warning): WARN MEAN_REVERSION_Z_MARGINAL; reduce size to 50% of max_position_usd.
  9. Fetch top-of-book YES ask depth; set orderSize = min(depth, max_position_usd) * sizeMultiplier.
  10. Emit OrderIntent: outcome=YES, side=sell, price=best_ask_YES, size_pUSD=orderSize, tif=IOC, builder={code, fee_bps:25}.
  11. Note: fees are operator-set at match time in V2 — feeRateBps is NOT on the signed order.
  12. Register position in state: entry_price, entry_time_ms, stop_price=entry_price+stop_bps/10000, exit_deadline_ms=now_ms()+time_exit_s*1000.
  13. Monitor position: if price rises above stop_price → emit buy IOC to close; if time > exit_deadline → emit buy IOC to close.
  14. Emit DecisionReport with intent_emitted=true, z_score, price_at_entry, reason MEAN_REVERSION_FADE_INITIATED.

10. Reference Implementation

Monitors markets for statistically unusual price spikes above price_threshold, checks NewsIngest event-density gate, and emits IOC sell OrderIntents when z_score >= z_score_min with reversal signals present. Manages open positions with stop-loss and time exit.

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

FUNCTION onPriceTick(market_id, priceTick):
  // --- 0. KillSwitch gate ---
  ks = FETCH internal.killswitch.status
  IF ks.active: CLOSE_OPEN_POSITIONS(); RETURN

  // --- 1. Manage open positions (stop-loss + time exit) ---
  pos = FETCH state.openPosition(market_id)
  IF pos:
    IF priceTick.best_ask >= pos.stop_price:
      EMIT OrderIntent(side='buy', tif='IOC', size=pos.size, reason='MEAN_REVERSION_STOP_LOSS')
      CLEAR state.openPosition(market_id)
      RETURN
    IF now_ms() >= pos.exit_deadline_ms:
      EMIT OrderIntent(side='buy', tif='IOC', size=pos.size, reason='MEAN_REVERSION_TIME_EXIT')
      CLEAR state.openPosition(market_id)
      RETURN

  // --- 2. Price threshold gate ---
  price = priceTick.best_ask
  IF price >= params.price_threshold_hard:  // 0.95
    EMIT DecisionReport(intent_emitted=false, reason='MEAN_REVERSION_PRICE_TOO_HIGH')
    RETURN
  IF price < params.price_threshold:  // 0.80
    RETURN  // not in fade zone

  // --- 3. NewsIngest gate ---
  newsState = FETCH internal.newsingest.eventDensity(market_id)
  IF newsState.active OR newsState.unavailable:  // fail-closed
    EMIT DecisionReport(intent_emitted=false, reason='MEAN_REVERSION_NEWS_ACTIVE')
    RETURN

  // --- 4. Z-score computation ---
  tape = FETCH ws_market.tradeTape(market_id, lookback_ticks=20)
  zScore = zScoreOfPriceChange(tape)
  IF zScore < params.z_score_min_hard:  // 1.0
    IF random() < 0.01:
      EMIT DecisionReport(intent_emitted=false, reason='MEAN_REVERSION_Z_TOO_LOW', z_score=zScore)
    RETURN

  // --- 5. Reversal signal check ---
  IF NOT reversalSignalPresent(tape):  // aggressor flip + large cancel on bid
    RETURN

  // --- 6. Size determination ---
  sizeMultiplier = 0.5 IF zScore < params.z_score_min ELSE 1.0  // 2.5 warning
  IF sizeMultiplier < 1.0: WARN('MEAN_REVERSION_Z_MARGINAL')
  depth = FETCH clob_public.depth(market_id, side='YES')
  orderSize = toPusdUnits(min(depth, params.max_position_usd) * sizeMultiplier)

  // --- 7. Emit fade OrderIntent (sell YES, IOC, V2: no feeRateBps) ---
  EMIT OrderIntent(
    market_id = market_id, outcome = 'YES', side = 'sell',
    price = price, size_pUSD = orderSize, tif = 'IOC', post_only = false,
    builder = {code: internal.builder_code, fee_bps: 25}
  )

  // --- 8. Register position for stop/time-exit monitoring ---
  SET state.openPosition(market_id, {
    entry_price: price, size: orderSize,
    stop_price: price + params.stop_bps / 10000,
    exit_deadline_ms: now_ms() + params.time_exit_s * 1000
  })

  EMIT DecisionReport(intent_emitted=true, z_score=zScore,
                      price_at_entry=price, reason='MEAN_REVERSION_FADE_INITIATED')

SDK calls used

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

Complexity: O(1) per price tick; O(lookback_ticks) for z-score computation

11. Wire Examples

Input — what arrives on the wire

Price tick — YES spike to 0.847, z_score=3.1, no news cyclews_market

{
  "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
  "best_ask_YES": "0.847",
  "z_score_20tick": "3.1",
  "taker_sell_ratio_5s": "0.65",
  "news_active": false,
  "depth_YES_pusd": "410.00",
  "received_at_ms": 1746790200000
}

Output — what the bot emits

OrderIntent — fade sell YES (IOC, builder-attributed)

{
  "intent_id": "oi_01HXMRS0000001A",
  "trace_id": "tr_01HXMRS000TR001",
  "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
  "outcome": "YES",
  "side": "sell",
  "price": "0.847",
  "size_pUSD": "300.00",
  "tif": "IOC",
  "post_only": false,
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "negrisk_aware": false,
  "decision": {
    "z_score": 3.1,
    "price_at_entry": 0.847,
    "stop_price": 0.862,
    "exit_deadline_ms": 1746790320000,
    "reasons": [
      "MEAN_REVERSION_FADE_INITIATED"
    ]
  },
  "comment": "fees are operator-set at match time in V2 — feeRateBps is NOT on the signed order"
}

DecisionReport — skipped (news cycle active)

{
  "report_id": "dr_01HXMRS999ZZZZ",
  "bot_id": "strat.mean_reversion_sniper",
  "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
  "intent_emitted": false,
  "z_score": 2.8,
  "reasons": [
    "MEAN_REVERSION_NEWS_ACTIVE"
  ],
  "sampled": false,
  "evaluated_at_ms": 1746790201000
}

12. Decision Logic

APPROVE

price >= price_threshold, z_score >= z_score_min, no active news cycle, reversal signal present, KillSwitch inactive. Emit sell IOC OrderIntent.

RESHAPE_REQUIRED

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

REJECT

price >= 0.95 (hard floor); z_score < 1.0; active news cycle; KillSwitch active; stale feed. Emit DecisionReport intent_emitted=false.

WARNING_ONLY

z_score between 1.0 and 2.5 triggers MEAN_REVERSION_Z_MARGINAL warn and 50% size reduction.

13. Standard Decision Output

This bot returns a OrderIntent object. See OrderIntent schema.

{
  "intent_id": "oi_01HXMRS0000001A",
  "trace_id": "tr_01HXMRS000TR001",
  "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
  "outcome": "YES",
  "side": "sell",
  "price": "0.847",
  "size_pUSD": "300.00",
  "tif": "IOC",
  "post_only": false,
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "negrisk_aware": false,
  "decision": {
    "z_score": 3.1,
    "price_at_entry": 0.847,
    "stop_price": 0.862,
    "exit_deadline_ms": 1746790320000,
    "reasons": [
      "MEAN_REVERSION_FADE_INITIATED"
    ]
  },
  "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
MEAN_REVERSION_FADE_INITIATEDINFOz_score >= z_score_min, price >= price_threshold, news gate clear, reversal signal present. Fade sell IOC emitted.Emit sell IOC OrderIntent; register position for stop/time-exit monitoring.A short sell was placed to capture an expected price reversal.
MEAN_REVERSION_Z_TOO_LOWINFOz_score is below the 1.0 hard floor. Price move is within normal variation; no fade warranted.Skip; emit sampled DecisionReport.The recent price move was not extreme enough to fade.
MEAN_REVERSION_Z_MARGINALWARNz_score is between 1.0 and 2.5. Fade is marginal; size reduced to 50%.Emit IOC at 50% size; log warning.A moderate price spike was detected. A smaller fade order was placed.
MEAN_REVERSION_PRICE_TOO_HIGHHARD_REJECTPrice >= 0.95 hard floor. Market is near certain resolution; fading is prohibited.Skip; no OrderIntent; emit DecisionReport.The market price is too close to certain resolution to fade safely.
MEAN_REVERSION_NEWS_ACTIVEHARD_REJECTNewsIngest signals an active material news cycle on this market's entity. Fading is blocked.Skip; no OrderIntent; emit DecisionReport.An active news event is affecting this market. No fade order was placed.
MEAN_REVERSION_STOP_LOSSWARNPrice rose above stop_price (entry + stop_bps/10000). Stop-loss triggered; position closed.Emit buy IOC to close; emit DecisionReport.The price continued to rise. The stop-loss automatically closed the position.
MEAN_REVERSION_TIME_EXITINFOHolding period exceeded time_exit_s. Time exit triggered; position closed.Emit buy IOC to close; emit DecisionReport.The maximum holding time elapsed. The position was automatically closed.
MEAN_REVERSION_HIGH_PRICE_THRESHOLDWARNprice_threshold config is above 0.90 (warning). Fading near-resolution markets carries elevated risk.Allow but log warning; require higher z_score.
STALE_MARKET_DATAHARD_REJECTws_market feed or NewsIngest feed is stale or unavailable.Skip; no OrderIntent emitted.Market data was too old to act on safely.
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch is active.Close open positions; no new OrderIntents.Trading is currently paused.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_strat_mrsniper_decisions_totalcountercountverdict, reason_codeTotal evaluation cycles by intent_emitted and reason code.
polytraders_strat_mrsniper_z_scorehistogramsigmaDistribution of z-scores at evaluation time across all checked markets.
polytraders_strat_mrsniper_position_hold_shistogramsecondsexit_reasonDuration of open fade positions by exit reason (stop_loss, time_exit, profit_take).
polytraders_strat_mrsniper_news_gate_blocks_totalcountercountmarket_idNumber of fade attempts blocked by NewsIngest gate.
polytraders_strat_mrsniper_intents_emitted_totalcountercountsideTotal OrderIntents emitted by side (sell=fade open, buy=close).
polytraders_strat_mrsniper_eval_latency_mshistogrammillisecondsWall-clock time from price tick to OrderIntent emit.

Alerts

AlertConditionSeverityRunbook
MrSniperHighNewsGateBlockRaterate(polytraders_strat_mrsniper_news_gate_blocks_total[10m]) > 10warn#runbook-mrsniper-news-gate
MrSniperHighStopLossRaterate(polytraders_strat_mrsniper_position_hold_s_bucket{exit_reason='stop_loss'}[15m]) / rate(polytraders_strat_mrsniper_position_hold_s_count[15m]) > 0.5warn#runbook-mrsniper-stop-loss
MrSniperStaleFeedrate(polytraders_strat_mrsniper_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1warn#runbook-mrsniper-stale-feed
MrSniperKillSwitchBlockingrate(polytraders_strat_mrsniper_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0page#runbook-killswitch

Dashboards

  • Grafana — Strategy / MeanReversionSniper z-score distribution and fade frequency
  • Grafana — Strategy / MeanReversionSniper position hold times and exit reasons

16. Developer Reporting

{
  "bot_id": "strat.mean_reversion_sniper",
  "market_id": "0xmrsniper00000000000000000000000000000000000000000000000000000001",
  "price_at_entry": 0.847,
  "z_score": 3.1,
  "price_threshold": 0.8,
  "news_gate_passed": true,
  "reversal_signal": true,
  "order_size_pusd": 300.0,
  "stop_price": 0.862,
  "exit_deadline_ms": 1746790320000,
  "intent_emitted": true,
  "reason": "MEAN_REVERSION_FADE_INITIATED",
  "emitted_at_ms": 1746790200000
}

17. Plain-English Reporting

SituationUser-facing explanation
Mean-reversion fade initiatedThe market price spiked unusually high with no active news to justify it. A short sell was placed to capture the expected return toward a more typical price level.
No fade — news cycle activeA material news event is currently affecting this market. Fading the price movement during active news would be risky. No order was placed.
No fade — z-score too lowThe recent price move was not statistically unusual enough to justify a fade trade. No order was placed.
Position closed — stop-loss triggeredThe market price continued to rise after the fade was placed. The stop-loss automatically closed the position to limit losses.
Position closed — time exitThe maximum holding time elapsed. The position was closed automatically to avoid any unresolved exposure.

18. Failure-Mode Block

main_failure_modeFading a genuine resolution signal: the market moves to 0.90+ because the underlying event has materially resolved (not overreaction), and the short YES position incurs a growing loss until the stop-loss triggers.
false_positive_riskNewsIngest feed is delayed; the bot fades a genuine news-driven price move because the event-density signal has not yet registered, resulting in a loss.
false_negative_riskz_score_min set too conservatively misses genuine overreaction episodes, especially on illiquid markets where z-scores are noisier.
safe_fallbackIf ws_market feed is stale (last_seen > 5s) or NewsIngest feed is unavailable, emit STALE_MARKET_DATA and skip without emitting any OrderIntent. If NewsIngest is unavailable, treat as news-active (conservative gate).
required_dependenciesws_market book and trade-tape stream, clob_public market endpoint (status + depth), NewsIngest event-density feed (internal), KillSwitch active flag, internal builder code

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
STALE_WS_FEEDPause ws_market WebSocket; let last_seen age beyond 5sAutomatic on WebSocket reconnect.
NEWS_GATE_UNAVAILABLEDisable NewsIngest internal feedAutomatic when NewsIngest feed is restored.
STOP_LOSS_TRIGGERSet mock price to entry_price + stop_bps/10000 + 0.001 after fade is placedAutomatic; position closed.
TIME_EXIT_TRIGGERAdvance system clock past exit_deadline_ms with open positionAutomatic; position closed.
KILL_SWITCH_ONSet killswitch.active=true with open fade positionAutomatic on manual KillSwitch reset.

20. State & Persistence

Cold-start recovery

On cold start, open positions are re-read from clob_auth. z-score cache is rebuilt from first ws_market tape ticks.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight20
Idempotency keyintent_id
Per-call timeout (ms)150
Backpressure strategydrop oldest pending price tick per market_id when queue depth > 5
Locking / mutual exclusionper-market_id mutex for open position state read/write

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchChecked first; closes open positions and blocks new intent emission when active.

Emits to (downstream consumers)

External services

ServiceEndpointSLA assumedOn failure
Polymarket CLOB WebSocket (ws_market)best-effort
NewsIngest (internal feed)internal SLA
Polymarket CLOB public API (depth)99.9%

23. Security Surfaces

On-chain contract calls

ContractMethodNetworkEffect
CTFExchangeV2polygon

Abuse vectors considered

  • Adversary manufactures a price spike to trigger the fade, then reverses to profit from the short position
  • Delayed NewsIngest feed allows fading during a genuine news event
  • Open fade position exploited if stop-loss level is known

Mitigations

  • z_score threshold filters out manufactured spikes lacking sustained momentum
  • NewsIngest unavailability treated as active-news (fail-closed); no fade without confirmed clear news gate
  • Stop-loss level computed from entry price + stop_bps; not exposed externally
  • V2 order timestamp(ms) invalidates replayed signed orders
  • IOC orders do not rest on the book; not visible to adversaries post-emission

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
NotesExecutes taker IOC orders to fade price momentum on standard binary markets. feeRateBps is not present on any signed order. Builder code injected on every intent.

API surfaces declared

clob_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 signed order construction. Updated collateral denomination to pUSD. Injected builder field (bytes32). EIP-712 Exchange domain version updated '1' to '2'. NewsIngest event-density feed integration updated to V2 internal bus schema.

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Emit fade when z_score=3.1, price=0.847, no news cycle, reversal signal presentprice_threshold=0.80, z_score_min=2.5, stop_bps=150, time_exit_s=120Sell IOC OrderIntent emitted; DecisionReport intent_emitted=true, reason=MEAN_REVERSION_FADE_INITIATED
Skip when price >= hard floor (0.96)price=0.96No OrderIntent; reason=MEAN_REVERSION_PRICE_TOO_HIGH
Skip when z_score < 1.0 hard floorz_score=0.8No OrderIntent; sampled DecisionReport reason=MEAN_REVERSION_Z_TOO_LOW
Skip when NewsIngest signals active news cyclenewsingest.active=true for market entityNo OrderIntent; reason=MEAN_REVERSION_NEWS_ACTIVE
Reduce size 50% when z_score marginal (1.8)z_score=1.8, z_score_min=2.5, max_position_usd=300OrderIntent emitted with size=150; WARN MEAN_REVERSION_Z_MARGINAL
Stop-loss triggers when price rises stop_bps above entryentry_price=0.847, stop_bps=150; mock price moves to 0.862Buy IOC OrderIntent emitted to close position; DecisionReport reason=MEAN_REVERSION_STOP_LOSS

Integration Tests

TestExpected result
Full cycle: price spike detected → z_score computed → news gate passed → sell IOC OrderIntent submittedOrder has builder.code (bytes32), no feeRateBps, side=sell, tif=IOC, EIP-712 domain version '2'
Time exit: position auto-closed after time_exit_s elapses with no stop-loss triggerBuy IOC OrderIntent emitted at time_exit_s; DecisionReport reason=MEAN_REVERSION_TIME_EXIT

Property Tests

PropertyRequired behaviour
Bot never holds a fade position beyond time_exit_s secondsAlways true
feeRateBps never present on any signed OrderIntentAlways true — V2 fees are operator-set at match time
Bot never fades when NewsIngest feed is unavailable (fail-closed on news gate)Always true

27. Operational Runbook

Mean-Reversion Sniper incidents are typically high stop-loss rates (genuine resolution signals being faded), NewsIngest feed outages (causing no-fade lockout), or stale ws_market feeds. High stop-loss rates require immediate review of market conditions.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
MrSniperHighNewsGateBlockRate
MrSniperHighStopLossRate
MrSniperStaleFeed
MrSniperKillSwitchBlocking

Manual overrides

Healthcheck

GET /internal/health/mean-reversion-sniper -> 200 if ws_market feed last_seen < 5s, NewsIngest feed live, KillSwitch inactive, Redis reachable.

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 news gate fail-closed and stop-loss triggerCI test run100% pass
feeRateBps absence verified; tif=IOC verified in integration testIntegration test asserting V2 order schemaPass

Promote to Limited live

GateHow measuredThreshold
p99 eval latency < 150ms over 24hpolytraders_strat_mrsniper_eval_latency_ms histogramp99 < 150ms
Stop-loss rate < 30% of all closed positions over 48h shadow runpolytraders_strat_mrsniper_position_hold_s_bucket exit_reason histogram< 30%

Promote to General live

GateHow measuredThreshold
E2E: price spike detected → z_score computed → news gate clear → sell IOC OrderIntent submitted on Polygon testnetE2E testPass
Time exit and stop-loss scenarios verified 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