Files
big-qmt/py-client/main.py
2026-08-29 12:00:51 +08:00

127 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
from logging.handlers import TimedRotatingFileHandler
import os
import sys
import schedule
import config
from dataclasses import dataclass
import yaml
import httpx
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)
logging.basicConfig(
level=logging.INFO,
format='[%(levelname)s] %(asctime)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
from sdk import APIError, Client
from strategy.trend.boot import StartTrend
from strategy.ipo import AutoBuyIpo
@dataclass(frozen=True, slots=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:
logging.error(f"无法创建互斥锁,错误代码:{ctypes.get_last_error()}")
return False
if ctypes.get_last_error() == error_already_exists:
logging.error("程序已在运行中,无法启动多个实例")
kernel32.CloseHandle(handle)
return False
logging.info(f"成功获取互斥锁:{mutex_name}")
return True
except Exception as exc:
logging.error(f"单实例检测失败:{exc}", exc_info=True)
return False
def wait_for_qmt_api(retry_interval: float = 5.0) -> None:
"""循环检查 API 地址,连通后才返回。"""
client = Client(config.global_config.qmt_base_url, config.global_config.qmt_token, config.HTTP_TIMEOUT)
retry_event = __import__("threading").Event()
while not retry_event.is_set():
try:
client.assets()
logging.info(f"API 服务已连通:{config.global_config.qmt_base_url}")
client.close()
return
except (APIError, httpx.RequestError) as exc:
logging.warning(
"API 服务未就绪:%s%g 秒后重试:%s",
config.global_config.qmt_base_url,
retry_interval,
exc,
)
retry_event.wait(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()")
wait_for_qmt_api()
# 自动打新与主策略隔离;申购服务失败不能阻止趋势策略启动。
schedule.every().day.at("10:00").do(AutoBuyIpo)
schedule.run_pending()
logging.info("IPO 自动打新启动成功")
STRATEGIES[config.account_config.strategy].start_strategy()
logging.info("%s 策略启动成功",config.account_config.strateg)
return 0
except (OSError, yaml.YAMLError, ValueError, RuntimeError, KeyError) as exc:
print(f"启动失败: {exc}", file=sys.stderr, flush=True)
wait_for_any_key()
return 1
if __name__ == "__main__":
sys.exit(main())