Skip to main content

Position Management

Master the art of managing DLMM liquidity positions. Learn how to create, modify, monitor, and optimize concentrated liquidity positions for maximum efficiency.

Position Lifecycle

1. Position Creation

import { 
createPosition,
LiquidityPosition,
PositionParameters
} from '@saros-finance/dlmm-sdk';

async function createOptimizedPosition(
pool: DLMMPool,
strategy: PositionStrategy,
capitalAmount: number,
walletAddress: string
): Promise<LiquidityPosition> {

const currentPrice = pool.getCurrentPrice();

// Calculate optimal range based on strategy
const range = calculateOptimalRange(currentPrice, strategy);

// Determine token amounts
const tokenAmounts = calculateTokenAmounts(
capitalAmount,
range,
currentPrice,
strategy.tokenRatio
);

const positionParams: PositionParameters = {
pool: pool,
lowerBinId: pool.getBinIdFromPrice(range.lower),
upperBinId: pool.getBinIdFromPrice(range.upper),
tokenXAmount: tokenAmounts.tokenX,
tokenYAmount: tokenAmounts.tokenY,
wallet: new PublicKey(walletAddress),
slippageTolerance: strategy.slippage || 0.01
};

const position = await createPosition(positionParams);

console.log('🎯 Position created:');
console.log(` Range: ${range.lower.toFixed(4)} - ${range.upper.toFixed(4)}`);
console.log(` Token X: ${tokenAmounts.tokenX}`);
console.log(` Token Y: ${tokenAmounts.tokenY}`);
console.log(` Position ID: ${position.positionId}`);

return position;
}

interface PositionStrategy {
type: 'market_making' | 'range_order' | 'passive_lp';
rangePercent: number; // ±% around current price
tokenRatio: number; // 0 = all Y, 0.5 = balanced, 1 = all X
slippage: number; // Position creation slippage
autoCompound: boolean; // Whether to auto-compound fees
}

2. Position Monitoring

class PositionMonitor {
private position: LiquidityPosition;
private metrics: PositionMetrics;

constructor(position: LiquidityPosition) {
this.position = position;
this.metrics = new PositionMetrics();
}

async updateMetrics(): Promise<PositionStatus> {
await this.position.refresh(); // Update position state from blockchain

const currentPrice = this.position.pool.getCurrentPrice();
const range = this.position.getPriceRange();

return {
isActive: this.isPositionActive(currentPrice, range),
utilization: this.calculateUtilization(),
feesEarned: this.position.getAccumulatedFees(),
impermanentLoss: await this.calculateCurrentIL(),
timeInRange: this.metrics.getTimeInRange(),
predictedReturn: this.predictReturn(),
recommendations: this.getRecommendations()
};
}

private isPositionActive(
currentPrice: number,
range: { lower: number; upper: number }
): boolean {
return currentPrice >= range.lower && currentPrice <= range.upper;
}

private calculateUtilization(): number {
// Calculate what percentage of your liquidity is earning fees
const range = this.position.getPriceRange();
const currentPrice = this.position.pool.getCurrentPrice();

if (!this.isPositionActive(currentPrice, range)) {
return 0; // No utilization if out of range
}

// For positions in range, calculate active liquidity percentage
return this.position.getActiveLiquidityPercent();
}

private async calculateCurrentIL(): Promise<number> {
const initialPrice = this.position.getCreationPrice();
const currentPrice = this.position.pool.getCurrentPrice();
const initialAmounts = this.position.getInitialAmounts();
const currentAmounts = this.position.getCurrentAmounts();

return calculateImpermanentLoss(
initialPrice,
currentPrice,
initialAmounts.tokenX,
initialAmounts.tokenY,
currentAmounts.tokenX,
currentAmounts.tokenY
);
}

private getRecommendations(): string[] {
const recommendations = [];
const status = this.getCurrentStatus();

if (!status.isActive) {
recommendations.push('Position is out of range - consider rebalancing');
}

if (status.feesEarned.total > 10) { // $10 threshold
recommendations.push('Consider collecting fees to compound or realize profits');
}

if (status.utilization < 0.5) {
recommendations.push('Low capital utilization - consider narrowing range');
}

if (status.impermanentLoss < -5) {
recommendations.push('High impermanent loss - monitor closely');
}

return recommendations;
}
}

