feat: Add position scaling controls to settings UI
**UI Updates (settings page):** Added new '📈 Position Scaling' section with: - Enable/disable toggle (defaults to OFF) - Min quality score slider (60-90, default 75) - Min profit to scale (0-2%, default 0.4%) - Scale size percent (10-100%, default 50%) - Max position multiplier (1-3x, default 2.0x) - Min ADX increase (0-15, default 5) - Max price position for scale (50-90%, default 70%) **Visual Feedback:** - Purple-themed section with warning banner - Real-time risk calculator showing: * Original position size (SOL example) * Scale addition amount * Total after 1 scale * Maximum possible position size - Dynamic descriptions explain each parameter - Warning: 'DISABLED by default' with red indicator **API Updates:** Extended /api/settings GET/POST to handle 7 new fields: - ENABLE_POSITION_SCALING - MIN_SCALE_QUALITY_SCORE - MIN_PROFIT_FOR_SCALE - MAX_SCALE_MULTIPLIER - SCALE_SIZE_PERCENT - MIN_ADX_INCREASE - MAX_PRICE_POSITION_FOR_SCALE **User Flow:** 1. Navigate to http://localhost:3001/settings 2. Scroll to '📈 Position Scaling' section 3. Toggle 'Enable Position Scaling' to 1 4. Adjust thresholds (defaults are conservative) 5. See live calculation of scaling impact 6. Click 'Save Settings' 7. Click 'Restart Bot' to apply **Safety:** - Feature OFF by default (requires explicit opt-in) - Warning banner explains scaling behavior - Risk calculator shows maximum exposure - Conservative defaults prevent aggressive scaling - All parameters adjustable via sliders **Example:** With defaults (SOL $210×10x = $2100): - Scale adds: $1050 (50% of $2100) - Total after 1 scale: $3150 - Max position (2x): $4200 User can now enable and configure position scaling without touching .env file!
This commit is contained in:
@@ -87,6 +87,17 @@ export async function GET() {
|
|||||||
USE_TRAILING_STOP: env.USE_TRAILING_STOP === 'true' || env.USE_TRAILING_STOP === undefined,
|
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_PERCENT: parseFloat(env.TRAILING_STOP_PERCENT || '0.3'),
|
||||||
TRAILING_STOP_ACTIVATION: parseFloat(env.TRAILING_STOP_ACTIVATION || '0.5'),
|
TRAILING_STOP_ACTIVATION: parseFloat(env.TRAILING_STOP_ACTIVATION || '0.5'),
|
||||||
|
|
||||||
|
// Position Scaling
|
||||||
|
ENABLE_POSITION_SCALING: env.ENABLE_POSITION_SCALING === 'true',
|
||||||
|
MIN_SCALE_QUALITY_SCORE: parseInt(env.MIN_SCALE_QUALITY_SCORE || '75'),
|
||||||
|
MIN_PROFIT_FOR_SCALE: parseFloat(env.MIN_PROFIT_FOR_SCALE || '0.4'),
|
||||||
|
MAX_SCALE_MULTIPLIER: parseFloat(env.MAX_SCALE_MULTIPLIER || '2.0'),
|
||||||
|
SCALE_SIZE_PERCENT: parseFloat(env.SCALE_SIZE_PERCENT || '50'),
|
||||||
|
MIN_ADX_INCREASE: parseFloat(env.MIN_ADX_INCREASE || '5'),
|
||||||
|
MAX_PRICE_POSITION_FOR_SCALE: parseFloat(env.MAX_PRICE_POSITION_FOR_SCALE || '70'),
|
||||||
|
|
||||||
|
// Safety
|
||||||
MAX_DAILY_DRAWDOWN: parseFloat(env.MAX_DAILY_DRAWDOWN || '-50'),
|
MAX_DAILY_DRAWDOWN: parseFloat(env.MAX_DAILY_DRAWDOWN || '-50'),
|
||||||
MAX_TRADES_PER_HOUR: parseInt(env.MAX_TRADES_PER_HOUR || '6'),
|
MAX_TRADES_PER_HOUR: parseInt(env.MAX_TRADES_PER_HOUR || '6'),
|
||||||
MIN_TIME_BETWEEN_TRADES: parseInt(env.MIN_TIME_BETWEEN_TRADES || '600'),
|
MIN_TIME_BETWEEN_TRADES: parseInt(env.MIN_TIME_BETWEEN_TRADES || '600'),
|
||||||
@@ -112,6 +123,16 @@ export async function POST(request: NextRequest) {
|
|||||||
const updates = {
|
const updates = {
|
||||||
MAX_POSITION_SIZE_USD: settings.MAX_POSITION_SIZE_USD.toString(),
|
MAX_POSITION_SIZE_USD: settings.MAX_POSITION_SIZE_USD.toString(),
|
||||||
LEVERAGE: settings.LEVERAGE.toString(),
|
LEVERAGE: settings.LEVERAGE.toString(),
|
||||||
|
|
||||||
|
// Per-symbol settings
|
||||||
|
SOLANA_ENABLED: settings.SOLANA_ENABLED.toString(),
|
||||||
|
SOLANA_POSITION_SIZE: settings.SOLANA_POSITION_SIZE.toString(),
|
||||||
|
SOLANA_LEVERAGE: settings.SOLANA_LEVERAGE.toString(),
|
||||||
|
ETHEREUM_ENABLED: settings.ETHEREUM_ENABLED.toString(),
|
||||||
|
ETHEREUM_POSITION_SIZE: settings.ETHEREUM_POSITION_SIZE.toString(),
|
||||||
|
ETHEREUM_LEVERAGE: settings.ETHEREUM_LEVERAGE.toString(),
|
||||||
|
|
||||||
|
// Risk management
|
||||||
STOP_LOSS_PERCENT: settings.STOP_LOSS_PERCENT.toString(),
|
STOP_LOSS_PERCENT: settings.STOP_LOSS_PERCENT.toString(),
|
||||||
TAKE_PROFIT_1_PERCENT: settings.TAKE_PROFIT_1_PERCENT.toString(),
|
TAKE_PROFIT_1_PERCENT: settings.TAKE_PROFIT_1_PERCENT.toString(),
|
||||||
TAKE_PROFIT_1_SIZE_PERCENT: settings.TAKE_PROFIT_1_SIZE_PERCENT.toString(),
|
TAKE_PROFIT_1_SIZE_PERCENT: settings.TAKE_PROFIT_1_SIZE_PERCENT.toString(),
|
||||||
@@ -124,6 +145,17 @@ export async function POST(request: NextRequest) {
|
|||||||
USE_TRAILING_STOP: settings.USE_TRAILING_STOP.toString(),
|
USE_TRAILING_STOP: settings.USE_TRAILING_STOP.toString(),
|
||||||
TRAILING_STOP_PERCENT: settings.TRAILING_STOP_PERCENT.toString(),
|
TRAILING_STOP_PERCENT: settings.TRAILING_STOP_PERCENT.toString(),
|
||||||
TRAILING_STOP_ACTIVATION: settings.TRAILING_STOP_ACTIVATION.toString(),
|
TRAILING_STOP_ACTIVATION: settings.TRAILING_STOP_ACTIVATION.toString(),
|
||||||
|
|
||||||
|
// Position Scaling
|
||||||
|
ENABLE_POSITION_SCALING: settings.ENABLE_POSITION_SCALING.toString(),
|
||||||
|
MIN_SCALE_QUALITY_SCORE: settings.MIN_SCALE_QUALITY_SCORE.toString(),
|
||||||
|
MIN_PROFIT_FOR_SCALE: settings.MIN_PROFIT_FOR_SCALE.toString(),
|
||||||
|
MAX_SCALE_MULTIPLIER: settings.MAX_SCALE_MULTIPLIER.toString(),
|
||||||
|
SCALE_SIZE_PERCENT: settings.SCALE_SIZE_PERCENT.toString(),
|
||||||
|
MIN_ADX_INCREASE: settings.MIN_ADX_INCREASE.toString(),
|
||||||
|
MAX_PRICE_POSITION_FOR_SCALE: settings.MAX_PRICE_POSITION_FOR_SCALE.toString(),
|
||||||
|
|
||||||
|
// Safety
|
||||||
MAX_DAILY_DRAWDOWN: settings.MAX_DAILY_DRAWDOWN.toString(),
|
MAX_DAILY_DRAWDOWN: settings.MAX_DAILY_DRAWDOWN.toString(),
|
||||||
MAX_TRADES_PER_HOUR: settings.MAX_TRADES_PER_HOUR.toString(),
|
MAX_TRADES_PER_HOUR: settings.MAX_TRADES_PER_HOUR.toString(),
|
||||||
MIN_TIME_BETWEEN_TRADES: settings.MIN_TIME_BETWEEN_TRADES.toString(),
|
MIN_TIME_BETWEEN_TRADES: settings.MIN_TIME_BETWEEN_TRADES.toString(),
|
||||||
|
|||||||
@@ -34,6 +34,17 @@ interface TradingSettings {
|
|||||||
USE_TRAILING_STOP: boolean
|
USE_TRAILING_STOP: boolean
|
||||||
TRAILING_STOP_PERCENT: number
|
TRAILING_STOP_PERCENT: number
|
||||||
TRAILING_STOP_ACTIVATION: number
|
TRAILING_STOP_ACTIVATION: 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
|
||||||
|
|
||||||
|
// Safety
|
||||||
MAX_DAILY_DRAWDOWN: number
|
MAX_DAILY_DRAWDOWN: number
|
||||||
MAX_TRADES_PER_HOUR: number
|
MAX_TRADES_PER_HOUR: number
|
||||||
MIN_TIME_BETWEEN_TRADES: number
|
MIN_TIME_BETWEEN_TRADES: number
|
||||||
@@ -520,6 +531,107 @@ export default function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</Section>
|
</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 */}
|
{/* Trade Limits */}
|
||||||
<Section title="⚠️ Safety Limits" description="Prevent overtrading and excessive losses">
|
<Section title="⚠️ Safety Limits" description="Prevent overtrading and excessive losses">
|
||||||
<Setting
|
<Setting
|
||||||
|
|||||||
Reference in New Issue
Block a user