Skip to main content

Perpetual Market Maker Strategy

The Perp Market Maker is a sophisticated two-sided market making strategy that profits from bid-ask spreads while using advanced microstructure analysis to avoid adverse selection.
This is an advanced strategy with 100+ parameters. We recommend starting with a preset and small position sizes until you understand the behavior.

How Market Making Works

The Basic Concept

Market makers provide liquidity by quoting both buy (bid) and sell (ask) prices. You profit from the spread between them:
         ASK: $100.10 ← You SELL here

    SPREAD ───┼─── $0.10 (10 bps) = Your profit!

         BID: $100.00 ← You BUY here
Example Trade Cycle:
  1. You place BID at 100.00,ASKat100.00, ASK at 100.10
  2. Someone sells to you at $100.00 (you now own the asset)
  3. Someone buys from you at $100.10 (you sell the asset)
  4. Net profit: $0.10 per unit (minus fees)

Why It’s Complex

The challenge: if price moves against you, you lose more than the spread profit.
GOOD SCENARIO:              BAD SCENARIO:
Price bounces in range      Price trends away

    $100.10 ─ SELL ✓            $100.10 ─ Your ASK (not filled)
        │                           │
    $100.00 ─ BUY ✓                 │ Price drops...

    Profit: $0.10              $99.00 ─ You're stuck long!
                               Loss: $1.00 (10x the spread)
This is why the Perp MM has 100+ parameters - to detect and avoid bad scenarios.

Core Configuration

Position Limits

max_position_usdt
float
default:"1000"
Maximum position size in USDT. Hard ceiling - bot will not exceed this.
quote_size_usdt
float
default:"25"
Size of each quote order in USDT.
leverage
integer
default:"75"
Leverage multiplier. Higher = more capital efficiency but higher liquidation risk.

Equity-Based Sizing (New in v7)

proactive_multilevel.equity_pct
float
default:"0"
The Growth Engine - Percentage of account equity to use for grid sizing.
  • 0 = Legacy behavior (uses base_size_pct)
  • 5.0 = 5% of account equity per grid cycle
  • 10.0 = 10% of account equity per grid cycle
Why it matters: As your account grows from profits, position sizes automatically scale up!
Effective Exposure Formula:
effective_exposure = equity_pct × leverage
Example:
  • 5% equity_pct × 75x leverage = 375% effective exposure
  • A 1% adverse move costs: 3.75% of your account
Use the Scaling Agent (/set equity_pct 5) to easily adjust this parameter. See Scaling Agent docs.

Fee Configuration

maker_fee_bps
integer
default:"2"
Maker fee in basis points. BloFin: 2 bps, Bybit: 2 bps
taker_fee_bps
integer
default:"6"
Taker fee in basis points. BloFin: 6 bps, Bybit: 5.5 bps
min_profitable_spread_bps
integer
default:"10"
Minimum spread required to quote. Must cover round-trip fees + profit.

Spread Configuration

base_spread_bps
integer
default:"15"
Base spread from mid-price in basis points.
min_spread_bps
integer
default:"8"
Minimum allowed spread (floor).
max_spread_bps
integer
default:"50"
Maximum allowed spread (ceiling).
inventory_skew_factor
float
default:"1.5"
How much to skew spread based on inventory. Higher = more aggressive rebalancing.

Loss Management Tiers

The bot uses a tiered loss management system that progressively reduces risk as losses accumulate:
Loss Depth    │ Tier   │ Action
──────────────┼────────┼─────────────────────────────
0-100 bps     │ Normal │ Standard trading
100-300 bps   │ Tier 1 │ Widen spreads, reduce size
300-600 bps   │ Tier 2 │ Stop new entries, defend only
600+ bps      │ Tier 3 │ Exit priority, minimal quotes
1000 bps      │ HARD   │ Emergency close ALL

Configuration

loss_management.enabled
boolean
default:"true"
Enable tiered loss management.
loss_management.tier_1_bps
integer
default:"100"
First tier threshold (1% loss).
loss_management.tier_2_bps
integer
default:"300"
Second tier threshold (3% loss).
loss_management.tier_3_bps
integer
default:"600"
Third tier threshold (6% loss).
loss_management.hard_stop_loss_bps
integer
default:"1000"
Emergency exit threshold (10% loss). No exceptions.

Time Decay System

Positions that age without profit are progressively exited:
Position Age    │ Action
────────────────┼────────────────────────────
< 24 hours      │ Wait for TP
24-48 hours     │ Tighten TP target
48 hours        │ Exit at breakeven if possible
48-72 hours     │ Accept small loss exit
72+ hours       │ Force exit (market order)
time_decay.enabled
boolean
default:"true"
Enable time-based exits.
time_decay.force_exit_enabled
boolean
default:"true"
Easy toggle for force exit. Set to false to let positions run indefinitely.
time_decay.breakeven_after_seconds
integer
default:"172800"
Exit at breakeven after this many seconds (48 hours).
time_decay.exit_loss_after_seconds
integer
default:"28800"
Accept loss exit after this many seconds (8 hours).
time_decay.force_exit_after_seconds
integer
default:"86400"
Force market exit after this many seconds (24 hours).
Standard Time Decay Values (v7):
  • Breakeven: 48 hours (172800 seconds)
  • Exit Loss: 8 hours (28800 seconds)
  • Force Exit: 48 hours (172800 seconds)
These values prevent positions from aging indefinitely while giving trades adequate time to recover.

Advanced Detection Systems

Whale Detection

