- 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.
325 lines
12 KiB
JavaScript
325 lines
12 KiB
JavaScript
/**
|
||
* 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;
|