1. Bot Identity
| Layer | Strategy Strategy |
|---|
| Bot class | Alpha Strategy |
|---|
| Authority | Trade |
|---|
| Status | PLANNED |
|---|
| Readiness | Spec started |
|---|
| Runs before | Risk guardrail pipeline |
|---|
| Runs after | Observation bus / internal analytics |
|---|
| Applies to | Polymarket binary markets where attention Z-score >= min_attention_zscore and price drift from base rate >= min_drift_bps, indicating narrative crowding above statistical base rate |
|---|
| Default mode | shadow_only |
|---|
| User-visible | Advanced details only |
|---|
| Developer owner | Polytraders core — Strategy pod |
|---|
2. Purpose
NarrativeCrowdingFade detects Polymarket binary markets where social attention and narrative momentum have pushed prices above the statistical base rate. When attention Z-score and price drift signal crowding, the bot fades the move — taking the opposite position to capture reversion toward the base rate.
3. Why This Bot Matters
News is genuinely paradigm-shifting
If the crowded narrative reflects real new information rather than sentiment overshoot, the fade position loses as the market price correctly adjusts to the new fundamental.
Stale input data
Acting on stale signals for NarrativeCrowdingFade produces trades based on outdated market conditions, generating adverse fills.
Emitting OrderIntents while KillSwitch is active bypasses risk controls.
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_attention_zscore | 2.0 | 1.5 | 1.0 | Minimum Z-score of attention (volume, social mentions) relative to market history before the bot considers a fade trade. |
| min_drift_bps | 200 | 100 | 50 | Minimum price drift in bps from the base rate required before the bot fades the move. |
| max_position_per_event | 400 | 600 | 800 | Maximum pUSD per event for narrative fade positions. |
| cool_off_after_news | 300 | 120 | 0 | Seconds to pause after a major news hit before re-evaluating the fade thesis. |
7. Detailed Parameter Instructions
min_attention_zscore
What it means
Minimum Z-score of attention (volume, social mentions) relative to market history before the bot considers a fade trade.
Default
{ "min_attention_zscore": 2.0 }
Why this default matters
Z-score >= 2.0 indicates statistically significant crowding, not normal attention.
Threshold logic
| Condition | Action |
|---|
| >= 2.0 | Allow fade evaluation |
| 1.5–2.0 | WARN NCF_WEAK_CROWDING; halve size |
| < 1.0 | SKIP NCF_ATTENTION_BELOW_FLOOR |
Developer check
if attention_zscore < params.hard: return skip('NCF_ATTENTION_BELOW_FLOOR')
User-facing English
The attention signal was not strong enough to indicate crowding.
min_drift_bps
What it means
Minimum price drift in bps from the base rate required before the bot fades the move.
Default
{ "min_drift_bps": 200 }
Why this default matters
200 bps ensures the market has meaningfully overshot the base rate before fading.
Threshold logic
| Condition | Action |
|---|
| >= 200 bps | EMIT fade IOC |
| 100–200 bps | WARN NCF_DRIFT_MARGINAL; halve size |
| < 50 bps | SKIP NCF_NO_DRIFT |
Developer check
if drift_bps < params.hard: return skip('NCF_NO_DRIFT')
User-facing English
The price drift was too small to justify a fade trade.
max_position_per_event
What it means
Maximum pUSD per event for narrative fade positions.
Default
{ "max_position_per_event": 400 }
Why this default matters
400 pUSD limits single-event narrative exposure.
Threshold logic
| Condition | Action |
|---|
| <= 400 pUSD | Normal sizing |
| > 800 pUSD | Reject config |
Developer check
if params.max_position_per_event > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')
User-facing English
Position capped at per-event maximum.
cool_off_after_news
What it means
Seconds to pause after a major news hit before re-evaluating the fade thesis.
Default
{ "cool_off_after_news": 300 }
Why this default matters
300s cool-off allows initial price discovery before the fade re-enters.
Threshold logic
| Condition | Action |
|---|
| >= 300s | Standard cool-off |
| < 120s | WARN NCF_SHORT_COOLOFF |
Developer check
if in_cooloff(market_id): return skip('NCF_COOLOFF_ACTIVE')
User-facing English
A brief pause is in effect after a major news event.
8. Default Configuration
{
"bot_id": "strat.narrativecrowdingfade",
"version": "0.1.0",
"mode": "shadow_only",
"defaults": {
"min_attention_zscore": 2.0,
"min_drift_bps": 200,
"max_position_per_event": 400,
"cool_off_after_news": 300
},
"locked": {
"min_attention_zscore": {
"min": 1.0
},
"min_drift_bps": {
"min": 50
},
"max_position_per_event": {
"min": 800
},
"cool_off_after_news": {
"min": 0
}
}
}
9. Implementation Flow
- Check KillSwitch; if active, emit no OrderIntents.
- FETCH NarrativeCrowdingFade analytics signal from internal engine.
- IF signal below hard floor: SKIP, emit sampled DecisionReport NCF_NO_EDGE.
- FETCH clob_public market status; skip if closed or resolved.
- FETCH ws_market book; compute current mid and available depth.
- IF signal < warning threshold: WARN NCF_MARGINAL; reduce size 50%.
- Compute order size = min(max_size_param, available_depth).
- EMIT IOC OrderIntent with builder code.
- EMIT DecisionReport with intent_emitted=true, reason=NCF_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 onSignalUpdate(market_id, signal):
ks = FETCH internal.killswitch.status
IF ks.active: RETURN
// Hard floor gate
IF signal.score < params.min_attention_zscore_hard:
IF random() < 0.01:
EMIT DecisionReport(intent_emitted=false, reason='NCF_NO_EDGE')
RETURN
mkt = FETCH clob_public.GET('/markets/' + market_id)
IF mkt.closed OR mkt.resolved: RETURN
// Warning threshold check
sizeMultiplier = 0.5 IF signal.score < params.min_attention_zscore_warn ELSE 1.0
IF sizeMultiplier < 1.0: WARN('NCF_MARGINAL')
// Book snapshot
book = FETCH ws_market.book(market_id)
mid = (book.best_bid + book.best_ask) / 2
depth = FETCH clob_public.depth(market_id)
// Size computation
orderSize = toPusdUnits(min(params.min_drift_bps * sizeMultiplier, depth.available))
EMIT OrderIntent(market=market_id, outcome='YES', side='buy', price=mid,
size_pUSD=orderSize, tif='IOC', builder=internalBuilderCode)
EMIT DecisionReport(intent_emitted=true, signal_score=signal.score,
reason='NCF_TRADE')
SDK calls used
ws_market.subscribe('book', [market_id])fetchClobPublic('/markets/' + market_id)internal.analyticsEngine.signal(market_id)buildOrderTypedData(orderParams, {name:'CTFExchange', version:'2', chainId:137})internal.builder_code
Complexity: O(1) per signal update per market
11. Wire Examples
Input — what arrives on the wire
NarrativeCrowdingFade analytics signal — internal (analytics engine)
{
"market_id": "0xnarrativ000000000000000000000000000000000000000000000000000000000001",
"signal_score": "0.75",
"received_at_ms": 1746790800000
}
Output — what the bot emits
OrderIntent — NarrativeCrowdingFade IOC buy YES
{
"intent_id": "oi_01HNCF0000001A",
"market_id": "0xnarrativ000000000000000000000000000000000000000000000000000000000001",
"outcome": "YES",
"side": "buy",
"price": "0.540",
"size_pUSD": "200.00",
"tif": "IOC",
"builder": {
"code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"fee_bps": 25
},
"decision": {
"signal_score": 0.75,
"reasons": [
"NCF_TRADE"
]
}
}
12. Decision Logic
APPROVE
All gates passed, KillSwitch inactive, market open. Emit IOC OrderIntent.
RESHAPE_REQUIRED
Not applicable — reshaping handled by downstream Risk guardrail.
REJECT
Signal below hard floor; stale data; market closed; KillSwitch active.
WARNING_ONLY
Signal in warning zone triggers 50% size reduction.
13. Standard Decision Output
This bot returns a OrderIntent object. See OrderIntent schema.
{
"intent_id": "oi_01HNCF0000001A",
"trace_id": "tr_01HNCF000TR001",
"market_id": "0xnarrativ000000000000000000000000000000000000000000000000000000000001",
"outcome": "YES",
"side": "buy",
"price": "0.540",
"size_pUSD": "200.00",
"tif": "IOC",
"post_only": false,
"builder": {
"code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
"fee_bps": 25
},
"negrisk_aware": false,
"decision": {
"signal_score": 0.75,
"reasons": [
"NCF_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 |
|---|
NCF_TRADE | INFO | All gates passed. IOC OrderIntent emitted for NarrativeCrowdingFade. | Emit IOC OrderIntent. | A NarrativeCrowdingFade trade was placed. |
NCF_MARGINAL | WARN | Edge is within the warning threshold; size reduced 50%. | Emit at 50% size; log warning. | A small edge was found; a reduced-size NarrativeCrowdingFade trade was placed. |
NCF_NO_EDGE | INFO | Edge below hard floor. Skipping. | Skip; emit sampled DecisionReport. | The edge was too small to justify a trade. |
NCF_HARD_REJECT | HARD_REJECT | A critical gate condition blocked the trade (stale data, kill switch, or hard parameter breach). | Skip; no OrderIntent. | A safety condition blocked the trade. |
KILL_SWITCH_ACTIVE | HARD_REJECT | Global kill switch is active. | Skip all markets; no OrderIntents emitted. | Trading is currently paused. |
15. Metrics & Logs
Metrics emitted
| Metric | Type | Unit | Labels | Meaning |
|---|
polytraders_strat_narrativecrowdingfade_decisions_total | counter | count | verdict, reason_code | Total evaluation cycles by verdict and reason code. |
polytraders_strat_narrativecrowdingfade_signal_score | histogram | score | | Distribution of analytics signal scores at evaluation. |
polytraders_strat_narrativecrowdingfade_intents_emitted_total | counter | count | outcome | Total IOC OrderIntents emitted. |
polytraders_strat_narrativecrowdingfade_eval_latency_ms | histogram | milliseconds | | Latency from signal receipt to OrderIntent emit. |
Alerts
| Alert | Condition | Severity | Runbook |
|---|
NarrativeCrowdingFadeStaleFeed | rate(polytraders_strat_narrativecrowdingfade_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1 | warn | #runbook-narrativecrowdingfade-stale |
NarrativeCrowdingFadeKillSwitch | rate(polytraders_strat_narrativecrowdingfade_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0 | page | #runbook-killswitch |
NarrativeCrowdingFadeNoEdge | rate(polytraders_strat_narrativecrowdingfade_decisions_total{verdict='skip',reason_code='NCF_NO_EDGE'}[10m]) / rate(polytraders_strat_narrativecrowdingfade_decisions_total[10m]) > 0.95 | warn | #runbook-narrativecrowdingfade-edge |
16. Developer Reporting
{
"bot_id": "strat.narrativecrowdingfade",
"market_id": "0xnarrativ000000000000000000000000000000000000000000000000000000000001",
"signal_score": 0.75,
"intent_emitted": true,
"reason": "NCF_TRADE",
"emitted_at_ms": 1746790800000
}
17. Plain-English Reporting
| Situation | User-facing explanation |
|---|
| NarrativeCrowdingFade trade placed | The NarrativeCrowdingFade strategy detected a suitable opportunity and placed a trade. |
| Edge too small — no trade | The signal was below the minimum threshold. No trade was placed. |
| Safety gate active — no trade | A safety condition (stale data, kill switch, or parameter limit) blocked the trade. |
18. Failure-Mode Block
| main_failure_mode | If the crowded narrative reflects real new information rather than sentiment overshoot, the fade position loses as the market price correctly adjusts to the new fundamental. |
|---|
| false_positive_risk | Signal mis-fires when market conditions change rapidly, producing trades that quickly move against the NarrativeCrowdingFade thesis. |
|---|
| false_negative_risk | Hard floor set too conservatively misses genuine opportunities. |
|---|
| safe_fallback | If ws_market feed stale or analytics signal unavailable, skip without emitting any OrderIntent. |
|---|
| required_dependencies | ws_market, clob_public, internal NarrativeCrowdingFade analytics engine, KillSwitch, internal builder code |
|---|
19. Failure-Injection Recipes
| Scenario | How to inject | Expected behaviour | Recovery |
|---|
SIGNAL_UNAVAILABLE | Cut internal analytics engine connection | | Automatic when engine reconnects. |
HARD_FLOOR_BREACH | Inject signal below hard floor | | Automatic on next valid signal. |
KILL_SWITCH_ON | Set killswitch.active=true | | Automatic on manual KillSwitch reset. |
20. State & Persistence
Cold-start recovery
On cold start, signals rebuilt from next analytics engine poll.
21. Concurrency & Idempotency
| Aspect | Specification |
|---|
| Execution model | actor-per-market |
| Max in-flight | 25 |
| Idempotency key | intent_id |
| Per-call timeout (ms) | 300 |
| Backpressure strategy | drop oldest signal per market_id when queue > 2 |
| Locking / mutual exclusion | per-market_id mutex for signal 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 | |
| Internal NarrativeCrowdingFade analytics engine | | internal SLA | |
23. Security Surfaces
Abuse vectors considered
- Signal injection to produce false NarrativeCrowdingFade trades
- Order sizing parameter manipulation to exceed position limits
Mitigations
- NarrativeCrowdingFade analytics signals sourced from authenticated internal engine only
- Hard limits on position size enforced before OrderIntent emission
- Builder code injected from secure internal config
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_marketinternal
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 signal=0.75 and all gates pass | standard config | IOC OrderIntent; reason=NCF_TRADE |
| Skip when signal below hard floor | signal=below_hard | No OrderIntent; sampled reason=NCF_NO_EDGE |
| Skip when KillSwitch active | killswitch.active=true | No OrderIntents emitted |
Integration Tests
| Test | Expected result |
|---|
| Full cycle: signal → computation → IOC OrderIntent on Polygon testnet | Order has builder.code, no feeRateBps, EIP-712 domain v2 |
Property Tests
| Property | Required behaviour |
|---|
| Bot never emits OrderIntent when KillSwitch is active | Always true |
| feeRateBps never present on any signed OrderIntent | Always true |
27. Operational Runbook
NarrativeCrowdingFade incidents are typically stale analytics feeds or kill-switch activations. Hard-floor skips are normal.
On-call actions
| Alert | First step | Diagnosis | Mitigation | Escalate to |
|---|
NarrativeCrowdingFadeStaleFeed | | | | |
NarrativeCrowdingFadeKillSwitch | | | | |
NarrativeCrowdingFadeNoEdge | | | | |
Manual overrides
Healthcheck
GET /internal/health/narrativecrowdingfade -> 200 if Analytics engine active; signal age < 60s; KillSwitch inactive.. Red: Analytics engine 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 |