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:
parent
dac3258d9b
commit
650855368e
|
@ -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,12 +220,20 @@ const TVChartContainer = () => {
|
|||
const tvWidget = new widget(widgetOptions)
|
||||
tvWidgetRef.current = tvWidget
|
||||
|
||||
// Create order lines and trade executions buttons
|
||||
tvWidgetRef.current.onChartReady(function () {
|
||||
createOLButton();
|
||||
createTEButton();
|
||||
setChartReady(true)
|
||||
})
|
||||
//eslint-disable-next-line
|
||||
}, [theme, isMobile, publicKey])
|
||||
|
||||
const createOLButton = () => {
|
||||
const button = tvWidgetRef?.current?.createButton()
|
||||
if (!button) {
|
||||
return
|
||||
}
|
||||
setChartReady(true)
|
||||
button.textContent = 'OL'
|
||||
if (showOrderLinesLocalStorage) {
|
||||
button.style.color =
|
||||
|
@ -226,9 +248,52 @@ const TVChartContainer = () => {
|
|||
}
|
||||
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)
|
||||
})
|
||||
//eslint-disable-next-line
|
||||
}, [theme, isMobile, publicKey])
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -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" />
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -9,5 +9,6 @@
|
|||
"outside-range": "订单价格在范围之外",
|
||||
"slippage-accept": "若您接受潜在的下滑请使用交易表格进行。",
|
||||
"slippage-warning": "您的订单价格({{updatedOrderPrice}})多余5%{{aboveBelow}}市场价格({{selectedMarketPrice}})表是您也许遭受可观的下滑。",
|
||||
"toggle-order-line": "切换订单线可见性"
|
||||
"toggle-order-line": "切换订单线可见性",
|
||||
"toggle-trade-executions": "切换成交可见性"
|
||||
}
|
|
@ -9,5 +9,6 @@
|
|||
"outside-range": "訂單價格在範圍之外",
|
||||
"slippage-accept": "若您接受潛在的下滑請使用交易表格進行。",
|
||||
"slippage-warning": "您的訂單價格({{updatedOrderPrice}})多餘5%{{aboveBelow}}市場價格({{selectedMarketPrice}})表是您也許遭受可觀的下滑。",
|
||||
"toggle-order-line": "切換訂單線可見性"
|
||||
"toggle-order-line": "切換訂單線可見性",
|
||||
"toggle-trade-executions": "切換成交可見性"
|
||||
}
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in New Issue