feat: Complete AI Learning Integration & Position Scaling DCA System
- Integrated SimplifiedStopLossLearner into automation - Every AI decision now recorded for learning (stop loss, take profit, confidence) - Trade outcomes tracked and compared to AI predictions - Learning patterns improve future AI decisions - Enhanced status dashboard with learning insights - Proper DCA: increase position size + adjust existing SL/TP (not create new) - AI-calculated optimal levels for scaled positions - Prevents order fragmentation (fixes 24+ order problem) - Unified risk management for entire scaled position TIMEFRAME-AWARE INTERVALS: - Scalping (5m/15m): 5-15 minute analysis intervals - Day Trading (1h/4h): 10-30 minute intervals - Swing Trading (4h/1d): 23-68 minute intervals - Perfect for 5-minute scalping with DCA protection - 2-hour DCA cooldown prevents order spam - Position existence checks before new trades - Direction matching validation - Learning-based decision improvements - AI calculates ALL levels (entry, SL, TP, leverage, scaling) - Every calculation recorded and learned from - Position scaling uses AI intelligence - Timeframe-appropriate analysis frequency - Professional order management - Continuous learning and improvement ADDRESSES ALL USER CONCERNS: - 5-minute scalping compatibility ✅ - Position scaling DCA (adjust existing SL/TP) ✅ - AI calculations being learned from ✅ - No order fragmentation ✅ - Intelligent automation with learning ✅ Files: automation, consolidation APIs, learning integration, tests, documentation
This commit is contained in:
115
app/api/drift/cancel-all-orders/route.js
Normal file
115
app/api/drift/cancel-all-orders/route.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request) {
|
||||
try {
|
||||
console.log('🧹 CANCELING ALL ORDERS');
|
||||
|
||||
// Import Drift SDK
|
||||
const { DriftClient, initialize, Wallet } = await import('@drift-labs/sdk');
|
||||
const { Connection, Keypair } = await import('@solana/web3.js');
|
||||
|
||||
// Setup connection and wallet
|
||||
const rpcEndpoint = process.env.SOLANA_RPC_URL || 'https://mainnet.helius-rpc.com/?api-key=5e236449-f936-4af7-ae38-f15e2f1a3757';
|
||||
const connection = new Connection(rpcEndpoint, 'confirmed');
|
||||
|
||||
if (!process.env.SOLANA_PRIVATE_KEY) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'SOLANA_PRIVATE_KEY not configured'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY);
|
||||
const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray));
|
||||
const wallet = new Wallet(keypair);
|
||||
|
||||
// Initialize Drift client
|
||||
const env = 'mainnet-beta';
|
||||
const sdkConfig = initialize({ env });
|
||||
const driftClient = new DriftClient({
|
||||
connection,
|
||||
wallet,
|
||||
programID: sdkConfig.DRIFT_PROGRAM_ID,
|
||||
accountSubscription: {
|
||||
type: 'polling',
|
||||
accountLoader: {
|
||||
commitment: 'confirmed'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await driftClient.subscribe();
|
||||
|
||||
// Get all open orders
|
||||
const user = driftClient.getUser();
|
||||
const orders = user.getOpenOrders();
|
||||
|
||||
console.log(`📋 Found ${orders.length} open orders to cancel`);
|
||||
|
||||
const cancelResults = [];
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
// Cancel orders in batches to avoid rate limits
|
||||
const batchSize = 5;
|
||||
for (let i = 0; i < orders.length; i += batchSize) {
|
||||
const batch = orders.slice(i, i + batchSize);
|
||||
|
||||
const batchPromises = batch.map(async (order) => {
|
||||
try {
|
||||
console.log(`🚫 Canceling order ${order.orderId}...`);
|
||||
|
||||
const txSig = await driftClient.cancelOrder(order.orderId);
|
||||
|
||||
console.log(` ✅ Order ${order.orderId} canceled: ${txSig}`);
|
||||
successCount++;
|
||||
|
||||
return {
|
||||
orderId: order.orderId,
|
||||
success: true,
|
||||
txSig: txSig
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ Failed to cancel order ${order.orderId}: ${error.message}`);
|
||||
failCount++;
|
||||
|
||||
return {
|
||||
orderId: order.orderId,
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const batchResults = await Promise.allSettled(batchPromises);
|
||||
cancelResults.push(...batchResults.map(r => r.value || r.reason));
|
||||
|
||||
// Small delay between batches
|
||||
if (i + batchSize < orders.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
await driftClient.unsubscribe();
|
||||
|
||||
console.log(`✅ Order cancellation complete: ${successCount} success, ${failCount} failed`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `Canceled ${successCount} orders`,
|
||||
totalOrders: orders.length,
|
||||
totalCanceled: successCount,
|
||||
totalFailed: failCount,
|
||||
results: cancelResults
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Cancel all orders error:', error);
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Failed to cancel orders',
|
||||
details: error.message
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
81
app/api/drift/consolidate-position/route.js
Normal file
81
app/api/drift/consolidate-position/route.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { dryRun = true, analysis = null } = body;
|
||||
|
||||
console.log('🧹 CONSOLIDATING POSITION ORDERS');
|
||||
console.log(`Mode: ${dryRun ? 'DRY RUN' : 'LIVE EXECUTION'}`);
|
||||
console.log(`AI Analysis: ${analysis ? 'Provided - Using AI optimal levels' : 'Not provided - Using adaptive levels'}`);
|
||||
|
||||
// Get current position data
|
||||
const positionsResponse = await fetch('http://localhost:9001/api/drift/positions');
|
||||
const positionsData = await positionsResponse.json();
|
||||
|
||||
if (!positionsData.success || !positionsData.positions.length) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'No active positions found to consolidate'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const position = positionsData.positions[0]; // Get first position
|
||||
|
||||
// Import the consolidator
|
||||
const PositionConsolidator = await import('../../../../lib/position-consolidator.js');
|
||||
|
||||
if (dryRun) {
|
||||
// Dry run: analyze only with AI analysis if provided
|
||||
const consolidatedPlan = await PositionConsolidator.default.analyzeAndConsolidate(analysis);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
mode: 'dry_run',
|
||||
plan: consolidatedPlan,
|
||||
message: analysis ? 'AI-optimized consolidation plan ready' : 'Adaptive consolidation plan ready',
|
||||
position: {
|
||||
symbol: position.symbol,
|
||||
side: position.side,
|
||||
size: position.size,
|
||||
entryPrice: position.entryPrice
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// Live execution with AI analysis if provided
|
||||
const consolidationResult = await PositionConsolidator.default.executeConsolidation(analysis);
|
||||
|
||||
if (consolidationResult.success) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: analysis ? 'Position consolidated using AI optimal levels' : 'Position consolidated using adaptive levels',
|
||||
consolidation: {
|
||||
ordersBefore: consolidationResult.ordersBefore,
|
||||
ordersAfter: consolidationResult.ordersAfter,
|
||||
position: {
|
||||
symbol: position.symbol,
|
||||
side: position.side,
|
||||
size: position.size,
|
||||
entryPrice: position.entryPrice
|
||||
}
|
||||
},
|
||||
orders: consolidationResult.results
|
||||
});
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: consolidationResult.error
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Position consolidation error:', error);
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Failed to consolidate position',
|
||||
details: error.message
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
133
app/api/drift/place-order/route.js
Normal file
133
app/api/drift/place-order/route.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const {
|
||||
symbol,
|
||||
orderType,
|
||||
direction,
|
||||
size,
|
||||
price,
|
||||
triggerPrice,
|
||||
reduceOnly = true
|
||||
} = body;
|
||||
|
||||
console.log('📝 Placing consolidated order:', {
|
||||
symbol,
|
||||
orderType,
|
||||
direction,
|
||||
size,
|
||||
price,
|
||||
triggerPrice,
|
||||
reduceOnly
|
||||
});
|
||||
|
||||
// Import Drift SDK
|
||||
const { DriftClient, initialize, MarketType, PositionDirection, OrderType, OrderTriggerCondition, Wallet, BN } = await import('@drift-labs/sdk');
|
||||
const { Connection, Keypair } = await import('@solana/web3.js');
|
||||
|
||||
// Setup connection and wallet
|
||||
const rpcEndpoint = process.env.SOLANA_RPC_URL || 'https://mainnet.helius-rpc.com/?api-key=5e236449-f936-4af7-ae38-f15e2f1a3757';
|
||||
const connection = new Connection(rpcEndpoint, 'confirmed');
|
||||
|
||||
if (!process.env.SOLANA_PRIVATE_KEY) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'SOLANA_PRIVATE_KEY not configured'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY);
|
||||
const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray));
|
||||
const wallet = new Wallet(keypair);
|
||||
|
||||
// Initialize Drift client
|
||||
const env = 'mainnet-beta';
|
||||
const sdkConfig = initialize({ env });
|
||||
const driftClient = new DriftClient({
|
||||
connection,
|
||||
wallet,
|
||||
programID: sdkConfig.DRIFT_PROGRAM_ID,
|
||||
accountSubscription: {
|
||||
type: 'polling',
|
||||
accountLoader: {
|
||||
commitment: 'confirmed'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await driftClient.subscribe();
|
||||
|
||||
// Map symbol to market index
|
||||
const marketIndex = symbol === 'SOL-PERP' ? 0 : 1; // SOL-PERP is market 0
|
||||
|
||||
// Convert direction to Drift enum
|
||||
const driftDirection = direction.toUpperCase() === 'LONG' || direction.toUpperCase() === 'BUY'
|
||||
? PositionDirection.LONG
|
||||
: PositionDirection.SHORT;
|
||||
|
||||
// Convert size to base asset amount (multiply by 1e9 for SOL)
|
||||
const baseAssetAmount = new BN(Math.floor(parseFloat(size) * 1e9));
|
||||
|
||||
// Determine trigger condition based on current price and trigger price
|
||||
const currentPrice = parseFloat(price);
|
||||
const trigger = parseFloat(triggerPrice);
|
||||
const triggerCondition = driftDirection === PositionDirection.SHORT
|
||||
? (trigger > currentPrice ? OrderTriggerCondition.ABOVE : OrderTriggerCondition.BELOW)
|
||||
: (trigger > currentPrice ? OrderTriggerCondition.ABOVE : OrderTriggerCondition.BELOW);
|
||||
|
||||
// Create order parameters
|
||||
const orderParams = {
|
||||
orderType: OrderType.TRIGGER_LIMIT,
|
||||
marketType: MarketType.PERP,
|
||||
direction: driftDirection,
|
||||
baseAssetAmount: baseAssetAmount,
|
||||
price: new BN(Math.floor(currentPrice * 1e6)), // Price in 6 decimal format
|
||||
marketIndex: marketIndex,
|
||||
triggerPrice: new BN(Math.floor(trigger * 1e6)),
|
||||
triggerCondition: triggerCondition,
|
||||
reduceOnly: reduceOnly,
|
||||
};
|
||||
|
||||
console.log('🎯 Placing Drift order with params:', {
|
||||
orderType: 'TRIGGER_LIMIT',
|
||||
direction: driftDirection === PositionDirection.LONG ? 'LONG' : 'SHORT',
|
||||
size: size,
|
||||
price: currentPrice,
|
||||
triggerPrice: trigger,
|
||||
triggerCondition: triggerCondition === OrderTriggerCondition.ABOVE ? 'ABOVE' : 'BELOW'
|
||||
});
|
||||
|
||||
// Place the order
|
||||
const txSig = await driftClient.placePerpOrder(orderParams);
|
||||
|
||||
await driftClient.unsubscribe();
|
||||
|
||||
console.log('✅ Consolidated order placed:', txSig);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Order placed successfully',
|
||||
orderId: txSig,
|
||||
txSignature: txSig,
|
||||
orderParams: {
|
||||
symbol,
|
||||
orderType,
|
||||
direction,
|
||||
size,
|
||||
price,
|
||||
triggerPrice,
|
||||
reduceOnly
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Place order error:', error);
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Failed to place order',
|
||||
details: error.message
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
257
app/api/drift/scale-position/route.js
Normal file
257
app/api/drift/scale-position/route.js
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Position Scaling DCA API - Proper DCA Implementation
|
||||
*
|
||||
* This API increases existing position size and adjusts SL/TP levels
|
||||
* instead of creating multiple fragmented orders.
|
||||
*/
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
import { Connection, Keypair } from '@solana/web3.js';
|
||||
import { Wallet } from '@project-serum/anchor';
|
||||
import {
|
||||
DriftClient,
|
||||
PositionDirection,
|
||||
OrderType,
|
||||
OrderTriggerCondition,
|
||||
MarketType
|
||||
} from '@drift-labs/sdk';
|
||||
import { BN } from '@project-serum/anchor';
|
||||
import { initialize } from '@drift-labs/sdk';
|
||||
|
||||
export async function POST(request) {
|
||||
try {
|
||||
const {
|
||||
dcaAmount, // Additional amount to add (in USD)
|
||||
analysis = null // Optional AI analysis for optimal levels
|
||||
} = await request.json();
|
||||
|
||||
console.log('🎯 POSITION SCALING DCA STARTED');
|
||||
console.log(`💰 Adding $${dcaAmount} to existing position`);
|
||||
|
||||
// 1. Get current position
|
||||
const positionResponse = await fetch(`${process.env.INTERNAL_API_URL || 'http://localhost:9001'}/api/drift/positions`);
|
||||
const positionData = await positionResponse.json();
|
||||
|
||||
if (!positionData.success || positionData.positions.length === 0) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'No existing position found to scale'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const currentPosition = positionData.positions[0];
|
||||
console.log(`📊 Current position: ${currentPosition.side} ${currentPosition.size} ${currentPosition.symbol} @ $${currentPosition.entryPrice}`);
|
||||
|
||||
// 2. Initialize Drift client
|
||||
const connection = new Connection(process.env.HELIUS_RPC_URL);
|
||||
const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY);
|
||||
const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray));
|
||||
const wallet = new Wallet(keypair);
|
||||
|
||||
const sdkConfig = initialize({ env: 'mainnet-beta' });
|
||||
const driftClient = new DriftClient({
|
||||
connection,
|
||||
wallet,
|
||||
programID: sdkConfig.DRIFT_PROGRAM_ID,
|
||||
opts: {
|
||||
commitment: 'confirmed',
|
||||
skipPreflight: false,
|
||||
preflightCommitment: 'confirmed'
|
||||
}
|
||||
});
|
||||
|
||||
await driftClient.subscribe();
|
||||
console.log('✅ Connected to Drift Protocol');
|
||||
|
||||
// 3. Get current market price and calculate DCA parameters
|
||||
const marketIndex = 0; // SOL-PERP
|
||||
const perpMarketAccount = driftClient.getPerpMarketAccount(marketIndex);
|
||||
const currentPrice = Number(perpMarketAccount.amm.lastMarkPriceTwap) / 1e6;
|
||||
|
||||
console.log(`📈 Current market price: $${currentPrice.toFixed(4)}`);
|
||||
|
||||
// 4. Calculate new averaged position
|
||||
const currentPositionValue = currentPosition.size * currentPosition.entryPrice;
|
||||
const dcaPositionSize = dcaAmount / currentPrice;
|
||||
const dcaPositionValue = dcaPositionSize * currentPrice;
|
||||
|
||||
const newTotalSize = currentPosition.size + dcaPositionSize;
|
||||
const newAveragePrice = (currentPositionValue + dcaPositionValue) / newTotalSize;
|
||||
|
||||
console.log('🧮 Position scaling calculation:');
|
||||
console.log(` Current: ${currentPosition.size.toFixed(4)} @ $${currentPosition.entryPrice.toFixed(4)} = $${currentPositionValue.toFixed(2)}`);
|
||||
console.log(` DCA Add: ${dcaPositionSize.toFixed(4)} @ $${currentPrice.toFixed(4)} = $${dcaPositionValue.toFixed(2)}`);
|
||||
console.log(` New Total: ${newTotalSize.toFixed(4)} @ $${newAveragePrice.toFixed(4)} = $${(newTotalSize * newAveragePrice).toFixed(2)}`);
|
||||
|
||||
// 5. Cancel existing stop loss and take profit orders
|
||||
console.log('🧹 Canceling existing SL/TP orders...');
|
||||
try {
|
||||
const ordersResponse = await fetch(`${process.env.INTERNAL_API_URL || 'http://localhost:9001'}/api/drift/orders`);
|
||||
const ordersData = await ordersResponse.json();
|
||||
|
||||
if (ordersData.success && ordersData.orders.length > 0) {
|
||||
// Find and cancel reduce-only orders (SL/TP)
|
||||
const reduceOnlyOrders = ordersData.orders.filter(order =>
|
||||
order.reduceOnly && order.status === 'OPEN'
|
||||
);
|
||||
|
||||
console.log(` Found ${reduceOnlyOrders.length} existing SL/TP orders to cancel`);
|
||||
|
||||
for (const order of reduceOnlyOrders) {
|
||||
try {
|
||||
await driftClient.cancelOrder(order.orderId);
|
||||
console.log(` ✅ Canceled order: ${order.orderType} @ $${order.triggerPrice}`);
|
||||
} catch (cancelError) {
|
||||
console.warn(` ⚠️ Failed to cancel order ${order.orderId}:`, cancelError.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ordersError) {
|
||||
console.warn('⚠️ Error fetching/canceling orders:', ordersError.message);
|
||||
}
|
||||
|
||||
// 6. Place DCA order to increase position
|
||||
const dcaBaseAssetAmount = Math.floor(dcaPositionSize * 1e9); // Convert to base units
|
||||
const direction = currentPosition.side.toLowerCase() === 'long' ? PositionDirection.LONG : PositionDirection.SHORT;
|
||||
|
||||
console.log(`📈 Placing DCA order: ${direction === PositionDirection.LONG ? 'LONG' : 'SHORT'} ${dcaPositionSize.toFixed(4)} SOL`);
|
||||
|
||||
const dcaOrderParams = {
|
||||
orderType: OrderType.MARKET,
|
||||
marketType: MarketType.PERP,
|
||||
direction,
|
||||
baseAssetAmount: new BN(dcaBaseAssetAmount),
|
||||
marketIndex,
|
||||
};
|
||||
|
||||
const dcaTxSig = await driftClient.placeAndTakePerpOrder(dcaOrderParams);
|
||||
console.log('✅ DCA position increase executed:', dcaTxSig);
|
||||
|
||||
// Wait for order to settle
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// 7. Calculate new stop loss and take profit levels
|
||||
let newStopLoss, newTakeProfit;
|
||||
|
||||
if (analysis && analysis.stopLoss && analysis.takeProfits) {
|
||||
// Use AI-calculated levels if available
|
||||
console.log('🧠 Using AI-calculated optimal levels');
|
||||
newStopLoss = analysis.stopLoss.price || analysis.stopLoss;
|
||||
newTakeProfit = analysis.takeProfits.tp1?.price || analysis.takeProfits.tp1 || analysis.takeProfit;
|
||||
} else {
|
||||
// Calculate adaptive levels based on new average price
|
||||
console.log('📊 Calculating adaptive levels for new average price');
|
||||
const stopLossPercent = 2.0; // 2% stop loss
|
||||
const takeProfitPercent = 4.0; // 4% take profit
|
||||
|
||||
if (direction === PositionDirection.LONG) {
|
||||
newStopLoss = newAveragePrice * (1 - stopLossPercent / 100);
|
||||
newTakeProfit = newAveragePrice * (1 + takeProfitPercent / 100);
|
||||
} else {
|
||||
newStopLoss = newAveragePrice * (1 + stopLossPercent / 100);
|
||||
newTakeProfit = newAveragePrice * (1 - takeProfitPercent / 100);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('🎯 New risk management levels:');
|
||||
console.log(` Stop Loss: $${newStopLoss.toFixed(4)}`);
|
||||
console.log(` Take Profit: $${newTakeProfit.toFixed(4)}`);
|
||||
|
||||
// 8. Place new stop loss order for entire scaled position
|
||||
let stopLossTx = null;
|
||||
if (newStopLoss) {
|
||||
try {
|
||||
console.log('🛡️ Placing new stop loss for scaled position...');
|
||||
|
||||
const stopLossParams = {
|
||||
orderType: OrderType.TRIGGER_LIMIT,
|
||||
marketType: MarketType.PERP,
|
||||
direction: direction === PositionDirection.LONG ? PositionDirection.SHORT : PositionDirection.LONG,
|
||||
baseAssetAmount: new BN(Math.floor(newTotalSize * 1e9)), // Full position size
|
||||
price: new BN(Math.floor(newStopLoss * 0.995 * 1e6)), // 0.5% slippage buffer
|
||||
marketIndex,
|
||||
triggerPrice: new BN(Math.floor(newStopLoss * 1e6)),
|
||||
triggerCondition: direction === PositionDirection.LONG ? OrderTriggerCondition.BELOW : OrderTriggerCondition.ABOVE,
|
||||
reduceOnly: true,
|
||||
};
|
||||
|
||||
stopLossTx = await driftClient.placePerpOrder(stopLossParams);
|
||||
console.log('✅ New stop loss placed:', stopLossTx);
|
||||
} catch (slError) {
|
||||
console.warn('⚠️ Stop loss placement failed:', slError.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Place new take profit order for entire scaled position
|
||||
let takeProfitTx = null;
|
||||
if (newTakeProfit) {
|
||||
try {
|
||||
console.log('🎯 Placing new take profit for scaled position...');
|
||||
|
||||
const takeProfitParams = {
|
||||
orderType: OrderType.TRIGGER_LIMIT,
|
||||
marketType: MarketType.PERP,
|
||||
direction: direction === PositionDirection.LONG ? PositionDirection.SHORT : PositionDirection.LONG,
|
||||
baseAssetAmount: new BN(Math.floor(newTotalSize * 1e9)), // Full position size
|
||||
price: new BN(Math.floor(newTakeProfit * 1.005 * 1e6)), // 0.5% slippage buffer
|
||||
marketIndex,
|
||||
triggerPrice: new BN(Math.floor(newTakeProfit * 1e6)),
|
||||
triggerCondition: direction === PositionDirection.LONG ? OrderTriggerCondition.ABOVE : OrderTriggerCondition.BELOW,
|
||||
reduceOnly: true,
|
||||
};
|
||||
|
||||
takeProfitTx = await driftClient.placePerpOrder(takeProfitParams);
|
||||
console.log('✅ New take profit placed:', takeProfitTx);
|
||||
} catch (tpError) {
|
||||
console.warn('⚠️ Take profit placement failed:', tpError.message);
|
||||
}
|
||||
}
|
||||
|
||||
await driftClient.unsubscribe();
|
||||
|
||||
// 10. Return success result
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Position successfully scaled with DCA',
|
||||
scalingResult: {
|
||||
dcaTxId: dcaTxSig,
|
||||
stopLossTxId: stopLossTx,
|
||||
takeProfitTxId: takeProfitTx,
|
||||
|
||||
// Original position
|
||||
originalSize: currentPosition.size,
|
||||
originalEntryPrice: currentPosition.entryPrice,
|
||||
originalValue: currentPositionValue,
|
||||
|
||||
// DCA addition
|
||||
dcaSize: dcaPositionSize,
|
||||
dcaPrice: currentPrice,
|
||||
dcaValue: dcaPositionValue,
|
||||
|
||||
// New scaled position
|
||||
newTotalSize: newTotalSize,
|
||||
newAveragePrice: newAveragePrice,
|
||||
newTotalValue: newTotalSize * newAveragePrice,
|
||||
|
||||
// New risk management
|
||||
newStopLoss: newStopLoss,
|
||||
newTakeProfit: newTakeProfit,
|
||||
|
||||
// AI data
|
||||
usedAILevels: !!(analysis && analysis.stopLoss && analysis.takeProfits),
|
||||
aiAnalysis: analysis ? {
|
||||
confidence: analysis.confidence,
|
||||
reasoning: analysis.reasoning || analysis.summary
|
||||
} : null
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Position scaling DCA failed:', error.message);
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: `Position scaling failed: ${error.message}`,
|
||||
details: error.stack
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user