Daniel Alnawaz commited on
Commit
83c4758
Β·
unverified Β·
1 Parent(s): 682a393

Added API

Browse files
_scan_coins.py CHANGED
@@ -45,18 +45,18 @@ COINS = ['TIA', 'LINK', 'NEAR', 'INJ', 'ARB', 'AVAX', 'SOL', 'BTC', 'ETH',
45
 
46
  # ── Qualification thresholds (minimum to be considered at all) ────────────────
47
  Q = {
48
- 'strategy_conservative': dict(min_ret=2.0, min_pf=1.10, max_dd=12.0),
49
- 'strategy_hype': dict(min_ret=3.0, min_pf=1.15, max_dd=99.0),
50
- 'strategy_scalp': dict(min_ret=1.0, min_pf=1.05, max_dd=15.0),
51
- 'strategy_ml_auto': dict(min_ret=-5.0, min_pf=0.90, max_dd=25.0),
52
  }
53
 
54
  # ── Capital allocation per strategy (must sum to 1.0) ────────────────────────
55
  CAPITAL = {
56
- 'strategy_hype': 0.33,
57
- 'strategy_conservative': 0.33,
58
- 'strategy_scalp': 0.34,
59
- 'strategy_ml_auto': 0.50, # 2 slots = 1.0 total
60
  }
61
 
62
 
@@ -254,7 +254,7 @@ if not slots:
254
  # ML slots share equally from the ML budget.
255
  # ─────────────────────────────────────────────────────────────────────────────
256
  ml_slot_count = sum(1 for s in slots if s['strategy'] == 'strategy_ml_auto')
257
- ml_per_slot = round(CAPITAL['strategy_ml_auto'], 4)
258
 
259
  final_slots = []
260
  for s in slots:
 
45
 
46
  # ── Qualification thresholds (minimum to be considered at all) ────────────────
47
  Q = {
48
+ 'strategy_conservative': dict(min_ret=8.0, min_pf=1.40, max_dd=12.0),
49
+ 'strategy_hype': dict(min_ret=8.0, min_pf=1.30, max_dd=99.0),
50
+ 'strategy_scalp': dict(min_ret=5.0, min_pf=1.20, max_dd=12.0),
51
+ 'strategy_ml_auto': dict(min_ret=8.0, min_pf=1.50, max_dd=15.0),
52
  }
53
 
54
  # ── Capital allocation per strategy (must sum to 1.0) ────────────────────────
55
  CAPITAL = {
56
+ 'strategy_hype': 0.40,
57
+ 'strategy_conservative': 0.40,
58
+ 'strategy_scalp': 0.00, # Disabled (poor recent performance)
59
+ 'strategy_ml_auto': 0.20, # 2 slots = 0.10 each
60
  }
61
 
62
 
 
254
  # ML slots share equally from the ML budget.
255
  # ─────────────────────────────────────────────────────────────────────────────
256
  ml_slot_count = sum(1 for s in slots if s['strategy'] == 'strategy_ml_auto')
257
+ ml_per_slot = round(CAPITAL['strategy_ml_auto'] / max(1, ml_slot_count), 4)
258
 
259
  final_slots = []
260
  for s in slots:
autonomous_bot.py CHANGED
@@ -19,6 +19,7 @@ from collections import deque
19
  from dataclasses import dataclass, field
20
  from datetime import datetime, timedelta, timezone
21
  from http.server import BaseHTTPRequestHandler, HTTPServer
 
22
  from typing import Any, Dict, List, Optional
23
 
24
  import pandas as pd
@@ -1133,10 +1134,11 @@ def main():
1133
 
1134
  bot = AutonomousTradingBot(bot_config, slots)
1135
 
1136
- # ── Health-check server (required by Hugging Face Spaces) ─────────────
1137
  # HF Docker Spaces expect an HTTP listener on port 7860 or the
1138
  # workload is killed after 30 min as "unhealthy".
1139
- _start_health_server()
 
1140
 
1141
  if args.live:
1142
  pk = os.getenv("HYPERLIQUID_PRIVATE_KEY")
@@ -1166,8 +1168,8 @@ def main():
1166
  f"{r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>6.2f}")
1167
 
1168
 
1169
- def _start_health_server(port: int = 7860):
1170
- """Start a minimal HTTP server so Hugging Face Spaces health checks pass."""
1171
  _start_time = datetime.now(timezone.utc)
1172
 
1173
  def _health_body() -> bytes:
@@ -1178,10 +1180,96 @@ def _start_health_server(port: int = 7860):
1178
  "ts": datetime.now(timezone.utc).isoformat(),
1179
  }).encode()
1180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1181
  class _Handler(BaseHTTPRequestHandler):
1182
  def do_GET(self):
1183
- body = _health_body()
1184
- self.send_response(200)
 
 
 
 
 
 
 
1185
  self.send_header("Content-Type", "application/json")
1186
  self.send_header("Content-Length", str(len(body)))
1187
  self.end_headers()
@@ -1200,12 +1288,16 @@ def _start_health_server(port: int = 7860):
1200
  def _serve():
1201
  try:
1202
  server = HTTPServer(("0.0.0.0", port), _Handler)
1203
- logger.info(f"Health-check server listening on port {port}")
 
 
 
 
1204
  server.serve_forever()
1205
  except Exception as e:
1206
- logger.warning(f"Health-check server failed: {e}")
1207
 
1208
- t = threading.Thread(target=_serve, daemon=True, name="health-server")
1209
  t.start()
1210
 
1211
 
 
19
  from dataclasses import dataclass, field
20
  from datetime import datetime, timedelta, timezone
21
  from http.server import BaseHTTPRequestHandler, HTTPServer
22
+ from pathlib import Path
23
  from typing import Any, Dict, List, Optional
24
 
25
  import pandas as pd
 
1134
 
1135
  bot = AutonomousTradingBot(bot_config, slots)
1136
 
1137
+ # ── API + Health-check server (required by Hugging Face Spaces) ─────────────
1138
  # HF Docker Spaces expect an HTTP listener on port 7860 or the
1139
  # workload is killed after 30 min as "unhealthy".
1140
+ # Also serves API endpoints for monitoring from Telegram
1141
+ _start_health_server(bot_instance=bot)
1142
 
1143
  if args.live:
1144
  pk = os.getenv("HYPERLIQUID_PRIVATE_KEY")
 
