@@ -125,6 +125,7 @@ class ExecutionCycleResult:
125125
126126
127127DEFAULT_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD = 1000.0
128+ SMALL_ACCOUNT_SAFE_HAVEN_CASH_SUBSTITUTE_LIMIT_USD = 2000.0
128129
129130
130131def _noop_sleep (_seconds ):
@@ -143,6 +144,16 @@ def _safe_haven_cash_symbols(*, portfolio: dict, allocation: dict) -> tuple[str,
143144 return tuple (dict .fromkeys (symbols ))
144145
145146
147+ def _positive_target_total (targets : dict ) -> float :
148+ total = 0.0
149+ for value in dict (targets or {}).values ():
150+ try :
151+ total += max (0.0 , float (value or 0.0 ))
152+ except (TypeError , ValueError ):
153+ continue
154+ return total
155+
156+
146157def _apply_safe_haven_cash_substitution (
147158 * ,
148159 plan ,
@@ -266,14 +277,20 @@ def _apply_small_account_whole_share_compatibility(
266277 target_values = dict (allocation .get ("targets" ) or {})
267278 candidate_symbols = tuple (
268279 dict .fromkeys (
269- tuple (allocation .get ("risk_symbols" , ()))
280+ str (symbol or "" ).strip ().upper ()
281+ for symbol in tuple (allocation .get ("risk_symbols" , ()))
270282 + tuple (allocation .get ("income_symbols" , ()))
283+ if str (symbol or "" ).strip ()
271284 )
272285 )
273286 if not candidate_symbols :
274- safe_haven_symbols = set (allocation .get ("safe_haven_symbols" , ()))
287+ safe_haven_symbols = set (
288+ _safe_haven_cash_symbols (portfolio = dict ((plan or {}).get ("portfolio" ) or {}), allocation = allocation )
289+ )
275290 candidate_symbols = tuple (
276- symbol for symbol in target_values if symbol not in safe_haven_symbols
291+ str (symbol or "" ).strip ().upper ()
292+ for symbol in target_values
293+ if str (symbol or "" ).strip ().upper () not in safe_haven_symbols
277294 )
278295 quote_prices = {}
279296 for symbol in candidate_symbols :
@@ -289,11 +306,32 @@ def _apply_small_account_whole_share_compatibility(
289306 symbols = candidate_symbols ,
290307 quantity_step = 1.0 ,
291308 )
309+ safe_haven_symbols = _safe_haven_cash_symbols (
310+ portfolio = dict ((plan or {}).get ("portfolio" ) or {}),
311+ allocation = allocation ,
312+ )
313+ remaining_non_safe_targets = [
314+ symbol
315+ for symbol in candidate_symbols
316+ if float (adjusted_targets .get (str (symbol or "" ).strip ().upper (), 0.0 ) or 0.0 ) > 0.0
317+ ]
318+ safe_haven_substituted : list [str ] = []
319+ if (
320+ substituted
321+ and not remaining_non_safe_targets
322+ and _positive_target_total (adjusted_targets ) <= SMALL_ACCOUNT_SAFE_HAVEN_CASH_SUBSTITUTE_LIMIT_USD
323+ ):
324+ for symbol in safe_haven_symbols :
325+ if float (adjusted_targets .get (symbol , 0.0 ) or 0.0 ) > 0.0 :
326+ adjusted_targets [symbol ] = 0.0
327+ safe_haven_substituted .append (symbol )
292328 adjusted_allocation = {** dict (allocation or {}), "targets" : adjusted_targets }
293329 if substituted :
294330 adjusted_allocation ["small_account_whole_share_substituted_symbols" ] = substituted
331+ if safe_haven_substituted :
332+ adjusted_allocation ["small_account_safe_haven_cash_substituted_symbols" ] = tuple (safe_haven_substituted )
295333 adjusted_plan = dict (plan or {})
296- if substituted :
334+ if substituted or safe_haven_substituted :
297335 adjusted_plan ["allocation" ] = adjusted_allocation
298336 return adjusted_plan , adjusted_allocation
299337
@@ -830,10 +868,17 @@ def record_dry_run(symbol, side, quantity, price, *, order_type):
830868 ** note_kwargs ,
831869 )
832870
871+ cash_sweep_substituted_to_cash = bool (
872+ allocation .get ("small_account_safe_haven_cash_substituted_symbols" )
873+ )
833874 if (
834875 not cash_sweep_sold_this_cycle
835876 and cash_sweep_symbol
836877 and cash_sweep_symbol in strategy_assets
878+ and (
879+ float (target_values .get (cash_sweep_symbol , 0.0 ) or 0.0 ) > 0.0
880+ or not cash_sweep_substituted_to_cash
881+ )
837882 ):
838883 cash_sweep_price = safe_quote_last_price (
839884 f"{ cash_sweep_symbol } .US" ,
0 commit comments