3131from application .strategy_run_persistence import (
3232 build_strategy_run_state ,
3333 claim_live_strategy_run ,
34+ has_effective_live_submission_claim ,
3435 is_duplicate_live_run ,
3536 persist_strategy_run_state ,
37+ read_live_strategy_run_claim ,
3638 read_latest_strategy_run_state ,
3739 resolve_strategy_run_period ,
3840)
4143from quant_platform_kit .common .execution_outcomes import (
4244 DEFAULT_EXECUTION_BLOCKING_SKIP_REASONS ,
4345 filter_execution_blocking_skips ,
44- is_terminal_funding_block ,
46+ is_funding_block ,
4547 resolve_strategy_run_stage ,
4648)
4749from quant_platform_kit .common .runtime_inputs import (
@@ -259,6 +261,8 @@ def send_and_capture(text: str) -> bool | None:
259261
260262
261263def _should_publish_cycle_notification (result : Mapping [str , Any ]) -> bool :
264+ if result .get ("notification_suppressed_by_policy" ):
265+ return False
262266 if result .get ("submitted_orders" ):
263267 return True
264268 if result .get ("error" ) or result .get ("ok" ) is False :
@@ -513,27 +517,22 @@ def log_message(message: str) -> None:
513517 masked_account = mask_account_id (account )
514518 existing_run = None
515519 if persist_strategy_runs and not settings .dry_run_only :
516- claim_acquired = claim_live_strategy_run (
520+ existing_run = read_latest_strategy_run_state (
517521 store = store ,
518522 account = masked_account ,
519523 strategy_profile = strategy_runtime .profile ,
520524 run_period = run_period ,
521- now = now ,
522525 )
523- existing_run = read_latest_strategy_run_state (
526+ existing_submission_claim = read_live_strategy_run_claim (
524527 store = store ,
525528 account = masked_account ,
526529 strategy_profile = strategy_runtime .profile ,
527530 run_period = run_period ,
528531 )
529- if not claim_acquired and existing_run is None :
530- existing_run = {
531- "stage" : "PENDING_SUBMISSION" ,
532- "as_of" : now .isoformat (),
533- "claim_only" : True ,
534- }
535- if not claim_acquired or is_duplicate_live_run (existing_run ):
536- duplicate_stage = str (existing_run .get ("stage" ) or "NO_ACTION" )
532+ claim_blocks_repeat = has_effective_live_submission_claim (existing_submission_claim )
533+ if claim_blocks_repeat or is_duplicate_live_run (existing_run ):
534+ existing_state = dict (existing_run or {})
535+ duplicate_stage = str (existing_state .get ("stage" ) or "PENDING_SUBMISSION" )
537536 duplicate_skipped_orders = [
538537 {
539538 "reason" : "duplicate_live_strategy_run" ,
@@ -559,17 +558,18 @@ def log_message(message: str) -> None:
559558 now = now ,
560559 )
561560 duplicate_state ["idempotency_skipped" ] = True
562- duplicate_state ["existing_strategy_run_stage" ] = existing_run .get ("stage" )
563- duplicate_state ["existing_strategy_run_as_of" ] = existing_run .get ("as_of" )
564- try :
565- strategy_run_persisted = persist_strategy_run_state (
566- store = store ,
567- state = duplicate_state ,
568- now = now ,
569- )
570- except Exception as exc :
571- strategy_run_persisted = False
572- strategy_run_persistence_error = f"{ type (exc ).__name__ } : { exc } "
561+ duplicate_state ["existing_strategy_run_stage" ] = existing_state .get ("stage" )
562+ duplicate_state ["existing_strategy_run_as_of" ] = existing_state .get ("as_of" )
563+ if not claim_blocks_repeat :
564+ try :
565+ strategy_run_persisted = persist_strategy_run_state (
566+ store = store ,
567+ state = duplicate_state ,
568+ now = now ,
569+ )
570+ except Exception as exc :
571+ strategy_run_persisted = False
572+ strategy_run_persistence_error = f"{ type (exc ).__name__ } : { exc } "
573573 result = {
574574 "ok" : True ,
575575 "api_kind" : "unofficial-reverse-engineered" ,
@@ -583,8 +583,9 @@ def log_message(message: str) -> None:
583583 "strategy_run_stage" : duplicate_stage ,
584584 "strategy_run_persisted" : strategy_run_persisted ,
585585 "idempotency_skipped" : True ,
586- "existing_strategy_run_stage" : existing_run .get ("stage" ),
587- "existing_strategy_run_as_of" : existing_run .get ("as_of" ),
586+ "submission_claim_blocks_repeat" : claim_blocks_repeat ,
587+ "existing_strategy_run_stage" : existing_state .get ("stage" ),
588+ "existing_strategy_run_as_of" : existing_state .get ("as_of" ),
588589 "submitted_orders" : [],
589590 "skipped_orders" : duplicate_skipped_orders ,
590591 "action_done" : False ,
@@ -636,6 +637,22 @@ def log_message(message: str) -> None:
636637 except Exception as exc :
637638 strategy_run_persisted = False
638639 strategy_run_persistence_error = f"{ type (exc ).__name__ } : { exc } "
640+
641+ submission_claim_acquired = False
642+
643+ def acquire_submission_claim () -> bool :
644+ nonlocal submission_claim_acquired
645+ if submission_claim_acquired :
646+ return True
647+ submission_claim_acquired = claim_live_strategy_run (
648+ store = store ,
649+ account = masked_account ,
650+ strategy_profile = strategy_runtime .profile ,
651+ run_period = run_period ,
652+ now = now ,
653+ )
654+ return submission_claim_acquired
655+
639656 execution_result = execute_value_target_plan (
640657 plan = plan ,
641658 market_data_port = market_data_port ,
@@ -649,24 +666,80 @@ def log_message(message: str) -> None:
649666 cash_only_execution = settings .cash_only_execution ,
650667 notional_buy_execution = notional_buy_execution_enabled (settings .strategy_profile ),
651668 fetch_order_status = lambda broker_order_id : client .get_order_status (account , broker_order_id ),
669+ before_live_submission = (
670+ acquire_submission_claim
671+ if persist_strategy_runs and not settings .dry_run_only
672+ else None
673+ ),
652674 )
675+ if execution_result .idempotency_blocked :
676+ existing_run = read_latest_strategy_run_state (
677+ store = store ,
678+ account = masked_account ,
679+ strategy_profile = strategy_runtime .profile ,
680+ run_period = run_period ,
681+ ) or {
682+ "stage" : "PENDING_SUBMISSION" ,
683+ "as_of" : now .isoformat (),
684+ "claim_only" : True ,
685+ }
686+ result = {
687+ "ok" : True ,
688+ "api_kind" : "unofficial-reverse-engineered" ,
689+ "account" : account ,
690+ "strategy_profile" : strategy_runtime .profile ,
691+ "strategy_display_name" : strategy_runtime .display_name ,
692+ "dry_run_only" : settings .dry_run_only ,
693+ "live_trading_enabled" : settings .live_trading_enabled ,
694+ "session_reused" : bool (getattr (client , "session_reused" , False )),
695+ "strategy_run_period" : run_period ,
696+ "strategy_run_stage" : str (existing_run .get ("stage" ) or "PENDING_SUBMISSION" ),
697+ "strategy_run_persisted" : strategy_run_persisted ,
698+ "idempotency_skipped" : True ,
699+ "existing_strategy_run_stage" : existing_run .get ("stage" ),
700+ "existing_strategy_run_as_of" : existing_run .get ("as_of" ),
701+ "submitted_orders" : [],
702+ "skipped_orders" : list (execution_result .skipped_orders ),
703+ "action_done" : False ,
704+ ** empty_strategy_plugin_alert_report_fields (),
705+ }
706+ if strategy_run_persistence_error :
707+ result ["strategy_run_persistence_error" ] = strategy_run_persistence_error
708+ return attach_strategy_plugin_result (
709+ result ,
710+ signals = strategy_plugin_signals ,
711+ error = strategy_plugin_error ,
712+ translator = translator ,
713+ )
653714 submitted_orders = list (execution_result .submitted_orders )
654715 skipped_orders = list (execution_result .skipped_orders )
655716 execution_notes = list (execution_result .execution_notes )
717+ decision_diagnostics = dict (getattr (evaluation .decision , "diagnostics" , {}) or {})
718+ strategy_funding_shortfall = (
719+ not submitted_orders
720+ and str (
721+ decision_diagnostics .get ("dca_skip_reason" )
722+ or decision_diagnostics .get ("skip_reason" )
723+ or ""
724+ ).strip ().lower ()
725+ == "insufficient_cash"
726+ )
727+ if strategy_funding_shortfall :
728+ skipped_orders .append ({"reason" : "insufficient_cash" })
656729 blocking_skips = filter_execution_blocking_skips (
657730 skipped_orders ,
658731 blocking_reasons = BROKER_EXECUTION_BLOCKING_SKIP_REASONS ,
659732 )
660733 execution_blocked = bool (blocking_skips )
661- funding_blocked = is_terminal_funding_block (blocking_skips )
662- terminal_funding_block = funding_blocked and not execution_result .action_done
734+ funding_blocked = is_funding_block (blocking_skips )
735+ funding_block = funding_blocked and not execution_result .action_done
663736 strategy_run_stage = (
664737 "PENDING_RECONCILIATION"
665738 if execution_result .pending_reconciliation
666739 else resolve_strategy_run_stage (
667740 dry_run_only = settings .dry_run_only ,
668741 execution_blocked = execution_blocked ,
669- terminal_funding_block = terminal_funding_block ,
742+ terminal_funding_block = funding_block ,
670743 action_done = execution_result .action_done ,
671744 )
672745 )
@@ -712,11 +785,14 @@ def log_message(message: str) -> None:
712785 }
713786 if execution_blocked :
714787 result ["execution_blocked" ] = True
715- result ["execution_block_retryable" ] = not terminal_funding_block
788+ result ["execution_block_retryable" ] = not submission_claim_acquired
716789 result ["execution_blocking_skips" ] = blocking_skips
717790 result ["error" ] = "Strategy execution blocked; see execution_blocking_skips."
718791 if funding_blocked :
719792 result ["funding_blocked" ] = True
793+ if str ((existing_run or {}).get ("stage" ) or "" ).upper () == "FUNDING_BLOCKED" :
794+ result ["notification_suppressed_by_policy" ] = True
795+ result ["notification_suppressed_reason" ] = "repeat_funding_blocked"
720796 if strategy_run_persistence_error :
721797 result ["strategy_run_persistence_error" ] = strategy_run_persistence_error
722798 if strategy_plugin_alert_result is not None :
@@ -744,9 +820,9 @@ def log_message(message: str) -> None:
744820 portfolio_snapshot = plan .get ("portfolio" , {}),
745821 evaluation_metadata = getattr (evaluation , "metadata" , None ),
746822 plan = plan ,
747- submitted_orders = list ( execution_result . submitted_orders ) ,
748- skipped_orders = list ( execution_result . skipped_orders ) ,
749- execution_notes = list ( execution_result . execution_notes ) ,
823+ submitted_orders = submitted_orders ,
824+ skipped_orders = skipped_orders ,
825+ execution_notes = execution_notes ,
750826 action_done = execution_result .action_done ,
751827 broker_submission_done = execution_result .broker_submission_done ,
752828 execution_status = result ["execution_status" ],
@@ -777,7 +853,7 @@ def log_message(message: str) -> None:
777853 elif send_cycle_notification :
778854 result ["notification_sent" ] = False
779855 result ["notification_suppressed" ] = True
780- result [ "notification_suppressed_reason" ] = "no_trade_or_error"
856+ result . setdefault ( "notification_suppressed_reason" , "no_trade_or_error" )
781857 else :
782858 result ["notification_sent" ] = False
783859 result ["notification_suppressed" ] = True
0 commit comments