package main import ( "encoding/json" "os" "strconv" ) // Config holds all system configuration. // Priority: .env vars > config.json > code defaults. type Config struct { TelegramBotToken string TelegramChatID string AlertCooldownSec int // seconds between alerts for same coin ArbThreshold float64 // minimum net profit % to trigger alert ScanIntervalMs int // how often scanner runs (milliseconds) // Automated trading TradeEnabled bool TradeThreshold float64 // minimum profit % to execute trade TradeAmountUSD float64 // amount per trade in USDT TradeCooldownMs int // ms between trades of same coin // Test mode (no real API keys needed) TestMode bool MockSlippagePct float64 // simulated slippage per order (e.g. 0.01 = 0.01%) // Bitget API BitgetAPIKey string BitgetAPISecret string BitgetPassphrase string // HyperLiquid API HLPrivateKey string // ed25519 private key hex HLAddress string // wallet address } // jsonConfig maps config.json fields (non-secret defaults checked into git). type jsonConfig struct { TestMode bool `json:"test_mode"` TradeEnabled bool `json:"trade_enabled"` ArbThreshold float64 `json:"arb_threshold"` ScanIntervalMs int `json:"scan_interval_ms"` TradeThreshold float64 `json:"trade_threshold"` TradeAmountUSD float64 `json:"trade_amount_usd"` TradeCooldownMs int `json:"trade_cooldown_ms"` AlertCooldownSec int `json:"alert_cooldown_sec"` MockSlippagePct float64 `json:"mock_slippage_pct"` } func LoadConfig() *Config { // 1. Load config.json defaults jsonCfg := loadJSONConfig() // 2. .env vars override config.json getEnv := func(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def } getFloat := func(key string, def float64) float64 { v := os.Getenv(key) if v == "" { return def } f, err := strconv.ParseFloat(v, 64) if err != nil { return def } return f } getBool := func(key string, def bool) bool { v := os.Getenv(key) if v == "" { return def } return v == "1" || v == "true" || v == "yes" } return &Config{ TelegramBotToken: getEnv("TELEGRAM_BOT_TOKEN", ""), TelegramChatID: getEnv("TELEGRAM_CHAT_ID", ""), AlertCooldownSec: int(getFloat("ALERT_COOLDOWN_SEC", float64(jsonCfg.AlertCooldownSec))), ArbThreshold: getFloat("ARB_THRESHOLD", jsonCfg.ArbThreshold), ScanIntervalMs: int(getFloat("SCAN_INTERVAL_MS", float64(jsonCfg.ScanIntervalMs))), TradeEnabled: getBool("TRADE_ENABLED", jsonCfg.TradeEnabled), TradeThreshold: getFloat("TRADE_THRESHOLD", jsonCfg.TradeThreshold), TradeAmountUSD: getFloat("TRADE_AMOUNT_USD", jsonCfg.TradeAmountUSD), TradeCooldownMs: int(getFloat("TRADE_COOLDOWN_MS", float64(jsonCfg.TradeCooldownMs))), TestMode: getBool("TEST_MODE", jsonCfg.TestMode), MockSlippagePct: getFloat("MOCK_SLIPPAGE_PCT", jsonCfg.MockSlippagePct), BitgetAPIKey: getEnv("BITGET_API_KEY", ""), BitgetAPISecret: getEnv("BITGET_API_SECRET", ""), BitgetPassphrase: getEnv("BITGET_PASSPHRASE", ""), HLPrivateKey: getEnv("HL_PRIVATE_KEY", ""), HLAddress: getEnv("HL_ADDRESS", ""), } } func loadJSONConfig() jsonConfig { def := jsonConfig{ ArbThreshold: 0.03, ScanIntervalMs: 500, TradeThreshold: 0.15, TradeAmountUSD: 10, TradeCooldownMs: 30000, AlertCooldownSec: 300, MockSlippagePct: 0.005, } data, err := os.ReadFile("config.json") if err != nil { return def // file not found, use code defaults } var cfg jsonConfig if err := json.Unmarshal(data, &cfg); err != nil { return def } // Only override if the JSON file actually set the field if cfg.ArbThreshold != 0 { def.ArbThreshold = cfg.ArbThreshold } if cfg.ScanIntervalMs != 0 { def.ScanIntervalMs = cfg.ScanIntervalMs } if cfg.TradeThreshold != 0 { def.TradeThreshold = cfg.TradeThreshold } if cfg.TradeAmountUSD != 0 { def.TradeAmountUSD = cfg.TradeAmountUSD } if cfg.TradeCooldownMs != 0 { def.TradeCooldownMs = cfg.TradeCooldownMs } if cfg.AlertCooldownSec != 0 { def.AlertCooldownSec = cfg.AlertCooldownSec } if cfg.MockSlippagePct != 0 { def.MockSlippagePct = cfg.MockSlippagePct } // Boolean fields: zero default is false, so use OR logic // When JSON has true → true || false = true (override) // When JSON has false → false || false = false (keep default) def.TestMode = cfg.TestMode || def.TestMode def.TradeEnabled = cfg.TradeEnabled || def.TradeEnabled return def }