feat(hedge): option-primary watch entry with leverage gate
Start strategy arms a watching plan instead of opening immediately; list filters by leverage; type is a dropdown defaulting to OTM. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -83,6 +83,7 @@ def init_hedge_plan_tables(conn: sqlite3.Connection) -> None:
|
||||
_ensure_column(conn, "hedge_plans", "strike_interval", "REAL")
|
||||
_ensure_column(conn, "hedge_plans", "min_option_hours", "REAL")
|
||||
_ensure_column(conn, "hedge_plans", "option_moneyness", "TEXT")
|
||||
_ensure_column(conn, "hedge_plans", "option_leverage", "REAL")
|
||||
_ensure_column(conn, "hedge_plans", "perp_direction", "TEXT")
|
||||
_ensure_column(conn, "hedge_plan_legs", "ct_mult", "REAL")
|
||||
|
||||
@@ -99,15 +100,19 @@ def _ensure_column(conn: sqlite3.Connection, table: str, col: str, typedef: str)
|
||||
conn.execute(f"ALTER TABLE {table} ADD COLUMN {col} {typedef}")
|
||||
|
||||
|
||||
_ACTIVE_STATUSES = ("opening", "active", "partial", "watching")
|
||||
|
||||
|
||||
def count_active_plans(conn: sqlite3.Connection, plan_type: Optional[str] = None) -> int:
|
||||
statuses = ",".join(f"'{s}'" for s in _ACTIVE_STATUSES)
|
||||
if plan_type:
|
||||
row = conn.execute(
|
||||
"SELECT COUNT(1) AS c FROM hedge_plans WHERE status IN ('opening','active','partial') AND plan_type=?",
|
||||
f"SELECT COUNT(1) AS c FROM hedge_plans WHERE status IN ({statuses}) AND plan_type=?",
|
||||
(plan_type,),
|
||||
).fetchone()
|
||||
else:
|
||||
row = conn.execute(
|
||||
"SELECT COUNT(1) AS c FROM hedge_plans WHERE status IN ('opening','active','partial')"
|
||||
f"SELECT COUNT(1) AS c FROM hedge_plans WHERE status IN ({statuses})"
|
||||
).fetchone()
|
||||
return int((row["c"] if row else 0) or 0)
|
||||
|
||||
@@ -200,7 +205,7 @@ def delete_plan(conn: sqlite3.Connection, plan_id: int) -> dict[str, Any]:
|
||||
if not plan:
|
||||
return {"ok": False, "msg": "计划不存在"}
|
||||
st = str(plan.get("status") or "")
|
||||
if st in ("opening", "active", "partial"):
|
||||
if st in ("opening", "active", "partial", "watching"):
|
||||
return {"ok": False, "msg": "进行中的计划不可删除,请先结束"}
|
||||
conn.execute("DELETE FROM hedge_plan_legs WHERE plan_id=?", (int(plan_id),))
|
||||
conn.execute("DELETE FROM hedge_plans WHERE id=?", (int(plan_id),))
|
||||
@@ -236,7 +241,23 @@ def attach_legs_to_plans(conn: sqlite3.Connection, plans: list[dict[str, Any]])
|
||||
legs = get_plan_legs(conn, int(p["id"]))
|
||||
row = dict(p)
|
||||
row["legs"] = legs
|
||||
row["contracts_summary"] = legs_contract_summary(legs)
|
||||
summary = legs_contract_summary(legs)
|
||||
if str(p.get("status") or "") == "watching" and (not legs or summary == "—"):
|
||||
money = str(p.get("option_moneyness") or "otm")
|
||||
money_lab = {"itm": "实/平", "atm": "平值", "otm": "虚值"}.get(money, money)
|
||||
parts = [f"盯盘·{money_lab}"]
|
||||
try:
|
||||
if p.get("strike_interval") not in (None, ""):
|
||||
parts.append(f"间隔{float(p.get('strike_interval')):g}")
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
try:
|
||||
if p.get("option_leverage") not in (None, ""):
|
||||
parts.append(f"杠杆≥{float(p.get('option_leverage')):g}")
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
summary = "·".join(parts)
|
||||
row["contracts_summary"] = summary
|
||||
row["missing_leg"] = missing_leg_role(legs)
|
||||
out.append(row)
|
||||
return out
|
||||
|
||||
Reference in New Issue
Block a user