fix(sync-close): reject pre-open fills when backfilling trade records
False external-close sync could match historical closing trades before opened_at, producing stop-loss results with closed_at earlier than opened_at. Only use fills at or after open time. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+19
-12
@@ -3206,7 +3206,7 @@ def fetch_latest_closing_fill(exchange_symbol, direction, opened_at_str, opened_
|
||||
since_ms = _to_ms_with_fallback(opened_at_ms, opened_at_str)
|
||||
close_side = "sell" if direction == "long" else "buy"
|
||||
|
||||
def pick_from_trades(trades):
|
||||
def pick_from_trades(trades, min_ts=None):
|
||||
if not trades:
|
||||
return None
|
||||
candidates = []
|
||||
@@ -3223,6 +3223,12 @@ def fetch_latest_closing_fill(exchange_symbol, direction, opened_at_str, opened_
|
||||
ts = t.get("timestamp")
|
||||
if ts is None:
|
||||
continue
|
||||
try:
|
||||
ts_i = int(ts)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if min_ts and ts_i < int(min_ts):
|
||||
continue
|
||||
candidates.append(t)
|
||||
if not candidates:
|
||||
return None
|
||||
@@ -3230,11 +3236,7 @@ def fetch_latest_closing_fill(exchange_symbol, direction, opened_at_str, opened_
|
||||
|
||||
try:
|
||||
trades = exchange.fetch_my_trades(exchange_symbol, since=since_ms, limit=100)
|
||||
hit = pick_from_trades(trades)
|
||||
if hit is None and since_ms:
|
||||
trades = exchange.fetch_my_trades(exchange_symbol, since=None, limit=100)
|
||||
hit = pick_from_trades(trades)
|
||||
return hit
|
||||
return pick_from_trades(trades, since_ms)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -3259,11 +3261,6 @@ def fetch_closing_fills_for_record(exchange_symbol, direction, opened_at_str, cl
|
||||
trades = exchange.fetch_my_trades(exchange_symbol, since=since_ms, limit=200)
|
||||
except Exception:
|
||||
trades = []
|
||||
if not trades and since_ms:
|
||||
try:
|
||||
trades = exchange.fetch_my_trades(exchange_symbol, since=None, limit=200)
|
||||
except Exception:
|
||||
trades = []
|
||||
for t in trades or []:
|
||||
if (t.get("side") or "").lower() != close_side:
|
||||
continue
|
||||
@@ -3357,6 +3354,9 @@ def resolve_synced_flat_close(row, opened_at_str, opened_at_ms=None):
|
||||
leverage = row["leverage"] or infer_leverage(sym)
|
||||
exchange_symbol = row["exchange_symbol"] or normalize_okx_symbol(sym)
|
||||
|
||||
open_ms = _to_ms_with_fallback(
|
||||
row["opened_at_ms"] if "opened_at_ms" in row.keys() else None, opened_at_str
|
||||
)
|
||||
trade = fetch_latest_closing_fill(exchange_symbol, direction, opened_at_str, opened_at_ms=opened_at_ms)
|
||||
exit_px = None
|
||||
closed_at_str = app_now_str()
|
||||
@@ -3367,7 +3367,14 @@ def resolve_synced_flat_close(row, opened_at_str, opened_at_ms=None):
|
||||
exit_px = None
|
||||
ts = trade.get("timestamp")
|
||||
if ts:
|
||||
closed_at_str = ms_to_app_local_str(int(ts))
|
||||
try:
|
||||
ts_i = int(ts)
|
||||
except (TypeError, ValueError):
|
||||
ts_i = None
|
||||
if ts_i is not None and open_ms and ts_i < int(open_ms):
|
||||
exit_px = None
|
||||
elif ts_i is not None:
|
||||
closed_at_str = ms_to_app_local_str(ts_i)
|
||||
|
||||
if exit_px is None or exit_px <= 0:
|
||||
p = get_price(sym)
|
||||
|
||||
Reference in New Issue
Block a user