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:
ImpossiblePairs 2021-09-22 06:17:12 -07:00 committed by GitHub
parent d09b9a4283
commit abfc8c6c69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 294 additions and 35 deletions

View File

@ -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" />
}

13
fix-husky.js Normal file
View File

@ -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);
}

View File

@ -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"