Strategy file: harrisScalpMM.js Market: spot
Description
Harris (2003) passive scalper with micro-profit exits and an Egginton-Van Ness-Van Ness (2016) tape-driven bid-stuffing detector. Spot.
How it works
Harris (2003) describes the 'scalping' MM archetype: a tight-spread, fast-cancel quoter that relies on depth on its side of the book to provide protection rather than on any directional forecast. The maker quotes one-tick inside the visible touch when depth on its side dominates the opposite side, then cancels immediately if the depth advantage flips. The strategy implements this as a depth-imbalance-gated quoter: it only joins the queue when bid_depth / ask_depth exceeds a threshold for buys, and the inverse for sells. Profitable only on venues where queue priority earns rebates or tight effective spreads.
Capital & Period
Order sizing and candle timeframe.
| Key | Type | Default | Description |
|---|---|---|---|
PERIOD |
select | 5 |
Candle timeframe Gunbot feeds the strategy. 1-3 min suits very liquid spot or perp; 5 min is the balanced default; 15 min reduces noise on thinner pairs at the cost of slower adaptation. |
TRADING_LIMIT |
range (range 10..5000) | 100 |
Order size in QUOTE currency (e.g. USDT) per buy attempt. Sets per-quote notional; raise to commit more per fill, lower to keep risk smaller per trade. Spot: TL is the quote amount per entry bid. Smaller TL = more, smaller fills. |
HS_TL |
range (range 10..5000) | 100 |
Strategy-specific TL override. When set, takes precedence over TRADING_LIMIT for THIS strategy only — lets you run different sizes per pair from the GUI without touching the global TL. |
HS_CAPITAL_ALLOC |
range (range 100..100000) | 2000 |
Hard cap on capital this strategy is allowed to commit on this pair. Drives circuit breakers (daily-loss = % of this) and exposure math. Set to the maximum quote balance you want this pair to ever consume. |
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 %. |
HS_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. |
HS_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. |
HS_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). |
Scalping
Entry depth, micro-profit exit target and exit-slide schedule.
| Key | Type | Default | Description |
|---|---|---|---|
HS_ENTRY_DEPTH |
range (range 1..50) | 5 |
How far below mid the passive bid is posted. Deeper = lower fill rate but better entry price. 5 bps suits 1m BTC/USDT; raise on lower-volatility pairs where the bid would otherwise sit too close. |
HS_MICRO_BPS |
range (range 1..50) | 4 |
Bps above break-even at which the immediate post-entry sell is posted. 4 bps gives roughly 1.5x fees coverage on Binance retail. Higher = waits for fatter profits; lower = scratches faster (lower expectancy per trade). |
HS_MICRO_STALE |
range (range 1..200) | 12 |
After this many cycles holding the position, the exit price begins sliding linearly DOWN toward break-even. Earlier = quicker concession to give up the micro target; later = patient about achieving full micro-profit. |
HS_MAX_STALE |
range (range 5..500) | 40 |
Hard cap on hold cycles. When reached, the exit collapses to BE + fee cushion and exits at market on the next favourable bid. BE_GUARD ensures this never realises a loss when enabled. |
HS_MAX_EXP |
range (range 10..95) | 60 |
Maximum portion of pair equity in the quote (target) asset. Above this, no new bids are posted. Lower to keep more dry-powder; higher to compound faster at the cost of less liquidity buffer. |
Stuffing Detector (live tape)
Egginton, Van Ness & Van Ness (2016) burst-rate + small-trade-size signature, computed from the live trade tape.
| Key | Type | Default | Description |
|---|---|---|---|
HS_USE_TAPE |
boolean | False |
When ON, pulls real trades from the shared tape and detects burst-stuffing via Poisson z-score and median-trade-size collapse. When OFF the detector is disabled (no false-positive heuristic). Recommended ON for Bybit/Binance/Kraken/OKX where the tape feed is available. |
HS_BURST_WINDOW_MS |
range (range 500..10000) | 2000 |
Recent trades window in which a burst is measured. Shorter = catches sharper bursts; longer = smoother but missier on fast bursts. |
HS_BASELINE_WINDOW_MS |
range (range 10000..600000) | 60000 |
Longer window used as the "normal" reference rate and median trade size. Larger = more stable baseline at the cost of slower adaptation to regime shifts. |
HS_BURST_Z |
range (range 1..6) | 3 |
Burst rate must exceed baseline by this many standard deviations (Poisson model) to count toward stuffing. 3.0 ≈ 0.13% false-positive rate per evaluation. Lower for hair-trigger; higher for very conservative. |
HS_BURST_SIZE_FRAC |
range (range 0.1..1) | 0.4 |
Burst median trade size must be BELOW this fraction of baseline median to flag stuffing (small-message signature per Egginton 2016). Lower = stricter (requires more obviously tiny bursts); higher = more permissive. |
HS_BURST_MIN_TRADES |
range (range 3..100) | 8 |
Minimum number of trades inside the burst window before the test fires — avoids statistical noise on near-empty windows. Raise on illiquid pairs. |
HS_STUFFING_COOL |
range (range 1..200) | 10 |
After a stuffing detection, the strategy cancels working orders and refuses to post new orders for this many cycles. |
Rebates & Display
Maker-rebate accounting (most spot exchanges are 0) and sidebar precision.
| Key | Type | Default | Description |
|---|---|---|---|
HS_MAKER_REBATE |
range (range -2..5) | 0 |
Per-side maker rebate (bps). Sidebar-only. Most spot exchanges run zero maker rebate; set if your venue offers one (e.g. Coinbase Pro tier-3 +0.05%). |
HS_SP |
range (range 0..8) | 6 |
Decimal places shown for prices in the sidebar. 6 suits low-priced spot tokens. |
HS_STALE_CYCLES |
range (range 1..60) | 5 |
How long a working entry bid may sit before being cancelled and re-posted at a fresh mid-relative price. |
Runtime & Exchange
Logging, fees and the cross-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 full internal model state. |
WARMUP_CYCLES |
range (range 1..60) | 5 |
Cycles to wait before the strategy starts placing orders. Lets candle history, OU/ADF fits, and tape caches reach a stable estimate. 5 is fine on liquid pairs; raise to 15-30 on illiquid pairs or longer candle periods. |
HS_FEE |
range (range 0..0.2) | 0.1 |
Taker fee for your exchange tier, as a percentage. Used in P&L bookkeeping and fee-aware exit-price math. Pull the exact number from your exchange fee schedule (VIP/maker tier) for accurate fees. |
HS_MAX_DD |
range (range 1..30) | 10 |
Peak-to-trough equity drawdown that pauses the strategy. Hits → strategy stops placing new orders until manual reset. Tighter (3-5%) for conservative, wider (10-20%) for higher-risk venues. |
HS_MAX_EXP |
range (range 10..95) | 60 |
Hard cap on capital deployed at any moment. Above this, new entry orders are blocked. Lower = more idle cash buffer; higher = more capital working but tighter liquidation risk on futures. |
HS_DAILY_LOSS |
range (range 0.5..10) | 2 |
Daily loss budget as a % of CAPITAL_ALLOC. When today's PnL falls below this threshold, the strategy halts for the remainder of the UTC day. Resets at next-day rollover. |
BE_GUARD |
boolean | True |
When ON, blocks any sell/close at a price BELOW the position break-even while holding a long. Wraps sellMarket, sellLimit, sellLimitPostOnly, closeMarket and closeLimit. Set false to disable (e.g. when you intentionally allow stop-loss exits below BE). |
NO_POST_ONLY |
boolean | False |
When ON, every post-only order is routed as a regular limit order instead. Auto-enabled for PancakeSwap and Aster (DEXes with no post-only flag). Turn ON manually for any exchange that rejects post-only with an error in your logs. |
NO_CLOSE_MARKET |
boolean | False |
When ON, position closes are routed through opposite-side market orders instead of closeMarket(). Auto-enabled when closeMarket is unavailable in the runtime. Use only if your exchange/build does not implement the close API. |
HS_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 |
|---|---|---|---|
HS_CONSEC_LOSSES_LIMIT |
range (range 1..20) | 5 |
Number of consecutive losses that trips the circuit breaker for HarrisScalpMM specifically. Default 5 (higher than the universal default of 3 because HS makes many small trades). After this many losses the strategy pauses until cooldown clears. |
HS_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. |
MIN_ORDER_QUOTE |
range (range 1..100) | 5 |
Floor on order size in quote currency to avoid exchange dust rejections. Match to your exchange's minimum notional. Default 5 USDT. |
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. |
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
- Harris, L. (2003). Trading and Exchanges: Market Microstructure for Practitioners. Oxford University Press. Chapter 13.
- Kirilenko, A., Kyle, A. S., Samadi, M. & Tuzun, T. (2017). The Flash Crash: high-frequency trading in an electronic market. Journal of Finance 72(3), 967–998.
- Menkveld, A. J. (2013). High frequency trading and the new market makers. Journal of Financial Markets 16(4), 712–740.
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