239 lines
7.6 KiB
Go
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
|
|
}
|