Files
trading_bot_v4/docs/PYRAMIDING_IMPLEMENTATION_PLAN.md
mindesbunister 96d1667ae6 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
2026-01-09 13:53:05 +01:00

12 KiB
Raw Blame History

Pyramiding / Signal Stacking Implementation Plan

Created: January 9, 2026
Completed: January 9, 2026
Status: FULLY IMPLEMENTED AND TESTED
Priority: HIGH - User-requested feature based on backtested data


Implementation Complete!

All 7 phases successfully implemented:

  • Phase 1: Configuration (config/trading.ts, .env)
  • Phase 2: Database Schema (prisma/schema.prisma)
  • Phase 3: Execute Endpoint (app/api/trading/execute/route.ts, lib/database/trades.ts)
  • Phase 4: Position Manager Core (lib/trading/position-manager.ts)
  • Phase 5: Telegram Notifications (lib/notifications/telegram.ts)
  • Phase 6: Testing (25 tests in 6 suites - ALL PASSING)
  • Phase 7: Documentation (.github/copilot-instructions.md updated)

Test Coverage: 25 pyramiding tests across 6 test suites, all passing Total Position Manager Tests: 164 tests, all passing

Ready for Production Deployment:

# 1. Run database migration
npx prisma migrate dev --name add_pyramiding_fields

# 2. Rebuild Docker container
docker compose build trading-bot
docker compose up -d --force-recreate trading-bot

# 3. Verify deployment
docker logs trading-bot-v4 | grep -i "pyramid"

📊 Background & Data-Driven Justification

Analysis Results (Jan 9, 2026)

From CSV analysis of ML v11.2 Long Only strategy on SOL/USDT 5-min chart:

Signal Spacing Pairs Win Rate Avg Combined P&L
≤72 bars (6h) 9 100% +4.82%
>72 bars 34 67.6% +1.94%

Key Finding: Signals within 72 bars (6 hours) of each other have 100% win rate - these represent trend confirmation and are ideal for stacking.

User-Selected Parameters

  • Stacking Window: 4 hours (48 bars on 5-min chart) - conservative vs 6h optimal
  • Base Leverage: 7x (down from previous 10x for safety)
  • Stacked Leverage: 14x total (7x + 7x)
  • Max Pyramid Levels: 2 (base + 1 stack)

🏗️ Implementation Components

1. Environment Variables (.env)

# === PYRAMIDING / STACKING ===
ENABLE_PYRAMIDING=true
BASE_LEVERAGE=7
STACK_LEVERAGE=7
MAX_LEVERAGE_TOTAL=14
STACKING_WINDOW_MINUTES=240        # 4 hours = 48 bars on 5-min chart
MAX_PYRAMID_LEVELS=2               # Base + 1 additional stack

# Per-symbol overrides (optional)
SOLANA_BASE_LEVERAGE=7
SOLANA_MAX_LEVERAGE=14
SOLANA_STACKING_WINDOW_MINUTES=240

2. Configuration (config/trading.ts)

Add to TradingConfig interface:

interface TradingConfig {
  // ... existing fields ...
  
  // Pyramiding settings
  enablePyramiding: boolean;
  baseLeverage: number;
  stackLeverage: number;
  maxLeverageTotal: number;
  stackingWindowMinutes: number;
  maxPyramidLevels: number;
}

Add to DEFAULT_TRADING_CONFIG:

enablePyramiding: true,
baseLeverage: 7,
stackLeverage: 7,
maxLeverageTotal: 14,
stackingWindowMinutes: 240,  // 4 hours
maxPyramidLevels: 2,

Add to getConfigFromEnv():

enablePyramiding: process.env.ENABLE_PYRAMIDING === 'true',
baseLeverage: parseInt(process.env.BASE_LEVERAGE || '7'),
stackLeverage: parseInt(process.env.STACK_LEVERAGE || '7'),
maxLeverageTotal: parseInt(process.env.MAX_LEVERAGE_TOTAL || '14'),
stackingWindowMinutes: parseInt(process.env.STACKING_WINDOW_MINUTES || '240'),
maxPyramidLevels: parseInt(process.env.MAX_PYRAMID_LEVELS || '2'),

3. Database Schema (prisma/schema.prisma)

Add to Trade model:

model Trade {
  // ... existing fields ...
  
  // Pyramiding tracking
  pyramidLevel        Int?      @default(1)     // 1 = base, 2 = first stack, etc.
  parentTradeId       String?                   // Reference to base trade if stacked
  stackedAt           DateTime?                 // When stack was added
  totalLeverageAtEntry Float?                   // Total leverage including stacks
  isStackedPosition   Boolean   @default(false) // True if this is a stack entry
}

