feat: add min hours-to-expiry filter on ops map (8-48h step 2)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,6 +9,7 @@ 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.expiry_filter import filter_rows_min_hours_to_expiry
|
||||
from packages.domain.range import resolve_range
|
||||
|
||||
router = APIRouter(prefix="/stats", tags=["stats"], dependencies=[Depends(require_auth)])
|
||||
@@ -26,14 +27,32 @@ def _range_info(range_name: str, date: str | None) -> dict:
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
|
||||
def _validate_min_hours(min_hours: float) -> float:
|
||||
# 8–48,步进 2(与前端一致)
|
||||
h = float(min_hours)
|
||||
if h < 8 or h > 48 or abs(h - round(h)) > 1e-9 or int(round(h)) % 2 != 0:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="min_hours must be even integer in [8, 48]",
|
||||
)
|
||||
return float(int(round(h)))
|
||||
|
||||
|
||||
@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),
|
||||
min_hours: float = Query(
|
||||
default=12,
|
||||
ge=8,
|
||||
le=48,
|
||||
description="采样时距离到期 ≥ 该小时数才计入",
|
||||
),
|
||||
) -> dict:
|
||||
s = get_settings()
|
||||
mh = _validate_min_hours(min_hours)
|
||||
info = _range_info(range, date)
|
||||
repo = Repository(s.db_path)
|
||||
try:
|
||||
@@ -43,13 +62,16 @@ def leverage_stats(
|
||||
side=side,
|
||||
underlying=s.underlying,
|
||||
)
|
||||
return leverage_stats_payload(
|
||||
rows = filter_rows_min_hours_to_expiry(rows, min_hours=mh)
|
||||
payload = leverage_stats_payload(
|
||||
rows,
|
||||
range_info=info,
|
||||
bucket_minutes=bucket_minutes,
|
||||
min_leverage=float(s.min_option_leverage),
|
||||
side=side,
|
||||
)
|
||||
payload["min_hours"] = mh
|
||||
return payload
|
||||
finally:
|
||||
repo.close()
|
||||
|
||||
@@ -60,8 +82,15 @@ def move_points_stats(
|
||||
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),
|
||||
min_hours: float = Query(
|
||||
default=12,
|
||||
ge=8,
|
||||
le=48,
|
||||
description="采样时距离到期 ≥ 该小时数才计入",
|
||||
),
|
||||
) -> dict:
|
||||
s = get_settings()
|
||||
mh = _validate_min_hours(min_hours)
|
||||
info = _range_info(range, date)
|
||||
repo = Repository(s.db_path)
|
||||
try:
|
||||
@@ -71,6 +100,7 @@ def move_points_stats(
|
||||
side=side,
|
||||
underlying=s.underlying,
|
||||
)
|
||||
rows = filter_rows_min_hours_to_expiry(rows, min_hours=mh)
|
||||
ymds = sorted({str(r.get("expiry_ymd")) for r in rows if r.get("expiry_ymd")})
|
||||
# 懒回填:已到期但缺锚点时尽量补齐(本地指数优先,失败则跳过)
|
||||
try:
|
||||
@@ -83,13 +113,15 @@ def move_points_stats(
|
||||
except Exception: # noqa: BLE001 — 回填失败不阻断统计
|
||||
pass
|
||||
settlements = repo.list_settlements(ymds)
|
||||
return move_points_stats_payload(
|
||||
payload = move_points_stats_payload(
|
||||
rows,
|
||||
settlements,
|
||||
range_info=info,
|
||||
bucket_minutes=bucket_minutes,
|
||||
side=side,
|
||||
)
|
||||
payload["min_hours"] = mh
|
||||
return payload
|
||||
finally:
|
||||
repo.close()
|
||||
|
||||
@@ -100,14 +132,21 @@ def ops_map(
|
||||
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),
|
||||
min_hours: float = Query(default=12, ge=8, le=48),
|
||||
) -> 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)
|
||||
mh = _validate_min_hours(min_hours)
|
||||
lev = leverage_stats(
|
||||
range=range, date=date, side=side, bucket_minutes=bucket_minutes, min_hours=mh
|
||||
)
|
||||
mov = move_points_stats(
|
||||
range=range, date=date, side=side, bucket_minutes=bucket_minutes, min_hours=mh
|
||||
)
|
||||
return {
|
||||
"range": range,
|
||||
"date": lev.get("date"),
|
||||
"side": side,
|
||||
"bucket_minutes": bucket_minutes,
|
||||
"min_hours": mh,
|
||||
"leverage": lev,
|
||||
"move_points": mov,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user