This example demonstrates a basic market making strategy that quotes bid/ask orders around the oracle price.
Complete Example
import {
DriftClient,
User,
BulkAccountLoader,
getLimitOrderParams,
PositionDirection,
PostOnlyParams,
calculateBidAskPrice,
convertToNumber,
BASE_PRECISION,
PRICE_PRECISION,
QUOTE_PRECISION,
BN,
} from '@drift-labs/sdk';
class SimpleMarketMaker {
private driftClient: DriftClient;
private user: User;
private marketIndex: number;
private spreadBps: number; // Spread in basis points
private orderSize: BN;
constructor(
driftClient: DriftClient,
user: User,
marketIndex: number,
spreadBps: number = 10, // 0.1%
orderSize: number = 1 // 1 SOL
) {
this.driftClient = driftClient;
this.user = user;
this.marketIndex = marketIndex;
this.spreadBps = spreadBps;
this.orderSize = new BN(orderSize).mul(BASE_PRECISION);
}
async run() {
console.log('Market maker starting...');
// Update quotes every 5 seconds
setInterval(async () => {
try {
await this.updateQuotes();
} catch (error) {
console.error('Error updating quotes:', error);
}
}, 5000);
}
async updateQuotes() {
// Get current market data
const perpMarket = this.driftClient.getPerpMarketAccount(this.marketIndex);
const oracleData = this.driftClient.getOracleDataForPerpMarket(this.marketIndex);
const oraclePrice = oracleData.price;
// Calculate bid/ask around oracle price
const spreadAmount = oraclePrice
.mul(new BN(this.spreadBps))
.div(new BN(10000));
const bidPrice = oraclePrice.sub(spreadAmount);
const askPrice = oraclePrice.add(spreadAmount);
console.log('\nUpdating quotes:');
console.log(' Oracle:', convertToNumber(oraclePrice, PRICE_PRECISION));
console.log(' Bid:', convertToNumber(bidPrice, PRICE_PRECISION));
console.log(' Ask:', convertToNumber(askPrice, PRICE_PRECISION));
// Cancel existing orders
await this.driftClient.cancelOrders(
MarketType.PERP,
this.marketIndex
);
// Place new bid
const bidOrder = getLimitOrderParams({
marketIndex: this.marketIndex,
direction: PositionDirection.LONG,
baseAssetAmount: this.orderSize,
price: bidPrice,
postOnly: PostOnlyParams.MUST_POST_ONLY,
});
// Place new ask
const askOrder = getLimitOrderParams({
marketIndex: this.marketIndex,
direction: PositionDirection.SHORT,
baseAssetAmount: this.orderSize,
price: askPrice,
postOnly: PostOnlyParams.MUST_POST_ONLY,
});
// Place both orders
await this.driftClient.placeOrders([bidOrder, askOrder]);
console.log(' Orders placed');
// Check inventory
await this.checkInventory();
}
async checkInventory() {
const position = this.user.getPerpPosition(this.marketIndex);
if (position) {
const size = convertToNumber(position.baseAssetAmount, BASE_PRECISION);
console.log(' Position size:', size);
// Adjust quotes if inventory gets too large
if (Math.abs(size) > 10) {
console.warn(' ⚠️ Large inventory - consider adjusting');
}
}
}
}
// Run the market maker
async function main() {
// Initialize DriftClient and User (see guides/initialization for setup)
const driftClient = /* initialize your DriftClient */ ;
const user = /* initialize your User */ ;
// Create market maker for SOL-PERP
const marketMaker = new SimpleMarketMaker(
driftClient,
user,
0, // SOL-PERP market index
10, // 0.1% spread
1 // 1 SOL per order
);
// Start market making
await marketMaker.run();
}
main().catch(console.error);
Key Concepts
Use PostOnlyParams.MUST_POST_ONLY to ensure you always provide liquidity and earn maker fees.
Monitor position size and adjust quotes or hedge when inventory gets too large.
Update quotes frequently to stay competitive and adjust to market conditions.
Set position limits and stop-losses to manage risk.
Advanced Features
Dynamic Spreads
// Adjust spread based on volatility
const volatility = calculateVolatility(recentPrices);
const dynamicSpread = baseSpread * (1 + volatility);
Inventory Skewing
// Skew quotes based on inventory
const inventorySkew = position.baseAssetAmount
.mul(new BN(5))
.div(new BN(1000));
const bidPrice = oraclePrice.sub(spreadAmount).sub(inventorySkew);
const askPrice = oraclePrice.add(spreadAmount).sub(inventorySkew);
Production Considerations
- Error handling: Robust retry logic
- Monitoring: Track fills, PnL, and position
- Risk limits: Maximum position size
- Multiple markets: Diversify across markets
- Gas optimization: Batch operations
For production market making, see keeper-bots-v2 for reference implementations.
Next Steps
Liquidation Bot
Build a liquidation bot
Advanced Features
Advanced SDK features