Files
big-qmt/py-client/main.py

140 lines
4.4 KiB
Python
Raw Normal View History

2026-08-28 18:52:27 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging as log
2026-08-28 22:46:04 +08:00
from logging.handlers import TimedRotatingFileHandler
2026-08-28 18:52:27 +08:00
import os
import sys
import time
import config
from dataclasses import dataclass
import yaml
2026-08-28 22:46:04 +08:00
import httpx
2026-08-28 18:52:27 +08:00
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)
2026-08-28 22:46:04 +08:00
from sdk import APIError, Client
2026-08-28 18:52:27 +08:00
from strategy.trend.boot import StartTrend
2026-08-29 00:44:41 +08:00
from strategy.ipo import AutoBuyIpo
2026-08-28 18:52:27 +08:00
2026-08-28 22:46:04 +08:00
@dataclass(frozen=True, slots=True)
2026-08-28 18:52:27 +08:00
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
2026-08-28 22:46:04 +08:00
def wait_for_qmt_api(retry_interval: float = 5.0) -> None:
2026-08-28 18:52:27 +08:00
"""循环检查 API 地址,连通后才返回。"""
client = Client(config.global_config.qmt_base_url, config.global_config.qmt_token, config.HTTP_TIMEOUT)
2026-08-28 22:46:04 +08:00
retry_event = __import__("threading").Event()
while not retry_event.is_set():
2026-08-28 18:52:27 +08:00
try:
2026-08-28 22:46:04 +08:00
client.assets()
2026-08-28 18:52:27 +08:00
log.info(f"API 服务已连通:{config.global_config.qmt_base_url}")
2026-08-28 22:46:04 +08:00
client.close()
2026-08-28 18:52:27 +08:00
return
2026-08-28 22:46:04 +08:00
except (APIError, httpx.RequestError) as exc:
2026-08-28 18:52:27 +08:00
log.warning(
2026-08-28 22:46:04 +08:00
"API 服务未就绪:%s%g 秒后重试:%s",
config.global_config.qmt_base_url,
retry_interval,
exc,
2026-08-28 18:52:27 +08:00
)
2026-08-28 22:46:04 +08:00
retry_event.wait(retry_interval)
def configure_logging(data_dir: str) -> None:
"""同时输出控制台日志和按天轮转的文本日志。"""
log_dir = os.path.join(data_dir, "logs")
os.makedirs(log_dir, exist_ok=True)
root = log.getLogger()
root.setLevel(log.INFO)
formatter = log.Formatter("%(asctime)s [%(levelname)s] %(message)s")
if not root.handlers:
console = log.StreamHandler()
console.setFormatter(formatter)
root.addHandler(console)
file_handler = TimedRotatingFileHandler(
os.path.join(log_dir, "py-client.log"),
when="midnight",
interval=1,
backupCount=30,
encoding="utf-8",
)
file_handler.setFormatter(formatter)
root.addHandler(file_handler)
2026-08-28 18:52:27 +08:00
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()")
2026-08-28 22:46:04 +08:00
configure_logging(config.global_config.qmt_data_dir)
wait_for_qmt_api()
2026-08-28 18:52:27 +08:00
2026-08-29 01:50:09 +08:00
# 自动打新与主策略隔离;申购服务失败不能阻止趋势策略启动。
2026-08-29 00:44:41 +08:00
AutoBuyIpo()
2026-08-28 18:52:27 +08:00
STRATEGIES[config.account_config.strategy].start_strategy()
return 0
2026-08-28 22:46:04 +08:00
except (OSError, yaml.YAMLError, ValueError, RuntimeError, KeyError) as exc:
2026-08-28 18:52:27 +08:00
print(f"启动失败: {exc}", file=sys.stderr, flush=True)
wait_for_any_key()
return 1
if __name__ == "__main__":
sys.exit(main())