feat: Use TRIGGER_MARKET for stop loss (guaranteed execution)
- Changed SL from TRIGGER_LIMIT to TRIGGER_MARKET - Guarantees SL execution even during volatile moves/gaps - Added optional TRIGGER_LIMIT mode for very liquid markets - TP orders remain as LIMIT for price precision - Follows best practice: stop-market for SL, limit for TP
This commit is contained in:
@@ -52,6 +52,19 @@ export interface PlaceExitOrdersResult {
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface PlaceExitOrdersOptions {
|
||||
symbol: string
|
||||
positionSizeUSD: number
|
||||
tp1Price: number
|
||||
tp2Price: number
|
||||
stopLossPrice: number
|
||||
tp1SizePercent: number
|
||||
tp2SizePercent: number
|
||||
direction: 'long' | 'short'
|
||||
useStopLimit?: boolean // Optional: use TRIGGER_LIMIT instead of TRIGGER_MARKET for SL
|
||||
stopLimitBuffer?: number // Optional: buffer percentage for stop-limit (default 0.5%)
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a position with a market order
|
||||
*/
|
||||
@@ -171,21 +184,16 @@ export async function openPosition(
|
||||
|
||||
|
||||
/**
|
||||
* Place on-chain exit orders (reduce-only LIMIT orders) so TP/SL show up in Drift UI.
|
||||
* This places reduce-only LIMIT orders for TP1, TP2 and a stop-loss LIMIT order.
|
||||
* NOTE: For a safer, more aggressive stop you'd want a trigger-market order; here
|
||||
* we use reduce-only LIMIT orders to ensure they are visible in the UI and low-risk.
|
||||
* Place on-chain exit orders (reduce-only orders) so TP/SL show up in Drift UI.
|
||||
*
|
||||
* Stop Loss Strategy:
|
||||
* - Default: TRIGGER_MARKET (guaranteed execution, recommended for most traders)
|
||||
* - Optional: TRIGGER_LIMIT with buffer (protects against extreme wicks in liquid markets)
|
||||
*
|
||||
* Take Profit Strategy:
|
||||
* - Always uses LIMIT orders to lock in desired prices
|
||||
*/
|
||||
export async function placeExitOrders(options: {
|
||||
symbol: string
|
||||
positionSizeUSD: number
|
||||
tp1Price: number
|
||||
tp2Price: number
|
||||
stopLossPrice: number
|
||||
tp1SizePercent: number // percent of position to close at TP1 (0-100)
|
||||
tp2SizePercent: number // percent of position to close at TP2 (0-100)
|
||||
direction: 'long' | 'short'
|
||||
}): Promise<PlaceExitOrdersResult> {
|
||||
export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<PlaceExitOrdersResult> {
|
||||
try {
|
||||
console.log('🛡️ Placing exit orders on-chain:', options.symbol)
|
||||
|
||||
@@ -265,29 +273,65 @@ export async function placeExitOrders(options: {
|
||||
}
|
||||
}
|
||||
|
||||
// Place Stop-Loss as TRIGGER_LIMIT order (proper stop loss that shows on Drift UI)
|
||||
const slUSD = options.positionSizeUSD // place full-size SL
|
||||
// Place Stop-Loss order
|
||||
// Default: TRIGGER_MARKET (guaranteed execution, RECOMMENDED for most traders)
|
||||
// Optional: TRIGGER_LIMIT with buffer (only for very liquid markets to avoid extreme wicks)
|
||||
const slUSD = options.positionSizeUSD
|
||||
const slBaseAmount = usdToBase(slUSD, options.stopLossPrice)
|
||||
|
||||
if (slBaseAmount >= Math.floor(marketConfig.minOrderSize * 1e9)) {
|
||||
const useStopLimit = options.useStopLimit ?? false
|
||||
const stopLimitBuffer = options.stopLimitBuffer ?? 0.5 // default 0.5% buffer
|
||||
|
||||
if (useStopLimit) {
|
||||
// TRIGGER_LIMIT: Protects against extreme wicks but may not fill during fast moves
|
||||
const limitPriceMultiplier = options.direction === 'long'
|
||||
? (1 - stopLimitBuffer / 100) // Long: limit below trigger
|
||||
: (1 + stopLimitBuffer / 100) // Short: limit above trigger
|
||||
|
||||
const orderParams: any = {
|
||||
orderType: OrderType.TRIGGER_LIMIT, // Use TRIGGER_LIMIT (not LIMIT with trigger)
|
||||
orderType: OrderType.TRIGGER_LIMIT,
|
||||
marketIndex: marketConfig.driftMarketIndex,
|
||||
direction: orderDirection,
|
||||
baseAssetAmount: new BN(slBaseAmount),
|
||||
triggerPrice: new BN(Math.floor(options.stopLossPrice * 1e6)), // trigger price
|
||||
price: new BN(Math.floor(options.stopLossPrice * 0.995 * 1e6)), // limit price slightly lower (0.5% buffer)
|
||||
triggerPrice: new BN(Math.floor(options.stopLossPrice * 1e6)),
|
||||
price: new BN(Math.floor(options.stopLossPrice * limitPriceMultiplier * 1e6)),
|
||||
triggerCondition: options.direction === 'long'
|
||||
? OrderTriggerCondition.BELOW // Long: trigger when price drops below
|
||||
: OrderTriggerCondition.ABOVE, // Short: trigger when price rises above
|
||||
? OrderTriggerCondition.BELOW
|
||||
: OrderTriggerCondition.ABOVE,
|
||||
reduceOnly: true,
|
||||
}
|
||||
|
||||
console.log('🚧 Placing SL trigger-limit order (reduce-only)...')
|
||||
console.log(`🛡️ Placing SL as TRIGGER_LIMIT (${stopLimitBuffer}% buffer)...`)
|
||||
console.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`)
|
||||
console.log(` Limit price: $${(options.stopLossPrice * 0.995).toFixed(4)}`)
|
||||
console.log(` Limit: $${(options.stopLossPrice * limitPriceMultiplier).toFixed(4)}`)
|
||||
console.log(` ⚠️ May not fill during fast moves - use for liquid markets only!`)
|
||||
|
||||
const sig = await (driftClient as any).placePerpOrder(orderParams)
|
||||
console.log('✅ SL trigger-limit order placed:', sig)
|
||||
signatures.push(sig)
|
||||
} else {
|
||||
// TRIGGER_MARKET: Guaranteed execution (RECOMMENDED)
|
||||
const orderParams: any = {
|
||||
orderType: OrderType.TRIGGER_MARKET,
|
||||
marketIndex: marketConfig.driftMarketIndex,
|
||||
direction: orderDirection,
|
||||
baseAssetAmount: new BN(slBaseAmount),
|
||||
triggerPrice: new BN(Math.floor(options.stopLossPrice * 1e6)),
|
||||
triggerCondition: options.direction === 'long'
|
||||
? OrderTriggerCondition.BELOW
|
||||
: OrderTriggerCondition.ABOVE,
|
||||
reduceOnly: true,
|
||||
}
|
||||
|
||||
console.log(`🛡️ Placing SL as TRIGGER_MARKET (guaranteed execution - RECOMMENDED)...`)
|
||||
console.log(` Trigger: ${options.direction === 'long' ? 'BELOW' : 'ABOVE'} $${options.stopLossPrice.toFixed(4)}`)
|
||||
console.log(` ✅ Will execute at market price when triggered (may slip but WILL fill)`)
|
||||
|
||||
const sig = await (driftClient as any).placePerpOrder(orderParams)
|
||||
console.log('✅ SL trigger-market order placed:', sig)
|
||||
signatures.push(sig)
|
||||
}
|
||||
} else {
|
||||
console.log('⚠️ SL size below market min, skipping on-chain SL')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user