@@ -139,6 +139,12 @@ class ExecutionCycleResult:
139139DEFAULT_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD = 1000.0
140140SMALL_ACCOUNT_SAFE_HAVEN_CASH_SUBSTITUTE_LIMIT_USD = 2000.0
141141MIN_NOTIONAL_BUY_USD = 1.0
142+ _ACCEPTED_ORDER_STATUSES = frozenset (
143+ {"accepted" , "filled" , "partiallyfilled" , "previewed" , "submitted" }
144+ )
145+ _BROKER_REJECTION_SKIP_REASONS = frozenset (
146+ {"broker_rejected" , "fractional_trading_disclosure_required" }
147+ )
142148SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_SYMBOLS = frozenset ({"TQQQ" , "SOXL" })
143149_SMALL_ACCOUNT_RETENTION_MIN_TARGET_SHARE_RATIO_DEFAULT = 0.85
144150SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_MIN_TARGET_SHARE_RATIO_BY_SYMBOL = {
@@ -561,6 +567,52 @@ def _submit_notional_buy_order(
561567 }
562568
563569
570+ def _order_submission_accepted (order : dict [str , Any ]) -> bool :
571+ status = "" .join (
572+ ch for ch in str (order .get ("status" ) or "" ).strip ().lower () if ch .isalnum ()
573+ )
574+ return status in _ACCEPTED_ORDER_STATUSES
575+
576+
577+ def _broker_rejection_reason (order : dict [str , Any ]) -> str :
578+ raw_payload = dict (order .get ("raw_payload" ) or {})
579+ for key , value in raw_payload .items ():
580+ normalized = "" .join (ch for ch in str (key ).lower () if ch .isalnum ())
581+ if normalized == "refcode" and str (value or "" ).strip () == "1219" :
582+ return "fractional_trading_disclosure_required"
583+ return "broker_rejected"
584+
585+
586+ def _record_order_result (
587+ order : dict [str , Any ],
588+ * ,
589+ submitted : list [dict [str , Any ]],
590+ skipped : list [dict [str , Any ]],
591+ ) -> bool :
592+ if _order_submission_accepted (order ):
593+ submitted .append (order )
594+ return True
595+ skipped .append ({** order , "reason" : _broker_rejection_reason (order )})
596+ return False
597+
598+
599+ def _orders_for_allocation_drift (
600+ submitted_orders : list [dict [str , Any ]],
601+ * ,
602+ prices : dict [str , float ],
603+ ) -> list [dict [str , Any ]]:
604+ projected_orders = []
605+ for order in submitted_orders :
606+ projected_order = dict (order )
607+ notional_usd = float (projected_order .get ("notional_usd" ) or 0.0 )
608+ symbol = str (projected_order .get ("symbol" ) or "" ).strip ().upper ()
609+ price = float (prices .get (symbol ) or 0.0 )
610+ if notional_usd > 0.0 and price > 0.0 :
611+ projected_order ["quantity" ] = notional_usd / price
612+ projected_orders .append (projected_order )
613+ return projected_orders
614+
615+
564616def execute_value_target_plan (
565617 * ,
566618 plan : dict [str , Any ],
@@ -680,19 +732,22 @@ def execute_value_target_plan(
680732 )
681733 continue
682734 sell_limit_price = price * float (limit_sell_discount )
683- submitted .append (
684- _submit_order (
685- execution_port ,
686- symbol = symbol ,
687- side = "sell" ,
688- quantity = quantity ,
689- limit_price = sell_limit_price ,
690- max_notional_usd = max_order_notional_usd ,
691- )
735+ order_result = _submit_order (
736+ execution_port ,
737+ symbol = symbol ,
738+ side = "sell" ,
739+ quantity = quantity ,
740+ limit_price = sell_limit_price ,
741+ max_notional_usd = max_order_notional_usd ,
692742 )
693- submitted_sell_orders .append (submitted [- 1 ])
694743 pending_sell_release_symbols .append (symbol )
695- sell_submitted = True
744+ if _record_order_result (
745+ order_result ,
746+ submitted = submitted ,
747+ skipped = skipped ,
748+ ):
749+ submitted_sell_orders .append (order_result )
750+ sell_submitted = True
696751 continue
697752
698753 confirmed_sell_release_value = compute_confirmed_sell_release_value (
@@ -775,15 +830,18 @@ def execute_value_target_plan(
775830 }
776831 )
777832 continue
778- submitted .append (
779- _submit_notional_buy_order (
780- execution_port ,
781- symbol = symbol ,
782- notional_usd = buy_budget ,
783- max_notional_usd = max_order_notional_usd ,
784- )
833+ order_result = _submit_notional_buy_order (
834+ execution_port ,
835+ symbol = symbol ,
836+ notional_usd = buy_budget ,
837+ max_notional_usd = max_order_notional_usd ,
785838 )
786- investable_cash = max (0.0 , investable_cash - buy_budget )
839+ if _record_order_result (
840+ order_result ,
841+ submitted = submitted ,
842+ skipped = skipped ,
843+ ):
844+ investable_cash = max (0.0 , investable_cash - buy_budget )
787845 continue
788846 limit_price = _limit_buy_price (symbol , price , limit_buy_premium , limit_buy_premium_by_symbol )
789847 quantity = _planned_buy_order_quantity (
@@ -820,27 +878,41 @@ def execute_value_target_plan(
820878 }
821879 )
822880 continue
823- submitted .append (
824- _submit_order (
825- execution_port ,
826- symbol = symbol ,
827- side = "buy" ,
828- quantity = quantity ,
829- limit_price = limit_price ,
830- max_notional_usd = max_order_notional_usd ,
831- )
881+ order_result = _submit_order (
882+ execution_port ,
883+ symbol = symbol ,
884+ side = "buy" ,
885+ quantity = quantity ,
886+ limit_price = limit_price ,
887+ max_notional_usd = max_order_notional_usd ,
832888 )
833- investable_cash = max (0.0 , investable_cash - (quantity * limit_price ))
889+ if _record_order_result (
890+ order_result ,
891+ submitted = submitted ,
892+ skipped = skipped ,
893+ ):
894+ investable_cash = max (0.0 , investable_cash - (quantity * limit_price ))
834895
835896 total_value = float (portfolio .get ("total_equity" ) or portfolio .get ("total_strategy_equity" ) or 0.0 )
836- drift_notes = build_small_account_allocation_drift_notes (
837- target_values = small_account_reference_target_values ,
838- current_values = market_values ,
839- current_quantities = current_quantities ,
840- prices = reference_prices ,
841- submitted_orders = submitted ,
842- total_value = total_value ,
843- cash_value = float (portfolio .get ("liquid_cash" ) or 0.0 ),
897+ has_broker_rejection = any (
898+ str (item .get ("reason" ) or "" ) in _BROKER_REJECTION_SKIP_REASONS
899+ for item in skipped
900+ )
901+ drift_notes = (
902+ ()
903+ if has_broker_rejection
904+ else build_small_account_allocation_drift_notes (
905+ target_values = small_account_reference_target_values ,
906+ current_values = market_values ,
907+ current_quantities = current_quantities ,
908+ prices = reference_prices ,
909+ submitted_orders = _orders_for_allocation_drift (
910+ submitted ,
911+ prices = reference_prices ,
912+ ),
913+ total_value = total_value ,
914+ cash_value = float (portfolio .get ("liquid_cash" ) or 0.0 ),
915+ )
844916 )
845917 execution_notes = tuple (execution_notes ) + tuple (drift_notes )
846918
0 commit comments