Metadata-Version: 2.4
Name: marketlens
Version: 1.0.0
Summary: Python SDK for Polymarket order book data and backtesting. Tick-level L2 snapshots, billions of deltas, full book reconstruction, and a strategy backtesting engine with realistic execution.
License-Expression: MIT
Requires-Python: >=3.10
Requires-Dist: httpx>=0.25
Requires-Dist: pandas>=1.5
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# marketlens

Backtest prediction market strategies on tick-level L2 order book data from Polymarket.

```bash
pip install marketlens
```

## Backtest

Define a strategy, run it against any market or series — the engine replays full L2 book state tick-by-tick with realistic execution.

```python
from marketlens import MarketLens
from marketlens.backtest import Strategy

class SpreadTimer(Strategy):
    def on_market_start(self, ctx, market, book):
        self._entered = False

    def on_book(self, ctx, market, book):
        if self._entered:
            return
        if book.spread_bps() and book.spread_bps() < 300:
            ctx.buy_yes(size="200")
            self._entered = True

client = MarketLens()  # uses MARKETLENS_API_KEY env var
result = client.backtest(
    SpreadTimer(), "sol-up-or-down-hourly",
    initial_cash="10000",
    after="2026-03-05T10:00Z", before="2026-03-05T14:00Z",
)
print(result)
```

Pass a market ID, series slug, or a list of series for multi-asset portfolios:

```python
# Single market
result = client.backtest(strategy, market_id, initial_cash="10000")

# Rolling series — walks every market in the series chronologically
result = client.backtest(strategy, "btc-up-or-down-5m", initial_cash="10000",
                         after="2026-03-05", before="2026-03-06")

# Multi-asset portfolio — shared capital across series
result = client.backtest(strategy,
    ["btc-up-or-down-5m", "eth-up-or-down-5m", "sol-up-or-down-5m"],
    initial_cash="10000", after="2026-03-05", before="2026-03-06")

# Structured product — parallel replay of all strike markets in the series
result = client.backtest(strategy, "btc-multi-strikes-weekly", initial_cash="10000")
```

### Execution realism

| Parameter | Default | Description |
|-----------|---------|-------------|
| `latency_ms` | `50` | Order-to-fill delay in milliseconds |
| `queue_position` | `False` | CLOB queue modeling — fills only when queue-ahead is drained by trades |
| `limit_fill_rate` | `0.1` | Fraction of trade size filling your limit (ignored when `queue_position=True`) |
| `slippage_bps` | `0` | Extra price penalty on market order fills |
| `fees` | `"polymarket"` | Auto-detects crypto vs sports fee schedule; `None` for zero fees |
| `max_fill_fraction` | `1.0` | Max fraction of each book level consumed per order |
| `include_trades` | `True` | Fetch trade data (required for limit fills and `on_trade`) |

### Strategy hooks

| Hook | Called when |
|------|------------|
| `on_book(ctx, market, book)` | Every book state change (snapshot or delta) |
| `on_trade(ctx, market, book, trade)` | Every executed trade |
| `on_fill(ctx, market, fill)` | Your order is filled |
| `on_market_start(ctx, market, book)` | A new market begins |
| `on_market_end(ctx, market)` | A market ends, before settlement |

`ctx` provides: `buy_yes()`, `sell_yes()`, `buy_no()`, `sell_no()`, `cancel_order()`, `cancel_all_orders()`, `position()`, `open_orders`, `books` (all active order books), and `reference_price()` (Binance spot for crypto underlyings).

### Results

```python
result.total_pnl            # net P&L
result.total_return         # as decimal (0.12 = 12%)
result.win_rate             # fraction of profitable settlements
result.sharpe_ratio         # per-settlement Sharpe
result.sortino_ratio        # downside-adjusted
result.max_drawdown         # peak-to-trough as fraction
result.profit_factor        # gross wins / gross losses
result.expectancy           # avg net P&L per settlement

result.trades_df()          # per-fill DataFrame
result.orders_df()          # per-order DataFrame
result.settlements_df()     # per-market settlement P&L
result.equity_df()          # equity curve time series
result.by_series()          # per-series P&L attribution
```

## Data

All list methods return auto-paginating iterators with `.to_list()` and `.to_dataframe()`.

### Order book replay

