Download run_eval.py from Avi-L/crypto-quant-eval: direct link, hf CLI and curl.
- Browser
- Download file 4.3 kB
-
https://huggingface.co/Avi-L/crypto-quant-eval/resolve/main/run_eval.py
- Command line
-
hf download hf://Avi-L/crypto-quant-eval/run_eval.py
-
curl -L -o run_eval.py https://huggingface.co/Avi-L/crypto-quant-eval/resolve/main/run_eval.py
4.3 kB
| """Run the institutional eval on a strategy. | |
| Usage: | |
| python run_eval.py --data /path/to/curated --symbol BTCUSDT --freq 1h \ | |
| --strategy funding_carry --cpcv-blocks 6 --cpcv-k 2 | |
| Pipeline: | |
| 1. load curated data, assert schema and no-lookahead invariants | |
| 2. CPCV (N=6, K=2 default) with purge=label horizon, embargo=8 bars | |
| 3. for each combination: train tuning-free strategy, test OOS | |
| 4. reconstruct paths, report PSR/DSR/PBO/MinBTL + raw risk metrics | |
| 5. record the source-hash manifest with every run | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| from crypto_eval.data import load_local, load_manifest | |
| from crypto_eval.engine import run_backtest | |
| from crypto_eval.strategies import STRATEGIES | |
| from crypto_eval.cv import make_cpcv, make_walkforward | |
| from crypto_eval.stats import eval_backtest, eval_trial_set | |
| PERIODS_PER_YEAR = { # bars per year by freq | |
| "1h": 24 * 365, "4h": 6 * 365, "1d": 365, "1m": 60 * 24 * 365, | |
| } | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--data", required=True) | |
| ap.add_argument("--symbol", default="BTCUSDT") | |
| ap.add_argument("--freq", default="1h") | |
| ap.add_argument("--strategy", default="funding_carry") | |
| ap.add_argument("--horizon", type=int, default=8, | |
| help="label horizon in bars (default 8 = one funding interval)") | |
| ap.add_argument("--embargo", type=int, default=8) | |
| ap.add_argument("--cpcv-blocks", type=int, default=6) | |
| ap.add_argument("--cpcv-k", type=int, default=2) | |
| ap.add_argument("--out", default="eval_report.json") | |
| args = ap.parse_args() | |
| data = load_local(args.data, args.symbol, args.freq) | |
| manifest = load_manifest(args.data) | |
| strategy_fn = STRATEGIES[args.strategy] | |
| n = len(data.bars) | |
| print(f"[eval] {args.symbol} {args.freq}: {n:,} bars, " | |
| f"{data.check_funding_coverage(args.freq):,} funding ticks in range") | |
| splitter, combos, audit = make_cpcv( | |
| data.bars.index, horizon=args.horizon, embargo=args.embargo, | |
| freq=args.freq, n_blocks=args.cpcv_blocks, k=args.cpcv_k, | |
| ) | |
| print(f"[eval] CPCV: {len(combos)} combinations, " | |
| f"{len(combos) * args.cpcv_blocks // args.cpcv_k} paths, audit OK") | |
| # strategies here are tuning-free; run OOS per combination | |
| positions = strategy_fn(data.bars, data.funding) | |
| bt = run_backtest(data.bars, positions, data.funding) | |
| oos_returns = [] | |
| for j, (train_idx, test_idx) in enumerate(combos): | |
| # with a tuning-free strategy, train is unused; the test slice is | |
| # the OOS segment. With tuned strategies, tune on train ONLY. | |
| rets = bt["net_return"].to_numpy()[test_idx] | |
| oos_returns.append(rets) | |
| ppy = PERIODS_PER_YEAR[args.freq] | |
| trial_stats = eval_trial_set(oos_returns) | |
| per_combo = [] | |
| for j, rets in enumerate(oos_returns): | |
| s = eval_backtest( | |
| rets, n_trials=1, trial_sr_variance=0.0, | |
| periods_per_year=ppy, | |
| ) | |
| per_combo.append(s) | |
| report = { | |
| "symbol": args.symbol, | |
| "freq": args.freq, | |
| "strategy": args.strategy, | |
| "n_bars": n, | |
| "cpcv": {"n_blocks": args.cpcv_blocks, "k": args.cpcv_k, | |
| "n_combinations": len(combos)}, | |
| "purge_horizon": args.horizon, | |
| "embargo": args.embargo, | |
| "audit": {"ok": audit.ok, "violations": audit.violations}, | |
| "data_manifest_sha256_inputs": sorted(set(manifest["outputs"].keys())), | |
| "costs": { | |
| "total_fees": bt["total_fees"], | |
| "total_slippage": bt["total_slippage"], | |
| "total_funding": bt["total_funding"], | |
| "net_total_pnl": bt["net_total_pnl"], | |
| }, | |
| "trials": trial_stats, | |
| "per_combination": per_combo, | |
| "decision_rule": "accept only if deflated_sharpe > 0.95 and PBO <= 0.5", | |
| } | |
| Path(args.out).write_text(json.dumps(report, indent=2, default=str)) | |
| print(f"[eval] wrote {args.out}") | |
| print(f"[eval] PBO={trial_stats['probability_of_backtest_overfitting']:.3f} " | |
| f"DSR(mean combo)=" | |
| f"{np.mean([c['deflated_sharpe'] for c in per_combo if c['deflated_sharpe'] is not None]):.3f}") | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |