feat: Complete Trading Bot v4 with Drift Protocol integration
Features: - Autonomous trading system with Drift Protocol on Solana - Real-time position monitoring with Pyth price feeds - Dynamic stop-loss and take-profit management - n8n workflow integration for TradingView signals - Beautiful web UI for settings management - REST API for trade execution and monitoring - Next.js 15 with standalone output mode - TypeScript with strict typing - Docker containerization with multi-stage builds - PostgreSQL database for trade history - Singleton pattern for Drift client connection pooling - BN.js for BigNumber handling (Drift SDK requirement) - Configurable stop-loss and take-profit levels - Breakeven trigger and profit locking - Daily loss limits and trade cooldowns - Slippage tolerance controls - DRY_RUN mode for safe testing - Real-time risk calculator - Interactive sliders for all parameters - Live preview of trade outcomes - Position sizing and leverage controls - Beautiful gradient design with Tailwind CSS - POST /api/trading/execute - Execute trades - POST /api/trading/close - Close positions - GET /api/trading/positions - Monitor active trades - GET /api/trading/check-risk - Validate trade signals - GET /api/settings - View configuration - POST /api/settings - Update configuration - Fixed Borsh serialization errors (simplified order params) - Resolved RPC rate limiting with singleton pattern - Fixed BigInt vs BN type mismatches - Corrected order execution flow - Improved position state management - Complete setup guides - Docker deployment instructions - n8n workflow configuration - API reference documentation - Risk management guidelines - Runs on port 3001 (external), 3000 (internal) - Uses Helius RPC for optimal performance - Production-ready with error handling - Health monitoring and logging
This commit is contained in:
129
app/api/settings/route.ts
Normal file
129
app/api/settings/route.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Settings API Endpoint
|
||||
*
|
||||
* Read and update trading bot configuration
|
||||
*/
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const ENV_FILE_PATH = path.join(process.cwd(), '.env')
|
||||
|
||||
function parseEnvFile(): Record<string, string> {
|
||||
try {
|
||||
const content = fs.readFileSync(ENV_FILE_PATH, 'utf-8')
|
||||
const env: Record<string, string> = {}
|
||||
|
||||
content.split('\n').forEach(line => {
|
||||
// Skip comments and empty lines
|
||||
if (line.trim().startsWith('#') || !line.trim()) return
|
||||
|
||||
const match = line.match(/^([A-Z_]+)=(.*)$/)
|
||||
if (match) {
|
||||
env[match[1]] = match[2]
|
||||
}
|
||||
})
|
||||
|
||||
return env
|
||||
} catch (error) {
|
||||
console.error('Failed to read .env file:', error)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function updateEnvFile(updates: Record<string, any>) {
|
||||
try {
|
||||
let content = fs.readFileSync(ENV_FILE_PATH, 'utf-8')
|
||||
|
||||
// Update each setting
|
||||
Object.entries(updates).forEach(([key, value]) => {
|
||||
const regex = new RegExp(`^${key}=.*$`, 'm')
|
||||
const newLine = `${key}=${value}`
|
||||
|
||||
if (regex.test(content)) {
|
||||
content = content.replace(regex, newLine)
|
||||
} else {
|
||||
// Add new line if key doesn't exist
|
||||
content += `\n${newLine}`
|
||||
}
|
||||
})
|
||||
|
||||
fs.writeFileSync(ENV_FILE_PATH, content, 'utf-8')
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Failed to write .env file:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const env = parseEnvFile()
|
||||
|
||||
const settings = {
|
||||
MAX_POSITION_SIZE_USD: parseFloat(env.MAX_POSITION_SIZE_USD || '50'),
|
||||
LEVERAGE: parseFloat(env.LEVERAGE || '5'),
|
||||
STOP_LOSS_PERCENT: parseFloat(env.STOP_LOSS_PERCENT || '-1.5'),
|
||||
TAKE_PROFIT_1_PERCENT: parseFloat(env.TAKE_PROFIT_1_PERCENT || '0.7'),
|
||||
TAKE_PROFIT_2_PERCENT: parseFloat(env.TAKE_PROFIT_2_PERCENT || '1.5'),
|
||||
EMERGENCY_STOP_PERCENT: parseFloat(env.EMERGENCY_STOP_PERCENT || '-2.0'),
|
||||
BREAKEVEN_TRIGGER_PERCENT: parseFloat(env.BREAKEVEN_TRIGGER_PERCENT || '0.4'),
|
||||
PROFIT_LOCK_TRIGGER_PERCENT: parseFloat(env.PROFIT_LOCK_TRIGGER_PERCENT || '1.0'),
|
||||
PROFIT_LOCK_PERCENT: parseFloat(env.PROFIT_LOCK_PERCENT || '0.4'),
|
||||
MAX_DAILY_DRAWDOWN: parseFloat(env.MAX_DAILY_DRAWDOWN || '-50'),
|
||||
MAX_TRADES_PER_HOUR: parseInt(env.MAX_TRADES_PER_HOUR || '6'),
|
||||
MIN_TIME_BETWEEN_TRADES: parseInt(env.MIN_TIME_BETWEEN_TRADES || '600'),
|
||||
SLIPPAGE_TOLERANCE: parseFloat(env.SLIPPAGE_TOLERANCE || '1.0'),
|
||||
DRY_RUN: env.DRY_RUN === 'true',
|
||||
}
|
||||
|
||||
return NextResponse.json(settings)
|
||||
} catch (error) {
|
||||
console.error('Failed to load settings:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to load settings' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const settings = await request.json()
|
||||
|
||||
const updates = {
|
||||
MAX_POSITION_SIZE_USD: settings.MAX_POSITION_SIZE_USD.toString(),
|
||||
LEVERAGE: settings.LEVERAGE.toString(),
|
||||
STOP_LOSS_PERCENT: settings.STOP_LOSS_PERCENT.toString(),
|
||||
TAKE_PROFIT_1_PERCENT: settings.TAKE_PROFIT_1_PERCENT.toString(),
|
||||
TAKE_PROFIT_2_PERCENT: settings.TAKE_PROFIT_2_PERCENT.toString(),
|
||||
EMERGENCY_STOP_PERCENT: settings.EMERGENCY_STOP_PERCENT.toString(),
|
||||
BREAKEVEN_TRIGGER_PERCENT: settings.BREAKEVEN_TRIGGER_PERCENT.toString(),
|
||||
PROFIT_LOCK_TRIGGER_PERCENT: settings.PROFIT_LOCK_TRIGGER_PERCENT.toString(),
|
||||
PROFIT_LOCK_PERCENT: settings.PROFIT_LOCK_PERCENT.toString(),
|
||||
MAX_DAILY_DRAWDOWN: settings.MAX_DAILY_DRAWDOWN.toString(),
|
||||
MAX_TRADES_PER_HOUR: settings.MAX_TRADES_PER_HOUR.toString(),
|
||||
MIN_TIME_BETWEEN_TRADES: settings.MIN_TIME_BETWEEN_TRADES.toString(),
|
||||
SLIPPAGE_TOLERANCE: settings.SLIPPAGE_TOLERANCE.toString(),
|
||||
DRY_RUN: settings.DRY_RUN.toString(),
|
||||
}
|
||||
|
||||
const success = updateEnvFile(updates)
|
||||
|
||||
if (success) {
|
||||
return NextResponse.json({ success: true })
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to save settings' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save settings:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to save settings' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user