const { DriftClient, initialize, OrderType, PositionDirection, OrderTriggerCondition, PostOnlyParams } = require('@drift-labs/sdk'); const { Connection, Keypair } = require('@solana/web3.js'); const { Wallet } = require('@coral-xyz/anchor'); /** * Test minimum percentages for stop loss and take profit with real small positions * This script will test progressively smaller percentages to find the actual minimum * that Drift Protocol accepts for order placement. */ class DriftMinimumPercentageTester { constructor() { this.driftClient = null; this.connection = null; this.testResults = []; } async initialize() { console.log('๐Ÿš€ Initializing Drift Protocol connection...'); try { // Initialize connection with fallback RPC endpoints const rpcEndpoints = [ process.env.SOLANA_RPC_URL_SECONDARY || 'https://api.mainnet-beta.solana.com', process.env.SOLANA_RPC_URL_TERTIARY || 'https://solana-mainnet.g.alchemy.com/v2/demo', process.env.SOLANA_RPC_URL_BACKUP || 'https://rpc.ankr.com/solana' ]; let connection = null; for (const endpoint of rpcEndpoints) { try { console.log(`๐Ÿ”„ Trying RPC endpoint: ${endpoint}`); connection = new Connection(endpoint, 'confirmed'); // Test the connection await connection.getVersion(); console.log(`โœ… Connected to: ${endpoint}`); break; } catch (error) { console.log(`โŒ Failed to connect to ${endpoint}: ${error.message}`); } } if (!connection) { throw new Error('Could not connect to any RPC endpoint'); } this.connection = connection; // Create wallet from private key if (!process.env.SOLANA_PRIVATE_KEY) { throw new Error('SOLANA_PRIVATE_KEY environment variable not set'); } const privateKeyArray = JSON.parse(process.env.SOLANA_PRIVATE_KEY); const keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray)); const wallet = new Wallet(keypair); console.log(`๐Ÿ“ Wallet address: ${keypair.publicKey.toString()}`); // Initialize Drift SDK const sdkConfig = initialize({ env: 'mainnet-beta' }); this.driftClient = new DriftClient({ connection: this.connection, wallet, programID: sdkConfig.DRIFT_PROGRAM_ID, opts: { commitment: 'confirmed', skipPreflight: false, preflightCommitment: 'confirmed' } }); await this.driftClient.subscribe(); console.log('โœ… Drift client initialized and subscribed'); // Check account balance await this.checkAccountStatus(); } catch (error) { console.error('โŒ Initialization failed:', error.message); throw error; } } async checkAccountStatus() { try { const user = this.driftClient.getUser(); const userAccount = user.getUserAccount(); // Get collateral and free collateral const totalCollateral = user.getTotalCollateral(); const freeCollateral = user.getFreeCollateral(); console.log('\n๐Ÿ’ฐ Account Status:'); console.log(` Total Collateral: $${(totalCollateral / 1e6).toFixed(2)}`); console.log(` Free Collateral: $${(freeCollateral / 1e6).toFixed(2)}`); if (freeCollateral < 1e6) { // Less than $1 console.log('โš ๏ธ Warning: Low free collateral - tests will use very small positions'); } // Get current positions const positions = user.getPositions().filter(pos => !pos.baseAssetAmount.eq(0)); console.log(` Open Positions: ${positions.length}`); positions.forEach((pos, index) => { const market = this.driftClient.getPerpMarketAccount(pos.marketIndex); const marketName = market.name || `Market ${pos.marketIndex}`; const size = Number(pos.baseAssetAmount) / 1e9; console.log(` ${index + 1}. ${marketName}: ${size.toFixed(4)} SOL`); }); } catch (error) { console.error('โŒ Failed to check account status:', error.message); } } async getCurrentPrice(marketIndex = 0) { try { const perpMarketAccount = this.driftClient.getPerpMarketAccount(marketIndex); const currentPrice = Number(perpMarketAccount.amm.lastMarkPriceTwap) / 1e6; return currentPrice; } catch (error) { console.error('โŒ Failed to get current price:', error.message); return null; } } async testMinimumPercentage(stopLossPercent, takeProfitPercent, marketIndex = 0) { console.log(`\n๐Ÿงช Testing SL: ${stopLossPercent}% / TP: ${takeProfitPercent}%`); try { const currentPrice = await this.getCurrentPrice(marketIndex); if (!currentPrice) { throw new Error('Could not get current price'); } console.log(` ๐Ÿ“Š Current SOL price: $${currentPrice.toFixed(4)}`); // Calculate prices const stopLossPrice = currentPrice * (1 - stopLossPercent / 100); const takeProfitPrice = currentPrice * (1 + takeProfitPercent / 100); console.log(` ๐ŸŽฏ Entry: $${currentPrice.toFixed(4)}`); console.log(` ๐Ÿ›‘ Stop Loss: $${stopLossPrice.toFixed(4)} (${stopLossPercent}% below)`); console.log(` ๐Ÿ’ฐ Take Profit: $${takeProfitPrice.toFixed(4)} (${takeProfitPercent}% above)`); // Use very small position size for testing const baseAssetAmount = Math.floor(0.001 * 1e9); // 0.001 SOL (~$0.20) const result = { stopLossPercent, takeProfitPercent, currentPrice, stopLossPrice, takeProfitPrice, baseAssetAmount, success: false, error: null, orderIds: [] }; try { // Step 1: Place a small long position console.log(' ๐Ÿ“ Step 1: Placing small long position...'); const longOrderParams = { orderType: OrderType.MARKET, marketIndex, direction: PositionDirection.LONG, baseAssetAmount, reduceOnly: false, }; const longOrderTx = await this.driftClient.placePerpOrder(longOrderParams); console.log(` โœ… Long position placed. TX: ${longOrderTx}`); result.orderIds.push(longOrderTx); // Wait a moment for order to settle await new Promise(resolve => setTimeout(resolve, 2000)); // Step 2: Place stop loss order console.log(' ๐Ÿ“ Step 2: Placing stop loss order...'); const stopLossParams = { orderType: OrderType.TRIGGER_LIMIT, marketIndex, direction: PositionDirection.SHORT, baseAssetAmount, price: Math.floor(stopLossPrice * 1e6), triggerPrice: Math.floor(stopLossPrice * 1e6), triggerCondition: OrderTriggerCondition.BELOW, reduceOnly: true, }; const stopLossTx = await this.driftClient.placePerpOrder(stopLossParams); console.log(` โœ… Stop loss placed. TX: ${stopLossTx}`); result.orderIds.push(stopLossTx); // Step 3: Place take profit order console.log(' ๐Ÿ“ Step 3: Placing take profit order...'); const takeProfitParams = { orderType: OrderType.TRIGGER_LIMIT, marketIndex, direction: PositionDirection.SHORT, baseAssetAmount, price: Math.floor(takeProfitPrice * 1e6), triggerPrice: Math.floor(takeProfitPrice * 1e6), triggerCondition: OrderTriggerCondition.ABOVE, reduceOnly: true, }; const takeProfitTx = await this.driftClient.placePerpOrder(takeProfitParams); console.log(` โœ… Take profit placed. TX: ${takeProfitTx}`); result.orderIds.push(takeProfitTx); result.success = true; console.log(` โœ… SUCCESS: ${stopLossPercent}%/${takeProfitPercent}% percentages work!`); // Cancel orders immediately to clean up await this.cleanupTestOrders(result.orderIds); } catch (orderError) { result.error = orderError.message; console.log(` โŒ FAILED: ${orderError.message}`); // Try to clean up any partial orders if (result.orderIds.length > 0) { await this.cleanupTestOrders(result.orderIds); } } this.testResults.push(result); return result; } catch (error) { console.error(` โŒ Test setup failed: ${error.message}`); return { stopLossPercent, takeProfitPercent, success: false, error: error.message, orderIds: [] }; } } async cleanupTestOrders(orderIds) { console.log(' ๐Ÿงน Cleaning up test orders...'); for (const orderId of orderIds) { try { // Cancel all open orders for the user const openOrders = this.driftClient.getUser().getOpenOrders(); for (const order of openOrders) { if (order.marketIndex === 0) { // SOL-PERP market await this.driftClient.cancelOrder(order.orderId); console.log(` ๐Ÿ—‘๏ธ Cancelled order ${order.orderId}`); } } await new Promise(resolve => setTimeout(resolve, 1000)); } catch (error) { console.log(` โš ๏ธ Could not cancel order: ${error.message}`); } } } async runComprehensiveTest() { console.log('๐ŸŽฏ Running Comprehensive Minimum Percentage Test'); console.log(' Goal: Find the actual minimum SL/TP percentages for Drift Protocol'); console.log(' Method: Test with real small positions, progressively reducing percentages\n'); // Test cases - start with current minimums and work down const testCases = [ // Current minimums (should work) { sl: 3.0, tp: 1.0, desc: 'Current minimums' }, // Moderate reductions { sl: 2.0, tp: 0.8, desc: 'Moderate reduction' }, { sl: 1.5, tp: 0.6, desc: 'Moderate-aggressive' }, // Aggressive reductions for scalping { sl: 1.0, tp: 0.5, desc: 'Aggressive scalping' }, { sl: 0.8, tp: 0.4, desc: 'Very aggressive' }, { sl: 0.5, tp: 0.3, desc: 'Ultra-tight scalping' }, // Extreme minimums (likely to fail) { sl: 0.3, tp: 0.2, desc: 'Extreme tight' }, { sl: 0.1, tp: 0.1, desc: 'Minimal possible' } ]; for (let i = 0; i < testCases.length; i++) { const testCase = testCases[i]; console.log(`\n๐Ÿ“‹ Test ${i + 1}/${testCases.length}: ${testCase.desc}`); const result = await this.testMinimumPercentage(testCase.sl, testCase.tp); if (!result.success) { console.log(`โš ๏ธ Stopping tests - found minimum threshold at previous test`); break; } // Wait between tests to avoid rate limiting if (i < testCases.length - 1) { console.log(' โณ Waiting 5 seconds before next test...'); await new Promise(resolve => setTimeout(resolve, 5000)); } } this.generateReport(); } generateReport() { console.log('\n๐Ÿ“Š TEST RESULTS SUMMARY'); console.log('=' .repeat(50)); const successfulTests = this.testResults.filter(r => r.success); const failedTests = this.testResults.filter(r => !r.success); console.log(`โœ… Successful tests: ${successfulTests.length}`); console.log(`โŒ Failed tests: ${failedTests.length}`); if (successfulTests.length > 0) { const tightestSuccessful = successfulTests[successfulTests.length - 1]; console.log('\n๐ŸŽฏ RECOMMENDED NEW MINIMUMS:'); console.log(` Stop Loss: ${tightestSuccessful.stopLossPercent}%`); console.log(` Take Profit: ${tightestSuccessful.takeProfitPercent}%`); console.log('\n๐Ÿ“ Code Update Needed:'); console.log(' File: app/api/drift/trade/route.js'); console.log(' Lines 273-274:'); console.log(` const stopLossPercentCalc = Math.max(stopLossPercent / 100, ${(tightestSuccessful.stopLossPercent / 100).toFixed(3)}) // ${tightestSuccessful.stopLossPercent}% minimum`); console.log(` const takeProfitPercentCalc = Math.max(takeProfitPercent / 100, ${(tightestSuccessful.takeProfitPercent / 100).toFixed(3)}) // ${tightestSuccessful.takeProfitPercent}% minimum`); } if (failedTests.length > 0) { console.log('\nโŒ Failed Percentages:'); failedTests.forEach(test => { console.log(` ${test.stopLossPercent}%/${test.takeProfitPercent}%: ${test.error}`); }); } console.log('\n๐Ÿ’ก Next Steps:'); console.log('1. Update the minimum percentages in the trading API'); console.log('2. Test with real trading scenarios'); console.log('3. Monitor order execution success rates'); console.log('4. Consider implementing dynamic minimums based on volatility'); } async cleanup() { if (this.driftClient) { try { await this.driftClient.unsubscribe(); console.log('โœ… Drift client unsubscribed'); } catch (error) { console.error('โš ๏ธ Error during cleanup:', error.message); } } } } // Main execution async function main() { const tester = new DriftMinimumPercentageTester(); try { await tester.initialize(); await tester.runComprehensiveTest(); } catch (error) { console.error('โŒ Test execution failed:', error.message); } finally { await tester.cleanup(); } } // Run if called directly if (require.main === module) { main().catch(console.error); } module.exports = { DriftMinimumPercentageTester };