feat: Complete pyramiding/position stacking implementation (ALL 7 phases)

Phase 1: Configuration
- Added pyramiding config to trading.ts interface and defaults
- Added 6 ENV variables: ENABLE_PYRAMIDING, BASE_LEVERAGE, STACK_LEVERAGE,
  MAX_LEVERAGE_TOTAL, MAX_PYRAMID_LEVELS, STACKING_WINDOW_MINUTES

Phase 2: Database Schema
- Added 5 Trade fields: pyramidLevel, parentTradeId, stackedAt,
  totalLeverageAtEntry, isStackedPosition
- Added index on parentTradeId for pyramid group queries

Phase 3: Execute Endpoint
- Added findExistingPyramidBase() - finds active base trade within window
- Added canAddPyramidLevel() - validates pyramid conditions
- Stores pyramid metadata on new trades

Phase 4: Position Manager Core
- Added pyramidGroups Map for trade ID grouping
- Added addToPyramidGroup() - groups stacked trades by parent
- Added closeAllPyramidLevels() - unified exit for all levels
- Added getTotalPyramidLeverage() - calculates combined leverage
- All exit triggers now close entire pyramid group

Phase 5: Telegram Notifications
- Added sendPyramidStackNotification() - notifies on stack entry
- Added sendPyramidCloseNotification() - notifies on unified exit

Phase 6: Testing (25 tests, ALL PASSING)
- Pyramid Detection: 5 tests
- Pyramid Group Tracking: 4 tests
- Unified Exit: 4 tests
- Leverage Calculation: 4 tests
- Notification Context: 2 tests
- Edge Cases: 6 tests

Phase 7: Documentation
- Updated .github/copilot-instructions.md with full implementation details
- Updated docs/PYRAMIDING_IMPLEMENTATION_PLAN.md status to COMPLETE

Parameters: 4h window, 7x base/stack leverage, 14x max total, 2 max levels
Data-driven: 100% win rate for signals ≤72 bars apart in backtesting
This commit is contained in:
mindesbunister
2026-01-09 13:53:05 +01:00
parent b2ff3026c6
commit 96d1667ae6
17 changed files with 2384 additions and 56 deletions

View File

@@ -17,6 +17,11 @@ interface TelegramNotificationOptions {
holdTimeSeconds: number
maxDrawdown?: number
maxGain?: number
// 🔺 Pyramiding fields (Jan 2026)
pyramidLevel?: number // 1 = base, 2 = first stack, etc.
isStackedPosition?: boolean // True if this is an add-on position
pyramidGroupSize?: number // Total positions in pyramid group
pyramidGroupPnL?: number // Combined P&L of entire pyramid group
}
interface TelegramWithdrawalOptions {
@@ -51,10 +56,11 @@ export async function sendPositionClosedNotification(options: TelegramNotificati
const message = `${exitReasonEmoji} POSITION CLOSED
${directionEmoji} ${options.symbol} ${options.direction.toUpperCase()}
${directionEmoji} ${options.symbol} ${options.direction.toUpperCase()}${options.pyramidLevel ? ` 🔺 Level ${options.pyramidLevel}${options.isStackedPosition ? ' (stacked)' : ' (base)'}` : ''}
💰 P&L: $${options.realizedPnL.toFixed(2)} (${priceChange >= 0 ? '+' : ''}${priceChange.toFixed(2)}%)
📊 Size: $${options.positionSize.toFixed(2)}
${options.pyramidGroupPnL !== undefined && options.pyramidGroupSize && options.pyramidGroupSize > 1 ? `🔺 Group P&L: $${options.pyramidGroupPnL.toFixed(2)} (${options.pyramidGroupSize} positions)` : ''}
📍 Entry: $${options.entryPrice.toFixed(2)}
🎯 Exit: $${options.exitPrice.toFixed(2)}
@@ -320,3 +326,71 @@ export async function sendTelegramMessage(message: string): Promise<void> {
console.error('❌ Error sending Telegram notification:', error)
}
}
/**
* 🔺 Send pyramid group closure notification (Jan 2026)
* Sends a summary notification when all positions in a pyramid group are closed together
*/
export interface PyramidGroupNotificationOptions {
symbol: string
direction: 'long' | 'short'
exitReason: string
totalPositions: number
combinedPnL: number
combinedSize: number
avgEntryPrice: number
exitPrice: number
pyramidLevels: number[] // e.g., [1, 2] for base + one stack
}
export async function sendPyramidGroupClosedNotification(options: PyramidGroupNotificationOptions): Promise<void> {
try {
const token = process.env.TELEGRAM_BOT_TOKEN
const chatId = process.env.TELEGRAM_CHAT_ID
if (!token || !chatId) {
logger.log('⚠️ Telegram credentials not configured, skipping notification')
return
}
const profitEmoji = options.combinedPnL >= 0 ? '💚' : '🔴'
const exitReasonEmoji = getExitReasonEmoji(options.exitReason)
const directionEmoji = options.direction === 'long' ? '📈' : '📉'
const priceChange = ((options.exitPrice - options.avgEntryPrice) / options.avgEntryPrice * 100) * (options.direction === 'long' ? 1 : -1)
const levelsStr = options.pyramidLevels.sort((a, b) => a - b).join(', ')
const message = `🔺 PYRAMID GROUP CLOSED
${directionEmoji} ${options.symbol} ${options.direction.toUpperCase()}
${profitEmoji} Combined P&L: $${options.combinedPnL.toFixed(2)} (${priceChange >= 0 ? '+' : ''}${priceChange.toFixed(2)}%)
📊 Total Size: $${options.combinedSize.toFixed(2)}
🔺 Positions: ${options.totalPositions} (levels: ${levelsStr})
📍 Avg Entry: $${options.avgEntryPrice.toFixed(2)}
🎯 Exit: $${options.exitPrice.toFixed(2)}
${exitReasonEmoji} Exit Reason: ${options.exitReason.toUpperCase()}`
const url = `https://api.telegram.org/bot${token}/sendMessage`
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
chat_id: chatId,
text: message,
parse_mode: 'HTML'
})
})
if (!response.ok) {
const errorData = await response.json()
console.error('❌ Telegram pyramid group notification failed:', errorData)
} else {
logger.log('✅ Telegram pyramid group notification sent')
}
} catch (error) {
console.error('❌ Error sending Telegram pyramid group notification:', error)
}
}