interface PositionStatus {
isActive: boolean;
utilization: number;
feesEarned: { tokenX: number; tokenY: number; total: number };
impermanentLoss: number;
timeInRange: number;
predictedReturn: number;
recommendations: string[];
}

3. Position Modification

class PositionManager {
async adjustPositionRange(
position: LiquidityPosition,
newLowerPrice: number,
newUpperPrice: number,
maintainValue: boolean = true
): Promise<LiquidityPosition> {

try {
// 1. Collect any pending fees first
const feeResult = await position.collectFees();
console.log(`Collected fees: ${feeResult.tokenX} X, ${feeResult.tokenY} Y`);

// 2. Remove liquidity from current position
const removedLiquidity = await position.removeLiquidity(1.0); // Remove 100%

console.log('🔄 Position liquidity removed, creating new range...');

// 3. Calculate token amounts for new position
let tokenXAmount = removedLiquidity.tokenX + feeResult.tokenX;
let tokenYAmount = removedLiquidity.tokenY + feeResult.tokenY;

if (maintainValue) {
// Rebalance token ratio for new range
const optimalAmounts = calculateOptimalTokenAmounts(
tokenXAmount,
tokenYAmount,
newLowerPrice,
newUpperPrice,
position.pool.getCurrentPrice()
);

tokenXAmount = optimalAmounts.tokenX;
tokenYAmount = optimalAmounts.tokenY;
}

// 4. Create new position with adjusted range
const newPosition = await createConcentratedPosition(
position.pool,
newLowerPrice,
newUpperPrice,
tokenXAmount,
tokenYAmount,
position.walletAddress
);

console.log('✅ Position range adjusted successfully!');
return newPosition;

} catch (error) {
console.error('❌ Failed to adjust position range:', error);
throw error;
}
}

async increasePosition(
position: LiquidityPosition,
additionalTokenX: number,
additionalTokenY: number
): Promise<void> {
try {
// Add more liquidity to existing position
await position.addLiquidity({
tokenXAmount: additionalTokenX,
tokenYAmount: additionalTokenY,
slippageTolerance: 0.01
});

console.log('✅ Position size increased!');
console.log(`Added: ${additionalTokenX} X, ${additionalTokenY} Y`);

} catch (error) {
console.error('❌ Failed to increase position:', error);
throw error;
}
}

async decreasePosition(
position: LiquidityPosition,
percentageToRemove: number // 0.0 to 1.0
): Promise<{ tokenX: number; tokenY: number }> {
try {
if (percentageToRemove <= 0 || percentageToRemove > 1) {
throw new Error('Invalid percentage: must be between 0 and 1');
}

const removedLiquidity = await position.removeLiquidity(percentageToRemove);

console.log('✅ Position size decreased!');
console.log(`Removed: ${removedLiquidity.tokenX} X, ${removedLiquidity.tokenY} Y`);

return removedLiquidity;

} catch (error) {
console.error('❌ Failed to decrease position:', error);
throw error;
}
}
}

function calculateOptimalTokenAmounts(
availableTokenX: number,
availableTokenY: number,
lowerPrice: number,
upperPrice: number,
currentPrice: number
): { tokenX: number; tokenY: number } {

// For concentrated liquidity, optimal ratio depends on:
// 1. Where current price sits in the range
// 2. Expected price movement direction
// 3. Available token balances

if (currentPrice <= lowerPrice) {
// Price below range - use all token Y
return { tokenX: 0, tokenY: availableTokenY };
}

if (currentPrice >= upperPrice) {
// Price above range - use all token X
return { tokenX: availableTokenX, tokenY: 0 };
}

// Price in range - calculate optimal ratio
const rangeProportion = (currentPrice - lowerPrice) / (upperPrice - lowerPrice);

// More token X needed as price approaches upper bound
const optimalTokenXRatio = 1 - rangeProportion;
const optimalTokenYRatio = rangeProportion;

// Calculate amounts based on limiting factor
const tokenXNeeded = availableTokenY * optimalTokenXRatio / optimalTokenYRatio;
const tokenYNeeded = availableTokenX * optimalTokenYRatio / optimalTokenXRatio;

if (tokenXNeeded <= availableTokenX) {
// Token Y is limiting factor
return { tokenX: tokenXNeeded, tokenY: availableTokenY };
} else {
// Token X is limiting factor
return { tokenX: availableTokenX, tokenY: tokenYNeeded };
}
}

