Metadata-Version: 2.4
Name: market-wave
Version: 2.2.0
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Rust
Classifier: Topic :: Scientific/Engineering
Classifier: Typing :: Typed
Requires-Dist: numpy>=1.24
Requires-Dist: pytest>=8 ; extra == 'dev'
Requires-Dist: matplotlib>=3.10.9 ; extra == 'dev'
Requires-Dist: maturin>=1.14,<2 ; extra == 'dev'
Requires-Dist: matplotlib>=3.10.9 ; extra == 'visualization'
Provides-Extra: dev
Provides-Extra: visualization
License-File: LICENSE
Summary: Adaptive limit-order market simulator with price-time matching.
Keywords: limit-order-book,market-microstructure,market-simulation,stochastic-simulation
Author: market-wave
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Changelog, https://github.com/smturtle2/market-wave/blob/main/CHANGELOG.md
Project-URL: Homepage, https://github.com/smturtle2/market-wave
Project-URL: Issues, https://github.com/smturtle2/market-wave/issues
Project-URL: Repository, https://github.com/smturtle2/market-wave

<div align="center">

# Market Wave

**Adaptive order flow. Exact price-time matching. No hidden price path.**

A seeded, in-memory continuous double auction for market-microstructure
experiments and synthetic scenario generation.

