207 lines
8.4 KiB
TypeScript
207 lines
8.4 KiB
TypeScript
/**
|
|
* 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<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-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<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')
|
|
|
|
// Also update in-memory environment so running process sees new values immediately
|
|
Object.entries(updates).forEach(([key, value]) => {
|
|
process.env[key] = value
|
|
})
|
|
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'),
|
|
|
|
// 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'),
|
|
ETHEREUM_ENABLED: env.ETHEREUM_ENABLED !== 'false',
|
|
ETHEREUM_POSITION_SIZE: parseFloat(env.ETHEREUM_POSITION_SIZE || '4'),
|
|
ETHEREUM_LEVERAGE: parseFloat(env.ETHEREUM_LEVERAGE || '1'),
|
|
|
|
// 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'),
|
|
|
|
// 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_QUALITY_SCORE: parseInt(env.MIN_QUALITY_SCORE || '60'),
|
|
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(),
|
|
|
|
// 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(),
|
|
|
|
// 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_QUALITY_SCORE: settings.MIN_QUALITY_SCORE.toString(),
|
|
SLIPPAGE_TOLERANCE: settings.SLIPPAGE_TOLERANCE.toString(),
|
|
DRY_RUN: settings.DRY_RUN.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 }
|
|
)
|
|
}
|
|
}
|
|
|