feat: Add per-symbol trading controls for SOL and ETH
- Add SymbolSettings interface with enabled/positionSize/leverage fields - Implement per-symbol ENV variables (SOLANA_*, ETHEREUM_*) - Add SOL and ETH sections to settings UI with enable/disable toggles - Add symbol-specific test buttons (SOL LONG/SHORT, ETH LONG/SHORT) - Update execute and test endpoints to check symbol enabled status - Add real-time risk/reward calculator per symbol - Rename 'Position Sizing' to 'Global Fallback' for clarity - Fix position manager P&L calculation for externally closed positions - Fix zero P&L bug affecting 12 historical trades - Add SQL scripts for recalculating historical P&L data - Move archive TypeScript files to .archive to fix build Defaults: - SOL: 10 base × 10x leverage = 100 notional (profit trading) - ETH: base × 1x leverage = notional (data collection) - Global: 10 × 10x for BTC and other symbols Configuration priority: Per-symbol ENV > Market config > Global ENV > Defaults
This commit is contained in:
@@ -9,8 +9,19 @@
|
||||
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
|
||||
@@ -95,8 +106,8 @@ export default function SettingsPage() {
|
||||
setRestarting(false)
|
||||
}
|
||||
|
||||
const testTrade = async (direction: 'long' | 'short') => {
|
||||
if (!confirm(`⚠️ This will execute a REAL ${direction.toUpperCase()} trade with current settings. Continue?`)) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -109,7 +120,7 @@ export default function SettingsPage() {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
symbol: 'SOLUSDT',
|
||||
symbol: symbol,
|
||||
direction: direction,
|
||||
}),
|
||||
})
|
||||
@@ -122,7 +133,7 @@ export default function SettingsPage() {
|
||||
: `SL: $${data.stopLoss?.toFixed(4)}`
|
||||
setMessage({
|
||||
type: 'success',
|
||||
text: `✅ ${direction.toUpperCase()} test trade executed! Size: $${data.positionSize?.toFixed(2)} | Entry: $${data.entryPrice?.toFixed(4)} | ${dualStopsMsg} | TX: ${data.positionId?.substring(0, 8)}...`
|
||||
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}` })
|
||||
@@ -138,11 +149,13 @@ export default function SettingsPage() {
|
||||
setSettings({ ...settings, [key]: value })
|
||||
}
|
||||
|
||||
const calculateRisk = () => {
|
||||
const calculateRisk = (baseSize?: number, leverage?: number) => {
|
||||
if (!settings) return null
|
||||
const maxLoss = settings.MAX_POSITION_SIZE_USD * settings.LEVERAGE * (Math.abs(settings.STOP_LOSS_PERCENT) / 100)
|
||||
const tp1Gain = settings.MAX_POSITION_SIZE_USD * settings.LEVERAGE * (settings.TAKE_PROFIT_1_PERCENT / 100) * (settings.TAKE_PROFIT_1_SIZE_PERCENT / 100)
|
||||
const tp2Gain = settings.MAX_POSITION_SIZE_USD * settings.LEVERAGE * (settings.TAKE_PROFIT_2_PERCENT / 100) * (settings.TAKE_PROFIT_2_SIZE_PERCENT / 100)
|
||||
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 }
|
||||
@@ -217,8 +230,150 @@ export default function SettingsPage() {
|
||||
|
||||
{/* Settings Sections */}
|
||||
<div className="space-y-6">
|
||||
{/* Position Sizing */}
|
||||
<Section title="💰 Position Sizing" description="Control your trade size and leverage">
|
||||
{/* Per-Symbol Position Sizing */}
|
||||
<Section title="<EFBFBD> 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}
|
||||
@@ -226,7 +381,7 @@ export default function SettingsPage() {
|
||||
min={10}
|
||||
max={10000}
|
||||
step={10}
|
||||
description="Base USD amount per trade. With 5x leverage, $50 = $250 position."
|
||||
description="Base USD amount per trade for unspecified symbols."
|
||||
/>
|
||||
<Setting
|
||||
label="Leverage"
|
||||
@@ -235,7 +390,7 @@ export default function SettingsPage() {
|
||||
min={1}
|
||||
max={20}
|
||||
step={1}
|
||||
description="Multiplier for your position. Higher = more profit AND more risk."
|
||||
description="Leverage multiplier for unspecified symbols."
|
||||
/>
|
||||
</Section>
|
||||
|
||||
@@ -466,21 +621,40 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
|
||||
{/* Test Trade Buttons */}
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
onClick={() => testTrade('long')}
|
||||
disabled={testing}
|
||||
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 border-2 border-green-400"
|
||||
>
|
||||
{testing ? '🧪 Executing...' : '🧪 Test LONG (REAL)'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => testTrade('short')}
|
||||
disabled={testing}
|
||||
className="flex-1 bg-gradient-to-r from-red-500 to-orange-500 text-white font-bold py-4 px-6 rounded-lg hover:from-red-600 hover:to-orange-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed border-2 border-red-400"
|
||||
>
|
||||
{testing ? '🧪 Executing...' : '🧪 Test SHORT (REAL)'}
|
||||
</button>
|
||||
<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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user