feat: add web dashboard for BTC ML optimizer
FastAPI dashboard on port 3088 with live iteration tracking, Sharpe ratio chart, LLM analysis panel, config editor, and download links. Orchestrator refactored to support library usage with run_optimization_loop(), stop_flag, and callbacks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
8ff35c1a86
commit
f13e1679cd
+168
@@ -8,6 +8,7 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
@@ -323,5 +324,172 @@ def main():
|
||||
""")
|
||||
|
||||
|
||||
# --- Library API for dashboard integration ---
|
||||
|
||||
# Shared state for dashboard
|
||||
_stop_event = threading.Event()
|
||||
_status = {
|
||||
"state": "idle", # idle, running, completed, error
|
||||
"iteration": 0,
|
||||
"max_iterations": MAX_ITERATIONS,
|
||||
"best_sharpe": 0.0,
|
||||
"error": None,
|
||||
"llm_suggestions": [], # list of {iteration, reasoning, changes}
|
||||
}
|
||||
_status_lock = threading.Lock()
|
||||
|
||||
|
||||
def get_status():
|
||||
"""Get current optimization status (thread-safe)."""
|
||||
with _status_lock:
|
||||
return dict(_status)
|
||||
|
||||
|
||||
def update_status(**kwargs):
|
||||
"""Update status fields (thread-safe)."""
|
||||
with _status_lock:
|
||||
_status.update(kwargs)
|
||||
|
||||
|
||||
def run_optimization_loop(callback=None, config_override=None):
|
||||
"""
|
||||
Run the optimization loop. Designed to be called from a background thread.
|
||||
|
||||
Args:
|
||||
callback: Called after each iteration with (iteration_number, iter_data_dict).
|
||||
config_override: Optional dict to use instead of loading from disk.
|
||||
"""
|
||||
_stop_event.clear()
|
||||
update_status(state="running", iteration=0, error=None, best_sharpe=0.0)
|
||||
|
||||
try:
|
||||
os.makedirs(RESULTS_DIR, exist_ok=True)
|
||||
ensure_data()
|
||||
|
||||
config_path = os.path.join(CONFIG_DIR, "initial_config.json")
|
||||
best_config_path = os.path.join(CONFIG_DIR, "best_config.json")
|
||||
|
||||
if config_override:
|
||||
config = config_override
|
||||
elif os.path.exists(best_config_path):
|
||||
with open(best_config_path) as f:
|
||||
config = json.load(f)
|
||||
else:
|
||||
with open(config_path) as f:
|
||||
config = json.load(f)
|
||||
|
||||
history = load_iteration_history()
|
||||
start_iter = len(history) + 1
|
||||
best_sharpe = max((h["sharpe"] for h in history), default=0)
|
||||
update_status(best_sharpe=best_sharpe)
|
||||
|
||||
setup_windows_remote()
|
||||
scp_to_windows(os.path.join(BASE_DIR, "ml_engine", "train_and_backtest.py"), "train_and_backtest.py")
|
||||
for tf in ["1h", "4h"]:
|
||||
data_file = os.path.join(DATA_DIR, f"btc_{tf}.csv")
|
||||
if os.path.exists(data_file):
|
||||
scp_to_windows(data_file, f"btc_{tf}.csv")
|
||||
|
||||
sys.path.insert(0, os.path.join(BASE_DIR, "llm_client"))
|
||||
from analyzer import analyze_and_suggest
|
||||
|
||||
for iteration in range(start_iter, MAX_ITERATIONS + 1):
|
||||
if _stop_event.is_set():
|
||||
update_status(state="completed")
|
||||
return
|
||||
update_status(iteration=iteration)
|
||||
|
||||
tmp_config = os.path.join(BASE_DIR, "config", "current_config.json")
|
||||
with open(tmp_config, "w") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
scp_to_windows(tmp_config, "config.json")
|
||||
|
||||
try:
|
||||
run_ml_training()
|
||||
except (RuntimeError, subprocess.TimeoutExpired) as e:
|
||||
if callback:
|
||||
callback(iteration, {"error": str(e)})
|
||||
if history:
|
||||
config = history[-1].get("config", config)
|
||||
continue
|
||||
|
||||
results_local = os.path.join(RESULTS_DIR, f"results_iter_{iteration}.json")
|
||||
scp_from_windows("results.json", results_local)
|
||||
with open(results_local) as f:
|
||||
results = json.load(f)
|
||||
|
||||
current_sharpe = results.get("sharpe_ratio", 0)
|
||||
is_best = current_sharpe > best_sharpe
|
||||
if is_best:
|
||||
best_sharpe = current_sharpe
|
||||
with open(best_config_path, "w") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
update_status(best_sharpe=best_sharpe)
|
||||
|
||||
iter_data = {
|
||||
"iteration": iteration,
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"sharpe": current_sharpe,
|
||||
"return": results.get("total_return_pct", 0),
|
||||
"max_drawdown": results.get("max_drawdown_pct", 0),
|
||||
"win_rate": results.get("win_rate", 0),
|
||||
"trades": results.get("trade_count", 0),
|
||||
"profit_factor": results.get("profit_factor", 0),
|
||||
"model_type": config.get("model_type", "unknown"),
|
||||
"is_best": is_best,
|
||||
"config": config,
|
||||
"results": results,
|
||||
}
|
||||
save_iteration(iter_data)
|
||||
history.append(iter_data)
|
||||
|
||||
if callback:
|
||||
callback(iteration, iter_data)
|
||||
|
||||
converged, reason = check_convergence(history)
|
||||
if converged:
|
||||
update_status(state="completed")
|
||||
return
|
||||
|
||||
if iteration >= MAX_ITERATIONS:
|
||||
update_status(state="completed")
|
||||
return
|
||||
|
||||
if _stop_event.is_set():
|
||||
update_status(state="completed")
|
||||
return
|
||||
|
||||
# LLM suggestion
|
||||
try:
|
||||
summary_history = [
|
||||
{k: h[k] for k in ("iteration", "sharpe", "return", "win_rate", "trades", "model_type")}
|
||||
for h in history
|
||||
]
|
||||
new_config, reasoning = analyze_and_suggest(config, results, summary_history)
|
||||
with _status_lock:
|
||||
_status["llm_suggestions"].append({
|
||||
"iteration": iteration,
|
||||
"reasoning": reasoning,
|
||||
})
|
||||
config = new_config
|
||||
except Exception:
|
||||
import random
|
||||
hp = config.get("hyperparameters", {})
|
||||
hp["learning_rate"] = hp.get("learning_rate", 0.05) * random.uniform(0.8, 1.2)
|
||||
hp["max_depth"] = max(3, min(10, hp.get("max_depth", 6) + random.choice([-1, 0, 1])))
|
||||
config["hyperparameters"] = hp
|
||||
|
||||
update_status(state="completed")
|
||||
|
||||
except Exception as e:
|
||||
update_status(state="error", error=str(e))
|
||||
raise
|
||||
|
||||
|
||||
def request_stop():
|
||||
"""Request graceful stop of the optimization loop."""
|
||||
_stop_event.set()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user