loadJSONConfig only checked != 0 for floats, missing bool overrides. Added def.X = cfg.X || def.X for both booleans.
157 lines
4.4 KiB
Go
157 lines
4.4 KiB
Go
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
|
|
}
|