Advanced Position Strategies

Dynamic Range Adjustment

class DynamicRangeManager {
private position: LiquidityPosition;
private config: RangeConfig;

interface RangeConfig {
targetUtilization: number; // Target % of time in range
rebalanceThreshold: number; // Price change % to trigger rebalance
maxRebalanceFrequency: number; // Max rebalances per day
gasCostLimit: number; // Max gas cost for rebalance
}

async autoAdjustRange(): Promise<boolean> {
const currentPrice = this.position.pool.getCurrentPrice();
const range = this.position.getPriceRange();
const utilization = this.calculateRecentUtilization();

// Check if adjustment is needed
if (utilization < this.config.targetUtilization) {
const newRange = this.calculateOptimalRange(currentPrice, utilization);

// Check if adjustment is profitable
const rebalanceCost = await this.estimateRebalanceCost();
const expectedBenefit = await this.estimateRangeBenefit(newRange);

if (expectedBenefit > rebalanceCost && this.canRebalance()) {
await this.adjustRange(newRange);
return true;
}
}

return false;
}

private calculateOptimalRange(
currentPrice: number,
currentUtilization: number
): { lower: number; upper: number } {
// If utilization is low, widen the range
// If utilization is high, consider tightening

let rangePercent = this.position.getCurrentRangePercent();

if (currentUtilization < this.config.targetUtilization) {
// Widen range to increase time in range
rangePercent *= 1.5;
} else if (currentUtilization > 0.9) {
// Tighten range to increase capital efficiency
rangePercent *= 0.8;
}

return {
lower: currentPrice * (1 - rangePercent / 100),
upper: currentPrice * (1 + rangePercent / 100)
};
}

private async estimateRebalanceCost(): Promise<number> {
// Estimate total cost of:
// 1. Collecting fees
// 2. Removing liquidity
// 3. Creating new position

const gasPrice = await this.position.pool.connection.getRecentBlockhash();
const estimatedGas = 300000; // Estimate based on typical operations

return estimatedGas * 0.000005; // Convert to SOL
}

private async estimateRangeBenefit(newRange: { lower: number; upper: number }): Promise<number> {
// Estimate additional fees from better positioned liquidity
// Based on historical trading data and range efficiency

const currentRange = this.position.getPriceRange();
const currentRangeWidth = currentRange.upper - currentRange.lower;
const newRangeWidth = newRange.upper - newRange.lower;

// Narrower range = higher capital efficiency = more fees per dollar
const efficiencyGain = currentRangeWidth / newRangeWidth;
const currentDailyFees = this.position.getDailyFeeRate();

return currentDailyFees * (efficiencyGain - 1) * 30; // 30 days projected benefit
}
}

Multi-Position Portfolio

