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 LayerIntelligence4.6 SocialSentiment

4.6 SocialSentiment

Intelligence Signal Service Read-only PLANNED Spec started capital · Indirect P2 · Data normalisation pending stub

Lightweight, secondary social-sentiment input — never primary trigger.

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

LayerIntelligence  Intelligence
Bot classSignal Service
AuthorityRead-only
StatusPLANNED
ReadinessSpec started
Runs before
Runs after
Applies to
Default modeshadow_only
User-visibleAdvanced details only
Developer ownerPolytraders core

2. Purpose

Lightweight, secondary social-sentiment input — never primary trigger.

3. Why This Bot Matters

  • Strategy uses sentiment as a primary trigger

    Social sentiment is noisy, gameable, and easily manipulated by bot networks. Treated as a primary signal it produces churn and drawdowns; treated as a secondary input — 'is the prior consistent with public mood?' — it adds a small but real filter.

  • No freshness or provenance on sentiment numbers

    A sentiment score with no source string and no timestamp cannot be audited or replayed. Every emission must carry both, or downstream consumers have no way to reason about the data they are reading.

  • Sentiment signal duplicated across teams

    Strategy authors otherwise build one-off sentiment fetchers each. Centralising the read gives a single rate-limited source, a single cache, and a single place to swap providers when one degrades.

4. Required Polymarket Inputs

InputSourceRequired?Use
Social post feed per market slugws_sportsYesPrimary sentiment scoring data source.
Market slug and condition_id mappingdataYesResolve market slug to condition_id for ObservationReport output.

5. Required Internal Inputs

InputSourceRequired?Use
KillSwitch active flagKillSwitchYesSuppress all sentiment emissions when KillSwitch is active.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
min_post_count1051Minimum number of posts in the sampling window required to emit a sentiment signal.
poll_interval_s60300600Seconds between social feed polls per market slug.

7. Detailed Parameter Instructions

min_post_count

What it means

Minimum number of posts in the sampling window required to emit a sentiment signal.

Default

{ "min_post_count": 10 }

Why this default matters

10 posts provides a statistically meaningful sample; fewer posts produce unreliable scores.

Threshold logic

ConditionAction
posts >= 10Normal — emit ObservationReport
5–9 postsWARN — low sample; emit with SOCIALSENTIMENT_LOW_SAMPLE warning
< 1 postReject — skip emission entirely

Developer check

if (p.min_post_count < p.hard) return;

User-facing English

Sentiment signals require a minimum number of recent posts to be reliable.

poll_interval_s

What it means

Seconds between social feed polls per market slug.

Default

{ "poll_interval_s": 60 }

Why this default matters

60 s provides near-real-time sentiment without overloading the social feed API.

Threshold logic

ConditionAction
interval <= 60 sNormal
60–300 sWARN — reduced signal freshness
> 600 sReject — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

