This commit is contained in:
2026-08-29 12:00:51 +08:00
parent 4e28182b3f
commit 9a43aaba23
11 changed files with 19 additions and 38 deletions

Binary file not shown.

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging as log import logging
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
import os import os
import sys import sys
import time import schedule
import config import config
from dataclasses import dataclass from dataclasses import dataclass
import yaml import yaml
@@ -16,6 +16,12 @@ GLOBAL_CONFIG_PATH = os.path.join(PROJECT_ROOT, "etc", "_global.yaml")
if PROJECT_ROOT not in sys.path: if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT) 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 sdk import APIError, Client
from strategy.trend.boot import StartTrend from strategy.trend.boot import StartTrend
from strategy.ipo import AutoBuyIpo from strategy.ipo import AutoBuyIpo
@@ -45,16 +51,16 @@ def check_single_instance(project_root: str) -> bool:
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
handle = kernel32.CreateMutexW(None, True, mutex_name) handle = kernel32.CreateMutexW(None, True, mutex_name)
if not handle or handle == invalid_handle_value: if not handle or handle == invalid_handle_value:
log.error(f"无法创建互斥锁,错误代码:{ctypes.get_last_error()}") logging.error(f"无法创建互斥锁,错误代码:{ctypes.get_last_error()}")
return False return False
if ctypes.get_last_error() == error_already_exists: if ctypes.get_last_error() == error_already_exists:
log.error("程序已在运行中,无法启动多个实例") logging.error("程序已在运行中,无法启动多个实例")
kernel32.CloseHandle(handle) kernel32.CloseHandle(handle)
return False return False
log.info(f"成功获取互斥锁:{mutex_name}") logging.info(f"成功获取互斥锁:{mutex_name}")
return True return True
except Exception as exc: except Exception as exc:
log.error(f"单实例检测失败:{exc}", exc_info=True) logging.error(f"单实例检测失败:{exc}", exc_info=True)
return False return False
@@ -66,11 +72,11 @@ def wait_for_qmt_api(retry_interval: float = 5.0) -> None:
while not retry_event.is_set(): while not retry_event.is_set():
try: try:
client.assets() client.assets()
log.info(f"API 服务已连通:{config.global_config.qmt_base_url}") logging.info(f"API 服务已连通:{config.global_config.qmt_base_url}")
client.close() client.close()
return return
except (APIError, httpx.RequestError) as exc: except (APIError, httpx.RequestError) as exc:
log.warning( logging.warning(
"API 服务未就绪:%s%g 秒后重试:%s", "API 服务未就绪:%s%g 秒后重试:%s",
config.global_config.qmt_base_url, config.global_config.qmt_base_url,
retry_interval, retry_interval,
@@ -78,27 +84,6 @@ def wait_for_qmt_api(retry_interval: float = 5.0) -> None:
) )
retry_event.wait(retry_interval) 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)
def wait_for_any_key() -> None: def wait_for_any_key() -> None:
print("按任意键退出...", flush=True) print("按任意键退出...", flush=True)
@@ -121,13 +106,15 @@ def main() -> int:
config.load() config.load()
if config.global_config is None or config.account_config is None: if config.global_config is None or config.account_config is None:
raise RuntimeError("配置尚未加载,请先调用 config.load()") raise RuntimeError("配置尚未加载,请先调用 config.load()")
configure_logging(config.global_config.qmt_data_dir)
wait_for_qmt_api() wait_for_qmt_api()
# 自动打新与主策略隔离;申购服务失败不能阻止趋势策略启动。 # 自动打新与主策略隔离;申购服务失败不能阻止趋势策略启动。
AutoBuyIpo() schedule.every().day.at("10:00").do(AutoBuyIpo)
schedule.run_pending()
logging.info("IPO 自动打新启动成功")
STRATEGIES[config.account_config.strategy].start_strategy() STRATEGIES[config.account_config.strategy].start_strategy()
logging.info("%s 策略启动成功",config.account_config.strateg)
return 0 return 0
except (OSError, yaml.YAMLError, ValueError, RuntimeError, KeyError) as exc: except (OSError, yaml.YAMLError, ValueError, RuntimeError, KeyError) as exc:
print(f"启动失败: {exc}", file=sys.stderr, flush=True) print(f"启动失败: {exc}", file=sys.stderr, flush=True)

View File

@@ -19,7 +19,7 @@ class Client:
self.base_url = base_url.rstrip("/") self.base_url = base_url.rstrip("/")
self.token = token self.token = token
self.timeout = timeout if timeout > 0 else 15.0 self.timeout = timeout if timeout > 0 else 15.0
self.account_type = "stock" self.account_type = "STOCK"
self.http = httpx.Client( self.http = httpx.Client(
base_url=self.base_url, base_url=self.base_url,
headers={"X-Token": token, "Accept": "application/json"}, headers={"X-Token": token, "Accept": "application/json"},

View File

@@ -99,12 +99,6 @@ def StartTrend() -> None:
profit_tracker=GridTrailingTracker(config.account_config.grid_step_pct), profit_tracker=GridTrailingTracker(config.account_config.grid_step_pct),
) )
logging.info(
"趋势策略启动:总资产=%.2f,持仓=%d,信号=%d",
assets.total,
len(positions),
len(signals),
)
Overview(assets, positions, config.account_config) Overview(assets, positions, config.account_config)
while True: while True: