Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/argo-launchers/run-backtest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ spec:
- name: image_tag
value: "${IMAGE_TAG}"
- name: experiment_config
value: "/usr/local/lib/python3.11/site-packages/core_runtime/argo/argo.json"
value: "/usr/local/lib/python3.11/site-packages/core_runtime/argo/bt_config_argo.json"
- name: scratch_root
value: "/mnt/scratch"
27 changes: 10 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Order, State, or Risk Engine.
Current local smoke is usable from the `core-runtime` repository root:

```bash
python -m core_runtime.local.backtest --config core_runtime/local/local.json
python -m core_runtime.local.backtest --config core_runtime/local/bt_config_local.json
```

Default output location:
Expand All @@ -67,14 +67,7 @@ From the `core-runtime` repository root:

```bash
python -m pip install -e .
python -m core_runtime.local.backtest --config core_runtime/local/local.json
```

If `tradingchassis_core` is not already resolvable in your environment, install `core` as a
sibling editable package in a monorepo workspace:

```bash
python -m pip install -e ../core
python -m core_runtime.local.backtest --config core_runtime/local/bt_config_local.json
```

---
Expand All @@ -83,8 +76,8 @@ python -m pip install -e ../core

| Mode | Entrypoint | Command shape | Notes |
| --- | --- | --- | --- |
| Local backtest | `core_runtime/local/backtest.py` | `python -m core_runtime.local.backtest --config core_runtime/local/local.json` | Main local runner. |
| Argo plan/run orchestration | `core_runtime/backtest/runtime/entrypoint.py` | `python -m core_runtime.backtest.runtime.entrypoint --config core_runtime/argo/argo.json --plan` | Planner and sweep-context emitter for Argo flow. |
| Local backtest | `core_runtime/local/backtest.py` | `python -m core_runtime.local.backtest --config core_runtime/local/bt_config_local.json` | Main local runner. |
| Argo plan/run orchestration | `core_runtime/backtest/runtime/entrypoint.py` | `python -m core_runtime.backtest.runtime.entrypoint --config core_runtime/argo/bt_config_argo.json --plan` | Planner and sweep-context emitter for Argo flow. |
| Sweep worker | `core_runtime/backtest/runtime/run_sweep.py` | `python -m core_runtime.backtest.runtime.run_sweep --context <path-to-sweep-json>` | Executes one sweep context. |

---
Expand All @@ -94,7 +87,7 @@ python -m pip install -e ../core
| Capability area | Status | Notes |
| --- | --- | --- |
| Canonical runtime paths | Active | `MarketEvent`, `OrderSubmittedEvent`, `ControlTimeEvent` |
| Compatibility paths | Active | Post-submission order/fill progression via snapshots, `OrderStateEvent`, and `DerivedFillEvent` |
| Runtime-local compatibility handling | Active | Raw venue order snapshots stay in runtime bookkeeping; Core receives canonical `OrderExecutionFeedbackEvent` (account-level only). |
| Deferred capabilities | Deferred | Runtime `FillEvent` ingress, `ExecutionFeedbackRecordSource`, replay/storage/Event Stream persistence, `ProcessingContext` |

---
Expand All @@ -115,11 +108,11 @@ python -m pip install -e ../core

---

## Compatibility paths
## Runtime-local compatibility handling

- snapshot-based post-submission progression
- `OrderStateEvent`
- `DerivedFillEvent`
- snapshot-based post-submission bookkeeping remains runtime-local
- Core ingestion uses account-level `OrderExecutionFeedbackEvent`
- no snapshot row payload is pushed into Core

---

Expand Down Expand Up @@ -160,7 +153,7 @@ tests/ Runtime tests and deterministic fixtures

Primary local config:

- `core_runtime/local/local.json`
- `core_runtime/local/bt_config_local.json`
- OCI config template (for local object storage auth setups): `core_runtime/local/oci.config.example`

Note: local JSON configs use cwd-relative paths for `tests/data/...` inputs and `.runtime/...`
Expand Down
2 changes: 1 addition & 1 deletion argo/templates/workflowtemplate-backtest-fanout.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ spec:

- name: experiment_config
description: "Path to experiment JSON inside the container"
value: /usr/local/lib/python3.11/site-packages/core_runtime/argo/argo.json
value: /usr/local/lib/python3.11/site-packages/core_runtime/argo/bt_config_argo.json

- name: scratch_root
description: "Scratch root inside the container"
Expand Down
File renamed without changes.
17 changes: 17 additions & 0 deletions core_runtime/backtest/adapters/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,23 @@ def read_orders_snapshot(self) -> tuple[Any, Any]:
"""Return (state_values, orders) from current snapshot boundary."""


class VenueAdapter(
VenueEventWaiter,
VenueClock,
MarketInputSource,
OrderSnapshotSource,
Protocol,
):
"""Composite runtime venue adapter contract for strategy runner."""

