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 LayerDiscovery0.5 NewMarketWatcher

0.5 NewMarketWatcher

Discovery Signal Service Read-onlyRecommend PLANNED Spec started capital · Indirect P2 · Data normalisation pending stub

Poll the Gamma API at high frequency to detect newly listed markets as soon as they appear, parse their initial metadata, and emit ObservationReports so strategies can evaluate early liquidity opportunities before the book fills.

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

LayerDiscovery  Discovery
Bot classSignal Service
AuthorityRead-onlyRecommend
StatusPLANNED
ReadinessSpec started
Runs beforeStrategy OrderIntent generation
Runs afterMarket data ingestion
Applies toNewly listed Polymarket markets not yet in the MarketScanner candidate list
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core — Intelligence pod

2. Purpose

Poll the Gamma API at high frequency to detect newly listed markets as soon as they appear, parse their initial metadata, and emit ObservationReports so strategies can evaluate early liquidity opportunities before the book fills.

3. Why This Bot Matters

  • New markets discovered only on the next slow scan cycle

    Early-entry advantage is lost; book may already be competitive by the time MarketScanner surfaces the market.

  • Unvalidated new market metadata forwarded to strategies

    Markets with incomplete rules text or missing resolution dates may produce erroneous strategy decisions.

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
New condition_id metadata appearing on Gamma APIGamma APIYesPrimary source for detecting newly listed markets via ETag-gated polling.
Initial book state (or absence of book)CLOBYesAssess whether any resting liquidity exists at listing time.
Tick-size, neg-risk flag, and resolution metadata at listingGamma APIYesPass initial metadata to strategies via ObservationReport.

5. Required Internal Inputs

InputSourceRequired?Use
Known condition_id set (from MarketScanner)disc.marketscannerYesDetect genuinely new markets by diffing against the known set.
KillSwitch active flagrisk.kill_switchYesSuppress emissions when KillSwitch is active.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
poll_interval_s1052How often in seconds the Gamma API is polled for newly listed markets.
min_listing_age_s30100Minimum age in seconds a new market must have before being emitted; prevents emitting markets that may be immediately delisted.

7. Detailed Parameter Instructions

poll_interval_s

What it means

How often in seconds the Gamma API is polled for newly listed markets.

Default

{ "poll_interval_s": 10 }

Why this default matters

10-second polling catches new listings within one cycle while staying well within Gamma API rate limits.

Threshold logic

ConditionAction
>= 10sNormal polling cadence
5–10sFast poll — WARN; monitor rate limits
< 2sReject — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

