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 LayerGovernance6.7 Portfolio Sync

6.7 Portfolio Sync

Governance Governance Service Explain LIVE General live capital · Indirect P7 · Governance & replay pending stub

PortfolioSync keeps the in-memory portfolio state — open positions and open orders — continuously synchronised with on-chain pUSD balances and CLOB open-order state. It reconciles the internal position store against clob_auth open orders and the Polygon on-chain balance every sync_interval_s. For negative-risk markets (NegRiskAdapter), it correctly aggregates multi-outcome positions using the negRisk flag. On discrepancy beyond discrepancy_alert_usd, it emits an alert and, if configured, pauses the affected strategy to prevent trading against a stale view of risk. Emits both a SettlementReport (position deltas) and an OperationsReport (sync health) on every cycle.

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

LayerGovernance  Governance
Bot classGovernance Service
AuthorityExplain
StatusLIVE
ReadinessGeneral live
Runs beforePnLReporter (provides position state), PortfolioGuard (provides current exposure for risk checks)
Runs afterEvery fill event; on configured sync_interval_s; on-demand after significant position change
Applies toAll open positions and open orders across all active markets, including negative-risk multi-outcome positions
Default modegeneral_live
User-visibleSummary only
Developer ownerPolytraders core — Governance pod

2. Purpose

PortfolioSync keeps the in-memory portfolio state — open positions and open orders — continuously synchronised with on-chain pUSD balances and CLOB open-order state. It reconciles the internal position store against clob_auth open orders and the Polygon on-chain balance every sync_interval_s. For negative-risk markets (NegRiskAdapter), it correctly aggregates multi-outcome positions using the negRisk flag. On discrepancy beyond discrepancy_alert_usd, it emits an alert and, if configured, pauses the affected strategy to prevent trading against a stale view of risk. Emits both a SettlementReport (position deltas) and an OperationsReport (sync health) on every cycle.

3. Why This Bot Matters

  • In-memory position state drifts from on-chain reality

    Strategies trade against a stale position view. Risk guards (PortfolioGuard, KillSwitch) see incorrect exposure, potentially allowing positions that exceed risk limits.

  • Negative-risk positions aggregated incorrectly

    Multi-outcome positions in negRisk markets have correlated payoffs. Treating them as independent binary positions overstates available margin and may cause liquidation.

  • Open-order count diverges from CLOB

    Strategies submit orders assuming capacity that no longer exists. This inflates fill expectations and distorts P&L projections.

  • Strategy not paused on drift beyond threshold

    Trading continues against a stale portfolio state. Risk exposure is uncontrolled until the next successful sync cycle.

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
Open orders from CLOB (per wallet, per market)clob_authYesCompare against internal open-order store to detect divergence.
On-chain pUSD balance (Polygon wallet)onchainYesCross-check internal pUSD accounting against actual on-chain balance.
Market metadata (negRisk flag, condition_id, outcome tokens)clob_authYesCorrectly aggregate multi-outcome positions in negRisk markets using NegRiskAdapter.

5. Required Internal Inputs

InputSourceRequired?Use
Internal position store snapshotin-memory stateYesThe current internal view of open positions that must be reconciled against external state.
KillSwitch active flagKillSwitchNoWhen KillSwitch is active, continue syncing but suppress auto-pause-strategy logic.

6. Parameter Guide

ParameterDefaultWarningHardWhat it controls
sync_interval_s30120300How often PortfolioSync reconciles the in-memory state against CLOB and on-chain sources.
discrepancy_alert_usd1050500Threshold in pUSD above which a position discrepancy triggers an alert (and optionally pauses the affected strategy).
auto_pause_strategy_on_driftTrueNoneNoneWhen true, automatically pauses the affected strategy when drift exceeds discrepancy_alert_usd hard threshold.

7. Detailed Parameter Instructions

sync_interval_s

What it means

How often PortfolioSync reconciles the in-memory state against CLOB and on-chain sources.

Default

{ "sync_interval_s": 30 }

Why this default matters

30s gives a 1-minute worst-case drift window. Longer intervals risk strategies trading against significantly stale positions.

Threshold logic

ConditionAction
sync_interval_s <= 30Normal sync cadence
30–120sWARN — drift window increased
> 300sReject — PARAMETER_CHANGE_REQUIRES_APPROVAL

Developer check

if (p.sync_interval_s > p.hard) throw ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')

User-facing English

Your portfolio state is updated regularly to stay in sync with the exchange.

discrepancy_alert_usd

What it means

Threshold in pUSD above which a position discrepancy triggers an alert (and optionally pauses the affected strategy).

Default

{ "discrepancy_alert_usd": 10 }

Why this default matters

10 pUSD catches even small drift from fill-rounding or missed cancellation events. 500 pUSD is a hard cap — drift above this is always critical.

Threshold logic

ConditionAction
drift <= 10 pUSDLog INFO; no alert
10–500 pUSDEmit WARN PORTFOLIO_SYNC_DISCREPANCY
> 500 pUSDEmit page PORTFOLIO_SYNC_CRITICAL_DISCREPANCY; auto-pause strategy

Developer check

if (drift > p.hard) alerting.emit('PORTFOLIO_SYNC_CRITICAL_DISCREPANCY', { drift_pusd: drift })

User-facing English

The system alerts the team if there is any significant mismatch between the internal record and the actual position.

auto_pause_strategy_on_drift

What it means

When true, automatically pauses the affected strategy when drift exceeds discrepancy_alert_usd hard threshold.

Default

{ "auto_pause_strategy_on_drift": true }

Why this default matters

True by default — trading against a stale position view is a safety risk. Auto-pause provides a safe floor without requiring manual intervention.

Threshold logic

ConditionAction
auto_pause_strategy_on_drift=true AND drift > hardPause affected strategy; emit PORTFOLIO_SYNC_STRATEGY_PAUSED
auto_pause_strategy_on_drift=false AND drift > hardEmit page alert only; strategy continues (operator assumes responsibility)

Developer check

if (p.auto_pause_strategy_on_drift && drift > p.discrepancy_alert_usd.hard) pauseStrategy(affected_strategy_id)

User-facing English

If the portfolio state cannot be verified, affected trading strategies are paused automatically as a precaution.

8. Default Configuration

{
  "bot_id": "gov.portfolio_sync",
  "version": "2.0.0",
  "mode": "general_live",
  "defaults": {
    "sync_interval_s": 30,
    "discrepancy_alert_usd": 10,
    "auto_pause_strategy_on_drift": true
  },
  "locked": {
    "sync_interval_s": {
      "max": 300
    },
    "discrepancy_alert_usd": {
      "max": 500
    }
  }
}

9. Implementation Flow

  1. Every sync_interval_s, snapshot the internal in-memory position store (all open positions and open orders).
  2. Fetch open orders from clob_auth for the wallet address; fetch on-chain pUSD balance from Polygon RPC.
  3. For each open position in the internal store: fetch the corresponding CLOB position and compare size, side, and average cost.
  4. For negative-risk markets (negRisk=true): use the NegRiskAdapter aggregation to compute the combined multi-outcome position before comparing.
  5. Compute drift_pusd = abs(internal_position_pusd - clob_position_pusd) for each market.
  6. If drift_pusd > discrepancy_alert_usd: emit WARN PORTFOLIO_SYNC_DISCREPANCY; if drift_pusd > discrepancy_alert_usd.hard, emit page and optionally pause strategy (auto_pause_strategy_on_drift).
  7. Apply deltas to the internal position store: add missing positions, remove closed positions, update sizes.
  8. Emit SettlementReport for each position delta (position added, removed, or updated).
  9. Emit OperationsReport for the full sync cycle: total_positions, synced_count, discrepancy_count, drift_pusd_max, sync_duration_ms.
  10. Retain SettlementReports for 7 years (position deltas are regulatory records). OperationsReports retained 1 year.

