Tutorial: Managing Concentrated Liquidity
Master the art of concentrated liquidity with Saros DLMM. Learn how to create, optimize, and manage profitable liquidity positions while minimizing risks and maximizing capital efficiency.
What You'll Learnā
By completing this tutorial, you'll understand:
- DLMM Fundamentals: How concentrated liquidity works vs traditional AMMs
- Position Creation: Setting up profitable liquidity ranges
- Risk Management: Protecting against impermanent loss and volatility
- Rebalancing Strategies: When and how to adjust positions
- Fee Optimization: Maximizing earnings from trading fees
- Advanced Tactics: Professional liquidity management techniques
Prerequisitesā
- Completed the Quick Start Guide
- Basic understanding of AMMs and liquidity provision
- DLMM SDK installed (
@saros-finance/dlmm-sdk) - Wallet with tokens for liquidity provision
- Understanding of price ranges and slippage
Part 1: Understanding DLMM Mechanicsā
Traditional AMM vs DLMMā
// Traditional AMM (like Uniswap V2)
// Your liquidity is spread across ALL possible prices (0 to ā)
// Capital efficiency: ~12%
// DLMM (Dynamic Liquidity Market Maker)
// Your liquidity is concentrated in specific price bins
// Capital efficiency: up to 4000x higher
import { DLMM, DLMMPool } from '@saros-finance/dlmm-sdk';
import { Connection, PublicKey } from '@solana/web3.js';
// Example: SOL-USDC pool analysis
const connection = new Connection('https://api.mainnet-beta.solana.com');
const pool = await DLMM.create(connection, new PublicKey('your-pool-address'));
// Traditional AMM would use ALL price ranges
const traditionalRange = { min: 0, max: Infinity };
console.log('Traditional AMM range:', traditionalRange);
// DLMM uses targeted ranges
const currentPrice = pool.getCurrentPrice();
const optimalRange = {
min: currentPrice * 0.95, // 5% below current
max: currentPrice * 1.05 // 5% above current
};
console.log('DLMM focused range:', optimalRange);
console.log('Capital efficiency gain:',
(traditionalRange.max - traditionalRange.min) / (optimalRange.max - optimalRange.min)
);
Understanding Bins and Price Levelsā
// Bins are discrete price levels where liquidity can be placed
// Each bin represents a specific price point
class BinEducation {
static demonstrateBinConcept(pool: DLMMPool) {
const binStep = pool.binStep;
const currentBinId = pool.activeId;
const currentPrice = pool.getCurrentPrice();
console.log('š§® BIN EDUCATION');
console.log('ā'.repeat(40));
console.log(`Current Price: $${currentPrice.toFixed(4)}`);
console.log(`Active Bin ID: ${currentBinId}`);
console.log(`Bin Step: ${binStep} (${(binStep/100).toFixed(2)}%)`);
// Show price levels for surrounding bins
for (let i = -5; i <= 5; i++) {
const binId = currentBinId + i;
const binPrice = currentPrice * Math.pow(1 + binStep/10000, i);
const isActive = i === 0 ? ' ā CURRENT' : '';
console.log(`Bin ${binId}: $${binPrice.toFixed(4)}${isActive}`);
}
}
}
Part 2: Creating Your First Positionā
Step 1: Pool Selection and Analysisā
// src/positionSetup.ts
import {
DLMM,
DLMMPool,
getBinArrays,
getPositionsByUser
} from '@saros-finance/dlmm-sdk';
import { Connection, PublicKey, Keypair } from '@solana/web3.js';
export class ConcentratedLiquidityManager {
private connection: Connection;
private wallet: Keypair;
constructor(rpcUrl: string, wallet: Keypair) {
this.connection = new Connection(rpcUrl, 'confirmed');
this.wallet = wallet;
}
async analyzePool(poolAddress: string): Promise<PoolAnalysis> {
console.log('š Analyzing pool for liquidity opportunity...');
const pool = await DLMM.create(this.connection, new PublicKey(poolAddress));
const currentPrice = pool.getCurrentPrice();
const binStep = pool.binStep;
// Get historical price data (simplified - in reality you'd use price feeds)
const priceHistory = await this.getRecentPriceHistory(pool);
const volatility = this.calculateVolatility(priceHistory);
// Analyze existing liquidity distribution
const binArrays = await getBinArrays(this.connection, pool.poolAddress);
const liquidityMap = this.buildLiquidityMap(binArrays);
return {
poolAddress,
currentPrice,
binStep,
volatility,
liquidityMap,
recommendations: this.generateRangeRecommendations(currentPrice, volatility, liquidityMap)
};
}
private async getRecentPriceHistory(pool: DLMMPool): Promise<number[]> {
// In a real implementation, you'd fetch from price feeds or transaction history
// For this example, we'll simulate realistic price movements
const currentPrice = pool.getCurrentPrice();
const prices: number[] = [];
for (let i = 0; i < 100; i++) {
const randomWalk = (Math.random() - 0.5) * 0.02; // ±1% random movement
const price = i === 0 ? currentPrice : prices[i-1] * (1 + randomWalk);
prices.push(price);
}
return prices;
}
private calculateVolatility(prices: number[]): number {
if (prices.length < 2) return 0;
const returns = [];
for (let i = 1; i < prices.length; i++) {
returns.push(Math.log(prices[i] / prices[i-1]));
}
const meanReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
const variance = returns.reduce((sum, ret) =>
sum + Math.pow(ret - meanReturn, 2), 0) / returns.length;
return Math.sqrt(variance * 365); // Annualized volatility
}
private buildLiquidityMap(binArrays: any[]): Map<number, number> {
const liquidityMap = new Map<number, number>();
for (const binArray of binArrays) {
for (const bin of binArray.bins) {
if (bin.liquidityGross.gt(0)) {
liquidityMap.set(bin.binId, bin.liquidityGross.toNumber());
}
}
}
return liquidityMap;
}
private generateRangeRecommendations(
currentPrice: number,
volatility: number,
liquidityMap: Map<number, number>
): RangeRecommendation[] {
const recommendations: RangeRecommendation[] = [];
// Conservative strategy: Wide range, lower fees, more stable
recommendations.push({
strategy: 'Conservative',
description: 'Wide range for stability and lower management overhead',
range: {
lower: currentPrice * (1 - volatility * 2),
upper: currentPrice * (1 + volatility * 2)
},
expectedAPY: 0.15,
riskLevel: 'LOW',
managementFrequency: 'Weekly'
});
// Aggressive strategy: Tight range, higher fees, needs active management
recommendations.push({
strategy: 'Aggressive',
description: 'Tight range for maximum fee capture, requires active monitoring',
range: {
lower: currentPrice * (1 - volatility * 0.5),
upper: currentPrice * (1 + volatility * 0.5)
},
expectedAPY: 0.45,
riskLevel: 'HIGH',
managementFrequency: 'Daily'
});
// Balanced strategy: Medium range with good risk/reward
recommendations.push({
strategy: 'Balanced',
description: 'Optimized balance between fees and risk',
range: {
lower: currentPrice * (1 - volatility),
upper: currentPrice * (1 + volatility)
},
expectedAPY: 0.28,
riskLevel: 'MEDIUM',
managementFrequency: 'Bi-weekly'
});
return recommendations;
}
}
interface PoolAnalysis {
poolAddress: string;
currentPrice: number;
binStep: number;
volatility: number;
liquidityMap: Map<number, number>;
recommendations: RangeRecommendation[];
}
interface RangeRecommendation {
strategy: string;
description: string;
range: {
lower: number;
upper: number;
};
expectedAPY: number;
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH';
managementFrequency: string;
}
Step 2: Position Creation with Optimal Parametersā
// src/positionCreator.ts
import {
createPosition,
addLiquidity,
PositionV2,
LiquidityParameterByWeight
} from '@saros-finance/dlmm-sdk';
export class PositionCreator {
private manager: ConcentratedLiquidityManager;
constructor(manager: ConcentratedLiquidityManager) {
this.manager = manager;
}
async createOptimizedPosition(config: PositionConfig): Promise<CreatedPosition> {
console.log('š Creating optimized DLMM position...');
console.log(`Strategy: ${config.strategy}`);
console.log(`Range: $${config.priceRange.lower.toFixed(4)} - $${config.priceRange.upper.toFixed(4)}`);
try {
// Step 1: Calculate bin IDs for the price range
const lowerBinId = this.priceToBinId(config.priceRange.lower, config.pool);
const upperBinId = this.priceToBinId(config.priceRange.upper, config.pool);
console.log(`š Bin range: ${lowerBinId} to ${upperBinId}`);
// Step 2: Optimize liquidity distribution
const liquidityDistribution = this.calculateOptimalDistribution(
lowerBinId,
upperBinId,
config.strategy,
config.pool
);
console.log('š§ Liquidity distribution calculated');
// Step 3: Create position with optimized parameters
const positionParams = {
user: this.manager['wallet'].publicKey,
poolAddress: config.pool.poolAddress,
lowerBinId,
upperBinId,
amountX: config.amountX,
amountY: config.amountY,
liquidityDistribution
};
const transaction = await createPosition(
this.manager['connection'],
positionParams,
this.manager['wallet'].publicKey
);
// Step 4: Sign and send transaction
transaction.feePayer = this.manager['wallet'].publicKey;
transaction.recentBlockhash = (
await this.manager['connection'].getLatestBlockhash()
).blockhash;
transaction.sign(this.manager['wallet']);
const signature = await this.manager['connection'].sendTransaction(transaction);
await this.manager['connection'].confirmTransaction(signature, 'confirmed');
console.log('ā
Position created successfully!');
console.log('š Transaction:', signature);
// Step 5: Extract position address and return details
const positionAddress = await this.extractPositionAddress(signature);
const createdPosition: CreatedPosition = {
address: positionAddress,
signature,
lowerBinId,
upperBinId,
priceRange: config.priceRange,
strategy: config.strategy,
createdAt: new Date(),
initialAmountX: config.amountX,
initialAmountY: config.amountY
};
return createdPosition;
} catch (error) {
console.error('ā Position creation failed:', error);
throw error;
}
}
private priceToBinId(price: number, pool: DLMMPool): number {
// Convert price to bin ID using pool's bin step
const currentPrice = pool.getCurrentPrice();
const currentBinId = pool.activeId;
const binStep = pool.binStep;
const priceRatio = price / currentPrice;
const binOffset = Math.log(priceRatio) / Math.log(1 + binStep / 10000);
return Math.round(currentBinId + binOffset);
}
private calculateOptimalDistribution(
lowerBinId: number,
upperBinId: number,
strategy: string,
pool: DLMMPool
): LiquidityParameterByWeight[] {
const distribution: LiquidityParameterByWeight[] = [];
const totalBins = upperBinId - lowerBinId + 1;
const currentBinId = pool.activeId;
for (let binId = lowerBinId; binId <= upperBinId; binId++) {
let weight = 0;
switch (strategy) {
case 'Conservative':
// Uniform distribution across all bins
weight = 1 / totalBins;
break;
case 'Aggressive':
// Concentrated around current price
const distance = Math.abs(binId - currentBinId);
weight = Math.exp(-distance / 3) / totalBins;
break;
case 'Balanced':
// Normal distribution centered on current price
const variance = totalBins / 4;
const normalizedDistance = Math.pow(binId - currentBinId, 2) / (2 * variance);
weight = Math.exp(-normalizedDistance) / totalBins;
break;
default:
weight = 1 / totalBins;
}
distribution.push({
binId,
weight: Math.max(weight, 0.001) // Minimum weight
});
}
// Normalize weights to sum to 1
const totalWeight = distribution.reduce((sum, item) => sum + item.weight, 0);
return distribution.map(item => ({
...item,
weight: item.weight / totalWeight
}));
}
private async extractPositionAddress(signature: string): Promise<string> {
// Extract position address from transaction logs
// This is a simplified implementation
// In practice, you'd parse transaction logs to find the created position
return 'extracted-position-address';
}
}
interface PositionConfig {
pool: DLMMPool;
strategy: 'Conservative' | 'Aggressive' | 'Balanced';
priceRange: {
lower: number;
upper: number;
};
amountX: number;
amountY: number;
}
interface CreatedPosition {
address: string;
signature: string;
lowerBinId: number;
upperBinId: number;
priceRange: {
lower: number;
upper: number;
};
strategy: string;
createdAt: Date;
initialAmountX: number;
initialAmountY: number;
}
Part 2: Position Monitoring and Performance Trackingā
Real-time Position Monitoringā
// src/positionMonitor.ts
import {
getPositionsByUser,
getPositionInfo,
collectFees
} from '@saros-finance/dlmm-sdk';
export class PositionMonitor {
private manager: ConcentratedLiquidityManager;
private positions: Map<string, PositionTracker> = new Map();
private isMonitoring: boolean = false;
constructor(manager: ConcentratedLiquidityManager) {
this.manager = manager;
}
async startMonitoring(): Promise<void> {
if (this.isMonitoring) {
console.log('š Already monitoring positions');
return;
}
console.log('šļø Starting position monitoring...');
this.isMonitoring = true;
// Load existing positions
await this.loadUserPositions();
// Start monitoring loop
const monitoringLoop = setInterval(async () => {
try {
await this.updateAllPositions();
await this.checkRebalanceNeeds();
await this.autoCollectFees();
} catch (error) {
console.error('ā Monitoring error:', error);
}
}, 30000); // Update every 30 seconds
console.log('ā
Monitoring started (30s intervals)');
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\nš Stopping position monitoring...');
clearInterval(monitoringLoop);
this.isMonitoring = false;
process.exit(0);
});
}
private async loadUserPositions(): Promise<void> {
try {
const userPositions = await getPositionsByUser(
this.manager['connection'],
this.manager['wallet'].publicKey
);
console.log(`š Found ${userPositions.length} existing positions`);
for (const position of userPositions) {
const tracker: PositionTracker = {
address: position.publicKey.toString(),
poolAddress: position.poolBinArrays[0]?.toString() || '',
lowerBinId: position.lowerBinId,
upperBinId: position.upperBinId,
strategy: 'Unknown', // Would be stored separately
createdAt: new Date(), // Would be fetched from transaction history
lastUpdate: new Date(),
metrics: {
currentValue: 0,
feesEarned: 0,
impermanentLoss: 0,
timeInRange: 1.0,
dailyYield: 0
},
alerts: []
};
this.positions.set(tracker.address, tracker);
}
} catch (error) {
console.error('ā Failed to load positions:', error);
}
}
private async updateAllPositions(): Promise<void> {
const updatePromises = Array.from(this.positions.values()).map(async (tracker) => {
try {
await this.updatePosition(tracker);
} catch (error) {
console.error(`ā Failed to update position ${tracker.address}:`, error);
}
});
await Promise.all(updatePromises);
}
private async updatePosition(tracker: PositionTracker): Promise<void> {
const positionInfo = await getPositionInfo(
this.manager['connection'],
new PublicKey(tracker.address)
);
if (!positionInfo) {
console.warn(`ā ļø Position ${tracker.address} not found`);
return;
}
// Update metrics
const currentValue = this.calculatePositionValue(positionInfo);
const feesEarned = this.calculateFeesEarned(positionInfo);
const impermanentLoss = this.calculateImpermanentLoss(tracker, positionInfo);
const timeInRange = this.calculateTimeInRange(tracker, positionInfo);
tracker.metrics = {
currentValue,
feesEarned,
impermanentLoss,
timeInRange,
dailyYield: this.calculateDailyYield(tracker, currentValue)
};
tracker.lastUpdate = new Date();
// Check for alerts
await this.checkPositionAlerts(tracker);
}
private calculatePositionValue(positionInfo: any): number {
// Calculate current USD value of the position
// This is a simplified calculation
return positionInfo.tokenX.amount + positionInfo.tokenY.amount;
}
private calculateFeesEarned(positionInfo: any): number {
return positionInfo.feeX.amount + positionInfo.feeY.amount;
}
private calculateImpermanentLoss(tracker: PositionTracker, positionInfo: any): number {
// Calculate impermanent loss vs holding
const initialValue = tracker.metrics.currentValue;
const currentValue = this.calculatePositionValue(positionInfo);
// Simplified IL calculation
return Math.max(0, initialValue - currentValue);
}
private calculateTimeInRange(tracker: PositionTracker, positionInfo: any): number {
// Calculate what percentage of time the position has been in active range
// This would require historical price data
return 0.85; // Example: 85% time in range
}
private calculateDailyYield(tracker: PositionTracker, currentValue: number): number {
const daysSinceCreated = (Date.now() - tracker.createdAt.getTime()) / (1000 * 60 * 60 * 24);
if (daysSinceCreated < 1) return 0;
const totalReturn = (currentValue - (tracker.metrics.currentValue || 0)) / currentValue;
return totalReturn / daysSinceCreated;
}
private async checkPositionAlerts(tracker: PositionTracker): Promise<void> {
tracker.alerts = [];
// Alert 1: Position out of range
if (tracker.metrics.timeInRange < 0.1) {
tracker.alerts.push({
type: 'OUT_OF_RANGE',
severity: 'HIGH',
message: 'Position is mostly out of range - consider rebalancing',
action: 'REBALANCE'
});
}
// Alert 2: High impermanent loss
if (tracker.metrics.impermanentLoss > tracker.metrics.currentValue * 0.05) {
tracker.alerts.push({
type: 'HIGH_IL',
severity: 'MEDIUM',
message: 'Impermanent loss exceeds 5% - monitor closely',
action: 'MONITOR'
});
}
// Alert 3: Low fee generation
if (tracker.metrics.dailyYield < 0.0001) {
tracker.alerts.push({
type: 'LOW_FEES',
severity: 'LOW',
message: 'Low fee generation - consider tighter range',
action: 'OPTIMIZE_RANGE'
});
}
// Alert 4: High uncollected fees
if (tracker.metrics.feesEarned > tracker.metrics.currentValue * 0.02) {
tracker.alerts.push({
type: 'UNCOLLECTED_FEES',
severity: 'MEDIUM',
message: 'Significant uncollected fees - consider harvesting',
action: 'COLLECT_FEES'
});
}
// Display alerts
if (tracker.alerts.length > 0) {
console.log(`šØ Position ${tracker.address.slice(0, 8)}... has ${tracker.alerts.length} alerts:`);
tracker.alerts.forEach(alert => {
console.log(` ${this.getAlertEmoji(alert.severity)} ${alert.message}`);
});
}
}
private getAlertEmoji(severity: string): string {
switch (severity) {
case 'HIGH': return 'š“';
case 'MEDIUM': return 'š”';
case 'LOW': return 'š¢';
default: return 'ā¹ļø';
}
}
private async checkRebalanceNeeds(): Promise<void> {
for (const [address, tracker] of this.positions) {
if (tracker.alerts.some(alert => alert.action === 'REBALANCE')) {
console.log(`š Position ${address.slice(0, 8)}... needs rebalancing`);
await this.suggestRebalanceStrategy(tracker);
}
}
}
private async suggestRebalanceStrategy(tracker: PositionTracker): Promise<void> {
console.log(`š” Rebalance suggestions for ${tracker.address.slice(0, 8)}...:`);
// Analyze current market conditions
const pool = await DLMM.create(
this.manager['connection'],
new PublicKey(tracker.poolAddress)
);
const currentPrice = pool.getCurrentPrice();
const positionPrice = (tracker.lowerBinId + tracker.upperBinId) / 2; // Simplified
const priceDeviation = Math.abs(currentPrice - positionPrice) / positionPrice;
if (priceDeviation > 0.1) { // 10% deviation
console.log('šÆ Suggestion: Close and recreate position around current price');
console.log(` Current price: $${currentPrice.toFixed(4)}`);
console.log(` Position center: $${positionPrice.toFixed(4)}`);
console.log(` Deviation: ${(priceDeviation * 100).toFixed(1)}%`);
} else {
console.log('ā
Position range is still optimal');
}
}
private async autoCollectFees(): Promise<void> {
for (const [address, tracker] of this.positions) {
if (tracker.alerts.some(alert => alert.action === 'COLLECT_FEES')) {
try {
console.log(`š° Auto-collecting fees for ${address.slice(0, 8)}...`);
const collectTransaction = await collectFees(
this.manager['connection'],
new PublicKey(address),
this.manager['wallet'].publicKey
);
const signature = await this.manager['connection'].sendTransaction(collectTransaction);
await this.manager['connection'].confirmTransaction(signature, 'confirmed');
console.log(`ā
Fees collected: ${signature}`);
// Update tracker
tracker.metrics.feesEarned = 0; // Reset after collection
} catch (error) {
console.error(`ā Fee collection failed for ${address}:`, error);
}
}
}
}
async generatePerformanceReport(): Promise<PerformanceReport> {
const positions = Array.from(this.positions.values());
const totalValue = positions.reduce((sum, pos) => sum + pos.metrics.currentValue, 0);
const totalFees = positions.reduce((sum, pos) => sum + pos.metrics.feesEarned, 0);
const avgTimeInRange = positions.reduce((sum, pos) => sum + pos.metrics.timeInRange, 0) / positions.length;
const totalIL = positions.reduce((sum, pos) => sum + pos.metrics.impermanentLoss, 0);
return {
totalPositions: positions.length,
totalValue,
totalFeesEarned: totalFees,
averageTimeInRange: avgTimeInRange,
totalImpermanentLoss: totalIL,
netPerformance: totalFees - totalIL,
averageDailyYield: positions.reduce((sum, pos) => sum + pos.metrics.dailyYield, 0) / positions.length,
bestPerformingStrategy: this.findBestStrategy(positions),
recommendations: this.generatePortfolioRecommendations(positions)
};
}
private findBestStrategy(positions: PositionTracker[]): string {
const strategyPerformance = new Map<string, number[]>();
for (const position of positions) {
if (!strategyPerformance.has(position.strategy)) {
strategyPerformance.set(position.strategy, []);
}
strategyPerformance.get(position.strategy)!.push(position.metrics.dailyYield);
}
let bestStrategy = 'Unknown';
let bestPerformance = -Infinity;
for (const [strategy, yields] of strategyPerformance) {
const avgYield = yields.reduce((sum, y) => sum + y, 0) / yields.length;
if (avgYield > bestPerformance) {
bestPerformance = avgYield;
bestStrategy = strategy;
}
}
return bestStrategy;
}
private generatePortfolioRecommendations(positions: PositionTracker[]): string[] {
const recommendations: string[] = [];
const outOfRangePositions = positions.filter(p => p.metrics.timeInRange < 0.2);
if (outOfRangePositions.length > 0) {
recommendations.push(`Rebalance ${outOfRangePositions.length} out-of-range positions`);
}
const lowYieldPositions = positions.filter(p => p.metrics.dailyYield < 0.0001);
if (lowYieldPositions.length > 0) {
recommendations.push(`Optimize ranges for ${lowYieldPositions.length} low-yield positions`);
}
const highILPositions = positions.filter(p =>
p.metrics.impermanentLoss > p.metrics.currentValue * 0.05
);
if (highILPositions.length > 0) {
recommendations.push(`Review ${highILPositions.length} positions with high impermanent loss`);
}
return recommendations;
}
}
interface PositionTracker {
address: string;
poolAddress: string;
lowerBinId: number;
upperBinId: number;
strategy: string;
createdAt: Date;
lastUpdate: Date;
metrics: PositionMetrics;
alerts: PositionAlert[];
}
interface PositionMetrics {
currentValue: number;
feesEarned: number;
impermanentLoss: number;
timeInRange: number;
dailyYield: number;
}
interface PositionAlert {
type: 'OUT_OF_RANGE' | 'HIGH_IL' | 'LOW_FEES' | 'UNCOLLECTED_FEES';
severity: 'HIGH' | 'MEDIUM' | 'LOW';
message: string;
action: 'REBALANCE' | 'MONITOR' | 'OPTIMIZE_RANGE' | 'COLLECT_FEES';
}
interface PerformanceReport {
totalPositions: number;
totalValue: number;
totalFeesEarned: number;
averageTimeInRange: number;
totalImpermanentLoss: number;
netPerformance: number;
averageDailyYield: number;
bestPerformingStrategy: string;
recommendations: string[];
}
Part 3: Advanced Rebalancing Strategiesā
Automated Rebalancing Systemā
// src/rebalancer.ts
import {
removeLiquidity,
createPosition,
addLiquidity
} from '@saros-finance/dlmm-sdk';
export class AutoRebalancer {
private monitor: PositionMonitor;
private creator: PositionCreator;
private rebalanceSettings: RebalanceSettings;
constructor(
monitor: PositionMonitor,
creator: PositionCreator,
settings: RebalanceSettings
) {
this.monitor = monitor;
this.creator = creator;
this.rebalanceSettings = settings;
}
async executeRebalance(positionAddress: string): Promise<RebalanceResult> {
console.log(`š Executing rebalance for ${positionAddress.slice(0, 8)}...`);
try {
// Step 1: Get current position info
const currentPosition = await getPositionInfo(
this.monitor['manager']['connection'],
new PublicKey(positionAddress)
);
if (!currentPosition) {
throw new Error('Position not found');
}
// Step 2: Analyze optimal new range
const pool = await DLMM.create(
this.monitor['manager']['connection'],
currentPosition.poolAddress
);
const currentPrice = pool.getCurrentPrice();
const optimalRange = this.calculateOptimalRange(currentPrice, pool);
console.log(`š New range: $${optimalRange.lower.toFixed(4)} - $${optimalRange.upper.toFixed(4)}`);
// Step 3: Remove liquidity from old position
const removeTransaction = await removeLiquidity(
this.monitor['manager']['connection'],
new PublicKey(positionAddress),
this.monitor['manager']['wallet'].publicKey,
100 // Remove 100% of liquidity
);
// Sign and send remove transaction
const removeSignature = await this.sendTransaction(removeTransaction);
console.log('ā
Old position closed:', removeSignature);
// Step 4: Create new position with optimal range
const newPositionConfig: PositionConfig = {
pool,
strategy: this.rebalanceSettings.preferredStrategy,
priceRange: optimalRange,
amountX: currentPosition.tokenX.amount,
amountY: currentPosition.tokenY.amount
};
const newPosition = await this.creator.createOptimizedPosition(newPositionConfig);
console.log('ā
New position created:', newPosition.address);
// Step 5: Calculate rebalance cost and benefit
const rebalanceCost = this.calculateRebalanceCost(removeSignature, newPosition.signature);
const expectedBenefit = this.estimateRebalanceBenefit(optimalRange, currentPrice);
return {
success: true,
oldPositionAddress: positionAddress,
newPositionAddress: newPosition.address,
rebalanceCost,
expectedBenefit,
netBenefit: expectedBenefit - rebalanceCost,
timestamp: new Date()
};
} catch (error) {
console.error('ā Rebalance failed:', error);
return {
success: false,
oldPositionAddress: positionAddress,
newPositionAddress: '',
rebalanceCost: 0,
expectedBenefit: 0,
netBenefit: 0,
timestamp: new Date(),
error: error.message
};
}
}
private calculateOptimalRange(currentPrice: number, pool: DLMMPool): { lower: number; upper: number } {
const binStep = pool.binStep / 10000; // Convert to decimal
switch (this.rebalanceSettings.strategy) {
case 'TIGHT':
return {
lower: currentPrice * (1 - binStep * 5),
upper: currentPrice * (1 + binStep * 5)
};
case 'MEDIUM':
return {
lower: currentPrice * (1 - binStep * 15),
upper: currentPrice * (1 + binStep * 15)
};
case 'WIDE':
return {
lower: currentPrice * (1 - binStep * 30),
upper: currentPrice * (1 + binStep * 30)
};
default:
return {
lower: currentPrice * 0.9,
upper: currentPrice * 1.1
};
}
}
private async sendTransaction(transaction: any): Promise<string> {
transaction.feePayer = this.monitor['manager']['wallet'].publicKey;
transaction.recentBlockhash = (
await this.monitor['manager']['connection'].getLatestBlockhash()
).blockhash;
transaction.sign(this.monitor['manager']['wallet']);
const signature = await this.monitor['manager']['connection'].sendTransaction(transaction);
await this.monitor['manager']['connection'].confirmTransaction(signature, 'confirmed');
return signature;
}
private calculateRebalanceCost(removeSignature: string, createSignature: string): number {
// Estimate transaction costs (simplified)
const avgTransactionFee = 0.0001; // ~$0.01 per transaction
return avgTransactionFee * 2; // Two transactions
}
private estimateRebalanceBenefit(range: {lower: number; upper: number}, currentPrice: number): number {
// Estimate increased fee generation from better range (simplified)
const rangeSize = range.upper - range.lower;
const efficiency = currentPrice / rangeSize; // Higher price/range ratio = more efficient
return efficiency * 0.0001; // Estimated daily benefit
}
}
interface RebalanceSettings {
strategy: 'TIGHT' | 'MEDIUM' | 'WIDE';
preferredStrategy: 'Conservative' | 'Aggressive' | 'Balanced';
autoCollectFees: boolean;
rebalanceThreshold: number; // Price deviation percentage
maxRebalanceFrequency: number; // Max rebalances per day
}
interface RebalanceResult {
success: boolean;
oldPositionAddress: string;
newPositionAddress: string;
rebalanceCost: number;
expectedBenefit: number;
netBenefit: number;
timestamp: Date;
error?: string;
}
Part 4: Risk Management and Safetyā
Comprehensive Risk Managementā
// src/riskManager.ts
export class RiskManager {
private maxPositionValue: number;
private maxImpermanentLoss: number;
private emergencySettings: EmergencySettings;
constructor(settings: RiskSettings) {
this.maxPositionValue = settings.maxPositionValue;
this.maxImpermanentLoss = settings.maxImpermanentLoss;
this.emergencySettings = settings.emergency;
}
async assessPositionRisk(position: PositionTracker): Promise<RiskAssessment> {
console.log(`š”ļø Assessing risk for position ${position.address.slice(0, 8)}...`);
const risks: RiskFactor[] = [];
// Risk Factor 1: Position size relative to portfolio
if (position.metrics.currentValue > this.maxPositionValue) {
risks.push({
type: 'POSITION_SIZE',
severity: 'HIGH',
description: 'Position size exceeds maximum allowed',
impact: 'Potential for large losses',
mitigation: 'Reduce position size or split into multiple positions'
});
}
// Risk Factor 2: Impermanent loss threshold
const ilPercentage = position.metrics.impermanentLoss / position.metrics.currentValue;
if (ilPercentage > this.maxImpermanentLoss) {
risks.push({
type: 'IMPERMANENT_LOSS',
severity: ilPercentage > this.maxImpermanentLoss * 2 ? 'CRITICAL' : 'HIGH',
description: `Impermanent loss at ${(ilPercentage * 100).toFixed(1)}%`,
impact: 'Erosion of principal value',
mitigation: 'Consider closing position or hedging'
});
}
// Risk Factor 3: Time out of range
if (position.metrics.timeInRange < 0.2) {
risks.push({
type: 'OUT_OF_RANGE',
severity: 'MEDIUM',
description: 'Position out of active trading range',
impact: 'No fee generation, potential IL',
mitigation: 'Rebalance position to current price range'
});
}
// Risk Factor 4: Pool liquidity risk
const poolRisk = await this.assessPoolRisk(position.poolAddress);
if (poolRisk.severity !== 'LOW') {
risks.push(poolRisk);
}
// Calculate overall risk score
const riskScore = this.calculateRiskScore(risks);
return {
positionAddress: position.address,
overallRisk: this.getRiskLevel(riskScore),
riskScore,
riskFactors: risks,
recommendedActions: this.generateRiskMitigation(risks),
emergencyActions: riskScore > 0.8 ? this.getEmergencyActions() : []
};
}
private async assessPoolRisk(poolAddress: string): Promise<RiskFactor> {
// Assess pool-specific risks (TVL, volume, volatility)
try {
const pool = await DLMM.create(
this.monitor['manager']['connection'],
new PublicKey(poolAddress)
);
// Check pool health indicators
const binArrays = await getBinArrays(this.monitor['manager']['connection'], pool.poolAddress);
const totalLiquidity = binArrays.reduce((sum, array) =>
sum + array.bins.reduce((binSum, bin) => binSum + bin.liquidityGross.toNumber(), 0), 0
);
if (totalLiquidity < 10000) { // Less than $10k TVL
return {
type: 'POOL_LIQUIDITY',
severity: 'HIGH',
description: 'Low pool liquidity may cause high slippage',
impact: 'Difficulty exiting position, high price impact',
mitigation: 'Monitor closely or consider larger pools'
};
}
return {
type: 'POOL_LIQUIDITY',
severity: 'LOW',
description: 'Pool liquidity appears healthy',
impact: 'Minimal',
mitigation: 'Continue monitoring'
};
} catch (error) {
return {
type: 'POOL_ACCESS',
severity: 'CRITICAL',
description: 'Cannot access pool data',
impact: 'Unable to assess position properly',
mitigation: 'Check network connection and pool status'
};
}
}
private calculateRiskScore(risks: RiskFactor[]): number {
const severityWeights = {
'LOW': 0.1,
'MEDIUM': 0.3,
'HIGH': 0.7,
'CRITICAL': 1.0
};
const totalRisk = risks.reduce((sum, risk) =>
sum + severityWeights[risk.severity], 0
);
return Math.min(1.0, totalRisk / risks.length);
}
private getRiskLevel(score: number): 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' {
if (score < 0.2) return 'LOW';
if (score < 0.5) return 'MEDIUM';
if (score < 0.8) return 'HIGH';
return 'CRITICAL';
}
private generateRiskMitigation(risks: RiskFactor[]): string[] {
return risks.map(risk => risk.mitigation);
}
private getEmergencyActions(): string[] {
return [
'Immediately close high-risk positions',
'Collect all available fees',
'Monitor market conditions closely',
'Consider hedging with derivatives',
'Reduce overall exposure'
];
}
}
interface RiskSettings {
maxPositionValue: number;
maxImpermanentLoss: number;
emergency: EmergencySettings;
}
interface EmergencySettings {
autoCloseEnabled: boolean;
maxLossThreshold: number;
emergencyContacts: string[];
}
interface RiskAssessment {
positionAddress: string;
overallRisk: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
riskScore: number;
riskFactors: RiskFactor[];
recommendedActions: string[];
emergencyActions: string[];
}
interface RiskFactor {
type: string;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
description: string;
impact: string;
mitigation: string;
}
Part 5: Complete Implementation Exampleā
Professional DLMM Management Systemā
// src/dlmmManager.ts
export class ProfessionalDLMMManager {
private liquidityManager: ConcentratedLiquidityManager;
private positionCreator: PositionCreator;
private monitor: PositionMonitor;
private rebalancer: AutoRebalancer;
private riskManager: RiskManager;
constructor(rpcUrl: string, wallet: Keypair) {
this.liquidityManager = new ConcentratedLiquidityManager(rpcUrl, wallet);
this.positionCreator = new PositionCreator(this.liquidityManager);
this.monitor = new PositionMonitor(this.liquidityManager);
const rebalanceSettings: RebalanceSettings = {
strategy: 'MEDIUM',
preferredStrategy: 'Balanced',
autoCollectFees: true,
rebalanceThreshold: 0.1, // Rebalance when price moves 10%
maxRebalanceFrequency: 2 // Max 2 rebalances per day
};
this.rebalancer = new AutoRebalancer(this.monitor, this.positionCreator, rebalanceSettings);
const riskSettings: RiskSettings = {
maxPositionValue: 10000, // $10k max per position
maxImpermanentLoss: 0.1, // 10% max IL
emergency: {
autoCloseEnabled: true,
maxLossThreshold: 0.2, // 20% loss triggers emergency close
emergencyContacts: ['admin@example.com']
}
};
this.riskManager = new RiskManager(riskSettings);
}
async runFullStrategy(poolAddress: string): Promise<void> {
console.log('šÆ Running Professional DLMM Management Strategy');
console.log('ā'.repeat(50));
try {
// Step 1: Analyze pool and market conditions
const analysis = await this.liquidityManager.analyzePool(poolAddress);
console.log(`š Pool Analysis Complete`);
console.log(` Current Price: $${analysis.currentPrice.toFixed(4)}`);
console.log(` Volatility: ${(analysis.volatility * 100).toFixed(1)}%`);
console.log(` Recommendations: ${analysis.recommendations.length}`);
// Step 2: Select optimal strategy
const selectedStrategy = analysis.recommendations[0];
console.log(`\nšÆ Selected Strategy: ${selectedStrategy.strategy}`);
console.log(` Expected APY: ${(selectedStrategy.expectedAPY * 100).toFixed(1)}%`);
console.log(` Risk Level: ${selectedStrategy.riskLevel}`);
// Step 3: Create initial position
const pool = await DLMM.create(
this.liquidityManager['connection'],
new PublicKey(poolAddress)
);
const positionConfig: PositionConfig = {
pool,
strategy: selectedStrategy.strategy as any,
priceRange: selectedStrategy.range,
amountX: 1000, // 1000 tokens (adjust based on your capital)
amountY: 1000 // 1000 tokens
};
const position = await this.positionCreator.createOptimizedPosition(positionConfig);
console.log(`\nā
Position Created: ${position.address}`);
// Step 4: Start monitoring and management
await this.monitor.startMonitoring();
// Step 5: Set up periodic reporting
this.setupPeriodicReporting();
console.log('\nš Automated management system is now active!');
console.log(' - Real-time monitoring: Every 30 seconds');
console.log(' - Performance reports: Every 6 hours');
console.log(' - Auto fee collection: When threshold reached');
console.log(' - Risk assessment: Continuous');
console.log(' - Rebalancing: When needed (max 2/day)');
} catch (error) {
console.error('ā Strategy execution failed:', error);
throw error;
}
}
private setupPeriodicReporting(): void {
// Generate performance report every 6 hours
setInterval(async () => {
try {
const report = await this.monitor.generatePerformanceReport();
this.printPerformanceReport(report);
} catch (error) {
console.error('ā Report generation failed:', error);
}
}, 6 * 60 * 60 * 1000); // 6 hours
}
private printPerformanceReport(report: PerformanceReport): void {
console.log('\nš PERFORMANCE REPORT');
console.log('ā'.repeat(40));
console.log(`š Total Positions: ${report.totalPositions}`);
console.log(`š° Total Value: $${report.totalValue.toFixed(2)}`);
console.log(`š Fees Earned: $${report.totalFeesEarned.toFixed(2)}`);
console.log(`ā” Avg Time in Range: ${(report.averageTimeInRange * 100).toFixed(1)}%`);
console.log(`š Impermanent Loss: $${report.totalImpermanentLoss.toFixed(2)}`);
console.log(`š Net Performance: $${report.netPerformance.toFixed(2)}`);
console.log(`š
Daily Yield: ${(report.averageDailyYield * 100).toFixed(3)}%`);
console.log(`š Best Strategy: ${report.bestPerformingStrategy}`);
if (report.recommendations.length > 0) {
console.log('\nš” RECOMMENDATIONS:');
report.recommendations.forEach((rec, i) => {
console.log(` ${i + 1}. ${rec}`);
});
}
console.log('ā'.repeat(40));
}
}
Usage Example: Complete Workflowā
// examples/professionalWorkflow.ts
import { ProfessionalDLMMManager } from '../src/dlmmManager';
import { Keypair, clusterApiUrl } from '@solana/web3.js';
async function runProfessionalWorkflow() {
console.log('š Professional DLMM Management Workflow');
try {
// Load wallet (in production, use secure key management)
const wallet = Keypair.generate(); // Replace with your actual wallet
console.log('š¼ Wallet loaded:', wallet.publicKey.toString());
// Initialize professional manager
const manager = new ProfessionalDLMMManager(
clusterApiUrl('mainnet-beta'),
wallet
);
// Run strategy on SOL-USDC pool
const solUsdcPool = 'your-sol-usdc-pool-address';
await manager.runFullStrategy(solUsdcPool);
// Keep running (in production, this would be a long-running service)
console.log('\nā³ Professional DLMM management is now active...');
console.log('Press Ctrl+C to stop');
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\nš Shutting down professional manager...');
// Here you'd clean up resources, close positions if needed, etc.
process.exit(0);
});
} catch (error) {
console.error('ā Professional workflow failed:', error);
process.exit(1);
}
}
// Run the workflow
runProfessionalWorkflow().catch(console.error);
Production Best Practicesā
Security Considerationsā
- Private Key Management: Never hardcode private keys
- Environment Variables: Use
.envfiles for sensitive data - Access Controls: Implement proper authentication
- Audit Logging: Track all position changes and transactions
- Emergency Stops: Implement circuit breakers for extreme conditions
Performance Optimizationā
- Connection Pooling: Reuse RPC connections efficiently
- Batch Operations: Combine multiple operations when possible
- Cache Management: Cache pool and position data appropriately
- Rate Limiting: Respect RPC endpoint limits
- Error Recovery: Implement robust retry mechanisms
Monitoring and Alertsā
// src/alerting.ts
export class AlertingSystem {
async sendAlert(alert: Alert): Promise<void> {
console.log(`šØ ${alert.severity} ALERT: ${alert.message}`);
// In production, integrate with:
// - Discord webhooks
// - Slack notifications
// - Email alerts
// - SMS for critical alerts
// - PagerDuty for emergencies
}
}
interface Alert {
type: string;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
message: string;
positionAddress?: string;
timestamp: Date;
}
Key Takeawaysā
ā Do'sā
- Start Conservative: Begin with wide ranges to understand DLMM behavior
- Monitor Actively: Check positions at least daily during volatile periods
- Collect Fees Regularly: Don't let uncollected fees accumulate
- Risk Management: Set clear stop-loss and position size limits
- Diversify Strategies: Use different approaches across multiple positions
ā Don'tsā
- Don't Set and Forget: DLMM positions require active management
- Don't Ignore Volatility: Adjust ranges based on market conditions
- Don't Overconcentrate: Spread liquidity across reasonable ranges
- Don't Skip Testing: Always test strategies on devnet first
- Don't Neglect Gas Costs: Factor transaction fees into profitability
What's Next?ā
š Congratulations! You're now a DLMM expert!
Advanced Topics:ā
- Optimizing DLMM Strategies - Professional techniques
- Building Analytics Dashboard - Track performance
- Implementing MEV Protection - Protect against front-running
- Multi-Pool Arbitrage - Cross-pool opportunities
Resources:ā
- š DLMM SDK Reference - Complete API documentation
- š ļø Troubleshooting Guide - Common issues and solutions
- š¬ Developer Support - Get help from the community
- š Pool Analytics - Real-time pool statistics
Ready to optimize your DLMM strategies? Let's dive deeper into advanced techniques! š