4. Execute Endpoint (app/api/trading/execute/route.ts)

A. Add Stacking Logic Check

Before opening a new position, check if we should stack:

// Check for existing open position in same direction
const existingOpenTrade = await prisma.trade.findFirst({
  where: {
    symbol: driftSymbol,
    direction: direction,
    exitReason: null,  // Still open
  },
  orderBy: { createdAt: 'desc' }
});

// Determine if this is a stack opportunity
let isStackEntry = false;
let effectiveLeverage = config.baseLeverage;
let pyramidLevel = 1;

if (existingOpenTrade && config.enablePyramiding) {
  const minutesSinceEntry = (Date.now() - new Date(existingOpenTrade.createdAt).getTime()) / 60000;
  const currentPyramidLevel = existingOpenTrade.pyramidLevel || 1;
  
  if (minutesSinceEntry <= config.stackingWindowMinutes && 
      currentPyramidLevel < config.maxPyramidLevels) {
    // This is a valid stack opportunity
    isStackEntry = true;
    pyramidLevel = currentPyramidLevel + 1;
    effectiveLeverage = config.stackLeverage;  // Add another 7x
    
    console.log(`📈 PYRAMID STACK: Signal within ${minutesSinceEntry.toFixed(0)} minutes`)
    console.log(`   Level: ${pyramidLevel}/${config.maxPyramidLevels}`)
    console.log(`   Adding: ${effectiveLeverage}x leverage`)
    console.log(`   Total: ${currentPyramidLevel * config.baseLeverage + effectiveLeverage}x`)
  } else if (minutesSinceEntry > config.stackingWindowMinutes) {
    console.log(`⏰ Stack window expired: ${minutesSinceEntry.toFixed(0)} min > ${config.stackingWindowMinutes} min`)
    console.log(`   Treating as independent signal (not stacking)`)
  } else if (currentPyramidLevel >= config.maxPyramidLevels) {
    console.log(`🔒 Max pyramid level reached: ${currentPyramidLevel}/${config.maxPyramidLevels}`)
    console.log(`   Ignoring additional signal`)
    return NextResponse.json({ 
      success: false, 
      error: 'Max pyramid level reached',
      details: { currentLevel: currentPyramidLevel, maxLevel: config.maxPyramidLevels }
    });
  }
}

B. Modify Position Opening

When opening position, use the determined leverage:

// Use effective leverage (base or stack)
const positionParams = {
  symbol: driftSymbol,
  direction: direction,
  leverage: effectiveLeverage,  // 7x for both base and stack
  // ... other params
};

C. Update Database Record

Store pyramiding info in trade record:

const tradeData = {
  // ... existing fields ...
  pyramidLevel: pyramidLevel,
  parentTradeId: isStackEntry ? existingOpenTrade.id : null,
  stackedAt: isStackEntry ? new Date() : null,
  totalLeverageAtEntry: isStackEntry 
    ? (existingOpenTrade.totalLeverageAtEntry || config.baseLeverage) + effectiveLeverage
    : effectiveLeverage,
  isStackedPosition: isStackEntry,
};

5. Position Manager (lib/trading/position-manager.ts)

A. Track Stacked Positions Together

Modify ActiveTrade interface:

interface ActiveTrade {
  // ... existing fields ...
  pyramidLevel: number;
  parentTradeId?: string;
  isStackedPosition: boolean;
  totalLeverage: number;
}

B. Unified Exit Logic

When closing a position, close ALL pyramid levels:

async closePosition(tradeId: string, reason: string) {
  const trade = this.activeTrades.get(tradeId);
  
  // If this is a base position with stacks, close all
  if (trade && !trade.isStackedPosition) {
    const stackedTrades = Array.from(this.activeTrades.values())
      .filter(t => t.parentTradeId === tradeId);
    
    for (const stackedTrade of stackedTrades) {
      console.log(`🔗 Closing stacked position: ${stackedTrade.id} (level ${stackedTrade.pyramidLevel})`);
      await this.executeClose(stackedTrade, reason);
    }
  }
  
  await this.executeClose(trade, reason);
}

6. Telegram Notifications (lib/notifications/telegram.ts)

Update position notifications to show pyramid info:

// For stacked entries
if (trade.isStackedPosition) {
  message += `\n📈 PYRAMID STACK (Level ${trade.pyramidLevel}/${config.maxPyramidLevels})`;
  message += `\n💪 Total Leverage: ${trade.totalLeverageAtEntry}x`;
  message += `\n⏱ Stacked ${Math.round((Date.now() - new Date(trade.parentTrade.createdAt).getTime()) / 60000)} min after base`;
}

🔒 Risk Management Safeguards

Critical Safety Checks

  1. Max Leverage Guard

    if (totalLeverage > config.maxLeverageTotal) {
      console.error(`❌ BLOCKED: Would exceed max leverage (${totalLeverage}x > ${config.maxLeverageTotal}x)`);
      return;
    }
    
  2. Same Direction Only

    • Only stack in same direction as existing position
    • Opposite signal should close existing position, not stack
  3. Time Window Enforcement

    • Hard 4-hour (240 minute) window
    • Signals outside window treated as independent trades
  4. Max Levels Cap

    • Maximum 2 pyramid levels (base + 1 stack = 14x max)
    • Additional signals beyond max are ignored with warning
  5. Quality Score Requirement

    • Stacked entries should still pass quality score threshold
    • Don't stack low-quality confirmations

📋 Implementation Checklist

Phase 1: Configuration

  • Add ENV variables to .env
  • Update TradingConfig interface in config/trading.ts
  • Add defaults to DEFAULT_TRADING_CONFIG
  • Add ENV parsing to getConfigFromEnv()

Phase 2: Database

  • Add new fields to Trade model in prisma/schema.prisma
  • Run npx prisma migrate dev --name add_pyramiding_fields
  • Run npx prisma generate

Phase 3: Execute Endpoint

  • Add existing position check logic
  • Add pyramid level calculation
  • Add stacking window validation
  • Modify position opening with effective leverage
  • Update trade record with pyramid info
  • Add logging for stack events

Phase 4: Position Manager

  • Update ActiveTrade interface
  • Add stack tracking to position Map
  • Implement unified exit (close all levels together)
  • Update MAE/MFE tracking for combined position

Phase 5: Notifications

  • Update Telegram entry notification with stack info
  • Update exit notification with combined P&L

Phase 6: Testing

  • Test base entry (7x leverage)
  • Test stack entry within window (14x total)
  • Test signal outside window (independent trade)
  • Test max pyramid level blocking
  • Test unified exit closes all levels
  • Test opposite direction closes stacked position

Phase 7: Documentation

  • Update copilot-instructions.md with pyramiding section
  • Add to Settings UI (optional)

📊 Expected Behavior Examples

Example 1: Successful Stack

10:00 - BUY signal (quality 95) → Open LONG 7x leverage
12:30 - BUY signal (quality 92) → Stack! Within 4h window
        → Add 7x leverage
        → Total position: 14x leverage
14:00 - TP1 hit → Close entire stacked position
        → P&L calculated on 14x total leverage

Example 2: Outside Window

10:00 - BUY signal → Open LONG 7x leverage
15:00 - BUY signal → 5 hours later, outside 4h window
        → Treated as independent trade
        → Close existing position, open new 7x position

Example 3: Max Level Reached

10:00 - BUY signal → Open LONG 7x (level 1)
11:00 - BUY signal → Stack 7x (level 2, total 14x)
12:00 - BUY signal → Max level reached!
        → Log warning, ignore signal
        → Keep existing 14x position

Example 4: Opposite Signal

10:00 - BUY signal → Open LONG 7x leverage
11:00 - SELL signal → Opposite direction!
        → Close LONG position entirely
        → Open new SHORT 7x position

🔗 References

  • Analysis Source: /home/icke/traderv4/docs/PYRAMIDING_IMPLEMENTATION_PLAN.md
  • TradingView Strategy: /home/icke/traderv4/workflows/trading/moneyline_v11_2_strategy.pinescript
  • CSV Analysis Data: ML_v11.2_Strat_MEXC_SOLUSDT.P_2026-01-09.csv
  • Optimal Threshold Analysis: 72 bars (6 hours) = 100% WR for stacked signals

⚠️ Important Notes

  1. Conservative Window: User chose 4 hours (48 bars) vs optimal 6 hours (72 bars) for safety
  2. Leverage Reduction: Base leverage reduced from 10x to 7x to accommodate stacking safely
  3. Total Cap: 14x maximum (7x + 7x) prevents over-leveraging
  4. Quality Gate: Stack signals should still meet quality thresholds

This plan is ready for implementation. Reference this document when implementing pyramiding in the trading bot.