The bot detects large orders (“whale walls”) and positions near them for better fill probability:
Orderbook Depth:

    │         ████████████ ← WHALE WALL (large bid)
    │      ███
    │   ██
    │  █
    └──────────────────────
       Price

Bot places orders NEAR whale walls for support/resistance.
whale_frontrun.enabled
boolean
default:"true"
Enable whale wall detection.
whale_frontrun.min_whale_volume_usdt
integer
default:"3000"
Minimum wall size to consider (in USDT).

Trap Detection

Detects when you’re being “trapped” - adverse fills that immediately move against you:
trap_detector.enabled
boolean
default:"true"
Enable trap detection.
trap_detector.toxicity_threshold_bps
integer
default:"15"
Exit if fill toxicity exceeds this level.

Cascade Detection

Detects liquidation cascades (multiple forced liquidations causing rapid price movement):
cascade_hunter.enabled
boolean
default:"true"
Enable cascade detection.

Geometric Multi-Level Quoting

Instead of single quotes, place multiple levels for better fill rates:
Level │ Distance │ Size
──────┼──────────┼─────
L1    │ 10 bps   │ $50  (closest to price)
L2    │ 20 bps   │ $40
L3    │ 35 bps   │ $32
L4    │ 55 bps   │ $26
L5    │ 80 bps   │ $21  (furthest from price)
geometric_sizing.enabled
boolean
default:"true"
Enable geometric multi-level quoting.
geometric_sizing.decay_factor
float
default:"0.92"
Size reduction per level. 0.92 = each level is 92% of previous.
geometric_sizing.max_levels
integer
default:"50"
Maximum number of quote levels.

XGrid Counter Scalp

When the main position is underwater and XGrid detects an opposite trend, the bot can open a counter-position to scalp profits while waiting for recovery:
Main Position: LONG -5% underwater
XGrid Signal: SHORT (trend down)

    ┌────┴────┐
    │ COUNTER │
    │  SCALP  │
    └────┬────┘

    Open SHORT position (30-50% of main)
    Quick TP at 5-10 bps
    Offset some losses while waiting
xgrid_counter_scalp.enabled
boolean
default:"false"
Enable counter-trend scalping.
xgrid_counter_scalp.max_counter_pct
integer
default:"50"
Maximum counter position as % of main position.
xgrid_counter_scalp.min_main_age_seconds
integer
default:"300"
Main position must be this old before counter-scalping (5 min).

Preset Configurations

The latest preset with equity-based sizing and XGrid counter scalp:
{
  "strategy": "perp_mm",
  "leverage": 75,
  "proactive_multilevel": {
    "equity_pct": 5.0,
    "levels": 5
  },
  "spread": {
    "base_spread_bps": 15,
    "min_spread_bps": 8
  },
  "fees": {
    "maker_fee_bps": 2,
    "taker_fee_bps": 6
  },
  "xgrid_counter_scalp": {
    "enabled": true,
    "max_counter_pct": 50
  },
  "reactive_spacing": {
    "enabled": true,
    "max_multiplier": 10.0
  },
  "loss_management": {
    "tier_1_bps": 100,
    "tier_2_bps": 300,
    "tier_3_bps": 600,
    "hard_stop_loss_bps": 1000
  },
  "time_decay": {
    "breakeven_after_seconds": 28800,
    "exit_loss_after_seconds": 28800,
    "force_exit_after_seconds": 86400,
    "force_exit_enabled": true
  }
}
v7 Key Features:
  • equity_pct: 5.0 - Grids sized as 5% of account equity
  • xgrid_counter_scalp: enabled - Automatic hedging
  • reactive_spacing.max_multiplier: 10 - Wide spread range for volatility
  • time_decay: 8h/8h/24h - Standard aging exits

HFT v6 (High Frequency)

{
  "enabled": true,
  "max_position_usdt": 2000,
  "quote_size_usdt": 10,
  "leverage": 75,
  "base_spread_bps": 12,
  "min_spread_bps": 7,
  "maker_fee_bps": 2,
  "taker_fee_bps": 6,
  "geometric_sizing": {
    "enabled": true,
    "decay_factor": 0.92,
    "max_levels": 50
  },
  "loss_management": {
    "enabled": true,
    "tier_1_bps": 100,
    "tier_2_bps": 300,
    "tier_3_bps": 600,
    "hard_stop_loss_bps": 1000
  },
  "time_decay": {
    "enabled": true,
    "breakeven_after_seconds": 28800,
    "exit_loss_after_seconds": 28800,
    "force_exit_after_seconds": 86400
  }
}

Risk Management Best Practices

Position Sizing

Never risk more than 2% of your account on a single position.Formula: max_position = account_balance * 0.02 / (leverage / 100)Example: 10,000accountat75xleveragemax10,000 account at 75x leverage → max 150 per position

Exchange-Specific Notes

BloFin:
  • Maker fee: 2 bps, Taker fee: 6 bps
  • Supports hedge mode
  • Password required for API
Bybit:
  • Maker fee: 2 bps, Taker fee: 5.5 bps
  • Supports hedge mode
  • Lower maximum leverage (50x vs 75x)

Troubleshooting

”Spread too tight, not quoting”

Your min_profitable_spread_bps is higher than current market spread.
  • Solution: Lower min_profitable_spread_bps or trade during higher volatility

”Position stuck at loss”

  1. Check loss management tier - may be in defensive mode
  2. Enable time decay if not already
  3. Check if XGrid counter scalp can help offset losses

”Orders keep getting cancelled”

  1. Check exchange rate limits
  2. Reduce geometric_max_levels to place fewer orders
  3. Increase quote refresh interval

Next Steps