"""Layer-B artifact audit of a public freqtrade strategy collection. Reads only what each strategy file DECLARES. Runs no backtest. git clone --depth 1 https://github.com/freqtrade/freqtrade-strategies python audit.py freqtrade-strategies/user_data/strategies With no argument it looks for ./freqtrade-strategies/user_data/strategies. """ import re, math, json, pathlib, sys from scipy.stats import norm DEFAULT = pathlib.Path("freqtrade-strategies") / "user_data" / "strategies" ROOT = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT if not ROOT.is_dir(): sys.exit(f"no such directory: {ROOT}\n" f"pass the path to user_data/strategies — see the docstring.") G = 0.5772156649015329 def e_max(n): """Expected max Sharpe of n independent zero-skill trials (Bailey/LdP).""" return (1 - G) * norm.ppf(1 - 1 / n) + G * norm.ppf(1 - 1 / (n * math.e)) def sharpe_required(n_trials, years): """Annualised Sharpe a result must exceed to clear the best-of-N null.""" return e_max(n_trials) / math.sqrt(years) PARAM_RE = re.compile( r'(Int|Decimal|Categorical|Real|Boolean)Parameter\s*\((?P[^\n]*)') EPOCH_RE = re.compile(r'(? 0] with_trials = [r for r in rows if r['total_trials']] print(f"corpus: {N} strategy files\n") print(f" tuned (>=1 optimisable parameter) {len(tuned):3d} ({len(tuned)/N:.0%})") print(f" declare a trial count anywhere {len(with_trials):3d} ({len(with_trials)/N:.0%})") print(f" declare a sample window {sum(r['declares_window'] for r in rows):3d}") print(f" mention fees/commission/slippage {sum(r['mentions_cost'] for r in rows):3d}") print(f" ship a profit % as the headline result {sum(r['declares_profit_pct'] for r in rows):3d}") print(f" flagged by the repo for lookahead bias {sum(r['lookahead_flagged'] for r in rows):3d}") print(f"\n tuned strategies that declare NO trial count: " f"{sum(1 for r in tuned if not r['total_trials'])}/{len(tuned)}") print(f" median optimisable params among tuned: " f"{sorted(r['n_opt_params'] for r in tuned)[len(tuned)//2]}") print(f" max optimisable params: {max(r['n_opt_params'] for r in tuned)} " f"({max(tuned, key=lambda r: r['n_opt_params'])['file']})") print("\n--- declared search budgets ---") print(f"{'file':<34}{'runs':>5}{'largest':>9}{'summed':>9} loss functions") for r in sorted(with_trials, key=lambda r: -(r['total_trials'] or 0)): print(f"{r['file']:<34}{r['runs']:>5}{r['max_trials'] or 0:>9}" f"{r['total_trials']:>9} {len(r['loss_fns'])} {','.join(r['loss_fns'])[:44]}") print("\n--- Sharpe required to clear the declared search, by window ---") print(f"{'trials':>8} | " + " | ".join(f"{y:>5}yr" for y in (0.5, 1, 2, 3, 5))) for n in (10, 70, 100, 1000, 2000, 5000, 40000): cells = " | ".join(f"{sharpe_required(n, y):6.2f} " for y in (0.5, 1, 2, 3, 5)) print(f"{n:>8} | {cells}") print("\n--- over-precise constants (fingerprint of a search) ---") prec = [r for r in rows if (r['roi_max_dp'] or 0) >= 3 or (r['stoploss_dp'] or 0) >= 3] print(f" {len(prec)}/{N} carry an ROI or stoploss constant with >=3 decimal places") for r in prec[:12]: print(f" {r['file']:<34} roi_dp={r['roi_max_dp']} sl_dp={r['stoploss_dp']} " f"{r['roi_vals'][:4]}") out = pathlib.Path.cwd() / "ft_audit.json" json.dump(rows, open(out, "w"), indent=1) print(f"\nwrote {out}")