Documentation Index
Fetch the complete documentation index at: https://mintlify.com/deniszidbaev-cmyk/polyclaw-trading/llms.txt
Use this file to discover all available pages before exploring further.
Every pipeline stage communicates exclusively through versioned JSON files persisted under runtime/. No stage may read from memory that a previous stage wrote, and no stage may skip validation of its upstream input. This section describes the envelope format, every validation rule, freshness constants, atomic write mechanics, and the complete stage-to-file mapping.
JSON envelope
Every runtime file that crosses a stage boundary uses this envelope:
{
"schema_version": 1,
"stage": "signal",
"generated_at": "2026-01-01T12:00:00Z",
"data": {}
}
| Field | Type | Description |
|---|
schema_version | integer | Must be exactly 1. Any other value raises ContractError. |
stage | string | The name of the stage that produced this file (e.g., "fetch", "signal"). |
generated_at | string | UTC ISO 8601 timestamp with Z suffix, rounded to the nearest second. |
data | object | Stage-specific payload; validated by each stage’s own validator function. |
Validation rules (fail-closed)
The contract validator in src/polyclaw_engine/contracts.py applies the following checks in order. Any failure raises ContractError and prevents the stage from continuing.
| Rule | What triggers it |
|---|
| Invalid JSON | File cannot be parsed as JSON. |
| Duplicate keys | The same key appears more than once in a JSON object. |
| Non-finite numbers | Any NaN, Infinity, or -Infinity value anywhere in data. |
Wrong schema_version | Value is anything other than 1. |
| Missing envelope fields | Any of schema_version, stage, generated_at, or data is absent. |
Wrong stage | stage does not match the value expected by the reading stage. |
| Malformed timestamp | generated_at is not a valid ISO 8601 string with a timezone offset. |
| Future timestamp | generated_at is more than 60 seconds in the future. |
| Staleness | Age of generated_at exceeds the reading stage’s max_age_sec threshold. |
| Bad price bounds | Any Polymarket probability price is outside [0.0, 1.0]. |
| Bad OHLC geometry | low > high, or open/close outside [low, high]. |
| Unsupported timeframes | timeframes object does not contain exactly "5m" and "15m". |
| Non-monotonic timestamps | Candle or price-history timestamps are not strictly increasing. |
| Non-string object keys | Any JSON object contains a non-string key. |
| Unsupported types | data tree contains a value type other than bool, None, str, int, float, list, or dict. |
Validation is fail-closed by design. There is no coercion, repair, or fallback for malformed, stale, or semantically invalid data. The pipeline halts and exits with code 2 on the first ContractError.
Freshness thresholds
Each reading stage enforces a maximum age on its upstream contract file. These constants are configurable via environment variables but the defaults are:
| Constant | Env var | Default | Applied by |
|---|
MAX_DECISION_AGE_SEC | MAX_DECISION_AGE_SEC | 600 s | validate, execute, report reading decisions |
MAX_MARKET_AGE_SEC | MAX_MARKET_AGE_SEC | 600 s | candles reading market snapshot |
MAX_CANDLE_AGE_SEC | MAX_CANDLE_AGE_SEC | 1200 s | analyze reading candles; signal reading analysis |
MAX_BANKROLL_AGE_SEC | MAX_BANKROLL_AGE_SEC | 600 s | validate reading bankroll state |
Atomic writes
Every runtime file is written atomically to prevent partial reads by the next stage. The sequence is:
- Create a unique temporary sibling in the same directory, named
.{filename}.{pid}.{uuid4hex}.tmp, opened with exclusive creation (x mode).
- Write the serialized JSON, call
handle.flush(), then os.fsync(handle.fileno()).
- Call
os.replace(temporary, destination) — atomic on POSIX; atomic on Windows (NTFS).
- In a
finally block, attempt to unlink the temporary file if os.replace did not complete.
JSON is serialized with indent=2, sort_keys=True, ensure_ascii=False, and allow_nan=False.
Stage-to-file mapping
The table below shows every runtime file that each stage reads and writes. All paths are relative to RUNTIME_DIR (default: runtime/).
| Stage | Reads | Writes |
|---|
fetch | (none; optionally reads --fixture for offline use) | market_snapshot.json |
candles | market_snapshot.json (stage: fetch, max age: MAX_MARKET_AGE_SEC) | candles.json |
analyze | candles.json (stage: candles, max age: MAX_CANDLE_AGE_SEC) | market_analysis.json |
signal | market_analysis.json (stage: analyze, max age: MAX_CANDLE_AGE_SEC) | decisions.json, manual_review.json |
bankroll | trade_registry.json (optional) | bankroll_state.json, risk_state.json, trade_registry.json (if initialized from default) |
validate | decisions.json (stage: signal, max age: MAX_DECISION_AGE_SEC), bankroll_state.json (stage: bankroll, max age: MAX_BANKROLL_AGE_SEC), risk_state.json (stage: bankroll, max age: MAX_BANKROLL_AGE_SEC), trade_registry.json (stage: bankroll or execute) | decisions.json (updated, stage: validate), manual_review.json |
execute | decisions.json (stage: validate, max age: MAX_DECISION_AGE_SEC), bankroll_state.json (stage: bankroll), risk_state.json (stage: bankroll), trade_registry.json (stage: bankroll or execute) | executions/YYYY-MM-DD.json (append), trade_registry.json, execution_status.json, manual_review.json (on failure) |
report | decisions.json (stage: validate, max age: MAX_DECISION_AGE_SEC), bankroll_state.json (stage: bankroll), risk_state.json (stage: bankroll), execution_status.json (stage: execute) | runtime_snapshot.json (terminal output also printed to stdout) |
Special runtime files
Beyond the main pipeline files, one additional file carries non-executable HOLD records across stages:
runtime/manual_review.json
Written (and overwritten) by the signal, validate, and execute stages. Contains all HOLD decisions that are not executable. Each item includes:
{
"schema_version": 1,
"stage": "signal",
"generated_at": "2026-01-01T12:00:00Z",
"data": {
"items": [
{
"market_id": "...",
"question": "Will BTC exceed $100k?",
"signal": "HOLD",
"candidate_signal": "YES",
"confidence": 0.63,
"blocking_reasons": ["candle quality below automatic execution threshold"],
"suggested_action": "Wait for clean EMA, RSI, candle, quality, and timeframe alignment, then rerun the cycle.",
"executable": false
}
]
}
}
All records in this file must have signal = "HOLD" and executable = false. Any other values cause ContractError.
manual_review.json is also overwritten by the validate stage (after risk sizing) and, when execution failures or blocked decisions occur, by the execute stage. The stage field in the envelope will reflect the most recent writer (signal, validate, or execute).
Data payload schemas
Each stage’s data object is validated by a dedicated validator in contracts.py. Key per-stage requirements are:
market_snapshot.json (data.markets) — each market must have market_id (unique, non-empty), question, end_date (ISO timestamp), boolean state flags (active, closed, resolved, accepting_orders), yes_price/no_price in [0, 1], non-negative liquidity_usd/volume_24h_usd, non-empty yes_token_id/no_token_id, a valid semantic_mapping object, and a non-empty price_history list with strictly increasing integer timestamps.
candles.json (data.markets) — each market must have exactly "5m" and "15m" keys in timeframes. Each candle within a timeframe must have timestamp (UTC bucket-aligned to the interval), valid OHLC geometry, a positive integer sample_count, and a boolean complete flag. Candle timestamps must be strictly monotonic.
market_analysis.json (data.markets) — each market’s timeframe entry must have a direction of "bullish", "bearish", or "neutral", a metrics object, a patterns object, and a candle_quality object with a score in [0, 1]. Each market must also have a market_quality object with a score in [0, 1].
decisions.json (data.decisions) — each decision must have a unique decision_id, unique market_id, signal of "YES", "NO", or "HOLD", confidence in [0, 1], a risk_validation object with a boolean approved, and a blocking_reasons list of non-empty strings. For executable (YES/NO) decisions, entry_price must be strictly inside (0, 1), and approved decisions must also have positive amount_usd, max_entry_price, and valid planned_exits with stop_price < entry_price < take_profit_price.
bankroll_state.json — must have equity_usd, working_bankroll_usd, reserve_usd, free_balance_usd, open_exposure_usd, drawdown_pct, risk_mode ("normal", "reduced", or "critical"), and risk_multiplier. All numeric fields must be finite and non-negative.
risk_state.json — must have day_key, limits, remaining, risk_mode, and risk_multiplier. Both limits and remaining must be objects with non-negative numeric values; remaining.trades must be an integer.
The execute stage re-reads fresh bankroll, risk, registry, expiry, semantic mapping, pattern, quality, and cooldown state immediately before launching the PolyClaw subprocess. A stale or missing upstream file at execution time causes a ContractError abort, not a silent skip.