Initial strategy32 research and live runtime

This commit is contained in:
2026-03-16 20:18:41 -07:00
commit c165a9add7
42 changed files with 10750 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
from __future__ import annotations
import copy
import json
import sys
from pathlib import Path
import pandas as pd
PACKAGE_PARENT = Path(__file__).resolve().parents[2]
if str(PACKAGE_PARENT) not in sys.path:
sys.path.insert(0, str(PACKAGE_PARENT))
from strategy29.backtest.window_analysis import evaluate_window_result, slice_bundle
from strategy32.backtest.simulator import Strategy32Backtester
from strategy32.config import PROFILE_V7_DEFAULT, build_strategy32_config
from strategy32.data import build_strategy32_market_bundle
WINDOWS = [(7, "1w"), (30, "1m"), (365, "1y"), (1095, "3y"), (1825, "5y")]
def balanced_score(results: dict[str, dict[str, float | int | str]]) -> float:
score = 0.0
for label, weight in (("1y", 1.0), ("3y", 1.0), ("5y", 1.2)):
annualized = float(results[label]["annualized_return"])
drawdown = abs(float(results[label]["max_drawdown"]))
score += weight * (annualized / max(drawdown, 0.01))
score += 0.15 * float(results["1m"]["total_return"])
return score
def main() -> None:
base = build_strategy32_config(PROFILE_V7_DEFAULT)
end = pd.Timestamp("2026-03-15 00:00:00", tz="UTC")
start = end - pd.Timedelta(days=max(days for days, _ in WINDOWS) + base.warmup_days + 14)
variants: list[tuple[str, dict[str, bool]]] = [
("v7_default", {}),
("v7_plus_expanded_hedge", {"enable_expanded_hedge": True}),
("v7_plus_max_holding_exit", {"enable_max_holding_exit": True}),
("v7_plus_expanded_hedge_plus_max_holding_exit", {"enable_expanded_hedge": True, "enable_max_holding_exit": True}),
]
print("fetching bundle...")
bundle, latest_completed_bar, accepted_symbols, rejected_symbols, quote_by_symbol = build_strategy32_market_bundle(
symbols=base.symbols,
auto_discover_symbols=base.auto_discover_symbols,
quote_assets=base.quote_assets,
excluded_base_assets=base.excluded_base_assets,
min_quote_volume_24h=base.discovery_min_quote_volume_24h,
start=start,
end=end,
timeframe=base.timeframe,
max_staleness_days=base.max_symbol_staleness_days,
)
print("latest", latest_completed_bar)
results: dict[str, dict[str, dict[str, float | int | str]]] = {}
summary_rows: list[dict[str, float | int | str]] = []
for name, overrides in variants:
cfg = copy.deepcopy(base)
for attr, value in overrides.items():
setattr(cfg, attr, value)
variant_results = {}
print(f"\nVARIANT {name}")
for days, label in WINDOWS:
eval_end = latest_completed_bar
eval_start = eval_end - pd.Timedelta(days=days)
raw_start = eval_start - pd.Timedelta(days=cfg.warmup_days)
sliced = slice_bundle(bundle, raw_start, eval_end)
backtester = Strategy32Backtester(cfg, sliced, trade_start=eval_start)
backtester.engine_config.initial_capital = 1000.0
result = backtester.run()
metrics = evaluate_window_result(result, eval_start=eval_start, bars_per_day=backtester.engine_config.bars_per_day)
metrics["engine_pnl"] = result.engine_pnl
metrics["total_trades"] = result.total_trades
variant_results[label] = metrics
print(
label,
"ret",
round(float(metrics["total_return"]) * 100, 2),
"mdd",
round(float(metrics["max_drawdown"]) * 100, 2),
"sharpe",
round(float(metrics["sharpe"]), 2),
"trades",
metrics["trade_count"],
)
score = balanced_score(variant_results)
results[name] = variant_results
summary_rows.append(
{
"name": name,
"balanced_score": score,
"ret_1w": float(variant_results["1w"]["total_return"]),
"ret_1m": float(variant_results["1m"]["total_return"]),
"ret_1y": float(variant_results["1y"]["total_return"]),
"ret_3y": float(variant_results["3y"]["total_return"]),
"ret_5y": float(variant_results["5y"]["total_return"]),
"mdd_1y": float(variant_results["1y"]["max_drawdown"]),
"mdd_3y": float(variant_results["3y"]["max_drawdown"]),
"mdd_5y": float(variant_results["5y"]["max_drawdown"]),
}
)
summary_rows.sort(key=lambda row: float(row["balanced_score"]), reverse=True)
payload = {
"strategy": "strategy32",
"analysis": "v7_branch_validation",
"profile": PROFILE_V7_DEFAULT,
"initial_capital": 1000.0,
"auto_discover_symbols": base.auto_discover_symbols,
"latest_completed_bar": str(latest_completed_bar),
"requested_symbols": [] if base.auto_discover_symbols else base.symbols,
"accepted_symbols": accepted_symbols,
"rejected_symbols": rejected_symbols,
"quote_by_symbol": quote_by_symbol,
"timeframe": base.timeframe,
"results": results,
"summary": summary_rows,
}
out = Path("/tmp/strategy32_v7_branch_validation.json")
out.write_text(json.dumps(payload, indent=2), encoding="utf-8")
print("\nRanked variants")
for row in summary_rows:
print(
row["name"],
"score",
round(float(row["balanced_score"]), 3),
"1y",
round(float(row["ret_1y"]) * 100, 2),
"3y",
round(float(row["ret_3y"]) * 100, 2),
"5y",
round(float(row["ret_5y"]) * 100, 2),
"mdd5y",
round(float(row["mdd_5y"]) * 100, 2),
)
print("\nwrote", out)
if __name__ == "__main__":
main()