Files
trading_bot_v3/lib/position-consolidator.js
mindesbunister abb8c8d7f1 fix: resolve container networking issues for automation system
- Fixed internal API URLs from localhost:9001 to localhost:3000 in automation core files
- Updated lib/simple-automation.js: Fixed 5 baseUrl references for internal container calls
- Updated app/api/drift/consolidate-position/route.js: Fixed positions API fetch URL
- Updated app/api/drift/scale-position/route.js: Fixed 2 internal API calls (positions and orders)
- Updated lib/position-consolidator.js: Fixed 3 internal API calls (cancel-all-orders, place-order, positions)

This resolves 'Network Error' and 'fetch failed' issues that prevented automation
cycles from executing properly within Docker container environment.

Root cause: Automation was making fetch calls to external port (9001) from within
container instead of internal port (3000), causing connection failures.

Result: Automation cycles now execute successfully with proper internal API connectivity.
2025-07-28 01:19:37 +02:00

325 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Position Consolidator - Clean Order Management
* Consolidates multiple fragmented orders into a simple 3-order structure
*/
class PositionConsolidator {
/**
* Analyze current position and create consolidation plan
*/
static async analyzeAndConsolidate(analysis = null) {
console.log('🧹 ANALYZING POSITION FOR CONSOLIDATION');
console.log('='.repeat(50));
// Get current position
const position = await this.getCurrentPosition();
if (!position) {
throw new Error('No active position found');
}
const symbol = position.symbol.replace('-PERP', '');
console.log(`📊 Position: ${position.side.toUpperCase()} ${position.size} ${symbol}`);
console.log(`💰 Entry: $${position.entryPrice.toFixed(4)}`);
console.log(`📈 Current: $${position.currentPrice?.toFixed(4) || 'N/A'}`);
console.log(`💸 P&L: ${position.pnl ? '$' + position.pnl.toFixed(2) : 'N/A'}`);
// Calculate optimal consolidated levels (with AI analysis if available)
const consolidatedPlan = this.calculateConsolidatedLevels(position, analysis);
console.log('\n📊 CONSOLIDATED ORDER PLAN:');
console.log('='.repeat(30));
console.log(`Stop Loss: $${consolidatedPlan.stopLoss.toFixed(4)} (${consolidatedPlan.stopLossPercent.toFixed(1)}% risk)`);
console.log(`Take Profit 1: $${consolidatedPlan.takeProfit1.toFixed(4)} (${consolidatedPlan.tp1Percent.toFixed(1)}% gain) - ${consolidatedPlan.tp1Size} ${symbol}`);
console.log(`Take Profit 2: $${consolidatedPlan.takeProfit2.toFixed(4)} (${consolidatedPlan.tp2Percent.toFixed(1)}% gain) - ${consolidatedPlan.tp2Size} ${symbol}`);
console.log(`Risk/Reward: ${consolidatedPlan.riskReward.toFixed(1)}:1`);
return consolidatedPlan;
}
/**
* Calculate optimal consolidated order levels
* Priority: AI-calculated levels > Dynamic adaptive levels
*/
static calculateConsolidatedLevels(position, analysis = null) {
const { side, size, entryPrice } = position;
let stopLoss, takeProfit1, takeProfit2;
let stopLossPercent, takeProfit1Percent, takeProfit2Percent;
let usingAILevels = false;
// 🧠 PRIORITY 1: Extract AI-calculated optimal levels
if (analysis) {
console.log('🧠 Checking AI analysis for optimal levels...');
// Extract AI stop loss
let aiStopLoss = null;
if (analysis.stopLoss?.price) {
aiStopLoss = analysis.stopLoss.price;
} else if (analysis.stopLoss && typeof analysis.stopLoss === 'number') {
aiStopLoss = analysis.stopLoss;
} else if (analysis.levels?.stopLoss) {
aiStopLoss = analysis.levels.stopLoss;
}
// Extract AI take profits
let aiTakeProfit1 = null, aiTakeProfit2 = null;
if (analysis.takeProfits?.tp1?.price) {
aiTakeProfit1 = analysis.takeProfits.tp1.price;
aiTakeProfit2 = analysis.takeProfits?.tp2?.price || null;
} else if (analysis.takeProfit && typeof analysis.takeProfit === 'number') {
aiTakeProfit1 = analysis.takeProfit;
} else if (analysis.levels?.takeProfit) {
aiTakeProfit1 = analysis.levels.takeProfit;
}
// Use AI levels if available
if (aiStopLoss && aiTakeProfit1) {
console.log('✅ Using AI-calculated optimal levels');
stopLoss = aiStopLoss;
takeProfit1 = aiTakeProfit1;
// Calculate percentages from AI prices
if (side.toLowerCase() === 'long') {
stopLossPercent = ((entryPrice - stopLoss) / entryPrice) * 100;
takeProfit1Percent = ((takeProfit1 - entryPrice) / entryPrice) * 100;
} else {
stopLossPercent = ((stopLoss - entryPrice) / entryPrice) * 100;
takeProfit1Percent = ((entryPrice - takeProfit1) / entryPrice) * 100;
}
// If no second TP from AI, calculate based on risk/reward extension
if (aiTakeProfit2) {
takeProfit2 = aiTakeProfit2;
if (side.toLowerCase() === 'long') {
takeProfit2Percent = ((takeProfit2 - entryPrice) / entryPrice) * 100;
} else {
takeProfit2Percent = ((entryPrice - takeProfit2) / entryPrice) * 100;
}
} else {
// Extend first TP by additional 60% distance for aggressive second target
const tpDistance = Math.abs(takeProfit1 - entryPrice);
takeProfit2 = side.toLowerCase() === 'long' ?
takeProfit1 + (tpDistance * 0.6) :
takeProfit1 - (tpDistance * 0.6);
takeProfit2Percent = takeProfit1Percent * 1.6; // 60% more than first TP
}
usingAILevels = true;
}
}
// 🎯 FALLBACK: Dynamic adaptive levels when AI levels unavailable
if (!usingAILevels) {
console.log('📊 Using dynamic adaptive levels (AI levels not available)...');
// Adaptive percentages based on position size and performance
const baseStopLossPercent = 2.0; // Base 2% stop loss
const baseTp1Percent = 3.5; // Base 3.5% first take profit
const baseTp2Percent = 6.0; // Base 6% second take profit
// Adjust based on position size (tighter for larger positions)
const positionValue = size * entryPrice;
const sizeMultiplier = Math.min(positionValue / 2000, 1.5); // Cap at 1.5x
stopLossPercent = baseStopLossPercent / sizeMultiplier;
takeProfit1Percent = baseTp1Percent * Math.min(sizeMultiplier, 1.2);
takeProfit2Percent = baseTp2Percent * Math.min(sizeMultiplier, 1.2);
if (side.toLowerCase() === 'long') {
stopLoss = entryPrice * (1 - stopLossPercent / 100);
takeProfit1 = entryPrice * (1 + takeProfit1Percent / 100);
takeProfit2 = entryPrice * (1 + takeProfit2Percent / 100);
} else {
stopLoss = entryPrice * (1 + stopLossPercent / 100);
takeProfit1 = entryPrice * (1 - takeProfit1Percent / 100);
takeProfit2 = entryPrice * (1 - takeProfit2Percent / 100);
}
}
// Position sizing: 70% at TP1, 30% at TP2
const tp1Size = Math.floor(size * 0.7 * 100) / 100; // 70% of position
const tp2Size = size - tp1Size; // Remaining 30%
const riskReward = takeProfit1Percent / stopLossPercent;
return {
stopLoss,
takeProfit1,
takeProfit2,
stopLossPercent,
tp1Percent: takeProfit1Percent,
tp2Percent: takeProfit2Percent,
tp1Size,
tp2Size,
riskReward,
totalOrders: 3 // Much cleaner than 24!
};
}
/**
* Execute consolidated order placement
*/
static async executeConsolidation(analysis = null) {
try {
console.log('\n🎯 EXECUTING CONSOLIDATED ORDERS');
console.log('='.repeat(40));
// Get current position first
const position = await this.getCurrentPosition();
if (!position) {
throw new Error('No active position found for consolidation');
}
// Calculate consolidation plan with AI analysis if provided
const consolidatedPlan = this.calculateConsolidatedLevels(position, analysis);
const results = [];
// 1. Cancel all existing orders first
console.log('1⃣ Canceling all existing orders...');
const cancelResult = await this.cancelAllOrders();
console.log(` ✅ Canceled ${cancelResult.totalCanceled} orders`);
// 2. Place consolidated stop loss
console.log('2⃣ Placing consolidated stop loss...');
const stopLossResult = await this.placeConsolidatedOrder({
type: 'STOP_LOSS',
symbol: position.symbol,
side: position.side === 'long' ? 'SHORT' : 'LONG',
size: position.size,
triggerPrice: consolidatedPlan.stopLoss,
price: consolidatedPlan.stopLoss
});
results.push(stopLossResult);
// 3. Place first take profit (70% of position)
console.log('3⃣ Placing primary take profit...');
const tp1Result = await this.placeConsolidatedOrder({
type: 'TAKE_PROFIT_1',
symbol: position.symbol,
side: position.side === 'long' ? 'SHORT' : 'LONG',
size: consolidatedPlan.tp1Size,
triggerPrice: consolidatedPlan.takeProfit1,
price: consolidatedPlan.takeProfit1
});
results.push(tp1Result);
// 4. Place second take profit (30% of position)
console.log('4⃣ Placing extended take profit...');
const tp2Result = await this.placeConsolidatedOrder({
type: 'TAKE_PROFIT_2',
symbol: position.symbol,
side: position.side === 'long' ? 'SHORT' : 'LONG',
size: consolidatedPlan.tp2Size,
triggerPrice: consolidatedPlan.takeProfit2,
price: consolidatedPlan.takeProfit2
});
results.push(tp2Result);
console.log('\n✅ CONSOLIDATION COMPLETE!');
console.log(`📊 Orders reduced from 24 → 3`);
console.log(`🎯 Clean risk management structure`);
console.log(`💰 Optimized profit taking strategy`);
return {
success: true,
message: 'Position successfully consolidated',
ordersBefore: 24,
ordersAfter: 3,
results: results
};
} catch (error) {
console.error('❌ Consolidation failed:', error.message);
return {
success: false,
error: error.message
};
}
}
/**
* Cancel all existing orders
*/
static async cancelAllOrders() {
try {
const response = await fetch('http://localhost:3000/api/drift/cancel-all-orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
return result;
} catch (error) {
console.error('Error canceling orders:', error);
return { success: false, totalCanceled: 0 };
}
}
/**
* Place a consolidated order via Drift API
*/
static async placeConsolidatedOrder(orderParams) {
try {
const response = await fetch('http://localhost:3000/api/drift/place-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
symbol: orderParams.symbol,
orderType: 'TRIGGER_LIMIT',
direction: orderParams.side,
size: orderParams.size.toString(),
price: orderParams.price.toString(),
triggerPrice: orderParams.triggerPrice.toString(),
reduceOnly: true
})
});
const result = await response.json();
if (result.success) {
console.log(`${orderParams.type}: ${orderParams.size} @ $${orderParams.triggerPrice.toFixed(4)}`);
} else {
console.log(`${orderParams.type} failed: ${result.error}`);
}
return result;
} catch (error) {
console.error(`Error placing ${orderParams.type}:`, error);
return { success: false, error: error.message };
}
}
/**
* Get current position from Drift API
*/
static async getCurrentPosition() {
try {
const response = await fetch('http://localhost:3000/api/drift/positions');
const result = await response.json();
if (result.success && result.positions.length > 0) {
const position = result.positions[0];
return {
symbol: position.symbol,
side: position.side,
size: position.size,
entryPrice: position.entryPrice,
currentPrice: position.markPrice,
pnl: position.unrealizedPnl
};
} else {
return null;
}
} catch (error) {
console.error('Error fetching position:', error);
return null;
}
}
}
module.exports = PositionConsolidator;