Automated two-sided market maker for Lighter perpetual futures. Spread width is driven by realized volatility, and an order-book imbalance (OBI) signal computed from Binance's order book serves as the alpha factor to bias quotes ahead of anticipated moves. Inventory skew tilts quotes to mean-revert position toward zero.
Affiliate link to support this project: Trade on Lighter — spot & perpetuals, fully decentralized, no KYC, zero fees (100% kickback with this link).
- Python 3.13+ (venv recommended)
- C compiler and Python dev headers — required to build the Cython extension
- Debian/Ubuntu:
sudo apt install build-essential python3-dev - macOS:
xcode-select --install - Windows (MinGW — recommended): install MinGW-w64 (or via WinLibs), add its
bin/toPATH, then build withpython setup_cython.py build_ext --inplace --compiler=mingw32 - Windows (MSVC — alternative): install Visual Studio Build Tools with the "Desktop development with C++" workload
- Debian/Ubuntu:
- Lighter API credentials (private key, public key, account index, API key index)
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python setup_cython.py build_ext --inplaceThe last command compiles the Cython extension (_vol_obi_fast.so) with -O3 -march=native -ffast-math for ~30x faster order book and spread computation. The bot works without it (pure-Python fallback) but the extension is strongly recommended for production.
- Create
.envwith your credentials:
API_KEY_PRIVATE_KEY=0x...
API_KEY_PUBLIC_KEY=0x...
API_KEY_INDEX=25
ACCOUNT_INDEX=107607
- Edit
config.jsonto tune trading parameters (see below).
source venv/bin/activate
python -u market_maker_v2.py --symbol BTCAlways use -u for unbuffered log output. Logs are written to stdout and logs/.
market_maker_v2.py Main market-making loop
vol_obi.py VolObiCalculator + RollingStats spread model
_vol_obi_fast.pyx Cython extension (RollingStats, VolObiCalculator, CBookSide)
setup_cython.py Build script for the Cython extension
binance_obi.py Binance Futures OBI alpha signal
orderbook.py Local order book update logic
orderbook_sanity.py WS vs REST book cross-check
ws_manager.py WebSocket subscription manager
utils.py Shared helpers (config loading, Parquet I/O)
logging_config.py Centralized logging setup
adjust_leverage.py CLI tool to set leverage/margin mode
config.json Runtime configuration
tests/ Unit tests (pytest)
- RollingStats maintains a ring buffer of mid-price samples and computes rolling volatility in O(1) using Welford's online algorithm for numerical stability.
- VolObiCalculator combines volatility with order-book imbalance (OBI) to produce skewed bid/ask half-spreads. Spreads widen with volatility and shift toward the heavier side of the book. A crossed-quote guard ensures bid < ask after tick rounding.
A background WebSocket streams Binance Futures depth data. The OBI signal biases quotes before they hit Lighter. Stale signals (>5s) are discarded.
The quote update threshold scales with volume quota pressure:
| Quota remaining | Multiplier | Effect at 10bp base |
|---|---|---|
| >= 500 | 1x | 10bp |
| 50 - 499 | 2x | 20bp |
| 10 - 49 | 3.5x | 35bp |
| 0 - 9 | 5x | 50bp |
This conserves quota when it's scarce by only requoting on larger price moves.
When quota drops below threshold, the bot can execute small IOC market orders via the free 15-second slot to generate fill volume and rebuild quota.
The maximum position size is computed dynamically each loop iteration from live account data: (available_capital * leverage - 2 * order_value) * 0.9. When position value reaches this limit, the side that would increase exposure is suppressed (buy orders suppressed when long at limit, sell orders suppressed when short at limit) and any existing orders on that side are canceled. The vol_obi skew normalization stays in sync with this dynamic limit.
- Orderbook sanity: periodic REST snapshots cross-checked against WS book
- Stale order poller: reconciles local vs exchange order state; PLACING orders timeout after 30s
- Circuit breaker: pauses trading after consecutive rejections; force-clears stuck orders after 5 retries
- Max live orders: caps open orders per market
- Adaptive backoff: exponential 429 backoff (15s→120s) with 5-minute time-based auto-decay
- Nonce safety:
acknowledge_failure+hard_refresh_nonceon all error paths
| Key | Default | Description |
|---|---|---|
leverage |
2 |
Leverage set at startup |
margin_mode |
cross |
cross or isolated |
levels_per_side |
1 |
Quote levels per side |
base_amount |
0.0002 |
Fallback order size (base currency) |
capital_usage_percent |
0.12 |
Fraction of balance per order |
default_quote_update_threshold_bps |
10.0 |
Base requote threshold (adaptive system may widen) |
spread_factor_level1 |
2.0 |
Level 1 spread multiplier |
order_timeout_seconds |
30.0 |
Order placement timeout |
position_value_threshold_usd |
11.0 |
USD threshold to consider position flat |
min_order_value_usd |
14.5 |
Minimum order value in USD |
| Key | Default | Description |
|---|---|---|
window_steps |
6000 |
Rolling window length |
step_ns |
100000000 |
Step size in nanoseconds (100ms) |
vol_to_half_spread |
48.0 |
Volatility to half-spread multiplier |
min_half_spread_bps |
8.0 |
Minimum half-spread (bps) |
c1_ticks |
20.0 |
OBI skew coefficient in ticks |
skew |
3.0 |
Global skew scaling factor |
looking_depth |
0.025 |
Book depth fraction for OBI |
min_warmup_samples |
100 |
Samples before live quoting |
warmup_seconds |
600 |
Warmup period (10 minutes) |
| Key | Default | Description |
|---|---|---|
source |
binance |
Alpha source (binance or none) |
stale_seconds |
5.0 |
Max age before signal is discarded |
window_size |
6000 |
Rolling window for Binance OBI |
min_samples |
150 |
Minimum samples before signal is active |
looking_depth |
0.025 |
Binance book depth fraction for OBI |
| Key | Default | Description |
|---|---|---|
min_loop_interval |
0.1 |
Min seconds between loop iterations |
rate_limit_send_interval |
0.1 |
Min seconds between order sends |
| Key | Default | Description |
|---|---|---|
enabled |
true |
Enable automatic quota recovery |
trigger_threshold |
5 |
Quota level that triggers recovery |
target_quota |
50 |
Target quota to recover to |
max_attempts |
3 |
Max recovery attempts per cooldown |
max_loss_usd |
2.0 |
Max acceptable loss per recovery order |
cooldown_seconds |
120 |
Cooldown between recovery cycles |
post_warmup_grace_seconds |
120 |
Grace period after warmup before recovery |
| Key | Default | Description |
|---|---|---|
ping_interval |
20 |
WS ping interval (seconds) |
recv_timeout |
30.0 |
WS receive timeout (seconds) |
account_recv_timeout |
300.0 |
Account WS receive timeout (seconds) |
reconnect_base_delay |
5 |
Base reconnect backoff (seconds) |
reconnect_max_delay |
60 |
Max reconnect backoff (seconds) |
| Key | Default | Description |
|---|---|---|
stale_order_poller_interval_sec |
3.0 |
Order reconciliation interval |
stale_order_debounce_count |
2 |
Mismatches before safety pause |
max_consecutive_order_rejections |
5 |
Rejections triggering circuit breaker |
circuit_breaker_cooldown_sec |
60.0 |
Cooldown after circuit breaker trips |
order_reconcile_timeout_sec |
2.0 |
Cancel confirmation timeout |
max_live_orders_per_market |
2 |
Max open orders per market |
Config precedence: env var > config.json > hardcoded default.
source venv/bin/activate
pytest tests/ -vThis is experimental trading software. It will likely lose money and is not competitive with professional market makers. Always start with small amounts and understand the risks of automated trading.