1168
  f"{r.total_return_pct:>7.2f} | {r.profit_factor:>4.2f} | {r.max_drawdown_pct:>6.2f}")
1169
 
1170
 
1171
+ def _start_health_server(port: int = 7860, bot_instance: Optional[AutonomousBot] = None):
1172
+ """Start HTTP server with health check + API endpoints (for HuggingFace Spaces)."""
1173
  _start_time = datetime.now(timezone.utc)
1174
 
1175
  def _health_body() -> bytes:
 
1180
  "ts": datetime.now(timezone.utc).isoformat(),
1181
  }).encode()
1182
 
1183
+ def _get_api_response(path: str) -> tuple[int, bytes]:
1184
+ """Route API requests and return (status_code, response_body)."""
1185
+ if path == "/api/status":
1186
+ try:
1187
+ data = {
1188
+ "timestamp": datetime.now(timezone.utc).isoformat(),
1189
+ "bot_active": True,
1190
+ "slots": []
1191
+ }
1192
+ if bot_instance:
1193
+ for slot in bot_instance.slots:
1194
+ data["slots"].append({
1195
+ "strategy": slot.strategy_name,
1196
+ "coin": slot.coin,
1197
+ "timeframe": slot.timeframe,
1198
+ "capital_fraction": slot.capital_frac,
1199
+ })
1200
+ return 200, json.dumps(data).encode()
1201
+ except Exception as e:
1202
+ return 500, json.dumps({"error": str(e)}).encode()
1203
+
1204
+ elif path == "/api/ml/progress":
1205
+ try:
1206
+ from pathlib import Path
1207
+ memory_dir = Path(__file__).parent / "reports"
1208
+ data = {"coins": {}, "summary": {"total_trades": 0, "coins_tracked": 0}}
1209
+
1210
+ for path_obj in memory_dir.glob("ml_auto_memory_*.jsonl"):
1211
+ coin = path_obj.stem.replace("ml_auto_memory_", "")
1212
+ try:
1213
+ with open(path_obj) as f:
1214
+ records = [json.loads(line) for line in f if line.strip()]
1215
+ if not records:
1216
+ continue
1217
+
1218
+ trades = len(records)
1219
+ wins = sum(1 for r in records if r.get("won"))
1220
+ win_rate = wins / trades if trades else 0
1221
+
1222
+ recent = records[-20:] if len(records) > 20 else records
1223
+ recent_wins = sum(1 for r in recent if r.get("won"))
1224
+ recent_wr = recent_wins / len(recent) if recent else 0
1225
+
1226
+ if recent_wr > win_rate + 0.05:
1227
+ trend = "🟒 IMPROVING"
1228
+ elif abs(recent_wr - win_rate) <= 0.05:
1229
+ trend = "🟑 STABLE"
1230
+ else:
1231
+ trend = "πŸ”΄ DEGRADING"
1232
+
1233
+ data["coins"][coin] = {
1234
+ "total_trades": trades,
1235
+ "win_rate": round(win_rate * 100, 1),
1236
+ "recent_win_rate": round(recent_wr * 100, 1),
1237
+ "trend": trend,
1238
+ }
1239
+ data["summary"]["total_trades"] += trades
1240
+ data["summary"]["coins_tracked"] += 1
1241
+ except Exception as e:
1242
+ logger.debug(f"Error analyzing {coin}: {e}")
1243
+
1244
+ return 200, json.dumps(data).encode()
1245
+ except Exception as e:
1246
+ return 500, json.dumps({"error": str(e)}).encode()
1247
+
1248
+ elif path == "/api/config":
1249
+ try:
1250
+ config_path = Path(__file__).parent / "bot_config.json"
1251
+ if config_path.exists():
1252
+ with open(config_path) as f:
1253
+ config = json.load(f)
1254
+ return 200, json.dumps(config).encode()
1255
+ return 404, json.dumps({"error": "Config not found"}).encode()
1256
+ except Exception as e:
1257
+ return 500, json.dumps({"error": str(e)}).encode()
1258
+
1259
+ else:
1260
+ return 404, json.dumps({"error": "Endpoint not found"}).encode()
1261
+
1262
  class _Handler(BaseHTTPRequestHandler):
1263
  def do_GET(self):
1264
+ path = self.path.split("?")[0] # Remove query string
1265
+
1266
+ if path.startswith("/api/"):
1267
+ status, body = _get_api_response(path)
1268
+ else:
1269
+ # Health check for root path
1270
+ status, body = 200, _health_body()
1271
+
1272
+ self.send_response(status)
1273
  self.send_header("Content-Type", "application/json")
1274
  self.send_header("Content-Length", str(len(body)))
1275
  self.end_headers()
 
1288
  def _serve():
1289
  try:
1290
  server = HTTPServer(("0.0.0.0", port), _Handler)
1291
+ logger.info(f"Bot API server listening on port {port}")
1292
+ logger.info(f" / - Health check")
1293
+ logger.info(f" /api/status - Bot status & slots")
1294
+ logger.info(f" /api/ml/progress - ML learning stats")
1295
+ logger.info(f" /api/config - Bot configuration")
1296
  server.serve_forever()
1297
  except Exception as e:
1298
+ logger.warning(f"API server failed: {e}")
1299
 
1300
+ t = threading.Thread(target=_serve, daemon=True, name="api-server")
1301
  t.start()
1302
 
1303
 
bot_config.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "generated_at": "2026-05-27T01:44:42.822174+00:00",
3
  "scan_days": 180,
4
  "ml_backtest_seed_path": "/Users/daniel.a/hyperliquid-bot/reports/ml_auto_backtest_seed.json",
5
  "slots": [
@@ -7,31 +7,25 @@
7
  "strategy": "strategy_hype",
8
  "coin": "TIA",
9
  "timeframe": "1h",
10
- "capital_frac": 0.33
11
  },
12
  {
13
  "strategy": "strategy_conservative",
14
  "coin": "WIF",
15
  "timeframe": "1h",
16
- "capital_frac": 0.33
17
- },
18
- {
19
- "strategy": "strategy_scalp",
20
- "coin": "ARB",
21
- "timeframe": "30m",
22
- "capital_frac": 0.17
23
  },
24
  {
25
  "strategy": "strategy_ml_auto",
26
  "coin": "SUI",
27
  "timeframe": "1h",
28
- "capital_frac": 0.085
29
  },
30
  {
31
  "strategy": "strategy_ml_auto",
32
  "coin": "NEAR",
33
  "timeframe": "1h",
34
- "capital_frac": 0.085
35
  }
