perf(monitor): refresh each price panel once per phase

Every strategy used to re-download its whole universe each phase (~75
downloads of 500+ tickers for the US book), which tripped Yahoo's rate
limit. The monitor now refreshes each panel once for the union of tickers
and runs strategies offline, like replay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 02:09:02 +08:00
parent 10a74ea4f7
commit 69636c3fae

View File

@@ -820,6 +820,7 @@ def cmd_morning(args):
# Download data (close + open)
close_data, open_data, _ = load_strategy_prices(
market, strategy_name, tickers, benchmark, with_open=True,
refresh=getattr(args, "refresh", True),
)
tickers = filter_tradable_tickers(close_data, tickers)
@@ -1601,8 +1602,14 @@ def cmd_monitor(args):
market_fee = MARKET_FEES.get(market, args.fixed_fee)
eligible = [s for s in strategies if strategy_supports_market(s, market)]
# One download per price panel per phase; strategies then read offline.
try:
refresh_panels(market, eligible, label="monitor")
except Exception as e:
print(f"[monitor] Panel refresh failed ({e}); using stored prices")
for strat_name in eligible:
sub_args = copy.copy(args)
sub_args.refresh = False
sub_args.strategy = strat_name
sub_args.market = market
sub_args.fixed_fee = market_fee
@@ -1695,6 +1702,18 @@ def cmd_monitor(args):
print()
def refresh_panels(market: str, strategy_names: list[str], label: str) -> None:
"""Refresh every price panel the strategies read, once, for the union of their tickers."""
requests: dict[str, set[str]] = {}
for name in strategy_names:
tickers, benchmark = strategy_universe(market, name)
requests.setdefault(strategy_data_market(market, name), set()).update(
price_request(market, list(tickers) + [benchmark]))
for data_market, request in requests.items():
print(f"[{label}] Refreshing {data_market} panel for {len(request)} tickers")
data_manager.update(data_market, sorted(request), with_open=True)
def _process_sessions(args, state: dict | None, start: pd.Timestamp | None = None,
label: str = "auto", refresh: bool = True) -> dict | None:
"""Run every completed session not yet recorded in ``state`` (catch-up safe).
@@ -1762,7 +1781,7 @@ def cmd_auto(args):
0 16 * * 1-5 cd /path/to/quant && uv run python trader.py auto --market cn
"""
state = load_state(args.market, args.strategy)
state = _process_sessions(args, state)
state = _process_sessions(args, state, refresh=getattr(args, "refresh", True))
if state is not None:
save_state(state, args.market, args.strategy)
pnl = state["daily_equity"][state["last_evening"]] - state["initial_capital"]
@@ -1786,14 +1805,7 @@ def cmd_replay(args):
and n not in LIVE_EXCLUDED_STRATEGIES and strategy_supports_market(n, args.market)]
# Refresh each price panel once for the union of tickers, then replay offline.
requests: dict[str, set[str]] = {}
for name in live_names:
tickers, benchmark = strategy_universe(args.market, name)
requests.setdefault(strategy_data_market(args.market, name), set()).update(
price_request(args.market, list(tickers) + [benchmark]))
for data_market, request in requests.items():
print(f"[replay] Refreshing {data_market} panel for {len(request)} tickers")
data_manager.update(data_market, sorted(request), with_open=True)
refresh_panels(args.market, live_names, label="replay")
for name in names:
if name.startswith("sim_"):