@@ -434,14 +434,25 @@ def execute_rebalance(
434434 )
435435 trade_logs .extend (_format_target_lines (target_weights , current_mv , equity , translator = translator ))
436436
437+ missing_price_symbols : list [str ] = []
438+ insufficient_buying_power_symbols : list [str ] = []
439+ min_notional_symbols : list [str ] = []
440+ quantity_zero_symbols : list [str ] = []
441+
437442 has_sell_plan = False
438443 for symbol in all_symbols :
439444 current = current_mv .get (symbol , 0.0 )
440445 target = target_mv .get (symbol , 0.0 )
446+ if current <= target + threshold :
447+ continue
441448 price = prices .get (symbol )
442- if price and current > target + threshold and int ((current - target ) / price ) > 0 :
449+ if not price :
450+ missing_price_symbols .append (symbol )
451+ continue
452+ if int ((current - target ) / price ) > 0 :
443453 has_sell_plan = True
444454 break
455+ quantity_zero_symbols .append (symbol )
445456
446457 anticipated_buying_power = get_available_buying_power (
447458 ib ,
@@ -450,19 +461,54 @@ def execute_rebalance(
450461 has_buy_plan = False
451462 for symbol , target in target_mv .items ():
452463 current = current_mv .get (symbol , 0.0 )
464+ if current >= target - threshold :
465+ continue
453466 price = prices .get (symbol )
454- if not price or current >= target - threshold :
467+ if not price :
468+ missing_price_symbols .append (symbol )
455469 continue
456470 buy_value = min (target - current , anticipated_buying_power * 0.95 )
471+ if buy_value <= 0 :
472+ insufficient_buying_power_symbols .append (symbol )
473+ continue
474+ if buy_value < 50 :
475+ min_notional_symbols .append (symbol )
476+ continue
457477 limit_price = round (price * limit_buy_premium , 2 )
458478 qty = int (buy_value / limit_price ) if limit_price > 0 else 0
459- if qty > 0 and buy_value >= 50 :
479+ if qty > 0 :
460480 has_buy_plan = True
461481 break
482+ quantity_zero_symbols .append (symbol )
462483
463484 if not has_sell_plan and not has_buy_plan :
464- execution_summary ["execution_status" ] = "no_op"
465- execution_summary ["no_op_reason" ] = "target_diff_below_threshold"
485+ reason = "target_diff_below_threshold"
486+ status = "no_op"
487+ if missing_price_symbols :
488+ symbols = "," .join (sorted (dict .fromkeys (missing_price_symbols )))
489+ reason = f"missing_price:{ symbols } "
490+ status = "blocked"
491+ execution_summary ["orders_skipped" ].extend (
492+ {"symbol" : symbol , "reason" : "missing_price" }
493+ for symbol in sorted (dict .fromkeys (missing_price_symbols ))
494+ )
495+ elif insufficient_buying_power_symbols :
496+ symbols = "," .join (sorted (dict .fromkeys (insufficient_buying_power_symbols )))
497+ reason = f"insufficient_buying_power:{ symbols } "
498+ status = "blocked"
499+ elif min_notional_symbols :
500+ symbols = "," .join (sorted (dict .fromkeys (min_notional_symbols )))
501+ reason = f"min_notional:{ symbols } "
502+ elif quantity_zero_symbols :
503+ symbols = "," .join (sorted (dict .fromkeys (quantity_zero_symbols )))
504+ reason = f"quantity_zero:{ symbols } "
505+
506+ execution_summary ["execution_status" ] = status
507+ execution_summary ["no_op_reason" ] = reason
508+ if reason != "target_diff_below_threshold" :
509+ execution_summary ["skipped_reasons" ].append (reason )
510+ if status == "blocked" :
511+ trade_logs .append (translator ("failed" , reason = reason ))
466512 return _finalize_result (trade_logs , execution_summary , return_summary = return_summary )
467513
468514 same_day_filled_symbols = _collect_same_day_filled_symbols (ib , set (all_symbols ), trade_date )
0 commit comments