User edition: prune docs, license gate, obfuscate core lib.

Keep deploy/basic docs only; integrate sq.bz121.com license client; encrypt strategy/trade/key_monitor/options/hedge_plan for release.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-17 16:30:58 +08:00
parent 53863559f4
commit 16ef0f31e3
154 changed files with 1998 additions and 27914 deletions
+12
View File
@@ -0,0 +1,12 @@
# obfuscated module — do not edit
import base64, marshal, zlib
_B = 'eNq7lppkmWqWkpJoYWmSbGqeaJJiYJRSYWCZKGZgYpASYJmW6m2hPT9FLAEoL2gClBc3NEqxOJqdmGpSzmWR6BUgLpjKGMwvyJ9SGygQ58znYnltyZW73c3tnzdf6L50cW6PJaO3jJsAt11oWBSXY4RwtoBzuFhKTACzZbSLC290uJulbRpPBK+AXbq4i9R7+9hIrhB3jlg+C6D57kYpJpaGqQAEmzEt'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12 -134
View File
@@ -1,134 +1,12 @@
"""期权模块 SQLite 表."""
from __future__ import annotations
import sqlite3
def init_options_tables(conn: sqlite3.Connection) -> None:
from lib.options.options_review_db import init_options_review_tables
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_trades (
id INTEGER PRIMARY KEY AUTOINCREMENT,
inst_id TEXT NOT NULL,
underlying TEXT NOT NULL,
opt_type TEXT NOT NULL,
strike REAL,
exp_time TEXT,
sheets INTEGER NOT NULL,
eth_amount REAL NOT NULL,
open_quote REAL,
premium_paid REAL,
status TEXT DEFAULT 'open',
close_quote REAL,
premium_received REAL,
realized_pnl REAL,
profit_alert_sent INTEGER DEFAULT 0,
signal_note TEXT,
exchange_ord_id TEXT,
close_ord_id TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
closed_at TIMESTAMP
)
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_convert_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_ccy TEXT,
to_ccy TEXT,
rfq_sz REAL,
received_sz REAL,
quote_id TEXT,
status TEXT,
message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_history_hidden (
history_key TEXT PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_transfer_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ccy TEXT,
amount REAL,
from_account TEXT,
to_account TEXT,
status TEXT,
message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_target_monitors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
inst_id TEXT NOT NULL,
underlying TEXT,
opt_type TEXT,
target_index REAL NOT NULL,
trade_id INTEGER,
sheets INTEGER,
status TEXT DEFAULT 'active',
trigger_idx REAL,
close_ord_id TEXT,
message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
triggered_at TIMESTAMP
)
"""
)
conn.execute(
"""
CREATE INDEX IF NOT EXISTS idx_options_target_monitors_status
ON options_target_monitors(status)
"""
)
init_options_review_tables(conn)
def sum_open_premium_paid(conn: sqlite3.Connection, inst_id: str) -> float | None:
"""同合约所有 open 腿权利金合计(加仓后显示/门控用)."""
inst = (inst_id or "").strip()
if not inst:
return None
row = conn.execute(
"""
SELECT SUM(premium_paid) AS total, COUNT(*) AS n
FROM options_trades
WHERE inst_id = ? AND status = 'open' AND premium_paid IS NOT NULL
""",
(inst,),
).fetchone()
if not row or int(row["n"] or 0) < 1:
return None
return round(float(row["total"] or 0), 4)
def sum_open_sheets(conn: sqlite3.Connection, inst_id: str) -> int | None:
"""同合约所有 open 腿张数合计."""
inst = (inst_id or "").strip()
if not inst:
return None
row = conn.execute(
"""
SELECT SUM(sheets) AS total, COUNT(*) AS n
FROM options_trades
WHERE inst_id = ? AND status = 'open'
""",
(inst,),
).fetchone()
if not row or int(row["n"] or 0) < 1:
return None
return int(row["total"] or 0)
# obfuscated module — do not edit
import base64, marshal, zlib
_B = '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'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
+12 -86
View File
@@ -1,86 +1,12 @@
"""期权历史列表(交易所 positions-history + 当前持仓)."""
from __future__ import annotations
from typing import Any
from lib.options.options_db import init_options_tables, sum_open_premium_paid
def enrich_position_row_display(
cfg: dict[str, Any],
ex: Any,
raw_pos: dict[str, Any],
*,
meta_cache: dict[str, dict[str, Any] | None] | None = None,
premium_override: float | None = None,
) -> dict[str, Any]:
from lib.exchange.okx_options_lib import format_position_row, format_usdc_amount, tick_sz_and_ct_mult
inst_id = str(raw_pos.get("instId") or "").strip()
tick_sz, ct_mult = tick_sz_and_ct_mult(ex, inst_id, meta_cache)
row = format_position_row(raw_pos, ct_mult=ct_mult, tick_sz=tick_sz)
if premium_override is not None:
row["premium_paid"] = premium_override
row["premium_paid_fmt"] = format_usdc_amount(premium_override)
return row
def load_options_history(ex: Any, cfg: dict[str, Any]) -> list[dict[str, Any]]:
"""与期权历史页相同的数据源:交易所全平记录 + 当前持仓,排除本地隐藏项."""
from lib.exchange.okx_options_lib import (
fetch_all_option_positions_history,
format_live_option_history_row,
format_option_history_row,
tick_sz_and_ct_mult,
)
meta_cache: dict[str, dict[str, Any] | None] = {}
items: list[dict[str, Any]] = []
raw_live = cfg["fetch_option_positions"](ex)
if raw_live is None:
return []
conn = cfg["get_db"]()
try:
init_options_tables(conn)
hidden_keys = {
str(r["history_key"])
for r in conn.execute("SELECT history_key FROM options_history_hidden").fetchall()
}
for p in raw_live:
inst = str(p.get("instId") or "").strip()
premium_override = sum_open_premium_paid(conn, inst) if inst else None
row = enrich_position_row_display(
cfg,
ex,
p,
meta_cache=meta_cache,
premium_override=premium_override,
)
open_ms = None
ctime = p.get("cTime") or (row.get("raw") or {}).get("cTime")
try:
if ctime is not None and str(ctime).strip():
open_ms = int(float(ctime))
except (TypeError, ValueError):
open_ms = None
items.append(format_live_option_history_row(row, open_ms=open_ms))
finally:
conn.close()
hist_raw = fetch_all_option_positions_history(ex, limit=200)
for raw in hist_raw:
inst_id = str(raw.get("instId") or "").strip()
tick_sz, ct_mult = tick_sz_and_ct_mult(ex, inst_id, meta_cache)
items.append(format_option_history_row(raw, tick_sz=tick_sz, ct_mult=ct_mult))
open_rows = [x for x in items if x.get("status") == "open"]
closed = [x for x in items if x.get("status") != "open"]
closed.sort(key=lambda x: int(x.get("close_ms") or 0), reverse=True)
open_rows.sort(key=lambda x: int(x.get("close_ms") or 0), reverse=True)
return [
x
for x in (open_rows + closed)
if str(x.get("history_key") or "") not in hidden_keys
]
# obfuscated module — do not edit
import base64, marshal, zlib
_B = 'eNplVnlwE/cVlsA6rMPWYa8kYhuDwcaAYVfalXZ1H6tbIhLW6l5ZK+1KtiX5SAgQN2ZiyNFO0xamTodMG6adaRh6JE0KUygOMUlg0lxNIC3TzIQyjSdASYP/yWQmk7bT36YkadK/37zf+973vve93xWuQnBmlmVwAq1iFgZlYCO7HybK0H4UZhPENCfGhaiUU1ir5go2Wk7Uiki7fdJR5hJYqpy1Z6u4S4o4q2WcRiTwaP1EPbVJMJza9kF1yp66NzGSnLnucLkEjBFXbYdk4P1Dv3764FMn31tdubR0cOH5Z65eeE298Nj5g5/+6tq5le5gR1BBFuRpX1e7Iugeg+KGPv+jD33yzkd/f/TUmbf/crzv7qscwKdGVibb8vkxFRUvyhSRmOWFen/a3cusbpLmFWRLHiITHfJggxSlqQR0Zjvka7d1hIp5u9/fllP4x2yhZEK07Unzralc0dPmyLdRCuHSlnBAnC115axjpDDiTYgEty2ja0ZftgnleXVLp9J2Ge5TxiaXzIKQu47DxFIlQAVtZDqg4wB/x3yiZN6NB3xavT9VkMQakW6dvmtWFme7Dd59sqyQ8JCGOUVht9YO8B9D01RHm7srRs6lC4IzLt2oSq1N5y2f88/BMIvDGMOhCMyicxhXjSjBfEQEbe7E8uVwmYYNTtraU8FQshyyPV4WWy9Yuu4T2c9iL5t+xIRbMUxPxJDe2mlLsxaGQ9N9dgOqn+pFaOS2OTIZYlJGNeGV9zIhV6oiJcLVPsd1i35a5BKh650s09s64OZAf6Ha7aG0PJBKqR1hmV+pkmYyDXXevTS4dszdniNrXVAiMy3Kpd1yaGWvVymR1SO77KmcqCEmW+3xYnypSud9qnl6lYvalhGJyknFsrpVuJsWF1o67ySm1iVzOXWu5m+T067BLqiNSng6JoMCWjidao8ZnEv3Z+ndkaQnrVybWTMakcvctYLWU+rIQ9MlZd47DPgJbGBRIrgP6PcfdV13ZMXSlvAtIRDpVoYMmLaJojDkIjBOhQF9QzWgb7UD8KvrN7K4dwufvxHkr1bkGchrU4oiO4H+/XsA/zfrkbFdf+ACw4D/N8bBfgwqcERDR4Ou5G6ZR97cK4wW4h3d45eP/+nSwR8c+viFhe9dXri4wGXUsWB3wUrTsiSZ1lo9QQoap4oSZ7rbq5R3Rrkv8OeVURDvaWRz4nXh2PL9RTmplLTXwwVV0SctyKfd8runaSlEZpO9DoBfPQzwz0zssG2q2MwTWAtxcnYkeoBD0ddmsomAmqAVlMqliUX1vj05/QQ+yuuHAP2B/UV3YFzFBPLrDI4Hl1ALk2BTsBSn8Z56zhyv56whptOhN3GsFCeNbVikFmJTyDweI95nw/ZYM+Fwc6rNa8wWtpegkTnsFBNuRtwqRwx+n8XQVCXhfI7rgy/CguZ54gnTS9i9zGn290Y9vIew28NOshkyPd4Uw0CfOZER6NMF9DkVI6AZ0tjbDLlO1vJ4klmPqa16rhf3O9eboq3E1DmLAUs5ehoh5+FWAltkNXmD7UJLhB3FtFSJWWymnBeJuPmufSFbquzBgyoIMRA3uBeNR1G129d0N2KOG8RJvKdRRUsTMOGZhIbXor03yjiOAv/iwo0Jo4wIEr2Vxc2xeggNN8SWm+hFroyHBCgWHV/cd8TI4SV7b3Nx8AiLEV6uz2YwCxqiLafMfZujszNca8QwTCK9exYdqdbjuP8tsem65cLUeZwcXA9HmdC+GHrD1rT17A33x6qLW8JM3xbDZv2MaPiU5SXUzPgvpPoF206B+jPoSS68PTkr3vo3s766GT+L9Q1E95X2poYEO04RonsS28/VMCI8I90q3Cm45wOcHpCMnK1i3GnjGiI1INr7rD02G9q5kRskrlssbGgglgL+8fBjH1555rO/Pn32oQ9/e2n5ytWjDx78dOHB4zcPffb8tZ+9887Zty5f+6n0wYOL1z795PK5ZxeeO/L2e5cuvf7Iwrsf6zVdj3z75w+du3nlhQ/e++4v44fOHn/3X4ev/eLVR5/687V/f/yd3/zu6kuPGbQY8K+4/dZdEoE7k7VRVM6qDKvH5DJnKB2UfU3ft7fe8Q9K4RYC/43TisJ4Ow2RNOSZjnZobo/cif93P+pJWuXJauNWL62NbAT6ndqYFrrz9P+9f2C8MkbFncJ0BgH7i925X1Ww3zXgn3UONun6gf6VaAgm7CSqrvjZ3ew8s4QfZ0jMiMaQm9OJXKRbFohPdybdPaxqA8iPV9+3rRtdNWsD+8F9iYyA+oGtYH9W7Dl1UU3J29ViCPgDOQH8AWWtRtOx6uu7dNJCNnkHn+YL/FFFSV5IUprCOqkP4M9LNZL1esuGkfm5so8uadwyb9Q2Srk9A7VvzZOFsEehzEATCqUmlg6smxyTJoRUEdoG8Ov6wf4GXKC/apmClQp6xaJOuUPYsoXuoks1BXT1nwCfCl2uU4qCUhWyRjeC+Ws3gPlIiC/3n8DqgB8TuO8NAq0EhsF9yRj1eMvYw4aww+C+L7JivAOFar3ECbPGwjJP1m+hulgy3gXdhse6kpmfsKp+wM+b1UgP6P+jckw3wvvnJt4/d/D+OV2iFH6VpIMKy0YA/6/UAX+DsC/cXqBStm/0d5dcWRAnSzpxn0gJ7vuoPBeWi6v/8//AAD4MvF8lGDbI81/CDBV2Ksrpy0eJRHXRLMI6TO4aiZyodpnrMKTn/R3i/T3J+/sW3t8/xzfE41sD8M0BfehE4P0Cjt65ryCOAH5gwE+dwNgj20C+iiGJxUrSfB7rtqRrS8gk84ZlbrKZofKQpCb1QxzA9ySzikrzau824K+HyzoRP59NYD7R/QC/dhDgX3XR2TY6Ich1TID6rxhB/SFOv749JxI4M2G3LJ/TNJQ5jyrjdd+dyxZGRdmIPFqi2gpigUDcAv8XVJbsuYVBdKCohZJf03cZ9Fe/o29IwetDyNfv5OvTfP3NoH5wK+jPux3gi2bB/Bn+Po6BfIr76n/HGUH/ZtD/EApzwfUgv8RFWGLKj7urYfOLmBD+IRey0eXr2POwBotaDNUT9WDNwqhtgSDID8zy/y9j9H7wvnce4NPtBvje5GymJC1J7u5sdFBR3Vf7E5UA/BaAT4OC+iZL1TyLwE9YEdexWH9giMdHAnxkAOCL6nj9DoL5QHbQn8uUDAkzMY8AR9aOqiTtOSqrHuHnO8zPd4CfL8bnz/H5oyB/BRHlsgL3MlHrDVEPaHWFwK2pSjankjygI+XQkjNLahM50e3yaEkepVY4qBAg4wNRJ68PF9BHRM/ffx/QhzsI7qd2H/9/MAH+z9gVXmG7poIE1njHlbSWGi8XxjKdyxh5q0xnAmoVwutvntdfD8C3jCVVSbpoaxVpn6oRTWlUaj4+x8cHePybAf5JuKM7mZ+GCrsCy2y+PacTiiKb+PpFvv4c/7+czRaSgkbar5an5X6bQhqNF9SxbWB+vikw3w1VxgQj42YEZaqmOso1y04cZ0yvTsD3Iv2cFbGV65gZqWE2tPqEo7KFq5W343gFOT0L/g/wj7cbbd+fHcKNTWwnel91J4vXJstb8cGKy1w3V//oHuH/fxMBBz8/F8+Pg+fHyuNr8fimeH4qPD8Ev3+f9z/N92fl+7OC/gwu7RQft/BxmI83voyr+LhpqdJopOSZ2uTyeM2aUKu6IsWGPcjv/y1U5XEnOtcELKB+daBdXojmuvj/ZzCeD3bTspLHVtwd5Xj9Mbz+5oH+oBGg78ABXp/f8G9HrjMt9EmFYwTQX2AIvI8axwm7bbZuths568u2vf8BeS3U0w=='
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
+12 -104
View File
@@ -1,104 +1,12 @@
"""中控只读聚合:OKX 期权持仓 / 资金 / 本地统计."""
from __future__ import annotations
from typing import Any
from lib.options.options_history_lib import load_options_history
from lib.options.options_stats_lib import compute_options_stats_from_history
def _compute_options_stats(ex, cfg) -> dict[str, Any]:
history = load_options_history(ex, cfg)
return compute_options_stats_from_history(history)
def build_options_hub_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
if not cfg.get("enabled"):
return {"ok": True, "enabled": False}
ex = cfg.get("exchange_options")
ready_fn = cfg.get("options_api_ready")
if not callable(ready_fn):
return {"ok": False, "enabled": True, "msg": "期权模块未就绪"}
ok, reason = ready_fn(ex)
if not ok:
return {"ok": False, "enabled": True, "msg": reason or "期权 API 未配置"}
try:
from lib.options.options_positions_lib import build_display_option_positions
raw = cfg["fetch_option_positions"](ex)
if raw is None:
return {"ok": False, "enabled": True, "msg": "获取期权持仓失败"}
positions = build_display_option_positions(cfg, ex, raw)
target_monitors: list[dict[str, Any]] = []
try:
conn = cfg["get_db"]()
try:
from lib.hedge_plan.hedge_plan_db import active_options_targets_by_inst
from lib.options.options_target_lib import list_active_targets, list_closing_targets, targets_by_inst
target_monitors = list_active_targets(conn) + list_closing_targets(conn)
tgt_map = targets_by_inst(conn)
hedge_target_map = active_options_targets_by_inst(conn)
target_monitors.extend(hedge_target_map.values())
for p in positions:
mon = tgt_map.get(str(p.get("inst_id") or ""))
if mon:
p["target_index"] = mon.get("target_index")
p["target_monitor_id"] = mon.get("id")
p["target_monitor"] = mon
hedge_target = hedge_target_map.get(str(p.get("inst_id") or ""))
if hedge_target:
p["hedge_plan_target"] = hedge_target
if not mon:
# 中控卡片共用 target_index 只读展示;实际平仓仍由对冲计划监控处理。
p["target_index"] = hedge_target.get("target_index")
try:
from lib.instance.instance_dashboard_lib import (
_format_options_target,
_resolve_options_source,
)
inst = str(p.get("inst_id") or "")
source_key, source_label = _resolve_options_source(conn, inst)
p["source"] = source_key
p["source_label"] = source_label
p["target_monitor_text"] = _format_options_target(p)
except Exception:
p.setdefault("source_label", "")
p.setdefault("target_monitor_text", "")
finally:
conn.close()
except Exception:
target_monitors = []
from lib.options.options_positions_lib import net_pnl_from_display_row
upl_total = 0.0
has_upl = False
for p in positions:
# 与持仓卡「净盈亏」一致(买一回收−权利金);不用交易所标记价 upl
net = net_pnl_from_display_row(p)
if net is None:
continue
has_upl = True
upl_total += float(net)
bal = cfg["fetch_options_balances"](ex)
stats = _compute_options_stats(ex, cfg)
return {
"ok": True,
"enabled": True,
"positions": positions,
"position_count": len(positions),
"target_monitors": target_monitors,
"upl_total_usdc": round(upl_total, 4) if has_upl else None,
"balances": bal,
"funding_usdc": bal.get("funding_usdc"),
"funding_usdt": bal.get("funding_usdt"),
"trading_usdc": bal.get("trading_usdc"),
"trading_usdt": bal.get("trading_usdt"),
"stats": stats,
"trade_budget": cfg.get("trade_budget"),
"account_label": cfg.get("account_label") or "OKX期权",
}
except Exception as e:
return {"ok": False, "enabled": True, "msg": str(e)}
# obfuscated module — do not edit
import base64, marshal, zlib
_B = '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'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
File diff suppressed because one or more lines are too long
+12 -124
View File
@@ -1,124 +1,12 @@
"""期权限价挂单:展示 enrichment + 超时自动撤单."""
from __future__ import annotations
import time
from typing import Any
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 order_age_seconds(order: dict[str, Any], *, now_ms: float | None = None) -> float | None:
"""根据交易所 cTime(ms) 估算挂单时长(秒)."""
ct = _safe_float(order.get("c_time") or order.get("cTime"))
if ct is None or ct <= 0:
return None
# OKX 一般为毫秒时间戳
if ct < 1e12:
ct *= 1000.0
now = float(now_ms if now_ms is not None else time.time() * 1000.0)
age = (now - ct) / 1000.0
return age if age >= 0 else 0.0
def is_close_pending_order(order: dict[str, Any]) -> bool:
"""平仓向限价挂单:卖出 / reduceOnly."""
side = str(order.get("side") or "").lower()
if side == "sell":
return True
return bool(order.get("reduce_only"))
def enrich_pending_orders(
orders: list[dict[str, Any]] | None,
*,
ttl_seconds: float = 600.0,
now_ms: float | None = None,
) -> list[dict[str, Any]]:
"""为 UI 附加挂单时长与自动撤倒计时."""
ttl = max(0.0, float(ttl_seconds or 0))
now = float(now_ms if now_ms is not None else time.time() * 1000.0)
out: list[dict[str, Any]] = []
for raw in orders or []:
o = dict(raw)
age = order_age_seconds(o, now_ms=now)
is_close = is_close_pending_order(o)
o["age_sec"] = round(age, 1) if age is not None else None
o["is_close_order"] = is_close
o["auto_cancel_enabled"] = bool(is_close and ttl > 0)
if age is not None and is_close and ttl > 0:
remain = max(0.0, ttl - age)
o["ttl_seconds"] = ttl
o["expire_in_sec"] = round(remain, 1)
o["stale"] = remain <= 0
else:
o["ttl_seconds"] = ttl if is_close else None
o["expire_in_sec"] = None
o["stale"] = False
out.append(o)
return out
def cancel_stale_close_pending_orders(
*,
fetch_pending: Any,
cancel_order: Any,
ttl_seconds: float = 600.0,
now_ms: float | None = None,
ex: Any = None,
) -> dict[str, Any]:
"""
平仓限价挂单超过 ttl 自动撤销.
fetch_pending(ex) -> list; cancel_order(ex, inst_id=..., ord_id=...).
"""
ttl = float(ttl_seconds or 0)
if ttl <= 0:
return {"ok": True, "cancelled": 0, "checked": 0, "skipped": "ttl_disabled"}
try:
orders = fetch_pending(ex) if ex is not None else fetch_pending()
except TypeError:
orders = fetch_pending(ex)
except Exception as e:
return {"ok": False, "msg": str(e), "cancelled": 0, "checked": 0}
enriched = enrich_pending_orders(orders or [], ttl_seconds=ttl, now_ms=now_ms)
cancelled: list[dict[str, Any]] = []
errors: list[str] = []
checked = 0
for o in enriched:
if not o.get("is_close_order"):
continue
checked += 1
if not o.get("stale"):
continue
inst = str(o.get("inst_id") or "").strip()
oid = str(o.get("ord_id") or "").strip()
if not inst or not oid:
continue
try:
if ex is not None:
res = cancel_order(ex, inst_id=inst, ord_id=oid)
else:
res = cancel_order(inst_id=inst, ord_id=oid)
except TypeError:
res = cancel_order(ex, inst_id=inst, ord_id=oid)
except Exception as e:
errors.append(f"{oid}:{e}")
continue
if res.get("ok"):
cancelled.append({"inst_id": inst, "ord_id": oid, "age_sec": o.get("age_sec")})
else:
errors.append(f"{oid}:{res.get('msg') or 'cancel_failed'}")
return {
"ok": True,
"cancelled": len(cancelled),
"checked": checked,
"orders": cancelled,
"errors": errors,
"ttl_seconds": ttl,
}
# obfuscated module — do not edit
import base64, marshal, zlib
_B = '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'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12 -143
View File
@@ -1,143 +1,12 @@
"""期权复盘(含对冲) SQLite 表."""
from __future__ import annotations
import sqlite3
SOURCE_OPTION = "option_spot"
SOURCE_PERP_OPTIONS = "perp_options"
SOURCE_OPTIONS_OPTIONS = "options_options"
SOURCE_TYPES = (SOURCE_OPTION, SOURCE_PERP_OPTIONS, SOURCE_OPTIONS_OPTIONS)
def init_options_review_tables(conn: sqlite3.Connection) -> None:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_review_trades (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_type TEXT NOT NULL,
history_key TEXT NOT NULL UNIQUE,
underlying TEXT,
opened_at TEXT,
closed_at TEXT,
hold_seconds INTEGER,
realized_pnl_total REAL,
status_raw TEXT,
synced_at TEXT,
-- 纯期权
pos_id TEXT,
inst_id TEXT,
opt_type TEXT,
strike REAL,
exp_time TEXT,
sheets INTEGER,
open_avg REAL,
close_avg REAL,
premium_paid REAL,
realized_pnl REAL,
-- 对冲计划
hedge_plan_id INTEGER,
plan_close_reason TEXT,
realized_pnl_perp REAL,
realized_pnl_options REAL,
premium_total REAL,
direction TEXT,
tp REAL,
sl REAL,
target_price REAL,
target_price_up REAL,
target_price_down REAL,
legs_json TEXT,
-- 双计防护:纯期权腿已归属对冲计划
linked_hedge_plan_id INTEGER,
excluded_as_hedge_leg INTEGER DEFAULT 0
)
"""
)
conn.execute(
"""
CREATE UNIQUE INDEX IF NOT EXISTS idx_options_review_trades_history_key
ON options_review_trades(history_key)
"""
)
conn.execute(
"""
CREATE UNIQUE INDEX IF NOT EXISTS idx_options_review_trades_hedge_plan
ON options_review_trades(hedge_plan_id)
WHERE hedge_plan_id IS NOT NULL
"""
)
conn.execute(
"""
CREATE INDEX IF NOT EXISTS idx_options_review_trades_closed
ON options_review_trades(closed_at)
"""
)
conn.execute(
"""
CREATE INDEX IF NOT EXISTS idx_options_review_trades_source
ON options_review_trades(source_type)
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_review_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
trade_id INTEGER NOT NULL UNIQUE,
strategy_tag TEXT,
direction_view TEXT,
entry_logic TEXT,
exit_reason TEXT,
followed_plan TEXT,
mistake_tags TEXT,
result_tag TEXT,
note TEXT,
images_json TEXT,
image TEXT,
reviewed_at TEXT,
updated_at TEXT,
FOREIGN KEY(trade_id) REFERENCES options_review_trades(id)
)
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_review_sync_state (
key TEXT PRIMARY KEY,
value TEXT,
updated_at TEXT
)
"""
)
conn.execute(
"""
CREATE TABLE IF NOT EXISTS options_review_hidden (
history_key TEXT PRIMARY KEY,
inst_id TEXT,
closed_at TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""
)
conn.execute(
"""
CREATE INDEX IF NOT EXISTS idx_options_review_hidden_inst
ON options_review_hidden(inst_id, closed_at)
"""
)
_ensure_column(conn, "options_review_trades", "linked_hedge_plan_id", "INTEGER")
_ensure_column(conn, "options_review_trades", "excluded_as_hedge_leg", "INTEGER DEFAULT 0")
_ensure_column(conn, "options_review_trades", "target_price_up", "REAL")
_ensure_column(conn, "options_review_trades", "target_price_down", "REAL")
def _ensure_column(conn: sqlite3.Connection, table: str, col: str, typedef: str) -> None:
rows = conn.execute(f"PRAGMA table_info({table})").fetchall()
names: set[str] = set()
for r in rows:
try:
names.add(str(r["name"]))
except (TypeError, KeyError, IndexError):
names.add(str(r[1]))
if col not in names:
conn.execute(f"ALTER TABLE {table} ADD COLUMN {col} {typedef}")
# obfuscated module — do not edit
import base64, marshal, zlib
_B = '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'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12 -173
View File
@@ -1,173 +1,12 @@
"""期权本地交易统计(胜率 / 盈亏 / 持仓时长)."""
from __future__ import annotations
from datetime import datetime
from typing import Any
from lib.instance.instance_embed_context_lib import profit_loss_ratio_from_averages
from lib.options.options_db import init_options_tables
def _parse_ts(raw: Any) -> datetime | None:
if raw is None or raw == "":
return None
s = str(raw).strip().replace(" ", "T", 1)
try:
return datetime.fromisoformat(s)
except (TypeError, ValueError):
return None
def _hold_seconds(created_at: Any, closed_at: Any) -> float | None:
start = _parse_ts(created_at)
end = _parse_ts(closed_at)
if start is None or end is None:
return None
sec = (end - start).total_seconds()
return sec if sec >= 0 else None
def _avg_seconds(values: list[float]) -> float | None:
if not values:
return None
return round(sum(values) / len(values), 1)
def compute_options_stats_from_history(history: list[dict[str, Any]]) -> dict[str, Any]:
"""基于期权历史列表(交易所)计算统计."""
wins: list[float] = []
losses: list[float] = []
win_holds: list[float] = []
loss_holds: list[float] = []
all_holds: list[float] = []
open_holds: list[float] = []
now = datetime.now()
for row in history:
if row.get("status") == "open":
start = _parse_ts(row.get("created_at"))
if start is not None:
sec = (now - start).total_seconds()
if sec >= 0:
open_holds.append(sec)
continue
pnl_raw = row.get("realized_pnl")
if pnl_raw is None:
continue
try:
pnl = float(pnl_raw)
except (TypeError, ValueError):
continue
hold = _hold_seconds(row.get("created_at"), row.get("closed_at"))
if hold is not None:
all_holds.append(hold)
if pnl > 0:
wins.append(pnl)
if hold is not None:
win_holds.append(hold)
elif pnl < 0:
losses.append(pnl)
if hold is not None:
loss_holds.append(hold)
total_closed = len(wins) + len(losses)
win_rate = round(len(wins) / total_closed * 100, 2) if total_closed else 0
avg_win = sum(wins) / len(wins) if wins else None
avg_loss = sum(losses) / len(losses) if losses else None
total_profit = round(sum(wins), 4) if wins else 0.0
total_loss = round(abs(sum(losses)), 4) if losses else 0.0
net_realized = round(sum(wins) + sum(losses), 4)
return {
"total_closed": total_closed,
"win_count": len(wins),
"loss_count": len(losses),
"win_rate": win_rate,
"profit_loss_ratio": profit_loss_ratio_from_averages(avg_win, avg_loss),
"avg_win": round(avg_win, 4) if avg_win is not None else None,
"avg_loss": round(abs(avg_loss), 4) if avg_loss is not None else None,
"total_profit": total_profit,
"total_loss": total_loss,
"net_realized_pnl": net_realized,
"avg_hold_sec": _avg_seconds(all_holds),
"avg_win_hold_sec": _avg_seconds(win_holds),
"avg_loss_hold_sec": _avg_seconds(loss_holds),
"open_count": len(open_holds),
"avg_open_hold_sec": _avg_seconds(open_holds),
}
def compute_options_stats(get_db) -> dict[str, Any]:
conn = get_db()
try:
init_options_tables(conn)
closed_rows = conn.execute(
"""
SELECT realized_pnl, created_at, closed_at
FROM options_trades
WHERE status = 'closed' AND realized_pnl IS NOT NULL
"""
).fetchall()
open_rows = conn.execute(
"""
SELECT created_at FROM options_trades WHERE status = 'open'
"""
).fetchall()
finally:
conn.close()
wins: list[float] = []
losses: list[float] = []
win_holds: list[float] = []
loss_holds: list[float] = []
all_holds: list[float] = []
now = datetime.now()
for row in closed_rows:
pnl = float(row["realized_pnl"])
hold = _hold_seconds(row["created_at"], row["closed_at"])
if hold is not None:
all_holds.append(hold)
if pnl > 0:
wins.append(pnl)
if hold is not None:
win_holds.append(hold)
elif pnl < 0:
losses.append(pnl)
if hold is not None:
loss_holds.append(hold)
open_holds: list[float] = []
for row in open_rows:
start = _parse_ts(row["created_at"])
if start is None:
continue
sec = (now - start).total_seconds()
if sec >= 0:
open_holds.append(sec)
total_closed = len(wins) + len(losses)
win_rate = round(len(wins) / total_closed * 100, 2) if total_closed else 0
avg_win = sum(wins) / len(wins) if wins else None
avg_loss = sum(losses) / len(losses) if losses else None
total_profit = round(sum(wins), 4) if wins else 0.0
total_loss = round(abs(sum(losses)), 4) if losses else 0.0
net_realized = round(sum(wins) + sum(losses), 4)
return {
"total_closed": total_closed,
"win_count": len(wins),
"loss_count": len(losses),
"win_rate": win_rate,
"profit_loss_ratio": profit_loss_ratio_from_averages(avg_win, avg_loss),
"avg_win": round(avg_win, 4) if avg_win is not None else None,
"avg_loss": round(abs(avg_loss), 4) if avg_loss is not None else None,
"total_profit": total_profit,
"total_loss": total_loss,
"net_realized_pnl": net_realized,
"avg_hold_sec": _avg_seconds(all_holds),
"avg_win_hold_sec": _avg_seconds(win_holds),
"avg_loss_hold_sec": _avg_seconds(loss_holds),
"open_count": len(open_holds),
"avg_open_hold_sec": _avg_seconds(open_holds),
}
# obfuscated module — do not edit
import base64, marshal, zlib
_B = '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'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
File diff suppressed because one or more lines are too long