calculate_trailing_stop
Automate trailing stop loss calculations for Bybit trades, enabling breakeven and profit protection based on market price, entry price, and specified triggers.
Instructions
Calculate trailing stop loss with breakeven and profit protection
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| breakevenTrigger | No | Price distance from entry to activate breakeven protection (optional) | |
| category | Yes | Category (linear, inverse) | |
| currentPrice | Yes | Current market price | |
| entryPrice | Yes | Entry price of the position | |
| initialStopLoss | Yes | Initial stop loss price | |
| profitProtectionTrigger | No | Price distance from entry to activate profit protection (optional) | |
| side | Yes | Position side (Buy for long, Sell for short) | |
| symbol | Yes | Symbol (e.g., ETHUSDT) | |
| trailingDistance | Yes | Trailing distance in price units |
Implementation Reference
- src/bybit-service.ts:526-748 (handler)Core handler function implementing trailing stop calculation logic with breakeven and profit protection features, unrealized PnL computation, risk warnings, and tick-size compliant price rounding.async calculateTrailingStop( category: string, symbol: string, entryPrice: number, currentPrice: number, side: 'Buy' | 'Sell', initialStopLoss: number, trailingDistance: number, breakevenTrigger?: number, profitProtectionTrigger?: number ): Promise<{ newStopLoss: number; trailingActivated: boolean; breakevenProtection: boolean; profitProtected: number; unrealizedPnL: number; unrealizedPnLPercentage: number; recommendations: { shouldUpdateStop: boolean; newStopPrice: number; protectionLevel: 'none' | 'breakeven' | 'profit'; reasoning: string; }; calculations: { priceMovement: number; priceMovementPercentage: number; stopLossMovement: number; maxDrawdownProtection: number; profitLocked: number; }; warnings: string[]; error?: string; }> { try { const warnings: string[] = []; const isLong = side === 'Buy'; // Validate inputs if (trailingDistance <= 0) { return { newStopLoss: initialStopLoss, trailingActivated: false, breakevenProtection: false, profitProtected: 0, unrealizedPnL: 0, unrealizedPnLPercentage: 0, recommendations: { shouldUpdateStop: false, newStopPrice: initialStopLoss, protectionLevel: 'none', reasoning: 'Invalid trailing distance' }, calculations: { priceMovement: 0, priceMovementPercentage: 0, stopLossMovement: 0, maxDrawdownProtection: 0, profitLocked: 0 }, warnings: ['Trailing distance must be greater than 0'], error: 'Invalid trailing distance' }; } // Calculate price movements const priceMovement = isLong ? currentPrice - entryPrice : entryPrice - currentPrice; const priceMovementPercentage = (priceMovement / entryPrice) * 100; // Calculate unrealized PnL const unrealizedPnL = priceMovement; const unrealizedPnLPercentage = priceMovementPercentage; // Set default triggers if not provided const breakevenTriggerDistance = breakevenTrigger || (Math.abs(entryPrice - initialStopLoss) * 1.5); const profitProtectionTriggerDistance = profitProtectionTrigger || (Math.abs(entryPrice - initialStopLoss) * 2.0); let newStopLoss = initialStopLoss; let trailingActivated = false; let breakevenProtection = false; let profitProtected = 0; let protectionLevel: 'none' | 'breakeven' | 'profit' = 'none'; let reasoning = 'No trailing conditions met'; // Check if price has moved favorably enough to activate trailing const favorableMovement = isLong ? currentPrice > entryPrice : currentPrice < entryPrice; if (favorableMovement) { // Calculate potential new stop loss based on trailing distance const potentialNewStop = isLong ? currentPrice - trailingDistance : currentPrice + trailingDistance; // Only move stop loss if it's better than current stop const shouldMoveStop = isLong ? potentialNewStop > initialStopLoss : potentialNewStop < initialStopLoss; if (shouldMoveStop) { newStopLoss = potentialNewStop; trailingActivated = true; // Check for breakeven protection const breakevenReached = isLong ? currentPrice >= entryPrice + breakevenTriggerDistance : currentPrice <= entryPrice - breakevenTriggerDistance; if (breakevenReached) { const breakevenStop = entryPrice; // Move stop to breakeven if (isLong ? breakevenStop > newStopLoss : breakevenStop < newStopLoss) { newStopLoss = breakevenStop; breakevenProtection = true; protectionLevel = 'breakeven'; reasoning = 'Breakeven protection activated - stop moved to entry price'; } } // Check for profit protection const profitProtectionReached = isLong ? currentPrice >= entryPrice + profitProtectionTriggerDistance : currentPrice <= entryPrice - profitProtectionTriggerDistance; if (profitProtectionReached) { const profitProtectionStop = isLong ? entryPrice + (profitProtectionTriggerDistance * 0.5) // Lock in 50% of the trigger distance as profit : entryPrice - (profitProtectionTriggerDistance * 0.5); if (isLong ? profitProtectionStop > newStopLoss : profitProtectionStop < newStopLoss) { newStopLoss = profitProtectionStop; profitProtected = Math.abs(profitProtectionStop - entryPrice); protectionLevel = 'profit'; reasoning = `Profit protection activated - locking in ${profitProtected.toFixed(2)} profit`; } } if (protectionLevel === 'none') { reasoning = `Trailing stop activated - following price with ${trailingDistance} distance`; } } else { reasoning = 'Price moved favorably but not enough to improve stop loss'; } } else { reasoning = 'Price has not moved favorably - maintaining original stop loss'; } // Calculate additional metrics const stopLossMovement = Math.abs(newStopLoss - initialStopLoss); const maxDrawdownProtection = Math.abs(currentPrice - newStopLoss); const profitLocked = Math.max(0, isLong ? newStopLoss - entryPrice : entryPrice - newStopLoss); // Add warnings for risk management if (unrealizedPnLPercentage < -5) { warnings.push('Position is down more than 5% - consider reviewing strategy'); } if (trailingDistance > Math.abs(entryPrice - initialStopLoss) * 2) { warnings.push('Trailing distance is very large compared to initial risk - may give back too much profit'); } if (Math.abs(unrealizedPnLPercentage) > 20) { warnings.push('Large unrealized PnL - consider taking partial profits'); } // Get instrument info for price formatting const instrumentInfo = await this.getInstrumentsInfo(category, symbol); if ('result' in instrumentInfo) { const priceFilter = instrumentInfo.result.list[0].priceFilter; const tickSize = parseFloat(priceFilter.tickSize); const decimalPlaces = tickSize.toString().split('.')[1]?.length || 0; // Round new stop loss to valid tick size newStopLoss = Math.round(newStopLoss / tickSize) * tickSize; newStopLoss = parseFloat(newStopLoss.toFixed(decimalPlaces)); } return { newStopLoss, trailingActivated, breakevenProtection, profitProtected, unrealizedPnL, unrealizedPnLPercentage, recommendations: { shouldUpdateStop: newStopLoss !== initialStopLoss, newStopPrice: newStopLoss, protectionLevel, reasoning }, calculations: { priceMovement, priceMovementPercentage, stopLossMovement, maxDrawdownProtection, profitLocked }, warnings }; } catch (error: any) { return { newStopLoss: initialStopLoss, trailingActivated: false, breakevenProtection: false, profitProtected: 0, unrealizedPnL: 0, unrealizedPnLPercentage: 0, recommendations: { shouldUpdateStop: false, newStopPrice: initialStopLoss, protectionLevel: 'none', reasoning: 'Error calculating trailing stop' }, calculations: { priceMovement: 0, priceMovementPercentage: 0, stopLossMovement: 0, maxDrawdownProtection: 0, profitLocked: 0 }, warnings: [], error: error.message }; } }
- src/index.ts:591-636 (registration)MCP tool registration defining the 'calculate_trailing_stop' tool name, description, and input schema for validation.{ name: 'calculate_trailing_stop', description: 'Calculate trailing stop loss with breakeven and profit protection', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Category (linear, inverse)', }, symbol: { type: 'string', description: 'Symbol (e.g., ETHUSDT)', }, entryPrice: { type: 'number', description: 'Entry price of the position', }, currentPrice: { type: 'number', description: 'Current market price', }, side: { type: 'string', description: 'Position side (Buy for long, Sell for short)', }, initialStopLoss: { type: 'number', description: 'Initial stop loss price', }, trailingDistance: { type: 'number', description: 'Trailing distance in price units', }, breakevenTrigger: { type: 'number', description: 'Price distance from entry to activate breakeven protection (optional)', }, profitProtectionTrigger: { type: 'number', description: 'Price distance from entry to activate profit protection (optional)', }, }, required: ['category', 'symbol', 'entryPrice', 'currentPrice', 'side', 'initialStopLoss', 'trailingDistance'], }, },
- src/index.ts:1024-1044 (handler)Tool dispatch handler in main MCP server that extracts parameters from request and calls the BybitService.calculateTrailingStop method.case 'calculate_trailing_stop': { const result = await this.bybitService.calculateTrailingStop( typedArgs.category, typedArgs.symbol, typedArgs.entryPrice, typedArgs.currentPrice, typedArgs.side, typedArgs.initialStopLoss, typedArgs.trailingDistance, typedArgs.breakevenTrigger, typedArgs.profitProtectionTrigger ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; }