2023-04-13 04:25:58 -07:00
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2023-02-08 01:58:03 -08:00
|
|
|
import { useEffect, useRef, useMemo, useState, useCallback } from 'react'
|
2022-09-13 23:24:26 -07:00
|
|
|
import { useTheme } from 'next-themes'
|
|
|
|
import {
|
|
|
|
widget,
|
|
|
|
ChartingLibraryWidgetOptions,
|
|
|
|
IChartingLibraryWidget,
|
|
|
|
ResolutionString,
|
2023-02-08 01:58:03 -08:00
|
|
|
IOrderLineAdapter,
|
2023-02-09 03:41:15 -08:00
|
|
|
EntityId,
|
2023-04-13 04:25:58 -07:00
|
|
|
IExecutionLineAdapter,
|
2022-09-13 23:24:26 -07:00
|
|
|
} from '@public/charting_library'
|
|
|
|
import mangoStore from '@store/mangoStore'
|
|
|
|
import { useViewport } from 'hooks/useViewport'
|
2023-02-09 03:41:15 -08:00
|
|
|
import {
|
|
|
|
DEFAULT_MARKET_NAME,
|
2023-02-08 01:58:03 -08:00
|
|
|
SHOW_ORDER_LINES_KEY,
|
2023-02-09 03:41:15 -08:00
|
|
|
SHOW_STABLE_PRICE_KEY,
|
|
|
|
} from 'utils/constants'
|
2022-09-13 23:24:26 -07:00
|
|
|
import { breakpoints } from 'utils/theme'
|
|
|
|
import { COLORS } from 'styles/colors'
|
2023-02-09 03:41:15 -08:00
|
|
|
import { useTranslation } from 'next-i18next'
|
2023-02-08 01:58:03 -08:00
|
|
|
import { notify } from 'utils/notifications'
|
|
|
|
import {
|
|
|
|
PerpMarket,
|
|
|
|
PerpOrder,
|
|
|
|
PerpOrderType,
|
|
|
|
Serum3Market,
|
|
|
|
Serum3OrderType,
|
|
|
|
Serum3SelfTradeBehavior,
|
|
|
|
Serum3Side,
|
|
|
|
} from '@blockworks-foundation/mango-v4'
|
|
|
|
import { Order } from '@project-serum/serum/lib/market'
|
|
|
|
import { PublicKey } from '@solana/web3.js'
|
|
|
|
import useLocalStorageState from 'hooks/useLocalStorageState'
|
|
|
|
import { formatNumericValue, getDecimalCount } from 'utils/numbers'
|
|
|
|
import { BN } from '@project-serum/anchor'
|
2023-03-04 21:48:09 -08:00
|
|
|
import Datafeed from 'apis/datafeed'
|
2023-03-04 12:42:35 -08:00
|
|
|
// import PerpDatafeed from 'apis/mngo/datafeed'
|
2023-02-14 15:41:47 -08:00
|
|
|
import useStablePrice from 'hooks/useStablePrice'
|
2023-02-27 23:20:11 -08:00
|
|
|
import { isMangoError } from 'types'
|
2023-03-31 13:29:06 -07:00
|
|
|
import { formatPrice } from 'apis/birdeye/helpers'
|
2023-04-13 04:25:58 -07:00
|
|
|
import useTradeHistory from 'hooks/useTradeHistory'
|
|
|
|
import dayjs from 'dayjs'
|
2022-09-13 23:24:26 -07:00
|
|
|
|
|
|
|
export interface ChartContainerProps {
|
|
|
|
container: ChartingLibraryWidgetOptions['container']
|
|
|
|
symbol: ChartingLibraryWidgetOptions['symbol']
|
|
|
|
interval: ChartingLibraryWidgetOptions['interval']
|
|
|
|
datafeedUrl: string
|
|
|
|
libraryPath: ChartingLibraryWidgetOptions['library_path']
|
|
|
|
chartsStorageUrl: ChartingLibraryWidgetOptions['charts_storage_url']
|
|
|
|
chartsStorageApiVersion: ChartingLibraryWidgetOptions['charts_storage_api_version']
|
|
|
|
clientId: ChartingLibraryWidgetOptions['client_id']
|
|
|
|
userId: ChartingLibraryWidgetOptions['user_id']
|
|
|
|
fullscreen: ChartingLibraryWidgetOptions['fullscreen']
|
|
|
|
autosize: ChartingLibraryWidgetOptions['autosize']
|
|
|
|
studiesOverrides: ChartingLibraryWidgetOptions['studies_overrides']
|
|
|
|
theme: string
|
|
|
|
}
|
|
|
|
|
2023-02-09 03:41:15 -08:00
|
|
|
function hexToRgb(hex: string) {
|
|
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
|
|
return result
|
|
|
|
? `rgb(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
|
|
|
|
result[3],
|
|
|
|
16
|
|
|
|
)})`
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
|
2022-09-13 23:24:26 -07:00
|
|
|
const TradingViewChart = () => {
|
2023-02-08 02:04:20 -08:00
|
|
|
const { t } = useTranslation(['tv-chart', 'trade'])
|
2022-09-13 23:24:26 -07:00
|
|
|
const { theme } = useTheme()
|
|
|
|
const { width } = useViewport()
|
|
|
|
const [chartReady, setChartReady] = useState(false)
|
2023-03-06 11:50:07 -08:00
|
|
|
const [headerReady, setHeaderReady] = useState(false)
|
2023-01-22 21:25:28 -08:00
|
|
|
const [spotOrPerp, setSpotOrPerp] = useState('spot')
|
2023-02-08 01:58:03 -08:00
|
|
|
const [showOrderLinesLocalStorage, toggleShowOrderLinesLocalStorage] =
|
|
|
|
useLocalStorageState(SHOW_ORDER_LINES_KEY, true)
|
|
|
|
const [showOrderLines, toggleShowOrderLines] = useState(
|
|
|
|
showOrderLinesLocalStorage
|
|
|
|
)
|
2023-04-13 04:25:58 -07:00
|
|
|
const tradeExecutions = mangoStore((s) => s.tradingView.tradeExecutions)
|
|
|
|
const { data: combinedTradeHistory, isLoading: loadingTradeHistory } =
|
|
|
|
useTradeHistory()
|
|
|
|
const [showTradeExecutions, toggleShowTradeExecutions] = useState(false)
|
|
|
|
const [cachedTradeHistory, setCachedTradeHistory] =
|
|
|
|
useState(combinedTradeHistory)
|
2023-02-14 15:41:47 -08:00
|
|
|
|
2023-02-09 03:41:15 -08:00
|
|
|
const [showStablePriceLocalStorage, toggleShowStablePriceLocalStorage] =
|
|
|
|
useLocalStorageState(SHOW_STABLE_PRICE_KEY, false)
|
|
|
|
const [showStablePrice, toggleShowStablePrice] = useState(
|
|
|
|
showStablePriceLocalStorage
|
|
|
|
)
|
|
|
|
const stablePrice = useStablePrice()
|
|
|
|
const stablePriceLine = mangoStore((s) => s.tradingView.stablePriceLine)
|
2022-09-13 23:24:26 -07:00
|
|
|
const selectedMarketName = mangoStore((s) => s.selectedMarket.current?.name)
|
|
|
|
const isMobile = width ? width < breakpoints.sm : false
|
|
|
|
|
2022-11-19 11:20:36 -08:00
|
|
|
const defaultProps = useMemo(
|
2022-09-13 23:24:26 -07:00
|
|
|
() => ({
|
|
|
|
symbol: DEFAULT_MARKET_NAME,
|
|
|
|
interval: '60' as ResolutionString,
|
|
|
|
theme: 'Dark',
|
|
|
|
container: 'tv_chart_container',
|
|
|
|
libraryPath: '/charting_library/',
|
|
|
|
fullscreen: false,
|
|
|
|
autosize: true,
|
|
|
|
studiesOverrides: {
|
2022-11-30 19:32:32 -08:00
|
|
|
'volume.volume.color.0': COLORS.DOWN[theme],
|
|
|
|
'volume.volume.color.1': COLORS.UP[theme],
|
2022-09-13 23:24:26 -07:00
|
|
|
'volume.precision': 4,
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
[theme]
|
|
|
|
)
|
|
|
|
|
2023-03-02 17:37:20 -08:00
|
|
|
const tvWidgetRef = useRef<IChartingLibraryWidget>()
|
|
|
|
const stablePriceButtonRef = useRef<HTMLElement>()
|
|
|
|
const orderLinesButtonRef = useRef<HTMLElement>()
|
2022-09-13 23:24:26 -07:00
|
|
|
|
2023-02-01 15:05:01 -08:00
|
|
|
const selectedMarket = useMemo(() => {
|
|
|
|
const group = mangoStore.getState().group
|
|
|
|
if (!group || !selectedMarketName)
|
|
|
|
return '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
|
|
|
|
|
2023-03-04 12:42:35 -08:00
|
|
|
if (!selectedMarketName?.toLowerCase().includes('perp')) {
|
2023-02-01 15:05:01 -08:00
|
|
|
return group
|
|
|
|
.getSerum3MarketByName(selectedMarketName)
|
|
|
|
.serumMarketExternal.toString()
|
|
|
|
} else {
|
|
|
|
return group.getPerpMarketByName(selectedMarketName).publicKey.toString()
|
|
|
|
}
|
|
|
|
}, [selectedMarketName])
|
|
|
|
|
2022-09-13 23:24:26 -07:00
|
|
|
useEffect(() => {
|
2022-12-24 08:38:25 -08:00
|
|
|
const group = mangoStore.getState().group
|
2023-02-01 15:05:01 -08:00
|
|
|
if (tvWidgetRef.current && chartReady && selectedMarket && group) {
|
2022-12-27 16:24:55 -08:00
|
|
|
try {
|
|
|
|
tvWidgetRef.current.setSymbol(
|
2023-02-01 15:05:01 -08:00
|
|
|
selectedMarket,
|
2022-12-27 16:24:55 -08:00
|
|
|
tvWidgetRef.current.activeChart().resolution(),
|
|
|
|
() => {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
console.warn('Trading View change symbol error: ', e)
|
|
|
|
}
|
2022-12-24 08:38:25 -08:00
|
|
|
}
|
2023-02-01 15:05:01 -08:00
|
|
|
}, [selectedMarket, chartReady])
|
2022-09-13 23:24:26 -07:00
|
|
|
|
2023-01-22 21:25:28 -08:00
|
|
|
useEffect(() => {
|
|
|
|
if (
|
|
|
|
selectedMarketName?.toLowerCase().includes('perp') &&
|
|
|
|
spotOrPerp !== 'perp'
|
|
|
|
) {
|
|
|
|
setSpotOrPerp('perp')
|
|
|
|
} else if (
|
|
|
|
!selectedMarketName?.toLowerCase().includes('perp') &&
|
|
|
|
spotOrPerp !== 'spot'
|
|
|
|
) {
|
|
|
|
setSpotOrPerp('spot')
|
|
|
|
}
|
|
|
|
}, [selectedMarketName, spotOrPerp])
|
|
|
|
|
2023-02-09 03:41:15 -08:00
|
|
|
useEffect(() => {
|
|
|
|
if (showStablePrice !== showStablePriceLocalStorage) {
|
|
|
|
toggleShowStablePriceLocalStorage(showStablePrice)
|
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
}, [
|
|
|
|
showStablePrice,
|
|
|
|
showStablePriceLocalStorage,
|
|
|
|
toggleShowStablePriceLocalStorage,
|
2023-03-02 17:37:20 -08:00
|
|
|
theme,
|
2023-02-25 11:59:28 -08:00
|
|
|
])
|
|
|
|
|
|
|
|
const drawStablePriceLine = useCallback(
|
|
|
|
(price: number) => {
|
|
|
|
if (!tvWidgetRef?.current?.chart()) return
|
|
|
|
const now = Date.now() / 1000
|
|
|
|
try {
|
2023-02-26 17:04:02 -08:00
|
|
|
const oldId = mangoStore.getState().tradingView.stablePriceLine
|
|
|
|
if (oldId) {
|
|
|
|
tvWidgetRef.current.chart().removeEntity(oldId)
|
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
const id = tvWidgetRef.current.chart().createShape(
|
|
|
|
{ time: now, price: price },
|
|
|
|
{
|
|
|
|
shape: 'horizontal_line',
|
|
|
|
overrides: {
|
|
|
|
linecolor: COLORS.FGD4[theme],
|
|
|
|
linestyle: 1,
|
|
|
|
linewidth: 1,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
2023-02-09 03:41:15 -08:00
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
if (id) {
|
|
|
|
return id
|
|
|
|
} else {
|
|
|
|
console.warn('failed to create stable price line')
|
2023-02-09 03:41:15 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
} catch {
|
2023-02-15 13:49:56 -08:00
|
|
|
console.warn('failed to create stable price line')
|
2023-02-09 03:41:15 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
},
|
|
|
|
[theme]
|
|
|
|
)
|
2023-02-09 03:41:15 -08:00
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
const removeStablePrice = useCallback((id: EntityId) => {
|
2023-02-09 03:41:15 -08:00
|
|
|
if (!tvWidgetRef?.current?.chart()) return
|
|
|
|
const set = mangoStore.getState().set
|
2023-02-25 11:59:28 -08:00
|
|
|
|
|
|
|
try {
|
|
|
|
tvWidgetRef.current.chart().removeEntity(id)
|
|
|
|
} catch (error) {
|
|
|
|
console.warn('stable price could not be removed')
|
2023-02-09 03:41:15 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
|
2023-02-09 03:41:15 -08:00
|
|
|
set((s) => {
|
2023-02-25 11:59:28 -08:00
|
|
|
s.tradingView.stablePriceLine = undefined
|
2023-02-09 03:41:15 -08:00
|
|
|
})
|
2023-02-25 11:59:28 -08:00
|
|
|
}, [])
|
2023-02-08 01:58:03 -08:00
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
// remove stable price line when toggling off
|
2023-01-22 21:25:28 -08:00
|
|
|
useEffect(() => {
|
2023-02-25 11:59:28 -08:00
|
|
|
if (tvWidgetRef.current && chartReady) {
|
|
|
|
if (!showStablePrice && stablePriceLine) {
|
|
|
|
removeStablePrice(stablePriceLine)
|
|
|
|
}
|
2023-01-22 21:25:28 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
}, [showStablePrice, chartReady, removeStablePrice, stablePriceLine])
|
2023-01-22 21:25:28 -08:00
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
// update stable price line when toggled on
|
2022-09-13 23:24:26 -07:00
|
|
|
useEffect(() => {
|
2023-02-25 11:59:28 -08:00
|
|
|
if (tvWidgetRef.current && chartReady) {
|
|
|
|
if (showStablePrice && stablePrice) {
|
|
|
|
const set = mangoStore.getState().set
|
|
|
|
set((s) => {
|
|
|
|
s.tradingView.stablePriceLine = drawStablePriceLine(stablePrice)
|
|
|
|
})
|
|
|
|
}
|
2023-02-08 01:58:03 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
}, [stablePrice, chartReady, showStablePrice, drawStablePriceLine])
|
2023-02-08 01:58:03 -08:00
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
useEffect(() => {
|
|
|
|
if (showOrderLines !== showOrderLinesLocalStorage) {
|
|
|
|
toggleShowOrderLinesLocalStorage(showOrderLines)
|
2023-02-08 01:58:03 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
}, [
|
|
|
|
showOrderLines,
|
|
|
|
showOrderLinesLocalStorage,
|
|
|
|
toggleShowOrderLinesLocalStorage,
|
2023-03-02 17:37:20 -08:00
|
|
|
theme,
|
2023-02-25 11:59:28 -08:00
|
|
|
])
|
2023-02-08 01:58:03 -08:00
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
const deleteLines = useCallback(() => {
|
2023-02-08 01:58:03 -08:00
|
|
|
const set = mangoStore.getState().set
|
|
|
|
const orderLines = mangoStore.getState().tradingView.orderLines
|
|
|
|
if (orderLines.size > 0) {
|
|
|
|
orderLines?.forEach((value: IOrderLineAdapter, key: string | BN) => {
|
|
|
|
orderLines.get(key)?.remove()
|
|
|
|
})
|
|
|
|
|
|
|
|
set((state) => {
|
|
|
|
state.tradingView.orderLines = new Map()
|
2022-09-13 23:24:26 -07:00
|
|
|
})
|
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
}, [])
|
2023-02-08 01:58:03 -08:00
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
const getOrderDecimals = useCallback(() => {
|
2023-02-08 01:58:03 -08:00
|
|
|
const selectedMarket = mangoStore.getState().selectedMarket.current
|
|
|
|
let minOrderDecimals = 4
|
|
|
|
let tickSizeDecimals = 2
|
|
|
|
if (!selectedMarket) return [minOrderDecimals, tickSizeDecimals]
|
|
|
|
if (selectedMarket instanceof PerpMarket) {
|
|
|
|
minOrderDecimals = getDecimalCount(selectedMarket.minOrderSize)
|
|
|
|
tickSizeDecimals = getDecimalCount(selectedMarket.tickSize)
|
|
|
|
} else {
|
|
|
|
const group = mangoStore.getState().group
|
|
|
|
const market = group?.getSerum3ExternalMarket(
|
|
|
|
selectedMarket.serumMarketExternal
|
|
|
|
)
|
|
|
|
if (market) {
|
|
|
|
minOrderDecimals = getDecimalCount(market.minOrderSize)
|
|
|
|
tickSizeDecimals = getDecimalCount(market.tickSize)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return [minOrderDecimals, tickSizeDecimals]
|
2023-02-25 11:59:28 -08:00
|
|
|
}, [])
|
|
|
|
|
|
|
|
const findSerum3MarketPkInOpenOrders = useCallback(
|
|
|
|
(o: Order): string | undefined => {
|
|
|
|
const openOrders = mangoStore.getState().mangoAccount.openOrders
|
|
|
|
let foundedMarketPk: string | undefined = undefined
|
|
|
|
for (const [marketPk, orders] of Object.entries(openOrders)) {
|
|
|
|
for (const order of orders) {
|
|
|
|
if (order.orderId.eq(o.orderId)) {
|
|
|
|
foundedMarketPk = marketPk
|
|
|
|
break
|
2023-02-08 01:58:03 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
}
|
|
|
|
if (foundedMarketPk) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return foundedMarketPk
|
|
|
|
},
|
|
|
|
[]
|
|
|
|
)
|
2023-02-08 01:58:03 -08:00
|
|
|
|
|
|
|
const modifyOrder = useCallback(
|
|
|
|
async (o: PerpOrder | Order, price: number) => {
|
|
|
|
const client = mangoStore.getState().client
|
|
|
|
const group = mangoStore.getState().group
|
|
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
|
|
const actions = mangoStore.getState().actions
|
|
|
|
const baseSize = o.size
|
|
|
|
if (!group || !mangoAccount) return
|
|
|
|
try {
|
|
|
|
let tx = ''
|
|
|
|
if (o instanceof PerpOrder) {
|
|
|
|
tx = await client.modifyPerpOrder(
|
|
|
|
group,
|
|
|
|
mangoAccount,
|
|
|
|
o.perpMarketIndex,
|
|
|
|
o.orderId,
|
|
|
|
o.side,
|
|
|
|
price,
|
|
|
|
Math.abs(baseSize),
|
|
|
|
undefined, // maxQuoteQuantity
|
|
|
|
Date.now(),
|
|
|
|
PerpOrderType.limit,
|
|
|
|
undefined,
|
|
|
|
undefined
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
const marketPk = findSerum3MarketPkInOpenOrders(o)
|
|
|
|
if (!marketPk) return
|
|
|
|
const market = group.getSerum3MarketByExternalMarket(
|
|
|
|
new PublicKey(marketPk)
|
|
|
|
)
|
|
|
|
tx = await client.modifySerum3Order(
|
|
|
|
group,
|
|
|
|
o.orderId,
|
|
|
|
mangoAccount,
|
|
|
|
market.serumMarketExternal,
|
|
|
|
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
|
|
|
|
price,
|
|
|
|
baseSize,
|
|
|
|
Serum3SelfTradeBehavior.decrementTake,
|
|
|
|
Serum3OrderType.limit,
|
|
|
|
Date.now(),
|
|
|
|
10
|
|
|
|
)
|
|
|
|
}
|
|
|
|
actions.fetchOpenOrders()
|
|
|
|
notify({
|
|
|
|
type: 'success',
|
|
|
|
title: 'Transaction successful',
|
|
|
|
txid: tx,
|
|
|
|
})
|
2023-02-27 23:20:11 -08:00
|
|
|
} catch (e) {
|
2023-02-08 01:58:03 -08:00
|
|
|
console.error('Error canceling', e)
|
2023-02-27 23:20:11 -08:00
|
|
|
if (!isMangoError(e)) return
|
2023-02-08 01:58:03 -08:00
|
|
|
notify({
|
|
|
|
title: 'Unable to modify order',
|
|
|
|
description: e.message,
|
|
|
|
txid: e.txid,
|
|
|
|
type: 'error',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
2023-02-25 11:59:28 -08:00
|
|
|
[findSerum3MarketPkInOpenOrders]
|
2023-02-08 01:58:03 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
const cancelSpotOrder = useCallback(
|
|
|
|
async (o: Order) => {
|
|
|
|
const client = mangoStore.getState().client
|
|
|
|
const group = mangoStore.getState().group
|
|
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
|
|
const actions = mangoStore.getState().actions
|
|
|
|
if (!group || !mangoAccount) return
|
|
|
|
const marketPk = findSerum3MarketPkInOpenOrders(o)
|
|
|
|
if (!marketPk) return
|
|
|
|
const market = group.getSerum3MarketByExternalMarket(
|
|
|
|
new PublicKey(marketPk)
|
|
|
|
)
|
|
|
|
try {
|
|
|
|
const tx = await client.serum3CancelOrder(
|
|
|
|
group,
|
|
|
|
mangoAccount,
|
|
|
|
market!.serumMarketExternal,
|
|
|
|
o.side === 'buy' ? Serum3Side.bid : Serum3Side.ask,
|
|
|
|
o.orderId
|
|
|
|
)
|
|
|
|
|
|
|
|
actions.fetchOpenOrders()
|
|
|
|
notify({
|
|
|
|
type: 'success',
|
|
|
|
title: 'Transaction successful',
|
|
|
|
txid: tx,
|
|
|
|
})
|
2023-02-27 23:20:11 -08:00
|
|
|
} catch (e) {
|
2023-02-08 01:58:03 -08:00
|
|
|
console.error('Error canceling', e)
|
2023-02-27 23:20:11 -08:00
|
|
|
if (!isMangoError(e)) return
|
2023-02-08 01:58:03 -08:00
|
|
|
notify({
|
|
|
|
title: t('trade:cancel-order-error'),
|
|
|
|
description: e.message,
|
|
|
|
txid: e.txid,
|
|
|
|
type: 'error',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
2023-02-25 11:59:28 -08:00
|
|
|
[t, findSerum3MarketPkInOpenOrders]
|
2023-02-08 01:58:03 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
const cancelPerpOrder = useCallback(
|
|
|
|
async (o: PerpOrder) => {
|
|
|
|
const client = mangoStore.getState().client
|
|
|
|
const group = mangoStore.getState().group
|
|
|
|
const mangoAccount = mangoStore.getState().mangoAccount.current
|
|
|
|
const actions = mangoStore.getState().actions
|
|
|
|
if (!group || !mangoAccount) return
|
|
|
|
try {
|
|
|
|
const tx = await client.perpCancelOrder(
|
|
|
|
group,
|
|
|
|
mangoAccount,
|
|
|
|
o.perpMarketIndex,
|
|
|
|
o.orderId
|
|
|
|
)
|
|
|
|
actions.fetchOpenOrders()
|
|
|
|
notify({
|
|
|
|
type: 'success',
|
|
|
|
title: 'Transaction successful',
|
|
|
|
txid: tx,
|
|
|
|
})
|
2023-02-27 23:20:11 -08:00
|
|
|
} catch (e) {
|
2023-02-08 01:58:03 -08:00
|
|
|
console.error('Error canceling', e)
|
2023-02-27 23:20:11 -08:00
|
|
|
if (!isMangoError(e)) return
|
2023-02-08 01:58:03 -08:00
|
|
|
notify({
|
|
|
|
title: t('trade:cancel-order-error'),
|
|
|
|
description: e.message,
|
|
|
|
txid: e.txid,
|
|
|
|
type: 'error',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[t]
|
|
|
|
)
|
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
const drawLine = useCallback(
|
|
|
|
(order: Order | PerpOrder) => {
|
|
|
|
const side =
|
|
|
|
typeof order.side === 'string'
|
|
|
|
? t(order.side)
|
|
|
|
: 'bid' in order.side
|
|
|
|
? t('buy')
|
|
|
|
: t('sell')
|
|
|
|
const isLong = side.toLowerCase() === 'buy'
|
|
|
|
const isShort = side.toLowerCase() === 'sell'
|
|
|
|
const [minOrderDecimals, tickSizeDecimals] = getOrderDecimals()
|
|
|
|
const orderSizeUi: string = formatNumericValue(
|
|
|
|
order.size,
|
|
|
|
minOrderDecimals
|
|
|
|
)
|
|
|
|
if (!tvWidgetRef?.current?.chart()) return
|
|
|
|
return (
|
|
|
|
tvWidgetRef.current
|
|
|
|
.chart()
|
|
|
|
.createOrderLine({ disableUndo: false })
|
|
|
|
.onMove(function (this: IOrderLineAdapter) {
|
|
|
|
const currentOrderPrice = order.price
|
|
|
|
const updatedOrderPrice = this.getPrice()
|
|
|
|
const selectedMarketPrice =
|
|
|
|
mangoStore.getState().selectedMarket.markPrice
|
|
|
|
if (
|
|
|
|
(isLong && updatedOrderPrice > 1.05 * selectedMarketPrice) ||
|
|
|
|
(isShort && updatedOrderPrice < 0.95 * selectedMarketPrice)
|
|
|
|
) {
|
|
|
|
tvWidgetRef.current?.showNoticeDialog({
|
|
|
|
title: t('tv-chart:outside-range'),
|
|
|
|
body:
|
|
|
|
t('tv-chart:slippage-warning', {
|
|
|
|
updatedOrderPrice: updatedOrderPrice,
|
|
|
|
aboveBelow:
|
|
|
|
side == 'buy' || side === 'long'
|
|
|
|
? t('above')
|
|
|
|
: t('below'),
|
|
|
|
selectedMarketPrice: selectedMarketPrice,
|
|
|
|
}) +
|
|
|
|
'<p><p>' +
|
|
|
|
t('tv-chart:slippage-accept'),
|
|
|
|
callback: () => {
|
|
|
|
this.setPrice(currentOrderPrice)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
tvWidgetRef.current?.showConfirmDialog({
|
|
|
|
title: t('tv-chart:modify-order'),
|
|
|
|
body: t('tv-chart:modify-order-details', {
|
|
|
|
marketName: selectedMarketName,
|
|
|
|
orderSize: orderSizeUi,
|
|
|
|
orderSide: side.toUpperCase(),
|
|
|
|
currentOrderPrice: formatNumericValue(
|
|
|
|
currentOrderPrice,
|
|
|
|
tickSizeDecimals
|
|
|
|
),
|
|
|
|
updatedOrderPrice: formatNumericValue(
|
|
|
|
updatedOrderPrice,
|
|
|
|
tickSizeDecimals
|
|
|
|
),
|
|
|
|
}),
|
|
|
|
callback: (res) => {
|
|
|
|
if (res) {
|
|
|
|
modifyOrder(order, updatedOrderPrice)
|
|
|
|
} else {
|
|
|
|
this.setPrice(currentOrderPrice)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.onCancel(function () {
|
|
|
|
tvWidgetRef.current?.showConfirmDialog({
|
|
|
|
title: t('tv-chart:cancel-order'),
|
|
|
|
body: t('tv-chart:cancel-order-details', {
|
|
|
|
marketName: selectedMarketName,
|
|
|
|
orderSize: orderSizeUi,
|
|
|
|
orderSide: side.toUpperCase(),
|
|
|
|
orderPrice: formatNumericValue(order.price, tickSizeDecimals),
|
|
|
|
}),
|
|
|
|
callback: (res) => {
|
|
|
|
if (res) {
|
|
|
|
if (order instanceof PerpOrder) {
|
|
|
|
cancelPerpOrder(order)
|
|
|
|
} else {
|
|
|
|
cancelSpotOrder(order)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.setPrice(order.price)
|
|
|
|
.setQuantity(orderSizeUi)
|
|
|
|
.setText(side.toUpperCase())
|
|
|
|
// .setTooltip(
|
|
|
|
// order.perpTrigger?.clientOrderId
|
|
|
|
// ? `${order.orderType} Order #: ${order.orderId}`
|
|
|
|
// : `Order #: ${order.orderId}`
|
|
|
|
// )
|
|
|
|
.setBodyTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
|
|
|
|
.setQuantityTextColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
|
|
|
|
.setCancelButtonIconColor(COLORS.FGD4[theme])
|
|
|
|
.setBodyBorderColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
|
|
|
|
.setQuantityBorderColor(
|
|
|
|
isLong ? COLORS.UP[theme] : COLORS.DOWN[theme]
|
|
|
|
)
|
|
|
|
.setCancelButtonBorderColor(
|
|
|
|
isLong ? COLORS.UP[theme] : COLORS.DOWN[theme]
|
|
|
|
)
|
|
|
|
.setBodyBackgroundColor(COLORS.BKG1[theme])
|
|
|
|
.setQuantityBackgroundColor(COLORS.BKG1[theme])
|
|
|
|
.setCancelButtonBackgroundColor(COLORS.BKG1[theme])
|
|
|
|
.setLineColor(isLong ? COLORS.UP[theme] : COLORS.DOWN[theme])
|
|
|
|
.setLineLength(3)
|
|
|
|
.setLineWidth(1)
|
|
|
|
.setLineStyle(1)
|
|
|
|
)
|
|
|
|
},
|
|
|
|
[
|
|
|
|
cancelPerpOrder,
|
|
|
|
cancelSpotOrder,
|
|
|
|
modifyOrder,
|
|
|
|
selectedMarketName,
|
|
|
|
t,
|
|
|
|
theme,
|
|
|
|
getOrderDecimals,
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
const drawLinesForMarket = useCallback(
|
|
|
|
(openOrders: Record<string, Order[] | PerpOrder[]>) => {
|
|
|
|
const set = mangoStore.getState().set
|
|
|
|
const newOrderLines = new Map()
|
|
|
|
const oOrders = Object.entries(openOrders).map(([marketPk, orders]) => ({
|
|
|
|
orders,
|
|
|
|
marketPk,
|
|
|
|
}))
|
|
|
|
if (oOrders?.length) {
|
|
|
|
const selectedMarket = mangoStore.getState().selectedMarket.current
|
|
|
|
const selectedMarketPk =
|
|
|
|
selectedMarket instanceof Serum3Market
|
|
|
|
? selectedMarket?.serumMarketExternal.toString()
|
|
|
|
: selectedMarket?.publicKey.toString()
|
|
|
|
for (const { orders, marketPk } of oOrders) {
|
|
|
|
if (marketPk === selectedMarketPk) {
|
|
|
|
for (const order of orders) {
|
|
|
|
newOrderLines.set(order.orderId.toString(), drawLine(order))
|
|
|
|
}
|
|
|
|
}
|
2023-02-08 01:58:03 -08:00
|
|
|
}
|
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
set((state) => {
|
|
|
|
state.tradingView.orderLines = newOrderLines
|
|
|
|
})
|
|
|
|
},
|
|
|
|
[drawLine]
|
|
|
|
)
|
|
|
|
|
|
|
|
const toggleOrderLines = useCallback(
|
|
|
|
(el: HTMLElement) => {
|
|
|
|
toggleShowOrderLines((prevState: boolean) => !prevState)
|
|
|
|
if (el.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
|
|
|
|
deleteLines()
|
|
|
|
el.style.color = COLORS.FGD4[theme]
|
|
|
|
} else {
|
|
|
|
const openOrders = mangoStore.getState().mangoAccount.openOrders
|
|
|
|
drawLinesForMarket(openOrders)
|
|
|
|
el.style.color = COLORS.ACTIVE[theme]
|
2023-02-08 01:58:03 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
},
|
|
|
|
[drawLinesForMarket, deleteLines, theme]
|
|
|
|
)
|
|
|
|
|
2023-04-13 04:25:58 -07:00
|
|
|
const toggleTradeExecutions = useCallback(
|
|
|
|
(el: HTMLElement) => {
|
|
|
|
toggleShowTradeExecutions((prevState) => !prevState)
|
|
|
|
if (el.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
|
|
|
|
el.style.color = COLORS.FGD4[theme]
|
|
|
|
} else {
|
|
|
|
el.style.color = COLORS.ACTIVE[theme]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[theme]
|
|
|
|
)
|
|
|
|
|
2023-03-02 17:37:20 -08:00
|
|
|
const createStablePriceButton = useCallback(() => {
|
|
|
|
const toggleStablePrice = (button: HTMLElement) => {
|
|
|
|
toggleShowStablePrice((prevState: boolean) => !prevState)
|
|
|
|
if (button.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
|
|
|
|
button.style.color = COLORS.FGD4[theme]
|
|
|
|
} else {
|
|
|
|
button.style.color = COLORS.ACTIVE[theme]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const button = tvWidgetRef?.current?.createButton()
|
|
|
|
if (!button) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
button.textContent = 'SP'
|
|
|
|
button.setAttribute('title', t('tv-chart:toggle-stable-price'))
|
|
|
|
button.addEventListener('click', () => toggleStablePrice(button))
|
|
|
|
if (showStablePriceLocalStorage) {
|
|
|
|
button.style.color = COLORS.ACTIVE[theme]
|
|
|
|
} else {
|
|
|
|
button.style.color = COLORS.FGD4[theme]
|
|
|
|
}
|
|
|
|
stablePriceButtonRef.current = button
|
|
|
|
}, [theme, t, showStablePriceLocalStorage])
|
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
const createOLButton = useCallback(() => {
|
|
|
|
const button = tvWidgetRef?.current?.createButton()
|
|
|
|
if (!button) {
|
|
|
|
return
|
2023-02-08 01:58:03 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
button.textContent = 'OL'
|
2023-03-02 17:37:20 -08:00
|
|
|
button.setAttribute('title', t('tv-chart:toggle-order-line'))
|
|
|
|
button.addEventListener('click', () => toggleOrderLines(button))
|
2023-02-25 11:59:28 -08:00
|
|
|
if (showOrderLinesLocalStorage) {
|
|
|
|
button.style.color = COLORS.ACTIVE[theme]
|
|
|
|
} else {
|
|
|
|
button.style.color = COLORS.FGD4[theme]
|
|
|
|
}
|
2023-03-02 17:37:20 -08:00
|
|
|
orderLinesButtonRef.current = button
|
|
|
|
}, [t, toggleOrderLines, showOrderLinesLocalStorage, theme])
|
2023-02-25 11:59:28 -08:00
|
|
|
|
2023-04-13 04:25:58 -07:00
|
|
|
const createTEButton = useCallback(() => {
|
|
|
|
const button = tvWidgetRef?.current?.createButton()
|
|
|
|
if (!button) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
button.textContent = 'TE'
|
|
|
|
button.setAttribute('title', t('tv-chart:toggle-trade-executions'))
|
|
|
|
button.addEventListener('click', () => toggleTradeExecutions(button))
|
|
|
|
if (showTradeExecutions) {
|
|
|
|
button.style.color = COLORS.ACTIVE[theme]
|
|
|
|
} else {
|
|
|
|
button.style.color = COLORS.FGD4[theme]
|
|
|
|
}
|
|
|
|
}, [t, toggleTradeExecutions, showTradeExecutions, theme])
|
|
|
|
|
2023-02-25 11:59:28 -08:00
|
|
|
useEffect(() => {
|
|
|
|
if (window) {
|
|
|
|
let chartStyleOverrides = {
|
|
|
|
'paneProperties.background': 'rgba(0,0,0,0)',
|
|
|
|
'paneProperties.backgroundType': 'solid',
|
|
|
|
'paneProperties.legendProperties.showBackground': false,
|
2023-03-04 12:52:09 -08:00
|
|
|
// 'paneProperties.vertGridProperties.color': 'rgba(0,0,0,0)',
|
|
|
|
// 'paneProperties.horzGridProperties.color': 'rgba(0,0,0,0)',
|
2023-02-25 11:59:28 -08:00
|
|
|
'paneProperties.legendProperties.showStudyTitles': false,
|
|
|
|
'scalesProperties.showStudyLastValue': false,
|
|
|
|
'scalesProperties.fontSize': 11,
|
|
|
|
}
|
|
|
|
|
|
|
|
const mainSeriesProperties = [
|
|
|
|
'candleStyle',
|
|
|
|
'hollowCandleStyle',
|
|
|
|
'haStyle',
|
|
|
|
'barStyle',
|
|
|
|
]
|
|
|
|
|
|
|
|
mainSeriesProperties.forEach((prop) => {
|
|
|
|
chartStyleOverrides = {
|
|
|
|
...chartStyleOverrides,
|
|
|
|
[`mainSeriesProperties.${prop}.barColorsOnPrevClose`]: true,
|
|
|
|
[`mainSeriesProperties.${prop}.drawWick`]: true,
|
|
|
|
[`mainSeriesProperties.${prop}.drawBorder`]: true,
|
|
|
|
[`mainSeriesProperties.${prop}.upColor`]: COLORS.UP[theme],
|
|
|
|
[`mainSeriesProperties.${prop}.downColor`]: COLORS.DOWN[theme],
|
|
|
|
[`mainSeriesProperties.${prop}.borderColor`]: COLORS.UP[theme],
|
|
|
|
[`mainSeriesProperties.${prop}.borderUpColor`]: COLORS.UP[theme],
|
|
|
|
[`mainSeriesProperties.${prop}.borderDownColor`]: COLORS.DOWN[theme],
|
|
|
|
[`mainSeriesProperties.${prop}.wickUpColor`]: COLORS.UP[theme],
|
|
|
|
[`mainSeriesProperties.${prop}.wickDownColor`]: COLORS.DOWN[theme],
|
|
|
|
}
|
|
|
|
})
|
2023-03-04 12:42:35 -08:00
|
|
|
const marketAddress =
|
|
|
|
mangoStore.getState().selectedMarket.current?.publicKey.toString() ||
|
|
|
|
'8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'
|
2023-02-25 11:59:28 -08:00
|
|
|
const widgetOptions: ChartingLibraryWidgetOptions = {
|
|
|
|
// debug: true,
|
2023-03-04 12:42:35 -08:00
|
|
|
symbol: marketAddress,
|
2023-03-04 21:48:09 -08:00
|
|
|
datafeed: Datafeed,
|
2023-02-25 11:59:28 -08:00
|
|
|
interval:
|
|
|
|
defaultProps.interval as ChartingLibraryWidgetOptions['interval'],
|
|
|
|
container:
|
|
|
|
defaultProps.container as ChartingLibraryWidgetOptions['container'],
|
|
|
|
library_path: defaultProps.libraryPath as string,
|
|
|
|
locale: 'en',
|
|
|
|
enabled_features: ['hide_left_toolbar_by_default'],
|
|
|
|
disabled_features: [
|
|
|
|
'use_localstorage_for_settings',
|
|
|
|
'timeframes_toolbar',
|
|
|
|
isMobile ? 'left_toolbar' : '',
|
|
|
|
'show_logo_on_all_charts',
|
|
|
|
'caption_buttons_text_if_possible',
|
|
|
|
'header_settings',
|
|
|
|
// 'header_chart_type',
|
|
|
|
'header_compare',
|
|
|
|
'compare_symbol',
|
|
|
|
'header_screenshot',
|
|
|
|
// 'header_widget_dom_node',
|
|
|
|
// 'header_widget',
|
|
|
|
'header_saveload',
|
|
|
|
'header_undo_redo',
|
|
|
|
'header_interval_dialog_button',
|
|
|
|
'show_interval_dialog_on_key_press',
|
|
|
|
'header_symbol_search',
|
|
|
|
'popup_hints',
|
|
|
|
],
|
2023-03-31 13:29:06 -07:00
|
|
|
// eslint-disable-next-line
|
|
|
|
// @ts-ignore
|
|
|
|
custom_formatters: {
|
|
|
|
priceFormatterFactory: () => {
|
|
|
|
return {
|
|
|
|
format: (price) => {
|
|
|
|
// return the appropriate format
|
|
|
|
return formatPrice(price)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2023-02-25 11:59:28 -08:00
|
|
|
fullscreen: defaultProps.fullscreen,
|
|
|
|
autosize: defaultProps.autosize,
|
|
|
|
studies_overrides: defaultProps.studiesOverrides,
|
|
|
|
theme:
|
|
|
|
theme === 'Light' || theme === 'Banana' || theme === 'Lychee'
|
|
|
|
? 'Light'
|
|
|
|
: 'Dark',
|
|
|
|
custom_css_url: '/styles/tradingview.css',
|
|
|
|
loading_screen: {
|
|
|
|
backgroundColor: COLORS.BKG1[theme],
|
|
|
|
},
|
|
|
|
overrides: {
|
|
|
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
|
|
...chartStyleOverrides,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-02-26 17:04:02 -08:00
|
|
|
console.log('creating new chart')
|
2023-02-25 11:59:28 -08:00
|
|
|
const tvWidget = new widget(widgetOptions)
|
|
|
|
tvWidgetRef.current = tvWidget
|
2023-03-04 09:10:34 -08:00
|
|
|
tvWidgetRef.current.onChartReady(() => {
|
2023-02-25 11:59:28 -08:00
|
|
|
setChartReady(true)
|
|
|
|
})
|
2023-03-06 11:50:07 -08:00
|
|
|
tvWidgetRef.current.headerReady().then(() => {
|
|
|
|
setHeaderReady(true)
|
|
|
|
})
|
2023-02-25 11:59:28 -08:00
|
|
|
}
|
2023-03-04 12:42:35 -08:00
|
|
|
}, [theme, defaultProps, isMobile])
|
2023-03-02 17:37:20 -08:00
|
|
|
|
|
|
|
// draw custom buttons when chart is ready
|
|
|
|
useEffect(() => {
|
|
|
|
if (
|
|
|
|
chartReady &&
|
2023-03-06 11:50:07 -08:00
|
|
|
headerReady &&
|
2023-03-02 17:37:20 -08:00
|
|
|
!orderLinesButtonRef.current &&
|
|
|
|
!stablePriceButtonRef.current
|
|
|
|
) {
|
2023-03-04 12:42:35 -08:00
|
|
|
createOLButton()
|
2023-04-13 04:25:58 -07:00
|
|
|
createTEButton()
|
2023-03-04 12:42:35 -08:00
|
|
|
createStablePriceButton()
|
2023-03-02 17:37:20 -08:00
|
|
|
}
|
2023-04-13 04:25:58 -07:00
|
|
|
}, [
|
|
|
|
createOLButton,
|
|
|
|
createTEButton,
|
|
|
|
chartReady,
|
|
|
|
createStablePriceButton,
|
|
|
|
headerReady,
|
|
|
|
])
|
2023-02-25 11:59:28 -08:00
|
|
|
|
|
|
|
// update order lines if a user's open orders change
|
|
|
|
useEffect(() => {
|
|
|
|
let subscription
|
|
|
|
if (chartReady && tvWidgetRef?.current) {
|
|
|
|
subscription = mangoStore.subscribe(
|
|
|
|
(state) => state.mangoAccount.openOrders,
|
|
|
|
(openOrders) => {
|
2023-03-02 17:37:20 -08:00
|
|
|
if (showOrderLines) {
|
|
|
|
const orderLines = mangoStore.getState().tradingView.orderLines
|
|
|
|
tvWidgetRef.current?.onChartReady(() => {
|
|
|
|
let matchingOrderLines = 0
|
|
|
|
let openOrdersForMarket = 0
|
|
|
|
|
|
|
|
const oOrders = Object.entries(openOrders).map(
|
|
|
|
([marketPk, orders]) => ({
|
|
|
|
orders,
|
|
|
|
marketPk,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
for (const [key] of orderLines) {
|
|
|
|
oOrders?.forEach(({ orders }) => {
|
|
|
|
for (const order of orders) {
|
|
|
|
if (order.orderId == key) {
|
|
|
|
matchingOrderLines += 1
|
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
}
|
2023-03-02 17:37:20 -08:00
|
|
|
})
|
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
|
2023-03-02 17:37:20 -08:00
|
|
|
const selectedMarket =
|
|
|
|
mangoStore.getState().selectedMarket.current
|
|
|
|
const selectedMarketPk =
|
|
|
|
selectedMarket instanceof Serum3Market
|
|
|
|
? selectedMarket?.serumMarketExternal.toString()
|
|
|
|
: selectedMarket?.publicKey.toString()
|
2023-02-25 11:59:28 -08:00
|
|
|
|
2023-03-02 17:37:20 -08:00
|
|
|
oOrders?.forEach(({ marketPk, orders }) => {
|
|
|
|
if (marketPk === selectedMarketPk) {
|
|
|
|
openOrdersForMarket = orders.length
|
|
|
|
}
|
|
|
|
})
|
2023-02-25 11:59:28 -08:00
|
|
|
|
2023-03-02 17:37:20 -08:00
|
|
|
tvWidgetRef.current?.activeChart().dataReady(() => {
|
|
|
|
if (
|
|
|
|
matchingOrderLines !== openOrdersForMarket ||
|
|
|
|
orderLines?.size !== matchingOrderLines
|
|
|
|
) {
|
|
|
|
deleteLines()
|
|
|
|
drawLinesForMarket(openOrders)
|
|
|
|
}
|
|
|
|
})
|
2023-02-25 11:59:28 -08:00
|
|
|
})
|
2023-03-02 17:37:20 -08:00
|
|
|
}
|
2023-02-25 11:59:28 -08:00
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return subscription
|
|
|
|
}, [chartReady, showOrderLines, deleteLines, drawLinesForMarket])
|
2022-09-13 23:24:26 -07:00
|
|
|
|
2023-04-13 04:25:58 -07:00
|
|
|
// Todo: fix types
|
|
|
|
const drawTradeExecutions = useCallback(
|
|
|
|
(trades: any[]) => {
|
|
|
|
const newTradeExecutions = new Map()
|
|
|
|
const filteredTrades = trades
|
|
|
|
.filter((trade) => {
|
|
|
|
return trade.market.name === selectedMarketName
|
|
|
|
})
|
2023-04-16 19:13:50 -07:00
|
|
|
.slice()
|
2023-04-13 04:25:58 -07:00
|
|
|
for (let i = 0; i < filteredTrades.length; i++) {
|
|
|
|
const trade = filteredTrades[i]
|
|
|
|
try {
|
|
|
|
const arrowID = tvWidgetRef
|
|
|
|
.current!.chart()
|
|
|
|
.createExecutionShape()
|
|
|
|
.setTime(dayjs(trade.time).unix())
|
|
|
|
.setDirection(trade.side)
|
|
|
|
.setArrowHeight(6)
|
|
|
|
.setArrowColor(
|
|
|
|
trade.side === 'buy' ? COLORS.UP[theme] : COLORS.DOWN[theme]
|
|
|
|
)
|
|
|
|
.setTooltip(`${trade.size} at ${trade.price}`)
|
|
|
|
if (arrowID) {
|
|
|
|
try {
|
|
|
|
newTradeExecutions.set(`${trade.time}${i}`, arrowID)
|
|
|
|
} catch (error) {
|
|
|
|
console.log('could not set newTradeExecution')
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.log(
|
|
|
|
`Could not create execution shape for trade ${trade.time}${i}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.log(`could not draw arrow: ${error}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return newTradeExecutions
|
|
|
|
},
|
|
|
|
[selectedMarketName, theme]
|
|
|
|
)
|
|
|
|
|
|
|
|
const removeTradeExecutions = useCallback(
|
|
|
|
(tradeExecutions: Map<string, IExecutionLineAdapter>) => {
|
|
|
|
const set = mangoStore.getState().set
|
|
|
|
if (chartReady && tvWidgetRef?.current) {
|
|
|
|
for (const val of tradeExecutions.values()) {
|
|
|
|
try {
|
|
|
|
val.remove()
|
|
|
|
} catch (error) {
|
|
|
|
console.log(`arrow ${val} could not be removed`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
set((s) => {
|
|
|
|
s.tradingView.tradeExecutions = new Map()
|
|
|
|
})
|
|
|
|
},
|
|
|
|
[chartReady, tvWidgetRef?.current]
|
|
|
|
)
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!loadingTradeHistory && showTradeExecutions) {
|
|
|
|
setCachedTradeHistory(combinedTradeHistory)
|
|
|
|
}
|
|
|
|
}, [loadingTradeHistory, showTradeExecutions])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (cachedTradeHistory.length !== combinedTradeHistory.length) {
|
|
|
|
setCachedTradeHistory(combinedTradeHistory)
|
|
|
|
}
|
|
|
|
}, [combinedTradeHistory])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
removeTradeExecutions(tradeExecutions)
|
|
|
|
if (
|
|
|
|
showTradeExecutions &&
|
|
|
|
tvWidgetRef &&
|
|
|
|
tvWidgetRef.current &&
|
|
|
|
chartReady
|
|
|
|
) {
|
|
|
|
const set = mangoStore.getState().set
|
|
|
|
set((s) => {
|
|
|
|
s.tradingView.tradeExecutions = drawTradeExecutions(cachedTradeHistory)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}, [cachedTradeHistory, selectedMarketName, showTradeExecutions])
|
|
|
|
|
2022-09-13 23:24:26 -07:00
|
|
|
return (
|
|
|
|
<div id={defaultProps.container as string} className="tradingview-chart" />
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default TradingViewChart
|