10. Reference Implementation

Every sync_interval_s: snapshots internal state, fetches CLOB open orders and on-chain pUSD balance, reconciles per-market positions (with negRisk aggregation), applies deltas, emits SettlementReport per delta and OperationsReport per cycle.

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

// ---- SYNC LOOP ----
FUNCTION runSync():
  sync_start = now()
  internal_snapshot = position_store.snapshot()  // all open positions

  // Fetch external state
  clob_orders = FETCH clob_auth.GET('/orders?wallet=' + wallet_address)
    ON_FAILURE: EMIT OperationsReport(event_type='PORTFOLIO_SYNC_CLOB_UNAVAILABLE'); RETURN
  onchain_balance = FETCH onchain.getBalance(wallet_address, 'pUSD')
    ON_FAILURE: EMIT OperationsReport(event_type='PORTFOLIO_SYNC_RPC_UNAVAILABLE'); RETURN

  // Reconcile per-market
  discrepancy_count = 0; drift_max = 0; deltas = []

  FOR market_id IN union(keys(internal_snapshot), keys(clob_orders)):
    internal_pos = internal_snapshot.get(market_id, ZERO)
    clob_pos = clob_orders.get(market_id, ZERO)

    market_meta = FETCH clob_auth.getMarketByConditionId(market_id)
    IF market_meta.negRisk:
      // Aggregate all outcome tokens via NegRiskAdapter
      clob_pos = negRiskAggregate(clob_orders, market_id)
      internal_pos = negRiskAggregate(internal_snapshot, market_id)

    drift_pusd = abs(internal_pos.size_pusd - clob_pos.size_pusd)
    drift_max = max(drift_max, drift_pusd)

    IF drift_pusd > config.discrepancy_alert_usd:
      discrepancy_count += 1
      alerting.emit('PORTFOLIO_SYNC_DISCREPANCY', { market_id, drift_pusd })

    IF drift_pusd > config.discrepancy_alert_usd.hard:  // 500 pUSD
      alerting.emit('PORTFOLIO_SYNC_CRITICAL_DISCREPANCY', { market_id, drift_pusd })
      IF config.auto_pause_strategy_on_drift:
        pauseStrategy(getStrategyForMarket(market_id))
        EMIT SettlementReport(event_type='PORTFOLIO_SYNC_STRATEGY_PAUSED', ...)

    delta_type = computeDeltaType(internal_pos, clob_pos)
    IF delta_type != 'NO_CHANGE':
      position_store.apply(market_id, clob_pos)
      deltas.append({ market_id, delta_type, drift_pusd, negRisk: market_meta.negRisk })
      EMIT SettlementReport(event_type='POSITION_DELTA', market_id=market_id, ...)

  // Ops report for this cycle
  EMIT OperationsReport({
    event_type:       'SYNC_CYCLE_COMPLETE',
    total_positions:  len(union(internal_snapshot, clob_orders)),
    synced_count:     len(deltas),
    discrepancy_count: discrepancy_count,
    drift_pusd_max:   drift_max,
    onchain_balance:  onchain_balance,
    sync_duration_ms: now() - sync_start
  })

SDK calls used

  • clob_auth.GET('/orders?wallet=<address>')
  • clob_auth.getMarketByConditionId(market_id)
  • onchain.getBalance(wallet_address, 'pUSD')
  • negRiskAggregate(orders, market_id)
  • alerting.emit('PORTFOLIO_SYNC_DISCREPANCY', { market_id, drift_pusd })
  • pauseStrategy(strategy_id)

Complexity: O(M) per sync cycle where M = number of open markets

11. Wire Examples

Input — what arrives on the wire

