feat: implement dual stop system and database tracking
- Add PostgreSQL database with Prisma ORM - Trade model: tracks entry/exit, P&L, order signatures, config snapshots - PriceUpdate model: tracks price movements for drawdown analysis - SystemEvent model: logs errors and system events - DailyStats model: aggregated performance metrics - Implement dual stop loss system (enabled by default) - Soft stop (TRIGGER_LIMIT) at -1.5% to avoid wicks - Hard stop (TRIGGER_MARKET) at -2.5% to guarantee exit - Configurable via USE_DUAL_STOPS, SOFT_STOP_PERCENT, HARD_STOP_PERCENT - Backward compatible with single stop modes - Add database service layer (lib/database/trades.ts) - createTrade(): save new trades with all details - updateTradeExit(): close trades with P&L calculations - addPriceUpdate(): track price movements during trade - getTradeStats(): calculate win rate, profit factor, avg win/loss - logSystemEvent(): log errors and system events - Update execute endpoint to use dual stops and save to database - Calculate dual stop prices when enabled - Pass dual stop parameters to placeExitOrders - Save complete trade record to database after execution - Add test trade button to settings page - New /api/trading/test endpoint for executing test trades - Displays detailed results including dual stop prices - Confirmation dialog before execution - Shows entry price, position size, stops, and TX signature - Generate Prisma client in Docker build - Update DATABASE_URL for container networking
This commit is contained in:
@@ -32,6 +32,7 @@ export default function SettingsPage() {
|
||||
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(() => {
|
||||
@@ -90,6 +91,44 @@ export default function SettingsPage() {
|
||||
setRestarting(false)
|
||||
}
|
||||
|
||||
const testTrade = async () => {
|
||||
if (!confirm('⚠️ This will execute a REAL trade 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: 'SOLUSDT',
|
||||
direction: 'long',
|
||||
}),
|
||||
})
|
||||
|
||||
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: `✅ 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 })
|
||||
@@ -342,26 +381,38 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="mt-8 flex gap-4">
|
||||
<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 Button */}
|
||||
<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"
|
||||
onClick={testTrade}
|
||||
disabled={testing}
|
||||
className="w-full 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 border-2 border-orange-400"
|
||||
>
|
||||
{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
|
||||
{testing ? '🧪 Executing Test Trade...' : '🧪 Test Trade (REAL - SOL Long)'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user