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:
dekun
2026-06-16 17:03:51 +08:00
parent 6287ca9129
commit ca1e25888d
2 changed files with 42 additions and 28 deletions
+19 -12
View File
@@ -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)