36
  ]
37
  }
 
1
  {
2
+ "generated_at": "2026-05-28T02:26:19.496078+00:00",
3
  "scan_days": 180,
4
  "ml_backtest_seed_path": "/Users/daniel.a/hyperliquid-bot/reports/ml_auto_backtest_seed.json",
5
  "slots": [
 
7
  "strategy": "strategy_hype",
8
  "coin": "TIA",
9
  "timeframe": "1h",
10
+ "capital_frac": 0.4
11
  },
12
  {
13
  "strategy": "strategy_conservative",
14
  "coin": "WIF",
15
  "timeframe": "1h",
16
+ "capital_frac": 0.4
 
 
 
 
 
 
17
  },
18
  {
19
  "strategy": "strategy_ml_auto",
20
  "coin": "SUI",
21
  "timeframe": "1h",
22
+ "capital_frac": 0.1
23
  },
24
  {
25
  "strategy": "strategy_ml_auto",
26
  "coin": "NEAR",
27
  "timeframe": "1h",
28
+ "capital_frac": 0.1
29
  }
30
  ]
31
  }
from DELETED
File without changes
investigate_config.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Investigate what changed with scalp and ml_auto strategies."""
3
+
4
+ import json
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ # Load current config
9
+ config_path = Path("bot_config.json")
10
+ with open(config_path) as f:
11
+ current_config = json.load(f)
12
+
13
+ print("=" * 70)
14
+ print("CURRENT BOT CONFIG")
15
+ print("=" * 70)
16
+ print(json.dumps(current_config, indent=2))
17
+
18
+ print("\n" + "=" * 70)
19
+ print("GIT HISTORY - Recent backtest runs")
20
+ print("=" * 70)
21
+
22
+ # Get recent commits that touch bot_config
23
+ result = subprocess.run(
24
+ ["git", "log", "--oneline", "-10", "bot_config.json"],
25
+ capture_output=True, text=True
26
+ )
27
+ print(result.stdout)
28
+
29
+ print("\n" + "=" * 70)
30
+ print("Configuration from 2 commits ago (before recent changes)")
31
+ print("=" * 70)
32
+
33
+ result = subprocess.run(
34
+ ["git", "show", "HEAD~2:bot_config.json"],
35
+ capture_output=True, text=True
36
+ )
37
+ if result.returncode == 0:
38
+ print(result.stdout)
39
+ else:
40
+ print("Could not retrieve historical config")
41
+
42
+ print("\n" + "=" * 70)
43
+ print("Analysis: Why did scalp and ml_auto appear in current config?")
44
+ print("=" * 70)
45
+ print("""
46
+ Based on the conversation summary:
47
+ - Scalp was removed in commit 0936622 ("removing scalp")
48
+ - But now it's back in the config with ARB 30m
49
+
50
+ Possible explanations:
51
+ 1. _scan_coins.py was run and re-added scalp with ARB
52
+ 2. User manually added slots back
53
+ 3. Configuration was reset
54
+
55
+ Let's check _scan_coins.py to see if it adds scalp...
56
+ """)
57
+
optimize_ml_coins.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ ML Auto Coin Optimizer - Find the best coins for ML strategy.
4
+
5
+ Backtests ML Auto on many coins to find:
6
+ β€’ Highest return
7
+ β€’ Best risk-adjusted (Sharpe)
8
+ β€’ Most consistent (lowest drawdown)
9
+ β€’ Best fit for ensemble learning
10
+ """
11
+
12
+ import sys
13
+ import importlib
14
+ import json
15
+ from datetime import datetime, timedelta, timezone
16
+ from pathlib import Path
17
+
18
+ import pandas as pd
19
+ from hbot import HyperliquidAPI
20
+ from backtest import StrategyBacktester
21
+
22
+ api = HyperliquidAPI(verify_ssl=False)
23
+
24
+ # Test on these coins (sorted by 180d volume/liquidity)
25
+ TEST_COINS = [
26
+ 'BTC', 'ETH', 'SOL', # Majors
27
+ 'TIA', 'ARB', 'AVAX', 'OP', # L1s/L2s
28
+ 'DOGE', 'WIF', 'LINK', # Alts with good volume
29
+ 'INJ', 'SUI', 'NEAR', 'APT', # High-beta ecosystem
30
+ 'DOGE', 'LINK', 'AVAX', # Diversified
31
+ ]
32
+
33
+ def fetch_candles(coin: str, timeframe: str = "1h", days: int = 180) -> pd.DataFrame:
34
+ """Fetch historical candles."""
35
+ try:
36
+ start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
37
+ raw = api.get_candles(coin, timeframe, start_ms)
38
+
39
+ if not raw:
40
+ return pd.DataFrame()
41
+
42
+ rows = [
43
+ {
44
+ "timestamp": pd.to_datetime(int(x["t"]), unit="ms", utc=True),
45
+ "open": float(x["o"]),
46
+ "high": float(x["h"]),
47
+ "low": float(x["l"]),
48
+ "close": float(x["c"]),
49
+ "volume": float(x["v"]),
50
+ }
51
+ for x in raw
52
+ ]
53
+ df = (
54
+ pd.DataFrame(rows)
55
+ .drop_duplicates("timestamp")
56
+ .sort_values("timestamp")
57
+ .set_index("timestamp")
58
+ )
59
+ return df
60
+ except Exception as e:
61
+ print(f" Error fetching {coin}: {e}")
62
+ return pd.DataFrame()
63
+
64
+
65
+ def backtest_ml_on_coin(coin: str, days: int = 180) -> dict:
66
+ """Backtest ML Auto strategy on a single coin."""
67
+
68
+ # Import fresh
69
+ for mod in ["strategy_ml_auto", "backtest", "strategy"]:
70
+ sys.modules.pop(mod, None)
71
+
72
+ import backtest as bt_mod
73
+ from strategy_ml_auto import StrategyEngine
74
+
75
+ # Fetch data
76
+ df = fetch_candles(coin, "1h", days)
77
+ if df.empty or len(df) < 100:
78
+ return None
79
+
80
+ # Backtest
81
+ try:
82
+ strategy = StrategyEngine({"use_regime_filter": False, "coin": coin})
83
+ tester = StrategyBacktester(
84
+ strategy=strategy,
85
+ starting_capital=1000.0,
86
+ fee_bps=4.32,
87
+ slippage_bps=1.0,
88
+ position_risk_fraction=0.005,
89
+ strategy_name="strategy_ml_auto",
90
+ )
91
+ result = tester.backtest(df, coin, "1h")
92
+ return {
93
+ "coin": coin,
94
+ "trades": result.trades,
95
+ "win_rate": result.win_rate * 100,
96
+ "return_pct": result.total_return_pct,
97
+ "profit_factor": result.profit_factor,
98
+ "sharpe": result.sharpe_ratio,
99
+ "max_dd": result.max_drawdown_pct,
100
+ "fees": result.total_fees_paid,
101
+ }
102
+ except Exception as e:
103
+ print(f" Backtest failed for {coin}: {e}")
104
+ return None
105
+
106
+
107
+ def main():
108
+ print("\n" + "="*90)
109
+ print(" ML AUTO COIN OPTIMIZER β€” Finding Best ML Coins")
110
+ print("="*90)
111
+ print(" Testing 180-day backtest on 1h timeframe across multiple coins...\n")
112
+
113
+ results = []
114
+
115
+ for i, coin in enumerate(sorted(set(TEST_COINS)), 1):
116
+ print(f" [{i:2d}/{len(set(TEST_COINS))}] Testing {coin:6s} ... ", end="", flush=True)
117
+
118
+ result = backtest_ml_on_coin(coin, days=180)
119
+
120
+ if result is None or result["trades"] == 0:
121
+ print("βœ— (no trades)")
122
+ continue
123
+
124
+ print(f"βœ“ {result['trades']:3d} trades | Return: {result['return_pct']:+6.2f}% | "
125
+ f"WR: {result['win_rate']:5.1f}% | Sharpe: {result['sharpe']:6.2f}")
126
+
127
+ results.append(result)
128
+
129
+ if not results:
130
+ print("\n ⚠️ No profitable strategies found!\n")
131
+ return
132
+
133
+ # Sort by composite score
134
+ results_sorted = sorted(
135
+ results,
136
+ key=lambda x: x["return_pct"] * 0.5 + x["sharpe"] * 20 - x["max_dd"] * 0.3,
137
+ reverse=True
138
+ )
139
+
140
+ print("\n" + "="*90)
141
+ print(" TOP ML AUTO COINS (sorted by composite score)")
142
+ print("="*90)
143
+ print(f"{'Rank':<6} {'Coin':<6} {'Trades':<8} {'Return%':<10} {'Win%':<8} "
144
+ f"{'PF':<6} {'Sharpe':<8} {'DD%':<7} {'Score':<8}")
145
+ print("─" * 90)
146
+
147
+ for i, r in enumerate(results_sorted[:15], 1):
148
+ score = r["return_pct"] * 0.5 + r["sharpe"] * 20 - r["max_dd"] * 0.3
149
+
150
+ # Rating
151
+ if r["return_pct"] >= 15:
152
+ rating = "πŸ₯‡"
153
+ elif r["return_pct"] >= 8:
154
+ rating = "πŸ₯ˆ"
155
+ elif r["return_pct"] >= 3:
156
+ rating = "πŸ₯‰"
157
+ else:
158
+ rating = " "
159
+
160
+ print(f"{i:<6} {r['coin']:<6} {r['trades']:<8} {r['return_pct']:>8.2f}% "
161
+ f"{r['win_rate']:>6.1f}% {r['profit_factor']:>5.2f} {r['sharpe']:>7.2f} "
162
+ f"{r['max_dd']:>6.2f}% {score:>7.2f} {rating}")
163
+
164
+ # Analysis
165
+ print("\n" + "="*90)
166
+ print(" ANALYSIS")
167
+ print("="*90)
168
+
169
+ profitable = [r for r in results if r["return_pct"] > 8]
170
+ high_sharpe = [r for r in results if r["sharpe"] > 1.5]
171
+ low_dd = [r for r in results if r["max_dd"] < 8]
172
+
173
+ print(f"\n Profitable (>8%): {len(profitable)} coins")
174
+ if profitable:
175
+ print(f" {', '.join(r['coin'] for r in profitable)}")
176
+
177
+ print(f"\n High Sharpe (>1.5): {len(high_sharpe)} coins")
178
+ if high_sharpe:
179
+ print(f" {', '.join(r['coin'] for r in high_sharpe)}")
180
+
181
+ print(f"\n Low DD (<8%): {len(low_dd)} coins")
182
+ if low_dd:
183
+ print(f" {', '.join(r['coin'] for r in low_dd)}")
184
+
185
+ # Current holdings vs best
186
+ current = ["SUI", "NEAR"]
187
+ print(f"\n Current ML coins: {', '.join(current)}")
188
+
189
+ current_results = {r["coin"]: r for r in results}
190
+ if any(c in current_results for c in current):
191
+ print(f"\n Current performance:")
192
+ for coin in current:
193
+ if coin in current_results:
194
+ r = current_results[coin]
195
+ print(f" {coin}: {r['return_pct']:+6.2f}% | {r['trades']:3d} trades | "
196
+ f"Sharpe {r['sharpe']:6.2f}")
197
+
198
+ best = results_sorted[0]
199
+ print(f"\n Best alternative:")
200
+ print(f" {best['coin']}: {best['return_pct']:+6.2f}% | {best['trades']:3d} trades | "
201
+ f"Sharpe {best['sharpe']:6.2f}")
202
+
203
+ improvement = best["return_pct"] - current_results[current[0]]["return_pct"]
204
+ print(f"\n Potential improvement: {improvement:+.2f}% by swapping to {best['coin']}")
205
+
206
+ print("\n" + "="*90 + "\n")
207
+
208
+ # Generate recommendations
209
+ print(" πŸ’‘ RECOMMENDATIONS:\n")
210
+
211
+ if len(profitable) >= 2:
212
+ top_2 = results_sorted[:2]
213
+ print(f" βœ… Replace SUI+NEAR with top 2 performers:")
214
+ print(f" β€’ {top_2[0]['coin']}: {top_2[0]['return_pct']:+.2f}%")
215
+ print(f" β€’ {top_2[1]['coin']}: {top_2[1]['return_pct']:+.2f}%")
216
+ print(f" Expected blended return: {(top_2[0]['return_pct'] + top_2[1]['return_pct'])/2:+.2f}%\n")
217
+
218
+ print(" ⚠️ Current SUI+NEAR performance:")
219
+ for coin in current:
220
+ if coin in current_results:
221
+ r = current_results[coin]
222
+ status = "πŸ“ˆ" if r['return_pct'] > 0 else "πŸ“‰"
223
+ print(f" {status} {coin}: {r['return_pct']:+6.2f}% ({r['trades']} trades)")
224
+
225
+ print("\n")
226
+
227
+
228
+ if __name__ == "__main__":
229
+ main()
230
+
reports/ml_auto_backtest_seed.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "generated_at": "2026-05-26T01:27:11.471248+00:00",
3
  "days": 180,
