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 -1
View File
@@ -1 +1,12 @@
"""Shared library package."""
# obfuscated module — do not edit
import base64, marshal, zlib
_B = 'eNq7lppkmWqWkpJoYWmSbGqeaJJiYJRSYWCZKGZjYpASYJmfymZhZuabJq2fFcPkHsQqwhkZIBbp5OAoGhoezRocI63tbbHf0NqaNTwq22aOubBZqoGRmBVQ/4R0oP5y5kwDwRhxVwsGlmCfKPvCIKF4wXgni3vLOlvu9V1a9vhCU3PDsQbLUDFZQZ+wbL6oSA4HLqdcQe9Qb1vO2LAccQfvEEH2OJksa2uOsAAHmyxJQd/9Fnyc4SE+vAxcpgYpJl7mQPfnmgEAMws4tw=='
_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 -48
View File
@@ -1,48 +1,12 @@
"""交易所策略适配器接口(各所 app 注入 ccxt 实现)."""
from __future__ import annotations
from typing import Any, Optional, Protocol
class StrategyExchangeAdapter(Protocol):
exchange_key: str
def normalize_symbol(self, raw: str) -> str: ...
def normalize_exchange_symbol(self, symbol: str) -> str: ...
def get_mark_price(self, symbol: str) -> Optional[float]: ...
def get_position(self, exchange_symbol: str, direction: str) -> dict[str, Any]:
"""返回 {contracts, entry_price, leverage?}."""
...
def amount_to_precision(self, exchange_symbol: str, amount: float) -> Optional[float]: ...
def price_to_precision(self, exchange_symbol: str, price: float) -> Optional[float]: ...
def market_add(
self, exchange_symbol: str, direction: str, amount: float, leverage: int
) -> dict[str, Any]: ...
def limit_add(
self,
exchange_symbol: str,
direction: str,
amount: float,
price: float,
leverage: int,
) -> dict[str, Any]: ...
def cancel_order(self, exchange_symbol: str, order_id: str) -> None: ...
def replace_position_tpsl(
self,
exchange_symbol: str,
direction: str,
stop_loss: float,
take_profit: float,
order_monitor_row: Any = None,
) -> None: ...
def ensure_live_ready(self) -> tuple[bool, str]: ...
# 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 -4
View File
@@ -1,4 +1,12 @@
"""Binance USDT-M 永续 — 策略交易交易所适配(见 strategy_config.build_strategy_config)."""
from lib.strategy.strategy_exchange_base import StrategyExchangeAdapter
__all__ = ["StrategyExchangeAdapter"]
# obfuscated module — do not edit
import base64, marshal, zlib
_B = 'eNq7lppkmWqWkpJoYWmSbGqeaJJuYJRSYWCZKKZnYpASYJmfymbBaMKRymOVbJZkGpwUnJJlyGCTbOmR4BhqkGJSHcPNHM4W7GJoVJMrqSd68WJPU/d8l1sb1jo2zVx0/ePEu/0HWxZ+u3TvQPuCVZdebb33cFvP1v0+F6bvcBRzcAsUCIzzsWIM4w7gZfaP8onlD8wQchAPdGOM90xj4ooKimSWFb8Dcp986r6ybF83JscgNl8Fj0CeyLg4FpWAUAeBQG/3dKB8hW/Cbvu0bPaoELtse+E43ig5L1HxUIdQZl9xN0GBCMFgZlm7WMdw7pBItkCroGDxWCfrxFSTd+bW2RGRnLZpZ8zEEoH+d0sD+v8gI9D/1VypJmKBbs4WATFxAjypisHi/KLhnhmti9tb+u9fW/L4Tnd/y8HejGAxX09HLtsQrgheAT6vNEeBAFfbCJ6gPHEfpwhRhmDfHHEH7xBB9jiZTEYZpoCIKJaADOZY7sAY9lg5T4n9Nhyx3AE+kQwxFsDwF08Hhn+6qXWCXToAe7J/fw=='
_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 -9
View File
@@ -1,9 +1,12 @@
"""
Gate.io USDT 永续 — 策略交易交易所侧能力.
实现方式:各 Gate 实例 app 通过 strategy_config.build_strategy_config(app_module) 注入
ccxt 下单,精度,换 TP/SL;本文件为文档与类型锚点,避免在各 app 重复实现滚仓公式.
"""
from lib.strategy.strategy_exchange_base import StrategyExchangeAdapter
__all__ = ["StrategyExchangeAdapter"]
# obfuscated module — do not edit
import base64, marshal, zlib
_B = 'eNoBMwLM/dZlYjllNmRkYTg5NGM1N2E0ZzAyZHgwOWEWLjQwZFA5b2UGOAE0CGUMOmM2YjVTYlNkajEAPGM5SGBBMTFkNDNyBBZcS18LRDRrfWBD04fZ096dEoa4pBmGyKLTpcHQg5GD+qqBjMCC+avfvePRicbc5o3X7qMeM2uAmqrX6oTfo9yHheoMgfTlGH5VF1AXhJr71IzvGFFJEUXdtKqMi74VFhZLBEIBAxhnWlsNU14GGgdFWwhcb0oVF1VAVQNNZlYKDF8MUUwFEUhmWQxRQg1RTBDU15DVvMRvV1dIEBTdje6HtPAag9bf3YOST9O6wxQxYB03dAvf/cnSoreAj4/R3djf87GCxcLcgbqEhIyEqu7Zpv7fsoBNjLWL1eG53KnNh6nhFgUUERjQs+7Qk+zRy67V6ojWgvuBj6fV4ZjciepMM4w2ZGRhETjudGZDE1URVVUdfUhaCQRaU1UlUFhFEQdLFzRkZGF2EDC5Mmg+UApTbTtiE1UIBxpHRBZVTVACGxcWQhYFFV1eTTxQTwJcBF5VAWdSWBIARjYwZDTjMjo9WAlaOzvIOEsxYzU3EzFlMDKRWTA5YSYOaGUXUUtGOQZcDkMKOCVdSl8XWkc90vOA19+C1q/mgY+C1cCNZVYXG0kRWTsJDlZQQAxHaBRHAEJuCFFSZRIRRlVEAVNAaRYWSwRCAQMYZ1xMAF1WD1MAb1UFTFUXERzuPAwJW11ACQcHZDZkZBI+OTRjMTdtMm8yiafaEQ=='
_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 -4
View File
@@ -1,4 +1,12 @@
"""OKX 永续 — 策略交易交易所适配(见 strategy_config.build_strategy_config)."""
from lib.strategy.strategy_exchange_base import StrategyExchangeAdapter
__all__ = ["StrategyExchangeAdapter"]
# obfuscated module — do not edit
import base64, marshal, zlib
_B = 'eNq7lppkmWqWkpJoYWmSbGqeaJJuYJRSYWCZKKZnYpASYJmfymbBaMKRymOVbJZkGpwUnJJlyGCTbOmR4JhkkGJSVmfrdP9qX/P9MxK3t3wXvXBmUdPS6Q1NU+7//Hahb0rTmlWX3zy68HRL78M3AhcnPxJ1EXVjcQlndsyP4uMOig33CvOJ4WRLE3MSYxWNjfO1CYvgD+IJl/a6CLQ/Vdb0VXG6rzuLWAyTv6IMU0BEFEtAGWuoqEu4mJcRUL5K1mSfcVoWI2+UVaadO29gtJSAoyuDA0O4t5e3izeDYGCwp3WgYxgvczhTsDUbg3eMWyLQf++S8nNZQ2LS7M6YuJmmmFi6JwD9f5gF6P8qPvsEFxY3sZwwxmDXcFvFQPdogWjPzOYvnQ2995q+77/bf/XC5BsZbE7eIj7xaTxcUbEu3G5pDmLs3pZRvGy23r5uTI5BbL6Wzm5ikS4xbDLZgR7sMRHRTOxpXLEyXoKOj23iIyJYHTlDeVItDCyFkk1MDBJMTE3yEwBT5X1P'
_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 -72
View File
@@ -1,72 +1,12 @@
"""策略交易记录页:已结束趋势 / 顺势加仓快照(三所统一)."""
from __future__ import annotations
import json
from typing import Any
from flask import flash, redirect, url_for
from lib.strategy.strategy_snapshot_lib import (
STRATEGY_SNAPSHOTS_MAX_ROWS,
dedupe_strategy_snapshots,
list_strategy_snapshots_split,
)
def load_strategy_records_page(
conn, *, limit: int = STRATEGY_SNAPSHOTS_MAX_ROWS
) -> dict[str, Any]:
try:
if dedupe_strategy_snapshots(conn):
conn.commit()
except Exception:
pass
trend, roll, symbols = list_strategy_snapshots_split(conn, limit=limit)
return {
"strategy_trend_records": trend,
"strategy_roll_records": roll,
"strategy_record_symbols": symbols,
"strategy_records_limit": limit,
"strategy_snapshots": trend + roll,
}
def register_strategy_records(app, cfg: dict[str, Any]) -> None:
login_required = cfg["login_required"]
get_db = cfg["get_db"]
def _lr(f):
return login_required(f)
@_lr
@app.route("/strategy/records")
def strategy_records_page():
m = cfg.get("app_module")
fn = getattr(m, "render_main_page", None)
if not callable(fn):
flash("render_main_page 未配置")
return redirect(url_for("strategy_trading_page"))
return fn("strategy_records")
@_lr
@app.route("/strategy/records/<int:snap_id>")
def strategy_records_detail(snap_id: int):
conn = get_db()
row = conn.execute(
"SELECT * FROM strategy_trade_snapshots WHERE id=?",
(int(snap_id),),
).fetchone()
conn.close()
if not row:
flash("未找到该策略快照")
return redirect(url_for("strategy_records_page"))
try:
snap = json.loads(row["snapshot_json"] or "{}")
except Exception:
snap = {}
dca = snap.get("dca_levels") or []
flash(
f"快照 #{snap_id} {row['strategy_type']} {row['symbol']} "
f"{row['result_label']} · 补仓档 {len(dca)} 项(详情见列表页)"
)
return redirect(url_for("strategy_records_page"))
# 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
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 -97
View File
@@ -1,97 +1,12 @@
"""趋势回调:各交易所止损刷新,市价加/平仓(通过 app 模块能力探测)."""
from __future__ import annotations
import time
from typing import Any
def _m(cfg: dict) -> Any:
return cfg["app_module"]
def trend_refresh_stop_only(cfg: dict, exchange_symbol: str, direction: str, stop_loss: float) -> None:
m = _m(cfg)
if hasattr(m, "_gate_place_stop_loss_only_position"):
if hasattr(m, "cancel_gate_swap_trigger_orders"):
m.cancel_gate_swap_trigger_orders(exchange_symbol)
m._gate_place_stop_loss_only_position(exchange_symbol, direction, stop_loss)
return
if hasattr(m, "_binance_place_stop_loss_only"):
m._binance_place_stop_loss_only(exchange_symbol, direction, stop_loss)
return
if hasattr(m, "_okx_place_stop_loss_only"):
m._okx_place_stop_loss_only(exchange_symbol, direction, stop_loss)
return
raise RuntimeError("当前实例未配置趋势回调止损挂单能力")
def trend_market_add(cfg: dict, exchange_symbol: str, direction: str, contracts: float, leverage: int):
m = _m(cfg)
ex = m.exchange
m.ensure_markets_loaded()
ex.set_leverage(int(leverage), exchange_symbol)
side = "buy" if direction == "long" else "sell"
if hasattr(m, "build_gate_order_params"):
params = m.build_gate_order_params(direction, reduce_only=False)
elif hasattr(m, "build_binance_order_params"):
params = m.build_binance_order_params(direction, reduce_only=False)
elif hasattr(m, "build_okx_order_params"):
params = m.build_okx_order_params(direction, reduce_only=False)
else:
params = {}
order_params = params if params is not None else {}
return ex.create_order(exchange_symbol, "market", side, float(contracts), None, order_params)
def trend_market_close(cfg: dict, exchange_symbol: str, direction: str, pos_qty: float, leverage: int):
m = _m(cfg)
ex = m.exchange
m.ensure_markets_loaded()
ex.set_leverage(int(leverage), exchange_symbol)
side = "sell" if direction == "long" else "buy"
amt = float(ex.amount_to_precision(exchange_symbol, float(pos_qty)))
if hasattr(m, "close_exchange_order"):
row = {
"exchange_symbol": exchange_symbol,
"symbol": exchange_symbol,
"direction": direction,
"order_amount": amt,
}
return m.close_exchange_order(row)
if hasattr(m, "build_gate_order_params"):
params = m.build_gate_order_params(direction, reduce_only=True)
return ex.create_order(exchange_symbol, "market", side, amt, None, params)
if hasattr(m, "build_binance_order_params"):
for params in m._binance_market_close_param_candidates(direction):
try:
return ex.create_order(exchange_symbol, "market", side, amt, None, params)
except Exception as e:
if not m._is_binance_close_param_retryable(str(e)):
raise
raise RuntimeError("平仓失败")
if hasattr(m, "build_okx_order_params"):
params = m.build_okx_order_params(direction, reduce_only=True)
return ex.create_order(exchange_symbol, "market", side, amt, None, params)
return ex.create_order(exchange_symbol, "market", side, amt, None, {"reduceOnly": True})
def trend_replace_tpsl(cfg: dict, order_row: dict, stop_loss: float, take_profit: float) -> None:
"""趋势保本移交:先撤条件单再挂保本止损 + 计划止盈(与下单监控一致)."""
m = _m(cfg)
fn = getattr(m, "replace_active_monitor_tpsl_on_exchange", None)
if not callable(fn):
raise RuntimeError("当前实例未配置止盈止损同步能力")
fn(order_row, float(stop_loss), float(take_profit))
def cancel_symbol_orders(cfg: dict, exchange_symbol: str) -> None:
m = _m(cfg)
if hasattr(m, "cancel_all_open_orders_for_symbol"):
m.cancel_all_open_orders_for_symbol(exchange_symbol)
return
if hasattr(m, "cancel_gate_swap_trigger_orders"):
m.cancel_gate_swap_trigger_orders(exchange_symbol)
if hasattr(m, "cancel_binance_futures_open_orders"):
m.cancel_binance_futures_open_orders(exchange_symbol)
if hasattr(m, "cancel_okx_swap_open_orders"):
m.cancel_okx_swap_open_orders(exchange_symbol)
# 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
+12 -143
View File
@@ -1,143 +1,12 @@
"""策略交易页:主站 index.html 所需数据(顺势加仓等)."""
from __future__ import annotations
from typing import Any, Callable, Optional
from lib.strategy.strategy_db import init_strategy_tables
from lib.strategy.strategy_roll_monitor_lib import roll_leg_status_label
def _row_to_dict(row) -> dict:
if row is None:
return {}
try:
return dict(row)
except Exception:
return {}
def count_active_trend_plans(conn, count_fn: Optional[Callable] = None) -> int:
if callable(count_fn):
return int(count_fn(conn) or 0)
try:
return int(
conn.execute(
"SELECT COUNT(*) FROM trend_pullback_plans WHERE status='active'"
).fetchone()[0]
)
except Exception:
return 0
def fetch_roll_page_data(
conn,
*,
default_risk_percent: float = 2.0,
count_active_trends: Optional[Callable] = None,
roll_cfg: dict | None = None,
) -> dict[str, Any]:
init_strategy_tables(conn)
monitors = []
for row in conn.execute(
"SELECT * FROM order_monitors WHERE status='active' ORDER BY id DESC"
).fetchall():
monitors.append(_row_to_dict(row))
roll_groups = []
for row in conn.execute(
"""SELECT g.* FROM roll_groups g
INNER JOIN order_monitors m ON m.id = g.order_monitor_id AND m.status='active'
WHERE g.status='active'
ORDER BY g.id DESC"""
).fetchall():
roll_groups.append(_row_to_dict(row))
active_gids = {int(g["id"]) for g in roll_groups if g.get("id") is not None}
roll_legs = []
for row in conn.execute(
"SELECT * FROM roll_legs ORDER BY id DESC LIMIT 80"
).fetchall():
leg = _row_to_dict(row)
gid = leg.get("roll_group_id")
if gid is not None and int(gid) not in active_gids:
continue
leg["status_label"] = roll_leg_status_label(leg.get("status"))
roll_legs.append(leg)
roll_legs = roll_legs[:50]
out = {
"roll_monitors": monitors,
"roll_groups": roll_groups,
"roll_legs": roll_legs,
"roll_trend_active": count_active_trend_plans(conn, count_active_trends),
"default_risk_percent": default_risk_percent,
}
if roll_cfg:
from lib.strategy.strategy_roll_ui_lib import enrich_roll_page_data
enrich_roll_page_data(conn, out, roll_cfg)
return out
DEFAULT_TREND_DISABLED_NOTE = (
"趋势回调(预览,自动补仓,程序止盈)须在本实例 .env 设置 "
"`LIVE_TRADING_ENABLED=true` 并重启对应 PM2 进程(如 crypto_gate / crypto_okx / crypto_binance)."
)
def strategy_render_extras(
conn,
page: str,
*,
default_risk_percent: float = 2.0,
count_active_trends: Optional[Callable] = None,
trend_disabled_note: str = "",
request_obj=None,
trend_cfg: Optional[dict] = None,
) -> dict[str, Any]:
"""render_main_page 策略相关页变量(含策略交易记录)."""
if page == "strategy_records":
from lib.strategy.strategy_records_register import load_strategy_records_page
return load_strategy_records_page(conn)
return strategy_page_template_vars(
conn,
page,
default_risk_percent=default_risk_percent,
count_active_trends=count_active_trends,
trend_disabled_note=trend_disabled_note,
request_obj=request_obj,
trend_cfg=trend_cfg,
)
def strategy_page_template_vars(
conn,
page: str,
*,
default_risk_percent: float = 2.0,
count_active_trends: Optional[Callable] = None,
trend_disabled_note: str = "",
request_obj=None,
trend_cfg: Optional[dict] = None,
) -> dict[str, Any]:
"""render_main_page 在 conn.close() 前合并进 render_template 的变量."""
if page not in ("strategy", "strategy_trend", "strategy_roll"):
return {}
roll_cfg = None
try:
from flask import current_app
roll_cfg = (current_app.extensions or {}).get("strategy_roll_cfg")
except Exception:
roll_cfg = None
out = fetch_roll_page_data(
conn,
default_risk_percent=default_risk_percent,
count_active_trends=count_active_trends,
roll_cfg=roll_cfg if isinstance(roll_cfg, dict) else None,
)
if trend_cfg and request_obj is not None:
from lib.strategy.strategy_trend_register import load_trend_page_context
out.update(load_trend_page_context(conn, request_obj, trend_cfg))
elif page == "strategy_trend":
out["trend_disabled_note"] = trend_disabled_note or DEFAULT_TREND_DISABLED_NOTE
return out
# 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