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.8 News Materiality Trader

3.8 News Materiality Trader

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

News Materiality Trader positions ahead of the book's full digestion of material news events. It receives news items from the NewsIngest pipeline (Reuters, AP, Bloomberg, league wires), scores them using a NLP materiality classifier, resolves the news entity to one or more watchlisted Polymarket markets via an entity-resolution dictionary, and emits an IOC OrderIntent when materiality_score >= materiality_threshold. A cooldown period (cooldown_s) prevents re-trading the same entity-market pair within the window. Order TTL (order_ttl_s) limits exposure if the IOC is not immediately filled. This is a user-controlled execution tool for reacting to material news before book equilibration; it does not predict resolution 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 afterNewsIngest materiality scorer + entity-resolution
Applies toStandard binary markets where a resolved entity match triggers a NLP materiality score >= materiality_threshold on incoming Reuters, AP, Bloomberg, or league wire events
Default modelimited_live
User-visibleAdvanced details only
Developer ownerPolytraders core — Strategy pod

2. Purpose

News Materiality Trader positions ahead of the book's full digestion of material news events. It receives news items from the NewsIngest pipeline (Reuters, AP, Bloomberg, league wires), scores them using a NLP materiality classifier, resolves the news entity to one or more watchlisted Polymarket markets via an entity-resolution dictionary, and emits an IOC OrderIntent when materiality_score >= materiality_threshold. A cooldown period (cooldown_s) prevents re-trading the same entity-market pair within the window. Order TTL (order_ttl_s) limits exposure if the IOC is not immediately filled. This is a user-controlled execution tool for reacting to material news before book equilibration; it does not predict resolution outcomes or make performance claims.

3. Why This Bot Matters

  • Low-materiality news classified as high

    The bot takes a position based on a rumour or corrected story, entering before the market corrects back, resulting in a loss when the initial price move reverses.

  • Entity resolution maps to wrong market

    A news event about 'Team X wins championship' is resolved to a market about a different competition, causing a trade on an unrelated market.

  • Book depth insufficient for the news event

    Filling the full IOC at the entry price depletes the top-of-book, causing significant slippage that overwhelms the news edge.

  • cooldown_s too short — re-trading the same event

    Multiple news items from the same event (e.g., breaking news + confirmation + detail) trigger multiple entries on the same market before the book has repriced.

  • 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
Watchlisted markets matched by entityinternal (entity-resolution dictionary + Gamma API)YesMap the news event's entity to one or more open Polymarket markets. Only markets in the watchlist are eligible.
Book depth and recent volatility on the target marketclob_public + ws_marketYesSize the IOC order to min(available_depth, max_position_usd) and check recent volatility hasn't already absorbed the news.
Market open/closed/resolved statusclob_publicYesSkip markets that are closed, resolved, or approaching resolution (< 30 min to close).

5. Required Internal Inputs

InputSourceRequired?Use
KillSwitch active flagKillSwitchYesAbort all intent emission immediately if KillSwitch is active.
NewsIngest materiality score + entity match + news iteminternal (NewsIngest pipeline)YesTrigger trade when materiality_score >= materiality_threshold and entity resolves to an open watchlist market.
NLP materiality classifierinternalYesScore each news item on a 0–1 scale for market-moving impact on the resolved entity's Polymarket market.
Entity-resolution dictionaryinternalYesMap news entities (persons, teams, countries, companies) to Polymarket condition IDs.
Builder code bytes32internal configYesInjected into builder field on every signed V2 OrderIntent.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
materiality_threshold0.720.550.4Minimum NLP materiality score (0–1) required before emitting an OrderIntent on a matched market.
cooldown_s1204520Minimum seconds between trades on the same entity-market pair. Prevents re-entering on follow-up coverage of the same event.
order_ttl_s90200300Maximum seconds the IOC order's intent is considered valid. If not filled within this window, the trade is abandoned (not re-sent).
max_position_usd300500750Maximum pUSD size per news-triggered IOC order.

7. Detailed Parameter Instructions

materiality_threshold

What it means

Minimum NLP materiality score (0–1) required before emitting an OrderIntent on a matched market.

Default

{ "materiality_threshold": 0.72 }

Why this default matters

0.72 filters out speculation and soft news, targeting only high-confidence material events. Below 0.55 the classifier confidence is marginal; below 0.40 the bot will not trade regardless of config.

Threshold logic