class DLMMPortfolio {
private positions: Map<string, LiquidityPosition> = new Map();
private walletAddress: string;

constructor(walletAddress: string) {
this.walletAddress = walletAddress;
}

async createLayeredPositions(
pool: DLMMPool,
totalCapital: number,
layers: number = 3
): Promise<LiquidityPosition[]> {

const currentPrice = pool.getCurrentPrice();
const positions: LiquidityPosition[] = [];
const capitalPerLayer = totalCapital / layers;

for (let i = 0; i < layers; i++) {
// Create positions with progressively wider ranges
const rangeMultiplier = Math.pow(2, i); // 1x, 2x, 4x range
const baseRange = 0.05; // 5% base range
const layerRange = baseRange * rangeMultiplier;

const position = await createConcentratedPosition(
pool,
currentPrice * (1 - layerRange),
currentPrice * (1 + layerRange),
(capitalPerLayer * 0.5) / currentPrice, // Token X amount
capitalPerLayer * 0.5, // Token Y amount
this.walletAddress
);

positions.push(position);
this.positions.set(position.positionId, position);

console.log(`Layer ${i + 1} created: ±${(layerRange * 100).toFixed(1)}% range`);
}

return positions;
}

async rebalancePortfolio(): Promise<RebalanceResult> {
const rebalanceResults: PositionRebalanceResult[] = [];

for (const [positionId, position] of this.positions.entries()) {
try {
const shouldRebalance = await this.evaluatePositionForRebalance(position);

if (shouldRebalance.recommended) {
const result = await this.rebalancePosition(position, shouldRebalance.strategy);
rebalanceResults.push({
positionId,
action: 'rebalanced',
result
});
} else {
rebalanceResults.push({
positionId,
action: 'maintained',
reason: shouldRebalance.reason
});
}

} catch (error) {
rebalanceResults.push({
positionId,
action: 'error',
error: error.message
});
}
}

return {
totalPositions: this.positions.size,
rebalanced: rebalanceResults.filter(r => r.action === 'rebalanced').length,
maintained: rebalanceResults.filter(r => r.action === 'maintained').length,
errors: rebalanceResults.filter(r => r.action === 'error').length,
details: rebalanceResults
};
}

async harvestAllFees(): Promise<FeeHarvestResult> {
const harvestResults = [];
let totalFeesX = 0;
let totalFeesY = 0;

for (const [positionId, position] of this.positions.entries()) {
try {
const fees = await position.collectFees();

totalFeesX += fees.tokenX;
totalFeesY += fees.tokenY;

harvestResults.push({
positionId,
success: true,
feesCollected: fees
});

} catch (error) {
harvestResults.push({
positionId,
success: false,
error: error.message
});
}
}

return {
totalFeesX,
totalFeesY,
positionResults: harvestResults
};
}

getPortfolioSummary(): PortfolioSummary {
let totalValue = 0;
let totalFeesEarned = 0;
let averageUtilization = 0;
let activePositions = 0;

for (const position of this.positions.values()) {
const positionValue = position.getTotalValue();
const fees = position.getAccumulatedFees();
const utilization = position.getCurrentUtilization();

totalValue += positionValue;
totalFeesEarned += fees.total;
averageUtilization += utilization;

if (position.isActive()) {
activePositions++;
}
}

return {
totalPositions: this.positions.size,
activePositions,
totalValueUSD: totalValue,
totalFeesEarned,
averageUtilization: averageUtilization / this.positions.size,
estimatedAPR: this.calculatePortfolioAPR()
};
}

private async evaluatePositionForRebalance(
position: LiquidityPosition
): Promise<RebalanceEvaluation> {
const currentPrice = position.pool.getCurrentPrice();
const range = position.getPriceRange();
const utilization = position.getCurrentUtilization();

// Check if out of range
if (currentPrice < range.lower || currentPrice > range.upper) {
return {
recommended: true,
strategy: 'recenter',
reason: 'Position is out of range'
};
}

// Check if poor utilization
if (utilization < 0.3) { // Less than 30% utilization
return {
recommended: true,
strategy: 'narrow_range',
reason: 'Low capital utilization'
};
}

// Check if range is too narrow (frequent rebalancing)
const rebalanceFrequency = await position.getRebalanceFrequency();
if (rebalanceFrequency > 3) { // More than 3 rebalances per week
return {
recommended: true,
strategy: 'widen_range',
reason: 'Too frequent rebalancing'
};
}

return {
recommended: false,
reason: 'Position is performing well'
};
}
}

interface RebalanceEvaluation {
recommended: boolean;
strategy?: 'recenter' | 'narrow_range' | 'widen_range';
reason: string;
}

interface PortfolioSummary {
totalPositions: number;
activePositions: number;
totalValueUSD: number;
totalFeesEarned: number;
averageUtilization: number;
estimatedAPR: number;
}

Fee Collection and Compounding

Automated Fee Collection

