Strategy file: inventoryBalancer.js Market: spot
Description
Avellaneda-Stoikov / GLT infinite-horizon spot rebalancer. Continuously rebalances toward a 50/50 base/quote split using the A-S reservation price and optimal spread. Spot.
How it works
The Avellaneda-Stoikov (2008) model derives optimal MM quotes by maximising the expected exponential utility of terminal wealth subject to an inventory penalty. The reservation price is r(s,q,t) = s - q·γ·σ²·(T-t) where s = mid, q = current inventory, γ = risk aversion, σ = volatility. The optimal half-spread is δ ≈ (γ·σ²·(T-t))/2 + (1/γ)·ln(1 + γ/k) where k governs the arrival rate of market orders against the quote. The strategy uses the infinite-horizon GLT (Guéant-Lehalle-Tapia 2013) variant which drops the dependence on terminal time T, yielding a stationary quoting rule that continuously rebalances inventory toward a target split. This is the workhorse model of academic MM literature.
Capital & Period
Order sizing and candle timeframe
| Key | Type | Default | Description |
|---|---|---|---|
PERIOD |
select | 5 |
Candle timeframe Gunbot feeds the strategy. 1-3 min for very liquid spot (BTC, ETH). 5 min is a balanced default. 15 min reduces noise and trade frequency on lower-volume pairs. |
TRADING_LIMIT |
range (range 100..5000) | 900 |
Gunbot-level order size cap in quote currency. The strategy also reads its own Per-order limit below; keep these two equal unless you know why they should differ. Hard cap on a single buy order from Gunbot core regardless of strategy logic. |
IB_TL |
range (range 100..5000) | 900 |
Quote-currency notional placed on each rebalance order. Raise for larger lots (fewer trades, more fee per turn); lower for smaller lots (more trades, finer rebalancing). |
IB_CAPITAL_ALLOC |
range (range 500..50000) | 9090 |
Per-pair capital cap in quote currency. The strategy treats min(baseBalance, this) as the working capital, so values above your actual quote balance are ignored. Set to the realistic per-pair allocation you want this strategy to manage. |
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 %. |
IB_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. |
IB_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. |
IB_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). |
A-S Model
Avellaneda-Stoikov / GLT parameters
| Key | Type | Default | Description |
|---|---|---|---|
IB_GAMMA |
range (range 200..3000) | 1000 |
Risk-aversion coefficient in the A-S reservation/spread formulas (return-space convention). Higher γ = wider quoted spread AND a stronger pull on the reservation price when inventory is non-neutral, so the strategy leans harder against accumulating inventory. Lower γ = tighter quotes and smaller inventory lean. Tune in tandem with κ. |
IB_KAPPA |
range (range 500..8000) | 3000 |
Order-arrival decay parameter from A-S. Larger κ → tighter floor on the constant-spread component (≈ 2/κ in return space, scaled by mid). Decrease κ to widen quotes uniformly across volatility regimes; increase κ to compress to a minimum spread. Default 3000 ≈ 6 bps floor. |
IB_TAU |
range (range 0.25..4) | 1 |
Horizon scaling factor. τ=1.0 corresponds to the steady-state GLT (infinite-horizon) interpretation used by this implementation. Scale up to amplify both spread components; scale down to compress. Co-tunes with γ; doubling τ at fixed γ has the same first-order effect as doubling γ. |
IB_VOL_LB |
range (range 10..100) | 30 |
Number of candles used by the Parkinson high-low volatility estimator. Longer lookback = smoother σ but slower reaction to regime shifts. 30 candles ≈ 30-150 minutes depending on PERIOD. Raise on very noisy pairs. |
IB_EWMA |
range (range 0.85..0.99) | 0.94 |
EWMA decay applied on top of the Parkinson estimate. Lower λ = faster adaptation (more weight on the latest reading); higher λ = smoother, more persistent σ. 0.94 ≈ 17-candle half-life (RiskMetrics-style). |
IB_MIN_SP |
range (range 2..30) | 8 |
Informational floor for the sidebar "captured spread" metric. Does NOT clamp actual quote prices — the quoting math runs as configured regardless. Used only to highlight in the GUI when the computed spread falls below this value. |
Rebalance & Exits
When to trade and exit rules
| Key | Type | Default | Description |
|---|---|---|---|
IB_REBAL |
range (range 3..20) | 8 |
Strategy only acts when |exposure - 50%| exceeds this. 8% means trades fire when the quote-asset weight is below 42% or above 58%. Lower = more frequent rebalances and more fees; higher = lazier rebalancing with larger inventory excursions between trades. |
IB_SIZE |
range (range 1..15) | 4 |
Fraction of total equity put through each rebalance trade. 4% = roughly 12 trades to flatten a fully one-sided book. Higher = faster correction but more market impact; lower = finer increments. |
IB_GAIN |
range (range 0..2) | 0.15 |
Take-profit floor for the rebalance sell side: a sell only fires once mid is at least this percentage above the position break-even. Prevents scratching at a loss; set to 0 to disable this gate (then only IB_REBAL governs sells). |
IB_MAX_DCA |
range (range 1..15) | 6 |
Maximum number of consecutive buy rebalances allowed without an intervening sell. Caps how deep the strategy can average down when price keeps falling. Lower = stricter inventory cap; higher = more averaging-down headroom on persistent dips. |
IB_COMPOUND |
range (range 0..100) | 30 |
Percentage of realised profit that is added back to the working capital (per-trade size grows over time). 0 = harvest everything to cash, no compounding. 100 = reinvest all PnL. |
Risk & Safety
Exposure caps and drawdown circuit breakers
| Key | Type | Default | Description |
|---|---|---|---|
IB_MAX_EXP |
range (range 30..95) | 70 |
Hard cap on quote-asset weight: further buys are blocked above this exposure. Caps how much of your capital ends up as the held asset. Lower for more cautious positioning; higher to allow heavier accumulation on dips. |
IB_MAX_DD |
range (range 2..30) | 12 |
Peak-to-trough equity drop that triggers a strategy pause. When tripped, no new orders are placed until equity recovers (no auto-reset on time). Set conservatively — this is a last-line safety stop. |
Fees
Fee tier (informational, for P&L bookkeeping)
| Key | Type | Default | Description |
|---|---|---|---|
IB_FEE |
range (range 0..0.2) | 0.1 |
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. |
IB_MAKER_FEE |
range (range -0.05..0.1) | 0.02 |
Maker fee for your tier, as a percentage. Set to your actual fee; if you get a rebate, use the Maker rebate variant instead. |
IB_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) | 5 |
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. |
IB_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 |
IB_STALE_CYCLES |
range (range 1..30) | 3 |
Cancel and re-quote resting orders after this many cycles to avoid stale quotes. |
IB_CROSS_PROT_BPS |
range (range 0..10) | 1 |
Buffer in bps to keep quotes outside the touch so post-only orders are not rejected. |
IB_DISABLE_CROSS_PROT |
boolean | False |
Disable the cross-protection clamp (advanced). |
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 |
|---|---|---|---|
IB_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. |
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 |
|---|---|---|---|
IB_LAYERS |
range (range 1..8) | 4 |
Number of laddered orders to place on each side of the book per cycle. 1 = single quote at the top. 4 = stack of four rungs walking deeper into the book. Higher numbers improve scale-in / scale-out but require larger MIN_ORDER_QUOTE per rung. |
IB_LAYER_SPAN_BPS |
range (range 0..50) | 10 |
How far the deepest rung sits below the top bid (or above the top ask), in basis points. The rungs are spaced linearly across this band. |
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
- Avellaneda, M. & Stoikov, S. (2008). High-frequency trading in a limit order book. Quantitative Finance 8(3), 217–224.
- Guéant, O., Lehalle, C.-A. & Fernandez-Tapia, J. (2013). Dealing with the inventory risk: a solution to the market making problem. Mathematics and Financial Economics 7, 477–507.
- Cartea, Á., Jaimungal, S. & Penalva, J. (2015). Algorithmic and High-Frequency Trading. Cambridge University Press. Chapter 10.
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