Files
aitrade/pkg/config/config.go
T
kaedwen ea141eb012
Build and Push Docker Image / build-and-push (push) Failing after 1m41s
initial
2026-07-02 20:09:44 +02:00

239 lines
7.6 KiB
Go

package config
import (
"fmt"
"os"
"strconv"
"strings"
"time"
)
type Config struct {
IBGateway IBGatewayConfig `yaml:"ib_gateway"`
Trading TradingConfig `yaml:"trading"`
Database DatabaseConfig `yaml:"database"`
Web WebConfig `yaml:"web"`
OIDC OIDCConfig `yaml:"oidc"`
News NewsConfig `yaml:"news"`
LLMScorer LLMScorerConfig `yaml:"llm_scorer"`
LogLevel string `yaml:"log_level"`
}
type IBGatewayConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
ClientID int `yaml:"client_id"`
MarketDataType int `yaml:"market_data_type"` // 1=Live, 2=Frozen, 3=Delayed(15min), 4=Delayed-Frozen
}
type TradingConfig struct {
Strategy string `yaml:"strategy"`
StopLossEnabled bool `yaml:"stop_loss_enabled"`
StopLossPercent float64 `yaml:"stop_loss_percent"`
MaxTradesPerHour int `yaml:"max_trades_per_hour"`
MaxParallelTrades int `yaml:"max_parallel_trades"`
PendingTime Duration `yaml:"pending_time"`
DryRun bool `yaml:"dry_run"`
DryRunBalance float64 `yaml:"dry_run_balance"`
TradingEnabled bool `yaml:"trading_enabled"`
TradingInterval Duration `yaml:"trading_interval"`
WatchSymbols []string `yaml:"watch_symbols"`
TakeProfitPercent float64 `yaml:"take_profit_percent"`
HoldTimeMinutes int `yaml:"hold_time_minutes"`
NegativeSentiment bool `yaml:"sell_on_negative_sentiment"`
MaxTradeValue float64 `yaml:"max_trade_value"`
}
type DatabaseConfig struct {
Path string `yaml:"path"`
}
type WebConfig struct {
Port string `yaml:"port"`
Host string `yaml:"host"`
}
type OIDCConfig struct {
Enabled bool `yaml:"enabled"`
Issuer string `yaml:"issuer"`
ClientID string `yaml:"client_id"`
ClientSecret string `yaml:"client_secret"`
RedirectURL string `yaml:"redirect_url"`
Scopes []string `yaml:"scopes"`
}
type NewsConfig struct {
PollInterval Duration `yaml:"poll_interval"`
DefaultRateLimit *RateLimit `yaml:"default_rate_limit,omitempty"`
Sources []NewsSource `yaml:"sources"`
}
type NewsSource struct {
Name string `yaml:"name"`
URL string `yaml:"url"`
Type string `yaml:"type"` // "rss", "api", etc.
Enabled bool `yaml:"enabled"`
Auth *NewsAuth `yaml:"auth,omitempty"`
Headers map[string]string `yaml:"headers,omitempty"`
RateLimit *RateLimit `yaml:"rate_limit,omitempty"`
}
type RateLimit struct {
MaxPerHour int `yaml:"max_per_hour,omitempty"` // Max requests per hour, 0 = unlimited
MaxPerDay int `yaml:"max_per_day,omitempty"` // Max requests per day, 0 = unlimited
}
type NewsAuth struct {
Type string `yaml:"type"` // "basic", "bearer", "apikey"
Username string `yaml:"username,omitempty"`
Password string `yaml:"password,omitempty"`
Token string `yaml:"token,omitempty"`
}
type LLMScorerConfig struct {
Enabled bool `yaml:"enabled"`
Endpoint string `yaml:"endpoint"`
ModelName string `yaml:"model_name"`
Timeout Duration `yaml:"timeout"`
Temperature float64 `yaml:"temperature"`
MaxRetries int `yaml:"max_retries"`
EnsembleWeight float64 `yaml:"ensemble_weight"` // 0.0-1.0, 1.0 = LLM only
}
func Load() (*Config, error) {
cfg := &Config{
IBGateway: IBGatewayConfig{
Host: getEnv("IB_GATEWAY_HOST", "127.0.0.1"),
Port: getEnvInt("IB_GATEWAY_PORT", 4001),
ClientID: getEnvInt("IB_CLIENT_ID", 1),
MarketDataType: getEnvInt("IB_MARKET_DATA_TYPE", 3), // Default: 3 = Delayed (15min, kostenlos)
},
Trading: TradingConfig{
Strategy: getEnv("TRADING_STRATEGY", "normal"),
StopLossEnabled: getEnvBool("STOP_LOSS_ENABLED", true),
StopLossPercent: getEnvFloat("STOP_LOSS_PERCENT", 3.0),
MaxTradesPerHour: getEnvInt("MAX_TRADES_PER_HOUR", 6),
MaxParallelTrades: getEnvInt("MAX_PARALLEL_TRADES", 5),
PendingTime: Duration{
Duration: time.Duration(getEnvInt("PENDING_TIME_SECONDS", 300)) * time.Second,
},
DryRun: getEnvBool("DRY_RUN", true),
DryRunBalance: getEnvFloat("DRY_RUN_BALANCE", 100000.0),
TradingEnabled: getEnvBool("TRADING_ENABLED", true),
TradingInterval: Duration{
Duration: time.Duration(getEnvInt("TRADING_INTERVAL_SECONDS", 60)) * time.Second,
},
WatchSymbols: strings.Split(getEnv("WATCH_SYMBOLS", "AAPL,MSFT,GOOGL,TSLA,AMZN"), ","),
TakeProfitPercent: getEnvFloat("TAKE_PROFIT_PERCENT", 5.0),
HoldTimeMinutes: getEnvInt("HOLD_TIME_MINUTES", 30),
NegativeSentiment: getEnvBool("SELL_ON_NEGATIVE_SENTIMENT", true),
MaxTradeValue: getEnvFloat("MAX_TRADE_VALUE", 0.0),
},
Database: DatabaseConfig{
Path: getEnv("DB_PATH", "./data/aitrade.db"),
},
Web: WebConfig{
Port: getEnv("WEB_PORT", "8080"),
Host: getEnv("WEB_HOST", "0.0.0.0"),
},
OIDC: OIDCConfig{
Enabled: getEnvBool("OIDC_ENABLED", false),
Issuer: getEnv("OIDC_ISSUER", ""),
ClientID: getEnv("OIDC_CLIENT_ID", ""),
ClientSecret: getEnv("OIDC_CLIENT_SECRET", ""),
RedirectURL: getEnv("OIDC_REDIRECT_URL", ""),
Scopes: strings.Split(getEnv("OIDC_SCOPES", "openid,profile,email"), ","),
},
News: NewsConfig{
PollInterval: Duration{
Duration: time.Duration(getEnvInt("NEWS_POLL_INTERVAL", 300)) * time.Second,
},
Sources: []NewsSource{}, // Will be loaded from YAML
},
LLMScorer: LLMScorerConfig{
Enabled: getEnvBool("LLM_SCORER_ENABLED", false),
Endpoint: getEnv("LLM_SCORER_ENDPOINT", "http://localhost:11434"),
ModelName: getEnv("LLM_SCORER_MODEL", "mistral"),
Timeout: Duration{
Duration: time.Duration(getEnvInt("LLM_SCORER_TIMEOUT_SECONDS", 30)) * time.Second,
},
Temperature: getEnvFloat("LLM_SCORER_TEMPERATURE", 0.3),
MaxRetries: getEnvInt("LLM_SCORER_MAX_RETRIES", 2),
EnsembleWeight: getEnvFloat("LLM_SCORER_ENSEMBLE_WEIGHT", 0.7),
},
LogLevel: getEnv("LOG_LEVEL", "info"),
}
if err := cfg.Validate(); err != nil {
return nil, err
}
return cfg, nil
}
func (c *Config) Validate() error {
validStrategies := map[string]bool{
"defensive": true,
"normal": true,
"aggressive": true,
}
if !validStrategies[c.Trading.Strategy] {
return fmt.Errorf("invalid strategy: %s (must be defensive, normal, or aggressive)", c.Trading.Strategy)
}
if c.Trading.StopLossPercent < 0 || c.Trading.StopLossPercent > 100 {
return fmt.Errorf("stop loss percent must be between 0 and 100")
}
if c.Trading.MaxTradesPerHour < 1 {
return fmt.Errorf("max trades per hour must be at least 1")
}
if c.Trading.MaxParallelTrades < 1 {
return fmt.Errorf("max parallel trades must be at least 1")
}
if c.OIDC.Enabled {
if c.OIDC.Issuer == "" || c.OIDC.ClientID == "" || c.OIDC.ClientSecret == "" {
return fmt.Errorf("OIDC enabled but missing required config (issuer, client_id, or client_secret)")
}
}
return nil
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intVal, err := strconv.Atoi(value); err == nil {
return intVal
}
}
return defaultValue
}
func getEnvFloat(key string, defaultValue float64) float64 {
if value := os.Getenv(key); value != "" {
if floatVal, err := strconv.ParseFloat(value, 64); err == nil {
return floatVal
}
}
return defaultValue
}
func getEnvBool(key string, defaultValue bool) bool {
if value := os.Getenv(key); value != "" {
if boolVal, err := strconv.ParseBool(value); err == nil {
return boolVal
}
}
return defaultValue
}