修改支撑阻力的企业微信推送
This commit is contained in:
@@ -127,6 +127,10 @@ BALANCE_REFRESH_SECONDS=60
|
|||||||
PRICE_REFRESH_SECONDS=5
|
PRICE_REFRESH_SECONDS=5
|
||||||
# 后台监控轮询周期(秒)
|
# 后台监控轮询周期(秒)
|
||||||
MONITOR_POLL_SECONDS=3
|
MONITOR_POLL_SECONDS=3
|
||||||
|
# 重启后多少秒内不做「外部平仓」同步(避免 API 未就绪误判)
|
||||||
|
RECONCILE_STARTUP_GRACE_SEC=90
|
||||||
|
# 连续多少次轮询确认交易所空仓后,才记为外部平仓(默认 3 次 ≈ 9 秒)
|
||||||
|
RECONCILE_FLAT_CONFIRM_POLLS=3
|
||||||
# 使用可用资金时的缓冲比例(如0.98代表用98%)
|
# 使用可用资金时的缓冲比例(如0.98代表用98%)
|
||||||
FULL_MARGIN_BUFFER_RATIO=0.98
|
FULL_MARGIN_BUFFER_RATIO=0.98
|
||||||
|
|
||||||
|
|||||||
+55
-18
@@ -227,7 +227,11 @@ AUTO_TRANSFER_BJ_HOUR = int(os.getenv("AUTO_TRANSFER_BJ_HOUR", "8"))
|
|||||||
WECHAT_TIMEOUT_SECONDS = int(os.getenv("WECHAT_TIMEOUT_SECONDS", "10"))
|
WECHAT_TIMEOUT_SECONDS = int(os.getenv("WECHAT_TIMEOUT_SECONDS", "10"))
|
||||||
AI_TIMEOUT_SECONDS = int(os.getenv("AI_TIMEOUT_SECONDS", "120"))
|
AI_TIMEOUT_SECONDS = int(os.getenv("AI_TIMEOUT_SECONDS", "120"))
|
||||||
MONITOR_POLL_SECONDS = int(os.getenv("MONITOR_POLL_SECONDS", "3"))
|
MONITOR_POLL_SECONDS = int(os.getenv("MONITOR_POLL_SECONDS", "3"))
|
||||||
|
RECONCILE_STARTUP_GRACE_SEC = int(os.getenv("RECONCILE_STARTUP_GRACE_SEC", "90"))
|
||||||
|
RECONCILE_FLAT_CONFIRM_POLLS = max(1, int(os.getenv("RECONCILE_FLAT_CONFIRM_POLLS", "3")))
|
||||||
KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
||||||
|
_APP_STARTED_AT = time.time()
|
||||||
|
_RECONCILE_FLAT_STREAK = {}
|
||||||
FULL_MARGIN_BUFFER_RATIO = float(os.getenv("FULL_MARGIN_BUFFER_RATIO", "0.98"))
|
FULL_MARGIN_BUFFER_RATIO = float(os.getenv("FULL_MARGIN_BUFFER_RATIO", "0.98"))
|
||||||
TRANSFER_CCY = os.getenv("TRANSFER_CCY", "USDT")
|
TRANSFER_CCY = os.getenv("TRANSFER_CCY", "USDT")
|
||||||
UPLOAD_FOLDER = resolve_path(os.getenv("UPLOAD_DIR", "static/images"))
|
UPLOAD_FOLDER = resolve_path(os.getenv("UPLOAD_DIR", "static/images"))
|
||||||
@@ -3339,38 +3343,54 @@ def is_no_position_error(err_msg):
|
|||||||
return any(k in msg for k in keywords)
|
return any(k in msg for k in keywords)
|
||||||
|
|
||||||
|
|
||||||
def get_live_position_contracts(exchange_symbol, direction):
|
def _gate_fetch_position_rows(exchange_symbol):
|
||||||
ensure_markets_loaded()
|
"""优先拉 USDT 本位全量持仓(与页面一致),避免单合约查询在重启后返回空列表误判空仓。"""
|
||||||
try:
|
try:
|
||||||
rows = exchange.fetch_positions([exchange_symbol])
|
ensure_markets_loaded()
|
||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
try:
|
||||||
|
return exchange.fetch_positions(None, {"settle": "usdt"}) or []
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if not exchange_symbol:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return exchange.fetch_positions([exchange_symbol]) or []
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _sum_live_position_contracts(rows, exchange_symbol, direction, relax_direction=False):
|
||||||
total = 0.0
|
total = 0.0
|
||||||
|
if not rows:
|
||||||
|
return total
|
||||||
|
direction = (direction or "long").strip().lower()
|
||||||
for p in rows:
|
for p in rows:
|
||||||
if not _position_matches_wanted_contract(exchange_symbol, p):
|
if not _position_matches_wanted_contract(exchange_symbol, p):
|
||||||
continue
|
continue
|
||||||
info = p.get("info", {}) or {}
|
contracts = _position_row_effective_contracts(p)
|
||||||
side = (p.get("side") or info.get("posSide") or "").lower()
|
|
||||||
contracts = p.get("contracts")
|
|
||||||
if contracts is None:
|
|
||||||
raw_pos = info.get("pos") or info.get("size")
|
|
||||||
try:
|
|
||||||
contracts = abs(float(raw_pos)) if raw_pos is not None else 0.0
|
|
||||||
except Exception:
|
|
||||||
contracts = 0.0
|
|
||||||
try:
|
|
||||||
contracts = float(contracts)
|
|
||||||
except Exception:
|
|
||||||
contracts = 0.0
|
|
||||||
if contracts <= 0:
|
if contracts <= 0:
|
||||||
continue
|
continue
|
||||||
if GATE_POS_MODE == "hedge":
|
if (not relax_direction) and GATE_POS_MODE == "hedge":
|
||||||
|
info = p.get("info", {}) or {}
|
||||||
|
side = (p.get("side") or info.get("posSide") or "").lower()
|
||||||
if side and side != direction:
|
if side and side != direction:
|
||||||
continue
|
continue
|
||||||
total += contracts
|
total += contracts
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
def get_live_position_contracts(exchange_symbol, direction):
|
||||||
|
rows = _gate_fetch_position_rows(exchange_symbol)
|
||||||
|
if rows is None:
|
||||||
|
return None
|
||||||
|
total = _sum_live_position_contracts(rows, exchange_symbol, direction, relax_direction=False)
|
||||||
|
if total <= 0 and GATE_POS_MODE == "hedge":
|
||||||
|
total = _sum_live_position_contracts(rows, exchange_symbol, direction, relax_direction=True)
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
def _select_live_position_row(rows, exchange_symbol, direction, relax_hedge=False):
|
def _select_live_position_row(rows, exchange_symbol, direction, relax_hedge=False):
|
||||||
"""在 fetch_positions 结果中取与当前监控方向一致、张数最大的一条(与 get_live_position_contracts 过滤规则一致)。"""
|
"""在 fetch_positions 结果中取与当前监控方向一致、张数最大的一条(与 get_live_position_contracts 过滤规则一致)。"""
|
||||||
if not rows:
|
if not rows:
|
||||||
@@ -3805,6 +3825,11 @@ def resolve_synced_flat_close(row, opened_at_str, opened_at_ms=None):
|
|||||||
|
|
||||||
|
|
||||||
def reconcile_external_closes(conn, days=None):
|
def reconcile_external_closes(conn, days=None):
|
||||||
|
global _RECONCILE_FLAT_STREAK
|
||||||
|
if not exchange_private_api_configured():
|
||||||
|
return 0
|
||||||
|
if time.time() - _APP_STARTED_AT < RECONCILE_STARTUP_GRACE_SEC:
|
||||||
|
return 0
|
||||||
synced_count = 0
|
synced_count = 0
|
||||||
cutoff_ms = None
|
cutoff_ms = None
|
||||||
if days is not None:
|
if days is not None:
|
||||||
@@ -3822,12 +3847,24 @@ def reconcile_external_closes(conn, days=None):
|
|||||||
# 手动同步按最近 N 天过滤,避免把更早历史单误同步进来
|
# 手动同步按最近 N 天过滤,避免把更早历史单误同步进来
|
||||||
if opened_ms is None or opened_ms < cutoff_ms:
|
if opened_ms is None or opened_ms < cutoff_ms:
|
||||||
continue
|
continue
|
||||||
exchange_symbol = r["exchange_symbol"] or normalize_exchange_symbol(r["symbol"])
|
exchange_symbol = resolve_monitor_exchange_symbol(r)
|
||||||
live_contracts = get_live_position_contracts(exchange_symbol, r["direction"])
|
live_contracts = get_live_position_contracts(exchange_symbol, r["direction"])
|
||||||
|
oid = int(r["id"])
|
||||||
if live_contracts is None:
|
if live_contracts is None:
|
||||||
|
_RECONCILE_FLAT_STREAK.pop(oid, None)
|
||||||
continue
|
continue
|
||||||
if live_contracts > 0:
|
if live_contracts > 0:
|
||||||
|
_RECONCILE_FLAT_STREAK.pop(oid, None)
|
||||||
continue
|
continue
|
||||||
|
streak = int(_RECONCILE_FLAT_STREAK.get(oid, 0)) + 1
|
||||||
|
_RECONCILE_FLAT_STREAK[oid] = streak
|
||||||
|
if streak < RECONCILE_FLAT_CONFIRM_POLLS:
|
||||||
|
continue
|
||||||
|
_RECONCILE_FLAT_STREAK.pop(oid, None)
|
||||||
|
print(
|
||||||
|
f"[reconcile_external_closes] {r['symbol']} id={oid} "
|
||||||
|
f"flat x{streak} polls -> sync close"
|
||||||
|
)
|
||||||
cancel_gate_swap_trigger_orders(exchange_symbol)
|
cancel_gate_swap_trigger_orders(exchange_symbol)
|
||||||
opened_at = get_opened_at_value(r)
|
opened_at = get_opened_at_value(r)
|
||||||
opened_at_ms = _to_ms_with_fallback(r["opened_at_ms"] if "opened_at_ms" in r.keys() else None, opened_at)
|
opened_at_ms = _to_ms_with_fallback(r["opened_at_ms"] if "opened_at_ms" in r.keys() else None, opened_at)
|
||||||
|
|||||||
Reference in New Issue
Block a user