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:
dekun
2026-06-07 17:38:26 +08:00
parent d56d9050aa
commit 0760873d9d
3 changed files with 82 additions and 11 deletions
+18
View File
@@ -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
View File
@@ -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:
+6
View File
@@ -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,