Stashed changes had real safetensors weight loading via VarMap::load.
Audit branch was conservative (validate-only). Kept the real loading
and simplified Mamba2 to use the same VarMap::load pattern as TFT.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The VaR engine's get_symbol_volatility used hardcoded per-asset-class
annual volatility values (e.g. 25% for equities, 80% for crypto) without
any indication to operators that real market data was not being used.
Now emits a tracing::warn on every static volatility lookup so operators
see it in logs. Also adds a volatility_overrides HashMap<String, f64>
field on VarEngine for manual per-symbol overrides until a real market
data feed is integrated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
reconnect() previously slept 500ms then unconditionally set
SessionState::Active, faking a successful reconnection regardless of
whether any broker or infrastructure was actually reachable.
Now performs a DB health check (SELECT 1) after the sleep. If the
check fails, state is set to Disconnected and an error is returned.
Active is only set when the health check passes. The DB ping is the
best available liveness probe until a real FIX/cTrader session is
wired into SessionRecovery.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A single CTRADER_LIVE=true env var was enough to route real money orders
through the broker. Now requires CTRADER_LIVE_CONFIRMED=I_UNDERSTAND_REAL_MONEY
alongside it, preventing accidental live trading from copy-pasted configs.
Also adds prominent warning log when live mode activates and info log
for demo mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both route_order and cancel_order used let _ = to discard DB update
errors after successful broker operations. This means an order could be
live at the broker while the database still shows PENDING_SUBMIT or
CANCEL_PENDING, with no log entry to alert operators.
Replaced with if let Err(db_err) that logs at error level with CRITICAL
prefix, client_order_id, and broker_order_id for manual reconciliation.
The RPC still returns success since the broker operation completed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous (quantity * 100_000.0) as i64 silently truncated
fractional lots and could overflow on extreme values. Added
convert_quantity_to_volume() that validates the result is finite,
non-negative, and within i64 range, using round() instead of
truncation. Includes 7 unit tests covering normal values, fractional
rounding, overflow, negative, NaN, infinity, and zero edge cases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
from_checkpoint was creating fresh models with random weights and setting
is_trained=true, allowing trading on noise. Now validates checkpoint exists
and does NOT set is_trained=true when weights are random.
Mamba2 predict errors are now wrapped as MLError::InferenceError so the
fallback manager can degrade model health. Both TFT and Mamba2 predict
refuse to run on untrained models, and is_ready() reflects trained state.
Also fixes TFT input_dim mismatch (16 vs 5+10+16=31).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
try_write() is non-blocking and silently drops the CTraderClient when
the lock is contended. This means a successfully connected broker
client could be lost without any error. Changed to write().await which
guarantees the client is stored. Made function async and updated the
call site in main.rs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Low-confidence ensemble votes now forced to Hold (was letting weak
signals through to order generation in both vote paths)
- Inference latency tracking uses EMA (alpha=0.1) instead of
overwriting with the latest sample
- retrain_model returns Status::Unimplemented instead of faking
success with a random job_id
- get_model_performance returns honest zeroes instead of hardcoded
fake metrics (accuracy=0.85 etc.)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Was only checking quantity limit and VaR. Now runs all 5 risk checks:
1. Kill switch / circuit breaker (via TradingServiceKillSwitch)
2. Max order size + position limits (via RiskRepository.get_risk_limits)
3. Daily loss / drawdown limit (via RiskRepository.get_risk_metrics)
4. Leverage limit (via config + RiskRepository.get_risk_metrics)
5. VaR limit (via RiskEngine.check_var_limit, existing)
Violations accumulate rather than short-circuit so callers see all
failures at once. Added 6 tests verifying violation type coverage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The update_position method previously acquired a read lock to get the
Arc<AtomicPosition>, dropped it, then called update_with_execution
outside any lock. Two concurrent fills for the same position could both
load the same old_quantity via Acquire, compute independent new quantities,
and the last Release store would silently discard the other fill — causing
the position to show e.g. 10 shares when it should show 20.
Now holds the positions write lock across the entire get-or-create +
update_with_execution sequence, serializing concurrent fills per position.
The validate_position_update call remains outside the lock to minimize
hold time (it does its own async reads and risk checks).
Also removes excessive per-step RDTSC latency tracking from the critical
path (total latency tracking is preserved) and adds a multi-threaded
regression test that spawns 10 concurrent +1 fills and asserts the final
quantity equals 10.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
clone_for_async() was creating independent AtomicU64 instances for
message_count, drop_count, last_heartbeat, and reconnect_attempts
instead of sharing the originals. This meant spawned tasks incremented
their own counters while get_stats() read the original's (always 0),
and the heartbeat monitor watched a counter never updated by the
connection task.
Also fixes last_heartbeat initializing to 0, which caused the first
heartbeat check to compute a huge elapsed time and immediately trigger
a false "connection appears dead" alert.
Changes:
- Change 4 struct fields from AtomicU64 to Arc<AtomicU64>
- Initialize last_heartbeat to HardwareTimestamp::now() instead of 0
- clone_for_async() now clones the Arcs (shared counters)
- Add test verifying counters are shared between original and clone
- Add test verifying heartbeat initialized to current time
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Casting negative f64 to u64 saturates to 0, corrupting avg_price and all
downstream PnL calculations. Add price_to_fixed_checked() that rejects
non-finite, zero, and negative prices with PositionError::InvalidPrice.
The critical update_with_execution path now uses the checked variant.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ubuntu 22.04's protobuf-compiler (v3.12) lacks proto3 optional field
support. Install protoc v25.1 from GitHub releases instead, which
matches the protoc version available in Debian bookworm-based images
used by the other 5 services.
Validated: docker build completes successfully, binary starts with
ml_training_service --help.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 6 service Dockerfiles were missing newly-added workspace crates
(web-gateway, ctrader-openapi, foxhunt-deploy, broker_gateway_service),
causing cargo workspace resolution failures during Docker builds.
Changes across all Dockerfiles:
- Add COPY directives for web-gateway, ctrader-openapi, foxhunt-deploy,
broker_gateway_service (new workspace members since Dockerfiles written)
- Add SQLX_OFFLINE=true env and .sqlx cache copy where missing
- Add perl and make system deps (needed for OpenSSL build from source)
- Remove COPY migrations (dir excluded by .dockerignore, not needed)
- Expand broker_gateway_service from 3-crate to full workspace copy
Validated: docker build --check passes all 6, cargo check -p passes all 6.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Proves the critical trading pipeline path works end-to-end:
OHLCV -> features -> ensemble -> prediction. Uses all 10 real candle
inference adapters (DQN, PPO, TFT, Mamba2, Liquid-CfC, TGGN, TLOB,
KAN, xLSTM, Diffusion) with random weights, 60 synthetic OHLCV bars,
and asserts confidence/action/model-count invariants. No external
dependencies (no DB, no Docker).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Spawn a background tokio task that feeds synthetic OHLCV bars into the
ensemble coordinator's per-symbol feature extractors. The extractors
require ~51 bars of warmup before they can produce real 51-dim feature
vectors; without this feed they remain cold and return zeros.
The task uses a random-walk price generator with realistic base prices
for the four baseline futures symbols (ES, NQ, ZN, 6E) and configurable
interval (MARKET_FEED_INTERVAL_MS, default 1s) and symbol list
(MARKET_FEED_SYMBOLS). In production this will be replaced by a
Databento or exchange data feed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Register TGGN, TLOB, KAN, xLSTM, and Diffusion inference adapters
alongside the existing DQN, PPO, TFT, Mamba2, and Liquid-CfC.
Rebalance weights to 0.10 each (equal weighting across 10 models).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use tokio::task::block_in_place for safe sync→async bridging
- Replace count(*) with count(1) in division context (QuestDB parser bug)
- Add coalesce() for stddev NULL handling (single-row edge case)
- Consolidate integration tests into single lifecycle test with DROP+CREATE
- All 4 QuestDB tests pass against real QuestDB 8.2.3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace stub metrics provider with real QuestDBMetricsProvider that
queries QuestDB via PostgreSQL wire protocol (port 8812) for rolling
model Sharpe, win rates, confidence buckets, and ensemble metrics.
- Add QuestDB service to docker-compose.yml (8.2.3, ports 9009/8812/9003)
- Create questdb_metrics.rs with 5 SQL queries and graceful fallback
- Wire QuestDBMetricsProvider into main.rs (replaces StubMetricsProvider)
- Fix unused Instant import in feedback_loop.rs (#[cfg(test)] scope)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
FeedbackLoop runs as a background tokio task, periodically running
weight and gate optimization cycles with kill switch monitoring and
retraining trigger detection. Wired into main.rs with a stub
MetricsProvider until QuestDB is deployed.
7 new tests covering kill switch, freeze/unfreeze, retraining triggers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Gate optimizer adjusts conviction gate thresholds based on win-rate per
confidence bucket with cooldown and kill switch safety rails. Model
registry provides lifecycle management (Candidate → Staging → Production
→ Archived) with InMemoryModelRegistry for testing. P&L attribution
decomposes realized trade P&L into per-model contributions using signal
alignment.
24 new tests across 3 modules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7-gate conviction system, autonomous feedback loop with kill switch,
and Rust-native model registry — backed by PostgreSQL + QuestDB.
Resolve merge conflicts in hyperopt/adapters/mod.rs and enhanced_ml.rs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 16 unit tests for Enhanced ML service components:
- EnsembleConfig::default() values (min_models, thresholds, voting)
- FeaturePreprocessor::classify_feature_type() for price, volume,
technical, sentiment, and unknown features
- FeaturePreprocessor normalization (z-score, tanh fallback, zero std_dev)
- FeaturePreprocessor default stats validation
- RuntimeModelInfo creation for all 5 model types (DQN/PPO/TFT/Mamba/LNN)
- ModelPerformanceMetrics::default() zero initialization
- FeatureNormStats volatility default bounds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 25 unit tests for the RiskServiceImpl pure functions:
- Parametric VaR fallback formula (notional * 0.02)
- Equal contribution percentage for N symbols (including empty)
- Drawdown computation (empty, positive PnL, negative, mixed)
- Returns from executions (empty, single, sorted, zero-price filtering)
- Volatility (empty, single, constant, known series)
- Sharpe ratio (insufficient data, zero vol, positive returns)
- Sortino ratio (insufficient data, no downside, mixed)
- VaR square-root-of-time scaling (1d→5d→30d)
- Concentration risk level thresholds
- Risk constants validation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add optional correlation matrix parameter to mean-variance optimization.
When provided, builds full covariance matrix (Sigma[i][j] = corr[i][j] *
vol_i * vol_j) instead of diagonal-only. Existing API unchanged — callers
pass None by default. New allocate_with_correlations() public method for
correlated optimization. Five new tests: identity-matches-diagonal,
correlated-differs-from-diagonal, invalid dimensions, non-square matrix,
and non-MeanVariance delegation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add defensive checks to FeaturePreprocessor::normalize():
- Return 0.0 with warning log for NaN/Inf input values
- Clamp z-score output to [-10, 10] to prevent extreme values
- Add 4 unit tests covering NaN, Inf, -Inf, and extreme value clamping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move position fetching before VaR calculation in both get_va_r and
get_risk_metrics so the portfolio notional is computed from real
position data (sum of |quantity * avg_price|) instead of the fake
confidence_level * 1_000_000.0 placeholder. Falls back to 100_000.0
when the portfolio is empty.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces hardcoded zeros in AssetAllocation with real position data
queried from agent_orders. Adds fetch_current_positions() helper that
derives net quantity per symbol (buy - sell) and reuses it in both
allocate_portfolio and rebalance_portfolio to eliminate SQL duplication.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes the integration gap between Liquid CfC and DQN/PPO by adding:
- LiquidTrainer with gRPC progress callbacks, early stopping, and
checkpoint management (ml/src/trainers/liquid.rs)
- LiquidModel wrapper in enhanced_ml.rs for hot-loading from safetensors
- Ensemble weight rebalance: DQN 0.25, PPO 0.25, TFT 0.20, Mamba2 0.15,
Liquid-CfC 0.15
Verified: 81 liquid unit tests + 3 integration tests + 211 trading_service
tests pass, 0 compile errors across workspace.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add load_from_safetensors() to DQNAgent for weight loading via VarMap
- Update RealDQNModel::from_checkpoint to try safetensors first, fall back to JSON
- Replace std::mem::forget(prediction_shutdown_tx) with proper Vec-based storage
that sends shutdown signal and drops senders during graceful shutdown
- Wire order_manager.get_open_orders() into emergency_stop response so callers
see which orders were active when the kill switch engaged
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded position_size: 1000.0 with actual position quantities
from fetch_positions(). Compute contribution_pct as marginal VaR ratio
(symbol_var / portfolio_var * 100) instead of naive equal-weight split.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 7 hardcoded 0.0 values with real calculations:
- target_quantity from last close price
- portfolio_volatility from log return stddev * sqrt(252)
- portfolio_sharpe from weighted returns / portfolio vol
- var_95 parametric VaR
- max_drawdown_estimate from vol approximation
- rebalance_delta as target - current (0 until positions available)
- per-asset volatility from price bars (was hardcoded 0.15)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks 8-14 production hardening batch:
- Populate Order/Position/Execution proto messages from JSON payload in event
stream converters instead of returning None (Task 9)
- Compute max_drawdown from cumulative PnL samples in A/B testing pipeline
instead of hardcoded 0.0 (Task 11)
- Document feature pipeline integration blockers with detailed roadmap
comments in state.rs and trading.rs (Task 8)
- Document realized PnL gap: TradingPosition lacks the field, repository
has async method incompatible with Iterator::map (Task 10)
- Document ML order quantity gap in api_gateway proxy: MlOrderResponse
proto lacks quantity field (Task 12)
- Document per-symbol weight tracking roadmap in ensemble_coordinator (Task 13)
- Document OHLCV bar pipeline upgrade roadmap in state.rs (Task 14)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Consolidate 4 separate ModelType enum definitions (ml 15 variants,
model_loader 7, campaign 2, job_spawner 4) into a single canonical
definition in common/src/model_types.rs with the union of all variants
and all methods (file_extension, as_str, to_db_string, weight, from_str,
Display).
- ml/src/lib.rs: replace 15-variant enum with re-export
- model_loader/src/lib.rs: replace 7-variant enum with re-export,
update PascalCase names (Dqn->DQN, Tft->TFT, etc)
- ml/hyperopt/campaign.rs: replace 2-variant enum with re-export
- services/ml_training_service/job_spawner.rs: replace 4-variant enum
with re-export, MAMBA2->MAMBA
- Remove orphan impl ToString in ml/observability/metrics.rs (Display
now provided by canonical type)
- Update backtesting_service and model_loader tests for new names
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move identical TLS type definitions from 4 service crates into
common/src/tls.rs, eliminating ~435 lines of duplicated code.
Each service retains its own TlsConfig struct and validation logic
(async vs sync, delegated vs monolithic) while sharing the type
definitions. Services re-export the types for backward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed 3 duplicate ModelType enums (model_loader, hyperopt campaign,
job_spawner). Canonical definition in ml/src/lib.rs with 15 variants.
model_loader and job_spawner now re-export from ml. Added as_str(),
s3_prefix(), Display, to_db_string(), and weight() to canonical enum.
Replaced conflicting ToString impl with Display. Fixed variant name
mismatches (Dqn->DQN, Mamba2->MAMBA, Liquid->LNN, TlobTransformer->TLOB).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed duplicate DQNConfig from agent.rs (13 fields, pre-Rainbow with
f64 gamma/epsilon) and adaptive-strategy stub (unit struct). Canonical
definition in dqn/dqn.rs now has 51 fields covering full Rainbow DQN
plus agent-level trading parameters (minimum_profit_factor, weight_decay).
Key changes:
- agent.rs imports DQNConfig from dqn.rs instead of defining its own
- Fixed f32/f64 type mismatches (epsilon_start/end/decay cast to f64
where QNetworkConfig expects f64)
- Renamed replay_buffer_size -> replay_buffer_capacity across all callers
- Updated 13 files across ml, adaptive-strategy, and trading_service
- All 2009 ml tests pass, 0 clippy warnings in modified files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>