def record(self, recorder: Any) -> bool:
"""Persist current simulation state.

Returns:
True when recorder capacity is exhausted and no record was written.
"""


class OrderSubmissionGateway(Protocol):
"""Outbound order command submission capability.

Expand Down
13 changes: 10 additions & 3 deletions core_runtime/backtest/adapters/venue.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
if TYPE_CHECKING:
from hftbacktest import ROIVectorMarketDepthBacktest

from tradingchassis_core.core.ports.venue_adapter import VenueAdapter
from core_runtime.backtest.adapters.protocols import VenueAdapter


@dataclass(frozen=True)
Expand Down Expand Up @@ -43,7 +43,14 @@ def read_orders_snapshot(self) -> tuple[Any, Any]:
self.hbt.orders(self.asset_no),
)

def record(self, recorder: Any) -> None:
def record(self, recorder: Any) -> bool:
"""Record the current backtest state using the given recorder."""
# hftbacktest recorder is a thin wrapper exposing .recorder.record(hbt).
recorder.recorder.record(self.hbt)
try:
recorder.recorder.record(self.hbt)
except IndexError:
# hftbacktest Recorder has a fixed capacity and raises IndexError
# when the record buffer is exhausted. Runtime treats this as
# recording exhaustion and keeps the backtest loop alive.
return True
return False
8 changes: 8 additions & 0 deletions core_runtime/backtest/engine/event_stream_cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ def next_index(self) -> int:
def attempt_position(self) -> ProcessingPosition:
return ProcessingPosition(index=self._next_index)

def attempt_positions(self, count: int) -> tuple[ProcessingPosition, ...]:
if count < 0:
raise ValueError("count must be >= 0")
return tuple(
ProcessingPosition(index=self._next_index + offset)
for offset in range(count)
)

def commit_success(self, position: ProcessingPosition) -> None:
if position.index != self._next_index:
raise ValueError(
Expand Down
29 changes: 14 additions & 15 deletions core_runtime/backtest/engine/hft_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,12 @@

import importlib
from dataclasses import dataclass
from typing import TYPE_CHECKING

from hftbacktest import (
BacktestAsset,
Recorder,
ROIVectorMarketDepthBacktest,
)
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from tradingchassis_core.core.domain.configuration import CoreConfiguration
from tradingchassis_core.core.risk.risk_config import RiskConfig

from tradingchassis_core.strategies.base import Strategy
from tradingchassis_core.strategies.strategy_config import StrategyConfig

from core_runtime.backtest.adapters.execution import HftBacktestExecutionAdapter
from core_runtime.backtest.adapters.venue import HftBacktestVenueAdapter
from core_runtime.backtest.engine.engine_base import (
Expand All @@ -27,6 +18,7 @@
BacktestResult,
)
from core_runtime.backtest.engine.strategy_runner import HftStrategyRunner
from core_runtime.backtest.strategy_api import Strategy, StrategyConfig


# pylint: disable=too-many-instance-attributes
Expand Down Expand Up @@ -81,8 +73,10 @@ class HftBacktestConfig(BacktestConfig):
core_cfg: CoreConfiguration


def _build_backtester(engine_cfg: HftEngineConfig) -> ROIVectorMarketDepthBacktest:
def _build_backtester(engine_cfg: HftEngineConfig) -> Any:
"""Create an ROIVectorMarketDepthBacktest from the engine configuration."""
from hftbacktest import BacktestAsset, ROIVectorMarketDepthBacktest

asset = BacktestAsset()

# For now we assume file paths. Later this can be replaced with an S3 resolver.
Expand Down Expand Up @@ -126,10 +120,13 @@ def _load_strategy_class(self, class_path: str) -> type[Strategy]:
module_path, class_name = class_path.split(":")
module = importlib.import_module(module_path)
cls = getattr(module, class_name)

if not issubclass(cls, Strategy):
if not callable(getattr(cls, "on_feed", None)):
raise TypeError(
f"Loaded class {class_name} does not implement on_feed."
)
if not callable(getattr(cls, "on_order_update", None)):
raise TypeError(
f"Loaded class {class_name} is not a subclass of Strategy."
f"Loaded class {class_name} does not implement on_order_update."
)

return cls
Expand All @@ -150,6 +147,8 @@ def run(self) -> BacktestResult:
hbt = _build_backtester(engine_cfg)

# 2) Prepare recorder (single asset, record every step)
from hftbacktest import Recorder

recorder = Recorder(1, engine_cfg.max_steps)

# 3) Build strategy and runner
Expand Down Expand Up @@ -182,6 +181,6 @@ def run(self) -> BacktestResult:
"strategy_name": strategy_cfg.class_path,
"strategy_params": strategy_cfg.params,
"risk_scope": risk_cfg.scope,
"risk_params": risk_cfg.params,
"risk_params": risk_cfg.model_dump(),
},
)
Loading
Loading