Add trailing stop feature for runner position + fix settings persistence

- Implemented trailing stop logic in Position Manager for remaining position after TP2
- Added new ActiveTrade fields: tp2Hit, trailingStopActive, peakPrice
- New config settings: useTrailingStop, trailingStopPercent, trailingStopActivation
- Added trailing stop UI section in settings page with explanations
- Fixed env file parsing regex to support numbers in variable names (A-Z0-9_)
- Settings now persist correctly across container restarts
- Added back arrow navigation on settings page
- Updated all API endpoints and test files with new fields
- Trailing stop activates when runner reaches configured profit level
- SL trails below peak price by configurable percentage
This commit is contained in:
mindesbunister
2025-10-27 12:11:10 +01:00
parent d3c04ea9c9
commit 4ae9c38ad8
8 changed files with 193 additions and 22 deletions

View File

@@ -19,7 +19,7 @@ function parseEnvFile(): Record<string, string> {
// Skip comments and empty lines
if (line.trim().startsWith('#') || !line.trim()) return
const match = line.match(/^([A-Z_]+)=(.*)$/)
const match = line.match(/^([A-Z0-9_]+)=(.*)$/)
if (match) {
env[match[1]] = match[2]
}
@@ -73,6 +73,9 @@ export async function GET() {
BREAKEVEN_TRIGGER_PERCENT: parseFloat(env.BREAKEVEN_TRIGGER_PERCENT || '0.4'),
PROFIT_LOCK_TRIGGER_PERCENT: parseFloat(env.PROFIT_LOCK_TRIGGER_PERCENT || '1.0'),
PROFIT_LOCK_PERCENT: parseFloat(env.PROFIT_LOCK_PERCENT || '0.4'),
USE_TRAILING_STOP: env.USE_TRAILING_STOP === 'true' || env.USE_TRAILING_STOP === undefined,
TRAILING_STOP_PERCENT: parseFloat(env.TRAILING_STOP_PERCENT || '0.3'),
TRAILING_STOP_ACTIVATION: parseFloat(env.TRAILING_STOP_ACTIVATION || '0.5'),
MAX_DAILY_DRAWDOWN: parseFloat(env.MAX_DAILY_DRAWDOWN || '-50'),
MAX_TRADES_PER_HOUR: parseInt(env.MAX_TRADES_PER_HOUR || '6'),
MIN_TIME_BETWEEN_TRADES: parseInt(env.MIN_TIME_BETWEEN_TRADES || '600'),
@@ -106,6 +109,9 @@ export async function POST(request: NextRequest) {
BREAKEVEN_TRIGGER_PERCENT: settings.BREAKEVEN_TRIGGER_PERCENT.toString(),
PROFIT_LOCK_TRIGGER_PERCENT: settings.PROFIT_LOCK_TRIGGER_PERCENT.toString(),
PROFIT_LOCK_PERCENT: settings.PROFIT_LOCK_PERCENT.toString(),
USE_TRAILING_STOP: settings.USE_TRAILING_STOP.toString(),
TRAILING_STOP_PERCENT: settings.TRAILING_STOP_PERCENT.toString(),
TRAILING_STOP_ACTIVATION: settings.TRAILING_STOP_ACTIVATION.toString(),
MAX_DAILY_DRAWDOWN: settings.MAX_DAILY_DRAWDOWN.toString(),
MAX_TRADES_PER_HOUR: settings.MAX_TRADES_PER_HOUR.toString(),
MIN_TIME_BETWEEN_TRADES: settings.MIN_TIME_BETWEEN_TRADES.toString(),

View File

@@ -198,11 +198,14 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
emergencyStopPrice,
currentSize: positionSizeUSD,
tp1Hit: false,
tp2Hit: false,
slMovedToBreakeven: false,
slMovedToProfit: false,
trailingStopActive: false,
realizedPnL: 0,
unrealizedPnL: 0,
peakPnL: 0,
peakPrice: entryPrice,
priceCheckCount: 0,
lastPrice: entryPrice,
lastUpdateTime: Date.now(),

View File

@@ -170,11 +170,14 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
emergencyStopPrice,
currentSize: positionSizeUSD,
tp1Hit: false,
tp2Hit: false,
slMovedToBreakeven: false,
slMovedToProfit: false,
trailingStopActive: false,
realizedPnL: 0,
unrealizedPnL: 0,
peakPnL: 0,
peakPrice: entryPrice,
priceCheckCount: 0,
lastPrice: entryPrice,
lastUpdateTime: Date.now(),

View File

@@ -169,11 +169,14 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
emergencyStopPrice,
currentSize: positionSizeUSD,
tp1Hit: false,
tp2Hit: false,
slMovedToBreakeven: false,
slMovedToProfit: false,
trailingStopActive: false,
realizedPnL: 0,
unrealizedPnL: 0,
peakPnL: 0,
peakPrice: entryPrice,
priceCheckCount: 0,
lastPrice: entryPrice,
lastUpdateTime: Date.now(),

View File

@@ -20,6 +20,9 @@ interface TradingSettings {
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
MAX_DAILY_DRAWDOWN: number
MAX_TRADES_PER_HOUR: number
MIN_TIME_BETWEEN_TRADES: number
@@ -161,7 +164,16 @@ export default function SettingsPage() {
<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>
<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>
@@ -293,7 +305,7 @@ export default function SettingsPage() {
min={0}
max={5}
step={0.1}
description="Move SL to breakeven (entry price) when profit reaches this level."
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 (%)"
@@ -302,7 +314,7 @@ export default function SettingsPage() {
min={0}
max={10}
step={0.1}
description="When profit reaches this level, lock in profit by moving SL."
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 (%)"
@@ -311,7 +323,44 @@ export default function SettingsPage() {
min={0}
max={5}
step={0.1}
description="Move SL to this profit level when lock trigger is hit."
description="When Profit Lock Trigger hits, move SL to this profit level. Should be > Breakeven Trigger."
/>
</Section>
{/* Trailing Stop */}
<Section title="🏃 Trailing Stop (Runner)" description="Let a small portion run with dynamic stop loss">
<div className="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
<p className="text-sm text-blue-400">
After TP2 closes, the remaining position (your "runner") can use a trailing stop loss that follows price.
This lets you capture big moves while protecting profit.
</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 runner position after TP2. 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="Runner must reach this profit % before trailing stop activates. Prevents premature stops. Example: 0.5% = wait until runner is +0.5% profit."
/>
</Section>