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.10 Resolution Fair-Value

3.10 Resolution Fair-Value

Strategy Alpha Strategy Trade PLANNED Spec started capital · Direct P8 · Additional strategies pending stub

Resolution Fair-Value detects Polymarket binary markets that are close to resolution and priced away from their fair value (e.g. trading at ¢96 when the outcome is virtually certain). It emits an IOC OrderIntent to trade toward fair value on unambiguous markets whose oracle signal is clean and within resolution window.

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
StatusPLANNED
ReadinessSpec started
Runs beforeRisk guardrail pipeline
Runs afterResolution tracker / Oracle monitor
Applies toNear-resolution Polymarket binary markets where the outcome is derivable from an authoritative source but the CLOB price has not yet converged to the fair value (typically ¢99–¢1)
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core — Strategy pod

2. Purpose

Resolution Fair-Value detects Polymarket binary markets that are close to resolution and priced away from their fair value (e.g. trading at ¢96 when the outcome is virtually certain). It emits an IOC OrderIntent to trade toward fair value on unambiguous markets whose oracle signal is clean and within resolution window.

3. Why This Bot Matters

  • Ambiguous resolution rule

    A market that appears certain may resolve 'N/A' or be disputed. Trading on assumed certainty creates a position that resolves against expectations.

  • Oracle data stale near resolution

    Oracle snapshots have a freshness window; a stale snapshot near resolution may show apparent certainty when the underlying data has moved.

  • UMA dispute window not respected

    A winning position can be disputed and reversed within the 2-hour challenge window. Ignoring open disputes creates timing risk.

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
CLOB mid-price and depthws_marketYesMeasure divergence from estimated fair value.
Market metadata (resolution source, end time, status)clob_publicYesVerify oracle source is clean, resolution is imminent, and market is open.
UMA dispute statusonchainYesCheck for open disputes before trading near resolution.

5. Required Internal Inputs

InputSourceRequired?Use
KillSwitch active flagKillSwitchYesAbort all intent emission if KillSwitch active.
Oracle signal / resolution tracker outputinternal (resolution tracker)YesDerive fair-value probability from authoritative resolution source.
Builder code bytes32internal configYesInjected into builder field on every signed V2 OrderIntent.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
min_edge_bps1005020Minimum divergence in bps between CLOB price and estimated fair value required to emit an OrderIntent.
max_size_per_market_usd5007501000Maximum pUSD exposure per market near resolution.
require_unambiguous_sourceTrueNoneNoneIf true (locked), only trade markets where the resolution rule parsing confidence is at maximum. Ambiguous rules block trading.
require_oracle_cleanTrueNoneNoneRequires oracle signal freshness and no open UMA dispute before emitting any OrderIntent.

7. Detailed Parameter Instructions

min_edge_bps

What it means

Minimum divergence in bps between CLOB price and estimated fair value required to emit an OrderIntent.

Default

{ "min_edge_bps": 100 }

Why this default matters

100 bps provides margin after Polymarket fees (~50 bps) and residual uncertainty near resolution.

Threshold logic

ConditionAction
>= 100 bpsEMIT IOC OrderIntent
50–100 bpsWARN RFV_EDGE_MARGINAL; emit at 50% size
< 20 bpsSKIP — RFV_NO_EDGE

Developer check

if edge_bps < params.hard: return skip('RFV_NO_EDGE')

User-facing English

The market price was already too close to the estimated fair value.

max_size_per_market_usd

What it means

Maximum pUSD exposure per market near resolution.

Default

{ "max_size_per_market_usd": 500 }

Why this default matters

500 pUSD limits single-market resolution risk.

Threshold logic

ConditionAction
<= 500 pUSDNormal sizing
> 1000 pUSDReject config — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

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

User-facing English

Trade size was capped at the configured per-market maximum.

require_unambiguous_source

What it means

If true (locked), only trade markets where the resolution rule parsing confidence is at maximum. Ambiguous rules block trading.

Default

{ "require_unambiguous_source": true }

Why this default matters

Prevents trading on markets where resolution could be disputed or voided.

Threshold logic

ConditionAction
ambiguous source detectedHARD_REJECT RFV_AMBIGUOUS_SOURCE

Developer check

if not oracle_clean: return skip('RFV_AMBIGUOUS_SOURCE')

User-facing English

The market's resolution rule was not fully clear; no trade was placed.

require_oracle_clean

What it means

