Add execution arrows to tradingview chart (#408)

* initial attempt. still kind of broken. some arrows dont appear, some appear where they shouldnt

* use drawTradeExecutions instead of drawShape

* remove console log

* remove extra line

* add trade execution display limit and toggle switch

* re-add trade execution removal block

* remove code that wasnt supposed to be committed

* Debugging

Trade history and account switching debugging.

* almost working except is lagging behind by one account change

* remove unused import

* rearrange order of operatiosn/useEffect calls, remove unused code/comments/logs

* Testing Unowned MangoAccounts

Loading of unowned mango account trade executions.

Not fully working when trying to switch accounts.

* debugging, default execution arrows to false, dont store between sessions

* remove unowned account functions

Co-authored-by: ImpossiblePairs <47860274+ImpossiblePairs@users.noreply.github.com>
This commit is contained in:
rjpeterson 2022-09-14 00:56:21 -10:00 committed by tjs
parent dac3258d9b
commit 650855368e
8 changed files with 165 additions and 27 deletions

View File

@ -18,6 +18,7 @@ import { PerpTriggerOrder } from '../@types/types'
import { useTranslation } from 'next-i18next'
import useLocalStorageState from '../hooks/useLocalStorageState'
import { useWallet, Wallet } from '@solana/wallet-adapter-react'
import dayjs from 'dayjs'
export interface ChartContainerProps {
container: ChartingLibraryWidgetOptions['container']
@ -36,24 +37,34 @@ export interface ChartContainerProps {
}
const SHOW_ORDER_LINES_KEY = 'showOrderLines-0.1'
const TRADE_EXECUTION_LIMIT = 100
const TVChartContainer = () => {
const { t } = useTranslation(['common', 'tv-chart'])
const { theme } = useTheme()
const { width } = useViewport()
const { wallet, publicKey } = useWallet()
const { wallet, publicKey, connected } = useWallet()
const [chartReady, setChartReady] = useState(false)
const [showOrderLinesLocalStorage, toggleShowOrderLinesLocalStorage] =
useLocalStorageState(SHOW_ORDER_LINES_KEY, true)
const [showOrderLines, toggleShowOrderLines] = useState(
showOrderLinesLocalStorage
)
const [showTradeExecutions, toggleShowTradeExecutions] = useState(
false
)
const setMangoStore = useMangoStore.getState().set
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
const selectedMarketConfig = useMangoStore((s) => s.selectedMarket.config)
const actions = useMangoStore((s) => s.actions)
const isMobile = width ? width < breakpoints.sm : false
const mangoClient = useMangoStore.getState().connection.client
const selectedMarketName = selectedMarketConfig.name
const tradeExecutions = useMangoStore((s) => s.tradingView.tradeExecutions)
const tradeHistoryAndLiquidations = useMangoStore((s) => s.tradeHistory.parsed)
const tradeHistory = tradeHistoryAndLiquidations.filter((t) => !('liqor' in t))
const [cachedTradeHistory, setCachedTradeHistory] = useState(tradeHistory)
// @ts-ignore
const defaultProps: ChartContainerProps = useMemo(
@ -103,6 +114,9 @@ const TVChartContainer = () => {
deleteLines()
drawLinesForMarket(openOrders)
}
if (showTradeExecutions) {
setCachedTradeHistory(tradeHistory)
}
}
)
}
@ -206,30 +220,81 @@ const TVChartContainer = () => {
const tvWidget = new widget(widgetOptions)
tvWidgetRef.current = tvWidget
// Create order lines and trade executions buttons
tvWidgetRef.current.onChartReady(function () {
const button = tvWidgetRef?.current?.createButton()
if (!button) {
return
}
createOLButton();
createTEButton();
setChartReady(true)
button.textContent = 'OL'
if (showOrderLinesLocalStorage) {
button.style.color =
theme === 'Dark' || theme === 'Mango'
? 'rgb(242, 201, 76)'
: 'rgb(255, 156, 36)'
} else {
button.style.color =
theme === 'Dark' || theme === 'Mango'
? 'rgb(138, 138, 138)'
: 'rgb(138, 138, 138)'
}
button.setAttribute('title', t('tv-chart:toggle-order-line'))
button.addEventListener('click', toggleOrderLines)
})
//eslint-disable-next-line
}, [theme, isMobile, publicKey])
const createOLButton = () => {
const button = tvWidgetRef?.current?.createButton()
if (!button) {
return
}
button.textContent = 'OL'
if (showOrderLinesLocalStorage) {
button.style.color =
theme === 'Dark' || theme === 'Mango'
? 'rgb(242, 201, 76)'
: 'rgb(255, 156, 36)'
} else {
button.style.color =
theme === 'Dark' || theme === 'Mango'
? 'rgb(138, 138, 138)'
: 'rgb(138, 138, 138)'
}
button.setAttribute('title', t('tv-chart:toggle-order-line'))
button.addEventListener('click', toggleOrderLines)
}
const createTEButton = () => {
const button = tvWidgetRef?.current?.createButton()
if (!button) {
return
}
button.textContent = 'TE'
if (showTradeExecutions) {
button.style.color =
theme === 'Dark' || theme === 'Mango'
? 'rgb(242, 201, 76)'
: 'rgb(255, 156, 36)'
} else {
button.style.color =
theme === 'Dark' || theme === 'Mango'
? 'rgb(138, 138, 138)'
: 'rgb(138, 138, 138)'
}
button.setAttribute('title', t('tv-chart:toggle-trade-executions'))
button.addEventListener('click', toggleTradeExecutions)
}
function cycleShowTradeExecutions () {
toggleShowTradeExecutions((prevState) => !prevState)
sleep(1000).then(() => {
toggleShowTradeExecutions((prevState) => !prevState)
})
}
function toggleTradeExecutions() {
toggleShowTradeExecutions((prevState) => !prevState)
if (
this.style.color === 'rgb(255, 156, 36)' ||
this.style.color === 'rgb(242, 201, 76)'
) {
this.style.color =
theme === 'Dark' || theme === 'Mango'
? 'rgb(138, 138, 138)'
: 'rgb(138, 138, 138)'
} else {
this.style.color =
theme === 'Dark' || theme === 'Mango'
? 'rgb(242, 201, 76)'
: 'rgb(255, 156, 36)'
}
}
function toggleOrderLines() {
toggleShowOrderLines((prevState) => !prevState)
if (
@ -310,7 +375,6 @@ const TVChartContainer = () => {
price: number,
wallet: Wallet
) => {
const mangoAccount = useMangoStore.getState().selectedMangoAccount.current
const mangoGroup = useMangoStore.getState().selectedMangoGroup.current
const marketConfig = useMangoStore.getState().selectedMarket.config
const askInfo =
@ -660,6 +724,72 @@ const TVChartContainer = () => {
return subscription
}, [chartReady, showOrderLines, selectedMarketName])
const drawTradeExecutions = (trades) => {
const newTradeExecutions = new Map()
trades
.filter(trade => {
return trade.marketName === selectedMarketName
})
.slice(0, TRADE_EXECUTION_LIMIT)
.forEach(trade => {
try {
const oldArrow = tradeExecutions.get(`${trade.seqNum}${trade.marketName}`)
oldArrow ? oldArrow.remove() : null;
const arrowID = tvWidgetRef.current!.chart()
.createExecutionShape()
.setTime(dayjs(trade.loadTimestamp).unix())
.setDirection(trade.side)
.setArrowHeight(6)
.setArrowColor(trade.side === 'buy' ? theme === 'Mango' ? '#AFD803' : '#5EBF4D' : theme === 'Mango' ? '#E54033' : '#CC2929')
if (arrowID) {
try {
newTradeExecutions.set(`${trade.seqNum}${trade.marketName}`, arrowID)
} catch (error) {
console.log('couldnt set newTradeExecution')
}
} else {
console.log(`Could not create execution shape for trade ${trade.seqNum}${trade.marketName}`)
}
} catch (error) {
console.log(`could not draw arrow: ${error}`)
}
})
return newTradeExecutions
}
const removeTradeExecutions = (tradeExecutions) => {
if (chartReady && tvWidgetRef?.current) {
for (const val of tradeExecutions.values()) {
val.remove()
}
}
setMangoStore((s) => {s.tradingView.tradeExecutions = new Map()})
}
useEffect(()=>{
showTradeExecutions ? cycleShowTradeExecutions() : null
}, [selectedMarketName, mangoAccount?.publicKey])
useEffect(() => {
if (tvWidgetRef && tvWidgetRef.current && chartReady) {
setCachedTradeHistory(tradeHistory)
}
}, [connected, showTradeExecutions])
useEffect(() => {
if (cachedTradeHistory.length !== tradeHistory.length) {
setCachedTradeHistory(tradeHistory)
}
}, [mangoAccount?.publicKey, tradeHistory])
useEffect(() => {
removeTradeExecutions(tradeExecutions)
if (showTradeExecutions && tvWidgetRef && tvWidgetRef.current && chartReady) {
setMangoStore((s) => {s.tradingView.tradeExecutions = drawTradeExecutions(cachedTradeHistory)})
}
}, [cachedTradeHistory, selectedMarketName])
return (
<div id={defaultProps.container as string} className="tradingview-chart" />
)

View File

@ -9,5 +9,6 @@
"outside-range": "Order Price Outside Range",
"slippage-accept": "Please use the trade input form if you wish to accept the potential slippage.",
"slippage-warning": "Your order price ({{updatedOrderPrice}}) is greater than 5% {{aboveBelow}} the current market price ({{selectedMarketPrice}}) indicating you might incur significant slippage.",
"toggle-order-line": "Toggle order line visibility"
"toggle-order-line": "Toggle order line visibility",
"toggle-trade-executions": "Toggle trade execution visibility"
}

View File

@ -9,5 +9,6 @@
"outside-range": "Order Price Outside Range",
"slippage-accept": "Please use the trade input form if you wish to accept the potential slippage.",
"slippage-warning": "Your order price ({{updatedOrderPrice}}) is greater than 5% {{aboveBelow}} the current market price ({{selectedMarketPrice}}) indicating you might incur significant slippage.",
"toggle-order-line": "Toggle order line visibility"
"toggle-order-line": "Toggle order line visibility",
"toggle-trade-executions": "Toggle trade execution visibility"
}

