/** * Trading Bot Settings UI * * Beautiful interface for managing trading parameters */ 'use client' import { useState, useEffect } from 'react' interface TradingSettings { // Global fallback settings MAX_POSITION_SIZE_USD: number LEVERAGE: number // Per-symbol settings SOLANA_ENABLED: boolean SOLANA_POSITION_SIZE: number SOLANA_LEVERAGE: number ETHEREUM_ENABLED: boolean ETHEREUM_POSITION_SIZE: number ETHEREUM_LEVERAGE: number // Risk management STOP_LOSS_PERCENT: number TAKE_PROFIT_1_PERCENT: number TAKE_PROFIT_1_SIZE_PERCENT: number TAKE_PROFIT_2_PERCENT: number TAKE_PROFIT_2_SIZE_PERCENT: number EMERGENCY_STOP_PERCENT: number BREAKEVEN_TRIGGER_PERCENT: number PROFIT_LOCK_TRIGGER_PERCENT: number PROFIT_LOCK_PERCENT: number USE_TRAILING_STOP: boolean TRAILING_STOP_PERCENT: number TRAILING_STOP_ACTIVATION: number // Position Scaling ENABLE_POSITION_SCALING: boolean MIN_SCALE_QUALITY_SCORE: number MIN_PROFIT_FOR_SCALE: number MAX_SCALE_MULTIPLIER: number SCALE_SIZE_PERCENT: number MIN_ADX_INCREASE: number MAX_PRICE_POSITION_FOR_SCALE: number // Safety MAX_DAILY_DRAWDOWN: number MAX_TRADES_PER_HOUR: number MIN_TIME_BETWEEN_TRADES: number MIN_QUALITY_SCORE: number SLIPPAGE_TOLERANCE: number DRY_RUN: boolean } export default function SettingsPage() { const [settings, setSettings] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [restarting, setRestarting] = useState(false) const [testing, setTesting] = useState(false) const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null) useEffect(() => { loadSettings() }, []) const loadSettings = async () => { try { const response = await fetch('/api/settings') const data = await response.json() setSettings(data) setLoading(false) } catch (error) { setMessage({ type: 'error', text: 'Failed to load settings' }) setLoading(false) } } const saveSettings = async () => { setSaving(true) setMessage(null) try { const response = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings), }) if (response.ok) { setMessage({ type: 'success', text: 'Settings saved! Click "Restart Bot" to apply changes.' }) } else { setMessage({ type: 'error', text: 'Failed to save settings' }) } } catch (error) { setMessage({ type: 'error', text: 'Failed to save settings' }) } setSaving(false) } const restartBot = async () => { setRestarting(true) setMessage(null) try { const response = await fetch('/api/restart', { method: 'POST', }) if (response.ok) { setMessage({ type: 'success', text: 'Bot is restarting... Settings will be applied in ~10 seconds.' }) } else { setMessage({ type: 'error', text: 'Failed to restart bot. Please restart manually with: docker restart trading-bot' }) } } catch (error) { setMessage({ type: 'error', text: 'Failed to restart bot. Please restart manually with: docker restart trading-bot' }) } setRestarting(false) } const testTrade = async (direction: 'long' | 'short', symbol: string = 'SOLUSDT') => { if (!confirm(`⚠️ This will execute a REAL ${direction.toUpperCase()} trade on ${symbol} with current settings. Continue?`)) { return } setTesting(true) setMessage(null) try { const response = await fetch('/api/trading/test', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ symbol: symbol, direction: direction, }), }) const data = await response.json() if (data.success) { const dualStopsMsg = data.useDualStops ? `Dual stops: Soft $${data.softStopPrice?.toFixed(4)} | Hard $${data.hardStopPrice?.toFixed(4)}` : `SL: $${data.stopLoss?.toFixed(4)}` setMessage({ type: 'success', text: `✅ ${symbol} ${direction.toUpperCase()} test trade executed! Size: $${data.positionSize?.toFixed(2)} | Entry: $${data.entryPrice?.toFixed(4)} | ${dualStopsMsg} | TX: ${data.positionId?.substring(0, 8)}...` }) } else { setMessage({ type: 'error', text: `Failed: ${data.error || data.message}` }) } } catch (error) { setMessage({ type: 'error', text: `Test trade failed: ${error instanceof Error ? error.message : 'Unknown error'}` }) } setTesting(false) } const updateSetting = (key: keyof TradingSettings, value: any) => { if (!settings) return setSettings({ ...settings, [key]: value }) } const calculateRisk = (baseSize?: number, leverage?: number) => { if (!settings) return null const size = baseSize ?? settings.MAX_POSITION_SIZE_USD const lev = leverage ?? settings.LEVERAGE const maxLoss = size * lev * (Math.abs(settings.STOP_LOSS_PERCENT) / 100) const tp1Gain = size * lev * (settings.TAKE_PROFIT_1_PERCENT / 100) * (settings.TAKE_PROFIT_1_SIZE_PERCENT / 100) const tp2Gain = size * lev * (settings.TAKE_PROFIT_2_PERCENT / 100) * (settings.TAKE_PROFIT_2_SIZE_PERCENT / 100) const fullWin = tp1Gain + tp2Gain return { maxLoss, tp1Gain, tp2Gain, fullWin } } if (loading) { return (
Loading settings...
) } if (!settings) return null const risk = calculateRisk() return (
{/* Header */}

⚙️ Trading Bot Settings

Configure your automated trading parameters

{/* Message */} {message && (
{message.text}
)} {/* Risk Calculator */} {risk && (

📊 Risk Calculator

Max Loss (SL)
-${risk.maxLoss.toFixed(2)}
TP1 Gain ({settings.TAKE_PROFIT_1_SIZE_PERCENT}%)
+${risk.tp1Gain.toFixed(2)}
TP2 Gain ({settings.TAKE_PROFIT_2_SIZE_PERCENT}%)
+${risk.tp2Gain.toFixed(2)}
Full Win
+${risk.fullWin.toFixed(2)}
Risk/Reward Ratio: 1:{(risk.fullWin / risk.maxLoss).toFixed(2)}
)} {/* Settings Sections */}
{/* Per-Symbol Position Sizing */}

Enable/disable Solana trading and set symbol-specific position sizing. When enabled, these settings override global defaults for SOL trades.

🟢 Enable Solana Trading
Accept SOL-PERP trade signals from TradingView
updateSetting('SOLANA_POSITION_SIZE', v)} min={1} max={10000} step={1} description={`Base capital for SOL trades. With ${settings.SOLANA_LEVERAGE}x leverage = $${(settings.SOLANA_POSITION_SIZE * settings.SOLANA_LEVERAGE).toFixed(0)} notional position.`} /> updateSetting('SOLANA_LEVERAGE', v)} min={1} max={20} step={1} description="Leverage multiplier for Solana positions only." /> {(() => { const solRisk = calculateRisk(settings.SOLANA_POSITION_SIZE, settings.SOLANA_LEVERAGE) return solRisk && (
SOL Risk/Reward
Max Loss: ${solRisk.maxLoss.toFixed(2)}
Full Win: ${solRisk.fullWin.toFixed(2)}
R:R 1:{(solRisk.fullWin / solRisk.maxLoss).toFixed(2)}
) })()}

