diff --git a/__pycache__/orchestrator.cpython-313.pyc b/__pycache__/orchestrator.cpython-313.pyc index b604ee0..b0bb017 100644 Binary files a/__pycache__/orchestrator.cpython-313.pyc and b/__pycache__/orchestrator.cpython-313.pyc differ diff --git a/config/best_config.json b/config/best_config.json new file mode 100644 index 0000000..b6bbe12 --- /dev/null +++ b/config/best_config.json @@ -0,0 +1,68 @@ +{ + "model_type": "xgboost", + "features": { + "use_price_position": true, + "use_momentum": true, + "use_volatility": true, + "use_volume": true, + "use_cycle": true, + "use_pca": false, + "pca_variance": 0.95, + "use_scaler": true + }, + "target": { + "type": "regression", + "forward_periods_1h": [ + 168, + 720, + 2160 + ], + "forward_periods_4h": [ + 42, + 180, + 540 + ], + "weights": [ + 0.2, + 0.3, + 0.5 + ], + "score_range": [ + 0, + 100 + ] + }, + "hyperparameters": { + "learning_rate": 0.01, + "max_depth": 4, + "n_estimators": 300, + "subsample": 0.8, + "colsample_bytree": 0.8, + "min_child_weight": 20, + "gamma": 0.3, + "reg_alpha": 0.5, + "reg_lambda": 3.0, + "lstm_hidden_size": 128, + "lstm_num_layers": 2, + "lstm_dropout": 0.3, + "lstm_epochs": 100, + "lstm_batch_size": 64, + "lstm_sequence_length": 30, + "lstm_patience": 10 + }, + "strategy": { + "strong_buy_threshold": 65, + "good_buy_threshold": 55, + "poor_threshold": 35 + }, + "training": { + "rolling_window": true, + "rolling_train_size": 2500, + "rolling_test_size": 300, + "walk_forward_windows": 5, + "train_pct": 0.7, + "validation_pct": 0.15, + "test_pct": 0.15 + }, + "timeframe": "4h" +} \ No newline at end of file diff --git a/config/current_config.json b/config/current_config.json index 9ac6e87..b6bbe12 100644 --- a/config/current_config.json +++ b/config/current_config.json @@ -1,92 +1,68 @@ { - "model_type": "hybrid", + "model_type": "xgboost", "features": { - "technical_indicators": [ - "RSI_14", - "RSI_7", - "MACD_line", - "MACD_signal", - "MACD_hist", - "BB_upper", - "BB_lower", - "BB_width", - "ATR_14", - "SMA_20", - "SMA_50", - "EMA_10", - "EMA_20", - "OBV", - "stoch_k", - "stoch_d", - "williams_r", - "CCI_20", - "ROC_10" - ], - "lookback_periods": [ - 3, - 5, - 10, - 20 - ], - "use_volume_features": true, - "use_volatility_features": true, - "use_candle_patterns": false, - "use_lag_features": true, - "lag_periods": [ - 1, - 2, - 3, - 5 - ], - "use_pca": true, + "use_price_position": true, + "use_momentum": true, + "use_volatility": true, + "use_volume": true, + "use_cycle": true, + "use_pca": false, "pca_variance": 0.95, "use_scaler": true }, "target": { - "type": "classification", - "direction": "both", - "horizon_candles": 4, - "threshold_pct": 1.0 + "type": "regression", + "forward_periods_1h": [ + 168, + 720, + 2160 + ], + "forward_periods_4h": [ + 42, + 180, + 540 + ], + "weights": [ + 0.2, + 0.3, + 0.5 + ], + "score_range": [ + 0, + 100 + ] }, "hyperparameters": { - "learning_rate": 0.001, - "max_depth": 5, + "learning_rate": 0.01, + "max_depth": 4, "n_estimators": 300, "subsample": 0.8, "colsample_bytree": 0.8, - "min_child_weight": 5, + "min_child_weight": 20, "gamma": 0.3, - "reg_alpha": 0.1, - "reg_lambda": 5.0, + "reg_alpha": 0.5, + "reg_lambda": 3.0, "lstm_hidden_size": 128, "lstm_num_layers": 2, "lstm_dropout": 0.3, "lstm_epochs": 100, "lstm_batch_size": 64, - "lstm_sequence_length": 20, + "lstm_sequence_length": 30, "lstm_patience": 10 }, "strategy": { - "entry_threshold": 0.65, - "exit_type": "trailing_stop", - "stop_loss_pct": 2.0, - "take_profit_pct": 4.0, - "trailing_stop_pct": 1.5, - "position_sizing": "confidence_scaled", - "max_position_pct": 100, - "min_confidence_to_trade": 0.5, - "dynamic_sl_tp": true, - "atr_sl_multiplier": 1.2, - "atr_tp_multiplier": 3.0 + "strong_buy_threshold": 65, + "good_buy_threshold": 55, + "poor_threshold": 35 }, "training": { + "rolling_window": true, + "rolling_train_size": 2500, + "rolling_test_size": 300, "walk_forward_windows": 5, "train_pct": 0.7, "validation_pct": 0.15, - "test_pct": 0.15, - "rolling_window": true, - "rolling_train_size": 3000, - "rolling_test_size": 200 + "test_pct": 0.15 }, "timeframe": "4h" } \ No newline at end of file diff --git a/config/llm_settings.json b/config/llm_settings.json new file mode 100644 index 0000000..2e532c5 --- /dev/null +++ b/config/llm_settings.json @@ -0,0 +1,21 @@ +{ + "provider": "ollama", + "model": "qwen3.5:27b", + "providers": { + "ollama": { + "base_url": "http://100.100.242.21:11434" + }, + "lmstudio": { + "base_url": "http://100.100.242.21:1234" + }, + "openai": { + "api_key": "" + }, + "anthropic": { + "api_key": "" + }, + "openrouter": { + "api_key": "" + } + } +} diff --git a/dashboard/server.py b/dashboard/server.py index 72aa0e8..1df07ab 100644 --- a/dashboard/server.py +++ b/dashboard/server.py @@ -9,6 +9,7 @@ import os import sys import threading +import requests from fastapi import FastAPI from fastapi.responses import FileResponse, HTMLResponse, JSONResponse from pydantic import BaseModel @@ -23,6 +24,7 @@ app = FastAPI(title="BTC Accumulation Signal Optimizer") CONFIG_DIR = os.path.join(BASE_DIR, "config") RESULTS_DIR = os.path.join(BASE_DIR, "results") ITERATIONS_LOG = os.path.join(RESULTS_DIR, "iterations.jsonl") +LLM_SETTINGS_PATH = os.path.join(CONFIG_DIR, "llm_settings.json") _opt_thread = None @@ -31,6 +33,64 @@ class ConfigUpdate(BaseModel): config: dict +class LLMSettingsUpdate(BaseModel): + provider: str + model: str + providers: dict + + +class TestConnectionRequest(BaseModel): + provider: str + providers: dict + + +class FetchModelsRequest(BaseModel): + provider: str + providers: dict + + +def _load_llm_settings(): + if os.path.exists(LLM_SETTINGS_PATH): + with open(LLM_SETTINGS_PATH) as f: + return json.load(f) + return { + "provider": "ollama", + "model": "qwen3.5:27b", + "providers": { + "ollama": {"base_url": "http://100.100.242.21:11434"}, + "lmstudio": {"base_url": "http://100.100.242.21:1234"}, + "openai": {"api_key": ""}, + "anthropic": {"api_key": ""}, + "openrouter": {"api_key": ""}, + }, + } + + +def _mask_api_key(key): + if not key or len(key) < 8: + return "" + return "••••••••" + key[-4:] + + +def _safe_settings(settings): + """Return settings with API keys masked.""" + out = json.loads(json.dumps(settings)) + for name, cfg in out.get("providers", {}).items(): + if "api_key" in cfg: + cfg["api_key"] = _mask_api_key(cfg["api_key"]) + return out + + +def _merge_api_keys(new_providers, existing_providers): + """Preserve existing API keys when the incoming value is masked.""" + for name, cfg in new_providers.items(): + if "api_key" in cfg: + masked = cfg["api_key"] + if masked.startswith("••••") or masked == "": + existing_key = existing_providers.get(name, {}).get("api_key", "") + cfg["api_key"] = existing_key + + @app.get("/api/status") def api_status(): return orchestrator.get_status() @@ -91,7 +151,11 @@ def api_best(): with open(best_path) as f: config = json.load(f) iterations = orchestrator.load_iteration_history() - best_iter = max(iterations, key=lambda x: x.get("cost_improvement", 0)) if iterations else {} + best_iter = ( + max(iterations, key=lambda x: x.get("cost_improvement", 0)) + if iterations + else {} + ) return {"config": config, "best_iteration": best_iter} @@ -110,32 +174,155 @@ def api_download_best_config(): return JSONResponse({"error": "No best config yet"}, status_code=404) -DASHBOARD_HTML = """ - -
- - -