Files
trading_bot_v4/app/api/settings/route.ts
mindesbunister 4ae9c38ad8 Add trailing stop feature for runner position + fix settings persistence
- Implemented trailing stop logic in Position Manager for remaining position after TP2
- Added new ActiveTrade fields: tp2Hit, trailingStopActive, peakPrice
- New config settings: useTrailingStop, trailingStopPercent, trailingStopActivation
- Added trailing stop UI section in settings page with explanations
- Fixed env file parsing regex to support numbers in variable names (A-Z0-9_)
- Settings now persist correctly across container restarts
- Added back arrow navigation on settings page
- Updated all API endpoints and test files with new fields
- Trailing stop activates when runner reaches configured profit level
- SL trails below peak price by configurable percentage
2025-10-27 12:11:10 +01:00

141 lines
5.0 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'
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')
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_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_ACTIVATION: parseFloat(env.TRAILING_STOP_ACTIVATION || '0.5'),
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_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_ACTIVATION: settings.TRAILING_STOP_ACTIVATION.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 }
)
}
}