a
This commit is contained in:
parent
6bf4ea7a06
commit
d9830f395b
|
|
@ -3,6 +3,7 @@
|
||||||
"""
|
"""
|
||||||
from fastapi import APIRouter, Query, HTTPException
|
from fastapi import APIRouter, Query, HTTPException
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
@ -15,20 +16,74 @@ from database.models import Trade
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
def get_date_range(period: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
根据时间段参数返回开始和结束日期
|
||||||
|
|
||||||
|
Args:
|
||||||
|
period: 时间段 ('1d', '7d', '30d', 'custom')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(start_date, end_date) 元组,格式为 'YYYY-MM-DD HH:MM:SS'
|
||||||
|
"""
|
||||||
|
end_date = datetime.now()
|
||||||
|
|
||||||
|
if period == '1d':
|
||||||
|
start_date = end_date - timedelta(days=1)
|
||||||
|
elif period == '7d':
|
||||||
|
start_date = end_date - timedelta(days=7)
|
||||||
|
elif period == '30d':
|
||||||
|
start_date = end_date - timedelta(days=30)
|
||||||
|
else:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
return start_date.strftime('%Y-%m-%d 00:00:00'), end_date.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def get_trades(
|
async def get_trades(
|
||||||
start_date: Optional[str] = Query(None),
|
start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
|
||||||
end_date: Optional[str] = Query(None),
|
end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
|
||||||
symbol: Optional[str] = Query(None),
|
period: Optional[str] = Query(None, description="快速时间段筛选: '1d'(最近1天), '7d'(最近7天), '30d'(最近30天)"),
|
||||||
status: Optional[str] = Query(None),
|
symbol: Optional[str] = Query(None, description="交易对筛选"),
|
||||||
limit: int = Query(100, ge=1, le=1000)
|
status: Optional[str] = Query(None, description="状态筛选: 'open', 'closed', 'cancelled'"),
|
||||||
|
limit: int = Query(100, ge=1, le=1000, description="返回记录数限制")
|
||||||
):
|
):
|
||||||
"""获取交易记录"""
|
"""
|
||||||
|
获取交易记录
|
||||||
|
|
||||||
|
支持两种筛选方式:
|
||||||
|
1. 快速时间段筛选:使用 period 参数 ('1d', '7d', '30d')
|
||||||
|
2. 自定义时间段筛选:使用 start_date 和 end_date 参数
|
||||||
|
|
||||||
|
如果同时提供了 period 和 start_date/end_date,period 优先级更高
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
|
# 如果提供了 period,使用快速时间段筛选
|
||||||
|
if period:
|
||||||
|
period_start, period_end = get_date_range(period)
|
||||||
|
if period_start and period_end:
|
||||||
|
start_date = period_start
|
||||||
|
end_date = period_end
|
||||||
|
|
||||||
|
# 格式化日期(如果只提供了日期,添加时间部分)
|
||||||
|
if start_date and len(start_date) == 10: # YYYY-MM-DD
|
||||||
|
start_date = f"{start_date} 00:00:00"
|
||||||
|
if end_date and len(end_date) == 10: # YYYY-MM-DD
|
||||||
|
end_date = f"{end_date} 23:59:59"
|
||||||
|
|
||||||
trades = Trade.get_all(start_date, end_date, symbol, status)
|
trades = Trade.get_all(start_date, end_date, symbol, status)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"total": len(trades),
|
"total": len(trades),
|
||||||
"trades": trades[:limit]
|
"trades": trades[:limit],
|
||||||
|
"filters": {
|
||||||
|
"start_date": start_date,
|
||||||
|
"end_date": end_date,
|
||||||
|
"period": period,
|
||||||
|
"symbol": symbol,
|
||||||
|
"status": status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
@ -36,13 +91,26 @@ async def get_trades(
|
||||||
|
|
||||||
@router.get("/stats")
|
@router.get("/stats")
|
||||||
async def get_trade_stats(
|
async def get_trade_stats(
|
||||||
start_date: Optional[str] = Query(None),
|
start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
|
||||||
end_date: Optional[str] = Query(None),
|
end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
|
||||||
symbol: Optional[str] = Query(None)
|
period: Optional[str] = Query(None, description="快速时间段筛选: '1d', '7d', '30d'"),
|
||||||
|
symbol: Optional[str] = Query(None, description="交易对筛选")
|
||||||
):
|
):
|
||||||
"""获取交易统计"""
|
"""获取交易统计"""
|
||||||
try:
|
try:
|
||||||
from fastapi import HTTPException
|
# 如果提供了 period,使用快速时间段筛选
|
||||||
|
if period:
|
||||||
|
period_start, period_end = get_date_range(period)
|
||||||
|
if period_start and period_end:
|
||||||
|
start_date = period_start
|
||||||
|
end_date = period_end
|
||||||
|
|
||||||
|
# 格式化日期
|
||||||
|
if start_date and len(start_date) == 10:
|
||||||
|
start_date = f"{start_date} 00:00:00"
|
||||||
|
if end_date and len(end_date) == 10:
|
||||||
|
end_date = f"{end_date} 23:59:59"
|
||||||
|
|
||||||
trades = Trade.get_all(start_date, end_date, symbol, None)
|
trades = Trade.get_all(start_date, end_date, symbol, None)
|
||||||
closed_trades = [t for t in trades if t['status'] == 'closed']
|
closed_trades = [t for t in trades if t['status'] == 'closed']
|
||||||
win_trades = [t for t in closed_trades if float(t['pnl']) > 0]
|
win_trades = [t for t in closed_trades if float(t['pnl']) > 0]
|
||||||
|
|
@ -56,9 +124,14 @@ async def get_trade_stats(
|
||||||
"win_rate": len(win_trades) / len(closed_trades) * 100 if closed_trades else 0,
|
"win_rate": len(win_trades) / len(closed_trades) * 100 if closed_trades else 0,
|
||||||
"total_pnl": sum(float(t['pnl']) for t in closed_trades),
|
"total_pnl": sum(float(t['pnl']) for t in closed_trades),
|
||||||
"avg_pnl": sum(float(t['pnl']) for t in closed_trades) / len(closed_trades) if closed_trades else 0,
|
"avg_pnl": sum(float(t['pnl']) for t in closed_trades) / len(closed_trades) if closed_trades else 0,
|
||||||
|
"filters": {
|
||||||
|
"start_date": start_date,
|
||||||
|
"end_date": end_date,
|
||||||
|
"period": period,
|
||||||
|
"symbol": symbol
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
from fastapi import HTTPException
|
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,127 @@
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-panel {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1.5rem;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-buttons button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-buttons button:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
border-color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-buttons button.active {
|
||||||
|
background: #2196F3;
|
||||||
|
color: white;
|
||||||
|
border-color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-inputs input[type="date"] {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-inputs span {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section input[type="text"],
|
||||||
|
.filter-section select {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary,
|
||||||
|
.btn-secondary {
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #2196F3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #1976D2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem;
|
||||||
|
color: #999;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.stats-summary {
|
.stats-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,40 @@ const TradeList = () => {
|
||||||
const [stats, setStats] = useState(null)
|
const [stats, setStats] = useState(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
|
// 筛选状态
|
||||||
|
const [period, setPeriod] = useState(null) // '1d', '7d', '30d', null
|
||||||
|
const [startDate, setStartDate] = useState('')
|
||||||
|
const [endDate, setEndDate] = useState('')
|
||||||
|
const [symbol, setSymbol] = useState('')
|
||||||
|
const [status, setStatus] = useState('')
|
||||||
|
const [useCustomDate, setUseCustomDate] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData()
|
loadData()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
|
const params = {
|
||||||
|
limit: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果使用快速时间段筛选
|
||||||
|
if (!useCustomDate && period) {
|
||||||
|
params.period = period
|
||||||
|
} else if (useCustomDate) {
|
||||||
|
// 使用自定义日期
|
||||||
|
if (startDate) params.start_date = startDate
|
||||||
|
if (endDate) params.end_date = endDate
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol) params.symbol = symbol
|
||||||
|
if (status) params.status = status
|
||||||
|
|
||||||
const [tradesData, statsData] = await Promise.all([
|
const [tradesData, statsData] = await Promise.all([
|
||||||
api.getTrades({ limit: 100 }),
|
api.getTrades(params),
|
||||||
api.getTradeStats()
|
api.getTradeStats(params)
|
||||||
])
|
])
|
||||||
setTrades(tradesData.trades || [])
|
setTrades(tradesData.trades || [])
|
||||||
setStats(statsData)
|
setStats(statsData)
|
||||||
|
|
@ -26,12 +51,131 @@ const TradeList = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handlePeriodChange = (newPeriod) => {
|
||||||
|
setPeriod(newPeriod)
|
||||||
|
setUseCustomDate(false)
|
||||||
|
setStartDate('')
|
||||||
|
setEndDate('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCustomDateToggle = () => {
|
||||||
|
setUseCustomDate(!useCustomDate)
|
||||||
|
if (!useCustomDate) {
|
||||||
|
setPeriod(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
setPeriod(null)
|
||||||
|
setStartDate('')
|
||||||
|
setEndDate('')
|
||||||
|
setSymbol('')
|
||||||
|
setStatus('')
|
||||||
|
setUseCustomDate(false)
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) return <div className="loading">加载中...</div>
|
if (loading) return <div className="loading">加载中...</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="trade-list">
|
<div className="trade-list">
|
||||||
<h2>交易记录</h2>
|
<h2>交易记录</h2>
|
||||||
|
|
||||||
|
{/* 筛选面板 */}
|
||||||
|
<div className="filter-panel">
|
||||||
|
<div className="filter-section">
|
||||||
|
<label>快速筛选:</label>
|
||||||
|
<div className="period-buttons">
|
||||||
|
<button
|
||||||
|
className={period === '1d' ? 'active' : ''}
|
||||||
|
onClick={() => handlePeriodChange('1d')}
|
||||||
|
>
|
||||||
|
最近1天
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={period === '7d' ? 'active' : ''}
|
||||||
|
onClick={() => handlePeriodChange('7d')}
|
||||||
|
>
|
||||||
|
最近7天
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={period === '30d' ? 'active' : ''}
|
||||||
|
onClick={() => handlePeriodChange('30d')}
|
||||||
|
>
|
||||||
|
最近30天
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={period === null && !useCustomDate ? 'active' : ''}
|
||||||
|
onClick={() => handlePeriodChange(null)}
|
||||||
|
>
|
||||||
|
全部
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="filter-section">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={useCustomDate}
|
||||||
|
onChange={handleCustomDateToggle}
|
||||||
|
/>
|
||||||
|
自定义时间段
|
||||||
|
</label>
|
||||||
|
{useCustomDate && (
|
||||||
|
<div className="date-inputs">
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={startDate}
|
||||||
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
placeholder="开始日期"
|
||||||
|
/>
|
||||||
|
<span>至</span>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={endDate}
|
||||||
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
|
placeholder="结束日期"
|
||||||
|
min={startDate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="filter-section">
|
||||||
|
<label>交易对:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={symbol}
|
||||||
|
onChange={(e) => setSymbol(e.target.value)}
|
||||||
|
placeholder="如: BTCUSDT"
|
||||||
|
style={{ width: '150px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="filter-section">
|
||||||
|
<label>状态:</label>
|
||||||
|
<select
|
||||||
|
value={status}
|
||||||
|
onChange={(e) => setStatus(e.target.value)}
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
>
|
||||||
|
<option value="">全部</option>
|
||||||
|
<option value="open">持仓中</option>
|
||||||
|
<option value="closed">已平仓</option>
|
||||||
|
<option value="cancelled">已取消</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="filter-actions">
|
||||||
|
<button className="btn-primary" onClick={loadData}>
|
||||||
|
查询
|
||||||
|
</button>
|
||||||
|
<button className="btn-secondary" onClick={handleReset}>
|
||||||
|
重置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{stats && (
|
{stats && (
|
||||||
<div className="stats-summary">
|
<div className="stats-summary">
|
||||||
<div className="stat-card">
|
<div className="stat-card">
|
||||||
|
|
@ -57,38 +201,42 @@ const TradeList = () => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<table className="trades-table">
|
{trades.length === 0 ? (
|
||||||
<thead>
|
<div className="no-data">暂无交易记录</div>
|
||||||
<tr>
|
) : (
|
||||||
<th>交易对</th>
|
<table className="trades-table">
|
||||||
<th>方向</th>
|
<thead>
|
||||||
<th>数量</th>
|
<tr>
|
||||||
<th>入场价</th>
|
<th>交易对</th>
|
||||||
<th>出场价</th>
|
<th>方向</th>
|
||||||
<th>盈亏</th>
|
<th>数量</th>
|
||||||
<th>状态</th>
|
<th>入场价</th>
|
||||||
<th>时间</th>
|
<th>出场价</th>
|
||||||
</tr>
|
<th>盈亏</th>
|
||||||
</thead>
|
<th>状态</th>
|
||||||
<tbody>
|
<th>时间</th>
|
||||||
{trades.map(trade => (
|
|
||||||
<tr key={trade.id}>
|
|
||||||
<td>{trade.symbol}</td>
|
|
||||||
<td className={trade.side === 'BUY' ? 'buy' : 'sell'}>{trade.side}</td>
|
|
||||||
<td>{parseFloat(trade.quantity).toFixed(4)}</td>
|
|
||||||
<td>{parseFloat(trade.entry_price).toFixed(4)}</td>
|
|
||||||
<td>{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}</td>
|
|
||||||
<td className={parseFloat(trade.pnl) >= 0 ? 'positive' : 'negative'}>
|
|
||||||
{parseFloat(trade.pnl).toFixed(2)} USDT
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span className={`status ${trade.status}`}>{trade.status}</span>
|
|
||||||
</td>
|
|
||||||
<td>{new Date(trade.entry_time).toLocaleString('zh-CN')}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{trades.map(trade => (
|
||||||
|
<tr key={trade.id}>
|
||||||
|
<td>{trade.symbol}</td>
|
||||||
|
<td className={trade.side === 'BUY' ? 'buy' : 'sell'}>{trade.side}</td>
|
||||||
|
<td>{parseFloat(trade.quantity).toFixed(4)}</td>
|
||||||
|
<td>{parseFloat(trade.entry_price).toFixed(4)}</td>
|
||||||
|
<td>{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}</td>
|
||||||
|
<td className={parseFloat(trade.pnl) >= 0 ? 'positive' : 'negative'}>
|
||||||
|
{parseFloat(trade.pnl).toFixed(2)} USDT
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span className={`status ${trade.status}`}>{trade.status}</span>
|
||||||
|
</td>
|
||||||
|
<td>{new Date(trade.entry_time).toLocaleString('zh-CN')}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,18 @@ export const api = {
|
||||||
getTrades: async (params = {}) => {
|
getTrades: async (params = {}) => {
|
||||||
const query = new URLSearchParams(params).toString();
|
const query = new URLSearchParams(params).toString();
|
||||||
const response = await fetch(`${API_BASE_URL}/api/trades?${query}`);
|
const response = await fetch(`${API_BASE_URL}/api/trades?${query}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('获取交易记录失败');
|
||||||
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
getTradeStats: async (params = {}) => {
|
getTradeStats: async (params = {}) => {
|
||||||
const query = new URLSearchParams(params).toString();
|
const query = new URLSearchParams(params).toString();
|
||||||
const response = await fetch(`${API_BASE_URL}/api/trades/stats?${query}`);
|
const response = await fetch(`${API_BASE_URL}/api/trades/stats?${query}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('获取交易统计失败');
|
||||||
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user