if (s < params.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');

User-facing English

New markets are checked frequently to catch early trading opportunities.

min_listing_age_s

What it means

Minimum age in seconds a new market must have before being emitted; prevents emitting markets that may be immediately delisted.

Default

{ "min_listing_age_s": 30 }

Why this default matters

A 30-second hold-off filters transient listings that are created and immediately cancelled.

Threshold logic

ConditionAction
>= 30sNormal hold-off
10–30sShort hold-off — WARN
0sNo hold-off; emit immediately

Developer check

if (age < params.min_listing_age_s) hold();

User-facing English

New markets are held briefly before being surfaced to confirm they are stable listings.

8. Default Configuration

{
  "bot_id": "disc.new_market_watcher",
  "version": "0.1.0",
  "mode": "shadow_only",
  "defaults": {
    "poll_interval_s": 10,
    "min_listing_age_s": 30,
    "alert_to_strategies": [],
    "require_rule_parse": true
  }
}

9. Implementation Flow

  1. On each poll cycle, send a conditional GET to Gamma API using the ETag from the previous response.
  2. If ETag matches (304 Not Modified), skip processing — no new markets.
  3. If new markets are present, diff against the known condition_id set to identify genuinely new listings.
  4. Check KillSwitch; if active, update known set but suppress emissions.
  5. For each new market, apply min_listing_age_s hold-off; hold in a pending set.
  6. After hold-off, fetch initial book state from CLOB and parse resolution metadata.
  7. If require_rule_parse=true, validate that resolution rules text and resolution date are present.
  8. Emit ObservationReport with condition_id, initial_book_state, neg_risk, tick_size, resolution_date.
  9. Add newly emitted markets to the known condition_id set.
  10. Log cycle summary: new_markets_detected, pending_count, emitted_count.

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 pollCycle():
  ks = FETCH internal.killswitch.status

  response = gamma.GET('/markets?active=true&closed=false',
                       headers={'If-None-Match': last_etag})
  IF response.status == 304: RETURN  // no new listings

  last_etag = response.headers['ETag']
  all_ids = {m.condition_id FOR m IN response.markets}
  new_ids = all_ids - known_condition_ids

  FOR condition_id IN new_ids:
    market = response.market(condition_id)
    listing_age_s = now() - market.created_at
    IF listing_age_s < params.min_listing_age_s:
      pending_set.add(condition_id, market); CONTINUE

    IF params.require_rule_parse AND NOT market.rules_text:
      pending_metadata.add(condition_id); CONTINUE  // INCOMPLETE_MARKET_METADATA

    book = FETCH clob_public.GET('/book?market=' + condition_id + '&depth=5')
    initial_state = 'empty' IF book.bids == [] AND book.asks == [] ELSE 'active'

    IF NOT ks.active:
      EMIT ObservationReport(condition_id, listing_age_s, market.neg_risk,
                             market.tick_size, market.resolution_date,
                             initial_state)

    known_condition_ids.add(condition_id)

  // Also re-check pending_metadata set
  FOR cid IN pending_metadata:
    market = gamma.GET('/market/' + cid)
    IF market.rules_text AND NOT ks.active:
      EMIT ObservationReport(cid, ...)
      known_condition_ids.add(cid)

  LOG poll cycle summary

SDK calls used

  • gamma.GET('/markets?active=true&closed=false', If-None-Match: <etag>)
  • gamma.GET('/market/<condition_id>')
  • fetchClobPublic('/book?market=<condition_id>&depth=5')

Complexity: O(N) where N = new markets per poll cycle; amortised O(1) with ETag 304

11. Wire Examples

Input — what arrives on the wire

Gamma API new market entrygamma_api

{
  "condition_id": "0xaabbccddeeff001122334455667788aabbccddeeff001122334455667788aabb",
  "question": "Will the new legislation pass by September 2026?",
  "active": true,
  "created_at": "2026-05-09T11:29:25Z",
  "neg_risk": false,
  "tick_size": 0.01,
  "resolution_date": "2026-09-01T00:00:00Z",
  "rules_text": "Resolves YES if the bill passes both chambers by midnight UTC on 2026-09-01."
}

Output — what the bot emits

ObservationReport — new market detected

{
  "report_id": "0xff002244668800aabbccddee112233ff002244668800aabbccddee11223344",
  "bot_id": "disc.new_market_watcher",
  "market_id": "0xaabbccddeeff001122334455667788aabbccddeeff001122334455667788aabb",
  "listing_detected_at_ms": 1746789000000,
  "listing_age_s": 35,
  "neg_risk": false,
  "tick_size": 0.01,
  "resolution_date": "2026-09-01T00:00:00Z",
  "initial_book_state": "empty",
  "warnings": [],
  "emitted_at_ms": 1746789035000
}

Reproduce locally

curl -H 'If-None-Match: "abc123"' 'https://gamma-api.polymarket.com/markets?active=true&closed=false'

12. Decision Logic

APPROVE

Not applicable — NewMarketWatcher emits ObservationReports, not approvals.

RESHAPE_REQUIRED

Not applicable — read-only watcher bot.

REJECT

Markets without resolution rules text when require_rule_parse=true receive INCOMPLETE_MARKET_METADATA and are not forwarded until metadata is present.

WARNING_ONLY

Markets at or below the hold-off threshold emit a NEW_MARKET_HOLDOFF warning while pending.

13. Standard Decision Output

This bot returns a ObservationReport object. See ObservationReport schema.

{
  "report_id": "0xff002244668800aabbccddee112233ff002244668800aabbccddee11223344",
  "bot_id": "disc.new_market_watcher",
  "market_id": "0xaabbccddeeff001122334455667788aabbccddeeff001122334455667788aabb",
  "listing_detected_at_ms": 1746789000000,
  "listing_age_s": 35,
  "neg_risk": false,
  "tick_size": 0.01,
  "resolution_date": "2026-09-01T00:00:00Z",
  "initial_book_state": "empty",
  "warnings": [],
  "emitted_at_ms": 1746789035000
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
INCOMPLETE_MARKET_METADATAWARNNew market is missing required rules text or resolution date; held in pending set.Retry on next poll cycle; emit once metadata is complete.A new market is being evaluated but is missing some required details.
NEW_MARKET_HOLDOFFINFOMarket is within the min_listing_age_s hold-off window.Hold in pending set; emit after hold-off expires.A newly listed market is being held briefly before surfacing.
KILL_SWITCH_ACTIVEHARD_REJECTKillSwitch is active; all emissions suppressed.Update known set but emit no ObservationReports.
STALE_MARKET_DATAHARD_REJECTGamma API unavailable; serving known set from cache only.Halt new-market emissions; retry on next poll cycle.
PARAMETER_CHANGE_REQUIRES_APPROVALHARD_REJECTpoll_interval_s below locked hard minimum of 2s.Reject config change.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_disc_newmarketwatcher_new_markets_detected_totalcountercountTotal new market_ids detected via Gamma API polling.
polytraders_disc_newmarketwatcher_reports_emitted_totalcountercountObservationReports successfully emitted for new markets.
polytraders_disc_newmarketwatcher_pending_holdoff_gaugegaugecountNumber of new markets currently in the hold-off pending set.
polytraders_disc_newmarketwatcher_poll_latency_mshistogrammsLatency of each Gamma API poll cycle.

Alerts

AlertConditionSeverityRunbook
NewMarketWatcherGammaAPIDownrate(polytraders_disc_newmarketwatcher_new_markets_detected_total[5m]) == 0 AND up{job='gamma_api'} == 0P1#runbook-newmarketwatcher-gamma-api
NewMarketWatcherHighPendingpolytraders_disc_newmarketwatcher_pending_holdoff_gauge > 20P2#runbook-newmarketwatcher-high-pending

Dashboards

  • Grafana — Discovery / NewMarketWatcher detection latency

Log levels

LevelWhat gets logged
DEBUGPer-new-market condition_id, listing_age_s, initial_book_state.
INFOPoll cycle summary: detected, pending, emitted.
WARNGamma API slow; high pending holdoff count.
ERRORGamma API unavailable; CLOB unreachable.

16. Developer Reporting

{
  "bot_id": "disc.new_market_watcher",
  "poll_cycle": 4820,
  "new_markets_detected": 2,
  "pending_holdoff": 1,
  "emitted": 1,
  "suppressed_killswitch": 0,
  "known_set_size": 318,
  "polled_at": "2026-05-09T11:30:00Z"
}

17. Plain-English Reporting

SituationUser-facing explanation
New market shown immediately after launchThis market was just listed on Polymarket. It may have very little liquidity right now — strategies will assess whether early positioning is appropriate.
New market not yet surfacedA newly listed market may be in the hold-off period while we confirm it is a stable listing before surfacing it.

18. Failure-Mode Block

main_failure_modeETag polling misses a burst of new listings if Gamma API returns a non-304 response that is then lost due to a network error.
false_positive_riskA market that is listed and immediately delisted within the hold-off window may still be emitted if the hold-off is shorter than the cancellation latency.
false_negative_riskA new market without a resolution rules text when require_rule_parse=true will be silently held until metadata appears, missing the early liquidity window.
safe_fallbackIf Gamma API is unavailable, continue serving the known set from cache; do not emit speculative new-market reports on stale data.
required_dependenciesGamma API ETag-capable listing endpoint, CLOB initial book snapshot, MarketScanner known condition_id set, KillSwitch active flag

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
GAMMA_API_DOWNBlock TCP to gamma-api.polymarket.comAutomatic when API recovers on next poll cycle.
NEW_MARKET_MISSING_RULESCreate a mock market with rules_text=null and require_rule_parse=trueAutomatic when Gamma API returns complete metadata.
KILL_SWITCH_ONSet killswitch.active=trueEmissions resume on next cycle after KillSwitch reset.

20. State & Persistence

Cold-start recovery

On cold start, known set is bootstrapped from MarketScanner's current candidate list.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded async loop
Max in-flight1
Idempotency keypoll_cycle_etag
Per-call timeout (ms)4000
Backpressure strategydrop newest
Locking / mutual exclusionnone

22. Dependencies

Depends on (must run first)

BotWhyContract
disc.marketscannerProvides baseline known condition_id set for new-market diffing.Expects a set of active condition_ids.
risk.kill_switchKillSwitch suppresses emissions but known set updates continue.If active, no ObservationReports emitted.

Emits to (downstream consumers)

BotWhyContract
disc.marketscannerNewMarketWatcher feeds newly detected markets back into MarketScanner for full tradability scoring.ObservationReport includes condition_id, neg_risk, tick_size, and resolution_date.

Sibling bots (same OrderIntent)

External services

ServiceEndpointSLA assumedOn failure
Gamma APIhttps://gamma-api.polymarket.com99.9% / 500ms p99Serve known set from cache; halt new-market emissions.

23. Security Surfaces

Abuse vectors considered

  • Gamma API returning spoofed new market metadata to inject fraudulent condition_ids

Mitigations

  • condition_id validated against 32-byte hex pattern before entering known set
  • require_rule_parse=true ensures minimal metadata quality before emission

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsyes
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesReads Gamma API enableNegRisk flag at listing time to classify new neg-risk markets; initial depth denominated in pUSD.

API surfaces declared

gammaclob_publicinternal

Networks supported

polygon

25. Versioning & Migration

FieldValue
spec2.0.0
implementation0.1.0
schema2
releasedNone
planned_releaseQ4-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
New market in Gamma API response not in known set triggers emission after hold-offcondition_id=0xnew, listing_age=35s, min_listing_age_s=30ObservationReport emitted with listing_age_s=35
Market missing resolution rules text blocked when require_rule_parse=truerules_text=null, require_rule_parse=trueMarket held with INCOMPLETE_MARKET_METADATA; not emitted
ETag 304 response skips processingETag matches previous responseNo processing; no new emissions

Integration Tests

TestExpected result
New market detected and forwarded to MarketScanner candidate list within 2 poll cyclesObservationReport from NewMarketWatcher appears in MarketScanner's next cycle
KillSwitch active suppresses emissions but known set still updatedNo ObservationReports; known set includes new market_id for next cycle

Property Tests

PropertyRequired behaviour
Every emitted market_id has listing_age_s >= min_listing_age_sAlways true
No emission when KillSwitch is activeAlways true

27. Operational Runbook

NewMarketWatcher incidents are typically Gamma API outages or high hold-off backlogs. Bot is read-only; incidents delay new market discovery but do not affect active positions.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
NewMarketWatcherGammaAPIDown
NewMarketWatcherHighPending

Manual overrides

Healthcheck

GET /internal/health/newmarketwatcher → green if Poll cycle completed within 3× poll_interval_s; no Gamma API errors in last 5 minutes.; red if No poll cycle in 3× interval or consecutive Gamma API failures.

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
ETag-based polling correctly identifies zero new markets on 304 responsesUnit test suite100% pass

Promote to Limited live

GateHow measuredThreshold
New market detection latency < 30s from Gamma API listing time over 48hpolytraders_disc_newmarketwatcher_poll_latency_ms p99< 30s

Promote to General live

GateHow measuredThreshold
Zero spurious emissions for markets that were immediately delistedManual review of emitted markets over 7 days0 false emissions

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