Initial release: TradeMind crypto trading bot with paper/live modes and strategy training
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
"""Lädt und validiert die YAML-Konfiguration (inkl. API-Keys aus Env)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
SUPPORTED_EXCHANGES = (
|
||||
"binance",
|
||||
"kraken",
|
||||
"coinbase",
|
||||
"kucoin",
|
||||
"bitmex",
|
||||
"okx",
|
||||
"bybit",
|
||||
)
|
||||
|
||||
|
||||
def _env(value: Optional[str]) -> str:
|
||||
"""Ersetzt ${ENV_VAR} Referenzen durch den jeweiligen Umgebungsvariable-Wert."""
|
||||
if value and value.startswith("${") and value.endswith("}"):
|
||||
return os.environ.get(value[2:-1], "")
|
||||
return value or ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExchangeConfig:
|
||||
name: str
|
||||
api_key: str = ""
|
||||
api_secret: str = ""
|
||||
password: str = "" # zB. binance passphrase / kucoin passkey
|
||||
sandbox: bool = True
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, name: str, data: Dict[str, Any]) -> "ExchangeConfig":
|
||||
data = data or {}
|
||||
return cls(
|
||||
name=name,
|
||||
api_key=_env(str(data.get("api_key", ""))),
|
||||
api_secret=_env(str(data.get("api_secret", ""))),
|
||||
password=_env(str(data.get("password", ""))),
|
||||
sandbox=bool(data.get("sandbox", True)),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradingConfig:
|
||||
quote_currency: str = "USDT"
|
||||
base_currency: str = "BTC"
|
||||
initial_balance: float = 10_000.0
|
||||
position_size_pct: float = 0.10 # Anteil des Portfolios pro Trade
|
||||
max_open_positions: int = 1
|
||||
fee_pct: float = 0.001 # 0.1 % pro Order (Spread/fee)
|
||||
slippage_pct: float = 0.0005
|
||||
timeframe: str = "1h"
|
||||
candles: int = 500
|
||||
dry_run: bool = True # True = Simulation / Paper-Trading
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "TradingConfig":
|
||||
data = data or {}
|
||||
return cls(
|
||||
quote_currency=data.get("quote_currency", "USDT"),
|
||||
base_currency=data.get("base_currency", "BTC"),
|
||||
initial_balance=float(data.get("initial_balance", 10_000.0)),
|
||||
position_size_pct=float(data.get("position_size_pct", 0.10)),
|
||||
max_open_positions=int(data.get("max_open_positions", 1)),
|
||||
fee_pct=float(data.get("fee_pct", 0.001)),
|
||||
slippage_pct=float(data.get("slippage_pct", 0.0005)),
|
||||
timeframe=data.get("timeframe", "1h"),
|
||||
candles=int(data.get("candles", 500)),
|
||||
dry_run=bool(data.get("dry_run", True)),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StrategyConfig:
|
||||
fast_period: int = 12
|
||||
slow_period: int = 26
|
||||
signal_period: int = 9
|
||||
rsi_period: int = 14
|
||||
rsi_overbought: float = 70.0
|
||||
rsi_oversold: float = 30.0
|
||||
atr_period: int = 14
|
||||
atr_stop_mult: float = 2.5
|
||||
allow_long: bool = True
|
||||
allow_short: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "StrategyConfig":
|
||||
data = data or {}
|
||||
return cls(
|
||||
fast_period=int(data.get("fast_period", 12)),
|
||||
slow_period=int(data.get("slow_period", 26)),
|
||||
signal_period=int(data.get("signal_period", 9)),
|
||||
rsi_period=int(data.get("rsi_period", 14)),
|
||||
rsi_overbought=float(data.get("rsi_overbought", 70.0)),
|
||||
rsi_oversold=float(data.get("rsi_oversold", 30.0)),
|
||||
atr_period=int(data.get("atr_period", 14)),
|
||||
atr_stop_mult=float(data.get("atr_stop_mult", 2.5)),
|
||||
allow_long=bool(data.get("allow_long", True)),
|
||||
allow_short=bool(data.get("allow_short", False)),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrainingConfig:
|
||||
mode: str = "walk-forward" # walk-forward | full
|
||||
train_ratio: float = 0.7
|
||||
generations: int = 20
|
||||
population: int = 40
|
||||
mutation_rate: float = 0.2
|
||||
crossover_rate: float = 0.4
|
||||
fitness_weight_return: float = 0.6
|
||||
fitness_weight_sharpe: float = 0.3
|
||||
fitness_weight_drawdown: float = 0.1
|
||||
seed: int = 42
|
||||
state_file: str = "state/weights.json"
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "TrainingConfig":
|
||||
data = data or {}
|
||||
return cls(
|
||||
mode=data.get("mode", "walk-forward"),
|
||||
train_ratio=float(data.get("train_ratio", 0.7)),
|
||||
generations=int(data.get("generations", 20)),
|
||||
population=int(data.get("population", 40)),
|
||||
mutation_rate=float(data.get("mutation_rate", 0.2)),
|
||||
crossover_rate=float(data.get("crossover_rate", 0.4)),
|
||||
fitness_weight_return=float(data.get("fitness_weight_return", 0.6)),
|
||||
fitness_weight_sharpe=float(data.get("fitness_weight_sharpe", 0.3)),
|
||||
fitness_weight_drawdown=float(data.get("fitness_weight_drawdown", 0.1)),
|
||||
seed=int(data.get("seed", 42)),
|
||||
state_file=data.get("state_file", "state/weights.json"),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
trading: TradingConfig
|
||||
strategy: StrategyConfig
|
||||
training: TrainingConfig
|
||||
exchanges: Dict[str, ExchangeConfig] = field(default_factory=dict)
|
||||
|
||||
def active_exchange(self) -> Optional[ExchangeConfig]:
|
||||
"""Erste Konfiguration mit aktiver API-Anbindung (oder der ersten)."""
|
||||
if not self.exchanges:
|
||||
return None
|
||||
for cfg in self.exchanges.values():
|
||||
if cfg.api_key and cfg.api_secret:
|
||||
return cfg
|
||||
return next(iter(self.exchanges.values()))
|
||||
|
||||
|
||||
def load(path: str) -> Config:
|
||||
with open(path, "r", encoding="utf-8") as fh:
|
||||
raw = yaml.safe_load(fh) or {}
|
||||
|
||||
trading = TradingConfig.from_dict(raw.get("trading", {}))
|
||||
strategy = StrategyConfig.from_dict(raw.get("strategy", {}))
|
||||
training = TrainingConfig.from_dict(raw.get("training", {}))
|
||||
|
||||
exchanges: Dict[str, ExchangeConfig] = {}
|
||||
for name, data in (raw.get("exchanges") or {}).items():
|
||||
name = name.lower()
|
||||
if name not in SUPPORTED_EXCHANGES:
|
||||
raise ValueError(
|
||||
f"Unbekannte Exchange '{name}'. Erlaubt: {', '.join(SUPPORTED_EXCHANGES)}"
|
||||
)
|
||||
exchanges[name] = ExchangeConfig.from_dict(name, data)
|
||||
|
||||
return Config(
|
||||
trading=trading,
|
||||
strategy=strategy,
|
||||
training=training,
|
||||
exchanges=exchanges,
|
||||
)
|
||||
Reference in New Issue
Block a user