#!/usr/bin/env python3 """ HALT DIAGNOSIS 1. Manifest integrity check 2. Repro anomaly check (1332 vs 387 trades) 3. Buy&Hold baseline benchmark """ import json import subprocess import zipfile import hashlib from pathlib import Path from datetime import datetime OUTPUT_DIR = Path("/home/node/.openclaw/workspace/bob_quant/halt_diagnosis") OUTPUT_DIR.mkdir(parents=True, exist_ok=True) FREQTRADE_BASE = Path("/opt/docker/freqtrade/shared_data/user_data") STRATEGIES_DIR = FREQTRADE_BASE / "strategies" CONFIGS_DIR = FREQTRADE_BASE / "configs" BACKTEST_BASE = FREQTRADE_BASE / "backtest_results" DOCKER_ENV = {"DOCKER_HOST": "tcp://socket-proxy:2375"} def get_file_hash(filepath: Path) -> str: """Generate SHA256 hash of file""" if not filepath.exists(): return "FILE_NOT_FOUND" with open(filepath, 'rb') as f: return hashlib.sha256(f.read()).hexdigest()[:16] def analyze_repro_run(run_id: str) -> dict: """Analyze a reproduction run for anomalies""" run_dir = Path(f"/home/node/.openclaw/workspace/bob_quant/exploration_repro_20260305/runs/{run_id}") if not run_dir.exists(): return {"error": "Run directory not found"} # Load params params_file = run_dir / "params.json" params = json.loads(params_file.read_text()) if params_file.exists() else {} # Load metrics metrics_file = run_dir / "metrics.json" metrics = json.loads(metrics_file.read_text()) if metrics_file.exists() else {} # Load stdout for CLI details stdout_file = run_dir / "stdout.txt" stdout = stdout_file.read_text() if stdout_file.exists() else "" # Extract from stdout timeframe = "1h" # Expected timerange = "20230601-20231231" # Expected pair_count = 5 # Expected # Check for anomalies in stdout if "timeframe : 1h" not in stdout.lower() and "timeframe: 1h" not in stdout.lower(): timeframe = "ANOMALY_DETECTED" if "timerange: 20230601-20231231" not in stdout: # Try to find actual timerange for line in stdout.split("\n"): if "timerange" in line.lower(): timerange = line.strip() break # Check pairs if "BTC/USDT" in stdout and "ETH/USDT" in stdout: # Count pairs in pairlist pair_count = stdout.count("/USDT") return { "run_id": run_id, "expected": { "timeframe": "1h", "timerange": "20230601-20231231", "pairs": 5, "ema_period": 20, "entry": 0.995, "exit": 0.985 }, "actual_from_config": params, "metrics": metrics, "anomalies": { "trade_ratio": metrics.get("trades", 0) / 387, # vs expected 387 "timeframe_mismatch": timeframe != "1h", "timerange_mismatch": "20230601-20231231" not in timerange, "too_many_trades": metrics.get("trades", 0) > 800 }, "diagnosis": "CANDY_CRUSH" if metrics.get("trades", 0) > 1000 else "OK" } def run_buy_and_hold(pair: str, timerange: str) -> dict: """Benchmark Buy&Hold for a pair""" run_id = f"BNH_{pair.replace('/', '')}_{timerange}" # Simple B&H strategy strategy_code = f"""from freqtrade.strategy import IStrategy from pandas import DataFrame class {run_id}(IStrategy): timeframe = '1h' stoploss = -0.99 minimal_roi = {{"0": 999}} max_open_trades = 1 def populate_buy_trend(self, dataframe: DataFrame, metadata: dict): dataframe.loc[:, 'buy'] = 0 if len(dataframe) > 0: # Buy on first candle dataframe.iloc[0, dataframe.columns.get_loc('buy')] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict): dataframe.loc[:, 'sell'] = 0 if len(dataframe) > 0: # Sell on last candle dataframe.iloc[-1, dataframe.columns.get_loc('sell')] = 1 return dataframe """ strat_file = STRATEGIES_DIR / f"{run_id}.py" strat_file.write_text(strategy_code) # Config with single pair config = { "max_open_trades": 1, "stake_currency": "USDT", "stake_amount": 1000, "dry_run": True, "dry_run_wallet": 1000, "timeframe": "1h", "trading_mode": "spot", "exchange": {"name": "binance", "pair_whitelist": [pair]}, "pairlists": [{"method": "StaticPairList"}], "telegram": {"enabled": False, "token": "x", "chat_id": "1"}, "api_server": {"enabled": False} } cfg_file = CONFIGS_DIR / f"cfg{run_id}.json" cfg_file.write_text(json.dumps(config, indent=2)) cmd = [ "docker", "run", "--rm", "--network", "host", "-v", f"{FREQTRADE_BASE}:/freqtrade/user_data:rw", "freqtradeorg/freqtrade:stable", "backtesting", "--strategy", run_id, "--config", f"/freqtrade/user_data/configs/cfg{run_id}.json", "--timeframe", "1h", "--timerange", timerange, "--fee", "0.0", # No fees for B&H "--no-color" ] try: proc = subprocess.run(cmd, capture_output=True, text=True, timeout=120, env=DOCKER_ENV) zip_files = list(BACKTEST_BASE.glob(f"*{run_id}*.zip")) if zip_files: with zipfile.ZipFile(max(zip_files, key=lambda p: p.stat().st_mtime), 'r') as zf: jsons = [f for f in zf.namelist() if f.endswith('.json') and '_config' not in f] if jsons: with zf.open(jsons[0]) as f: data = json.load(f) strat = list(data["strategy"].keys())[0] s = data["strategy"][strat] return { "pair": pair, "timerange": timerange, "profit_pct": round((s.get("profit_total") or 0) * 100, 2), "trades": s.get("total_trades", 0), "status": "SUCCESS" } except Exception as e: return {"pair": pair, "timerange": timerange, "status": "FAILED", "error": str(e)} return {"pair": pair, "timerange": timerange, "status": "NO_DATA"} def main(): print("=" * 70) print("šŸ›‘ HALT DIAGNOSIS") print("=" * 70) diagnosis = { "timestamp": datetime.now().isoformat(), "phase": "HALT_DIAGNOSIS", "checks": {} } # Check 1: Repro Anomaly print("\n--- 1. REPRO ANOMALY CHECK (1332 vs 387 trades) ---") repro_analysis = analyze_repro_run("REPRO_001") diagnosis["checks"]["repro_anomaly"] = repro_analysis print(f"Expected trades: 387") print(f"Actual trades: {repro_analysis.get('metrics', {}).get('trades', 'N/A')}") print(f"Trade ratio: {repro_analysis.get('anomalies', {}).get('trade_ratio', 'N/A'):.1f}x") if repro_analysis.get("anomalies", {}).get("too_many_trades"): print("\nšŸ”“ ANOMALY DETECTED: 3.4x more trades than expected!") print("\nPossible causes:") print(" 1. Different data downloaded (price spikes = more EMA crossings)") print(" 2. EMA calculation differs (TA-Lib vs pandas)") print(" 3. Entry logic too loose (close > EMA*0.995 vs close > EMA*0.999)") print(" 4. Config override (max_open_trades, fees, etc.)") # Check 2: Buy & Hold Benchmark print("\n--- 2. BUY & HOLD BENCHMARK ---") pairs = ["BTC/USDT", "ETH/USDT", "SOL/USDT", "ADA/USDT", "AVAX/USDT"] timeranges = ["20230601-20231231", "20240101-20241231"] benchmark_results = [] for tr in timeranges: print(f"\nTimerange: {tr}") range_results = [] for pair in pairs: result = run_buy_and_hold(pair, tr) range_results.append(result) if result.get("status") == "SUCCESS": print(f" {pair}: {result.get('profit_pct')}%") benchmark_results.append({"timerange": tr, "results": range_results}) # Calculate average profits = [r.get("profit_pct", 0) for r in range_results if r.get("status") == "SUCCESS"] if profits: avg = sum(profits) / len(profits) print(f" Average: {avg:.2f}%") diagnosis["checks"]["buy_and_hold"] = benchmark_results # Check 3: Manifest Integrity print("\n--- 3. MANIFEST INTEGRITY CHECK ---") # Check current manifest manifest_file = Path("/home/node/.openclaw/workspace/bob_quant/manifest.json") if manifest_file.exists(): manifest = json.loads(manifest_file.read_text()) integrity = { "manifest_exists": True, "total_runs_documented": manifest.get("summary", {}).get("total_runs_attempted", 0), "positive_weak": manifest.get("summary", {}).get("positive_weak", 0), "files_exist": {} } for report in manifest.get("reports", []): filepath = Path("/home/node/.openclaw/workspace/bob_quant") / Path(report).name integrity["files_exist"][report] = filepath.exists() diagnosis["checks"]["manifest_integrity"] = integrity print(f"Total runs documented: {integrity['total_runs_documented']}") print(f"POSITIVE_WEAK: {integrity['positive_weak']}") print(f"Reports: {sum(integrity['files_exist'].values())}/{len(integrity['files_exist'])} exist") # Save diagnosis diagnosis_file = OUTPUT_DIR / "halt_diagnosis_report.json" with open(diagnosis_file, 'w') as f: json.dump(diagnosis, f, indent=2, default=str) # Summary report print("\n" + "=" * 70) print("šŸ“Š DIAGNOSIS SUMMARY") print("=" * 70) # B&H comparison print("\nBuy&Hold vs Strategy Performance:") for tr_data in benchmark_results: tr = tr_data["timerange"] profits = [r.get("profit_pct", 0) for r in tr_data["results"] if r.get("status") == "SUCCESS"] avg_bnh = sum(profits) / len(profits) if profits else 0 print(f" {tr}:") print(f" Buy&Hold: {avg_bnh:.2f}%") if tr == "20230601-20231231": print(f" EXPLORATION_TEST_MULTI (claim): +10.24%") print(f" Our repro: -89.9%") print(f"\nDiagnosis file: {diagnosis_file}") return diagnosis if __name__ == "__main__": main()