class AutoFeeCollector {
private positions: LiquidityPosition[];
private config: FeeCollectionConfig;

interface FeeCollectionConfig {
minFeeThreshold: number; // Minimum fee amount to collect ($)
maxGasCostRatio: number; // Max gas cost as % of fees
compoundStrategy: 'reinvest' | 'withdraw' | 'mixed';
checkInterval: number; // Minutes between checks
}

async startAutoCollection() {
console.log('🔄 Starting automated fee collection...');

const collectionLoop = async () => {
try {
await this.collectFeesIfProfitable();
} catch (error) {
console.error('Fee collection error:', error);
}

// Schedule next check
setTimeout(collectionLoop, this.config.checkInterval * 60 * 1000);
};

collectionLoop();
}

private async collectFeesIfProfitable(): Promise<void> {
for (const position of this.positions) {
const fees = position.getPendingFees();
const feeValueUSD = fees.tokenX * await getTokenPrice(position.pool.tokenX.mintAddress) +
fees.tokenY * await getTokenPrice(position.pool.tokenY.mintAddress);

if (feeValueUSD >= this.config.minFeeThreshold) {
const gasCost = await this.estimateCollectionGasCost();
const gasCostUSD = gasCost * await getTokenPrice('SOL');

// Only collect if gas cost is reasonable
if (gasCostUSD <= feeValueUSD * this.config.maxGasCostRatio) {
await this.collectAndProcessFees(position);
} else {
console.log(`Skipping fee collection - gas cost too high: $${gasCostUSD} vs fees $${feeValueUSD}`);
}
}
}
}

private async collectAndProcessFees(position: LiquidityPosition): Promise<void> {
const fees = await position.collectFees();

console.log(`💰 Collected fees: ${fees.tokenX} X, ${fees.tokenY} Y`);

switch (this.config.compoundStrategy) {
case 'reinvest':
await this.reinvestFees(position, fees);
break;
case 'withdraw':
// Fees are already in wallet, no action needed
console.log('Fees withdrawn to wallet');
break;
case 'mixed':
await this.mixedFeeStrategy(position, fees);
break;
}
}

private async reinvestFees(
position: LiquidityPosition,
fees: { tokenX: number; tokenY: number }
): Promise<void> {
// Add collected fees back to the position
await position.addLiquidity({
tokenXAmount: fees.tokenX,
tokenYAmount: fees.tokenY,
slippageTolerance: 0.02
});

console.log('🔄 Fees reinvested into position');
}

private async mixedFeeStrategy(
position: LiquidityPosition,
fees: { tokenX: number; tokenY: number }
): Promise<void> {
// Reinvest 70%, withdraw 30%
const reinvestPercent = 0.7;

await position.addLiquidity({
tokenXAmount: fees.tokenX * reinvestPercent,
tokenYAmount: fees.tokenY * reinvestPercent,
slippageTolerance: 0.02
});

// Remaining 30% stays in wallet
console.log('🔄 70% fees reinvested, 30% withdrawn');
}
}

Position Performance Analytics

class PositionAnalytics {
async generatePerformanceReport(
position: LiquidityPosition,
startDate: Date,
endDate: Date = new Date()
): Promise<PerformanceReport> {

const duration = (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24);
const initialValue = position.getInitialValue();
const currentValue = position.getTotalValue();
const feesEarned = position.getAccumulatedFees();

// Calculate performance metrics
const totalReturn = ((currentValue - initialValue) / initialValue) * 100;
const feeAPR = (feesEarned.total / initialValue) * (365 / duration) * 100;
const impermanentLoss = await this.calculateImpermanentLoss(position);
const utilization = await this.calculateAverageUtilization(position, duration);

// Benchmark against alternatives
const hodlReturn = this.calculateHodlReturn(position, startDate, endDate);
const traditionalAMMReturn = await this.estimateTraditionalAMMReturn(position, duration);

return {
position: {
id: position.positionId,
range: position.getPriceRange(),
duration: duration,
initialValue: initialValue,
currentValue: currentValue
},
performance: {
totalReturnPercent: totalReturn,
feeAPR: feeAPR,
impermanentLoss: impermanentLoss,
netReturnPercent: totalReturn - Math.abs(impermanentLoss),
utilization: utilization
},
benchmarks: {
hodlReturn: hodlReturn,
traditionalAMM: traditionalAMMReturn,
bestStrategy: this.identifyBestStrategy(totalReturn, hodlReturn, traditionalAMMReturn)
},
recommendations: this.generateRecommendations(position, {
totalReturn,
feeAPR,
impermanentLoss,
utilization
})
};
}

private generateRecommendations(
position: LiquidityPosition,
metrics: any
): string[] {
const recommendations = [];

if (metrics.utilization < 0.5) {
recommendations.push('Consider narrowing position range for better capital efficiency');
}

if (metrics.impermanentLoss < -10) {
recommendations.push('High impermanent loss detected - consider rebalancing or hedging');
}

if (metrics.feeAPR > 50) {
recommendations.push('Excellent fee generation - consider increasing position size');
}

if (metrics.feeAPR < 5) {
recommendations.push('Low fee generation - consider moving to more active price range');
}

const currentPrice = position.pool.getCurrentPrice();
const range = position.getPriceRange();

if (currentPrice < range.lower * 1.1) {
recommendations.push('Price approaching lower bound - consider rebalancing up');
}

if (currentPrice > range.upper * 0.9) {
recommendations.push('Price approaching upper bound - consider rebalancing down');
}

return recommendations;
}
}

