feat中控市价单:实例按第一档实值与杠杆立即全仓开仓。

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-11-03 18:50:46 +08:00
parent 4e14c92385
commit 8a29f2d51b
6 changed files with 947 additions and 1 deletions
+74 -1
View File
@@ -561,6 +561,22 @@ def band_bounds(trigger_px: float, deviation_pct: float) -> tuple[float, float]:
return lo, hi
def _chain_contract_leverage(c: dict[str, Any], *, index_px: float) -> float | None:
"""用链上报价估合约杠杆(币本位≈1/ask)."""
from lib.options.options_pricing_lib import equivalent_contract_leverage
ask = _safe_float(c.get("ask"))
ct_mult = _safe_float(c.get("ct_mult")) or 0.01
if ask is None or ask <= 0 or ct_mult <= 0:
return None
return equivalent_contract_leverage(
index_px=index_px,
eth_amount=float(ct_mult),
total_premium=float(ask) * float(ct_mult),
margin_mode="coin",
)
def pick_nearest_itm_contract(
cfg: dict[str, Any],
ex: Any,
@@ -569,8 +585,12 @@ def pick_nearest_itm_contract(
opt_type: str,
index_px: float,
min_expiry_hours: float,
target_leverage: float | None = None,
) -> dict[str, Any] | None:
"""按当前指数选最近一档实值;到期 ≥ min_expiry_hours."""
"""按当前指数选最近一档实值;到期 ≥ min_expiry_hours.
target_leverage 有值时:仅在第一档实值(最近行权距)内,选杠杆最接近目标的合约。
"""
from lib.exchange.okx_options_lib import build_option_chain, option_buy_liquidity_ok
from lib.options.options_pricing_lib import option_moneyness
@@ -614,6 +634,59 @@ def pick_nearest_itm_contract(
if not candidates:
return None
candidates.sort(key=lambda x: (x[0], str(x[1].get("inst_id") or "")))
want_lev = _safe_float(target_leverage)
if want_lev is not None and want_lev > 0:
min_dist = candidates[0][0]
rung = [c for d, c in candidates if abs(float(d) - float(min_dist)) <= 1e-9]
scored: list[tuple[float, int, dict[str, Any], dict[str, Any] | None]] = []
for c in rung:
inst = str(c.get("inst_id") or "")
if not inst:
continue
q: dict[str, Any] | None = None
try:
q = cfg["quote_option_contract"](ex, inst)
except Exception:
q = None
ask = (q or {}).get("ask") if q else c.get("ask")
ask_sz = (q or {}).get("ask_sz") if q else c.get("ask_sz")
ok_liq, _ = option_buy_liquidity_ok(ask, ask_sz)
lev = None
if q and ask is not None:
ct = _safe_float((q or {}).get("ct_mult")) or _safe_float(c.get("ct_mult")) or 0.01
from lib.options.options_pricing_lib import equivalent_contract_leverage
lev = equivalent_contract_leverage(
index_px=index_px,
eth_amount=float(ct),
total_premium=float(ask) * float(ct),
margin_mode="coin",
)
if lev is None:
lev = _chain_contract_leverage(c, index_px=index_px)
if lev is None:
continue
scored.append((abs(float(lev) - float(want_lev)), 0 if ok_liq else 1, c, q))
if scored:
scored.sort(key=lambda x: (x[0], x[1], str(x[2].get("inst_id") or "")))
best_c = dict(scored[0][2])
if scored[0][3]:
best_c["quote"] = scored[0][3]
best_c["picked_leverage"] = _chain_contract_leverage(best_c, index_px=index_px)
if scored[0][3] and scored[0][3].get("ask") is not None:
ct = _safe_float(scored[0][3].get("ct_mult")) or _safe_float(best_c.get("ct_mult")) or 0.01
from lib.options.options_pricing_lib import equivalent_contract_leverage
best_c["picked_leverage"] = equivalent_contract_leverage(
index_px=index_px,
eth_amount=float(ct),
total_premium=float(scored[0][3]["ask"]) * float(ct),
margin_mode="coin",
)
return best_c
# 杠杆估不出时退回第一档最近合约
# 优先有卖一深度的最近档
for _dist, c in candidates[:12]:
inst = str(c.get("inst_id") or "")
+6
View File
@@ -152,6 +152,12 @@ def init_options_tables(conn: sqlite3.Connection) -> None:
ensure_cond_order_table(conn)
except Exception:
pass
try:
from lib.options.options_market_order_lib import ensure_market_order_table
ensure_market_order_table(conn)
except Exception:
pass
init_options_review_tables(conn)
+651
View File
@@ -0,0 +1,651 @@
"""中控市价开仓:按条件选第一档实值+杠杆接近,立即全仓复利开仓."""
from __future__ import annotations
import sqlite3
import time
from typing import Any
ACTIVE_STATUSES = ("open",)
def _safe_float(v: Any) -> float | None:
if v is None or v == "":
return None
try:
return float(v)
except (TypeError, ValueError):
return None
def ensure_market_order_table(conn: sqlite3.Connection) -> None:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_market_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hub_order_id TEXT NOT NULL UNIQUE,
underlying TEXT NOT NULL,
opt_type TEXT NOT NULL,
min_expiry_hours REAL NOT NULL,
target_leverage REAL NOT NULL,
picked_leverage REAL,
target_index REAL,
profit_exit_enabled INTEGER DEFAULT 0,
profit_exit_mult REAL DEFAULT 1.0,
pending_target_index REAL,
pending_profit_exit_enabled INTEGER,
pending_profit_exit_mult REAL,
exit_update_pending INTEGER DEFAULT 0,
status TEXT NOT NULL DEFAULT 'open',
end_reason TEXT,
opened_inst_id TEXT,
opened_trade_id INTEGER,
opened_at TEXT,
message TEXT,
created_at TEXT,
updated_at TEXT,
ended_at TEXT
)
"""
)
conn.execute(
"""
CREATE INDEX IF NOT EXISTS idx_options_market_orders_status
ON options_market_orders(status)
"""
)
def _now() -> str:
try:
from lib.options.options_time_lib import options_app_now_str
return options_app_now_str()
except Exception:
return time.strftime("%Y-%m-%d %H:%M:%S")
def _notify_mkt(
send_wechat: Any,
*,
title: str,
order: dict[str, Any],
account_label: str = "",
extra: list[str] | None = None,
) -> None:
if not callable(send_wechat) or not order:
return
try:
lines = [
title,
f"账户:{account_label or 'OKX期权'}",
f"单号:{order.get('hub_order_id') or '—'}",
f"标的:{order.get('underlying') or '—'} {order.get('opt_type') or '—'}",
f"杠杆目标:{order.get('target_leverage') if order.get('target_leverage') is not None else '—'}",
]
for line in extra or []:
if line:
lines.append(line)
send_wechat("\n".join(lines))
except Exception:
pass
def _row_to_dict(row: sqlite3.Row | None) -> dict[str, Any] | None:
if row is None:
return None
d = dict(row)
d["profit_exit_enabled"] = bool(int(d.get("profit_exit_enabled") or 0))
d["exit_update_pending"] = bool(int(d.get("exit_update_pending") or 0))
if d.get("pending_profit_exit_enabled") is not None:
d["pending_profit_exit_enabled"] = bool(int(d["pending_profit_exit_enabled"]))
return d
def list_market_orders(conn: sqlite3.Connection, *, active_only: bool = False) -> list[dict[str, Any]]:
ensure_market_order_table(conn)
if active_only:
rows = conn.execute(
f"""
SELECT * FROM options_market_orders
WHERE status IN ({",".join("?" * len(ACTIVE_STATUSES))})
ORDER BY id DESC
""",
ACTIVE_STATUSES,
).fetchall()
else:
rows = conn.execute(
"SELECT * FROM options_market_orders ORDER BY id DESC LIMIT 100"
).fetchall()
return [_row_to_dict(r) for r in rows if r]
def get_market_order_by_hub_id(conn: sqlite3.Connection, hub_order_id: str) -> dict[str, Any] | None:
ensure_market_order_table(conn)
row = conn.execute(
"SELECT * FROM options_market_orders WHERE hub_order_id = ?",
((hub_order_id or "").strip(),),
).fetchone()
return _row_to_dict(row)
def has_active_market_order(conn: sqlite3.Connection) -> bool:
ensure_market_order_table(conn)
row = conn.execute(
f"""
SELECT 1 FROM options_market_orders
WHERE status IN ({",".join("?" * len(ACTIVE_STATUSES))})
LIMIT 1
""",
ACTIVE_STATUSES,
).fetchone()
return row is not None
def execute_market_open(
cfg: dict[str, Any],
ex: Any,
payload: dict[str, Any],
*,
get_db: Any = None,
send_wechat: Any = None,
account_label: str = "",
) -> dict[str, Any]:
"""立即选约并全仓复利开仓;成功后写入市价单记录."""
from lib.options.options_cond_order_lib import (
_apply_exit_to_position,
_fetch_index,
has_active_cond_order,
machine_has_option_position,
pick_nearest_itm_contract,
)
from lib.options.options_db import init_options_tables
from lib.options.options_position_limit_lib import option_position_limit_block_msg
from lib.options.options_profit_exit_lib import normalize_profit_exit_mult
hub_order_id = (payload.get("hub_order_id") or "").strip()
if not hub_order_id:
return {"ok": False, "msg": "缺少 hub_order_id"}
underlying = (payload.get("underlying") or "ETH").strip().upper() or "ETH"
try:
from lib.options.options_underly_lib import resolve_default_underly, underly_not_allowed_msg
if not (payload.get("underlying") or "").strip():
underlying = resolve_default_underly()
uly_block = underly_not_allowed_msg(underlying)
if uly_block:
return {"ok": False, "msg": uly_block}
except Exception:
pass
opt_type = (payload.get("opt_type") or "P").strip().upper()
if opt_type not in ("P", "C"):
return {"ok": False, "msg": "opt_type 须为 P 或 C"}
min_expiry_hours = _safe_float(payload.get("min_expiry_hours"))
if min_expiry_hours is None or min_expiry_hours <= 0:
return {"ok": False, "msg": "min_expiry_hours 须 > 0"}
target_leverage = _safe_float(payload.get("target_leverage") or payload.get("leverage"))
if target_leverage is None or target_leverage <= 0:
return {"ok": False, "msg": "杠杆倍数须 > 0"}
exit_cfg = payload.get("exit") if isinstance(payload.get("exit"), dict) else {}
target_index = _safe_float(exit_cfg.get("target_index") or payload.get("target_index"))
pe_en = bool(exit_cfg.get("profit_exit_enabled") or payload.get("profit_exit_enabled"))
pe_mult = normalize_profit_exit_mult(
exit_cfg.get("profit_exit_mult", payload.get("profit_exit_mult")), default=1.0
)
if target_index is not None and target_index <= 0:
return {"ok": False, "msg": "target_index 无效"}
if not pe_en and target_index is None:
return {"ok": False, "msg": "请至少配置目标位或翻倍出场"}
if ex is None:
return {"ok": False, "msg": "期权交易所未就绪"}
db_fn = get_db or (cfg.get("get_db") if isinstance(cfg, dict) else None)
if not callable(db_fn):
return {"ok": False, "msg": "缺少 get_db"}
conn = db_fn()
try:
init_options_tables(conn)
existing = get_market_order_by_hub_id(conn, hub_order_id)
if existing:
return {"ok": True, "msg": "市价单已存在", "order": existing, "deduped": True}
if has_active_cond_order(conn):
return {"ok": False, "msg": "本机已有活跃条件单"}
if has_active_market_order(conn):
return {"ok": False, "msg": "本机已有活跃市价单"}
if machine_has_option_position(cfg, ex):
return {"ok": False, "msg": "本机已有期权仓,拒绝市价单"}
limit_msg = option_position_limit_block_msg(
ex,
opening_inst_id="",
fetch_positions=cfg.get("fetch_option_positions"),
)
if limit_msg:
return {"ok": False, "msg": limit_msg}
try:
conn.commit()
except Exception:
pass
finally:
try:
conn.close()
except Exception:
pass
idx = _fetch_index(cfg, ex, underlying)
if idx is None or idx <= 0:
return {"ok": False, "msg": "指数价获取失败"}
picked = pick_nearest_itm_contract(
cfg,
ex,
underlying=underlying,
opt_type=opt_type,
index_px=float(idx),
min_expiry_hours=float(min_expiry_hours),
target_leverage=float(target_leverage),
)
if not picked or not picked.get("inst_id"):
return {"ok": False, "msg": "暂无合适的第一档实值合约"}
inst_id = str(picked["inst_id"])
from lib.exchange.okx_options_lib import option_buy_liquidity_ok
q = picked.get("quote") or cfg["quote_option_contract"](ex, inst_id)
ok_liq, msg_liq = option_buy_liquidity_ok(q.get("ask"), q.get("ask_sz"))
if not ok_liq:
return {"ok": False, "msg": f"深度不足: {msg_liq or '无卖一'}"}
from lib.options.options_coin_open_lib import open_coin_option_buy_full
note = f"中控市价单:{hub_order_id}"
result = open_coin_option_buy_full(
cfg,
ex,
inst_id=inst_id,
signal_note=note,
target_index=target_index,
profit_exit_enabled=pe_en,
profit_exit_mult=pe_mult,
target_sheets=None,
)
if not result.get("ok"):
return {
"ok": False,
"msg": result.get("msg") or "开仓失败",
"picked_inst_id": inst_id,
"picked_leverage": picked.get("picked_leverage"),
}
trade_id = result.get("trade_id")
picked_lev = _safe_float(picked.get("picked_leverage"))
now = _now()
order_stub = {
"hub_order_id": hub_order_id,
"underlying": underlying,
"opt_type": opt_type,
"target_leverage": float(target_leverage),
"opened_inst_id": inst_id,
"target_index": target_index,
"profit_exit_enabled": pe_en,
"profit_exit_mult": pe_mult,
}
conn = db_fn()
try:
init_options_tables(conn)
ensure_market_order_table(conn)
conn.execute(
"""
INSERT INTO options_market_orders (
hub_order_id, underlying, opt_type, min_expiry_hours, target_leverage,
picked_leverage, target_index, profit_exit_enabled, profit_exit_mult,
status, opened_inst_id, opened_trade_id, opened_at, message,
created_at, updated_at
) VALUES (?,?,?,?,?,?,?,?,?, 'open', ?,?,?,?,?,?)
""",
(
hub_order_id,
underlying,
opt_type,
float(min_expiry_hours),
float(target_leverage),
float(picked_lev) if picked_lev is not None else None,
float(target_index) if target_index is not None else None,
1 if pe_en else 0,
float(pe_mult),
inst_id,
int(trade_id) if trade_id is not None else None,
now,
f"已开仓 {inst_id}",
now,
now,
),
)
conn.commit()
order = get_market_order_by_hub_id(conn, hub_order_id) or order_stub
_apply_exit_to_position(
conn,
order,
target_index=target_index,
profit_exit_enabled=pe_en,
profit_exit_mult=float(pe_mult),
)
_notify_mkt(
send_wechat,
title="【OKX期权·市价单开仓】",
order=order,
account_label=account_label,
extra=[
f"合约:{inst_id}",
f"实选杠杆:{picked_lev:g}" if picked_lev is not None else "",
f"目标杠杆:{float(target_leverage):g}",
],
)
return {
"ok": True,
"msg": "市价开仓成功",
"order": order,
"inst_id": inst_id,
"trade_id": trade_id,
"picked_leverage": picked_lev,
}
finally:
try:
conn.close()
except Exception:
pass
def update_market_order_exit(
conn: sqlite3.Connection,
hub_order_id: str,
*,
target_index: Any = None,
profit_exit_enabled: Any = None,
profit_exit_mult: Any = None,
) -> dict[str, Any]:
from lib.options.options_cond_order_lib import _apply_exit_to_position, _exit_close_in_progress
from lib.options.options_profit_exit_lib import normalize_profit_exit_mult
order = get_market_order_by_hub_id(conn, hub_order_id)
if not order:
return {"ok": False, "msg": "市价单不存在"}
if order.get("status") != "open":
return {"ok": False, "msg": "仅持仓中的市价单可改出场"}
new_target = _safe_float(target_index) if target_index is not None else order.get("target_index")
if target_index is not None and str(target_index).strip() == "":
new_target = None
if new_target is not None and new_target <= 0:
return {"ok": False, "msg": "target_index 无效"}
if profit_exit_enabled is None:
pe_en = bool(order.get("profit_exit_enabled"))
else:
pe_en = bool(profit_exit_enabled)
pe_mult = normalize_profit_exit_mult(
profit_exit_mult if profit_exit_mult is not None else order.get("profit_exit_mult"),
default=1.0,
)
if not pe_en and new_target is None:
return {"ok": False, "msg": "请至少配置目标位或翻倍出场"}
closing = _exit_close_in_progress(conn, str(order.get("opened_inst_id") or ""))
now = _now()
if closing:
conn.execute(
"""
UPDATE options_market_orders SET
pending_target_index = ?,
pending_profit_exit_enabled = ?,
pending_profit_exit_mult = ?,
exit_update_pending = 1,
message = ?,
updated_at = ?
WHERE hub_order_id = ?
""",
(
float(new_target) if new_target is not None else None,
1 if pe_en else 0,
float(pe_mult),
"出场已更新,等待本轮平仓挂单结束后生效",
now,
hub_order_id,
),
)
conn.commit()
return {
"ok": True,
"msg": "已记录,本轮平仓结束后生效",
"deferred": True,
"order": get_market_order_by_hub_id(conn, hub_order_id),
}
_apply_exit_to_position(
conn,
order,
target_index=new_target,
profit_exit_enabled=pe_en,
profit_exit_mult=pe_mult,
)
conn.execute(
"""
UPDATE options_market_orders SET
target_index = ?,
profit_exit_enabled = ?,
profit_exit_mult = ?,
exit_update_pending = 0,
pending_target_index = NULL,
pending_profit_exit_enabled = NULL,
pending_profit_exit_mult = NULL,
message = ?,
updated_at = ?
WHERE hub_order_id = ?
""",
(
float(new_target) if new_target is not None else None,
1 if pe_en else 0,
float(pe_mult),
"出场已更新并生效",
now,
hub_order_id,
),
)
conn.commit()
return {"ok": True, "msg": "出场已更新", "order": get_market_order_by_hub_id(conn, hub_order_id)}
def _end_order(
conn: sqlite3.Connection,
order: dict[str, Any],
*,
end_reason: str,
message: str,
send_wechat: Any = None,
account_label: str = "",
) -> None:
now = _now()
conn.execute(
"""
UPDATE options_market_orders SET
status = 'done',
end_reason = ?,
message = ?,
updated_at = ?,
ended_at = ?
WHERE hub_order_id = ?
""",
(end_reason, message, now, now, order["hub_order_id"]),
)
conn.commit()
_notify_mkt(
send_wechat,
title="【OKX期权·市价单结束】",
order=order,
account_label=account_label,
extra=[f"原因:{end_reason}", f"说明:{message}"],
)
def run_market_orders_tick(
conn_or_get_db: Any,
cfg: dict[str, Any],
*,
send_wechat: Any = None,
account_label: str = "",
) -> None:
"""持仓消失时结束市价单;应用延迟出场."""
get_db = conn_or_get_db if callable(conn_or_get_db) else None
if get_db is None:
conn = conn_or_get_db
ensure_market_order_table(conn)
ex = cfg.get("exchange_options")
if ex is None:
return
for order in list_market_orders(conn, active_only=True):
try:
_tick_one_market(
conn,
cfg,
ex,
order,
send_wechat=send_wechat,
account_label=account_label,
)
except Exception as e:
try:
conn.execute(
"UPDATE options_market_orders SET message=?, updated_at=? WHERE hub_order_id=?",
(f"监控异常: {e}"[:200], _now(), order["hub_order_id"]),
)
conn.commit()
except Exception:
pass
return
conn = get_db()
orders: list[dict[str, Any]] = []
ex: Any = None
try:
ensure_market_order_table(conn)
ex = cfg.get("exchange_options")
if ex is not None:
orders = list_market_orders(conn, active_only=True)
try:
conn.commit()
except Exception:
pass
finally:
try:
conn.close()
except Exception:
pass
if ex is None or not orders:
return
for order in orders:
conn = get_db()
try:
_tick_one_market(
conn,
cfg,
ex,
order,
send_wechat=send_wechat,
account_label=account_label,
)
try:
conn.commit()
except Exception:
pass
except Exception as e:
try:
conn.execute(
"UPDATE options_market_orders SET message=?, updated_at=? WHERE hub_order_id=?",
(f"监控异常: {e}"[:200], _now(), order["hub_order_id"]),
)
conn.commit()
except Exception:
pass
finally:
try:
conn.close()
except Exception:
pass
def _tick_one_market(
conn: sqlite3.Connection,
cfg: dict[str, Any],
ex: Any,
order: dict[str, Any],
*,
send_wechat: Any,
account_label: str,
) -> None:
from lib.options.options_cond_order_lib import (
_apply_exit_to_position,
_exit_close_in_progress,
machine_has_option_position,
)
if str(order.get("status") or "") != "open":
return
if order.get("exit_update_pending") and not _exit_close_in_progress(
conn, str(order.get("opened_inst_id") or "")
):
pe_en = order.get("pending_profit_exit_enabled")
if pe_en is None:
pe_en = order.get("profit_exit_enabled")
_apply_exit_to_position(
conn,
order,
target_index=_safe_float(order.get("pending_target_index")),
profit_exit_enabled=bool(pe_en),
profit_exit_mult=float(
order.get("pending_profit_exit_mult")
if order.get("pending_profit_exit_mult") is not None
else order.get("profit_exit_mult")
or 1.0
),
)
conn.execute(
"""
UPDATE options_market_orders SET
target_index = pending_target_index,
profit_exit_enabled = COALESCE(pending_profit_exit_enabled, profit_exit_enabled),
profit_exit_mult = COALESCE(pending_profit_exit_mult, profit_exit_mult),
exit_update_pending = 0,
pending_target_index = NULL,
pending_profit_exit_enabled = NULL,
pending_profit_exit_mult = NULL,
message = ?,
updated_at = ?
WHERE hub_order_id = ?
""",
("延迟出场已生效", _now(), order["hub_order_id"]),
)
conn.commit()
inst_id = str(order.get("opened_inst_id") or "")
if not inst_id:
return
if machine_has_option_position(cfg, ex):
return
raw = cfg["fetch_option_positions"](ex) or []
still = any(str(p.get("instId") or "") == inst_id for p in raw)
if still:
return
_end_order(
conn,
order,
end_reason="closed",
message="持仓已平,市价单完成",
send_wechat=send_wechat,
account_label=account_label,
)
+7
View File
@@ -1546,6 +1546,7 @@ def _start_monitor_thread(app: Flask, cfg: dict[str, Any]) -> None:
def _cond_order_tick(_conn=None) -> None:
from lib.options.options_cond_order_lib import run_cond_orders_tick
from lib.options.options_market_order_lib import run_market_orders_tick
# 传 get_db:条件单开仓前会释放连接,避免嵌套锁库
run_cond_orders_tick(
@@ -1554,6 +1555,12 @@ def _start_monitor_thread(app: Flask, cfg: dict[str, Any]) -> None:
send_wechat=cfg.get("send_wechat"),
account_label=str(cfg.get("account_label") or ""),
)
run_market_orders_tick(
cfg["get_db"],
cfg,
send_wechat=cfg.get("send_wechat"),
account_label=str(cfg.get("account_label") or ""),
)
t = threading.Thread(
target=options_monitor_loop,