Metadata-Version: 2.4
Name: market-wave
Version: 2.1.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.**

[![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/)
[![Tests](https://github.com/smturtle2/market-wave/actions/workflows/workflow.yml/badge.svg)](https://github.com/smturtle2/market-wave/actions/workflows/workflow.yml)
[![License](https://img.shields.io/badge/license-MIT-0b7285.svg)](https://github.com/smturtle2/market-wave/blob/main/LICENSE)

</div>

Market Wave is a seeded, in-memory continuous double auction driven
by an adaptive ensemble of N predictive distributions. Orders arrive in
continuous time, walk a live limit-order book, and match with price-time
priority. Prices, spreads, and liquidity emerge only from those orders and
executions—never from a latent price path or a post-generation correction.

The model represents aggregate market intent, not named traders. It is built
for market-microstructure experiments and synthetic scenario generation, not
for forecasting or calibrating a particular venue.

![Market Wave live quotes, matched trades, and symmetric level-rank depth](https://raw.githubusercontent.com/smturtle2/market-wave/main/artifacts/reference_depth_api.png)

*One 300-second run (seed 7, N=64). Top: step-end best quotes and matched trades
at their event times. Bottom: step-end resting quantity by side-relative rank;
absolute price never enters the depth axis.*

## Install

Market Wave requires CPython 3.10 or newer. Published wheels contain the Rust
simulation engine; source installations require a Rust 1.83+ toolchain.

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

The renderer is optional, so simulation-only installs do not pull in
Matplotlib:

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

## Quick start

Every configuration field is explicit. With the same Market Wave wheel target,
a fresh `Market` with the same configuration and seed produces exactly the same
sequence. Version 2.1 intentionally starts a new seeded sequence and is not
trace-compatible with 2.0.

```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 = tuple(market.stream(count=300))
last = steps[-1]
trade_count = sum(
    isinstance(event, Trade)
    for step in steps
    for event in step.events
)

print("best bid:", last.book.best_bid)
print("best ask:", last.book.best_ask)
print("trades:", trade_count)
```

For eager native batching, `market.step(n)` advances `n` consecutive feedback
intervals in one extension call. `step()` and `step(1)` return one `Step`,
`step(0)` returns `()`, and values above one return `tuple[Step, ...]`.

`flow_component_count=64` is an ensemble-resolution choice, not a calibrated
market constant. Larger values sample the unit retention interval more densely
and closer to both endpoints, at greater ensemble cost; values down to one are
valid.

## Visualize depth

Pass an already-produced, finite sequence of consecutive steps to the pure
renderer:

```python
from market_wave import render_depth_heatmap

path = render_depth_heatmap(
    steps,
    "artifacts/depth.png",
    level_count=12,
    title="Reference run · symmetric level ladder",
)
print(path)
```

The visualization contract is deliberately narrow:

- x-axis: simulation time, with one step-end book snapshot per column;
- y-axis: side-relative book rank, independent of absolute price;
- row order: `Ask L{N} ... Ask L1 | Bid L1 ... Bid L{N}` from top to bottom;
- color: a shared `log(1 + resting quantity)` scale;
- input: a non-empty `Sequence[Step]` with consecutive indices and contiguous
  times;
- output: a PNG file; missing parent directories are created and the resolved
  `Path` is returned.

Rendering never advances or mutates the market. A larger
[six-scenario comparison](https://github.com/smturtle2/market-wave/blob/main/artifacts/n_distribution_scenarios.png)
shows how activity, lifetime, placement width, seed, and N change the visible
market.

## How the engine works

```text
N adaptive predictive laws in the Rust engine
        │
        ├── combine side probabilities and price PMFs
        ├── combine quantity distributions
        └── combine lifetime distributions
        │
        ▼
sample each exact marginal by conditional factorization
        │
        ▼
submit → match → rest → expire
        │
        ▼
completed Step feedback returns to every law
```

### 1. N memory scales, one completed observation

Every predictive law observes the same completed step. Law `i` retains a
different fraction of its prior evidence:

```text
rho_i = (i + 0.5) / N
```

`rho_i` is the evidence retained at each update, so larger values mean longer
memory. The evenly spaced spectrum supplies multiple time scales without a
hand-tuned decay schedule. Each law tracks order intensity, side probability,
relative-price scale, order-size scale, and cancellation hazard.

### 2. Preserve the aggregate marginal, then sample directly

The engine combines all N laws into side-conditional aggregate distributions.
For each price, quantity, and lifetime marginal it chooses an ephemeral
conditional component and samples the corresponding truncated law directly.
Those choices are independent, are never stored on an order, and never select
which predictive law receives feedback. Price offsets use discrete-Laplace
probability mass functions (PMFs), quantities use geometric components, and
resting lifetimes use exponential components. Their support is unbounded except
for the positive-price boundary.

### 3. Let visible liquidity reshape flow

When both book sides provide a support-preserving solution, the engine divides
each price PMF into marketable, spread-improving, and neutral regions. It then
applies the minimum-KL reweighting that balances predicted buy and sell quote
impact while preserving the conditional shape inside each region. If such a
projection is infeasible, the unconditioned aggregate distributions are used.

Likelihood-ratio correction keeps the resulting liquidity constraint from
teaching the base price law its own selection bias. This feedback changes order
flow; it never moves a price directly.

### 4. Match before learning

Crossing orders consume resting liquidity at maker prices under strict
price-time priority. Only an unfilled remainder rests. Expiration clocks begin
when orders rest, and fully filled orders cannot emit later cancellation events.
The resulting submissions, sides, offsets, sizes, expirations, and live-order
exposure feed every predictive law exactly once at the end of the half-open
step.

## Public contract

All `MarketConfig` fields are required:

| Field | Contract |
|---|---|
| `initial_price` | positive integer and an exact multiple of `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 in ticks, at least zero |
| `mean_order_size_lots` | finite mean quantity in lots, at least one |
| `mean_order_lifetime_seconds` | finite mean resting lifetime greater than zero |
| `flow_component_count` | positive integer N |
| `seed` | integer |

The constructor rejects non-finite or numerically unrepresentable values.
Prices and quantities remain exact Python integers.

The top-level API is intentionally small:

| Surface | Contract |
|---|---|
| `Market.step(n=1)` | eagerly advances `n` intervals; returns one `Step` for `n=1`, otherwise a tuple |
| `Market.stream(count)` | lazily advances the same market; `count=None` is unbounded |
| `Step.events` | chronological `Submission`, `Trade`, and `Cancellation` values in `[start_time, end_time)` |
| `Step.book` | immutable step-end `BookSnapshot` with ranked `Level` values |
| `Market.book` | current immutable book snapshot |
| `Market.buy_distribution`, `sell_distribution` | current aggregate price laws |
| `EntryDistribution.probability()`, `.cdf()` | exact public aggregate price PMF and CDF |
| `render_depth_heatmap()` | optional, non-mutating PNG renderer |

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

## Quantitative checks

The test suite covers matching invariants, event lifecycles, exact seeded
reproducibility, direct conditional marginal sampling, numerical boundaries,
native batching, independent threaded markets, feedback, and visualization
semantics. A separate fixed regression run is also compared with 256 permuted,
Poisson, or Gaussian null samples: sign persistence, activity persistence,
one-step absolute-return persistence, event-count dispersion, and return
kurtosis must each exceed the 99th percentile of the relevant null. Its 10-step
variance ratio must remain inside the central 98% of the permuted-return null.

The repository includes a reproducible native-engine benchmark:

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

On the 2.1.0 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. This is a workload and machine measurement rather than a runtime
guarantee; use the script to compare builds on the target deployment host.

## Scope

Market Wave is an in-memory CPython package backed by a Rust engine. It
intentionally provides no CLI, persistence layer, replay engine, hidden
calibration state, named-agent model, or financial forecast. The engine retains
only bounded predictive state and the live order book; callers choose which
yielded results to keep.

Released under the [MIT License](https://github.com/smturtle2/market-wave/blob/main/LICENSE).

