Add hub-only OKX amp stats for ETH/BTC session windows.
Read-only 1H index candles, point amplitude metrics, history save and CSV export; no order-path changes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
"""中控振幅统计 API."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi.responses import Response
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from amp_stats_store import delete_history, get_history, list_history, save_history
|
||||
from lib.hub.amp_stats_lib import (
|
||||
build_export_csv,
|
||||
compute_amp_stats,
|
||||
export_filename,
|
||||
rows_page,
|
||||
)
|
||||
|
||||
|
||||
class ComputeBody(BaseModel):
|
||||
symbol: str = "eth"
|
||||
start_hour: int = 16
|
||||
period: str = "2m"
|
||||
custom_days: Optional[int] = None
|
||||
page: int = 1
|
||||
page_size: int = 20
|
||||
|
||||
|
||||
class SaveBody(BaseModel):
|
||||
result: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
def create_amp_stats_router() -> APIRouter:
|
||||
router = APIRouter(prefix="/api/amp-stats", tags=["amp-stats"])
|
||||
|
||||
@router.get("/meta")
|
||||
def api_meta():
|
||||
return {
|
||||
"ok": True,
|
||||
"exchange": "okx",
|
||||
"symbols": [
|
||||
{"key": "eth", "label": "ETH"},
|
||||
{"key": "btc", "label": "BTC"},
|
||||
],
|
||||
"end_hour": 16,
|
||||
"start_hours": list(range(24)),
|
||||
"periods": [
|
||||
{"key": "1m", "label": "1个月"},
|
||||
{"key": "2m", "label": "2个月"},
|
||||
{"key": "3m", "label": "3个月"},
|
||||
{"key": "6m", "label": "半年"},
|
||||
{"key": "1y", "label": "1年"},
|
||||
{"key": "custom", "label": "自定义"},
|
||||
],
|
||||
"default_period": "2m",
|
||||
"timeframe": "1H",
|
||||
"metric_note": "振幅与距离均为点数:振幅=最高-最低=(开→高)+(开→低)",
|
||||
}
|
||||
|
||||
@router.post("/compute")
|
||||
def api_compute(body: ComputeBody):
|
||||
try:
|
||||
result = compute_amp_stats(
|
||||
symbol=body.symbol,
|
||||
start_hour=body.start_hour,
|
||||
period=body.period,
|
||||
custom_days=body.custom_days,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
||||
page = rows_page(result.get("rows") or [], page=body.page, page_size=body.page_size)
|
||||
return {
|
||||
"ok": True,
|
||||
"result": result,
|
||||
"page": page,
|
||||
}
|
||||
|
||||
@router.get("/history")
|
||||
def api_history(symbol: str = "", limit: int = 50):
|
||||
return {"ok": True, "items": list_history(symbol=symbol, limit=limit)}
|
||||
|
||||
@router.post("/history")
|
||||
def api_history_save(body: SaveBody):
|
||||
payload = body.result if isinstance(body.result, dict) else {}
|
||||
if not payload.get("rows") and not payload.get("summary"):
|
||||
raise HTTPException(status_code=400, detail="无可保存的结果")
|
||||
item = save_history(payload)
|
||||
return {"ok": True, "item": item}
|
||||
|
||||
@router.get("/history/{item_id}")
|
||||
def api_history_detail(item_id: str):
|
||||
item = get_history(item_id)
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="历史不存在")
|
||||
return {"ok": True, "item": item}
|
||||
|
||||
@router.delete("/history/{item_id}")
|
||||
def api_history_delete(item_id: str):
|
||||
if not delete_history(item_id):
|
||||
raise HTTPException(status_code=404, detail="历史不存在")
|
||||
return {"ok": True}
|
||||
|
||||
@router.get("/export")
|
||||
def api_export(
|
||||
history_id: str = Query(default=""),
|
||||
symbol: str = Query(default="eth"),
|
||||
start_hour: int = Query(default=16),
|
||||
period: str = Query(default="2m"),
|
||||
custom_days: Optional[int] = Query(default=None),
|
||||
):
|
||||
if (history_id or "").strip():
|
||||
item = get_history(history_id.strip())
|
||||
if not item:
|
||||
raise HTTPException(status_code=404, detail="历史不存在")
|
||||
payload = item
|
||||
else:
|
||||
try:
|
||||
payload = compute_amp_stats(
|
||||
symbol=symbol,
|
||||
start_hour=start_hour,
|
||||
period=period,
|
||||
custom_days=custom_days,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
||||
csv_text = build_export_csv(payload)
|
||||
name = export_filename(payload)
|
||||
return Response(
|
||||
content=csv_text.encode("utf-8"),
|
||||
media_type="text/csv; charset=utf-8",
|
||||
headers={"Content-Disposition": f'attachment; filename="{name}"'},
|
||||
)
|
||||
|
||||
return router
|
||||
Reference in New Issue
Block a user