Requires oracle signal freshness and no open UMA dispute before emitting any OrderIntent.

Default

{ "require_oracle_clean": true }

Why this default matters

Stale oracle or open dispute creates timing risk near resolution.

Threshold logic

ConditionAction
oracle stale or dispute openHARD_REJECT RFV_ORACLE_NOT_CLEAN

Developer check

if oracle_stale or dispute_open: return skip('RFV_ORACLE_NOT_CLEAN')

User-facing English

The oracle data was not fresh or a dispute is open — no trade placed.

8. Default Configuration

{
  "bot_id": "strat.resolution_fair_value",
  "version": "0.1.0",
  "mode": "shadow_only",
  "defaults": {
    "min_edge_bps": 100,
    "max_size_per_market_usd": 500,
    "require_unambiguous_source": true,
    "require_oracle_clean": true
  },
  "locked": {
    "require_unambiguous_source": {
      "value": true
    },
    "require_oracle_clean": {
      "value": true
    },
    "min_edge_bps": {
      "min": 20
    }
  }
}

9. Implementation Flow

  1. Check KillSwitch; if active, emit no OrderIntents.
  2. FETCH clob_public market status; skip if closed or resolved.
  3. Check oracle signal from resolution tracker; if stale or source ambiguous, emit RFV_ORACLE_NOT_CLEAN / RFV_AMBIGUOUS_SOURCE and skip.
  4. Check onchain UMA dispute status; if dispute open, skip.
  5. Compute fair_value from oracle signal; edge_bps = |fair_value - clob_mid| * 10000.
  6. IF edge_bps < hard (20 bps): emit sampled DecisionReport RFV_NO_EDGE; skip.
  7. IF edge_bps < warning (50 bps): WARN RFV_EDGE_MARGINAL; reduce size 50%.
  8. Compute order size = min(max_size_per_market_usd, available_depth).
  9. EMIT IOC OrderIntent toward fair value; EMIT DecisionReport reason=RFV_EDGE_TRADE.

10. Reference Implementation

Pseudocode is language-agnostic. FETCH = read input. EMIT = produce output. IF/THEN/ELSE = decision. Translate directly to TypeScript, Python, Go, or Rust.

FUNCTION onOracleUpdate(market_id, oracleSignal):
  ks = FETCH internal.killswitch.status
  IF ks.active: RETURN

  mkt = FETCH clob_public.GET('/markets/' + market_id)
  IF mkt.closed OR mkt.resolved: RETURN

  // Oracle freshness and dispute checks
  IF oracleSignal.stale OR NOT oracleSignal.source_unambiguous:
    EMIT DecisionReport(intent_emitted=false, reason='RFV_ORACLE_NOT_CLEAN')
    RETURN

  disputeStatus = FETCH onchain.UMADisputeStatus(market_id)
  IF disputeStatus.open:
    EMIT DecisionReport(intent_emitted=false, reason='RFV_ORACLE_NOT_CLEAN')
    RETURN

  // Edge computation
  book = FETCH ws_market.book(market_id)
  clobMid = (book.best_bid + book.best_ask) / 2
  fairValue = oracleSignal.fair_value
  edgeBps = abs(fairValue - clobMid) * 10000

  IF edgeBps < params.min_edge_bps_hard:  // 20 bps
    IF random() < 0.01:
      EMIT DecisionReport(intent_emitted=false, reason='RFV_NO_EDGE')
    RETURN

  sizeMultiplier = 0.5 IF edgeBps < params.min_edge_bps_warn ELSE 1.0
  IF sizeMultiplier < 1.0: WARN('RFV_EDGE_MARGINAL')

  depth = FETCH clob_public.depth(market_id)
  orderSize = toPusdUnits(min(params.max_size_per_market_usd * sizeMultiplier, depth.available))
  direction = 'YES' IF fairValue > clobMid ELSE 'NO'

  EMIT OrderIntent(market=market_id, outcome=direction, side='buy', price=clobMid,
                   size_pUSD=orderSize, tif='IOC', builder=internalBuilderCode)
  EMIT DecisionReport(intent_emitted=true, edge_bps=edgeBps, reason='RFV_EDGE_TRADE')

SDK calls used

  • ws_market.subscribe('book', [market_id])
  • fetchClobPublic('/markets/' + market_id)
  • onchain.UMADisputeStatus(market_id)
  • internal.resolutionTracker.signal(market_id)
  • buildOrderTypedData(orderParams, {name:'CTFExchange', version:'2', chainId:137})

