refactor: 重构市场数据接口和移除敏感信息

- 创建 MarketController,将市场相关接口从 AccountController 移出
- 添加 /api/copy-trading/markets/latest-price 接口供前端获取最新价
- 在 PolymarketClobService 中封装获取订单表和最优价的逻辑
- 支持多元市场(二元、三元及以上)的最优价获取
- 市价单价格添加调整系数(买单+0.01,卖单-0.02)
- 移除所有测试文件中的真实私钥和API凭证
- 更新前端API服务,添加markets相关接口
This commit is contained in:
WrBug
2025-11-29 11:08:01 +08:00
parent b4fb16af9e
commit 4e89f53c24
15 changed files with 1400 additions and 166 deletions
+86 -85
View File
@@ -252,7 +252,7 @@ const PositionList: React.FC = () => {
// 加载市场价格
try {
const response = await apiService.accounts.getMarketPrice({ marketId: position.marketId })
const response = await apiService.markets.getMarketPrice({ marketId: position.marketId })
if (response.data.code === 0 && response.data.data) {
setMarketPrice(response.data.data)
// 默认使用最优买价作为限价
@@ -324,7 +324,8 @@ const PositionList: React.FC = () => {
const request: PositionSellRequest = {
accountId: selectedPosition.accountId,
marketId: selectedPosition.marketId,
side: selectedPosition.side as 'YES' | 'NO',
side: selectedPosition.side,
outcomeIndex: selectedPosition.outcomeIndex, // 传递 outcomeIndex
orderType: orderType,
quantity: sellQuantity,
price: orderType === 'LIMIT' ? limitPrice : undefined
@@ -791,70 +792,70 @@ const PositionList: React.FC = () => {
// 只有当前仓位才显示盈亏和已实现盈亏列
if (positionFilter === 'current') {
baseColumns.push(
{
title: '盈亏',
dataIndex: 'pnl',
key: 'pnl',
render: (pnl: string, record: AccountPosition) => {
const pnlNum = parseFloat(pnl || '0')
const percentPnl = parseFloat(record.percentPnl || '0')
return (
<div>
<div style={{
color: pnlNum >= 0 ? '#3f8600' : '#cf1322',
fontWeight: 'bold'
}}>
{pnlNum >= 0 ? '+' : ''}{formatNumber(pnl, 2)} USDC
</div>
baseColumns.push(
{
title: '盈亏',
dataIndex: 'pnl',
key: 'pnl',
render: (pnl: string, record: AccountPosition) => {
const pnlNum = parseFloat(pnl || '0')
const percentPnl = parseFloat(record.percentPnl || '0')
return (
<div>
<div style={{
color: pnlNum >= 0 ? '#3f8600' : '#cf1322',
fontWeight: 'bold'
}}>
{pnlNum >= 0 ? '+' : ''}{formatNumber(pnl, 2)} USDC
</div>
<div style={{
fontSize: '12px',
color: percentPnl >= 0 ? '#3f8600' : '#cf1322'
}}>
{formatPercent(record.percentPnl)}
</div>
</div>
)
},
align: 'right' as const,
width: 150,
sorter: (a: AccountPosition, b: AccountPosition) => {
const pnlA = parseFloat(a.pnl || '0')
const pnlB = parseFloat(b.pnl || '0')
return pnlA - pnlB
}
},
{
title: '已实现盈亏',
dataIndex: 'realizedPnl',
key: 'realizedPnl',
render: (realizedPnl: string | undefined, record: AccountPosition) => {
if (!realizedPnl) return '-'
const pnlNum = parseFloat(realizedPnl)
const percentPnl = parseFloat(record.percentRealizedPnl || '0')
return (
<div>
<div style={{
color: pnlNum >= 0 ? '#3f8600' : '#cf1322',
fontWeight: 'bold'
}}>
{pnlNum >= 0 ? '+' : ''}{formatNumber(realizedPnl, 2)} USDC
</div>
{record.percentRealizedPnl && (
<div style={{
fontSize: '12px',
color: percentPnl >= 0 ? '#3f8600' : '#cf1322'
}}>
{formatPercent(record.percentPnl)}
{formatPercent(record.percentRealizedPnl)}
</div>
</div>
)
},
align: 'right' as const,
width: 150,
sorter: (a: AccountPosition, b: AccountPosition) => {
const pnlA = parseFloat(a.pnl || '0')
const pnlB = parseFloat(b.pnl || '0')
return pnlA - pnlB
}
)}
</div>
)
},
{
title: '已实现盈亏',
dataIndex: 'realizedPnl',
key: 'realizedPnl',
render: (realizedPnl: string | undefined, record: AccountPosition) => {
if (!realizedPnl) return '-'
const pnlNum = parseFloat(realizedPnl)
const percentPnl = parseFloat(record.percentRealizedPnl || '0')
return (
<div>
<div style={{
color: pnlNum >= 0 ? '#3f8600' : '#cf1322',
fontWeight: 'bold'
}}>
{pnlNum >= 0 ? '+' : ''}{formatNumber(realizedPnl, 2)} USDC
</div>
{record.percentRealizedPnl && (
<div style={{
fontSize: '12px',
color: percentPnl >= 0 ? '#3f8600' : '#cf1322'
}}>
{formatPercent(record.percentRealizedPnl)}
</div>
)}
</div>
)
},
align: 'right' as const,
width: 150
}
)
align: 'right' as const,
width: 150
}
)
}
// 只有当前仓位才显示操作列
@@ -910,7 +911,7 @@ const PositionList: React.FC = () => {
<div style={{ marginBottom: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '12px', marginBottom: '12px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<h2 style={{ margin: 0 }}></h2>
<h2 style={{ margin: 0 }}></h2>
{/* WebSocket 连接状态指示器 */}
<Tag
color={wsConnected ? 'green' : 'orange'}
@@ -976,9 +977,9 @@ const PositionList: React.FC = () => {
return nameA.localeCompare(nameB, 'zh-CN')
})
.map(account => ({
value: account.id,
label: account.accountName || `账户 ${account.id}`
}))
value: account.id,
label: account.accountName || `账户 ${account.id}`
}))
]}
/>
<div style={{
@@ -988,10 +989,10 @@ const PositionList: React.FC = () => {
display: 'inline-flex',
gap: '4px'
}}>
<Radio.Group
value={positionFilter}
onChange={(e) => setPositionFilter(e.target.value)}
size={isMobile ? 'small' : 'middle'}
<Radio.Group
value={positionFilter}
onChange={(e) => setPositionFilter(e.target.value)}
size={isMobile ? 'small' : 'middle'}
style={{ display: 'flex', gap: '4px' }}
>
<Radio.Button
@@ -1027,7 +1028,7 @@ const PositionList: React.FC = () => {
{currentCount}
</Tag>
</span>
</Radio.Button>
</Radio.Button>
<Radio.Button
value="historical"
style={{
@@ -1061,8 +1062,8 @@ const PositionList: React.FC = () => {
{historicalCount}
</Tag>
</span>
</Radio.Button>
</Radio.Group>
</Radio.Button>
</Radio.Group>
</div>
</div>
</div>
@@ -1082,20 +1083,20 @@ const PositionList: React.FC = () => {
)}
</Card>
) : (
<Card>
<Table
dataSource={filteredPositions}
columns={columns}
rowKey={(record, index) => `${record.accountId}-${record.marketId}-${index}`}
loading={loading}
pagination={{
pageSize: 20,
showSizeChanger: !isMobile,
showTotal: (total) => `${total} 个仓位${searchKeyword ? `(已过滤)` : ''}`
}}
scroll={isMobile ? { x: 1500 } : undefined}
/>
</Card>
<Card>
<Table
dataSource={filteredPositions}
columns={columns}
rowKey={(record, index) => `${record.accountId}-${record.marketId}-${index}`}
loading={loading}
pagination={{
pageSize: 20,
showSizeChanger: !isMobile,
showTotal: (total) => `${total} 个仓位${searchKeyword ? `(已过滤)` : ''}`
}}
scroll={isMobile ? { x: 1500 } : undefined}
/>
</Card>
)}
{/* 出售模态框 */}
+15 -3
View File
@@ -105,11 +105,23 @@ export const apiService = {
sellPosition: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/positions/sell', data),
},
/**
* 市场数据 API
*/
markets: {
/**
* 获取市场价格
* 获取市场价格(通过 Gamma API
*/
getMarketPrice: (data: any) =>
apiClient.post<ApiResponse<any>>('/copy-trading/accounts/markets/price', data)
getMarketPrice: (data: { marketId: string }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/markets/price', data),
/**
* 获取最新价(从订单表获取,供前端下单时显示)
*/
getLatestPrice: (data: { tokenId: string }) =>
apiClient.post<ApiResponse<any>>('/copy-trading/markets/latest-price', data)
},
/**
+4 -2
View File
@@ -163,7 +163,8 @@ export interface AccountPosition {
marketTitle?: string
marketSlug?: string
marketIcon?: string // 市场图标 URL
side: string // YES 或 NO
side: string // 结果名称(如 "YES", "NO", "Pakistan" 等)
outcomeIndex?: number // 结果索引(0, 1, 2...),用于计算 tokenId
quantity: string
avgPrice: string
currentPrice: string
@@ -193,7 +194,8 @@ export interface PositionListResponse {
export interface PositionSellRequest {
accountId: number
marketId: string
side: 'YES' | 'NO'
side: string // 结果名称(如 "YES", "NO", "Pakistan" 等)
outcomeIndex?: number // 结果索引(0, 1, 2...),用于计算 tokenId(推荐提供)
orderType: 'MARKET' | 'LIMIT'
quantity: string
price?: string // 限价订单必需