interface PerformanceReport {
position: {
id: string;
range: { lower: number; upper: number };
duration: number;
initialValue: number;
currentValue: number;
};
performance: {
totalReturnPercent: number;
feeAPR: number;
impermanentLoss: number;
netReturnPercent: number;
utilization: number;
};
benchmarks: {
hodlReturn: number;
traditionalAMM: number;
bestStrategy: string;
};
recommendations: string[];
}

Risk Management

Position Risk Assessment

interface RiskProfile {
volatilityRisk: number; // Risk from price volatility
liquidityRisk: number; // Risk from low trading volume
concentrationRisk: number; // Risk from narrow ranges
impermanentLossRisk: number; // Potential IL exposure
overallRiskScore: number; // Combined risk (1-10 scale)
}

async function assessPositionRisk(
position: LiquidityPosition,
marketData: MarketData
): Promise<RiskProfile> {

const range = position.getPriceRange();
const currentPrice = position.pool.getCurrentPrice();

// 1. Volatility Risk
const rangeWidth = ((range.upper - range.lower) / currentPrice) * 100;
const historicalVolatility = marketData.dailyVolatility * 100;
const volatilityRisk = Math.min(10, Math.max(1, 10 - (rangeWidth / historicalVolatility)));

// 2. Liquidity Risk
const poolLiquidity = await position.pool.getTotalLiquidity();
const liquidityRisk = poolLiquidity < 100000 ? 8 :
poolLiquidity < 500000 ? 5 :
poolLiquidity < 1000000 ? 3 : 1;

// 3. Concentration Risk
const concentrationRisk = rangeWidth < 5 ? 8 : // Very narrow = high risk
rangeWidth < 15 ? 5 : // Moderate = medium risk
rangeWidth < 30 ? 2 : 1; // Wide = low risk

// 4. Impermanent Loss Risk
const maxPriceMove = Math.max(
Math.abs((range.upper - currentPrice) / currentPrice),
Math.abs((currentPrice - range.lower) / currentPrice)
) * 100;
const ilRisk = maxPriceMove > 50 ? 9 :
maxPriceMove > 20 ? 6 :
maxPriceMove > 10 ? 3 : 1;

// 5. Overall Risk Score (weighted average)
const overallRisk = (
volatilityRisk * 0.3 +
liquidityRisk * 0.2 +
concentrationRisk * 0.3 +
ilRisk * 0.2
);

return {
volatilityRisk,
liquidityRisk,
concentrationRisk,
impermanentLossRisk: ilRisk,
overallRiskScore: Math.round(overallRisk)
};
}

Emergency Position Management

class EmergencyManager {
async emergencyExit(
position: LiquidityPosition,
reason: 'high_volatility' | 'smart_contract_risk' | 'market_crash'
): Promise<EmergencyExitResult> {

console.log(`🚨 Emergency exit triggered: ${reason}`);

try {
// 1. Collect any pending fees first
const fees = await position.collectFees();
console.log(`Collected emergency fees: ${fees.tokenX + fees.tokenY}`);

// 2. Remove all liquidity immediately
const removedLiquidity = await position.removeLiquidity(1.0);
console.log(`Emergency liquidity removed: ${removedLiquidity.tokenX} X, ${removedLiquidity.tokenY} Y`);

// 3. Optional: Convert to stablecoin if market crash
if (reason === 'market_crash') {
const conversionResult = await this.convertToStablecoin(removedLiquidity, fees);
return {
success: true,
exitReason: reason,
liquidityRecovered: removedLiquidity,
feesCollected: fees,
stablecoinConverted: conversionResult.totalUSDC
};
}

return {
success: true,
exitReason: reason,
liquidityRecovered: removedLiquidity,
feesCollected: fees
};

} catch (error) {
console.error('❌ Emergency exit failed:', error);

return {
success: false,
exitReason: reason,
error: error.message
};
}
}

private async convertToStablecoin(
liquidity: { tokenX: number; tokenY: number },
fees: { tokenX: number; tokenY: number }
): Promise<{ totalUSDC: number }> {
// Implementation would swap both tokens to USDC
// Using the main Saros AMM SDK for swaps

const totalTokenX = liquidity.tokenX + fees.tokenX;
const totalTokenY = liquidity.tokenY + fees.tokenY;

// Swap logic here using main SDK
console.log(`Converting ${totalTokenX} X and ${totalTokenY} Y to USDC`);

return { totalUSDC: 0 }; // Placeholder
}
}

