- 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
94 lines
3.0 KiB
Python
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}
|