Add entry plan page with CRUD, archive flow, and win-rate stats.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-22 16:19:56 +08:00
parent bd759c42d6
commit 091317276d
9 changed files with 1876 additions and 1 deletions
+118 -1
View File
@@ -60,7 +60,16 @@ from hub_symbol_archive_lib import (
update_review_quote,
upsert_trade_overlay,
)
from hub_macro_calendar_lib import (
from hub_entry_plan_lib import (
compute_entry_plan_stats,
create_entry_plan,
delete_entry_plan,
get_entry_plan,
init_db as init_entry_plan_db,
list_entry_plans,
meta_payload as entry_plan_meta_payload,
update_entry_plan,
)
MACRO_EVENT_LABELS,
MACRO_EVENT_TYPES,
create_event as create_macro_event,
@@ -691,6 +700,7 @@ def root_redirect():
@app.get("/monitor")
@app.get("/plan")
@app.get("/market")
@app.get("/archive")
@app.get("/dashboard")
@@ -2375,6 +2385,113 @@ async def api_archive_sync():
return body
@app.get("/api/entry-plans/meta")
def api_entry_plans_meta():
init_entry_plan_db()
exchanges = []
for ex in enabled_exchanges(load_settings()):
exchanges.append(
{
"id": ex.get("id"),
"key": ex.get("key"),
"name": ex.get("name"),
}
)
return {"ok": True, **entry_plan_meta_payload(exchanges)}
@app.get("/api/entry-plans")
def api_entry_plans_list(status: str = "active"):
init_entry_plan_db()
try:
rows = list_entry_plans(status=status)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
return {"ok": True, "plans": rows, "count": len(rows), "status": status.strip().lower()}
@app.get("/api/entry-plans/stats")
def api_entry_plan_stats(
dimension: str = "symbol",
period: str = "all",
date_from: str = "",
date_to: str = "",
):
init_entry_plan_db()
try:
stats = compute_entry_plan_stats(
dimension=dimension,
period=period,
date_from=date_from,
date_to=date_to,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
return {"ok": True, "stats": stats}
@app.get("/api/entry-plans/{plan_id}")
def api_entry_plan_detail(plan_id: int):
init_entry_plan_db()
row = get_entry_plan(int(plan_id))
if not row:
raise HTTPException(status_code=404, detail="计划不存在")
return {"ok": True, "plan": row}
class EntryPlanBody(BaseModel):
plan_date: str = ""
exchange_key: str = ""
symbol: str = ""
plan_type: str = ""
trend_timeframe: str = ""
entry_timeframe: str = ""
direction: str = ""
target_level: str = ""
current_range: str = ""
entry_scheme: str = ""
result: str | None = None
pnl_amount: float | None = None
note: str = ""
@app.post("/api/entry-plans")
def api_entry_plan_create(body: EntryPlanBody = Body(...)):
init_entry_plan_db()
try:
row = create_entry_plan(body.model_dump(exclude_unset=True))
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
return {"ok": True, "plan": row}
@app.patch("/api/entry-plans/{plan_id}")
def api_entry_plan_update(plan_id: int, body: EntryPlanBody = Body(...)):
init_entry_plan_db()
payload = body.model_dump(exclude_unset=True)
if not payload:
raise HTTPException(status_code=400, detail="无更新字段")
try:
row = update_entry_plan(int(plan_id), payload)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
if not row:
raise HTTPException(status_code=404, detail="计划不存在")
return {"ok": True, "plan": row}
@app.delete("/api/entry-plans/{plan_id}")
def api_entry_plan_delete(plan_id: int):
init_entry_plan_db()
try:
ok = delete_entry_plan(int(plan_id))
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
if not ok:
raise HTTPException(status_code=404, detail="计划不存在或已归档")
return {"ok": True, "id": int(plan_id)}
@app.get("/api/hub/fund-overview")
def api_hub_fund_overview():
from hub_fund_history_lib import build_fund_overview