{
  "label": "CLOB open orders response",
  "source": "clob_auth",
  "payload": {
    "wallet": "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
    "open_orders": [
      {
        "order_id": "ord_00456",
        "market_id": "0x9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
        "side": "BUY",
        "size_pusd": 1250.0,
        "price": 0.6,
        "negRisk": false,
        "timestamp": 1746791900000
      }
    ],
    "onchain_balance_pusd": 52341.0
  }
}

Output — what the bot emits

{
  "label": "SettlementReport — POSITION_DELTA",
  "payload": {
    "report_id": "settlement_portfoliosync_1746792000000",
    "bot_id": "gov.portfolio_sync",
    "event_type": "POSITION_DELTA",
    "market_id": "0x9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
    "delta_type": "UPDATED",
    "internal_size_pusd": 1200.0,
    "clob_size_pusd": 1250.0,
    "drift_pusd": 50.0,
    "negRisk": false,
    "action_taken": "internal_store_updated",
    "synced_at_ms": 1746792000000,
    "report_kind": "SettlementReport",
    "retained_until": "2033-05-09"
  }
}

12. Decision Logic

APPROVE

Not applicable — PortfolioSync does not approve or reject trading decisions.

RESHAPE_REQUIRED

Not applicable.

REJECT

Not applicable as a trading decision. PortfolioSync may pause a strategy if drift exceeds the hard threshold and auto_pause_strategy_on_drift=true.

WARNING_ONLY

Drift below the hard threshold emits a WARN but does not pause any strategy. Stale price or CLOB connectivity issues emit WARN STALE_DATA.

13. Standard Decision Output

This bot returns a SettlementReport object. See SettlementReport schema.

{
  "report_id": "settlement_portfoliosync_1746792000000",
  "bot_id": "gov.portfolio_sync",
  "event_type": "POSITION_DELTA",
  "market_id": "0x9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
  "delta_type": "UPDATED",
  "internal_size_pusd": 1200.0,
  "clob_size_pusd": 1250.0,
  "drift_pusd": 50.0,
  "negRisk": false,
  "action_taken": "internal_store_updated",
  "synced_at_ms": 1746792000000,
  "report_kind": "SettlementReport"
}

14. Reason Codes

CodeSeverityMeaningActionUser-facing message
PORTFOLIO_SYNC_CYCLE_COMPLETEINFOFull sync cycle completed; OperationsReport emitted.No action — routine.
PORTFOLIO_SYNC_DISCREPANCYWARNPosition drift between internal store and CLOB exceeds discrepancy_alert_usd threshold.Emit alert; apply delta to internal store; log drift_pusd.A small difference was found between the internal record and the exchange. It was corrected.
PORTFOLIO_SYNC_CRITICAL_DISCREPANCYWARNPosition drift exceeds the hard threshold (500 pUSD). This indicates a significant state divergence.Emit page alert; optionally pause affected strategy (auto_pause_strategy_on_drift).A significant difference was found. The affected strategy has been paused while the team investigates.
PORTFOLIO_SYNC_STRATEGY_PAUSEDWARNA strategy was automatically paused because position drift exceeded the hard threshold.Notify on-call; do not resume until drift is investigated and resolved.A trading strategy was paused as a precaution while portfolio state is being verified.
PORTFOLIO_SYNC_CLOB_UNAVAILABLEWARNCLOB auth API was unavailable during a sync cycle; no delta applied.Skip cycle; emit alert; retry on next interval.
PORTFOLIO_SYNC_NEGRISK_AGGREGATEINFOA negative-risk multi-outcome market was encountered; NegRiskAdapter aggregation applied.No action — informational.
STALE_DATAWARNCLOB or RPC data is older than sync_interval_s; internal store may be stale.Emit WARN; skip delta application; retry sync on next cycle.Portfolio synchronisation is delayed. The team has been notified.
PARAMETER_CHANGE_REQUIRES_APPROVALHARD_REJECTsync_interval_s exceeds the 300s hard maximum.Reject the config change; emit alert.

15. Metrics & Logs

Metrics emitted

