@ -1596,7 +1596,7 @@ class PaperTradingEngine:
)
)
return True
return True
except Exception as exc :
except Exception as exc :
logger . warning ( " parking_sell_for_event_failed " , error = str ( exc ) )
logger . warning ( " parking_sell_for_event_failed " , error = str ( exc ) or repr ( exc ) , exc_type = type ( exc ) . __name__ , symbol = sym , qty = shares_to_sell )
return False
return False
def _parking_position_value (
def _parking_position_value (
@ -2502,7 +2502,11 @@ class PaperTradingEngine:
engine_daily_new_risk_used = engine_risk_used if engine_cfg else 0.0 ,
engine_daily_new_risk_used = engine_risk_used if engine_cfg else 0.0 ,
)
)
freed = False
freed = False
parking_had_qty = False
if plan . skip_reason == " insufficient_cash " :
if plan . skip_reason == " insufficient_cash " :
# Check if parking has shares before attempting liquidation
_pst = self . _state . get_parking_state ( session_id )
parking_had_qty = _pst is not None and ( _pst . get ( " qty " ) or 0 ) > 0
# 1) Attempt to free parking cash before giving up
# 1) Attempt to free parking cash before giving up
needed = plan . shares * float ( candidate . entry_price_est ) if plan . shares else float ( candidate . entry_price_est )
needed = plan . shares * float ( candidate . entry_price_est ) if plan . shares else float ( candidate . entry_price_est )
freed = self . _parking_liquidate_for_event ( session_id , today , needed )
freed = self . _parking_liquidate_for_event ( session_id , today , needed )
@ -2537,9 +2541,7 @@ class PaperTradingEngine:
)
)
if plan . skip_reason :
if plan . skip_reason :
# If we just freed parking cash but plan still shows insufficient_cash,
# Case 1: parking freed but cash not yet reflected in account (Alpaca latency)
# Alpaca may not have settled the SGOV sell yet → skip without recording
# so the event retries on the next run_next_open.
if freed and plan . skip_reason == " insufficient_cash " :
if freed and plan . skip_reason == " insufficient_cash " :
logger . warning (
logger . warning (
" paper_engine_parking_freed_cash_not_settled " ,
" paper_engine_parking_freed_cash_not_settled " ,
@ -2547,6 +2549,15 @@ class PaperTradingEngine:
hint = " SGOV sold but cash not yet reflected in account — will retry on next run " ,
hint = " SGOV sold but cash not yet reflected in account — will retry on next run " ,
)
)
continue
continue
# Case 2: parking existed but sell failed (transient Alpaca error)
# Don't permanently reject — retry next run when sell may succeed.
if not freed and parking_had_qty and plan . skip_reason == " insufficient_cash " :
logger . warning (
" paper_engine_parking_sell_failed_skip " ,
symbol = candidate . symbol ,
hint = " Parking sell failed (transient?) — skipping without recording to allow retry " ,
)
continue
self . _state . record_processed_event (
self . _state . record_processed_event (
session_id , candidate . event_id , today . isoformat ( ) ,
session_id , candidate . event_id , today . isoformat ( ) ,
" rejected " , skip_reason = plan . skip_reason ,
" rejected " , skip_reason = plan . skip_reason ,