Add weekend filter, take-profit, and profit column to amp stats.

Long-straddle effective move uses TP on path hit (>=) else abs change; mark Sat/Sun on settlement days.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-23 02:47:20 +08:00
parent 789ab43dbe
commit b64c742fc9
7 changed files with 512 additions and 105 deletions
+71 -23
View File
@@ -13,9 +13,10 @@ from lib.hub.amp_stats_lib import (
compute_amp_stats,
export_filename,
normalize_straddle_premium,
normalize_take_profit,
normalize_weekend_filter,
reframe_amp_stats,
rows_page,
straddle_long_stats,
summarize_rows,
)
@@ -25,6 +26,8 @@ class ComputeBody(BaseModel):
period: str = "2m"
custom_days: Optional[int] = None
straddle_premium: Optional[float] = None
take_profit: Optional[float] = None
weekend_filter: str = "all"
page: int = 1
page_size: int = 20
@@ -33,11 +36,21 @@ class SaveBody(BaseModel):
result: dict[str, Any] = Field(default_factory=dict)
class StraddleBody(BaseModel):
"""已有日表上按权利金重算买跨对照(不拉 K 线)."""
class ReframeBody(BaseModel):
"""已有日表上改周末/权利金/止盈(不拉 K 线)."""
rows: list[dict[str, Any]] = Field(default_factory=list)
rows_all: list[dict[str, Any]] = Field(default_factory=list)
symbol: str = "eth"
start_hour: int = 16
period: str = "2m"
sample_days: int = 60
straddle_premium: Optional[float] = None
take_profit: Optional[float] = None
weekend_filter: str = "all"
price_source: str = ""
inst_id: str = ""
page: int = 1
page_size: int = 20
def create_amp_stats_router() -> APIRouter:
@@ -62,10 +75,16 @@ def create_amp_stats_router() -> APIRouter:
{"key": "1y", "label": "1年"},
{"key": "custom", "label": "自定义"},
],
"weekend_filters": [
{"key": "all", "label": "全部"},
{"key": "exclude", "label": "排除周末"},
{"key": "only", "label": "仅周末"},
],
"default_period": "2m",
"default_weekend_filter": "all",
"timeframe": "1H",
"metric_note": "振幅与距离均为点数:振幅=最高-最低=(开→高)+(开→低)",
"straddle_note": "买跨对照:双边权利金可设;越过用严格>;盈亏=|收-开|-权利金",
"straddle_note": "买跨:越过权利金用>;止盈≥触达用止盈点否则|涨跌|;收益=有效波动-权利金",
}
@router.post("/compute")
@@ -77,6 +96,8 @@ def create_amp_stats_router() -> APIRouter:
period=body.period,
custom_days=body.custom_days,
straddle_premium=body.straddle_premium,
take_profit=body.take_profit,
weekend_filter=body.weekend_filter,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@@ -89,19 +110,28 @@ def create_amp_stats_router() -> APIRouter:
"page": page,
}
@router.post("/straddle")
def api_straddle(body: StraddleBody):
@router.post("/reframe")
def api_reframe(body: ReframeBody):
rows_all = body.rows_all or []
if not rows_all:
raise HTTPException(status_code=400, detail="无日表可重算")
try:
prem = normalize_straddle_premium(body.straddle_premium)
result = reframe_amp_stats(
rows_all=rows_all,
symbol=body.symbol,
start_hour=body.start_hour,
period=body.period,
sample_days=body.sample_days,
straddle_premium=body.straddle_premium,
take_profit=body.take_profit,
weekend_filter=body.weekend_filter,
price_source=body.price_source,
inst_id=body.inst_id,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
if prem is None:
return {"ok": True, "straddle": None}
try:
st = straddle_long_stats(body.rows or [], prem)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {"ok": True, "straddle": st}
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):
@@ -110,7 +140,7 @@ def create_amp_stats_router() -> APIRouter:
@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"):
if not payload.get("rows") and not payload.get("rows_all") and not payload.get("summary"):
raise HTTPException(status_code=400, detail="无可保存的结果")
item = save_history(payload)
return {"ok": True, "item": item}
@@ -136,28 +166,46 @@ def create_amp_stats_router() -> APIRouter:
period: str = Query(default="2m"),
custom_days: Optional[int] = Query(default=None),
straddle_premium: Optional[float] = Query(default=None),
take_profit: Optional[float] = Query(default=None),
weekend_filter: str = Query(default="all"),
):
if (history_id or "").strip():
item = get_history(history_id.strip())
if not item:
raise HTTPException(status_code=404, detail="历史不存在")
payload = dict(item)
rows_all = item.get("rows_all") or item.get("rows") or []
try:
prem = normalize_straddle_premium(straddle_premium)
payload = reframe_amp_stats(
rows_all=rows_all,
symbol=item.get("symbol") or symbol,
start_hour=int(item.get("start_hour") if item.get("start_hour") is not None else start_hour),
period=str(item.get("period") or period),
sample_days=int(item.get("sample_days_requested") or 60),
straddle_premium=straddle_premium
if straddle_premium is not None
else item.get("straddle_premium"),
take_profit=take_profit if take_profit is not None else item.get("take_profit"),
weekend_filter=weekend_filter or item.get("weekend_filter") or "all",
price_source=str(item.get("price_source") or ""),
inst_id=str(item.get("inst_id") or ""),
missing=item.get("missing_days") or [],
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
if prem is not None:
summary = summarize_rows(payload.get("rows") or [], straddle_premium=prem)
payload["summary"] = summary
payload["straddle_premium"] = prem
else:
try:
# validate enums early
normalize_weekend_filter(weekend_filter)
normalize_straddle_premium(straddle_premium)
normalize_take_profit(take_profit)
payload = compute_amp_stats(
symbol=symbol,
start_hour=start_hour,
period=period,
custom_days=custom_days,
straddle_premium=straddle_premium,
take_profit=take_profit,
weekend_filter=weekend_filter,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc