Refine runtime data flow and UI layering
This commit is contained in:
@@ -152,10 +152,11 @@ class Gateway:
|
||||
)
|
||||
|
||||
# Load and display existing portfolio state if available
|
||||
summary = self.storage.load_file("summary")
|
||||
dashboard_snapshot = self.storage.build_dashboard_snapshot_from_state(self.state_sync.state)
|
||||
summary = dashboard_snapshot.get("summary")
|
||||
if summary:
|
||||
holdings = self.storage.load_file("holdings") or []
|
||||
trades = self.storage.load_file("trades") or []
|
||||
holdings = dashboard_snapshot.get("holdings") or []
|
||||
trades = dashboard_snapshot.get("trades") or []
|
||||
current_date = self.state_sync.state.get("current_date")
|
||||
self._dashboard.update(
|
||||
date=current_date or "-",
|
||||
|
||||
@@ -61,7 +61,7 @@ async def market_status_monitor(gateway: Any) -> None:
|
||||
status = gateway.market_service.get_market_status()
|
||||
if status["status"] == "open" and not gateway.storage.is_live_session_active:
|
||||
gateway.storage.start_live_session()
|
||||
summary = gateway.storage.load_file("summary") or {}
|
||||
summary = gateway.storage.build_dashboard_snapshot_from_state(gateway.state_sync.state).get("summary") or {}
|
||||
gateway._session_start_portfolio_value = summary.get(
|
||||
"totalAssetValue",
|
||||
gateway.storage.initial_cash,
|
||||
@@ -240,14 +240,15 @@ async def run_live_cycle(gateway: Any, date: str, tickers: list[str]) -> None:
|
||||
|
||||
|
||||
async def finalize_cycle(gateway: Any, date: str) -> None:
|
||||
summary = gateway.storage.load_file("summary") or {}
|
||||
dashboard_snapshot = gateway.storage.build_dashboard_snapshot_from_state(gateway.state_sync.state)
|
||||
summary = dashboard_snapshot.get("summary") or {}
|
||||
if gateway.storage.is_live_session_active:
|
||||
summary.update(gateway.storage.get_live_returns())
|
||||
|
||||
await gateway.state_sync.on_cycle_end(date, portfolio_summary=summary)
|
||||
holdings = gateway.storage.load_file("holdings") or []
|
||||
trades = gateway.storage.load_file("trades") or []
|
||||
leaderboard = gateway.storage.load_file("leaderboard") or []
|
||||
holdings = dashboard_snapshot.get("holdings") or []
|
||||
trades = dashboard_snapshot.get("trades") or []
|
||||
leaderboard = dashboard_snapshot.get("leaderboard") or []
|
||||
if leaderboard:
|
||||
await gateway.state_sync.on_leaderboard_update(leaderboard)
|
||||
gateway._dashboard.update(date=date, status="Running", portfolio=summary, holdings=holdings, trades=trades)
|
||||
@@ -319,7 +320,7 @@ async def run_backtest_dates(gateway: Any, dates: list[str]) -> None:
|
||||
await gateway.on_strategy_trigger(date=date)
|
||||
await asyncio.sleep(0.1)
|
||||
await gateway.state_sync.on_system_message(f"Backtest complete - {len(dates)} days")
|
||||
summary = gateway.storage.load_file("summary") or {}
|
||||
summary = gateway.storage.build_dashboard_snapshot_from_state(gateway.state_sync.state).get("summary") or {}
|
||||
gateway._dashboard.update(status="Complete", portfolio=summary, days_completed=len(dates))
|
||||
gateway._dashboard.stop()
|
||||
gateway._dashboard.print_final_summary()
|
||||
|
||||
@@ -164,9 +164,10 @@ def sync_runtime_state(gateway: Any) -> None:
|
||||
gateway._dashboard.initial_cash = gateway.storage.initial_cash
|
||||
gateway._dashboard.enable_memory = bool(gateway.config.get("enable_memory", False))
|
||||
|
||||
summary = gateway.storage.load_file("summary") or {}
|
||||
holdings = gateway.storage.load_file("holdings") or []
|
||||
trades = gateway.storage.load_file("trades") or []
|
||||
dashboard_snapshot = gateway.storage.build_dashboard_snapshot_from_state(gateway.state_sync.state)
|
||||
summary = dashboard_snapshot.get("summary") or {}
|
||||
holdings = dashboard_snapshot.get("holdings") or []
|
||||
trades = dashboard_snapshot.get("trades") or []
|
||||
gateway._dashboard.update(
|
||||
portfolio=summary,
|
||||
holdings=holdings,
|
||||
|
||||
@@ -361,6 +361,7 @@ async def handle_get_stock_range_explain(gateway: Any, websocket: Any, data: dic
|
||||
end_date=end_date,
|
||||
article_ids=article_ids if isinstance(article_ids, list) else None,
|
||||
limit=100,
|
||||
refresh_if_stale=False,
|
||||
)
|
||||
result = payload.get("result")
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from backend.data.market_store import MarketStore
|
||||
from .research_db import ResearchDb
|
||||
from .runtime_db import RuntimeDb
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -22,12 +21,18 @@ class StorageService:
|
||||
Storage service for data persistence
|
||||
|
||||
Responsibilities:
|
||||
1. Load/save dashboard JSON files
|
||||
1. Export dashboard JSON files
|
||||
(summary, holdings, stats, trades, leaderboard)
|
||||
2. Load/save internal state (_internal_state.json)
|
||||
3. Load/save server state (server_state.json) with feed history
|
||||
4. Manage portfolio state persistence
|
||||
5. Support loading from saved state to resume execution
|
||||
|
||||
Notes:
|
||||
- team_dashboard/*.json is treated as an export/compatibility layer
|
||||
rather than the authoritative runtime source of truth.
|
||||
- authoritative runtime reads should prefer in-memory state, server_state,
|
||||
runtime.db, and market_research.db.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -49,7 +54,7 @@ class StorageService:
|
||||
self.initial_cash = initial_cash
|
||||
self.config_name = config_name
|
||||
|
||||
# Dashboard file paths
|
||||
# Dashboard export file paths
|
||||
self.files = {
|
||||
"summary": self.dashboard_dir / "summary.json",
|
||||
"holdings": self.dashboard_dir / "holdings.json",
|
||||
@@ -66,7 +71,6 @@ class StorageService:
|
||||
self.state_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.server_state_file = self.state_dir / "server_state.json"
|
||||
self.runtime_db = RuntimeDb(self.state_dir / "runtime.db")
|
||||
self.research_db = ResearchDb(self.state_dir / "research.db")
|
||||
self.market_store = MarketStore()
|
||||
|
||||
# Feed history (for agent messages)
|
||||
@@ -84,16 +88,8 @@ class StorageService:
|
||||
|
||||
logger.info(f"Storage service initialized: {self.dashboard_dir}")
|
||||
|
||||
def load_file(self, file_type: str) -> Optional[Any]:
|
||||
"""
|
||||
Load dashboard JSON file
|
||||
|
||||
Args:
|
||||
file_type: One of: summary, holdings, stats, trades, leaderboard
|
||||
|
||||
Returns:
|
||||
Loaded data or None if file doesn't exist
|
||||
"""
|
||||
def load_export_file(self, file_type: str) -> Optional[Any]:
|
||||
"""Load dashboard export JSON file."""
|
||||
file_path = self.files.get(file_type)
|
||||
if not file_path or not file_path.exists():
|
||||
return None
|
||||
@@ -105,14 +101,12 @@ class StorageService:
|
||||
logger.error(f"Failed to load {file_type}.json: {e}")
|
||||
return None
|
||||
|
||||
def save_file(self, file_type: str, data: Any):
|
||||
"""
|
||||
Save dashboard JSON file
|
||||
def load_file(self, file_type: str) -> Optional[Any]:
|
||||
"""Backward-compatible alias for export-layer JSON reads."""
|
||||
return self.load_export_file(file_type)
|
||||
|
||||
Args:
|
||||
file_type: One of: summary, holdings, stats, trades, leaderboard
|
||||
data: Data to save
|
||||
"""
|
||||
def save_export_file(self, file_type: str, data: Any):
|
||||
"""Save dashboard export JSON file."""
|
||||
file_path = self.files.get(file_type)
|
||||
if not file_path:
|
||||
logger.error(f"Unknown file type: {file_type}")
|
||||
@@ -129,6 +123,48 @@ class StorageService:
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save {file_type}.json: {e}")
|
||||
|
||||
def save_file(self, file_type: str, data: Any):
|
||||
"""Backward-compatible alias for export-layer JSON writes."""
|
||||
self.save_export_file(file_type, data)
|
||||
|
||||
def build_dashboard_snapshot_from_state(
|
||||
self,
|
||||
state: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Build dashboard view data from runtime state instead of JSON exports."""
|
||||
runtime_state = state or self.load_server_state()
|
||||
portfolio = dict(runtime_state.get("portfolio") or {})
|
||||
holdings = list(runtime_state.get("holdings") or [])
|
||||
stats = runtime_state.get("stats") or self._get_default_stats()
|
||||
trades = list(runtime_state.get("trades") or [])
|
||||
leaderboard = list(runtime_state.get("leaderboard") or [])
|
||||
|
||||
summary = {
|
||||
"totalAssetValue": portfolio.get("total_value", self.initial_cash),
|
||||
"totalReturn": portfolio.get("pnl_percent", 0.0),
|
||||
"cashPosition": portfolio.get("cash", self.initial_cash),
|
||||
"tickerWeights": stats.get("tickerWeights", {}),
|
||||
"totalTrades": len(trades),
|
||||
"pnlPct": portfolio.get("pnl_percent", 0.0),
|
||||
"balance": portfolio.get("total_value", self.initial_cash),
|
||||
"equity": portfolio.get("equity", []),
|
||||
"baseline": portfolio.get("baseline", []),
|
||||
"baseline_vw": portfolio.get("baseline_vw", []),
|
||||
"momentum": portfolio.get("momentum", []),
|
||||
"equity_return": portfolio.get("equity_return", []),
|
||||
"baseline_return": portfolio.get("baseline_return", []),
|
||||
"baseline_vw_return": portfolio.get("baseline_vw_return", []),
|
||||
"momentum_return": portfolio.get("momentum_return", []),
|
||||
}
|
||||
|
||||
return {
|
||||
"summary": summary,
|
||||
"holdings": holdings,
|
||||
"stats": stats,
|
||||
"trades": trades,
|
||||
"leaderboard": leaderboard,
|
||||
}
|
||||
|
||||
def check_file_updates(self) -> Dict[str, bool]:
|
||||
"""
|
||||
Check which dashboard files have been updated since last check
|
||||
@@ -297,7 +333,7 @@ class StorageService:
|
||||
def initialize_empty_dashboard(self):
|
||||
"""Initialize empty dashboard files with default values"""
|
||||
# Summary
|
||||
self.save_file(
|
||||
self.save_export_file(
|
||||
"summary",
|
||||
{
|
||||
"totalAssetValue": self.initial_cash,
|
||||
@@ -315,10 +351,10 @@ class StorageService:
|
||||
)
|
||||
|
||||
# Holdings
|
||||
self.save_file("holdings", [])
|
||||
self.save_export_file("holdings", [])
|
||||
|
||||
# Stats
|
||||
self.save_file(
|
||||
self.save_export_file(
|
||||
"stats",
|
||||
{
|
||||
"totalAssetValue": self.initial_cash,
|
||||
@@ -335,7 +371,7 @@ class StorageService:
|
||||
)
|
||||
|
||||
# Trades
|
||||
self.save_file("trades", [])
|
||||
self.save_export_file("trades", [])
|
||||
|
||||
# Leaderboard with model info
|
||||
self.generate_leaderboard()
|
||||
@@ -375,7 +411,7 @@ class StorageService:
|
||||
ranking_entries.append(entry)
|
||||
|
||||
leaderboard = team_entries + ranking_entries
|
||||
self.save_file("leaderboard", leaderboard)
|
||||
self.save_export_file("leaderboard", leaderboard)
|
||||
logger.info("Leaderboard generated with model info")
|
||||
|
||||
def update_leaderboard_model_info(self):
|
||||
@@ -398,7 +434,7 @@ class StorageService:
|
||||
entry["modelName"] = model_name
|
||||
entry["modelProvider"] = model_provider
|
||||
|
||||
self.save_file("leaderboard", existing)
|
||||
self.save_export_file("leaderboard", existing)
|
||||
logger.info("Leaderboard model info updated")
|
||||
|
||||
def get_current_timestamp_ms(self, date: str = None) -> int:
|
||||
@@ -653,7 +689,7 @@ class StorageService:
|
||||
"momentum": state.get("momentum_history", []),
|
||||
}
|
||||
|
||||
self.save_file("summary", summary)
|
||||
self.save_export_file("summary", summary)
|
||||
|
||||
def _generate_holdings(
|
||||
self,
|
||||
@@ -715,7 +751,7 @@ class StorageService:
|
||||
# Sort by weight
|
||||
holdings.sort(key=lambda x: abs(x["weight"]), reverse=True)
|
||||
|
||||
self.save_file("holdings", holdings)
|
||||
self.save_export_file("holdings", holdings)
|
||||
|
||||
def _generate_stats(self, state: Dict[str, Any], net_value: float):
|
||||
"""Generate stats.json"""
|
||||
@@ -738,7 +774,7 @@ class StorageService:
|
||||
},
|
||||
}
|
||||
|
||||
self.save_file("stats", stats)
|
||||
self.save_export_file("stats", stats)
|
||||
|
||||
def _generate_trades(self, state: Dict[str, Any]):
|
||||
"""Generate trades.json"""
|
||||
@@ -764,7 +800,7 @@ class StorageService:
|
||||
},
|
||||
)
|
||||
|
||||
self.save_file("trades", trades)
|
||||
self.save_export_file("trades", trades)
|
||||
|
||||
# Server State Management Methods
|
||||
|
||||
@@ -1001,12 +1037,12 @@ class StorageService:
|
||||
Args:
|
||||
state: Server state dictionary to update
|
||||
"""
|
||||
# Load dashboard data
|
||||
summary = self.load_file("summary") or {}
|
||||
holdings = self.load_file("holdings") or []
|
||||
stats = self.load_file("stats") or self._get_default_stats()
|
||||
trades = self.load_file("trades") or []
|
||||
leaderboard = self.load_file("leaderboard") or []
|
||||
dashboard_snapshot = self.build_dashboard_snapshot_from_state(state)
|
||||
summary = dashboard_snapshot.get("summary") or {}
|
||||
holdings = dashboard_snapshot.get("holdings") or []
|
||||
stats = dashboard_snapshot.get("stats") or self._get_default_stats()
|
||||
trades = dashboard_snapshot.get("trades") or []
|
||||
leaderboard = dashboard_snapshot.get("leaderboard") or []
|
||||
internal_state = self.load_internal_state()
|
||||
|
||||
# Update state
|
||||
@@ -1040,7 +1076,6 @@ class StorageService:
|
||||
Start tracking live returns for current trading session.
|
||||
Captures current values as session start baseline.
|
||||
"""
|
||||
summary = self.load_file("summary") or {}
|
||||
state = self.load_internal_state()
|
||||
|
||||
# Capture current values as session start
|
||||
@@ -1052,7 +1087,7 @@ class StorageService:
|
||||
self._session_start_equity = (
|
||||
equity_history[-1]["v"]
|
||||
if equity_history
|
||||
else summary.get("totalAssetValue", self.initial_cash)
|
||||
else self.initial_cash
|
||||
)
|
||||
self._session_start_baseline = (
|
||||
baseline_history[-1]["v"]
|
||||
|
||||
Reference in New Issue
Block a user