/** * Settings API Endpoint * * Read and update trading bot configuration */ import { NextRequest, NextResponse } from 'next/server' import fs from 'fs' import path from 'path' import { DEFAULT_TRADING_CONFIG } from '@/config/trading' const ENV_FILE_PATH = path.join(process.cwd(), '.env') function parseEnvFile(): Record { try { const content = fs.readFileSync(ENV_FILE_PATH, 'utf-8') const env: Record = {} content.split('\n').forEach(line => { // Skip comments and empty lines if (line.trim().startsWith('#') || !line.trim()) return const match = line.match(/^([A-Z0-9_]+)=(.*)$/) if (match) { env[match[1]] = match[2] } }) return env } catch (error) { console.error('Failed to read .env file:', error) return {} } } function updateEnvFile(updates: Record) { 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') // Note: Changes to .env require container restart to take full effect // In-memory updates below are temporary and may not persist across all modules Object.entries(updates).forEach(([key, value]) => { process.env[key] = value.toString() }) console.log('⚠️ Settings updated in .env file. Container restart recommended for all changes to take effect.') return true } catch (error) { console.error('Failed to write .env file:', error) return false } } export async function GET() { try { const env = parseEnvFile() const settings = { // Global fallback MAX_POSITION_SIZE_USD: parseFloat(env.MAX_POSITION_SIZE_USD || '50'), LEVERAGE: parseFloat(env.LEVERAGE || '5'), USE_PERCENTAGE_SIZE: env.USE_PERCENTAGE_SIZE === 'true', // Per-symbol settings SOLANA_ENABLED: env.SOLANA_ENABLED !== 'false', SOLANA_POSITION_SIZE: parseFloat(env.SOLANA_POSITION_SIZE || '210'), SOLANA_LEVERAGE: parseFloat(env.SOLANA_LEVERAGE || '10'), SOLANA_USE_PERCENTAGE_SIZE: env.SOLANA_USE_PERCENTAGE_SIZE === 'true', ETHEREUM_ENABLED: env.ETHEREUM_ENABLED !== 'false', ETHEREUM_POSITION_SIZE: parseFloat(env.ETHEREUM_POSITION_SIZE || '4'), ETHEREUM_LEVERAGE: parseFloat(env.ETHEREUM_LEVERAGE || '1'), ETHEREUM_USE_PERCENTAGE_SIZE: env.ETHEREUM_USE_PERCENTAGE_SIZE === 'true', // Risk management STOP_LOSS_PERCENT: parseFloat(env.STOP_LOSS_PERCENT || '-1.5'), TAKE_PROFIT_1_PERCENT: parseFloat(env.TAKE_PROFIT_1_PERCENT || '0.7'), TAKE_PROFIT_1_SIZE_PERCENT: parseFloat(env.TAKE_PROFIT_1_SIZE_PERCENT || '50'), TAKE_PROFIT_2_PERCENT: parseFloat(env.TAKE_PROFIT_2_PERCENT || '1.5'), TAKE_PROFIT_2_SIZE_PERCENT: parseFloat(env.TAKE_PROFIT_2_SIZE_PERCENT || '50'), 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'), USE_TRAILING_STOP: env.USE_TRAILING_STOP === 'true' || env.USE_TRAILING_STOP === undefined, TRAILING_STOP_PERCENT: parseFloat(env.TRAILING_STOP_PERCENT || '0.3'), TRAILING_STOP_ATR_MULTIPLIER: parseFloat(env.TRAILING_STOP_ATR_MULTIPLIER || '1.5'), TRAILING_STOP_MIN_PERCENT: parseFloat(env.TRAILING_STOP_MIN_PERCENT || '0.25'), TRAILING_STOP_MAX_PERCENT: parseFloat(env.TRAILING_STOP_MAX_PERCENT || '0.9'), TRAILING_STOP_ACTIVATION: parseFloat(env.TRAILING_STOP_ACTIVATION || '0.5'), // ATR-based Dynamic Targets USE_ATR_BASED_TARGETS: env.USE_ATR_BASED_TARGETS === 'true' || env.USE_ATR_BASED_TARGETS === undefined, ATR_MULTIPLIER_FOR_TP2: parseFloat(env.ATR_MULTIPLIER_FOR_TP2 || '2.0'), MIN_TP2_PERCENT: parseFloat(env.MIN_TP2_PERCENT || '0.7'), MAX_TP2_PERCENT: parseFloat(env.MAX_TP2_PERCENT || '3.0'), // Position Scaling ENABLE_POSITION_SCALING: env.ENABLE_POSITION_SCALING === 'true', MIN_SCALE_QUALITY_SCORE: parseInt(env.MIN_SCALE_QUALITY_SCORE || '75'), MIN_PROFIT_FOR_SCALE: parseFloat(env.MIN_PROFIT_FOR_SCALE || '0.4'), MAX_SCALE_MULTIPLIER: parseFloat(env.MAX_SCALE_MULTIPLIER || '2.0'), SCALE_SIZE_PERCENT: parseFloat(env.SCALE_SIZE_PERCENT || '50'), MIN_ADX_INCREASE: parseFloat(env.MIN_ADX_INCREASE || '5'), MAX_PRICE_POSITION_FOR_SCALE: parseFloat(env.MAX_PRICE_POSITION_FOR_SCALE || '70'), // Safety 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'), MIN_SIGNAL_QUALITY_SCORE: parseInt(env.MIN_SIGNAL_QUALITY_SCORE || '60'), MIN_SIGNAL_QUALITY_SCORE_LONG: parseInt(env.MIN_SIGNAL_QUALITY_SCORE_LONG || env.MIN_SIGNAL_QUALITY_SCORE || '60'), MIN_SIGNAL_QUALITY_SCORE_SHORT: parseInt(env.MIN_SIGNAL_QUALITY_SCORE_SHORT || env.MIN_SIGNAL_QUALITY_SCORE || '60'), SLIPPAGE_TOLERANCE: parseFloat(env.SLIPPAGE_TOLERANCE || '1.0'), DRY_RUN: env.DRY_RUN === 'true', // Adaptive Leverage (Dec 1, 2025) USE_ADAPTIVE_LEVERAGE: env.USE_ADAPTIVE_LEVERAGE === 'true', HIGH_QUALITY_LEVERAGE: parseFloat(env.HIGH_QUALITY_LEVERAGE || '5'), LOW_QUALITY_LEVERAGE: parseFloat(env.LOW_QUALITY_LEVERAGE || '1'), QUALITY_LEVERAGE_THRESHOLD_LONG: parseInt(env.QUALITY_LEVERAGE_THRESHOLD_LONG || env.QUALITY_LEVERAGE_THRESHOLD || '95'), QUALITY_LEVERAGE_THRESHOLD_SHORT: parseInt(env.QUALITY_LEVERAGE_THRESHOLD_SHORT || '90'), } 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(), // Per-symbol settings SOLANA_ENABLED: settings.SOLANA_ENABLED.toString(), SOLANA_POSITION_SIZE: settings.SOLANA_POSITION_SIZE.toString(), SOLANA_LEVERAGE: settings.SOLANA_LEVERAGE.toString(), ETHEREUM_ENABLED: settings.ETHEREUM_ENABLED.toString(), ETHEREUM_POSITION_SIZE: settings.ETHEREUM_POSITION_SIZE.toString(), ETHEREUM_LEVERAGE: settings.ETHEREUM_LEVERAGE.toString(), // Risk management STOP_LOSS_PERCENT: settings.STOP_LOSS_PERCENT.toString(), TAKE_PROFIT_1_PERCENT: settings.TAKE_PROFIT_1_PERCENT.toString(), TAKE_PROFIT_1_SIZE_PERCENT: settings.TAKE_PROFIT_1_SIZE_PERCENT.toString(), TAKE_PROFIT_2_PERCENT: settings.TAKE_PROFIT_2_PERCENT.toString(), TAKE_PROFIT_2_SIZE_PERCENT: settings.TAKE_PROFIT_2_SIZE_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(), USE_TRAILING_STOP: settings.USE_TRAILING_STOP.toString(), TRAILING_STOP_PERCENT: settings.TRAILING_STOP_PERCENT.toString(), TRAILING_STOP_ATR_MULTIPLIER: (settings.TRAILING_STOP_ATR_MULTIPLIER ?? DEFAULT_TRADING_CONFIG.trailingStopAtrMultiplier).toString(), TRAILING_STOP_MIN_PERCENT: (settings.TRAILING_STOP_MIN_PERCENT ?? DEFAULT_TRADING_CONFIG.trailingStopMinPercent).toString(), TRAILING_STOP_MAX_PERCENT: (settings.TRAILING_STOP_MAX_PERCENT ?? DEFAULT_TRADING_CONFIG.trailingStopMaxPercent).toString(), TRAILING_STOP_ACTIVATION: settings.TRAILING_STOP_ACTIVATION.toString(), // ATR-based Dynamic Targets USE_ATR_BASED_TARGETS: (settings as any).USE_ATR_BASED_TARGETS?.toString() || 'true', ATR_MULTIPLIER_FOR_TP2: (settings as any).ATR_MULTIPLIER_FOR_TP2?.toString() || '2.0', MIN_TP2_PERCENT: (settings as any).MIN_TP2_PERCENT?.toString() || '0.7', MAX_TP2_PERCENT: (settings as any).MAX_TP2_PERCENT?.toString() || '3.0', // Position Scaling ENABLE_POSITION_SCALING: settings.ENABLE_POSITION_SCALING.toString(), MIN_SCALE_QUALITY_SCORE: settings.MIN_SCALE_QUALITY_SCORE.toString(), MIN_PROFIT_FOR_SCALE: settings.MIN_PROFIT_FOR_SCALE.toString(), MAX_SCALE_MULTIPLIER: settings.MAX_SCALE_MULTIPLIER.toString(), SCALE_SIZE_PERCENT: settings.SCALE_SIZE_PERCENT.toString(), MIN_ADX_INCREASE: settings.MIN_ADX_INCREASE.toString(), MAX_PRICE_POSITION_FOR_SCALE: settings.MAX_PRICE_POSITION_FOR_SCALE.toString(), // Safety 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(), MIN_SIGNAL_QUALITY_SCORE: settings.MIN_SIGNAL_QUALITY_SCORE.toString(), MIN_SIGNAL_QUALITY_SCORE_LONG: settings.MIN_SIGNAL_QUALITY_SCORE_LONG.toString(), MIN_SIGNAL_QUALITY_SCORE_SHORT: settings.MIN_SIGNAL_QUALITY_SCORE_SHORT.toString(), SLIPPAGE_TOLERANCE: settings.SLIPPAGE_TOLERANCE.toString(), DRY_RUN: settings.DRY_RUN.toString(), // Adaptive Leverage (Dec 1, 2025) USE_ADAPTIVE_LEVERAGE: settings.USE_ADAPTIVE_LEVERAGE.toString(), HIGH_QUALITY_LEVERAGE: settings.HIGH_QUALITY_LEVERAGE.toString(), LOW_QUALITY_LEVERAGE: settings.LOW_QUALITY_LEVERAGE.toString(), QUALITY_LEVERAGE_THRESHOLD_LONG: settings.QUALITY_LEVERAGE_THRESHOLD_LONG.toString(), QUALITY_LEVERAGE_THRESHOLD_SHORT: settings.QUALITY_LEVERAGE_THRESHOLD_SHORT.toString(), } const success = updateEnvFile(updates) if (success) { try { const { getPositionManager } = await import('@/lib/trading/position-manager') const manager = getPositionManager() manager.refreshConfig() console.log('⚙️ Position manager config refreshed after settings update') } catch (pmError) { console.error('Failed to refresh position manager config:', pmError) } 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 } ) } }