This commit is contained in:
2026-08-28 18:52:27 +08:00
parent e0ecdfba52
commit d09f271569
89 changed files with 1716 additions and 2515 deletions

111
py-client/main.py Normal file
View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging as log
import os
import sys
import time
import config
from dataclasses import dataclass
import yaml
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
GLOBAL_CONFIG_PATH = os.path.join(PROJECT_ROOT, "etc", "_global.yaml")
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
from sdk import Client
from strategy.trend.boot import StartTrend
@dataclass(frozen=True)
class StrategyDefinition:
mutex_scope: str
start_strategy: object
STRATEGIES = {
"trend": StrategyDefinition("Trend", StartTrend),
}
def require_windows() -> bool:
return os.name == "nt"
def check_single_instance(project_root: str) -> bool:
"""使用 Windows 命名互斥锁保证单实例。"""
try:
import ctypes
error_already_exists = 183
invalid_handle_value = -1
safe_path = project_root.replace(":", "_").replace("\\", "_")
mutex_name = f"Global\\QMT_System_{safe_path}"
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
handle = kernel32.CreateMutexW(None, True, mutex_name)
if not handle or handle == invalid_handle_value:
log.error(f"无法创建互斥锁,错误代码:{ctypes.get_last_error()}")
return False
if ctypes.get_last_error() == error_already_exists:
log.error("程序已在运行中,无法启动多个实例")
kernel32.CloseHandle(handle)
return False
log.info(f"成功获取互斥锁:{mutex_name}")
return True
except Exception as exc:
log.error(f"单实例检测失败:{exc}", exc_info=True)
return False
def ping_api_host(
rpc_host: str,
retry_interval: float = 5.0,
connect_timeout: float = 3.0,
) -> None:
"""循环检查 API 地址,连通后才返回。"""
client = Client(config.global_config.qmt_base_url, config.global_config.qmt_token, config.HTTP_TIMEOUT)
while True:
try:
assets = client.assets()
log.info(f"API 服务已连通:{config.global_config.qmt_base_url}")
return
except:
log.warning(
f"API 服务未就绪:{config.global_config.qmt_base_url}{retry_interval:g} 秒后重试"
)
time.sleep(retry_interval)
def wait_for_any_key() -> None:
print("按任意键退出...", flush=True)
if os.name == "nt":
import msvcrt
msvcrt.getch()
elif sys.stdin.isatty():
sys.stdin.read(1)
def main() -> int:
try:
if not require_windows():
log.error("本程序仅支持 Windows 环境运行")
return 1
if not check_single_instance(PROJECT_ROOT):
return 1
config.load()
if config.global_config is None or config.account_config is None:
raise RuntimeError("配置尚未加载,请先调用 config.load()")
ping_api_host(config.global_config.qmt_base_url)
STRATEGIES[config.account_config.strategy].start_strategy()
return 0
except (OSError, yaml.YAMLError, ValueError) as exc:
print(f"启动失败: {exc}", file=sys.stderr, flush=True)
wait_for_any_key()
return 1
if __name__ == "__main__":
sys.exit(main())