Complexity: O(1) per oracle signal per market

11. Wire Examples

Input — what arrives on the wire

Oracle signal — market at ¢96 with fair value 1.0internal (resolution tracker)

{
  "market_id": "0xrfv0000000000000000000000000000000000000000000000000000000000001",
  "fair_value": "1.0",
  "clob_mid": "0.960",
  "oracle_fresh": true,
  "dispute_open": false,
  "received_at_ms": 1746790800000
}

Output — what the bot emits

OrderIntent — RFV IOC buy YES

{
  "intent_id": "oi_01HRFV0000001A",
  "market_id": "0xrfv0000000000000000000000000000000000000000000000000000000000001",
  "outcome": "YES",
  "side": "buy",
  "price": "0.960",
  "size_pUSD": "300.00",
  "tif": "IOC",
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "decision": {
    "edge_bps": 400.0,
    "reasons": [
      "RFV_EDGE_TRADE"
    ]
  }
}

12. Decision Logic

APPROVE

edge_bps >= min_edge_bps, oracle clean, no dispute, unambiguous source, market open, KillSwitch inactive.

RESHAPE_REQUIRED

Not applicable — reshaping handled by downstream Risk guardrail.

REJECT

edge_bps < 20 bps; oracle stale; dispute open; ambiguous source; KillSwitch active.

WARNING_ONLY

edge_bps between 20 and 50 bps triggers warning and 50% size reduction.

13. Standard Decision Output

This bot returns a OrderIntent object. See OrderIntent schema.

