1010try :
1111 from quant_platform_kit .common .small_account_compatibility import (
1212 apply_small_account_cash_compatibility ,
13+ build_small_account_allocation_drift_notes ,
1314 )
1415except ImportError : # pragma: no cover - compatibility with older pinned shared wheels
1516 @dataclass (frozen = True )
@@ -123,6 +124,9 @@ def apply_small_account_cash_compatibility(
123124 cash_substitution_notes = tuple (notes ),
124125 )
125126
127+ def build_small_account_allocation_drift_notes (** _kwargs ):
128+ return ()
129+
126130@dataclass (frozen = True )
127131class ExecutionCycleResult :
128132 submitted_orders : tuple [dict [str , Any ], ...]
@@ -204,6 +208,30 @@ def _safe_haven_cash_symbols(*, portfolio: dict[str, Any], allocation: dict[str,
204208 return tuple (dict .fromkeys (symbols ))
205209
206210
211+ def _small_account_drift_reference_targets (
212+ allocation : dict [str , Any ],
213+ * ,
214+ portfolio : dict [str , Any ] | None = None ,
215+ ) -> dict [str , float ]:
216+ allocation = dict (allocation or {})
217+ targets = {
218+ str (symbol or "" ).strip ().upper (): float (value or 0.0 )
219+ for symbol , value in dict (allocation .get ("targets" ) or {}).items ()
220+ }
221+ candidate_symbols = tuple (
222+ dict .fromkeys (
223+ str (symbol or "" ).strip ().upper ()
224+ for symbol in tuple (allocation .get ("risk_symbols" , ()))
225+ + tuple (allocation .get ("income_symbols" , ()))
226+ if str (symbol or "" ).strip ()
227+ )
228+ )
229+ if not candidate_symbols :
230+ safe_haven_symbols = set (_safe_haven_cash_symbols (portfolio = dict (portfolio or {}), allocation = allocation ))
231+ candidate_symbols = tuple (symbol for symbol in targets if symbol not in safe_haven_symbols )
232+ return {symbol : targets .get (symbol , 0.0 ) for symbol in candidate_symbols if symbol in targets }
233+
234+
207235def _positive_target_total (targets : dict [str , Any ]) -> float :
208236 total = 0.0
209237 for value in dict (targets or {}).values ():
@@ -432,6 +460,11 @@ def execute_value_target_plan(
432460 plan ,
433461 threshold_usd = safe_haven_cash_substitute_threshold_usd ,
434462 )
463+ plan_portfolio = dict (plan .get ("portfolio" ) or {})
464+ small_account_reference_target_values = _small_account_drift_reference_targets (
465+ dict (plan .get ("allocation" ) or {}),
466+ portfolio = plan_portfolio ,
467+ )
435468 plan = _apply_small_account_whole_share_compatibility (
436469 plan ,
437470 market_data_port = market_data_port ,
@@ -451,6 +484,10 @@ def execute_value_target_plan(
451484 str (k ).upper (): float (v or 0.0 )
452485 for k , v in dict (portfolio .get ("sellable_quantities" ) or {}).items ()
453486 }
487+ current_quantities = {
488+ str (k ).upper (): float (v or 0.0 )
489+ for k , v in dict (portfolio .get ("quantities" ) or sellable_quantities ).items ()
490+ }
454491 threshold = float (
455492 execution .get ("current_min_trade" )
456493 or execution .get ("trade_threshold_value" )
@@ -468,6 +505,7 @@ def execute_value_target_plan(
468505
469506 submitted : list [dict [str , Any ]] = []
470507 skipped : list [dict [str , Any ]] = []
508+ reference_prices : dict [str , float ] = {}
471509
472510 tradable_deltas : list [tuple [str , float , float ]] = []
473511 for symbol in sorted (set (targets ) | set (market_values )):
@@ -487,6 +525,7 @@ def execute_value_target_plan(
487525 if price is None :
488526 skipped .append ({"symbol" : symbol , "reason" : "quote_unavailable" })
489527 continue
528+ reference_prices [symbol ] = price
490529 tradable_deltas .append ((symbol , delta_value , price ))
491530
492531 for symbol , delta_value , price in [item for item in tradable_deltas if item [1 ] < 0 ]:
@@ -567,6 +606,18 @@ def execute_value_target_plan(
567606 )
568607 investable_cash = max (0.0 , investable_cash - (quantity * limit_price ))
569608
609+ total_value = float (portfolio .get ("total_equity" ) or portfolio .get ("total_strategy_equity" ) or 0.0 )
610+ drift_notes = build_small_account_allocation_drift_notes (
611+ target_values = small_account_reference_target_values ,
612+ current_values = market_values ,
613+ current_quantities = current_quantities ,
614+ prices = reference_prices ,
615+ submitted_orders = submitted ,
616+ total_value = total_value ,
617+ cash_value = float (portfolio .get ("liquid_cash" ) or 0.0 ),
618+ )
619+ execution_notes = tuple (execution_notes ) + tuple (drift_notes )
620+
570621 return ExecutionCycleResult (
571622 submitted_orders = tuple (submitted ),
572623 skipped_orders = tuple (skipped ),
0 commit comments