Files
market_intel/apps/api/routes/stats.py
T
2026-08-01 10:33:19 +08:00

114 lines
3.8 KiB
Python

"""日/周/月统计 API。"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Query
from apps.api.auth import require_auth
from apps.worker.settle import ensure_settlements_for_ymds
from packages.config import get_settings
from packages.db import Repository
from packages.domain.aggregate import leverage_stats_payload, move_points_stats_payload
from packages.domain.range import resolve_range
router = APIRouter(prefix="/stats", tags=["stats"], dependencies=[Depends(require_auth)])
def _range_info(range_name: str, date: str | None) -> dict:
s = get_settings()
try:
return resolve_range(
range_name,
date,
month_mode=s.month_range_mode,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
@router.get("/leverage")
def leverage_stats(
range: str = Query(default="day", pattern="^(day|week|month)$"),
date: str | None = Query(default=None, description="锚点日 YYYY-MM-DD(上海)"),
side: str = Query(default="both", pattern="^(C|P|both)$"),
bucket_minutes: int = Query(default=60, ge=15, le=120),
) -> dict:
s = get_settings()
info = _range_info(range, date)
repo = Repository(s.db_path)
try:
rows = repo.fetch_option_quotes(
start_ms=info["start_ms"],
end_ms=info["end_ms"],
side=side,
underlying=s.underlying,
)
return leverage_stats_payload(
rows,
range_info=info,
bucket_minutes=bucket_minutes,
min_leverage=float(s.min_option_leverage),
side=side,
)
finally:
repo.close()
@router.get("/move_points")
def move_points_stats(
range: str = Query(default="day", pattern="^(day|week|month)$"),
date: str | None = Query(default=None, description="锚点日 YYYY-MM-DD"),
side: str = Query(default="both", pattern="^(C|P|both)$"),
bucket_minutes: int = Query(default=60, ge=15, le=120),
) -> dict:
s = get_settings()
info = _range_info(range, date)
repo = Repository(s.db_path)
try:
rows = repo.fetch_option_quotes(
start_ms=info["start_ms"],
end_ms=info["end_ms"],
side=side,
underlying=s.underlying,
)
ymds = sorted({str(r.get("expiry_ymd")) for r in rows if r.get("expiry_ymd")})
# 懒回填:已到期但缺锚点时尽量补齐(本地指数优先,失败则跳过)
try:
ensure_settlements_for_ymds(
repo,
ymds,
underlying=s.underlying,
index_inst_id=s.index_inst_id,
)
except Exception: # noqa: BLE001 — 回填失败不阻断统计
pass
settlements = repo.list_settlements(ymds)
return move_points_stats_payload(
rows,
settlements,
range_info=info,
bucket_minutes=bucket_minutes,
side=side,
)
finally:
repo.close()
@router.get("/ops-map")
def ops_map(
range: str = Query(default="day", pattern="^(day|week|month)$"),
date: str | None = Query(default=None),
side: str = Query(default="both", pattern="^(C|P|both)$"),
bucket_minutes: int = Query(default=60, ge=15, le=120),
) -> dict:
lev = leverage_stats(range=range, date=date, side=side, bucket_minutes=bucket_minutes)
mov = move_points_stats(range=range, date=date, side=side, bucket_minutes=bucket_minutes)
return {
"range": range,
"date": lev.get("date"),
"side": side,
"bucket_minutes": bucket_minutes,
"leverage": lev,
"move_points": mov,
}