interface EmergencyExitResult {
success: boolean;
exitReason: string;
liquidityRecovered?: { tokenX: number; tokenY: number };
feesCollected?: { tokenX: number; tokenY: number };
stablecoinConverted?: number;
error?: string;
}

Testing Position Management

Position Simulation

// Simulate position performance under different market scenarios
async function simulatePositionPerformance(
pool: DLMMPool,
positionConfig: PositionConfig,
priceScenarios: PriceScenario[]
): Promise<SimulationResult[]> {

const results: SimulationResult[] = [];

for (const scenario of priceScenarios) {
const simulation = new PositionSimulator(pool, positionConfig);
const result = await simulation.runScenario(scenario);
results.push(result);
}

return results;
}

interface PriceScenario {
name: string;
pricePoints: Array<{ price: number; time: number }>; // Price over time
volume: number; // Trading volume multiplier
}

class PositionSimulator {
private pool: DLMMPool;
private config: PositionConfig;

constructor(pool: DLMMPool, config: PositionConfig) {
this.pool = pool;
this.config = config;
}

async runScenario(scenario: PriceScenario): Promise<SimulationResult> {
let totalFees = 0;
let timeInRange = 0;
let maxDrawdown = 0;

const range = {
lower: this.config.lowerPrice,
upper: this.config.upperPrice
};

for (let i = 0; i < scenario.pricePoints.length; i++) {
const { price, time } = scenario.pricePoints[i];

// Check if position is active at this price
if (price >= range.lower && price <= range.upper) {
timeInRange += time;

// Calculate fees earned during this period
const volumeInRange = scenario.volume * time * this.getVolumeMultiplier(price, range);
const feesEarned = volumeInRange * (this.pool.feeTier / 10000);
totalFees += feesEarned;
}

// Track maximum impermanent loss
const currentIL = this.calculateILForPrice(price);
maxDrawdown = Math.min(maxDrawdown, currentIL);
}

const totalTime = scenario.pricePoints.reduce((sum, point) => sum + point.time, 0);
const utilization = timeInRange / totalTime;

return {
scenarioName: scenario.name,
totalFeesEarned: totalFees,
timeInRangePercent: utilization * 100,
maxDrawdown: maxDrawdown,
netReturn: totalFees + maxDrawdown, // Fees minus worst IL
utilization: utilization
};
}

private getVolumeMultiplier(price: number, range: { lower: number; upper: number }): number {
// Volume tends to be higher near range boundaries
const rangeMidpoint = (range.lower + range.upper) / 2;
const distanceFromMid = Math.abs(price - rangeMidpoint) / (rangeMidpoint - range.lower);

// Higher multiplier near edges where more trading occurs
return 1 + distanceFromMid * 0.5;
}

private calculateILForPrice(price: number): number {
// Calculate IL for this specific price vs initial price
const initialPrice = (this.config.lowerPrice + this.config.upperPrice) / 2;
const priceRatio = price / initialPrice;

// Simplified IL calculation for concentrated positions
return -Math.abs((Math.sqrt(priceRatio) - 1) * 100);
}
}

interface SimulationResult {
scenarioName: string;
totalFeesEarned: number;
timeInRangePercent: number;
maxDrawdown: number;
netReturn: number;
utilization: number;
}

Next Steps

✅ Position management mastered
➡️ Next: DLMM API Reference

Or explore practical applications: