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:
|
except:
|
||||||
return None
|
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线收盘价
|
# 获取5分钟K线收盘价
|
||||||
def get_5m_close(symbol):
|
def get_5m_close(symbol):
|
||||||
try:
|
try:
|
||||||
|
|||||||
+57
-10
@@ -686,6 +686,33 @@ def _trend_hit_take_profit(direction: str, mark_price: float, take_profit: float
|
|||||||
return tp < entry and pf <= tp
|
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:
|
def _should_finalize_trend_flat(row, pos, plan_id: int, m) -> bool:
|
||||||
"""首仓后交易所报无仓:需过开仓宽限期 + 连续空仓轮询,避免误判止损。"""
|
"""首仓后交易所报无仓:需过开仓宽限期 + 连续空仓轮询,避免误判止损。"""
|
||||||
if pos is None:
|
if pos is None:
|
||||||
@@ -736,20 +763,22 @@ def check_trend_pullback_plans(cfg: dict) -> None:
|
|||||||
sl = float(row["stop_loss"])
|
sl = float(row["stop_loss"])
|
||||||
tp = float(row["take_profit"])
|
tp = float(row["take_profit"])
|
||||||
lev = int(row["leverage"] or 1)
|
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:
|
try:
|
||||||
local_open = float(row["order_amount_open"] or 0)
|
local_open = float(row["order_amount_open"] or 0)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
local_open = 0.0
|
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)
|
age = _trend_plan_open_age_sec(row, m)
|
||||||
if age < TREND_OPEN_GRACE_SEC * 2:
|
if age < TREND_OPEN_GRACE_SEC * 2:
|
||||||
print(
|
print(
|
||||||
@@ -791,8 +820,21 @@ def check_trend_pullback_plans(cfg: dict) -> None:
|
|||||||
break
|
break
|
||||||
amt = float(m.exchange.amount_to_precision(ex_sym, leg_amounts[legs_done]))
|
amt = float(m.exchange.amount_to_precision(ex_sym, leg_amounts[legs_done]))
|
||||||
if amt <= 0:
|
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
|
break
|
||||||
|
try:
|
||||||
add_resp = trend_market_add(cfg, ex_sym, direction, amt, lev)
|
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
|
||||||
fill_px = m.extract_trade_price_from_order(add_resp) or pf
|
fill_px = m.extract_trade_price_from_order(add_resp) or pf
|
||||||
old_avg = float(row["avg_entry_price"] or fill_px)
|
old_avg = float(row["avg_entry_price"] or fill_px)
|
||||||
old_open = float(row["order_amount_open"] or 0)
|
old_open = float(row["order_amount_open"] or 0)
|
||||||
@@ -812,6 +854,11 @@ def check_trend_pullback_plans(cfg: dict) -> None:
|
|||||||
row = conn.execute(
|
row = conn.execute(
|
||||||
"SELECT * FROM trend_pullback_plans WHERE id=?", (row["id"],)
|
"SELECT * FROM trend_pullback_plans WHERE id=?", (row["id"],)
|
||||||
).fetchone()
|
).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:
|
try:
|
||||||
trend_refresh_stop_only(cfg, ex_sym, direction, sl)
|
trend_refresh_stop_only(cfg, ex_sym, direction, sl)
|
||||||
except Exception:
|
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)
|
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():
|
def test_trend_effective_margin_first_leg_only():
|
||||||
plan = {
|
plan = {
|
||||||
"plan_margin_capital": 12.11,
|
"plan_margin_capital": 12.11,
|
||||||
|
|||||||
Reference in New Issue
Block a user