feat: agent-reviewed overnight_recovery_agent_top10; guard monitor hot-reload
overnight_recovery_agent_top10 (strategies/agent_review.py) re-ranks the
overnight_recovery top 50 with an LLM agent's daily scores:
final = quant percentile + 0.25 x score (-2..+2), vetoes for five event types,
top 10 on the base strategy's rebalance rows and hysteresis. Score files are
stamped by `agent_review.py submit` and steer a session only if stamped before
its 16:00 New York close; without one the strategy holds exactly what
overnight_recovery_top10 holds (checked on the live panel 2016-2026).
- agent_review.py: candidates / submit / evaluate; skill
.claude/skills/agent-stock-review drives the daily research.
- US only, skipped by precompute (no history before its first score file);
runs in the original book and in selected_2026_09 (from the book's start).
- .gitignore tracks .claude/skills/ and ignores data/agent/.
Monitor fix: hot-reload re-imports trader.py but not strategies/*. After the
2026-09-29 strategy commits the stale modules made every phase's panel refresh
fail ("Unknown signal 'low_risk'") and US books skipped 09-29 until a restart.
_reload_strategies now builds every factory and keeps the old registry when
any fails; CLAUDE.md says to restart the monitor after changing strategy code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
33
trader.py
33
trader.py
@@ -42,6 +42,7 @@ import yfinance as yf
|
||||
import data_manager
|
||||
import universe_history
|
||||
from strategies.base import BarStrategy
|
||||
from strategies.agent_review import AgentReviewStrategy
|
||||
from strategies.buy_and_hold import BuyAndHoldStrategy
|
||||
from strategies.dual_momentum import DualMomentumStrategy
|
||||
from strategies.factor_combo import FactorComboStrategy
|
||||
@@ -129,7 +130,9 @@ LIVE_BOOKS: dict[str, dict] = {
|
||||
# strategies from the 2026-09 PIT research, and the trend_rider ETF
|
||||
# strategies (lost 7-12% in Apr-Sep, 10-year Sharpe 1.0-1.4): v7_vt24
|
||||
# and v5_vt30 (best 10-year Sharpe of their version) and v7_vt36 (the
|
||||
# V7+VT36 champion).
|
||||
# V7+VT36 champion). overnight_recovery_agent_top10 was added on
|
||||
# 2026-09-30 at the user's request; it starts with the book, and until
|
||||
# its first agent score file it holds what overnight_recovery_top10 holds.
|
||||
"strategies": {
|
||||
"us": [
|
||||
"fc_up_cap_quality_mom_biweekly", "fc_up_cap_quality_mom_weekly",
|
||||
@@ -141,7 +144,7 @@ LIVE_BOOKS: dict[str, dict] = {
|
||||
"fc_ma200_mom7m_rec126_monthly", "fc_ma200_mom7m_rec126_biweekly",
|
||||
"fc_rec_mfilt_ma200_monthly", "fc_rec_mfilt_ma200_biweekly",
|
||||
"hybrid_alpha_top10", "recovery_mom_top10",
|
||||
"overnight_recovery_top10",
|
||||
"overnight_recovery_top10", "overnight_recovery_agent_top10",
|
||||
"trend_rider_v7_vt24", "trend_rider_v7_vt36", "trend_rider_v5_vt30",
|
||||
],
|
||||
"cn": [
|
||||
@@ -301,6 +304,9 @@ STRATEGY_REGISTRY = {
|
||||
"us_three_sleeve": lambda **kw: us_three_sleeve(),
|
||||
# half overnight_recovery_top10, half QQQ 200-day trend with a 30% vol target (TQQQ + SHY)
|
||||
"us_momentum_qqq_trend": lambda **kw: us_momentum_qqq_trend(),
|
||||
# overnight_recovery's top 50, re-ranked with an LLM agent's daily scores
|
||||
# (strategies/agent_review.py, agent_review.py); live from 2026-09-29, no backtest of its own
|
||||
"overnight_recovery_agent_top10": lambda **kw: AgentReviewStrategy(),
|
||||
# --- TrendRiderV6: stock-picking + V5 regime engine ---
|
||||
"trend_rider_v6": lambda **kw: TrendRiderV6(),
|
||||
"trend_rider_v6_top10": lambda **kw: TrendRiderV6(top_n=10),
|
||||
@@ -334,6 +340,11 @@ MIXED_STRATEGY_EXTRA_TICKERS = {
|
||||
"us_momentum_qqq_trend": MOMENTUM_QQQ_TREND_ETFS,
|
||||
}
|
||||
|
||||
# Strategies steered by agent score files (data/agent/us/). US only; before
|
||||
# their first score file they hold what their base strategy holds, so they
|
||||
# have no backtest of their own and precompute_backtest.py skips them.
|
||||
AGENT_REVIEW_STRATEGIES = {"overnight_recovery_agent_top10"}
|
||||
|
||||
# Registered strategies the live monitor does not run, with the reason.
|
||||
LIVE_EXCLUDED_STRATEGIES = {
|
||||
"buy_and_hold": "equal weight across ~500 names is not investable with whole "
|
||||
@@ -359,7 +370,8 @@ def strategy_fields(strategy_name: str) -> tuple[str, ...]:
|
||||
def strategy_supports_market(strategy_name: str, market: str) -> bool:
|
||||
"""ETF-based strategies trade USD-listed ETFs, so they only run in the US book."""
|
||||
base_name = strategy_name.removeprefix("sim_")
|
||||
if base_name in ETF_STRATEGY_UNIVERSES or base_name in MIXED_STRATEGY_EXTRA_TICKERS:
|
||||
if base_name in ETF_STRATEGY_UNIVERSES or base_name in MIXED_STRATEGY_EXTRA_TICKERS \
|
||||
or base_name in AGENT_REVIEW_STRATEGIES:
|
||||
return market == "us"
|
||||
return True
|
||||
|
||||
@@ -1770,10 +1782,23 @@ def cmd_monitor(args):
|
||||
return min(all_candidates, key=lambda x: x[0])
|
||||
|
||||
def _reload_strategies():
|
||||
"""Re-read STRATEGY_REGISTRY to pick up new strategies without restart."""
|
||||
"""Re-read STRATEGY_REGISTRY to pick up new strategies without restart.
|
||||
|
||||
Only trader.py is reloaded; strategies/* modules stay as first
|
||||
imported. A registry whose entries need newer strategy code would
|
||||
fail at every phase (2026-09-29: one stale signal name broke the panel
|
||||
refresh for every book), so every factory is built first and the old
|
||||
registry is kept if any fails; changed strategy code needs a restart.
|
||||
"""
|
||||
import importlib
|
||||
import trader as _self_mod
|
||||
importlib.reload(_self_mod)
|
||||
for name, factory in _self_mod.STRATEGY_REGISTRY.items():
|
||||
try:
|
||||
factory()
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"{name} needs strategy code this process has not loaded "
|
||||
f"({exc}); restart the monitor") from exc
|
||||
current = list(_self_mod.DEFAULT_MONITOR_STRATEGIES)
|
||||
return current, _self_mod.STRATEGY_REGISTRY
|
||||
|
||||
|
||||
Reference in New Issue
Block a user