修复okx 趋势回调
This commit is contained in:
+47
-14
@@ -2675,12 +2675,46 @@ def _position_matches_wanted_contract(exchange_symbol, position):
|
|||||||
if sym == exchange_symbol:
|
if sym == exchange_symbol:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
return normalize_okx_symbol(sym or "") == normalize_okx_symbol(exchange_symbol or "")
|
if normalize_okx_symbol(sym or "") == normalize_okx_symbol(exchange_symbol or ""):
|
||||||
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
|
pass
|
||||||
|
info = position.get("info") or {}
|
||||||
|
inst = (info.get("instId") or "").strip().upper()
|
||||||
|
if not inst:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
ensure_markets_loaded()
|
||||||
|
want = exchange.market(exchange_symbol)
|
||||||
|
mid = (want.get("id") or "").strip().upper()
|
||||||
|
if mid and inst == mid:
|
||||||
|
return True
|
||||||
|
base = (want.get("base") or "").strip().upper()
|
||||||
|
quote = (want.get("quote") or "").strip().upper()
|
||||||
|
if base and quote and inst == f"{base}-{quote}-SWAP":
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _fetch_okx_swap_position_rows(exchange_symbol=None):
|
def _okx_position_direction(position):
|
||||||
|
info = position.get("info") or {}
|
||||||
|
side = (position.get("side") or info.get("posSide") or "").strip().lower()
|
||||||
|
if side in ("long", "short"):
|
||||||
|
return side
|
||||||
|
try:
|
||||||
|
raw = float(info.get("pos") or position.get("contracts") or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raw = 0.0
|
||||||
|
if raw > 0:
|
||||||
|
return "long"
|
||||||
|
if raw < 0:
|
||||||
|
return "short"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_okx_swap_position_rows():
|
||||||
"""OKX 单合约 fetch_positions([sym]) 常返回空;与 /api/prices 一致拉全量 SWAP 再本地匹配。"""
|
"""OKX 单合约 fetch_positions([sym]) 常返回空;与 /api/prices 一致拉全量 SWAP 再本地匹配。"""
|
||||||
ensure_markets_loaded()
|
ensure_markets_loaded()
|
||||||
rows = None
|
rows = None
|
||||||
@@ -2695,16 +2729,11 @@ def _fetch_okx_swap_position_rows(exchange_symbol=None):
|
|||||||
continue
|
continue
|
||||||
if rows is None:
|
if rows is None:
|
||||||
return None
|
return None
|
||||||
if not exchange_symbol:
|
|
||||||
return rows
|
return rows
|
||||||
out = []
|
|
||||||
for p in rows:
|
|
||||||
if _position_matches_wanted_contract(exchange_symbol, p):
|
|
||||||
out.append(p)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def _select_live_position_row(rows, exchange_symbol, direction, relax_hedge=False):
|
def _select_live_position_row(rows, exchange_symbol, direction, relax_hedge=False):
|
||||||
|
exchange_symbol = normalize_okx_symbol(exchange_symbol or "")
|
||||||
if not rows:
|
if not rows:
|
||||||
return None
|
return None
|
||||||
candidates = []
|
candidates = []
|
||||||
@@ -2716,8 +2745,13 @@ def _select_live_position_row(rows, exchange_symbol, direction, relax_hedge=Fals
|
|||||||
contracts = _position_row_effective_contracts(p)
|
contracts = _position_row_effective_contracts(p)
|
||||||
if contracts <= 0:
|
if contracts <= 0:
|
||||||
continue
|
continue
|
||||||
if (not relax_hedge) and OKX_POS_MODE == "hedge":
|
want_dir = (direction or "").lower()
|
||||||
if side and side != (direction or "").lower():
|
if OKX_POS_MODE == "net" or side == "net":
|
||||||
|
pos_dir = _okx_position_direction(p)
|
||||||
|
if pos_dir and pos_dir != want_dir:
|
||||||
|
continue
|
||||||
|
elif (not relax_hedge) and OKX_POS_MODE == "hedge":
|
||||||
|
if side and side != want_dir:
|
||||||
continue
|
continue
|
||||||
candidates.append((contracts, p))
|
candidates.append((contracts, p))
|
||||||
if not candidates and (not relax_hedge) and OKX_POS_MODE == "hedge":
|
if not candidates and (not relax_hedge) and OKX_POS_MODE == "hedge":
|
||||||
@@ -3004,12 +3038,11 @@ def is_no_position_error(err_msg):
|
|||||||
|
|
||||||
|
|
||||||
def get_live_position_contracts(exchange_symbol, direction):
|
def get_live_position_contracts(exchange_symbol, direction):
|
||||||
rows = _fetch_okx_swap_position_rows(exchange_symbol)
|
ex_sym = normalize_okx_symbol(exchange_symbol or "")
|
||||||
|
rows = _fetch_okx_swap_position_rows()
|
||||||
if rows is None:
|
if rows is None:
|
||||||
return None
|
return None
|
||||||
if not rows:
|
prow = _select_live_position_row(rows, ex_sym, direction)
|
||||||
return 0.0
|
|
||||||
prow = _select_live_position_row(rows, exchange_symbol, direction)
|
|
||||||
if not prow:
|
if not prow:
|
||||||
return 0.0
|
return 0.0
|
||||||
return _position_row_effective_contracts(prow)
|
return _position_row_effective_contracts(prow)
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ MONITOR_TYPE_TREND = MONITOR_TYPE_TREND_PULLBACK
|
|||||||
|
|
||||||
# 趋势回调:交易所报空仓需连续 N 次轮询确认,避免 OKX 等 API 瞬时误判立即结束计划
|
# 趋势回调:交易所报空仓需连续 N 次轮询确认,避免 OKX 等 API 瞬时误判立即结束计划
|
||||||
_TREND_FLAT_STREAK: dict[int, int] = {}
|
_TREND_FLAT_STREAK: dict[int, int] = {}
|
||||||
TREND_FLAT_CONFIRM_POLLS = max(1, int(os.getenv("TREND_FLAT_CONFIRM_POLLS", "3")))
|
TREND_FLAT_CONFIRM_POLLS = max(1, int(os.getenv("TREND_FLAT_CONFIRM_POLLS", "5")))
|
||||||
TREND_OPEN_GRACE_SEC = max(0, int(os.getenv("TREND_OPEN_GRACE_SEC", "90")))
|
TREND_OPEN_GRACE_SEC = max(0, int(os.getenv("TREND_OPEN_GRACE_SEC", "180")))
|
||||||
|
|
||||||
|
|
||||||
def trend_add_zone_label(direction: str) -> str:
|
def trend_add_zone_label(direction: str) -> str:
|
||||||
@@ -417,24 +417,58 @@ def _trend_plan_open_age_sec(row, m) -> float:
|
|||||||
to_ms = getattr(m, "_to_ms_with_fallback", None)
|
to_ms = getattr(m, "_to_ms_with_fallback", None)
|
||||||
if callable(to_ms):
|
if callable(to_ms):
|
||||||
opened_ms = to_ms(opened_ms, row["opened_at"] if "opened_at" in row.keys() else None)
|
opened_ms = to_ms(opened_ms, row["opened_at"] if "opened_at" in row.keys() else None)
|
||||||
|
if opened_ms is None and "opened_at" in row.keys():
|
||||||
|
opened_ms = to_ms(None, row["opened_at"])
|
||||||
if not opened_ms:
|
if not opened_ms:
|
||||||
return 999999.0
|
return 0.0
|
||||||
return max(0.0, (time.time() * 1000 - opened_ms) / 1000.0)
|
return max(0.0, (time.time() * 1000 - opened_ms) / 1000.0)
|
||||||
|
|
||||||
|
|
||||||
|
def _trend_hit_take_profit(direction: str, mark_price: float, take_profit: float, avg_entry: float) -> bool:
|
||||||
|
try:
|
||||||
|
pf = float(mark_price)
|
||||||
|
tp = float(take_profit)
|
||||||
|
entry = float(avg_entry)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return False
|
||||||
|
if entry <= 0 or tp <= 0:
|
||||||
|
return False
|
||||||
|
direction = (direction or "long").lower()
|
||||||
|
if direction == "long":
|
||||||
|
return tp > entry and pf >= tp
|
||||||
|
return tp < entry and pf <= tp
|
||||||
|
|
||||||
|
|
||||||
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 or float(pos) > 0:
|
if pos is None:
|
||||||
|
return False
|
||||||
|
if float(pos) > 0:
|
||||||
_TREND_FLAT_STREAK.pop(plan_id, None)
|
_TREND_FLAT_STREAK.pop(plan_id, None)
|
||||||
return False
|
return False
|
||||||
if not int(row["first_order_done"] or 0):
|
if not int(row["first_order_done"] or 0):
|
||||||
return False
|
return False
|
||||||
if _trend_plan_open_age_sec(row, m) < TREND_OPEN_GRACE_SEC:
|
age = _trend_plan_open_age_sec(row, m)
|
||||||
|
if age < TREND_OPEN_GRACE_SEC:
|
||||||
_TREND_FLAT_STREAK.pop(plan_id, None)
|
_TREND_FLAT_STREAK.pop(plan_id, None)
|
||||||
return False
|
return False
|
||||||
|
try:
|
||||||
|
local_open = float(row["order_amount_open"] or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
local_open = 0.0
|
||||||
|
required = TREND_FLAT_CONFIRM_POLLS
|
||||||
|
if local_open > 0 and age < TREND_OPEN_GRACE_SEC * 2:
|
||||||
|
required = max(required, TREND_FLAT_CONFIRM_POLLS * 2)
|
||||||
streak = int(_TREND_FLAT_STREAK.get(plan_id, 0)) + 1
|
streak = int(_TREND_FLAT_STREAK.get(plan_id, 0)) + 1
|
||||||
_TREND_FLAT_STREAK[plan_id] = streak
|
_TREND_FLAT_STREAK[plan_id] = streak
|
||||||
return streak >= TREND_FLAT_CONFIRM_POLLS
|
if streak >= required:
|
||||||
|
print(
|
||||||
|
f"[trend_pullback] flat finalize plan={plan_id} sym={row['symbol']} "
|
||||||
|
f"age={age:.0f}s streak={streak} local_open={local_open}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check_trend_pullback_plans(cfg: dict) -> None:
|
def check_trend_pullback_plans(cfg: dict) -> None:
|
||||||
@@ -464,6 +498,19 @@ def check_trend_pullback_plans(cfg: dict) -> None:
|
|||||||
pos = m.get_live_position_contracts(ex_sym, direction)
|
pos = m.get_live_position_contracts(ex_sym, direction)
|
||||||
if pos is None:
|
if pos is None:
|
||||||
continue
|
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:
|
||||||
|
age = _trend_plan_open_age_sec(row, m)
|
||||||
|
if age < TREND_OPEN_GRACE_SEC * 2:
|
||||||
|
print(
|
||||||
|
f"[trend_pullback] pos fallback plan={plan_id} sym={sym} "
|
||||||
|
f"ex_pos=0 local_open={local_open} age={age:.0f}s",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
pos = local_open
|
||||||
legs_done = int(row["legs_done"] or 0)
|
legs_done = int(row["legs_done"] or 0)
|
||||||
try:
|
try:
|
||||||
leg_amounts = [float(x) for x in json.loads(row["leg_amounts_json"] or "[]")]
|
leg_amounts = [float(x) for x in json.loads(row["leg_amounts_json"] or "[]")]
|
||||||
@@ -473,7 +520,8 @@ def check_trend_pullback_plans(cfg: dict) -> None:
|
|||||||
grid = json.loads(row["grid_prices_json"] or "[]")
|
grid = json.loads(row["grid_prices_json"] or "[]")
|
||||||
except Exception:
|
except Exception:
|
||||||
grid = []
|
grid = []
|
||||||
hit_tp = (direction == "long" and pf >= tp) or (direction == "short" and pf <= tp)
|
avg_e = float(row["avg_entry_price"] or pf or 0)
|
||||||
|
hit_tp = _trend_hit_take_profit(direction, pf, tp, avg_e)
|
||||||
if hit_tp and pos > 0:
|
if hit_tp and pos > 0:
|
||||||
try:
|
try:
|
||||||
close_resp = trend_market_close(cfg, ex_sym, direction, float(pos), lev)
|
close_resp = trend_market_close(cfg, ex_sym, direction, float(pos), lev)
|
||||||
|
|||||||
Reference in New Issue
Block a user