"""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