ConditionAction
>= 0.72EMIT IOC OrderIntent
0.55–0.72WARN NEWS_MATERIALITY_SCORE_MARGINAL; emit at 50% size
< 0.40 (hard floor)SKIP — NEWS_MATERIALITY_TOO_LOW

Developer check

if score < params.hard: return skip('NEWS_MATERIALITY_TOO_LOW')

User-facing English

The news item did not score highly enough on the materiality classifier to justify a trade.

cooldown_s

What it means

Minimum seconds between trades on the same entity-market pair. Prevents re-entering on follow-up coverage of the same event.

Default

{ "cooldown_s": 120 }

Why this default matters

120 seconds ensures the book has time to partially reprice after the first trade before a second entry. Below 45s the book may not have moved at all; below 20s the bot will not trade regardless of config.

Threshold logic

ConditionAction
>= 120sNormal cooldown
45–120sWARN NEWS_MATERIALITY_SHORT_COOLDOWN; may re-trade same event
< 20s (hard floor)Reject config — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

if params.cooldown_s < params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')

User-facing English

— not yet authored —

order_ttl_s

What it means

Maximum seconds the IOC order's intent is considered valid. If not filled within this window, the trade is abandoned (not re-sent).

Default

{ "order_ttl_s": 90 }

Why this default matters

90 seconds limits news-edge exposure to the window where the event is still not fully priced in. Beyond 200s the news is likely fully digested and the edge is gone.

Threshold logic

ConditionAction
<= 90sNormal TTL window
90–300sWARN NEWS_MATERIALITY_LONG_TTL; news likely digested before fill
> 300s (hard ceiling)Reject config — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

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

User-facing English

The news trade window has passed. The order was not placed as the opportunity may no longer be valid.

max_position_usd

What it means

Maximum pUSD size per news-triggered IOC order.

Default

{ "max_position_usd": 300 }

Why this default matters

300 pUSD per event limits single-news exposure. Above 500 pUSD the order may consume most of the top-of-book depth and cause significant slippage.

Threshold logic

ConditionAction
<= 300 pUSDNormal size
300–750 pUSDWARN; confirm depth supports order; slippage risk elevated
> 750 pUSDReject config — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

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

User-facing English

The news trade was sized to fit within available market liquidity.

8. Default Configuration

{
  "bot_id": "strat.news_materiality_trader",
  "version": "2.1.0",
  "mode": "limited_live",
  "defaults": {
    "materiality_threshold": 0.72,
    "cooldown_s": 120,
    "order_ttl_s": 90,
    "max_position_usd": 300
  },
  "locked": {
    "materiality_threshold": {
      "min": 0.4
    },
    "cooldown_s": {
      "min": 20
    },
    "order_ttl_s": {
      "max": 300
    },
    "max_position_usd": {
      "max": 750
    }
  }
}

9. Implementation Flow

  1. Check KillSwitch active flag; if active, emit no OrderIntents.
  2. Subscribe to NewsIngest internal bus for materiality-scored news events.
  3. On each news event: check materiality_score >= materiality_threshold hard floor (0.40); if below, skip NEWS_MATERIALITY_TOO_LOW (sampled 1/100).
  4. Run entity-resolution: map news entity to Polymarket condition ID(s) via entity-resolution dictionary. If no match, skip NEWS_MATERIALITY_NO_MARKET_MATCH.
  5. For each matched market: confirm market is open and not approaching resolution (< 30 min to close) via clob_public.
  6. Check cooldown: if last trade on this entity-market pair was < cooldown_s seconds ago, skip NEWS_MATERIALITY_COOLDOWN_ACTIVE.
  7. If materiality_score < warning threshold (0.72): WARN NEWS_MATERIALITY_SCORE_MARGINAL; reduce size to 50% of max_position_usd.
  8. Fetch top-of-book depth from clob_public; set orderSize = min(depth, max_position_usd) * sizeMultiplier.
  9. Check recent market volatility (ws_market): if price has already moved > 50% of expected news impact, skip NEWS_MATERIALITY_ALREADY_DIGESTED.
  10. Emit OrderIntent: outcome=YES (or NO depending on event direction), side=buy, price=best_ask, 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 cooldown state: last_trade_ms for entity-market pair.
  13. Emit DecisionReport with intent_emitted=true, materiality_score, entity_id, market_id, reason NEWS_MATERIALITY_TRADE_TRIGGERED.

