From 3a4aba21d891fc4d81617e84ecedac318dd1c8be Mon Sep 17 00:00:00 2001 From: bxvtr Date: Fri, 15 May 2026 13:44:30 +0000 Subject: [PATCH 1/5] docs: improve language and use consistent terminology --- .github/pull-request-template.md | 2 +- CHANGELOG.md | 4 +- CONTRIBUTING.md | 2 +- README.md | 182 +++++++++--------- SECURITY.md | 2 +- docs/code-map/core-pipeline-map.md | 8 +- docs/code-map/repository-map.md | 2 +- docs/flows/control-time-and-scheduling.md | 12 +- docs/how-to/update-core-step-pipeline.md | 2 +- docs/index.md | 2 +- docs/reference/events-reference.md | 2 +- examples/core_step_quickstart.py | 2 +- tests/semantics/test_core_pipeline_clean.py | 2 +- .../semantics/test_core_wakeup_final_state.py | 2 +- .../core/domain/candidate_intent.py | 2 +- .../core/domain/execution_control_apply.py | 2 +- .../core/domain/intent_combination.py | 2 +- .../core/domain/policy_risk_decision.py | 2 +- tradingchassis_core/core/domain/state.py | 2 +- tradingchassis_core/core/events/events.py | 2 +- .../execution_control/execution_control.py | 2 +- tradingchassis_core/core/risk/risk_engine.py | 2 +- tradingchassis_core/core/risk/risk_policy.py | 4 +- 23 files changed, 128 insertions(+), 118 deletions(-) diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md index fd4f083..5434298 100644 --- a/.github/pull-request-template.md +++ b/.github/pull-request-template.md @@ -23,7 +23,7 @@ Describe key implementation decisions. - [ ] Change preserves deterministic behavior - [ ] Tests confirm reproducibility -- [ ] No hidden state introduced +- [ ] No hidden State introduced ## Performance Impact diff --git a/CHANGELOG.md b/CHANGELOG.md index 97050db..ef8e7db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ This changelog starts from the clean Core package baseline. ### Added - Deterministic `run_core_step` and `run_core_wakeup_step` architecture. -- CoreWakeupStep final-state Strategy evaluation: reduce all entries, then `CoreWakeupStrategyEvaluator` once. +- CoreWakeupStep final Strategy evaluation: reduce all entries, then `CoreWakeupStrategyEvaluator` once. - Canonical Event input models and `EventStreamEntry`/`ProcessingPosition`. -- Intent candidate record pipeline with dominance/reconciliation. +- Intent candidate record Pipeline with dominance/reconciliation. - Risk Engine (policy-only) admission and Execution Control plan/apply integration. - `CoreStepResult.dispatchable_intents` and `ControlSchedulingObligation` outputs. - Core-only quickstart example and focused semantics test coverage. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f984aa..4a74f28 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,7 +48,7 @@ python -m build - Register canonical category handling in `core/domain/event_model.py`. - Update canonical reduction behavior in `core/domain/processing.py`. -### CoreStep/CoreWakeupStep pipeline +### CoreStep/CoreWakeupStep Pipeline - Update `core/domain/processing_step.py` for deterministic flow changes. - Keep reconciliation/policy/apply transitions explicit and side-effect-safe. diff --git a/README.md b/README.md index 3428744..a653d24 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,11 @@ simulation, Live trading, Venue Adapters, and infrastructure around you change. > Terminology: Definitions and related terms match the > [`canonical terminology`](https://tradingchassis.github.io/docs/latest/00-guides/terminology/). -> In-repo pointers: [`core/docs/README.md`](docs/README.md) and +> In-repo pointers: [`core/docs/index.md`](docs/index.md) and > [`core/docs/code-map/core-pipeline-map.md`](docs/code-map/core-pipeline-map.md). + + ## Why it is relevant Trading systems often drift when Backtesting logic, Live logic, policy limits, and @@ -41,34 +43,6 @@ canonical Events, invoke Core, and perform Execution and dispatch outside Core u and Execution Control semantics stay identical across those Runtimes when the Event Stream and Configuration match. -## What it gives you - -| What you get | Why it matters | -| --- | --- | -| One deterministic Core pipeline | Same Event-step path for reduction → evaluation → candidates → Risk Engine → Execution Control apply | -| Canonical Event input model (`EventStreamEntry`) | Aligns with Event Stream + Processing Order; State is `f(Event Stream, Configuration)` | -| Strategy output as Intents | Internal, order/Venue-agnostic commands before Venue Adapter-specific shapes | -| Risk Engine separated from Execution Control | Risk Engine (policy) vs Queue / scheduling / rate-aware presentation split, as in the intent pipeline (Strategy → Risk → Queue → Adapter) | -| `dispatchable_intents` + optional Control Scheduling Obligation | Runtime performs Execution and may inject canonical `ControlTimeEvent` when a **rate-limit** obligation is realized ([`docs/flows/control-time-and-scheduling.md`](docs/flows/control-time-and-scheduling.md)); inflight deferral does not emit that obligation by default | - -## Control time and scheduling (Core) - -`ControlSchedulingObligation` is a **non-canonical**, time-dependent hint produced -when Execution Control **apply** defers for **rate limits**. **Inflight** gating is -**feedback-dependent** and does not, by default, produce this obligation; queued -work is reconsidered after canonical execution Events update state. Runtimes must -not flush Core queues outside the normal `run_core_step` / Execution Control apply -path. See [`docs/flows/control-time-and-scheduling.md`](docs/flows/control-time-and-scheduling.md). -| Runtime-independent package | Test trading semantics without production I/O; explicit ownership boundary | -| Shared kernel across environments | Serious Backtesting and Live parity for the decision engine—no secondary copy of Strategy/Risk Engine/Execution Control code elsewhere | - -In short: one pipeline, canonical Events, Intents inside Core, policy vs Execution -Control split, dispatchable Intents plus optional Control Scheduling Obligation for -the Runtime, and a boundary that makes parity and testing practical—not a second -copy of decision logic per environment. - -## Why it matters for trading - The gap between tested behavior and Live trading behavior can dominate outcomes. **Backtesting** is only a reliable guide if the **same** Core decision logic—Strategy, Risk Engine, Execution Control—can drive Live when the Event Stream and @@ -82,6 +56,33 @@ decision engine itself. Wall-clock scheduling, Venue behavior, Venue Adapter mapping, latency, liquidity, market-data quality, and infrastructure failure modes stay in the Runtime, Venue Adapter, and Venue—not in Core. + + +## What it gives you + +| What you get | Why it matters | +| --- | --- | +| One deterministic Core Pipeline | Same Event-step path for reduction → evaluation → candidates → Risk Engine → Execution Control apply | +| Canonical Event input model (`EventStreamEntry`) | Aligns with Event Stream + Processing Order; State is `f(Event Stream, Configuration)` | +| Strategy output as Intents | Internal, order/Venue-agnostic commands before Venue Adapter-specific shapes | +| Risk Engine separated from Execution Control | Risk Engine (policy) vs Queue / scheduling / rate-aware presentation split, as in the Intent Pipeline (Strategy → Risk → Queue → Adapter) | +| `dispatchable_intents` + optional Control Scheduling Obligation | Runtime performs Execution and may inject canonical `ControlTimeEvent` when a **rate-limit** obligation is realized ([`docs/flows/control-time-and-scheduling.md`](docs/flows/control-time-and-scheduling.md)); inflight deferral does not emit that obligation by default | + +Core is designed to reduce decision-logic drift between Backtesting +and Live: the same canonical Event + `run_core_step` / reduction APIs +can drive both worlds when each Runtime constructs comparable `EventStreamEntry` +sequences under the same Configuration. Normalizing feeds, timestamps, and +control semantics before they enter Core narrows unnecessary divergence. + +Core does not remove every simulation-vs-production gap. Individual Venue +behavior, latency, fills and liquidity, market-data quality, Venue Adapter +behavior, Runtime scheduling, and infrastructure failure modes can still +differ and must be modeled outside Core. What Core removes is a major +source of mismatch—duplicating and subtly diverging Strategy/Risk Engine/ +Execution Control itself. + + + ## How it fits into a full system Backtesting Runtimes, Live Runtimes, and local Research or simulation harnesses can @@ -102,23 +103,10 @@ flowchart TB Core never replaces the Runtime: the Runtime is responsible for feeding canonical Events and for turning `dispatchable_intents` into Venue traffic (and for everything Kubernetes, credentials, and operations-related). What stays stable is the Core -pipeline and contracts; what varies by design is Runtime choice, Venue Adapter, +Pipeline and contracts; what varies by design is Runtime choice, Venue Adapter, Venue, and deployment. -## Backtesting and Live parity - -Core is designed to reduce decision-logic drift between Backtesting -and Live: the same canonical Event + `run_core_step` / reduction APIs -can drive both worlds when each Runtime constructs comparable `EventStreamEntry` -sequences under the same Configuration. Normalizing feeds, timestamps, and -control semantics before they enter Core narrows unnecessary divergence. - -Core does not remove every simulation-vs-production gap. Individual Venue -behavior, latency, fills and liquidity, market-data quality, Venue Adapter -behavior, Runtime scheduling, and infrastructure failure modes can still -differ and must be modeled outside Core. What Core removes is a major -source of mismatch—duplicating and subtly diverging Strategy/Risk Engine/ -Execution Control itself. + ## When to use `tradingchassis_core` @@ -135,34 +123,7 @@ Execution Control itself. - You expect this package to ship a full Kubernetes Runtime, deployment manifests, or production operations. - You expect Core to execute orders, talk to Venues, replace Venue Adapters, or perform external dispatch. -## Full pipeline - -Internal processing pipeline, in sequential order: - -```text -Runtime reduces to canonical Events - - -> process_event_entry / process_canonical_event - -> Strategy evaluation - -> generated Intents - -> candidate records - -> dominance / reconciliation - -> Risk Engine (policy) - -> Execution Control plan/apply - -> CoreStepResult.dispatchable_intents - -Runtime dispatches Intents into Orders -``` - -## Input / Core / Output / Not Owned By Core - -- Input: `EventStreamEntry` values with canonical Events and Event Stream position. -- Core does: deterministic reduction, Strategy evaluation boundary, candidate - merge/dominance, Risk Engine (policy), Execution Control planning/apply. -- Output: `CoreStepResult` with generated/candidate Intents, optional - `dispatchable_intents`, and optional `control_scheduling_obligation`. -- Not owned by Core: raw market/feed I/O, Venue Adapters, external dispatch, - credentials/environment wiring, Runtime orchestration, Kubernetes/deployment. + ## Quickstart @@ -199,6 +160,67 @@ print(result.generated_intents, result.dispatchable_intents) See `examples/core_step_quickstart.py` for a full runnable walkthrough. + + +## Full Pipeline + +Internal processing Pipeline, in sequential order: + +```text +Runtime reduces to canonical Events + + -> process_event_entry / process_canonical_event + -> Strategy evaluation + -> generated Intents + -> candidate records + -> dominance / reconciliation + -> Risk Engine (policy) + -> Execution Control plan/apply + -> CoreStepResult.dispatchable_intents + +Runtime dispatches Intents into Orders +``` + + + +## Input / Core / Output / Not Owned By Core + +- Input: `EventStreamEntry` values with canonical Events and Event Stream position. +- Core does: deterministic reduction, Strategy evaluation boundary, candidate + merge/dominance, Risk Engine (policy), Execution Control planning/apply. +- Output: `CoreStepResult` with generated/candidate Intents, optional + `dispatchable_intents`, and optional `control_scheduling_obligation`. +- Not owned by Core: raw market/feed I/O, Venue Adapters, external dispatch, + credentials/environment wiring, Runtime orchestration, Kubernetes/deployment. + +### Ownership Boundary + +| Core owns | Runtime owns | +| --- | --- | +| canonical models/contracts | raw I/O and feed adapters | +| State reduction and ordering | Venue Adapters and transport | +| Strategy evaluation boundary | adapter-side Execution | +| candidate Intents and reconciliation | credentials/env wiring | +| Risk Engine (policy) | Backtesting/Live orchestration | +| Execution Control | Kubernetes/deployment | +| `CoreStepResult` decision contract | Runtime lifecycle glue | + + + +## Control time and scheduling (Core) + +`ControlSchedulingObligation` is a **non-canonical**, time-dependent hint produced +when Execution Control **apply** defers for **rate limits**. **Inflight** gating is +**feedback-dependent** and does not, by default, produce this obligation; queued +work is reconsidered after canonical execution Events update State. Runtimes must +not flush Core Queues outside the normal `run_core_step` / Execution Control apply +path. See [`docs/flows/control-time-and-scheduling.md`](docs/flows/control-time-and-scheduling.md). + +In short: one Pipeline, canonical Events, Intents inside Core, policy vs Execution +Control split, dispatchable Intents plus optional Control Scheduling Obligation for +the Runtime, and a boundary that makes parity and testing practical—not a second +copy of decision logic per environment. + ## Public Entrypoints | Entrypoint | Purpose | @@ -210,8 +232,6 @@ See `examples/core_step_quickstart.py` for a full runnable walkthrough. | `process_event_entry` | Reduce one `EventStreamEntry` into `StrategyState` | | `process_canonical_event` | Reduce one canonical Event into `StrategyState` | - - ## CoreWakeupStep semantics CoreWakeupStep is not "parallel Event processing". @@ -221,21 +241,11 @@ canonical entries, and Core reduces them in that order before making one decisio - `run_core_step` handles one `EventStreamEntry`. - `run_core_wakeup_step` handles an ordered batch of `EventStreamEntry` values. - Runtime is responsible for normalizing and ordering simultaneous raw inputs. -- Core reduces all wakeup entries in order, evaluates Strategy once on the final state, +- Core reduces all wakeup entries in order, evaluates Strategy once on the final State, then runs Policy Admission and ExecutionControl once. - Runtime dispatches after the returned `CoreStepResult`. -## Ownership Boundary - -| Core owns | Runtime owns | -| --- | --- | -| canonical models/contracts | raw I/O and feed adapters | -| State reduction and ordering | Venue Adapters and transport | -| Strategy evaluation boundary | adapter-side Execution | -| candidate Intents and reconciliation | credentials/env wiring | -| Risk Engine (policy) | Backtesting/Live orchestration | -| Execution Control | Kubernetes/deployment | -| `CoreStepResult` decision contract | Runtime lifecycle glue | + ## Developer Commands diff --git a/SECURITY.md b/SECURITY.md index 5228104..aa5e6af 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -25,7 +25,7 @@ Include: This policy covers the Core package in this repository, including: - canonical Event and Intent contracts -- deterministic CoreStep/CoreWakeupStep decision pipeline +- deterministic CoreStep/CoreWakeupStep decision Pipeline - package integrity and dependency usage in `tradingchassis_core` ## Secrets and Credentials Policy diff --git a/docs/code-map/core-pipeline-map.md b/docs/code-map/core-pipeline-map.md index cfd4ed3..a1ed6a0 100644 --- a/docs/code-map/core-pipeline-map.md +++ b/docs/code-map/core-pipeline-map.md @@ -1,6 +1,6 @@ # Core Pipeline Map -This map captures the only supported deterministic decision pipeline for +This map captures the only supported deterministic decision Pipeline for TradingChassis Core. ## Step-by-step flow @@ -17,7 +17,7 @@ TradingChassis Core. in the current slice—see `../flows/control-time-and-scheduling.md`). 9. Runtime can dispatch later and inject further canonical Events (including `ControlTimeEvent` when an obligation is realized); Core does not perform - external dispatch or mutate queues outside this pipeline. + external dispatch or mutate Queues outside this Pipeline. ## Core APIs @@ -29,7 +29,7 @@ TradingChassis Core. ## Determinism notes - Processing Order monotonicity is enforced by `ProcessingPosition`. -- Core logic is side-effect-safe apart from deterministic state mutation. +- Core logic is side-effect-safe apart from deterministic State mutation. - Runtime adapters and external dispatch concerns are outside Core. @@ -44,7 +44,7 @@ Wakeup flow: 1. Runtime supplies an ordered batch of `EventStreamEntry` values. 2. `run_core_wakeup_reduction` calls `process_event_entry` for each entry in order. -3. `CoreWakeupStrategyEvaluator.evaluate` runs **once** on the fully reduced state +3. `CoreWakeupStrategyEvaluator.evaluate` runs **once** on the fully reduced State (`CoreWakeupStrategyContext` carries all entries). 4. `run_core_wakeup_decision` snapshots queued intents once, combines generated + queued once, applies dominance/reconciliation once, Policy Admission once, and diff --git a/docs/code-map/repository-map.md b/docs/code-map/repository-map.md index d050461..7a1044f 100644 --- a/docs/code-map/repository-map.md +++ b/docs/code-map/repository-map.md @@ -6,7 +6,7 @@ High-level map for the standalone Core package. - `tradingchassis_core/__init__.py`: public package boundary exports - `tradingchassis_core/core/domain/`: canonical contracts and deterministic - pipeline orchestration + Pipeline orchestration - `tradingchassis_core/core/risk/`: policy-only Risk Engine evaluator/config - `tradingchassis_core/core/execution_control/`: Execution Control primitives - `tradingchassis_core/core/events/`: internal Event bus/sink utilities diff --git a/docs/flows/control-time-and-scheduling.md b/docs/flows/control-time-and-scheduling.md index 1257ce3..8f8c966 100644 --- a/docs/flows/control-time-and-scheduling.md +++ b/docs/flows/control-time-and-scheduling.md @@ -12,11 +12,11 @@ to Execution Control deferral. - **ControlTimeEvent** — Canonical **control** category Event. It becomes part of deterministic history only after the **Runtime** injects it as `EventStreamEntry` input (same ingestion path as other canonical Events). -- **Inflight** — Core-side **intent-operation** gating: a sendability / operation +- **Inflight** — Core-side **Intent-operation** gating: a sendability / operation slot (for example keyed by `client_order_id`) is occupied because an earlier - intent operation is still awaiting **canonical execution feedback**. This is + Intent operation is still awaiting **canonical execution feedback**. This is not the same as venue-side “order ownership”; Core models sendability for the - decision pipeline. + decision Pipeline. - **Rate-limit deferral** — Execution control blocks dispatch because the configured **token / time budget** for orders or cancels is not yet available at the apply clock (`now_ts_ns_local` in `CoreExecutionControlApplyContext`). @@ -33,11 +33,11 @@ to Execution Control deferral. **Not in scope for the current contract:** inflight timeout, wall-clock recovery, or “synthetic” obligations for inflight-only waits. -**Not implied:** every queued intent produces a scheduling obligation or a future +**Not implied:** every queued Intent produces a scheduling obligation or a future `ControlTimeEvent`. Obligations are for **rate-limit** rechecks in the current Core slice. -## Clean Core pipeline (unchanged) +## Clean Core Pipeline (unchanged) 1. `EventStreamEntry` 2. `process_event_entry` / `process_canonical_event` @@ -55,7 +55,7 @@ they are selected only in the mutable **apply** stage (`apply_execution_control_ ## Runtime ownership -- Runtimes **must not** mutate Core queues (`StrategyState.queued_intents`, etc.) +- Runtimes **must not** mutate Core Queues (`StrategyState.queued_intents`, etc.) directly outside the normal Core step / Execution Control apply path. - Queue flush / sendability decisions remain **ExecutionControl-owned** inside Core when `CoreExecutionControlApplyContext` is supplied to `run_core_step` / diff --git a/docs/how-to/update-core-step-pipeline.md b/docs/how-to/update-core-step-pipeline.md index aed51cb..0b67c7a 100644 --- a/docs/how-to/update-core-step-pipeline.md +++ b/docs/how-to/update-core-step-pipeline.md @@ -19,7 +19,7 @@ Recommended workflow: Guardrails: -- No Runtime dispatch logic in Core pipeline code. +- No Runtime dispatch logic in Core Pipeline code. - No legacy compatibility contract restoration. - Keep deterministic behavior and public API coherence. diff --git a/docs/index.md b/docs/index.md index 433938a..f7e8365 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,7 +7,7 @@ This documentation set describes the standalone clean Core package baseline. - `reference/public-api.md`: supported root exports and package boundary - `reference/events-reference.md`: canonical Events and Intent contracts - `flows/control-time-and-scheduling.md`: rate-limit vs inflight deferral and obligations -- `code-map/core-pipeline-map.md`: deterministic pipeline walkthrough +- `code-map/core-pipeline-map.md`: deterministic Pipeline walkthrough - `code-map/repository-map.md`: package layout and ownership map - `how-to/add-canonical-event.md`: extending canonical Event contracts - `how-to/update-core-step-pipeline.md`: changing CoreStep/CoreWakeupStep behavior diff --git a/docs/reference/events-reference.md b/docs/reference/events-reference.md index e1e79e4..f5a7e20 100644 --- a/docs/reference/events-reference.md +++ b/docs/reference/events-reference.md @@ -5,7 +5,7 @@ contracts. Pydantic models are the schema source of truth. ## Canonical Event Models -- `MarketEvent`: book/trade market data input for state reduction +- `MarketEvent`: book/trade market data input for State reduction - `ControlTimeEvent`: canonical **control** wakeup; becomes stream history only after Runtime injection. Reducer updates monotone time (and processing cursor when positioned). Scheduling **obligations** are a separate non-canonical output; diff --git a/examples/core_step_quickstart.py b/examples/core_step_quickstart.py index c7187de..cd308d8 100644 --- a/examples/core_step_quickstart.py +++ b/examples/core_step_quickstart.py @@ -119,7 +119,7 @@ def main() -> None: state = tc.StrategyState(event_bus=tc.NullEventBus()) # Core consumes canonical Events. Here we use ControlTimeEvent as a simple - # canonical trigger Event to drive the deterministic step pipeline. + # canonical trigger Event to drive the deterministic step Pipeline. result_v1 = run_v1_generated_only(state) result_v2 = run_v2_with_policy_and_apply(state) diff --git a/tests/semantics/test_core_pipeline_clean.py b/tests/semantics/test_core_pipeline_clean.py index 7daae42..921a3dc 100644 --- a/tests/semantics/test_core_pipeline_clean.py +++ b/tests/semantics/test_core_pipeline_clean.py @@ -1,4 +1,4 @@ -"""Clean CoreStep/CoreWakeupStep pipeline tests.""" +"""Clean CoreStep/CoreWakeupStep Pipeline tests.""" from __future__ import annotations diff --git a/tests/semantics/test_core_wakeup_final_state.py b/tests/semantics/test_core_wakeup_final_state.py index 67b1dab..bcde4f6 100644 --- a/tests/semantics/test_core_wakeup_final_state.py +++ b/tests/semantics/test_core_wakeup_final_state.py @@ -1,4 +1,4 @@ -"""Final-state CoreWakeupStep Strategy evaluation semantics (Phase WU2).""" +"""Final CoreWakeupStep Strategy evaluation semantics (Phase WU2).""" from __future__ import annotations diff --git a/tradingchassis_core/core/domain/candidate_intent.py b/tradingchassis_core/core/domain/candidate_intent.py index 2fba7c8..4f75467 100644 --- a/tradingchassis_core/core/domain/candidate_intent.py +++ b/tradingchassis_core/core/domain/candidate_intent.py @@ -1,4 +1,4 @@ -"""Core-owned non-canonical candidate intent provenance models.""" +"""Core-owned non-canonical candidate Intent provenance models.""" from __future__ import annotations diff --git a/tradingchassis_core/core/domain/execution_control_apply.py b/tradingchassis_core/core/domain/execution_control_apply.py index ff614b0..ef5f279 100644 --- a/tradingchassis_core/core/domain/execution_control_apply.py +++ b/tradingchassis_core/core/domain/execution_control_apply.py @@ -137,7 +137,7 @@ def apply_execution_control_plan( events. ``control_scheduling_obligation`` is selected only from **rate-limit** - deferrals (time-dependent). **Inflight** gating queues or blocks work without + deferrals (time-dependent). **Inflight** gating Queues or blocks work without adding a scheduling obligation; that case is resolved when later canonical events update sendability (not via a Core-derived wake time in this slice). """ diff --git a/tradingchassis_core/core/domain/intent_combination.py b/tradingchassis_core/core/domain/intent_combination.py index 5f575f7..1222421 100644 --- a/tradingchassis_core/core/domain/intent_combination.py +++ b/tradingchassis_core/core/domain/intent_combination.py @@ -1,4 +1,4 @@ -"""Pure helper for Core-step candidate intent combination.""" +"""Pure helper for Core-step candidate Intent combination.""" from __future__ import annotations diff --git a/tradingchassis_core/core/domain/policy_risk_decision.py b/tradingchassis_core/core/domain/policy_risk_decision.py index 19e364e..b3551ca 100644 --- a/tradingchassis_core/core/domain/policy_risk_decision.py +++ b/tradingchassis_core/core/domain/policy_risk_decision.py @@ -16,7 +16,7 @@ class PolicyIntentEvaluator(Protocol): - """Side-effect-safe policy evaluator contract for one candidate intent.""" + """Side-effect-safe policy evaluator contract for one candidate Intent.""" def evaluate_policy_intent( self, diff --git a/tradingchassis_core/core/domain/state.py b/tradingchassis_core/core/domain/state.py index 6aab540..65ee928 100644 --- a/tradingchassis_core/core/domain/state.py +++ b/tradingchassis_core/core/domain/state.py @@ -27,7 +27,7 @@ @dataclass(slots=True) class QueuedIntent: - """An intent stored for later sending (data-only Queue).""" + """An Intent stored for later sending (data-only Queue).""" intent: OrderIntent queued_at_ts_ns: int diff --git a/tradingchassis_core/core/events/events.py b/tradingchassis_core/core/events/events.py index 36d8155..5f1fad1 100644 --- a/tradingchassis_core/core/events/events.py +++ b/tradingchassis_core/core/events/events.py @@ -7,7 +7,7 @@ @dataclass(slots=True) class OrderStateTransitionEvent: - """Observability payload for unexpected order-state transitions.""" + """Observability payload for unexpected Order-State transitions.""" ts_ns_local: int instrument: str diff --git a/tradingchassis_core/core/execution_control/execution_control.py b/tradingchassis_core/core/execution_control/execution_control.py index b7de265..4dd3dd7 100644 --- a/tradingchassis_core/core/execution_control/execution_control.py +++ b/tradingchassis_core/core/execution_control/execution_control.py @@ -92,7 +92,7 @@ def route_after_policy_rate_limit( max_orders_per_sec: float | None, max_cancels_per_sec: float | None, ) -> _RateRoutingResult: - """Route policy-allowed intent by rate-limits (accept now vs stage).""" + """Route policy-allowed Intent by rate-limits (accept now vs stage).""" if it.intent_type == "cancel": if max_cancels_per_sec is not None: allowed, wake_ts = self.consume_rate( diff --git a/tradingchassis_core/core/risk/risk_engine.py b/tradingchassis_core/core/risk/risk_engine.py index 7d7f7da..912c312 100644 --- a/tradingchassis_core/core/risk/risk_engine.py +++ b/tradingchassis_core/core/risk/risk_engine.py @@ -121,7 +121,7 @@ def evaluate_policy_intent( state: StrategyState, now_ts_ns_local: int, ) -> tuple[bool, str | None]: - """Evaluate one intent with policy-only checks and no side effects.""" + """Evaluate one Intent with policy-only checks and no side effects.""" raw_intents = [intent] diff --git a/tradingchassis_core/core/risk/risk_policy.py b/tradingchassis_core/core/risk/risk_policy.py index 0356d6e..83a0e69 100644 --- a/tradingchassis_core/core/risk/risk_policy.py +++ b/tradingchassis_core/core/risk/risk_policy.py @@ -109,7 +109,7 @@ def normalize_intent(self, it: OrderIntent, state: StrategyState) -> Normalizati return self._constraints_policy.normalize_intent(it, state) def validate_intent(self, it: OrderIntent, state: StrategyState) -> tuple[bool, str]: - """Outbound intent sanity. + """Outbound Intent sanity. Even if your schemas allow 0 placeholders, outbound intents should still be sensible. """ @@ -189,7 +189,7 @@ def hard_checks( gross_q = sum(v[0] for v in book.values()) net_q = sum(v[1] for v in book.values()) - # Apply delta for this intent (new or replace). + # Apply delta for this Intent (new or replace). new_abs = notional new_signed = notional if it.side == "buy" else -notional From b6c6e57848bb4fe43a500fd4dda250715c58336c Mon Sep 17 00:00:00 2001 From: bxvtr Date: Fri, 15 May 2026 14:06:08 +0000 Subject: [PATCH 2/5] feat(core): expose policy evaluator extension point --- CHANGELOG.md | 5 + README.md | 49 +++++- U3_DEAD_CODE_CANDIDATES.md | 18 +++ pyproject.toml | 2 +- tests/docs/extension-points.md | 23 +++ tests/runnable/core_step_with_risk_engine.py | 109 ++++++++++++++ tests/semantics/test_fill_event_reduction.py | 93 ++++++++++++ tests/semantics/test_public_api_clean.py | 1 + .../test_risk_engine_pipeline_integration.py | 141 ++++++++++++++++++ .../test_runnable_risk_engine_example.py | 22 +++ tradingchassis_core/__init__.py | 2 + 11 files changed, 461 insertions(+), 4 deletions(-) create mode 100644 U3_DEAD_CODE_CANDIDATES.md create mode 100644 tests/docs/extension-points.md create mode 100644 tests/runnable/core_step_with_risk_engine.py create mode 100644 tests/semantics/test_fill_event_reduction.py create mode 100644 tests/semantics/test_risk_engine_pipeline_integration.py create mode 100644 tests/semantics/test_runnable_risk_engine_example.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ef8e7db..6cd3e1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,16 @@ This changelog starts from the clean Core package baseline. - Risk Engine (policy-only) admission and Execution Control plan/apply integration. - `CoreStepResult.dispatchable_intents` and `ControlSchedulingObligation` outputs. - Core-only quickstart example and focused semantics test coverage. +- Root export of `PolicyIntentEvaluator` and documentation of extension points vs convenience implementations. +- Pipeline integration tests for `RiskEngine` as `policy_evaluator` in `run_core_step`. +- `FillEvent` reducer and Pipeline tests. +- Runnable RiskEngine example at `tests/runnable/core_step_with_risk_engine.py`. ### Changed - Package metadata, exports, and docs reset for standalone Core library identity. - Pydantic models established as contract source of truth across public API docs. +- README clarifies internally wired Pipeline vs externally supplied extension points. ### Removed diff --git a/README.md b/README.md index a653d24..125c9bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TradingChassis Core -`tradingchassis_core` is the stable deterministic trading decision kernel +`tradingchassis_core` is the stable deterministic trading decision engine for TradingChassis: an Event-step engine that applies ordered canonical Events (the Event Stream under Processing Order and Configuration) and produces `CoreStepResult` outputs—including Strategy-generated and @@ -24,6 +24,40 @@ simulation, Live trading, Venue Adapters, and infrastructure around you change. +## Internally wired vs externally supplied + +The clean Core Pipeline is always the same shape; some pieces run inside Core +when you call step APIs, and others must be supplied by your Runtime or tests. + +### Internally wired (always part of Core when you call step APIs) + +- `process_event_entry` / `process_canonical_event` and canonical reducers +- Candidate combination, dominance, and reconciliation (`combine_candidate_intent_records`) +- Policy admission **mechanism** when `CorePolicyAdmissionContext` is provided +- Execution Control plan/apply **mechanism** when policy + apply contexts are provided +- `CoreStepResult` / `CoreStepDecision` production + +### Externally supplied extension points + +- **Strategy evaluator** — `CoreStepStrategyEvaluator` or `CoreWakeupStrategyEvaluator` +- **Policy evaluator** — any object implementing `PolicyIntentEvaluator` (passed via `CorePolicyAdmissionContext`) +- **Execution Control** — `ExecutionControl` instance (passed via `CoreExecutionControlApplyContext`) +- **Configuration** — optional `CoreConfiguration` for positioned market reduction +- **Event bus** — `StrategyState` requires an `EventBus`; use `NullEventBus` for standalone Core/tests + +### Convenience implementations (optional; not wired by default) + +- **`RiskEngine`** — provided `PolicyIntentEvaluator` with built-in policy gates +- **`ExecutionControl`** — provided queue/rate/inflight implementation +- **`NullEventBus`** — discards observability events for tests and examples + +The minimal quickstart uses an inline allow-all policy to stay small. That does +**not** mean `RiskEngine` is unused or dead. Use `RiskEngine` when you want the +built-in policy behavior. See `examples/core_step_quickstart.py` (minimal) and +`tests/runnable/core_step_with_risk_engine.py` (RiskEngine variant). + + + ## Why it is relevant Trading systems often drift when Backtesting logic, Live logic, policy limits, and @@ -37,7 +71,7 @@ deployments, different Venue Adapters) may change; Core should not. A typical notebook or one-off Backtesting script inlines feed handling, Strategy rules, Risk Engine (policy) checks, and how orders are sent in one place. That is fast to sketch but tends to fork: the Live path reimplements similar ideas with different bugs and -timing. Core keeps the decision kernel in one place: Runtimes normalize inputs into +timing. Core keeps the decision engine in one place: Runtimes normalize inputs into canonical Events, invoke Core, and perform Execution and dispatch outside Core using `CoreStepResult`; Strategy, Risk Engine, and Execution Control semantics stay identical across those Runtimes when the @@ -111,7 +145,7 @@ Venue, and deployment. ## When to use `tradingchassis_core` - Building an internal trading system where Backtesting and Live should share decision semantics. -- Wanting a deterministic Strategy / Risk Engine / Execution Control kernel. +- Wanting a deterministic Strategy / Risk Engine / Execution Control engine. - Separating trading semantics from Venue Adapters, I/O, and Kubernetes wiring. - Testing decisions and Intents without full Backtesting or Live machinery. - Sharing one decision path across simulation and production. @@ -133,6 +167,12 @@ Run the quickstart python examples/core_step_quickstart.py ``` +Optional RiskEngine policy example (same Pipeline, built-in policy): + +```bash +python tests/runnable/core_step_with_risk_engine.py +``` + or minimal shape: ```python @@ -231,6 +271,8 @@ copy of decision logic per environment. | `run_core_wakeup_step` | Reduce all entries, evaluate Strategy once, then one decision pass | | `process_event_entry` | Reduce one `EventStreamEntry` into `StrategyState` | | `process_canonical_event` | Reduce one canonical Event into `StrategyState` | +| `PolicyIntentEvaluator` | Protocol for policy admission (`evaluate_policy_intent`) | +| `RiskEngine` | Convenience `PolicyIntentEvaluator` implementation | ## CoreWakeupStep semantics @@ -254,6 +296,7 @@ From root: ```bash python -m pip install -e ".[dev]" python examples/core_step_quickstart.py +python tests/runnable/core_step_with_risk_engine.py ./scripts/check.sh python -m build ``` diff --git a/U3_DEAD_CODE_CANDIDATES.md b/U3_DEAD_CODE_CANDIDATES.md new file mode 100644 index 0000000..19aa60a --- /dev/null +++ b/U3_DEAD_CODE_CANDIDATES.md @@ -0,0 +1,18 @@ +# U3 dead-code cleanup candidates + +See Phase U1 audit. **Do not delete in U2.** Full table: candidates listed in the Phase U2 report +and in-repo paths below. + +| Candidate | U3 action | +| --- | --- | +| `StrategyState.pop_queued_intents` | Defer; audit `core-runtime` callers | +| `RiskEngine.build_constraints` | Defer; add Strategy contract test or remove | +| `fold_event_stream_entries` | Keep utility or demote export | +| `SlotKey`, `stable_slot_order_id` | Remove export or add MM example | +| `core/events/events.py` telemetry | Remove if no monorepo emitter | +| `core/events/sinks/sink_logging.py` | Remove or document optional | +| Apply detail record exports | Narrow `__all__` after usage audit | + +**Not for removal:** `RiskEngine`, `PolicyIntentEvaluator`, `FillEvent`, internal `RiskPolicy` / `ExecutionConstraintsPolicy`. + +Move to `docs/roadmap/` when `docs/` directory permissions allow writes. diff --git a/pyproject.toml b/pyproject.toml index c7b6052..bc0b421 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "TradingChassis-core" version = "0.1.0" -description = "Deterministic trading decision kernel." +description = "Deterministic trading decision engine." readme = "README.md" requires-python = ">=3.11" authors = [{ name = "TradingChassis Core Contributors" }] diff --git a/tests/docs/extension-points.md b/tests/docs/extension-points.md new file mode 100644 index 0000000..61f0981 --- /dev/null +++ b/tests/docs/extension-points.md @@ -0,0 +1,23 @@ +# Core extension points (supplement) + +Canonical copies live under `core/docs/` when writable. This file mirrors U2 documentation updates. + +## Externally supplied + +- `CoreStepStrategyEvaluator` / `CoreWakeupStrategyEvaluator` +- `PolicyIntentEvaluator` (root export) via `CorePolicyAdmissionContext` +- `ExecutionControl` via `CoreExecutionControlApplyContext` +- `CoreConfiguration` +- `NullEventBus` / custom `EventBus` for `StrategyState` + +## Convenience implementations + +- `RiskEngine` — optional `PolicyIntentEvaluator` +- `ExecutionControl` — optional apply implementation +- `NullEventBus` — standalone tests/examples + +## Internally wired + +Reduction, candidate reconciliation, policy admission mechanism, Execution Control plan/apply mechanism, `CoreStepResult`. + +See `README.md` and `U3_DEAD_CODE_CANDIDATES.md` at the `core/` root. diff --git a/tests/runnable/core_step_with_risk_engine.py b/tests/runnable/core_step_with_risk_engine.py new file mode 100644 index 0000000..9a8b6cc --- /dev/null +++ b/tests/runnable/core_step_with_risk_engine.py @@ -0,0 +1,109 @@ +"""Core-only step using the provided RiskEngine as policy evaluator. + +See ``examples/core_step_quickstart.py`` for the minimal inline allow-all policy variant. +Run: ``python tests/runnable/core_step_with_risk_engine.py`` from the ``core/`` directory. +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +_CORE_ROOT = Path(__file__).resolve().parents[2] +if str(_CORE_ROOT) not in sys.path: + sys.path.insert(0, str(_CORE_ROOT)) + +import tradingchassis_core as tc # noqa: E402 +from tradingchassis_core.core.domain.types import NotionalLimits # noqa: E402 + +INSTRUMENT = "BTC-USDC-PERP" + + +class _OneIntentEvaluator: + def evaluate(self, context: object) -> list[tc.NewOrderIntent]: + _ = context + return [ + tc.NewOrderIntent( + intent_type="new", + ts_ns_local=1_000, + instrument=INSTRUMENT, + client_order_id="risk-example-intent", + intents_correlation_id="corr-risk-example", + side="buy", + order_type="limit", + intended_qty=tc.Quantity(value=1.0, unit="contracts"), + intended_price=tc.Price(currency="USDC", value=100.0), + time_in_force="GTC", + ) + ] + + +def _control_entry(index: int, ts_ns_local: int) -> tc.EventStreamEntry: + return tc.EventStreamEntry( + position=tc.ProcessingPosition(index=index), + event=tc.ControlTimeEvent( + ts_ns_local_control=ts_ns_local, + reason="scheduled_control_recheck", + due_ts_ns_local=ts_ns_local, + realized_ts_ns_local=ts_ns_local, + obligation_reason="rate_limit", + obligation_due_ts_ns_local=ts_ns_local, + runtime_correlation=None, + ), + ) + + +def _risk_config() -> tc.RiskConfig: + return tc.RiskConfig( + scope="example", + trading_enabled=True, + notional_limits=NotionalLimits( + currency="USDC", + max_gross_notional=1_000_000.0, + max_single_order_notional=1_000_000.0, + ), + position_limits=None, + quote_limits=None, + order_rate_limits=None, + max_loss=None, + ) + + +def main() -> None: + state = tc.StrategyState(event_bus=tc.NullEventBus()) + state.update_market( + instrument=INSTRUMENT, + best_bid=99.0, + best_ask=101.0, + best_bid_qty=1.0, + best_ask_qty=1.0, + tick_size=0.1, + lot_size=0.01, + contract_size=1.0, + ts_ns_local=1_000, + ts_ns_exch=999, + ) + + policy_engine = tc.RiskEngine(_risk_config()) + result = tc.run_core_step( + state, + _control_entry(0, 1_000), + strategy_evaluator=_OneIntentEvaluator(), + policy_admission_context=tc.CorePolicyAdmissionContext( + policy_evaluator=policy_engine, + now_ts_ns_local=1_000, + ), + execution_control_apply_context=tc.CoreExecutionControlApplyContext( + execution_control=tc.ExecutionControl(), + now_ts_ns_local=1_000, + activate_dispatchable_outputs=True, + ), + ) + + print("CoreStep with RiskEngine (Core-only; Runtime dispatches later)") + print("generated:", [i.client_order_id for i in result.generated_intents]) + print("dispatchable:", [i.client_order_id for i in result.dispatchable_intents]) + + +if __name__ == "__main__": + main() diff --git a/tests/semantics/test_fill_event_reduction.py b/tests/semantics/test_fill_event_reduction.py new file mode 100644 index 0000000..f2fb495 --- /dev/null +++ b/tests/semantics/test_fill_event_reduction.py @@ -0,0 +1,93 @@ +"""FillEvent canonical reduction and Core step Pipeline coverage.""" + +from __future__ import annotations + +import tradingchassis_core as tc + +INSTRUMENT = "BTC-USDC-PERP" +CLIENT_ORDER_ID = "fill-order-1" +FILL_TS = 200 + + +def _fill_event(*, cum_qty: float, remaining_qty: float | None) -> tc.FillEvent: + return tc.FillEvent( + ts_ns_exch=FILL_TS - 1, + ts_ns_local=FILL_TS, + instrument=INSTRUMENT, + client_order_id=CLIENT_ORDER_ID, + side="buy", + filled_price=tc.Price(currency="USDC", value=100.0), + cum_filled_qty=tc.Quantity(value=cum_qty, unit="contracts"), + remaining_qty=( + None + if remaining_qty is None + else tc.Quantity(value=remaining_qty, unit="contracts") + ), + time_in_force="GTC", + liquidity_flag="taker", + ) + + +def _order_submitted_entry(index: int, ts: int) -> tc.EventStreamEntry: + return tc.EventStreamEntry( + position=tc.ProcessingPosition(index=index), + event=tc.OrderSubmittedEvent( + ts_ns_local_dispatch=ts, + instrument=INSTRUMENT, + client_order_id=CLIENT_ORDER_ID, + side="buy", + order_type="limit", + intended_price=tc.Price(currency="USDC", value=100.0), + intended_qty=tc.Quantity(value=1.0, unit="contracts"), + time_in_force="GTC", + intent_correlation_id=None, + dispatch_attempt_id=None, + runtime_correlation=None, + ), + ) + + +def _fill_entry(index: int) -> tc.EventStreamEntry: + return tc.EventStreamEntry( + position=tc.ProcessingPosition(index=index), + event=_fill_event(cum_qty=1.0, remaining_qty=0.0), + ) + + +def test_fill_event_via_process_event_entry_updates_state() -> None: + state = tc.StrategyState(event_bus=tc.NullEventBus()) + tc.process_event_entry(state, _order_submitted_entry(0, 100)) + assert state.has_working_order(INSTRUMENT, CLIENT_ORDER_ID) + + tc.process_event_entry(state, _fill_entry(1)) + + assert state.sim_ts_ns_local == FILL_TS + fills = state.fills[INSTRUMENT] + assert len(fills) == 1 + assert fills[0].client_order_id == CLIENT_ORDER_ID + assert state.fill_cum_qty[INSTRUMENT][CLIENT_ORDER_ID] == 1.0 + assert not state.has_working_order(INSTRUMENT, CLIENT_ORDER_ID) + + +def test_run_core_step_strategy_evaluator_sees_fill_reduced_state() -> None: + class _AssertFillReducedEvaluator: + def evaluate(self, context: tc.CoreStepStrategyContext) -> list[tc.OrderIntent]: + assert context.state.sim_ts_ns_local == FILL_TS + assert INSTRUMENT in context.state.fills + assert len(context.state.fills[INSTRUMENT]) == 1 + assert isinstance(context.event, tc.FillEvent) + return [] + + state = tc.StrategyState(event_bus=tc.NullEventBus()) + tc.process_event_entry(state, _order_submitted_entry(0, 100)) + + result = tc.run_core_step( + state, + _fill_entry(1), + strategy_evaluator=_AssertFillReducedEvaluator(), + ) + + assert result.generated_intents == () + assert result.candidate_intent_records == () + assert result.dispatchable_intents == () + assert result.core_step_decision is None diff --git a/tests/semantics/test_public_api_clean.py b/tests/semantics/test_public_api_clean.py index 6eecc4b..2cccc2f 100644 --- a/tests/semantics/test_public_api_clean.py +++ b/tests/semantics/test_public_api_clean.py @@ -19,6 +19,7 @@ def test_public_api_exposes_clean_core_symbols() -> None: "CoreWakeupStrategyEvaluator", "CoreStepResult", "CoreStepDecision", + "PolicyIntentEvaluator", "PolicyRiskDecision", "ExecutionControlDecision", "CandidateIntentRecord", diff --git a/tests/semantics/test_risk_engine_pipeline_integration.py b/tests/semantics/test_risk_engine_pipeline_integration.py new file mode 100644 index 0000000..743a27c --- /dev/null +++ b/tests/semantics/test_risk_engine_pipeline_integration.py @@ -0,0 +1,141 @@ +"""RiskEngine as PolicyIntentEvaluator inside the real Core step pipeline.""" + +from __future__ import annotations + +import tradingchassis_core as tc +from tradingchassis_core.core.domain.types import NotionalLimits + +INSTRUMENT = "BTC-USDC-PERP" +NOW_TS = 100 + + +class _OneNewIntentEvaluator: + def evaluate(self, context: object) -> list[tc.NewOrderIntent]: + _ = context + return [ + tc.NewOrderIntent( + intent_type="new", + ts_ns_local=NOW_TS, + instrument=INSTRUMENT, + client_order_id="risk-pipeline-intent", + intents_correlation_id="corr-risk-pipeline", + side="buy", + order_type="limit", + intended_qty=tc.Quantity(value=1.0, unit="contracts"), + intended_price=tc.Price(currency="USDC", value=100.0), + time_in_force="GTC", + ) + ] + + +def _control_entry(index: int, ts: int) -> tc.EventStreamEntry: + return tc.EventStreamEntry( + position=tc.ProcessingPosition(index=index), + event=tc.ControlTimeEvent( + ts_ns_local_control=ts, + reason="scheduled_control_recheck", + due_ts_ns_local=ts, + realized_ts_ns_local=ts, + obligation_reason="rate_limit", + obligation_due_ts_ns_local=ts, + runtime_correlation=None, + ), + ) + + +def _risk_config(*, trading_enabled: bool) -> tc.RiskConfig: + return tc.RiskConfig( + scope="test", + trading_enabled=trading_enabled, + notional_limits=NotionalLimits( + currency="USDC", + max_gross_notional=1_000_000.0, + max_single_order_notional=1_000_000.0, + ), + position_limits=None, + quote_limits=None, + order_rate_limits=None, + max_loss=None, + ) + + +def _prime_market(state: tc.StrategyState) -> None: + state.update_market( + instrument=INSTRUMENT, + best_bid=99.0, + best_ask=101.0, + best_bid_qty=1.0, + best_ask_qty=1.0, + tick_size=0.1, + lot_size=0.01, + contract_size=1.0, + ts_ns_local=NOW_TS, + ts_ns_exch=NOW_TS - 1, + ) + + +def test_risk_engine_accepts_generated_intent_in_run_core_step() -> None: + """RiskEngine is a valid optional policy_evaluator for the full step Pipeline.""" + state = tc.StrategyState(event_bus=tc.NullEventBus()) + _prime_market(state) + policy_engine = tc.RiskEngine(_risk_config(trading_enabled=True)) + + result = tc.run_core_step( + state, + _control_entry(0, NOW_TS), + strategy_evaluator=_OneNewIntentEvaluator(), + policy_admission_context=tc.CorePolicyAdmissionContext( + policy_evaluator=policy_engine, + now_ts_ns_local=NOW_TS, + ), + execution_control_apply_context=tc.CoreExecutionControlApplyContext( + execution_control=tc.ExecutionControl(), + now_ts_ns_local=NOW_TS, + activate_dispatchable_outputs=True, + ), + ) + + assert tuple(i.client_order_id for i in result.generated_intents) == ("risk-pipeline-intent",) + assert result.core_step_decision is not None + policy_decision = result.core_step_decision.policy_risk_decision + assert policy_decision is not None + assert tuple(i.client_order_id for i in policy_decision.accepted_intents) == ( + "risk-pipeline-intent", + ) + assert policy_decision.rejected_intents == () + assert tuple(i.client_order_id for i in result.dispatchable_intents) == ( + "risk-pipeline-intent", + ) + + +def test_risk_engine_rejects_generated_intent_when_trading_disabled() -> None: + """Trading-disabled RiskConfig rejects new intents through policy admission.""" + state = tc.StrategyState(event_bus=tc.NullEventBus()) + _prime_market(state) + policy_engine = tc.RiskEngine(_risk_config(trading_enabled=False)) + + result = tc.run_core_step( + state, + _control_entry(0, NOW_TS), + strategy_evaluator=_OneNewIntentEvaluator(), + policy_admission_context=tc.CorePolicyAdmissionContext( + policy_evaluator=policy_engine, + now_ts_ns_local=NOW_TS, + ), + execution_control_apply_context=tc.CoreExecutionControlApplyContext( + execution_control=tc.ExecutionControl(), + now_ts_ns_local=NOW_TS, + activate_dispatchable_outputs=True, + ), + ) + + assert tuple(i.client_order_id for i in result.generated_intents) == ("risk-pipeline-intent",) + assert len(result.candidate_intent_records) == 1 + assert result.candidate_intent_records[0].origin is tc.CandidateIntentOrigin.GENERATED + assert result.dispatchable_intents == () + assert result.core_step_decision is not None + assert len(result.core_step_decision.policy_rejected_intents) == 1 + policy_decision = result.core_step_decision.policy_risk_decision + assert policy_decision is not None + assert policy_decision.accepted_intents == () + assert len(policy_decision.rejected_intents) == 1 diff --git a/tests/semantics/test_runnable_risk_engine_example.py b/tests/semantics/test_runnable_risk_engine_example.py new file mode 100644 index 0000000..0679554 --- /dev/null +++ b/tests/semantics/test_runnable_risk_engine_example.py @@ -0,0 +1,22 @@ +"""Smoke test for the runnable RiskEngine example script.""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +_CORE_ROOT = Path(__file__).resolve().parents[2] +_EXAMPLE = _CORE_ROOT / "tests" / "runnable" / "core_step_with_risk_engine.py" + + +def test_runnable_risk_engine_example_exits_zero() -> None: + result = subprocess.run( + [sys.executable, str(_EXAMPLE)], + cwd=_CORE_ROOT, + check=False, + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + assert "risk-example-intent" in result.stdout diff --git a/tradingchassis_core/__init__.py b/tradingchassis_core/__init__.py index 4f5f241..77905d6 100644 --- a/tradingchassis_core/__init__.py +++ b/tradingchassis_core/__init__.py @@ -22,6 +22,7 @@ ) from tradingchassis_core.core.domain.policy_risk_decision import ( PolicyAdmissionResult, + PolicyIntentEvaluator, PolicyRejectedCandidate, PolicyRiskDecision, ) @@ -120,6 +121,7 @@ "ExecutionControlDispatchableRecord", "ExecutionControlHandledRecord", "apply_execution_control_plan", + "PolicyIntentEvaluator", "PolicyRiskDecision", "PolicyRejectedCandidate", "PolicyAdmissionResult", From 345d18d6c2bf3d36b960c1c42df03e144dafb343 Mon Sep 17 00:00:00 2001 From: bxvtr Date: Fri, 15 May 2026 14:20:04 +0000 Subject: [PATCH 3/5] feat(core): clarify policy extension points and RiskEngine usage --- CHANGELOG.md | 3 +- README.md | 26 +++--- U3_DEAD_CODE_CANDIDATES.md | 18 ---- docs/code-map/core-pipeline-map.md | 26 +++++- docs/flows/control-time-and-scheduling.md | 6 +- docs/how-to/update-core-step-pipeline.md | 2 +- .../update-policy-and-execution-control.md | 4 +- docs/how-to/use-policy-evaluator.md | 54 ++++++++++++ docs/index.md | 12 ++- docs/reference/public-api.md | 59 +++++++++++-- docs/roadmap/dead-code-cleanup-candidates.md | 85 +++++++++++++++++++ .../core_step_with_risk_engine.py | 24 ++++-- tests/docs/extension-points.md | 23 ----- .../test_runnable_risk_engine_example.py | 2 +- 14 files changed, 268 insertions(+), 76 deletions(-) delete mode 100644 U3_DEAD_CODE_CANDIDATES.md create mode 100644 docs/how-to/use-policy-evaluator.md create mode 100644 docs/roadmap/dead-code-cleanup-candidates.md rename {tests/runnable => examples}/core_step_with_risk_engine.py (76%) delete mode 100644 tests/docs/extension-points.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cd3e1d..44e0822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,8 @@ This changelog starts from the clean Core package baseline. - Root export of `PolicyIntentEvaluator` and documentation of extension points vs convenience implementations. - Pipeline integration tests for `RiskEngine` as `policy_evaluator` in `run_core_step`. - `FillEvent` reducer and Pipeline tests. -- Runnable RiskEngine example at `tests/runnable/core_step_with_risk_engine.py`. +- Runnable Risk Engine example at `examples/core_step_with_risk_engine.py`. +- Extension-point docs under `docs/` and U3 candidate list at `docs/roadmap/dead-code-cleanup-candidates.md`. ### Changed diff --git a/README.md b/README.md index 125c9bc..3247218 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,14 @@ when you call step APIs, and others must be supplied by your Runtime or tests. ### Convenience implementations (optional; not wired by default) -- **`RiskEngine`** — provided `PolicyIntentEvaluator` with built-in policy gates +- **Risk Engine** (`RiskEngine`) — provided `PolicyIntentEvaluator` with built-in policy gates - **`ExecutionControl`** — provided queue/rate/inflight implementation - **`NullEventBus`** — discards observability events for tests and examples The minimal quickstart uses an inline allow-all policy to stay small. That does -**not** mean `RiskEngine` is unused or dead. Use `RiskEngine` when you want the -built-in policy behavior. See `examples/core_step_quickstart.py` (minimal) and -`tests/runnable/core_step_with_risk_engine.py` (RiskEngine variant). +**not** mean the Risk Engine is unused or dead. Use `RiskEngine` when you want the +built-in Risk Engine policy behavior. See `examples/core_step_quickstart.py` (minimal) and +`examples/core_step_with_risk_engine.py` (Risk Engine variant). @@ -84,7 +84,7 @@ Configuration are comparable. Deterministic Core logic driven by canonical Event makes that logic reproducible and unit-testable without duplicating it in each Runtime. -This package does **not** guarantee profitable trading, perfect Backtesting/Live +This package does **not** guarantee profitable trading, perfect Backtesting and Live equality, or identical fills. It **does** remove a major class of drift: the decision engine itself. Wall-clock scheduling, Venue behavior, Venue Adapter mapping, latency, liquidity, market-data quality, and infrastructure failure modes @@ -167,10 +167,10 @@ Run the quickstart python examples/core_step_quickstart.py ``` -Optional RiskEngine policy example (same Pipeline, built-in policy): +Optional Risk Engine policy example (same Pipeline, built-in policy): ```bash -python tests/runnable/core_step_with_risk_engine.py +python examples/core_step_with_risk_engine.py ``` or minimal shape: @@ -198,7 +198,9 @@ print(result.generated_intents, result.dispatchable_intents) # Expected: () () — no Strategy or Risk Engine/Execution Control path in this snippet. ``` -See `examples/core_step_quickstart.py` for a full runnable walkthrough. +See `examples/core_step_quickstart.py` for a full runnable walkthrough and +[`docs/how-to/use-policy-evaluator.md`](docs/how-to/use-policy-evaluator.md) for policy extension points. +Planned U3 cleanup candidates: [`docs/roadmap/dead-code-cleanup-candidates.md`](docs/roadmap/dead-code-cleanup-candidates.md). @@ -241,7 +243,7 @@ Runtime dispatches Intents into Orders | State reduction and ordering | Venue Adapters and transport | | Strategy evaluation boundary | adapter-side Execution | | candidate Intents and reconciliation | credentials/env wiring | -| Risk Engine (policy) | Backtesting/Live orchestration | +| Risk Engine (policy) | Backtesting and Live orchestration | | Execution Control | Kubernetes/deployment | | `CoreStepResult` decision contract | Runtime lifecycle glue | @@ -272,7 +274,7 @@ copy of decision logic per environment. | `process_event_entry` | Reduce one `EventStreamEntry` into `StrategyState` | | `process_canonical_event` | Reduce one canonical Event into `StrategyState` | | `PolicyIntentEvaluator` | Protocol for policy admission (`evaluate_policy_intent`) | -| `RiskEngine` | Convenience `PolicyIntentEvaluator` implementation | +| Risk Engine (`RiskEngine`) | Convenience `PolicyIntentEvaluator` implementation | ## CoreWakeupStep semantics @@ -284,7 +286,7 @@ canonical entries, and Core reduces them in that order before making one decisio - `run_core_wakeup_step` handles an ordered batch of `EventStreamEntry` values. - Runtime is responsible for normalizing and ordering simultaneous raw inputs. - Core reduces all wakeup entries in order, evaluates Strategy once on the final State, - then runs Policy Admission and ExecutionControl once. + then runs Policy Admission and Execution Control once. - Runtime dispatches after the returned `CoreStepResult`. @@ -296,7 +298,7 @@ From root: ```bash python -m pip install -e ".[dev]" python examples/core_step_quickstart.py -python tests/runnable/core_step_with_risk_engine.py +python examples/core_step_with_risk_engine.py ./scripts/check.sh python -m build ``` diff --git a/U3_DEAD_CODE_CANDIDATES.md b/U3_DEAD_CODE_CANDIDATES.md deleted file mode 100644 index 19aa60a..0000000 --- a/U3_DEAD_CODE_CANDIDATES.md +++ /dev/null @@ -1,18 +0,0 @@ -# U3 dead-code cleanup candidates - -See Phase U1 audit. **Do not delete in U2.** Full table: candidates listed in the Phase U2 report -and in-repo paths below. - -| Candidate | U3 action | -| --- | --- | -| `StrategyState.pop_queued_intents` | Defer; audit `core-runtime` callers | -| `RiskEngine.build_constraints` | Defer; add Strategy contract test or remove | -| `fold_event_stream_entries` | Keep utility or demote export | -| `SlotKey`, `stable_slot_order_id` | Remove export or add MM example | -| `core/events/events.py` telemetry | Remove if no monorepo emitter | -| `core/events/sinks/sink_logging.py` | Remove or document optional | -| Apply detail record exports | Narrow `__all__` after usage audit | - -**Not for removal:** `RiskEngine`, `PolicyIntentEvaluator`, `FillEvent`, internal `RiskPolicy` / `ExecutionConstraintsPolicy`. - -Move to `docs/roadmap/` when `docs/` directory permissions allow writes. diff --git a/docs/code-map/core-pipeline-map.md b/docs/code-map/core-pipeline-map.md index a1ed6a0..ca5336e 100644 --- a/docs/code-map/core-pipeline-map.md +++ b/docs/code-map/core-pipeline-map.md @@ -48,8 +48,32 @@ Wakeup flow: (`CoreWakeupStrategyContext` carries all entries). 4. `run_core_wakeup_decision` snapshots queued intents once, combines generated + queued once, applies dominance/reconciliation once, Policy Admission once, and - ExecutionControl plan/apply once. + Execution Control plan/apply once. 5. `CoreStepResult.dispatchable_intents` is returned; Runtime dispatches later. `run_core_step` remains single-entry: one reduction, one step-level Strategy evaluation, one decision pass. + +## Internally wired vs externally supplied + +### Internally wired + +- Steps 1–3, 5, and 8 in the flow above (reduction, candidates, `CoreStepResult`) +- Policy admission **machinery** when `CorePolicyAdmissionContext` is provided +- Execution Control plan/apply **machinery** when apply context is provided + +### Externally supplied extension points + +- **Strategy** — `CoreStepStrategyEvaluator` or `CoreWakeupStrategyEvaluator` +- **Policy** — `PolicyIntentEvaluator` via `CorePolicyAdmissionContext` +- **Execution Control instance** — `ExecutionControl` via `CoreExecutionControlApplyContext` +- **Configuration** — optional `CoreConfiguration` +- **Event bus** — `StrategyState(event_bus=...)`; `NullEventBus` for standalone use + +### Convenience implementations + +- Risk Engine (`RiskEngine`) — optional built-in `PolicyIntentEvaluator` (`examples/core_step_with_risk_engine.py`) +- `ExecutionControl` — default queue/rate/inflight behavior (instance still supplied by caller) +- `NullEventBus` — no-op bus for tests and examples + +See `../reference/public-api.md` and `../how-to/use-policy-evaluator.md`. diff --git a/docs/flows/control-time-and-scheduling.md b/docs/flows/control-time-and-scheduling.md index 8f8c966..0d13594 100644 --- a/docs/flows/control-time-and-scheduling.md +++ b/docs/flows/control-time-and-scheduling.md @@ -9,7 +9,7 @@ to Execution Control deferral. - **ControlSchedulingObligation** — Non-canonical Core output: a structured hint that a **time-dependent** recheck may be useful. It is **not** part of the canonical Event Stream and does not mutate `StrategyState`. -- **ControlTimeEvent** — Canonical **control** category Event. It becomes part of +- **Control-Time Event** (`ControlTimeEvent`) — Canonical **control** category Event. It becomes part of the deterministic history only after the **Runtime** injects it as `EventStreamEntry` input (same ingestion path as other canonical Events). - **Inflight** — Core-side **Intent-operation** gating: a sendability / operation @@ -17,7 +17,7 @@ to Execution Control deferral. Intent operation is still awaiting **canonical execution feedback**. This is not the same as venue-side “order ownership”; Core models sendability for the decision Pipeline. -- **Rate-limit deferral** — Execution control blocks dispatch because the +- **Rate-limit deferral** — Execution Control blocks dispatch because the configured **token / time budget** for orders or cancels is not yet available at the apply clock (`now_ts_ns_local` in `CoreExecutionControlApplyContext`). - **Inflight deferral** — Dispatch is blocked because **inflight** gating applies, @@ -57,7 +57,7 @@ they are selected only in the mutable **apply** stage (`apply_execution_control_ - Runtimes **must not** mutate Core Queues (`StrategyState.queued_intents`, etc.) directly outside the normal Core step / Execution Control apply path. -- Queue flush / sendability decisions remain **ExecutionControl-owned** inside +- Queue flush / sendability decisions remain **Execution Control-owned** inside Core when `CoreExecutionControlApplyContext` is supplied to `run_core_step` / wakeup APIs. diff --git a/docs/how-to/update-core-step-pipeline.md b/docs/how-to/update-core-step-pipeline.md index 0b67c7a..e888ee0 100644 --- a/docs/how-to/update-core-step-pipeline.md +++ b/docs/how-to/update-core-step-pipeline.md @@ -30,6 +30,6 @@ When updating wakeup behavior: 1. Keep `run_core_wakeup_reduction` as reduction-only (no per-entry Strategy calls). 2. Use `CoreWakeupStrategyEvaluator` and `wakeup_strategy_evaluator=` for batch evaluation. -3. Preserve one Policy Admission and one ExecutionControl apply per wakeup in +3. Preserve one Policy Admission and one Execution Control apply per wakeup in `run_core_wakeup_decision`. 4. Add tests in `tests/semantics/test_core_wakeup_final_state.py`. diff --git a/docs/how-to/update-policy-and-execution-control.md b/docs/how-to/update-policy-and-execution-control.md index 3a825be..0768b97 100644 --- a/docs/how-to/update-policy-and-execution-control.md +++ b/docs/how-to/update-policy-and-execution-control.md @@ -9,7 +9,9 @@ The Risk Engine (policy) and Execution Control are separate deterministic phases - Core integration: `core/domain/policy_risk_decision.py` and `run_core_step` policy phase - Built-in policy-only evaluator: - `core/risk/risk_engine.py` + `core/risk/risk_engine.py` (public Risk Engine class `RiskEngine`; internal `RiskPolicy` / `ExecutionConstraintsPolicy`) +- User guide: + `use-policy-evaluator.md` When updating Risk Engine policy behavior: diff --git a/docs/how-to/use-policy-evaluator.md b/docs/how-to/use-policy-evaluator.md new file mode 100644 index 0000000..9d9e071 --- /dev/null +++ b/docs/how-to/use-policy-evaluator.md @@ -0,0 +1,54 @@ +# How to use a policy evaluator + +Core policy admission is optional. When you pass `CorePolicyAdmissionContext`, Core +calls your evaluator for each **generated** candidate intent (queued candidates +passthrough unchanged). + +## Extension point: `PolicyIntentEvaluator` + +Root export: `tradingchassis_core.PolicyIntentEvaluator` + +Implement: + +```python +def evaluate_policy_intent( + self, + *, + intent: OrderIntent, + state: StrategyState, + now_ts_ns_local: int, +) -> tuple[bool, str | None]: + ... +``` + +Pass the instance via `CorePolicyAdmissionContext(policy_evaluator=..., now_ts_ns_local=...)`. + +Any object satisfying this contract works. Core does not require `RiskEngine`. + +## Convenience implementation: Risk Engine (`RiskEngine`) + +The built-in **Risk Engine** (`RiskEngine`) implements `PolicyIntentEvaluator` with +policy gates (trading enabled, max loss, normalization, hard limits). Configure +with `RiskConfig`. + +Runnable example: + +```bash +cd core +python examples/core_step_with_risk_engine.py +``` + +Minimal quickstart (`examples/core_step_quickstart.py`) uses an inline allow-all +policy to stay small. That does not mean the Risk Engine is unused. + +## Execution Control apply + +Policy admission alone does not mutate queues or produce dispatchables. Also pass +`CoreExecutionControlApplyContext` with a supplied `ExecutionControl` instance and +set `activate_dispatchable_outputs=True` when you want `CoreStepResult.dispatchable_intents`. + +See also: + +- `reference/public-api.md` +- `code-map/core-pipeline-map.md` +- `update-policy-and-execution-control.md` diff --git a/docs/index.md b/docs/index.md index f7e8365..ea5cc1e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,6 +12,8 @@ This documentation set describes the standalone clean Core package baseline. - `how-to/add-canonical-event.md`: extending canonical Event contracts - `how-to/update-core-step-pipeline.md`: changing CoreStep/CoreWakeupStep behavior - `how-to/update-policy-and-execution-control.md`: changing Risk Engine / Execution Control behavior +- `how-to/use-policy-evaluator.md`: `PolicyIntentEvaluator`, `RiskEngine`, and examples +- `roadmap/dead-code-cleanup-candidates.md`: U3 removal candidates (audit before delete) ## Package Purpose @@ -38,7 +40,15 @@ canonical contracts, State reduction, and step-level decision outputs. Pydantic contract models in `tradingchassis_core/core/domain/types.py` are the source of truth for canonical Event/Intent schemas. +## Extension points (summary) + +- **Supplied by Runtime/tests:** Strategy evaluators, `PolicyIntentEvaluator`, `ExecutionControl`, `CoreConfiguration`, `EventBus` +- **Convenience:** `RiskEngine`, `NullEventBus` +- **Wired inside Core:** reduction, candidate reconciliation, policy/EC mechanisms, `CoreStepResult` + +Examples: `examples/core_step_quickstart.py` (inline policy), `examples/core_step_with_risk_engine.py` (Risk Engine). + ## Out of Scope - Runtime orchestration and Order lifecycle ownership -- Venue Adapters, Backtesting/Live I/O, external dispatch +- Venue Adapters, Backtesting and Live I/O, external dispatch diff --git a/docs/reference/public-api.md b/docs/reference/public-api.md index ff7a77f..4ccbaa1 100644 --- a/docs/reference/public-api.md +++ b/docs/reference/public-api.md @@ -2,6 +2,45 @@ The public package boundary is the `tradingchassis_core` root import. +## Internally wired vs externally supplied + +### Internally wired (when step APIs run) + +These run inside Core when you call `run_core_step` / wakeup APIs (no substitute +implementation required): + +- `process_event_entry` / `process_canonical_event` and canonical reducers +- Candidate combination, dominance, and reconciliation +- Policy admission **mechanism** when `CorePolicyAdmissionContext` is provided +- Execution Control plan/apply **mechanism** when policy + apply contexts are provided +- `CoreStepResult` / `CoreStepDecision` production + +### Externally supplied extension points + +| Symbol | Role | +| --- | --- | +| `CoreStepStrategyEvaluator` / `CoreWakeupStrategyEvaluator` | Strategy evaluation | +| `PolicyIntentEvaluator` | Policy admission (`evaluate_policy_intent`) via `CorePolicyAdmissionContext` | +| `ExecutionControl` | Queue/rate/inflight apply via `CoreExecutionControlApplyContext` | +| `CoreConfiguration` | Optional instrument metadata for positioned market reduction | +| `EventBus` / `NullEventBus` | `StrategyState` requires a bus; use `NullEventBus` for standalone Core | + +### Convenience implementations (optional) + +| Symbol | Role | +| --- | --- | +| Risk Engine (`RiskEngine`) | Built-in `PolicyIntentEvaluator` (not wired by default) | +| `ExecutionControl` | Default Execution Control apply implementation (you still supply an instance) | +| `NullEventBus` | Discards events for tests and examples | + +**Internal (not public extension points):** `RiskPolicy`, `ExecutionConstraintsPolicy`, +and other modules under `core/risk/` except `RiskEngine` / `RiskConfig`. + +Examples: + +- Minimal inline policy: `examples/core_step_quickstart.py` +- Built-in Risk Engine policy: `examples/core_step_with_risk_engine.py` + ## Canonical Events - `MarketEvent` @@ -23,21 +62,30 @@ The public package boundary is the `tradingchassis_core` root import. - `EventStreamEntry` - `ProcessingPosition` -- `CorePolicyAdmissionContext` -- `CoreExecutionControlApplyContext` +- `CorePolicyAdmissionContext` (holds `PolicyIntentEvaluator`) +- `CoreExecutionControlApplyContext` (holds `ExecutionControl`) - `CoreStepDecision` - `CoreStepResult` - `CoreWakeupReductionResult` - `CoreWakeupStrategyContext` - `CoreWakeupStrategyEvaluator` +## Policy and risk + +- `PolicyIntentEvaluator` (protocol) +- `PolicyRiskDecision` +- `PolicyAdmissionResult` +- `PolicyRejectedCandidate` +- `RiskEngine` (convenience `PolicyIntentEvaluator`) +- `RiskConfig` +- `RiskConstraints` (data model; often built for Strategy via `RiskEngine.build_constraints`) + ## Supporting deterministic models - `CoreConfiguration` - `StrategyState` - `CandidateIntentRecord` - `CandidateIntentOrigin` -- `PolicyRiskDecision` - `ExecutionControlDecision` - `ExecutionControl` - `ControlSchedulingObligation` (non-canonical; **rate-limit** recheck hint in the @@ -51,12 +99,12 @@ The public package boundary is the `tradingchassis_core` root import. - `ReplaceOrderIntent` - `Price` - `Quantity` +- `NotionalLimits` ## Runtime-safe utilities - `NullEventBus` -- `RiskEngine` (Risk Engine; policy-only) -- `RiskConfig` +- `fold_event_stream_entries` (batch reduction helper) ## Publicly absent by design @@ -67,3 +115,4 @@ The public package boundary is the `tradingchassis_core` root import. - `OrderStateEvent` - `DerivedFillEvent` - `VenueAdapter` / `VenuePolicy` +- `RiskPolicy` / `ExecutionConstraintsPolicy` (internal to `RiskEngine`) diff --git a/docs/roadmap/dead-code-cleanup-candidates.md b/docs/roadmap/dead-code-cleanup-candidates.md new file mode 100644 index 0000000..5673f2a --- /dev/null +++ b/docs/roadmap/dead-code-cleanup-candidates.md @@ -0,0 +1,85 @@ +# U3 dead-code cleanup candidates + +Phase U1/U2 identified components that may be removable in U3. **Do not delete until +evidence is confirmed** (including a `core-runtime` audit where noted). + +## Candidates + +### `StrategyState.pop_queued_intents` + +| Field | Detail | +| --- | --- | +| **Current evidence** | Defined in `core/domain/state.py`; no callers in Core source, tests, or examples | +| **Possible purpose** | Legacy queue drain API; Execution Control uses `pop_queued_intents_for_order` and `merge_intents_into_queue` | +| **Keep if** | Runtime or external tools call it | +| **Delete in U3 if** | Monorepo search shows zero callers | +| **Recommended action** | Defer; audit `core-runtime` before removal | + +### `RiskEngine.build_constraints` + +| Field | Detail | +| --- | --- | +| **Current evidence** | `risk_engine.py` only; no Core/tests/examples callers | +| **Possible purpose** | Build `RiskConstraints` for Strategy evaluation from `RiskConfig` | +| **Keep if** | Documented Strategy contract; add test showing evaluator consumption | +| **Delete in U3 if** | Strategy never uses `RiskConstraints` in clean Core/Runtime | +| **Recommended action** | Defer; pair with Strategy integration example or remove method | + +### `fold_event_stream_entries` + +| Field | Detail | +| --- | --- | +| **Current evidence** | Root export; zero in-repo usage | +| **Possible purpose** | Ergonomic batch reduction helper for replay harnesses | +| **Keep if** | Add test + docs example | +| **Delete in U3 if** | Runtimes always loop `process_event_entry` | +| **Recommended action** | Keep as utility or demote from `__all__` | + +### `SlotKey`, `stable_slot_order_id` + +| Field | Detail | +| --- | --- | +| **Current evidence** | Root export; zero usage in Core/tests/examples | +| **Possible purpose** | Deterministic market-making client order IDs | +| **Keep if** | Add MM strategy example using slots | +| **Delete in U3 if** | No monorepo consumer after search | +| **Recommended action** | Remove export or move to optional helper module | + +### Telemetry models in `core/events/events.py` + +| Field | Detail | +| --- | --- | +| **Current evidence** | Listed in `event_model.TELEMETRY_EVENT_TYPES`; not exported; not reduced by Core | +| **Possible purpose** | Future observability bus payloads (`RiskDecisionEvent`, `DerivedPnLEvent`, etc.) | +| **Keep if** | Runtime emits via custom sinks | +| **Delete in U3 if** | No emitter/listener in monorepo | +| **Recommended action** | Remove module or mark internal-only | + +### `core/events/sinks/sink_logging.py` + +| Field | Detail | +| --- | --- | +| **Current evidence** | No imports in Core | +| **Possible purpose** | Example `EventSink` for debugging | +| **Keep if** | Documented in how-to for custom buses | +| **Delete in U3 if** | Unused and duplicated by Runtime logging | +| **Recommended action** | Remove or document as optional pattern | + +### Exported apply detail records + +(`ExecutionControlBlockedRecord`, `ExecutionControlDispatchableRecord`, `ExecutionControlHandledRecord`, `ExecutionControlApplyResult`) + +| Field | Detail | +| --- | --- | +| **Current evidence** | Used by `apply_execution_control_plan`; typical consumers read `CoreStepResult` / `CoreStepDecision` | +| **Possible purpose** | Advanced introspection of the apply stage | +| **Keep if** | External tools depend on root export | +| **Delete in U3 if** | Only `CoreStepDecision` needed publicly | +| **Recommended action** | Narrow `__all__` after usage audit | + +## Not candidates for U3 removal + +- **Risk Engine** (`RiskEngine`) — public convenience `PolicyIntentEvaluator` (see `examples/core_step_with_risk_engine.py`) +- **`PolicyIntentEvaluator`** — root-exported extension protocol +- **`FillEvent`** — canonical reducer with tests in `tests/semantics/test_fill_event_reduction.py` +- **`RiskPolicy`**, **`ExecutionConstraintsPolicy`** — internal helpers used by `RiskEngine`; not public extension points diff --git a/tests/runnable/core_step_with_risk_engine.py b/examples/core_step_with_risk_engine.py similarity index 76% rename from tests/runnable/core_step_with_risk_engine.py rename to examples/core_step_with_risk_engine.py index 9a8b6cc..d57f0ea 100644 --- a/tests/runnable/core_step_with_risk_engine.py +++ b/examples/core_step_with_risk_engine.py @@ -1,7 +1,11 @@ -"""Core-only step using the provided RiskEngine as policy evaluator. +"""Core-only example: built-in Risk Engine as ``PolicyIntentEvaluator``. -See ``examples/core_step_quickstart.py`` for the minimal inline allow-all policy variant. -Run: ``python tests/runnable/core_step_with_risk_engine.py`` from the ``core/`` directory. +Demonstrates the full policy + Execution Control apply path. The main quickstart +(``examples/core_step_quickstart.py``) intentionally uses a minimal inline +allow-all policy; use this example when you want the provided Risk Engine gates. + +Core returns ``dispatchable_intents``; a Runtime performs Execution and Venue +dispatch later. Core does not dispatch externally. """ from __future__ import annotations @@ -9,16 +13,16 @@ import sys from pathlib import Path -_CORE_ROOT = Path(__file__).resolve().parents[2] -if str(_CORE_ROOT) not in sys.path: - sys.path.insert(0, str(_CORE_ROOT)) +if __package__ in (None, ""): + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) -import tradingchassis_core as tc # noqa: E402 -from tradingchassis_core.core.domain.types import NotionalLimits # noqa: E402 +import tradingchassis_core as tc +from tradingchassis_core.core.domain.types import NotionalLimits INSTRUMENT = "BTC-USDC-PERP" +# Strategy Evaluator: emits one generated Intent for this Core step. class _OneIntentEvaluator: def evaluate(self, context: object) -> list[tc.NewOrderIntent]: _ = context @@ -39,6 +43,7 @@ def evaluate(self, context: object) -> list[tc.NewOrderIntent]: def _control_entry(index: int, ts_ns_local: int) -> tc.EventStreamEntry: + # Control-Time Event as a simple canonical driver (scheduling obligations come from apply). return tc.EventStreamEntry( position=tc.ProcessingPosition(index=index), event=tc.ControlTimeEvent( @@ -84,6 +89,7 @@ def main() -> None: ts_ns_exch=999, ) + # Built-in Risk Engine (`RiskEngine`) satisfies PolicyIntentEvaluator for policy admission. policy_engine = tc.RiskEngine(_risk_config()) result = tc.run_core_step( state, @@ -100,7 +106,7 @@ def main() -> None: ), ) - print("CoreStep with RiskEngine (Core-only; Runtime dispatches later)") + print("CoreStep with Risk Engine (Core-only; Runtime dispatches later)") print("generated:", [i.client_order_id for i in result.generated_intents]) print("dispatchable:", [i.client_order_id for i in result.dispatchable_intents]) diff --git a/tests/docs/extension-points.md b/tests/docs/extension-points.md deleted file mode 100644 index 61f0981..0000000 --- a/tests/docs/extension-points.md +++ /dev/null @@ -1,23 +0,0 @@ -# Core extension points (supplement) - -Canonical copies live under `core/docs/` when writable. This file mirrors U2 documentation updates. - -## Externally supplied - -- `CoreStepStrategyEvaluator` / `CoreWakeupStrategyEvaluator` -- `PolicyIntentEvaluator` (root export) via `CorePolicyAdmissionContext` -- `ExecutionControl` via `CoreExecutionControlApplyContext` -- `CoreConfiguration` -- `NullEventBus` / custom `EventBus` for `StrategyState` - -## Convenience implementations - -- `RiskEngine` — optional `PolicyIntentEvaluator` -- `ExecutionControl` — optional apply implementation -- `NullEventBus` — standalone tests/examples - -## Internally wired - -Reduction, candidate reconciliation, policy admission mechanism, Execution Control plan/apply mechanism, `CoreStepResult`. - -See `README.md` and `U3_DEAD_CODE_CANDIDATES.md` at the `core/` root. diff --git a/tests/semantics/test_runnable_risk_engine_example.py b/tests/semantics/test_runnable_risk_engine_example.py index 0679554..3443c7e 100644 --- a/tests/semantics/test_runnable_risk_engine_example.py +++ b/tests/semantics/test_runnable_risk_engine_example.py @@ -7,7 +7,7 @@ from pathlib import Path _CORE_ROOT = Path(__file__).resolve().parents[2] -_EXAMPLE = _CORE_ROOT / "tests" / "runnable" / "core_step_with_risk_engine.py" +_EXAMPLE = _CORE_ROOT / "examples" / "core_step_with_risk_engine.py" def test_runnable_risk_engine_example_exits_zero() -> None: From 78dec005cde7cc2d196cf16115af0084173075d6 Mon Sep 17 00:00:00 2001 From: bxvtr Date: Fri, 15 May 2026 14:27:14 +0000 Subject: [PATCH 4/5] refactor(core): remove confirmed dead code and narrow exports --- CHANGELOG.md | 10 +- docs/code-map/repository-map.md | 7 +- docs/reference/public-api.md | 14 +-- docs/roadmap/dead-code-cleanup-candidates.md | 95 +++++-------------- tests/semantics/test_public_api_clean.py | 7 ++ tradingchassis_core/__init__.py | 16 ---- tradingchassis_core/core/domain/__init__.py | 14 --- .../core/domain/event_model.py | 15 --- .../core/domain/intent_combination.py | 16 ---- tradingchassis_core/core/domain/processing.py | 21 +--- tradingchassis_core/core/domain/state.py | 33 ------- tradingchassis_core/core/events/events.py | 46 --------- 12 files changed, 51 insertions(+), 243 deletions(-) delete mode 100644 tradingchassis_core/core/events/events.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 44e0822..dc57654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,20 @@ This changelog starts from the clean Core package baseline. ## [Unreleased] +### Removed + +- `StrategyState.pop_queued_intents` (unused; Execution Control uses per-order queue helpers). +- `fold_event_stream_entries` and root export (use `process_event_entry` in order). +- Unused telemetry models in `core/events/events.py`. +- Unused `combine_candidate_intents` helper. +- Root exports for Execution Control apply detail types and `apply_execution_control_plan` (internal Execution Control apply stage). + ### Added - Deterministic `run_core_step` and `run_core_wakeup_step` architecture. - CoreWakeupStep final Strategy evaluation: reduce all entries, then `CoreWakeupStrategyEvaluator` once. - Canonical Event input models and `EventStreamEntry`/`ProcessingPosition`. -- Intent candidate record Pipeline with dominance/reconciliation. +- Intent Pipeline candidate records with dominance/reconciliation. - Risk Engine (policy-only) admission and Execution Control plan/apply integration. - `CoreStepResult.dispatchable_intents` and `ControlSchedulingObligation` outputs. - Core-only quickstart example and focused semantics test coverage. diff --git a/docs/code-map/repository-map.md b/docs/code-map/repository-map.md index 7a1044f..d3c3d34 100644 --- a/docs/code-map/repository-map.md +++ b/docs/code-map/repository-map.md @@ -9,12 +9,13 @@ High-level map for the standalone Core package. Pipeline orchestration - `tradingchassis_core/core/risk/`: policy-only Risk Engine evaluator/config - `tradingchassis_core/core/execution_control/`: Execution Control primitives -- `tradingchassis_core/core/events/`: internal Event bus/sink utilities +- `tradingchassis_core/core/events/`: Event bus/sink utilities (`NullEventBus`; `LoggingEventSink` for Runtime) ## Tests and examples - `tests/semantics/`: focused contract and deterministic behavior tests -- `examples/core_step_quickstart.py`: public-import quickstart +- `examples/core_step_quickstart.py`: minimal inline-policy quickstart +- `examples/core_step_with_risk_engine.py`: Risk Engine policy quickstart ## Top-level package docs and metadata @@ -30,7 +31,7 @@ Core owns: - canonical Events and Processing Order contracts - deterministic reduction and step decisions -- Intent candidate, Risk Engine (policy), Execution Control outputs +- Intent candidate records, Risk Engine (policy), Execution Control outputs Core does not own: diff --git a/docs/reference/public-api.md b/docs/reference/public-api.md index 4ccbaa1..ccecdb6 100644 --- a/docs/reference/public-api.md +++ b/docs/reference/public-api.md @@ -6,12 +6,12 @@ The public package boundary is the `tradingchassis_core` root import. ### Internally wired (when step APIs run) -These run inside Core when you call `run_core_step` / wakeup APIs (no substitute +These run inside Core when you call `run_core_step` / CoreWakeupStep APIs (no substitute implementation required): - `process_event_entry` / `process_canonical_event` and canonical reducers - Candidate combination, dominance, and reconciliation -- Policy admission **mechanism** when `CorePolicyAdmissionContext` is provided +- Policy Admission **mechanism** when `CorePolicyAdmissionContext` is provided - Execution Control plan/apply **mechanism** when policy + apply contexts are provided - `CoreStepResult` / `CoreStepDecision` production @@ -20,7 +20,7 @@ implementation required): | Symbol | Role | | --- | --- | | `CoreStepStrategyEvaluator` / `CoreWakeupStrategyEvaluator` | Strategy evaluation | -| `PolicyIntentEvaluator` | Policy admission (`evaluate_policy_intent`) via `CorePolicyAdmissionContext` | +| `PolicyIntentEvaluator` | Policy Admission (`evaluate_policy_intent`) via `CorePolicyAdmissionContext` | | `ExecutionControl` | Queue/rate/inflight apply via `CoreExecutionControlApplyContext` | | `CoreConfiguration` | Optional instrument metadata for positioned market reduction | | `EventBus` / `NullEventBus` | `StrategyState` requires a bus; use `NullEventBus` for standalone Core | @@ -76,7 +76,7 @@ Examples: - `PolicyRiskDecision` - `PolicyAdmissionResult` - `PolicyRejectedCandidate` -- `RiskEngine` (convenience `PolicyIntentEvaluator`) +- Risk Engine (`RiskEngine`) (convenience `PolicyIntentEvaluator`) - `RiskConfig` - `RiskConstraints` (data model; often built for Strategy via `RiskEngine.build_constraints`) @@ -104,7 +104,6 @@ Examples: ## Runtime-safe utilities - `NullEventBus` -- `fold_event_stream_entries` (batch reduction helper) ## Publicly absent by design @@ -115,4 +114,7 @@ Examples: - `OrderStateEvent` - `DerivedFillEvent` - `VenueAdapter` / `VenuePolicy` -- `RiskPolicy` / `ExecutionConstraintsPolicy` (internal to `RiskEngine`) +- `RiskPolicy` / `ExecutionConstraintsPolicy` (internal to Risk Engine / `RiskEngine`) +- `fold_event_stream_entries` (removed U3; loop `process_event_entry` instead) +- `apply_execution_control_plan` and apply detail record types (internal; use `CoreStepResult`) +- Telemetry event types formerly in `core/events/events.py` (removed U3) diff --git a/docs/roadmap/dead-code-cleanup-candidates.md b/docs/roadmap/dead-code-cleanup-candidates.md index 5673f2a..945c14c 100644 --- a/docs/roadmap/dead-code-cleanup-candidates.md +++ b/docs/roadmap/dead-code-cleanup-candidates.md @@ -1,85 +1,34 @@ -# U3 dead-code cleanup candidates +# U3 dead-code cleanup — status -Phase U1/U2 identified components that may be removable in U3. **Do not delete until -evidence is confirmed** (including a `core-runtime` audit where noted). +Phase U3 audit and cleanup (completed). This document records what was removed, +what was kept, and what remains deferred. -## Candidates +## Removed in U3 -### `StrategyState.pop_queued_intents` - -| Field | Detail | -| --- | --- | -| **Current evidence** | Defined in `core/domain/state.py`; no callers in Core source, tests, or examples | -| **Possible purpose** | Legacy queue drain API; Execution Control uses `pop_queued_intents_for_order` and `merge_intents_into_queue` | -| **Keep if** | Runtime or external tools call it | -| **Delete in U3 if** | Monorepo search shows zero callers | -| **Recommended action** | Defer; audit `core-runtime` before removal | - -### `RiskEngine.build_constraints` - -| Field | Detail | -| --- | --- | -| **Current evidence** | `risk_engine.py` only; no Core/tests/examples callers | -| **Possible purpose** | Build `RiskConstraints` for Strategy evaluation from `RiskConfig` | -| **Keep if** | Documented Strategy contract; add test showing evaluator consumption | -| **Delete in U3 if** | Strategy never uses `RiskConstraints` in clean Core/Runtime | -| **Recommended action** | Defer; pair with Strategy integration example or remove method | - -### `fold_event_stream_entries` - -| Field | Detail | +| Item | Rationale | | --- | --- | -| **Current evidence** | Root export; zero in-repo usage | -| **Possible purpose** | Ergonomic batch reduction helper for replay harnesses | -| **Keep if** | Add test + docs example | -| **Delete in U3 if** | Runtimes always loop `process_event_entry` | -| **Recommended action** | Keep as utility or demote from `__all__` | +| `StrategyState.pop_queued_intents` | No callers in the Core Pipeline; Runtime tests (`core-runtime`) only monkeypatch the name to assert it is **not** invoked | +| `fold_event_stream_entries` | Zero callers; batch reduction is `process_event_entry` in a loop | +| `core/events/events.py` telemetry models | Never emitted; only referenced by unused `TELEMETRY_EVENT_TYPES` | +| `combine_candidate_intents` | Unused wrapper around `combine_candidate_intent_records` | +| Root exports: `apply_execution_control_plan`, `ExecutionControlApplyContext`, `ExecutionControlApplyResult`, `ExecutionControlBlockedRecord`, `ExecutionControlDispatchableRecord`, `ExecutionControlHandledRecord` | No monorepo consumers outside Core; pipeline uses `CoreStepResult` / `CoreStepDecision` | -### `SlotKey`, `stable_slot_order_id` +## Kept (monorepo usage) -| Field | Detail | +| Item | Rationale | | --- | --- | -| **Current evidence** | Root export; zero usage in Core/tests/examples | -| **Possible purpose** | Deterministic market-making client order IDs | -| **Keep if** | Add MM strategy example using slots | -| **Delete in U3 if** | No monorepo consumer after search | -| **Recommended action** | Remove export or move to optional helper module | - -### Telemetry models in `core/events/events.py` - -| Field | Detail | -| --- | --- | -| **Current evidence** | Listed in `event_model.TELEMETRY_EVENT_TYPES`; not exported; not reduced by Core | -| **Possible purpose** | Future observability bus payloads (`RiskDecisionEvent`, `DerivedPnLEvent`, etc.) | -| **Keep if** | Runtime emits via custom sinks | -| **Delete in U3 if** | No emitter/listener in monorepo | -| **Recommended action** | Remove module or mark internal-only | - -### `core/events/sinks/sink_logging.py` - -| Field | Detail | -| --- | --- | -| **Current evidence** | No imports in Core | -| **Possible purpose** | Example `EventSink` for debugging | -| **Keep if** | Documented in how-to for custom buses | -| **Delete in U3 if** | Unused and duplicated by Runtime logging | -| **Recommended action** | Remove or document as optional pattern | - -### Exported apply detail records +| `RiskEngine.build_constraints` | Called from Runtime (`core-runtime` `strategy_runner.py`) for Strategy evaluation | +| `SlotKey`, `stable_slot_order_id` | Used by Runtime (`core-runtime` `debug_strategy.py`) | +| `sink_logging.LoggingEventSink` | Used by Runtime (`core-runtime` `strategy_runner.py`) | +| Risk Engine (`RiskEngine`), `RiskPolicy`, `ExecutionConstraintsPolicy`, `PolicyIntentEvaluator`, canonical Events, Core step APIs | Active extension points / Pipeline | -(`ExecutionControlBlockedRecord`, `ExecutionControlDispatchableRecord`, `ExecutionControlHandledRecord`, `ExecutionControlApplyResult`) +## Deferred (intentionally not removed) -| Field | Detail | +| Item | Evidence needed before future removal | | --- | --- | -| **Current evidence** | Used by `apply_execution_control_plan`; typical consumers read `CoreStepResult` / `CoreStepDecision` | -| **Possible purpose** | Advanced introspection of the apply stage | -| **Keep if** | External tools depend on root export | -| **Delete in U3 if** | Only `CoreStepDecision` needed publicly | -| **Recommended action** | Narrow `__all__` after usage audit | +| `PolicyAdmissionResult` / `PolicyRejectedCandidate` at root | Exported for Policy Admission introspection; only used inside Core today — narrow export if no external adopters | +| `SlotKey` in Core-only docs | Document as Runtime/strategy helper if Core export is ever questioned again | -## Not candidates for U3 removal +## Not candidates -- **Risk Engine** (`RiskEngine`) — public convenience `PolicyIntentEvaluator` (see `examples/core_step_with_risk_engine.py`) -- **`PolicyIntentEvaluator`** — root-exported extension protocol -- **`FillEvent`** — canonical reducer with tests in `tests/semantics/test_fill_event_reduction.py` -- **`RiskPolicy`**, **`ExecutionConstraintsPolicy`** — internal helpers used by `RiskEngine`; not public extension points +See U2 extension-point docs: Risk Engine, Execution Control, `FillEvent`, and Core step APIs remain public and tested. diff --git a/tests/semantics/test_public_api_clean.py b/tests/semantics/test_public_api_clean.py index 2cccc2f..a204efa 100644 --- a/tests/semantics/test_public_api_clean.py +++ b/tests/semantics/test_public_api_clean.py @@ -59,6 +59,13 @@ def test_public_api_does_not_expose_removed_compatibility_symbols() -> None: "".join(["decide_", "intents"]), "".join(["Venue", "Adapter"]), "".join(["Venue", "Policy"]), + "fold_event_stream_entries", + "apply_execution_control_plan", + "ExecutionControlApplyContext", + "ExecutionControlApplyResult", + "ExecutionControlBlockedRecord", + "ExecutionControlDispatchableRecord", + "ExecutionControlHandledRecord", ) for symbol in removed: assert not hasattr(tc, symbol) diff --git a/tradingchassis_core/__init__.py b/tradingchassis_core/__init__.py index 77905d6..52c928b 100644 --- a/tradingchassis_core/__init__.py +++ b/tradingchassis_core/__init__.py @@ -9,14 +9,6 @@ CandidateIntentRecord, ) from tradingchassis_core.core.domain.configuration import CoreConfiguration -from tradingchassis_core.core.domain.execution_control_apply import ( - ExecutionControlApplyContext, - ExecutionControlApplyResult, - ExecutionControlBlockedRecord, - ExecutionControlDispatchableRecord, - ExecutionControlHandledRecord, - apply_execution_control_plan, -) from tradingchassis_core.core.domain.execution_control_decision import ( ExecutionControlDecision, ) @@ -27,7 +19,6 @@ PolicyRiskDecision, ) from tradingchassis_core.core.domain.processing import ( - fold_event_stream_entries, process_canonical_event, process_event_entry, ) @@ -102,7 +93,6 @@ "EventStreamEntry", "process_canonical_event", "process_event_entry", - "fold_event_stream_entries", "run_core_step", "run_core_wakeup_reduction", "run_core_wakeup_decision", @@ -115,12 +105,6 @@ "CorePolicyAdmissionContext", "CoreWakeupReductionResult", "ExecutionControlDecision", - "ExecutionControlApplyContext", - "ExecutionControlApplyResult", - "ExecutionControlBlockedRecord", - "ExecutionControlDispatchableRecord", - "ExecutionControlHandledRecord", - "apply_execution_control_plan", "PolicyIntentEvaluator", "PolicyRiskDecision", "PolicyRejectedCandidate", diff --git a/tradingchassis_core/core/domain/__init__.py b/tradingchassis_core/core/domain/__init__.py index 9fbf55e..1966e7e 100644 --- a/tradingchassis_core/core/domain/__init__.py +++ b/tradingchassis_core/core/domain/__init__.py @@ -4,14 +4,6 @@ CandidateIntentOrigin, CandidateIntentRecord, ) -from tradingchassis_core.core.domain.execution_control_apply import ( - ExecutionControlApplyContext, - ExecutionControlApplyResult, - ExecutionControlBlockedRecord, - ExecutionControlDispatchableRecord, - ExecutionControlHandledRecord, - apply_execution_control_plan, -) from tradingchassis_core.core.domain.execution_control_decision import ExecutionControlDecision from tradingchassis_core.core.domain.policy_risk_decision import ( PolicyAdmissionResult, @@ -34,12 +26,6 @@ "CandidateIntentOrigin", "CandidateIntentRecord", "ExecutionControlDecision", - "ExecutionControlApplyContext", - "ExecutionControlApplyResult", - "ExecutionControlBlockedRecord", - "ExecutionControlDispatchableRecord", - "ExecutionControlHandledRecord", - "apply_execution_control_plan", "PolicyRiskDecision", "PolicyRejectedCandidate", "PolicyAdmissionResult", diff --git a/tradingchassis_core/core/domain/event_model.py b/tradingchassis_core/core/domain/event_model.py index 9f51443..f2afe8a 100644 --- a/tradingchassis_core/core/domain/event_model.py +++ b/tradingchassis_core/core/domain/event_model.py @@ -11,12 +11,6 @@ OrderExecutionFeedbackEvent, OrderSubmittedEvent, ) -from tradingchassis_core.core.events.events import ( - DerivedPnLEvent, - ExposureDerivedEvent, - OrderStateTransitionEvent, - RiskDecisionEvent, -) from tradingchassis_core.core.execution_control.types import ControlSchedulingObligation @@ -41,15 +35,6 @@ class CanonicalEventCategory(str, Enum): ControlTimeEvent: CanonicalEventCategory.CONTROL, } -TELEMETRY_EVENT_TYPES: frozenset[type[object]] = frozenset( - { - RiskDecisionEvent, - DerivedPnLEvent, - ExposureDerivedEvent, - OrderStateTransitionEvent, - } -) - NON_CANONICAL_CONTROL_HELPER_TYPES: frozenset[type[object]] = frozenset( {ControlSchedulingObligation} ) diff --git a/tradingchassis_core/core/domain/intent_combination.py b/tradingchassis_core/core/domain/intent_combination.py index 1222421..4ae2d88 100644 --- a/tradingchassis_core/core/domain/intent_combination.py +++ b/tradingchassis_core/core/domain/intent_combination.py @@ -35,22 +35,6 @@ def _dominance_rank(intent: OrderIntent) -> int: return 0 -def combine_candidate_intents( - *, - generated_intents: Sequence[OrderIntent], - queued_intents: Sequence[OrderIntent], -) -> tuple[OrderIntent, ...]: - """Compatibility helper returning only effective intent values. - - Prefer ``combine_candidate_intent_records`` when origin/provenance is needed. - """ - records = combine_candidate_intent_records( - generated_intents=generated_intents, - queued_intents=queued_intents, - ) - return tuple(record.intent for record in records) - - def combine_candidate_intent_records( *, generated_intents: Sequence[OrderIntent], diff --git a/tradingchassis_core/core/domain/processing.py b/tradingchassis_core/core/domain/processing.py index d15c8a0..b7303ec 100644 --- a/tradingchassis_core/core/domain/processing.py +++ b/tradingchassis_core/core/domain/processing.py @@ -15,7 +15,7 @@ from __future__ import annotations import math -from collections.abc import Iterable, Mapping +from collections.abc import Mapping from tradingchassis_core.core.domain.configuration import CoreConfiguration from tradingchassis_core.core.domain.event_model import ( @@ -238,22 +238,3 @@ def process_event_entry( position=entry.position, configuration=configuration, ) - - -def fold_event_stream_entries( - state: StrategyState, - entries: Iterable[EventStreamEntry], - *, - configuration: CoreConfiguration | None = None, -) -> StrategyState: - """Fold ordered EventStreamEntry values into the provided state. - - This utility is intentionally minimal and deterministic: - - entries are applied in caller-provided order; - - each entry is processed through ``process_event_entry``; - - errors from canonical/ordering validation are propagated unchanged; - - the same state instance is returned for ergonomic chaining. - """ - for entry in entries: - process_event_entry(state, entry, configuration=configuration) - return state diff --git a/tradingchassis_core/core/domain/state.py b/tradingchassis_core/core/domain/state.py index 65ee928..06a0821 100644 --- a/tradingchassis_core/core/domain/state.py +++ b/tradingchassis_core/core/domain/state.py @@ -575,36 +575,3 @@ def _matching_entries(key: str) -> list[QueuedIntent]: dropped.append(intent) return queued, replaced_in_queue, dropped - - def pop_queued_intents( - self, - instrument: str, - *, - max_items: int | None = None, - ) -> list[OrderIntent]: - q: deque[QueuedIntent] = self.queued_intents.setdefault(instrument, deque()) - if not q: - return [] - items = list(q) - items.sort(key=lambda qi: (qi.priority, qi.queued_at_ts_ns)) - - filtered: list[QueuedIntent] = [] - for qi in items: - if self.has_inflight(instrument, qi.intent.client_order_id): - continue - filtered.append(qi) - - selected = filtered if max_items is None else filtered[:max(0, max_items)] - if max_items is not None and max_items <= 0: - return [] - - selected_ids = {id(x) for x in selected} - out: list[OrderIntent] = [] - new_q: deque[QueuedIntent] = deque() - for qi in q: - if id(qi) in selected_ids: - out.append(qi.intent) - else: - new_q.append(qi) - self.queued_intents[instrument] = new_q - return out diff --git a/tradingchassis_core/core/events/events.py b/tradingchassis_core/core/events/events.py deleted file mode 100644 index 5f1fad1..0000000 --- a/tradingchassis_core/core/events/events.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Non-canonical telemetry records.""" - -from __future__ import annotations - -from dataclasses import dataclass - - -@dataclass(slots=True) -class OrderStateTransitionEvent: - """Observability payload for unexpected Order-State transitions.""" - - ts_ns_local: int - instrument: str - client_order_id: str - prev_state: str | None - next_state: str - - -@dataclass(slots=True) -class DerivedPnLEvent: - """Observability payload for derived realized-PnL changes.""" - - ts_ns_local: int - instrument: str - delta_pnl: float - cum_realized_pnl: float - - -@dataclass(slots=True) -class ExposureDerivedEvent: - """Observability payload for derived exposure changes.""" - - ts_ns_local: int - instrument: str - exposure: float - delta_exposure: float - - -@dataclass(slots=True) -class RiskDecisionEvent: - """Observability payload summarizing policy-risk outcomes.""" - - ts_ns_local: int - accepted: int - rejected: int - reject_reasons: dict[str, int] From c617882b6055ef1fc6f49e1cfeb675238386842d Mon Sep 17 00:00:00 2001 From: bxvtr Date: Fri, 15 May 2026 14:37:26 +0000 Subject: [PATCH 5/5] docs: improve graph readability --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3247218..fa19876 100644 --- a/README.md +++ b/README.md @@ -128,10 +128,10 @@ Execution, scheduling glue, and Control-Time Event injection when a Control Sche ```mermaid flowchart TB - R1["Runtime:
canonical Event"] --> Entry["EventStreamEntry:
canonical Event + ProcessingPosition"] - Entry --> Core["TradingChassis Core:
CoreStep / CoreWakeupStep"] - Core --> Result["CoreStepResult:
dispatchable Intents + Control Scheduling Obligation"] - Result --> R2["Runtime:
dispatch / scheduling / I/O"] + R1["**Runtime**
canonical Event"] --> Entry["**EventStreamEntry**
canonical Event + ProcessingPosition"] + Entry --> Core["**TradingChassis Core**
CoreStep / CoreWakeupStep"] + Core --> Result["**CoreStepResult**
dispatchable Intents + Control Scheduling Obligation"] + Result --> R2["**Runtime**
dispatch / scheduling / I/O"] ``` Core never replaces the Runtime: the Runtime is responsible for feeding canonical