4
  "coins": {
5
  "INJ_1h": {
@@ -17,23 +17,23 @@
17
  "coin": "SUI",
18
  "timeframe": "1h",
19
  "days": 180,
20
- "trades": 51,
21
- "win_rate": 0.3922,
22
- "total_return_pct": 16.6672,
23
- "profit_factor": 1.5335,
24
- "max_drawdown_pct": 7.3214,
25
- "sharpe_ratio": 1.4458
26
  },
27
  "WIF_1h": {
28
  "coin": "WIF",
29
  "timeframe": "1h",
30
  "days": 180,
31
- "trades": 47,
32
- "win_rate": 0.2553,
33
- "total_return_pct": 2.0146,
34
- "profit_factor": 1.082,
35
- "max_drawdown_pct": 15.3181,
36
- "sharpe_ratio": 0.3044
37
  },
38
  "NEAR_1h": {
39
  "coin": "NEAR",
@@ -50,89 +50,89 @@
50
  "coin": "ARB",
51
  "timeframe": "1h",
52
  "days": 180,
53
- "trades": 47,
54
- "win_rate": 0.2979,
55
- "total_return_pct": -0.7004,
56
- "profit_factor": 1.0116,
57
  "max_drawdown_pct": 9.0079,
58
- "sharpe_ratio": 0.0925
59
  },
60
  "TIA_1h": {
61
  "coin": "TIA",
62
  "timeframe": "1h",
63
  "days": 180,
64
- "trades": 81,
65
- "win_rate": 0.2469,
66
- "total_return_pct": -14.4036,
67
- "profit_factor": 0.7708,
68
- "max_drawdown_pct": 18.1684,
69
- "sharpe_ratio": -0.9351
70
  },
71
  "ETH_1h": {
72
  "coin": "ETH",
73
  "timeframe": "1h",
74
  "days": 180,
75
- "trades": 87,
76
- "win_rate": 0.3218,
77
- "total_return_pct": 3.1797,
78
- "profit_factor": 1.1092,
79
- "max_drawdown_pct": 13.5227,
80
- "sharpe_ratio": 0.5133
81
  },
82
  "AVAX_1h": {
83
  "coin": "AVAX",
84
  "timeframe": "1h",
85
  "days": 180,
86
- "trades": 58,
87
- "win_rate": 0.2241,
88
- "total_return_pct": -6.1659,
89
- "profit_factor": 0.9099,
90
- "max_drawdown_pct": 13.961,
91
- "sharpe_ratio": -0.2511
92
  },
93
  "LINK_1h": {
94
  "coin": "LINK",
95
  "timeframe": "1h",
96
  "days": 180,
97
- "trades": 44,
98
- "win_rate": 0.1818,
99
- "total_return_pct": -12.2838,
100
- "profit_factor": 0.6972,
101
- "max_drawdown_pct": 13.1115,
102
- "sharpe_ratio": -0.9768
103
  },
104
  "OP_1h": {
105
  "coin": "OP",
106
  "timeframe": "1h",
107
  "days": 180,
108
- "trades": 23,
109
- "win_rate": 0.2174,
110
- "total_return_pct": -2.6188,
111
- "profit_factor": 0.8857,
112
- "max_drawdown_pct": 9.8678,
113
- "sharpe_ratio": -0.2088
114
  },
115
  "DOGE_1h": {
116
  "coin": "DOGE",
117
  "timeframe": "1h",
118
  "days": 180,
119
- "trades": 59,
120
- "win_rate": 0.2373,
121
- "total_return_pct": -3.3419,
122
- "profit_factor": 0.9756,
123
- "max_drawdown_pct": 20.2514,
124
- "sharpe_ratio": -0.0191
125
  },
126
  "APT_1h": {
127
  "coin": "APT",
128
  "timeframe": "1h",
129
  "days": 180,
130
- "trades": 72,
131
- "win_rate": 0.1528,
132
- "total_return_pct": -24.91,
133
- "profit_factor": 0.5768,
134
- "max_drawdown_pct": 25.7906,
135
- "sharpe_ratio": -1.96
136
  },
137
  "BTC_1h": {
138
  "coin": "BTC",
@@ -149,12 +149,12 @@
149
  "coin": "SOL",
150
  "timeframe": "1h",
151
  "days": 180,
152
- "trades": 9,
153
- "win_rate": 0.1111,
154
- "total_return_pct": -5.1493,
155
- "profit_factor": 0.4041,
156
- "max_drawdown_pct": 8.1601,
157
- "sharpe_ratio": -1.2027
158
  }
159
  }