Enable/disable Ethereum trading and set symbol-specific position sizing. When enabled, these settings override global defaults for ETH trades.

🟢 Enable Ethereum Trading
Accept ETH-PERP trade signals from TradingView
updateSetting('ETHEREUM_POSITION_SIZE', v)} min={1} max={10000} step={1} description={`Base capital for ETH trades. With ${settings.ETHEREUM_LEVERAGE}x leverage = $${(settings.ETHEREUM_POSITION_SIZE * settings.ETHEREUM_LEVERAGE).toFixed(0)} notional position. Drift minimum: ~$38-40 (0.01 ETH).`} /> updateSetting('ETHEREUM_LEVERAGE', v)} min={1} max={20} step={1} description="Leverage multiplier for Ethereum positions only." /> {(() => { const ethRisk = calculateRisk(settings.ETHEREUM_POSITION_SIZE, settings.ETHEREUM_LEVERAGE) return ethRisk && (
ETH Risk/Reward
Max Loss: ${ethRisk.maxLoss.toFixed(2)}
Full Win: ${ethRisk.fullWin.toFixed(2)}
R:R 1:{(ethRisk.fullWin / ethRisk.maxLoss).toFixed(2)}
) })()}
{/* Global Position Sizing (Fallback) */}

These are fallback values used for any symbol that doesn't have its own specific settings (like BTC-PERP). SOL and ETH ignore these when their own settings are configured above.