MetricTypeUnitLabelsMeaning
polytraders_gov_portfoliosync_positions_synced_totalcountercountdelta_typeTotal position deltas applied (ADDED, REMOVED, UPDATED) across all sync cycles.
polytraders_gov_portfoliosync_discrepancy_countgaugecountNumber of markets with drift above discrepancy_alert_usd at the last sync cycle.
polytraders_gov_portfoliosync_drift_pusd_maxgaugeusdMaximum per-market drift (pUSD) observed in the last sync cycle.
polytraders_gov_portfoliosync_sync_duration_mshistogrammsWall-clock duration of a full sync cycle.
polytraders_gov_portfoliosync_strategies_paused_totalcountercountstrategy_idTotal strategy pause events triggered by critical position drift.
polytraders_gov_portfoliosync_sync_cycles_totalcountercountstatusTotal sync cycles completed, labelled by status (ok, clob_unavailable, rpc_unavailable).

Alerts

AlertConditionSeverityRunbook
PortfolioSyncCriticalDiscrepancypolytraders_gov_portfoliosync_drift_pusd_max > 500page#runbook-portfoliosync-critical-discrepancy
PortfolioSyncStrategyPausedrate(polytraders_gov_portfoliosync_strategies_paused_total[5m]) > 0page#runbook-portfoliosync-strategy-paused
PortfolioSyncClobUnavailablerate(polytraders_gov_portfoliosync_sync_cycles_total{status='clob_unavailable'}[10m]) > 0warn#runbook-portfoliosync-clob-unavailable
PortfolioSyncNoSyncIn5mrate(polytraders_gov_portfoliosync_sync_cycles_total[5m]) == 0page#runbook-portfoliosync-no-sync

Dashboards

  • Grafana — Governance / PortfolioSync position drift timeline
  • Grafana — Governance / PortfolioSync negRisk position aggregation

16. Developer Reporting

{
  "bot_id": "gov.portfolio_sync",
  "event_type": "SYNC_CYCLE_COMPLETE",
  "total_positions": 12,
  "synced_count": 11,
  "discrepancy_count": 1,
  "drift_pusd_max": 50.0,
  "negRisk_positions": 2,
  "paused_strategies": [],
  "sync_duration_ms": 420,
  "synced_at_ms": 1746792000000
}

17. Plain-English Reporting

SituationUser-facing explanation
Sync cycle completed with no discrepanciesYour portfolio state matches the exchange records exactly.
Minor discrepancy detected and correctedA small difference was found between the internal record and the exchange. It was corrected automatically.
Strategy paused due to portfolio driftA significant difference was found between the internal position record and the exchange. The affected strategy has been paused while the team investigates.
Multi-outcome market position in portfolioSome positions are in multi-outcome markets. These are tracked and valued using the correct multi-outcome formula.

18. Failure-Mode Block

main_failure_modeCLOB auth API or Polygon RPC is unavailable during a sync cycle. The internal position store cannot be reconciled; strategies may trade against stale state.
false_positive_riskTransient CLOB API latency causes a fill-in-progress to appear as a position discrepancy, triggering a spurious WARN that resolves on the next sync cycle.
false_negative_riskA negRisk position is not correctly identified (negRisk flag missing on market metadata), leading to under-aggregation of multi-outcome exposure and a silent undercount of position size.
safe_fallbackIf CLOB API or RPC is unavailable, skip the sync cycle, emit WARN, and retry on the next scheduled interval. Do not apply partial deltas. Emit PORTFOLIO_SYNC_CLOB_UNAVAILABLE. If drift exceeds hard threshold from a stale state, emit the page alert regardless.
required_dependenciesCLOB auth API (open orders per wallet), Polygon on-chain RPC (pUSD balance), Internal in-memory position store, Market metadata API (negRisk flag)

19. Failure-Injection Recipes

