diff --git a/AGENTS.md b/AGENTS.md index 13bce864..fe3774da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -43,8 +43,11 @@ Organization documentation rules are checked by `python scripts/check_style.py`, Prefer focused import paths over the `quant_platform_kit.common` facade in new code: `quant_platform_kit.execution`, `quant_platform_kit.reconciliation`, or -`quant_platform_kit.common.`. Guard with -`PYTHONPATH=src python scripts/check_common_facade_imports.py`. +`quant_platform_kit.common.`. Do not add bulk re-exports to +`common/__init__.py`. Platform thin-main layout lives in +`execution_kernel/platform_http.py`. Guard with +`PYTHONPATH=src python scripts/check_common_facade_imports.py` (and +`tests/test_common_facade_imports.py`). ## Naming and documentation diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index d584a10e..5bbf7744 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -99,7 +99,7 @@ QuantStrategyLab is a multi-broker, multi-market quantitative trading system. It | Notifications | `notifications/` | Multi-channel notification pipeline: Email, SMS, Push, Telegram, Webhook. Shared envelope (`RenderedNotification`, `NotificationPublisher`) for consistent delivery | | Strategy Lifecycle | `strategy_lifecycle/` | Backtest orchestration, drift detection, performance monitoring, AI review, rollback management, health scoring, return collection, audit logging | | Runtime Contracts | `common/` | Strategy contracts, runtime assembly, runtime reports, plugin artifact parsing (legacy wide re-exports; prefer subpackages below) | -| Execution contracts | `execution/`, `execution_kernel/` | Commands, receipts, outcomes, paper admission, runtime command gate (`execution/`); pure no-I/O submit guards (`execution_kernel/`) | +| Execution contracts | `execution/`, `execution_kernel/` | Commands, receipts, fills, guarded ports, paper admission, runtime command gate (`execution/`); pure no-I/O submit guards (`execution_kernel/`); platform thin-main + explicit re-export template in `execution_kernel/platform_http.py` | | Reconciliation | `reconciliation/` | Broker reconciliation evidence, enrollment, recovery workflows, live continuity | | Strategy Plugins | `common/strategy_plugins.py` | Plugin signal parsing, compatibility checks, alert generation | | Portfolio Ports | `common/ports.py` | Port interfaces for market data, portfolio snapshots, execution, notifications | diff --git a/scripts/check_common_facade_imports.py b/scripts/check_common_facade_imports.py index e57362df..5a95457c 100755 --- a/scripts/check_common_facade_imports.py +++ b/scripts/check_common_facade_imports.py @@ -5,7 +5,9 @@ 1. common/__init__.py stays within a line budget (blocks unbounded facade growth). 2. Topic subpackages ``execution`` and ``reconciliation`` import cleanly and expose every name listed in their ``__all__``. - 3. Optionally (--strict) fail on wide ``from quant_platform_kit.common import`` + 3. ``execution_kernel.platform_http`` documents the Wave2 thin-main template + (module map + explicit re-export rules). + 4. Optionally (--strict) fail on wide ``from quant_platform_kit.common import`` in src/ outside ``common/`` itself (legacy code may still use the facade). Usage: @@ -21,13 +23,37 @@ import sys from pathlib import Path -# Baseline recorded during refactor/untangle-p0-p2; shrink over time as the facade slims. -COMMON_INIT_LINE_BUDGET = 650 +# Baseline after #782; keep closed so new APIs land in subpackages, not here. +COMMON_INIT_LINE_BUDGET = 620 FORBIDDEN_IMPORT_PREFIX = "from quant_platform_kit.common import" SKIP_DIRS = {".git", "__pycache__", ".venv", "venv", "build", "dist"} -SUBPACKAGES = ("quant_platform_kit.execution", "quant_platform_kit.reconciliation") +SUBPACKAGES = ( + "quant_platform_kit.execution", + "quant_platform_kit.reconciliation", + "quant_platform_kit.execution_kernel", +) + +PLATFORM_HTTP_MODULE_KEYS = frozenset( + { + "main", + "http_routes", + "runtime_wiring", + "notifications_runtime", + "strategy_plugins_runtime", + "platform_entry", + } +) +PLATFORM_THIN_MAIN_RULE_KEYS = frozenset( + { + "explicit_reexports", + "all_list", + "no_star_api", + "entry_bind", + "behavior", + } +) def repo_root_from_script() -> Path: @@ -64,6 +90,34 @@ def check_subpackage_exports(package_name: str) -> list[str]: return errors +def check_platform_http_template() -> list[str]: + errors: list[str] = [] + try: + from quant_platform_kit.execution_kernel.platform_http import ( + describe_platform_http_modules, + describe_platform_thin_main_rules, + ) + except Exception as exc: # noqa: BLE001 + return [f"failed to import platform_http template: {exc}"] + + modules = describe_platform_http_modules() + missing_modules = PLATFORM_HTTP_MODULE_KEYS - frozenset(modules) + if missing_modules: + errors.append( + "platform_http.describe_platform_http_modules missing keys: " + + ", ".join(sorted(missing_modules)) + ) + + rules = describe_platform_thin_main_rules() + missing_rules = PLATFORM_THIN_MAIN_RULE_KEYS - frozenset(rules) + if missing_rules: + errors.append( + "platform_http.describe_platform_thin_main_rules missing keys: " + + ", ".join(sorted(missing_rules)) + ) + return errors + + def iter_python_files(root: Path) -> list[Path]: src = root / "src" files: list[Path] = [] @@ -121,6 +175,7 @@ def main() -> int: errors.extend(check_common_line_budget(root)) for package in SUBPACKAGES: errors.extend(check_subpackage_exports(package)) + errors.extend(check_platform_http_template()) wide_import_hits = check_forbidden_wide_imports(root) if wide_import_hits: diff --git a/src/quant_platform_kit/common/__init__.py b/src/quant_platform_kit/common/__init__.py index 203ad2e7..74f314f8 100644 --- a/src/quant_platform_kit/common/__init__.py +++ b/src/quant_platform_kit/common/__init__.py @@ -11,7 +11,8 @@ Prefer ``quant_platform_kit.common.`` or the subpackages above over ``from quant_platform_kit.common import ...`` in new in-repo code. Add new domain APIs via a dedicated subpackage re-export layer, not new bulk imports -here. +here. ``scripts/check_common_facade_imports.py`` enforces the line budget and +subpackage export integrity; use ``--strict`` when hunting wide facade imports. """ from .us_equity_sessions import ( diff --git a/src/quant_platform_kit/execution/__init__.py b/src/quant_platform_kit/execution/__init__.py index c2047528..dbcd586b 100644 --- a/src/quant_platform_kit/execution/__init__.py +++ b/src/quant_platform_kit/execution/__init__.py @@ -4,8 +4,15 @@ Import from this package in new code instead of the ``common`` facade. For pure submission guards (no I/O), see ``quant_platform_kit.execution_kernel``. +For platform HTTP thin-main layout, see +``quant_platform_kit.execution_kernel.platform_http``. """ +from quant_platform_kit.common.account_identity import ( + ACCOUNT_IDENTITY_RECEIPT_SCHEMA_VERSION, + AccountIdentityGuardedExecutionPort, + evaluate_account_identity, +) from quant_platform_kit.common.capital_base import ( CAPITAL_BASE_CONTRACT_VERSION, CapitalBaseBinding, @@ -17,6 +24,20 @@ build_capital_base_snapshot, validate_capital_base, ) +from quant_platform_kit.common.execution_capabilities import ( + FRACTIONAL_SHARE_EXECUTION_CAPABILITY, + FRACTIONAL_SHARE_EXECUTION_SKIP_REASON, + NOTIONAL_TO_WHOLE_SHARE_COMPAT_SKIP_REASON, + definition_requires_fractional_share_execution, + fractional_share_execution_unsupported_reason, + notional_buy_compat_mode_enabled, + platform_supports_fractional_share_execution, +) +from quant_platform_kit.common.execution_fills import ( + EXECUTION_FILL_SCHEMA_VERSION, + ExecutionFillAccumulation, + accumulate_execution_fills, +) from quant_platform_kit.common.execution_commands import ( DEFAULT_EXECUTION_COMMAND_NAMESPACE, EXECUTION_COMMAND_EVENT_SCHEMA_VERSION, @@ -77,6 +98,12 @@ LongOnlyReduceOnlyValidation, validate_long_only_reduce_only_order, ) +from quant_platform_kit.common.order_status import ( + available_sell_quantity, + compute_confirmed_sell_release_value, + is_confirmed_fill_status, + normalize_order_status_payload, +) from quant_platform_kit.common.paper_execution_admission import ( PAPER_RISK_ADMISSION_RECEIPT_INTENT_FIELD, PAPER_RISK_ADMISSION_RECEIPT_SCHEMA_VERSION, @@ -98,6 +125,9 @@ build_paper_execution_command_binding, consume_due_paper_execution_commands, ) +from quant_platform_kit.common.pending_order_evidence import ( + validate_pending_order_evidence, +) from quant_platform_kit.common.reduce_only_command_admission import ( REDUCE_ONLY_ORDER_DIGEST_FIELD, ReduceOnlyCommandAdmission, @@ -105,6 +135,10 @@ build_reduce_only_order_digest, evaluate_reduce_only_command_admission, ) +from quant_platform_kit.common.risk_guarded_execution_port import ( + RiskGuardedExecutionPort, + RiskGuardedExecutionRejected, +) from quant_platform_kit.common.runtime_command_gate import ( RUNTIME_COMMAND_GATE_RECEIPT_SCHEMA_VERSION, RuntimeCommandAction, @@ -117,8 +151,19 @@ evaluate_runtime_command_gate, normalize_runtime_command_integrity_findings, ) +from quant_platform_kit.common.runtime_target import ( + RuntimeExecutionEnvironment, + resolve_runtime_execution_environment, +) +from quant_platform_kit.common.strategy_release import ( + RUNTIME_LOADED_RECEIPT_SCHEMA_VERSION, + build_runtime_loaded_receipt, + validate_runtime_loaded_receipt, +) __all__ = [ + "ACCOUNT_IDENTITY_RECEIPT_SCHEMA_VERSION", + "AccountIdentityGuardedExecutionPort", "CAPITAL_BASE_CONTRACT_VERSION", "CapitalBaseBinding", "CapitalBaseFinding", @@ -136,6 +181,7 @@ "EXECUTION_COMMAND_EVENT_SCHEMA_VERSION", "EXECUTION_COMMAND_SCHEMA_VERSION", "EXECUTION_COMMAND_STRATEGY_RELEASE_FIELD", + "EXECUTION_FILL_SCHEMA_VERSION", "EXECUTION_LEDGER_DIGEST_SCHEMA_VERSION", "EXECUTION_RECEIPT_BROKER_CONFIRMATIONS", "EXECUTION_RECEIPT_OUTCOMES", @@ -144,9 +190,13 @@ "ExecutionCommandEvent", "ExecutionCommandState", "ExecutionCommandStore", + "ExecutionFillAccumulation", "ExecutionMarkerStore", + "FRACTIONAL_SHARE_EXECUTION_CAPABILITY", + "FRACTIONAL_SHARE_EXECUTION_SKIP_REASON", "LongOnlyReduceOnlyFinding", "LongOnlyReduceOnlyValidation", + "NOTIONAL_TO_WHOLE_SHARE_COMPAT_SKIP_REASON", "PAPER_EXECUTION_COMMAND_CONSUMER_SCHEMA_VERSION", "PAPER_RISK_ADMISSION_RECEIPT_INTENT_FIELD", "PAPER_RISK_ADMISSION_RECEIPT_SCHEMA_VERSION", @@ -160,8 +210,11 @@ "PaperRiskAdmissionReceipt", "REDUCE_ONLY_ORDER_DIGEST_FIELD", "RUNTIME_COMMAND_GATE_RECEIPT_SCHEMA_VERSION", + "RUNTIME_LOADED_RECEIPT_SCHEMA_VERSION", "ReduceOnlyCommandAdmission", "ReduceOnlyCommandFinding", + "RiskGuardedExecutionPort", + "RiskGuardedExecutionRejected", "RuntimeCommandAction", "RuntimeCommandExposureEffect", "RuntimeCommandGateDecision", @@ -169,6 +222,7 @@ "RuntimeCommandGateMode", "RuntimeCommandGatePolicy", "RuntimeCommandIntegrityFinding", + "RuntimeExecutionEnvironment", "STAGE_COMPLETED", "STAGE_DRY_RUN_COMPLETED", "STAGE_EXECUTION_BLOCKED", @@ -179,35 +233,49 @@ "STAGE_RECONCILED", "STAGE_SUBMITTED", "TERMINAL_EXECUTION_COMMAND_STATES", + "accumulate_execution_fills", "attach_execution_receipt", "attach_runtime_execution_receipt", + "available_sell_quantity", "build_capital_base_snapshot", "build_execution_command_store_from_env", "build_execution_receipt", "build_paper_execution_command_binding", "build_paper_risk_admission_receipt", "build_reduce_only_order_digest", + "build_runtime_loaded_receipt", "calculate_execution_receipt_sha256", "calculate_paper_risk_admission_receipt_sha256", "canonical_execution_receipt_json", "canonical_paper_risk_admission_receipt_json", + "compute_confirmed_sell_release_value", "consume_due_paper_execution_commands", + "definition_requires_fractional_share_execution", + "evaluate_account_identity", "evaluate_paper_execution_admission", "evaluate_reduce_only_command_admission", "evaluate_runtime_command_gate", "filter_execution_blocking_skips", + "fractional_share_execution_unsupported_reason", + "is_confirmed_fill_status", "is_funding_block", "is_retryable_strategy_run_stage", "is_terminal_funding_block", "is_terminal_strategy_run_stage", + "normalize_order_status_payload", "normalize_runtime_command_integrity_findings", "normalize_skip_reason", "normalize_stage", + "notional_buy_compat_mode_enabled", + "platform_supports_fractional_share_execution", "resolve_execution_receipt_fact", + "resolve_runtime_execution_environment", "resolve_strategy_run_stage", "validate_capital_base", "validate_execution_command_release_binding", "validate_execution_command_transition", "validate_execution_receipt", "validate_long_only_reduce_only_order", + "validate_pending_order_evidence", + "validate_runtime_loaded_receipt", ] diff --git a/src/quant_platform_kit/execution_kernel/__init__.py b/src/quant_platform_kit/execution_kernel/__init__.py index 5a558796..a1f4aade 100644 --- a/src/quant_platform_kit/execution_kernel/__init__.py +++ b/src/quant_platform_kit/execution_kernel/__init__.py @@ -10,7 +10,11 @@ deny_live_submit_without_durable_identity, deny_new_cycle_when_submission_unknown, ) -from .platform_http import PlatformHttpSurface, describe_platform_http_modules +from .platform_http import ( + PlatformHttpSurface, + describe_platform_http_modules, + describe_platform_thin_main_rules, +) from .types import ( ExecutionGuardDecision, ExecutionMode, @@ -32,4 +36,5 @@ "deny_live_submit_without_durable_identity", "deny_new_cycle_when_submission_unknown", "describe_platform_http_modules", + "describe_platform_thin_main_rules", ] diff --git a/src/quant_platform_kit/execution_kernel/platform_http.py b/src/quant_platform_kit/execution_kernel/platform_http.py index f751fb6c..7eb4d8c3 100644 --- a/src/quant_platform_kit/execution_kernel/platform_http.py +++ b/src/quant_platform_kit/execution_kernel/platform_http.py @@ -1,15 +1,49 @@ """Platform HTTP composition helpers (template for execution runtimes). -Downstream platform repositories (CharlesSchwabPlatform, BinancePlatform, …) -historically kept Flask route tables, runtime wiring and notification fallbacks -in a single ``main.py``. Prefer the local split pattern already applied in -CharlesSchwabPlatform: +Downstream platform repositories (CharlesSchwabPlatform, BinancePlatform, +InteractiveBrokersPlatform, LongBridgePlatform, …) historically kept Flask +route tables, runtime wiring and notification fallbacks in a single +``main.py``. Wave2 platform redos follow this thin-main + explicit re-export +layout (CharlesSchwabPlatform is the reference): -- ``http_routes.py`` — Flask app + HTTP handlers +Module split +------------ +- ``http_routes.py`` — Flask app + HTTP handlers (``/run``, ``/dry-run``, …) - ``runtime_wiring.py`` — composer / broker / strategy assembly - ``notifications_runtime.py`` — channel adapters and failure notifications - ``strategy_plugins_runtime.py`` — plugin signal presentation -- ``main.py`` — thin entry (``app.run`` / ASGI) +- ``platform_entry.py`` — late-bound entry lookup for monkeypatch-friendly tests +- ``main.py`` — thin process entry: named imports + ``__all__`` + ``app.run`` + +Thin ``main.py`` contract (explicit re-exports; no ``import *`` as the API) +--------------------------------------------------------------------------- +Prefer named imports that re-export the public surface, then list the same +names in ``__all__``: + +.. code-block:: python + + from runtime_wiring import build_composer, validate_config, ... + from notifications_runtime import publish_notification, ... + from strategy_plugins_runtime import load_strategy_plugin_signals, ... + from http_routes import app, handle_schwab, health, ... + + import platform_entry as _platform_entry + _platform_entry.bind(__import__("sys").modules[__name__]) + + __all__ = [ + "app", + "build_composer", + "handle_schwab", + "health", + # …every symbol tests or Gunicorn may patch/import from main + ] + + if __name__ == "__main__": + app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8080))) + +Do not use ``from http_routes import *`` (or similar) as the sole public API: +patch targets and review diffs must stay name-stable. Keep implementation in +the split modules; ``main`` only binds and re-exports. This module intentionally stays documentation-oriented until a coordinated QPK pin upgrades all platforms to share one Flask factory. Do not import it @@ -40,9 +74,24 @@ class PlatformHttpSurface: def describe_platform_http_modules() -> Mapping[str, str]: """Return the recommended module layout for a platform repository.""" return { - "main": "Thin process entry; bind host/port only.", + "main": ( + "Thin process entry: explicit named re-exports + __all__; " + "bind host/port only (no import * as the public API)." + ), "http_routes": "Flask/FastAPI app factory and route handlers.", "runtime_wiring": "Build composer, broker adapters, strategy adapters.", "notifications_runtime": "Notification publish and runtime-error fallback.", "strategy_plugins_runtime": "Plugin signal lines and alert fan-out.", + "platform_entry": "Late-bound entry module for monkeypatch-friendly tests.", + } + + +def describe_platform_thin_main_rules() -> Mapping[str, str]: + """Return the Wave2 thin-main re-export rules platforms should follow.""" + return { + "explicit_reexports": "Use named imports from split modules into main.", + "all_list": "Mirror every re-exported public name in main.__all__.", + "no_star_api": "Do not use import * as the sole public surface.", + "entry_bind": "Bind platform_entry to the process main module for patches.", + "behavior": "Preserve route and execute_cycle behavior while splitting.", } diff --git a/tests/test_common_facade_imports.py b/tests/test_common_facade_imports.py new file mode 100644 index 00000000..2b7ce6e1 --- /dev/null +++ b/tests/test_common_facade_imports.py @@ -0,0 +1,34 @@ +"""Facade import guard: prefer execution/reconciliation over wide common imports.""" + +from __future__ import annotations + +import runpy +import sys +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[1] +SCRIPT = REPO_ROOT / "scripts" / "check_common_facade_imports.py" + + +def test_check_common_facade_imports_script_passes(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.syspath_prepend(str(REPO_ROOT / "src")) + monkeypatch.setattr(sys, "argv", [str(SCRIPT)]) + with pytest.raises(SystemExit) as excinfo: + runpy.run_path(str(SCRIPT), run_name="__main__") + assert excinfo.value.code == 0 + + +def test_execution_and_reconciliation_packages_import() -> None: + from quant_platform_kit import execution, reconciliation + from quant_platform_kit.execution_kernel import ( + describe_platform_http_modules, + describe_platform_thin_main_rules, + ) + + assert "ExecutionCommand" in execution.__all__ + assert "RiskGuardedExecutionPort" in execution.__all__ + assert "BrokerReconciliationEvidence" in reconciliation.__all__ + assert "main" in describe_platform_http_modules() + assert "no_star_api" in describe_platform_thin_main_rules()