feat: Add position scaling for strong confirmation signals

**Feature: Position Scaling**
Allows adding to existing profitable positions when high-quality signals confirm trend strength.

**Configuration (config/trading.ts):**
- enablePositionScaling: false (disabled by default - enable after testing)
- minScaleQualityScore: 75 (higher bar than initial 60)
- minProfitForScale: 0.4% (must be at/past TP1)
- maxScaleMultiplier: 2.0 (max 200% of original size)
- scaleSizePercent: 50% (add 50% of original position)
- minAdxIncrease: 5 (ADX must strengthen)
- maxPricePositionForScale: 70% (don't chase resistance)

**Validation Logic (check-risk endpoint):**
Same-direction signal triggers scaling check if enabled:
1. Quality score ≥75 (stronger than initial entry)
2. Position profitable ≥0.4% (at/past TP1)
3. ADX increased ≥5 points (trend strengthening)
4. Price position <70% (not near resistance)
5. Total size <2x original (risk management)
6. Returns 'allowed: true, reason: Position scaling' if all pass

**Execution (execute endpoint):**
- Opens additional position at scale size (50% of original)
- Updates ActiveTrade: timesScaled, totalScaleAdded, currentSize
- Tracks originalAdx from first entry for comparison
- Returns 'action: scaled' with scale details

**ActiveTrade Interface:**
Added fields:
- originalAdx?: number (for scaling validation)
- timesScaled?: number (track scaling count)
- totalScaleAdded?: number (total USD added)

**Example Scenario:**
1. LONG SOL at $176 (quality: 45, ADX: 13.4) - weak but entered
2. Price hits $176.70 (+0.4%) - at TP1
3. New LONG signal (quality: 78, ADX: 19) - strong confirmation
4. Scaling validation:  Quality 78  Profit +0.4%  ADX +5.6  Price 68%
5. Adds 50% more position at $176.70
6. Total position: 150% of original size

**Conservative Design:**
- Disabled by default (requires manual enabling)
- Only scales INTO profitable positions (never averaging down)
- Requires significant quality improvement (75 vs 60)
- Requires trend confirmation (ADX increase)
- Hard cap at 2x original size
- Won't chase near resistance levels

**Next Steps:**
1. Enable in settings: ENABLE_POSITION_SCALING=true
2. Test with small positions first
3. Monitor data: do scaled positions outperform?
4. Adjust thresholds based on results

**Safety:**
- All existing duplicate prevention logic intact
- Flip logic unchanged (still requires quality check)
- Position Manager tracks scaling state
- Can be toggled on/off without code changes
This commit is contained in:
mindesbunister
2025-11-03 15:35:33 +01:00
parent 57f0457f95
commit 8a8d4a348c
4 changed files with 233 additions and 6 deletions

View File

@@ -213,18 +213,79 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
trade => trade.symbol === driftSymbol && trade.direction !== body.direction
)
// SAFETY CHECK: Prevent multiple positions on same symbol
// Check for same direction position (scaling vs duplicate)
const sameDirectionPosition = existingTrades.find(
trade => trade.symbol === driftSymbol && trade.direction === body.direction
)
if (sameDirectionPosition) {
// Position scaling enabled - scale into existing position
if (config.enablePositionScaling) {
console.log(`📈 POSITION SCALING: Adding to existing ${body.direction} position on ${driftSymbol}`)
// Calculate scale size
const scaleSize = (positionSize * leverage) * (config.scaleSizePercent / 100)
console.log(`💰 Scaling position:`)
console.log(` Original size: $${sameDirectionPosition.positionSize}`)
console.log(` Scale size: $${scaleSize} (${config.scaleSizePercent}% of original)`)
console.log(` Leverage: ${leverage}x`)
// Open additional position
const scaleResult = await openPosition({
symbol: driftSymbol,
direction: body.direction,
sizeUSD: scaleSize,
slippageTolerance: config.slippageTolerance,
})
if (!scaleResult.success) {
console.error('❌ Failed to scale position:', scaleResult.error)
return NextResponse.json(
{
success: false,
error: 'Position scaling failed',
message: scaleResult.error,
},
{ status: 500 }
)
}
console.log(`✅ Scaled into position at $${scaleResult.fillPrice?.toFixed(4)}`)
// Update Position Manager tracking
const timesScaled = (sameDirectionPosition.timesScaled || 0) + 1
const totalScaleAdded = (sameDirectionPosition.totalScaleAdded || 0) + scaleSize
const newTotalSize = sameDirectionPosition.currentSize + (scaleResult.fillSize || 0)
// Update the trade tracking (simplified - just update the active trade object)
sameDirectionPosition.timesScaled = timesScaled
sameDirectionPosition.totalScaleAdded = totalScaleAdded
sameDirectionPosition.currentSize = newTotalSize
console.log(`📊 Position scaled: ${timesScaled}x total, $${totalScaleAdded.toFixed(2)} added`)
return NextResponse.json({
success: true,
action: 'scaled',
positionId: sameDirectionPosition.positionId,
symbol: driftSymbol,
direction: body.direction,
scalePrice: scaleResult.fillPrice,
scaleSize: scaleSize,
totalSize: newTotalSize,
timesScaled: timesScaled,
timestamp: new Date().toISOString(),
})
}
// Scaling disabled - block duplicate
console.log(`⛔ DUPLICATE POSITION BLOCKED: Already have ${body.direction} position on ${driftSymbol}`)
return NextResponse.json(
{
success: false,
error: 'Duplicate position detected',
message: `Already have an active ${body.direction} position on ${driftSymbol}. Close it first.`,
message: `Already have an active ${body.direction} position on ${driftSymbol}. Enable position scaling in settings to add to this position.`,
},
{ status: 400 }
)
@@ -366,6 +427,10 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
maxAdverseExcursion: 0,
maxFavorablePrice: entryPrice,
maxAdversePrice: entryPrice,
// Position scaling tracking
originalAdx: body.adx, // Store for scaling validation
timesScaled: 0,
totalScaleAdded: 0,
priceCheckCount: 0,
lastPrice: entryPrice,
lastUpdateTime: Date.now(),