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 -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())