Merge pull request #82 from blockworks-foundation/tv-orders
add order lines to trading view chart
This commit is contained in:
commit
7b3578a9b4
|
@ -1,16 +1,37 @@
|
|||
import { useEffect, useRef, useMemo, useState } from 'react'
|
||||
import { useEffect, useRef, useMemo, useState, useCallback } from 'react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import {
|
||||
widget,
|
||||
ChartingLibraryWidgetOptions,
|
||||
IChartingLibraryWidget,
|
||||
ResolutionString,
|
||||
IOrderLineAdapter,
|
||||
} from '@public/charting_library'
|
||||
import mangoStore from '@store/mangoStore'
|
||||
import { useViewport } from 'hooks/useViewport'
|
||||
import { CHART_DATA_FEED, DEFAULT_MARKET_NAME } from 'utils/constants'
|
||||
import {
|
||||
CHART_DATA_FEED,
|
||||
DEFAULT_MARKET_NAME,
|
||||
SHOW_ORDER_LINES_KEY,
|
||||
} from 'utils/constants'
|
||||
import { breakpoints } from 'utils/theme'
|
||||
import { COLORS } from 'styles/colors'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
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'
|
||||
import SpotDatafeed from 'apis/birdeye/datafeed'
|
||||
import PerpDatafeed from 'apis/mngo/datafeed'
|
||||
|
||||
|
@ -30,12 +51,27 @@ export interface ChartContainerProps {
|
|||
theme: string
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const TradingViewChart = () => {
|
||||
const { t } = useTranslation(['tv-chart', 'trade'])
|
||||
const { theme } = useTheme()
|
||||
const { width } = useViewport()
|
||||
|
||||
const [chartReady, setChartReady] = useState(false)
|
||||
const [spotOrPerp, setSpotOrPerp] = useState('spot')
|
||||
const [showOrderLinesLocalStorage, toggleShowOrderLinesLocalStorage] =
|
||||
useLocalStorageState(SHOW_ORDER_LINES_KEY, true)
|
||||
const [showOrderLines, toggleShowOrderLines] = useState(
|
||||
showOrderLinesLocalStorage
|
||||
)
|
||||
const selectedMarketName = mangoStore((s) => s.selectedMarket.current?.name)
|
||||
const isMobile = width ? width < breakpoints.sm : false
|
||||
|
||||
|
@ -183,26 +219,7 @@ const TradingViewChart = () => {
|
|||
: 'Dark',
|
||||
custom_css_url: '/styles/tradingview.css',
|
||||
loading_screen: {
|
||||
backgroundColor:
|
||||
theme === 'Dark'
|
||||
? COLORS.BKG1.Dark
|
||||
: theme === 'Light'
|
||||
? COLORS.BKG1.Light
|
||||
: theme === 'Mango Classic'
|
||||
? COLORS.BKG1['Mango Classic']
|
||||
: theme === 'Medium'
|
||||
? COLORS.BKG1.Medium
|
||||
: theme === 'Avocado'
|
||||
? COLORS.BKG1.Avocado
|
||||
: theme === 'Blueberry'
|
||||
? COLORS.BKG1.Blueberry
|
||||
: theme === 'Banana'
|
||||
? COLORS.BKG1.Banana
|
||||
: theme === 'Lychee'
|
||||
? COLORS.BKG1.Lychee
|
||||
: theme === 'Olive'
|
||||
? COLORS.BKG1.Olive
|
||||
: COLORS.BKG1['High Contrast'],
|
||||
backgroundColor: COLORS.BKG1[theme],
|
||||
},
|
||||
overrides: {
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
|
@ -215,12 +232,437 @@ const TradingViewChart = () => {
|
|||
tvWidgetRef.current = tvWidget
|
||||
|
||||
tvWidgetRef.current.onChartReady(function () {
|
||||
createOLButton()
|
||||
if (showOrderLines) {
|
||||
const openOrders = mangoStore.getState().mangoAccount.openOrders
|
||||
deleteLines()
|
||||
drawLinesForMarket(openOrders)
|
||||
}
|
||||
setChartReady(true)
|
||||
})
|
||||
//eslint-disable-next-line
|
||||
}
|
||||
}, [theme, isMobile, defaultProps, spotOrPerp])
|
||||
|
||||
const createOLButton = () => {
|
||||
const button = tvWidgetRef?.current?.createButton()
|
||||
if (!button) {
|
||||
return
|
||||
}
|
||||
button.textContent = 'OL'
|
||||
if (showOrderLinesLocalStorage) {
|
||||
button.style.color = COLORS.ACTIVE[theme]
|
||||
} else {
|
||||
button.style.color = COLORS.FGD4[theme]
|
||||
}
|
||||
button.setAttribute('title', t('tv-chart:toggle-order-line'))
|
||||
button.addEventListener('click', toggleOrderLines)
|
||||
}
|
||||
|
||||
function toggleOrderLines(this: HTMLElement) {
|
||||
toggleShowOrderLines((prevState: boolean) => !prevState)
|
||||
if (this.style.color === hexToRgb(COLORS.ACTIVE[theme])) {
|
||||
deleteLines()
|
||||
this.style.color = COLORS.FGD4[theme]
|
||||
} else {
|
||||
const openOrders = mangoStore.getState().mangoAccount.openOrders
|
||||
drawLinesForMarket(openOrders)
|
||||
this.style.color = COLORS.ACTIVE[theme]
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (showOrderLines !== showOrderLinesLocalStorage) {
|
||||
toggleShowOrderLinesLocalStorage(showOrderLines)
|
||||
}
|
||||
}, [showOrderLines])
|
||||
|
||||
// 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) => {
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const selectedMarket = mangoStore.getState().selectedMarket.current
|
||||
const selectedMarketPk =
|
||||
selectedMarket instanceof Serum3Market
|
||||
? selectedMarket?.serumMarketExternal.toString()
|
||||
: selectedMarket?.publicKey.toString()
|
||||
|
||||
oOrders?.forEach(({ marketPk }) => {
|
||||
if (marketPk === selectedMarketPk) {
|
||||
openOrdersForMarket += 1
|
||||
}
|
||||
})
|
||||
|
||||
tvWidgetRef.current?.activeChart().dataReady(() => {
|
||||
if (
|
||||
(showOrderLines &&
|
||||
matchingOrderLines !== openOrdersForMarket) ||
|
||||
orderLines?.size != matchingOrderLines
|
||||
) {
|
||||
deleteLines()
|
||||
drawLinesForMarket(openOrders)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
return subscription
|
||||
}, [chartReady, showOrderLines])
|
||||
|
||||
const drawLinesForMarket = (
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set((state) => {
|
||||
state.tradingView.orderLines = newOrderLines
|
||||
})
|
||||
}
|
||||
|
||||
const deleteLines = () => {
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getOrderDecimals() {
|
||||
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]
|
||||
}
|
||||
|
||||
function drawLine(order: Order | PerpOrder) {
|
||||
const side =
|
||||
typeof order.side === 'string'
|
||||
? t(order.side)
|
||||
: 'bid' in order.side
|
||||
? t('trade:long')
|
||||
: t('trade:short')
|
||||
const isLong = side === 'buy' || side === 'long'
|
||||
const isShort = side === 'sell' || side === 'short'
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.error('Error canceling', e)
|
||||
notify({
|
||||
title: 'Unable to modify order',
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
[t]
|
||||
)
|
||||
|
||||
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,
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.error('Error canceling', e)
|
||||
notify({
|
||||
title: t('trade:cancel-order-error'),
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
[t]
|
||||
)
|
||||
|
||||
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,
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.error('Error canceling', e)
|
||||
notify({
|
||||
title: t('trade:cancel-order-error'),
|
||||
description: e.message,
|
||||
txid: e.txid,
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
},
|
||||
[t]
|
||||
)
|
||||
|
||||
const findSerum3MarketPkInOpenOrders = (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
|
||||
}
|
||||
}
|
||||
if (foundedMarketPk) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return foundedMarketPk
|
||||
}
|
||||
|
||||
return (
|
||||
<div id={defaultProps.container as string} className="tradingview-chart" />
|
||||
)
|
||||
|
|
|
@ -23,6 +23,7 @@ export async function getStaticProps({ locale }: { locale: string }) {
|
|||
'settings',
|
||||
'trade',
|
||||
'close-account',
|
||||
'tv-chart',
|
||||
])),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"instantaneous-funding": "Instantaneous Funding",
|
||||
"interval-seconds": "Interval (seconds)",
|
||||
"limit-price": "Limit Price",
|
||||
"long": "Long",
|
||||
"margin": "Margin",
|
||||
"no-balances": "No balances",
|
||||
"no-orders": "No open orders",
|
||||
|
@ -46,6 +47,7 @@
|
|||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"short": "Short",
|
||||
"show-asks": "Show Asks",
|
||||
"show-bids": "Show Bids",
|
||||
"side": "Side",
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"advanced-order": "Advanced Order Type",
|
||||
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, please cancel this order and use the Advanced Trade Form.",
|
||||
"cancel-order": "Cancel Your Order?",
|
||||
"cancel-order-details": "Would you like to cancel your order for {{orderSize}} {{baseSymbol}} {{orderSide}} at ${{orderPrice}}?",
|
||||
"modify-order": "Modify Your Order?",
|
||||
"modify-order-details": "Would you like to change your order from a {{orderSize}} {{baseSymbol}} {{orderSide}} at ${{currentOrderPrice}} to a {{orderSize}} {{baseSymbol}} LIMIT {{orderSide}} at ${{updatedOrderPrice}}?",
|
||||
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, cancel this order and use the trade order form.",
|
||||
"cancel-order": "Cancel Order",
|
||||
"cancel-order-details": "Cancel your order for {{orderSide}} {{orderSize}} {{marketName}} at {{orderPrice}}",
|
||||
"modify-order": "Modify Order",
|
||||
"modify-order-details": "Edit your {{marketName}} order from {{orderSide}} {{orderSize}} at {{currentOrderPrice}} to {{orderSide}} {{orderSize}} at {{updatedOrderPrice}}",
|
||||
"order-details": " ({{orderType}} {{orderSide}}) if price is {{triggerCondition}} {{triggerPrice}}",
|
||||
"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"
|
||||
"slippage-accept": "Use the trade order form if you wish to accept the potential slippage.",
|
||||
"slippage-warning": "{{updatedOrderPrice}} is greater than 5% {{aboveBelow}} the current market price of {{selectedMarketPrice}}. Executing this trade could incur significant slippage.",
|
||||
"toggle-order-line": "Toggle order line visibility",
|
||||
"toggle-trade-executions": "Toggle trade execution visibility"
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
"instantaneous-funding": "Instantaneous Funding",
|
||||
"interval-seconds": "Interval (seconds)",
|
||||
"limit-price": "Limit Price",
|
||||
"long": "Long",
|
||||
"margin": "Margin",
|
||||
"no-balances": "No balances",
|
||||
"no-orders": "No open orders",
|
||||
|
@ -46,6 +47,7 @@
|
|||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"short": "Short",
|
||||
"show-asks": "Show Asks",
|
||||
"show-bids": "Show Bids",
|
||||
"side": "Side",
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"advanced-order": "Advanced Order Type",
|
||||
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, please cancel this order and use the Advanced Trade Form.",
|
||||
"cancel-order": "Cancel Your Order?",
|
||||
"cancel-order-details": "Would you like to cancel your order for {{orderSize}} {{baseSymbol}} {{orderSide}} at ${{orderPrice}}?",
|
||||
"modify-order": "Modify Your Order?",
|
||||
"modify-order-details": "Would you like to change your order from a {{orderSize}} {{baseSymbol}} {{orderSide}} at ${{currentOrderPrice}} to a {{orderSize}} {{baseSymbol}} LIMIT {{orderSide}} at ${{updatedOrderPrice}}?",
|
||||
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, cancel this order and use the trade order form.",
|
||||
"cancel-order": "Cancel Order",
|
||||
"cancel-order-details": "Cancel your order for {{orderSide}} {{orderSize}} {{marketName}} at {{orderPrice}}",
|
||||
"modify-order": "Modify Order",
|
||||
"modify-order-details": "Edit your {{marketName}} order from {{orderSide}} {{orderSize}} at {{currentOrderPrice}} to {{orderSide}} {{orderSize}} at {{updatedOrderPrice}}",
|
||||
"order-details": " ({{orderType}} {{orderSide}}) if price is {{triggerCondition}} {{triggerPrice}}",
|
||||
"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"
|
||||
"slippage-accept": "Use the trade order form if you wish to accept the potential slippage.",
|
||||
"slippage-warning": "{{updatedOrderPrice}} is greater than 5% {{aboveBelow}} the current market price of {{selectedMarketPrice}}. Executing this trade could incur significant slippage.",
|
||||
"toggle-order-line": "Toggle order line visibility",
|
||||
"toggle-trade-executions": "Toggle trade execution visibility"
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
"instantaneous-funding": "Instantaneous Funding",
|
||||
"interval-seconds": "Interval (seconds)",
|
||||
"limit-price": "Limit Price",
|
||||
"long": "Long",
|
||||
"margin": "Margin",
|
||||
"no-balances": "No balances",
|
||||
"no-orders": "No open orders",
|
||||
|
@ -46,6 +47,7 @@
|
|||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"short": "Short",
|
||||
"show-asks": "Show Asks",
|
||||
"show-bids": "Show Bids",
|
||||
"side": "Side",
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"advanced-order": "高級訂單類型",
|
||||
"advanced-order-details": "在圖表窗口中高級訂單類型只能取消。如果需要新條件,請取消此訂單並使用高級交易表格進行。",
|
||||
"cancel-order": "取消訂單嗎?",
|
||||
"cancel-order-details": "您確定要取消{{orderSize}} {{baseSymbol}} {{orderSide}} 價格${{orderPrice}}的掛單嗎?",
|
||||
"modify-order": "改您的訂單嗎?",
|
||||
"modify-order-details": "您確定要把{{orderSize}} {{baseSymbol}}{{orderSide}} 價格${{currentOrderPrice}}的掛單改成{{orderSize}} {{baseSymbol}}限價{{orderSide}} 價格${{updatedOrderPrice}}嗎?",
|
||||
"order-details": "({{orderType}}{{orderSide}})若價格{{triggerCondition}}{{triggerPrice}}",
|
||||
"outside-range": "訂單價格在範圍之外",
|
||||
"slippage-accept": "若您接受潛在的下滑請使用交易表格進行。",
|
||||
"slippage-warning": "您的訂單價格({{updatedOrderPrice}})多餘5%{{aboveBelow}}市場價格({{selectedMarketPrice}})表是您也許遭受可觀的下滑。",
|
||||
"toggle-order-line": "切換訂單線可見性"
|
||||
"advanced-order": "Advanced Order Type",
|
||||
"advanced-order-details": "Advanced order types in the chart window may only be cancelled. If new conditions are required, cancel this order and use the trade order form.",
|
||||
"cancel-order": "Cancel Order",
|
||||
"cancel-order-details": "Cancel your order for {{orderSide}} {{orderSize}} {{marketName}} at {{orderPrice}}",
|
||||
"modify-order": "Modify Order",
|
||||
"modify-order-details": "Edit your {{marketName}} order from {{orderSide}} {{orderSize}} at {{currentOrderPrice}} to {{orderSide}} {{orderSize}} at {{updatedOrderPrice}}",
|
||||
"order-details": " ({{orderType}} {{orderSide}}) if price is {{triggerCondition}} {{triggerPrice}}",
|
||||
"outside-range": "Order Price Outside Range",
|
||||
"slippage-accept": "Use the trade order form if you wish to accept the potential slippage.",
|
||||
"slippage-warning": "{{updatedOrderPrice}} is greater than 5% {{aboveBelow}} the current market price of {{selectedMarketPrice}}. Executing this trade could incur significant slippage.",
|
||||
"toggle-order-line": "Toggle order line visibility",
|
||||
"toggle-trade-executions": "Toggle trade execution visibility"
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
"instantaneous-funding": "Instantaneous Funding",
|
||||
"interval-seconds": "Interval (seconds)",
|
||||
"limit-price": "Limit Price",
|
||||
"long": "Long",
|
||||
"margin": "Margin",
|
||||
"no-balances": "No balances",
|
||||
"no-orders": "No open orders",
|
||||
|
@ -45,6 +46,7 @@
|
|||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"short": "Short",
|
||||
"show-asks": "Show Asks",
|
||||
"show-bids": "Show Bids",
|
||||
"side": "Side",
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
"instantaneous-funding": "Instantaneous Funding",
|
||||
"interval-seconds": "Interval (seconds)",
|
||||
"limit-price": "Limit Price",
|
||||
"long": "Long",
|
||||
"margin": "Margin",
|
||||
"no-balances": "No balances",
|
||||
"no-orders": "No open orders",
|
||||
|
@ -45,6 +46,7 @@
|
|||
"sells": "Sells",
|
||||
"settle-funds": "Settle Funds",
|
||||
"settle-funds-error": "Failed to settle funds",
|
||||
"short": "Short",
|
||||
"show-asks": "Show Asks",
|
||||
"show-bids": "Show Bids",
|
||||
"side": "Side",
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -2,7 +2,7 @@ import dayjs from 'dayjs'
|
|||
import produce from 'immer'
|
||||
import create from 'zustand'
|
||||
import { subscribeWithSelector } from 'zustand/middleware'
|
||||
import { AnchorProvider, Wallet, web3 } from '@project-serum/anchor'
|
||||
import { AnchorProvider, BN, Wallet, web3 } from '@project-serum/anchor'
|
||||
import { Connection, Keypair, PublicKey } from '@solana/web3.js'
|
||||
import { OpenOrders, Order } from '@project-serum/serum/lib/market'
|
||||
import { Orderbook } from '@project-serum/serum'
|
||||
|
@ -49,6 +49,7 @@ import spotBalancesUpdater from './spotBalancesUpdater'
|
|||
import { PerpMarket } from '@blockworks-foundation/mango-v4/'
|
||||
import perpPositionsUpdater from './perpPositionsUpdater'
|
||||
import { DEFAULT_PRIORITY_FEE } from '@components/settings/RpcSettings'
|
||||
import { IOrderLineAdapter } from '@public/charting_library/charting_library'
|
||||
|
||||
const GROUP = new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX')
|
||||
|
||||
|
@ -329,6 +330,9 @@ export type MangoStore = {
|
|||
data: TokenStatsItem[] | null
|
||||
}
|
||||
tradeForm: TradeForm
|
||||
tradingView: {
|
||||
orderLines: Map<string | BN, IOrderLineAdapter>
|
||||
}
|
||||
wallet: {
|
||||
tokens: TokenAccount[]
|
||||
nfts: {
|
||||
|
@ -473,6 +477,9 @@ const mangoStore = create<MangoStore>()(
|
|||
data: [],
|
||||
},
|
||||
tradeForm: DEFAULT_TRADE_FORM,
|
||||
tradingView: {
|
||||
orderLines: new Map(),
|
||||
},
|
||||
wallet: {
|
||||
tokens: [],
|
||||
nfts: {
|
||||
|
|
|
@ -45,6 +45,8 @@ export const RPC_PROVIDER_KEY = 'rpcProviderKey-0.4'
|
|||
|
||||
export const PRIORITY_FEE_KEY = 'priorityFeeKey-0.1'
|
||||
|
||||
export const SHOW_ORDER_LINES_KEY = 'showOrderLines-0.1'
|
||||
|
||||
// Unused
|
||||
export const PROFILE_CATEGORIES = [
|
||||
'borrower',
|
||||
|
|
Loading…
Reference in New Issue