@@ -547,6 +547,17 @@ def _sell_order_quantity(
547547
548548
549549DEFAULT_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD = 1000.0
550+ SMALL_ACCOUNT_SAFE_HAVEN_CASH_SUBSTITUTE_LIMIT_USD = 2000.0
551+
552+
553+ def _positive_target_total (targets : dict [str , Any ]) -> float :
554+ total = 0.0
555+ for value in dict (targets or {}).values ():
556+ try :
557+ total += max (0.0 , float (value or 0.0 ))
558+ except (TypeError , ValueError ):
559+ continue
560+ return total
550561
551562
552563def _apply_safe_haven_cash_substitution_to_weights (
@@ -651,6 +662,7 @@ def execute_rebalance(
651662 ),
652663 "safe_haven_cash_substituted_symbols" : [],
653664 "small_account_whole_share_substituted_symbols" : [],
665+ "small_account_safe_haven_cash_substituted_symbols" : [],
654666 "residual_cash_estimate" : float (account_values .get ("buying_power" , 0.0 ) or 0.0 ),
655667 "current_stock_weight" : 0.0 ,
656668 "current_safe_haven_weight" : 0.0 ,
@@ -726,11 +738,17 @@ def execute_rebalance(
726738
727739 target_mv = {symbol : investable * weight for symbol , weight in target_weights .items ()}
728740 small_account_candidate_symbols = tuple (
729- dict .fromkeys (tuple (allocation ["risk_symbols" ]) + tuple (allocation ["income_symbols" ]))
741+ dict .fromkeys (
742+ str (symbol or "" ).strip ().upper ()
743+ for symbol in tuple (allocation ["risk_symbols" ]) + tuple (allocation ["income_symbols" ])
744+ if str (symbol or "" ).strip ()
745+ )
730746 )
731747 if not small_account_candidate_symbols :
732748 small_account_candidate_symbols = tuple (
733- symbol for symbol in target_mv if symbol not in safe_haven_symbols
749+ str (symbol or "" ).strip ().upper ()
750+ for symbol in target_mv
751+ if str (symbol or "" ).strip ().upper () not in safe_haven_symbols
734752 )
735753 target_mv , small_account_substituted_symbols = project_unbuyable_value_targets_to_cash (
736754 target_mv ,
@@ -740,9 +758,36 @@ def execute_rebalance(
740758 )
741759 for symbol in small_account_substituted_symbols :
742760 target_weights [symbol ] = 0.0
761+ remaining_non_safe_targets = [
762+ symbol
763+ for symbol in small_account_candidate_symbols
764+ if float (target_mv .get (str (symbol or "" ).strip ().upper (), 0.0 ) or 0.0 ) > 0.0
765+ ]
766+ small_account_safe_haven_cash_substituted_symbols : list [str ] = []
767+ if (
768+ small_account_substituted_symbols
769+ and not remaining_non_safe_targets
770+ and _positive_target_total (target_mv ) <= SMALL_ACCOUNT_SAFE_HAVEN_CASH_SUBSTITUTE_LIMIT_USD
771+ ):
772+ for symbol in safe_haven_symbols :
773+ normalized_symbol = str (symbol or "" ).strip ().upper ()
774+ if float (target_mv .get (normalized_symbol , 0.0 ) or 0.0 ) > 0.0 :
775+ target_mv [normalized_symbol ] = 0.0
776+ target_weights [normalized_symbol ] = 0.0
777+ small_account_safe_haven_cash_substituted_symbols .append (normalized_symbol )
778+ if safe_haven_symbols :
779+ execution_summary ["realized_safe_haven_weight" ] = float (
780+ sum (
781+ float (target_weights .get (str (symbol or "" ).strip ().upper (), 0.0 ) or 0.0 )
782+ for symbol in safe_haven_symbols
783+ )
784+ )
743785 execution_summary ["small_account_whole_share_substituted_symbols" ] = list (
744786 small_account_substituted_symbols
745787 )
788+ execution_summary ["small_account_safe_haven_cash_substituted_symbols" ] = (
789+ small_account_safe_haven_cash_substituted_symbols
790+ )
746791 trade_logs = []
747792 target_hash = _build_target_hash (target_weights )
748793 execution_summary ["target_vs_current" ] = _build_target_diff_rows (target_weights , current_mv , equity )
0 commit comments