Documentation Index
Fetch the complete documentation index at: https://mintlify.com/dhir1007/nanoARB/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The MarketMakerStrategy implements an automated market-making strategy with:
- Multi-level quote generation
- Inventory-based price skewing
- Position limit management
- Quote lifecycle tracking
- Configurable spread and refresh intervals
This strategy continuously provides liquidity by placing limit orders on both sides of the market.
MarketMakerConfig
pub struct MarketMakerConfig {
pub base_spread_ticks: i64, // Base spread in ticks
pub inventory_skew_factor: f64, // Spread skew based on inventory (-1 to +1)
pub max_inventory: i64, // Maximum inventory (position limit)
pub order_size: u32, // Order size per level
pub num_levels: usize, // Number of levels to quote
pub min_edge_ticks: i64, // Minimum edge required (in ticks)
pub cancel_distance_ticks: i64, // Cancel distance from BBO (in ticks)
pub tick_size: i64, // Tick size
pub refresh_interval_ns: i64, // Refresh interval (in nanoseconds)
}
Configuration Parameters
base_spread_ticks
The base spread between bid and ask quotes, measured in ticks.
Default: 2
Example:
// With base_spread_ticks = 2 and tick_size = 25:
// If mid = 50000, bid = 49975, ask = 50025
inventory_skew_factor
Controls how much to skew quotes based on current inventory position. Range: 0.0 to 1.0.
Default: 0.5
Behavior:
- Positive inventory (long) → Lower bids, higher asks (encourage selling)
- Negative inventory (short) → Higher bids, lower asks (encourage buying)
- Factor of 0.0 → No skew
- Factor of 1.0 → Maximum skew
Formula:
inv_ratio = position / max_inventory // -1.0 to +1.0
skew_ticks = inv_ratio * inventory_skew_factor * base_spread_ticks
max_inventory
Maximum absolute position size allowed. Strategy stops quoting on a side when this limit is reached.
Default: 50
Example:
let config = MarketMakerConfig {
max_inventory: 100,
..Default::default()
};
// Position of +100 → Stop placing buy orders
// Position of -100 → Stop placing sell orders
order_size
Quantity of contracts for each quote order.
Default: 5
num_levels
Number of price levels to quote on each side of the market.
Default: 3
Example:
// With num_levels = 3, tick_size = 25:
// Bid levels: [50000, 49975, 49950]
// Ask levels: [50025, 50050, 50075]
min_edge_ticks
Minimum edge required before placing quotes (currently not actively used in implementation).
Default: 1
cancel_distance_ticks
Distance from the best bid/offer at which to cancel stale orders.
Default: 10
file:///home/daytona/workspace/source/crates/nano-strategy/src/market_maker.rs:229-252
tick_size
Raw price units per tick.
Default: 25
refresh_interval_ns
How often to refresh quotes, in nanoseconds.
Default: 100_000_000 (100ms)
Default Configuration
impl Default for MarketMakerConfig {
fn default() -> Self {
Self {
base_spread_ticks: 2,
inventory_skew_factor: 0.5,
max_inventory: 50,
order_size: 5,
num_levels: 3,
min_edge_ticks: 1,
cancel_distance_ticks: 10,
tick_size: 25,
refresh_interval_ns: 100_000_000, // 100ms
}
}
}
QuoteManager
Manages the lifecycle of active quote orders.
pub struct QuoteManager {
bid_orders: HashMap<OrderId, (Price, Quantity)>,
ask_orders: HashMap<OrderId, (Price, Quantity)>,
next_order_id: u64,
pending_acks: HashMap<OrderId, Side>,
}
Methods
new
Creates a new quote manager.
next_order_id
pub fn next_order_id(&mut self) -> OrderId
Generates the next unique order ID.
on_order_submit
pub fn on_order_submit(
&mut self,
order_id: OrderId,
side: Side,
price: Price,
quantity: Quantity,
)
Records a submitted order and marks it as pending acknowledgment.
on_order_ack
pub fn on_order_ack(&mut self, order_id: OrderId)
Removes the order from pending acknowledgments.
on_order_reject
pub fn on_order_reject(&mut self, order_id: OrderId)
Removes a rejected order from tracking.
on_fill
pub fn on_fill(&mut self, order_id: OrderId, fill_qty: Quantity)
Updates remaining quantity after a fill. Removes the order if fully filled.
file:///home/daytona/workspace/source/crates/nano-strategy/src/market_maker.rs:108-122
on_cancel
pub fn on_cancel(&mut self, order_id: OrderId)
Removes a cancelled order from tracking.
Quantity Tracking
pub fn total_bid_quantity(&self) -> Quantity
pub fn total_ask_quantity(&self) -> Quantity
Returns total outstanding quantity on bids or asks.
Order Lists
pub fn bid_order_ids(&self) -> Vec<OrderId>
pub fn ask_order_ids(&self) -> Vec<OrderId>
Returns all active order IDs for bids or asks.
MarketMakerStrategy
pub struct MarketMakerStrategy {
base: BaseStrategy,
config: MarketMakerConfig,
quotes: QuoteManager,
instrument_id: u32,
last_quote_time: Timestamp,
fair_value: Option<Price>,
}
Constructor
new
pub fn new(
name: &str,
instrument_id: u32,
config: MarketMakerConfig,
tick_value: f64,
) -> Self
Creates a new market-making strategy.
Parameters:
name: Strategy identifier
instrument_id: ID of the instrument to trade
config: Market maker configuration
tick_value: Value of one tick for P&L calculation
Example:
use nano_strategy::{MarketMakerStrategy, MarketMakerConfig};
let config = MarketMakerConfig {
base_spread_ticks: 2,
max_inventory: 100,
order_size: 10,
num_levels: 5,
..Default::default()
};
let mut strategy = MarketMakerStrategy::new(
"ES_MM",
101, // instrument_id
config,
12.5, // tick_value for ES futures
);
Core Methods
calculate_quotes
fn calculate_quotes(&self, mid: Price) -> (Price, Price)
Calculates bid and ask prices with inventory skew applied.
file:///home/daytona/workspace/source/crates/nano-strategy/src/market_maker.rs:197-222
Algorithm:
- Calculate inventory ratio:
position / max_inventory
- Calculate skew in ticks:
inv_ratio * skew_factor * base_spread
- Apply skew to bid/ask prices around mid
Example:
// Position: +50, Max: 100, Skew Factor: 0.5, Base Spread: 2 ticks
// inv_ratio = 50/100 = 0.5
// skew_ticks = 0.5 * 0.5 * 2 = 0.5 ticks
// If mid = 50000, tick_size = 25:
// bid = 50000 - 25 - 12 = 49963 (lowered)
// ask = 50000 + 25 - 12 = 50013 (lowered)
// Net effect: Encourages selling to reduce long position
should_refresh_quotes
fn should_refresh_quotes(&self, current_time: Timestamp) -> bool
Checks if enough time has passed since the last quote update.
generate_quotes
fn generate_quotes(
&mut self,
book: &dyn OrderBook,
current_time: Timestamp,
) -> Vec<Order>
Generates new quote orders across multiple levels.
file:///home/daytona/workspace/source/crates/nano-strategy/src/market_maker.rs:255-322
Process:
- Check if mid price is available
- Check position limits (stop quoting if at max)
- Calculate skewed bid/ask prices
- Generate orders for each level
- Record orders in quote manager
- Update last quote time
generate_cancels
fn generate_cancels(&self, book: &dyn OrderBook) -> Vec<OrderId>
Identifies orders that are too far from the best bid/offer and should be cancelled.
Accessor Methods
quotes
pub fn quotes(&self) -> &QuoteManager
Returns a reference to the quote manager.
config
pub fn config(&self) -> &MarketMakerConfig
Returns the strategy configuration.
fair_value
pub fn fair_value(&self) -> Option<Price>
Returns the current fair value estimate (mid price).
Strategy Implementation
The MarketMakerStrategy implements the Strategy trait:
on_market_data
file:///home/daytona/workspace/source/crates/nano-strategy/src/market_maker.rs:348-366
Called when market data updates:
- Updates base strategy (P&L, position)
- Checks if strategy is ready
- Checks if quotes need refreshing
- Generates new quotes if refresh interval elapsed
on_fill
file:///home/daytona/workspace/source/crates/nano-strategy/src/market_maker.rs:368-371
Handles fill events:
- Updates position and P&L via base strategy
- Updates remaining quantity in quote manager
on_order_ack, on_order_reject, on_order_cancel
file:///home/daytona/workspace/source/crates/nano-strategy/src/market_maker.rs:373-384
Forward order lifecycle events to the quote manager.
Complete Example
use nano_strategy::{MarketMakerStrategy, MarketMakerConfig, StrategyState};
use nano_core::traits::{Strategy, OrderBook};
use nano_core::types::TimeInForce;
fn main() {
// Configure the market maker
let config = MarketMakerConfig {
base_spread_ticks: 2, // 2 tick spread
inventory_skew_factor: 0.6, // Moderate skew
max_inventory: 200, // Max 200 contracts
order_size: 10, // 10 contracts per order
num_levels: 3, // Quote 3 levels deep
min_edge_ticks: 1,
cancel_distance_ticks: 10, // Cancel if 10 ticks away
tick_size: 25, // 0.25 price increment
refresh_interval_ns: 100_000_000, // 100ms
};
let mut strategy = MarketMakerStrategy::new(
"ES_MarketMaker",
101, // ES futures instrument ID
config,
12.5, // $12.50 per tick
);
// Set to trading state
strategy.base.set_state(StrategyState::Trading);
// In your main loop:
// let orders = strategy.on_market_data(&order_book);
// for order in orders {
// exchange.submit_order(order);
// }
// Monitor position and P&L
println!("Position: {}", strategy.position());
println!("P&L: ${:.2}", strategy.pnl());
println!("Active bids: {}", strategy.quotes().bid_order_ids().len());
println!("Active asks: {}", strategy.quotes().ask_order_ids().len());
}
Inventory Management
The strategy implements inventory risk management through:
- Position Limits: Stops quoting when
max_inventory is reached
- Price Skewing: Adjusts quotes to encourage inventory reduction
- Symmetric Limits: Applies limits to both long and short positions
Example Scenario:
// Config: max_inventory = 100, skew_factor = 0.5
// Position: 0 (flat)
// Bid: Mid - 1 tick, Ask: Mid + 1 tick (symmetric)
// Position: +80 (long)
// Bid: Mid - 1.4 ticks, Ask: Mid + 0.6 ticks (skewed down)
// Encourages selling to reduce inventory
// Position: +100 (max long)
// Bid: None (stopped), Ask: Active
// Only allows position reduction
- Quote refresh is throttled by
refresh_interval_ns
- Old quotes beyond
cancel_distance_ticks are cancelled
- All order tracking uses efficient HashMap lookups
- Position checks prevent over-trading
See Also