updateSetting('MAX_POSITION_SIZE_USD', v)} min={10} max={10000} step={10} description="Base USD amount per trade for unspecified symbols." /> updateSetting('LEVERAGE', v)} min={1} max={20} step={1} description="Leverage multiplier for unspecified symbols." />
{/* Risk Management */}
updateSetting('STOP_LOSS_PERCENT', v)} min={-10} max={-0.1} step={0.1} description="Close 100% of position when price drops this much. Protects from large losses." /> updateSetting('TAKE_PROFIT_1_PERCENT', v)} min={0.1} max={10} step={0.1} description="Price level for first take profit exit." /> updateSetting('TAKE_PROFIT_1_SIZE_PERCENT', v)} min={1} max={100} step={1} description="What % of position to close at TP1. Example: 50 = close half." /> updateSetting('TAKE_PROFIT_2_PERCENT', v)} min={0.1} max={20} step={0.1} description="Price level for second take profit exit." /> updateSetting('TAKE_PROFIT_2_SIZE_PERCENT', v)} min={1} max={100} step={1} description="What % of remaining position to close at TP2. Example: 100 = close rest." /> updateSetting('EMERGENCY_STOP_PERCENT', v)} min={-20} max={-0.1} step={0.1} description="Hard stop for flash crashes. Should be wider than regular SL." />
{/* Dynamic Adjustments */}
updateSetting('BREAKEVEN_TRIGGER_PERCENT', v)} min={0} max={5} step={0.1} description="After TP1 closes, move SL to this profit level. Should be between 0% (breakeven) and TP1%. Example: 0.4% = locks in +4% account profit on remaining position." /> updateSetting('PROFIT_LOCK_TRIGGER_PERCENT', v)} min={0} max={10} step={0.1} description="After TP1, if price continues to this level, move SL higher to lock more profit. Must be > TP1 and < TP2." /> updateSetting('PROFIT_LOCK_PERCENT', v)} min={0} max={5} step={0.1} description="When Profit Lock Trigger hits, move SL to this profit level. Should be > Breakeven Trigger." />
{/* Trailing Stop */}

After TP2 closes, the remaining position (your "runner") can use a trailing stop loss that follows price. This lets you capture big moves while protecting profit.

updateSetting('USE_TRAILING_STOP', v === 1)} min={0} max={1} step={1} description="Enable trailing stop for runner position after TP2. 0 = disabled, 1 = enabled." /> updateSetting('TRAILING_STOP_PERCENT', v)} min={0.1} max={2} step={0.1} description="How far below peak price (for longs) to trail the stop loss. Example: 0.3% = SL trails 0.3% below highest price reached." /> updateSetting('TRAILING_STOP_ACTIVATION', v)} min={0.1} max={5} step={0.1} description="Runner must reach this profit % before trailing stop activates. Prevents premature stops. Example: 0.5% = wait until runner is +0.5% profit." />
{/* Position Scaling */}

⚠️ Advanced Feature: Scale into existing profitable positions when high-quality signals confirm trend strength.

When enabled: Same-direction signals will ADD to position (not rejected) if quality ≥{settings?.MIN_SCALE_QUALITY_SCORE || 75}, profit ≥{settings?.MIN_PROFIT_FOR_SCALE || 0.4}%, ADX increased ≥{settings?.MIN_ADX_INCREASE || 5}, and price position <{settings?.MAX_PRICE_POSITION_FOR_SCALE || 70}%.