160
  }
 
1
  {
2
+ "generated_at": "2026-05-28T02:01:22.311732+00:00",
3
  "days": 180,
4
  "coins": {
5
  "INJ_1h": {
 
17
  "coin": "SUI",
18
  "timeframe": "1h",
19
  "days": 180,
20
+ "trades": 28,
21
+ "win_rate": 0.3929,
22
+ "total_return_pct": 18.9368,
23
+ "profit_factor": 2.0416,
24
+ "max_drawdown_pct": 5.2638,
25
+ "sharpe_ratio": 1.683
26
  },
27
  "WIF_1h": {
28
  "coin": "WIF",
29
  "timeframe": "1h",
30
  "days": 180,
31
+ "trades": 23,
32
+ "win_rate": 0.2609,
33
+ "total_return_pct": 1.8033,
34
+ "profit_factor": 1.129,
35
+ "max_drawdown_pct": 5.9185,
36
+ "sharpe_ratio": 0.2863
37
  },
38
  "NEAR_1h": {
39
  "coin": "NEAR",
 
50
  "coin": "ARB",
51
  "timeframe": "1h",
52
  "days": 180,
53
+ "trades": 49,
54
+ "win_rate": 0.2857,
55
+ "total_return_pct": -2.7523,
56
+ "profit_factor": 0.9535,
57
  "max_drawdown_pct": 9.0079,
58
+ "sharpe_ratio": -0.0933
59
  },
60
  "TIA_1h": {
61
  "coin": "TIA",
62
  "timeframe": "1h",
63
  "days": 180,
64
+ "trades": 25,
65
+ "win_rate": 0.08,
66
+ "total_return_pct": -16.0572,
67
+ "profit_factor": 0.2677,
68
+ "max_drawdown_pct": 16.0572,
69
+ "sharpe_ratio": -2.8536
70
  },
71
  "ETH_1h": {
72
  "coin": "ETH",
73
  "timeframe": "1h",
74
  "days": 180,
75
+ "trades": 37,
76
+ "win_rate": 0.2432,
77
+ "total_return_pct": -2.038,
78
+ "profit_factor": 0.9889,
79
+ "max_drawdown_pct": 11.5952,
80
+ "sharpe_ratio": 0.0324
81
  },
82
  "AVAX_1h": {
83
  "coin": "AVAX",
84
  "timeframe": "1h",
85
  "days": 180,
86
+ "trades": 83,
87
+ "win_rate": 0.253,
88
+ "total_return_pct": -14.6898,
89
+ "profit_factor": 0.8073,
90
+ "max_drawdown_pct": 18.1576,
91
+ "sharpe_ratio": -0.868
92
  },
93
  "LINK_1h": {
94
  "coin": "LINK",
95
  "timeframe": "1h",
96
  "days": 180,
97
+ "trades": 67,
98
+ "win_rate": 0.2836,
99
+ "total_return_pct": -5.4177,
100
+ "profit_factor": 0.9354,
101
+ "max_drawdown_pct": 13.8572,
102
+ "sharpe_ratio": -0.1937
103
  },
104
  "OP_1h": {
105
  "coin": "OP",
106
  "timeframe": "1h",
107
  "days": 180,
108
+ "trades": 60,
109
+ "win_rate": 0.2667,
110
+ "total_return_pct": -7.1059,
111
+ "profit_factor": 0.8779,
112
+ "max_drawdown_pct": 16.1324,
113
+ "sharpe_ratio": -0.438
114
  },
115
  "DOGE_1h": {
116
  "coin": "DOGE",
117
  "timeframe": "1h",
118
  "days": 180,
119
+ "trades": 79,
120
+ "win_rate": 0.3165,
121
+ "total_return_pct": 2.1745,
122
+ "profit_factor": 1.0875,
123
+ "max_drawdown_pct": 16.9499,
124
+ "sharpe_ratio": 0.4138
125
  },
126
  "APT_1h": {
127
  "coin": "APT",
128
  "timeframe": "1h",
129
  "days": 180,
130
+ "trades": 84,
131
+ "win_rate": 0.2024,
132
+ "total_return_pct": -25.4033,
133
+ "profit_factor": 0.618,
134
+ "max_drawdown_pct": 26.6465,
135
+ "sharpe_ratio": -2.0437
136
  },
137
  "BTC_1h": {
138
  "coin": "BTC",
 
149
  "coin": "SOL",
150
  "timeframe": "1h",
151
  "days": 180,
152
+ "trades": 50,
153
+ "win_rate": 0.2,
154
+ "total_return_pct": -10.6837,
155
+ "profit_factor": 0.7761,
156
+ "max_drawdown_pct": 17.0197,
157
+ "sharpe_ratio": -0.6932
158
  }
159
  }
160
  }