ScenarioHow to injectExpected behaviourRecovery
CLOB_API_UNAVAILABLEBlock TCP to clob.polymarket.com during a sync cyclePORTFOLIO_SYNC_CLOB_UNAVAILABLE emitted; no delta applied; internal state unchanged; retry on next intervalCLOB reconnects; next cycle performs full sync.
CRITICAL_DRIFTManually edit the in-memory position store to show 600 pUSD more than CLOBPORTFOLIO_SYNC_CRITICAL_DISCREPANCY page alert; affected strategy paused (auto_pause_strategy_on_drift=true)Investigate root cause; resume strategy via manual override after verification.
NEGRISK_MISCONFIGUREDSet negRisk=false on a multi-outcome market; submit 3 outcome token positionsPositions reconciled as independent binary positions; drift may be flagged if NegRisk aggregation would give different totalUpdate market metadata with correct negRisk flag; re-sync.
ONCHAIN_RPC_FAILUREBlock Polygon RPC calls during syncPORTFOLIO_SYNC_RPC_UNAVAILABLE in OperationsReport; on-chain balance check skipped; position deltas still applied from CLOB dataRPC reconnects; on-chain balance check resumes on next cycle.
SYNC_LOOP_STALLIntroduce 10s artificial delay in CLOB fetch (beyond timeout_ms=5000)CLOB fetch times out; PORTFOLIO_SYNC_CLOB_UNAVAILABLE emitted; cycle skippedCLOB latency normalises; next cycle completes within timeout.

20. State & Persistence

Cold-start recovery

On restart, position_store is rebuilt from a full CLOB sync on startup. First sync cycle restores complete state before any trading is allowed.

21. Concurrency & Idempotency

AspectSpecification
Execution modelsingle-threaded event loop (sync cycle) + async CLOB/RPC fetch
Max in-flight50
Idempotency keymarket_id + synced_at_ms
Per-call timeout (ms)5000
Backpressure strategyif CLOB fetch exceeds timeout, skip cycle and emit PORTFOLIO_SYNC_CLOB_UNAVAILABLE
Locking / mutual exclusionper-market RwLock on position_store entries

22. Dependencies

Depends on (must run first)

BotWhyContract
internal.clob_authOpen orders and market metadata are fetched from clob_auth on each sync cycle.
internal.onchain_rpcpUSD on-chain balance cross-check requires Polygon RPC access.

Emits to (downstream consumers)

Sibling bots (same OrderIntent)

BotWhyContract
gov.pnl_reporterPortfolioSync provides the position state that PnLReporter uses for unrealised P&L computation.

Used by (auto-aggregated)

6.3

External services

ServiceEndpointSLA assumedOn failure
Polymarket CLOB v2 (open orders, market metadata)99.95% / 200ms p99 (Polymarket-published)
Polygon on-chain RPC (pUSD balance)99.9% / 500ms p99

23. Security Surfaces

Abuse vectors considered

  • Raising discrepancy_alert_usd hard threshold to suppress critical drift alerts
  • Disabling auto_pause_strategy_on_drift to allow trading against stale state
  • Manipulating the CLOB open-order response to show smaller positions than actual

Mitigations

  • discrepancy_alert_usd has a hard maximum of 500 pUSD enforced at config load
  • sync_interval_s has a hard maximum of 300s enforced at config load
  • On-chain balance provides an independent cross-check that cannot be manipulated via CLOB API
  • PortfolioSync never signs orders or holds private keys — read-only access to CLOB and RPC

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 on Polygon
NotesPortfolioSync correctly handles negative-risk multi-outcome positions via NegRiskAdapter aggregation. All position sizes and pUSD balances are denominated in pUSD (not USDC.e). Open orders fetched via clob_auth V2 endpoint.

API surfaces declared

clob_authonchaininternal

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 denomination, V1 CLOB open-order endpoint)v2 (pUSD denomination, clob_auth V2 endpoint)CLOB V2 cutoverUpdated all position sizes from USDC.e to pUSD denomination. Switched to clob_auth V2 open-order endpoint (py-clob-client-v2). Added NegRiskAdapter aggregation path for negative-risk multi-outcome markets. Updated SettlementReport schema to include negRisk flag per delta.

