Dashboard Cohesion Fixes - Research Notes
Experiment Overview
| Item |
Details |
| Date |
2025-02-11 |
| Goal |
Fix 13 integration bugs and test gaps between the backtest engine, web dashboard (FastAPI + Vue.js), and live trader so all three components work cohesively |
| Environment |
Windows 10, Python 3.11, FastAPI, Vue.js 3, pytest |
| Status |
Success - 818 tests passing (93 new), 0 failures |
Context
The backtest engine, FastAPI web dashboard, and live trader were architecturally sound individually but had specific wiring issues at the seams where they communicate. The gate_status.json file serves as IPC between scripts/live_trader.py (writer) and the dashboard API (reader), but many fields the dashboard expected were never written. The broker API wrapper had parameter name mismatches, and there was zero test coverage for the API layer and live trader decision logic.
Verified Workflow
Phase 1: Critical Bugs
- Fix
broker.submit_order(order_type=...) to broker.submit_order(type=...) in alpaca_trading/api/routes/orders.py (3 locations)
- Extend
GateStatus dataclass with price, regime, magnitude fields
- Extend
write_gate_status() with market_regime, buy_threshold, sell_threshold params
- Update
compute_gate_status_from_result() to populate new fields from feats dict
- Replace duplicate
load_gate_status() in signals.py with lazy import from server.py
Phase 2: Functional Completeness
- Add "New Trade" button wiring in
App.vue for the existing but unreachable TradeControls modal
- Add incremental log broadcasting in
DataBroadcaster via fetch_logs callback
- Improve empty-state handling in
AccountChart.vue
Phase 3: Tests (93 new tests)
tests/test_api_server.py - 41 tests: FastAPI TestClient for all REST routes + WebSocket
tests/test_live_trader.py - 22 tests: GateStatus, write_gate_status, compute_gate_status_from_result
tests/test_backtest.py - 30 tests: TestRealisticBacktestEngine (GARCH sizing, drawdown, guardrails)
Phase 4: Cleanup
- Replace hardcoded
obs_dim in (4700, 4800, ...) with dynamic get_target_features_from_obs_dim()
- Centralize PnL cost constants from
DashboardConfig defaults
Failed Attempts (Critical)
| Attempt |
Why it Failed |
Lesson Learned |
Module-level import from ..server import get_gate_status_data in signals.py |
Circular import: signals.py -> server.py -> routes/init.py -> signals.py |
Use lazy import inside a function wrapper: def _get_gate_data(): from ..server import get_gate_status_data; return get_gate_status_data() |
Using replace_all=True to rename get_gate_status_data() -> _get_gate_data() in signals.py |
Also replaced the call inside the wrapper function, creating infinite recursion |
Never use replace_all when the old string appears in the replacement's own definition |
hasattr(model, 'obs_dim') to guard obs_dim arithmetic in backtest engine |
Mock objects return True for hasattr on ANY attribute; mock.obs_dim is a Mock, not int |
Use isinstance(getattr(model, 'obs_dim', None), int) to guard against both missing attributes and Mock objects |
Patching alpaca_trading.api.routes.market.get_market_session in tests |
Import wasn't resolved at module level due to lazy loading |
Patch at the source: alpaca_trading.market_hours.get_market_session |
Using @pytest.mark.asyncio for ConnectionManager tests |
Methods being tested were synchronous; pytest-asyncio not properly configured |
Check if methods under test are truly async before marking tests |
| Loss streak cooldown test: checking cooldown expired at bar 200 |
Loss streak of 3 was still active, re-triggering cooldown at bar 200 |
Insert a win (_update_win_rate_tracking(True)) to reset streak before testing past-cooldown behavior |
Final Parameters
Gate Status JSON Structure (write_gate_status output)
{
"updated_at": "2025-02-11T12:00:00Z",
"market_regime": {"regime": "trending", "volatility": "medium", "garch_forecast": 0.18},
"buy_threshold": 0.55,
"sell_threshold": -0.45,
"symbols": {
"AAPL": {
"signal": 1,
"confidence": 0.72,
"final_status": "READY",
"block_reason": "",
"price": 155.50,
"regime": "normal",
"magnitude": 0.025,
"updated_at": "2025-02-11T12:00:00Z",
"gates": {
"confidence": {"pass": true, "reason": null},
"win_rate": {"pass": true, "reason": null}
}
}
}
}
Dynamic obs_dim Detection Pattern
from ..gpu.inference_obs_builder import get_target_features_from_obs_dim as _get_features
is_native_model = False
if hasattr(model, 'obs_dim') and isinstance(getattr(model, 'obs_dim', None), int):
try:
_get_features(model.obs_dim, window)
is_native_model = True
except (ValueError, KeyError):
pass
Lazy Import Pattern for Circular Dependencies
def _get_gate_data() -> dict:
"""Lazy import to avoid circular dependency with server module."""
from ..server import get_gate_status_data
return get_gate_status_data()
Key Insights
- Gate status JSON is the critical IPC seam - any field mismatch between writer (live_trader) and reader (API routes) silently defaults to zeros/neutrals
broker.submit_order() uses type= not order_type= - the Alpaca SDK parameter names don't match REST conventions
- FastAPI circular imports are common when routes need server-level functions; lazy imports inside helper functions are the cleanest fix
- Mock objects in Python have all attributes, making
hasattr checks unreliable - always use isinstance for type guards
- WebSocket log broadcasting works best as incremental push (track
_last_log_count, send only new entries)
- The
RealisticBacktestEngine closely mirrors live trader behavior but had zero tests - highest-risk coverage gap
- Pre-existing tests numbered ~725; adding 93 new tests brought total to 818 with no regressions
References
- Plan file:
.claude/plans/playful-soaring-sutherland.md
- Related skills:
dashboard-pnl-visualization, broker-order-limitations, trading-gates-pattern-filter, drawdown-guardrails-pattern
- Files modified:
orders.py, signals.py, server.py, websocket.py, live_trader.py, engine.py, App.vue, AccountChart.vue, websocket.js
- New test files:
tests/test_api_server.py (41 tests), tests/test_live_trader.py (22 tests), tests/test_backtest.py (+30 tests)
1---2name: dashboard-cohesion-fixes3description: Integration fixes between backtest engine, FastAPI dashboard, Vue.js frontend, and live trader4---56# Dashboard Cohesion Fixes - Research Notes78## Experiment Overview9| Item | Details |10|------|---------|11| **Date** | 2025-02-11 |12| **Goal** | Fix 13 integration bugs and test gaps between the backtest engine, web dashboard (FastAPI + Vue.js), and live trader so all three components work cohesively |13| **Environment** | Windows 10, Python 3.11, FastAPI, Vue.js 3, pytest |14| **Status** | Success - 818 tests passing (93 new), 0 failures |1516## Context17The backtest engine, FastAPI web dashboard, and live trader were architecturally sound individually but had specific wiring issues at the seams where they communicate. The gate_status.json file serves as IPC between `scripts/live_trader.py` (writer) and the dashboard API (reader), but many fields the dashboard expected were never written. The broker API wrapper had parameter name mismatches, and there was zero test coverage for the API layer and live trader decision logic.1819## Verified Workflow2021### Phase 1: Critical Bugs221. Fix `broker.submit_order(order_type=...)` to `broker.submit_order(type=...)` in `alpaca_trading/api/routes/orders.py` (3 locations)232. Extend `GateStatus` dataclass with `price`, `regime`, `magnitude` fields243. Extend `write_gate_status()` with `market_regime`, `buy_threshold`, `sell_threshold` params254. Update `compute_gate_status_from_result()` to populate new fields from feats dict265. Replace duplicate `load_gate_status()` in `signals.py` with lazy import from `server.py`2728### Phase 2: Functional Completeness296. Add "New Trade" button wiring in `App.vue` for the existing but unreachable `TradeControls` modal307. Add incremental log broadcasting in `DataBroadcaster` via `fetch_logs` callback318. Improve empty-state handling in `AccountChart.vue`3233### Phase 3: Tests (93 new tests)349. `tests/test_api_server.py` - 41 tests: FastAPI TestClient for all REST routes + WebSocket3510. `tests/test_live_trader.py` - 22 tests: GateStatus, write_gate_status, compute_gate_status_from_result3611. `tests/test_backtest.py` - 30 tests: TestRealisticBacktestEngine (GARCH sizing, drawdown, guardrails)3738### Phase 4: Cleanup3912. Replace hardcoded `obs_dim in (4700, 4800, ...)` with dynamic `get_target_features_from_obs_dim()`4013. Centralize PnL cost constants from `DashboardConfig` defaults4142## Failed Attempts (Critical)4344| Attempt | Why it Failed | Lesson Learned |45|---------|---------------|----------------|46| Module-level import `from ..server import get_gate_status_data` in signals.py | Circular import: signals.py -> server.py -> routes/__init__.py -> signals.py | Use lazy import inside a function wrapper: `def _get_gate_data(): from ..server import get_gate_status_data; return get_gate_status_data()` |47| Using `replace_all=True` to rename `get_gate_status_data()` -> `_get_gate_data()` in signals.py | Also replaced the call *inside* the wrapper function, creating infinite recursion | Never use replace_all when the old string appears in the replacement's own definition |48| `hasattr(model, 'obs_dim')` to guard obs_dim arithmetic in backtest engine | Mock objects return True for `hasattr` on ANY attribute; `mock.obs_dim` is a Mock, not int | Use `isinstance(getattr(model, 'obs_dim', None), int)` to guard against both missing attributes and Mock objects |49| Patching `alpaca_trading.api.routes.market.get_market_session` in tests | Import wasn't resolved at module level due to lazy loading | Patch at the source: `alpaca_trading.market_hours.get_market_session` |50| Using `@pytest.mark.asyncio` for ConnectionManager tests | Methods being tested were synchronous; pytest-asyncio not properly configured | Check if methods under test are truly async before marking tests |51| Loss streak cooldown test: checking cooldown expired at bar 200 | Loss streak of 3 was still active, re-triggering cooldown at bar 200 | Insert a win (`_update_win_rate_tracking(True)`) to reset streak before testing past-cooldown behavior |5253## Final Parameters5455### Gate Status JSON Structure (write_gate_status output)56```json57{58 "updated_at": "2025-02-11T12:00:00Z",59 "market_regime": {"regime": "trending", "volatility": "medium", "garch_forecast": 0.18},60 "buy_threshold": 0.55,61 "sell_threshold": -0.45,62 "symbols": {63 "AAPL": {64 "signal": 1,65 "confidence": 0.72,66 "final_status": "READY",67 "block_reason": "",68 "price": 155.50,69 "regime": "normal",70 "magnitude": 0.025,71 "updated_at": "2025-02-11T12:00:00Z",72 "gates": {73 "confidence": {"pass": true, "reason": null},74 "win_rate": {"pass": true, "reason": null}75 }76 }77 }78}79```8081### Dynamic obs_dim Detection Pattern82```python83from ..gpu.inference_obs_builder import get_target_features_from_obs_dim as _get_features8485is_native_model = False86if hasattr(model, 'obs_dim') and isinstance(getattr(model, 'obs_dim', None), int):87 try:88 _get_features(model.obs_dim, window)89 is_native_model = True90 except (ValueError, KeyError):91 pass92```9394### Lazy Import Pattern for Circular Dependencies95```python96def _get_gate_data() -> dict:97 """Lazy import to avoid circular dependency with server module."""98 from ..server import get_gate_status_data99 return get_gate_status_data()100```101102## Key Insights103- Gate status JSON is the critical IPC seam - any field mismatch between writer (live_trader) and reader (API routes) silently defaults to zeros/neutrals104- `broker.submit_order()` uses `type=` not `order_type=` - the Alpaca SDK parameter names don't match REST conventions105- FastAPI circular imports are common when routes need server-level functions; lazy imports inside helper functions are the cleanest fix106- Mock objects in Python have all attributes, making `hasattr` checks unreliable - always use `isinstance` for type guards107- WebSocket log broadcasting works best as incremental push (track `_last_log_count`, send only new entries)108- The `RealisticBacktestEngine` closely mirrors live trader behavior but had zero tests - highest-risk coverage gap109- Pre-existing tests numbered ~725; adding 93 new tests brought total to 818 with no regressions110111## References112- Plan file: `.claude/plans/playful-soaring-sutherland.md`113- Related skills: `dashboard-pnl-visualization`, `broker-order-limitations`, `trading-gates-pattern-filter`, `drawdown-guardrails-pattern`114- Files modified: `orders.py`, `signals.py`, `server.py`, `websocket.py`, `live_trader.py`, `engine.py`, `App.vue`, `AccountChart.vue`, `websocket.js`115- New test files: `tests/test_api_server.py` (41 tests), `tests/test_live_trader.py` (22 tests), `tests/test_backtest.py` (+30 tests)