updateSetting('ENABLE_POSITION_SCALING', v === 1)} min={0} max={1} step={1} description="🔴 DISABLED by default. Enable to allow scaling into profitable positions. 0 = block duplicates (safe), 1 = allow scaling (aggressive)." /> updateSetting('MIN_SCALE_QUALITY_SCORE', v)} min={60} max={90} step={5} description="Scaling signal must score this high (0-100). Higher = more selective. Recommended: 75 (vs 60 for initial entry)." /> updateSetting('MIN_PROFIT_FOR_SCALE', v)} min={0} max={2} step={0.1} description="Position must be this profitable before scaling. Example: 0.4% = must be at/past TP1. NEVER scales into losing positions." /> updateSetting('SCALE_SIZE_PERCENT', v)} min={10} max={100} step={10} description="Add this % of original position size. Example: 50% = if original was $2100, scale adds $1050." /> updateSetting('MAX_SCALE_MULTIPLIER', v)} min={1} max={3} step={0.5} description="Max total position size after scaling. Example: 2.0 = can scale to 200% of original (original + 1 scale of 100%)." /> updateSetting('MIN_ADX_INCREASE', v)} min={0} max={15} step={1} description="ADX must increase by this much since entry. Example: 5 = if entered at ADX 15, scale requires ADX ≥20. Confirms trend strengthening." /> updateSetting('MAX_PRICE_POSITION_FOR_SCALE', v)} min={50} max={90} step={5} description="Don't scale if price is above this % of range. Example: 70% = blocks scaling near resistance. Prevents chasing." /> {/* Risk Calculator for Scaling */} {settings.ENABLE_POSITION_SCALING && (

📊 Scaling Impact (SOL Example)

Original Position: ${settings.SOLANA_POSITION_SIZE * settings.SOLANA_LEVERAGE}
Scale Addition ({settings.SCALE_SIZE_PERCENT}%): +${((settings.SOLANA_POSITION_SIZE * settings.SOLANA_LEVERAGE) * (settings.SCALE_SIZE_PERCENT / 100)).toFixed(0)}
Total After 1 Scale: ${((settings.SOLANA_POSITION_SIZE * settings.SOLANA_LEVERAGE) * (1 + settings.SCALE_SIZE_PERCENT / 100)).toFixed(0)}
Max Position ({settings.MAX_SCALE_MULTIPLIER}x): ${((settings.SOLANA_POSITION_SIZE * settings.SOLANA_LEVERAGE) * settings.MAX_SCALE_MULTIPLIER).toFixed(0)}
)}
{/* Trade Limits */}
updateSetting('MAX_DAILY_DRAWDOWN', v)} min={-1000} max={-10} step={10} description="Stop trading if daily loss exceeds this amount." /> updateSetting('MAX_TRADES_PER_HOUR', v)} min={1} max={20} step={1} description="Maximum number of trades allowed per hour." /> updateSetting('MIN_TIME_BETWEEN_TRADES', v)} min={0} max={60} step={1} description="Minimum wait time between trades to prevent overtrading." /> updateSetting('MIN_QUALITY_SCORE', v)} min={0} max={100} step={5} description="Minimum signal quality score required to accept a trade. Signals below this score will be blocked." />
{/* Execution */}
updateSetting('SLIPPAGE_TOLERANCE', v)} min={0.1} max={5} step={0.1} description="Maximum acceptable price slippage on market orders." />
🧪 Dry Run Mode
Simulate trades without executing. Enable for testing.
{/* Action Buttons */}
{/* Primary Actions */}
{/* Test Trade Buttons */}
🧪 Test Trades (REAL MONEY)
💡 Save settings first, then click Restart Bot to apply changes
) } function Section({ title, description, children }: { title: string, description: string, children: React.ReactNode }) { return (

{title}

{description}

{children}
) } function Setting({ label, value, onChange, min, max, step, description }: { label: string value: number onChange: (value: number) => void min: number max: number step: number description: string }) { return (
onChange(parseFloat(e.target.value))} min={min} max={max} step={step} className="w-24 bg-slate-700 text-white px-3 py-2 rounded-lg border border-slate-600 focus:border-blue-500 focus:outline-none" />
onChange(parseFloat(e.target.value))} min={min} max={max} step={step} className="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer slider" />

{description}

) }