diff --git a/components/lib/SlManagerComponent.tsx b/components/lib/SlManagerComponent.tsx index 8375a385..feec1f56 100644 --- a/components/lib/SlManagerComponent.tsx +++ b/components/lib/SlManagerComponent.tsx @@ -7,13 +7,16 @@ import { RadioGroup, Radio, Grid, - TextField + TextField, + Box } from '@material-ui/core' import { COMBINED_SL_EXIT_STRATEGY, SL_ORDER_TYPE } from '../../types/plans' import { COMBINED_SL_EXIT_STRATEGY_LABEL, EXIT_STRATEGIES, - EXIT_STRATEGIES_DETAILS + EXIT_STRATEGIES_DETAILS, + POSITION_STATE, + POSITION_STATE_LABEL } from '../../lib/constants' const SlManagerComponent = ({ state, onChange, exitStrategies }) => { @@ -52,33 +55,56 @@ const SlManagerComponent = ({ state, onChange, exitStrategies }) => { When Combined trailing SL triggers - - onChange({ - combinedExitStrategy: e.target - .value as COMBINED_SL_EXIT_STRATEGY - }) - } - > - {[ - COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, - COMBINED_SL_EXIT_STRATEGY.EXIT_LOSING - ].map(combinedExitStrategy => ( - } - label={ - - {COMBINED_SL_EXIT_STRATEGY_LABEL[combinedExitStrategy]} - + {[POSITION_STATE.WINNING, POSITION_STATE.LOSING].map(item => ( + + + {POSITION_STATE_LABEL[item]} + + - ))} - + style={{ paddingLeft: '4px' }} + onChange={e => + onChange( + item === POSITION_STATE.WINNING + ? { + combinedExitStrategyWinning: e.target + .value as COMBINED_SL_EXIT_STRATEGY + } + : { + combinedExitStrategyLosing: e.target + .value as COMBINED_SL_EXIT_STRATEGY + } + ) + } + > + {[ + COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, + COMBINED_SL_EXIT_STRATEGY.EXIT_LOSING + ].map(combinedExitStrategy => ( + } + label={ + + { + COMBINED_SL_EXIT_STRATEGY_LABEL[ + combinedExitStrategy + ] + } + + } + /> + ))} + + + ))} diff --git a/lib/browserUtils.ts b/lib/browserUtils.ts index 0152c2db..8d37d577 100644 --- a/lib/browserUtils.ts +++ b/lib/browserUtils.ts @@ -117,10 +117,6 @@ export const formatFormDataForApi = ({ strategy: string data: AvailablePlansConfig }): SUPPORTED_TRADE_CONFIG | null => { - const getOnSquareOffSetAborted = ({ exitStrategy, combinedExitStrategy }) => - exitStrategy === EXIT_STRATEGIES.MULTI_LEG_PREMIUM_THRESHOLD && - combinedExitStrategy === COMBINED_SL_EXIT_STRATEGY.EXIT_ALL - switch (strategy) { case STRATEGIES.DIRECTIONAL_OPTION_SELLING: { const { @@ -169,7 +165,9 @@ export const formatFormDataForApi = ({ trailEveryPercentageChangeValue, trailingSlPercent, exitStrategy, - combinedExitStrategy + combinedExitStrategy, + combinedExitStrategyLosing, + combinedExitStrategyWinning } = data as ATM_STRADDLE_CONFIG const apiProps: ATM_STRADDLE_TRADE = { @@ -180,12 +178,10 @@ export const formatFormDataForApi = ({ trailEveryPercentageChangeValue ), trailingSlPercent: Number(trailingSlPercent), - onSquareOffSetAborted: getOnSquareOffSetAborted({ - exitStrategy, - combinedExitStrategy - }), maxSkewPercent: Number(maxSkewPercent), thresholdSkewPercent: Number(thresholdSkewPercent), + combinedExitStrategyLosing, + combinedExitStrategyWinning, ...getSchedulingApiProps({ isAutoSquareOffEnabled, squareOffTime, @@ -212,7 +208,9 @@ export const formatFormDataForApi = ({ trailingSlPercent, exitStrategy, expireIfUnsuccessfulInMins, - combinedExitStrategy + combinedExitStrategy, + combinedExitStrategyLosing, + combinedExitStrategyWinning } = data as ATM_STRANGLE_CONFIG const apiProps: ATM_STRANGLE_TRADE = { @@ -223,11 +221,9 @@ export const formatFormDataForApi = ({ trailEveryPercentageChangeValue ), trailingSlPercent: Number(trailingSlPercent), - onSquareOffSetAborted: getOnSquareOffSetAborted({ - exitStrategy, - combinedExitStrategy - }), inverted: Boolean(inverted), + combinedExitStrategyLosing, + combinedExitStrategyWinning, ...getSchedulingApiProps({ isAutoSquareOffEnabled, squareOffTime, diff --git a/lib/constants.ts b/lib/constants.ts index 0cade471..1a997ed4 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -87,6 +87,16 @@ export enum ANCILLARY_TASKS { CLEANUP_COMPLETED_JOBS = 'CLEANUP_COMPLETED_JOBS' } +export enum POSITION_STATE { + WINNING = 'WINNING', + LOSING = 'LOSING' +} + +export const POSITION_STATE_LABEL = { + [POSITION_STATE.WINNING]: 'If winning', + [POSITION_STATE.LOSING]: 'If losing' +} + export const COMBINED_SL_EXIT_STRATEGY_LABEL = { [COMBINED_SL_EXIT_STRATEGY.EXIT_ALL]: 'Exit all legs', [COMBINED_SL_EXIT_STRATEGY.EXIT_LOSING]: @@ -133,6 +143,8 @@ export const STRATEGIES_DETAILS = { slOrderType: SL_ORDER_TYPE.SLM, slLimitPricePercent: 1, combinedExitStrategy: COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, + combinedExitStrategyWinning: COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, + combinedExitStrategyLosing: COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, rollback: { onBrokenHedgeOrders: false, onBrokenPrimaryOrders: false, @@ -169,6 +181,8 @@ export const STRATEGIES_DETAILS = { slOrderType: SL_ORDER_TYPE.SLM, slLimitPricePercent: 1, combinedExitStrategy: COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, + combinedExitStrategyWinning: COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, + combinedExitStrategyLosing: COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, rollback: { onBrokenHedgeOrders: false, onBrokenPrimaryOrders: false, diff --git a/lib/exit-strategies/multiLegPremiumThreshold.ts b/lib/exit-strategies/multiLegPremiumThreshold.ts index d41b9fa4..d2ecf498 100644 --- a/lib/exit-strategies/multiLegPremiumThreshold.ts +++ b/lib/exit-strategies/multiLegPremiumThreshold.ts @@ -30,7 +30,7 @@ import dayjs from 'dayjs' import { KiteOrder } from '../../types/kite' import { COMBINED_SL_EXIT_STRATEGY } from '../../types/plans' import { ATM_STRADDLE_TRADE, ATM_STRANGLE_TRADE } from '../../types/trade' -import { EXIT_STRATEGIES, USER_OVERRIDE } from '../constants' +import { EXIT_STRATEGIES, USER_OVERRIDE, VOLATILITY_TYPE } from '../constants' import console from '../logging' import { addToNextQueue, EXIT_TRADING_Q_NAME } from '../queue' import { @@ -44,6 +44,16 @@ import { import { doSquareOffPositions } from './autoSquareOff' +const isPositionLosing = ( + volatilityType: VOLATILITY_TYPE, + currentPrice: number, + buyPrice: number +) => { + return volatilityType === VOLATILITY_TYPE.SHORT + ? currentPrice > buyPrice + : currentPrice < buyPrice +} + const patchTradeWithTrailingSL = async ({ dbId, trailingSl }) => { try { await patchDbTrade({ @@ -69,6 +79,46 @@ const tradeHeartbeat = async dbId => { return data } +const handleExitLosingLeg = async ( + losingLegs: GET_LTP_RESPONSE[], + winningLegs: GET_LTP_RESPONSE[], + legsOrders: KiteOrder[], + kite: any, + initialJobData: CombinedPremiumJobDataInterface +) => { + const squareOffLosingLegs = losingLegs.map(losingLeg => + legsOrders.find( + legOrder => legOrder.tradingsymbol === losingLeg.tradingSymbol + ) + ) + // console.log('squareOffLosingLegs', logDeep(squareOffLosingLegs)) + const bringToCostOrders = winningLegs.map(winningLeg => + legsOrders.find( + legOrder => legOrder.tradingsymbol === winningLeg.tradingSymbol + ) + ) + // console.log('bringToCostOrders', logDeep(bringToCostOrders)) + // 1. square off losing legs + await doSquareOffPositions( + squareOffLosingLegs as KiteOrder[], + kite, + initialJobData + ) + // 2. bring the winning legs to cost + return await addToNextQueue( + { + ...initialJobData, + // override the slmPercent and exitStrategy in initialJobData + slmPercent: 0, + exitStrategy: EXIT_STRATEGIES.INDIVIDUAL_LEG_SLM_1X + }, + { + _nextTradingQueue: EXIT_TRADING_Q_NAME, + rawKiteOrdersResponse: bringToCostOrders + } + ) +} + export type CombinedPremiumJobDataInterface = ( | ATM_STRADDLE_TRADE | ATM_STRANGLE_TRADE @@ -98,7 +148,9 @@ async function multiLegPremiumThreshold ({ user, trailEveryPercentageChangeValue, lastTrailingSlTriggerAtPremium, - combinedExitStrategy = COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, + combinedExitStrategyLosing = COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, + combinedExitStrategyWinning = COMBINED_SL_EXIT_STRATEGY.EXIT_ALL, + volatilityType = VOLATILITY_TYPE.SHORT, _id: dbId } = initialJobData const kite = syncGetKiteInstance(user) @@ -164,7 +216,7 @@ async function multiLegPremiumThreshold ({ : null // 418 checkAgainstSl = trailingSlTotalPremium ?? initialSlTotalPremium // 418 - if (liveTotalPremium < checkAgainstSl) { + if (!isPositionLosing(volatilityType, liveTotalPremium, checkAgainstSl)) { const lastInflectionPoint = lastTrailingSlTriggerAtPremium ?? initialPremiumReceived // 380 // liveTotalPremium = 360 @@ -224,85 +276,118 @@ async function multiLegPremiumThreshold ({ } } - if (liveTotalPremium < checkAgainstSl) { + /** + * SHORT + * ce = 100, pe=100, sl-10 + * initialPremium = 200 + * initialCheckAgainstSl = 220 + * + * livePremium = 180, checkAgainstSL = 198 + * + * livePremium = 190, checkAgainstSL = 198 + * + * livePremium = 200 -> sl triggered + * ce = 120, pe = 80 + * losingleg = ce, winingleg = pe + */ + + if (!isPositionLosing(volatilityType, liveTotalPremium, checkAgainstSl)) { const rejectMsg = `🟢 [multiLegPremiumThreshold] liveTotalPremium (${liveTotalPremium}) < threshold (${checkAgainstSl})` return Promise.reject(new Error(rejectMsg)) } // terminate the checker - const exitMsg = `☢️ [multiLegPremiumThreshold] triggered! liveTotalPremium (${liveTotalPremium}) > threshold (${checkAgainstSl})` + const exitMsg = `☢️ [multiLegPremiumThreshold] triggered for ${volatilityType} position! liveTotalPremium (${liveTotalPremium}) & threshold (${checkAgainstSl})` console.log(exitMsg) - if (combinedExitStrategy === COMBINED_SL_EXIT_STRATEGY.EXIT_LOSING) { - // get the avg entry prices - const avgSymbolPrice = legsOrders.reduce( - (accum, order) => ({ - ...accum, - [order.tradingsymbol]: order.average_price - }), - {} - ) + // get the avg entry prices + const avgSymbolPrice = legsOrders.reduce( + (accum, order) => ({ + ...accum, + [order.tradingsymbol]: order.average_price + }), + {} + ) - // console.log('avgSymbolPrice', logDeep(avgSymbolPrice)) - - // future proofing by allowing for any number of positions to be trailed together - const { losingLegs, winningLegs } = liveSymbolPrices.reduce( - (accum, leg) => { - const { lastPrice, tradingSymbol } = leg - if (avgSymbolPrice[tradingSymbol] < lastPrice) { - return { - ...accum, - losingLegs: [...accum.losingLegs, leg] - } - } + // console.log('avgSymbolPrice', logDeep(avgSymbolPrice)) + // future proofing by allowing for any number of positions to be trailed together + const { losingLegs, winningLegs } = liveSymbolPrices.reduce( + (accum, leg) => { + const { lastPrice, tradingSymbol } = leg + const isLosingLeg = isPositionLosing( + volatilityType, + lastPrice, + avgSymbolPrice[tradingSymbol] + ) + + if (isLosingLeg) { return { ...accum, - winningLegs: [...accum.winningLegs, leg] + losingLegs: [...accum.losingLegs, leg] } - }, - { - losingLegs: [], - winningLegs: [] } - ) - // console.log('losingLegs', logDeep(losingLegs)) - // console.log('winningLegs', logDeep(winningLegs)) + return { + ...accum, + winningLegs: [...accum.winningLegs, leg] + } + }, + { + losingLegs: [], + winningLegs: [] + } + ) + + // console.log('losingLegs', logDeep(losingLegs)) + // console.log('winningLegs', logDeep(winningLegs)) - const squareOffLosingLegs = losingLegs.map(losingLeg => - legsOrders.find( - legOrder => legOrder.tradingsymbol === losingLeg.tradingSymbol - ) - ) - // console.log('squareOffLosingLegs', logDeep(squareOffLosingLegs)) - const bringToCostOrders = winningLegs.map(winningLeg => - legsOrders.find( - legOrder => legOrder.tradingsymbol === winningLeg.tradingSymbol - ) + const isPositionInLoss = isPositionLosing( + volatilityType, + liveTotalPremium, + initialSlTotalPremium + ) + + console.log( + `☢️ [multiLegPremiumThreshold] ${volatilityType} position is ${ + isPositionInLoss ? 'losing' : 'winning' + }! liveTotalPremium (${liveTotalPremium}) & initialSlTotalPremium (${initialSlTotalPremium})` + ) + + if (isPositionInLoss) { + console.log( + `☢️ [multiLegPremiumThreshold] ${combinedExitStrategyLosing} trigered for losing ${volatilityType} position` ) - // console.log('bringToCostOrders', logDeep(bringToCostOrders)) - // 1. square off losing legs - await doSquareOffPositions( - squareOffLosingLegs as KiteOrder[], + if (combinedExitStrategyLosing === COMBINED_SL_EXIT_STRATEGY.EXIT_ALL) { + return doSquareOffPositions(squareOffOrders!, kite, { + ...initialJobData, + onSquareOffSetAborted: true + }) + } + return handleExitLosingLeg( + losingLegs, + winningLegs, + legsOrders, kite, initialJobData ) - // 2. bring the winning legs to cost - return await addToNextQueue( - { + } else { + console.log( + `☢️ [multiLegPremiumThreshold] ${combinedExitStrategyLosing} trigered for winning ${volatilityType} position` + ) + if (combinedExitStrategyWinning === COMBINED_SL_EXIT_STRATEGY.EXIT_ALL) { + return doSquareOffPositions(squareOffOrders!, kite, { ...initialJobData, - // override the slmPercent and exitStrategy in initialJobData - slmPercent: 0, - exitStrategy: EXIT_STRATEGIES.INDIVIDUAL_LEG_SLM_1X - }, - { - _nextTradingQueue: EXIT_TRADING_Q_NAME, - rawKiteOrdersResponse: bringToCostOrders - } + onSquareOffSetAborted: true + }) + } + return handleExitLosingLeg( + losingLegs, + winningLegs, + legsOrders, + kite, + initialJobData ) } - - return doSquareOffPositions(squareOffOrders!, kite, initialJobData) } catch (e) { console.log('☢️ [multiLegPremiumThreshold] terminated', e) return Promise.resolve(e) diff --git a/types/plans.ts b/types/plans.ts index c857ec18..c1a49683 100644 --- a/types/plans.ts +++ b/types/plans.ts @@ -63,6 +63,8 @@ export interface ATM_STRADDLE_CONFIG extends SavedPlanMeta { slOrderType: SL_ORDER_TYPE slLimitPricePercent?: number combinedExitStrategy?: COMBINED_SL_EXIT_STRATEGY + combinedExitStrategyWinning?: COMBINED_SL_EXIT_STRATEGY + combinedExitStrategyLosing?: COMBINED_SL_EXIT_STRATEGY } export interface ATM_STRANGLE_CONFIG extends SavedPlanMeta { @@ -88,6 +90,8 @@ export interface ATM_STRANGLE_CONFIG extends SavedPlanMeta { slOrderType: SL_ORDER_TYPE slLimitPricePercent?: number combinedExitStrategy?: COMBINED_SL_EXIT_STRATEGY + combinedExitStrategyWinning?: COMBINED_SL_EXIT_STRATEGY + combinedExitStrategyLosing?: COMBINED_SL_EXIT_STRATEGY } export interface DIRECTIONAL_OPTION_SELLING_CONFIG extends SavedPlanMeta {