Strategy file: cartaJaiMicroMM.js Market: futures
Description
Cartea-Jaimungal-Penalva (2015) Proposition 10.5 inventory skew. Drift-aware speculative MM with micro-profit exits. Futures.
How it works
Cartea, Jaimungal & Ricci (2014) extend Avellaneda-Stoikov by adding short-term alpha signals (such as order-book imbalance, microprice deviation, or trade-flow imbalance) into the HJB equation that governs optimal quoting under inventory aversion. The resulting quotes skew not only against inventory but also in the direction of the predictive signal, capturing edge that pure A-S leaves on the table. The strategy implements a discretised version of the optimal control: each cycle it solves a small dynamic-programming step that trades off expected fill rate, alpha-driven expected price drift, and inventory cost. Empirically dominates A-S on futures venues with informative microstructure signals.
Capital & Period
Order sizing, leverage, candle timeframe
| Key | Type | Default | Description |
|---|---|---|---|
PERIOD |
select | 1 |
Candle timeframe. |
LEVERAGE |
range (range 1..20) | 5 |
Futures leverage. |
TRADING_LIMIT |
range (range 50..5000) | 400 |
Gunbot-level cap; keep equal to Per-order size. |
CJ_TL |
range (range 50..5000) | 400 |
Quote-currency notional per order. |
CJ_CAPITAL_ALLOC |
range (range 500..50000) | 5000 |
Per-pair capital cap. |
Allocation
How much of THIS pair’s wallet the strategy may use, what it must start with, and the hard config gate. Trading is BLOCKED until Wallet allocation % is set AND Allocation confirmed is ON. Optional grid splits an order into laddered post-only rungs for better fills.
| Key | Type | Default | Description |
|---|---|---|---|
ALLOC_PCT |
range (range 1..100) | 25 |
Percentage of THIS pair’s wallet balance the strategy may deploy. Effective cap = min(wallet × this %, the absolute Capital allocation). Every order is clamped to it and can never exceed it. REQUIRED — trading is blocked until this is set (>0) and Allocation confirmed is ON. |
ALLOC_CONFIRMED |
boolean | False |
Explicit acknowledgement that the allocation above is correct for this pair. The strategy will NOT place any entry order until this is ON. A clear "ALLOCATION CONFIGURED" line is logged once it is; otherwise an "ALLOCATION NOT CONFIGURED — refusing to trade" line is logged each cycle. |
ALLOC_SPLIT_TOL |
range (range 0..100) | 15 |
How far the starting base/quote split may deviate from what this strategy needs before it refuses to start. Long-only strategies want mostly quote/cash; two-sided market-makers want ~50/50 base/quote; futures want free margin. Mismatch beyond this % blocks trading (unless Enforce start inventory is OFF). |
ALLOC_ENFORCE_SPLIT |
boolean | True |
When ON, the strategy refuses to trade until the starting base/quote split is within tolerance of what it needs (archetype-aware). When OFF, a mismatch is logged as a warning but trading proceeds. |
ALLOC_AUTO_REBALANCE |
boolean | True |
Two-sided market-makers only. ON by default: if the pair starts off the target base/quote split, the strategy places a single bounded market order on start to reach it (e.g. buys ~half the allocation into base for a 50/50 maker), then begins normal trading. Set to OFF to instead stay blocked and log the exact amount to buy/sell manually. Long-only and futures strategies ignore this. |
ALLOC_RESERVE_PCT |
range (range 0..50) | 0 |
A buffer, as % of wallet, kept BELOW the allocation cap and never deployed. Use to leave headroom for fees/slippage. Effective cap = min(wallet × allocation %, absolute cap) − wallet × this %. |
CJ_GRID |
boolean | False |
When ON, an entry order is split into several laddered post-only rungs across a price band instead of one order — often gets better average fills. Cumulative size is still clamped to the allocation. OFF = single order. |
CJ_GRID_LEVELS |
range (range 2..10) | 3 |
Number of laddered rungs to split an entry into when "Split entries into a grid" is ON. More rungs = finer fills but more orders. |
CJ_GRID_SPAN_PCT |
range (range 0.1..5) | 0.5 |
Price band width, as % from the reference price, across which the grid rungs are spread. e.g. 0.5 places rungs from the reference price down to 0.5% below it (for buys). |
CJ Inventory Skew
Closed-form half-spread skew (CJ-Penalva Prop. 10.5)
| Key | Type | Default | Description |
|---|---|---|---|
CJ_GAMMA |
range (range 100..2000) | 500 |
Coefficient in the (γ/2)·σ²·τ·(2q+1) inventory penalty. Larger γ = stronger inventory lean (the wider/tighter the two sides get when q ≠ 0). Default 500 chosen so c1 ≈ 4 bps at typical 1-min σ; tune up/down to scale the inventory effect. |
CJ_TAU |
range (range 0.5..4) | 1.5 |
Horizon scaling. Co-tunes with γ — doubling τ at fixed γ doubles the inventory skew. |
CJ_BASE_HALF |
range (range 1..25) | 5 |
Floor half-spread when inventory is flat. The full CJ closed-form (1/k − μ/2λ) base is replaced with this configurable floor. |
CJ_MAX_HALF |
range (range 5..60) | 25 |
Cap on the total half-spread including inventory skew. |
CJ_VOL_LB |
range (range 10..60) | 20 |
Lookback used by the Parkinson volatility estimator that feeds σ in the skew formula. |
Drift-Aware Exit
Heuristic — extends the exit target when drift favours the position
| Key | Type | Default | Description |
|---|---|---|---|
CJ_MICRO_BPS |
range (range 1..20) | 4 |
Base profit target above break-even for the post-only exit. Smaller = exit faster with less profit per turn. |
CJ_DRIFT_EMA |
range (range 0.05..0.6) | 0.2 |
Smoothing weight for the per-cycle log-return drift estimate. Higher = more responsive drift signal. |
CJ_DRIFT_MULT |
range (range 0..6) | 3 |
How far the drift signal can extend the exit target above MICRO_BPS. 0 = drift extension disabled (always exit at MICRO_BPS). |
CJ_MICRO_STALE |
range (range 3..40) | 10 |
After this many cycles in position with no exit, the target starts sliding down toward break-even. |
CJ_MAX_STALE |
range (range 5..80) | 25 |
Cycles in position before the strategy gives up and fires a market scratch. |
Exits & Inventory
Position cap and order management
| Key | Type | Default | Description |
|---|---|---|---|
CJ_MAX_POS_Q |
range (range 0.5..6) | 2.5 |
Inventory cap as multiples of TL. |
CJ_STALE_CYCLES |
range (range 2..20) | 4 |
Resting orders older than N cycles are cancelled. |
Risk & Safety
Exposure caps and circuit breakers
| Key | Type | Default | Description |
|---|---|---|---|
CJ_MAX_EXP |
range (range 20..100) | 70 |
Max position notional / (wallet × leverage). |
CJ_MAX_DD |
range (range 2..30) | 5 |
Peak-to-trough equity drop pause. |
CJ_DAILY_LOSS |
range (range 0.5..10) | 2 |
Daily P&L floor as a percentage of allocation. |
Fees
Fee tier (informational)
| Key | Type | Default | Description |
|---|---|---|---|
CJ_FEE |
range (range 0..0.2) | 0.05 |
Taker fee for your exchange tier, as a percentage. Used in P&L bookkeeping inside the strategy and to size break-even targets. Get the exact number from your exchange fee schedule for your VIP/maker tier; defaults assume retail tier on Bybit/Binance. |
CJ_MAKER_REBATE |
range (range -0.05..0.1) | 0.02 |
Maker rebate as a positive percentage of fill notional. Bybit institutional MM tier ≈ 0.015%; many retail tiers are zero. Used to estimate harvested rebate value in the sidebar. |
CJ_SP |
range (range 2..8) | 4 |
Number of decimal places used to format prices in logs and the sidebar. Purely display — does not affect order rounding (Gunbot handles tick size automatically). Set higher for low-priced pairs (SHIB, etc.) and lower for high-priced pairs (BTC, ETH). |
Runtime & Exchange
Logging, warmup, exchange safety toggles
| Key | Type | Default | Description |
|---|---|---|---|
LOG_LEVEL |
select | NORMAL |
Controls how much the strategy writes to Gunbot logs. SILENT keeps only errors and circuit-breaker messages. NORMAL writes one summary line per cycle plus order events — recommended for live trading. DEBUG adds the internal model state and every decision; use only when investigating a problem. |
WARMUP_CYCLES |
range (range 0..30) | 10 |
How many cycles the strategy collects data before placing any orders. Indicators (ATR, vol, etc.) need a few cycles to stabilise; trading too early gives noisy values. Raise this on slower timeframes (15m candles or longer) or after restarting an empty pair. 0 disables the warmup. |
MIN_ORDER_QUOTE |
range (range 1..25) | 5 |
Floor for order notional in quote currency (typically USDT). The runtime always uses max(this, exchange-minimum), so setting this lower than the exchange minimum has no effect. Raise it if dust orders are getting rejected or you want to enforce a larger per-trade size. |
BE_GUARD |
boolean | True |
When ON, blocks any sell or position-close priced below the position break-even while holding a long. Prevents stop-loss, scratch, max-drawdown and cap-reduce paths from realising a loss — the pair will hold the position until price recovers above break-even. Off = sells below break-even are permitted (standard stop-loss behaviour). Recommended ON for spot accumulation; consider OFF on highly leveraged futures where forced closes are needed. |
NO_POST_ONLY |
boolean | False |
When ON, the strategy uses regular limit orders instead of post-only. Turn this on only if your exchange does not support post-only flags or rejects them, or if you do not benefit from a maker rebate. The strategy auto-applies this on PancakeSwap and Aster regardless of this toggle. OFF (default) is correct for every standard CEX. |
NO_CLOSE_MARKET |
boolean | False |
When ON, position closes use opposite-side market orders instead of gb.method.closeMarket(). Leave OFF on modern Gunbot builds on Bybit/Binance/OKX — closeMarket() handles one-way and hedge mode correctly. Turn ON only if your Gunbot version lacks closeMarket() or it misbehaves on your exchange. Auto-applied when closeMarket is unavailable. |
CJ_TRACE |
boolean | False |
When ON, writes a structured JSONL audit trail to gunbot_logs/quantroduction/ |
VERBOSE_LOGS |
boolean | False |
When ON, the full Quantroduction × Gunbot dashboard (every config value + state snapshot) re-emits every VERBOSE_INTERVAL_MIN minutes for forensic audit. When OFF (default), the dashboard only fires once per Gunbot restart per pair. |
VERBOSE_INTERVAL_MIN |
range (range 5..240) | 30 |
How often the dashboard re-emits when VERBOSE_LOGS is ON. Lower = more frequent / noisier; higher = quieter. Has no effect when VERBOSE_LOGS is OFF. |
BE_GUARD_BLOCK_MARKET_SELLS |
boolean | True |
Default ON (legacy). Set OFF to enable SOFT BE_GUARD: market sells and closeMarket pass through (treated as urgent / stop-loss). Only limit sells below break-even remain blocked. Prevents BE_GUARD from silently swallowing stop-loss exits. |
SCRATCH_LIVENESS_MIN |
range (range 0..120) | 0 |
When >0: if the pair holds inventory and hasn't filled in N minutes AND the bid is at break-even+1bp, force a market exit to rotate capital. 0 = disabled. Typical: 30. |
CONSEC_RESET_CYCLES |
range (range 60..2880) | 480 |
After this many cycles without any order activity, auto-reset consecutiveLosses to 0. Default 480 cycles ≈ 2h at 15s/cycle. Prevents a 3-loss streak from killing the pair for the entire session. |
DRIFT_ATR_FRAC |
range (range 0..0.5) | 0 |
When >0: scale drift-requote threshold to this fraction of the recent 10-candle high-low range (clamped 2-100 bps). 0 = use fixed *_STALE_DRIFT_BPS. Typical: 0.05 = 5% of recent range. Adapts drift detection to per-pair volatility. |
SKEW_QTY_MAX |
range (range 1..5) | 2.5 |
Maximum ask:bid qty ratio when inventory is heavily skewed. 2.5 means a heavily-bagged pair quotes up to 2.5x ask qty vs bid qty to drain inventory faster. Set 1.0 to disable (symmetric quoting). |
SKEW_QTY_TARGET |
range (range 0.1..0.9) | 0.5 |
Target inventory fraction of pair equity. 0.5 = 50/50 balanced base/quote. Skew kicks in proportionally as actual exposure deviates from target. |
PORTFOLIO_INCLUDE |
boolean | True |
When ON (default), this pair participates in the shared PORTFOLIO_EXP_BUDGET — its exposure counts toward portfolio total and bids pause when budget is exceeded. When OFF, the pair stands alone (use PAIR_EXP_BUDGET for own cap). |
PORTFOLIO_EXP_BUDGET |
range (range 0..1) | 0 |
Cap on total portfolio inventory as fraction of total allocated equity (sum across PORTFOLIO_INCLUDE pairs). 0 = disabled. Typical: 0.6 = 60% inventory cap across the included portfolio. When exceeded, bids pause but exits remain active. |
PAIR_EXP_BUDGET |
range (range 0..1) | 0 |
This pair's own exposure cap as fraction of pair equity (inventory / pair allocated capital). Applies independently of portfolio budget. 0 = disabled. Typical: 0.2 = 20% per-pair cap. |
DISABLE_BREAKER_WINDDOWN |
boolean | False |
When OFF (default), an active breaker (3 consecutive losses or daily loss limit) actively frees capital: cancels open orders and scratch-sells inventory IF profitable (bid >= BE+1bp). When ON, legacy halt-and-hold behaviour. |
TRACE_ALL |
boolean | False |
Master switch for the Quantroduction tracer. When ON, writes detailed per-cycle JSONL to gunbot_logs/quantroduction/ AND emits a verbose console summary. Equivalent to setting every |
Safety & Tuning (v1.0-beta)
Universal safety + tuning knobs added in v1.0-beta. These were previously hidden from the chart page even though the strategy reads them. Setting any of these here will be applied per-pair.
| Key | Type | Default | Description |
|---|---|---|---|
CJ_ASK_CLAMP_BUFFER_BPS |
range (range 0..50) | 2 |
When BE_GUARD is on and the strategy computes a sub-BE ask, the clamp snaps the ask up to break-even + this many bps. Default 2 = ask sits 2 bps above BE. Larger = more profitable but slower fills. |
CJ_DISABLE_ASK_CLAMP |
boolean | False |
Default OFF (clamp active). Set ON to bypass the break-even-aware ask snap; ask remains at the strategy's raw computed value. Use only if you want the ask to occasionally sit below BE. |
CJ_PERIOD |
string | 1 |
Candle period in minutes used for this strategy's indicators. Common values: '1', '5', '15'. Some strategies need a long history to warm up indicators. |
CJ_STALE_DRIFT_BPS |
range (range 1..50) | 8 |
Cancel and requote when an on-book order has drifted more than N bps from the current computed quote. Default 8. Lower = more cancels (higher fee burn); higher = more stale quotes. ATR-scaled override available via DRIFT_ATR_FRAC. |
CJ_SKEW_SCALE |
range (range 0.5..4) | 1 |
Multiplier on the inventory skew term. 1.0 (default) matches CJP 2015 Prop 10.5 textbook convention with its wrapping (1/2) factor. 2.0 removes that wrapping = more aggressive skew (closer to direct A-S extensions). Try 2.0 if positions are not unwinding fast enough when bagged. |
STRICT_LITERATURE |
boolean | False |
Master switch. When ON, the strategy disables every operator-grade safety override (BE_GUARD, cross-protection floors, BE ask clamps, drift-ATR scaling, asymmetric sizing, scratch liveness, breaker wind-down) so it runs as faithful as we can make it to the cited academic paper. Use this for paper-comparison backtests or academic research. WARNING: in strict mode the strategy can take real losses (papers prove optimality only in expectation, not per-trade). |
NO_CLOSE_MARKET |
toggle | False |
When true, the strategy NEVER falls back to a market sell to close a held inventory — only post-only limits. Use to prevent any taker fee on exits. Default false (allow market close when necessary). |
QUANTRODUCTION_TRACE |
toggle | False |
When true, this single pair writes detailed cycle events to gunbot_logs/quantroduction/ |
RESET_BREAKER_ONCE |
toggle | False |
One-shot: set true and Gunbot will clear all tripped risk breakers (consec_losses, daily_loss, max_dd) on this pair on the next cycle, then re-arm them. The setting auto-clears itself after firing. Use when a breaker tripped on a now-stale condition. |
RESET_STATS_ONCE |
toggle | False |
One-shot: set true and Gunbot will clear customStratStore counters (wins, losses, totalPnL, peakEquity, maxDD, dailyPnL, dailyLossLimit) on this pair on the next cycle. The setting auto-clears itself after firing. Useful after a recovery / regime change. |
Laddered Quoting
Spread each side of the quote into a stack of post-only rungs instead of one full-size order.
| Key | Type | Default | Description |
|---|---|---|---|
CJ_LAYERS |
range (range 1..8) | 4 |
Number of laddered post-only rungs per side per cycle. |
CJ_LAYER_SPAN_BPS |
range (range 0..50) | 8 |
Spread of the rung ladder, in basis points. |
Portfolio & Runtime (per-pair overrides)
Per-pair runtime knobs. Override any of these on a single pair without changing the strategy defaults.
| Key | Type | Default | Description |
|---|---|---|---|
SPREAD_PNL_JUMP_GUARD |
range (range 0.1..1) | 0.5 |
Realized-PnL deltas larger than this fraction of pair equity are treated as deposits/withdrawals/data-glitches and skipped. Default 0.5 means a single delta over 50% of equity is ignored. Lower for tighter glitch detection on small accounts. |
References & further reading
- Cartea, Á., Jaimungal, S. & Ricci, J. (2014). Buy low, sell high: a high frequency trading perspective. SIAM Journal on Financial Mathematics 5(1), 415–444.
- Cartea, Á. & Jaimungal, S. (2016). Incorporating order-flow into optimal execution. Mathematics and Financial Economics 10, 339–364.
- Guéant, O. (2016). The Financial Mathematics of Market Liquidity. Chapman & Hall/CRC.
Configuration playbook
- Full Tier 1-3 stack: see
OPERATOR_GUIDE.mdquick-start - Standalone pair (not in portfolio):
"PORTFOLIO_INCLUDE": false, "PAIR_EXP_BUDGET": 0.20 - Clear stuck breaker:
"RESET_BREAKER_ONCE": true - Clear historical loss counters:
"RESET_STATS_ONCE": true(preserves total PnL) - Enable JSONL tracer:
"TRACE_ALL": true - Soft BE_GUARD:
"BE_GUARD": true, "BE_GUARD_BLOCK_MARKET_SELLS": false