@@ -40,6 +40,42 @@ def should_sell_cash_sweep_to_fund_whole_share_buy(
4040 if current_buying_power + sweep_capacity >= quote_price :
4141 return True
4242 return False
43+ try :
44+ from quant_platform_kit .common .small_account_compatibility import (
45+ project_unbuyable_value_targets_to_cash ,
46+ )
47+ except ImportError : # pragma: no cover - compatibility with older pinned shared wheels
48+ def project_unbuyable_value_targets_to_cash (
49+ target_values ,
50+ prices ,
51+ * ,
52+ symbols = None ,
53+ quantity_step = 1.0 ,
54+ ):
55+ adjusted = {
56+ str (symbol or "" ).strip ().upper (): float (value or 0.0 )
57+ for symbol , value in dict (target_values or {}).items ()
58+ }
59+ step = max (0.0 , float (quantity_step or 0.0 ))
60+ if step <= 0.0 :
61+ return adjusted , ()
62+ candidate_symbols = (
63+ tuple (adjusted )
64+ if symbols is None
65+ else tuple (dict .fromkeys (str (symbol or "" ).strip ().upper () for symbol in symbols ))
66+ )
67+ normalized_prices = {
68+ str (symbol or "" ).strip ().upper (): float (price or 0.0 )
69+ for symbol , price in dict (prices or {}).items ()
70+ }
71+ substituted = []
72+ for symbol in candidate_symbols :
73+ target_value = max (0.0 , float (adjusted .get (symbol , 0.0 ) or 0.0 ))
74+ price = max (0.0 , float (normalized_prices .get (symbol , 0.0 ) or 0.0 ))
75+ if price > 0.0 and 0.0 < target_value < (price * step ):
76+ adjusted [symbol ] = 0.0
77+ substituted .append (symbol )
78+ return adjusted , tuple (dict .fromkeys (substituted ))
4379from quant_platform_kit .common .quantity import (
4480 floor_to_quantity_step ,
4581 format_quantity ,
@@ -613,6 +649,7 @@ def execute_rebalance(
613649 max (0.0 , float (safe_haven_cash_substitute_threshold_usd or 0.0 ))
614650 ),
615651 "safe_haven_cash_substituted_symbols" : [],
652+ "small_account_whole_share_substituted_symbols" : [],
616653 "residual_cash_estimate" : float (account_values .get ("buying_power" , 0.0 ) or 0.0 ),
617654 "current_stock_weight" : 0.0 ,
618655 "current_safe_haven_weight" : 0.0 ,
@@ -684,6 +721,24 @@ def execute_rebalance(
684721 current_mv [symbol ] = qty * price
685722
686723 target_mv = {symbol : investable * weight for symbol , weight in target_weights .items ()}
724+ small_account_candidate_symbols = tuple (
725+ dict .fromkeys (tuple (allocation ["risk_symbols" ]) + tuple (allocation ["income_symbols" ]))
726+ )
727+ if not small_account_candidate_symbols :
728+ small_account_candidate_symbols = tuple (
729+ symbol for symbol in target_mv if symbol not in safe_haven_symbols
730+ )
731+ target_mv , small_account_substituted_symbols = project_unbuyable_value_targets_to_cash (
732+ target_mv ,
733+ prices ,
734+ symbols = small_account_candidate_symbols ,
735+ quantity_step = order_quantity_step ,
736+ )
737+ for symbol in small_account_substituted_symbols :
738+ target_weights [symbol ] = 0.0
739+ execution_summary ["small_account_whole_share_substituted_symbols" ] = list (
740+ small_account_substituted_symbols
741+ )
687742 trade_logs = []
688743 target_hash = _build_target_hash (target_weights )
689744 execution_summary ["target_vs_current" ] = _build_target_diff_rows (target_weights , current_mv , equity )
0 commit comments