10. Reference Implementation

Consumes news events from the NewsIngest internal bus, scores materiality via NLP classifier, resolves entities to open Polymarket markets, and emits IOC OrderIntents when materiality exceeds threshold and cooldown has elapsed.

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

FUNCTION onNewsEvent(newsItem):
  // --- 0. KillSwitch gate ---
  ks = FETCH internal.killswitch.status
  IF ks.active: RETURN

  // --- 1. Materiality score gate ---
  score = newsItem.materiality_score  // from NLP classifier
  IF score < params.materiality_threshold_hard:  // 0.40
    IF random() < 0.01:
      EMIT DecisionReport(intent_emitted=false, reason='NEWS_MATERIALITY_TOO_LOW', score=score)
    RETURN

  // --- 2. Entity resolution ---
  markets = FETCH internal.entityDict.resolve(newsItem.entity_id)
  IF NOT markets:
    EMIT DecisionReport(intent_emitted=false, reason='NEWS_MATERIALITY_NO_MARKET_MATCH')
    RETURN

  FOR market_id IN markets:
    // --- 3. Market status check ---
    mkt = FETCH clob_public.GET('/markets/' + market_id)
    IF mkt.closed OR mkt.resolved OR mkt.minutes_to_close < 30:
      CONTINUE

    // --- 4. Cooldown check ---
    lastTrade = FETCH state.lastTradeMs(newsItem.entity_id, market_id)
    IF (now_ms() - lastTrade) < params.cooldown_s * 1000:
      EMIT DecisionReport(intent_emitted=false, reason='NEWS_MATERIALITY_COOLDOWN_ACTIVE')
      CONTINUE

    // --- 5. Already-digested check ---
    recentMove = FETCH ws_market.priceChangeSinceMs(market_id, newsItem.received_at_ms)
    IF recentMove > 0.5 * expectedNewsImpact(score):
      EMIT DecisionReport(intent_emitted=false, reason='NEWS_MATERIALITY_ALREADY_DIGESTED')
      CONTINUE

    // --- 6. Size determination ---
    sizeMultiplier = 0.5 IF score < params.materiality_threshold ELSE 1.0
    IF sizeMultiplier < 1.0: WARN('NEWS_MATERIALITY_SCORE_MARGINAL')
    depth = FETCH clob_public.depth(market_id, side='YES')
    orderSize = toPusdUnits(min(depth, params.max_position_usd) * sizeMultiplier)

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

    // --- 8. Register cooldown ---
    SET state.lastTradeMs(newsItem.entity_id, market_id, now_ms())

    EMIT DecisionReport(intent_emitted=true, materiality_score=score,
                        entity_id=newsItem.entity_id, market_id=market_id,
                        reason='NEWS_MATERIALITY_TRADE_TRIGGERED')

SDK calls used

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

Complexity: O(n_markets_matched) per news event; typically O(1) for single-entity events

11. Wire Examples

Input — what arrives on the wire

NewsIngest event — high materiality, entity resolvedinternal (NewsIngest bus)

{
  "event_id": "news_01HXNMT000EVENT",
  "entity_id": "entity_candidate_A_primary",
  "headline": "Candidate A wins state primary by large margin",
  "source": "Reuters",
  "materiality_score": 0.81,
  "direction": "positive",
  "matched_market_ids": [
    "0xnewsmat000000000000000000000000000000000000000000000000000000001"
  ],
  "received_at_ms": 1746790600000
}

Output — what the bot emits

OrderIntent — news IOC buy YES (builder-attributed)

{
  "intent_id": "oi_01HXNMT0000001A",
  "trace_id": "tr_01HXNMT000TR001",
  "market_id": "0xnewsmat000000000000000000000000000000000000000000000000000000001",
  "outcome": "YES",
  "side": "buy",
  "price": "0.438",
  "size_pUSD": "300.00",
  "tif": "IOC",
  "post_only": false,
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "negrisk_aware": false,
  "decision": {
    "materiality_score": 0.81,
    "entity_id": "entity_candidate_A_primary",
    "news_source": "Reuters",
    "reasons": [
      "NEWS_MATERIALITY_TRADE_TRIGGERED"
    ]
  },
  "comment": "fees are operator-set at match time in V2 — feeRateBps is NOT on the signed order"
}

DecisionReport — skipped (score too low), sampled 1/100

