fix(trend): use mark price for DCA trigger on gate_bot
Poll trend plans with mark price (same as UI) instead of ticker last, add get_symbol_mark_price to gate_bot, tolerate position API blips, and log DCA order failures. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4370,6 +4370,24 @@ def get_price(symbol):
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def get_symbol_mark_price(symbol):
|
||||
"""趋势补仓/标记价展示:优先 ticker.mark,与页面浮盈亏口径一致。"""
|
||||
ex_sym = normalize_exchange_symbol(symbol)
|
||||
try:
|
||||
ensure_markets_loaded()
|
||||
ticker = exchange.fetch_ticker(ex_sym)
|
||||
m = _coerce_float(ticker.get("mark"), ticker.get("last"))
|
||||
if m is None:
|
||||
info = ticker.get("info") or {}
|
||||
m = _coerce_float(info.get("mark_price"), info.get("last"))
|
||||
if m is not None and m > 0:
|
||||
return float(m)
|
||||
except Exception:
|
||||
pass
|
||||
p = get_price(symbol)
|
||||
return float(p) if p is not None else None
|
||||
|
||||
# 获取5分钟K线收盘价
|
||||
def get_5m_close(symbol):
|
||||
try:
|
||||
|
||||
+58
-11
@@ -686,6 +686,33 @@ def _trend_hit_take_profit(direction: str, mark_price: float, take_profit: float
|
||||
return tp < entry and pf <= tp
|
||||
|
||||
|
||||
def _trend_poll_price(m, sym: str, ex_sym: str, direction: str) -> Optional[float]:
|
||||
"""补仓/止盈判定用标记价(与页面「标记价」一致),无标记价时回退 last。"""
|
||||
fn = getattr(m, "get_symbol_mark_price", None)
|
||||
if callable(fn):
|
||||
try:
|
||||
px = fn(sym)
|
||||
if px is not None and float(px) > 0:
|
||||
return float(px)
|
||||
except Exception:
|
||||
pass
|
||||
metrics_fn = getattr(m, "get_live_position_exchange_metrics", None)
|
||||
if callable(metrics_fn):
|
||||
try:
|
||||
met = metrics_fn(ex_sym, direction)
|
||||
if met and met.get("mark_price") is not None:
|
||||
px = float(met["mark_price"])
|
||||
if px > 0:
|
||||
return px
|
||||
except Exception:
|
||||
pass
|
||||
px = m.get_price(sym)
|
||||
try:
|
||||
return float(px) if px is not None else None
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _should_finalize_trend_flat(row, pos, plan_id: int, m) -> bool:
|
||||
"""首仓后交易所报无仓:需过开仓宽限期 + 连续空仓轮询,避免误判止损。"""
|
||||
if pos is None:
|
||||
@@ -736,20 +763,22 @@ def check_trend_pullback_plans(cfg: dict) -> None:
|
||||
sl = float(row["stop_loss"])
|
||||
tp = float(row["take_profit"])
|
||||
lev = int(row["leverage"] or 1)
|
||||
p = m.get_price(sym)
|
||||
if not p:
|
||||
continue
|
||||
pf = float(p)
|
||||
last_p = row["last_mark_price"]
|
||||
last_pf = float(last_p) if last_p is not None else pf
|
||||
pos = m.get_live_position_contracts(ex_sym, direction)
|
||||
if pos is None:
|
||||
continue
|
||||
try:
|
||||
local_open = float(row["order_amount_open"] or 0)
|
||||
except (TypeError, ValueError):
|
||||
local_open = 0.0
|
||||
if float(pos) <= 0 and local_open > 0:
|
||||
pf = _trend_poll_price(m, sym, ex_sym, direction)
|
||||
if pf is None:
|
||||
continue
|
||||
last_p = row["last_mark_price"]
|
||||
last_pf = float(last_p) if last_p is not None else pf
|
||||
pos = m.get_live_position_contracts(ex_sym, direction)
|
||||
if pos is None:
|
||||
if local_open > 0 and int(row["first_order_done"] or 0):
|
||||
pos = local_open
|
||||
else:
|
||||
continue
|
||||
elif float(pos) <= 0 and local_open > 0:
|
||||
age = _trend_plan_open_age_sec(row, m)
|
||||
if age < TREND_OPEN_GRACE_SEC * 2:
|
||||
print(
|
||||
@@ -791,8 +820,21 @@ def check_trend_pullback_plans(cfg: dict) -> None:
|
||||
break
|
||||
amt = float(m.exchange.amount_to_precision(ex_sym, leg_amounts[legs_done]))
|
||||
if amt <= 0:
|
||||
print(
|
||||
f"[trend_pullback] dca skip plan={plan_id} leg={legs_done + 1} "
|
||||
f"amt_precision=0 raw={leg_amounts[legs_done]}",
|
||||
flush=True,
|
||||
)
|
||||
break
|
||||
try:
|
||||
add_resp = trend_market_add(cfg, ex_sym, direction, amt, lev)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[trend_pullback] dca order failed plan={plan_id} sym={sym} "
|
||||
f"leg={legs_done + 1} level={level} mark={pf} err={e}",
|
||||
flush=True,
|
||||
)
|
||||
break
|
||||
add_resp = trend_market_add(cfg, ex_sym, direction, amt, lev)
|
||||
fill_px = m.extract_trade_price_from_order(add_resp) or pf
|
||||
old_avg = float(row["avg_entry_price"] or fill_px)
|
||||
old_open = float(row["order_amount_open"] or 0)
|
||||
@@ -812,6 +854,11 @@ def check_trend_pullback_plans(cfg: dict) -> None:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM trend_pullback_plans WHERE id=?", (row["id"],)
|
||||
).fetchone()
|
||||
print(
|
||||
f"[trend_pullback] dca filled plan={plan_id} leg={legs_done} "
|
||||
f"fill={fill_px} avg={new_avg} open={old_open + amt}",
|
||||
flush=True,
|
||||
)
|
||||
try:
|
||||
trend_refresh_stop_only(cfg, ex_sym, direction, sl)
|
||||
except Exception:
|
||||
|
||||
@@ -17,6 +17,12 @@ def test_trend_dca_short_not_before_first_level():
|
||||
assert trend_dca_level_reached(direction, 0.3413, 0.3413)
|
||||
|
||||
|
||||
def test_trend_dca_long_mark_below_trigger():
|
||||
direction = "long"
|
||||
assert trend_dca_level_reached(direction, 0.344, 0.3465)
|
||||
assert not trend_dca_level_reached(direction, 0.347, 0.3465)
|
||||
|
||||
|
||||
def test_trend_effective_margin_first_leg_only():
|
||||
plan = {
|
||||
"plan_margin_capital": 12.11,
|
||||
|
||||
Reference in New Issue
Block a user