Phase 2: Market context capture at entry
- Added getFundingRate() method to DriftService - Capture expectedEntryPrice from oracle before order execution - Capture fundingRateAtEntry from Drift Protocol - Save market context fields to database (expectedEntryPrice, fundingRateAtEntry) - Calculate entry slippage percentage in createTrade() - Fixed template literal syntax errors in execute endpoint Database fields populated: - expectedEntryPrice: Oracle price before order - entrySlippagePct: Calculated from entrySlippage - fundingRateAtEntry: Current funding rate from Drift Next: Phase 3 (analytics API) or test market context on next trade
This commit is contained in:
@@ -94,7 +94,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
{
|
{
|
||||||
success: false,
|
success: false,
|
||||||
error: 'Insufficient collateral',
|
error: 'Insufficient collateral',
|
||||||
message: `Free collateral: $${health.freeCollateral.toFixed(2)}`,
|
message: 'Free collateral: $' + health.freeCollateral.toFixed(2),
|
||||||
},
|
},
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
)
|
)
|
||||||
@@ -122,7 +122,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
console.error('❌ Failed to close opposite position:', closeResult.error)
|
console.error('❌ Failed to close opposite position:', closeResult.error)
|
||||||
// Continue anyway - we'll try to open the new position
|
// Continue anyway - we'll try to open the new position
|
||||||
} else {
|
} else {
|
||||||
console.log(`✅ Closed ${oppositePosition.direction} position at $${closeResult.closePrice?.toFixed(4)} (P&L: $${closeResult.realizedPnL?.toFixed(2)})`)
|
console.log('✅ Closed ' + oppositePosition.direction + ' position at $' + closeResult.closePrice?.toFixed(4) + ' (P&L: $' + closeResult.realizedPnL?.toFixed(2) + ')')
|
||||||
|
|
||||||
// Position Manager will handle cleanup (including order cancellation)
|
// Position Manager will handle cleanup (including order cancellation)
|
||||||
// The executeExit method already removes the trade and updates database
|
// The executeExit method already removes the trade and updates database
|
||||||
@@ -135,11 +135,33 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
// Calculate position size with leverage
|
// Calculate position size with leverage
|
||||||
const positionSizeUSD = config.positionSize * config.leverage
|
const positionSizeUSD = config.positionSize * config.leverage
|
||||||
|
|
||||||
console.log(`💰 Opening ${body.direction} position:`)
|
console.log('💰 Opening ' + body.direction + ' position:')
|
||||||
console.log(` Symbol: ${driftSymbol}`)
|
console.log(' Symbol: ' + driftSymbol)
|
||||||
console.log(` Base size: $${config.positionSize}`)
|
console.log(' Base size: $' + config.positionSize)
|
||||||
console.log(` Leverage: ${config.leverage}x`)
|
console.log(' Leverage: ' + config.leverage + 'x')
|
||||||
console.log(` Total position: $${positionSizeUSD}`)
|
console.log(' Total position: $' + positionSizeUSD)
|
||||||
|
|
||||||
|
// Capture market context BEFORE opening position
|
||||||
|
const { getMarketConfig } = await import('@/config/trading')
|
||||||
|
const marketConfig = getMarketConfig(driftSymbol)
|
||||||
|
|
||||||
|
let expectedEntryPrice: number | undefined
|
||||||
|
let fundingRateAtEntry: number | undefined
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get expected entry price from oracle
|
||||||
|
expectedEntryPrice = await driftService.getOraclePrice(marketConfig.driftMarketIndex)
|
||||||
|
console.log('📊 Expected entry price: $' + expectedEntryPrice.toFixed(4))
|
||||||
|
|
||||||
|
// Get funding rate
|
||||||
|
fundingRateAtEntry = await driftService.getFundingRate(marketConfig.driftMarketIndex) || undefined
|
||||||
|
if (fundingRateAtEntry) {
|
||||||
|
console.log('💸 Funding rate: ' + (fundingRateAtEntry * 100).toFixed(4) + '%')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Failed to capture market context:', error)
|
||||||
|
// Don't fail the trade if market context capture fails
|
||||||
|
}
|
||||||
|
|
||||||
// Open position
|
// Open position
|
||||||
const openResult = await openPosition({
|
const openResult = await openPosition({
|
||||||
@@ -184,9 +206,9 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
config.hardStopPercent,
|
config.hardStopPercent,
|
||||||
body.direction
|
body.direction
|
||||||
)
|
)
|
||||||
console.log('🛡️🛡️ Dual stop system enabled:')
|
console.log('🛡️ Dual stop system enabled:')
|
||||||
console.log(` Soft stop: $${softStopPrice.toFixed(4)} (${config.softStopPercent}%)`)
|
console.log(' Soft stop: $' + softStopPrice.toFixed(4) + ' (' + config.softStopPercent + '%)')
|
||||||
console.log(` Hard stop: $${hardStopPrice.toFixed(4)} (${config.hardStopPercent}%)`)
|
console.log(' Hard stop: $' + hardStopPrice.toFixed(4) + ' (' + config.hardStopPercent + '%)')
|
||||||
}
|
}
|
||||||
|
|
||||||
const tp1Price = calculatePrice(
|
const tp1Price = calculatePrice(
|
||||||
@@ -202,10 +224,10 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
)
|
)
|
||||||
|
|
||||||
console.log('📊 Trade targets:')
|
console.log('📊 Trade targets:')
|
||||||
console.log(` Entry: $${entryPrice.toFixed(4)}`)
|
console.log(' Entry: $' + entryPrice.toFixed(4))
|
||||||
console.log(` SL: $${stopLossPrice.toFixed(4)} (${config.stopLossPercent}%)`)
|
console.log(' SL: $' + stopLossPrice.toFixed(4) + ' (' + config.stopLossPercent + '%)')
|
||||||
console.log(` TP1: $${tp1Price.toFixed(4)} (${config.takeProfit1Percent}%)`)
|
console.log(' TP1: $' + tp1Price.toFixed(4) + ' (' + config.takeProfit1Percent + '%)')
|
||||||
console.log(` TP2: $${tp2Price.toFixed(4)} (${config.takeProfit2Percent}%)`)
|
console.log(' TP2: $' + tp2Price.toFixed(4) + ' (' + config.takeProfit2Percent + '%)')
|
||||||
|
|
||||||
// Calculate emergency stop
|
// Calculate emergency stop
|
||||||
const emergencyStopPrice = calculatePrice(
|
const emergencyStopPrice = calculatePrice(
|
||||||
@@ -316,6 +338,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
symbol: driftSymbol,
|
symbol: driftSymbol,
|
||||||
direction: body.direction,
|
direction: body.direction,
|
||||||
entryPrice,
|
entryPrice,
|
||||||
|
entrySlippage: openResult.slippage,
|
||||||
positionSizeUSD: positionSizeUSD,
|
positionSizeUSD: positionSizeUSD,
|
||||||
leverage: config.leverage,
|
leverage: config.leverage,
|
||||||
stopLossPrice,
|
stopLossPrice,
|
||||||
@@ -334,12 +357,15 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
hardStopPrice,
|
hardStopPrice,
|
||||||
signalStrength: body.signalStrength,
|
signalStrength: body.signalStrength,
|
||||||
timeframe: body.timeframe,
|
timeframe: body.timeframe,
|
||||||
|
// Market context
|
||||||
|
expectedEntryPrice,
|
||||||
|
fundingRateAtEntry,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('💾 Trade saved to database')
|
console.log('💾 Trade saved to database')
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
console.error('❌ Failed to save trade to database:', dbError)
|
console.error('❌ Failed to save trade to database:', dbError)
|
||||||
// Don't fail the trade if database save fails
|
// Don't fail the database save fails
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ Trade executed successfully!')
|
console.log('✅ Trade executed successfully!')
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ export interface CreateTradeParams {
|
|||||||
signalStrength?: string
|
signalStrength?: string
|
||||||
timeframe?: string
|
timeframe?: string
|
||||||
isTestTrade?: boolean
|
isTestTrade?: boolean
|
||||||
|
// Market context fields
|
||||||
|
expectedEntryPrice?: number
|
||||||
|
fundingRateAtEntry?: number
|
||||||
|
atrAtEntry?: number
|
||||||
|
adxAtEntry?: number
|
||||||
|
volumeAtEntry?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateTradeStateParams {
|
export interface UpdateTradeStateParams {
|
||||||
@@ -73,13 +79,16 @@ export interface UpdateTradeExitParams {
|
|||||||
maxGain?: number
|
maxGain?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new trade record
|
|
||||||
*/
|
|
||||||
export async function createTrade(params: CreateTradeParams) {
|
export async function createTrade(params: CreateTradeParams) {
|
||||||
const prisma = getPrismaClient()
|
const prisma = getPrismaClient()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Calculate entry slippage if expected price provided
|
||||||
|
let entrySlippagePct: number | undefined
|
||||||
|
if (params.expectedEntryPrice && params.entrySlippage !== undefined) {
|
||||||
|
entrySlippagePct = params.entrySlippage
|
||||||
|
}
|
||||||
|
|
||||||
const trade = await prisma.trade.create({
|
const trade = await prisma.trade.create({
|
||||||
data: {
|
data: {
|
||||||
positionId: params.positionId,
|
positionId: params.positionId,
|
||||||
@@ -109,6 +118,13 @@ export async function createTrade(params: CreateTradeParams) {
|
|||||||
timeframe: params.timeframe,
|
timeframe: params.timeframe,
|
||||||
status: 'open',
|
status: 'open',
|
||||||
isTestTrade: params.isTestTrade || false,
|
isTestTrade: params.isTestTrade || false,
|
||||||
|
// Market context
|
||||||
|
expectedEntryPrice: params.expectedEntryPrice,
|
||||||
|
entrySlippagePct: entrySlippagePct,
|
||||||
|
fundingRateAtEntry: params.fundingRateAtEntry,
|
||||||
|
atrAtEntry: params.atrAtEntry,
|
||||||
|
adxAtEntry: params.adxAtEntry,
|
||||||
|
volumeAtEntry: params.volumeAtEntry,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -233,6 +233,31 @@ export class DriftService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get funding rate for a perpetual market
|
||||||
|
* Returns funding rate as percentage (e.g., 0.01 = 1% per 8 hours)
|
||||||
|
*/
|
||||||
|
async getFundingRate(marketIndex: number): Promise<number | null> {
|
||||||
|
this.ensureInitialized()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const perpMarketAccount = this.driftClient!.getPerpMarketAccount(marketIndex)
|
||||||
|
if (!perpMarketAccount) {
|
||||||
|
console.warn(`⚠️ No perp market account found for index ${marketIndex}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funding rate is stored as a number with 9 decimals (1e9)
|
||||||
|
// Convert to percentage
|
||||||
|
const fundingRate = Number(perpMarketAccount.amm.lastFundingRate) / 1e9
|
||||||
|
|
||||||
|
return fundingRate
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Failed to get funding rate for market ${marketIndex}:`, error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get account health (margin ratio)
|
* Get account health (margin ratio)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user