Files
big-qmt/py-client/strategy/zt/open.py
2026-09-06 13:12:48 +08:00

120 lines
4.5 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.
"""使用 dcm 信号建立做 T 底仓。"""
from __future__ import annotations
from datetime import datetime
import logging as log
import math
from libs.calc import calc_buy_volume
from sdk import OP_BUY
from libs.runtime import Runtime
from libs.order import PlaceOrderRequest
from .state import PendingOrder, TState
def open_signal(run: Runtime, state: TState, ticks, signals, available: float) -> float:
"""逐个验证开仓信号并提交买入委托,返回本轮剩余资金。"""
for item in signals:
try:
now = datetime.now()
if (now.hour, now.minute) >= (14, 50):
break
if item.signal_key != "dcm" or item.code in run.account_cfg.excluded_codes:
continue
item_state = state.items.get(item.code)
if item_state is not None and item_state.base_qty > 0:
continue
# 1. 验证信号配置允许开仓的时间区间。
signal_config = run.global_cfg.signals.get("dcm")
if signal_config is None or not check_timezone(signal_config.timezone):
continue
# 2. 检查该证券是否已有买入委托锁,防止重复下单。
if (
state.busy(item.code)
or run.orders.busy(item.code, "BUY")
or run.orders.busy(item.code, "SELL")
):
continue
# 3. 验证行情和最新价格是否有效。
tick = ticks.get(item.code)
price = tick.last_price if tick else 0.0
if (
not math.isfinite(price)
or price <= 0
or price > run.account_cfg.zt_max_price
):
continue
# 4. 根据单笔买入金额计算整手开仓数量,预留少量价差和费用。
budget = min(run.account_cfg.buy_value, available)
volume = calc_buy_volume(price, budget)
amount = price * volume * 1.01
if volume <= 0 or price * volume > budget or amount > available:
continue
# 5. 等待价格从观察低点反弹,防止直接接下跌中的“飞刀”。
if not run.open_watch.triggered("ZT 建仓", item.code, price):
continue
order_id = run.orders.new_order_id("base")
request = PlaceOrderRequest(
OP_BUY, item.code, volume, order_id, "zt", kind="base"
)
state.new_order(
PendingOrder(
order_id,
item.code,
"base",
volume,
datetime.now().date().isoformat(),
)
)
# 即使响应丢失,也保留资金预算和 pending不能继续使用这笔钱。
available -= amount
if run.orders.place(run.client, request):
run.open_watch.forget(item.code)
log.info("[ZT 建仓] %s 买入 %d 股,等待实际成交", item.code, volume)
except Exception:
log.exception("[ZT 建仓] %s 处理异常,继续后续信号", item.code)
return available
def check_timezone(timezone: str, now: datetime | None = None) -> bool:
"""验证当前时间是否处于配置区间。
``*`` 表示全天允许;多个区间用逗号分隔,例如
``9:30-10:30,13:30-14:30``。同时支持跨午夜区间。
"""
timezone = str(timezone or "").strip()
if timezone == "*":
return True
current = now or datetime.now()
current_minutes = current.hour * 60 + current.minute
for section in timezone.split(","):
bounds = section.strip().split("-")
if len(bounds) != 2:
continue
start = _parse_minutes(bounds[0])
end = _parse_minutes(bounds[1])
if start is None or end is None:
continue
if start <= end and start <= current_minutes <= end:
return True
if start > end and (current_minutes >= start or current_minutes <= end):
return True
return False
def _parse_minutes(value: str) -> int | None:
"""把 ``时:分`` 转换为当天分钟数,无效值返回 None。"""
try:
hour_text, minute_text = value.strip().split(":")
hour, minute = int(hour_text), int(minute_text)
except (TypeError, ValueError):
return None
if not 0 <= hour <= 23 or not 0 <= minute <= 59:
return None
return hour * 60 + minute