From a2b0bba05fc7adf293a860636a71aefa44c70e8a Mon Sep 17 00:00:00 2001 From: yanweidong Date: Thu, 17 Sep 2026 15:10:46 +0800 Subject: [PATCH] add launcher --- .gitignore | 1 + launcher/build.bat | 21 ++ launcher/qmt_auto_login.ahk | 99 +++++++++ launcher/setup.bat | 35 +++ launcher/start.go | 300 ++++++++++++++++++++++++++ launcher/start.log | 264 +++++++++++++++++++++++ launcher/start_test.go | 28 +++ py-client/etc/liao.yaml | 2 +- py-client/libs/runtime.py | 26 ++- py-client/strategy/trend/positions.py | 5 + 10 files changed, 778 insertions(+), 3 deletions(-) create mode 100644 launcher/build.bat create mode 100644 launcher/qmt_auto_login.ahk create mode 100644 launcher/setup.bat create mode 100644 launcher/start.go create mode 100644 launcher/start.log create mode 100644 launcher/start_test.go diff --git a/.gitignore b/.gitignore index 69acab1..33fbaa3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ logs/ # Go workspace file go.work go.work.sum +setup.txt # env file .env diff --git a/launcher/build.bat b/launcher/build.bat new file mode 100644 index 0000000..0d96b05 --- /dev/null +++ b/launcher/build.bat @@ -0,0 +1,21 @@ +@echo off +setlocal + +where go.exe >nul 2>&1 +if errorlevel 1 ( + echo Go was not found in PATH. + exit /b 1 +) + +pushd "%~dp0" || exit /b 1 +go build -trimpath -ldflags "-s -w -H=windowsgui" -o start.exe start.go +set "BUILD_EXIT=%errorlevel%" +popd + +if not "%BUILD_EXIT%"=="0" ( + echo Failed to build start.exe. + exit /b %BUILD_EXIT% +) + +echo Built "%~dp0start.exe" successfully. +exit /b 0 diff --git a/launcher/qmt_auto_login.ahk b/launcher/qmt_auto_login.ahk new file mode 100644 index 0000000..d2d02be --- /dev/null +++ b/launcher/qmt_auto_login.ahk @@ -0,0 +1,99 @@ +; ============================================================================== +; 国金证券 QMT 自动登录脚本 (精准识别版) +; 适用版本: 2.0.8.300 | 窗口类名: Qt5QWindowIcon +; ============================================================================== +#Requires AutoHotkey v2.0 +#SingleInstance Force +#Warn All, Off + +; ---------- 核心配置区 ---------- +global QMT_PATH := "D:\gjqmt\bin.x64\XtItClient.exe" ; 程序路径自己填 +global USERNAME := EnvGet("QMT_ACCOUNT_ID") ; 账号自己填 +global PASSWORD := EnvGet("QMT_ACCOUNT_PWD") ; 密码自己填 +; 精准窗口识别特征 +global QMT_CLASS := "ahk_class Qt5QWindowIcon" ; 自己抓 国金不用管 理论所有程序都可以 +global QMT_EXE := "ahk_exe XtItClient.exe" ; 自己抓 国金不用管 理论所有程序都可以 +global QMT_TITLE := QMT_CLASS . " " . QMT_EXE ; 不用管 + +; 强制等待时间:60秒 +global FORCE_WAIT_MS := 60000 +; ------------------------------ + +if (USERNAME = "" or PASSWORD = "") +{ + MsgBox("❌ QMT_AC,QMT_PWD 未配置") + ExitApp() +} + +; 1. 环境清理:确保没有残留进程 +if ProcessExist("XtItClient.exe") { + ProcessClose("XtItClient.exe") + Sleep(2000) +} + +; 2. 以管理员权限启动 QMT +try { + Run(QMT_PATH, "", "runas") +} catch Error as e { + MsgBox("启动失败!请检查路径是否正确:`n" . QMT_PATH) + ExitApp() +} + +; 3. 等待窗口初步加载 +; 只要检测到窗口类名 Qt5QWindowIcon 出现,立即启动 30 秒计时 +if !WinWait(QMT_TITLE, , 30) { + MsgBox("❌ QMT 窗口在 30 秒内未启动,流程终止。") + ExitApp() +} + +; 4. 【强制等待】精准 30 秒倒计时 +startTime := A_TickCount +while (A_TickCount - startTime < FORCE_WAIT_MS) { + remaining := Ceil((FORCE_WAIT_MS - (A_TickCount - startTime)) / 1000) + ToolTip("已识别 QMT 窗口,强制等待中... 剩余 " . remaining . " 秒开始输入") + Sleep(100) +} +ToolTip() ; 清除倒计时 + +; 5. 精准激活并执行登录 +if WinExist(QMT_TITLE) { + ; 强制置顶并激活 + WinActivate(QMT_TITLE) + if !WinWaitActive(QMT_TITLE, , 5) { + ToolTip("无法激活 QMT 窗口,请手动点击窗口") + Sleep(2000) + ToolTip() + } + + ; 锁定键鼠,确保输入不被干扰 + BlockInput(true) + + ; --- 填写账号 --- + ; 针对 Qt 界面,全选后 Backspace 是最稳妥的清理方式 + Send("^a{Backspace}") + Sleep(2000) + SendText(USERNAME) + Sleep(2000) + + ; --- 切换到密码框 --- + Send("{Tab}") + Sleep(2000) + + ; --- 填写密码 --- + Send("^a{Backspace}") + Sleep(2000) + SendText(PASSWORD) + Sleep(2000) + + ; --- 回车登录 --- + Send("{Enter}") + + BlockInput(false) ; 解锁键鼠 +} + +; 6. 任务完成 +ToolTip("✅ 30秒强制等待结束,登录流程已完成") +SetTimer () => ToolTip(), -5000 + +; 脚本保持运行。若需登录后自动退出,取消下方注释 +ExitApp() \ No newline at end of file diff --git a/launcher/setup.bat b/launcher/setup.bat new file mode 100644 index 0000000..d6acb5b --- /dev/null +++ b/launcher/setup.bat @@ -0,0 +1,35 @@ +@echo off +setlocal + +set "TASK_NAME=qmt_launcher" +set "LAUNCHER_EXE=%~dp0start.exe" +if not exist "%LAUNCHER_EXE%" ( + call "%~dp0build.bat" + if errorlevel 1 ( + echo Failed to prepare start.exe. + pause + exit /b 1 + ) +) + +powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^ + "$ErrorActionPreference = 'Stop';" ^ + "$taskName = '%TASK_NAME%';" ^ + "$launcherDir = (Resolve-Path -LiteralPath '%~dp0').Path;" ^ + "$workingDir = Split-Path -Parent $launcherDir;" ^ + "$launcherExe = Join-Path $launcherDir 'start.exe';" ^ + "$action = New-ScheduledTaskAction -Execute $launcherExe -WorkingDirectory $workingDir;" ^ + "$trigger = New-ScheduledTaskTrigger -AtLogOn -User ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name);" ^ + "$principal = New-ScheduledTaskPrincipal -UserId ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) -LogonType Interactive -RunLevel Highest;" ^ + "$settings = New-ScheduledTaskSettingsSet -Hidden:$false;" ^ + "Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null" + +if errorlevel 1 ( + echo Failed to create task "%TASK_NAME%". + pause + exit /b 1 +) + +echo Task "%TASK_NAME%" created or updated successfully with a visible window. +pause +exit /b 0 diff --git a/launcher/start.go b/launcher/start.go new file mode 100644 index 0000000..1dca62c --- /dev/null +++ b/launcher/start.go @@ -0,0 +1,300 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "time" + "unsafe" +) + +const ( + collectorURL = "http://139.224.247.176:13499/collector.exe" + statusURL = "http://127.0.0.1:5000/status" + qmtStartupDelay = 5 * time.Minute + statusInterval = 1 * time.Minute + restartCooldown = 15 * time.Minute + failureThreshold = 3 + httpTimeout = 5 * time.Second + createNewProcessGroup = 0x00000200 + errorAlreadyExists = syscall.Errno(183) + mutexName = `Local\qmt_launcher_single_instance` +) + +func acquireSingleInstance() (syscall.Handle, error) { + kernel32 := syscall.NewLazyDLL("kernel32.dll") + createMutex := kernel32.NewProc("CreateMutexW") + name, err := syscall.UTF16PtrFromString(mutexName) + if err != nil { + return 0, err + } + handle, _, callErr := createMutex.Call(0, 0, uintptr(unsafe.Pointer(name))) + if handle == 0 { + return 0, fmt.Errorf("CreateMutexW failed: %v", callErr) + } + if callErr == errorAlreadyExists { + _ = syscall.CloseHandle(syscall.Handle(handle)) + return 0, fmt.Errorf("another launcher instance is already running") + } + return syscall.Handle(handle), nil +} + +var ( + launcherDir string + projectRoot string + collectorPath string + ahkScript string + runScript string + httpClient = &http.Client{Timeout: httpTimeout} +) + +func initPaths() error { + executable, err := os.Executable() + if err != nil { + return fmt.Errorf("locate launcher executable: %w", err) + } + executable, err = filepath.EvalSymlinks(executable) + if err != nil { + return fmt.Errorf("resolve launcher executable: %w", err) + } + launcherDir = filepath.Dir(executable) + projectRoot = filepath.Dir(launcherDir) + collectorPath = filepath.Join(projectRoot, "bin", "collector.exe") + ahkScript = filepath.Join(launcherDir, "qmt_auto_login.ahk") + runScript = filepath.Join(projectRoot, "run.bat") + return nil +} + +func configureLogging() (*os.File, error) { + logFile, err := os.OpenFile(filepath.Join(launcherDir, "start.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) + if err != nil { + return nil, err + } + log.SetOutput(logFile) + log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) + return logFile, nil +} + +func downloadCollector() error { + if info, err := os.Stat(collectorPath); err == nil && !info.IsDir() { + log.Printf("INFO collector.exe already exists: %s", collectorPath) + return nil + } else if err != nil && !os.IsNotExist(err) { + return err + } + + if err := os.MkdirAll(filepath.Dir(collectorPath), 0o755); err != nil { + return err + } + temporaryPath := collectorPath + ".download" + log.Printf("INFO Downloading collector.exe from %s", collectorURL) + + response, err := httpClient.Get(collectorURL) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return fmt.Errorf("download returned HTTP %d", response.StatusCode) + } + + output, err := os.Create(temporaryPath) + if err != nil { + return err + } + _, copyErr := io.Copy(output, response.Body) + closeErr := output.Close() + if copyErr != nil || closeErr != nil { + _ = os.Remove(temporaryPath) + if copyErr != nil { + return copyErr + } + return closeErr + } + info, err := os.Stat(temporaryPath) + if err != nil || info.Size() == 0 { + _ = os.Remove(temporaryPath) + if err != nil { + return err + } + return fmt.Errorf("downloaded collector.exe is empty") + } + if err := os.Rename(temporaryPath, collectorPath); err != nil { + _ = os.Remove(temporaryPath) + return err + } + log.Printf("INFO collector.exe downloaded: %s", collectorPath) + return nil +} + +func launchQMTLogin() error { + if _, err := os.Stat(ahkScript); err != nil { + return fmt.Errorf("AHK script not found: %s", ahkScript) + } + log.Printf("INFO Launching %s", ahkScript) + + shell32 := syscall.NewLazyDLL("shell32.dll") + shellExecute := shell32.NewProc("ShellExecuteW") + verb, _ := syscall.UTF16PtrFromString("open") + file, _ := syscall.UTF16PtrFromString(ahkScript) + result, _, callErr := shellExecute.Call(0, uintptr(unsafe.Pointer(verb)), uintptr(unsafe.Pointer(file)), 0, 0, 1) + if result <= 32 { + return fmt.Errorf("ShellExecuteW failed (%d): %v", result, callErr) + } + return nil +} + +func launchRunScript() error { + if _, err := os.Stat(runScript); err != nil { + return fmt.Errorf("run.bat not found: %s", runScript) + } + log.Printf("INFO Launching %s", runScript) + command := exec.Command("cmd.exe", "/c", runScript) + command.Dir = projectRoot + command.SysProcAttr = &syscall.SysProcAttr{CreationFlags: createNewProcessGroup} + return command.Start() +} + +func restartSystem() error { + if err := launchQMTLogin(); err != nil { + return err + } + log.Printf("INFO Waiting %d seconds for QMT login", int(qmtStartupDelay/time.Second)) + time.Sleep(qmtStartupDelay) + if err := launchRunScript(); err != nil { + return err + } + time.Sleep(statusInterval) + return nil +} + +func systemIsHealthy() bool { + response, err := httpClient.Get(statusURL) + if err != nil { + log.Printf("WARNING Status check failed: %v", err) + return false + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + log.Printf("WARNING Status endpoint returned HTTP %d", response.StatusCode) + return false + } + + body, err := io.ReadAll(response.Body) + if err != nil { + return false + } + + bodyString := strings.ReplaceAll(string(body), "Null", "\"\"") + bodyString = strings.ReplaceAll(bodyString, "NULL", "\"\"") + bodyString = strings.ReplaceAll(bodyString, "NONE", "\"\"") + bodyString = strings.ReplaceAll(bodyString, "None", "\"\"") + bodyString = strings.ReplaceAll(bodyString, "Nil", "\"\"") + + var payload struct { + Status map[string]any `json:"status"` + } + + if err := json.Unmarshal([]byte(bodyString), &payload); err != nil { + log.Printf("WARNING Status check failed: %v, %s", err, bodyString) + return false + } + healthy := payload.Status != nil && payload.Status["qmt_status"] == "connected" + if !healthy { + log.Printf("WARNING System is not fully started: %v", payload.Status) + } + return healthy +} + +func isTradingTime(now time.Time) bool { + weekday := now.Weekday() + if weekday == time.Saturday || weekday == time.Sunday { + return false + } + + hhmm := now.Hour()*100 + now.Minute() + return (hhmm >= 910 && hhmm <= 1130) || (hhmm >= 1240 && hhmm <= 1500) +} + +func run() error { + log.Print("INFO ==================================================") + log.Print("INFO QMT launcher supervisor started") + if err := downloadCollector(); err != nil { + return err + } + consecutiveFailures := 0 + var lastRestart time.Time + wasOutsideTradingTime := false + for { + if !isTradingTime(time.Now()) { + if !wasOutsideTradingTime { + log.Print("INFO Outside trading hours; skipping health check and startup") + } + wasOutsideTradingTime = true + consecutiveFailures = 0 + time.Sleep(statusInterval) + continue + } + if wasOutsideTradingTime { + log.Print("INFO Trading hours started; resuming health checks") + wasOutsideTradingTime = false + } + + if systemIsHealthy() { + consecutiveFailures = 0 + time.Sleep(statusInterval) + continue + } + + consecutiveFailures++ + if consecutiveFailures < failureThreshold { + log.Printf("WARNING Health check failed %d/%d; waiting before restart", consecutiveFailures, failureThreshold) + time.Sleep(statusInterval) + continue + } + if remaining := restartCooldown - time.Since(lastRestart); !lastRestart.IsZero() && remaining > 0 { + log.Printf("WARNING Restart suppressed by cooldown; retrying in approximately %s", remaining.Round(time.Second)) + time.Sleep(statusInterval) + continue + } + + log.Printf("WARNING Health check failed %d consecutive times; restarting system", consecutiveFailures) + if err := restartSystem(); err != nil { + return err + } + lastRestart = time.Now() + consecutiveFailures = 0 + time.Sleep(statusInterval) + } +} + +func main() { + instanceMutex, err := acquireSingleInstance() + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + defer syscall.CloseHandle(instanceMutex) + + if err := initPaths(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + logFile, err := configureLogging() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer logFile.Close() + if err := run(); err != nil { + log.Printf("ERROR Launcher failed: %v", err) + os.Exit(1) + } +} diff --git a/launcher/start.log b/launcher/start.log new file mode 100644 index 0000000..554b25d --- /dev/null +++ b/launcher/start.log @@ -0,0 +1,264 @@ +2026-08-13 15:22:43,135 INFO QMT launcher supervisor started +2026-08-13 15:22:43,135 INFO Downloading collector.exe from http://139.224.247.176/collector.exe +2026-08-13 15:22:43,244 ERROR Launcher cycle failed +Traceback (most recent call last): + File "D:\work\quant\qmt-v2\launcher\start.py", line 107, in main + download_collector() + File "D:\work\quant\qmt-v2\launcher\start.py", line 47, in download_collector + with urlopen(COLLECTOR_URL, timeout=HTTP_TIMEOUT_SECONDS) as response: + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\devapps\Python311\Lib\urllib\request.py", line 216, in urlopen + return opener.open(url, data, timeout) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\devapps\Python311\Lib\urllib\request.py", line 525, in open + response = meth(req, response) + ^^^^^^^^^^^^^^^^^^^ + File "D:\devapps\Python311\Lib\urllib\request.py", line 634, in http_response + response = self.parent.error( + ^^^^^^^^^^^^^^^^^^ + File "D:\devapps\Python311\Lib\urllib\request.py", line 563, in error + return self._call_chain(*args) + ^^^^^^^^^^^^^^^^^^^^^^^ + File "D:\devapps\Python311\Lib\urllib\request.py", line 496, in _call_chain + result = func(*args) + ^^^^^^^^^^^ + File "D:\devapps\Python311\Lib\urllib\request.py", line 643, in http_error_default + raise HTTPError(req.full_url, code, msg, hdrs, fp) +urllib.error.HTTPError: HTTP Error 404: Not Found +2026-08-13 15:26:45,007 INFO QMT launcher supervisor started +2026-08-13 15:26:45,008 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026-08-13 15:26:45,008 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026-08-13 15:26:45,106 INFO Waiting 300 seconds for QMT login +2026-08-13 21:53:04,684 INFO QMT launcher supervisor started +2026-08-13 21:53:04,686 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026-08-13 21:53:04,686 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026-08-13 21:53:06,267 INFO Waiting 180 seconds for QMT login +2026-08-13 22:01:06,389 INFO QMT launcher supervisor started +2026-08-13 22:01:06,389 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026-08-13 22:01:06,389 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026-08-13 22:01:06,463 INFO Waiting 180 seconds for QMT login +2026-08-13 22:04:06,464 INFO Launching D:\work\quant\qmt-v2\run.bat +2026-08-13 23:02:15,722 INFO ================================================== +2026-08-13 23:02:15,722 INFO QMT launcher supervisor started +2026-08-13 23:02:15,722 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026-08-13 23:02:17,756 WARNING Status check failed: +2026-08-13 23:02:17,756 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026-08-13 23:02:17,837 INFO Waiting 300 seconds for QMT login +2026-08-13 23:07:17,838 INFO Launching D:\work\quant\qmt-v2\run.bat +2026-08-13 23:27:05,964 INFO ================================================== +2026-08-13 23:27:05,965 INFO QMT launcher supervisor started +2026-08-13 23:27:05,965 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026-08-13 23:27:08,034 WARNING Status check failed: +2026-08-13 23:27:08,035 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026-08-13 23:27:08,118 INFO Waiting 300 seconds for QMT login +2026/08/13 23:45:13.608527 INFO ================================================== +2026/08/13 23:45:13.622235 INFO QMT launcher supervisor started +2026/08/13 23:45:13.622235 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/13 23:45:13.623496 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/13 23:45:13.624026 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/13 23:45:13.703711 INFO Waiting 300 seconds for QMT login +2026/08/13 23:50:13.704349 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/13 23:52:13.715253 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/13 23:52:13.715407 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/13 23:52:13.767285 INFO Waiting 300 seconds for QMT login +2026/08/14 08:48:36.427941 INFO ================================================== +2026/08/14 08:48:36.961109 INFO QMT launcher supervisor started +2026/08/14 08:48:37.692841 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/14 08:48:37.769022 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/14 08:48:38.177308 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/14 08:48:39.449445 INFO Waiting 300 seconds for QMT login +2026/08/14 08:53:39.559317 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/14 08:55:39.569610 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/14 08:55:39.569610 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/14 08:55:39.622915 INFO Waiting 300 seconds for QMT login +2026/08/14 09:00:39.623224 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/14 09:02:39.640363 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/14 09:02:39.640363 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/14 09:02:39.687327 INFO Waiting 300 seconds for QMT login +2026/08/15 08:50:00.885570 INFO ================================================== +2026/08/15 08:50:01.657003 INFO QMT launcher supervisor started +2026/08/15 08:50:01.659591 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/15 08:50:01.662166 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/15 08:50:01.894393 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/15 08:50:04.507973 INFO Waiting 300 seconds for QMT login +2026/08/16 08:52:26.025434 INFO ================================================== +2026/08/16 08:52:26.104316 INFO QMT launcher supervisor started +2026/08/16 08:52:26.125470 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/16 08:52:26.127520 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/16 08:52:26.129067 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/16 08:52:27.204828 INFO Waiting 300 seconds for QMT login +2026/08/16 08:57:27.234137 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/17 07:48:38.513724 INFO ================================================== +2026/08/17 07:48:38.824692 INFO QMT launcher supervisor started +2026/08/17 07:48:38.856254 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/17 07:48:38.875302 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/17 07:48:39.231263 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/17 07:48:40.845492 INFO Waiting 300 seconds for QMT login +2026/08/17 07:53:40.940886 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/17 09:25:42.472480 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/17 09:25:42.476066 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/17 09:25:42.698749 INFO Waiting 300 seconds for QMT login +2026/08/17 09:30:42.699596 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/17 09:32:42.711957 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/17 09:32:42.712191 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/17 09:32:42.762591 INFO Waiting 300 seconds for QMT login +2026/08/17 09:37:42.763160 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/17 09:40:42.778731 WARNING Status check failed: invalid character 'N' looking for beginning of value +2026/08/17 09:40:42.779241 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/17 09:40:42.832414 INFO Waiting 300 seconds for QMT login +2026/08/18 08:03:14.885643 INFO ================================================== +2026/08/18 08:03:15.353370 INFO QMT launcher supervisor started +2026/08/18 08:03:15.758480 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/18 08:03:16.301320 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/18 08:03:16.385159 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/18 08:03:17.010169 INFO Waiting 300 seconds for QMT login +2026/08/18 08:08:17.204987 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/18 08:10:17.215378 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/18 08:10:17.215747 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/18 08:10:17.258153 INFO Waiting 300 seconds for QMT login +2026/08/18 08:15:17.258601 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/18 13:58:18.503095 WARNING Status check failed: invalid character 'N' looking for beginning of value +2026/08/18 13:58:18.528903 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/18 13:58:19.022561 INFO Waiting 300 seconds for QMT login +2026/08/18 13:59:24.123137 INFO ================================================== +2026/08/18 13:59:24.136062 INFO QMT launcher supervisor started +2026/08/18 13:59:24.136062 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/18 13:59:24.139154 WARNING Status check failed: invalid character 'N' looking for beginning of value +2026/08/18 13:59:24.139154 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/18 13:59:24.222221 INFO Waiting 300 seconds for QMT login +2026/08/18 14:03:19.023203 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/18 14:04:24.222305 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/18 14:05:19.039743 WARNING Status check failed: invalid character 'N' looking for beginning of value +2026/08/18 14:05:19.039889 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/18 14:05:19.091765 INFO Waiting 300 seconds for QMT login +2026/08/18 16:50:14.766362 INFO ================================================== +2026/08/18 16:50:14.813222 INFO QMT launcher supervisor started +2026/08/18 16:50:14.829377 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/18 16:50:15.043111 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/18 16:50:15.239254 WARNING Health check failed 1/3; waiting before restart +2026/08/18 16:51:15.239641 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/18 16:51:15.240155 WARNING Health check failed 2/3; waiting before restart +2026/08/18 16:52:15.241716 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/18 16:52:15.241716 WARNING Health check failed 3 consecutive times; restarting system +2026/08/18 16:52:15.242314 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/18 16:52:15.333484 INFO Waiting 300 seconds for QMT login +2026/08/18 16:57:15.334509 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/19 19:22:16.079187 INFO ================================================== +2026/08/19 19:22:16.773161 INFO QMT launcher supervisor started +2026/08/19 19:22:16.891082 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/19 19:22:16.941701 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/19 19:22:17.669752 WARNING Health check failed 1/3; waiting before restart +2026/08/19 19:23:17.696305 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/19 19:23:17.696812 WARNING Health check failed 2/3; waiting before restart +2026/08/19 19:24:17.698348 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/19 19:24:17.698348 WARNING Health check failed 3 consecutive times; restarting system +2026/08/19 19:24:17.699493 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/19 19:24:17.778244 INFO Waiting 300 seconds for QMT login +2026/08/19 19:29:17.787076 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/20 00:46:19.533672 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 00:46:19.557647 WARNING Health check failed 1/3; waiting before restart +2026/08/20 00:47:19.558550 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 00:47:19.558550 WARNING Health check failed 2/3; waiting before restart +2026/08/20 00:48:19.561081 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 00:48:19.561171 WARNING Health check failed 3 consecutive times; restarting system +2026/08/20 00:48:19.562250 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/20 00:48:20.393672 INFO Waiting 300 seconds for QMT login +2026/08/20 00:53:20.394245 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/20 00:55:20.405666 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 00:55:20.405666 WARNING Health check failed 1/3; waiting before restart +2026/08/20 09:04:29.885549 INFO ================================================== +2026/08/20 09:04:30.104470 INFO QMT launcher supervisor started +2026/08/20 09:04:30.188385 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/20 09:04:30.296629 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 09:04:30.904740 WARNING Health check failed 1/3; waiting before restart +2026/08/20 09:05:30.933442 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 09:05:30.933442 WARNING Health check failed 2/3; waiting before restart +2026/08/20 09:06:30.935437 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 09:06:30.935437 WARNING Health check failed 3 consecutive times; restarting system +2026/08/20 09:06:30.936235 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/20 09:06:31.015171 INFO Waiting 300 seconds for QMT login +2026/08/20 09:11:31.019034 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/20 18:38:33.643883 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 18:38:33.644433 WARNING Health check failed 1/3; waiting before restart +2026/08/20 18:39:33.645042 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 18:39:33.645042 WARNING Health check failed 2/3; waiting before restart +2026/08/20 18:40:33.645183 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/20 18:40:33.645183 WARNING Health check failed 3 consecutive times; restarting system +2026/08/20 18:40:33.645183 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/20 18:40:33.708064 INFO Waiting 300 seconds for QMT login +2026/08/21 07:43:26.860445 INFO ================================================== +2026/08/21 07:43:26.900291 INFO QMT launcher supervisor started +2026/08/21 07:43:26.900802 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/21 07:43:26.900802 INFO Outside trading hours; skipping health check and startup +2026/08/21 07:56:42.689005 INFO ================================================== +2026/08/21 07:56:42.956200 INFO QMT launcher supervisor started +2026/08/21 07:56:43.003770 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/21 07:56:43.098420 INFO Outside trading hours; skipping health check and startup +2026/08/21 09:10:43.146562 INFO Trading hours started; resuming health checks +2026/08/21 09:10:43.151689 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/21 09:10:43.152235 WARNING Health check failed 1/3; waiting before restart +2026/08/21 09:11:43.153756 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/21 09:11:43.153892 WARNING Health check failed 2/3; waiting before restart +2026/08/21 09:12:43.155121 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/21 09:12:43.155121 WARNING Health check failed 3 consecutive times; restarting system +2026/08/21 09:12:43.158139 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/21 09:12:43.602943 INFO Waiting 300 seconds for QMT login +2026/08/21 09:17:43.603673 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/21 09:41:43.700827 WARNING Status check failed: invalid character 'N' looking for beginning of value +2026/08/21 09:41:43.701040 WARNING Health check failed 1/3; waiting before restart +2026/08/21 09:42:43.705588 WARNING Status check failed: invalid character 'N' looking for beginning of value +2026/08/21 09:42:43.705588 WARNING Health check failed 2/3; waiting before restart +2026/08/21 09:43:43.708222 WARNING Status check failed: invalid character 'N' looking for beginning of value +2026/08/21 09:43:43.708222 WARNING Health check failed 3 consecutive times; restarting system +2026/08/21 09:43:43.708222 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/21 09:43:43.755418 INFO Waiting 300 seconds for QMT login +2026/08/24 07:34:46.696720 INFO ================================================== +2026/08/24 07:34:46.886535 INFO QMT launcher supervisor started +2026/08/24 07:34:47.102305 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/24 07:34:47.117274 INFO Outside trading hours; skipping health check and startup +2026/08/25 07:26:35.556191 INFO ================================================== +2026/08/25 07:26:35.588196 INFO QMT launcher supervisor started +2026/08/25 07:26:35.597019 INFO collector.exe already exists: D:\work\quant\qmt-v2\bin\collector.exe +2026/08/25 07:26:35.598275 INFO Outside trading hours; skipping health check and startup +2026/08/25 09:10:35.669830 INFO Trading hours started; resuming health checks +2026/08/25 09:10:35.796782 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:10:35.798360 WARNING Health check failed 1/3; waiting before restart +2026/08/25 09:11:35.798720 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:11:35.798720 WARNING Health check failed 2/3; waiting before restart +2026/08/25 09:12:35.800607 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:12:35.800607 WARNING Health check failed 3 consecutive times; restarting system +2026/08/25 09:12:35.802484 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/25 09:12:36.274607 INFO Waiting 300 seconds for QMT login +2026/08/25 09:17:36.276883 INFO Launching D:\work\quant\qmt-v2\run.bat +2026/08/25 09:19:36.305826 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:19:36.305826 WARNING Health check failed 1/3; waiting before restart +2026/08/25 09:20:36.306951 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:20:36.306951 WARNING Health check failed 2/3; waiting before restart +2026/08/25 09:21:36.308254 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:21:36.308254 WARNING Restart suppressed by cooldown; retrying in approximately 12m0s +2026/08/25 09:22:36.312490 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:22:36.312490 WARNING Restart suppressed by cooldown; retrying in approximately 11m0s +2026/08/25 09:23:36.313957 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:23:36.313957 WARNING Restart suppressed by cooldown; retrying in approximately 10m0s +2026/08/25 09:24:36.315175 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:24:36.315175 WARNING Restart suppressed by cooldown; retrying in approximately 9m0s +2026/08/25 09:25:36.317724 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:25:36.317758 WARNING Restart suppressed by cooldown; retrying in approximately 8m0s +2026/08/25 09:26:36.318252 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:26:36.318252 WARNING Restart suppressed by cooldown; retrying in approximately 7m0s +2026/08/25 09:27:36.320154 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:27:36.320237 WARNING Restart suppressed by cooldown; retrying in approximately 6m0s +2026/08/25 09:28:36.322021 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:28:36.322021 WARNING Restart suppressed by cooldown; retrying in approximately 5m0s +2026/08/25 09:29:36.323197 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:29:36.323354 WARNING Restart suppressed by cooldown; retrying in approximately 4m0s +2026/08/25 09:30:36.323848 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:30:36.323848 WARNING Restart suppressed by cooldown; retrying in approximately 3m0s +2026/08/25 09:31:36.325264 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:31:36.325264 WARNING Restart suppressed by cooldown; retrying in approximately 2m0s +2026/08/25 09:32:36.326993 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:32:36.327058 WARNING Restart suppressed by cooldown; retrying in approximately 1m0s +2026/08/25 09:33:36.328252 WARNING Status check failed: Get "http://127.0.0.1:5000/status": dial tcp 127.0.0.1:5000: connectex: No connection could be made because the target machine actively refused it. +2026/08/25 09:33:36.328252 WARNING Health check failed 15 consecutive times; restarting system +2026/08/25 09:33:36.328757 INFO Launching D:\work\quant\qmt-v2\launcher\qmt_auto_login.ahk +2026/08/25 09:33:36.384905 INFO Waiting 300 seconds for QMT login diff --git a/launcher/start_test.go b/launcher/start_test.go new file mode 100644 index 0000000..b7cd52b --- /dev/null +++ b/launcher/start_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "reflect" + "testing" +) + +func TestNormalizeJSONEmptyValues(t *testing.T) { + input := map[string]any{ + "json_null": nil, + "nil_text": "nil", + "null_text": " NULL ", + "normal": "connected", + "nested": []any{nil, "NIL", 1.0}, + } + want := map[string]any{ + "json_null": "", + "nil_text": "", + "null_text": "", + "normal": "connected", + "nested": []any{"", "", 1.0}, + } + + got := normalizeJSONEmptyValues(input) + if !reflect.DeepEqual(got, want) { + t.Fatalf("normalizeJSONEmptyValues() = %#v, want %#v", got, want) + } +} diff --git a/py-client/etc/liao.yaml b/py-client/etc/liao.yaml index 8502048..8fb4db2 100644 --- a/py-client/etc/liao.yaml +++ b/py-client/etc/liao.yaml @@ -1,6 +1,6 @@ account_id: 8889399698 host_key: liao -buy_value: 5000 +buy_value: 10000 min_cash_ratio: 0.10 grid_step_pct: 1 strategy: trend diff --git a/py-client/libs/runtime.py b/py-client/libs/runtime.py index e757773..862cc80 100644 --- a/py-client/libs/runtime.py +++ b/py-client/libs/runtime.py @@ -1,11 +1,13 @@ """策略单次运行所需的公共上下文对象。""" +import logging from concurrent.futures import ThreadPoolExecutor -from dataclasses import dataclass +from dataclasses import dataclass, field -from config import AccountConfig, GlobalConfig +from config import AccountConfig, GlobalConfig, HTTP_TIMEOUT from sdk import Client from libs.grid_take_profit import GridTrailingTracker +from libs.http import get_json from libs.order import OrderBook from libs.watch import DipWatch @@ -22,3 +24,23 @@ class Runtime: add_watch: DipWatch profit_tracker: GridTrailingTracker executor: ThreadPoolExecutor | None = None + server_inital: dict[str, list[str]] = field(init=False) + + def __post_init__(self) -> None: + """读取服务端初始化数据,失败时保留空证券列表。""" + self.server_inital = {"all_codes": [], "today_pass_codes": []} + try: + url = f"{self.global_cfg.api_host.rstrip('/')}/a/initial" + payload = get_json(url, HTTP_TIMEOUT) + if not isinstance(payload, dict) or str(payload.get("code")) != "0": + raise ValueError(f"服务端初始化响应失败: {payload!r}") + data = payload.get("data") + if not isinstance(data, dict): + raise ValueError("服务端初始化 data 必须为对象") + for key in ("all_codes", "today_pass_codes"): + codes = data.get(key) + if not isinstance(codes, list) or not all(isinstance(code, str) for code in codes): + raise ValueError(f"服务端初始化 {key} 必须为字符串列表") + self.server_inital = data + except Exception: + logging.exception("[初始化] 获取服务端初始化数据失败") diff --git a/py-client/strategy/trend/positions.py b/py-client/strategy/trend/positions.py index 90828a0..e73dfc1 100644 --- a/py-client/strategy/trend/positions.py +++ b/py-client/strategy/trend/positions.py @@ -166,6 +166,11 @@ def handle_loss( return TradeDecision(False, f"次数无效:{add_num}") if pnl_rate > LOSS_TIERS[add_num]: return TradeDecision(False) + + all_codes = runtime.server_inital.get("all_codes") or [] + if position.stock_code not in all_codes: + return TradeDecision(False, "不在服务端初始化股票池内,禁止补仓") + if tick.last_price > 200 or position.market_value >= 20_000: return TradeDecision(False, "价格或仓位市值超过补仓限制") if not runtime.add_watch.triggered("补仓", position.stock_code, tick.last_price):