[![PyPI](https://img.shields.io/pypi/v/market-wave.svg)](https://pypi.org/project/market-wave/)
[![Python](https://img.shields.io/pypi/pyversions/market-wave.svg)](https://pypi.org/project/market-wave/)
[![CI](https://github.com/smturtle2/market-wave/actions/workflows/workflow.yml/badge.svg?branch=main)](https://github.com/smturtle2/market-wave/actions/workflows/workflow.yml)
[![License](https://img.shields.io/pypi/l/market-wave.svg)](LICENSE)

[Quick start](#quick-start) · [Visualization](#visualization) ·
[Model](#how-it-works) · [API](#public-api) · [Development](#development)

</div>

![Market Wave run report with quotes, maker-price fills, bilateral depth, spread, and order flow](https://raw.githubusercontent.com/smturtle2/market-wave/main/artifacts/reference_depth_api.png)

<p align="center"><sub>One 300-second run · seed 7 · N=64 · generated by <code>render_run()</code></sub></p>

## Why Market Wave?

Market Wave generates a market from orders and executions—not from a latent
price series that is corrected after the fact.

- **Exact microstructure** — continuous-time arrivals, maker-price execution,
  strict price-time priority, partial fills, resting remainders, and expiry.
- **Adaptive order flow** — an ensemble of predictive laws learns from every
  completed step at different memory scales.
- **Visible-liquidity feedback** — the live book can reshape marketable and
  spread-improving flow without moving prices directly.
- **Native performance** — the market, order book, RNG, flow, and projection
  solver run in Rust through PyO3.
- **Reproducible experiments** — explicit configuration, immutable results, and
  deterministic seeded sequences for the same release and platform target.
- **Honest visualization** — event-time fills, step-end states, real interval
  widths, shared comparison scales, and no invented midpoint.

> Market Wave models aggregate market intent, not named traders. It is a
> research simulator, not a forecasting or venue-calibration product.

## Installation

Published wheels include the native Rust engine and support CPython 3.10+.

```bash
pip install market-wave
```

Visualization is optional:

```bash
pip install "market-wave[visualization]"
```

Building from source requires Rust 1.83+.

## Quick start

```python
from market_wave import Market, MarketConfig, Trade

market = Market(
    MarketConfig(
        initial_price=100_000,
        tick_size=1,
        step_seconds=1.0,
        order_rate=20.0,
        mean_price_offset_ticks=4.0,
        mean_order_size_lots=3.0,
        mean_order_lifetime_seconds=5.0,
        flow_component_count=64,
        seed=7,
    )
)

steps = market.step(300)
last = steps[-1]
executed_lots = sum(
    event.quantity
    for step in steps
    for event in step.events
    if isinstance(event, Trade)
)

print("best bid:", last.book.best_bid)
print("best ask:", last.book.best_ask)
print("executed lots:", executed_lots)
```

`step()` and `step(1)` return one `Step`; `step(0)` returns `()`; values above
one return `tuple[Step, ...]`. Use `market.stream(count)` when results should be
consumed lazily.

## Visualization

The optional visualization namespace exposes three focused renderers:

```python
from market_wave.visualization import (
    render_book,
    render_comparison,
    render_run,
)

render_run(steps, "run.png", title="Reference run", level_count=10)
render_book(steps[-1], "book.png", level_count=10)
render_comparison(
    {"baseline": steps, "alternative": alternative_steps},
    "comparison.png",
    columns=2,
)
```

| Renderer | Question answered |
|---|---|
| `render_run()` | How did quotes, fills, depth, spread, and order flow evolve? |
| `render_book()` | What exactly was resting at one selected step? |
| `render_comparison()` | How do several runs differ on shared price and depth scales? |

Rendering is non-mutating and writes an atomically replaced PNG. Prices are
converted to integer tick offsets before float plotting, so very large absolute
prices retain their local structure. Bilateral depth uses real step widths and
the fixed row order `Ask LN … Ask L1 | Bid L1 … Bid LN`.

To regenerate the repository's 32-scenario factorial atlas:

```bash
uv run python benchmarks/visualize_32_scenarios.py
```

## How it works

```text
N predictive laws at different memory scales
                  │
                  ▼
combine side, price, quantity, and lifetime marginals
                  │
                  ▼
condition feasible order flow on visible liquidity
                  │
                  ▼
sample → submit → match → rest → expire
                  │
                  ▼
feed the completed Step back to every law
```

### Adaptive ensemble

Law `i` retains `rho_i = (i + 0.5) / N` of its prior evidence. The evenly
spaced retention spectrum supplies short and long memory without a hand-tuned
decay schedule. Every law observes the same completed market step.

### Direct marginal sampling

The engine combines the ensemble before each draw, then samples mathematically
equivalent conditional components directly: discrete-Laplace price offsets,
geometric quantities, and exponential resting lifetimes. Component selection
is ephemeral and never decides which laws receive feedback.

### Liquidity-aware flow

When the visible book admits a support-preserving solution, a minimum-KL
projection reweights marketable, spread-improving, and neutral price regions.
Conditional shapes remain intact. If projection is infeasible, the aggregate
unconditioned distribution is used.

### Exact matching lifecycle

Crossing orders consume resting liquidity at maker prices. Only the unfilled
remainder rests, expiry begins at rest time, and fully filled orders cannot
later cancel. Feedback is applied once, after the half-open step completes.

## Configuration

Every `MarketConfig` field is explicit and required.

| Field | Contract |
|---|---|
| `initial_price` | positive integer, aligned to `tick_size` |
| `tick_size` | positive integer |
| `step_seconds` | finite seconds greater than zero |
| `order_rate` | finite expected orders/second, at least zero |
| `mean_price_offset_ticks` | finite mean absolute offset, at least zero |
| `mean_order_size_lots` | finite mean quantity, at least one |
| `mean_order_lifetime_seconds` | finite mean resting lifetime greater than zero |
| `flow_component_count` | positive ensemble size N |
| `seed` | integer |

Prices and quantities remain exact Python integers throughout the public model.

## Public API

| Surface | Contract |
|---|---|
| `Market.step(n=1)` | eagerly advances consecutive feedback intervals |
| `Market.stream(count)` | lazily advances the same market |
| `Step.events` | chronological `Submission`, `Trade`, and `Cancellation` values |
| `Step.book` | immutable step-end `BookSnapshot` |
| `Market.book` | current immutable book snapshot |
| `Market.buy_distribution`, `sell_distribution` | current aggregate price laws |
| `EntryDistribution.probability()`, `.cdf()` | exact aggregate price PMF and CDF |
| `market_wave.visualization` | optional run, book, and comparison PNG renderers |

Calling `step()` or consuming `stream()` mutates only the market's forward
state. Returned events, steps, books, and distributions do not retain a mutable
engine back-reference.

## Performance and validation

The repository tests matching invariants, event lifecycles, seeded
reproducibility, conditional marginal sampling, numerical boundaries, native
batching, parallel markets, adaptive feedback, and visualization semantics.

```bash
uv run python benchmarks/benchmark_market.py
```

On the release validation host (Linux x86-64, CPython 3.14), the default
`flow_component_count=64` workload measured 4.04 ms/step after warm-up, or
247.6 steps/s. Treat this as a comparison point, not a runtime guarantee.

## Development

```bash
uv sync --extra dev
uv run maturin develop --release --locked
uv run pytest
cargo test --manifest-path rust/Cargo.toml
```

Release artifacts are built for Linux glibc, Linux musl, macOS universal2, and
Windows through GitHub Actions. See the [changelog](CHANGELOG.md) for versioned
behavior changes.

## Scope and license

Market Wave intentionally has no CLI, persistence layer, replay engine, hidden
calibration state, named-agent model, or financial forecast. Callers decide
which generated immutable results to retain.

Released under the [MIT License](LICENSE).