run_all_backtests.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Comprehensive backtest runner for all active strategies in bot config."""
3
+
4
+ import json
5
+ import sys
6
+ from datetime import datetime, timedelta, timezone
7
+ from pathlib import Path
8
+
9
+ import pandas as pd
10
+ from hbot import HyperliquidAPI
11
+ from backtest import StrategyBacktester
12
+ from strategy_conservative import StrategyEngine as ConservativeEngine
13
+ from strategy_hype import StrategyEngine as HypeEngine
14
+ from strategy_ml_auto import StrategyEngine as MLAutoEngine
15
+ from strategy_scalp import StrategyEngine as ScalpEngine
16
+
17
+
18
+ # ── Initialize API with SSL verification disabled ────────────────────
19
+ api = HyperliquidAPI(verify_ssl=False)
20
+
21
+
22
+ # ── Load historical data ──────────────────────────────────────────────
23
+ def fetch_candles(coin: str, timeframe: str, days: int = 180) -> pd.DataFrame:
24
+ """Fetch candles from Hyperliquid API."""
25
+
26
+ interval_map = {
27
+ "1m": "1m", "5m": "5m", "15m": "15m", "30m": "30m",
28
+ "1h": "1h", "4h": "4h", "1d": "1d"
29
+ }
30
+ interval = interval_map.get(timeframe, "1h")
31
+
32
+ try:
33
+ start_ms = int((datetime.now(timezone.utc) - timedelta(days=days)).timestamp() * 1000)
34
+ raw = api.get_candles(coin, interval, start_ms)
35
+
36
+ if not raw:
37
+ print(f" ⚠️ No data for {coin} {interval}")
38
+ return pd.DataFrame()
39
+
40
+ rows = [
41
+ {
42
+ "timestamp": pd.to_datetime(int(x["t"]), unit="ms", utc=True),
43
+ "open": float(x["o"]),
44
+ "high": float(x["h"]),
45
+ "low": float(x["l"]),
46
+ "close": float(x["c"]),
47
+ "volume": float(x["v"]),
48
+ }
49
+ for x in raw
50
+ ]
51
+ df = pd.DataFrame(rows).drop_duplicates("timestamp").sort_values("timestamp").set_index("timestamp")
52
+ return df
53
+
54
+ except Exception as e:
55
+ print(f" βœ— Error fetching {coin} {timeframe}: {e}")
56
+ return pd.DataFrame()
57
+
58
+
59
+ # ── Main backtest runner ──────────────────────────────────────────────
60
+ def main():
61
+ """Run backtest for all strategies in bot config."""
62
+
63
+ config_path = Path(__file__).parent / "bot_config.json"
64
+ with open(config_path) as f:
65
+ config = json.load(f)
66
+
67
+ print(f"\n{'='*70}")
68
+ print(f" BACKTEST REPORT - All Active Strategies")
69
+ print(f" Generated: {datetime.utcnow().isoformat()}")
70
+ print(f"{'='*70}\n")
71
+
72
+ all_results = []
73
+
74
+ for slot in config["slots"]:
75
+ strategy_name = slot["strategy"]
76
+ coin = slot["coin"]
77
+ timeframe = slot["timeframe"]
78
+ capital_frac = slot["capital_frac"]
79
+
80
+ print(f"\nπŸ”„ Testing {strategy_name:25} | {coin} {timeframe} (allocation: {capital_frac:.1%})")
81
+ print(f" {'─'*65}")
82
+
83
+ # Fetch data
84
+ df = fetch_candles(coin, timeframe, days=180)
85
+ if df.empty:
86
+ print(f" βœ— SKIPPED (no data)")
87
+ continue
88
+
89
+ print(f" βœ“ Loaded {len(df)} candles")
90
+
91
+ # Instantiate strategy
92
+ if strategy_name == "strategy_hype":
93
+ strategy = HypeEngine({"use_regime_filter": False})
94
+ elif strategy_name == "strategy_conservative":
95
+ strategy = ConservativeEngine({"use_regime_filter": False})
96
+ elif strategy_name == "strategy_scalp":
97
+ strategy = ScalpEngine({"use_regime_filter": False})
98
+ elif strategy_name == "strategy_ml_auto":
99
+ strategy = MLAutoEngine({"use_regime_filter": False, "coin": coin})
100
+ else:
101
+ print(f" βœ— Unknown strategy: {strategy_name}")
102
+ continue
103
+
104
+ # Run backtest
105
+ tester = StrategyBacktester(
106
+ strategy=strategy,
107
+ starting_capital=1000.0,
108
+ fee_bps=3.0,
109
+ slippage_bps=2.0,
110
+ position_risk_fraction=0.02,
111
+ strategy_name=strategy_name,
112
+ )
113
+
114
+ result = tester.backtest(df, coin, timeframe)
115
+ all_results.append({
116
+ "slot": f"{strategy_name:25}",
117
+ "coin": coin,
118
+ "tf": timeframe,
119
+ "alloc": f"{capital_frac:.1%}",
120
+ "trades": result.trades,
121
+ "win%": f"{result.win_rate*100:.1f}",
122
+ "ret%": f"{result.total_return_pct:+.2f}",
123
+ "pf": f"{result.profit_factor:.2f}",
124
+ "dd%": f"{result.max_drawdown_pct:.2f}",
125
+ "sharpe": f"{result.sharpe_ratio:.2f}",
126
+ "fees": f"${result.total_fees_paid:.2f}",
127
+ })
128
+
129
+ status = "βœ“" if result.total_return_pct >= 5.0 else "βœ—" if result.total_return_pct < 0 else "~"
130
+ print(f" {status} Trades: {result.trades:3d} | Win: {result.win_rate*100:5.1f}% | "
131
+ f"Return: {result.total_return_pct:+6.2f}% | PF: {result.profit_factor:5.2f} | "
132
+ f"DD: {result.max_drawdown_pct:5.2f}% | Sharpe: {result.sharpe_ratio:5.2f}")
133
+ print(f" Fees paid: ${result.total_fees_paid:.2f}")
134
+
135
+ # Print summary table
136
+ print(f"\n{'='*70}")
137
+ print(f" SUMMARY TABLE (180-day backtest)")
138
+ print(f"{'='*70}\n")
139
+
140
+ header = f"{'Strategy':<27} {'Coin':<6} {'TF':<5} {'Alloc':<7} {'Trades':<8} {'Win%':<8} {'Return%':<10} {'PF':<6} {'DD%':<7} {'Sharpe':<8} {'Fees':<10}"
141
+ print(header)
142
+ print("─" * len(header))
143
+
144
+ total_return = 0.0
145
+ for r in all_results:
146
+ ret_pct = float(r["ret%"])
147
+ total_return += ret_pct * float(r["alloc"].strip("%")) / 100.0
148
+
149
+ print(f"{r['slot']:<27} {r['coin']:<6} {r['tf']:<5} {r['alloc']:<7} "
150
+ f"{r['trades']:<8} {r['win%']:<8} {r['ret%']:<10} {r['pf']:<6} "
151
+ f"{r['dd%']:<7} {r['sharpe']:<8} {r['fees']:<10}")
152
+
153
+ print("─" * len(header))
154
+ print(f"{'PORTFOLIO BLENDED':<27} {'':6} {'':5} {'100.0%':<7} "
155
+ f"{'':8} {'':8} {total_return:+10.2f}% {'':6} {'':7} {'':8}")
156
+ print(f"\n{'='*70}\n")
157
+
158
+
159
+ if __name__ == "__main__":
160
+ main()
161
+
162
+
163
+
164
+
165
+
track_ml_learning.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ ML Auto Performance Tracker - Monitor model learning over time.
4
+
5
+ Shows:
6
+ β€’ Live win rate vs baseline rule-only performance
7
+ β€’ Training examples collected per coin
8
+ β€’ Model confidence trends
9
+ β€’ Shadow mode status
10
+ β€’ Recommendation on when to rotate coins
11
+ """
12
+
13
+ import json
14
+ import os
15
+ from pathlib import Path
16
+ from datetime import datetime
17
+ from collections import defaultdict
18
+
19
+ MEMORY_DIR = Path(__file__).parent / "reports"
20
+
21
+ def analyze_memory_file(coin: str):
22
+ """Analyze a single coin's memory ledger."""
23
+ path = MEMORY_DIR / f"ml_auto_memory_{coin}.jsonl"
24
+
25
+ if not path.exists():
26
+ return None
27
+
28
+ records = []
29
+ try:
30
+ with open(path) as f:
31
+ for line in f:
32
+ line = line.strip()
33
+ if line:
34
+ records.append(json.loads(line))
35
+ except Exception as e:
36
+ print(f" Error reading {coin}: {e}")
37
+ return None
38
+
39
+ if not records:
40
+ return None
41
+
42
+ # Calculate stats
43
+ total_trades = len(records)
44
+ wins = sum(1 for r in records if r.get("won"))
45
+ win_rate = wins / total_trades if total_trades else 0
46
+
47
+ # Recent performance (last 20 trades)
48
+ recent = records[-20:]
49
+ recent_wins = sum(1 for r in recent if r.get("won"))
50
+ recent_wr = recent_wins / len(recent) if recent else 0
51
+
52
+ # Historical performance (before last 20)
53
+ historical = records[:-20] if len(records) > 20 else []
54
+ historical_wins = sum(1 for r in historical if r.get("won"))
55
+ historical_wr = historical_wins / len(historical) if historical else 0
56
+
57
+ # Side distribution
58
+ long_trades = sum(1 for r in records if r.get("side") == 1)
59
+ short_trades = sum(1 for r in records if r.get("side") == -1)
60
+
61
+ # Calculate PnL (rough estimate)
62
+ total_pnl = 0
63
+ for r in records:
64
+ entry = float(r.get("entry", 0))
65
+ exit_px = float(r.get("exit", 0))
66
+ side = int(r.get("side", 1))
67
+ if entry and exit_px:
68
+ pnl = (exit_px - entry) * side * abs(exit_px) / 100 # Rough estimate
69
+ total_pnl += pnl if r.get("won") else pnl * -0.2
70
+
71
+ # Newest and oldest trades
72
+ oldest_ts = records[0].get("ts", "unknown")
73
+ newest_ts = records[-1].get("ts", "unknown")
74
+
75
+ return {
76
+ "coin": coin,
77
+ "total_trades": total_trades,
78
+ "win_rate": win_rate,
79
+ "recent_wr": recent_wr,
80
+ "historical_wr": historical_wr,
81
+ "improvement": recent_wr - historical_wr if historical else 0,
82
+ "long_trades": long_trades,
83
+ "short_trades": short_trades,
84
+ "oldest_ts": oldest_ts,
85
+ "newest_ts": newest_ts,
86
+ "memory_file_size": path.stat().st_size,
87
+ }
88
+
89
+
90
+ def main():
91
+ print("\n" + "="*80)
92
+ print(" ML AUTO LEARNING TRACKER β€” Performance Over Time")
93
+ print("="*80 + "\n")
94
+
95
+ # Find all memory files
96
+ memory_files = list(MEMORY_DIR.glob("ml_auto_memory_*.jsonl"))
97
+
98
+ if not memory_files:
99
+ print(" ⚠️ No ML memory files found yet. Start trading to collect data!\n")
100
+ return
101
+
102
+ results = []
103
+ for path in sorted(memory_files):
104
+ coin = path.stem.replace("ml_auto_memory_", "")
105
+ analysis = analyze_memory_file(coin)
106
+ if analysis:
107
+ results.append(analysis)
108
+
109
+ if not results:
110
+ print(" ⚠️ Could not analyze any memory files.\n")
111
+ return
112
+
113
+ # Print detailed report
114
+ print(f"{'Coin':<8} {'Trades':<8} {'Win%':<8} {'Recent%':<10} {'Trend':<12} {'Long:Short':<12} {'Status':<15}")
115
+ print("─" * 80)
116
+
117
+ for r in sorted(results, key=lambda x: x["win_rate"], reverse=True):
118
+ win_pct = r["win_rate"] * 100
119
+ recent_pct = r["recent_wr"] * 100
120
+ improvement = r["improvement"] * 100
121
+
122
+ # Trend indicator
123
+ if improvement > 5:
124
+ trend = f"↗️ +{improvement:.1f}pp"
125
+ status = "🟒 IMPROVING"
126
+ elif improvement > 0:
127
+ trend = f"β†’ +{improvement:.1f}pp"
128
+ status = "🟑 STABLE"
129
+ else:
130
+ trend = f"β†˜ {improvement:.1f}pp"
131
+ status = "πŸ”΄ DEGRADING"
132
+
133
+ side_ratio = f"{r['long_trades']}:{r['short_trades']}"
134
+
135
+ print(f"{r['coin']:<8} {r['total_trades']:<8} {win_pct:>6.1f}% {recent_pct:>8.1f}% "
136
+ f"{trend:<12} {side_ratio:<12} {status:<15}")
137
+
138
+ print("\n" + "="*80)
139
+ print(" INTERPRETATION GUIDE")
140
+ print("="*80 + """
141
+ 🟒 IMPROVING (Recent% > Historical%)
142
+ β†’ Model is learning! Confidence in predictions is rising.
143
+ β†’ Keep coin active, monitor for stability.
144
+
145
+ 🟑 STABLE (Recent% β‰ˆ Historical%)
146
+ β†’ Model has plateaued. Good consistent baseline.
147
+ β†’ Performance is predictable and repeatable.
148
+
149
+ πŸ”΄ DEGRADING (Recent% < Historical%)
150
+ β†’ Model fit to old market regime. Performance drifting.
151
+ β†’ Consider retraining or rotating to new coin.
152
+ β†’ If stays negative >30 trades, rotate out.
153
+
154
+ Win Rate Interpretation:
155
+ β€’ >50% = Excellent (2:1 RR makes this highly profitable)
156
+ β€’ >40% = Good
157
+ β€’ >30% = Acceptable (2.5:1 RR needed)
158
+ β€’ <30% = Poor (not sustainable with fixed RR)
159
+
160
+ Trades per Coin:
161
+ β€’ <10 trades = too early to judge, keep collecting data
162
+ β€’ 10-30 trades = initial verdict forming
163
+ β€’ 30+ trades = reliable signal of model quality
164
+ """ + "="*80 + "\n")
165
+
166
+ # Summary stats
167
+ total_all = sum(r["total_trades"] for r in results)
168
+ avg_wr = sum(r["win_rate"] for r in results) / len(results) if results else 0
169
+
170
+ print(f" πŸ“Š SUMMARY")
171
+ print(f" Total trades across all coins: {total_all}")
172
+ print(f" Average win rate: {avg_wr*100:.1f}%")
173
+ print(f" Coins tracked: {len(results)}")
174
+
175
+ # Recommendations
176
+ print(f"\n πŸ’‘ RECOMMENDATIONS")
177
+ improving = [r for r in results if r["improvement"] > 0.05]
178
+ degrading = [r for r in results if r["improvement"] < -0.05 and r["total_trades"] > 30]
179
+ early = [r for r in results if r["total_trades"] < 10]
180
+
181
+ if improving:
182
+ print(f" βœ… Keep active: {', '.join(r['coin'] for r in improving)} (learning)")
183
+ if degrading:
184
+ print(f" ⚠️ Consider rotating: {', '.join(r['coin'] for r in degrading)} (degrading)")
185
+ if early:
186
+ print(f" ⏳ Collect more data: {', '.join(r['coin'] for r in early)} (<10 trades)")
187
+
188
+ print()
189
+
190
+
191
+ if __name__ == "__main__":
192
+ main()
193
+