fix bug
This commit is contained in:
90
py-client/strategy/zt/rules.py
Normal file
90
py-client/strategy/zt/rules.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""ZT 正T/反T 的触发与数量规则:纯函数,不碰网络、存储和线程。
|
||||
|
||||
设计要点:
|
||||
|
||||
* 以建仓价 ``base_cost`` 为中枢,中性带 ``zt_t_band_pct`` 内不做任何动作。
|
||||
现价低于下沿只考虑正T,高于上沿只考虑反T —— 同一时刻只可能命中一种方向,
|
||||
天然满足"一只股票每天只做一轮",不需要额外的冲突仲裁。
|
||||
* 数量一律再受券商现实约束封顶:卖出封顶 ``can_use_volume``(T+1 只在这里
|
||||
体现),买入封顶可用资金。正T 当天买入的份额当天不可卖,因此它的平仓腿
|
||||
能卖多少完全由 ``can_use_volume`` 决定,卖不动就自然留成隔夜持仓。
|
||||
"""
|
||||
|
||||
from .rounds import KIND_LONG_T, KIND_SHORT_T
|
||||
|
||||
LOT = 100
|
||||
|
||||
|
||||
def choose_kind(price: float, base_cost: float, band_pct: float) -> str | None:
|
||||
"""按现价相对建仓价的位置决定本轮方向;中性带内返回 None。"""
|
||||
if price <= 0 or base_cost <= 0 or band_pct < 0:
|
||||
return None
|
||||
if price <= base_cost * (1 - band_pct / 100):
|
||||
return KIND_LONG_T
|
||||
if price >= base_cost * (1 + band_pct / 100):
|
||||
return KIND_SHORT_T
|
||||
return None
|
||||
|
||||
|
||||
def price_allowed(price: float, max_price: float) -> bool:
|
||||
"""高价股不参与做 T。"""
|
||||
return 0 < price <= max_price
|
||||
|
||||
|
||||
def entry_volume(kind: str, *, price: float, open_hands: int, sell_ratio: float,
|
||||
base_qty: int, can_use_volume: int, available: float) -> int:
|
||||
"""开仓腿计划数量;0 表示不提交。"""
|
||||
if price <= 0:
|
||||
return 0
|
||||
if kind == KIND_LONG_T:
|
||||
# 正T 买入:按手数取量,再受可用资金封顶。
|
||||
affordable = int(max(0.0, available) // (price * LOT)) * LOT
|
||||
return max(0, min(open_hands * LOT, affordable))
|
||||
if kind == KIND_SHORT_T:
|
||||
# 反T 卖出:按建仓数量比例取整手,再受可卖库存封顶。
|
||||
planned = int(base_qty * sell_ratio) // LOT * LOT
|
||||
return max(0, min(planned, _whole_lots(can_use_volume)))
|
||||
return 0
|
||||
|
||||
|
||||
def exit_volume(kind: str, *, residual_qty: int, price: float,
|
||||
can_use_volume: int, available: float) -> int:
|
||||
"""平仓腿可提交数量;0 表示当前无法平仓(T+1 冻结或资金不足)。"""
|
||||
if residual_qty <= 0 or price <= 0:
|
||||
return 0
|
||||
if kind == KIND_LONG_T:
|
||||
return max(0, min(residual_qty, _whole_lots(can_use_volume)))
|
||||
if kind == KIND_SHORT_T:
|
||||
affordable = int(max(0.0, available) // (price * LOT)) * LOT
|
||||
return max(0, min(residual_qty, affordable))
|
||||
return 0
|
||||
|
||||
|
||||
def entry_triggered(kind: str, price: float, base_cost: float, *, band_pct: float,
|
||||
rebound_confirmed: bool, retrace_confirmed: bool) -> bool:
|
||||
"""开仓腿是否满足触发条件。"""
|
||||
if choose_kind(price, base_cost, band_pct) != kind:
|
||||
return False
|
||||
if kind == KIND_LONG_T:
|
||||
return rebound_confirmed # 低吸要等反弹确认,不接下跌中的飞刀
|
||||
return retrace_confirmed # 高抛要等盈利网格回撤,不追最高点
|
||||
|
||||
|
||||
def exit_triggered(kind: str, price: float, entry_avg_price: float, *,
|
||||
buy_fall_pct: float, profit_step_pct: float,
|
||||
rebound_confirmed: bool) -> bool:
|
||||
"""平仓腿是否满足触发条件。"""
|
||||
if entry_avg_price <= 0:
|
||||
return False
|
||||
if kind == KIND_SHORT_T:
|
||||
# 反T 买回:较卖出均价回落 buy_fall_pct 且已见反弹。
|
||||
target = entry_avg_price * (1 - buy_fall_pct / 100)
|
||||
return price <= target and rebound_confirmed
|
||||
if kind == KIND_LONG_T:
|
||||
# 正T 卖出:较买入均价上涨一个网格步长。
|
||||
return price >= entry_avg_price * (1 + profit_step_pct / 100)
|
||||
return False
|
||||
|
||||
|
||||
def _whole_lots(volume: int) -> int:
|
||||
return max(0, int(volume)) // LOT * LOT
|
||||
Reference in New Issue
Block a user