feat: Add per-symbol trading controls for SOL and ETH
- Add SymbolSettings interface with enabled/positionSize/leverage fields - Implement per-symbol ENV variables (SOLANA_*, ETHEREUM_*) - Add SOL and ETH sections to settings UI with enable/disable toggles - Add symbol-specific test buttons (SOL LONG/SHORT, ETH LONG/SHORT) - Update execute and test endpoints to check symbol enabled status - Add real-time risk/reward calculator per symbol - Rename 'Position Sizing' to 'Global Fallback' for clarity - Fix position manager P&L calculation for externally closed positions - Fix zero P&L bug affecting 12 historical trades - Add SQL scripts for recalculating historical P&L data - Move archive TypeScript files to .archive to fix build Defaults: - SOL: 10 base × 10x leverage = 100 notional (profit trading) - ETH: base × 1x leverage = notional (data collection) - Global: 10 × 10x for BTC and other symbols Configuration priority: Per-symbol ENV > Market config > Global ENV > Defaults
This commit is contained in:
@@ -13,6 +13,84 @@ import { getMergedConfig } from '@/config/trading'
|
||||
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
|
||||
import { createTrade } from '@/lib/database/trades'
|
||||
|
||||
/**
|
||||
* Calculate signal quality score (same logic as check-risk endpoint)
|
||||
*/
|
||||
function calculateQualityScore(params: {
|
||||
atr?: number
|
||||
adx?: number
|
||||
rsi?: number
|
||||
volumeRatio?: number
|
||||
pricePosition?: number
|
||||
direction: 'long' | 'short'
|
||||
}): number | undefined {
|
||||
// If no metrics provided, return undefined
|
||||
if (!params.atr || params.atr === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let score = 50 // Base score
|
||||
|
||||
// ATR check
|
||||
if (params.atr < 0.6) {
|
||||
score -= 15
|
||||
} else if (params.atr > 2.5) {
|
||||
score -= 20
|
||||
} else {
|
||||
score += 10
|
||||
}
|
||||
|
||||
// ADX check
|
||||
if (params.adx && params.adx > 0) {
|
||||
if (params.adx > 25) {
|
||||
score += 15
|
||||
} else if (params.adx < 18) {
|
||||
score -= 15
|
||||
} else {
|
||||
score += 5
|
||||
}
|
||||
}
|
||||
|
||||
// RSI check
|
||||
if (params.rsi && params.rsi > 0) {
|
||||
if (params.direction === 'long') {
|
||||
if (params.rsi > 50 && params.rsi < 70) {
|
||||
score += 10
|
||||
} else if (params.rsi > 70) {
|
||||
score -= 10
|
||||
}
|
||||
} else {
|
||||
if (params.rsi < 50 && params.rsi > 30) {
|
||||
score += 10
|
||||
} else if (params.rsi < 30) {
|
||||
score -= 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volume check
|
||||
if (params.volumeRatio && params.volumeRatio > 0) {
|
||||
if (params.volumeRatio > 1.2) {
|
||||
score += 10
|
||||
} else if (params.volumeRatio < 0.8) {
|
||||
score -= 10
|
||||
}
|
||||
}
|
||||
|
||||
// Price position check
|
||||
if (params.pricePosition && params.pricePosition > 0) {
|
||||
if (params.direction === 'long' && params.pricePosition > 90) {
|
||||
score -= 15
|
||||
} else if (params.direction === 'short' && params.pricePosition < 10) {
|
||||
score -= 15
|
||||
} else {
|
||||
score += 5
|
||||
}
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
export interface ExecuteTradeRequest {
|
||||
symbol: string // TradingView symbol (e.g., 'SOLUSDT')
|
||||
direction: 'long' | 'short'
|
||||
@@ -25,7 +103,6 @@ export interface ExecuteTradeRequest {
|
||||
rsi?: number
|
||||
volumeRatio?: number
|
||||
pricePosition?: number
|
||||
qualityScore?: number // Calculated by check-risk endpoint
|
||||
}
|
||||
|
||||
export interface ExecuteTradeResponse {
|
||||
@@ -44,7 +121,6 @@ export interface ExecuteTradeResponse {
|
||||
tp2Percent?: number
|
||||
entrySlippage?: number
|
||||
timestamp?: string
|
||||
qualityScore?: number // Signal quality score (0-100)
|
||||
error?: string
|
||||
message?: string
|
||||
}
|
||||
@@ -90,6 +166,28 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
// Get trading configuration
|
||||
const config = getMergedConfig()
|
||||
|
||||
// Get symbol-specific position sizing
|
||||
const { getPositionSizeForSymbol } = await import('@/config/trading')
|
||||
const { size: positionSize, leverage, enabled } = getPositionSizeForSymbol(driftSymbol, config)
|
||||
|
||||
// Check if trading is enabled for this symbol
|
||||
if (!enabled) {
|
||||
console.log(`⛔ Trading disabled for ${driftSymbol}`)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Symbol trading disabled',
|
||||
message: `Trading is currently disabled for ${driftSymbol}. Enable it in settings.`,
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
console.log(`📐 Symbol-specific sizing for ${driftSymbol}:`)
|
||||
console.log(` Enabled: ${enabled}`)
|
||||
console.log(` Position size: $${positionSize}`)
|
||||
console.log(` Leverage: ${leverage}x`)
|
||||
|
||||
// Initialize Drift service if not already initialized
|
||||
const driftService = await initializeDriftService()
|
||||
|
||||
@@ -141,12 +239,12 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
}
|
||||
|
||||
// Calculate position size with leverage
|
||||
const positionSizeUSD = config.positionSize * config.leverage
|
||||
const positionSizeUSD = positionSize * leverage
|
||||
|
||||
console.log(`💰 Opening ${body.direction} position:`)
|
||||
console.log(` Symbol: ${driftSymbol}`)
|
||||
console.log(` Base size: $${config.positionSize}`)
|
||||
console.log(` Leverage: ${config.leverage}x`)
|
||||
console.log(` Base size: $${positionSize}`)
|
||||
console.log(` Leverage: ${leverage}x`)
|
||||
console.log(` Total position: $${positionSizeUSD}`)
|
||||
|
||||
// Open position
|
||||
@@ -246,6 +344,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
unrealizedPnL: 0,
|
||||
peakPnL: 0,
|
||||
peakPrice: entryPrice,
|
||||
// MAE/MFE tracking
|
||||
maxFavorableExcursion: 0,
|
||||
maxAdverseExcursion: 0,
|
||||
maxFavorablePrice: entryPrice,
|
||||
@@ -287,6 +386,11 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
console.error('❌ Unexpected error placing exit orders:', err)
|
||||
}
|
||||
|
||||
// Add to position manager for monitoring AFTER orders are placed
|
||||
await positionManager.addTrade(activeTrade)
|
||||
|
||||
console.log('✅ Trade added to position manager for monitoring')
|
||||
|
||||
// Create response object
|
||||
const response: ExecuteTradeResponse = {
|
||||
success: true,
|
||||
@@ -304,7 +408,6 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
tp2Percent: config.takeProfit2Percent,
|
||||
entrySlippage: openResult.slippage,
|
||||
timestamp: new Date().toISOString(),
|
||||
qualityScore: body.qualityScore, // Include quality score in response
|
||||
}
|
||||
|
||||
// Attach exit order signatures to response
|
||||
@@ -314,6 +417,16 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
|
||||
// Save trade to database
|
||||
try {
|
||||
// Calculate quality score if metrics available
|
||||
const qualityScore = calculateQualityScore({
|
||||
atr: body.atr,
|
||||
adx: body.adx,
|
||||
rsi: body.rsi,
|
||||
volumeRatio: body.volumeRatio,
|
||||
pricePosition: body.pricePosition,
|
||||
direction: body.direction,
|
||||
})
|
||||
|
||||
await createTrade({
|
||||
positionId: openResult.transactionSignature!,
|
||||
symbol: driftSymbol,
|
||||
@@ -343,20 +456,19 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
rsiAtEntry: body.rsi,
|
||||
volumeAtEntry: body.volumeRatio,
|
||||
pricePositionAtEntry: body.pricePosition,
|
||||
signalQualityScore: body.qualityScore,
|
||||
signalQualityScore: qualityScore,
|
||||
})
|
||||
|
||||
console.log('💾 Trade saved to database')
|
||||
if (qualityScore !== undefined) {
|
||||
console.log(`💾 Trade saved with quality score: ${qualityScore}/100`)
|
||||
} else {
|
||||
console.log('💾 Trade saved to database')
|
||||
}
|
||||
} catch (dbError) {
|
||||
console.error('❌ Failed to save trade to database:', dbError)
|
||||
// Don't fail the trade if database save fails
|
||||
}
|
||||
|
||||
// NOW add to position manager for monitoring (after database save)
|
||||
await positionManager.addTrade(activeTrade)
|
||||
|
||||
console.log('✅ Trade added to position manager for monitoring')
|
||||
|
||||
console.log('✅ Trade executed successfully!')
|
||||
|
||||
return NextResponse.json(response)
|
||||
|
||||
Reference in New Issue
Block a user