234 lines
6.4 KiB
TypeScript
234 lines
6.4 KiB
TypeScript
import { buildQuery, get, post } from './request'
|
||
import type { ApiResponse, PageResult } from './types'
|
||
|
||
export type { PageResult }
|
||
|
||
/**
|
||
* 订单项(与 doc.json definitions["model.ClobOrder"] 对齐)
|
||
* GET /clob/order/getOrderList 列表项
|
||
*/
|
||
export interface ClobOrderItem {
|
||
ID: number
|
||
assetID?: string
|
||
createdAt?: string
|
||
updatedAt?: string
|
||
expiration?: number
|
||
feeRateBps?: number
|
||
market?: string
|
||
orderType?: number
|
||
originalSize?: number
|
||
outcome?: string
|
||
price?: number
|
||
side?: number
|
||
sizeMatched?: number
|
||
status?: number
|
||
userID?: number
|
||
[key: string]: unknown
|
||
}
|
||
|
||
/** 订单列表响应 */
|
||
export interface OrderListResponse {
|
||
code: number
|
||
data: PageResult<ClobOrderItem>
|
||
msg: string
|
||
}
|
||
|
||
/** 订单状态:1=未成交(Live),2=已成交,0=已取消等 */
|
||
export const OrderStatus = {
|
||
Live: 1,
|
||
Matched: 2,
|
||
Cancelled: 0,
|
||
} as const
|
||
|
||
/**
|
||
* GET /clob/order/getOrderList 请求参数
|
||
*/
|
||
export interface GetOrderListParams {
|
||
page?: number
|
||
pageSize?: number
|
||
/** 订单状态筛选:1=未成交 */
|
||
status?: number
|
||
startCreatedAt?: string
|
||
endCreatedAt?: string
|
||
marketID?: string
|
||
tokenID?: string
|
||
userID?: number
|
||
}
|
||
|
||
/**
|
||
* 分页获取订单列表
|
||
* GET /clob/order/getOrderList
|
||
* 需鉴权:x-token、x-user-id
|
||
*/
|
||
export async function getOrderList(
|
||
params: GetOrderListParams = {},
|
||
config?: { headers?: Record<string, string> },
|
||
): Promise<OrderListResponse> {
|
||
const { page = 1, pageSize = 10, status, startCreatedAt, endCreatedAt, marketID, tokenID, userID } =
|
||
params
|
||
const query = buildQuery({
|
||
page,
|
||
pageSize,
|
||
status,
|
||
startCreatedAt,
|
||
endCreatedAt,
|
||
marketID,
|
||
tokenID,
|
||
userID,
|
||
})
|
||
return get<OrderListResponse>('/clob/order/getOrderList', query, config)
|
||
}
|
||
|
||
/** 取消订单请求体(request.CancelOrderReq) */
|
||
export interface CancelOrderReq {
|
||
orderID: number
|
||
tokenID: string
|
||
userID: number
|
||
}
|
||
|
||
export type { ApiResponse }
|
||
|
||
/**
|
||
* POST /clob/order/cancelOrder
|
||
* 撤销订单(撮合引擎),需鉴权 x-token
|
||
*/
|
||
export async function cancelOrder(
|
||
data: CancelOrderReq,
|
||
config?: { headers?: Record<string, string> },
|
||
): Promise<ApiResponse> {
|
||
return post<ApiResponse>('/clob/order/cancelOrder', data, config)
|
||
}
|
||
|
||
/** 钱包 History 展示项(与 Wallet.vue HistoryItem 一致) */
|
||
export interface HistoryDisplayItem {
|
||
id: string
|
||
market: string
|
||
side: 'Yes' | 'No'
|
||
activity: string
|
||
value: string
|
||
activityDetail?: string
|
||
profitLoss?: string
|
||
profitLossNegative?: boolean
|
||
timeAgo?: string
|
||
avgPrice?: string
|
||
shares?: string
|
||
iconChar?: string
|
||
iconClass?: string
|
||
}
|
||
|
||
/** Side: Buy=1, Sell=2 */
|
||
const Side = { Buy: 1, Sell: 2 } as const
|
||
|
||
/** 订单份额接口按 6 位小数传(1_000_000 = 1 share),需除以该系数转为展示值 */
|
||
const ORDER_SIZE_SCALE = 1_000_000
|
||
|
||
function formatTimeAgo(createdAt: string | undefined): string {
|
||
if (!createdAt) return ''
|
||
const d = new Date(createdAt)
|
||
const now = Date.now()
|
||
const diff = now - d.getTime()
|
||
if (diff < 60000) return 'Just now'
|
||
if (diff < 3600000) return `${Math.floor(diff / 60000)} minutes ago`
|
||
if (diff < 86400000) return `${Math.floor(diff / 3600000)} hours ago`
|
||
if (diff < 604800000) return `${Math.floor(diff / 86400000)} days ago`
|
||
return d.toLocaleDateString()
|
||
}
|
||
|
||
/**
|
||
* 将 ClobOrderItem 映射为钱包 History 展示项
|
||
* price 为整数(已乘 10000),size 按 6 位小数传(1_000_000 = 1 share)
|
||
*/
|
||
export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem {
|
||
const id = String(order.ID ?? '')
|
||
const market = order.market ?? ''
|
||
const outcome = order.outcome ?? 'Yes'
|
||
const sideNum = order.side ?? Side.Buy
|
||
const sideLabel = sideNum === Side.Sell ? 'Sell' : 'Buy'
|
||
const activity = `${sideLabel} ${outcome}`
|
||
const priceBps = order.price ?? 0
|
||
const priceCents = Math.round(priceBps / 100)
|
||
const sizeRaw = order.sizeMatched ?? order.originalSize ?? 0
|
||
const size = sizeRaw / ORDER_SIZE_SCALE
|
||
const valueUsd = (priceBps / 10000) * size
|
||
const value = `$${valueUsd.toFixed(2)}`
|
||
const verb = sideNum === Side.Sell ? 'Sold' : 'Bought'
|
||
const activityDetail = `${verb} ${Math.floor(size)} ${outcome} at ${priceCents}¢`
|
||
const avgPrice = `${priceCents}¢`
|
||
const timeAgo = formatTimeAgo(order.createdAt)
|
||
return {
|
||
id,
|
||
market,
|
||
side: outcome === 'No' ? 'No' : 'Yes',
|
||
activity,
|
||
value,
|
||
activityDetail,
|
||
profitLoss: value,
|
||
profitLossNegative: false,
|
||
timeAgo,
|
||
avgPrice,
|
||
shares: String(Math.floor(size)),
|
||
}
|
||
}
|
||
|
||
/** 钱包 Open Orders 展示项(与 Wallet.vue OpenOrder 一致) */
|
||
export interface OpenOrderDisplayItem {
|
||
id: string
|
||
market: string
|
||
side: 'Yes' | 'No'
|
||
outcome: string
|
||
price: string
|
||
filled: string
|
||
total: string
|
||
expiration: string
|
||
actionLabel?: string
|
||
filledDisplay?: string
|
||
orderID?: number
|
||
tokenID?: string
|
||
/** 已成交数量达到原始总数量,不可撤单 */
|
||
fullyFilled?: boolean
|
||
}
|
||
|
||
/** OrderType GTC=0 表示 Until Cancelled */
|
||
const OrderType = { GTC: 0, GTD: 1 } as const
|
||
|
||
/**
|
||
* 将 ClobOrderItem 映射为钱包 Open Orders 展示项(未成交订单)
|
||
* price 为整数(已乘 10000),originalSize/sizeMatched 按 6 位小数传(1_000_000 = 1 share)
|
||
*/
|
||
export function mapOrderToOpenOrderItem(order: ClobOrderItem): OpenOrderDisplayItem {
|
||
const id = String(order.ID ?? '')
|
||
const market = order.market ?? ''
|
||
const sideNum = order.side ?? Side.Buy
|
||
const side = sideNum === Side.Sell ? 'No' : 'Yes'
|
||
const outcome = order.outcome || (side === 'Yes' ? 'Yes' : 'No')
|
||
const priceBps = order.price ?? 0
|
||
const priceCents = Math.round(priceBps / 100)
|
||
const price = `${priceCents}¢`
|
||
const originalSizeRaw = order.originalSize ?? 0
|
||
const sizeMatchedRaw = order.sizeMatched ?? 0
|
||
const originalSize = originalSizeRaw / ORDER_SIZE_SCALE
|
||
const sizeMatched = sizeMatchedRaw / ORDER_SIZE_SCALE
|
||
const filled = `${Math.floor(sizeMatched)}/${Math.floor(originalSize)}`
|
||
const totalUsd = (priceBps / 10000) * originalSize
|
||
const total = `$${totalUsd.toFixed(2)}`
|
||
const expiration =
|
||
order.orderType === OrderType.GTC ? 'Until Cancelled' : order.expiration?.toString() ?? ''
|
||
const actionLabel = sideNum === Side.Buy ? `Buy ${outcome}` : `Sell ${outcome}`
|
||
const fullyFilled = originalSize <= 0 || sizeMatched >= originalSize
|
||
return {
|
||
id,
|
||
market,
|
||
side,
|
||
outcome,
|
||
price,
|
||
filled,
|
||
total,
|
||
expiration,
|
||
actionLabel,
|
||
filledDisplay: filled,
|
||
orderID: order.ID,
|
||
tokenID: order.assetID,
|
||
fullyFilled,
|
||
}
|
||
}
|