feat: Complete Trading Bot v4 with Drift Protocol integration

Features:
- Autonomous trading system with Drift Protocol on Solana
- Real-time position monitoring with Pyth price feeds
- Dynamic stop-loss and take-profit management
- n8n workflow integration for TradingView signals
- Beautiful web UI for settings management
- REST API for trade execution and monitoring

- Next.js 15 with standalone output mode
- TypeScript with strict typing
- Docker containerization with multi-stage builds
- PostgreSQL database for trade history
- Singleton pattern for Drift client connection pooling
- BN.js for BigNumber handling (Drift SDK requirement)

- Configurable stop-loss and take-profit levels
- Breakeven trigger and profit locking
- Daily loss limits and trade cooldowns
- Slippage tolerance controls
- DRY_RUN mode for safe testing

- Real-time risk calculator
- Interactive sliders for all parameters
- Live preview of trade outcomes
- Position sizing and leverage controls
- Beautiful gradient design with Tailwind CSS

- POST /api/trading/execute - Execute trades
- POST /api/trading/close - Close positions
- GET /api/trading/positions - Monitor active trades
- GET /api/trading/check-risk - Validate trade signals
- GET /api/settings - View configuration
- POST /api/settings - Update configuration

- Fixed Borsh serialization errors (simplified order params)
- Resolved RPC rate limiting with singleton pattern
- Fixed BigInt vs BN type mismatches
- Corrected order execution flow
- Improved position state management

- Complete setup guides
- Docker deployment instructions
- n8n workflow configuration
- API reference documentation
- Risk management guidelines

- Runs on port 3001 (external), 3000 (internal)
- Uses Helius RPC for optimal performance
- Production-ready with error handling
- Health monitoring and logging
This commit is contained in:
mindesbunister
2025-10-24 14:24:36 +02:00
commit 2405bff68a
45 changed files with 15683 additions and 0 deletions

384
app/settings/page.tsx Normal file
View File

@@ -0,0 +1,384 @@
/**
* Trading Bot Settings UI
*
* Beautiful interface for managing trading parameters
*/
'use client'
import { useState, useEffect } from 'react'
interface TradingSettings {
MAX_POSITION_SIZE_USD: number
LEVERAGE: number
STOP_LOSS_PERCENT: number
TAKE_PROFIT_1_PERCENT: number
TAKE_PROFIT_2_PERCENT: number
EMERGENCY_STOP_PERCENT: number
BREAKEVEN_TRIGGER_PERCENT: number
PROFIT_LOCK_TRIGGER_PERCENT: number
PROFIT_LOCK_PERCENT: number
MAX_DAILY_DRAWDOWN: number
MAX_TRADES_PER_HOUR: number
MIN_TIME_BETWEEN_TRADES: 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 [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! Restart the 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 updateSetting = (key: keyof TradingSettings, value: any) => {
if (!settings) return
setSettings({ ...settings, [key]: value })
}
const calculateRisk = () => {
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)
const tp2Gain = settings.MAX_POSITION_SIZE_USD * settings.LEVERAGE * (settings.TAKE_PROFIT_2_PERCENT / 100)
const fullWin = tp1Gain / 2 + tp2Gain / 2 // 50% at each TP
return { maxLoss, tp1Gain, tp2Gain, 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">
<h1 className="text-4xl font-bold text-white mb-2"> Trading Bot Settings</h1>
<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 (50%)</div>
<div className="text-white text-2xl font-bold">+${(risk.tp1Gain / 2).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">TP2 Gain (50%)</div>
<div className="text-white text-2xl font-bold">+${(risk.tp2Gain / 2).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">
{/* Position Sizing */}
<Section title="💰 Position Sizing" description="Control your trade size and leverage">
<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. With 5x leverage, $50 = $250 position."
/>
<Setting
label="Leverage"
value={settings.LEVERAGE}
onChange={(v) => updateSetting('LEVERAGE', v)}
min={1}
max={20}
step={1}
description="Multiplier for your position. Higher = more profit AND more risk."
/>
</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 (%)"
value={settings.TAKE_PROFIT_1_PERCENT}
onChange={(v) => updateSetting('TAKE_PROFIT_1_PERCENT', v)}
min={0.1}
max={10}
step={0.1}
description="Close 50% of position at this profit level. Locks in early gains."
/>
<Setting
label="Take Profit 2 (%)"
value={settings.TAKE_PROFIT_2_PERCENT}
onChange={(v) => updateSetting('TAKE_PROFIT_2_PERCENT', v)}
min={0.1}
max={20}
step={0.1}
description="Close remaining 50% at this profit level. Captures larger moves."
/>
<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="Move SL to breakeven (entry price) when profit reaches this level."
/>
<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="When profit reaches this level, lock in profit by moving SL."
/>
<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="Move SL to this profit level when lock trigger is hit."
/>
</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 (seconds)"
value={settings.MIN_TIME_BETWEEN_TRADES}
onChange={(v) => updateSetting('MIN_TIME_BETWEEN_TRADES', v)}
min={0}
max={3600}
step={60}
description="Minimum wait time between trades to prevent overtrading."
/>
</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>
{/* Save Button */}
<div className="mt-8 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={loadSettings}
className="bg-slate-700 text-white font-bold py-4 px-6 rounded-lg hover:bg-slate-600 transition-all"
>
🔄 Reset
</button>
</div>
<div className="mt-4 text-center text-slate-500 text-sm">
Changes require bot restart to take effect
</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>
)
}