177 lines
5.7 KiB
Python
177 lines
5.7 KiB
Python
|
|
"""趋势策略启动器。
|
|||
|
|
|
|||
|
|
该模块负责组合 SDK、配置、状态存储和趋势策略组件,供 main.py 调用。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
import time
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
import config
|
|||
|
|
from libs import init_signals, market_allow_open, trading_time
|
|||
|
|
from sdk import Client
|
|||
|
|
from .state import State
|
|||
|
|
from .order import OrderBook
|
|||
|
|
from .watch import DipWatch
|
|||
|
|
from .runtime import Runtime
|
|||
|
|
from .open import open_signal
|
|||
|
|
from .positions import manage_positions
|
|||
|
|
|
|||
|
|
|
|||
|
|
def Overview(assets, positions, account_cfg=None) -> None:
|
|||
|
|
"""打印策略启动时的账户、资金和持仓概览。
|
|||
|
|
|
|||
|
|
该函数对应 Go 客户端 ``logic.Overview``。为便于单独测试,可以
|
|||
|
|
显式传入账户配置;未传入时使用 ``config.account_config``。
|
|||
|
|
"""
|
|||
|
|
account_cfg = account_cfg or config.account_config
|
|||
|
|
|
|||
|
|
print("\n" + "=" * 80)
|
|||
|
|
print(f"【时间】{datetime.now():%Y-%m-%d %H:%M:%S}")
|
|||
|
|
if account_cfg is not None:
|
|||
|
|
print(
|
|||
|
|
"【配置】"
|
|||
|
|
f"account_id: {account_cfg.account_id} "
|
|||
|
|
f"host_key: {account_cfg.host_key} "
|
|||
|
|
f"buy_value: {account_cfg.buy_value:.0f}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if assets is not None:
|
|||
|
|
print(
|
|||
|
|
f"【资金】总资产:{assets.total:.2f}元,"
|
|||
|
|
f"可用资金:{assets.available:.2f}元"
|
|||
|
|
)
|
|||
|
|
else:
|
|||
|
|
print("【资金】查询失败")
|
|||
|
|
|
|||
|
|
print(f"【持仓】{len(positions)}只")
|
|||
|
|
print("=" * 80)
|
|||
|
|
for position in positions:
|
|||
|
|
if position.volume <= 0:
|
|||
|
|
continue
|
|||
|
|
print(
|
|||
|
|
f"【持仓】{position.stock_code} {position.stock_name} "
|
|||
|
|
f"持仓={position.volume} 可用={position.can_use_volume} "
|
|||
|
|
f"冻结={position.frozen_volume} 在途={position.on_road_volume} "
|
|||
|
|
f"昨仓={position.yesterday_volume} 成本={position.open_price:.3f} "
|
|||
|
|
f"现价={position.last_price:.3f} 市值={position.market_value:.2f} "
|
|||
|
|
f"浮盈={position.float_profit:.2f} "
|
|||
|
|
f"盈亏比例={position.profit_rate * 100:.2f}%"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
def StartTrend() -> None:
|
|||
|
|
"""初始化趋势策略,并以 30 秒间隔持续执行。"""
|
|||
|
|
client = Client(
|
|||
|
|
config.global_config.qmt_base_url,
|
|||
|
|
config.global_config.qmt_token,
|
|||
|
|
config.HTTP_TIMEOUT,
|
|||
|
|
)
|
|||
|
|
assets = client.assets()
|
|||
|
|
_, positions = client.positions()
|
|||
|
|
|
|||
|
|
storeState = State.for_strategy(
|
|||
|
|
config.global_config.qmt_data_dir,
|
|||
|
|
config.account_config.strategy,
|
|||
|
|
config.account_config.account_id,
|
|||
|
|
)
|
|||
|
|
storeState.sync_positions(positions)
|
|||
|
|
|
|||
|
|
# 获取本策略的信号开仓数据
|
|||
|
|
signals = init_signals(config.global_config,["morning","tail","arbitrage"])
|
|||
|
|
run = Runtime(
|
|||
|
|
client=client,
|
|||
|
|
global_cfg=config.global_config,
|
|||
|
|
account_cfg=config.account_config,
|
|||
|
|
state=storeState,
|
|||
|
|
orders=OrderBook(),
|
|||
|
|
open_watch=DipWatch(),
|
|||
|
|
add_watch=DipWatch(),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
logging.info(
|
|||
|
|
"趋势策略启动:总资产=%.2f,持仓=%d,信号=%d",
|
|||
|
|
assets.total,
|
|||
|
|
len(positions),
|
|||
|
|
len(signals),
|
|||
|
|
)
|
|||
|
|
Overview(assets, positions, config.account_config)
|
|||
|
|
|
|||
|
|
while True:
|
|||
|
|
started_at = time.monotonic()
|
|||
|
|
try:
|
|||
|
|
RunOnce(run, signals)
|
|||
|
|
except Exception:
|
|||
|
|
# 单轮错误只记录日志,下一轮仍继续运行。
|
|||
|
|
logging.exception("趋势策略本轮执行失败")
|
|||
|
|
|
|||
|
|
elapsed = time.monotonic() - started_at
|
|||
|
|
time.sleep(max(0.0, 30.0 - elapsed))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def RunOnce(run: Runtime, signals) -> None:
|
|||
|
|
"""按固定步骤执行一轮趋势策略, ``RunOnce``。"""
|
|||
|
|
if not trading_time(datetime.now()):
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 1. 取消超过有效期仍未完成的委托订单。
|
|||
|
|
try:
|
|||
|
|
run.orders.cancel_expired(run.client)
|
|||
|
|
except Exception:
|
|||
|
|
logging.exception("取消过期订单失败")
|
|||
|
|
|
|||
|
|
# 2. 验证可用资金;低于资金安全线时禁止开新仓。
|
|||
|
|
try:
|
|||
|
|
assets = run.client.assets()
|
|||
|
|
except Exception:
|
|||
|
|
logging.exception("获取资产失败")
|
|||
|
|
return
|
|||
|
|
if assets.available < assets.total * run.account_cfg.min_cash_ratio:
|
|||
|
|
logging.info("资金总闸:可用金额太少,禁止开新仓")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 3. 获取大盘状态,只有大盘信号允许时才执行开仓。
|
|||
|
|
market_ok = market_allow_open(run.global_cfg.api_host)
|
|||
|
|
|
|||
|
|
# 4. 获取当前持仓及持仓证券代码。
|
|||
|
|
try:
|
|||
|
|
position_codes, positions = run.client.positions()
|
|||
|
|
except Exception:
|
|||
|
|
logging.exception("获取持仓失败")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 5. 验证有效开仓信号:排除已有持仓,并按 signal_allow 过滤。
|
|||
|
|
position_code_set = set(position_codes)
|
|||
|
|
allow_open = [
|
|||
|
|
signal for signal in signals if signal.code not in position_code_set
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# 6. 获取持仓和待开仓证券的实时行情 tick。
|
|||
|
|
all_codes = list(position_codes)
|
|||
|
|
all_codes.extend(
|
|||
|
|
signal.code for signal in allow_open if signal.code not in position_code_set
|
|||
|
|
)
|
|||
|
|
try:
|
|||
|
|
ticks = run.client.full_tick(list(dict.fromkeys(all_codes)))
|
|||
|
|
except Exception:
|
|||
|
|
logging.exception("获取行情失败")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# 7. 执行开仓:必须同时存在有效信号且大盘允许开仓。
|
|||
|
|
if allow_open and market_ok:
|
|||
|
|
open_signal(run, ticks, allow_open)
|
|||
|
|
|
|||
|
|
# 8. 持仓计算。当前 Go 版本的 managePositions 为空,保留扩展入口。
|
|||
|
|
manage_positions(run, ticks, positions, market_ok,assets.available)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def SignalFilter(signals, allowed_names):
|
|||
|
|
"""只保留账户配置明确允许使用的信号。"""
|
|||
|
|
if not allowed_names:
|
|||
|
|
return []
|
|||
|
|
allowed = set(allowed_names)
|
|||
|
|
return [signal for signal in signals if signal.signal_key in allowed]
|