@@ -137,6 +137,7 @@ class ExecutionCycleResult:
137137
138138DEFAULT_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD = 1000.0
139139SMALL_ACCOUNT_SAFE_HAVEN_CASH_SUBSTITUTE_LIMIT_USD = 2000.0
140+ MIN_NOTIONAL_BUY_USD = 1.0
140141SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_SYMBOLS = frozenset ({"TQQQ" , "SOXL" })
141142SMALL_ACCOUNT_EXISTING_WHOLE_SHARE_RETENTION_MIN_TARGET_SHARE_RATIO_BY_SYMBOL = {
142143 "SOXX" : 0.90 ,
@@ -443,6 +444,38 @@ def _submit_order(
443444 }
444445
445446
447+ def _submit_notional_buy_order (
448+ execution_port : ExecutionPort ,
449+ * ,
450+ symbol : str ,
451+ notional_usd : float ,
452+ max_notional_usd : float | None ,
453+ ) -> dict [str , Any ]:
454+ metadata = {"notional_usd" : round (float (notional_usd ), 2 )}
455+ if max_notional_usd is not None :
456+ metadata ["max_notional_usd" ] = float (max_notional_usd )
457+ report = execution_port .submit_order (
458+ OrderIntent (
459+ symbol = symbol ,
460+ side = "buy" ,
461+ quantity = 0.0 ,
462+ order_type = "market" ,
463+ time_in_force = "day" ,
464+ metadata = metadata ,
465+ )
466+ )
467+ return {
468+ "symbol" : report .symbol ,
469+ "side" : report .side ,
470+ "quantity" : report .quantity ,
471+ "order_type" : "market" ,
472+ "notional_usd" : round (float (notional_usd ), 2 ),
473+ "status" : report .status ,
474+ "broker_order_id" : report .broker_order_id ,
475+ "raw_payload" : report .raw_payload ,
476+ }
477+
478+
446479def execute_value_target_plan (
447480 * ,
448481 plan : dict [str , Any ],
@@ -455,6 +488,7 @@ def execute_value_target_plan(
455488 max_order_notional_usd : float | None = None ,
456489 safe_haven_cash_substitute_threshold_usd : float = DEFAULT_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD ,
457490 cash_only_execution : bool = True ,
491+ notional_buy_execution : bool = False ,
458492) -> ExecutionCycleResult :
459493 del dry_run_only # ExecutionPort owns preview vs live submission.
460494 plan = substitute_small_safe_haven_targets_with_cash (
@@ -466,12 +500,13 @@ def execute_value_target_plan(
466500 dict (plan .get ("allocation" ) or {}),
467501 portfolio = plan_portfolio ,
468502 )
469- plan = _apply_small_account_whole_share_compatibility (
470- plan ,
471- market_data_port = market_data_port ,
472- limit_buy_premium = limit_buy_premium ,
473- limit_buy_premium_by_symbol = limit_buy_premium_by_symbol ,
474- )
503+ if not notional_buy_execution :
504+ plan = _apply_small_account_whole_share_compatibility (
505+ plan ,
506+ market_data_port = market_data_port ,
507+ limit_buy_premium = limit_buy_premium ,
508+ limit_buy_premium_by_symbol = limit_buy_premium_by_symbol ,
509+ )
475510 allocation = dict (plan .get ("allocation" ) or {})
476511 portfolio = dict (plan .get ("portfolio" ) or {})
477512 execution = dict (plan .get ("execution" ) or {})
@@ -578,12 +613,16 @@ def execute_value_target_plan(
578613 buy_budget = min (float (delta_value ), investable_cash )
579614 if order_notional_cap is not None :
580615 buy_budget = min (buy_budget , order_notional_cap )
581- limit_price = _limit_buy_price (
582- symbol , price , limit_buy_premium , limit_buy_premium_by_symbol
583- )
584- quantity = _floor_quantity (buy_budget / limit_price ) if limit_price > 0 else 0
585- if quantity > 0 :
586- estimated_buy_cost += quantity * limit_price
616+ if notional_buy_execution :
617+ if buy_budget >= MIN_NOTIONAL_BUY_USD :
618+ estimated_buy_cost += buy_budget
619+ else :
620+ limit_price = _limit_buy_price (
621+ symbol , price , limit_buy_premium , limit_buy_premium_by_symbol
622+ )
623+ quantity = _floor_quantity (buy_budget / limit_price ) if limit_price > 0 else 0
624+ if quantity > 0 :
625+ estimated_buy_cost += quantity * limit_price
587626 if estimated_buy_cost > investable_cash :
588627 _buys_blocked_reason = "pending_sell_release"
589628 for symbol , _delta_value , _price in buy_deltas :
@@ -611,6 +650,27 @@ def execute_value_target_plan(
611650 buy_budget = min (float (delta_value ), investable_cash )
612651 if order_notional_cap is not None :
613652 buy_budget = min (buy_budget , order_notional_cap )
653+ if notional_buy_execution :
654+ if buy_budget < MIN_NOTIONAL_BUY_USD :
655+ skipped .append (
656+ {
657+ "symbol" : symbol ,
658+ "reason" : "buy_notional_below_minimum" ,
659+ "notional_usd" : round (buy_budget , 2 ),
660+ "min_notional_usd" : MIN_NOTIONAL_BUY_USD ,
661+ }
662+ )
663+ continue
664+ submitted .append (
665+ _submit_notional_buy_order (
666+ execution_port ,
667+ symbol = symbol ,
668+ notional_usd = buy_budget ,
669+ max_notional_usd = max_order_notional_usd ,
670+ )
671+ )
672+ investable_cash = max (0.0 , investable_cash - buy_budget )
673+ continue
614674 limit_price = _limit_buy_price (symbol , price , limit_buy_premium , limit_buy_premium_by_symbol )
615675 quantity = _floor_quantity (buy_budget / limit_price ) if limit_price > 0 else 0
616676 if quantity <= 0 :
0 commit comments