Metadata-Version: 2.4
Name: market-wave
Version: 2.0.0
Summary: Adaptive limit-order market simulator with price-time matching.
Author: market-wave
License-Expression: MIT
Project-URL: Homepage, https://github.com/smturtle2/market-wave
Project-URL: Repository, https://github.com/smturtle2/market-wave
Project-URL: Issues, https://github.com/smturtle2/market-wave/issues
Project-URL: Changelog, https://github.com/smturtle2/market-wave/blob/main/CHANGELOG.md
Keywords: limit-order-book,market-microstructure,market-simulation,stochastic-simulation
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Scientific/Engineering
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Provides-Extra: visualization
Requires-Dist: matplotlib>=3.10.9; extra == "visualization"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: matplotlib>=3.10.9; extra == "dev"
Dynamic: license-file

<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 symmetric order-book depth heatmap](https://raw.githubusercontent.com/smturtle2/market-wave/main/artifacts/reference_depth_api.png)

*A 300-second seeded run rendered by the public API. Time runs left to right;
Ask L1 and Bid L1 meet at the center of the price-independent level ladder.*

## Install

Market Wave requires Python 3.10 or newer.

```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 identical Market Wave, Python, and
NumPy versions, a fresh `Market` with the same configuration and seed produces
exactly the same sequence.

```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)
```

`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
        │
        ├── combine side probabilities and price PMFs
        ├── combine quantity distributions
        └── combine lifetime distributions
        │
        ▼
sample each aggregate CDF directly
        │
        ▼
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. Aggregate first, then sample

**An order is never assigned to one component.** The engine combines all N laws
into side-conditional aggregate distributions and samples their aggregate CDFs
directly. 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()` | advances one feedback interval and returns one immutable `Step` |
| `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, aggregate-CDF sampling, numerical boundaries, 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.

## Scope

Market Wave is Python-only and in memory. 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).
