Complete implementation of adaptive leverage configuration via web interface:
Frontend (app/settings/page.tsx):
- Added 4 fields to TradingSettings interface:
* USE_ADAPTIVE_LEVERAGE: boolean
* HIGH_QUALITY_LEVERAGE: number
* LOW_QUALITY_LEVERAGE: number
* QUALITY_LEVERAGE_THRESHOLD: number
- Added complete Adaptive Leverage section with:
* Purple-themed informational box explaining quality-based leverage
* Toggle switch for enabling/disabling (🎯 Enable Adaptive Leverage)
* Number inputs for high leverage (1-20), low leverage (1-20), threshold (80-100)
* Visual tier display showing leverage multipliers and position sizes
* Dynamic calculation based on $560 free collateral
Backend (app/api/settings/route.ts):
- GET handler: Load 4 adaptive leverage fields from environment variables
- POST handler: Save 4 adaptive leverage fields to .env file
- Proper type conversion (boolean from 'true', numbers from parseInt/parseFloat)
Visual Tier Display Example:
Below Threshold: Blocked (no trade)
Changes enable users to adjust leverage settings via web UI instead of
manually editing .env file and restarting container.
1074 lines
48 KiB
TypeScript
1074 lines
48 KiB
TypeScript
/**
|
||
* 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
|
||
USE_PERCENTAGE_SIZE: boolean
|
||
|
||
// Per-symbol settings
|
||
SOLANA_ENABLED: boolean
|
||
SOLANA_POSITION_SIZE: number
|
||
SOLANA_LEVERAGE: number
|
||
SOLANA_USE_PERCENTAGE_SIZE: boolean
|
||
ETHEREUM_ENABLED: boolean
|
||
ETHEREUM_POSITION_SIZE: number
|
||
ETHEREUM_LEVERAGE: number
|
||
ETHEREUM_USE_PERCENTAGE_SIZE: boolean
|
||
|
||
// 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_ATR_MULTIPLIER: number
|
||
TRAILING_STOP_MIN_PERCENT: number
|
||
TRAILING_STOP_MAX_PERCENT: number
|
||
TRAILING_STOP_ACTIVATION: number
|
||
|
||
// ATR-based Dynamic Targets
|
||
USE_ATR_BASED_TARGETS: boolean
|
||
ATR_MULTIPLIER_FOR_TP2: number
|
||
MIN_TP2_PERCENT: number
|
||
MAX_TP2_PERCENT: 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
|
||
|
||
// Adaptive Leverage (Quality-based)
|
||
USE_ADAPTIVE_LEVERAGE: boolean
|
||
HIGH_QUALITY_LEVERAGE: number
|
||
LOW_QUALITY_LEVERAGE: number
|
||
QUALITY_LEVERAGE_THRESHOLD: number
|
||
|
||
// Safety
|
||
MAX_DAILY_DRAWDOWN: number
|
||
MAX_TRADES_PER_HOUR: number
|
||
MIN_TIME_BETWEEN_TRADES: number
|
||
MIN_SIGNAL_QUALITY_SCORE: number
|
||
MIN_SIGNAL_QUALITY_SCORE_LONG: number
|
||
MIN_SIGNAL_QUALITY_SCORE_SHORT: 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 syncPositions = async () => {
|
||
setLoading(true)
|
||
setMessage(null)
|
||
try {
|
||
const response = await fetch('/api/trading/sync-positions', {
|
||
method: 'POST',
|
||
})
|
||
|
||
const data = await response.json()
|
||
|
||
if (data.success) {
|
||
const { results } = data
|
||
let msg = '✅ Position sync complete! '
|
||
if (results.added.length > 0) msg += `Added: ${results.added.join(', ')}. `
|
||
if (results.removed.length > 0) msg += `Removed: ${results.removed.join(', ')}. `
|
||
if (results.unchanged.length > 0) msg += `Already tracking: ${results.unchanged.join(', ')}. `
|
||
if (results.errors.length > 0) msg += `⚠️ Errors: ${results.errors.length}`
|
||
setMessage({ type: 'success', text: msg })
|
||
} else {
|
||
setMessage({ type: 'error', text: `Sync failed: ${data.error || data.message}` })
|
||
}
|
||
} catch (error) {
|
||
setMessage({ type: 'error', text: `Sync failed: ${error instanceof Error ? error.message : 'Unknown error'}` })
|
||
}
|
||
setLoading(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) // Remaining % after TP1
|
||
const runnerPercent = 100 - settings.TAKE_PROFIT_1_SIZE_PERCENT // Calculate runner % for display
|
||
|
||
// Use ATR-based TP2 if enabled, otherwise use static
|
||
const tp2Percent = settings.USE_ATR_BASED_TARGETS
|
||
? `${settings.MIN_TP2_PERCENT}-${settings.MAX_TP2_PERCENT}% (ATR-based)`
|
||
: `${settings.TAKE_PROFIT_2_PERCENT}% (static)`
|
||
|
||
// For calculation, use max potential TP2 if ATR-based
|
||
const tp2CalcPercent = settings.USE_ATR_BASED_TARGETS
|
||
? settings.MAX_TP2_PERCENT
|
||
: settings.TAKE_PROFIT_2_PERCENT
|
||
|
||
const runnerValue = tp2RunnerSize * lev * (tp2CalcPercent / 100) // Runner value at TP2
|
||
const fullWin = tp1Gain + runnerValue
|
||
|
||
return { maxLoss, tp1Gain, runnerValue, fullWin, tp2Percent, runnerPercent }
|
||
}
|
||
|
||
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 ({risk.runnerPercent}%)</div>
|
||
<div className="text-white text-2xl font-bold">+${risk.runnerValue.toFixed(2)}</div>
|
||
<div className="text-xs text-green-300 mt-1">{risk.tp2Percent}</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 (${settings.SOLANA_USE_PERCENTAGE_SIZE ? '%' : 'USD'})`}
|
||
value={settings.SOLANA_POSITION_SIZE}
|
||
onChange={(v) => updateSetting('SOLANA_POSITION_SIZE', v)}
|
||
min={1}
|
||
max={settings.SOLANA_USE_PERCENTAGE_SIZE ? 100 : 10000}
|
||
step={1}
|
||
description={
|
||
settings.SOLANA_USE_PERCENTAGE_SIZE
|
||
? `Percentage of free collateral for SOL trades. With ${settings.SOLANA_LEVERAGE}x leverage.`
|
||
: `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 (${settings.ETHEREUM_USE_PERCENTAGE_SIZE ? '%' : 'USD'})`}
|
||
value={settings.ETHEREUM_POSITION_SIZE}
|
||
onChange={(v) => updateSetting('ETHEREUM_POSITION_SIZE', v)}
|
||
min={1}
|
||
max={settings.ETHEREUM_USE_PERCENTAGE_SIZE ? 100 : 10000}
|
||
step={1}
|
||
description={
|
||
settings.ETHEREUM_USE_PERCENTAGE_SIZE
|
||
? `Percentage of free collateral for ETH trades. With ${settings.ETHEREUM_LEVERAGE}x leverage.`
|
||
: `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>
|
||
|
||
{/* Adaptive Leverage */}
|
||
<Section title="⚡ Adaptive Leverage" description="Quality-based dynamic leverage adjustment">
|
||
<div className="mb-4 p-3 bg-purple-500/10 border border-purple-500/30 rounded-lg">
|
||
<p className="text-sm text-purple-400">
|
||
Automatically adjust leverage based on signal quality score. High-quality signals get more leverage for maximum profit, borderline signals use lower leverage for risk management.
|
||
</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 Adaptive Leverage</div>
|
||
<div className="text-slate-400 text-sm">
|
||
Dynamically adjust leverage based on signal quality
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => updateSetting('USE_ADAPTIVE_LEVERAGE', !settings.USE_ADAPTIVE_LEVERAGE)}
|
||
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
|
||
settings.USE_ADAPTIVE_LEVERAGE ? 'bg-green-500' : 'bg-slate-600'
|
||
}`}
|
||
>
|
||
<span
|
||
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
|
||
settings.USE_ADAPTIVE_LEVERAGE ? 'translate-x-7' : 'translate-x-1'
|
||
}`}
|
||
/>
|
||
</button>
|
||
</div>
|
||
<Setting
|
||
label="High Quality Leverage"
|
||
value={settings.HIGH_QUALITY_LEVERAGE}
|
||
onChange={(v) => updateSetting('HIGH_QUALITY_LEVERAGE', v)}
|
||
min={1}
|
||
max={20}
|
||
step={1}
|
||
description={`Leverage for exceptional signals (Quality ${settings.QUALITY_LEVERAGE_THRESHOLD}+ LONG, 90+ SHORT). Current: ${settings.HIGH_QUALITY_LEVERAGE}x leverage.`}
|
||
/>
|
||
<Setting
|
||
label="Low Quality Leverage"
|
||
value={settings.LOW_QUALITY_LEVERAGE}
|
||
onChange={(v) => updateSetting('LOW_QUALITY_LEVERAGE', v)}
|
||
min={1}
|
||
max={20}
|
||
step={1}
|
||
description={`Leverage for borderline signals (Quality 90-${settings.QUALITY_LEVERAGE_THRESHOLD-1} LONG, 80-89 SHORT). Current: ${settings.LOW_QUALITY_LEVERAGE}x leverage.`}
|
||
/>
|
||
<Setting
|
||
label="Quality Threshold"
|
||
value={settings.QUALITY_LEVERAGE_THRESHOLD}
|
||
onChange={(v) => updateSetting('QUALITY_LEVERAGE_THRESHOLD', v)}
|
||
min={80}
|
||
max={100}
|
||
step={1}
|
||
description="Minimum quality score for high-quality leverage tier (applies to LONG signals, SHORT uses 90+)."
|
||
/>
|
||
<div className="p-4 bg-slate-700/50 rounded-lg">
|
||
<div className="text-sm text-slate-300 mb-2">Leverage Tiers (with $560 collateral)</div>
|
||
<div className="space-y-2 text-xs">
|
||
<div className="flex justify-between">
|
||
<span className="text-slate-400">🔥 High Quality (Q{settings.QUALITY_LEVERAGE_THRESHOLD}+ LONG, Q90+ SHORT):</span>
|
||
<span className="text-green-400 font-bold">{settings.HIGH_QUALITY_LEVERAGE}x = ${(560 * settings.HIGH_QUALITY_LEVERAGE).toFixed(0)} position</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-slate-400">📊 Low Quality (Q90-{settings.QUALITY_LEVERAGE_THRESHOLD-1} LONG, Q80-89 SHORT):</span>
|
||
<span className="text-yellow-400 font-bold">{settings.LOW_QUALITY_LEVERAGE}x = ${(560 * settings.LOW_QUALITY_LEVERAGE).toFixed(0)} position</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-slate-400">❌ Below Threshold:</span>
|
||
<span className="text-red-400 font-bold">Blocked (no trade)</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</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>
|
||
|
||
{/* ATR-based Dynamic Targets */}
|
||
<Section title="📈 ATR-Based Dynamic Targets" description="Automatically scale TP2 based on market volatility to capture big moves">
|
||
<div className="mb-4 p-3 bg-green-500/10 border border-green-500/30 rounded-lg">
|
||
<p className="text-sm text-green-400 mb-2">
|
||
<strong>🎯 Capture Big Moves:</strong> When ATR is high (volatile markets), TP2 automatically scales higher to catch 4-5% moves instead of exiting early at 0.7%.
|
||
</p>
|
||
<p className="text-xs text-gray-400">
|
||
Example: If ATR = 1.2% and multiplier = 2.0, then TP2 = 2.4% (instead of fixed 0.7%). Perfect for trending markets!
|
||
</p>
|
||
</div>
|
||
<Setting
|
||
label="Enable ATR-Based Targets"
|
||
value={(settings as any).USE_ATR_BASED_TARGETS ? 1 : 0}
|
||
onChange={(v) => updateSetting('USE_ATR_BASED_TARGETS', v === 1)}
|
||
min={0}
|
||
max={1}
|
||
step={1}
|
||
description="Enable dynamic TP2 based on Average True Range (market volatility). 0 = fixed TP2, 1 = adaptive TP2."
|
||
/>
|
||
<Setting
|
||
label="ATR Multiplier for TP2"
|
||
value={(settings as any).ATR_MULTIPLIER_FOR_TP2 || 2.0}
|
||
onChange={(v) => updateSetting('ATR_MULTIPLIER_FOR_TP2', v)}
|
||
min={1.0}
|
||
max={4.0}
|
||
step={0.1}
|
||
description="Multiply ATR by this value to get TP2 target. Higher = more aggressive targets in volatile markets."
|
||
/>
|
||
<Setting
|
||
label="Minimum TP2 (%)"
|
||
value={(settings as any).MIN_TP2_PERCENT || 0.7}
|
||
onChange={(v) => updateSetting('MIN_TP2_PERCENT', v)}
|
||
min={0.3}
|
||
max={2.0}
|
||
step={0.1}
|
||
description="Safety floor - TP2 will never go below this level even in low-volatility markets."
|
||
/>
|
||
<Setting
|
||
label="Maximum TP2 (%)"
|
||
value={(settings as any).MAX_TP2_PERCENT || 3.0}
|
||
onChange={(v) => updateSetting('MAX_TP2_PERCENT', v)}
|
||
min={1.0}
|
||
max={5.0}
|
||
step={0.1}
|
||
description="Safety cap - TP2 will never exceed this level. Example: 3.0% = 30% account gain at 10x leverage."
|
||
/>
|
||
</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 (${100 - settings.TAKE_PROFIT_1_SIZE_PERCENT}% Runner)`}
|
||
description={`TP2 activates trailing stop on full ${100 - settings.TAKE_PROFIT_1_SIZE_PERCENT}% remaining`}
|
||
>
|
||
<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 {100 - settings.TAKE_PROFIT_1_SIZE_PERCENT}% remaining position for maximum runner potential.
|
||
Current split: {settings.TAKE_PROFIT_1_SIZE_PERCENT}% at TP1, {100 - settings.TAKE_PROFIT_1_SIZE_PERCENT}% becomes runner.
|
||
</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 ${100 - settings.TAKE_PROFIT_1_SIZE_PERCENT}% runner position when TP2 triggers. 0 = disabled, 1 = enabled.`}
|
||
/>
|
||
<Setting
|
||
label="Trailing Stop Distance (%) [FALLBACK]"
|
||
value={settings.TRAILING_STOP_PERCENT}
|
||
onChange={(v) => updateSetting('TRAILING_STOP_PERCENT', v)}
|
||
min={0.1}
|
||
max={2}
|
||
step={0.1}
|
||
description="Legacy fallback used only if ATR data is unavailable. Normally, ATR-based trailing is used instead."
|
||
/>
|
||
<Setting
|
||
label="ATR Trailing Multiplier"
|
||
value={settings.TRAILING_STOP_ATR_MULTIPLIER}
|
||
onChange={(v) => updateSetting('TRAILING_STOP_ATR_MULTIPLIER', v)}
|
||
min={1.0}
|
||
max={3.0}
|
||
step={0.1}
|
||
description="🔥 NEW: Trailing distance = (ATR × multiplier). Example: 0.5% ATR × 1.5 = 0.75% trailing. Higher = more room for runner, lower = tighter protection."
|
||
/>
|
||
<Setting
|
||
label="Min Trailing Distance (%)"
|
||
value={settings.TRAILING_STOP_MIN_PERCENT}
|
||
onChange={(v) => updateSetting('TRAILING_STOP_MIN_PERCENT', v)}
|
||
min={0.1}
|
||
max={1.0}
|
||
step={0.05}
|
||
description="Minimum trailing distance cap. Prevents ultra-tight stops in low ATR conditions."
|
||
/>
|
||
<Setting
|
||
label="Max Trailing Distance (%)"
|
||
value={settings.TRAILING_STOP_MAX_PERCENT}
|
||
onChange={(v) => updateSetting('TRAILING_STOP_MAX_PERCENT', v)}
|
||
min={0.5}
|
||
max={2.0}
|
||
step={0.1}
|
||
description="Maximum trailing distance cap. Prevents excessively wide stops in high ATR conditions."
|
||
/>
|
||
<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={`${100 - settings.TAKE_PROFIT_1_SIZE_PERCENT}% 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 <{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 - Global Fallback (0-100)"
|
||
value={settings.MIN_SIGNAL_QUALITY_SCORE}
|
||
onChange={(v) => updateSetting('MIN_SIGNAL_QUALITY_SCORE', v)}
|
||
min={0}
|
||
max={100}
|
||
step={5}
|
||
description="Global fallback minimum quality score (used for BTC and other symbols). Direction-specific thresholds override this for SOL/ETH."
|
||
/>
|
||
<Setting
|
||
label="Min Quality Score - LONG Signals (0-100)"
|
||
value={settings.MIN_SIGNAL_QUALITY_SCORE_LONG}
|
||
onChange={(v) => updateSetting('MIN_SIGNAL_QUALITY_SCORE_LONG', v)}
|
||
min={0}
|
||
max={100}
|
||
step={5}
|
||
description="Minimum quality for LONG signals. Set to 90 based on data: quality 90-94 longs show 71.4% WR (+$44.77 profit)."
|
||
/>
|
||
<Setting
|
||
label="Min Quality Score - SHORT Signals (0-100)"
|
||
value={settings.MIN_SIGNAL_QUALITY_SCORE_SHORT}
|
||
onChange={(v) => updateSetting('MIN_SIGNAL_QUALITY_SCORE_SHORT', v)}
|
||
min={0}
|
||
max={100}
|
||
step={5}
|
||
description="Minimum quality for SHORT signals. Set to 95 based on data: quality 90-94 shorts show 28.6% WR (-$553.76 toxic). Blocks low-quality shorts."
|
||
/>
|
||
</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={syncPositions}
|
||
disabled={loading}
|
||
className="flex-1 bg-gradient-to-r from-orange-500 to-red-500 text-white font-bold py-4 px-6 rounded-lg hover:from-orange-600 hover:to-red-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||
title="Re-sync Position Manager with actual Drift positions"
|
||
>
|
||
{loading ? '🔄 Syncing...' : '🔄 Sync Positions'}
|
||
</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>
|
||
)
|
||
}
|