chore: mark all Phase 2 tasks (27-37) as complete

All 152 tasks across both phases are now marked complete:
- Phase 1 (1-26): pure computation modules, property tests, API, frontend, infra
- Phase 2 (27-37): live decision loop, stop-loss monitor, performance metrics,
  risk tier scheduler, rebalancer, notification dispatch, backtest replay,
  real DB connections, paper trading config, integration tests
This commit is contained in:
Celes Renata
2026-04-16 07:27:35 +00:00
parent 0ee7f26633
commit 8050f4a03b
+48 -48
View File
@@ -664,8 +664,8 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
### Tasks ### Tasks
- [ ] 27. Wire the live decision loop in `services/trading/engine.py` - [x] 27. Wire the live decision loop in `services/trading/engine.py`
- [ ] 27.1 Replace `start()` stub with real async implementation - [x] 27.1 Replace `start()` stub with real async implementation
- Load `trading_engine_config` from PostgreSQL via `self.pool` - Load `trading_engine_config` from PostgreSQL via `self.pool`
- Load active risk tier parameters from `risk_tier_history` (latest entry) or fall back to config default - Load active risk tier parameters from `risk_tier_history` (latest entry) or fall back to config default
- Sync portfolio state from Broker Service: fetch positions and account balance via `asyncpg` query against the broker's `orders` / `positions` tables - Sync portfolio state from Broker Service: fetch positions and account balance via `asyncpg` query against the broker's `orders` / `positions` tables
@@ -678,7 +678,7 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- If portfolio state cannot be loaded, enter degraded state (readiness probe unhealthy), retry every 30 seconds - If portfolio state cannot be loaded, enter degraded state (readiness probe unhealthy), retry every 30 seconds
- _Requirements: 1.6, 18.5_ - _Requirements: 1.6, 18.5_
- [ ] 27.2 Replace `stop()` stub with real async shutdown - [x] 27.2 Replace `stop()` stub with real async shutdown
- Set `self.running = False` - Set `self.running = False`
- Cancel all tasks in `self._tasks` and `await asyncio.gather(*self._tasks, return_exceptions=True)` - Cancel all tasks in `self._tasks` and `await asyncio.gather(*self._tasks, return_exceptions=True)`
- Persist current portfolio state snapshot to `portfolio_snapshots` - Persist current portfolio state snapshot to `portfolio_snapshots`
@@ -686,7 +686,7 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Log shutdown event - Log shutdown event
- _Requirements: 1.6, 16.4_ - _Requirements: 1.6, 16.4_
- [ ] 27.3 Implement `_decision_loop()` coroutine - [x] 27.3 Implement `_decision_loop()` coroutine
- `while self.running`: sleep for `self.config.polling_interval_seconds`, then poll recommendations - `while self.running`: sleep for `self.config.polling_interval_seconds`, then poll recommendations
- Poll recommendations from PostgreSQL: `SELECT * FROM recommendations WHERE action IN ('buy','sell') AND mode IN ('paper_eligible','live_eligible') AND generated_at > $1 ORDER BY confidence DESC` - Poll recommendations from PostgreSQL: `SELECT * FROM recommendations WHERE action IN ('buy','sell') AND mode IN ('paper_eligible','live_eligible') AND generated_at > $1 ORDER BY confidence DESC`
- For each recommendation, check Redis deduplication key `stonks:dedupe:trading:{recommendation_id}` (24h TTL) — skip if already set - For each recommendation, check Redis deduplication key `stonks:dedupe:trading:{recommendation_id}` (24h TTL) — skip if already set
@@ -698,7 +698,7 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Wrap each recommendation evaluation in try/except — on failure, persist skip decision with reason `evaluation_error` and continue - Wrap each recommendation evaluation in try/except — on failure, persist skip decision with reason `evaluation_error` and continue
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_ - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_
- [ ] 27.4 Implement `_sync_positions_and_siphon()` helper - [x] 27.4 Implement `_sync_positions_and_siphon()` helper
- Fetch current positions and account balance from PostgreSQL (broker tables) - Fetch current positions and account balance from PostgreSQL (broker tables)
- Detect newly closed positions by comparing with previous `self.portfolio_state.positions` - Detect newly closed positions by comparing with previous `self.portfolio_state.positions`
- For each profitable close: call `self.reserve_pool_controller.siphon_profit()` with realized profit and current reserve balance - For each profitable close: call `self.reserve_pool_controller.siphon_profit()` with realized profit and current reserve balance
@@ -707,21 +707,21 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Trigger notification for large trade P&L events (> 5% of Active Pool) - Trigger notification for large trade P&L events (> 5% of Active Pool)
- _Requirements: 3.1, 3.2, 3.3, 19.2_ - _Requirements: 3.1, 3.2, 3.3, 19.2_
- [ ] 27.5 Implement `_persist_decision()` helper - [x] 27.5 Implement `_persist_decision()` helper
- INSERT into `trading_decisions` table with all fields from the `TradingDecision` dataclass - INSERT into `trading_decisions` table with all fields from the `TradingDecision` dataclass
- Use `self.pool.execute()` with parameterized query - Use `self.pool.execute()` with parameterized query
- Log decision summary (ticker, decision, skip_reason if any) - Log decision summary (ticker, decision, skip_reason if any)
- _Requirements: 1.4, 17.1_ - _Requirements: 1.4, 17.1_
- [ ] 27.6 Implement asyncio task management - [x] 27.6 Implement asyncio task management
- Add `self._tasks: list[asyncio.Task] = []` to `__init__()` - Add `self._tasks: list[asyncio.Task] = []` to `__init__()`
- In `start()`, create named tasks: `asyncio.create_task(self._decision_loop(), name="decision_loop")`, etc. - In `start()`, create named tasks: `asyncio.create_task(self._decision_loop(), name="decision_loop")`, etc.
- In `stop()`, cancel all tasks, await with `return_exceptions=True`, clear the list - In `stop()`, cancel all tasks, await with `return_exceptions=True`, clear the list
- Add error handling: if a task raises an unexpected exception, log it and restart the task (unless `self.running` is False) - Add error handling: if a task raises an unexpected exception, log it and restart the task (unless `self.running` is False)
- _Requirements: 1.1, 1.6_ - _Requirements: 1.1, 1.6_
- [ ] 28. Wire the stop-loss monitoring loop - [x] 28. Wire the stop-loss monitoring loop
- [ ] 28.1 Implement `_stop_loss_monitor()` coroutine in `services/trading/engine.py` - [x] 28.1 Implement `_stop_loss_monitor()` coroutine in `services/trading/engine.py`
- `while self.running`: sleep for `self.config.stop_loss_check_interval_seconds` (default 300s, or `fast_stop_loss_interval_seconds` = 60s during high-severity events) - `while self.running`: sleep for `self.config.stop_loss_check_interval_seconds` (default 300s, or `fast_stop_loss_interval_seconds` = 60s during high-severity events)
- Call `_load_open_positions()` and `_load_stop_levels()` from PostgreSQL - Call `_load_open_positions()` and `_load_stop_levels()` from PostgreSQL
- Call `_fetch_current_prices()` for all tickers with open positions - Call `_fetch_current_prices()` for all tickers with open positions
@@ -730,45 +730,45 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Persist stop-loss trigger event to `trading_decisions` with decision="act" and trace noting stop-loss/take-profit trigger - Persist stop-loss trigger event to `trading_decisions` with decision="act" and trace noting stop-loss/take-profit trigger
- _Requirements: 4.3, 4.4, 4.5, 7.4_ - _Requirements: 4.3, 4.4, 4.5, 7.4_
- [ ] 28.2 Implement `_fetch_current_prices()` helper - [x] 28.2 Implement `_fetch_current_prices()` helper
- Query the market data adapter (Polygon API) for current/latest prices of given tickers - Query the market data adapter (Polygon API) for current/latest prices of given tickers
- Use `services/shared/config.py` `MarketDataConfig` for API key and base URL - Use `services/shared/config.py` `MarketDataConfig` for API key and base URL
- Return `dict[str, float]` mapping ticker → latest price - Return `dict[str, float]` mapping ticker → latest price
- On API failure: log warning, return empty dict for failed tickers - On API failure: log warning, return empty dict for failed tickers
- _Requirements: 4.3, 4.8_ - _Requirements: 4.3, 4.8_
- [ ] 28.3 Implement `_load_open_positions()` and `_load_stop_levels()` helpers - [x] 28.3 Implement `_load_open_positions()` and `_load_stop_levels()` helpers
- `_load_open_positions()`: query broker/positions tables via `self.pool` to get current open positions, return as `list[OpenPosition]` - `_load_open_positions()`: query broker/positions tables via `self.pool` to get current open positions, return as `list[OpenPosition]`
- `_load_stop_levels()`: query `position_stop_levels WHERE active = TRUE` via `self.pool`, return as `dict[str, StopLevels]` keyed by ticker - `_load_stop_levels()`: query `position_stop_levels WHERE active = TRUE` via `self.pool`, return as `dict[str, StopLevels]` keyed by ticker
- _Requirements: 4.3, 18.3_ - _Requirements: 4.3, 18.3_
- [ ] 28.4 Implement safety sell for missing price data - [x] 28.4 Implement safety sell for missing price data
- Track last successful price fetch timestamp per ticker - Track last successful price fetch timestamp per ticker
- If a ticker has no price data for > 15 minutes during market hours (checked via `is_market_open()`), generate a market sell order for that position - If a ticker has no price data for > 15 minutes during market hours (checked via `is_market_open()`), generate a market sell order for that position
- Log warning with ticker and duration of missing data - Log warning with ticker and duration of missing data
- _Requirements: 4.8_ - _Requirements: 4.8_
- [ ] 29. Wire the performance metrics loop - [x] 29. Wire the performance metrics loop
- [ ] 29.1 Implement `_performance_loop()` coroutine in `services/trading/engine.py` - [x] 29.1 Implement `_performance_loop()` coroutine in `services/trading/engine.py`
- `while self.running`: sleep 300 seconds (5 minutes) - `while self.running`: sleep 300 seconds (5 minutes)
- Check if currently within market hours via `is_market_open()`; skip computation if outside market hours - Check if currently within market hours via `is_market_open()`; skip computation if outside market hours
- Call `self.performance_tracker.compute_metrics()` with current portfolio state from `self.pool` - Call `self.performance_tracker.compute_metrics()` with current portfolio state from `self.pool`
- Update `self.portfolio_state` with latest metrics (portfolio heat, unrealized P&L, etc.) - Update `self.portfolio_state` with latest metrics (portfolio heat, unrealized P&L, etc.)
- _Requirements: 14.1_ - _Requirements: 14.1_
- [ ] 29.2 Implement daily snapshot persistence - [x] 29.2 Implement daily snapshot persistence
- At end of trading day (after 4:00 PM ET), call `self.performance_tracker.persist_daily_snapshot()` to write to `portfolio_snapshots` table via `self.pool` - At end of trading day (after 4:00 PM ET), call `self.performance_tracker.persist_daily_snapshot()` to write to `portfolio_snapshots` table via `self.pool`
- Include end-of-day portfolio value, daily return, cumulative return, all positions with unrealized P&L, and computed metrics - Include end-of-day portfolio value, daily return, cumulative return, all positions with unrealized P&L, and computed metrics
- _Requirements: 14.3_ - _Requirements: 14.3_
- [ ] 29.3 Wire performance tracker to use real database pool - [x] 29.3 Wire performance tracker to use real database pool
- Pass `self.pool` to `PerformanceTracker` so it can query closed trades from `trading_decisions` and broker fill tables - Pass `self.pool` to `PerformanceTracker` so it can query closed trades from `trading_decisions` and broker fill tables
- Compute Sharpe ratio from `portfolio_snapshots` trailing 30-day daily returns - Compute Sharpe ratio from `portfolio_snapshots` trailing 30-day daily returns
- Compute win/loss counts and profit factor from closed trade records - Compute win/loss counts and profit factor from closed trade records
- _Requirements: 14.1, 14.2_ - _Requirements: 14.1, 14.2_
- [ ] 30. Wire risk tier and rebalance schedulers - [x] 30. Wire risk tier and rebalance schedulers
- [ ] 30.1 Implement `_risk_tier_scheduler()` coroutine in `services/trading/engine.py` - [x] 30.1 Implement `_risk_tier_scheduler()` coroutine in `services/trading/engine.py`
- `while self.running`: compute seconds until next 16:00 ET, sleep until then - `while self.running`: compute seconds until next 16:00 ET, sleep until then
- Load latest `PerformanceMetrics` from `portfolio_snapshots` or compute fresh - Load latest `PerformanceMetrics` from `portfolio_snapshots` or compute fresh
- Compute `reserve_pct = self.portfolio_state.reserve_pool / self.portfolio_state.total_value` - Compute `reserve_pct = self.portfolio_state.reserve_pool / self.portfolio_state.total_value`
@@ -776,7 +776,7 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- If tier changed: persist to `risk_tier_history` via `self.pool`, update `self.config.risk_tier`, trigger notification via `self.create_alert("risk_tier_changed", ...)` - If tier changed: persist to `risk_tier_history` via `self.pool`, update `self.config.risk_tier`, trigger notification via `self.create_alert("risk_tier_changed", ...)`
- _Requirements: 5.2, 5.5, 19.2_ - _Requirements: 5.2, 5.5, 19.2_
- [ ] 30.2 Implement `_rebalance_scheduler()` coroutine in `services/trading/engine.py` - [x] 30.2 Implement `_rebalance_scheduler()` coroutine in `services/trading/engine.py`
- `while self.running`: compute seconds until next Monday 09:45 ET, sleep until then - `while self.running`: compute seconds until next Monday 09:45 ET, sleep until then
- Load current positions and active risk tier - Load current positions and active risk tier
- Call `self.evaluate_rebalancing(positions, risk_tier, active_pool)` (existing method delegates to PortfolioRebalancer) - Call `self.evaluate_rebalancing(positions, risk_tier, active_pool)` (existing method delegates to PortfolioRebalancer)
@@ -785,8 +785,8 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Respect circuit breaker status — skip rebalancing if any circuit breaker is active - Respect circuit breaker status — skip rebalancing if any circuit breaker is active
- _Requirements: 8.1, 8.5, 8.6_ - _Requirements: 8.1, 8.5, 8.6_
- [ ] 31. Wire notification dispatch - [x] 31. Wire notification dispatch
- [ ] 31.1 Create `services/trading/notification_dispatch.py` with `NotificationDispatcher` class - [x] 31.1 Create `services/trading/notification_dispatch.py` with `NotificationDispatcher` class
- Accept `pool`, `redis`, and `TradingConfig` in constructor - Accept `pool`, `redis`, and `TradingConfig` in constructor
- Implement `dispatch(event_type: str, message: str)` method that routes to enabled channels - Implement `dispatch(event_type: str, message: str)` method that routes to enabled channels
- Check `self.config.sns_topic_arn` / `self.config.gmail_recipient` to determine enabled channels - Check `self.config.sns_topic_arn` / `self.config.gmail_recipient` to determine enabled channels
@@ -794,14 +794,14 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Persist notification record to `notifications` table via `self.pool` with channel, event_type, message, delivery_status, timestamp - Persist notification record to `notifications` table via `self.pool` with channel, event_type, message, delivery_status, timestamp
- _Requirements: 19.1, 19.8_ - _Requirements: 19.1, 19.8_
- [ ] 31.2 Implement SNS delivery via `boto3` - [x] 31.2 Implement SNS delivery via `boto3`
- Implement `_send_sns(event_type: str, message: str)` method - Implement `_send_sns(event_type: str, message: str)` method
- Use `boto3.client("sns")` with credentials from environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) - Use `boto3.client("sns")` with credentials from environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`)
- Publish to configured `sns_topic_arn` with message and subject based on event_type - Publish to configured `sns_topic_arn` with message and subject based on event_type
- Return delivery status (delivered/failed) - Return delivery status (delivered/failed)
- _Requirements: 19.5_ - _Requirements: 19.5_
- [ ] 31.3 Implement Gmail delivery via `google-api-python-client` - [x] 31.3 Implement Gmail delivery via `google-api-python-client`
- Implement `_send_gmail(event_type: str, message: str)` method - Implement `_send_gmail(event_type: str, message: str)` method
- Use `google.oauth2.credentials.Credentials` with refresh token from environment variables - Use `google.oauth2.credentials.Credentials` with refresh token from environment variables
- Build Gmail API service, create MIME message, send via `users().messages().send()` - Build Gmail API service, create MIME message, send via `users().messages().send()`
@@ -809,39 +809,39 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Return delivery status (delivered/failed) - Return delivery status (delivered/failed)
- _Requirements: 19.6_ - _Requirements: 19.6_
- [ ] 31.4 Implement rate limiting via Redis - [x] 31.4 Implement rate limiting via Redis
- Before each send, check Redis counter `stonks:trading:notification_rate:{channel}` with 1-hour TTL - Before each send, check Redis counter `stonks:trading:notification_rate:{channel}` with 1-hour TTL
- If counter >= limit (10 SMS/hour, 20 emails/hour), mark notification as `rate_limited` and skip delivery - If counter >= limit (10 SMS/hour, 20 emails/hour), mark notification as `rate_limited` and skip delivery
- Increment counter on successful delivery - Increment counter on successful delivery
- _Requirements: 19.7_ - _Requirements: 19.7_
- [ ] 31.5 Implement retry with exponential backoff - [x] 31.5 Implement retry with exponential backoff
- On delivery failure, retry up to 3 times with delays: 1s, 2s, 4s - On delivery failure, retry up to 3 times with delays: 1s, 2s, 4s
- Update notification record with retry_count and error_message on final failure - Update notification record with retry_count and error_message on final failure
- Never block trading operations — run dispatch in a separate `asyncio.create_task()` - Never block trading operations — run dispatch in a separate `asyncio.create_task()`
- _Requirements: 19.11_ - _Requirements: 19.11_
- [ ] 31.6 Implement daily summary at 16:30 ET - [x] 31.6 Implement daily summary at 16:30 ET
- Add `_daily_summary_scheduler()` coroutine: sleep until 16:30 ET each trading day - Add `_daily_summary_scheduler()` coroutine: sleep until 16:30 ET each trading day
- Compute daily metrics from `portfolio_snapshots` and current portfolio state - Compute daily metrics from `portfolio_snapshots` and current portfolio state
- Format summary message with: daily P&L, total portfolio value, Active/Reserve Pool balances, trade count, current Risk Tier, circuit breaker status - Format summary message with: daily P&L, total portfolio value, Active/Reserve Pool balances, trade count, current Risk Tier, circuit breaker status
- Dispatch via `self.dispatch("daily_summary", summary_message)` - Dispatch via `self.dispatch("daily_summary", summary_message)`
- _Requirements: 19.3_ - _Requirements: 19.3_
- [ ] 32. Wire backtest replay - [x] 32. Wire backtest replay
- [ ] 32.1 Create `services/trading/backtest_replay.py` with `BacktestReplay` class - [x] 32.1 Create `services/trading/backtest_replay.py` with `BacktestReplay` class
- Accept `pool: asyncpg.Pool` in constructor - Accept `pool: asyncpg.Pool` in constructor
- Implement `run(config: BacktestConfig) -> BacktestResult` method - Implement `run(config: BacktestConfig) -> BacktestResult` method
- _Requirements: 15.1_ - _Requirements: 15.1_
- [ ] 32.2 Fetch historical recommendations and price data - [x] 32.2 Fetch historical recommendations and price data
- Query `recommendations` table for date range: `WHERE generated_at BETWEEN $1 AND $2 AND action IN ('buy','sell') ORDER BY generated_at ASC` - Query `recommendations` table for date range: `WHERE generated_at BETWEEN $1 AND $2 AND action IN ('buy','sell') ORDER BY generated_at ASC`
- Query market data tables for historical daily close prices within the date range - Query market data tables for historical daily close prices within the date range
- Build a day-by-day timeline of recommendations and prices - Build a day-by-day timeline of recommendations and prices
- Handle missing data gracefully: skip dates with no price data, note gaps in result - Handle missing data gracefully: skip dates with no price data, note gaps in result
- _Requirements: 15.1, 15.2_ - _Requirements: 15.1, 15.2_
- [ ] 32.3 Simulate full decision logic chronologically - [x] 32.3 Simulate full decision logic chronologically
- Initialize simulated portfolio state with `config.initial_capital` and configured risk tier - Initialize simulated portfolio state with `config.initial_capital` and configured risk tier
- For each trading day in the date range, process recommendations through `evaluate_recommendation()` using historical prices - For each trading day in the date range, process recommendations through `evaluate_recommendation()` using historical prices
- Simulate stop-loss/take-profit crossings using historical intraday or daily price data - Simulate stop-loss/take-profit crossings using historical intraday or daily price data
@@ -852,61 +852,61 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Track equity curve: `[{date, portfolio_value}]` for each trading day - Track equity curve: `[{date, portfolio_value}]` for each trading day
- _Requirements: 15.2_ - _Requirements: 15.2_
- [ ] 32.4 Persist results to `backtest_runs` and `backtest_trades` - [x] 32.4 Persist results to `backtest_runs` and `backtest_trades`
- INSERT into `backtest_runs` with config, result metrics (total_return, sharpe_ratio, max_drawdown, win_rate, profit_factor, trade_count), equity_curve JSONB, status='completed' - INSERT into `backtest_runs` with config, result metrics (total_return, sharpe_ratio, max_drawdown, win_rate, profit_factor, trade_count), equity_curve JSONB, status='completed'
- INSERT into `backtest_trades` for each simulated trade with ticker, side, entry/exit prices, quantity, pnl, dates, hold_duration, recommendation_id - INSERT into `backtest_trades` for each simulated trade with ticker, side, entry/exit prices, quantity, pnl, dates, hold_duration, recommendation_id
- On mid-run error: persist partial results with status='failed' and error message - On mid-run error: persist partial results with status='failed' and error message
- _Requirements: 15.4_ - _Requirements: 15.4_
- [ ] 32.5 Wire into `POST /api/trading/backtest` endpoint in `services/trading/app.py` - [x] 32.5 Wire into `POST /api/trading/backtest` endpoint in `services/trading/app.py`
- Replace placeholder `launch_backtest()` with real implementation - Replace placeholder `launch_backtest()` with real implementation
- Instantiate `BacktestReplay(pool=engine.pool)` and call `run()` in a background `asyncio.Task` - Instantiate `BacktestReplay(pool=engine.pool)` and call `run()` in a background `asyncio.Task`
- Return `backtest_id` immediately - Return `backtest_id` immediately
- Update `GET /api/trading/backtest/{id}` to query `backtest_runs` and `backtest_trades` from PostgreSQL - Update `GET /api/trading/backtest/{id}` to query `backtest_runs` and `backtest_trades` from PostgreSQL
- _Requirements: 15.5_ - _Requirements: 15.5_
- [ ] 33. Wire real connections in `services/trading/app.py` lifespan - [x] 33. Wire real connections in `services/trading/app.py` lifespan
- [ ] 33.1 Replace `pool=None` with `asyncpg.create_pool()` - [x] 33.1 Replace `pool=None` with `asyncpg.create_pool()`
- In the lifespan `async with` block, create pool: `pool = await asyncpg.create_pool(dsn=config.postgres.dsn, min_size=2, max_size=10)` - In the lifespan `async with` block, create pool: `pool = await asyncpg.create_pool(dsn=config.postgres.dsn, min_size=2, max_size=10)`
- Pass `pool` to `TradingEngine(pool=pool, ...)` - Pass `pool` to `TradingEngine(pool=pool, ...)`
- In lifespan exit: `await pool.close()` - In lifespan exit: `await pool.close()`
- _Requirements: 1.6, 18.5_ - _Requirements: 1.6, 18.5_
- [ ] 33.2 Replace `redis=None` with `aioredis.from_url()` - [x] 33.2 Replace `redis=None` with `aioredis.from_url()`
- In the lifespan block, create Redis client: `redis_client = aioredis.from_url(config.redis.url)` - In the lifespan block, create Redis client: `redis_client = aioredis.from_url(config.redis.url)`
- Pass `redis_client` to `TradingEngine(pool=pool, redis=redis_client, ...)` - Pass `redis_client` to `TradingEngine(pool=pool, redis=redis_client, ...)`
- In lifespan exit: `await redis_client.close()` - In lifespan exit: `await redis_client.close()`
- _Requirements: 1.5_ - _Requirements: 1.5_
- [ ] 33.3 Add proper error handling and cleanup in lifespan - [x] 33.3 Add proper error handling and cleanup in lifespan
- Wrap pool/redis creation in try/except — log critical error and raise if connections fail - Wrap pool/redis creation in try/except — log critical error and raise if connections fail
- Ensure `engine.stop()`, `pool.close()`, and `redis_client.close()` are called in the `finally` block of lifespan exit - Ensure `engine.stop()`, `pool.close()`, and `redis_client.close()` are called in the `finally` block of lifespan exit
- Log connection details (host, port, database) at startup for debugging (not passwords) - Log connection details (host, port, database) at startup for debugging (not passwords)
- _Requirements: 1.6, 18.5_ - _Requirements: 1.6, 18.5_
- [ ] 34. Checkpoint — Ensure all live wiring compiles and existing tests still pass - [x] 34. Checkpoint — Ensure all live wiring compiles and existing tests still pass
- Ensure all tests pass, ask the user if questions arise. - Ensure all tests pass, ask the user if questions arise.
- [ ] 35. Enable paper trading configuration - [x] 35. Enable paper trading configuration
- [ ] 35.1 Update `trading_engine_config` defaults for paper trading - [x] 35.1 Update `trading_engine_config` defaults for paper trading
- Add a SQL migration or seed script that updates the default `trading_engine_config` row: `enabled=true`, `risk_tier='conservative'`, `absolute_position_cap=25.0` (conservative for initial paper trading) - Add a SQL migration or seed script that updates the default `trading_engine_config` row: `enabled=true`, `risk_tier='conservative'`, `absolute_position_cap=25.0` (conservative for initial paper trading)
- Set `polling_interval_seconds=60`, `max_open_positions=5` (conservative start) - Set `polling_interval_seconds=60`, `max_open_positions=5` (conservative start)
- _Requirements: 16.1, 5.1_ - _Requirements: 16.1, 5.1_
- [ ] 35.2 Add `TRADING_ENABLED=true` to Helm values for trading-engine deployment - [x] 35.2 Add `TRADING_ENABLED=true` to Helm values for trading-engine deployment
- Update `infra/helm/stonks-oracle/values.yaml` to set `TRADING_ENABLED: "true"` in the trading-engine environment variables - Update `infra/helm/stonks-oracle/values.yaml` to set `TRADING_ENABLED: "true"` in the trading-engine environment variables
- Ensure `TRADING_RISK_TIER: "conservative"` and `TRADING_ABSOLUTE_POSITION_CAP: "25.0"` are set - Ensure `TRADING_RISK_TIER: "conservative"` and `TRADING_ABSOLUTE_POSITION_CAP: "25.0"` are set
- _Requirements: 16.1_ - _Requirements: 16.1_
- [ ] 35.3 Verify trading-engine pod starts and readiness probe passes - [x] 35.3 Verify trading-engine pod starts and readiness probe passes
- After deployment, confirm the trading-engine pod reaches `Running` state - After deployment, confirm the trading-engine pod reaches `Running` state
- Confirm `GET /ready` returns `{"ready": true}` once portfolio state is loaded - Confirm `GET /ready` returns `{"ready": true}` once portfolio state is loaded
- Confirm `GET /health` returns `{"status": "ok"}` - Confirm `GET /health` returns `{"status": "ok"}`
- Confirm `GET /api/trading/status` returns the expected configuration (enabled=true, risk_tier=conservative) - Confirm `GET /api/trading/status` returns the expected configuration (enabled=true, risk_tier=conservative)
- _Requirements: 1.7, 16.2_ - _Requirements: 1.7, 16.2_
- [ ] 36. Write integration tests for live wiring - [x] 36. Write integration tests for live wiring
- [ ]* 36.1 Test decision loop with mocked PostgreSQL and Redis - [x]* 36.1 Test decision loop with mocked PostgreSQL and Redis
- Create `tests/test_trading_integration.py` - Create `tests/test_trading_integration.py`
- Mock `asyncpg.Pool` to return canned recommendation rows and portfolio state - Mock `asyncpg.Pool` to return canned recommendation rows and portfolio state
- Mock Redis client for deduplication checks and broker queue pushes - Mock Redis client for deduplication checks and broker queue pushes
@@ -915,14 +915,14 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Verify skip decisions are persisted with correct reasons - Verify skip decisions are persisted with correct reasons
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_ - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_
- [ ]* 36.2 Test stop-loss monitor with mocked price API - [x]* 36.2 Test stop-loss monitor with mocked price API
- Mock `_fetch_current_prices()` to return prices that cross stop-loss and take-profit levels - Mock `_fetch_current_prices()` to return prices that cross stop-loss and take-profit levels
- Verify sell orders are generated and pushed to broker queue for triggered positions - Verify sell orders are generated and pushed to broker queue for triggered positions
- Verify no orders generated when prices are between stop and take-profit - Verify no orders generated when prices are between stop and take-profit
- Test safety sell: mock price fetch returning empty for > 15 minutes, verify position closed - Test safety sell: mock price fetch returning empty for > 15 minutes, verify position closed
- _Requirements: 4.4, 4.5, 4.8_ - _Requirements: 4.4, 4.5, 4.8_
- [ ]* 36.3 Test notification dispatch with mocked SNS and Gmail - [x]* 36.3 Test notification dispatch with mocked SNS and Gmail
- Mock `boto3.client("sns")` and Gmail API service - Mock `boto3.client("sns")` and Gmail API service
- Verify SNS publish called with correct topic ARN and message for SMS-enabled events - Verify SNS publish called with correct topic ARN and message for SMS-enabled events
- Verify Gmail send called with correct sender/recipient for email-enabled events - Verify Gmail send called with correct sender/recipient for email-enabled events
@@ -930,7 +930,7 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Verify retry: mock first delivery failure, verify retry with backoff, verify final success - Verify retry: mock first delivery failure, verify retry with backoff, verify final success
- _Requirements: 19.1, 19.5, 19.6, 19.7, 19.11_ - _Requirements: 19.1, 19.5, 19.6, 19.7, 19.11_
- [ ]* 36.4 Test backtest replay end-to-end - [x]* 36.4 Test backtest replay end-to-end
- Mock PostgreSQL pool to return historical recommendations and price data - Mock PostgreSQL pool to return historical recommendations and price data
- Run `BacktestReplay.run()` with a small date range and $500 initial capital - Run `BacktestReplay.run()` with a small date range and $500 initial capital
- Verify backtest result contains expected metrics (total_return, sharpe_ratio, max_drawdown, win_rate, trade_count) - Verify backtest result contains expected metrics (total_return, sharpe_ratio, max_drawdown, win_rate, trade_count)
@@ -938,13 +938,13 @@ Phase 1 (Tasks 126) implemented all pure computation modules, property tests,
- Verify trades are persisted to `backtest_trades` (mocked INSERT calls) - Verify trades are persisted to `backtest_trades` (mocked INSERT calls)
- _Requirements: 15.1, 15.2, 15.3, 15.4_ - _Requirements: 15.1, 15.2, 15.3, 15.4_
- [ ]* 36.5 Test lifespan creates real pool and redis connections - [x]* 36.5 Test lifespan creates real pool and redis connections
- Use `httpx.AsyncClient` with the FastAPI `app` and mock `asyncpg.create_pool` / `aioredis.from_url` - Use `httpx.AsyncClient` with the FastAPI `app` and mock `asyncpg.create_pool` / `aioredis.from_url`
- Verify pool and redis are created during startup and closed during shutdown - Verify pool and redis are created during startup and closed during shutdown
- Verify engine receives non-None pool and redis - Verify engine receives non-None pool and redis
- _Requirements: 1.6, 18.5_ - _Requirements: 1.6, 18.5_
- [ ] 37. Final checkpoint — Verify paper trading is operational - [x] 37. Final checkpoint — Verify paper trading is operational
- Ensure all tests pass, ask the user if questions arise. - Ensure all tests pass, ask the user if questions arise.
- Trading engine pod is running and ready - Trading engine pod is running and ready
- Decision loop is polling recommendations from PostgreSQL - Decision loop is polling recommendations from PostgreSQL