Detects snapshot poisoning by positive re-derivation (pre-inception, raw-balance leak, adaptive sanity bound); quarantines (never deletes); tiered fail-closed health (HEALTHY commits, DEGRADED report-only, CRITICAL holds) with age-adaptive MIN_CLEAN; self-heals to HEALTHY via natural clean-snapshot refill. Backs the 2026-07-02 audit finding (36/37 snapshots poisoned, OSS steering live params off a phantom drawdown). Impl: scripts/metric_integrity.py, scripts/migrate_p010_metric_integrity.py, compute_oss.py clean-filter+tier, apply_adaptations.py tier gate. Tests: tests/test_p010_metric_integrity.py (10).
66 KiB
kind, name, description
| kind | name | description |
|---|---|---|
| responsibility | alpaca-trading-system-health | Standing responsibility that monitors and optimizes the Alpaca multi-track trading system. Two mandates: (1) Prime Directive — valid consensus-backed trades SHALL reach the broker, with external dependency failures as the only acceptable blocks. (2) Return Maximization — the system actively adapts positions, tickers, and allocation to market regime to maximize risk-adjusted returns, measured by a composite Optimization Success Score (Calmar Ratio + Profit Factor + Rolling Alpha). All adaptations are reversible and bounded. |
Goal
Prime Directive: Valid trades are not blocked by the Alpaca trading system.
Every block that prevents a Sophie-consensus execute decision from reaching
the broker is either self-healed within one cycle or escalated with a clear root
cause. External dependency failures are classified, never confused with internal
issues, and not counted against system health.
Return Optimization: The primary objective is to match the S&P 500 benchmark net of costs while bounding peak-to-trough drawdown to ≤25%. Risk-adjusted outperformance is a secondary goal, pursued only when market-regime detection confirms the system has an edge. The system measures its Optimization Success Score (OSS) on a rolling 30-day window, adapts positions, tickers, and allocation to market regime within bounded, reversible guardrails, and never chases returns the market does not offer. The target OSS is ≥ 0.5 (benchmark-matching institutional-grade); ≥ 1.0 is aspirational in favorable regimes. The floor is 0.3 (below which adaptation and hedging are required).
Requires
kojo-audit: a current structured audit of open orders, signal freshness per track, DNS reachability, and reconcile drift — sourced from the Kojo auditor cron job (a4476b04abab, runs 07:00 and 20:00 daily)sophie-consensus: the latest consensus decisions from Sophie (execute / watch / skip per ticker) — sourced from the consensus table in signals.db written by Sophie+Ross pipeline jobs (43ad4780620a, 8d027ddff088)execution-results: the latest Ross trade execution outcomes — sourced from broker_orders in trade_journal.db
Maintains
The current system health truth. Postcondition: every block is classified as
internal (actionable, must self-heal) or external (documented, escalated).
Postcondition: no internal block persists across two consecutive audit cycles
without escalation. Postcondition: the system never reports "0 issues" while
valid trades are blocked.
system-health
Overall health: healthy (all gates pass, no blocks), degraded (non-fatal
blocks found, some trades may be affected), or blocked (trades are being
prevented from reaching the broker). Material: the status and a summary of
every active block.
active-blocks
Every currently active block preventing trade execution. Each block carries:
source (gate/component), ticker (affected symbol), reason (machine-readable
classification), classified (internal | external), first_seen (timestamp),
last_seen (timestamp), self_heal_attempts (count). Material: the block set
and each block's classified/reason fields.
stale-data
Every data source that is stale beyond its freshness threshold. Each entry
carries: track (eddie/maggie/frank/maya/janet/parker), last_signal
(timestamp), hours_since, last_run_at (from track_status), threshold_hours,
blocking (whether the staleness is currently preventing trades). Material:
the track name, hours_since, and blocking fields.
failure-log
Rolling log of the last 50 failure events. Each event carries: timestamp,
component, classification (internal | external), resolved (boolean),
resolution (self-healed | escalated | transient-cleared), duration_s
(seconds until resolution). Immaterial: raw stack traces, full HTTP response
bodies.
return-optimization
The composite Optimization Success Score and its components, computed on a rolling 30-day window from trade_journal.db. OSS weights: Calmar Ratio 40%, Profit Factor 25%, Rolling Alpha over SPY 15%, Win Rate 10%, Adaptation Win Rate 10% (per P-010).
oss: the current OSS (0.0–5.0+)oss_target: 1.0 (institutional-grade),oss_floor: 0.5 (adaptation required)calmar: rolling CAGR ÷ max drawdown (absolute)profit_factor: gross profit ÷ gross loss (all closed trades, 30-day rolling)rolling_alpha: portfolio excess return over SPY benchmark (annualized %)portfolio_beta: rolling beta to SPY — measures how much of our return is just tracking the marketwin_rate_adaptations: % of rotation/hedge adjustments that improved 7-day forward returnlast_computed: timestamp of most recent OSS calculationtrend: improving | flat | degrading — direction of OSS over last 3 readings
Material: all numeric fields. Immaterial: individual trade P&L lines, raw price data.
Postcondition: OSS is computed at least once per trading day. Postcondition: when OSS falls below 0.5, at least one adaptation rule fires within 2 cycles. Postcondition: when portfolio_beta > 0.6 AND market_regime is bearish, a hedge or rotation rule fires within 1 cycle.
market-regime
The current detected market regime, refreshed each cycle from yfinance (VIX, SPX 20-day SMA, Nasdaq 20-day SMA, BTC trend). Regimes are:
-
bull: VIX < 18 AND SPX above 20-SMA AND Nasdaq above 20-SMA -
neutral: VIX 18–25 OR mixed signals (SPX and Nasdaq diverge) -
bear: VIX > 25 OR (SPX below 20-SMA AND Nasdaq below 20-SMA) -
crisis: VIX > 35 AND SPX drawdown > 10% from 30-day high -
vix_close: latest VIX close -
spx_vs_sma: SPX position relative to 20-SMA (% above/below) -
ndx_vs_sma: Nasdaq position relative to 20-SMA (% above/below) -
btc_trend_5d: BTC 5-day % change -
regime: bull | neutral | bear | crisis -
regime_changed_at: timestamp of last regime transition
Material: regime, vix_close, spx_vs_sma. Immaterial: raw tick data from yfinance.
parameter-state
The current values of all tunable system parameters, their bounds, the last adjustment reason, and the OSS delta since adjustment. This is the authoritative record — parameter changes are always written here before taking effect.
max_concurrent_positions: {value, min: 5, max: 15, adjusted_at, reason}confidence_floor: {value, min: 0.65, max: 0.85, adjusted_at, reason}stop_loss_pct: {value, min: -0.03, max: -0.07, adjusted_at, reason}profit_take_pct: {value, min: 0.03, max: 0.10, adjusted_at, reason}stale_exit_days: {value, min: 3, max: 7, adjusted_at, reason}position_size_pct: {value, min: 0.05, max: 0.15, adjusted_at, reason}parker_hedge_cap_pct: {value, min: 0.10, max: 0.30, adjusted_at, reason}oss_target: {value: 0.5, min: 0.3, max: 1.5, adjusted_at, reason}oss_floor: {value: 0.3, min: 0.1, max: 0.7, adjusted_at, reason}oss_weights: {calmar: 0.40, pf: 0.25, alpha: 0.15, win_rate: 0.10, adapt_wr: 0.10, min_per_weight: 0.05, max_per_weight: 0.50, adjusted_at, reason} — weights must sum to 1.0hedge_regime_buckets: {moderate: {min_bearish: 0.20, max_positions: 1, size_pct: 0.01}, heavy: {min_bearish: 0.40, max_positions: 2, size_pct: 0.015}, extreme: {min_bearish: 0.70, max_positions: 3, size_pct: 0.02}, adjusted_at, reason}track_frequencies: per-track {eddie, maggie, frank, maya, janet, parker} each: {runs_per_day, min: 1, max: 6, adjusted_at, reason}pipeline_frequency: {runs_per_day, min: 2, max: 6, adjusted_at, reason}oss_at_adjustment: OSS value when the last parameter change was madeoss_since_adjustment: current OSS vs oss_at_adjustment delta
Material: all fields. Postcondition: every parameter change is logged with a reason, the OSS at time of change, and the change is reversible.
performance-summary
The current trading performance across all venues (Alpaca equity + Robinhood Crypto), sourced from performance_snapshots and trade_journal.db. This is the user-facing P&L record — human-readable, benchmarked, and attributed.
total_equity: current total equity across all venues (cash + positions)cash: idle cash not in positionsrealized_pnl: sum of realized_trades.realized_pnl over the trailing 30 daysunrealized_pnl: sum of positions.unrealized_pnl across all open positionswin_rate: winning_trades / total_trades from performance_snapshotsprofit_factor: gross_profit / |gross_loss|total_return_pct: (end_equity - start_equity) / start_equity × 100max_drawdown_pct: largest peak-to-trough decline in equitymax_drawdown_usd: dollar value of the worst drawdownsharpe_ratio: risk-adjusted return (from performance_snapshots)benchmark_return_pct: SPY return over the same period (for alpha calculation)alpha_pct: total_return_pct - benchmark_return_pct (our edge over passive)per_track_pnl: per-track attribution — for each track that produced trades: {track: str, trades: int, win_rate: float, total_pnl: float, avg_hold_days: float}per_venue_pnl: {alpaca_equity: {trades, pnl}, robinhood_crypto: {trades, pnl}}daily_pnl_7d: array of last 7 days: [{date, pnl, cumulative_equity}]largest_winner: {symbol, pnl, opened_at, closed_at, track}largest_loser: {symbol, pnl, opened_at, closed_at, track}open_positions: [{symbol, qty, entry_price, current_price, unrealized_pnl, days_held}]
Material: all numeric and attribution fields. Immaterial: individual order IDs, raw JSON. Postcondition: all closed trades in the period are counted. Postcondition: open-position prices are current within 5 minutes.
Continuity
- self-driven: re-check every 5 minutes
- input-driven: wake immediately when Kojo audit detects anomalies (open orders, stale tracks, DNS failures, reconcile drift)
- input-driven: wake when Sophie+Ross pipeline completes (new consensus decisions or execution results may reveal blocks)
Strategies
- When a block is internal and matches a known remediation rule: self-heal immediately, verify, log, and continue
- When a block is internal but no remediation rule matches: escalate to user with full diagnostic context (which gate, what state triggered it, what data was used)
- When a block is external: document the dependency, record the failure, and do NOT attempt to fix (external dependencies are out of scope)
- When a block persists across 3 self-heal attempts: escalate — do not loop
- During market-closed hours: position-exit rules and circuit-breaker resets still run (lifecycle hooks are NOT gated on market-open)
- Asset-class isolation: equity position limits never block crypto trades and vice versa (per-platform capital pools)
- Exit orders always bypass circuit breakers and position-count gates (exits are self-curing — blocking them creates infinite loops)
- When classification is ambiguous (could be internal or external): classify as internal and self-heal — it is cheaper to attempt a fix and fail than to leave a valid trade blocked
- Return-maximization strategies:
- When market regime is
bearAND portfolio_beta > 0.6: positions are market-tracking into a downtrend — rotate from high-beta equities to defensive/uncorrelated (inverse ETFs via Parker, consumer staples, utilities, or cash). Target: reduce portfolio_beta to ≤ 0.3 within 2 cycles - When market regime is
bullAND portfolio_beta < 0.3: the system is under-allocated to the uptrend — increase exposure by allowing higher position counts (up to MAX_CONCURRENT_POSITIONS) and larger allocation per position (up to 15% of equity) - When market regime is
crisis: all discretionary buys are paused regardless of consensus — only Parker hedge signals and manual exits execute. Existing long positions are evaluated for stop-loss exits at -5% - When OSS is
degrading(>2 consecutive drops): investigate which component is dragging (usually max drawdown widening or win rate dropping). If max drawdown is the culprit, tighten stop-losses. If win rate is dropping, reduce new position entry velocity — raise confidence floor by 0.05 - Every adaptation produces a before/after record: {regime, beta_before,
action, beta_after, 7d_forward_return}. These records feed back into
win_rate_adaptations— adaptations that repeatedly fail to improve returns are retired - Cash as a position: when no track produces signals with confidence ≥ 0.75 for 3 consecutive cycles, default to cash. Cash allocation is tracked as a deliberate optimization choice, not a system failure
- Discovery continuity: ticker discovery is not gated on OSS performance. The system SHALL run discovery lenses every cycle regardless of whether current positions are profitable. A system that only looks for new tickers when things are going well is blind exactly when it needs new sources of return most. Discovery is cheap (screening); admission is careful (gates).
- Rotation constraints: no single adaptation rule may allocate > 30% of available equity without corroborating signals from at least 2 tracks. Parker hedge allocation is capped at 25% of equity regardless of regime
Remediation Rules
Rule 1: Stale Circuit Breaker
Detect: An active circuit breaker in trade_journal.db with triggered_at
older than BREAKER_AUTO_RESET_HOURS (4h) AND today's daily loss below
BREAKER_MIN_LOSS_PCT (2%) of equity.
Fix: Deactivate the breaker by setting active = 0 in circuit_breaker table
and log the reset with reason "Auto-reset: {hours}h idle, daily loss below
threshold".
Verify: Re-query circuit_breaker — active = 0. Re-compute daily P&L —
still below threshold.
Escalate: After 2 reset attempts within 24h, the breaker is re-tripping
from live losses. Escalate to user with the full breaker history.
Rule 2: Position-Limit Block Preventing Exits
Detect: Gate 2 (position-count) is blocking an order, AND the order
direction is bearish/sell/short (an exit).
Fix: This is a false-positive — exits reduce position count and are
self-curing. The gate code already handles this (is_exit check in gates.py:50),
so this rule catches edge cases where direction is misclassified. Re-evaluate
direction from the consensus decision's direction field and retry with
corrected classification.
Verify: Exit order submitted successfully.
Escalate: After 2 failures — escalate with the exact consensus decision
JSON and gate evaluation trace.
Rule 3: Poll-Fill Timeout (Order Actually Filled)
Detect: Ross executor reports error_poll_timeout on a submitted order,
but _verify_order_after_timeout() was either not called or returned None.
Fix: Re-query the broker for the order by client_order_id. If filled:
journal the fill and mark as executed. If still pending: poll once more
(30s). If genuinely unfilled: cancel and re-submit.
Verify: Order status is terminal (filled/partially_filled/canceled).
Escalate: After 2 re-submit attempts — escalate with the order ID and
full poll history.
Rule 4: Stale Track Data Blocking Consensus
Detect: A track's last_signal timestamp is beyond its freshness threshold
(Eddie 72h, Maggie 168h, Frank 168h, Maya 24h, Janet 12h, Parker 24h) AND
track_status shows last_run_at is ALSO stale (no recent run).
Fix: Trigger the track's cron job immediately via cronjob(action='run', job_id=<track_job_id>). Wait for completion (max 120s). Re-read signals.db.
Verify: New signals exist OR track_status shows a fresh run (even if 0
signals — no_data is valid).
Escalate: If track run fails or times out — escalate. If track ran but
produced 0 signals for 3 consecutive cycles — escalate (possible source
degradation).
Rule 5: Orphaned Consensus (Execute Decisions Never Traded)
Detect: Gap analysis (sources.reconcile in auditor, or direct query:
consensus rows with action='execute' that lack a matching row in trades
table, AND created_at > 1h ago).
Fix: Ross orchestrator already self-heals orphaned consensus (lines 166-217
of orchestrator.py). Verify the self-healing ran: check that orphaned consensus
IDs now have trade rows. If not: manually invoke mark_ticker_unavailable for
tickers that genuinely cannot trade (SPAC, delisted, etc.) and re-scan.
Verify: Zero orphaned execute decisions older than 1h.
Escalate: After 2 cycles of failed self-healing — escalate with orphaned
consensus IDs.
Rule 6: DNS / Data-Source Unreachable
Detect: sources.dns shows fc.yahoo.com unreachable (yfinance failing)
OR any track's data source returns connection errors for > 2 consecutive runs.
Fix: External dependency — do NOT attempt to fix. Verify the failure is
genuinely external (network-layer, not auth/permission). Log and classify
as external.
Verify: Re-test DNS resolution and connectivity. If recovered: was
transient. If still failed: confirmed external.
Escalate: After 3 consecutive failures — escalate with the hostname,
error string, and affected tracks.
Rule 7: Multi-Venue Asset Misrouting
Detect: A crypto ticker (BTC-USD, ETH-USD, SOL-USD) triggers an Alpaca
equity error ("BUY needs notional, SELL needs qty" or "symbol not found")
OR an equity ticker hits Robinhood Crypto.
Fix: Verify infer_asset_class(ticker) in config.py returned the correct
class. If the ticker is missing from CRYPTO_SYMBOLS: add it (after verifying
on docs.robinhood.com/crypto/trading). If the code path passed wrong
asset_class to execute_trade: fix the call site.
Verify: Re-run the trade with corrected asset class — order routes to
correct adapter.
Escalate: After 1 failure — always escalate (misrouting means money
on wrong venue). Include the ticker, the inferred class, and the expected class.
Rule 8: Market-Regime Position Mismatch
Detect: market_regime is bear or crisis AND portfolio_beta > 0.6.
Positions are tracking the market downward — holding long equities in a
broad sell-off.
Principle — Regime-graded hedging intensity: Hedging intensity scales with regime severity. As VIX rises from 18→35+, the system progressively shifts defense posture: cash accumulation → defensive sector rotation → 1x inverse ETFs → leveraged inverse ETFs. The contract defines the direction of travel; the system discovers the optimal allocation percentages through OSS feedback and per-strategy attribution (P-004).
Fix: (a) If Parker already has active buy signals for inverse ETFs
(SH, SQQQ, SPXU, BITI): execute them at up to 25% equity allocation.
1x inverse (SH, PSQ) is preferred over leveraged (-2x/-3x) unless
VIX > 30 and regime is crisis — per Rule 18 decay risk.
(b) If no Parker signals: scan existing long positions for those with the
highest beta correlation to SPY and generate exit signals (stale-exit or
stop-loss at -5%). Rotate freed capital to defensive sectors (consumer
staples, healthcare, utilities) or cash — these provide positive
expected return in down markets with zero leverage decay risk.
(c) If VIX > 30 and beta > 0.8: go to 50% cash — sell
the 2 largest high-beta positions. Cash at 4-5% risk-free yield is a
competitive position, not a retreat.
Verify: Re-compute portfolio_beta after adaptation — target ≤ 0.3.
Re-compute OSS on next cycle — improvement expected.
Escalate: If beta remains > 0.5 after 2 adaptations in bear regime —
escalate with full portfolio composition and regime data.
Rule 9: Regime-Inappropriate Sector Exposure
Detect: Market regime is bear AND portfolio holds > 2 positions in
high-beta sectors (tech, consumer discretionary, small-cap, crypto) that
are each down > 3% since entry.
Fix: Generate exit signals for the 2 worst performers. Rotate capital
into either (a) Parker inverse ETFs if bearish signal exists, or (b) cash
if no hedge signal. Do NOT buy new long positions until regime normalizes.
Verify: High-beta exposure reduced by ≥ 40%. Exit orders confirmed filled.
Escalate: If exits fill but capital sits idle > 3 cycles without a
viable defensive entry — escalate with regime timeline.
Rule 10: OSS Degradation — Drawdown-Driven
Detect: OSS is degrading AND the Calmar Ratio component dropped > 20%
from its 30-day peak. This means max drawdown is widening faster than returns
are growing — the system is taking pain without reward.
Fix: Reduce position sizes by 25% across all new entries (scale factor
0.75x). Tighten stop-loss to -3% from -5% for new positions. Do NOT modify
position-sizes for existing positions (only new entries). Pause the track
with the worst 7-day return if its win rate < 40%.
Verify: Re-compute max drawdown after 3 cycles — trajectory should
flatten. Paused track's win rate trend should stabilize.
Escalate: If max drawdown continues to widen after 5 cycles — escalate
with full drawdown waterfall chart and per-track P&L attribution.
Rule 11: Hedge Inefficiency — Hedges Costing More Than Saving
Detect: Parker hedge positions are open AND profit_factor on hedge-only
trades (filtered by Parker track ID) is < 0.8 (hedges are net losers) over
the last 30 days AND market regime is NOT crisis.
Fix: Hedge drag is real — inverse ETFs have daily decay. Reduce Parker
allocation to 10% of equity (from 25% cap). Only hold SH (1x inverse, no
decay) unless VIX > 30 (then SQQQ/SPXU are warranted). Generate exit signals
for leveraged hedges (SQQQ, SPXU) that are > 3 days old.
Verify: Hedge P&L improves within 5 cycles (profit_factor > 0.9).
Escalate: If hedge drag persists — escalate with per-ticker hedge P&L
and hold durations.
Rule 12: Continuous Ticker Discovery
Principle — Ticker discovery is continuous, not conditional. Alpha factors decay over time (Hubble 2026, AlphaCrafter 2026). The system SHALL maintain an active pipeline of candidate tickers across multiple discovery lenses regardless of current OSS or portfolio state. Discovery happens in the background; admission to the portfolio is gated separately.
Discovery lenses (at least 3 must be active per cycle):
| Lens | Source | What it finds |
|---|---|---|
| Fundamental | Frank (financials/screeners) | Undervalued/quality stocks |
| Insider/Event | Maggie (insider, SEC filings) | Asymmetric-info opportunities |
| Technical/Momentum | Price/volume scans | Breakouts, trend initiations, RSI divergences |
| Inverse/Hedge | Parker (inverse ETFs) | Down-market defense candidates |
| Regime-Appropriate | Market regime × sector data | Sector rotation into regime-favored sectors |
| Correlation-Seeking | Correlation matrix vs existing positions | Uncorrelated diversifiers |
Detect (admission gate): A new candidate ticker is discovered with: (a) signal confidence ≥ 0.70 from its source lens, (b) Pearson correlation ≤ 0.4 to ALL existing open positions over the trailing 30-day window, (c) position-count headroom exists (current positions < MAX_POSITIONS after accounting for pending orders), AND (d) the ticker passes all standard gates (Gate 1-7).
Fix: Admit the candidate to the consensus pipeline with a
discovery_lens tag. For the first 2 cycles: use 50% normal position
size (probation sizing). After 2 cycles with positive unrealized P&L:
graduate to full position size.
Verify: At least 3 discovery lenses produced candidates this cycle. At least 1 candidate admitted OR clearly documented "no viable candidate passes correlation gate." Per-source yield tracked (see P-005).
Escalate: If zero candidates pass correlation gate for 5 consecutive cycles — the system's existing positions are too correlated with each other or the discovery universe is too narrow. Escalate with full correlation matrix and per-lens candidate count.
Rule 13: Adaptive Parameter Tuning
Detect: OSS trend is degrading OR improving over 5+ cycles AND a
specific OSS component (Calmar, Profit Factor, Alpha) is the dominant driver.
Fix — Confidence Floor: When OSS is improving (bull confirmation): lower
confidence_floor by 0.05 (min 0.65) to capture more entries. When OSS is
degrading (entries are losing): raise confidence_floor by 0.05 (max 0.85)
to filter out low-quality signals.
Fix — Position Size: When Calmar is the degraded component (drawdown
widening): reduce position_size_pct by 0.02 (min 0.05). When Calmar is
improving and regime is bull: increase position_size_pct by 0.02 (max 0.15).
Fix — Stop-Loss: When Profit Factor < 0.8 (losers are too large):
tighten stop_loss_pct to -0.03. When Profit Factor > 2.0 (winners dominate):
relax stop_loss_pct to -0.07.
Fix — Profit-Take: When bull regime and Win Rate > 60%: increase
profit_take_pct to 0.08 (let winners run). When bear regime or Win Rate
< 40%: decrease profit_take_pct to 0.04 (take profits faster).
Fix — Max Positions: When OSS > 1.2 and regime is bull and cash > 30%:
increase max_concurrent_positions by 2 (max 15). When OSS < 0.5:
decrease by 2 (min 5).
Verify: Re-compute OSS after 3 cycles — the adjusted component should
show improvement. If not: revert the parameter to its prior value.
Escalate: If any parameter change fails to improve its target component
within 3 cycles — auto-unwind and escalate with before/after data.
Rule 14: Dynamic Cron Frequency Adjustment
Detect: Signal-producing tracks (tracks that generated ≥3 signals in
their last 3 runs) are running below their maximum frequency AND market
regime is bull OR bear (not neutral) — the market is moving and
opportunities exist. Conversely: tracks producing 0 signals for 8+
consecutive runs in a neutral regime are wasting compute.
Fix — Increase frequency: For signal-producing tracks in directional
markets, increase runs_per_day by 1 (max 6). Also increase
pipeline_frequency by 1 (max 6) to process the additional signals.
Fix — Decrease frequency: For silent tracks in neutral markets, decrease
runs_per_day to 1 (min). This preserves API call budget for active tracks.
Implementation: Update the track's cron job schedule via
cronjob(action='update', job_id=<track_job_id>, schedule=<new_cron_expr>).
Do NOT pause or remove jobs — only adjust their schedule.
Verify: Track runs at new frequency within 1 day. Signal count per
day increases (for frequency-up) or API call count decreases (for
frequency-down) without signal quality degradation.
Escalate: If increased frequency produces 0 additional signals after 3
cycles — revert to prior frequency (wasted compute). If decreased frequency
causes a track to miss a regime-relevant signal — escalate with the missed
signal details.
Rule 15: Breaker Cycling — Block/Reset Loop
Detect: The same breaker type (position-limit, loss-limit) re-triggers within 30 minutes of a Rule 1 auto-reset, AND no exit orders filled between the reset and the re-trigger. This indicates the breaker is mechanically correct but structurally insufficient — exits are not happening fast enough to sustain the reset.
Fix: Do NOT wait for another 4h cycle. Classify as INTERNAL structural deadlock. Generate exit orders for the 2 highest-beta positions (sourced from portfolio_beta.py) that meet relaxed exit criteria: held ≥1 day (instead of 3) OR profit ≥1% (instead of 5%). If no positions meet even relaxed criteria, go to 50% cash — sell the 2 highest-beta positions at market regardless of profit. The breaker is self-correct; the exits are not.
Verify: At least 2 exit orders submitted with status accepted or
filled. Re-compute position count — < 10/10.
Escalate: After 1 cycle — breaker cycling is a structural deadlock. The contract has exhausted its self-resolution surface. Escalate with: (a) the breaker history (IDs and timestamps of the last 3), (b) all current positions with age + beta + unrealized P&L, (c) which exit conditions each position failed, and (d) the specific relaxed criteria applied.
Rule 16: Data Pipeline Integrity — Stale Local Tables
Detect: Any OSS sub-component or performance-summary field returns 0, None,
or a value that contradicts live broker API state. Specifically: (a) open
position count from positions table ≠ live API count, (b) unrealized P&L
from local tables differs from live API by >20%, (c) portfolio_beta computed
from local data produces NaN or 0 when positions exist.
Fix: Bypass the local table. Query the live broker adapter
(AlpacaLiveAdapter.get_positions() for equity, RobinhoodCrypto.get_positions()
for crypto) directly. Recompute the affected sub-component from live data.
Log the stale table name and the delta between local and live data.
Verify: Re-computed sub-component matches live API within ±5% tolerance. The stale table is flagged in the health report for manual repair.
Escalate: If the same table is flagged stale for 2 consecutive cycles —
escalate with the table name, row count delta, and whether the table schema
needs migration or the write path is broken. A table that was never populated
(positions) is a code bug; a table that was populated but is now stale is
a sync gap.
Source quality enhancement: After checking data freshness, the system SHALL compute Source Health Scores (P-006) for each active data source. Sources with SHS < 0.40 in the current cycle SHALL be logged with degradation reason (low authority, staleness, narrow coverage, or novelty exhaustion). Sources with SHS < 0.25 for 3 consecutive cycles SHALL be replaced — the system searches for alternative free sources covering the same data domain (e.g., replacing a stale screener with an alternative API endpoint, adding a new financial data provider, broadening from single-exchange to multi-exchange data).
Verify: All active data sources have SHS ≥ 0.25. At least one source improvement attempted in the last 30 cycles. Source provenance tags present on all signals generated this cycle.
Rule 17: Signal Direction & Action Validation
Detect: A consensus decision has action=execute but the combination
of ticker, direction, and position state makes execution impossible or
meaningless: (a) direction=neutral — non-actionable by definition,
(b) direction=bearish AND no existing position held in the live portfolio
(exit without entry), (c) direction=bearish on an inverse-ETF ticker
(SH, SQQQ, SPXU, BITI) where no buy fill exists — hedge exit without hedge
entry.
Fix: Reclassify action=skip in the consensus table with a machine-readable
reason: skipped_neutral, skipped_no_position, or skipped_no_hedge_entry.
For case (a): log and skip — neutral signals are information, not action.
For case (b): log and skip — exit signals for unheld tickers are noise.
For case (c): log and skip — Parker exit signals are only valid when a
corresponding buy fill exists in broker_orders.
Verify: Consensus action updated to skip. No future Ross execution
attempt for this ticker×direction without a fresh buy signal first.
Escalate: Never — these are signal-quality filters, not system failures. They reduce noise and prevent the orchestrator from attempting impossible trades.
Rule 18: Inverse ETF Decay Shield
Detect: An open Parker hedge position uses a leveraged inverse ETF (SQQQ, SPXU, SOXS, BITI — any -2x or -3x daily-reset instrument) AND at least one decay condition is met:
(a) Duration decay: Hold duration > 10 trading days. Daily-reset decay compounds with time — leveraged inverse ETFs held beyond 2 weeks lose significant value even if the underlying index direction is correct.
(b) Low-VIX decay: VIX < 20. Implied volatility is too low to justify the leverage cost and daily rebalancing drag. Leveraged inverse instruments are designed for high-volatility environments.
(c) Choppy-market decay: Market texture is "choppy" — SPX oscillating within ±3% of its 20-day SMA for >5 consecutive trading days. In range-bound markets, the daily-reset mechanism of leveraged ETFs maximizes volatility decay regardless of eventual direction.
Fix: Exit the leveraged inverse position immediately. Log the exit
reason as decay_duration, low_vix, or choppy_decay.
If a hedge is STILL warranted (regime remains bear AND portfolio_beta
0.5 after exit): open a replacement position using the 1x inverse equivalent — SH (S&P 500 -1x) for broad market exposure, PSQ (Nasdaq -1x) for tech exposure. 1x inverse ETFs have no daily-reset decay and can be held for the duration of the bear regime.
If no hedge is warranted: rotate the freed capital to cash or defensive sectors per Rule 8.
Verify: Leveraged inverse positions closed. If regime still warrants hedge: replacement 1x inverse position opened OR defensive rotation executed. portfolio_beta ≤ 0.5 within 2 cycles.
Escalate: If the same ticker's leveraged inverse position is exited-and-reopened >2 times in 30 days — the system is cycling in and out of leveraged hedges. Escalate with per-exit decay cost estimate (entry price vs exit price, holding days) and recommend staying in 1x inverse until VIX > 25.
Rule 19: Missed Signal Hindsight Detection
Principle — The system learns from what it missed. Credit assignment research (Zhang 2026, 47 methods surveyed) demonstrates that post-hoc hindsight analysis — using information available only AFTER the trading window closes — identifies root causes of missed opportunities that feed-forward detection cannot see.
Detect (runs every 10 cycles, counterfactual analysis): For each of the 10 largest daily movers (by absolute % change) in the system's benchmark universe over the past 10 trading days:
(a) Did any Persona generate a signal for this ticker? → NO: failure mode = coverage gap (source didn't see it) → YES, but confidence < threshold: failure mode = signal too weak → YES, confidence ≥ threshold, but didn't trade: failure mode = gate filtering (which gate blocked it?)
(b) If a trade COULD have been opened (signal existed, confidence ≥ 0.70, passed all gates) but wasn't: failure mode = execution gap
Fix — Root cause triage:
| Root cause | Contract action |
|---|---|
| Coverage gap (no signal) | Add ticker to discovery candidate list. If ≥3 missed tickers from same sector: broaden that discovery lens's screening criteria |
| Signal too weak (conf < 0.70) | Log; if same ticker missed 3+ times: lower confidence floor for that lens by 0.05 (min 0.60) |
| Gate filtering (passed source, blocked by gate) | Log which gate blocked it. If same gate blocks ≥3 viable trades in 10 cycles: escalate for gate calibration review |
| Execution gap (could have, didn't) | Escalate — this is an orchestrator or order-routing failure |
Verify: Hindsight analysis completed for 10 largest movers. At least 1 root cause identified OR documented "no missed opportunities." Source coverage gap counts trend downward over rolling 90-day window.
Escalate: If coverage gap count increases for 3 consecutive hindsight cycles — the system's data universe is shrinking, not expanding. Escalate with per-source coverage breadth trend.
Invariants
-
P-001 — Paper trading is dead code. The paper trading adapter (
brokers/alpaca_paper.py) is NEVER imported, NEVER invoked, and has NO code path in any production pipeline, cron job, or system module. TheALPACA_ENVrouting mechanism no longer supports apapermode — all adapters route to live production only. The file may exist on disk as a historical artifact but carries zero operational risk because (a) no import chain reaches it, (b)brokers/__init__.pyno longer exportsAlpacaPaperAdapter, (c)brokers/router.pyhardcodes live-only routing, (d)reconcile.pyhardcodes live-only reconciliation, (e)consensus/ross/adapter.pyhardcodes live-only adapter selection, and (f) all metric queries filtervenue != 'alpaca_paper'as defense-in-depth. Only live production trading across Alpaca Live and Robinhood Crypto SHALL count in any performance calculation, OSS computation, equity curve, drawdown measurement, P&L report, or health evaluation. -
P-002 — Dust positions are filtered from position counting. Positions with
|market_value| < $0.01(one cent) are considered dust — fractional remnants from fills/partials that have zero economic significance. Gate 2 (position-count limit,gates.py:144-150) and Gate 7 (gross-exposure cap,gates.py:270-294, all four sum expressions) SHALL filter these positions using>= cfg.DUST_POSITION_THRESHOLD(functionally equivalent to the former> 0since all dust has market_value ≤ $0.00).compute_oss.py(scripts/compute_oss.py:182-184) also SHALL filter dust from open position counting. This invariant is verified by 4 property-based tests covering 800+ generated examples (seetests/test_gate2_dust_filter.pyandtests/test_gate2_property_invariants.py):- INV-001: Any number of dust positions never blocks Gate 2 (200 examples)
- INV-002: If Gate 2 blocks, non-dust count ≥ limit (300 examples)
- INV-003: Adding dust positions never changes allowed→blocked (200 examples)
- INV-004: Filter applied symmetrically to both equity and crypto (100 examples)
The threshold constant
DUST_POSITION_THRESHOLD = 0.01is defined atconsensus/ross/config.py:76(single source of truth) and SHALL NOT be raised without Theo's approval — raising it would hide small but real positions; lowering it is safe but unnecessary.
-
P-003 — Returns are trade-attributed, not balance-based. The equity curve in every performance computation (Sharpe ratio, max drawdown, total return percentage, OSS rolling alpha, and Calmar ratio) SHALL be built from actual trading activity — realized P&L from closed trades plus unrealized P&L from open positions — and SHALL NEVER use raw account balances from
sync_state.broker_equity. Deposits, withdrawals, dividends, cash transfers, and fees SHALL NOT be counted as trading returns.Equity curve formula:
equity[t] = ACCOUNT_EQUITY + cumulative_realized_pnl[t] + unrealized_pnl[t]whereACCOUNT_EQUITYis the initial capital fromconsensus/ross/config.py:119(single source of truth),cumulative_realized_pnl[t]is the sum ofrealized_pnlfromrealized_trades(filteredunmatched=0,venue != 'alpaca_paper') up to dayt, andunrealized_pnl[t]is the currentSUM(unrealized_pl)frombroker_positionsfiltered byABS(market_value) >= DUST_POSITION_THRESHOLD.Implementation:
perf.py:compute_equity_metrics()(lines 137–261) builds the equity curve from trade-attributed data only.sync_state.broker_equityis used for reconcile timing (date grid) but its value is never read for return computation. This invariant is verified by 3 property-based tests covering 500+ generated examples (seetests/test_perf_equity_source.pyandtests/test_perf_equity_property_invariants.py):- INV-001 (Additivity):
end_equity = ACCOUNT_EQUITY + Σ(realized_pnl) + unrealized_pnl(200 examples) - INV-002 (Deposit Exclusion): Deposits of any amount do not affect trade-attributed return (200 examples)
- INV-003 (Flat when idle): With zero trades, equity stays at
ACCOUNT_EQUITYregardless of deposits (100 examples)
Rationale: On 2026-06-17, a $500 deposit into the Alpaca Live account caused
sync_state.broker_equityto jump from $201 to $701. The old balance-basedcompute_equity_metrics()treated this as a 249% overnight return, inflating the OSS from a correct value near 0 to 2.595 (false positive). Actual realized P&L across all trades was -$1.67 on 24 trades. The fix was validated by [fv] protocol on 2026-06-30: claim → verify → cross-check (all 4 cron outputs, all venues, all position-counting paths) → draft → 500+ property test examples → zero regression (140/140 tests). - INV-001 (Additivity):
-
P-004 — Hedging strategies are independently attributed and performance-graded. Every hedge position (inverse ETF, defensive sector rotation, cash allocation) SHALL carry a
hedge_strategytag in broker_orders and realized_trades. The health monitor SHALL compute per-strategy profit factor and win rate independently over a rolling 60-day window:hedge_strategy Instruments Description inverse_1xSH, PSQ, RWM 1x inverse ETFs — daily-reset, no leverage decay inverse_leveragedSQQQ, SPXU, SOXS, BITI -2x/-3x inverse — decay risk, crisis-only defensive_sectorXLP, XLV, XLU, consumer staples, healthcare, utilities Long defensive — positive carry cash_allocationCash (>40% of equity) Yield-bearing, zero correlation Strategies with
profit_factor < 0.7over the 60-day window SHALL be flagged for review. Strategies withprofit_factor < 0.5over 60 days SHALL be retired — the contract auto-removes them from the authorized hedging toolkit. A retired strategy may be re-evaluated after 30 days. This invariant enables emergent optimization: the system discovers through attribution which hedging strategies work in which regimes, rather than having allocation percentages hardcoded.This invariant is verified by property-based tests that confirm every hedge order carries a valid
hedge_strategytag, per-strategy P&L sums are consistent with total hedge P&L, and retired strategies are excluded from new hedge entry signals. -
P-005 — Ticker universe breadth is continuously monitored and diversified. The system SHALL maintain a minimum discovery breadth: at least 3 active discovery lenses producing candidates each cycle, with at least 10 unique candidate tickers evaluated in any rolling 30-day window. No single discovery lens may account for >60% of open positions over a 60-day window (prevents lens monoculture).
Each discovery lens SHALL carry a
discovery_lenstag on all signals and trades it produces. The health monitor SHALL compute per-lens yield:Metric Formula Threshold Lens hit rate realized_trades with profit > 0 / total trades from lens Alert if < 0.3 over 60d Lens profit factor gross_profit / gross_loss from lens trades Retire lens if < 0.5 over 60d Lens novelty rate candidates with correlation < 0.3 to existing / total candidates Alert if < 0.2 over 30d A discovery lens with profit_factor < 0.5 over 60 days SHALL be paused (its candidates are generated but not admitted). A lens with novelty_rate < 0.2 over 30 days is producing only correlated candidates — its search criteria SHALL be broadened.
This invariant ensures the system never settles into a narrow field of view where it only sees tickers similar to what it already holds. The correlation admission gate (Rule 12) and novelty monitoring (P-005) together force the system to continuously expand its field of view.
-
P-006 — Data source quality and coverage improve continuously through experiential feedback. Every Persona's data source carries a quality score that evolves over time based on downstream trade outcomes and timeliness. Source quality degrades when signals from that source lead to losing trades or arrive too late; source quality improves when signals lead to winning trades with timely execution.
Source quality dimensions (scored per source per cycle, 0.0–1.0):
Dimension Formula Degradation trigger Authority rolling 60d hit rate: winning trades / total trades from source < 0.35 over 60 days Timeliness signals that resulted in fills before adverse price move / total signals < 0.50 over 30 days Coverage breadth unique tickers surfaced / unique tickers in benchmark universe < 0.10 over 90 days Novelty decay new (unseen) tickers surfaced this cycle / total tickers surfaced < 0.05 over 30 days The combined Source Health Score (SHS) = (authority × 0.4) + (timeliness × 0.3) + (coverage × 0.2) + (novelty × 0.1). A source with SHS < 0.40 SHALL be flagged for review. A source with SHS < 0.25 SHALL be replaced (the system discovers an alternative free source covering the same data domain) or retired (its signals are generated but not admitted to the consensus pipeline).
Source provenance: Every signal entering the consensus pipeline SHALL carry a
source_provenancetag identifying: (a) the Persona that generated it, (b) the data source(s) it drew from, (c) the timestamp of the underlying data, and (d) the confidence score. This enables post-hoc tracing: when a trade wins or loses, the system knows which data source contributed to the decision.Coverage improvement obligation: Every 30 cycles (≈ trading month), the system SHALL attempt to add at least one new free data source or expand an existing source's coverage (additional tickers, additional data fields, additional frequency). New sources start with probationary SHS = 0.50 and are evaluated after 30 cycles. This is the mechanism by which the system's field of view widens over time.
This invariant is verified by property-based tests that confirm every signal carries a valid
source_provenancetag, SHS scores are computed for all active sources, and retired sources are excluded from signal generation. -
P-007 — Benchmark integrity is self-validating. Every alpha and beta computation SHALL use a live, point-in-time benchmark return fetched this cycle via
yfinance(^GSPC). The benchmark source SHALL NOT contain hardcoded literals — any constant appearing in benchmark calculation is a contract violation. Alpha sign SHALL flip correctly when portfolio return crosses the benchmark return (positive alpha = outperformance). Benchmark fetch failure SHALL be surfaced asbenchmark_errorin the OSS output rather than silently using a stale value. Property-tested viatest_benchmark_integrity.py. -
P-008 — Trade execution costs are modeled and bounded. No trade SHALL be admitted whose expected edge (signal confidence × signal strength) is less than the modeled round-trip cost (half-spread + commission + slippage). The cost model SHALL use realized
slippage_bpsfrombroker_ordersas feedback — if median slippage exceeds 50 bps over a 30-day rolling window, the cost gate SHALL tighten (raise the admission threshold). A cost-inefficient trade that slips past the gate SHALL contribute tocost_breach_countand degrade the OSS via the Cost- Aware Gate (Gate 2.3). StockBench/FINSABER evidence: for a $1K account, trading 1% positions, a single 50-bps slip on entry AND exit is $0.10 round-trip (0.01%) — negligible. The gate exists to prevent death by a thousand cuts, not to reject single marginal trades. -
P-009 — The Optimization Success Score and every sub-metric are sign- correct and monotonic. The Calmar Ratio SHALL be negative when CAGR is negative (never
abs(CAGR) ÷ max_drawdown). Rolling Alpha SHALL be positive when portfolio return exceeds benchmark return. A losing trading period (negative total P&L, negative CAGR) SHALL NEVER produce an OSS ≥ a winning trading period (positive total P&L, positive CAGR). Theoss_trendindicator SHALL correctly reflect the direction of the last 3 OSS readings: improving (rising), flat (within ±0.05 band), or degrading (falling). Property-tested viatest_oss_monotonicity.py. -
P-010 — Every adaptation decision is evidence-backed and reversible. Every parameter change SHALL write a full before/after record to the
adaptationstable with: the parameter name, prior value, new value, OSS at time of change, market regime, and the adaptation rule that triggered it. After each change, the system SHALL wait at least 3 evaluation cycles (≈3 trading days). If forward OSS (OSS now minus OSS at time of change) is non-positive for 3 consecutive post-adaptation evaluations, the adaptation SHALL auto-unwind — the parameter reverts to its pre-adaptation value and the unwinding is logged as a separate adaptation record withresult='unwound'. Thewin_rate_adaptationsmetric SHALL reflect the fraction of adaptations whose forward OSS delta was positive. An adaptation loop that produces 5 consecutive unwound adaptations SHALL escalate to the user — the optimizer has lost its signal. Property-tested viatest_adaptation_persistence.py. -
P-011 — The declared system objective is drawdown-first, not growth- first. The primary outcome the contract promises is: "match the S&P 500 benchmark net of trading costs, with peak-to-trough drawdown bounded to ≤25%." Risk-adjusted outperformance above the benchmark is a secondary objective, pursued only when market-regime detection confirms the system has an edge. Hedge capacity SHALL be regime-driven (Gate 2.1 buckets: moderate/heavy/extreme) and SHALL be sized to actually bound drawdown —
HEDGE_POSITION_SIZE_PCTscales with bearish_score, andPARKER_HEDGE_CAP_PCTsets the ceiling. Cash (zero-beta) is a valid optimization result when no edge is detected. The contract enforces improvement trajectory, not absolute return thresholds — a stable OSS of 0.7 with low drawdown is preferable to a volatile OSS oscillating between 0.3 and 1.5. StockBench/ FINSABER evidence (arXiv:2505.19081): small accounts ($1K–$10K) are cost- constrained; benchmark-matching net of costs is the honest achievable outcome, not "maximize growth." -
P-012 — Internal performance metrics are self-validating, self-healing, and fail-closed against poisoning. The
performance_snapshotstable feeds the Optimization Success Score (P-009), which drives live parameter adaptation (P-010). A poisoned snapshot therefore does not merely produce a wrong number — it steers the system's parameter-adaptation loop AWAY from the optimal state. The contract's North Star is that the system regresses to its mean HEALTHY state on its own and stays on mission (capturing risk-adjusted returns) without user intervention; this invariant is the mechanism that guarantees it.Detection by positive re-derivation (not blocklist). Every snapshot SHALL be validated by re-deriving it against ground truth AT READ TIME, never trusted merely because it was written or because it lacks a known-bad shape. A snapshot is INVALID if any hold: (a) it is dated before the first realized trade (
realized_trades.closed_at,unmatched=0,venue != 'alpaca_paper') — such rows are pre-inception non-data and SHALL NEVER be reconstructed by fabricating equity (zero-assumptions doctrine); (b)start_equitydoes not equal the trade-attributed equity-curve inception anchor (thesys_startdeposit from the P-003 deposit schedule) within $1 — this catches raw-balance leaks (the $100,383 and flat-$200 poisonings); (c)|total_return_pct|exceeds the adaptive sanity bound, the looser of 40% absolute or 5× the rolling standard deviation of clean returns — this catches the +249% deposit- inflation and −99.8% raw-balance poisonings without false-positiving a legitimately volatile week. Because validation is a pure function of the row plus the current capital schedule, the SAME validator detects both current and historical poisoning and self-corrects as the P-003 formula improves.Containment by quarantine, never deletion. Invalid rows SHALL be flagged (
quarantined=1,quarantine_reason), preserved as an audit trail of the bug, and EXCLUDED from every OSS input window (compute_oss_from_snapshot, beta, trend). OSS SHALL be computed only from rows that pass validation this cycle.Tiered, fail-closed health — defined relative to what is achievable. The adaptation controller SHALL read a health tier derived from clean-snapshot availability, where the clean-snapshot floor
MIN_CLEANramps with system age (clamp(age_days × 0.5, 3, 8)) so a young or freshly-healed system is never starved into permanent inaction:- HEALTHY (clean ≥
MIN_CLEAN): full loop — propose AND commit to.env.prod. - DEGRADED (1 ≤ clean <
MIN_CLEAN): evaluate + auto-unwind run (bookkeeping / immune system), the proposal is computed and REPORTED, but SHALL NOT be committed to.env.prod. - CRITICAL (clean = 0): hold all adaptation; only bookkeeping runs; emit a loud alert.
Self-healing by natural refill. Because poisoned history is quarantined rather than resurrected, the OSS window refills with genuine post-inception snapshots as they accrue (~1/day). A system that starts DEGRADED regresses to HEALTHY unaided as clean rows accumulate — no user intervention required to unblock adaptation. The evaluate/auto-unwind half of P-010 ALWAYS runs regardless of tier, because reverting a proven-bad change is pure risk reduction. Only the new-risk action (committing a new adaptation) is gated.
Rationale: On 2026-07-02, an audit found that 36 of 37 snapshots were poisoned — 29 with a raw ~$100K paper-balance leak producing −99.8% returns, and pre-inception rows dated before the first trade — while OSS was silently computed from a mix of real and fabricated rows, driving live
POSITION_SIZE_PCTtightening against a phantom −2.35% drawdown. Quarantine restored a truthful (if thin) window; the tier gate held the controller report-only until real clean data accrues. Property-tested viatests/test_p010_metric_integrity.py(detector correctness, tier gating, regress-to-HEALTHY, quarantine-never-deletes). Detector:scripts/metric_integrity.py; migration:scripts/migrate_p010_metric_integrity.py; controller gate:scripts/apply_adaptations.py:main. - HEALTHY (clean ≥
-
The prime directive SHALL NOT be weakened by any contract update. Any change that would allow valid trades to be silently blocked is invalid.
-
Internal failures SHALL be distinguished from external failures. "Alpaca API returned 500" is external; "gate evaluated stale position count" is internal.
-
Every remediation action produces a structured audit trail entry with classification, action taken, and verification result.
-
Strategy parameters and cron schedules MAY be dynamically adjusted by return-optimization rules (13-14), within bounded ranges, only when OSS or regime data justifies the change. See
#### parameter-statefor current values and justification trail. The stability surface (breakers, exit-gates, per-position caps) SHALL NOT be modified. -
During market-closed hours: position exits, breaker resets, and reconciliation continue to execute (these are lifecycle hooks, not trade execution).
-
The contract itself is a living document. Updates that tighten detection or expand remediation are encouraged. Updates that weaken the detection surface or narrow the definition of "blocked" compromise the prime directive.
-
Return-optimization guardrails:
-
All adaptation decisions SHALL be reversible — no parameter change persists beyond the regime that triggered it without fresh corroboration
-
No adaptation may increase position size beyond MAX_CONCURRENT_POSITIONS × (equity ÷ MAX_CONCURRENT_POSITIONS) — the per-position cap is invariant
-
Return maximization targets capturable returns only — the contract does not promise returns the market does not offer. Regime detection may indicate "no edge available," and cash is a valid optimization result
-
The OSS target (1.0) is aspirational — the contract enforces improvement trajectory, not absolute thresholds. A stable OSS of 0.7 is better than a volatile OSS oscillating between 0.3 and 1.2
-
Adaptations that fail to improve OSS over 3 consecutive evaluations (≈3 trading days) are automatically unwound — the system returns to its pre-adaptation state
-
Circuit breakers, position-count gates, and exit rules (the stability surface) are NEVER modified by optimization rules. Optimization operates within the stability surface, not on it
-
Data source version integrity: Every external API endpoint used by a track SHALL be documented with its current version in the track's source header. When a track produces 0 signals for 3+ consecutive runs, the health monitor SHALL verify the endpoint version against the documented version before classifying as
no_data(valid). A version mismatch (deprecated endpoint, API EOL) is internal, not external — the contract is responsible for keeping its data sources current. -
Execution mode integrity: Every cron job that runs a pure Python script (track signal generation, DB maintenance, data collection — where the script itself produces the complete output without LLM processing) SHALL be configured with
no_agent=true. The health monitor SHALL verify this configuration for all track and pipeline jobs. An agent-mode configuration on a script-only job is an internal misconfiguration — the LLM may perform unrelated actions (web search, reasoning) instead of executing the script, silently degrading signal freshness. -
Orchestrator status coverage: The Ross orchestrator SHALL handle every known execution status code produced by
execute_trade(). Status codes that indicate permanent failure (error_rejected,skipped_no_position,skipped_non_fractionable) SHALL be marked as permanently consumed (action=skip), not retried. Status codes that indicate transient failure (error_poll_timeout,error_liquidity) MAY be retried up to the orchestrator's retry limit. The set of permanently-skipped statuses SHALL be updated whenever a new adapter returns a novel failure code.
Reporting
Every remediation cycle produces a structured report:
1. Cron Output — Health Summary (every cycle)
🛡️ Alpaca System Health — 2026-06-28 15:00 UTC
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Status: HEALTHY | 0 blocks | 0 stale | DNS OK
Regime: BULL (VIX 15.2) | OSS: 1.23 ▲ (Calmar 1.4)
Equity: $697.35 | Cash: $542.50 | Open Orders: 0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
With issues:
🛡️ Alpaca System Health — 2026-06-28 15:00 UTC
Status: ADAPTING | 1 block | Maya stale (27h) | Regime: BEAR
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📉 OSS: 0.42 ▼ (Calmar 0.3, PF 0.8, Beta 0.78)
└─ Warning: Below floor (0.5) — adaptation required
└─ Action: Rule 8 — Beta 0.78 in bear regime → rotate defensively
⚠️ BLOCK: [details as before]
⚠️ STALE: [details as before]
📊 ADAPTATION: [details as before]
2. Cron Output — P&L Dashboard (10:00 AM and 6:00 PM daily, or on significant change)
📊 Alpaca P&L Dashboard — 2026-06-28 (30-day trailing)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💰 Total Equity: $697.35 | Cash: $542.50 | Open: $154.85 (3 pos)
📈 PERFORMANCE
Realized P&L (30d): +$42.18 (+6.4%)
Unrealized P&L: +$8.72
Benchmark (SPY 30d): +2.1%
Alpha: +4.3% ← our edge
📉 RISK
Max Drawdown: 4.2% ($29.14)
Sharpe Ratio: 1.47
Profit Factor: 1.82 (gross profit ÷ gross loss)
Win Rate: 58% (11W / 8L)
🏆 TOP/BOTTOM
Largest Winner: NVDA +$18.42 (Eddie, held 4d)
Largest Loser: AMD -$5.21 (Janet, held 2d)
📋 BY TRACK
Track Trades Win% P&L Avg Hold
───────── ────── ──── ───── ────────
Eddie 4 75% +$22.14 4.2d
Maggie 3 67% +$14.31 5.1d
Frank 4 50% +$5.73 3.8d
Maya 2 50% -$0.80 7.0d
Janet 4 50% -$0.20 1.8d
Parker 2 100% +$1.00 2.0d
───────── ────── ──── ───── ────────
TOTAL 19 58% +$42.18 3.8d
📋 BY VENUE
Alpaca Equity: 17 trades | +$35.94
Robinhood Crypto: 2 trades | +$6.24
📅 DAILY P&L (7 days)
Mon +$12.40 ████████
Tue -$3.20 ██
Wed +$18.72 ████████████
Thu +$5.41 ███
Fri +$8.85 ██████
Sat — (market closed)
Sun — (market closed)
🔓 OPEN POSITIONS
ENVX 5.0 @ $11.23 → $11.82 +$2.95 (5d)
CLF 10.0 @ $19.10 → $20.05 +$9.50 (3d)
ETH 0.01 @ $2,452 → $2,389 -$4.73 (2d)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3. RA-H OS Knowledge Graph
Every remediation cycle creates a [LEARN] alpaca-system-health: {timestamp}
node with:
- metadata: { type: "system-health", status: "healthy" | "degraded" | "blocked" }
- source: Full structured JSON report
- description: Summary of blocks found, fixed, escalated, and P&L highlights
A separate [REPORT] alpaca-pnl-dashboard: {date} node is created for each
daily P&L dashboard with the full performance-summary data for long-term
trending and cross-session comparison.
4. Periodic Digest (via existing Kojo auditor)
Kojo's existing 07:00 and 20:00 reports serve as the operational digest. The system-health contract supplements Kojo: Kojo audits and reports, the system-health contract detects, adapts, and self-heals.
Execution
- Gather truth — Run
auditor_query.pyfor live system data. Query signals.db for latest consensus decisions and execution results. - Classify state — Compare against thresholds. Classify every anomaly as internal or external using the fault-domain rules.
- Detect market regime — Fetch VIX, SPX 20-SMA, Nasdaq 20-SMA, and BTC trend via yfinance (reuse Parker's market data pipeline). Classify regime as bull/neutral/bear/crisis.
- Compute OSS — Query trade_journal.db for closed trades in the last 30 days. Compute Calmar Ratio, Profit Factor, Rolling Alpha over SPY, Portfolio Beta, and Win Rate of adaptations. Blend into OSS score.
- Check adaptation triggers — Compare OSS vs floor (0.5), portfolio_beta vs regime, and sector exposure vs regime. Fire adaptation rules (8-12) if triggered.
- Apply operational remediation — For each internal block not addressed by adaptation rules, apply matching remediation rule (1-17). External blocks are documented, not fixed.
- Re-verify — After all remediations and adaptations, re-run auditor_query.py and re-compute OSS. Only report what persists.
- Generate report — Produce structured output (cron format + RA-H node).
- Record adaptation outcomes — For each adaptation fired, log a before/after record in trade_journal.db for future OSS computation.
- Wait 5 min → repeat from step 1.
Audit Trail Format
{
"run_id": "system-health-20260628-150000",
"timestamp": "2026-06-28T15:00:00Z",
"duration_ms": 8234,
"system_state": {
"equity": 697.35,
"cash": 542.50,
"open_orders": 0,
"market_open": false,
"tracks": {
"eddie": {"fresh": true, "hours_since": 46},
"maya": {"fresh": false, "hours_since": 27},
"janet": {"fresh": true, "hours_since": 0},
"parker": {"fresh": true, "hours_since": 1}
},
"dns": {"fc.yahoo.com": true}
},
"market_regime": {
"regime": "bear",
"vix_close": 27.4,
"spx_vs_sma": -2.3,
"ndx_vs_sma": -3.8
},
"return_optimization": {
"oss": 0.42,
"oss_trend": "degrading",
"calmar": 0.31,
"profit_factor": 0.82,
"rolling_alpha": -8.5,
"portfolio_beta": 0.78,
"win_rate_adaptations": 0.40
},
"blocks_found": 2,
"blocks_internal": 1,
"blocks_external": 1,
"blocks_fixed": 1,
"blocks_escalated": 0,
"adaptations_fired": 2,
"adaptations": [
{
"rule": "Rule 8: Market-Regime Position Mismatch",
"target": "portfolio_beta",
"action": "Rotated high-beta to cash",
"before": {"beta": 0.78, "regime": "bear"},
"after": {"beta": 0.35},
"result": "adapting"
},
{
"rule": "Rule 9: Regime-Inappropriate Sector",
"target": "NVDA, AMD",
"action": "Exited high-beta losers; rotated to cash",
"before": {"exposure_pct": 22},
"after": {"exposure_pct": 8},
"result": "adapting"
}
],
"remediations": [
{
"rule": "Rule 1: Stale Circuit Breaker",
"target": "equity-position-limit",
"action": "deactivated active breaker",
"result": "fixed",
"duration_ms": 120
}
]
}
Errors
- Alpaca API unreachable — classified as external. Log, do not fix.
- Robinhood Crypto API unreachable — classified as external. Log, do not fix.
- signals.db corrupted — classified as internal. Attempt SQLite integrity check. If unrecoverable: escalate immediately (data loss).
- trade_journal.db corrupted — classified as internal. Attempt SQLite integrity check. If unrecoverable: escalate immediately.
- auditor_query.py fails — classified as internal. Check Python venv path, verify dependencies. If persistent: escalate.
- Cron job trigger fails (cronjob action='run' returns error) — classified as internal. Retry once. If persistent: escalate.
- Etherscan API key expired or endpoint deprecated — classified as internal. The contract is responsible for keeping its data sources current. Verify the API key against Etherscan's account portal. Verify the endpoint version against the documented version in track_4_maya.py. If deprecated: migrate to the current endpoint. If key expired: escalate (requires human credential rotation).
- yfinance returns empty data (not error) — classified as internal if the
system marks it as
no_datawithout verifying the data source is reachable. Before classifying asno_data: (a) verify yfinance can fetch a known benchmark ticker (SPY), (b) check for rate-limiting response headers, (c) compare against Rule 6 DNS check. If yfinance is reachable but specific tickers return empty: classify asno_data(valid). If yfinance is unreachable or rate-limited: classify as external (Rule 6). - ChromaDB/LLM dependency unreachable — classified as external if the dependency is hosted remotely. If running locally and the process is down: classified as internal — restart the process. If persistently down: escalate.
- Cron job running in wrong execution mode — classified as internal. If a
track's cron job is configured with agent-mode (
no_agent=falseor unset) when it should run as a script (no_agent=true), the health monitor SHALL flag this as an internal misconfiguration. Fix: update the cron job viacronjob(action='update', no_agent=true).
Skills
- kojo-auditor
- pipeline-idempotency-audit
Tools
cli:python3: required for running auditor_query.py and track scriptscli:sqlite3: required for direct database queries when auditor is unavailablecli:curl: required for DNS reachability tests and API health checks