211 lines
5.0 KiB
TypeScript
211 lines
5.0 KiB
TypeScript
import mangoStore from '@store/mangoStore'
|
|
import { OrderbookL2, isMangoError } from 'types'
|
|
import { notify } from './notifications'
|
|
import * as sentry from '@sentry/nextjs'
|
|
import { Bank } from '@blockworks-foundation/mango-v4'
|
|
|
|
export const calculateLimitPriceForMarketOrder = (
|
|
orderBook: OrderbookL2,
|
|
size: number,
|
|
side: 'buy' | 'sell',
|
|
): number => {
|
|
const orders = side === 'buy' ? orderBook.asks : orderBook.bids
|
|
let acc = 0
|
|
let selectedOrder
|
|
const orderSize = size
|
|
for (const order of orders) {
|
|
acc += order[1]
|
|
if (acc >= orderSize) {
|
|
selectedOrder = order
|
|
break
|
|
}
|
|
}
|
|
|
|
if (!selectedOrder) {
|
|
throw new Error(
|
|
'Unable to place market order for this order size. Please retry.',
|
|
)
|
|
}
|
|
|
|
if (side === 'buy') {
|
|
return selectedOrder[0] * 1.05
|
|
} else {
|
|
return selectedOrder[0] * 0.95
|
|
}
|
|
}
|
|
|
|
export const calculateEstPriceForBaseSize = (
|
|
orderBook: OrderbookL2,
|
|
size: number,
|
|
side: 'buy' | 'sell',
|
|
): number => {
|
|
const orders = side === 'buy' ? orderBook.asks : orderBook.bids
|
|
let acc = 0
|
|
let selectedOrder
|
|
const orderSize = size
|
|
for (const order of orders) {
|
|
acc += order[1]
|
|
if (acc >= orderSize) {
|
|
selectedOrder = order
|
|
break
|
|
}
|
|
}
|
|
|
|
if (!selectedOrder) {
|
|
throw new Error('Unable to calculate market order. Please retry.')
|
|
}
|
|
|
|
if (side === 'buy') {
|
|
return selectedOrder[0]
|
|
} else {
|
|
return selectedOrder[0]
|
|
}
|
|
}
|
|
|
|
export const calculateSlippage = (
|
|
orderBook: OrderbookL2,
|
|
size: number,
|
|
side: 'buy' | 'sell',
|
|
markPrice: number,
|
|
): number => {
|
|
const bb = orderBook?.bids?.length > 0 && Number(orderBook.bids[0][0])
|
|
const ba = orderBook?.asks?.length > 0 && Number(orderBook.asks[0][0])
|
|
const referencePrice = bb && ba ? (bb + ba) / 2 : markPrice
|
|
|
|
if (Number(size)) {
|
|
const estimatedPrice = calculateEstPriceForBaseSize(
|
|
orderBook,
|
|
Number(size),
|
|
side,
|
|
)
|
|
|
|
const slippageAbs =
|
|
Number(size) > 0 ? Math.abs(estimatedPrice - referencePrice) : 0
|
|
|
|
const slippageRel = (slippageAbs / referencePrice) * 100
|
|
return slippageRel
|
|
}
|
|
return 0
|
|
}
|
|
|
|
export const handlePlaceTriggerOrder = async (
|
|
positionBank: Bank | undefined,
|
|
quoteBank: Bank | undefined,
|
|
amountIn: number,
|
|
triggerPrice: string,
|
|
orderType: TriggerOrderTypes,
|
|
isReducingShort: boolean,
|
|
flipPrices: boolean,
|
|
setSubmitting: (s: boolean) => void,
|
|
) => {
|
|
try {
|
|
const client = mangoStore.getState().client
|
|
const group = mangoStore.getState().group
|
|
const actions = mangoStore.getState().actions
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
// const inputBank = mangoStore.getState().swap.inputBank
|
|
// const outputBank = mangoStore.getState().swap.outputBank
|
|
|
|
if (!mangoAccount || !group || !positionBank || !quoteBank || !triggerPrice)
|
|
return
|
|
setSubmitting(true)
|
|
|
|
// const amountIn = amountInAsDecimal.toNumber()
|
|
|
|
const isReduceLong = !isReducingShort
|
|
|
|
try {
|
|
let tx
|
|
if (orderType === TriggerOrderTypes.STOP_LOSS) {
|
|
if (isReduceLong) {
|
|
tx = await client.tcsStopLossOnDeposit(
|
|
group,
|
|
mangoAccount,
|
|
positionBank,
|
|
quoteBank,
|
|
parseFloat(triggerPrice),
|
|
flipPrices,
|
|
amountIn,
|
|
null,
|
|
null,
|
|
)
|
|
} else {
|
|
tx = await client.tcsStopLossOnBorrow(
|
|
group,
|
|
mangoAccount,
|
|
quoteBank,
|
|
positionBank,
|
|
parseFloat(triggerPrice),
|
|
!flipPrices,
|
|
amountIn,
|
|
null,
|
|
true,
|
|
null,
|
|
)
|
|
}
|
|
}
|
|
if (orderType === TriggerOrderTypes.TAKE_PROFIT) {
|
|
if (isReduceLong) {
|
|
tx = await client.tcsTakeProfitOnDeposit(
|
|
group,
|
|
mangoAccount,
|
|
positionBank,
|
|
quoteBank,
|
|
parseFloat(triggerPrice),
|
|
flipPrices,
|
|
amountIn,
|
|
null,
|
|
null,
|
|
)
|
|
} else {
|
|
tx = await client.tcsTakeProfitOnBorrow(
|
|
group,
|
|
mangoAccount,
|
|
quoteBank,
|
|
positionBank,
|
|
parseFloat(triggerPrice),
|
|
!flipPrices,
|
|
amountIn,
|
|
null,
|
|
true,
|
|
null,
|
|
)
|
|
}
|
|
}
|
|
notify({
|
|
title: 'Transaction confirmed',
|
|
type: 'success',
|
|
txid: tx?.signature,
|
|
noSound: true,
|
|
})
|
|
actions.fetchGroup()
|
|
await actions.reloadMangoAccount(tx?.slot)
|
|
} catch (e) {
|
|
console.error('onSwap error: ', e)
|
|
sentry.captureException(e)
|
|
if (isMangoError(e)) {
|
|
notify({
|
|
title: 'Transaction failed',
|
|
description: e.message,
|
|
txid: e?.txid,
|
|
type: 'error',
|
|
})
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('Swap error:', e)
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
export enum OrderTypes {
|
|
LIMIT = 'Limit',
|
|
MARKET = 'Market',
|
|
}
|
|
|
|
export enum TriggerOrderTypes {
|
|
STOP_LOSS = 'trade:stop-loss',
|
|
TAKE_PROFIT = 'trade:take-profit',
|
|
}
|