Add Tradingview Order Lines (#32)
* Update Cancel and Place Order Functions Updated function to cancel and place a new order. Requires 2 tx's until mango-client can be updated with the modify order tx. * Completed handleModifyOrder Completed handleModifyOrder and removed handlePlaceOrder as it is no longer required. * Formatting and Context Updates Added some additional context to the Modify order notifications, specifying modified orders as Limit orders, and updated USD formatting. * Update yarn.lock * Added toggles for OpenOrders initialization and OrderInProgress Added an additional order line update on initialization of an OpenOrders account and added a toggle for orders in progress to prevent a moved order line from moving back to it's original price whilst a transaction for an order line is pending.
This commit is contained in:
parent
d09b9a4283
commit
abfc8c6c69
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import {
|
||||
widget,
|
||||
|
@ -6,9 +6,14 @@ import {
|
|||
IChartingLibraryWidget,
|
||||
ResolutionString,
|
||||
} from '../charting_library' // Make sure to follow step 1 of the README
|
||||
// import { useMarket } from '../../utils/markets';
|
||||
import { CHART_DATA_FEED } from '../../utils/chartDataConnector'
|
||||
import useMangoStore from '../../stores/useMangoStore'
|
||||
import useMangoStore, { mangoClient } from '../../stores/useMangoStore'
|
||||
import { useOpenOrders } from '../../hooks/useOpenOrders'
|
||||
import { useSortableData } from '../../hooks/useSortableData'
|
||||
import { Order, Market } from '@project-serum/serum/lib/market'
|
||||
import { PerpOrder, PerpMarket } from '@blockworks-foundation/mango-client'
|
||||
import { notify } from '../../utils/notifications'
|
||||
import { sleep, formatUsdValue } from '../../utils'
|
||||
|
||||
// This is a basic example of how to create a TV widget
|
||||
// You can add more feature such as storing charts in localStorage
|
||||
|
@ -35,6 +40,19 @@ const TVChartContainer = () => {
|
|||
const selectedMarketConfig = useMangoStore((s) => s.selectedMarket.config)
|
||||
const { theme } = useTheme()
|
||||
|
||||
const selectedMarketName = selectedMarketConfig.name
|
||||
const openOrders = useOpenOrders()
|
||||
const { items } = useSortableData(openOrders)
|
||||
const actions = useMangoStore((s) => s.actions)
|
||||
const connected = useMangoStore((s) => s.wallet.connected)
|
||||
const selectedMarginAccount =
|
||||
useMangoStore.getState().selectedMangoAccount.current
|
||||
const selectedMarketPrice = useMangoStore((s) => s.selectedMarket.markPrice)
|
||||
const [lines, setLines] = useState(new Map())
|
||||
const [moveInProgress, toggleMoveInProgress] = useState(false)
|
||||
const [openOrdersIntialized, toggleOpenOrdersIntialized] = useState(false)
|
||||
const [orderInProgress, toggleOrderInProgress] = useState(false)
|
||||
|
||||
// @ts-ignore
|
||||
const defaultProps: ChartContainerProps = {
|
||||
symbol: selectedMarketConfig.name,
|
||||
|
@ -123,27 +141,251 @@ const TVChartContainer = () => {
|
|||
|
||||
const tvWidget = new widget(widgetOptions)
|
||||
tvWidgetRef.current = tvWidget
|
||||
|
||||
tvWidget.onChartReady(() => {
|
||||
// tvWidget.headerReady().then(() => {
|
||||
// const button = tvWidget.createButton()
|
||||
// button.setAttribute('title', 'Click to show a notification popup')
|
||||
// button.classList.add('apply-common-tooltip')
|
||||
// button.addEventListener('click', () =>
|
||||
// tvWidget.showNoticeDialog({
|
||||
// title: 'Notification',
|
||||
// body: 'TradingView Charting Library API works correctly',
|
||||
// callback: () => {
|
||||
// // console.log('It works!!');
|
||||
// },
|
||||
// })
|
||||
// )
|
||||
// button.innerHTML = 'Check API'
|
||||
// })
|
||||
})
|
||||
//eslint-disable-next-line
|
||||
}, [selectedMarketConfig, theme])
|
||||
|
||||
const handleCancelOrder = async (
|
||||
order: Order | PerpOrder,
|
||||
market: Market | PerpMarket
|
||||
) => {
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
const selectedMangoGroup =
|
||||
useMangoStore.getState().selectedMangoGroup.current
|
||||
const selectedMangoAccount =
|
||||
useMangoStore.getState().selectedMangoAccount.current
|
||||
|
||||
let txid
|
||||
try {
|
||||
if (!selectedMangoGroup || !selectedMangoAccount) return
|
||||
if (market instanceof Market) {
|
||||
txid = await mangoClient.cancelSpotOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet,
|
||||
market,
|
||||
order as Order
|
||||
)
|
||||
} else if (market instanceof PerpMarket) {
|
||||
txid = await mangoClient.cancelPerpOrder(
|
||||
selectedMangoGroup,
|
||||
selectedMangoAccount,
|
||||
wallet,
|
||||
market,
|
||||
order as PerpOrder
|
||||
)
|
||||
}
|
||||
notify({ title: 'Successfully cancelled order', txid })
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: 'Error cancelling order',
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
return false
|
||||
} finally {
|
||||
sleep(500).then(() => {
|
||||
actions.fetchMangoAccounts()
|
||||
actions.updateOpenOrders()
|
||||
toggleOrderInProgress(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleModifyOrder = async (
|
||||
order: Order | PerpOrder,
|
||||
market: Market | PerpMarket,
|
||||
price: number
|
||||
) => {
|
||||
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
|
||||
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
|
||||
const { askInfo, bidInfo } = useMangoStore.getState().selectedMarket
|
||||
const wallet = useMangoStore.getState().wallet.current
|
||||
|
||||
if (!wallet || !mangoGroup || !mangoAccount || !market) return
|
||||
|
||||
try {
|
||||
const orderPrice = price
|
||||
|
||||
if (!orderPrice) {
|
||||
notify({
|
||||
title: 'Price not available',
|
||||
description: 'Please try again',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
const orderType = 'limit'
|
||||
let txid
|
||||
if (market instanceof Market) {
|
||||
txid = await mangoClient.modifySpotOrder(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
mangoGroup.mangoCache,
|
||||
market,
|
||||
wallet,
|
||||
order,
|
||||
order.side,
|
||||
orderPrice,
|
||||
order.size,
|
||||
orderType
|
||||
)
|
||||
} else {
|
||||
txid = await mangoClient.modifyPerpOrder(
|
||||
mangoGroup,
|
||||
mangoAccount,
|
||||
mangoGroup.mangoCache,
|
||||
market,
|
||||
wallet,
|
||||
order,
|
||||
order.side,
|
||||
orderPrice,
|
||||
order.size,
|
||||
orderType,
|
||||
0,
|
||||
order.side === 'buy' ? askInfo : bidInfo
|
||||
)
|
||||
}
|
||||
|
||||
notify({ title: 'Successfully placed trade', txid })
|
||||
} catch (e) {
|
||||
notify({
|
||||
title: 'Error placing order',
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
sleep(1000).then(() => {
|
||||
actions.fetchMangoAccounts()
|
||||
actions.updateOpenOrders()
|
||||
toggleOrderInProgress(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getLine(order, market) {
|
||||
return tvWidgetRef.current
|
||||
.chart()
|
||||
.createOrderLine({ disableUndo: false })
|
||||
.onMove(function () {
|
||||
toggleMoveInProgress(true)
|
||||
toggleOrderInProgress(true)
|
||||
const currentOrderPrice = order.price
|
||||
const updatedOrderPrice = this.getPrice()
|
||||
if (
|
||||
(order.side === 'buy' &&
|
||||
updatedOrderPrice > 1.05 * selectedMarketPrice) ||
|
||||
(order.side === 'sell' &&
|
||||
updatedOrderPrice < 0.95 * selectedMarketPrice)
|
||||
) {
|
||||
tvWidgetRef.current.showNoticeDialog({
|
||||
title: 'Order Price Outside Range',
|
||||
body:
|
||||
`Your order price (${formatUsdValue(
|
||||
updatedOrderPrice
|
||||
)}) is greater than 5% ${
|
||||
order.side == 'buy' ? 'above' : 'below'
|
||||
} the current market price (${formatUsdValue(
|
||||
selectedMarketPrice
|
||||
)}). ` +
|
||||
' indicating you might incur significant slippage. <p><p>Please use the trade input form if you wish to accept the potential slippage.',
|
||||
callback: () => {
|
||||
this.setPrice(currentOrderPrice)
|
||||
toggleMoveInProgress(false)
|
||||
toggleOrderInProgress(false)
|
||||
},
|
||||
})
|
||||
} else {
|
||||
tvWidgetRef.current.showConfirmDialog({
|
||||
title: 'Modify Your Order?',
|
||||
body: `Would you like to change your order from a
|
||||
${order.size} ${market.config.baseSymbol} ${
|
||||
order.side
|
||||
} at ${formatUsdValue(currentOrderPrice)}
|
||||
to a
|
||||
${order.size} ${market.config.baseSymbol} LIMIT ${
|
||||
order.side
|
||||
} at ${formatUsdValue(updatedOrderPrice)}?
|
||||
`,
|
||||
callback: (res) => {
|
||||
if (res) {
|
||||
handleModifyOrder(order, market.account, updatedOrderPrice)
|
||||
} else {
|
||||
this.setPrice(currentOrderPrice)
|
||||
toggleOrderInProgress(false)
|
||||
}
|
||||
toggleMoveInProgress(false)
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
.onCancel(function () {
|
||||
toggleOrderInProgress(true)
|
||||
tvWidgetRef.current.showConfirmDialog({
|
||||
title: 'Cancel Your Order?',
|
||||
body: `Would you like to cancel your order for
|
||||
${order.size} ${market.config.baseSymbol} ${
|
||||
order.side
|
||||
} at ${formatUsdValue(order.price)}
|
||||
`,
|
||||
callback: (res) => {
|
||||
if (res) {
|
||||
handleCancelOrder(order, market.account)
|
||||
} else {
|
||||
toggleOrderInProgress(false)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
.setText(`${market.config.baseSymbol} ${order.side.toUpperCase()}`)
|
||||
.setBodyBorderColor(order.side == 'buy' ? '#AFD803' : '#E54033')
|
||||
.setBodyBackgroundColor('#000000')
|
||||
.setBodyTextColor('#F2C94C')
|
||||
.setLineLength(3)
|
||||
.setLineColor(order.side == 'buy' ? '#AFD803' : '#E54033')
|
||||
.setQuantity(order.size)
|
||||
.setTooltip(`Order #: ${order.orderId}`)
|
||||
.setQuantityBorderColor(order.side == 'buy' ? '#AFD803' : '#E54033')
|
||||
.setQuantityBackgroundColor('#000000')
|
||||
.setQuantityTextColor('#F2C94C')
|
||||
.setCancelButtonBorderColor(order.side == 'buy' ? '#AFD803' : '#E54033')
|
||||
.setCancelButtonBackgroundColor('#000000')
|
||||
.setCancelButtonIconColor('#F2C94C')
|
||||
.setPrice(order.price)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (openOrders != null && openOrdersIntialized === false) {
|
||||
toggleOpenOrdersIntialized(true)
|
||||
}
|
||||
}, [openOrders])
|
||||
|
||||
useEffect(() => {
|
||||
if (!moveInProgress && openOrdersIntialized && orderInProgress === false) {
|
||||
const tempLines = new Map()
|
||||
tvWidgetRef.current.onChartReady(() => {
|
||||
if (lines.size > 0) {
|
||||
lines.forEach((value, key) => {
|
||||
lines.get(key).remove()
|
||||
})
|
||||
}
|
||||
setLines(lines)
|
||||
|
||||
items.map(({ order, market }) => {
|
||||
if (market.config.name == selectedMarketName) {
|
||||
tempLines.set(order.orderId.toString(), getLine(order, market))
|
||||
}
|
||||
})
|
||||
})
|
||||
setLines(tempLines)
|
||||
}
|
||||
}, [
|
||||
selectedMarginAccount,
|
||||
connected,
|
||||
selectedMarketName,
|
||||
selectedMarketPrice,
|
||||
openOrdersIntialized,
|
||||
])
|
||||
|
||||
return <div id={defaultProps.containerId} className="tradingview-chart" />
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
const fs = require('fs');
|
||||
|
||||
// If Operating System is Windows, fix Husky for Yarn
|
||||
if (process.platform === 'win32') {
|
||||
const huskyScript = fs.readFileSync('.git/hooks/husky.sh', {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
const fixedHuskyScript = huskyScript.replace(
|
||||
'run_command yarn run --silent;;',
|
||||
'run_command npx --no-install;;'
|
||||
);
|
||||
fs.writeFileSync('.git/hooks/husky.sh', fixedHuskyScript);
|
||||
}
|
30
yarn.lock
30
yarn.lock
|
@ -993,18 +993,19 @@
|
|||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@blockworks-foundation/mango-client@git+https://github.com/blockworks-foundation/mango-client-v3.git":
|
||||
version "3.0.12"
|
||||
resolved "git+https://github.com/blockworks-foundation/mango-client-v3.git#5eeadbc3529af84db705951335cfe6592cd94683"
|
||||
"@blockworks-foundation/mango-client@^3.0.18":
|
||||
version "3.0.18"
|
||||
resolved "https://registry.yarnpkg.com/@blockworks-foundation/mango-client/-/mango-client-3.0.18.tgz#f665406365610fc9bbe3fecdd191fec9bd36df88"
|
||||
integrity sha512-tcKrkp8R8fIpVnrcUWFeQLKQK/bRHDntHOIVwo1R3MbwCWcPrnt2Y68Ev4LEpBzQ1B8WXcHqwOXc2hS2Wx/bnA==
|
||||
dependencies:
|
||||
"@project-serum/serum" "0.13.55"
|
||||
"@project-serum/sol-wallet-adapter" "^0.2.0"
|
||||
"@solana/spl-token" "^0.1.6"
|
||||
"@solana/web3.js" "1.21.0"
|
||||
axios "^0.21.1"
|
||||
big.js "^6.1.1"
|
||||
bigint-buffer "^1.1.5"
|
||||
bn.js "^5.1.0"
|
||||
borsh "https://github.com/defactojob/borsh-js#field-mapper"
|
||||
buffer-layout "^1.2.1"
|
||||
yargs "^17.0.1"
|
||||
|
||||
|
@ -2379,6 +2380,13 @@ available-typed-arrays@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9"
|
||||
integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==
|
||||
|
||||
axios@^0.21.1:
|
||||
version "0.21.4"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
||||
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
babel-jest@^26.6.3:
|
||||
version "26.6.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056"
|
||||
|
@ -2596,15 +2604,6 @@ borsh@^0.4.0:
|
|||
bs58 "^4.0.0"
|
||||
text-encoding-utf-8 "^1.0.2"
|
||||
|
||||
"borsh@https://github.com/defactojob/borsh-js#field-mapper":
|
||||
version "0.3.1"
|
||||
resolved "https://github.com/defactojob/borsh-js#33a0d24af281112c0a48efb3fa503f3212443de9"
|
||||
dependencies:
|
||||
"@types/bn.js" "^4.11.5"
|
||||
bn.js "^5.0.0"
|
||||
bs58 "^4.0.0"
|
||||
text-encoding-utf-8 "^1.0.2"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
|
@ -4297,6 +4296,11 @@ flatten@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
||||
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
||||
|
||||
follow-redirects@^1.14.0:
|
||||
version "1.14.4"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
|
||||
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
|
|
Loading…
Reference in New Issue