1. Bot Identity
| Layer | Strategy Strategy |
|---|
| Bot class | Alpha Strategy |
|---|
| Authority | Trade |
|---|
| Status | PLANNED |
|---|
| Readiness | Spec started |
|---|
| Runs before | Risk guardrail pipeline |
|---|
| Runs after | Resolution tracker / Oracle monitor |
|---|
| Applies to | Near-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 mode | shadow_only |
|---|
| User-visible | Advanced details only |
|---|
| Developer owner | Polytraders 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.
6. Parameter Guide
| Parameter | Default | Warning | Hard | What it controls |
|---|
| min_edge_bps | 100 | 50 | 20 | Minimum divergence in bps between CLOB price and estimated fair value required to emit an OrderIntent. |
| max_size_per_market_usd | 500 | 750 | 1000 | Maximum pUSD exposure per market near resolution. |
| require_unambiguous_source | True | None | None | If true (locked), only trade markets where the resolution rule parsing confidence is at maximum. Ambiguous rules block trading. |
| require_oracle_clean | True | None | None | Requires 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
| Condition | Action |
|---|
| >= 100 bps | EMIT IOC OrderIntent |
| 50–100 bps | WARN RFV_EDGE_MARGINAL; emit at 50% size |
| < 20 bps | SKIP — 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
| Condition | Action |
|---|
| <= 500 pUSD | Normal sizing |
| > 1000 pUSD | Reject 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
| Condition | Action |
|---|
| ambiguous source detected | HARD_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
| Condition | Action |
|---|
| oracle stale or dispute open | HARD_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
- Check KillSwitch; if active, emit no OrderIntents.
- FETCH clob_public market status; skip if closed or resolved.
- Check oracle signal from resolution tracker; if stale or source ambiguous, emit RFV_ORACLE_NOT_CLEAN / RFV_AMBIGUOUS_SOURCE and skip.
- Check onchain UMA dispute status; if dispute open, skip.
- Compute fair_value from oracle signal; edge_bps = |fair_value - clob_mid| * 10000.
- IF edge_bps < hard (20 bps): emit sampled DecisionReport RFV_NO_EDGE; skip.
- IF edge_bps < warning (50 bps): WARN RFV_EDGE_MARGINAL; reduce size 50%.
- Compute order size = min(max_size_per_market_usd, available_depth).
- 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.0 — internal (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
| Code | Severity | Meaning | Action | User-facing message |
|---|
RFV_EDGE_TRADE | INFO | edge_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_EDGE | INFO | edge_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_MARGINAL | WARN | edge_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_CLEAN | HARD_REJECT | Oracle data is stale or a UMA dispute is open. | Skip; no OrderIntent. | Data or dispute status blocked the trade. |
RFV_AMBIGUOUS_SOURCE | HARD_REJECT | Resolution rule parsing confidence is not at maximum. | Skip; no OrderIntent. | Resolution rules were not fully clear — no trade placed. |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_strat_rfv_decisions_total | counter | count | verdict, reason_code | Total evaluation cycles by verdict and reason code. |
polytraders_strat_rfv_edge_bps | histogram | basis_points | | Distribution of fair-value edge in bps. |
polytraders_strat_rfv_intents_emitted_total | counter | count | outcome | Total IOC OrderIntents emitted by outcome. |
polytraders_strat_rfv_eval_latency_ms | histogram | milliseconds | | Latency from oracle signal to OrderIntent emit. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
RFVOracleNotClean | rate(polytraders_strat_rfv_decisions_total{reason_code='RFV_ORACLE_NOT_CLEAN'}[5m]) > 0.2 | warn | #runbook-rfv-oracle |
RFVKillSwitch | rate(polytraders_strat_rfv_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0 | page | #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
| Situation | User-facing explanation |
|---|
| Near-resolution trade placed | The market was priced below what the authoritative data suggests. An order was placed to capture the mispricing. |
| Oracle not clean — no trade | The data source was stale or a dispute was open. No trade was placed until the oracle is confirmed. |
| Ambiguous resolution rule | The market's resolution criteria were not fully clear. No trade was placed. |
18. Failure-Mode Block
| main_failure_mode | Ambiguous resolution: market resolves N/A or in an unexpected direction because the resolution rule was less clear than the oracle signal suggested. |
|---|
| false_positive_risk | Oracle signal appears clean but is based on preliminary data that gets revised before official resolution. |
|---|
| false_negative_risk | min_edge_bps set too high misses genuine near-certainty mispricings on liquid markets. |
|---|
| safe_fallback | If oracle stale or dispute open, emit RFV_ORACLE_NOT_CLEAN and skip without emitting any OrderIntent. |
|---|
| required_dependencies | ws_market, clob_public, onchain UMA dispute status, internal resolution tracker, KillSwitch, internal builder code |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
ORACLE_STALE | Freeze resolution tracker updates for > 60s | | Automatic when tracker resumes. |
DISPUTE_OPEN | Set uma_dispute=open for test market | | Automatic when dispute window closes. |
KILL_SWITCH_ON | Set killswitch.active=true | | Automatic 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
| Aspect | Specification |
|---|
| Execution model | actor-per-market |
| Max in-flight | 30 |
| Idempotency key | intent_id |
| Per-call timeout (ms) | 250 |
| Backpressure strategy | drop oldest oracle update per market_id when queue depth > 2 |
| Locking / mutual exclusion | per-market_id mutex for oracle state |
22. Dependencies
Depends on (must run first)
| Bot | Why | Contract |
|---|
| risk.kill_switch | Checked first; blocks all intent emission when active. | |
Emits to (downstream consumers)
External services
| Service | Endpoint | SLA assumed | On 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
| Aspect | Value |
|---|
| CLOB version | v2 |
| Collateral asset | pUSD |
| EIP-712 Exchange domain version | 2 |
| Aware of builderCode field | yes |
| Aware of negative-risk markets | no |
| Multi-chain ready | no |
| SDK used | py-clob-client-v2 |
| Settlement contract | CTFExchangeV2 |
| Notes | Bot 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
| Field | Value |
|---|
| spec | 2.0.0 |
| implementation | 0.1.0 |
| schema | 2 |
| released | None |
| planned_release | Q3-2026 |
Migration history
| Date | From | To | Reason | Action taken |
|---|
| 2026-04-28 | n/a | v2-spec | Spec drafted post-CLOB-V2 cutover; bot not yet implemented | Designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain) |
26. Acceptance Tests
Unit Tests
| Test | Setup | Expected result |
|---|
| Emit IOC when edge=400 bps, oracle clean, no dispute | min_edge_bps=100, fair_value=1.0, clob_mid=0.960 | IOC OrderIntent; reason=RFV_EDGE_TRADE |
| Skip when dispute open | uma_dispute=open | No OrderIntent; reason=RFV_ORACLE_NOT_CLEAN |
| Skip when edge < 20 bps hard floor | fair_value=0.980, clob_mid=0.979 | No OrderIntent; sampled reason=RFV_NO_EDGE |
Integration Tests
| Test | Expected result |
|---|
| Full cycle: oracle signal → fair value → IOC OrderIntent on Polygon testnet | Order has builder.code, no feeRateBps, EIP-712 domain v2 |
Property Tests
| Property | Required behaviour |
|---|
| Bot never trades when dispute is open | Always true |
| feeRateBps never present on any signed OrderIntent | Always 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
| Alert | First step | Diagnosis | Mitigation | Escalate 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..
29. Developer Checklist
Ready-to-ship score: 27/27 sections complete · 100%
| Requirement | Status |
|---|
| Purpose defined | ✓ done |
| Required inputs listed | ✓ done |
| Parameters defined | ✓ done |
| Defaults defined | ✓ done |
| Warning thresholds defined | ✓ done |
| Hard thresholds defined | ✓ done |
| Safe fallback defined | ✓ done |
| Structured output defined | ✓ done |
| Developer log defined | ✓ done |
| Plain-English explanation | ✓ done |
| Unit tests defined | ✓ done |
| Integration tests defined | ✓ done |
| Property tests defined | ✓ done |
| Failure-mode block complete | ✓ done |
| Reference implementation pseudocode | ✓ done |
| Wire examples (input + output) | ✓ done |
| Reason codes listed | ✓ done |
| Metrics & logs defined | ✓ done |
| State & persistence defined | ✓ done |
| Concurrency & idempotency defined | ✓ done |
| Dependencies declared | ✓ done |
| Security surfaces declared | ✓ done |
| Polymarket V2 compatibility declared | ✓ done |
| Version & migration history declared | ✓ done |
| Operational runbook defined | ✓ done |
| Promotion gates defined | ✓ done |
| Failure-injection recipes defined | ✓ done |