Remove legacy LSTM retraining
This commit is contained in:
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
import json
|
||||
import math
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from functools import lru_cache
|
||||
from typing import Any
|
||||
|
||||
from crypto_spot_bot.config import Settings
|
||||
@@ -174,8 +173,6 @@ def _validate_candidates(
|
||||
torch_model = _torch_recurrent_model_name(symbol, lstm_artifact or {})
|
||||
if torch_model and _can_use_torch_recurrent(returns, symbol, lstm_artifact or {}):
|
||||
models.append(torch_model)
|
||||
if _can_use_lstm(returns, settings, symbol, lstm_artifact or {}):
|
||||
models.append("lstm")
|
||||
rows: list[dict[str, float | str]] = []
|
||||
start = max(8, len(returns) - validation_window)
|
||||
for model in models:
|
||||
@@ -211,8 +208,6 @@ def _predict_next_return(
|
||||
return _ar_predict(returns, 3)
|
||||
if model in {"torch_lstm", "torch_gru"}:
|
||||
return _torch_recurrent_predict(returns, symbol, lstm_artifact or {})
|
||||
if model == "lstm" and settings is not None:
|
||||
return _lstm_predict(returns, settings, symbol, lstm_artifact or {})
|
||||
return 0.0
|
||||
|
||||
|
||||
@@ -244,52 +239,6 @@ def _ar_predict(returns: list[float], lag_count: int) -> float:
|
||||
return _clamp(prediction, -cap, cap)
|
||||
|
||||
|
||||
def _can_use_lstm(
|
||||
returns: list[float],
|
||||
settings: Settings,
|
||||
symbol: str | None,
|
||||
lstm_artifact: dict[str, Any],
|
||||
) -> bool:
|
||||
if not settings.time_series_lstm_enabled:
|
||||
return False
|
||||
params = _lstm_params(settings, symbol, lstm_artifact)
|
||||
return len(returns) >= params["lookback"] + 16
|
||||
|
||||
|
||||
def _lstm_params(settings: Settings, symbol: str | None, lstm_artifact: dict[str, Any]) -> dict[str, float | int]:
|
||||
params: dict[str, float | int] = {
|
||||
"lookback": settings.time_series_lstm_lookback,
|
||||
"units": settings.time_series_lstm_units,
|
||||
"ridge": settings.time_series_lstm_ridge,
|
||||
}
|
||||
default_params = lstm_artifact.get("default")
|
||||
if isinstance(default_params, dict):
|
||||
params.update(_clean_lstm_params(default_params))
|
||||
symbols = lstm_artifact.get("symbols")
|
||||
symbol_params = symbols.get(symbol.upper()) if symbol and isinstance(symbols, dict) else None
|
||||
if isinstance(symbol_params, dict):
|
||||
params.update(_clean_lstm_params(symbol_params))
|
||||
return {
|
||||
"lookback": int(_clamp(float(params["lookback"]), 6.0, 128.0)),
|
||||
"units": int(_clamp(float(params["units"]), 2.0, 16.0)),
|
||||
"ridge": _clamp(float(params["ridge"]), 1e-8, 0.5),
|
||||
}
|
||||
|
||||
|
||||
def _clean_lstm_params(data: dict[str, Any]) -> dict[str, float | int]:
|
||||
clean: dict[str, float | int] = {}
|
||||
for key in ("lookback", "units", "ridge"):
|
||||
value = data.get(key)
|
||||
if isinstance(value, (int, float)):
|
||||
clean[key] = value
|
||||
elif isinstance(value, str):
|
||||
try:
|
||||
clean[key] = float(value)
|
||||
except ValueError:
|
||||
continue
|
||||
return clean
|
||||
|
||||
|
||||
def _torch_recurrent_model_name(symbol: str | None, lstm_artifact: dict[str, Any]) -> str | None:
|
||||
entry = _torch_recurrent_entry(symbol, lstm_artifact)
|
||||
if not entry:
|
||||
@@ -519,39 +468,6 @@ def _dot(left: list[float], right: list[float]) -> float:
|
||||
return sum(left[index] * right[index] for index in range(min(len(left), len(right))))
|
||||
|
||||
|
||||
def _lstm_predict(
|
||||
returns: list[float],
|
||||
settings: Settings,
|
||||
symbol: str | None,
|
||||
lstm_artifact: dict[str, Any],
|
||||
) -> float:
|
||||
params = _lstm_params(settings, symbol, lstm_artifact)
|
||||
lookback = int(params["lookback"])
|
||||
units = int(params["units"])
|
||||
ridge = float(params["ridge"])
|
||||
if len(returns) <= lookback + 8:
|
||||
return _predict_next_return("drift", returns)
|
||||
|
||||
scale = _return_scale(returns)
|
||||
normalized = [_clamp(value / scale, -6.0, 6.0) for value in returns]
|
||||
states = _lstm_states(normalized, units)
|
||||
rows: list[list[float]] = []
|
||||
targets: list[float] = []
|
||||
for index in range(lookback, len(returns)):
|
||||
rows.append([1.0] + states[index - 1])
|
||||
targets.append(normalized[index])
|
||||
coeffs = _ols(rows, targets, ridge)
|
||||
if not coeffs:
|
||||
return _predict_next_return("drift", returns)
|
||||
|
||||
features = [1.0] + states[-1]
|
||||
prediction = sum(coeff * feature for coeff, feature in zip(coeffs, features))
|
||||
prediction = _clamp(prediction, -4.0, 4.0) * scale
|
||||
recent_abs = sorted(abs(value) for value in returns[-48:]) if len(returns) >= 8 else [0.01]
|
||||
cap = max(recent_abs[int(len(recent_abs) * 0.9)], 0.0002)
|
||||
return _clamp(prediction, -cap, cap)
|
||||
|
||||
|
||||
def _return_scale(returns: list[float]) -> float:
|
||||
recent = returns[-120:] if len(returns) > 120 else returns
|
||||
values = sorted(abs(value) for value in recent if math.isfinite(value))
|
||||
@@ -562,67 +478,6 @@ def _return_scale(returns: list[float]) -> float:
|
||||
return max(max(median, mean * 0.5), 1e-5)
|
||||
|
||||
|
||||
def _lstm_states(normalized_returns: list[float], units: int) -> list[list[float]]:
|
||||
weights = _lstm_weights(units)
|
||||
hidden = [0.0 for _ in range(units)]
|
||||
cell = [0.0 for _ in range(units)]
|
||||
states: list[list[float]] = []
|
||||
for value in normalized_returns:
|
||||
hidden, cell = _lstm_step(value, hidden, cell, weights)
|
||||
states.append(hidden[:])
|
||||
return states
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def _lstm_weights(units: int) -> tuple[list[list[float]], list[list[list[float]]], list[list[float]]]:
|
||||
input_weights: list[list[float]] = []
|
||||
recurrent_weights: list[list[list[float]]] = []
|
||||
biases: list[list[float]] = []
|
||||
base_biases = (-0.15, 0.70, 0.05, 0.0)
|
||||
for gate in range(4):
|
||||
gate_input: list[float] = []
|
||||
gate_recurrent: list[list[float]] = []
|
||||
gate_bias: list[float] = []
|
||||
for unit in range(units):
|
||||
gate_input.append(0.55 * math.sin((gate + 1) * (unit + 1) * 1.61803398875))
|
||||
gate_recurrent.append(
|
||||
[
|
||||
0.14 * math.sin((gate + 3) * (unit + 1) * (source + 1) * 0.731)
|
||||
for source in range(units)
|
||||
]
|
||||
)
|
||||
gate_bias.append(base_biases[gate] + 0.03 * math.sin((gate + 1) * (unit + 1)))
|
||||
input_weights.append(gate_input)
|
||||
recurrent_weights.append(gate_recurrent)
|
||||
biases.append(gate_bias)
|
||||
return input_weights, recurrent_weights, biases
|
||||
|
||||
|
||||
def _lstm_step(
|
||||
value: float,
|
||||
hidden: list[float],
|
||||
cell: list[float],
|
||||
weights: tuple[list[list[float]], list[list[list[float]]], list[list[float]]],
|
||||
) -> tuple[list[float], list[float]]:
|
||||
input_weights, recurrent_weights, biases = weights
|
||||
units = len(hidden)
|
||||
next_hidden = [0.0 for _ in range(units)]
|
||||
next_cell = [0.0 for _ in range(units)]
|
||||
for unit in range(units):
|
||||
gate_values = []
|
||||
for gate in range(4):
|
||||
raw = input_weights[gate][unit] * value + biases[gate][unit]
|
||||
raw += sum(recurrent_weights[gate][unit][source] * hidden[source] for source in range(units))
|
||||
gate_values.append(raw)
|
||||
input_gate = _sigmoid(gate_values[0])
|
||||
forget_gate = _sigmoid(gate_values[1])
|
||||
output_gate = _sigmoid(gate_values[2])
|
||||
candidate = math.tanh(gate_values[3])
|
||||
next_cell[unit] = forget_gate * cell[unit] + input_gate * candidate
|
||||
next_hidden[unit] = output_gate * math.tanh(next_cell[unit])
|
||||
return next_hidden, next_cell
|
||||
|
||||
|
||||
def _sigmoid(value: float) -> float:
|
||||
if value >= 40:
|
||||
return 1.0
|
||||
|
||||
Reference in New Issue
Block a user