Files
wtf-backend/app/api/routes/charts.py
T
Bot fef69fa4ba feat(api): real klines/trades from indexer + auto table creation
- charts.py: serve OHLCV bars & trades from Postgres (klines/trades tables written by indexer), no synthetic data
- entities.py: add TradeModel/KlineModel matching indexer schema
- main.py: idempotent create_all on startup
- config.py: unify default DATABASE_URL with docker-compose/indexer
2026-08-31 02:41:20 +08:00

94 lines
3.0 KiB
Python

import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.dependencies import get_db_session
from app.models.entities import KlineModel, TradeModel
logger = logging.getLogger(__name__)
kline_router = APIRouter(prefix="/v1/charts")
@kline_router.get("/klines")
async def get_market_klines(
market_address: str = Query(..., description="预测市场合约地址"),
outcome_index: int = Query(0, description="结果选项序号 (0-based)"),
timeframe: str = Query("5s", description="时间粒度: 1s, 5s, 1m, 1h"),
limit: int = Query(1000, le=5000, description="返回的最大 K 线根数"),
db: AsyncSession = Depends(get_db_session),
):
"""
【TradingView 标准 OHLCV K 线历史接口】
返回由 Indexer 从链上真实成交事件确定性聚合的 K 线。
无成交历史时返回空数组(绝不生成假数据)。
"""
m_addr = market_address.lower()
# 当前仅支持 5 秒基准聚合(Indexer 按 5s 窗口写入),其他粒度在后续版本扩展
rows = (
await db.execute(
select(KlineModel)
.where(
KlineModel.market_address == m_addr,
KlineModel.outcome_index == outcome_index,
)
.order_by(KlineModel.bar_time.asc())
.limit(limit)
)
).scalars().all()
bars = [
{
"time": row.bar_time,
"open": float(row.open),
"high": float(row.high),
"low": float(row.low),
"close": float(row.close),
"volume": float(row.volume),
}
for row in rows
]
return {
"market": m_addr,
"outcome_index": outcome_index,
"timeframe": timeframe,
"has_trades": len(bars) > 0,
"bars": bars,
}
@kline_router.get("/trades")
async def get_market_trades(
market_address: str = Query(..., description="预测市场合约地址"),
outcome_index: int = Query(0, description="结果选项序号 (0-based)"),
limit: int = Query(100, le=500),
db: AsyncSession = Depends(get_db_session),
):
"""真实链上逐笔成交明细(由 Indexer 写入)"""
m_addr = market_address.lower()
rows = (
await db.execute(
select(TradeModel)
.where(
TradeModel.market_address == m_addr,
TradeModel.outcome_index == outcome_index,
)
.order_by(TradeModel.ts.desc())
.limit(limit)
)
).scalars().all()
trades = [
{
"tx_hash": row.tx_hash,
"type": row.trade_type,
"price": float(row.price),
"volume": float(row.volume),
"block_number": row.block_number,
"ts": row.ts,
}
for row in rows
]
return {"market": m_addr, "outcome_index": outcome_index, "trades": trades}