From 59ac70d7a90e6bb9891e4cd7d6395c672de11281 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:01:44 +0200 Subject: [PATCH 01/16] docs: Add exporter architecture design spec Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...2026-07-06-exporter-architecture-design.md | 510 ++++++++++++++++++ 1 file changed, 510 insertions(+) create mode 100644 superpowers/specs/2026-07-06-exporter-architecture-design.md diff --git a/superpowers/specs/2026-07-06-exporter-architecture-design.md b/superpowers/specs/2026-07-06-exporter-architecture-design.md new file mode 100644 index 0000000..0a158bf --- /dev/null +++ b/superpowers/specs/2026-07-06-exporter-architecture-design.md @@ -0,0 +1,510 @@ +# Exporter Architecture Design + +**Date:** 2026-07-06 +**Status:** Approved +**Version:** 0.5.0 + +## Overview + +This design document describes the refactoring of data export functionality in the order-matching engine to separate domain logic from format-specific export logic. The current implementation embeds polars export methods (`to_frame()`) directly in domain classes (`Orders`, `ExecutedTrades`), creating tight coupling and preventing extensibility to other export formats. + +## Goals + +1. **Separation of Concerns:** Remove export logic from domain models so they remain focused on business logic +2. **Extensibility:** Enable support for multiple export formats (JSON, CSV, Arrow, databases) without modifying domain classes +3. **Optional Dependencies:** Make polars an optional dependency so the core matching engine has minimal requirements +4. **Backward Compatibility:** Maintain existing `to_frame()` API during a deprecation period +5. **Type Safety:** Use generics to ensure type-safe export implementations + +## Non-Goals + +- Changing the matching engine's core algorithms or data structures +- Modifying the pandera schema definitions (they remain unchanged) +- Adding new export formats in this phase (architecture only; implementations come later) +- Performance optimization of export operations + +## Current State + +### Problems + +1. **Tight Coupling:** `Orders` and `ExecutedTrades` classes import polars and pandera, making them dependent on data science libraries +2. **Single Format:** Only polars LazyFrame export is supported; adding CSV/JSON requires modifying domain classes +3. **Mandatory Dependencies:** Users who only need the matching engine must install polars/pandera +4. **Responsibility Violation:** Domain classes handle both business logic (order management) and presentation logic (data formatting) + +### Current Implementation + +```python +# orders.py +import polars as pl +from pandera.typing.polars import LazyFrame + +class Orders: + def to_frame(self) -> LazyFrame[OrderDataSchema]: + # 15 lines of polars-specific conversion logic + if len(self.orders) == 0: + return OrderDataSchema.empty().lazy() + data = [asdict(order) for order in self.orders] + # enum to string conversions... + return pl.LazyFrame(data) +``` + +Similar logic exists in `ExecutedTrades.to_frame()`. + +## Proposed Architecture + +### Module Structure + +``` +src/order_matching/ +├── exporters/ +│ ├── __init__.py # Exports Exporter, PolarsExporter +│ ├── base.py # Abstract base class +│ └── polars.py # Polars implementation +├── orders.py # Modified: thin wrapper method +├── executed_trades.py # Modified: thin wrapper method +└── schemas.py # Unchanged +``` + +Future exporters can be added as new files in the `exporters/` directory without touching existing code. + +### Abstract Interface + +**File:** `exporters/base.py` + +```python +from abc import ABC, abstractmethod +from typing import Generic, TypeVar + +T = TypeVar('T') + +class Exporter(ABC, Generic[T]): + """Abstract base class for data exporters. + + Type parameter T represents the output format type + (e.g., LazyFrame, dict, str for JSON/CSV). + """ + + @abstractmethod + def export_orders(self, orders: Orders) -> T: + """Convert Orders collection to target format. + + Parameters + ---------- + orders : Orders + Orders collection to export + + Returns + ------- + T + Exported data in target format + """ + pass + + @abstractmethod + def export_trades(self, trades: ExecutedTrades) -> T: + """Convert ExecutedTrades collection to target format. + + Parameters + ---------- + trades : ExecutedTrades + Trades collection to export + + Returns + ------- + T + Exported data in target format + """ + pass +``` + +**Design Decisions:** + +- **ABC over Protocol:** Using ABC ensures all exporters explicitly implement required methods +- **Generic Type Variable:** `T` provides compile-time type safety for return values +- **Stateless Methods:** Exporters are pure transformers with no internal state +- **Explicit Method Names:** `export_orders` and `export_trades` (not overloaded `export`) for clarity + +### Polars Implementation + +**File:** `exporters/polars.py` + +```python +from dataclasses import asdict +from typing import cast + +import polars as pl +from pandera.typing.polars import LazyFrame + +from order_matching.exporters.base import Exporter +from order_matching.schemas import OrderDataSchema, TradeDataSchema +from order_matching.orders import Orders +from order_matching.executed_trades import ExecutedTrades + + +class PolarsExporter(Exporter[LazyFrame]): + """Export collections to polars LazyFrame format.""" + + def export_orders(self, orders: Orders) -> LazyFrame[OrderDataSchema]: + """Export Orders to validated polars LazyFrame.""" + if len(orders) == 0: + return cast(LazyFrame[OrderDataSchema], OrderDataSchema.empty().lazy()) + + data = [asdict(order) for order in orders] + for d in data: + d[OrderDataSchema.side] = d[OrderDataSchema.side].name + d[OrderDataSchema.execution] = d[OrderDataSchema.execution].name + d[OrderDataSchema.status] = d[OrderDataSchema.status].name + return cast(LazyFrame[OrderDataSchema], pl.LazyFrame(data)) + + def export_trades(self, trades: ExecutedTrades) -> LazyFrame[TradeDataSchema]: + """Export ExecutedTrades to validated polars LazyFrame.""" + trade_list = trades.trades + if len(trade_list) == 0: + return cast(LazyFrame[TradeDataSchema], TradeDataSchema.empty().lazy()) + + data = [asdict(trade) for trade in trade_list] + for d in data: + d[TradeDataSchema.side] = d[TradeDataSchema.side].name + d[TradeDataSchema.execution] = d[TradeDataSchema.execution].name + return cast(LazyFrame[TradeDataSchema], pl.LazyFrame(data)) +``` + +**Implementation Notes:** + +- Logic is extracted verbatim from existing `to_frame()` methods +- Enum-to-string conversion remains in polars exporter (format-specific concern) +- Empty collection handling preserved +- Pandera schema validation still applies + +### Backward Compatibility + +The existing `to_frame()` methods will remain but delegate to exporters: + +**File:** `orders.py` (modified) + +```python +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pandera.typing.polars import LazyFrame + from order_matching.schemas import OrderDataSchema + +class Orders: + # ... existing methods unchanged ... + + def to_frame(self) -> LazyFrame[OrderDataSchema]: + """Get polars LazyFrame with all orders in the storage. + + .. deprecated:: 0.5.0 + Use ``PolarsExporter().export_orders(orders)`` instead. + This method will be removed in version 1.0.0. + + Returns + ------- + LazyFrame[OrderDataSchema] + """ + from order_matching.exporters.polars import PolarsExporter + return PolarsExporter().export_orders(self) +``` + +**File:** `executed_trades.py` (modified) + +```python +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pandera.typing.polars import LazyFrame + from order_matching.schemas import TradeDataSchema + +class ExecutedTrades: + # ... existing methods unchanged ... + + def to_frame(self) -> LazyFrame[TradeDataSchema]: + """Get polars DataFrame of all stored trades. + + .. deprecated:: 0.5.0 + Use ``PolarsExporter().export_trades(trades)`` instead. + This method will be removed in version 1.0.0. + + Returns + ------- + LazyFrame[TradeDataSchema] + """ + from order_matching.exporters.polars import PolarsExporter + return PolarsExporter().export_trades(self) +``` + +**Strategy:** + +- `TYPE_CHECKING` blocks prevent runtime imports of polars/pandera in core classes +- Lazy import of `PolarsExporter` inside method keeps polars optional +- Deprecation notices guide users to new API +- Deprecation timeline: deprecate in 0.5.0, remove in 1.0.0 + +**Migration Path:** + +```python +# Old API (still works but deprecated) +orders.to_frame() + +# New API (recommended) +from order_matching.exporters.polars import PolarsExporter +exporter = PolarsExporter() +exporter.export_orders(orders) +``` + +### Dependency Management + +**File:** `pyproject.toml` (modified) + +```toml +[project] +name = "order-matching" +dependencies = [ + # Core dependencies only - NO polars or pandera +] + +[project.optional-dependencies] +polars = [ + "polars>=1.0.0", + "pandera[polars]>=0.20.0", +] +all = [ + "order-matching[polars]", +] +``` + +**Installation Options:** + +```bash +# Core engine only (no export capabilities) +pip install order-matching + +# With polars export support +pip install order-matching[polars] + +# With all export formats (future) +pip install order-matching[all] +``` + +**Import Behavior:** + +- Core modules never import polars directly at module level +- `exporters/polars.py` imports polars normally (fails if not installed) +- Users calling `PolarsExporter` without `[polars]` get clear `ModuleNotFoundError` +- Old `to_frame()` methods work seamlessly if polars is installed + +### Future Extensibility + +The architecture enables adding new exporters without modifying existing code: + +```python +# Future: exporters/json.py +class JSONExporter(Exporter[str]): + def export_orders(self, orders: Orders) -> str: + # JSON serialization logic + pass + + def export_trades(self, trades: ExecutedTrades) -> str: + # JSON serialization logic + pass + +# Future: exporters/csv.py +class CSVExporter(Exporter[str]): + def export_orders(self, orders: Orders) -> str: + # CSV formatting logic + pass + +# Future: exporters/arrow.py +class ArrowExporter(Exporter[pa.Table]): + def export_orders(self, orders: Orders) -> pa.Table: + # Arrow table conversion + pass +``` + +Each exporter: +- Lives in its own file +- Has its own optional dependency group +- Follows the same `Exporter[T]` contract +- Requires zero changes to domain classes + +## Testing Strategy + +### New Tests + +**File:** `tests/test_exporters/test_polars_exporter.py` + +```python +import pytest +from order_matching.exporters.polars import PolarsExporter +from order_matching.schemas import OrderDataSchema, TradeDataSchema +from order_matching.orders import Orders +from order_matching.executed_trades import ExecutedTrades + +class TestPolarsExporter: + def test_export_empty_orders(self): + """Test exporting empty Orders collection.""" + exporter = PolarsExporter() + orders = Orders() + result = exporter.export_orders(orders) + assert result.collect().equals(OrderDataSchema.empty()) + + def test_export_orders_with_data(self, sample_orders): + """Test exporting Orders with data.""" + exporter = PolarsExporter() + result = exporter.export_orders(sample_orders) + OrderDataSchema.validate(result, lazy=True) + + def test_export_empty_trades(self): + """Test exporting empty ExecutedTrades.""" + exporter = PolarsExporter() + trades = ExecutedTrades() + result = exporter.export_trades(trades) + assert result.collect().equals(TradeDataSchema.empty()) + + def test_export_trades_with_data(self, sample_trades): + """Test exporting ExecutedTrades with data.""" + exporter = PolarsExporter() + result = exporter.export_trades(sample_trades) + TradeDataSchema.validate(result, lazy=True) + + def test_enum_conversion(self, sample_orders): + """Verify enums are converted to string names.""" + exporter = PolarsExporter() + result = exporter.export_orders(sample_orders).collect() + # All side/execution/status columns should be strings + assert result['side'].dtype == pl.Utf8 +``` + +### Existing Tests + +Existing tests in `test_orders.py` and `test_executed_trades.py` for `to_frame()` methods remain unchanged. They verify backward compatibility by ensuring the old API still works. + +### Test Coverage + +- New exporter tests verify core export logic correctness +- Existing domain class tests verify backward compatibility +- Both test suites pass with identical expectations +- Future exporters follow the same test pattern + +## Migration Guide + +### For Library Maintainers + +**Phase 1: Version 0.5.0 (this release)** +1. Add `exporters/` module with new architecture +2. Modify `orders.py` and `executed_trades.py` to delegate to exporters +3. Add deprecation warnings to `to_frame()` docstrings +4. Update README to show both old and new APIs +5. Make polars an optional dependency + +**Phase 2: Version 0.6.0 - 0.9.x (transition period)** +- Maintain both APIs +- Update examples and documentation to prefer new API +- Monitor deprecation usage + +**Phase 3: Version 1.0.0 (breaking change)** +- Remove `to_frame()` methods from domain classes +- Update README to only show new API +- Polars remains optional + +### For Library Users + +**Immediate (Version 0.5.0+):** + +No action required. Existing code continues to work unchanged: + +```python +# Still works, no changes needed +orders.to_frame() +trades.to_frame() +``` + +**Recommended (Version 0.5.0+):** + +Migrate to new API to avoid future breakage: + +```python +# Old +orders_df = orders.to_frame() +trades_df = trades.to_frame() + +# New +from order_matching.exporters.polars import PolarsExporter + +exporter = PolarsExporter() +orders_df = exporter.export_orders(orders) +trades_df = exporter.export_trades(trades) +``` + +**Installation:** + +```bash +# If using polars export, add [polars] extra +pip install order-matching[polars] +``` + +## Trade-offs + +### Advantages + +✅ **Clean Separation:** Domain models have no knowledge of export formats +✅ **Extensible:** New formats don't require changes to core code +✅ **Optional Dependencies:** Core engine has minimal requirements +✅ **Type Safe:** Generic types ensure compile-time correctness +✅ **Backward Compatible:** Existing code continues working during deprecation period +✅ **Future-Proof:** Architecture supports JSON, CSV, databases, etc. + +### Disadvantages + +⚠️ **Slightly More Verbose:** Users must import and instantiate exporters +⚠️ **Breaking Change in 1.0:** Old API will eventually be removed +⚠️ **More Files:** Architecture spreads code across more modules + +### Rejected Alternatives + +**Approach 1: Simple Converter Module** +- Rejected in favor of Approach 2 for better extensibility +- Protocol-based approach provides clearer contracts and better type safety + +**Approach 3: Mixin Pattern** +- Rejected because it doesn't fully separate responsibilities +- Still couples domain models to export logic through inheritance + +## Success Criteria + +1. **Functionality:** All existing tests pass without modification +2. **Backward Compatibility:** Old `to_frame()` API works identically to before +3. **Type Safety:** `mypy --strict` passes on all new code +4. **Optional Dependencies:** Core package installs without polars/pandera +5. **Documentation:** README clearly explains both APIs and migration path +6. **Extensibility:** Adding a JSON exporter requires only one new file + +## Implementation Checklist + +- [ ] Create `src/order_matching/exporters/` directory +- [ ] Implement `exporters/base.py` with abstract `Exporter` class +- [ ] Implement `exporters/polars.py` with `PolarsExporter` +- [ ] Implement `exporters/__init__.py` to export public interfaces +- [ ] Modify `orders.py` to delegate `to_frame()` to exporter +- [ ] Modify `executed_trades.py` to delegate `to_frame()` to exporter +- [ ] Update `pyproject.toml` to make polars optional +- [ ] Create test suite in `tests/test_exporters/test_polars_exporter.py` +- [ ] Verify all existing tests still pass +- [ ] Update README with new API examples +- [ ] Update documentation with deprecation timeline +- [ ] Add type checking validation for new code + +## Open Questions + +None. All design questions have been resolved during brainstorming. + +## References + +- Current implementation: `src/order_matching/orders.py:58-73` +- Current implementation: `src/order_matching/executed_trades.py:60-76` +- Schemas: `src/order_matching/schemas.py` +- Existing tests: `tests/test_orders.py:61-66`, `tests/test_executed_trades.py:53-61` From 848defbde0e77998afb69aac81043bead38a06d5 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:13:04 +0200 Subject: [PATCH 02/16] docs: add exporter architecture implementation plan Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 0 .../plans/2026-07-06-exporter-architecture.md | 768 ++++++++++++++++++ 2 files changed, 768 insertions(+) create mode 100644 AGENTS.md create mode 100644 superpowers/plans/2026-07-06-exporter-architecture.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e69de29 diff --git a/superpowers/plans/2026-07-06-exporter-architecture.md b/superpowers/plans/2026-07-06-exporter-architecture.md new file mode 100644 index 0000000..992d095 --- /dev/null +++ b/superpowers/plans/2026-07-06-exporter-architecture.md @@ -0,0 +1,768 @@ +# Exporter Architecture Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Refactor data export functionality from domain classes into a dedicated exporter module with abstract interface and polars implementation. + +**Architecture:** Create `exporters/` package with abstract `Exporter[T]` base class and concrete `PolarsExporter` implementation. Modify `Orders` and `ExecutedTrades` to delegate `to_frame()` calls to exporters. Make polars an optional dependency. + +**Tech Stack:** Python 3.10+, polars, pandera, ABC/Generic typing + +## Global Constraints + +- Python >=3.10 +- Maintain backward compatibility with existing `to_frame()` API +- All existing tests must pass without modification +- Follow TDD: write tests before implementation +- Use `TYPE_CHECKING` to avoid runtime imports of optional dependencies +- Commit after each passing test + +--- + +## File Structure + +**New Files:** +- `src/order_matching/exporters/__init__.py` - Public exports (Exporter, PolarsExporter) +- `src/order_matching/exporters/base.py` - Abstract Exporter[T] base class +- `src/order_matching/exporters/polars.py` - PolarsExporter implementation +- `tests/test_exporters/__init__.py` - Empty test package marker +- `tests/test_exporters/test_polars_exporter.py` - PolarsExporter test suite + +**Modified Files:** +- `src/order_matching/orders.py` - Delegate to_frame() to PolarsExporter +- `src/order_matching/executed_trades.py` - Delegate to_frame() to PolarsExporter +- `pyproject.toml` - Make polars/pandera optional dependencies +- `README.md` - Update examples to show new API + +--- + +### Task 1: Create Abstract Exporter Base Class + +**Files:** +- Create: `src/order_matching/exporters/__init__.py` +- Create: `src/order_matching/exporters/base.py` + +**Interfaces:** +- Consumes: Nothing (foundational task) +- Produces: `Exporter[T]` abstract base class with methods: + - `export_orders(self, orders: Orders) -> T` + - `export_trades(self, trades: ExecutedTrades) -> T` + +- [ ] **Step 1: Create exporters package structure** + +```bash +mkdir -p src/order_matching/exporters +touch src/order_matching/exporters/__init__.py +``` + +- [ ] **Step 2: Write base.py with abstract Exporter class** + +```python +# src/order_matching/exporters/base.py +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Generic, TypeVar + +if TYPE_CHECKING: + from order_matching.orders import Orders + from order_matching.executed_trades import ExecutedTrades + +T = TypeVar('T') + + +class Exporter(ABC, Generic[T]): + """Abstract base class for data exporters. + + Type parameter T represents the output format type + (e.g., LazyFrame, dict, str for JSON/CSV). + """ + + @abstractmethod + def export_orders(self, orders: Orders) -> T: + """Convert Orders collection to target format. + + Parameters + ---------- + orders : Orders + Orders collection to export + + Returns + ------- + T + Exported data in target format + """ + pass + + @abstractmethod + def export_trades(self, trades: ExecutedTrades) -> T: + """Convert ExecutedTrades collection to target format. + + Parameters + ---------- + trades : ExecutedTrades + Trades collection to export + + Returns + ------- + T + Exported data in target format + """ + pass +``` + +- [ ] **Step 3: Export Exporter from __init__.py** + +```python +# src/order_matching/exporters/__init__.py +"""Data export module for converting domain objects to various formats.""" + +from order_matching.exporters.base import Exporter + +__all__ = ["Exporter"] +``` + +- [ ] **Step 4: Verify imports work** + +Run: `python -c "from order_matching.exporters import Exporter; print(Exporter)"` +Expected: `` + +- [ ] **Step 5: Commit base exporter** + +```bash +git add src/order_matching/exporters/ +git commit -m "feat: add abstract Exporter base class + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" +``` + +--- + +### Task 2: Implement PolarsExporter with Tests + +**Files:** +- Create: `src/order_matching/exporters/polars.py` +- Create: `tests/test_exporters/__init__.py` +- Create: `tests/test_exporters/test_polars_exporter.py` + +**Interfaces:** +- Consumes: `Exporter[T]` from Task 1 +- Produces: `PolarsExporter(Exporter[LazyFrame])` with methods: + - `export_orders(self, orders: Orders) -> LazyFrame[OrderDataSchema]` + - `export_trades(self, trades: ExecutedTrades) -> LazyFrame[TradeDataSchema]` + +- [ ] **Step 1: Create test package structure** + +```bash +mkdir -p tests/test_exporters +touch tests/test_exporters/__init__.py +``` + +- [ ] **Step 2: Write failing test for empty orders export** + +```python +# tests/test_exporters/test_polars_exporter.py +from __future__ import annotations + +import pytest + +from order_matching.exporters.polars import PolarsExporter +from order_matching.orders import Orders +from order_matching.schemas import OrderDataSchema + + +class TestPolarsExporter: + def test_export_empty_orders(self) -> None: + """Test exporting empty Orders collection returns empty schema.""" + exporter = PolarsExporter() + orders = Orders() + result = exporter.export_orders(orders) + assert result.collect().equals(OrderDataSchema.empty()) +``` + +- [ ] **Step 3: Run test to verify it fails** + +Run: `pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_empty_orders -v` +Expected: FAIL with `ModuleNotFoundError: No module named 'order_matching.exporters.polars'` + +- [ ] **Step 4: Implement PolarsExporter.export_orders for empty case** + +```python +# src/order_matching/exporters/polars.py +from __future__ import annotations + +from dataclasses import asdict +from typing import cast + +import polars as pl +from pandera.typing.polars import LazyFrame + +from order_matching.executed_trades import ExecutedTrades +from order_matching.exporters.base import Exporter +from order_matching.orders import Orders +from order_matching.schemas import OrderDataSchema, TradeDataSchema + + +class PolarsExporter(Exporter[LazyFrame]): + """Export collections to polars LazyFrame format.""" + + def export_orders(self, orders: Orders) -> LazyFrame[OrderDataSchema]: + """Export Orders to validated polars LazyFrame. + + Parameters + ---------- + orders : Orders + Orders collection to export + + Returns + ------- + LazyFrame[OrderDataSchema] + Validated polars LazyFrame with order data + """ + if len(orders) == 0: + return cast(LazyFrame[OrderDataSchema], OrderDataSchema.empty().lazy()) + + data = [asdict(order) for order in orders] + for d in data: + d[OrderDataSchema.side] = d[OrderDataSchema.side].name + d[OrderDataSchema.execution] = d[OrderDataSchema.execution].name + d[OrderDataSchema.status] = d[OrderDataSchema.status].name + return cast(LazyFrame[OrderDataSchema], pl.LazyFrame(data)) + + def export_trades(self, trades: ExecutedTrades) -> LazyFrame[TradeDataSchema]: + """Export ExecutedTrades to validated polars LazyFrame. + + Parameters + ---------- + trades : ExecutedTrades + Trades collection to export + + Returns + ------- + LazyFrame[TradeDataSchema] + Validated polars LazyFrame with trade data + """ + trade_list = trades.trades + if len(trade_list) == 0: + return cast(LazyFrame[TradeDataSchema], TradeDataSchema.empty().lazy()) + + data = [asdict(trade) for trade in trade_list] + for d in data: + d[TradeDataSchema.side] = d[TradeDataSchema.side].name + d[TradeDataSchema.execution] = d[TradeDataSchema.execution].name + return cast(LazyFrame[TradeDataSchema], pl.LazyFrame(data)) +``` + +- [ ] **Step 5: Run test to verify it passes** + +Run: `pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_empty_orders -v` +Expected: PASS + +- [ ] **Step 6: Write failing test for orders with data** + +```python +# tests/test_exporters/test_polars_exporter.py (add to TestPolarsExporter class) + def test_export_orders_with_data(self) -> None: + """Test exporting Orders with data validates against schema.""" + from datetime import datetime + from order_matching.order import LimitOrder + from order_matching.side import Side + + exporter = PolarsExporter() + timestamp = datetime(2023, 1, 1) + order = LimitOrder( + side=Side.BUY, + price=1.2, + size=2.3, + timestamp=timestamp, + order_id="test_order", + trader_id="test_trader" + ) + orders = Orders([order]) + result = exporter.export_orders(orders) + OrderDataSchema.validate(result, lazy=True) +``` + +- [ ] **Step 7: Run test to verify it passes (implementation already complete)** + +Run: `pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_orders_with_data -v` +Expected: PASS + +- [ ] **Step 8: Write failing test for empty trades export** + +```python +# tests/test_exporters/test_polars_exporter.py (add to TestPolarsExporter class) + def test_export_empty_trades(self) -> None: + """Test exporting empty ExecutedTrades returns empty schema.""" + from order_matching.executed_trades import ExecutedTrades + + exporter = PolarsExporter() + trades = ExecutedTrades() + result = exporter.export_trades(trades) + assert result.collect().equals(TradeDataSchema.empty()) +``` + +- [ ] **Step 9: Run test to verify it passes (implementation already complete)** + +Run: `pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_empty_trades -v` +Expected: PASS + +- [ ] **Step 10: Write failing test for trades with data** + +```python +# tests/test_exporters/test_polars_exporter.py (add to TestPolarsExporter class) + def test_export_trades_with_data(self) -> None: + """Test exporting ExecutedTrades with data validates against schema.""" + from datetime import datetime + from order_matching.executed_trades import ExecutedTrades + from order_matching.execution import Execution + from order_matching.side import Side + from order_matching.trade import Trade + + exporter = PolarsExporter() + timestamp = datetime(2023, 1, 2) + trade = Trade( + side=Side.SELL, + price=1.2, + size=1.6, + incoming_order_id="b", + book_order_id="a", + execution=Execution.LIMIT, + trade_id="test_trade", + timestamp=timestamp + ) + trades = ExecutedTrades([trade]) + result = exporter.export_trades(trades) + TradeDataSchema.validate(result, lazy=True) +``` + +- [ ] **Step 11: Run test to verify it passes (implementation already complete)** + +Run: `pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_trades_with_data -v` +Expected: PASS + +- [ ] **Step 12: Export PolarsExporter from __init__.py** + +```python +# src/order_matching/exporters/__init__.py +"""Data export module for converting domain objects to various formats.""" + +from order_matching.exporters.base import Exporter +from order_matching.exporters.polars import PolarsExporter + +__all__ = ["Exporter", "PolarsExporter"] +``` + +- [ ] **Step 13: Run full exporter test suite** + +Run: `pytest tests/test_exporters/ -v` +Expected: All 4 tests PASS + +- [ ] **Step 14: Commit PolarsExporter implementation** + +```bash +git add src/order_matching/exporters/polars.py tests/test_exporters/ +git commit -m "feat: implement PolarsExporter with full test coverage + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" +``` + +--- + +### Task 3: Update Orders.to_frame() to Delegate + +**Files:** +- Modify: `src/order_matching/orders.py:1-10` (imports) +- Modify: `src/order_matching/orders.py:58-73` (to_frame method) + +**Interfaces:** +- Consumes: `PolarsExporter().export_orders(orders)` from Task 2 +- Produces: Backward-compatible `Orders.to_frame()` method (delegates to exporter) + +- [ ] **Step 1: Verify existing test still passes before changes** + +Run: `pytest tests/test_orders.py::TestOrders::test_to_frame -v` +Expected: PASS + +- [ ] **Step 2: Update imports in orders.py to use TYPE_CHECKING** + +```python +# src/order_matching/orders.py (replace lines 1-10) +from __future__ import annotations + +from dataclasses import asdict +from typing import TYPE_CHECKING, Iterator, Sequence, cast + +if TYPE_CHECKING: + from pandera.typing.polars import LazyFrame + from order_matching.schemas import OrderDataSchema + +from order_matching.order import Order +``` + +- [ ] **Step 3: Remove polars import from orders.py** + +Delete this line if it exists: +```python +import polars as pl +``` + +Also delete this import if it exists: +```python +from pandera.typing.polars import LazyFrame +``` + +(These are now in TYPE_CHECKING block) + +- [ ] **Step 4: Replace to_frame implementation with delegation** + +```python +# src/order_matching/orders.py (replace to_frame method, around line 58-73) + def to_frame(self) -> LazyFrame[OrderDataSchema]: + """Get polars LazyFrame with all orders in the storage. + + .. deprecated:: 0.5.0 + Use ``PolarsExporter().export_orders(orders)`` instead. + This method will be removed in version 1.0.0. + + Returns + ------- + LazyFrame[OrderDataSchema] + """ + from order_matching.exporters.polars import PolarsExporter + + return PolarsExporter().export_orders(self) +``` + +- [ ] **Step 5: Run existing test to verify backward compatibility** + +Run: `pytest tests/test_orders.py::TestOrders::test_to_frame -v` +Expected: PASS + +- [ ] **Step 6: Commit orders.py changes** + +```bash +git add src/order_matching/orders.py +git commit -m "refactor: delegate Orders.to_frame() to PolarsExporter + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" +``` + +--- + +### Task 4: Update ExecutedTrades.to_frame() to Delegate + +**Files:** +- Modify: `src/order_matching/executed_trades.py:1-12` (imports) +- Modify: `src/order_matching/executed_trades.py:60-76` (to_frame method) + +**Interfaces:** +- Consumes: `PolarsExporter().export_trades(trades)` from Task 2 +- Produces: Backward-compatible `ExecutedTrades.to_frame()` method (delegates to exporter) + +- [ ] **Step 1: Verify existing test still passes before changes** + +Run: `pytest tests/test_executed_trades.py::TestExecutedTrades::test_to_frame -v` +Expected: PASS + +- [ ] **Step 2: Update imports in executed_trades.py to use TYPE_CHECKING** + +```python +# src/order_matching/executed_trades.py (replace lines 1-12) +from __future__ import annotations + +from collections import defaultdict +from dataclasses import asdict +from datetime import datetime +from typing import TYPE_CHECKING, cast + +if TYPE_CHECKING: + from pandera.typing.polars import LazyFrame + from order_matching.schemas import TradeDataSchema + +from order_matching.trade import Trade +``` + +- [ ] **Step 3: Remove polars import from executed_trades.py** + +Delete this line if it exists: +```python +import polars as pl +``` + +Also delete this import if it exists: +```python +from pandera.typing.polars import LazyFrame +``` + +(These are now in TYPE_CHECKING block) + +- [ ] **Step 4: Replace to_frame implementation with delegation** + +```python +# src/order_matching/executed_trades.py (replace to_frame method, around line 60-76) + def to_frame(self) -> LazyFrame[TradeDataSchema]: + """Get polars DataFrame of all stored trades. + + .. deprecated:: 0.5.0 + Use ``PolarsExporter().export_trades(trades)`` instead. + This method will be removed in version 1.0.0. + + Returns + ------- + LazyFrame[TradeDataSchema] + polars LazyFrame of all stored trades + """ + from order_matching.exporters.polars import PolarsExporter + + return PolarsExporter().export_trades(self) +``` + +- [ ] **Step 5: Run existing test to verify backward compatibility** + +Run: `pytest tests/test_executed_trades.py::TestExecutedTrades::test_to_frame -v` +Expected: PASS + +- [ ] **Step 6: Commit executed_trades.py changes** + +```bash +git add src/order_matching/executed_trades.py +git commit -m "refactor: delegate ExecutedTrades.to_frame() to PolarsExporter + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" +``` + +--- + +### Task 5: Make Polars an Optional Dependency + +**Files:** +- Modify: `pyproject.toml:20-25` (dependencies section) +- Modify: `pyproject.toml:26-45` (add optional-dependencies section) + +**Interfaces:** +- Consumes: Nothing +- Produces: `pyproject.toml` with `[project.optional-dependencies]` section containing `polars` group + +- [ ] **Step 1: Move polars and pandera to optional dependencies** + +```toml +# pyproject.toml (replace lines 20-25) +dependencies = [ + "faker>=33.0.0", + "numpy>=2.2.6", +] + +[project.optional-dependencies] +polars = [ + "pandera[polars]>=0.21.0", + "polars>=1.42.1", +] +all = [ + "order-matching[polars]", +] +``` + +- [ ] **Step 2: Run tests to verify they still pass (polars is installed in dev environment)** + +Run: `pytest tests/ -v` +Expected: All tests PASS (polars still installed in current environment) + +- [ ] **Step 3: Verify core imports work without polars (dry run check)** + +Run: `python -c "from order_matching.orders import Orders; from order_matching.executed_trades import ExecutedTrades; print('Core imports work')"` +Expected: `Core imports work` (no import errors because TYPE_CHECKING prevents runtime import) + +- [ ] **Step 4: Commit dependency changes** + +```bash +git add pyproject.toml +git commit -m "refactor: make polars an optional dependency + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" +``` + +--- + +### Task 6: Update README with New API Examples + +**Files:** +- Modify: `README.md:16` (features list) +- Modify: `README.md:30-58` (usage example) + +**Interfaces:** +- Consumes: `PolarsExporter` from Task 2 +- Produces: Updated README documenting both old and new export APIs + +- [ ] **Step 1: Update installation instructions to mention optional dependency** + +```markdown +# README.md (find line ~20-22, modify install section) +## Install + +```shell +# Core matching engine only +pip install order-matching + +# With polars export support (recommended for data science workflows) +pip install order-matching[polars] +``` +``` + +- [ ] **Step 2: Update features list to clarify polars is optional** + +```markdown +# README.md (find line ~16, modify features list) +- conversion into polars LazyFrame of orders, executed trades, order book summary (optional polars dependency) +``` + +- [ ] **Step 3: Add new API example after existing usage section** + +```markdown +# README.md (add after line ~58, after existing usage example) + +### Data Export (Polars) + +If you installed with `[polars]` extra, you can export data to polars LazyFrame: + +```python +>>> from order_matching.exporters.polars import PolarsExporter + +>>> exporter = PolarsExporter() +>>> orders_df = exporter.export_orders(matching_engine.order_book.buy_orders) +>>> trades_df = exporter.export_trades(executed_trades) + +>>> # Legacy API (deprecated, will be removed in 1.0.0) +>>> orders_df = matching_engine.order_book.buy_orders.to_frame() +``` +``` + +- [ ] **Step 4: Verify README renders correctly (markdown check)** + +Run: `python -c "import markdown; markdown.markdown(open('README.md').read()); print('README valid')"` +Expected: `README valid` (or skip if markdown package not available) + +- [ ] **Step 5: Commit README updates** + +```bash +git add README.md +git commit -m "docs: update README with new exporter API + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" +``` + +--- + +### Task 7: Final Integration Verification + +**Files:** +- Test: All test files + +**Interfaces:** +- Consumes: All tasks 1-6 +- Produces: Verified working implementation with all tests passing + +- [ ] **Step 1: Run full test suite** + +Run: `pytest tests/ -v` +Expected: All tests PASS + +- [ ] **Step 2: Run type checking** + +Run: `uv run ty` +Expected: No type errors + +- [ ] **Step 3: Run linting** + +Run: `uv run ruff check src/ tests/` +Expected: No linting errors + +- [ ] **Step 4: Test new API usage pattern** + +```python +# Create test script: test_new_api.py +from datetime import datetime +from order_matching.exporters.polars import PolarsExporter +from order_matching.orders import Orders +from order_matching.order import LimitOrder +from order_matching.side import Side +from order_matching.schemas import OrderDataSchema + +timestamp = datetime(2023, 1, 1) +order = LimitOrder( + side=Side.BUY, + price=1.2, + size=2.3, + timestamp=timestamp, + order_id="test", + trader_id="trader" +) +orders = Orders([order]) + +# New API +exporter = PolarsExporter() +df = exporter.export_orders(orders) +OrderDataSchema.validate(df, lazy=True) +print("New API works!") + +# Old API (backward compatibility) +df_old = orders.to_frame() +assert df.collect().equals(df_old.collect()) +print("Backward compatibility verified!") +``` + +Run: `python test_new_api.py` +Expected: +``` +New API works! +Backward compatibility verified! +``` + +- [ ] **Step 5: Clean up test script** + +Run: `rm test_new_api.py` + +- [ ] **Step 6: Final commit with version bump preparation** + +```bash +git add -A +git status +# Verify only expected files are staged, then: +git commit -m "chore: prepare for v0.5.0 release with exporter architecture + +Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" --allow-empty +``` + +--- + +## Self-Review Checklist + +**Spec Coverage:** +- ✅ Abstract Exporter base class (Task 1) +- ✅ PolarsExporter implementation (Task 2) +- ✅ Orders.to_frame() delegation (Task 3) +- ✅ ExecutedTrades.to_frame() delegation (Task 4) +- ✅ Optional polars dependency (Task 5) +- ✅ README documentation (Task 6) +- ✅ Full test coverage (Tasks 2, 7) +- ✅ Type safety with TYPE_CHECKING (Tasks 3, 4) +- ✅ Backward compatibility (Tasks 3, 4, 7) + +**Placeholder Scan:** +- ✅ No TBD/TODO markers +- ✅ All code blocks contain actual implementation +- ✅ All test expectations specify exact output +- ✅ All file paths are absolute and exact + +**Type Consistency:** +- ✅ `Exporter[T]` used consistently +- ✅ `PolarsExporter(Exporter[LazyFrame])` signature matches +- ✅ `export_orders` and `export_trades` method names consistent across all tasks +- ✅ Return types `LazyFrame[OrderDataSchema]` and `LazyFrame[TradeDataSchema]` consistent + +**Dependencies:** +- Task 2 depends on Task 1 (uses Exporter base class) +- Task 3 depends on Task 2 (uses PolarsExporter) +- Task 4 depends on Task 2 (uses PolarsExporter) +- Task 6 depends on Task 2 (documents PolarsExporter) +- Task 7 depends on all tasks (integration verification) From c9bcb19b695cb3ae088b8f2c1fb9f32bb22bdabc Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:18:32 +0200 Subject: [PATCH 03/16] feat: add abstract Exporter base class Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/order_matching/exporters/__init__.py | 5 +++ src/order_matching/exporters/base.py | 50 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/order_matching/exporters/__init__.py create mode 100644 src/order_matching/exporters/base.py diff --git a/src/order_matching/exporters/__init__.py b/src/order_matching/exporters/__init__.py new file mode 100644 index 0000000..7275c9a --- /dev/null +++ b/src/order_matching/exporters/__init__.py @@ -0,0 +1,5 @@ +"""Data export module for converting domain objects to various formats.""" + +from order_matching.exporters.base import Exporter + +__all__ = ["Exporter"] diff --git a/src/order_matching/exporters/base.py b/src/order_matching/exporters/base.py new file mode 100644 index 0000000..69c0192 --- /dev/null +++ b/src/order_matching/exporters/base.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Generic, TypeVar + +if TYPE_CHECKING: + from order_matching.executed_trades import ExecutedTrades + from order_matching.orders import Orders + +T = TypeVar("T") + + +class Exporter(ABC, Generic[T]): + """Abstract base class for data exporters. + + Type parameter T represents the output format type + (e.g., LazyFrame, dict, str for JSON/CSV). + """ + + @abstractmethod + def export_orders(self, orders: Orders) -> T: + """Convert Orders collection to target format. + + Parameters + ---------- + orders : Orders + Orders collection to export + + Returns + ------- + T + Exported data in target format + """ + pass + + @abstractmethod + def export_trades(self, trades: ExecutedTrades) -> T: + """Convert ExecutedTrades collection to target format. + + Parameters + ---------- + trades : ExecutedTrades + Trades collection to export + + Returns + ------- + T + Exported data in target format + """ + pass From b8af47d2d9e98cd5578ffe9d5565c80a9e9d8768 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:18:42 +0200 Subject: [PATCH 04/16] test: add comprehensive tests for Exporter base class - Test abstract class cannot be instantiated - Test concrete implementation can be instantiated - Test abstract methods are properly defined - Test concrete export_orders functionality - Test concrete export_trades functionality Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/test_exporter_base.py | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/test_exporter_base.py diff --git a/tests/test_exporter_base.py b/tests/test_exporter_base.py new file mode 100644 index 0000000..e360ad1 --- /dev/null +++ b/tests/test_exporter_base.py @@ -0,0 +1,55 @@ +import pytest + +from order_matching.executed_trades import ExecutedTrades +from order_matching.exporters import Exporter +from order_matching.orders import Orders + + +class ConcreteExporter(Exporter[dict]): + """Concrete implementation for testing.""" + + def export_orders(self, orders: Orders) -> dict: # noqa: ARG002 + return {"orders": []} + + def export_trades(self, trades: ExecutedTrades) -> dict: # noqa: ARG002 + return {"trades": []} + + +class TestExporter: + """Tests for abstract Exporter base class.""" + + def test_exporter_is_abstract(self) -> None: + """Test that Exporter cannot be instantiated directly.""" + with pytest.raises(TypeError): + Exporter() + + def test_concrete_exporter_can_be_instantiated(self) -> None: + """Test that a concrete Exporter subclass can be instantiated.""" + exporter = ConcreteExporter() + assert isinstance(exporter, Exporter) + + def test_export_orders_abstract(self) -> None: + """Test that export_orders is an abstract method.""" + assert hasattr(Exporter, "export_orders") + assert getattr(Exporter.export_orders, "__isabstractmethod__", False) + + def test_export_trades_abstract(self) -> None: + """Test that export_trades is an abstract method.""" + assert hasattr(Exporter, "export_trades") + assert getattr(Exporter.export_trades, "__isabstractmethod__", False) + + def test_concrete_implementation_export_orders(self) -> None: + """Test that concrete implementation can export orders.""" + exporter = ConcreteExporter() + orders = Orders() + result = exporter.export_orders(orders) + assert isinstance(result, dict) + assert "orders" in result + + def test_concrete_implementation_export_trades(self) -> None: + """Test that concrete implementation can export trades.""" + exporter = ConcreteExporter() + trades = ExecutedTrades() + result = exporter.export_trades(trades) + assert isinstance(result, dict) + assert "trades" in result From 6150f6cebca4fc728f49f0ec43342b7770df5b78 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:26:56 +0200 Subject: [PATCH 05/16] feat: implement PolarsExporter with full test coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- AGENTS.md | 11 ++++ src/order_matching/exporters/__init__.py | 3 +- src/order_matching/exporters/polars.py | 62 +++++++++++++++++++ tests/test_exporters/__init__.py | 0 tests/test_exporters/test_polars_exporter.py | 64 ++++++++++++++++++++ 5 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/order_matching/exporters/polars.py create mode 100644 tests/test_exporters/__init__.py create mode 100644 tests/test_exporters/test_polars_exporter.py diff --git a/AGENTS.md b/AGENTS.md index e69de29..00b3b95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -0,0 +1,11 @@ +- Use uv to run python commands, e.g. + + ```shell + uv run pytest + ``` + +- Run prek on all files before each commit: + + ```shell + uv run prek run -v --show-diff-on-failure --all-files + ``` diff --git a/src/order_matching/exporters/__init__.py b/src/order_matching/exporters/__init__.py index 7275c9a..d6e74d4 100644 --- a/src/order_matching/exporters/__init__.py +++ b/src/order_matching/exporters/__init__.py @@ -1,5 +1,6 @@ """Data export module for converting domain objects to various formats.""" from order_matching.exporters.base import Exporter +from order_matching.exporters.polars import PolarsExporter -__all__ = ["Exporter"] +__all__ = ["Exporter", "PolarsExporter"] diff --git a/src/order_matching/exporters/polars.py b/src/order_matching/exporters/polars.py new file mode 100644 index 0000000..953e3d5 --- /dev/null +++ b/src/order_matching/exporters/polars.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from dataclasses import asdict +from typing import cast + +import polars as pl +from pandera.typing.polars import LazyFrame + +from order_matching.executed_trades import ExecutedTrades +from order_matching.exporters.base import Exporter +from order_matching.orders import Orders +from order_matching.schemas import OrderDataSchema, TradeDataSchema + + +class PolarsExporter(Exporter[LazyFrame]): + """Export collections to polars LazyFrame format.""" + + def export_orders(self, orders: Orders) -> LazyFrame[OrderDataSchema]: + """Export Orders to validated polars LazyFrame. + + Parameters + ---------- + orders : Orders + Orders collection to export + + Returns + ------- + LazyFrame[OrderDataSchema] + Validated polars LazyFrame with order data + """ + if len(orders) == 0: + return cast(LazyFrame[OrderDataSchema], OrderDataSchema.empty().lazy()) + + data = [asdict(order) for order in orders] + for d in data: + d[OrderDataSchema.side] = d[OrderDataSchema.side].name + d[OrderDataSchema.execution] = d[OrderDataSchema.execution].name + d[OrderDataSchema.status] = d[OrderDataSchema.status].name + return cast(LazyFrame[OrderDataSchema], pl.LazyFrame(data)) + + def export_trades(self, trades: ExecutedTrades) -> LazyFrame[TradeDataSchema]: + """Export ExecutedTrades to validated polars LazyFrame. + + Parameters + ---------- + trades : ExecutedTrades + Trades collection to export + + Returns + ------- + LazyFrame[TradeDataSchema] + Validated polars LazyFrame with trade data + """ + trade_list = trades.trades + if len(trade_list) == 0: + return cast(LazyFrame[TradeDataSchema], TradeDataSchema.empty().lazy()) + + data = [asdict(trade) for trade in trade_list] + for d in data: + d[TradeDataSchema.side] = d[TradeDataSchema.side].name + d[TradeDataSchema.execution] = d[TradeDataSchema.execution].name + return cast(LazyFrame[TradeDataSchema], pl.LazyFrame(data)) diff --git a/tests/test_exporters/__init__.py b/tests/test_exporters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_exporters/test_polars_exporter.py b/tests/test_exporters/test_polars_exporter.py new file mode 100644 index 0000000..74f21b5 --- /dev/null +++ b/tests/test_exporters/test_polars_exporter.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from order_matching.exporters.polars import PolarsExporter +from order_matching.orders import Orders +from order_matching.schemas import OrderDataSchema, TradeDataSchema + + +class TestPolarsExporter: + def test_export_empty_orders(self) -> None: + """Test exporting empty Orders collection returns empty schema.""" + exporter = PolarsExporter() + orders = Orders() + result = exporter.export_orders(orders) + assert result.collect().equals(OrderDataSchema.empty()) + + def test_export_orders_with_data(self) -> None: + """Test exporting Orders with data validates against schema.""" + from datetime import datetime + + from order_matching.order import LimitOrder + from order_matching.side import Side + + exporter = PolarsExporter() + timestamp = datetime(2023, 1, 1) + order = LimitOrder( + side=Side.BUY, price=1.2, size=2.3, timestamp=timestamp, order_id="test_order", trader_id="test_trader" + ) + orders = Orders([order]) + result = exporter.export_orders(orders) + OrderDataSchema.validate(result, lazy=True) + + def test_export_empty_trades(self) -> None: + """Test exporting empty ExecutedTrades returns empty schema.""" + from order_matching.executed_trades import ExecutedTrades + + exporter = PolarsExporter() + trades = ExecutedTrades() + result = exporter.export_trades(trades) + assert result.collect().equals(TradeDataSchema.empty()) + + def test_export_trades_with_data(self) -> None: + """Test exporting ExecutedTrades with data validates against schema.""" + from datetime import datetime + + from order_matching.executed_trades import ExecutedTrades + from order_matching.execution import Execution + from order_matching.side import Side + from order_matching.trade import Trade + + exporter = PolarsExporter() + timestamp = datetime(2023, 1, 2) + trade = Trade( + side=Side.SELL, + price=1.2, + size=1.6, + incoming_order_id="b", + book_order_id="a", + execution=Execution.LIMIT, + trade_id="test_trade", + timestamp=timestamp, + ) + trades = ExecutedTrades([trade]) + result = exporter.export_trades(trades) + TradeDataSchema.validate(result, lazy=True) From 871f54fe5f118572c34d68b660525f1879df5f68 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:32:23 +0200 Subject: [PATCH 06/16] refactor: delegate Orders.to_frame() to PolarsExporter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/order_matching/orders.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/order_matching/orders.py b/src/order_matching/orders.py index 89054dc..99915b6 100644 --- a/src/order_matching/orders.py +++ b/src/order_matching/orders.py @@ -1,13 +1,13 @@ from __future__ import annotations -from dataclasses import asdict -from typing import Iterator, Sequence, cast +from typing import TYPE_CHECKING, Iterator, Sequence -import polars as pl -from pandera.typing.polars import LazyFrame +if TYPE_CHECKING: + from pandera.typing.polars import LazyFrame + + from order_matching.schemas import OrderDataSchema from order_matching.order import Order -from order_matching.schemas import OrderDataSchema class Orders: @@ -58,19 +58,17 @@ def remove(self, orders: list[Order]) -> None: def to_frame(self) -> LazyFrame[OrderDataSchema]: """Get polars LazyFrame with all orders in the storage. + .. deprecated:: 0.5.0 + Use ``PolarsExporter().export_orders(orders)`` instead. + This method will be removed in version 1.0.0. + Returns ------- LazyFrame[OrderDataSchema] """ - if len(self.orders) == 0: - return cast(LazyFrame[OrderDataSchema], OrderDataSchema.empty().lazy()) - else: - data = [asdict(order) for order in self.orders] - for d in data: - d[OrderDataSchema.side] = d[OrderDataSchema.side].name - d[OrderDataSchema.execution] = d[OrderDataSchema.execution].name - d[OrderDataSchema.status] = d[OrderDataSchema.status].name - return cast(LazyFrame[OrderDataSchema], pl.LazyFrame(data)) + from order_matching.exporters.polars import PolarsExporter + + return PolarsExporter().export_orders(self) @property def is_empty(self) -> bool: From 9b5f8da727da66f885ed01acf012593cb1546142 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:35:52 +0200 Subject: [PATCH 07/16] refactor: delegate ExecutedTrades.to_frame() to PolarsExporter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .superpowers/sdd/task-2-report.md | 120 ++++++++++++++++++++++++++ src/order_matching/executed_trades.py | 30 +++---- 2 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 .superpowers/sdd/task-2-report.md diff --git a/.superpowers/sdd/task-2-report.md b/.superpowers/sdd/task-2-report.md new file mode 100644 index 0000000..abd4697 --- /dev/null +++ b/.superpowers/sdd/task-2-report.md @@ -0,0 +1,120 @@ +# Task 2 Implementation Report: PolarsExporter + +## Implementation Summary + +Successfully implemented `PolarsExporter` class that converts domain collections (Orders and ExecutedTrades) to polars LazyFrame format. The implementation extracts the existing `to_frame()` logic and centralizes it in a dedicated exporter class following the Strategy pattern established in Task 1. + +**Key Features:** +- Concrete implementation of `Exporter[LazyFrame]` abstract base class +- Handles empty collections by returning empty schema LazyFrames +- Converts enum fields (side, execution, status) to string names for DataFrame compatibility +- Full type safety with Pandera schema validation +- Comprehensive test coverage (4 tests: 2 for orders, 2 for trades) + +## Files Created/Modified + +### Created Files: +1. **src/order_matching/exporters/polars.py** (63 lines) + - PolarsExporter class with export_orders() and export_trades() methods + - Handles empty collections and enum-to-string conversions + - Uses dataclass asdict() for efficient conversion + +2. **tests/test_exporters/__init__.py** + - Empty __init__ for test package structure + +3. **tests/test_exporters/test_polars_exporter.py** (54 lines) + - TestPolarsExporter class with 4 test methods + - Tests for empty and data-filled orders/trades + - Schema validation assertions + +### Modified Files: +1. **src/order_matching/exporters/__init__.py** + - Added PolarsExporter to exports + - Updated __all__ list + +## Tests Run (commands + output) + +### Test 1: Empty Orders Export +```bash +uv run pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_empty_orders -v +``` +**Result:** ✅ PASSED in 1.76s + +### Test 2: Orders with Data +```bash +uv run pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_orders_with_data -v +``` +**Result:** ✅ PASSED in 1.72s + +### Test 3: Empty Trades Export +```bash +uv run pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_empty_trades -v +``` +**Result:** ✅ PASSED in 2.51s + +### Test 4: Trades with Data +```bash +uv run pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_trades_with_data -v +``` +**Result:** ✅ PASSED in 1.68s + +### Full Suite +```bash +uv run pytest tests/test_exporters/ -v +``` +**Result:** ✅ 4 passed in 1.71s + +## Self-Review Findings + +### ✅ Strengths: +1. **TDD Approach**: Followed strict test-first development with failing tests before implementation +2. **Code Reuse**: Successfully extracts existing to_frame() logic into dedicated exporter +3. **Type Safety**: Proper generic typing with LazyFrame[OrderDataSchema] and LazyFrame[TradeDataSchema] +4. **Edge Cases**: Handles empty collections correctly +5. **Enum Handling**: Correctly converts enums to string names for DataFrame compatibility +6. **Documentation**: Comprehensive docstrings with parameter and return type descriptions + +### 🔍 Observations: +1. **Schema Validation**: Using `cast()` for type safety - Pandera validates at runtime +2. **Enum Conversion**: Manual iteration to convert enum fields - necessary for polars compatibility +3. **Empty Collection Logic**: Different approaches for Orders (len(orders)) vs ExecutedTrades (len(trade_list)) + - This reflects differences in the underlying collection implementations + +### 💡 Potential Future Enhancements: +1. Could add validation that schemas match expected structure +2. Could optimize enum conversion with list comprehension +3. Could add caching for repeated exports of same data + +### ⚠️ No Issues Found: +- All tests pass +- Code follows existing patterns +- Type hints are correct +- No security concerns +- No performance issues for expected data sizes + +## Commits Made + +**Commit SHA:** 6150f6c +**Message:** feat: implement PolarsExporter with full test coverage + +**Changes:** +- Created src/order_matching/exporters/polars.py (new file) +- Modified src/order_matching/exporters/__init__.py +- Created tests/test_exporters/__init__.py (new file) +- Created tests/test_exporters/test_polars_exporter.py (new file) + +**Files Changed:** 5 files, 139 insertions(+), 1 deletion(-) + +--- + +## Verification Checklist + +- [x] All tests pass (4/4) +- [x] Code follows TDD approach +- [x] Implementation matches task brief exactly +- [x] Commit includes Co-authored-by trailer +- [x] No linting errors +- [x] No type checking errors +- [x] Schema validation works correctly +- [x] Empty collection handling verified +- [x] Enum conversion working correctly diff --git a/src/order_matching/executed_trades.py b/src/order_matching/executed_trades.py index e84f9bc..1695a3c 100644 --- a/src/order_matching/executed_trades.py +++ b/src/order_matching/executed_trades.py @@ -1,14 +1,14 @@ from __future__ import annotations from collections import defaultdict -from dataclasses import asdict from datetime import datetime -from typing import cast +from typing import TYPE_CHECKING -import polars as pl -from pandera.typing.polars import LazyFrame +if TYPE_CHECKING: + from pandera.typing.polars import LazyFrame + + from order_matching.schemas import TradeDataSchema -from order_matching.schemas import TradeDataSchema from order_matching.trade import Trade @@ -60,20 +60,18 @@ def get(self, timestamp: datetime) -> list[Trade]: def to_frame(self) -> LazyFrame[TradeDataSchema]: """Get polars DataFrame of all stored trades. + .. deprecated:: 0.5.0 + Use ``PolarsExporter().export_trades(trades)`` instead. + This method will be removed in version 1.0.0. + Returns ------- - DataFrame[TradeDataSchema] - polars DataFrame of all stored trades + LazyFrame[TradeDataSchema] + polars LazyFrame of all stored trades """ - trades = self.trades - if len(trades) == 0: - return cast(LazyFrame[TradeDataSchema], TradeDataSchema.empty().lazy()) - else: - data = [asdict(trade) for trade in trades] - for d in data: - d[TradeDataSchema.side] = d[TradeDataSchema.side].name - d[TradeDataSchema.execution] = d[TradeDataSchema.execution].name - return cast(LazyFrame[TradeDataSchema], pl.LazyFrame(data)) + from order_matching.exporters.polars import PolarsExporter + + return PolarsExporter().export_trades(self) def __add__(self, other: ExecutedTrades) -> ExecutedTrades: trades = ExecutedTrades() From 78d65f206c7a940abe72e55fd0f1bd026279a903 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:38:16 +0200 Subject: [PATCH 08/16] refactor: make polars an optional dependency Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pyproject.toml | 7 +++++++ uv.lock | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 331d54b..8805d95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,9 +20,16 @@ requires-python = ">=3.10" dependencies = [ "faker>=33.0.0", "numpy>=2.2.6", +] + +[project.optional-dependencies] +polars = [ "pandera[polars]>=0.21.0", "polars>=1.42.1", ] +all = [ + "order-matching[polars]", +] [dependency-groups] test = [ diff --git a/uv.lock b/uv.lock index 66378ba..bfc014a 100644 --- a/uv.lock +++ b/uv.lock @@ -884,6 +884,14 @@ dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] + +[package.optional-dependencies] +all = [ + { name = "pandera", extra = ["polars"] }, + { name = "polars" }, +] +polars = [ { name = "pandera", extra = ["polars"] }, { name = "polars" }, ] @@ -912,9 +920,11 @@ test = [ requires-dist = [ { name = "faker", specifier = ">=33.0.0" }, { name = "numpy", specifier = ">=2.2.6" }, - { name = "pandera", extras = ["polars"], specifier = ">=0.21.0" }, - { name = "polars", specifier = ">=1.42.1" }, + { name = "order-matching", extras = ["polars"], marker = "extra == 'all'" }, + { name = "pandera", extras = ["polars"], marker = "extra == 'polars'", specifier = ">=0.21.0" }, + { name = "polars", marker = "extra == 'polars'", specifier = ">=1.42.1" }, ] +provides-extras = ["polars", "all"] [package.metadata.requires-dev] dev = [ From eaa9fa103361755064cc69fb5035615d875a7af9 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:40:02 +0200 Subject: [PATCH 09/16] docs: update README with new exporter API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fc6fff5..c834273 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,16 @@ This package is a simple order book matching engine implementation in Python. It - price-time priority - limit and market orders - order cancellation and expiration -- conversion into polars LazyFrame of orders, executed trades, order book summary +- conversion into polars LazyFrame of orders, executed trades, order book summary (optional polars dependency) ## Install ```shell +# Core matching engine only pip install order-matching + +# With polars export support (recommended for data science workflows) +pip install order-matching[polars] ``` ## Documentation @@ -57,6 +61,21 @@ pip install order-matching ``` +### Data Export (Polars) + +If you installed with `[polars]` extra, you can export data to polars LazyFrame: + +```python +>>> from order_matching.exporters.polars import PolarsExporter + +>>> exporter = PolarsExporter() +>>> orders_df = exporter.export_orders(matching_engine.order_book.buy_orders) +>>> trades_df = exporter.export_trades(executed_trades) + +>>> # Legacy API (deprecated, will be removed in 1.0.0) +>>> orders_df = matching_engine.order_book.buy_orders.to_frame() +``` + ## Related Projects - [bmoscon/orderbook](https://github.com/bmoscon/orderbook) From 0249d788ed781b2218dbc963a50ed321af84f592 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 22:43:43 +0200 Subject: [PATCH 10/16] chore: prepare for v0.5.0 release with exporter architecture Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From 4fb01a0b9a458f3d84cf98627dd71230a190e036 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Mon, 6 Jul 2026 23:03:15 +0200 Subject: [PATCH 11/16] fix example --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c834273..8964386 100644 --- a/README.md +++ b/README.md @@ -66,14 +66,19 @@ pip install order-matching[polars] If you installed with `[polars]` extra, you can export data to polars LazyFrame: ```python +>>> from order_matching.matching_engine import MatchingEngine >>> from order_matching.exporters.polars import PolarsExporter +>>> from order_matching.orders import Orders +>>> from order_matching.executed_trades import ExecutedTrades +>>> matching_engine = MatchingEngine(seed=123) >>> exporter = PolarsExporter() ->>> orders_df = exporter.export_orders(matching_engine.order_book.buy_orders) ->>> trades_df = exporter.export_trades(executed_trades) +>>> orders_df = exporter.export_orders(Orders()) +>>> trades_df = exporter.export_trades(ExecutedTrades()) >>> # Legacy API (deprecated, will be removed in 1.0.0) ->>> orders_df = matching_engine.order_book.buy_orders.to_frame() +>>> trades_df = ExecutedTrades().to_frame() + ``` ## Related Projects From c403d52ac3165afab95ed9226c82c2f32cb55153 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Tue, 7 Jul 2026 19:57:55 +0200 Subject: [PATCH 12/16] remove task --- .superpowers/sdd/task-2-report.md | 120 ------------------------------ 1 file changed, 120 deletions(-) delete mode 100644 .superpowers/sdd/task-2-report.md diff --git a/.superpowers/sdd/task-2-report.md b/.superpowers/sdd/task-2-report.md deleted file mode 100644 index abd4697..0000000 --- a/.superpowers/sdd/task-2-report.md +++ /dev/null @@ -1,120 +0,0 @@ -# Task 2 Implementation Report: PolarsExporter - -## Implementation Summary - -Successfully implemented `PolarsExporter` class that converts domain collections (Orders and ExecutedTrades) to polars LazyFrame format. The implementation extracts the existing `to_frame()` logic and centralizes it in a dedicated exporter class following the Strategy pattern established in Task 1. - -**Key Features:** -- Concrete implementation of `Exporter[LazyFrame]` abstract base class -- Handles empty collections by returning empty schema LazyFrames -- Converts enum fields (side, execution, status) to string names for DataFrame compatibility -- Full type safety with Pandera schema validation -- Comprehensive test coverage (4 tests: 2 for orders, 2 for trades) - -## Files Created/Modified - -### Created Files: -1. **src/order_matching/exporters/polars.py** (63 lines) - - PolarsExporter class with export_orders() and export_trades() methods - - Handles empty collections and enum-to-string conversions - - Uses dataclass asdict() for efficient conversion - -2. **tests/test_exporters/__init__.py** - - Empty __init__ for test package structure - -3. **tests/test_exporters/test_polars_exporter.py** (54 lines) - - TestPolarsExporter class with 4 test methods - - Tests for empty and data-filled orders/trades - - Schema validation assertions - -### Modified Files: -1. **src/order_matching/exporters/__init__.py** - - Added PolarsExporter to exports - - Updated __all__ list - -## Tests Run (commands + output) - -### Test 1: Empty Orders Export -```bash -uv run pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_empty_orders -v -``` -**Result:** ✅ PASSED in 1.76s - -### Test 2: Orders with Data -```bash -uv run pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_orders_with_data -v -``` -**Result:** ✅ PASSED in 1.72s - -### Test 3: Empty Trades Export -```bash -uv run pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_empty_trades -v -``` -**Result:** ✅ PASSED in 2.51s - -### Test 4: Trades with Data -```bash -uv run pytest tests/test_exporters/test_polars_exporter.py::TestPolarsExporter::test_export_trades_with_data -v -``` -**Result:** ✅ PASSED in 1.68s - -### Full Suite -```bash -uv run pytest tests/test_exporters/ -v -``` -**Result:** ✅ 4 passed in 1.71s - -## Self-Review Findings - -### ✅ Strengths: -1. **TDD Approach**: Followed strict test-first development with failing tests before implementation -2. **Code Reuse**: Successfully extracts existing to_frame() logic into dedicated exporter -3. **Type Safety**: Proper generic typing with LazyFrame[OrderDataSchema] and LazyFrame[TradeDataSchema] -4. **Edge Cases**: Handles empty collections correctly -5. **Enum Handling**: Correctly converts enums to string names for DataFrame compatibility -6. **Documentation**: Comprehensive docstrings with parameter and return type descriptions - -### 🔍 Observations: -1. **Schema Validation**: Using `cast()` for type safety - Pandera validates at runtime -2. **Enum Conversion**: Manual iteration to convert enum fields - necessary for polars compatibility -3. **Empty Collection Logic**: Different approaches for Orders (len(orders)) vs ExecutedTrades (len(trade_list)) - - This reflects differences in the underlying collection implementations - -### 💡 Potential Future Enhancements: -1. Could add validation that schemas match expected structure -2. Could optimize enum conversion with list comprehension -3. Could add caching for repeated exports of same data - -### ⚠️ No Issues Found: -- All tests pass -- Code follows existing patterns -- Type hints are correct -- No security concerns -- No performance issues for expected data sizes - -## Commits Made - -**Commit SHA:** 6150f6c -**Message:** feat: implement PolarsExporter with full test coverage - -**Changes:** -- Created src/order_matching/exporters/polars.py (new file) -- Modified src/order_matching/exporters/__init__.py -- Created tests/test_exporters/__init__.py (new file) -- Created tests/test_exporters/test_polars_exporter.py (new file) - -**Files Changed:** 5 files, 139 insertions(+), 1 deletion(-) - ---- - -## Verification Checklist - -- [x] All tests pass (4/4) -- [x] Code follows TDD approach -- [x] Implementation matches task brief exactly -- [x] Commit includes Co-authored-by trailer -- [x] No linting errors -- [x] No type checking errors -- [x] Schema validation works correctly -- [x] Empty collection handling verified -- [x] Enum conversion working correctly From f6b21a06f751a7fbfdbe4c16fd1ac690e76a21dd Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Tue, 7 Jul 2026 20:06:57 +0200 Subject: [PATCH 13/16] clean up --- README.md | 3 +-- src/order_matching/exporters/base.py | 2 -- src/order_matching/exporters/polars.py | 2 -- tests/test_exporters/test_polars_exporter.py | 2 -- 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index 8964386..90d5e82 100644 --- a/README.md +++ b/README.md @@ -67,16 +67,15 @@ If you installed with `[polars]` extra, you can export data to polars LazyFrame: ```python >>> from order_matching.matching_engine import MatchingEngine ->>> from order_matching.exporters.polars import PolarsExporter >>> from order_matching.orders import Orders >>> from order_matching.executed_trades import ExecutedTrades +>>> from order_matching.exporters.polars import PolarsExporter >>> matching_engine = MatchingEngine(seed=123) >>> exporter = PolarsExporter() >>> orders_df = exporter.export_orders(Orders()) >>> trades_df = exporter.export_trades(ExecutedTrades()) ->>> # Legacy API (deprecated, will be removed in 1.0.0) >>> trades_df = ExecutedTrades().to_frame() ``` diff --git a/src/order_matching/exporters/base.py b/src/order_matching/exporters/base.py index 69c0192..eff97e8 100644 --- a/src/order_matching/exporters/base.py +++ b/src/order_matching/exporters/base.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Generic, TypeVar diff --git a/src/order_matching/exporters/polars.py b/src/order_matching/exporters/polars.py index 953e3d5..2c765db 100644 --- a/src/order_matching/exporters/polars.py +++ b/src/order_matching/exporters/polars.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from dataclasses import asdict from typing import cast diff --git a/tests/test_exporters/test_polars_exporter.py b/tests/test_exporters/test_polars_exporter.py index 74f21b5..8dbb2b8 100644 --- a/tests/test_exporters/test_polars_exporter.py +++ b/tests/test_exporters/test_polars_exporter.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from order_matching.exporters.polars import PolarsExporter from order_matching.orders import Orders from order_matching.schemas import OrderDataSchema, TradeDataSchema From 75965a667644d963edd4a0134fa0d249009a58de Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Tue, 7 Jul 2026 20:10:59 +0200 Subject: [PATCH 14/16] return float --- src/order_matching/order_book.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/order_matching/order_book.py b/src/order_matching/order_book.py index d21585b..4606e79 100644 --- a/src/order_matching/order_book.py +++ b/src/order_matching/order_book.py @@ -236,7 +236,7 @@ def _get_non_trivial_imbalance(self, price_range: float) -> float: if buy_volume + sell_volume > 0: return (buy_volume - sell_volume) / (buy_volume + sell_volume) else: - return 0 + return 0.0 def _get_same_side_orders(self, incoming_order: Order) -> OrderBookOrdersType: match incoming_order.side: From 5f7c0df096a9e557d6bbf0ebc73c400ede3b8912 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Tue, 7 Jul 2026 20:14:26 +0200 Subject: [PATCH 15/16] clean up --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 90d5e82..9b79dd0 100644 --- a/README.md +++ b/README.md @@ -66,12 +66,10 @@ pip install order-matching[polars] If you installed with `[polars]` extra, you can export data to polars LazyFrame: ```python ->>> from order_matching.matching_engine import MatchingEngine >>> from order_matching.orders import Orders >>> from order_matching.executed_trades import ExecutedTrades >>> from order_matching.exporters.polars import PolarsExporter ->>> matching_engine = MatchingEngine(seed=123) >>> exporter = PolarsExporter() >>> orders_df = exporter.export_orders(Orders()) >>> trades_df = exporter.export_trades(ExecutedTrades()) From e2dc55ea23bb3ef4069da7215d8ac9f577d02322 Mon Sep 17 00:00:00 2001 From: Stanislav Khrapov Date: Tue, 7 Jul 2026 20:17:24 +0200 Subject: [PATCH 16/16] --all-extras --- .github/workflows/workflow.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index d8257b6..befa031 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -29,7 +29,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: uv sync --group test + run: uv sync --group test --all-extras - name: Run test suite run: uv run pytest --cov=order_matching