26. Acceptance Tests

Unit Tests

TestSetupExpected result
Discrepancy alert fires above discrepancy_alert_usdinternal_size=1000 pUSD, clob_size=1060 pUSD (drift=60)PORTFOLIO_SYNC_DISCREPANCY emitted; internal store updated
Strategy paused at hard thresholddrift_pusd=600 > hard=500; auto_pause_strategy_on_drift=truePORTFOLIO_SYNC_CRITICAL_DISCREPANCY alert; strategy paused; PORTFOLIO_SYNC_STRATEGY_PAUSED emitted
negRisk position aggregated correctly3 outcome tokens in same negRisk market; sizes [400, 300, 200] pUSDCombined position = NegRiskAdapter aggregate; not sum of three binary positions
sync_interval_s above hard maximum rejectedsync_interval_s=400ConfigError PARAMETER_CHANGE_REQUIRES_APPROVAL
No delta applied on partial CLOB API responseCLOB returns partial response (missing 3 positions)PORTFOLIO_SYNC_CLOB_UNAVAILABLE emitted; no partial delta applied; internal store unchanged

Integration Tests

TestExpected result
End-to-end sync cycle: internal store matches CLOB after reconciliationSettlementReport per delta + OperationsReport per cycle emitted; Postgres updated
On-chain balance cross-check passes after fillpUSD balance within reconcile tolerance; SettlementReport onchain_reconciled=true

Property Tests

PropertyRequired behaviour
Internal position store is never more stale than sync_interval_s + sync_durationAlways true — partial sync is never applied
Every position delta emits exactly one SettlementReportAlways true

27. Operational Runbook

PortfolioSync incidents fall into: critical position drift (strategy auto-paused), CLOB/RPC unavailability (sync skipped), or negRisk misconfiguration (silent exposure undercount). Drift above 500 pUSD is always P1.

On-call actions

AlertFirst stepDiagnosisMitigationEscalate to
PortfolioSyncCriticalDiscrepancyIdentify which market(s) have critical drift. Check CLOB and on-chain balance directly. Do NOT resume strategy until root cause is confirmed.Governance pod lead immediately
PortfolioSyncStrategyPausedReview PORTFOLIO_SYNC_CRITICAL_DISCREPANCY logs. Confirm which strategy was paused and why.Governance pod lead + strategy owner
PortfolioSyncClobUnavailableCheck Polymarket CLOB API status. If unavailable, monitor sync recovery. Strategies are not paused but are trading against stale state.SRE on-call if CLOB unavailability exceeds 5 minutes
PortfolioSyncNoSyncIn5mCheck PortfolioSync process status and internal bus connectivity.Governance pod lead

Manual overrides

  • polytraders gov portfolio force-sync — Trigger an immediate full sync cycle outside the normal interval.
  • polytraders gov portfolio resume-strategy --strategy-id <id> --reviewed-by <operator> — Resume an auto-paused strategy after drift root cause has been investigated and resolved.

Healthcheck

Endpoint: /internal/health/portfolio-sync | Green: Last sync cycle completed within 2x sync_interval_s; CLOB reachable; discrepancy_count = 0; no strategies paused. | Red: No sync in 2x sync_interval_s; CLOB unavailable > 5 min; critical drift detected; strategies paused.

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: drift detection, negRisk aggregation, auto-pause thresholdCI test run100% pass

Promote to Limited live

GateHow measuredThreshold
Sync cycle latency p99 < 5s for 50 open positionspolytraders_gov_portfoliosync_sync_duration_ms histogram< 5s
Critical drift auto-pause fires correctly in failure injectionFailure injection testPass

Promote to General live

GateHow measuredThreshold
End-to-end: negRisk position correctly aggregated and reconciled with CLOBE2E test with staging CLOBPass
7-year retention applied to SettlementReport position deltasPostgres retention policy auditPass

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