{
  "intent_id": "oi_01HRFV0000001A",
  "trace_id": "tr_01HRFV000TR001",
  "market_id": "0xrfv0000000000000000000000000000000000000000000000000000000000001",
  "outcome": "YES",
  "side": "buy",
  "price": "0.960",
  "size_pUSD": "300.00",
  "tif": "IOC",
  "post_only": false,
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "negrisk_aware": false,
  "decision": {
    "edge_bps": 400.0,
    "fair_value": 1.0,
    "clob_mid": 0.96,
    "reasons": [
      "RFV_EDGE_TRADE"
    ]
  },
  "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
RFV_EDGE_TRADEINFOedge_bps >= min_edge_bps, oracle clean, no dispute. IOC OrderIntent emitted.Emit IOC OrderIntent.A mispricing near resolution was found and a trade was placed.
RFV_NO_EDGEINFOedge_bps < 20 bps hard floor. Price is sufficiently close to fair value.Skip; emit sampled DecisionReport.The market was already priced close to fair value.
RFV_EDGE_MARGINALWARNedge_bps between 20 and 50 bps; size reduced 50%.Emit at 50% size; log warning.A small mispricing was found; a reduced-size trade was placed.
RFV_ORACLE_NOT_CLEANHARD_REJECTOracle data is stale or a UMA dispute is open.Skip; no OrderIntent.Data or dispute status blocked the trade.
RFV_AMBIGUOUS_SOURCEHARD_REJECTResolution rule parsing confidence is not at maximum.Skip; no OrderIntent.Resolution rules were not fully clear — no trade placed.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_strat_rfv_decisions_totalcountercountverdict, reason_codeTotal evaluation cycles by verdict and reason code.
polytraders_strat_rfv_edge_bpshistogrambasis_pointsDistribution of fair-value edge in bps.
polytraders_strat_rfv_intents_emitted_totalcountercountoutcomeTotal IOC OrderIntents emitted by outcome.
polytraders_strat_rfv_eval_latency_mshistogrammillisecondsLatency from oracle signal to OrderIntent emit.

Alerts

AlertConditionSeverityRunbook
RFVOracleNotCleanrate(polytraders_strat_rfv_decisions_total{reason_code='RFV_ORACLE_NOT_CLEAN'}[5m]) > 0.2warn#runbook-rfv-oracle
RFVKillSwitchrate(polytraders_strat_rfv_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0page#runbook-killswitch

16. Developer Reporting

{
  "bot_id": "strat.resolution_fair_value",
  "market_id": "0xrfv0000000000000000000000000000000000000000000000000000000000001",
  "fair_value": 1.0,
  "clob_mid": 0.96,
  "edge_bps": 400.0,
  "oracle_fresh": true,
  "dispute_open": false,
  "intent_emitted": true,
  "reason": "RFV_EDGE_TRADE",
  "emitted_at_ms": 1746790800000
}

17. Plain-English Reporting

SituationUser-facing explanation
Near-resolution trade placedThe market was priced below what the authoritative data suggests. An order was placed to capture the mispricing.
Oracle not clean — no tradeThe data source was stale or a dispute was open. No trade was placed until the oracle is confirmed.
Ambiguous resolution ruleThe market's resolution criteria were not fully clear. No trade was placed.

18. Failure-Mode Block

main_failure_modeAmbiguous resolution: market resolves N/A or in an unexpected direction because the resolution rule was less clear than the oracle signal suggested.
false_positive_riskOracle signal appears clean but is based on preliminary data that gets revised before official resolution.
false_negative_riskmin_edge_bps set too high misses genuine near-certainty mispricings on liquid markets.
safe_fallbackIf oracle stale or dispute open, emit RFV_ORACLE_NOT_CLEAN and skip without emitting any OrderIntent.
required_dependenciesws_market, clob_public, onchain UMA dispute status, internal resolution tracker, KillSwitch, internal builder code

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
ORACLE_STALEFreeze resolution tracker updates for > 60sAutomatic when tracker resumes.
DISPUTE_OPENSet uma_dispute=open for test marketAutomatic when dispute window closes.
KILL_SWITCH_ONSet killswitch.active=trueAutomatic on manual KillSwitch reset.

20. State & Persistence

Cold-start recovery

On cold start, oracle signals rebuilt from next resolution-tracker poll; dispute state re-fetched from onchain.

21. Concurrency & Idempotency

AspectSpecification
Execution modelactor-per-market
Max in-flight30
Idempotency keyintent_id
Per-call timeout (ms)250
Backpressure strategydrop oldest oracle update per market_id when queue depth > 2
Locking / mutual exclusionper-market_id mutex for oracle state

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 WebSocket (ws_market)best-effort
UMA Optimistic Oracle (onchain)99.9%

23. Security Surfaces

Abuse vectors considered

  • Oracle signal spoofing to induce trades on incorrect fair values
  • Dispute-status delay causing bot to trade during open dispute window

Mitigations

  • Oracle signals sourced from internal resolution tracker using authenticated feeds
  • UMA dispute status fetched directly from onchain; staleness treated as dispute_open=true

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
NotesBot not yet implemented; designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain). feeRateBps not present on any signed OrderIntent.

API surfaces declared

clob_publicclob_authws_marketonchaininternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ3-2026

Migration history

DateFromToReasonAction taken
2026-04-28n/av2-specSpec drafted post-CLOB-V2 cutover; bot not yet implementedDesigned against V2 schema (pUSD, builder codes, V2 EIP-712 domain)

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Emit IOC when edge=400 bps, oracle clean, no disputemin_edge_bps=100, fair_value=1.0, clob_mid=0.960IOC OrderIntent; reason=RFV_EDGE_TRADE
Skip when dispute openuma_dispute=openNo OrderIntent; reason=RFV_ORACLE_NOT_CLEAN
Skip when edge < 20 bps hard floorfair_value=0.980, clob_mid=0.979No OrderIntent; sampled reason=RFV_NO_EDGE

Integration Tests

TestExpected result
Full cycle: oracle signal → fair value → IOC OrderIntent on Polygon testnetOrder has builder.code, no feeRateBps, EIP-712 domain v2

Property Tests

PropertyRequired behaviour
Bot never trades when dispute is openAlways true
feeRateBps never present on any signed OrderIntentAlways true

27. Operational Runbook

RFV incidents are typically oracle staleness or open UMA disputes blocking trades. Escalate if oracle staleness persists > 5 min on active markets near resolution.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
RFVOracleNotClean
RFVKillSwitch

Manual overrides

Healthcheck

GET /internal/health/resolution-fair-value -> 200 if Oracle snapshots < 60s old; no markets blocked by open disputes for > 30s; KillSwitch inactive.. Red: Resolution tracker down or KillSwitch active..

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
Unit tests pass including dispute-open block and oracle-stale blockCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
p99 eval latency < 250ms over 24h shadow runpolytraders_strat_rfv_eval_latency_ms histogramp99 < 250ms

Promote to General live

GateHow measuredThreshold
E2E: oracle signal → fair value → IOC OrderIntent on Polygon testnet with no feeRateBpsE2E 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