顺势加仓 v2:程序监控滚仓、文档页与平仓同步
重写滚仓计仓与四种加仓方式(市价/斐波/突破),程序盯 mark 触价成交;风险读监控单;pending 可删不可改;手动平仓同步结束滚仓。新增 /strategy/roll/docs 说明页与顺势加仓滚仓说明.md。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+248
-173
@@ -1,14 +1,23 @@
|
||||
"""顺势加仓(滚仓):纯计算。人工触发;止盈锁定首仓;斐波仅算限价。"""
|
||||
"""顺势加仓(滚仓):纯计算。人工触发;止盈锁定首仓;程序监控触价市价成交。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
from fib_key_monitor_lib import calc_fib_plan, fib_ratio_from_type
|
||||
from fib_key_monitor_lib import calc_fib_plan, fib_invalidate_by_mark
|
||||
|
||||
ROLL_MAX_LEGS_LONG = 3
|
||||
ROLL_MAX_LEGS_SHORT = 3
|
||||
ROLL_STOP_OFFSET_PCT_DEFAULT = 1.0
|
||||
|
||||
MARKET_MODE = "market"
|
||||
FIB_MODES = frozenset({"fib_618", "fib_786"})
|
||||
BREAKOUT_MODE = "breakout"
|
||||
|
||||
MODE_LABELS = {
|
||||
MARKET_MODE: "市价加仓",
|
||||
"fib_618": "斐波0.618",
|
||||
"fib_786": "斐波0.786",
|
||||
BREAKOUT_MODE: "突破加仓",
|
||||
}
|
||||
|
||||
|
||||
def fib_ratio_from_mode(mode: str) -> Optional[float]:
|
||||
@@ -20,6 +29,11 @@ def fib_ratio_from_mode(mode: str) -> Optional[float]:
|
||||
return None
|
||||
|
||||
|
||||
def mode_label(mode: str) -> str:
|
||||
m = (mode or MARKET_MODE).strip().lower()
|
||||
return MODE_LABELS.get(m, m)
|
||||
|
||||
|
||||
def fib_limit_entry(direction: str, upper: float, lower: float, mode: str) -> Tuple[Optional[float], Optional[str]]:
|
||||
"""H/L 仅用于计算限价加仓价;多:下沿=止损侧;空:上沿=止损侧。"""
|
||||
ratio = fib_ratio_from_mode(mode)
|
||||
@@ -43,49 +57,6 @@ def max_roll_legs(direction: str) -> int:
|
||||
return ROLL_MAX_LEGS_LONG if (direction or "long").strip().lower() == "long" else ROLL_MAX_LEGS_SHORT
|
||||
|
||||
|
||||
def resolve_roll_stop_spec(
|
||||
*,
|
||||
new_stop_loss: Optional[float] = None,
|
||||
stop_offset_pct: Optional[float] = None,
|
||||
entry_ref: float = 0.0,
|
||||
) -> tuple[str, float]:
|
||||
"""
|
||||
解析滚仓止损输入。
|
||||
- stop_offset_pct:相对合并均价的偏移%,如 1 表示 1%(多:均价下方;空:均价上方)。
|
||||
- new_stop_loss:兼容旧版绝对止损价;若数值很小(如 1.0)且相对均价过低,视为偏移%。
|
||||
"""
|
||||
if stop_offset_pct is not None:
|
||||
try:
|
||||
pct = float(stop_offset_pct)
|
||||
if pct > 0:
|
||||
return "offset", pct
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
if new_stop_loss is not None:
|
||||
try:
|
||||
sl = float(new_stop_loss)
|
||||
if sl > 0:
|
||||
ref = float(entry_ref or 0)
|
||||
if ref > 0 and sl <= min(30.0, ref * 0.25):
|
||||
return "offset", sl
|
||||
return "absolute", sl
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return "offset", ROLL_STOP_OFFSET_PCT_DEFAULT
|
||||
|
||||
|
||||
def unified_stop_from_avg(direction: str, avg: float, offset_pct: float) -> float:
|
||||
"""合并均价 ± offset% 作为新统一止损(非保本)。"""
|
||||
avg_f = float(avg)
|
||||
pct = float(offset_pct) / 100.0
|
||||
if avg_f <= 0 or pct <= 0:
|
||||
return 0.0
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "short":
|
||||
return avg_f * (1.0 + pct)
|
||||
return avg_f * (1.0 - pct)
|
||||
|
||||
|
||||
def avg_entry_after_add(
|
||||
qty_existing: float,
|
||||
entry_existing: float,
|
||||
@@ -102,44 +73,8 @@ def avg_entry_after_add(
|
||||
return (q1 * e1 + q2 * e2) / total
|
||||
|
||||
|
||||
def solve_add_amount_for_avg_stop_offset(
|
||||
direction: str,
|
||||
qty_existing: float,
|
||||
entry_existing: float,
|
||||
add_price: float,
|
||||
offset_pct: float,
|
||||
risk_budget_usdt: float,
|
||||
) -> Tuple[Optional[float], Optional[str]]:
|
||||
"""
|
||||
合并后止损 = 合并均价 ± offset%,且触及止损时总亏损 ≈ risk_budget。
|
||||
loss = offset% × (Q1·E1 + Q2·E2) => Q2 = (B/p − Q1·E1) / E2
|
||||
"""
|
||||
try:
|
||||
q1 = float(qty_existing)
|
||||
e1 = float(entry_existing)
|
||||
e2 = float(add_price)
|
||||
b = float(risk_budget_usdt)
|
||||
p = float(offset_pct) / 100.0
|
||||
except (TypeError, ValueError):
|
||||
return None, "参数格式错误"
|
||||
if q1 <= 0 or e1 <= 0 or e2 <= 0 or b <= 0:
|
||||
return None, "持仓或风险预算无效"
|
||||
if p <= 0 or p >= 1:
|
||||
return None, "止损偏移%须大于 0 且小于 100"
|
||||
direction = (direction or "long").strip().lower()
|
||||
need_notional = b / p
|
||||
q2 = (need_notional - q1 * e1) / e2
|
||||
if q2 <= 0:
|
||||
return None, "按当前偏移%与总风险%,无需加仓或无法再加(已满足风险上限)"
|
||||
new_avg = avg_entry_after_add(q1, e1, q2, e2)
|
||||
sl = unified_stop_from_avg(direction, new_avg, offset_pct)
|
||||
if direction == "short":
|
||||
if sl <= e2:
|
||||
return None, "做空:合并后止损须高于加仓价(请减小偏移%或风险%)"
|
||||
else:
|
||||
if sl >= e2:
|
||||
return None, "做多:合并后止损须低于加仓价(请减小偏移%或风险%)"
|
||||
return q2, None
|
||||
def calc_risk_budget_usdt(capital_base_usdt: float, risk_percent: float) -> float:
|
||||
return float(capital_base_usdt) * (float(risk_percent) / 100.0)
|
||||
|
||||
|
||||
def solve_add_amount_for_total_risk(
|
||||
@@ -149,11 +84,12 @@ def solve_add_amount_for_total_risk(
|
||||
add_price: float,
|
||||
new_stop: float,
|
||||
risk_budget_usdt: float,
|
||||
contract_size: float = 1.0,
|
||||
) -> Tuple[Optional[float], Optional[str]]:
|
||||
"""
|
||||
已知合并后若触及 new_stop 总亏损=risk_budget,反推本次加仓张数 Q2。
|
||||
long: (avg - SL) * (Q1+Q2) = B => Q2 = (B - Q1*(E1-SL)) / (E2-SL)
|
||||
short: (SL - avg) * (Q1+Q2) = B => Q2 = (B - Q1*(SL-E1)) / (SL-E2)
|
||||
合并持仓打到 new_stop 时总亏损 ≈ risk_budget(方案 C)。
|
||||
long: (avg - SL) * (Q1+Q2) * cs = B => Q2 = (B/cs - Q1*(E1-SL)) / (E2-SL)
|
||||
short: (SL - avg) * (Q1+Q2) * cs = B => Q2 = (B/cs - Q1*(SL-E1)) / (SL-E2)
|
||||
"""
|
||||
try:
|
||||
q1 = float(qty_existing)
|
||||
@@ -161,27 +97,196 @@ def solve_add_amount_for_total_risk(
|
||||
e2 = float(add_price)
|
||||
sl = float(new_stop)
|
||||
b = float(risk_budget_usdt)
|
||||
cs = float(contract_size) if contract_size else 1.0
|
||||
except (TypeError, ValueError):
|
||||
return None, "参数格式错误"
|
||||
if q1 <= 0 or e1 <= 0 or e2 <= 0 or b <= 0:
|
||||
if q1 <= 0 or e1 <= 0 or e2 <= 0 or b <= 0 or cs <= 0:
|
||||
return None, "持仓或风险预算无效"
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "short":
|
||||
denom = sl - e2
|
||||
numer = b - q1 * (sl - e1)
|
||||
numer = b / cs - q1 * (sl - e1)
|
||||
if denom <= 0:
|
||||
return None, "做空:新止损须高于限价加仓价"
|
||||
return None, "做空:新止损须高于加仓价"
|
||||
else:
|
||||
denom = e2 - sl
|
||||
numer = b - q1 * (e1 - sl)
|
||||
numer = b / cs - q1 * (e1 - sl)
|
||||
if denom <= 0:
|
||||
return None, "做多:新止损须低于限价/市价加仓价"
|
||||
return None, "做多:新止损须低于加仓价"
|
||||
q2 = numer / denom
|
||||
if q2 <= 0:
|
||||
return None, "按当前新止损与总风险%,无需加仓或无法再加(已满足风险上限)"
|
||||
return None, "按当前新止损与风险预算,无需加仓或无法再加(已满足风险上限)"
|
||||
return q2, None
|
||||
|
||||
|
||||
def loss_at_stop_usdt(
|
||||
direction: str,
|
||||
avg: float,
|
||||
qty: float,
|
||||
stop: float,
|
||||
contract_size: float = 1.0,
|
||||
) -> float:
|
||||
cs = float(contract_size or 1.0)
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "short":
|
||||
return (float(stop) - float(avg)) * float(qty) * cs
|
||||
return (float(avg) - float(stop)) * float(qty) * cs
|
||||
|
||||
|
||||
def reward_at_tp_usdt(
|
||||
direction: str,
|
||||
avg: float,
|
||||
take_profit: float,
|
||||
qty: float,
|
||||
contract_size: float = 1.0,
|
||||
) -> float:
|
||||
cs = float(contract_size or 1.0)
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "short":
|
||||
return (float(avg) - float(take_profit)) * float(qty) * cs
|
||||
return (float(take_profit) - float(avg)) * float(qty) * cs
|
||||
|
||||
|
||||
def roll_fib_trigger_crossed(
|
||||
direction: str,
|
||||
prev_mark: Optional[float],
|
||||
mark: float,
|
||||
limit_price: float,
|
||||
) -> bool:
|
||||
"""斐波:多=向下穿越限价;空=向上穿越限价。"""
|
||||
try:
|
||||
m = float(mark)
|
||||
lv = float(limit_price)
|
||||
pm = float(prev_mark) if prev_mark is not None else None
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "long":
|
||||
if pm is None:
|
||||
return m <= lv
|
||||
return pm > lv and m <= lv
|
||||
if pm is None:
|
||||
return m >= lv
|
||||
return pm < lv and m >= lv
|
||||
|
||||
|
||||
def roll_breakout_trigger_crossed(
|
||||
direction: str,
|
||||
prev_mark: Optional[float],
|
||||
mark: float,
|
||||
breakthrough_price: float,
|
||||
) -> bool:
|
||||
"""突破:多=向上穿越突破价;空=向下穿越突破价。"""
|
||||
try:
|
||||
m = float(mark)
|
||||
bp = float(breakthrough_price)
|
||||
pm = float(prev_mark) if prev_mark is not None else None
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "long":
|
||||
if pm is None:
|
||||
return m > bp
|
||||
return pm <= bp and m > bp
|
||||
if pm is None:
|
||||
return m < bp
|
||||
return pm >= bp and m < bp
|
||||
|
||||
|
||||
def roll_fib_invalidate(direction: str, mark: float, upper: float, lower: float) -> bool:
|
||||
"""斐波 pending 失效:止盈侧突破(多 mark>=H;空 mark<=L)。"""
|
||||
return fib_invalidate_by_mark(direction, mark, upper, lower)
|
||||
|
||||
|
||||
def roll_breakout_invalidate(direction: str, mark: float, stop_loss: float) -> bool:
|
||||
"""突破 pending 失效:未到突破价先触达止损侧(多 mark<=S;空 mark>=S)。"""
|
||||
try:
|
||||
m = float(mark)
|
||||
sl = float(stop_loss)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
direction = (direction or "long").strip().lower()
|
||||
if direction == "long":
|
||||
return m <= sl
|
||||
return m >= sl
|
||||
|
||||
|
||||
def validate_roll_geometry(
|
||||
direction: str,
|
||||
add_mode: str,
|
||||
*,
|
||||
new_stop_loss: float,
|
||||
add_price: Optional[float] = None,
|
||||
fib_upper: Optional[float] = None,
|
||||
fib_lower: Optional[float] = None,
|
||||
breakthrough_price: Optional[float] = None,
|
||||
entry_existing: float = 0.0,
|
||||
initial_take_profit: float = 0.0,
|
||||
mark_price: Optional[float] = None,
|
||||
) -> Optional[str]:
|
||||
direction = (direction or "long").strip().lower()
|
||||
mode = (add_mode or MARKET_MODE).strip().lower()
|
||||
try:
|
||||
sl = float(new_stop_loss)
|
||||
tp = float(initial_take_profit)
|
||||
e1 = float(entry_existing or 0)
|
||||
except (TypeError, ValueError):
|
||||
return "止损/止盈格式错误"
|
||||
if sl <= 0 or tp <= 0:
|
||||
return "止损与首仓止盈须大于0"
|
||||
if direction == "long":
|
||||
if e1 > 0 and tp <= e1:
|
||||
return "做多:首仓止盈须高于当前持仓均价"
|
||||
else:
|
||||
if e1 > 0 and tp >= e1:
|
||||
return "做空:首仓止盈须低于当前持仓均价"
|
||||
|
||||
if mode == MARKET_MODE:
|
||||
if add_price is None or float(add_price) <= 0:
|
||||
return "市价加仓需要有效参考价"
|
||||
entry_add = float(add_price)
|
||||
elif mode in FIB_MODES:
|
||||
if fib_upper is None or fib_lower is None:
|
||||
return "斐波须填写上沿 H 与下沿 L"
|
||||
entry_add, err = fib_limit_entry(direction, float(fib_upper), float(fib_lower), mode)
|
||||
if err:
|
||||
return err
|
||||
if entry_add is None or entry_add <= 0:
|
||||
return "无法计算斐波限价"
|
||||
elif mode == BREAKOUT_MODE:
|
||||
if breakthrough_price is None:
|
||||
return "突破加仓须填写突破价"
|
||||
try:
|
||||
bp = float(breakthrough_price)
|
||||
except (TypeError, ValueError):
|
||||
return "突破价格式错误"
|
||||
if bp <= 0:
|
||||
return "突破价须大于0"
|
||||
entry_add = bp
|
||||
if direction == "long":
|
||||
if sl >= bp:
|
||||
return "做多:止损须低于突破价"
|
||||
if mark_price is not None and float(mark_price) <= bp:
|
||||
return "做多:当前价须高于突破价(等待向上突破)"
|
||||
else:
|
||||
if sl <= bp:
|
||||
return "做空:止损须高于突破价"
|
||||
if mark_price is not None and float(mark_price) >= bp:
|
||||
return "做空:当前价须低于突破价(等待向下突破)"
|
||||
else:
|
||||
return "加仓方式无效"
|
||||
|
||||
if mode != BREAKOUT_MODE:
|
||||
entry_add = float(entry_add) # type: ignore[arg-type]
|
||||
if direction == "long":
|
||||
if sl >= entry_add:
|
||||
return "做多:新止损须低于加仓价"
|
||||
else:
|
||||
if sl <= entry_add:
|
||||
return "做空:新止损须高于加仓价"
|
||||
return None
|
||||
|
||||
|
||||
def preview_roll(
|
||||
*,
|
||||
direction: str,
|
||||
@@ -191,92 +296,78 @@ def preview_roll(
|
||||
initial_take_profit: float,
|
||||
add_mode: str,
|
||||
new_stop_loss: Optional[float] = None,
|
||||
stop_offset_pct: Optional[float] = None,
|
||||
risk_percent: float,
|
||||
capital_base_usdt: float,
|
||||
add_price: Optional[float] = None,
|
||||
fib_upper: Optional[float] = None,
|
||||
fib_lower: Optional[float] = None,
|
||||
breakthrough_price: Optional[float] = None,
|
||||
legs_done: int = 0,
|
||||
contract_size: float = 1.0,
|
||||
) -> Tuple[Optional[dict[str, Any]], Optional[str]]:
|
||||
direction = (direction or "long").strip().lower()
|
||||
if legs_done >= max_roll_legs(direction):
|
||||
return None, f"{'做多' if direction == 'long' else '做空'}滚仓已达 {max_roll_legs(direction)} 次上限"
|
||||
mode = (add_mode or "market").strip().lower()
|
||||
if mode == "market":
|
||||
if add_price is None or add_price <= 0:
|
||||
return None, "市价加仓需要有效参考价"
|
||||
entry_add = float(add_price)
|
||||
mode_label = "市价"
|
||||
elif mode in FIB_MODES:
|
||||
if fib_upper is None or fib_lower is None:
|
||||
return None, "斐波限价须填写上沿与下沿"
|
||||
entry_add, err = fib_limit_entry(direction, float(fib_upper), float(fib_lower), mode)
|
||||
if err:
|
||||
return None, err
|
||||
mode_label = "斐波0.618" if "618" in mode else "斐波0.786"
|
||||
else:
|
||||
return None, "加仓方式无效"
|
||||
mode = (add_mode or MARKET_MODE).strip().lower()
|
||||
if new_stop_loss is None:
|
||||
return None, "请填写新止损价"
|
||||
try:
|
||||
tp = float(initial_take_profit)
|
||||
sl = float(new_stop_loss)
|
||||
except (TypeError, ValueError):
|
||||
return None, "止盈格式错误"
|
||||
if tp <= 0:
|
||||
return None, "首仓止盈须大于0"
|
||||
stop_mode, stop_val = resolve_roll_stop_spec(
|
||||
new_stop_loss=new_stop_loss,
|
||||
stop_offset_pct=stop_offset_pct,
|
||||
entry_ref=entry_existing,
|
||||
return None, "止损价格式错误"
|
||||
if sl <= 0:
|
||||
return None, "止损须大于0"
|
||||
|
||||
geom_err = validate_roll_geometry(
|
||||
direction,
|
||||
mode,
|
||||
new_stop_loss=sl,
|
||||
add_price=add_price,
|
||||
fib_upper=fib_upper,
|
||||
fib_lower=fib_lower,
|
||||
breakthrough_price=breakthrough_price,
|
||||
entry_existing=entry_existing,
|
||||
initial_take_profit=initial_take_profit,
|
||||
mark_price=add_price if mode == BREAKOUT_MODE else add_price,
|
||||
)
|
||||
if direction == "long":
|
||||
if tp <= entry_existing:
|
||||
return None, "做多:首仓止盈须高于当前持仓均价参考"
|
||||
if geom_err:
|
||||
return None, geom_err
|
||||
|
||||
if mode == MARKET_MODE:
|
||||
entry_add = float(add_price) # validated
|
||||
elif mode in FIB_MODES:
|
||||
entry_add, _ = fib_limit_entry(direction, float(fib_upper), float(fib_lower), mode)
|
||||
entry_add = float(entry_add or 0)
|
||||
else:
|
||||
if tp >= entry_existing:
|
||||
return None, "做空:首仓止盈须低于当前持仓均价参考"
|
||||
risk_budget = float(capital_base_usdt) * (float(risk_percent) / 100.0)
|
||||
offset_pct: Optional[float] = None
|
||||
if stop_mode == "offset":
|
||||
offset_pct = float(stop_val)
|
||||
q2_raw, err = solve_add_amount_for_avg_stop_offset(
|
||||
direction, qty_existing, entry_existing, entry_add, offset_pct, risk_budget
|
||||
)
|
||||
else:
|
||||
sl = float(stop_val)
|
||||
if sl <= 0:
|
||||
return None, "止损须大于0"
|
||||
if direction == "long":
|
||||
if sl >= entry_add:
|
||||
return None, "做多:新止损须低于加仓价"
|
||||
else:
|
||||
if sl <= entry_add:
|
||||
return None, "做空:新止损须高于加仓价"
|
||||
q2_raw, err = solve_add_amount_for_total_risk(
|
||||
direction, qty_existing, entry_existing, entry_add, sl, risk_budget
|
||||
)
|
||||
entry_add = float(breakthrough_price or 0)
|
||||
|
||||
risk_budget = calc_risk_budget_usdt(capital_base_usdt, risk_percent)
|
||||
q2_raw, err = solve_add_amount_for_total_risk(
|
||||
direction,
|
||||
qty_existing,
|
||||
entry_existing,
|
||||
entry_add,
|
||||
sl,
|
||||
risk_budget,
|
||||
contract_size,
|
||||
)
|
||||
if err:
|
||||
return None, err
|
||||
q2 = float(q2_raw)
|
||||
new_qty = qty_existing + q2
|
||||
new_avg = avg_entry_after_add(qty_existing, entry_existing, q2, entry_add)
|
||||
if stop_mode == "offset":
|
||||
sl = unified_stop_from_avg(direction, new_avg, offset_pct)
|
||||
if direction == "long":
|
||||
loss_at_sl = (new_avg - sl) * new_qty
|
||||
reward_at_tp = (tp - new_avg) * new_qty
|
||||
else:
|
||||
loss_at_sl = (sl - new_avg) * new_qty
|
||||
reward_at_tp = (new_avg - tp) * new_qty
|
||||
cs = float(contract_size or 1.0)
|
||||
loss_sl = loss_at_stop_usdt(direction, new_avg, new_qty, sl, cs)
|
||||
reward_tp = reward_at_tp_usdt(direction, new_avg, initial_take_profit, new_qty, cs)
|
||||
return {
|
||||
"symbol": symbol,
|
||||
"direction": direction,
|
||||
"add_mode": mode,
|
||||
"add_mode_label": mode_label,
|
||||
"add_mode_label": mode_label(mode),
|
||||
"add_price": round(entry_add, 10),
|
||||
"new_stop_loss": round(sl, 10),
|
||||
"stop_offset_pct": offset_pct,
|
||||
"stop_mode": stop_mode,
|
||||
"initial_take_profit": tp,
|
||||
"breakthrough_price": float(breakthrough_price) if breakthrough_price not in (None, "") else None,
|
||||
"initial_take_profit": float(initial_take_profit),
|
||||
"risk_percent": float(risk_percent),
|
||||
"risk_budget_usdt": round(risk_budget, 4),
|
||||
"add_amount_raw": q2,
|
||||
@@ -284,27 +375,11 @@ def preview_roll(
|
||||
"entry_existing": float(entry_existing),
|
||||
"qty_after": new_qty,
|
||||
"avg_entry_after": round(new_avg, 10),
|
||||
"loss_at_sl_usdt": round(loss_at_sl, 4),
|
||||
"reward_at_tp_usdt": round(reward_at_tp, 4),
|
||||
"loss_at_sl_usdt": round(loss_sl, 4),
|
||||
"reward_at_tp_usdt": round(reward_tp, 4),
|
||||
"legs_done": int(legs_done),
|
||||
"leg_index_next": int(legs_done) + 1,
|
||||
"fib_upper": fib_upper,
|
||||
"fib_lower": fib_lower,
|
||||
"contract_size": cs,
|
||||
}, None
|
||||
|
||||
|
||||
def roll_stop_after_fill(
|
||||
direction: str,
|
||||
qty_before: float,
|
||||
entry_before: float,
|
||||
add_qty: float,
|
||||
fill_price: float,
|
||||
*,
|
||||
stop_offset_pct: Optional[float] = None,
|
||||
absolute_stop: Optional[float] = None,
|
||||
) -> float:
|
||||
"""成交后按合并均价重算统一止损(偏移%模式)或沿用绝对止损。"""
|
||||
if stop_offset_pct is not None and float(stop_offset_pct) > 0:
|
||||
avg = avg_entry_after_add(qty_before, entry_before, add_qty, fill_price)
|
||||
return unified_stop_from_avg(direction, avg, float(stop_offset_pct))
|
||||
return float(absolute_stop or 0)
|
||||
|
||||
Reference in New Issue
Block a user