@ -1825,6 +1825,24 @@ class PaperTradingEngine:
lookback_rows = await self . _detector . get_candidates_for_lookback (
lookback_rows = await self . _detector . get_candidates_for_lookback (
today , self . _lookback_start_date ( today ) , self . _config
today , self . _lookback_start_date ( today ) , self . _config
)
)
# Override entry_price_est with current market price for lookback rows.
# Sizing was designed for the historical reaction-day price; filling at
# today's market price without updating entry_price_est causes share counts
# to be based on a stale price, which can result in the account going into
# negative cash (cost = shares × current_price > shares × hist_price).
if lookback_rows :
lb_symbols = list ( { str ( r . get ( " symbol " , " " ) ) . upper ( ) for r in lookback_rows if r . get ( " symbol " ) } )
try :
latest_bars = self . _broker . get_latest_bars ( lb_symbols )
for row in lookback_rows :
sym = str ( row . get ( " symbol " , " " ) ) . upper ( )
bar = latest_bars . get ( sym )
if bar is not None :
row [ " lookback_original_entry_price_est " ] = row . get ( " entry_price_est " )
row [ " entry_price_est " ] = bar . close
except Exception as e :
logger . warning ( " lookback_price_override_failed " , error = str ( e ) )
if lookback_rows :
if lookback_rows :
macro_data_lb = await self . _fetch_macro ( today )
macro_data_lb = await self . _fetch_macro ( today )
lookback_entries , lookback_rejected = await self . _process_entries (
lookback_entries , lookback_rejected = await self . _process_entries (
@ -2322,6 +2340,42 @@ class PaperTradingEngine:
ecfg = next ( ( e for e in engines if e . engine_id == eid ) , None )
ecfg = next ( ( e for e in engines if e . engine_id == eid ) , None )
candidate_batches . append ( ( ecfg , acs ) )
candidate_batches . append ( ( ecfg , acs ) )
# ============================================================
# Lookback MHD expiration filter (mirrors BacktestRunner run.py:1832-1843).
# Reject lookback candidates whose holding window has expired or whose
# remaining holding period is too short to be worthwhile.
# ============================================================
min_remaining = self . _config . execution . lookback_min_remaining_days
if min_remaining is not None or True : # always run expiry check
from libs . backtest . execution import build_effective_execution_config
filtered_batches : list [ tuple [ Any , list [ Any ] ] ] = [ ]
for ecfg , batch in candidate_batches :
filtered : list [ Any ] = [ ]
for candidate in batch :
if not candidate . features . get ( " is_lookback_entry " , False ) :
filtered . append ( candidate )
continue
elapsed = int ( candidate . features . get ( " lookback_days_elapsed " , 0 ) )
eff_exec = build_effective_execution_config ( candidate , self . _config )
candidate_mhd = eff_exec . max_holding_days
if elapsed > = candidate_mhd :
logger . info (
" lookback_skip_expired " ,
symbol = candidate . symbol , elapsed = elapsed , mhd = candidate_mhd ,
)
continue
remaining = candidate_mhd - elapsed
if min_remaining is not None and remaining < min_remaining :
logger . info (
" lookback_skip_insufficient_remaining " ,
symbol = candidate . symbol , elapsed = elapsed , mhd = candidate_mhd ,
remaining = remaining , min_required = min_remaining ,
)
continue
filtered . append ( candidate )
filtered_batches . append ( ( ecfg , filtered ) )
candidate_batches = filtered_batches
# ============================================================
# ============================================================
# Phase 2: Cross-Engine Global Score Ranking
# Phase 2: Cross-Engine Global Score Ranking
# Apply strategy_engine_selection_mode before allocation.
# Apply strategy_engine_selection_mode before allocation.
@ -3187,6 +3241,11 @@ class PaperTradingEngine:
and unrealized_r > engine_cfg . rotation_max_unrealized_r
and unrealized_r > engine_cfg . rotation_max_unrealized_r
) :
) :
continue
continue
if (
engine_cfg . rotation_min_unrealized_r is not None
and unrealized_r > = engine_cfg . rotation_min_unrealized_r
) :
continue
try :
try :
self . _broker . close_position ( sym )
self . _broker . close_position ( sym )