View File

@ -9,5 +9,6 @@
"outside-range": "Order Price Outside Range",
"slippage-accept": "Please use the trade input form if you wish to accept the potential slippage.",
"slippage-warning": "Your order price ({{updatedOrderPrice}}) is greater than 5% {{aboveBelow}} the current market price ({{selectedMarketPrice}}) indicating you might incur significant slippage.",
"toggle-order-line": "Toggle order line visibility"
"toggle-order-line": "Toggle order line visibility",
"toggle-trade-executions": "Toggle trade execution visibility"
}

View File

@ -9,5 +9,6 @@
"outside-range": "Цена ордера вне диапазона",
"slippage-accept": "Пожалуйста, используйте форму ввода сделки, если вы хотите принять потенциальное проскальзывание.",
"slippage-warning": "Цена вашего ордера ({{updatedOrderPrice}}) превышает 5% {{aboveBelow}} от текущей рыночной цены ({{selectedMarketPrice}}) что указывает на то, что вы можете понести значительное проскальзывание.",
"toggle-order-line": "Переключить видимость строки ордера"
"toggle-order-line": "Переключить видимость строки ордера",
"toggle-trade-executions": "Toggle trade execution visibility"
}

View File

@ -9,5 +9,6 @@
"outside-range": "订单价格在范围之外",
"slippage-accept": "若您接受潜在的下滑请使用交易表格进行。",
"slippage-warning": "您的订单价格({{updatedOrderPrice}})多余5%{{aboveBelow}}市场价格({{selectedMarketPrice}})表是您也许遭受可观的下滑。",
"toggle-order-line": "切换订单线可见性"
"toggle-order-line": "切换订单线可见性",
"toggle-trade-executions": "切换成交可见性"
}

