package main import ( "os" "strconv" "strings" "time" ) type Config struct { QMTBaseURL string QMTToken string AccountType string AccountID string HostKey string APIHost string DataDir string HTTPTimeout time.Duration OrderTimeout time.Duration LoopInterval time.Duration OpenMoney float64 MinCashRatio float64 LossTriggerPct float64 GridStepPct float64 MinProfitPct float64 AdoptExisting bool ReadyCacheStart int WatchTimeout time.Duration ReboundThreshold float64 } func loadConfig() Config { cfg := Config{ QMTBaseURL: env("QMT_BASE_URL", "http://127.0.0.1:10086"), QMTToken: env("QMT_TOKEN", "QMTbyYanweidong"), AccountType: env("QMT_ACCOUNT", "stock"), AccountID: env("ACCOUNT_ID", ""), HostKey: env("HOST_KEY", ""), APIHost: strings.TrimRight(env("API_HOST", "http://139.224.247.176:13499"), "/"), DataDir: env("DATA_DIR", "D:/qmt_strategy_state"), HTTPTimeout: durationEnv("HTTP_TIMEOUT_SEC", 5) * time.Second, OrderTimeout: durationEnv("ORDER_TIMEOUT_SEC", 60) * time.Second, LoopInterval: durationEnv("LOOP_INTERVAL_SEC", 30) * time.Second, OpenMoney: floatEnv("OPEN_MONEY", 5000), MinCashRatio: floatEnv("MIN_CASH_RATIO", 0.1), LossTriggerPct: floatEnv("LOSS_TRIGGER_PCT", -30), GridStepPct: floatEnv("GRID_STEP_PCT", 1), MinProfitPct: floatEnv("MIN_PROFIT_PCT", 2), AdoptExisting: boolEnv("ADOPT_EXISTING_POSITIONS", true), ReadyCacheStart: intEnv("READY_CACHE_START", 925), WatchTimeout: durationEnv("WATCH_TIMEOUT_SEC", 300) * time.Second, ReboundThreshold: floatEnv("REBOUND_THRESHOLD", 0.61), } if strings.TrimSpace(cfg.AccountID) == "" { logf("ERROR", "ACCOUNT_ID 为空") os.Exit(1) } if strings.TrimSpace(cfg.HostKey) == "" { logf("ERROR", "HOST_KEY 为空") os.Exit(1) } if cfg.MinCashRatio < 0 || cfg.MinCashRatio >= 1 { logf("ERROR", "MIN_CASH_RATIO 必须在 [0, 1)") os.Exit(1) } if cfg.OpenMoney <= 0 { logf("ERROR", "OPEN_MONEY 必须大于 0") os.Exit(1) } if err := os.MkdirAll(cfg.DataDir, 0o755); err != nil { logf("ERROR", "创建 DATA_DIR 失败: %v", err) os.Exit(1) } return cfg } func env(key, fallback string) string { if v := strings.TrimSpace(os.Getenv(key)); v != "" { return v } return fallback } func intEnv(key string, fallback int) int { v := strings.TrimSpace(os.Getenv(key)) if v == "" { return fallback } n, err := strconv.Atoi(v) if err != nil { return fallback } return n } func floatEnv(key string, fallback float64) float64 { v := strings.TrimSpace(os.Getenv(key)) if v == "" { return fallback } f, err := strconv.ParseFloat(v, 64) if err != nil { return fallback } return f } func durationEnv(key string, fallbackSec int) time.Duration { return time.Duration(intEnv(key, fallbackSec)) } func boolEnv(key string, fallback bool) bool { v := strings.ToLower(strings.TrimSpace(os.Getenv(key))) if v == "" { return fallback } return v == "1" || v == "true" || v == "yes" }