Add entry plan page with CRUD, archive flow, and win-rate stats.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+118
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user