{
  "report_id": "dr_01HXNMT999ZZZZ",
  "bot_id": "strat.news_materiality_trader",
  "entity_id": "entity_candidate_B",
  "intent_emitted": false,
  "materiality_score": 0.28,
  "reasons": [
    "NEWS_MATERIALITY_TOO_LOW"
  ],
  "sampled": true,
  "evaluated_at_ms": 1746790601000
}

12. Decision Logic

APPROVE

materiality_score >= materiality_threshold, entity resolves to open market, cooldown elapsed, depth sufficient, KillSwitch inactive. Emit IOC OrderIntent.

RESHAPE_REQUIRED

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

REJECT

score < 0.40 hard floor; no entity match; cooldown active; market closed; KillSwitch active; stale feed. Emit DecisionReport intent_emitted=false.

WARNING_ONLY

score between 0.40 and 0.72 triggers NEWS_MATERIALITY_SCORE_MARGINAL and 50% size reduction.

13. Standard Decision Output

This bot returns a OrderIntent object. See OrderIntent schema.

{
  "intent_id": "oi_01HXNMT0000001A",
  "trace_id": "tr_01HXNMT000TR001",
  "market_id": "0xnewsmat000000000000000000000000000000000000000000000000000000001",
  "outcome": "YES",
  "side": "buy",
  "price": "0.438",
  "size_pUSD": "300.00",
  "tif": "IOC",
  "post_only": false,
  "builder": {
    "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
    "fee_bps": 25
  },
  "negrisk_aware": false,
  "decision": {
    "materiality_score": 0.81,
    "entity_id": "entity_candidate_A_primary",
    "news_source": "Reuters",
    "reasons": [
      "NEWS_MATERIALITY_TRADE_TRIGGERED"
    ]
  },
  "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
NEWS_MATERIALITY_TRADE_TRIGGEREDINFOmateriality_score >= threshold, entity resolved, cooldown cleared, market open. IOC OrderIntent emitted.Emit IOC OrderIntent; register cooldown.A material news event was detected and a trade was placed to capture the expected price move.
NEWS_MATERIALITY_TOO_LOWINFOmateriality_score is below the 0.40 hard floor. News is not material enough to trade.Skip; emit sampled DecisionReport.The news item was not rated as material enough to justify a trade.
NEWS_MATERIALITY_SCORE_MARGINALWARNmateriality_score between 0.40 and 0.72. Trade is marginal; size reduced to 50%.Emit IOC at 50% size; log warning.A moderately material news event was detected. A smaller trade was placed.
NEWS_MATERIALITY_NO_MARKET_MATCHINFONews entity did not resolve to any open watchlist market in the entity-resolution dictionary.Skip; emit DecisionReport.
NEWS_MATERIALITY_COOLDOWN_ACTIVEINFOLast trade on this entity-market pair was within cooldown_s seconds.Skip; emit DecisionReport.A trade was placed recently for this news entity. The cooldown period must pass before another trade.
NEWS_MATERIALITY_ALREADY_DIGESTEDINFOMarket price has already moved significantly since the news event; likely already digested.Skip; emit DecisionReport.The market appears to have already reacted to this news. No trade was placed.
NEWS_MATERIALITY_SHORT_COOLDOWNWARNcooldown_s config is below the 45s warning threshold. Re-trading same event risk elevated.Allow but log warning.
STALE_MARKET_DATAHARD_REJECTclob_public or ws_market data is stale (> 5s) for target market.Skip; no OrderIntent emitted.Market data was too old to act on safely.
KILL_SWITCH_ACTIVEHARD_REJECTGlobal kill switch is active.Skip all news events; no OrderIntents emitted.Trading is currently paused.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_strat_newsmateriality_decisions_totalcountercountverdict, reason_codeTotal news events evaluated by intent_emitted and reason code.
polytraders_strat_newsmateriality_scorehistogramscoreDistribution of NLP materiality scores across all evaluated news events.
polytraders_strat_newsmateriality_intents_emitted_totalcountercountnews_source, entity_categoryTotal IOC OrderIntents emitted by news source and entity category.
polytraders_strat_newsmateriality_cooldown_blocks_totalcountercountentity_idNumber of trades blocked by active cooldown per entity.
polytraders_strat_newsmateriality_eval_latency_mshistogrammillisecondsWall-clock time from news event receipt to OrderIntent emit.
polytraders_strat_newsmateriality_digested_skips_totalcountercountmarket_idNews events skipped because market had already priced the news.

Alerts

AlertConditionSeverityRunbook
NewsMaterialityHighLatencyhistogram_quantile(0.99, rate(polytraders_strat_newsmateriality_eval_latency_ms_bucket[5m])) > 500warn#runbook-newsmateriality-latency
NewsMaterialityHighDigestedRaterate(polytraders_strat_newsmateriality_digested_skips_total[10m]) / rate(polytraders_strat_newsmateriality_decisions_total[10m]) > 0.6warn#runbook-newsmateriality-digested
NewsMaterialityStaleFeedrate(polytraders_strat_newsmateriality_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1warn#runbook-newsmateriality-stale-feed
NewsMaterialityKillSwitchBlockingrate(polytraders_strat_newsmateriality_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0page#runbook-killswitch

Dashboards

  • Grafana — Strategy / NewsMateriality score distribution and trade frequency
  • Grafana — Strategy / NewsMateriality entity resolution hit rate and cooldown blocks

16. Developer Reporting

{
  "bot_id": "strat.news_materiality_trader",
  "market_id": "0xnewsmat000000000000000000000000000000000000000000000000000000001",
  "materiality_score": 0.81,
  "entity_id": "entity_candidate_A_primary",
  "news_source": "Reuters",
  "order_size_pusd": 300.0,
  "price_at_entry": 0.438,
  "cooldown_cleared": true,
  "depth_pusd": 520.0,
  "intent_emitted": true,
  "reason": "NEWS_MATERIALITY_TRADE_TRIGGERED",
  "emitted_at_ms": 1746790600000
}

17. Plain-English Reporting

SituationUser-facing explanation
News-triggered trade placedA material news event was detected and matched to this market. An order was placed to position ahead of the expected book repricing.
News scored too low — no tradeThe news item did not rate highly enough on the materiality classifier to justify a trade. No order was placed.
Cooldown active — no tradeA trade was placed recently on the same market for the same news entity. The cooldown period must elapse before another trade is considered.
News already digested — no tradeThe market price had already moved significantly before the order was placed, suggesting the news is already priced in. No order was placed to avoid chasing the move.

18. Failure-Mode Block

main_failure_modeFalse materiality classification: the NLP classifier scores a rumour or corrected story as material, triggering a trade that reverses when the story is clarified, producing a loss.
false_positive_riskHigh materiality score on irrelevant news that shares entity keywords with a target market (e.g., 'Team X wins match' when the market is about a different competition).
false_negative_riskmateriality_threshold set too high misses genuine market-moving events, especially fast-breaking sports results where the classifier has less training data.
safe_fallbackIf NewsIngest feed is unavailable or clob_public market data is stale (> 5s), emit STALE_MARKET_DATA DecisionReport and skip without emitting any OrderIntent. Fail closed on feed unavailability.
required_dependenciesNewsIngest internal feed (Reuters, AP, Bloomberg, league wires + NLP classifier), Entity-resolution dictionary, clob_public market endpoint (status + depth), ws_market (recent price volatility check), KillSwitch active flag, internal builder code

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
LOW_MATERIALITY_NEWSSend mock news event with materiality_score=0.25 (< 0.40 hard floor)Automatic; next qualifying news event can fire.
COOLDOWN_ACTIVESet state.lastTradeMs = now_ms() - 30s (< cooldown_s=120)Automatic when cooldown_s elapses.
NEWS_ALREADY_DIGESTEDSet mock price change since news to > 50% of expected impactAutomatic; subsequent news events on same market can trade.
NLP_CLASSIFIER_UNAVAILABLETake NLP classifier offlineAutomatic when classifier restores.
KILL_SWITCH_ONSet killswitch.active=trueAutomatic on manual KillSwitch reset.

20. State & Persistence

Cold-start recovery

On cold start, cooldown state is cleared (conservative: may re-trade within cooldown on restart). Price state rebuilt from first ws_market tick.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop
Max in-flight20
Idempotency keyintent_id
Per-call timeout (ms)300
Backpressure strategydrop oldest pending news event per entity when queue depth > 3
Locking / mutual exclusionper entity_id+market_id mutex for cooldown state read/write

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 public API99.9% (Polymarket-published)
Polymarket CLOB WebSocket (ws_market)best-effort
NewsIngest internal feed (Reuters, AP, Bloomberg, league wires)internal SLA
NLP materiality classifier (internal)internal SLA

23. Security Surfaces

On-chain contract calls

ContractMethodNetworkEffect
CTFExchangeV2polygon

Abuse vectors considered

  • Injecting synthetic news into the NewsIngest feed to trigger trades on specific markets
  • Adversary front-runs the bot by monitoring entity-resolution patterns to predict which markets will be traded on material news
  • Cooldown manipulation: flooding the system with low-score news to exhaust entity-market pair cooldown windows

Mitigations

  • NewsIngest feed is authenticated and sourced only from verified news providers; synthetic injection requires compromising the internal feed
  • IOC orders do not rest on the book; adversary cannot exploit known bid/ask placement
  • Hard materiality floor (0.40) prevents cooldown exhaustion via noise events
  • V2 order timestamp(ms) invalidates replayed signed orders

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 on standard binary markets when a material news event is scored above materiality_threshold by the NLP classifier. feeRateBps is not present on any signed order. Builder code injected on every intent.

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 order construction. Updated collateral to pUSD. Injected builder field (bytes32) on every OrderIntent. EIP-712 Exchange domain version updated from '1' to '2'. NewsIngest pipeline updated to V2 internal bus schema. Entity-resolution dictionary updated for 2026 market coverage.

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Emit IOC when materiality_score=0.81, entity resolves, cooldown clearedscore=0.81, threshold=0.72, cooldown elapsed, market open, depth=520IOC OrderIntent emitted; DecisionReport intent_emitted=true, reason=NEWS_MATERIALITY_TRADE_TRIGGERED
Skip when score < hard floor (0.40)materiality_score=0.35No OrderIntent; sampled DecisionReport reason=NEWS_MATERIALITY_TOO_LOW
Skip when cooldown activelast_trade_ms = now_ms() - 30s (cooldown_s=120)No OrderIntent; reason=NEWS_MATERIALITY_COOLDOWN_ACTIVE
Reduce size 50% when score marginal (0.60)score=0.60, threshold=0.72, max_position_usd=300OrderIntent emitted with size=150; WARN NEWS_MATERIALITY_SCORE_MARGINAL
Skip when no entity match in dictionarynews entity not in entity-resolution dictionaryNo OrderIntent; reason=NEWS_MATERIALITY_NO_MARKET_MATCH
Skip when KillSwitch activekillswitch.active=trueNo OrderIntents emitted

Integration Tests

TestExpected result
Full cycle: news event arrives → NLP scores → entity resolved → IOC OrderIntent submittedOrder has builder.code (bytes32), no feeRateBps, tif=IOC, EIP-712 domain version '2'
Cooldown correctly prevents re-trading same entity-market pair within cooldown_sSecond intent blocked for cooldown_s; emitted after cooldown elapsed

Property Tests

PropertyRequired behaviour
Bot never trades a market without a confirmed entity match in the resolution dictionaryAlways true
feeRateBps never present on any signed OrderIntentAlways true — V2 fees are operator-set at match time
Cooldown always enforced per entity-market pair; no two intents within cooldown_s on same pairAlways true

27. Operational Runbook

News Materiality Trader incidents are typically high already-digested rates (slow news pipeline latency), NLP classifier outages (blocking all trades), or false-materiality classifications causing unexpected positions. Classifier outages fail-closed and resolve automatically.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
NewsMaterialityHighLatency
NewsMaterialityHighDigestedRate
NewsMaterialityStaleFeed
NewsMaterialityKillSwitchBlocking

Manual overrides

Healthcheck

GET /internal/health/news-materiality-trader -> 200 if NewsIngest feed live, NLP classifier reachable, clob_public reachable, KillSwitch inactive, at least one news event evaluated in last 10 min.

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 cooldown invariant and entity-resolution gateCI 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 < 300ms over 24h (from news event receipt to OrderIntent emit)polytraders_strat_newsmateriality_eval_latency_ms histogramp99 < 300ms
Already-digested rate < 40% of qualifying events over 48h shadow runpolytraders_strat_newsmateriality_digested_skips_total / decisions_total< 40%

Promote to General live

GateHow measuredThreshold
E2E: news event arrives → NLP scores → entity resolved → IOC OrderIntent submitted on Polygon testnetE2E testPass
Cooldown enforcement verified: same entity-market pair not re-traded within cooldown_s 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