-
Notifications
You must be signed in to change notification settings - Fork 0
Enable paper monthly execution dedup #140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -216,6 +216,7 @@ def build_rebalance_config(self, *, strategy_plugin_signals=()) -> LongBridgeReb | |
| execution_dedup_enabled=resolve_execution_dedup_enabled( | ||
| env_reader=self.env_reader, | ||
| dry_run_only=self.dry_run_only, | ||
| account_scope=self.account_region, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a deployment is described by Useful? React with 👍 / 👎. |
||
| ), | ||
| execution_state_store=build_execution_marker_store_from_env( | ||
| env_reader=self.env_reader, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| ROOT = Path(__file__).resolve().parents[1] | ||
| if str(ROOT) not in sys.path: | ||
| sys.path.insert(0, str(ROOT)) | ||
|
|
||
| from application.execution_state import ( # noqa: E402 | ||
| ExecutionMarkerStore, | ||
| _report_matches_execution, | ||
| resolve_execution_dedup_enabled, | ||
| ) | ||
|
|
||
|
|
||
| def _env_with_dedup(raw_value): | ||
| def reader(name, default=None): | ||
| if name == "LONGBRIDGE_EXECUTION_DEDUP_ENABLED": | ||
| return raw_value | ||
| return default | ||
|
|
||
| return reader | ||
|
|
||
|
|
||
| def test_execution_dedup_defaults_to_enabled_for_paper_account_scope(): | ||
| assert ( | ||
| resolve_execution_dedup_enabled( | ||
| env_reader=_env_with_dedup(None), | ||
| dry_run_only=False, | ||
| account_scope="PAPER", | ||
| ) | ||
| is True | ||
| ) | ||
|
|
||
|
|
||
| def test_execution_dedup_defaults_to_disabled_for_live_non_paper_scope(): | ||
| assert ( | ||
| resolve_execution_dedup_enabled( | ||
| env_reader=_env_with_dedup(None), | ||
| dry_run_only=False, | ||
| account_scope="HK", | ||
| ) | ||
| is False | ||
| ) | ||
|
|
||
|
|
||
| def test_execution_dedup_env_override_wins_for_paper_scope(): | ||
| assert ( | ||
| resolve_execution_dedup_enabled( | ||
| env_reader=_env_with_dedup("false"), | ||
| dry_run_only=False, | ||
| account_scope="PAPER", | ||
| ) | ||
| is False | ||
| ) | ||
|
|
||
|
|
||
| def test_prior_report_match_treats_successful_no_action_as_completed(): | ||
| payload = { | ||
| "platform": "longbridge", | ||
| "strategy_profile": "mega_cap_leader_rotation_top50_balanced", | ||
| "account_scope": "PAPER", | ||
| "dry_run": False, | ||
| "status": "ok", | ||
| "summary": { | ||
| "signal_date": "2026-06-04", | ||
| "action_done": False, | ||
| "orders_previewed_count": 0, | ||
| "order_events_count": 0, | ||
| "orders_skipped_count": 0, | ||
| }, | ||
| "diagnostics": { | ||
| "signal_snapshot": { | ||
| "signal_as_of": "2026-06-01", | ||
| "market_date": "2026-06-01", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| assert ( | ||
| _report_matches_execution( | ||
| payload, | ||
| platform="longbridge", | ||
| strategy_profile="mega_cap_leader_rotation_top50_balanced", | ||
| account_scope="PAPER", | ||
| signal_date="2026-06-01", | ||
| effective_date="", | ||
| dry_run_only=False, | ||
| ) | ||
| is True | ||
| ) | ||
|
|
||
|
|
||
| def test_prior_report_match_does_not_treat_blocked_no_action_as_completed(): | ||
| payload = { | ||
| "platform": "longbridge", | ||
| "strategy_profile": "mega_cap_leader_rotation_top50_balanced", | ||
| "account_scope": "PAPER", | ||
| "dry_run": False, | ||
| "status": "ok", | ||
| "summary": { | ||
| "signal_date": "2026-06-04", | ||
| "action_done": False, | ||
| "orders_skipped_count": 1, | ||
| }, | ||
| } | ||
|
|
||
| assert ( | ||
| _report_matches_execution( | ||
| payload, | ||
| platform="longbridge", | ||
| strategy_profile="mega_cap_leader_rotation_top50_balanced", | ||
| account_scope="PAPER", | ||
| signal_date="2026-06-04", | ||
| effective_date="", | ||
| dry_run_only=False, | ||
| ) | ||
| is False | ||
| ) | ||
|
|
||
|
|
||
| def test_prior_report_scan_is_scoped_to_signal_month(): | ||
| observed = {} | ||
|
|
||
| class FakeClient: | ||
| def list_blobs(self, bucket_name, *, prefix): | ||
| observed["bucket_name"] = bucket_name | ||
| observed["prefix"] = prefix | ||
| return () | ||
|
|
||
| store = ExecutionMarkerStore( | ||
| local_dir=None, | ||
| gcs_prefix_uri="gs://bucket/execution-reports", | ||
| client_factory=lambda **_kwargs: FakeClient(), | ||
| ) | ||
|
|
||
| assert ( | ||
| store.has_prior_execution_report( | ||
| platform="longbridge", | ||
| strategy_profile="mega_cap_leader_rotation_top50_balanced", | ||
| account_scope="PAPER", | ||
| signal_date="2026-06-04", | ||
| effective_date="", | ||
| dry_run_only=False, | ||
| ) | ||
| is False | ||
| ) | ||
| assert observed == { | ||
| "bucket_name": "bucket", | ||
| "prefix": ( | ||
| "execution-reports/longbridge/" | ||
| "mega_cap_leader_rotation_top50_balanced/PAPER/2026-06" | ||
| ), | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This narrows prior-report fallback to the signal month, but runtime reports are persisted under the run
started_atmonth, not necessarily the signal snapshot month. For monthly snapshots whose signal date is from the previous month but the execution/report is produced during the next month's execution window, this searches the previous-month prefix and misses the successful current-month report, allowing a duplicate execution attempt despite the fallback.Useful? React with 👍 / 👎.