# Trading Bot — Remaining Work Plan **Created**: 2026-02-25 **Status**: Complete ## Overview This plan addresses all remaining TODOs, placeholders, and incomplete features in the trading bot. Tasks are ordered by dependency — earlier tasks unblock later ones. --- ## Task 1: Learning engine — expand default weights to all 9 strategies **Status**: [x] Complete **Files**: `services/learning_engine/main.py` **Problem**: `_load_strategy_weights()` defaults to 3 strategies (momentum, mean_reversion, news_driven) but the signal generator uses all 9. When Redis has no cached weights, the learning engine operates on a stale subset. **Fix**: Change the default fallback to all 9 strategies with equal weights (~0.111 each), matching `scripts/seed_strategies.py`. --- ## Task 2: Portfolio sync — compute actual daily P&L **Status**: [x] Complete **Files**: `services/api_gateway/tasks/portfolio_sync.py` **Problem**: `daily_pnl` is hardcoded to `0.0` in `_sync_once()` (line 60). **Fix**: Before inserting the new snapshot, query the most recent prior snapshot for today. Compute `daily_pnl = current_total_value - day_start_total_value`. If no prior snapshot exists for today, the first snapshot's daily_pnl is 0.0. --- ## Task 3: Portfolio API — cumulative P&L from first snapshot **Status**: [x] Complete **Files**: `services/api_gateway/routes/portfolio.py` **Problem**: `total_pnl` on line 90 just returns `latest.daily_pnl` instead of the cumulative P&L since inception. **Fix**: Query the earliest `PortfolioSnapshot` and compute: - `total_pnl = latest.total_value - earliest.total_value` - `total_pnl_pct = total_pnl / earliest.total_value * 100` --- ## Task 4: Portfolio API — read trading pause flag from Redis **Status**: [x] Complete **Files**: `services/api_gateway/routes/portfolio.py` **Problem**: `trading_active` is hardcoded to `True` (line 92). **Fix**: Read the `trading:paused` key from Redis (same key used by `services/api_gateway/routes/controls.py`). Return `trading_active = not paused`. --- ## Task 5: Portfolio metrics — compute max drawdown from snapshots **Status**: [x] Complete **Files**: `services/api_gateway/routes/portfolio.py` **Problem**: `max_drawdown` is hardcoded to `0.0` (line 170). **Fix**: Query all `PortfolioSnapshot` rows, compute the running peak and maximum percentage drop from peak. Return as a positive decimal (e.g. 0.12 = 12% drawdown). --- ## Task 6: Portfolio metrics — compute avg hold duration from trade outcomes **Status**: [x] Complete **Files**: `services/api_gateway/routes/portfolio.py` **Problem**: `avg_hold_duration` is hardcoded to `"0h"` (line 172). **Fix**: Query `TradeOutcome` rows, average the `hold_duration_seconds` column, and format as a human-readable string (e.g. "4h 30m", "2d 6h"). --- ## Task 7: Trade executor — respect the trading pause flag **Status**: [x] Complete **Files**: `services/trade_executor/risk_manager.py` **Problem**: The controls API sets/clears `trading:paused` in Redis, but the trade executor's `RiskManager.check_risk()` never checks it. Pausing has no effect. **Fix**: Accept a Redis client in `RiskManager.__init__()`. Add a check at the top of `check_risk()` that reads `trading:paused` and rejects with `"trading_paused"` if set. Wire the Redis client through from `trade_executor/main.py`. --- ## Task 8: Learning engine — resolve placeholder strategy_id **Status**: [x] Complete **Files**: `services/learning_engine/main.py` **Problem**: `WeightAdjustment` on line 207 uses `strategy_id=UUID(int=0)` as a placeholder. This means weight history records can't be linked to the correct strategy. **Fix**: On startup, load the `strategies` table into a `name -> UUID` lookup dict. Use this lookup when building `WeightAdjustment` records. Fall back to `UUID(int=0)` only if the strategy name isn't in the DB. --- ## Task 9: Pass strategy_sources from signal through to learning engine **Status**: [x] Complete **Files**: `services/trade_executor/main.py`, `services/learning_engine/main.py` **Problem**: `TradeExecution` doesn't carry `strategy_sources`. When the learning engine stores the opening trade (line 136), `strategy_sources` is always `[]`. This means credit attribution (`evaluator.attribute_credit()`) has no strategies to reward. **Fix**: 1. Add `strategy_sources: list[str] = []` to the `TradeExecution` schema. 2. In the trade executor's `process_signal()`, copy `signal.strategy_sources` into the execution message. 3. In the learning engine, read `trade.strategy_sources` (via the extended schema) and store them in the opening trade record. --- ## Task 10: Update CLAUDE.md and add top-level README **Status**: [x] Complete **Files**: `.claude/CLAUDE.md`, `README.md` **Fix**: - Remove "No CI/CD pipeline yet" from Known Issues (Woodpecker exists). - Update strategy count references (3 → 9) in Project Structure section. - Update model count (14 → 16 tables). - Create a concise top-level `README.md` with: project description, architecture diagram (text), quickstart (docker compose up), dev setup, and test commands. --- ## Execution Order Tasks are grouped by dependency: **Group A (independent, do first)**: - Task 1 (learning engine weights) - Task 7 (trade executor pause flag) - Task 9 (strategy_sources passthrough) **Group B (depends on nothing, can parallel with A)**: - Task 2 (portfolio sync daily P&L) - Task 8 (learning engine strategy_id lookup) **Group C (depends on Task 2)**: - Task 3 (cumulative P&L API) - Task 4 (trading_active flag API) - Task 5 (max drawdown API) - Task 6 (avg hold duration API) **Group D (after all code changes)**: - Task 10 (documentation) --- ## Test Strategy After each task, run the relevant unit tests: ```bash python -m pytest tests/ -v -m "not integration" --tb=short ``` Existing tests should continue to pass. New tests may be needed for: - Task 7: risk manager pause check - Task 9: strategy_sources in TradeExecution schema