Files
trading_bot_v4/app/settings/page.tsx
mindesbunister 5acc61cf66 Fix P&L calculation and update Copilot instructions
- Fix P&L calculation in Position Manager to use actual entry vs exit price instead of SDK's potentially incorrect realizedPnL
- Calculate actual profit percentage and apply to closed position size for accurate dollar amounts
- Update database record for last trade from incorrect 6.58 to actual .66 P&L
- Update .github/copilot-instructions.md to reflect TP2-as-runner system changes
- Document 25% runner system (5x larger than old 5%) with ATR-based trailing
- Add critical P&L calculation pattern to common pitfalls section
- Mark Phase 5 complete in development roadmap
2025-11-07 16:24:43 +01:00

829 lines
36 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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
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<TradingSettings | null>(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)
// Calculate gains/losses for risk calculator
const tp1Gain = size * lev * (settings.TAKE_PROFIT_1_PERCENT / 100) * (settings.TAKE_PROFIT_1_SIZE_PERCENT / 100)
const tp2RunnerSize = size * (1 - settings.TAKE_PROFIT_1_SIZE_PERCENT / 100) // 25% remaining after TP1
const runnerValue = tp2RunnerSize * lev * (settings.TAKE_PROFIT_2_PERCENT / 100) // Full 25% runner value at TP2
const fullWin = tp1Gain + runnerValue
return { maxLoss, tp1Gain, runnerValue, fullWin }
}
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 flex items-center justify-center">
<div className="text-white text-xl">Loading settings...</div>
</div>
)
}
if (!settings) return null
const risk = calculateRisk()
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 py-8 px-4">
<div className="max-w-5xl mx-auto">
{/* Header */}
<div className="mb-8">
<div className="flex items-center space-x-4 mb-4">
<a href="/" className="text-gray-400 hover:text-white transition">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
</a>
<div>
<h1 className="text-4xl font-bold text-white"> Trading Bot Settings</h1>
</div>
</div>
<p className="text-slate-400">Configure your automated trading parameters</p>
</div>
{/* Message */}
{message && (
<div className={`mb-6 p-4 rounded-lg ${
message.type === 'success' ? 'bg-green-500/20 text-green-400 border border-green-500/50' : 'bg-red-500/20 text-red-400 border border-red-500/50'
}`}>
{message.text}
</div>
)}
{/* Risk Calculator */}
{risk && (
<div className="mb-8 bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-6">
<h2 className="text-xl font-bold text-white mb-4">📊 Risk Calculator</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-red-500/10 border border-red-500/50 rounded-lg p-4">
<div className="text-red-400 text-sm mb-1">Max Loss (SL)</div>
<div className="text-white text-2xl font-bold">-${risk.maxLoss.toFixed(2)}</div>
</div>
<div className="bg-blue-500/10 border border-blue-500/50 rounded-lg p-4">
<div className="text-blue-400 text-sm mb-1">TP1 Gain ({settings.TAKE_PROFIT_1_SIZE_PERCENT}%)</div>
<div className="text-white text-2xl font-bold">+${risk.tp1Gain.toFixed(2)}</div>
</div>
<div className="bg-green-500/10 border border-green-500/50 rounded-lg p-4">
<div className="text-green-400 text-sm mb-1">Runner Value (25%)</div>
<div className="text-white text-2xl font-bold">+${risk.runnerValue.toFixed(2)}</div>
</div>
<div className="bg-purple-500/10 border border-purple-500/50 rounded-lg p-4">
<div className="text-purple-400 text-sm mb-1">Full Win</div>
<div className="text-white text-2xl font-bold">+${risk.fullWin.toFixed(2)}</div>
</div>
</div>
<div className="mt-4 text-slate-400 text-sm">
Risk/Reward Ratio: 1:{(risk.fullWin / risk.maxLoss).toFixed(2)}
</div>
</div>
)}
{/* Settings Sections */}
<div className="space-y-6">
{/* Per-Symbol Position Sizing */}
<Section title="<22> Solana (SOL-PERP)" description="Individual settings for Solana perpetual trading">
<div className="mb-4 p-3 bg-purple-500/10 border border-purple-500/30 rounded-lg">
<p className="text-sm text-purple-400">
Enable/disable Solana trading and set symbol-specific position sizing. When enabled, these settings override global defaults for SOL trades.
</p>
</div>
<div className="flex items-center justify-between p-4 bg-slate-700/30 rounded-lg mb-4">
<div className="flex-1">
<div className="text-white font-medium mb-1">🟢 Enable Solana Trading</div>
<div className="text-slate-400 text-sm">
Accept SOL-PERP trade signals from TradingView
</div>
</div>
<button
onClick={() => updateSetting('SOLANA_ENABLED', !settings.SOLANA_ENABLED)}
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
settings.SOLANA_ENABLED ? 'bg-green-500' : 'bg-slate-600'
}`}
>
<span
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
settings.SOLANA_ENABLED ? 'translate-x-7' : 'translate-x-1'
}`}
/>
</button>
</div>
<Setting
label="SOL Position Size (USD)"
value={settings.SOLANA_POSITION_SIZE}
onChange={(v) => 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.`}
/>
<Setting
label="SOL Leverage"
value={settings.SOLANA_LEVERAGE}
onChange={(v) => 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 && (
<div className="p-4 bg-slate-700/50 rounded-lg">
<div className="text-sm text-slate-300 mb-2">SOL Risk/Reward</div>
<div className="flex gap-4 text-xs">
<div>
<span className="text-red-400">Max Loss: </span>
<span className="text-white font-bold">${solRisk.maxLoss.toFixed(2)}</span>
</div>
<div>
<span className="text-green-400">Full Win: </span>
<span className="text-white font-bold">${solRisk.fullWin.toFixed(2)}</span>
</div>
<div>
<span className="text-purple-400">R:R </span>
<span className="text-white font-bold">1:{(solRisk.fullWin / solRisk.maxLoss).toFixed(2)}</span>
</div>
</div>
</div>
)
})()}
</Section>
<Section title="⚡ Ethereum (ETH-PERP)" description="Individual settings for Ethereum perpetual trading">
<div className="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
<p className="text-sm text-blue-400">
Enable/disable Ethereum trading and set symbol-specific position sizing. When enabled, these settings override global defaults for ETH trades.
</p>
</div>
<div className="flex items-center justify-between p-4 bg-slate-700/30 rounded-lg mb-4">
<div className="flex-1">
<div className="text-white font-medium mb-1">🟢 Enable Ethereum Trading</div>
<div className="text-slate-400 text-sm">
Accept ETH-PERP trade signals from TradingView
</div>
</div>
<button
onClick={() => updateSetting('ETHEREUM_ENABLED', !settings.ETHEREUM_ENABLED)}
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
settings.ETHEREUM_ENABLED ? 'bg-green-500' : 'bg-slate-600'
}`}
>
<span
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
settings.ETHEREUM_ENABLED ? 'translate-x-7' : 'translate-x-1'
}`}
/>
</button>
</div>
<Setting
label="ETH Position Size (USD)"
value={settings.ETHEREUM_POSITION_SIZE}
onChange={(v) => 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).`}
/>
<Setting
label="ETH Leverage"
value={settings.ETHEREUM_LEVERAGE}
onChange={(v) => 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 && (
<div className="p-4 bg-slate-700/50 rounded-lg">
<div className="text-sm text-slate-300 mb-2">ETH Risk/Reward</div>
<div className="flex gap-4 text-xs">
<div>
<span className="text-red-400">Max Loss: </span>
<span className="text-white font-bold">${ethRisk.maxLoss.toFixed(2)}</span>
</div>
<div>
<span className="text-green-400">Full Win: </span>
<span className="text-white font-bold">${ethRisk.fullWin.toFixed(2)}</span>
</div>
<div>
<span className="text-purple-400">R:R </span>
<span className="text-white font-bold">1:{(ethRisk.fullWin / ethRisk.maxLoss).toFixed(2)}</span>
</div>
</div>
</div>
)
})()}
</Section>
{/* Global Position Sizing (Fallback) */}
<Section title="💰 Global Position Sizing (Fallback)" description="Default settings for symbols without specific config (e.g., BTC)">
<div className="mb-4 p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
<p className="text-sm text-yellow-400">
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.
</p>
</div>
<Setting
label="Position Size (USD)"
value={settings.MAX_POSITION_SIZE_USD}
onChange={(v) => updateSetting('MAX_POSITION_SIZE_USD', v)}
min={10}
max={10000}
step={10}
description="Base USD amount per trade for unspecified symbols."
/>
<Setting
label="Leverage"
value={settings.LEVERAGE}
onChange={(v) => updateSetting('LEVERAGE', v)}
min={1}
max={20}
step={1}
description="Leverage multiplier for unspecified symbols."
/>
</Section>
{/* Risk Management */}
<Section title="🛡️ Risk Management" description="Stop loss and take profit levels">
<Setting
label="Stop Loss (%)"
value={settings.STOP_LOSS_PERCENT}
onChange={(v) => 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."
/>
<Setting
label="Take Profit 1 Price (%)"
value={settings.TAKE_PROFIT_1_PERCENT}
onChange={(v) => updateSetting('TAKE_PROFIT_1_PERCENT', v)}
min={0.1}
max={10}
step={0.1}
description="Price level for first take profit exit."
/>
<Setting
label="Take Profit 1 Size (%)"
value={settings.TAKE_PROFIT_1_SIZE_PERCENT}
onChange={(v) => 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."
/>
<Setting
label="Take Profit 2 Price (%)"
value={settings.TAKE_PROFIT_2_PERCENT}
onChange={(v) => updateSetting('TAKE_PROFIT_2_PERCENT', v)}
min={0.1}
max={20}
step={0.1}
description="Price level where runner trailing stop activates (no close operation)."
/>
<Setting
label="Emergency Stop (%)"
value={settings.EMERGENCY_STOP_PERCENT}
onChange={(v) => 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."
/>
</Section>
{/* Dynamic Adjustments */}
<Section title="🎯 Dynamic Stop Loss" description="Automatically adjust SL as trade moves in profit">
<Setting
label="Breakeven Trigger (%)"
value={settings.BREAKEVEN_TRIGGER_PERCENT}
onChange={(v) => 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."
/>
<Setting
label="Profit Lock Trigger (%)"
value={settings.PROFIT_LOCK_TRIGGER_PERCENT}
onChange={(v) => 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."
/>
<Setting
label="Profit Lock Amount (%)"
value={settings.PROFIT_LOCK_PERCENT}
onChange={(v) => 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."
/>
</Section>
{/* Trailing Stop */}
<Section title="🏃 Trailing Stop (25% Runner)" description="TP2 activates trailing stop on full remaining 25%">
<div className="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
<p className="text-sm text-blue-400">
NEW SYSTEM: When TP2 price is hit, no position is closed. Instead, trailing stop activates on the full 25% remaining position for maximum runner potential.
This gives you a 5x larger runner (25% vs 5%) to capture extended moves.
</p>
</div>
<Setting
label="Use Trailing Stop"
value={settings.USE_TRAILING_STOP ? 1 : 0}
onChange={(v) => updateSetting('USE_TRAILING_STOP', v === 1)}
min={0}
max={1}
step={1}
description="Enable trailing stop for 25% runner position when TP2 triggers. 0 = disabled, 1 = enabled."
/>
<Setting
label="Trailing Stop Distance (%)"
value={settings.TRAILING_STOP_PERCENT}
onChange={(v) => 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."
/>
<Setting
label="Trailing Stop Activation (%)"
value={settings.TRAILING_STOP_ACTIVATION}
onChange={(v) => updateSetting('TRAILING_STOP_ACTIVATION', v)}
min={0.1}
max={5}
step={0.1}
description="25% runner must reach this profit % before trailing stop activates. Prevents premature stops. Example: 0.5% = wait until runner is +0.5% profit."
/>
</Section>
{/* Position Scaling */}
<Section title="📈 Position Scaling" description="Add to profitable positions with strong confirmation">
<div className="mb-4 p-3 bg-purple-500/10 border border-purple-500/30 rounded-lg">
<p className="text-sm text-purple-400 mb-2">
<strong>⚠️ Advanced Feature:</strong> Scale into existing profitable positions when high-quality signals confirm trend strength.
</p>
<p className="text-xs text-gray-400">
<strong>When enabled:</strong> 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 &lt;{settings?.MAX_PRICE_POSITION_FOR_SCALE || 70}%.
</p>
</div>
<Setting
label="Enable Position Scaling"
value={settings.ENABLE_POSITION_SCALING ? 1 : 0}
onChange={(v) => 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)."
/>
<Setting
label="Min Quality Score for Scaling"
value={settings.MIN_SCALE_QUALITY_SCORE}
onChange={(v) => 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)."
/>
<Setting
label="Min Profit to Scale (%)"
value={settings.MIN_PROFIT_FOR_SCALE}
onChange={(v) => 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."
/>
<Setting
label="Scale Size (%)"
value={settings.SCALE_SIZE_PERCENT}
onChange={(v) => 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."
/>
<Setting
label="Max Total Position Size (multiplier)"
value={settings.MAX_SCALE_MULTIPLIER}
onChange={(v) => 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%)."
/>
<Setting
label="Min ADX Increase"
value={settings.MIN_ADX_INCREASE}
onChange={(v) => 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."
/>
<Setting
label="Max Price Position for Scale (%)"
value={settings.MAX_PRICE_POSITION_FOR_SCALE}
onChange={(v) => 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 && (
<div className="mt-4 p-4 bg-purple-900/20 border border-purple-500/30 rounded-lg">
<h4 className="text-sm font-semibold text-purple-400 mb-2">📊 Scaling Impact (SOL Example)</h4>
<div className="space-y-1 text-xs text-gray-300">
<div className="flex justify-between">
<span>Original Position:</span>
<span className="font-mono">${settings.SOLANA_POSITION_SIZE * settings.SOLANA_LEVERAGE}</span>
</div>
<div className="flex justify-between">
<span>Scale Addition ({settings.SCALE_SIZE_PERCENT}%):</span>
<span className="font-mono text-yellow-400">+${((settings.SOLANA_POSITION_SIZE * settings.SOLANA_LEVERAGE) * (settings.SCALE_SIZE_PERCENT / 100)).toFixed(0)}</span>
</div>
<div className="flex justify-between border-t border-purple-500/30 pt-1 mt-1">
<span className="font-semibold">Total After 1 Scale:</span>
<span className="font-mono font-semibold text-purple-400">${((settings.SOLANA_POSITION_SIZE * settings.SOLANA_LEVERAGE) * (1 + settings.SCALE_SIZE_PERCENT / 100)).toFixed(0)}</span>
</div>
<div className="flex justify-between">
<span className="font-semibold">Max Position ({settings.MAX_SCALE_MULTIPLIER}x):</span>
<span className="font-mono font-semibold text-red-400">${((settings.SOLANA_POSITION_SIZE * settings.SOLANA_LEVERAGE) * settings.MAX_SCALE_MULTIPLIER).toFixed(0)}</span>
</div>
</div>
</div>
)}
</Section>
{/* Trade Limits */}
<Section title=" Safety Limits" description="Prevent overtrading and excessive losses">
<Setting
label="Max Daily Loss (USD)"
value={settings.MAX_DAILY_DRAWDOWN}
onChange={(v) => updateSetting('MAX_DAILY_DRAWDOWN', v)}
min={-1000}
max={-10}
step={10}
description="Stop trading if daily loss exceeds this amount."
/>
<Setting
label="Max Trades Per Hour"
value={settings.MAX_TRADES_PER_HOUR}
onChange={(v) => updateSetting('MAX_TRADES_PER_HOUR', v)}
min={1}
max={20}
step={1}
description="Maximum number of trades allowed per hour."
/>
<Setting
label="Cooldown Between Trades (minutes)"
value={settings.MIN_TIME_BETWEEN_TRADES}
onChange={(v) => updateSetting('MIN_TIME_BETWEEN_TRADES', v)}
min={0}
max={60}
step={1}
description="Minimum wait time between trades to prevent overtrading."
/>
<Setting
label="Min Quality Score (0-100)"
value={settings.MIN_QUALITY_SCORE}
onChange={(v) => 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."
/>
</Section>
{/* Execution */}
<Section title=" Execution Settings" description="Order execution parameters">
<Setting
label="Slippage Tolerance (%)"
value={settings.SLIPPAGE_TOLERANCE}
onChange={(v) => updateSetting('SLIPPAGE_TOLERANCE', v)}
min={0.1}
max={5}
step={0.1}
description="Maximum acceptable price slippage on market orders."
/>
<div className="flex items-center justify-between p-4 bg-slate-700/30 rounded-lg">
<div className="flex-1">
<div className="text-white font-medium mb-1">🧪 Dry Run Mode</div>
<div className="text-slate-400 text-sm">
Simulate trades without executing. Enable for testing.
</div>
</div>
<button
onClick={() => updateSetting('DRY_RUN', !settings.DRY_RUN)}
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
settings.DRY_RUN ? 'bg-blue-500' : 'bg-slate-600'
}`}
>
<span
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
settings.DRY_RUN ? 'translate-x-7' : 'translate-x-1'
}`}
/>
</button>
</div>
</Section>
</div>
{/* Action Buttons */}
<div className="mt-8 space-y-4">
{/* Primary Actions */}
<div className="flex gap-4">
<button
onClick={saveSettings}
disabled={saving}
className="flex-1 bg-gradient-to-r from-blue-500 to-purple-500 text-white font-bold py-4 px-6 rounded-lg hover:from-blue-600 hover:to-purple-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
>
{saving ? '💾 Saving...' : '💾 Save Settings'}
</button>
<button
onClick={restartBot}
disabled={restarting}
className="flex-1 bg-gradient-to-r from-green-500 to-emerald-500 text-white font-bold py-4 px-6 rounded-lg hover:from-green-600 hover:to-emerald-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
>
{restarting ? '🔄 Restarting...' : '🔄 Restart Bot'}
</button>
<button
onClick={loadSettings}
className="bg-slate-700 text-white font-bold py-4 px-6 rounded-lg hover:bg-slate-600 transition-all"
>
↺ Reset
</button>
</div>
{/* Test Trade Buttons */}
<div className="space-y-4">
<div className="text-center text-slate-300 text-sm font-bold">🧪 Test Trades (REAL MONEY)</div>
<div className="flex gap-4">
<button
onClick={() => testTrade('long', 'SOLUSDT')}
disabled={testing || !settings.SOLANA_ENABLED}
className="flex-1 bg-gradient-to-r from-purple-500 to-purple-600 text-white font-bold py-4 px-6 rounded-lg hover:from-purple-600 hover:to-purple-700 transition-all disabled:opacity-50 disabled:cursor-not-allowed border-2 border-purple-400"
>
{testing ? '🧪 Executing...' : '💎 Test SOL LONG'}
</button>
<button
onClick={() => testTrade('short', 'SOLUSDT')}
disabled={testing || !settings.SOLANA_ENABLED}
className="flex-1 bg-gradient-to-r from-purple-600 to-red-500 text-white font-bold py-4 px-6 rounded-lg hover:from-purple-700 hover:to-red-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed border-2 border-purple-400"
>
{testing ? '🧪 Executing...' : '💎 Test SOL SHORT'}
</button>
</div>
<div className="flex gap-4">
<button
onClick={() => testTrade('long', 'ETHUSDT')}
disabled={testing || !settings.ETHEREUM_ENABLED}
className="flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white font-bold py-4 px-6 rounded-lg hover:from-blue-600 hover:to-blue-700 transition-all disabled:opacity-50 disabled:cursor-not-allowed border-2 border-blue-400"
>
{testing ? '🧪 Executing...' : '⚡ Test ETH LONG'}
</button>
<button
onClick={() => testTrade('short', 'ETHUSDT')}
disabled={testing || !settings.ETHEREUM_ENABLED}
className="flex-1 bg-gradient-to-r from-blue-600 to-red-500 text-white font-bold py-4 px-6 rounded-lg hover:from-blue-700 hover:to-red-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed border-2 border-blue-400"
>
{testing ? '🧪 Executing...' : '⚡ Test ETH SHORT'}
</button>
</div>
</div>
</div>
<div className="mt-4 text-center text-slate-400 text-sm">
💡 Save settings first, then click Restart Bot to apply changes
</div>
</div>
</div>
)
}
function Section({ title, description, children }: { title: string, description: string, children: React.ReactNode }) {
return (
<div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-6">
<h2 className="text-xl font-bold text-white mb-1">{title}</h2>
<p className="text-slate-400 text-sm mb-6">{description}</p>
<div className="space-y-4">
{children}
</div>
</div>
)
}
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 (
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-white font-medium">{label}</label>
<input
type="number"
value={value}
onChange={(e) => 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"
/>
</div>
<input
type="range"
value={value}
onChange={(e) => 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"
/>
<p className="text-slate-400 text-sm">{description}</p>
</div>
)
}