- Updated copilot-instructions.md: Changed status to DEPLOYED TO PRODUCTION - Updated PYRAMIDING_IMPLEMENTATION_PLAN.md: Added Phase 8 deployment status - Added deployment verification checklist with git commit references - Both documentation files now reflect production deployment state
12 KiB
Pyramiding / Signal Stacking Implementation Plan
Created: January 9, 2026
Completed: January 9, 2026
Deployed: January 9, 2026
Status: ✅ FULLY IMPLEMENTED, TESTED, AND DEPLOYED TO PRODUCTION
Priority: HIGH - User-requested feature based on backtested data
✅ Implementation & Deployment 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)
- ✅ Phase 8: Production Deployment (docker-compose.yml env vars, container restart)
Test Coverage: 25 pyramiding tests across 6 test suites, all passing Total Position Manager Tests: 164 tests, all passing
Production Deployment Verified (Jan 9, 2026):
- ✅ Database schema synced (5 pyramiding columns in Trade table)
- ✅ Environment variables loaded in container (6 vars)
- ✅ docker-compose.yml updated with env var mappings
- ✅ Container running and healthy
- ✅ Git commits:
96d1667(implementation),c378d34(docker-compose.yml fix)
📊 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
-
Max Leverage Guard
if (totalLeverage > config.maxLeverageTotal) { console.error(`❌ BLOCKED: Would exceed max leverage (${totalLeverage}x > ${config.maxLeverageTotal}x)`); return; } -
Same Direction Only
- Only stack in same direction as existing position
- Opposite signal should close existing position, not stack
-
Time Window Enforcement
- Hard 4-hour (240 minute) window
- Signals outside window treated as independent trades
-
Max Levels Cap
- Maximum 2 pyramid levels (base + 1 stack = 14x max)
- Additional signals beyond max are ignored with warning
-
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
TradingConfiginterface inconfig/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
- Conservative Window: User chose 4 hours (48 bars) vs optimal 6 hours (72 bars) for safety
- Leverage Reduction: Base leverage reduced from 10x to 7x to accommodate stacking safely
- Total Cap: 14x maximum (7x + 7x) prevents over-leveraging
- 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.