`walk()` replays full L2 book state for any market or series. Pass a market ID, series slug, or condition ID — the same interface for everything.

```python
for market, book in client.orderbook.walk("btc-up-or-down-5m", status="resolved"):
    print(market.question, book.midpoint, book.spread_bps())

# As a DataFrame
df = client.orderbook.walk(market_id, after=start, before=end).to_dataframe()
```

### Candles, trades, markets

```python
candles = client.markets.candles(market_id, resolution="1h").to_dataframe()
trades = client.markets.trades(market_id, after=start, before=end).to_list()
active = client.markets.list(status="active", sort="-volume", limit=10).first_page()
```

### Bulk export

Download full-day Parquet files — one file per market per day, no pagination.

```python
path = client.exports.download(market_id, table="deltas", date="2026-03-07")
paths = client.exports.download_range(
    market_id, table="snapshots", after="2026-03-01", before="2026-03-08")
```

## Structured Products & Surfaces

For multi-strike series (survival, density, barrier), all sibling markets replay in parallel. `walk.books` holds the latest book for every strike, and `walk.surface()` fits the implied probability distribution at each tick.

```python
walk = client.orderbook.walk("btc-multi-strikes-weekly")
for market, book in walk:
    surface = walk.surface()
    if surface:
        for s in surface.survival_strikes():
            print(f"${s.strike:,.0f} P(above)={s.fitted_prob:.3f}")
        print(f"implied_mean=${float(surface.implied_mean):,.0f}")
```

| Type | Source | Stats |
|------|--------|-------|
| `survival` | "above $X" multi-strike markets | `implied_mean`, `implied_cv`, `implied_skew` |
| `density` | Neg-risk range + tail markets | `implied_mean`, `implied_cv`, `implied_skew` |
| `barrier` | Hit-price reach/dip markets | `implied_peak`, `implied_trough` |

Pre-computed surfaces updated every 5 minutes are also available via `client.signals.surfaces()`.

## OrderBook

Every `OrderBook` instance — live or replayed — carries analytical methods:

```python
book.microprice()              # size-weighted mid from best level
book.weighted_midpoint(n=3)    # n-level weighted mid
book.spread_bps()              # spread in basis points
book.imbalance(levels=3)       # bid/ask imbalance [-1, 1]
book.impact("BUY", "1000")     # VWAP for $1k market buy
book.slippage("BUY", "1000")   # slippage from mid
book.depth_within("0.02")      # (bid, ask) depth within 2c of mid
```

## Reference Prices

Binance spot at 1-second resolution for crypto underlyings (BTC, ETH, SOL, XRP, etc.). Available directly or inside backtests via `ctx.reference_price()`.

```python
for candle in client.reference.candles("BTC", after=start, before=end):
    print(candle.timestamp, candle.close)
```

## API Reference

| Resource | Methods |
|----------|---------|
| `client.markets` | `list()` `get()` `trades()` `candles()` |
| `client.events` | `list()` `get()` `markets()` |
| `client.series` | `list()` `get()` `markets()` `walk()` `events()` |
| `client.orderbook` | `get()` `history()` `metrics()` `walk()` |
| `client.signals` | `surfaces()` `surface()` `history()` |
| `client.reference` | `candles()` |
| `client.exports` | `download()` `download_range()` |

Async: use `AsyncMarketLens` — every method has an async counterpart.

## Examples

| Example | Description |
|---------|-------------|
| [`backtest_basic.py`](examples/backtest_basic.py) | Spread-timing strategy on a rolling series |
| [`backtest_limit_orders.py`](examples/backtest_limit_orders.py) | Market-making with CLOB queue position simulation |
| [`backtest_surface.py`](examples/backtest_surface.py) | Surface mispricing with spot-distance filtering |
| [`backtest_portfolio.py`](examples/backtest_portfolio.py) | Multi-series portfolio with shared capital |
| [`execution_cost.py`](examples/execution_cost.py) | Book depth, spread, impact and slippage |
| [`microstructure.py`](examples/microstructure.py) | Feature matrix — does imbalance predict outcome? |
| [`implied_surfaces.py`](examples/implied_surfaces.py) | Survival, density, and barrier surfaces |
| [`event_strikes.py`](examples/event_strikes.py) | Structured product walk with live surface fitting |

## License

MIT
