REMOVED ARTIFICIAL CONSTRAINTS: - Eliminated 3% minimum stop loss requirement - Eliminated 1% minimum take profit requirement - AI can now choose ANY percentage based on market analysis - Updated app/api/drift/trade/route.js to use exact AI percentages - Removed Math.max() constraints that forced minimums - AI now has 0.1%+ to 50%+ percentage freedom - Modified AI_RISK_MANAGEMENT.md to reflect new freedom - Removed all references to artificial 3%/1% minimums - Added ultra-tight scalping examples (0.1%-1%) - Updated volatility guidelines for all trading styles PROVEN WITH REAL ORDERS: - Transaction: 35QmCqWFzwJ1X2nm5M8rgExKEMbWTRqxCa1GryEsR595zYwBLqCzDowUYm3J2u13WMvYR2PRoS3eAMSzXfGvEVbe - Confirmed: 0.5% SL / 0.25% TP working on Drift Protocol - Verified: Orders visible in Drift UI with correct trigger prices - Optimal risk management based on actual market conditions - Support for all trading styles: scalping to position trading - No more forced suboptimal stops due to artificial limits - Professional-grade percentage precision The AI can now freely optimize percentages for maximum trading effectiveness!
387 lines
13 KiB
JavaScript
387 lines
13 KiB
JavaScript
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 };
|