优化:订单簿信息的完善
This commit is contained in:
parent
6b12938c89
commit
8c455ba00a
@ -249,11 +249,11 @@ export class ClobSdk {
|
|||||||
// 2. 基于 'e' 字段的事件
|
// 2. 基于 'e' 字段的事件
|
||||||
switch (msg.e) {
|
switch (msg.e) {
|
||||||
case 'price_size_all':
|
case 'price_size_all':
|
||||||
console.log('[ClobSdk] 回调: price_size_all', { m: msg.m, t: msg.t, bids: msg.b, asks: msg.s });
|
console.log('[ClobSdk] 回调: price_size_all', { i: msg.i, m: msg.m, t: msg.t, bids: msg.b, asks: msg.s });
|
||||||
this.listeners.priceSizeAll.forEach(cb => cb(msg as PriceSizePolyMsg));
|
this.listeners.priceSizeAll.forEach(cb => cb(msg as PriceSizePolyMsg));
|
||||||
break;
|
break;
|
||||||
case 'price_size_delta':
|
case 'price_size_delta':
|
||||||
console.log('[ClobSdk] 回调: price_size_delta', { m: msg.m, t: msg.t, bids: msg.b, asks: msg.s });
|
console.log('[ClobSdk] 回调: price_size_delta', { i: msg.i, m: msg.m, t: msg.t, bids: msg.b, asks: msg.s });
|
||||||
this.listeners.priceSizeDelta.forEach(cb => cb(msg as PriceSizePolyMsg));
|
this.listeners.priceSizeDelta.forEach(cb => cb(msg as PriceSizePolyMsg));
|
||||||
break;
|
break;
|
||||||
case 'trade':
|
case 'trade':
|
||||||
|
|||||||
@ -99,18 +99,35 @@ export interface OrderBookRow {
|
|||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
/** Yes token 订单簿(index 0) */
|
||||||
|
asksYes?: OrderBookRow[]
|
||||||
|
bidsYes?: OrderBookRow[]
|
||||||
|
/** No token 订单簿(index 1) */
|
||||||
|
asksNo?: OrderBookRow[]
|
||||||
|
bidsNo?: OrderBookRow[]
|
||||||
|
lastPriceYes?: number
|
||||||
|
lastPriceNo?: number
|
||||||
|
spreadYes?: number
|
||||||
|
spreadNo?: number
|
||||||
|
/** @deprecated 兼容旧用法,优先使用 asksYes/asksNo */
|
||||||
asks?: OrderBookRow[]
|
asks?: OrderBookRow[]
|
||||||
bids?: OrderBookRow[]
|
bids?: OrderBookRow[]
|
||||||
lastPrice?: number
|
lastPrice?: number
|
||||||
spread?: number
|
spread?: number
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
connected?: boolean
|
connected?: boolean
|
||||||
/** 市场 Yes 选项文案,来自 market.outcomes[0] */
|
|
||||||
yesLabel?: string
|
yesLabel?: string
|
||||||
/** 市场 No 选项文案,来自 market.outcomes[1] */
|
|
||||||
noLabel?: string
|
noLabel?: string
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
|
asksYes: () => [],
|
||||||
|
bidsYes: () => [],
|
||||||
|
asksNo: () => [],
|
||||||
|
bidsNo: () => [],
|
||||||
|
lastPriceYes: undefined,
|
||||||
|
lastPriceNo: undefined,
|
||||||
|
spreadYes: undefined,
|
||||||
|
spreadNo: undefined,
|
||||||
asks: () => [],
|
asks: () => [],
|
||||||
bids: () => [],
|
bids: () => [],
|
||||||
lastPrice: undefined,
|
lastPrice: undefined,
|
||||||
@ -122,33 +139,60 @@ const props = withDefaults(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// State
|
// State:up = Yes 交易,down = No 交易
|
||||||
const activeTrade = ref('up')
|
const activeTrade = ref('up')
|
||||||
|
|
||||||
// 使用 props 或回退到 mock 数据(来自 mockData 统一封装)
|
|
||||||
const internalAsks = ref<OrderBookRow[]>([...MOCK_ORDER_BOOK_ASKS])
|
const internalAsks = ref<OrderBookRow[]>([...MOCK_ORDER_BOOK_ASKS])
|
||||||
const internalBids = ref<OrderBookRow[]>([...MOCK_ORDER_BOOK_BIDS])
|
const internalBids = ref<OrderBookRow[]>([...MOCK_ORDER_BOOK_BIDS])
|
||||||
const internalLastPrice = ref(MOCK_ORDER_BOOK_LAST_PRICE)
|
const internalLastPrice = ref(MOCK_ORDER_BOOK_LAST_PRICE)
|
||||||
const internalSpread = ref(MOCK_ORDER_BOOK_SPREAD)
|
const internalSpread = ref(MOCK_ORDER_BOOK_SPREAD)
|
||||||
|
|
||||||
// 当有外部数据时使用 props,否则在 USE_MOCK_ORDER_BOOK 时用 mock
|
// 根据 activeTrade 选择当前 tab 对应的数据
|
||||||
const asks = computed(() =>
|
const asks = computed(() => {
|
||||||
props.asks?.length ? props.asks : USE_MOCK_ORDER_BOOK ? internalAsks.value : [],
|
const isYes = activeTrade.value === 'up'
|
||||||
)
|
const fromYes = isYes ? props.asksYes : props.asksNo
|
||||||
const bids = computed(() =>
|
const fromLegacy = props.asks
|
||||||
props.bids?.length ? props.bids : USE_MOCK_ORDER_BOOK ? internalBids.value : [],
|
const hasYesNo = (fromYes?.length ?? 0) > 0
|
||||||
)
|
const hasLegacy = (fromLegacy?.length ?? 0) > 0
|
||||||
const displayLastPrice = computed(() =>
|
if (hasYesNo) return fromYes ?? []
|
||||||
props.lastPrice ?? (USE_MOCK_ORDER_BOOK ? internalLastPrice.value : 0),
|
if (hasLegacy) return fromLegacy ?? []
|
||||||
)
|
return USE_MOCK_ORDER_BOOK ? internalAsks.value : []
|
||||||
const displaySpread = computed(() =>
|
})
|
||||||
props.spread ?? (USE_MOCK_ORDER_BOOK ? internalSpread.value : 0),
|
const bids = computed(() => {
|
||||||
)
|
const isYes = activeTrade.value === 'up'
|
||||||
|
const fromYes = isYes ? props.bidsYes : props.bidsNo
|
||||||
|
const fromLegacy = props.bids
|
||||||
|
const hasYesNo = (fromYes?.length ?? 0) > 0
|
||||||
|
const hasLegacy = (fromLegacy?.length ?? 0) > 0
|
||||||
|
if (hasYesNo) return fromYes ?? []
|
||||||
|
if (hasLegacy) return fromLegacy ?? []
|
||||||
|
return USE_MOCK_ORDER_BOOK ? internalBids.value : []
|
||||||
|
})
|
||||||
|
const displayLastPrice = computed(() => {
|
||||||
|
const isYes = activeTrade.value === 'up'
|
||||||
|
const fromYesNo = isYes ? props.lastPriceYes : props.lastPriceNo
|
||||||
|
if (fromYesNo != null) return fromYesNo
|
||||||
|
if (props.lastPrice != null) return props.lastPrice
|
||||||
|
return USE_MOCK_ORDER_BOOK ? internalLastPrice.value : 0
|
||||||
|
})
|
||||||
|
const displaySpread = computed(() => {
|
||||||
|
const isYes = activeTrade.value === 'up'
|
||||||
|
const fromYesNo = isYes ? props.spreadYes : props.spreadNo
|
||||||
|
if (fromYesNo != null) return fromYesNo
|
||||||
|
if (props.spread != null) return props.spread
|
||||||
|
return USE_MOCK_ORDER_BOOK ? internalSpread.value : 0
|
||||||
|
})
|
||||||
|
|
||||||
// 仅在没有外部数据且开启 mock 时运行 mock 更新
|
// 仅在没有外部数据且开启 mock 时运行 mock 更新
|
||||||
let mockInterval: ReturnType<typeof setInterval> | undefined
|
let mockInterval: ReturnType<typeof setInterval> | undefined
|
||||||
watch(
|
watch(
|
||||||
() => props.connected || (props.asks?.length ?? 0) > 0,
|
() =>
|
||||||
|
props.connected ||
|
||||||
|
(props.asksYes?.length ?? 0) > 0 ||
|
||||||
|
(props.bidsYes?.length ?? 0) > 0 ||
|
||||||
|
(props.asksNo?.length ?? 0) > 0 ||
|
||||||
|
(props.bidsNo?.length ?? 0) > 0 ||
|
||||||
|
(props.asks?.length ?? 0) > 0,
|
||||||
(hasRealData) => {
|
(hasRealData) => {
|
||||||
if (hasRealData && mockInterval) {
|
if (hasRealData && mockInterval) {
|
||||||
clearInterval(mockInterval)
|
clearInterval(mockInterval)
|
||||||
|
|||||||
@ -47,10 +47,14 @@
|
|||||||
<!-- Order Book Section -->
|
<!-- Order Book Section -->
|
||||||
<v-card class="order-book-card" elevation="0" rounded="lg">
|
<v-card class="order-book-card" elevation="0" rounded="lg">
|
||||||
<OrderBook
|
<OrderBook
|
||||||
:asks="orderBookAsks"
|
:asks-yes="orderBookAsksYes"
|
||||||
:bids="orderBookBids"
|
:bids-yes="orderBookBidsYes"
|
||||||
:last-price="clobLastPrice"
|
:asks-no="orderBookAsksNo"
|
||||||
:spread="clobSpread"
|
:bids-no="orderBookBidsNo"
|
||||||
|
:last-price-yes="clobLastPriceYes"
|
||||||
|
:last-price-no="clobLastPriceNo"
|
||||||
|
:spread-yes="clobSpreadYes"
|
||||||
|
:spread-no="clobSpreadNo"
|
||||||
:loading="clobLoading"
|
:loading="clobLoading"
|
||||||
:connected="clobConnected"
|
:connected="clobConnected"
|
||||||
:yes-label="yesLabel"
|
:yes-label="yesLabel"
|
||||||
@ -357,15 +361,30 @@ const currentMarket = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// --- CLOB WebSocket 订单簿与成交 ---
|
// --- CLOB WebSocket 订单簿与成交 ---
|
||||||
|
// 按 token 索引区分:0 = Yes,1 = No
|
||||||
|
type OrderBookRows = { price: number; shares: number }[]
|
||||||
const clobSdkRef = ref<ClobSdk | null>(null)
|
const clobSdkRef = ref<ClobSdk | null>(null)
|
||||||
const orderBookAsks = ref<{ price: number; shares: number }[]>([])
|
const orderBookByToken = ref<Record<number, { asks: OrderBookRows; bids: OrderBookRows }>>({
|
||||||
const orderBookBids = ref<{ price: number; shares: number }[]>([])
|
0: { asks: [], bids: [] },
|
||||||
const clobLastPrice = ref<number | undefined>(undefined)
|
1: { asks: [], bids: [] },
|
||||||
const clobSpread = ref<number | undefined>(undefined)
|
})
|
||||||
|
const clobLastPriceByToken = ref<Record<number, number | undefined>>({ 0: undefined, 1: undefined })
|
||||||
|
const clobSpreadByToken = ref<Record<number, number | undefined>>({ 0: undefined, 1: undefined })
|
||||||
const clobConnected = ref(false)
|
const clobConnected = ref(false)
|
||||||
const clobLoading = ref(false)
|
const clobLoading = ref(false)
|
||||||
|
/** 当前订阅的 tokenIds,用于根据 msg.m 匹配 Yes(0)/No(1) */
|
||||||
|
const clobTokenIdsRef = ref<string[]>([])
|
||||||
|
|
||||||
function priceSizeToRows(record: Record<string, number> | undefined): { price: number; shares: number }[] {
|
const orderBookAsksYes = computed(() => orderBookByToken.value[0]?.asks ?? [])
|
||||||
|
const orderBookBidsYes = computed(() => orderBookByToken.value[0]?.bids ?? [])
|
||||||
|
const orderBookAsksNo = computed(() => orderBookByToken.value[1]?.asks ?? [])
|
||||||
|
const orderBookBidsNo = computed(() => orderBookByToken.value[1]?.bids ?? [])
|
||||||
|
const clobLastPriceYes = computed(() => clobLastPriceByToken.value[0])
|
||||||
|
const clobLastPriceNo = computed(() => clobLastPriceByToken.value[1])
|
||||||
|
const clobSpreadYes = computed(() => clobSpreadByToken.value[0])
|
||||||
|
const clobSpreadNo = computed(() => clobSpreadByToken.value[1])
|
||||||
|
|
||||||
|
function priceSizeToRows(record: Record<string, number> | undefined): OrderBookRows {
|
||||||
if (!record) return []
|
if (!record) return []
|
||||||
return Object.entries(record)
|
return Object.entries(record)
|
||||||
.filter(([, shares]) => shares > 0)
|
.filter(([, shares]) => shares > 0)
|
||||||
@ -375,15 +394,32 @@ function priceSizeToRows(record: Record<string, number> | undefined): { price: n
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTokenIndex(msg: PriceSizePolyMsg): number {
|
||||||
|
const ids = clobTokenIdsRef.value
|
||||||
|
const m = msg.m
|
||||||
|
if (m != null && ids.length >= 2) {
|
||||||
|
const idx = ids.findIndex((id) => String(id) === String(m))
|
||||||
|
if (idx === 0 || idx === 1) return idx
|
||||||
|
}
|
||||||
|
if (typeof msg.i === 'number' && (msg.i === 0 || msg.i === 1)) return msg.i
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
function applyPriceSizeAll(msg: PriceSizePolyMsg) {
|
function applyPriceSizeAll(msg: PriceSizePolyMsg) {
|
||||||
orderBookAsks.value = priceSizeToRows(msg.s).sort((a, b) => a.price - b.price)
|
const idx = getTokenIndex(msg)
|
||||||
orderBookBids.value = priceSizeToRows(msg.b).sort((a, b) => b.price - a.price)
|
const asks = priceSizeToRows(msg.s).sort((a, b) => a.price - b.price)
|
||||||
updateSpreadFromBook()
|
const bids = priceSizeToRows(msg.b).sort((a, b) => b.price - a.price)
|
||||||
|
orderBookByToken.value = {
|
||||||
|
...orderBookByToken.value,
|
||||||
|
[idx]: { asks, bids },
|
||||||
|
}
|
||||||
|
updateSpreadForToken(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyPriceSizeDelta(msg: PriceSizePolyMsg) {
|
function applyPriceSizeDelta(msg: PriceSizePolyMsg) {
|
||||||
|
const idx = getTokenIndex(msg)
|
||||||
const mergeDelta = (
|
const mergeDelta = (
|
||||||
current: { price: number; shares: number }[],
|
current: OrderBookRows,
|
||||||
delta: Record<string, number> | undefined,
|
delta: Record<string, number> | undefined,
|
||||||
asc: boolean,
|
asc: boolean,
|
||||||
) => {
|
) => {
|
||||||
@ -399,16 +435,23 @@ function applyPriceSizeDelta(msg: PriceSizePolyMsg) {
|
|||||||
.map(([price, shares]) => ({ price, shares }))
|
.map(([price, shares]) => ({ price, shares }))
|
||||||
.sort((a, b) => (asc ? a.price - b.price : b.price - a.price))
|
.sort((a, b) => (asc ? a.price - b.price : b.price - a.price))
|
||||||
}
|
}
|
||||||
orderBookAsks.value = mergeDelta(orderBookAsks.value, msg.s, true)
|
const prev = orderBookByToken.value[idx] ?? { asks: [], bids: [] }
|
||||||
orderBookBids.value = mergeDelta(orderBookBids.value, msg.b, false)
|
const asks = mergeDelta(prev.asks, msg.s, true)
|
||||||
updateSpreadFromBook()
|
const bids = mergeDelta(prev.bids, msg.b, false)
|
||||||
|
orderBookByToken.value = {
|
||||||
|
...orderBookByToken.value,
|
||||||
|
[idx]: { asks, bids },
|
||||||
|
}
|
||||||
|
updateSpreadForToken(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSpreadFromBook() {
|
function updateSpreadForToken(idx: number) {
|
||||||
const bestAsk = orderBookAsks.value[0]?.price
|
const book = orderBookByToken.value[idx]
|
||||||
const bestBid = orderBookBids.value[0]?.price
|
if (!book) return
|
||||||
|
const bestAsk = book.asks[0]?.price
|
||||||
|
const bestBid = book.bids[0]?.price
|
||||||
if (bestAsk != null && bestBid != null) {
|
if (bestAsk != null && bestBid != null) {
|
||||||
clobSpread.value = bestAsk - bestBid
|
clobSpreadByToken.value = { ...clobSpreadByToken.value, [idx]: bestAsk - bestBid }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,8 +460,10 @@ function connectClob(tokenIds: string[]) {
|
|||||||
clobSdkRef.value = null
|
clobSdkRef.value = null
|
||||||
clobLoading.value = true
|
clobLoading.value = true
|
||||||
clobConnected.value = false
|
clobConnected.value = false
|
||||||
orderBookAsks.value = []
|
clobTokenIdsRef.value = tokenIds
|
||||||
orderBookBids.value = []
|
orderBookByToken.value = { 0: { asks: [], bids: [] }, 1: { asks: [], bids: [] } }
|
||||||
|
clobLastPriceByToken.value = { 0: undefined, 1: undefined }
|
||||||
|
clobSpreadByToken.value = { 0: undefined, 1: undefined }
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
url: getClobWsUrl(),
|
url: getClobWsUrl(),
|
||||||
@ -442,7 +487,10 @@ function connectClob(tokenIds: string[]) {
|
|||||||
sdk.onTrade((msg: TradePolyMsg) => {
|
sdk.onTrade((msg: TradePolyMsg) => {
|
||||||
const priceNum = parseFloat(msg.p)
|
const priceNum = parseFloat(msg.p)
|
||||||
if (Number.isFinite(priceNum)) {
|
if (Number.isFinite(priceNum)) {
|
||||||
clobLastPrice.value = Math.round(priceNum * 100)
|
const priceCents = Math.round(priceNum * 100)
|
||||||
|
const side = msg.side?.toLowerCase()
|
||||||
|
const tokenIdx = side === 'buy' ? 0 : 1
|
||||||
|
clobLastPriceByToken.value = { ...clobLastPriceByToken.value, [tokenIdx]: priceCents }
|
||||||
}
|
}
|
||||||
// 追加到 Activity 列表
|
// 追加到 Activity 列表
|
||||||
const side = msg.side?.toLowerCase() === 'buy' ? 'Yes' : 'No'
|
const side = msg.side?.toLowerCase() === 'buy' ? 'Yes' : 'No'
|
||||||
@ -919,10 +967,12 @@ watch(
|
|||||||
clobTokenIds,
|
clobTokenIds,
|
||||||
(tokenIds) => {
|
(tokenIds) => {
|
||||||
if (tokenIds.length > 0) {
|
if (tokenIds.length > 0) {
|
||||||
// 用接口返回的 Yes 价格作为初始 lastPrice
|
|
||||||
const payload = tradeMarketPayload.value
|
const payload = tradeMarketPayload.value
|
||||||
if (payload?.yesPrice != null) {
|
if (payload?.yesPrice != null) {
|
||||||
clobLastPrice.value = Math.round(payload.yesPrice * 100)
|
clobLastPriceByToken.value = {
|
||||||
|
...clobLastPriceByToken.value,
|
||||||
|
0: Math.round(payload.yesPrice * 100),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
connectClob(tokenIds)
|
connectClob(tokenIds)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user