feat: register V7+VT36 as SOTA and add monitor hot-reload
- Register trend_rider_v7_vt36 (target_vol=0.36, min_lev=0.75) in strategy registry, ETF universe map, and bridge metadata. 10y backtest: Ann 60.5%, Sharpe 1.87, MaxDD -29.2%. - Add hot-reload to monitor: each phase re-imports trader module to pick up newly registered strategies without restart. New strategies are logged on detection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -380,6 +380,12 @@ STRATEGY_META = {
|
|||||||
"params": {},
|
"params": {},
|
||||||
"markets": ["us"],
|
"markets": ["us"],
|
||||||
},
|
},
|
||||||
|
"trend_rider_v7_vt36": {
|
||||||
|
"label": "Trend Rider V7 (VT36% + PT30) ★ SOTA",
|
||||||
|
"category": "tactical_allocation",
|
||||||
|
"params": {},
|
||||||
|
"markets": ["us"],
|
||||||
|
},
|
||||||
# --- Stock-picker ensembles (US S&P 500 universe) ---
|
# --- Stock-picker ensembles (US S&P 500 universe) ---
|
||||||
"ensemble_alpha_top10": {
|
"ensemble_alpha_top10": {
|
||||||
"label": "Ensemble Alpha Top 10",
|
"label": "Ensemble Alpha Top 10",
|
||||||
|
|||||||
27
trader.py
27
trader.py
@@ -178,6 +178,7 @@ STRATEGY_REGISTRY = {
|
|||||||
"trend_rider_v7": lambda **kw: TrendRiderV7(),
|
"trend_rider_v7": lambda **kw: TrendRiderV7(),
|
||||||
"trend_rider_v7_vt24": lambda **kw: TrendRiderV7(target_vol=0.24, min_lev=0.5),
|
"trend_rider_v7_vt24": lambda **kw: TrendRiderV7(target_vol=0.24, min_lev=0.5),
|
||||||
"trend_rider_v7_vt32": lambda **kw: TrendRiderV7(target_vol=0.32, min_lev=0.7),
|
"trend_rider_v7_vt32": lambda **kw: TrendRiderV7(target_vol=0.32, min_lev=0.7),
|
||||||
|
"trend_rider_v7_vt36": lambda **kw: TrendRiderV7(target_vol=0.36, min_lev=0.75),
|
||||||
# --- Stock-picker ensemble strategies (S&P 500 universe) ---
|
# --- Stock-picker ensemble strategies (S&P 500 universe) ---
|
||||||
"ensemble_alpha_top10": lambda **kw: EnsembleAlphaStrategy(top_n=10),
|
"ensemble_alpha_top10": lambda **kw: EnsembleAlphaStrategy(top_n=10),
|
||||||
"ensemble_alpha_top12": lambda **kw: EnsembleAlphaStrategy(top_n=12),
|
"ensemble_alpha_top12": lambda **kw: EnsembleAlphaStrategy(top_n=12),
|
||||||
@@ -222,6 +223,7 @@ ETF_STRATEGY_UNIVERSES = {
|
|||||||
"trend_rider_v7": sorted(set(ETF_UNIVERSE)),
|
"trend_rider_v7": sorted(set(ETF_UNIVERSE)),
|
||||||
"trend_rider_v7_vt24": sorted(set(ETF_UNIVERSE)),
|
"trend_rider_v7_vt24": sorted(set(ETF_UNIVERSE)),
|
||||||
"trend_rider_v7_vt32": sorted(set(ETF_UNIVERSE)),
|
"trend_rider_v7_vt32": sorted(set(ETF_UNIVERSE)),
|
||||||
|
"trend_rider_v7_vt36": sorted(set(ETF_UNIVERSE)),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Strategies that use the market's stock universe PLUS fixed extra ETF tickers.
|
# Strategies that use the market's stock universe PLUS fixed extra ETF tickers.
|
||||||
@@ -1296,6 +1298,7 @@ def cmd_monitor(args):
|
|||||||
print(f" Evening: {sched['eve_h']:02d}:{sched['eve_m']:02d} {sched['tz']}")
|
print(f" Evening: {sched['eve_h']:02d}:{sched['eve_m']:02d} {sched['tz']}")
|
||||||
print(f" Fixed fee: {fee:.2f}/trade")
|
print(f" Fixed fee: {fee:.2f}/trade")
|
||||||
print(f" Strategies: {', '.join(strategies)}")
|
print(f" Strategies: {', '.join(strategies)}")
|
||||||
|
print(f" Auto-reload: ON (new strategies picked up each phase)")
|
||||||
print(f"{'='*60}")
|
print(f"{'='*60}")
|
||||||
|
|
||||||
# Use UTC as common reference for sleeping
|
# Use UTC as common reference for sleeping
|
||||||
@@ -1328,12 +1331,34 @@ def cmd_monitor(args):
|
|||||||
all_candidates.extend(_next_events_for_market(mkt, now_utc))
|
all_candidates.extend(_next_events_for_market(mkt, now_utc))
|
||||||
return min(all_candidates, key=lambda x: x[0])
|
return min(all_candidates, key=lambda x: x[0])
|
||||||
|
|
||||||
|
def _reload_strategies():
|
||||||
|
"""Re-read STRATEGY_REGISTRY to pick up new strategies without restart."""
|
||||||
|
import importlib
|
||||||
|
import trader as _self_mod
|
||||||
|
importlib.reload(_self_mod)
|
||||||
|
current = list(_self_mod.STRATEGY_REGISTRY.keys())
|
||||||
|
return current, _self_mod.STRATEGY_REGISTRY
|
||||||
|
|
||||||
def _run_phase(market, phase, now_utc):
|
def _run_phase(market, phase, now_utc):
|
||||||
"""Run all strategies for a market/phase."""
|
"""Run all strategies for a market/phase."""
|
||||||
|
nonlocal strategies
|
||||||
sched = market_schedules[market]
|
sched = market_schedules[market]
|
||||||
tz = sched["tz"]
|
tz = sched["tz"]
|
||||||
now_local = now_utc.astimezone(tz)
|
now_local = now_utc.astimezone(tz)
|
||||||
|
|
||||||
|
# Hot-reload strategy list from registry
|
||||||
|
try:
|
||||||
|
reloaded_names, reloaded_reg = _reload_strategies()
|
||||||
|
new_strats = set(reloaded_names) - set(strategies)
|
||||||
|
if new_strats:
|
||||||
|
print(f"[monitor] Hot-reload: +{len(new_strats)} new strategies: "
|
||||||
|
f"{', '.join(sorted(new_strats))}")
|
||||||
|
strategies = reloaded_names
|
||||||
|
# Update the global registry so cmd_morning/cmd_auto use new strategies
|
||||||
|
globals().update({"STRATEGY_REGISTRY": reloaded_reg})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[monitor] Hot-reload failed (using cached list): {e}")
|
||||||
|
|
||||||
print(f"\n[monitor] {'='*55}")
|
print(f"\n[monitor] {'='*55}")
|
||||||
print(f"[monitor] {market.upper()} {phase.upper()} at "
|
print(f"[monitor] {market.upper()} {phase.upper()} at "
|
||||||
f"{now_local.strftime('%Y-%m-%d %H:%M:%S %Z')}")
|
f"{now_local.strftime('%Y-%m-%d %H:%M:%S %Z')}")
|
||||||
@@ -1370,7 +1395,7 @@ def cmd_monitor(args):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
print(f"[monitor] {market.upper()} {phase} done — "
|
print(f"[monitor] {market.upper()} {phase} done — "
|
||||||
f"{len(strategies)} strategies")
|
f"{len(strategies)} strategies\n")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
now_utc = datetime.now(utc)
|
now_utc = datetime.now(utc)
|
||||||
|
|||||||
Reference in New Issue
Block a user