if (p.poll_interval_s > p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL');

User-facing English

Social feed is polled regularly to ensure sentiment signals are fresh.

8. Default Configuration

{
  "bot_id": "intel.socialsentiment",
  "version": "0.1.0",
  "mode": "planned",
  "defaults": {
    "min_post_count": 10,
    "poll_interval_s": 60,
    "sample_rate": 10
  },
  "locked": {
    "min_post_count": {
      "min": 1
    },
    "poll_interval_s": {
      "max": 600
    }
  }
}

9. Implementation Flow

— not yet authored —

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 pollSentiment(market_slug, window_s):
  // 0. KillSwitch check
  IF FETCH internal.killswitch.status == ACTIVE:
    RETURN

  // 1. Fetch recent social posts for market slug
  posts = FETCH ws_sports.social_feed(slug=market_slug, since=now()-window_s)
  IF len(posts) < min_post_count:
    EMIT WARN 'SOCIALSENTIMENT_LOW_SAMPLE'
    RETURN

  // 2. Score each post
  scores = [scoreSentiment(p) for p in posts]
  agg = mean(scores)

  // 3. Staleness check
  IF now() - posts[0].ts > staleness_threshold_s:
    EMIT WARN 'STALE_DATA'
    RETURN

  // 4. Sample gate (high-volume)
  IF sample_counter % sample_rate != 0:
    sample_counter++
    RETURN

  // 5. Emit ObservationReport
  EMIT ObservationReport {
    report_id: gen_id(),
    kind: 'ObservationReport',
    market_slug: market_slug,
    sentiment_score: agg,
    post_count: len(posts),
    window_s: window_s,
    emitted_at_ms: now_ms()
  }
  sample_counter = 0

SDK calls used

  • ws_sports.social_feed(slug, since)
  • data.GET('/markets?slug=<slug>')
  • internal.killswitch.status

Complexity: O(P) per poll where P = posts in window

11. Wire Examples

Input — what arrives on the wire

{
  "label": "Social feed poll for a market slug",
  "source": "ws_sports",
  "payload": {
    "market_slug": "btc-price-above-100k-dec-2026",
    "window_s": 3600,
    "post_count": 42,
    "timestamp_ms": 1746703000000
  }
}

Output — what the bot emits

{
  "label": "ObservationReport — positive sentiment signal",
  "payload": {
    "report_id": "rep_ss_btc100k_1746703000000",
    "trace_id": "trc_0xbeef0102030405060708",
    "bot_id": "intel.socialsentiment",
    "kind": "ObservationReport",
    "market_slug": "btc-price-above-100k-dec-2026",
    "sentiment_score": 0.62,
    "post_count": 42,
    "window_s": 3600,
    "emitted_at_ms": 1746703010000
  }
}

12. Decision Logic

APPROVE

— not yet authored —

RESHAPE_REQUIRED

— not yet authored —

REJECT

— not yet authored —

WARNING_ONLY

— not yet authored —

13. Standard Decision Output

This bot returns a RiskVote object. See RiskVote schema.

{
  "report_id": "rep_ss_btc100k_1746703000000",
  "trace_id": "trc_0xbeef0102030405060708",
  "bot_id": "intel.socialsentiment",
  "kind": "ObservationReport",
  "market_slug": "btc-price-above-100k-dec-2026",
  "sentiment_score": 0.62,
  "post_count": 42,
  "window_s": 3600,
  "warnings": [],
  "emitted_at_ms": 1746703010000
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
SOCIALSENTIMENT_LOW_SAMPLEWARNFewer posts than min_post_count in the sampling window; sentiment score unreliable.Skip emission for this poll cycle.Not enough recent discussion to generate a reliable sentiment signal.
STALE_DATAWARNMost recent post is older than staleness_threshold_s; feed may be lagging.Suppress emission; alert on-call if condition persists > 5 min.
KILL_SWITCH_ACTIVEHARD_REJECTKillSwitch is active; all sentiment emissions suppressed.Continue polling but suppress all ObservationReport emissions.Sentiment signals paused while trading is suspended system-wide.
SOCIALSENTIMENT_FEED_ERRORWARNSocial feed returned a non-200 status or empty payload unexpectedly.Log WARN; skip this poll cycle; retry on next interval.
PARAMETER_CHANGE_REQUIRES_APPROVALHARD_REJECTA parameter violates a locked bound (e.g. sample_rate < 1).Reject config change; do not apply.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_intel_socialsentiment_observations_emitted_totalcountercountmarket_slugObservationReports emitted per market slug.
polytraders_intel_socialsentiment_low_sample_skips_totalcountercountPoll cycles skipped due to insufficient post count.
polytraders_intel_socialsentiment_feed_latency_mshistogrammsLatency of social feed fetch per poll cycle.
polytraders_intel_socialsentiment_sentiment_scoregaugescoremarket_slugLatest aggregated sentiment score per market slug (-1 to +1).

Alerts

AlertConditionSeverityRunbook
SocialSentimentFeedStalerate(polytraders_intel_socialsentiment_observations_emitted_total[10m]) == 0warn#runbook-socialsentiment-feed-stale
SocialSentimentHighSkipRaterate(polytraders_intel_socialsentiment_low_sample_skips_total[10m]) > 5warn#runbook-socialsentiment-high-skip-rate

Dashboards

  • Grafana — Intelligence / SocialSentiment emission rate per market
  • Grafana — Intelligence / SocialSentiment score distribution

16. Developer Reporting

{
  "bot_id": "intel.socialsentiment",
  "market_slug": "btc-price-above-100k-dec-2026",
  "post_count": 42,
  "sentiment_score": 0.62,
  "low_sample_skips": 0,
  "killswitch_active": false,
  "emitted_at_ms": 1746703010000
}

17. Plain-English Reporting

SituationUser-facing explanation
Strategy adjusted position size based on positive sentiment signalRecent social discussion about this market shows a positive trend. The system used this as one factor in its assessment, alongside price and liquidity data.
No sentiment signal emitted for a marketThere was not enough recent discussion about this market to generate a reliable sentiment signal. Signals require a minimum volume of posts.

18. Failure-Mode Block

main_failure_modeSocial feed API outage during high-activity periods causes SocialSentiment to miss sentiment spikes, leaving downstream strategies without fresh signal data.
false_positive_riskA coordinated posting burst by a small group of accounts inflates the sentiment score for a market without reflecting genuine market opinion.
false_negative_riskGenuine sentiment shift occurs outside the polling window or below the min_post_count threshold, causing the signal to be suppressed.
safe_fallbackIf feed returns an error or zero posts for > 5 min, emit STALE_DATA WARN and suppress further emissions until feed recovers.
required_dependenciesws_sports social feed, KillSwitch active flag, Data API for slug resolution

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
FEED_OUTAGEReturn 503 from ws_sports social feed for 5 minAutomatic on feed recovery; next successful poll emits normally
LOW_SAMPLE_FLOODReturn 1 post per poll for all slugs for 30 minAutomatic when post volume normalises
KILL_SWITCH_ONSet killswitch.active=true during active pollingEmissions resume automatically on KillSwitch reset

20. State & Persistence

Cold-start recovery

On cold start, state re-initialised from next poll cycle; no historical backfill required.

21. Concurrency & Idempotency

AspectSpecification
Execution modelasync poll loop per market slug
Max in-flight20
Idempotency keymarket_slug + window_start_ms
Per-call timeout (ms)8000
Backpressure strategydrop-after-buffer — skip slugs that have not drained within 2x poll interval
Locking / mutual exclusionRedis SETNX on slug + window_start_ms to prevent duplicate emissions

22. Dependencies

Depends on (must run first)

BotWhyContract
risk.kill_switchSuppress emissions when KillSwitch is active.

Emits to (downstream consumers)

BotWhyContract
strat.sentiment_aware_strategies

External services

ServiceEndpointSLA assumedOn failure
Polymarket social/sports feedws_sports99.5% / 1 s p99

23. Security Surfaces

Abuse vectors considered

  • Adversary floods social feed with synthetic posts to artificially inflate sentiment score
  • Feed provider returns manipulated scores for targeted market slugs

Mitigations

  • min_post_count gate prevents low-signal spikes from small coordinated posting bursts
  • Scores are informational only — downstream strategies independently validate before acting
  • sample_rate throttle limits emission frequency to prevent signal flooding

24. Polymarket V2 Compatibility

AspectValue
CLOB versionv2
Collateral assetpUSD
EIP-712 Exchange domain version2
Aware of builderCode fieldno
Aware of negative-risk marketsno
Multi-chain readyno
SDK usedpy-clob-client-v2
Settlement contractCTFExchangeV2
NotesRead-only signal service; references Polymarket Gamma market slugs and pUSD volumes. No order signing.

API surfaces declared

ws_sportsdatainternal

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
High-volume market emits ObservationReport after poll42 posts, sentiment_score=0.62, poll_interval_s=60ObservationReport emitted with sentiment_score=0.62, post_count=42
Low sample below hard floor skips emission0 posts in window, min_post_count hard=1No ObservationReport; low_sample_skips counter incremented
KillSwitch suppresses emissionkillswitch.active=true; 50 posts availableNo ObservationReport; KILL_SWITCH_ACTIVE logged

Integration Tests

TestExpected result
Full lifecycle: feed poll → sentiment score → ObservationReport consumed by strategyStrategy receives ObservationReport with correct sentiment_score and market_slug
Feed outage: STALE_DATA emitted after 5 min of feed errorsSTALE_DATA WARN logged; no ObservationReports during outage

Property Tests

PropertyRequired behaviour
SocialSentiment never submits or signs ordersAlways true
No ObservationReport emitted when KillSwitch is activeAlways true

27. Operational Runbook

SocialSentiment incidents are most commonly feed outages or low-volume conditions. Neither blocks trading. Monitor for extended stale periods.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
SocialSentimentFeedStaleCheck ws_sports feed health endpoint and last_emitted_at_ms. Verify feed credentials are valid.Intelligence pod lead if feed is down > 15 min
SocialSentimentHighSkipRateCheck post volume for affected market slugs. If genuinely low, reduce min_post_count via config.Intelligence pod lead if all slugs affected simultaneously

Manual overrides

  • force_low_sample_bypass — Set config.min_post_count=1 temporarily in staging to verify emission pipeline — Debugging emission pipeline in staging environment only

Healthcheck

Endpoint: /internal/health/socialsentiment | Green: Last emission < 2x poll_interval_s ago AND Redis reachable AND feed returning 200 | Red: No emission for > 10 min on any active slug OR Redis unreachable

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 for low-sample gate, KillSwitch suppression, and sample rateCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Feed latency p99 < 2 s over 24 h on stagingpolytraders_intel_socialsentiment_feed_latency_ms histogramp99 < 2000 ms

Promote to General live

GateHow measuredThreshold
Zero spurious emissions when KillSwitch active during 7-day soakIntegration test log audit0 emissions during KillSwitch active period

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