fix(reconcile): guard Binance/OKX restart false flat sync
Add startup grace and consecutive flat polls before external-close reconcile, matching Gate, to avoid stopping monitors and canceling TP/SL when positions API is not ready after restart. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -324,6 +324,10 @@ POSITION_SIZING_MODE = load_position_sizing_mode()
|
|||||||
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")))
|
||||||
|
_APP_STARTED_AT = time.time()
|
||||||
|
_RECONCILE_FLAT_STREAK = {}
|
||||||
KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
||||||
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")
|
||||||
@@ -4110,6 +4114,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:
|
||||||
@@ -4143,9 +4152,26 @@ def reconcile_external_closes(conn, days=None):
|
|||||||
exchange_symbol = r["exchange_symbol"] or normalize_exchange_symbol(r["symbol"])
|
exchange_symbol = r["exchange_symbol"] or normalize_exchange_symbol(r["symbol"])
|
||||||
live_contracts = get_live_position_contracts(exchange_symbol, r["direction"])
|
live_contracts = get_live_position_contracts(exchange_symbol, r["direction"])
|
||||||
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
|
||||||
|
if r["status"] != "error":
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_RECONCILE_FLAT_STREAK.pop(oid, None)
|
||||||
|
print(
|
||||||
|
f"[reconcile_external_closes] error recovery {r['symbol']} id={oid} flat -> sync close"
|
||||||
|
)
|
||||||
cancel_binance_futures_open_orders(exchange_symbol)
|
cancel_binance_futures_open_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)
|
||||||
|
|||||||
@@ -290,6 +290,10 @@ POSITION_SIZING_MODE = load_position_sizing_mode()
|
|||||||
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")))
|
||||||
|
_APP_STARTED_AT = time.time()
|
||||||
|
_RECONCILE_FLAT_STREAK = {}
|
||||||
BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC = max(
|
BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC = max(
|
||||||
15, int(os.getenv("BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC", "60"))
|
15, int(os.getenv("BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC", "60"))
|
||||||
)
|
)
|
||||||
@@ -3402,6 +3406,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:
|
||||||
@@ -3435,9 +3444,26 @@ def reconcile_external_closes(conn, days=None):
|
|||||||
exchange_symbol = r["exchange_symbol"] or normalize_okx_symbol(r["symbol"])
|
exchange_symbol = r["exchange_symbol"] or normalize_okx_symbol(r["symbol"])
|
||||||
live_contracts = get_live_position_contracts(exchange_symbol, r["direction"])
|
live_contracts = get_live_position_contracts(exchange_symbol, r["direction"])
|
||||||
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
|
||||||
|
if r["status"] != "error":
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_RECONCILE_FLAT_STREAK.pop(oid, None)
|
||||||
|
print(
|
||||||
|
f"[reconcile_external_closes] error recovery {r['symbol']} id={oid} flat -> sync close"
|
||||||
|
)
|
||||||
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)
|
||||||
result, pnl_amount, closed_at, miss_reason = resolve_synced_flat_close(r, opened_at, opened_at_ms=opened_at_ms)
|
result, pnl_amount, closed_at, miss_reason = resolve_synced_flat_close(r, opened_at, opened_at_ms=opened_at_ms)
|
||||||
|
|||||||
Reference in New Issue
Block a user