View File

@ -9,5 +9,6 @@
"outside-range": "訂單價格在範圍之外",
"slippage-accept": "若您接受潛在的下滑請使用交易表格進行。",
"slippage-warning": "您的訂單價格({{updatedOrderPrice}})多餘5%{{aboveBelow}}市場價格({{selectedMarketPrice}})表是您也許遭受可觀的下滑。",
"toggle-order-line": "切換訂單線可見性"
"toggle-order-line": "切換訂單線可見性",
"toggle-trade-executions": "切換成交可見性"
}

View File

@ -40,7 +40,7 @@ import {
} from '../components/SettingsModal'
import { MSRM_DECIMALS } from '@project-serum/serum/lib/token-instructions'
import { decodeBook } from '../hooks/useHydrateStore'
import { IOrderLineAdapter } from '../public/charting_library/charting_library'
import { IExecutionLineAdapter, IOrderLineAdapter } from '../public/charting_library/charting_library'
import { Wallet } from '@solana/wallet-adapter-react'
import { coingeckoIds, fetchNftsFromHolaplexIndexer } from 'utils/tokens'
import bs58 from 'bs58'
@ -351,6 +351,7 @@ export type MangoStore = {
marketsInfo: any[]
tradingView: {
orderLines: Map<string, IOrderLineAdapter>
tradeExecutions: Map<string, IExecutionLineAdapter>
}
coingeckoPrices: { data: any[]; loading: boolean }
}
@ -470,6 +471,7 @@ const useMangoStore = create<
},
tradingView: {
orderLines: new Map(),
tradeExecutions: new Map(),
},
coingeckoPrices: { data: [], loading: false },
profile: {