Initial standalone crypto_okx with one-click deploy.

Add deploy/manage.sh bootstrap for git.bz121.com/dekun/crypto_okx and point docs at this repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-08-13 20:00:59 +08:00
commit a1abe159fa
238 changed files with 69214 additions and 0 deletions
+377
View File
@@ -0,0 +1,377 @@
"""单独期权翻倍出场:盈利达权利金×倍数后按买一限价平仓.
1 倍 = 盈利金额等于初始权利金 ⇒ 买一可回收 ≥ 权利金 × (1 + 倍数).
与「目标位」并行;与仅微信提醒的 OKX_OPTIONS_PROFIT_ALERT_RATIO 独立.
"""
from __future__ import annotations
import sqlite3
from typing import Any, Callable
from lib.options.options_db import init_options_tables, sum_open_premium_paid
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_profit_exit_columns(conn: sqlite3.Connection) -> None:
init_options_tables(conn)
for ddl in (
"ALTER TABLE options_trades ADD COLUMN profit_exit_enabled INTEGER DEFAULT 0",
"ALTER TABLE options_trades ADD COLUMN profit_exit_mult REAL DEFAULT 1.0",
"ALTER TABLE options_trades ADD COLUMN profit_exit_state TEXT DEFAULT 'idle'",
):
try:
conn.execute(ddl)
except Exception:
pass
def normalize_profit_exit_mult(raw: Any, *, default: float = 1.0) -> float:
try:
mult = float(raw)
except (TypeError, ValueError):
mult = float(default)
if mult <= 0:
mult = float(default)
return round(mult, 4)
def profit_exit_hit(
*,
premium_paid: float,
recycle_usdc: float,
mult: float,
) -> bool:
"""1倍:盈利=权利金 ⇒ recycle ≥ premium×(1+mult)."""
prem = float(premium_paid or 0)
recv = float(recycle_usdc or 0)
m = float(mult or 0)
if prem <= 0 or m <= 0 or recv <= 0:
return False
return recv + 1e-9 >= prem * (1.0 + m)
def required_recycle_usdc(premium_paid: float, mult: float) -> float | None:
prem = float(premium_paid or 0)
m = float(mult or 0)
if prem <= 0 or m <= 0:
return None
return round(prem * (1.0 + m), 4)
def set_profit_exit(
conn: sqlite3.Connection,
*,
inst_id: str,
enabled: bool,
mult: float | None = None,
) -> dict[str, Any]:
ensure_profit_exit_columns(conn)
inst = (inst_id or "").strip()
if not inst:
return {"ok": False, "msg": "缺少 inst_id"}
m = normalize_profit_exit_mult(mult if mult is not None else 1.0)
rows = conn.execute(
"""
SELECT id FROM options_trades
WHERE inst_id = ? AND status = 'open'
""",
(inst,),
).fetchall()
if not rows:
return {"ok": False, "msg": "未找到该合约的本地开仓记录"}
if enabled:
conn.execute(
"""
UPDATE options_trades
SET profit_exit_enabled = 1,
profit_exit_mult = ?,
profit_exit_state = 'active'
WHERE inst_id = ? AND status = 'open'
""",
(m, inst),
)
else:
conn.execute(
"""
UPDATE options_trades
SET profit_exit_enabled = 0,
profit_exit_state = 'idle'
WHERE inst_id = ? AND status = 'open'
""",
(inst,),
)
return {
"ok": True,
"inst_id": inst,
"profit_exit_enabled": bool(enabled),
"profit_exit_mult": m if enabled else None,
"updated": len(rows),
}
def profit_exit_by_inst(conn: sqlite3.Connection) -> dict[str, dict[str, Any]]:
"""进行中(active/closing)的翻倍出场,按合约取最新一条规则."""
ensure_profit_exit_columns(conn)
rows = conn.execute(
"""
SELECT inst_id, profit_exit_enabled, profit_exit_mult, profit_exit_state
FROM options_trades
WHERE status = 'open'
AND (
CAST(COALESCE(profit_exit_enabled, 0) AS INTEGER) = 1
OR COALESCE(profit_exit_state, 'idle') IN ('active', 'closing')
)
ORDER BY id DESC
"""
).fetchall()
out: dict[str, dict[str, Any]] = {}
for r in rows:
inst = str(r["inst_id"] or "").strip()
if not inst or inst in out:
continue
enabled = int(r["profit_exit_enabled"] or 0) == 1
state = str(r["profit_exit_state"] or "idle")
if not enabled and state not in ("active", "closing"):
continue
mult = normalize_profit_exit_mult(r["profit_exit_mult"], default=1.0)
out[inst] = {
"inst_id": inst,
"profit_exit_enabled": enabled or state in ("active", "closing"),
"profit_exit_mult": mult,
"profit_exit_state": state if state in ("active", "closing") else ("active" if enabled else "idle"),
"required_recycle": None,
}
for inst, info in out.items():
prem = sum_open_premium_paid(conn, inst)
if prem is not None:
info["premium_paid"] = prem
info["required_recycle"] = required_recycle_usdc(prem, float(info["profit_exit_mult"]))
return out
def _mark_state(conn: sqlite3.Connection, inst_id: str, state: str) -> None:
conn.execute(
"""
UPDATE options_trades
SET profit_exit_state = ?
WHERE inst_id = ? AND status = 'open'
""",
(state, inst_id),
)
def _commit(conn: sqlite3.Connection) -> None:
try:
conn.commit()
except Exception:
pass
def _result_fully_done(result: dict[str, Any]) -> bool:
if result.get("already_flat"):
return True
if result.get("fully_closed"):
return True
remaining = result.get("remaining_sheets")
if remaining is not None and int(remaining) <= 0 and result.get("ok"):
return True
return False
def close_option_by_bid_profit_exit(
cfg: dict[str, Any],
ex: Any,
inst_id: str,
*,
sheets: int | None = None,
) -> dict[str, Any]:
from lib.options.options_close_exec_lib import close_option_by_bid1
return close_option_by_bid1(
cfg,
ex,
inst_id,
sheets=sheets,
require_recycle_gate=False,
signal_note="翻倍出场",
)
def _estimate_recycle(
cfg: dict[str, Any],
ex: Any,
pos: dict[str, Any],
premium_paid: float | None,
) -> float | None:
from lib.options.options_positions_lib import attach_close_preview
row = dict(pos)
attach_close_preview(cfg, ex, row, premium_paid=premium_paid)
preview = row.get("close_preview") if isinstance(row.get("close_preview"), dict) else {}
if preview.get("bid_invalid"):
return None
return _safe_float(preview.get("total_received"))
def _notify_profit_exit_close(
cfg: dict[str, Any] | None,
send_wechat: Callable[[str], None] | None,
*,
account_label: str,
inst_id: str,
mult: float,
premium_paid: float | None,
recycle: float | None,
result: dict[str, Any],
conn: Any = None,
) -> None:
if result.get("fully_closed") or result.get("already_flat"):
if cfg is not None:
try:
from lib.options.options_notify_lib import notify_options_close
notify_options_close(
cfg,
conn,
inst_id=inst_id,
reason=f"翻倍出场({mult:g}倍)",
sheets=result.get("submitted_sheets"),
premium_received=result.get("premium_received"),
close_quote=result.get("locked_bid_px") or result.get("bid"),
)
return
except Exception:
pass
if not send_wechat:
return
try:
send_wechat(
"\n".join(
[
"【OKX期权·翻倍出场】",
f"账户:{account_label}",
f"合约:{inst_id}",
f"倍数:{mult:g}(1倍=盈利=权利金)",
f"权利金:{premium_paid if premium_paid is not None else ''}",
f"可回收:{recycle if recycle is not None else ''}",
f"提交张数:{result.get('submitted_sheets') or ''}",
f"状态:{'已全平' if (result.get('fully_closed') or result.get('already_flat')) else '挂单中/部分'}",
]
)
)
except Exception:
pass
def run_options_profit_exits(
conn: sqlite3.Connection,
positions: list[dict[str, Any]],
*,
close_fn: Callable[[str], dict[str, Any]],
recycle_fn: Callable[[dict[str, Any], float | None], float | None] | None = None,
send_wechat: Callable[[str], None] | None = None,
account_label: str = "OKX期权",
cfg: dict[str, Any] | None = None,
ex: Any = None,
) -> int:
"""扫描开启翻倍出场的 open 仓;买一可回收达标后限价平仓.返回本次新触发条数."""
ensure_profit_exit_columns(conn)
pos_by_inst = {str(p.get("inst_id") or p.get("instId") or ""): p for p in positions}
hedge_managed: set[str] = set()
try:
from lib.hedge_plan.hedge_plan_db import active_hedge_option_inst_ids, init_hedge_plan_tables
init_hedge_plan_tables(conn)
hedge_managed = active_hedge_option_inst_ids(conn)
except Exception:
return 0
rules = profit_exit_by_inst(conn)
triggered = 0
for inst_id, info in list(rules.items()):
if not inst_id:
continue
if inst_id in hedge_managed:
_mark_state(conn, inst_id, "idle")
conn.execute(
"""
UPDATE options_trades
SET profit_exit_enabled = 0, profit_exit_state = 'idle'
WHERE inst_id = ? AND status = 'open'
""",
(inst_id,),
)
_commit(conn)
continue
pos = pos_by_inst.get(inst_id)
if not pos:
# 持仓已平:收尾
_mark_state(conn, inst_id, "done")
_commit(conn)
continue
state = str(info.get("profit_exit_state") or "active")
mult = normalize_profit_exit_mult(info.get("profit_exit_mult"), default=1.0)
prem = sum_open_premium_paid(conn, inst_id)
if prem is None or prem <= 0:
continue
if state == "closing":
result = close_fn(inst_id)
if result.get("already_flat") or _result_fully_done(result):
_mark_state(conn, inst_id, "done")
_commit(conn)
else:
_mark_state(conn, inst_id, "closing")
_commit(conn)
continue
if not info.get("profit_exit_enabled"):
continue
if recycle_fn is not None:
recycle = recycle_fn(pos, prem)
elif cfg is not None and ex is not None:
recycle = _estimate_recycle(cfg, ex, pos, prem)
else:
continue
if recycle is None:
continue
if not profit_exit_hit(premium_paid=prem, recycle_usdc=recycle, mult=mult):
continue
result = close_fn(inst_id)
if result.get("already_flat"):
_mark_state(conn, inst_id, "done")
_commit(conn)
continue
if not result.get("ok"):
_mark_state(conn, inst_id, "active")
_commit(conn)
continue
done = _result_fully_done(result)
_mark_state(conn, inst_id, "done" if done else "closing")
_commit(conn)
triggered += 1
_notify_profit_exit_close(
cfg,
send_wechat,
account_label=account_label,
inst_id=inst_id,
mult=mult,
premium_paid=prem,
recycle=recycle,
result=result,
conn=conn,
)
return triggered