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:
+8
-3
@@ -1,10 +1,16 @@
|
||||
# 本地真实配置(含 API 密钥),勿提交
|
||||
**/.env
|
||||
.env
|
||||
license.env
|
||||
|
||||
# 允许提交模板
|
||||
!**/.env.example
|
||||
!.env.example
|
||||
!license.env.example
|
||||
|
||||
# 许可状态(运行时)
|
||||
data/license_state.json
|
||||
data/
|
||||
|
||||
# Python
|
||||
**/__pycache__/
|
||||
@@ -25,6 +31,5 @@ manual_trading_hub/hub_ai_fund_history.json
|
||||
manual_trading_hub/data/
|
||||
backups/
|
||||
|
||||
# 数据库与上传(运行时生成)
|
||||
**/*.sqlite
|
||||
**/crypto.db
|
||||
# 加密发布产物与明文备份(勿提交)
|
||||
dist/
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# AI 复盘与模型配置说明
|
||||
|
||||
三个 `crypto_monitor_*` 实例共用仓库根目录 **`ai_client.py`**(通过 `PYTHONPATH=..` 导入).用于 **交易记录与复盘** 页的 AI 点评,短评建议,以及从复盘截图提取结构化 JSON.
|
||||
|
||||
---
|
||||
|
||||
## 一,二选一:`AI_PROVIDER`
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| **`openai`**(默认) | OpenAI 兼容 **Chat Completions** 接口 |
|
||||
| **`ollama`** | 本机 Ollama **`/api/generate`**(流式 NDJSON) |
|
||||
|
||||
在对应子目录 **`.env`** 中设置(各所 `.env.example` 已含模板):
|
||||
|
||||
```bash
|
||||
AI_PROVIDER=openai
|
||||
AI_TIMEOUT_SECONDS=120
|
||||
|
||||
# OpenAI 兼容网关(默认)
|
||||
OPENAI_API_BASE=https://op.bz121.com/v1
|
||||
OPENAI_API_KEY=你的密钥
|
||||
OPENAI_MODEL=gemma4:e4b
|
||||
|
||||
# 本机 Ollama(仅当 AI_PROVIDER=ollama)
|
||||
OLLAMA_API=http://127.0.0.1:11434/api/generate
|
||||
AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
||||
```
|
||||
|
||||
### OpenAI 兼容网关
|
||||
|
||||
- **Base URL**:`https://op.bz121.com/v1`(请求路径为 `{base}/chat/completions`).
|
||||
- **API Key**:在 [op.bz121.com](https://op.bz121.com/) 登录后,于 **`gateway.json`** 页面复制(与网关账号一致).
|
||||
- **默认模型**:`gemma4:e4b`(可通过 `OPENAI_MODEL` 覆盖).
|
||||
|
||||
### Ollama
|
||||
|
||||
- 需本机已安装并拉取对应模型;`AI_PROVIDER=ollama` 时使用 `OLLAMA_API` 与 `AI_MODEL`.
|
||||
- 三所 `app.py` **不再** 直连 Ollama;统一走 `ai_client.ai_generate` / `ai_review` / `ai_short_advice`.
|
||||
|
||||
---
|
||||
|
||||
## 二,部署注意
|
||||
|
||||
1. **PM2 / 手工启动**:`ecosystem.config.cjs` 中 **`PYTHONPATH=..`** 必须包含仓库根,否则无法 `from ai_client import ...`.
|
||||
2. 修改 `.env` 后重启对应实例,例如:`pm2 restart crypto_binance`(名称以你机器为准).
|
||||
3. **`git pull`** 不会改 `.env`;若 `.env.example` 新增 AI 变量,请手动补进本机 `.env`.
|
||||
4. **勿** 将含真实 `OPENAI_API_KEY` 的 `.env` 提交 Git.
|
||||
|
||||
---
|
||||
|
||||
## 三,功能入口(网页)
|
||||
|
||||
登录后进入 **「交易记录与复盘」**:
|
||||
|
||||
- 单条记录 **AI 复盘** / **短评**(依赖上述配置).
|
||||
- 上传复盘图后 **从图片提取** 字段(内部调用 `ai_generate`,与所选 provider 一致).
|
||||
|
||||
若请求超时或返回错误,请检查:密钥是否有效,网关是否可达,`AI_TIMEOUT_SECONDS` 是否过短,Ollama 是否已启动(仅 ollama 模式).
|
||||
|
||||
---
|
||||
|
||||
## 四,相关文件
|
||||
|
||||
| 路径 | 说明 |
|
||||
|------|------|
|
||||
| `ai_client.py` | 统一封装 OpenAI / Ollama |
|
||||
| `crypto_monitor_*/.env.example` | 各所环境变量模板 |
|
||||
| 各所《部署文档.md》§ AI 复盘 | 与本文一致的简表 |
|
||||
| 各所《使用说明.md》 | 运行前配置中的 AI 项 |
|
||||
@@ -1,91 +1,53 @@
|
||||
# 复盘交易系统(crypto_monitor_user · 用户版)
|
||||
# 复盘交易系统(crypto_monitor_user · 用户版)
|
||||
|
||||
多交易所 **USDT 永续** 的下单监控,**关键位**,**策略交易**,**止盈止损 / 移动保本** 与 **AI 复盘**,三所独立部署 + 可选 **中控** 聚合监控.
|
||||
多交易所 USDT 永续监控与中控聚合。本仓库为**用户发行版**:含部署文档与基础说明;核心业务逻辑以加密形式交付,不以策略源码文档形式公开。
|
||||
|
||||
**远程仓库**:[https://git.bz121.com/dekun/crypto_monitor_user.git](https://git.bz121.com/dekun/crypto_monitor_user.git)
|
||||
**远程仓库**:[https://git.bz121.com/dekun/crypto_monitor_user.git](https://git.bz121.com/dekun/crypto_monitor_user.git)
|
||||
**授权服务**:[https://sq.bz121.com/](https://sq.bz121.com/) · 说明见 [docs/license.md](./docs/license.md)
|
||||
|
||||
---
|
||||
|
||||
## 部署环境(必读)
|
||||
## 部署环境
|
||||
|
||||
| 项 | 约定 |
|
||||
|----|------|
|
||||
| 系统 | **Ubuntu 22.04 / 24.04** |
|
||||
| 用户 | **root** |
|
||||
| 路径 | **`/opt/crypto_monitor_user`** |
|
||||
| 进程 | **PM2**(唯一推荐的常驻方式) |
|
||||
| 系统 | Ubuntu 22.04 / 24.04 |
|
||||
| 用户 | root |
|
||||
| 路径 | `/opt/crypto_monitor_user` |
|
||||
| 进程 | PM2 |
|
||||
|
||||
**环境详解**(Python 3.10+,Node,PM2 安装与启动顺序):**[docs/ubuntu-server.md](./docs/ubuntu-server.md)**
|
||||
**一键部署管理器**:`curl -fsSL .../deploy/manage.sh | bash` → **[deploy/README.md](./deploy/README.md)**
|
||||
详情:[docs/ubuntu-server.md](./docs/ubuntu-server.md) · [deploy/README.md](./deploy/README.md)
|
||||
|
||||
```bash
|
||||
# 新服务器(推荐)
|
||||
curl -fsSL https://git.bz121.com/dekun/crypto_monitor_user/raw/branch/main/deploy/manage.sh | bash
|
||||
|
||||
# 或手动 clone
|
||||
# 或
|
||||
cd /opt
|
||||
git clone https://git.bz121.com/dekun/crypto_monitor_user.git crypto_monitor_user
|
||||
cd /opt/crypto_monitor_user
|
||||
bash deploy/manage.sh
|
||||
```
|
||||
|
||||
配置与运维脚本: **[docs/env-sync-scripts.md](./docs/env-sync-scripts.md)** · **[备份与恢复.md](./备份与恢复.md)**
|
||||
部署后请配置仓库根目录 **`license.env`**(从 `license.env.example` 复制),打开任意实例或中控的 **`/license`** 激活。
|
||||
|
||||
运维:[备份与恢复.md](./备份与恢复.md) · [docs/env配置说明.md](./docs/env配置说明.md)
|
||||
|
||||
---
|
||||
|
||||
## 功能导航
|
||||
## 目录
|
||||
|
||||
| 功能 | 说明 | 文档 |
|
||||
|------|------|------|
|
||||
| **关键位监控** | 箱体/收敛自动开仓,阻力支撑提醒,斐波限价;止盈止损方案与 **移动保本** 开关 | 各所 [关键位自动下单说明.md](./crypto_monitor_binance/关键位自动下单说明.md)(Gate/OKX 目录内同名);方案细则 **[关键位止盈止损与移动保本更新说明.md](./关键位止盈止损与移动保本更新说明.md)** |
|
||||
| **实盘下单 / 下单监控** | 首仓,以损定仓;监控内 **止盈 / 止损**,**移动保本**(步进 R,偏移%) | 各所 [使用说明.md](./crypto_monitor_binance/使用说明.md) · 顶栏「实盘下单」`/trade` |
|
||||
| **策略交易** | **趋势回调** + **顺势加仓**(`/strategy` 双栏) | **[策略交易说明.md](./策略交易说明.md)** · 趋势细则 [docs/trend-pullback-strategy.md](./docs/trend-pullback-strategy.md) |
|
||||
| **策略交易记录** | 已结束计划快照(最近 100 条),筛选与展开详情 | [策略交易说明.md §五](./策略交易说明.md) · 顶栏 `/strategy/records` |
|
||||
| **交易复盘** | 平仓记录,错过机会,图表;**AI 点评** | **[AI复盘与模型配置说明.md](./AI复盘与模型配置说明.md)** · 顶栏「交易记录与复盘」`/records` |
|
||||
| **中控** | 多账户持仓/委托聚合,行情 K 线,紧急全平(**不在中控网页下单**) | [manual_trading_hub/使用说明.md](./manual_trading_hub/使用说明.md) · [部署文档.md](./manual_trading_hub/部署文档.md) |
|
||||
|
||||
其它专题:[计仓模式](./docs/position-sizing-mode.md) · [每日自动划转](./docs/auto-transfer-daily.md) · [Chrome 快捷方式图标](./docs/shortcut-icon.md)
|
||||
|
||||
---
|
||||
|
||||
## 仓库目录
|
||||
|
||||
| 目录 | 交易所 / 角色 | 部署文档 |
|
||||
|------|----------------|----------|
|
||||
| `crypto_monitor_binance/` | Binance U 本位永续 | [部署文档.md](./crypto_monitor_binance/部署文档.md) |
|
||||
| `crypto_monitor_gate/` | Gate | [部署文档.md](./crypto_monitor_gate/部署文档.md) |
|
||||
| `crypto_monitor_okx/` | OKX 永续 | [部署文档.md](./crypto_monitor_okx/部署文档.md) |
|
||||
| `manual_trading_hub/` | 中控 + 子代理 | [部署文档.md](./manual_trading_hub/部署文档.md) |
|
||||
| `lib/` | **共用模块**(策略,关键位,交易,中控库,AI,静态与模板) | **[docs/lib-structure.md](./docs/lib-structure.md)** |
|
||||
| `brand/` | 各所共用图标与 manifest | — |
|
||||
| `docs/`,`deploy/`,`scripts/`,`tests/` | 文档,环境,脚本,单元测试 | — |
|
||||
|
||||
共用代码 import 示例:`from lib.strategy.strategy_db import init_strategy_tables`(各所启动时仍将仓库根加入 `PYTHONPATH`).详见 **[docs/lib-structure.md](./docs/lib-structure.md)**.
|
||||
|
||||
---
|
||||
|
||||
## 技术要点
|
||||
|
||||
- **Python 3.10+**,Flask,ccxt,SQLite(`crypto.db`)
|
||||
- 三所 `.env` 前缀不同(`BINANCE_*` / `GATE_*` / `OKX_*`),**不可混用**
|
||||
- 实盘须 `LIVE_TRADING_ENABLED=true` 且理解 API 权限与 IP 白名单风险
|
||||
- 经 **SOCKS** 访问交易所时配置各所 `*_SOCKS_PROXY` 并安装 PySocks
|
||||
|
||||
---
|
||||
|
||||
## 推荐阅读顺序
|
||||
|
||||
1. [docs/ubuntu-server.md](./docs/ubuntu-server.md) — 装 Python / Node / PM2,PM2 启动三所 + 中控
|
||||
2. 各所 **`.env`**(从 `.env.example` 复制)
|
||||
3. 所用功能对应上表 **功能导航** 文档
|
||||
4. [备份与恢复.md](./备份与恢复.md) — 生产机备份习惯
|
||||
| 目录 | 说明 |
|
||||
|------|------|
|
||||
| `crypto_monitor_binance/` | Binance 实例 |
|
||||
| `crypto_monitor_gate/` | Gate 实例 |
|
||||
| `crypto_monitor_okx/` | OKX 实例 |
|
||||
| `manual_trading_hub/` | 中控 |
|
||||
| `lib/` | 共用模块(发行版核心逻辑已加密) |
|
||||
| `deploy/` / `docs/` | 部署与基础文档 |
|
||||
|
||||
---
|
||||
|
||||
## 安全
|
||||
|
||||
- **勿** 将 `.env`,API Secret,`.pem` 提交 Git
|
||||
- 公网暴露中控须配置登录,`HUB_BRIDGE_TOKEN`,HTTPS Cookie
|
||||
- 勿将 `.env`、`license.env`、API Secret 提交 Git
|
||||
- 公网须改默认密码并配置 HTTPS
|
||||
- 实盘风险由使用者自行承担
|
||||
|
||||
若子目录 README 与本文冲突,以 **子目录《部署文档》与当前代码** 为准.
|
||||
|
||||
@@ -231,3 +231,8 @@ AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
||||
|
||||
APP_TIMEZONE=Asia/Shanghai
|
||||
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日;开仓整点限制见 TRADING_DAY_RESET_OPEN_GUARD_ENABLED
|
||||
|
||||
# --- 软件授权(整机共用,也可写仓库根 license.env)---
|
||||
# LICENSE_API_URL=https://sq.bz121.com
|
||||
# LICENSE_CLIENT_KEY=与授权站 CLIENT_API_KEY 相同
|
||||
# LICENSE_OFFLINE_GRACE_HOURS=72
|
||||
|
||||
@@ -1,83 +1,12 @@
|
||||
# crypto_monitor_binance
|
||||
# crypto_monitor_binance(用户版)
|
||||
|
||||
基于 **Flask** 的加密货币 **下单监控 / 关键位监控 / 交易复盘** 小系统,行情与实盘接口统一走 **Binance(USDT-M 永续)**,通过 **ccxt** 访问.
|
||||
Binance USDT-M 永续实例。
|
||||
|
||||
## 功能概要
|
||||
|
||||
- **关键位监控**:价格与硬条件校验,企业微信推送(可选)
|
||||
- **下单监控**:本地风控(含移动保本逻辑),触达止盈/止损后尝试市价平仓并记账
|
||||
- **策略交易**:顶栏 `/strategy`(趋势回调 + 顺势加仓),见仓库根 [策略交易说明.md](../策略交易说明.md)
|
||||
- **AI 复盘**:OpenAI 兼容网关(默认)或 Ollama,见 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)
|
||||
- **实盘(可选)**:`LIVE_TRADING_ENABLED=true` 且配置 `BINANCE_API_KEY` / `BINANCE_API_SECRET` 时,支持合约开仓,平仓,余额读取与内部划转(依赖 API 权限)
|
||||
- **止盈止损(Binance)**:市价成交后挂 **`STOP_MARKET`**(止损),**`TAKE_PROFIT_MARKET`**(止盈);双向持仓带 `positionSide`;不显式传 `reduceOnly`(避免 API `-1106`).触发参考价由 `BINANCE_TRIGGER_WORKING_TYPE` 控制(最新价 / 标记价)
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Python 3.10+(建议)
|
||||
- 依赖:`flask`,`requests`,`ccxt`,`werkzeug`,`Pillow`(K 线图可选);经 SOCKS 代理时需 **`PySocks`**
|
||||
|
||||
安装示例:
|
||||
|
||||
```bash
|
||||
# 推荐在 /opt/crypto_monitor_user 执行仓库根目录 deploy/setup_env.sh
|
||||
cd /opt/crypto_monitor_user/crypto_monitor_binance
|
||||
source .venv/bin/activate
|
||||
pip install -r ../requirements.txt
|
||||
```
|
||||
|
||||
页面上的 **「当日资金(交易账户)」** 与 **「可开仓」可用 U** 仅统计 **Binance U 本位永续合约账户**(`fetch_balance` 的 `swap` / FAPI `assets` 中的 USDT),**不会**再用现货余额顶替.
|
||||
|
||||
## 配置说明(`.env.example` → `.env`)
|
||||
|
||||
- **`.env.example`**:模板(可提交 Git);首次:`cp .env.example .env` 后编辑.
|
||||
- **`.env`**:本机真实配置(勿提交);`app.py` 只读此文件.`git pull` 不覆盖 `.env`;升级前可 `cp .env .env.backup.$(date +%Y%m%d)`.
|
||||
|
||||
与 Binance 相关的常用变量:
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `BINANCE_API_KEY` / `BINANCE_API_SECRET` | 币安 API(需合约等权限) |
|
||||
| `LIVE_TRADING_ENABLED` | `true` 时允许真实下单;`false` 仅本地逻辑 |
|
||||
| `BINANCE_MARGIN_MODE` | `cross` 全仓 / `isolated` 逐仓 |
|
||||
| `BINANCE_POSITION_MODE` | `hedge` 双向(需账户开启双向持仓)/ `oneway` 单向 |
|
||||
| `BINANCE_TRIGGER_WORKING_TYPE` | `CONTRACT_PRICE` 或 `MARK_PRICE`(条件单触发参考) |
|
||||
| `BINANCE_SOCKS_PROXY` / `BINANCE_HTTP_PROXY` | 可选代理(与部署文档一致) |
|
||||
| `EXCHANGE_DISPLAY_NAME` | 页面展示的交易所名称,默认 `Binance` |
|
||||
| `BINANCE_ACCOUNT_LABEL` | 推送文案中的账户备注 |
|
||||
|
||||
其余变量(登录,企业微信,风控参数,**`AI_PROVIDER` / `OPENAI_*` / `OLLAMA_*`**,数据库路径等)见 **`.env.example` 内注释** 或 `app.py` 顶部默认值.
|
||||
|
||||
## 运行
|
||||
|
||||
生产环境使用 **PM2**(`ecosystem.config.cjs`).临时调试:
|
||||
- **部署**:[部署文档.md](./部署文档.md) · 仓库根 [docs/ubuntu-server.md](../docs/ubuntu-server.md)
|
||||
- **授权**:启动后打开 `/license`,见 [docs/license.md](../docs/license.md)
|
||||
- **配置**:复制 `.env.example` → `.env`,填写 API 与登录信息
|
||||
|
||||
```bash
|
||||
cd /opt/crypto_monitor_user/crypto_monitor_binance
|
||||
source .venv/bin/activate
|
||||
python app.py
|
||||
# 推荐用仓库根 deploy/setup_env.sh 创建 .venv
|
||||
```
|
||||
|
||||
环境说明见 [docs/ubuntu-server.md](../docs/ubuntu-server.md).
|
||||
|
||||
默认监听端口由 `.env` 的 `APP_PORT` 决定(未设置时多为 `5000`).
|
||||
|
||||
## 部署(Linux / PM2 / SSH SOCKS)
|
||||
|
||||
详见 **[部署文档.md](./部署文档.md)**(Ubuntu + PM2 + 可选 SOCKS 访问 Binance).
|
||||
|
||||
## 自检脚本
|
||||
|
||||
```bash
|
||||
python scripts/verify_binance_funding.py
|
||||
```
|
||||
|
||||
用于核对 Key 前缀(不含 Secret)并尝试读取资金钱包 / 合约钱包 USDT(需网络与 API 权限).
|
||||
|
||||
## 数据与脚本
|
||||
|
||||
- 默认 SQLite:`crypto.db`(路径由 `DB_PATH` 指定)
|
||||
- `scripts/fix_breakeven_labels.py`:批量修正「止损」但盈亏为正的记录标签(见部署文档附录)
|
||||
|
||||
## 风险与合规
|
||||
|
||||
实盘交易有亏损风险.请自行确认 API 权限,IP 白名单,杠杆与保证金模式与币安账户设置一致,并遵守当地法律法规与 Binance 用户协议.
|
||||
|
||||
@@ -306,6 +306,12 @@ app.secret_key = os.getenv("FLASK_SECRET_KEY", "crypto_monitor_2026_secret_key")
|
||||
from lib.instance.instance_embed_lib import attach_embed_templates
|
||||
|
||||
attach_embed_templates(app, _REPO_ROOT)
|
||||
try:
|
||||
from lib.license.flask_gate import install_license_gate
|
||||
|
||||
install_license_gate(app)
|
||||
except Exception as _lic_err:
|
||||
print(f"[license] binance: {_lic_err}")
|
||||
|
||||
# ====================== 登录配置 ======================
|
||||
USERNAME = os.getenv("APP_USERNAME", "dekun")
|
||||
|
||||
Binary file not shown.
@@ -1,139 +0,0 @@
|
||||
# 使用说明
|
||||
|
||||
**本文件对应仓库:`crypto_monitor_binance`(Binance U 本位永续).**
|
||||
功能,界面与 **Gate.io USDT 永续版**(目录 `crypto_monitor_gate`)基本一致,差异主要在 **`.env` 里交易所密钥与部分参数名**(`BINANCE_*` / `GATE_*`),文末有对照.
|
||||
|
||||
**部署,代理,PM2 等**请参考本仓库说明或 **`crypto_monitor_gate`** 下的 **`部署文档.md`**(该文以 Gate + SSH SOCKS 为例;Binance 侧将 API 与密钥改为 `BINANCE_*` 即可类比).
|
||||
**关键位自动开仓的规则,RR,结案原因**见本目录 **`关键位自动下单说明.md`**.
|
||||
|
||||
---
|
||||
|
||||
## 1. 它能做什么
|
||||
|
||||
面向个人盘面的 **Web 控制台**,主要能力包括:
|
||||
|
||||
| 模块 | 说明 |
|
||||
|------|------|
|
||||
| **关键位监控** | 录入上/下沿与类型,按 **5m 收线** 做硬条件过滤;符合条件后 **企业微信** 提醒,部分类型可 **自动市价开仓**(见第 4 节与专门文档). |
|
||||
| **实盘下单监控** | 手工填止损/止盈,**以损定仓** 市价开单,挂上条件止盈止损,并在页面跟踪浮盈亏,保本逻辑等. |
|
||||
| **交易记录 / 复盘** | 平仓结果,盈亏,错过的单等归档与导出;可选 **AI 复盘**(见仓库根 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)). |
|
||||
| **策略交易** | 顶栏 `/strategy`:**趋势回调**(左)与 **顺势加仓**(右)左右并列;细则见 [策略交易说明.md](../策略交易说明.md). |
|
||||
| **策略交易记录** | 顶栏 `/strategy/records`:趋势/顺势分两栏,可筛选,库内保留最近 100 条结束快照. |
|
||||
|
||||
后台按 **`MONITOR_POLL_SECONDS`**(默认几秒)轮询行情与监控逻辑.**切勿**在未理解规则时同时运行两套程序共用一个实盘账户.
|
||||
|
||||
---
|
||||
|
||||
## 2. 运行前必须配置(`.env`)
|
||||
|
||||
首次在本目录执行 **`cp .env.example .env`**,再编辑 `.env`(`.env` 勿提交 Git;`git pull` 不会改你的 `.env`,升级前建议 `cp .env .env.backup.$(date +%Y%m%d)`).
|
||||
|
||||
至少检查以下项(具体键名以 **`.env.example`** 为准):
|
||||
|
||||
| 类别 | 说明 |
|
||||
|------|------|
|
||||
| **登录网页** | `APP_PASSWORD`:打开站点后的登录口令.`FLASK_SECRET_KEY`:Session 密钥,请勿使用默认值. |
|
||||
| **企业微信** | `WECHAT_WEBHOOK`:告警与关键位推送机器人的 Webhook. |
|
||||
| **是否真下单** | `LIVE_TRADING_ENABLED=false`:**不会**向交易所发送开仓指令(适合测试流程).改为 `true` 且密钥正确才会实盘. |
|
||||
| **交易所 API** | **本仓库:** `BINANCE_API_KEY`,`BINANCE_API_SECRET`;永续相关见 `BINANCE_MARGIN_MODE`,`BINANCE_POSITION_MODE`,`BINANCE_TRIGGER_WORKING_TYPE` 等.**勿**把 `.env` 提交到 Git. |
|
||||
| **关键位 RR / 止损外扩** | `KEY_AUTO_MIN_PLANNED_RR`,`KEY_STOP_OUTSIDE_BREAKOUT_PCT`(详见 `关键位自动下单说明.md`). |
|
||||
| **AI 复盘** | 默认 `AI_PROVIDER=openai`,`OPENAI_API_BASE=https://op.bz121.com/v1`,`OPENAI_API_KEY`,`OPENAI_MODEL=gemma4:e4b`;或 `AI_PROVIDER=ollama` + `OLLAMA_API` / `AI_MODEL`.详见 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md). |
|
||||
|
||||
网络需要代理时可配置 **`BINANCE_SOCKS_PROXY` / `BINANCE_HTTP_PROXY`**(与 Gate 版 `GATE_*_PROXY` 用法类似).
|
||||
|
||||
---
|
||||
|
||||
## 3. 如何启动与登录
|
||||
|
||||
1. 准备 Python 虚拟环境并安装依赖(如 `flask`,`requests`,`ccxt`,按需 `Pillow`,`PySocks` 等),配置好 `.env`.
|
||||
2. 启动 Flask 应用(可用 **`ecosystem.config.cjs`** 交给 PM2,或本地 `python app.py` / `flask run`,以你当前脚本为准).
|
||||
3. 浏览器访问站点,打开 **`/login`**,使用 **`.env` 里的 `APP_PASSWORD`** 登录.
|
||||
|
||||
登录后顶栏:**关键位监控** | **实盘下单**(默认首页)| **策略交易**(`/strategy`,趋势回调 + 顺势加仓双栏)| **策略交易记录**(`/strategy/records`,最近 100 条结束快照)| **交易记录与复盘** | **统计分析**.
|
||||
|
||||
---
|
||||
|
||||
## 4. 关键位监控(顶栏「关键位监控」→ `/key_monitor`)
|
||||
|
||||
### 4.1 添加一条关键位
|
||||
|
||||
1. **币种**:如 `BTC` 或 `BTC/USDT`(会规范成内部符号).
|
||||
2. **类型**(必选其一):
|
||||
|
||||
| 类型 | 行为摘要 |
|
||||
|------|----------|
|
||||
| **箱体突破** | 通过门控且计划 RR 达标 → **自动市价开仓**(需 `LIVE_TRADING_ENABLED=true` 且无其他持仓占位).结案后本条从列表消失并记入历史. |
|
||||
| **收敛突破** | 同上(自动开仓类). |
|
||||
| **关键阻力位** | **不自动开仓**;触发后 **发 1 次微信**,然后本条 **结案进历史**. |
|
||||
| **关键支撑位** | 同上(仅提醒). |
|
||||
| **回调触价开仓** | **不挂交易所限价**;标记价回调触达 E 后 **下一轮询市价开仓**(RR 门槛同 `KEY_AUTO_MIN_PLANNED_RR`);有效期 **24h** |
|
||||
| **突破触价开仓** | **不挂交易所限价**;标记价 **穿越 E 立即市价开仓**;先触 SL/TP 侧失效;有效期 **24h** |
|
||||
|
||||
3. **方向**:做多 / 做空(回调/突破触价,箱体 / 收敛 / 斐波必选;阻力/支撑不选).
|
||||
4. **价位**:箱体/收敛/阻力/支撑填 **上沿 / 下沿**;触价填 **入场 E / 止损 SL / 止盈 TP**.
|
||||
|
||||
**限制:**
|
||||
活跃持仓数达到 **`MAX_ACTIVE_POSITIONS`**(默认 1)时,**不允许**再添加「**箱体突破** / **收敛突破**」;仍可添加「**关键阻力位 / 支撑位**」.
|
||||
若 **4h EMA55** 与你的方向逆势,页面会 **额外 Flash 提示**,**不阻挡**提交.
|
||||
|
||||
### 4.2 触发后会发生什么(简版)
|
||||
|
||||
- **箱体 / 收敛**:门控通过后算计划 SL/TP 与 RR;不达标 → 微信说明 + **`rr_insufficient`** 结案;达标 → **市价开仓**,成功 **`auto_opened`** / 失败 **`exchange_failed`**,均不重试同一关键位.
|
||||
- **阻力 / 支撑**:仅 **单次推送** → **`key_level_alert_only`** 结案.
|
||||
|
||||
详细公式与字段见 **`关键位自动下单说明.md`**.
|
||||
|
||||
### 4.3 列表与历史
|
||||
|
||||
当前条目与历史记录的用法与 Gate 版相同;结案后可在历史区查阅 **`close_reason`**.
|
||||
|
||||
---
|
||||
|
||||
## 5. 实盘下单(顶栏「实盘下单」→ `/trade`)
|
||||
|
||||
- 持仓上限由 **`MAX_ACTIVE_POSITIONS`** 控制(默认 1).
|
||||
- **人工开仓**计划盈亏比不得低于 **`MANUAL_MIN_PLANNED_RR`**(默认 1.4:1).
|
||||
- 填写币种,方向,杠杆(可选),止损/止盈(价格或百分比按表单).
|
||||
- 移动保本等选项按页面与 `.env` 默认.
|
||||
|
||||
开仓成功后卡片 **「来源」**:手工一般为 **下单监控**;关键位自动为 **关键位监控**.
|
||||
|
||||
---
|
||||
|
||||
## 6. 企业微信
|
||||
|
||||
推送逻辑与 Gate 版一致;未配置 **`WECHAT_WEBHOOK`** 时可能没有消息,请以 **交易所端** 核对持仓与挂单.
|
||||
|
||||
---
|
||||
|
||||
## 7. 强烈建议的风险与运维习惯
|
||||
|
||||
1. **先用 `LIVE_TRADING_ENABLED=false`** 熟悉流程再实盘.
|
||||
2. **API 权限**最小化,密钥勿泄露.
|
||||
3. **同一账户避免多程序重复开仓**.
|
||||
4. **自动备份**:服务器上执行 `bash scripts/install_backup_cron.sh`(每天北京时间 0:00 → `/root/backups`,保留 30 天);升级前也可 `bash scripts/backup_data.sh` 手动跑一次.
|
||||
5. 升级代码后留意 **首轮启动**有无数据库迁移报错.
|
||||
|
||||
---
|
||||
|
||||
## 8. 常见问题(简要)
|
||||
|
||||
| 现象 | 可自查 |
|
||||
|------|--------|
|
||||
| 关键位永远不触发 | 门控五项,日成交量排名,`KLINE_TIMEFRAME`. |
|
||||
| 有信号但不自动开仓 | `LIVE_TRADING_ENABLED`,RR 阈值,是否已有持仓,API/保证金错误信息. |
|
||||
| 加不了箱体/收敛 | 是否已有持仓. |
|
||||
| 推送收不到 | Webhook,网络. |
|
||||
|
||||
---
|
||||
|
||||
## 9. Gate 版(`crypto_monitor_gate`)差异速查
|
||||
|
||||
| 项目 | Binance 本仓库 | Gate 版 |
|
||||
|------|----------------|--------|
|
||||
| API 变量 | `BINANCE_API_KEY`,`BINANCE_API_SECRET`,`BINANCE_*` | `GATE_API_KEY`,`GATE_API_SECRET`,`GATE_*` |
|
||||
| 代理示例 | `BINANCE_SOCKS_PROXY` | `GATE_SOCKS_PROXY` |
|
||||
| TP/SL 实现 | `_binance_place_tp_sl_orders` | `_gate_place_tp_sl_orders`,`GATE_TPSL_*` |
|
||||
| 资金舍入口径 | **`FUNDS_DECIMALS`**(与记账一致) | 以 Gate 仓库实现为准 |
|
||||
|
||||
业务流程(登录,四种关键位,手工单,单仓)两份程序对齐;仅需更换目录与 `.env`.
|
||||
@@ -1,192 +0,0 @@
|
||||
# 关键位监控说明(自动开仓 + 人工盯盘)
|
||||
|
||||
**适用:Gate / Binance / OKX 三所实例(共用 `lib/key_monitor/key_auto_order_lib.py`)**
|
||||
|
||||
## 环境开关 `KEY_AUTO_ORDER_ENABLED`(默认 `false`)
|
||||
|
||||
| 计仓模式 | 开关 | 关键位程序自动单 |
|
||||
|----------|------|------------------|
|
||||
| `risk`(以损定仓) | `false` | **全部关闭**(含触价);支撑/阻力微信提醒仍可用 |
|
||||
| `risk` | `true` | 箱体/收敛/斐波/假突破/触价均可自动(旧行为) |
|
||||
| `full_margin`(全仓) | `false` | 全部关闭(含触价) |
|
||||
| `full_margin` | `true` | **仅触价**自动;箱体/斐波等仍禁止 |
|
||||
|
||||
**不受本开关影响:** 人工实盘下单,关键支撑/阻力提醒,**顺势加仓**(`risk` 下),趋势回调(`risk` 下).全仓模式下策略自动仍禁止.
|
||||
|
||||
修改 `.env` 后须 **重启 PM2**.复盘「开仓类型」与统计分段会随开关联动隐藏关键位选项.
|
||||
|
||||
---
|
||||
|
||||
**适用:`crypto_monitor_binance`(Binance U 本位)**
|
||||
Gate / OKX 见各自目录下同名文档;共享逻辑在 `lib/key_monitor/`.
|
||||
|
||||
本文档与 `.env`,`check_key_monitors`,`add_key`,`_key_hard_checks`,`_process_key_rs_level_alert` 一致.
|
||||
|
||||
---
|
||||
|
||||
## 一,监控类型总览
|
||||
|
||||
| 录入类型 | 录入时选方向 | 自动市价开仓 | 触发与结案 |
|
||||
|----------|--------------|--------------|------------|
|
||||
| **箱体突破** | **必选** 多/空 | **是**(门控 + RR) | 条件满足 → 开仓或 `rr_insufficient` / `exchange_failed` → **一次性删除** |
|
||||
| **收敛突破** | **必选** 多/空 | **是**(同上) | 同上 |
|
||||
| **关键阻力位** | **不选**(`direction=watch`) | **否** | 5m 收盘突破上/下沿 → 微信 **3 次** → `key_level_alert_done` |
|
||||
| **关键支撑位** | **不选** | **否** | 同上(与阻力位**相同规则**:填上沿+下沿,程序双向监控) |
|
||||
| 斐波回调 0.618 / 0.786 | 必选 | 限价挂单逻辑 | 见斐波说明(**不在下文展开**) |
|
||||
| **回调触价开仓** | **必选** 多/空 | **程序盯价 → 回调触 E 后市价** | 见下文 **§四** |
|
||||
| **突破触价开仓** | **必选** 多/空 | **程序盯价 → 穿越 E 立即市价** | 见下文 **§四** |
|
||||
|
||||
**添加时(箱体/收敛/斐波/触价):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30)**;上沿 **>** 下沿(触价开仓填 E/SL/TP,上下沿仅作展示占位).
|
||||
|
||||
---
|
||||
|
||||
## 二,关键阻力位 / 关键支撑位(人工盯盘)
|
||||
|
||||
### 2.1 录入
|
||||
|
||||
- 填写 **上沿 `upper`** 与 **下沿 `lower`**(程序同时监控两侧,**无法预先判定**做多还是做空).
|
||||
- 页面 **不显示,不要求** 方向;库中 `direction` 初始为 `watch`,**首次突破后** 写入 `long`(向上突破上沿)或 `short`(向下突破下沿).
|
||||
|
||||
### 2.2 触发(极简)
|
||||
|
||||
- 周期:**`KLINE_TIMEFRAME`(默认 5m)最近一根已闭合 K** 的 **收盘价**(非影线).
|
||||
- **向上突破上沿:** `收盘 > upper` → 推断方向 **多 / 向上**,本次监控任务开始按节奏提醒.
|
||||
- **向下突破下沿:** `收盘 < lower` → 推断方向 **空 / 向下**,本次任务同样开始提醒.
|
||||
- **任一侧突破即结束本条监控周期**(不会在突破后再等待另一侧;上沿,下沿谁先满足用谁,同根 K 仅可能满足一侧).
|
||||
|
||||
**不参与:** 量能,二确 K,越过幅度下限,日成交排名(运行时),计划 RR,自动开仓.
|
||||
|
||||
### 2.3 微信提醒次数
|
||||
|
||||
| 配置 | 默认 | 含义 |
|
||||
|------|------|------|
|
||||
| `KEY_ALERT_MAX_TIMES` | `3` | 突破后最多推送 3 次 |
|
||||
| `KEY_ALERT_INTERVAL_MINUTES` | `5` | 相邻两次推送至少间隔 5 分钟 |
|
||||
|
||||
- 第 1 次:首次检测到突破的当次轮询(若已闭合 5m 满足条件).
|
||||
- 第 2,3 次:仅按间隔推送(**不要求**价格仍在箱外).
|
||||
- 第 3 次推送后:写入 `key_monitor_history`,`close_reason=**key_level_alert_done**`,从 `key_monitors` **删除**.
|
||||
|
||||
### 2.4 与箱体/收敛的区别
|
||||
|
||||
| 项目 | 阻力/支撑 | 箱体/收敛 |
|
||||
|------|-----------|-----------|
|
||||
| 方向 | 程序推断 | 人工选择 |
|
||||
| K 线根数 | 1 根闭合 5m | 2 根(突破 K + 确认 K) |
|
||||
| 提醒次数 | 3 次后结案 | 自动单:触发后 1 次业务推送并结案 |
|
||||
|
||||
---
|
||||
|
||||
## 三,箱体突破 / 收敛突破(自动开仓)
|
||||
|
||||
### 3.1 K 线结构(默认索引)
|
||||
|
||||
| 角色 | 环境变量 | 默认 | 含义 |
|
||||
|------|----------|------|------|
|
||||
| 突破 K | `KEY_CONFIRM_BREAKOUT_BAR` | `-2` | 倒数第 2 根闭合 K |
|
||||
| 确认 K | `KEY_CONFIRM_BAR` | `-1` | 倒数第 1 根闭合 K |
|
||||
|
||||
### 3.2 硬门控(须全部通过)
|
||||
|
||||
1. **有效突破(收盘越界)**
|
||||
- 多:`突破 K 收盘 > upper`
|
||||
- 空:`突破 K 收盘 < lower`
|
||||
|
||||
2. **突破越过幅度(仅下限)**
|
||||
- 多:`(突破 K 收盘 − upper) / upper × 100 > KEY_BREAKOUT_AMP_MIN_PCT`(默认 **0.03%**)
|
||||
- 空:`(lower − 突破 K 收盘) / lower × 100 >` 同上
|
||||
- **无上限**;突破过猛由 **计划 RR** 过滤.
|
||||
- **不再**使用 K 线实体占开盘价比例;`KEY_BREAKOUT_AMP_MAX_PCT` **已不参与门控**.
|
||||
|
||||
3. **确认 K 不进箱体**
|
||||
- 多:确认 K 收盘 **`> upper`**(不得在 `[lower, upper]` 内)
|
||||
- 空:确认 K 收盘 **`< lower`**
|
||||
|
||||
4. **量能:** 突破 K 成交量 > 前 `KEY_VOLUME_MA_BARS`(默认 20)根均量 × `KEY_VOLUME_RATIO_MIN`(默认 1.3)
|
||||
|
||||
5. **日成交量排名:** 运行时仍须前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30)
|
||||
|
||||
6. **计划 RR(最后经济门控):** 按确认 K 收盘 **E** 计算 SL/TP 后,`RR` **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5)才市价开仓
|
||||
|
||||
### 3.3 止损 / 止盈(确认 K 收盘为 E)
|
||||
|
||||
箱体高 **H = |upper − lower|**.止损锚在 **突破 K 极值** 外侧:
|
||||
|
||||
| 方向 | 止损(标准/趋势方案) |
|
||||
|------|------------------------|
|
||||
| 多 | 突破 K **最低价** × (1 − `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
|
||||
| 空 | 突破 K **最高价** × (1 + `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
|
||||
|
||||
止盈方案见下表(与改版前一致):
|
||||
|
||||
| 方案 | `sl_tp_mode` | 多:SL / TP | 空:SL / TP |
|
||||
|------|--------------|-------------|-------------|
|
||||
| 标准突破 | `standard` | 突破 K 低外侧% / **E+H** | 突破 K 高外侧% / **E−H** |
|
||||
| 箱体 1R·止盈 1.5H | `box_1p5` | **E−H** / **E+1.5×H** | **E+H** / **E−1.5×H** |
|
||||
| 趋势单·自填止盈 | `trend_manual` | 突破 K 低 × (1−`KEY_TREND_STOP_OUTSIDE_PCT`%) / **录入止盈** | 突破 K 高外侧% / **录入止盈** |
|
||||
|
||||
### 3.4 一次性结案(`close_reason`)
|
||||
|
||||
| `close_reason` | 含义 |
|
||||
|----------------|------|
|
||||
| `box_opposite_break` | 标记价先突破反向边界(多:≤下沿;空:≥上沿) |
|
||||
| `rr_insufficient` | 门控通过但 RR 不达标或 SL/TP 几何无效 |
|
||||
| `exchange_failed` | RR 达标但实盘/交易所等原因未开仓 |
|
||||
| `auto_opened` | RR 达标且市价开仓成功 |
|
||||
| `key_level_alert_done` | 阻力/支撑 **3 次提醒** 完成 |
|
||||
|
||||
---
|
||||
|
||||
## 四,回调 / 突破触价开仓(程序触价,无交易所挂单)
|
||||
|
||||
### 4.1 录入
|
||||
|
||||
- **回调触价开仓**:方向必选多/空;填写 **计划入场价 E**,**止损 SL**,**止盈 TP**(做多须 `SL < E < TP`).
|
||||
- **突破触价开仓**:同上;添加时当前价须在突破方向一侧(做多:价低于 E;做空:价高于 E).
|
||||
- 计划 RR 以 **E** 为基准,须 **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5).
|
||||
- 可选移动保本,时间平仓;**全仓杠杆模式**下可用.
|
||||
|
||||
### 4.2 触发与结案
|
||||
|
||||
| 类型 | 触发条件(标记价) |
|
||||
|------|-------------------|
|
||||
| **回调触价** | 做多 `≤ E`;做空 `≥ E` → 下一轮询市价开仓 |
|
||||
| **突破触价** | 做多**向上穿越** E;做空**向下穿越** E → **立即**市价开仓 |
|
||||
|
||||
- 未成交前标记价先触 **TP 侧** → `trigger_tp_invalidate`.
|
||||
- **突破触价**另:未穿越 E 先触 **SL 侧** → `trigger_sl_invalidate`.
|
||||
- **24h** 未触发 → `trigger_entry_expired`.
|
||||
- 成功 → `trigger_entry_filled`;触发后开仓失败 → `trigger_exchange_failed`.
|
||||
|
||||
### 4.3 计仓与占位
|
||||
|
||||
- **以损定仓**:按 E,SL 反推保证金,触发时重算;**全仓杠杆**:可用×缓冲比例,BTC/ETH 10x,其它 5x.
|
||||
- **占当日开仓意图**(已开 + 待触发),未成交不占持仓;同币仅 1 条触价监控(含回调/突破).
|
||||
|
||||
共享逻辑:`trigger_entry_key_monitor_lib.py`;轮询:`check_trigger_entry_key_monitors`.
|
||||
|
||||
---
|
||||
|
||||
## 五,环境与参数(`.env` 摘要)
|
||||
|
||||
| 变量 | 箱体/收敛 | 阻力/支撑 |
|
||||
|------|-----------|-----------|
|
||||
| `KEY_BREAKOUT_AMP_MIN_PCT` | 突破越过下限(默认 0.03) | 不用 |
|
||||
| `KEY_BREAKOUT_AMP_MAX_PCT` | **已废弃门控** | 不用 |
|
||||
| `KEY_VOLUME_*` / `KEY_CONFIRM_*` | 用 | 不用 |
|
||||
| `KEY_AUTO_MIN_PLANNED_RR` | 用 | 不用 |
|
||||
| `KEY_ALERT_MAX_TIMES` / `KEY_ALERT_INTERVAL_MINUTES` | 不用 | 用(默认 3 次 / 5 分钟) |
|
||||
| `KEY_DAILY_VOLUME_RANK_MAX` | 添加时 + 运行时 | **仅添加时** |
|
||||
|
||||
---
|
||||
|
||||
## 六,相关代码
|
||||
|
||||
| 说明 | 位置 |
|
||||
|------|------|
|
||||
| 共享判定 | `key_monitor_lib.py` |
|
||||
| 主循环 | `check_key_monitors` |
|
||||
| 自动门控 | `_key_hard_checks` |
|
||||
| 阻力/支撑提醒 | `_process_key_rs_level_alert` |
|
||||
| 录入 | `add_key` |
|
||||
| 开仓 | `_market_open_for_key_monitor` |
|
||||
@@ -1,147 +0,0 @@
|
||||
# 界面与风控更新说明(Binance 实例)
|
||||
|
||||
## 顶栏导航(4 项)
|
||||
|
||||
| 顺序 | 名称 | 路由 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | 关键位监控 | `/key_monitor` | 关键位添加,实时门控,历史 |
|
||||
| 2 | 实盘下单 | `/trade` | 人工开仓,划转,实时持仓(**默认首页** `/` → `/trade`) |
|
||||
| 3 | 交易记录与复盘 | `/records` | 交易记录,复盘表单,AI 历史(受顶栏 UTC 时间窗筛选) |
|
||||
| 4 | 统计分析 | `/stats` | 按北京时间交易日切日 + 分品类统计块 |
|
||||
|
||||
## 关键位监控页
|
||||
|
||||
- 标题去掉「5m」;规则条从 `.env` 读取(周期,确认K,量能,自动开仓盈亏比,日成交量排名).
|
||||
- 左列:活跃关键位,**pos-card** 样式展示现价/距上沿/距下沿/门控.
|
||||
- 右列:关键位历史(失效/结案),与左列等高滚动;**受顶栏 UTC 列表时间窗筛选**(默认 UTC 当日).
|
||||
- 监控类型新增:**斐波回调0.618**,**斐波回调0.786**(与 Gate 主站同一套规则,计算逻辑见仓库根目录 `fib_key_monitor_lib.py`).
|
||||
|
||||
### 斐波关键位监控(方案 A:交易所限价)
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 同币互斥 | 每个币种只能有一条斐波监控(0.618 与 0.786 不可并存) |
|
||||
| 上下沿 | 上沿 **H**,下沿 **L**(须 H > L) |
|
||||
| 挂单价 E | **做多** `E = H − ratio × (H − L)`(自 H 向下回撤);**做空** `E = L + ratio × (H − L)`(自 L 向上反弹) |
|
||||
| 做多 | 限价 @ E,止损 L,止盈 H |
|
||||
| 做空 | 限价 @ E,止损 H,止盈 L |
|
||||
| 添加后 | **立即**在 Binance U 本位挂限价单;卡片显示 **挂E**,限价单 ID |
|
||||
| 失效 | 以**标记价**判断:做多且标记价 ≥ H,做空且标记价 ≤ L,且限价**未成交** → 撤销该限价单并结案 |
|
||||
| 成交后 | 挂交易所 TP/SL(含 Algo 通道条件单)→ 写入 **实盘下单监控**(`monitor_type=关键位监控`,`key_signal_type=斐波回调…`)→ 从关键位列表移除 |
|
||||
| 撤单 | 仅撤本条斐波的订单 ID,**不会**对该合约 `cancel_all_orders` / 全撤 Algo,避免误伤其他委托 |
|
||||
| 盈亏比 | 计划 RR 须 > `KEY_AUTO_MIN_PLANNED_RR`;0.618 理论约 1.6:1,0.786 约 3.7:1 |
|
||||
| 日成交量 | 与箱体/收敛相同,须在前 `KEY_DAILY_VOLUME_RANK_MAX` 名内方可添加 |
|
||||
|
||||
后台轮询:`check_fib_key_monitors()`;箱体/收敛仍走 `check_key_monitors()`.
|
||||
|
||||
手动删除关键位时,未成交斐波会先撤限价再删库.
|
||||
|
||||
### 箱体 / 收敛自动开仓(来源标注)
|
||||
|
||||
- 自动开仓写入 `order_monitors.key_signal_type`:`箱体突破` 或 `收敛突破`.
|
||||
- 持仓与交易记录展示「来源 · 信号类型」.
|
||||
|
||||
## 列表时间窗(UTC,全站顶栏)
|
||||
|
||||
共用模块:仓库根目录 `history_window_lib.py`(Gate / Binance 主站一致).
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 默认 | **UTC 当日**(`win_preset=utc_today`,从 UTC 0:00 至当前时刻) |
|
||||
| 可选 | 近 24 小时,近 7 天,自定义起止(UTC,`datetime-local`) |
|
||||
| 作用范围 | 关键位历史,交易记录列表,复盘记录 API,AI 历史 API,导出「交易记录」「关键位历史」 |
|
||||
| 与统计的关系 | **仅影响列表/导出**;**统计分析页仍按北京时间 `TRADING_DAY_RESET_HOUR`(默认 8:00)切交易日** |
|
||||
| 库内时间 | DB 存北京时间字符串;后端用 `utc_window_to_bj_sql_strings()` 换算后再 SQL 比较 |
|
||||
| 切换方式 | 顶栏「列表筛选(UTC)」→ 选预设 → **应用**(保留当前路由,如 `/records?win_preset=…`) |
|
||||
|
||||
查询参数示例:
|
||||
|
||||
- `?win_preset=utc_today`
|
||||
- `?win_preset=utc_last24h` / `utc_last7d`
|
||||
- `?win_preset=custom&from_utc=2026-05-18 00:00:00&to_utc=2026-05-19 12:00:00`
|
||||
|
||||
## 交易记录与复盘
|
||||
|
||||
- 交易记录盈亏以**本地估算**为准(平仓时按成交/计划价计算);盈亏列可标注 **估**.
|
||||
- 与币安 App 不一致时,请在「核对修改」或复盘中 **手工填写** `reviewed_pnl_amount` 覆盖展示(不再提供批量「同步交易所盈亏」).
|
||||
- **列表默认只显示当前 UTC 时间窗内**的记录(见上节);导出 CSV 同步该时间窗.
|
||||
- 表头 **「止损(开仓)」**:展示开仓快照 `initial_stop_loss`(无则回退 `stop_loss`);核对/复盘仍可用有效止损字段.
|
||||
- 平仓写入 `trade_records` 时:`stop_loss` 与 `initial_stop_loss` 均写入**开仓时止损快照**;`key_signal_type` 保留箱体/收敛/斐波来源(`fib_key_monitor_lib.key_signal_type_for_trade_record`).
|
||||
- **开仓类型**(`entry_reason`):机器单平仓入库时,若未手填,按 `key_signal_type` 自动映射(见下表);列表/导出「开仓类型」列 = 复盘核对值优先,否则入库值,否则按信号映射.
|
||||
|
||||
| `key_signal_type` | 自动写入的 `entry_reason` |
|
||||
|-------------------|---------------------------|
|
||||
| 箱体突破 | 关键位箱体突破 |
|
||||
| 收敛突破 | 关键位收敛突破 |
|
||||
| 斐波回调0.618 | 关键位斐波0.618 |
|
||||
| 斐波回调0.786 | 关键位斐波0.786 |
|
||||
|
||||
- 复盘表单 **开仓类型** 下拉新增上述四条固定文案(与趋势/波段类并列).
|
||||
- 复盘 **离场触发** 新增 **「止盈」**;从交易记录「填入复盘」时,若结果为「止盈/保本止盈/移动止盈/止损/手动平仓」会自动选中对应触发项,并按 `key_signal_type` 预填开仓类型.
|
||||
- 勾选「保存时自动生成多周期 K 线图」时:以 **平仓时间** 为锚点,各周期向前约 `ORDER_CHART_LIMIT`(默认 100)根 K 线(`_fetch_ohlcv_ending_at`),不再固定拉「最近 100 根」.
|
||||
- `/api/journals`,`/api/reviews` 支持同一时间窗 query,与列表一致.
|
||||
|
||||
### 导出(交易记录 v3)
|
||||
|
||||
- 文件名:`trade_records_v3_YYYYMMDD.csv`
|
||||
- 相对 v2 增加:`key_signal_type`,`initial_stop_loss`(及开仓快照列),`planned_rr`,`actual_rr`,`risk_amount`,交易所盈亏与时间字段等;末列「开仓类型」为有效展示文案.
|
||||
- 「关键位历史」导出同样受 UTC 时间窗限制.
|
||||
|
||||
## 实盘下单页
|
||||
|
||||
- 左列:实盘下单监控(表单,划转,规则).
|
||||
- 右列:实时持仓(独立模块).
|
||||
- **人工开仓门控**:计划盈亏比 < `MANUAL_MIN_PLANNED_RR`(默认 **1.4**)时前端弹窗 + 后端拒绝.
|
||||
- **移动保本**(勾选启用):监控轮询达到触发 RR 后,止损阶梯上移时**同步交易所**——**先撤**该合约全部 TP/SL(含 Algo 条件单)**再挂**新止损 + 原止盈(`replace_active_monitor_tpsl_on_exchange`).仅交易所成功后才写库;失败发企业微信告警.未配置实盘 API 时仍只更新本地.
|
||||
|
||||
## 统计分析页(`/stats`)
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 切日 | **北京时间**;交易日边界 = 每日 `TRADING_DAY_RESET_HOUR:00`(`.env` 默认 **8**) |
|
||||
| 品类下拉 | 页顶 **「统计品类」** 下拉切换(默认「全部交易」):全部交易,下单监控,关键位箱体突破,关键位收敛结构,关键位斐波0.618,关键位斐波0.786;一次只显示所选品类的日/周/月 |
|
||||
| URL | 切换后写入 `stats_segment=`(如 `all`,`manual`,`key_box`,`key_conv`,`key_fib618`,`key_fib786`),刷新 `/stats` 可保持选项 |
|
||||
| 每块指标 | 日 / 周 / 月:开单次数,平仓笔数,胜率,净盈亏,回撤,连续亏损等(与原口径一致) |
|
||||
| 开单次数 | 人工块:`monitor_type=下单监控` 且无 `key_signal_type`;关键位块:按 `order_monitors.key_signal_type` 计数 |
|
||||
| 不受 UTC 窗影响 | 统计始终基于库内全部已平仓记录,按北京交易日归类,**不**随顶栏 UTC 列表窗切换 |
|
||||
|
||||
## 持仓与计仓
|
||||
|
||||
- `MAX_ACTIVE_POSITIONS` 默认 **1**(可在 `.env` 调大).
|
||||
- 关键位自动开仓:在已有持仓时,若 `KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT=true`,按**首笔开仓前**交易账户资金快照计仓(字段 `trading_sessions.key_sizing_capital_snapshot`).
|
||||
|
||||
## 配置
|
||||
|
||||
详见 `.env.example` 中「关键位门控」「交易执行 / 人工风控」注释段.
|
||||
|
||||
## 自动备份(服务器)
|
||||
|
||||
- 脚本:`scripts/backup_data.sh`(`crypto.db` + `static/images`)
|
||||
- 定时:`scripts/install_backup_cron.sh` → 每天 **北京时间 0:00**,目录 **`/root/backups/<实例名>/YYYY-MM-DD/`**,保留 **30** 天
|
||||
- 详见 `部署文档.md` 第 5.4 节(自动备份)
|
||||
|
||||
## 数据库(启动时自动迁移)
|
||||
|
||||
`key_monitors` 斐波字段:`fib_limit_order_id`,`fib_entry_price`,`fib_stop_loss`,`fib_take_profit`,`fib_order_amount`,`fib_margin_capital`,`fib_leverage`.
|
||||
|
||||
`trade_records` / `order_monitors`:`key_signal_type`,`exchange_realized_pnl`,`exchange_opened_at`,`exchange_closed_at`,`exchange_sync_key`,`entry_reason`,`reviewed_entry_reason`,`initial_stop_loss`.
|
||||
|
||||
**历史数据**:本次**不做**旧记录的批量回填(`entry_reason` / `initial_stop_loss` / `key_signal_type` 等);仅**新产生**的平仓与复盘按新逻辑写入.旧行展示可回退已有字段.
|
||||
|
||||
## 涉及文件(便于排查)
|
||||
|
||||
| 路径 | 说明 |
|
||||
|------|------|
|
||||
| `history_window_lib.py` | UTC 时间窗解析与转北京时间 SQL 字符串 |
|
||||
| `fib_key_monitor_lib.py` | 斐波计算,`KEY_ENTRY_REASON_BY_SIGNAL`,`entry_reason_from_key_signal` |
|
||||
| `crypto_monitor_binance/app.py` | 列表筛选,统计分块,导出 v3,复盘 K 线锚点,入库逻辑 |
|
||||
| `crypto_monitor_binance/templates/index.html` | 顶栏时间窗,统计分块 UI,止损(开仓)列,复盘预填 |
|
||||
|
||||
## 升级步骤
|
||||
|
||||
1. `git pull` 后对比 `.env.example`,把新增变量合并进本地 `.env`.
|
||||
2. 在 VPS 上为 Binance / Gate / **各执行一次** `bash scripts/install_backup_cron.sh`(若尚未安装).
|
||||
3. 重启 Binance 实例(如 `pm2 restart crypto_binance`);SQLite 会自动 `ALTER` 缺列(斐波,交易所盈亏,`entry_reason` 等).
|
||||
4. 浏览器强刷(Ctrl+F5)避免旧版 `index.html` 缓存.
|
||||
5. 打开任意页确认顶栏出现 **「列表筛选(UTC)」**;`/stats` 可见分品类统计与「北京 8:00 切日」说明.
|
||||
6. 建议先用测试币验证斐波:限价挂出,标记价失效撤单,成交后 TP/SL 与订单监控是否正常;平仓后检查交易记录止损(开仓)与开仓类型.
|
||||
@@ -231,3 +231,8 @@ AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
||||
|
||||
APP_TIMEZONE=Asia/Shanghai
|
||||
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日;开仓整点限制见 TRADING_DAY_RESET_OPEN_GUARD_ENABLED
|
||||
|
||||
# --- 软件授权(整机共用,也可写仓库根 license.env)---
|
||||
# LICENSE_API_URL=https://sq.bz121.com
|
||||
# LICENSE_CLIENT_KEY=与授权站 CLIENT_API_KEY 相同
|
||||
# LICENSE_OFFLINE_GRACE_HOURS=72
|
||||
|
||||
@@ -1,90 +1,11 @@
|
||||
# crypto_monitor_gate
|
||||
# crypto_monitor_gate(用户版)
|
||||
|
||||
基于 **Flask** 的加密货币 **下单监控 / 关键位监控 / 交易复盘** 小系统,行情与实盘接口统一走 **Gate.io USDT 永续**,通过 **ccxt** 访问.
|
||||
Gate USDT 永续实例。
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| **[使用说明.md](./使用说明.md)** | 日常怎么用:登录,关键位四类,手工开仓,单仓与微信等 |
|
||||
| **[关键位自动下单说明.md](./关键位自动下单说明.md)** | 关键位自动开仓的 RR,止盈止损,结案原因与 `.env` |
|
||||
| **[部署文档.md](./部署文档.md)** | Ubuntu,PM2,**SSH SOCKS** 访问 Gate API 等 |
|
||||
|
||||
另:**Binance U 本位** 对等实现见同级的 **`crypto_monitor_binance`** 仓库.
|
||||
|
||||
---
|
||||
|
||||
## 功能概要
|
||||
|
||||
- **关键位监控**:5m 收线硬条件,企业微信推送;**箱体 / 收敛** 在 RR 达标时可 **自动市价开仓**(见专门文档);**阻力 / 支撑** 仅单次提醒结案
|
||||
- **下单监控**:本地风控(含移动保本),止盈/止损触达后轮询尝试平仓并记账
|
||||
- **实盘(可选)**:`LIVE_TRADING_ENABLED=true` 且配置 **`GATE_API_KEY` / `GATE_API_SECRET`** 时,支持开仓,挂单 TP/SL,余额与划转(权限依账户而定)
|
||||
- **止盈止损(Gate)**:市价成交后经 **`_gate_place_tp_sl_orders`** 挂单;优先 **仓位类 `price_orders`**(受 `GATE_TPSL_USE_POSITION_ORDER`,`GATE_TPSL_PRICE_TYPE`,`GATE_POS_MODE` 等影响)
|
||||
|
||||
---
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Python 3.10+(建议)
|
||||
- 依赖:`flask`,`requests`,`ccxt`,`werkzeug`,`PySocks`(经 SOCKS 代理时);`Pillow`(K 线导出等可选用)
|
||||
|
||||
安装示例:
|
||||
- **部署**:[部署文档.md](./部署文档.md) · 仓库根 [docs/ubuntu-server.md](../docs/ubuntu-server.md)
|
||||
- **授权**:启动后打开 `/license`,见 [docs/license.md](../docs/license.md)
|
||||
- **配置**:复制 `.env.example` → `.env`
|
||||
|
||||
```bash
|
||||
cd /opt/crypto_monitor_user/crypto_monitor_gate
|
||||
source .venv/bin/activate
|
||||
pip install -r ../requirements.txt
|
||||
```
|
||||
|
||||
## 配置(`.env.example` → `.env`)
|
||||
|
||||
- **`.env.example`**:模板(可提交 Git);首次:`cp .env.example .env` 后编辑.
|
||||
- **`.env`**:本机真实配置(勿提交);`git pull` 不覆盖;升级前建议备份(见《部署文档》§5.2).
|
||||
|
||||
项目启动时加载**仓库根目录**下的 `.env`.常用项:
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `GATE_API_KEY` / `GATE_API_SECRET` | Gate API(需合约与对应权限) |
|
||||
| `LIVE_TRADING_ENABLED` | `true` 允许真实下单;`false` 仅本地与推送逻辑 |
|
||||
| `GATE_MARGIN_MODE` / `GATE_POS_MODE` | 保证金与持仓模式 |
|
||||
| `GATE_TPSL_USE_POSITION_ORDER` / `GATE_TPSL_PRICE_TYPE` 等 | 条件止盈止损行为 |
|
||||
| `GATE_SOCKS_PROXY` | 可选;直连不稳时 SSH 动态转发(详见部署文档) |
|
||||
| `APP_PASSWORD` / `FLASK_SECRET_KEY` | Web 登录与 Session |
|
||||
| `WECHAT_WEBHOOK` | 企业微信机器人 |
|
||||
| `EXCHANGE_DISPLAY_NAME` / `GATE_ACCOUNT_LABEL` | 页面与推送展示的账户文案 |
|
||||
|
||||
其余见 **`.env.example` 内注释** 或 **`app.py` 顶部默认值**.
|
||||
|
||||
## 运行
|
||||
|
||||
生产使用 **PM2**(`ecosystem.config.cjs`).调试:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate && python app.py
|
||||
```
|
||||
|
||||
见 [docs/ubuntu-server.md](../docs/ubuntu-server.md).
|
||||
|
||||
端口由 **`APP_PORT`** 控制(未设置默认 **5000**).浏览器登录 **`/login`**,口令为 **`APP_PASSWORD`**.
|
||||
|
||||
## 部署(Linux / PM2 / SSH SOCKS)
|
||||
|
||||
见 **[部署文档.md](./部署文档.md)**.
|
||||
|
||||
## 自检脚本
|
||||
|
||||
```bash
|
||||
python scripts/verify_gate_funding.py
|
||||
```
|
||||
|
||||
用于核对密钥前缀(不落 Secret),资金/合约可读性等(需网络与权限).
|
||||
|
||||
## 数据与脚本
|
||||
|
||||
- 默认 SQLite:由 **`DB_PATH`** 指定(常见为项目下 `crypto.db`)
|
||||
- `scripts/fix_breakeven_labels.py`:修正「止损」但盈亏为正的记录标签(参见部署文档说明)
|
||||
|
||||
## 风险与合规
|
||||
|
||||
实盘有亏损风险.请确认 API 权限,IP 白名单,杠杆与保证金模式与 **Gate.io** 后台一致,并遵守当地法律法规与交易所用户协议.
|
||||
|
||||
@@ -305,6 +305,12 @@ app.secret_key = os.getenv("FLASK_SECRET_KEY", "crypto_monitor_2026_secret_key")
|
||||
from lib.instance.instance_embed_lib import attach_embed_templates
|
||||
|
||||
attach_embed_templates(app, _REPO_ROOT)
|
||||
try:
|
||||
from lib.license.flask_gate import install_license_gate
|
||||
|
||||
install_license_gate(app)
|
||||
except Exception as _lic_err:
|
||||
print(f"[license] gate: {_lic_err}")
|
||||
|
||||
# ====================== 登录配置 ======================
|
||||
USERNAME = os.getenv("APP_USERNAME", "dekun")
|
||||
|
||||
Binary file not shown.
@@ -1,147 +0,0 @@
|
||||
# 使用说明
|
||||
|
||||
**本文件对应仓库:`crypto_monitor_gate`(Gate.io USDT 永续).**
|
||||
功能,界面与 **Binance U 本位版**(目录 `crypto_monitor_binance`)基本一致,差异主要在 **`.env` 里交易所密钥与部分参数名**(`GATE_*` / `BINANCE_*`),文末有对照.
|
||||
|
||||
**更细的部署(SSH 代理,PM2,依赖安装)** 见同目录 **`部署文档.md`**.
|
||||
**关键位自动开仓的规则,RR,结案原因** 见 **`关键位自动下单说明.md`**.
|
||||
|
||||
---
|
||||
|
||||
## 1. 它能做什么
|
||||
|
||||
面向个人盘面的 **Web 控制台**,主要能力包括:
|
||||
|
||||
| 模块 | 说明 |
|
||||
|------|------|
|
||||
| **关键位监控** | 录入上/下沿与类型,按 **5m 收线** 做硬条件过滤;符合条件后 **企业微信** 提醒,部分类型可 **自动市价开仓**(见第 4 节与专门文档). |
|
||||
| **实盘下单监控** | 手工填止损/止盈,**以损定仓** 市价开单,挂上条件止盈止损,并在页面跟踪浮盈亏,保本逻辑等. |
|
||||
| **交易记录 / 复盘** | 平仓结果,盈亏,错过的单等归档与导出;可选 **AI 复盘**(见 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)). |
|
||||
| **策略交易** | 顶栏 `/strategy`:趋势回调 + 顺势加仓双栏;见 [策略交易说明.md](../策略交易说明.md). |
|
||||
|
||||
后台按 **`MONITOR_POLL_SECONDS`**(默认几秒)轮询行情与监控逻辑.**切勿**在未理解规则时同时运行两套程序共用一个实盘账户.
|
||||
|
||||
---
|
||||
|
||||
## 2. 运行前必须配置(`.env`)
|
||||
|
||||
首次在本目录执行 **`cp .env.example .env`**,再编辑 `.env`(`.env` 勿提交 Git;`git pull` 不会改你的 `.env`,升级前建议 `cp .env .env.backup.$(date +%Y%m%d)`).
|
||||
|
||||
至少检查以下项(具体键名以 **`.env.example`** 为准):
|
||||
|
||||
| 类别 | 说明 |
|
||||
|------|------|
|
||||
| **登录网页** | `APP_PASSWORD`:打开站点后的登录口令.`FLASK_SECRET_KEY`:Session 密钥,请勿使用默认值. |
|
||||
| **企业微信** | `WECHAT_WEBHOOK`:告警与关键位推送机器人的 Webhook. |
|
||||
| **是否真下单** | `LIVE_TRADING_ENABLED=false`:**不会**向交易所发送开仓指令(适合测试流程).改为 `true` 且密钥正确才会实盘. |
|
||||
| **交易所 API** | **本仓库:** `GATE_API_KEY`,`GATE_API_SECRET`;合约相关见 `GATE_MARGIN_MODE`,`GATE_POS_MODE`,`GATE_TPSL_*` 等.**勿**把 `.env` 提交到 Git. |
|
||||
| **关键位 RR / 止损外扩** | `KEY_AUTO_MIN_PLANNED_RR`,`KEY_STOP_OUTSIDE_BREAKOUT_PCT`(详见 `关键位自动下单说明.md`). |
|
||||
| **AI 复盘** | `AI_PROVIDER=openai`(默认)或 `ollama`;变量见 `.env.example` 与 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md). |
|
||||
|
||||
网络不稳定时可为 Gate 配置 **`GATE_SOCKS_PROXY`** 等(见 **`部署文档.md`**).
|
||||
|
||||
---
|
||||
|
||||
## 3. 如何启动与登录
|
||||
|
||||
1. 按 **`部署文档.md`** 建好虚拟环境,安装依赖(如 `flask`,`requests`,`ccxt`,按需 `Pillow`,`PySocks` 等),配置好 `.env`.
|
||||
2. 启动 Flask 应用(本仓库可用 **`ecosystem.config.cjs`** 交给 PM2,或本地 `python app.py` / `flask run`,以你当前脚本为准).
|
||||
3. 浏览器访问站点,打开 **`/login`**,使用 **`.env` 里的 `APP_PASSWORD`** 登录.
|
||||
|
||||
登录后顶栏:**关键位监控** | **实盘下单** | **策略交易**(`/strategy`)| **策略交易记录**(`/strategy/records`)| **交易记录与复盘** | **统计分析**.
|
||||
|
||||
---
|
||||
|
||||
## 4. 关键位监控(顶栏「关键位监控」→ `/key_monitor`)
|
||||
|
||||
### 4.1 添加一条关键位
|
||||
|
||||
1. **币种**:如 `BTC` 或 `BTC/USDT`(会规范成内部符号).
|
||||
2. **类型**(必选其一):
|
||||
|
||||
| 类型 | 行为摘要 |
|
||||
|------|----------|
|
||||
| **箱体突破** | 通过门控且计划 RR 达标 → **自动市价开仓**(需 `LIVE_TRADING_ENABLED=true` 且无其他持仓占位).结案后本条从列表消失并记入历史. |
|
||||
| **收敛突破** | 同上(自动开仓类). |
|
||||
| **关键阻力位** | **不自动开仓**;触发后 **发 1 次微信**,然后本条 **结案进历史**. |
|
||||
| **关键支撑位** | 同上(仅提醒). |
|
||||
| **回调触价开仓** | **不挂交易所限价**;标记价回调触达 E 后 **下一轮询市价开仓**(RR 门槛同 `KEY_AUTO_MIN_PLANNED_RR`);有效期 **24h** |
|
||||
| **突破触价开仓** | **不挂交易所限价**;标记价 **穿越 E 立即市价开仓**;先触 SL/TP 侧失效;有效期 **24h** |
|
||||
|
||||
3. **方向**:做多 / 做空(触价开仓 / 箱体 / 收敛 / 斐波必选;阻力/支撑不选).
|
||||
4. **价位**:箱体/收敛/阻力/支撑填 **上沿 / 下沿**;触价开仓填 **入场 E / 止损 SL / 止盈 TP**.
|
||||
|
||||
**限制:**
|
||||
活跃持仓数达到 **`MAX_ACTIVE_POSITIONS`**(默认 1)时,**不允许**再添加「**箱体突破** / **收敛突破**」;仍可添加「**关键阻力位 / 支撑位**」.
|
||||
若 **4h EMA55** 与你的方向逆势,页面会 **额外 Flash 提示**,**不阻挡**提交.
|
||||
|
||||
### 4.2 触发后会发生什么(简版)
|
||||
|
||||
- **箱体 / 收敛**:门控通过后计算计划 SL/TP 与 RR;不达标则 **微信说明 + `rr_insufficient` 结案**;达标则尝试 **市价开仓**,成功 **`auto_opened`**,失败 **`exchange_failed`**——均 **不重试同一关键位**.
|
||||
- **阻力 / 支撑**:仅 **单次推送** → **`key_level_alert_only`** 结案.
|
||||
|
||||
详细公式,结案字段,与企业微信文案口径见 **`关键位自动下单说明.md`**.
|
||||
|
||||
### 4.3 列表与历史
|
||||
|
||||
- 当前条目可 **删除**(会按规则记入历史的情形见页面说明).
|
||||
- **关键位历史**:已结案记录;可配合导出链接(若有)做备份.
|
||||
|
||||
---
|
||||
|
||||
## 5. 实盘下单(顶栏「实盘下单」→ `/trade`)
|
||||
|
||||
用于 **自己点按钮** 开单:
|
||||
|
||||
- 持仓上限由 **`MAX_ACTIVE_POSITIONS`** 控制(默认 1,与关键位自动单共用).
|
||||
- **人工开仓**时计划盈亏比不得低于 **`MANUAL_MIN_PLANNED_RR`**(默认 1.4:1),否则页面弹窗且后端拒绝.
|
||||
- 填写币种,方向,杠杆(可选),止损/止盈(价格或百分比按表单说明).
|
||||
- 勾选是否启用 **移动保本** 等行为以 `.env`/页面默认值为准.
|
||||
|
||||
平仓通过页面 **平仓**(或等价入口),会从交易所市价处理并更新记录.**删除/误操作可能造成真实盈亏**,请先确认环境与方向.
|
||||
|
||||
开仓成功后持仓卡片上会显示 **「来源」**:手工单一般为 **下单监控**;来自关键位自动单的为 **关键位监控**.
|
||||
|
||||
---
|
||||
|
||||
## 6. 企业微信会看到什么
|
||||
|
||||
- 关键位:按类型与结案结果推送(RR 不足,下单失败,自动开仓成功,仅阻力支撑提醒等),**每条关键位结案路径原则上一条主推送**(详见 `关键位自动下单说明.md`).
|
||||
- 手工开仓,平仓,部分异常也会在规则满足时推送(以代码与配置为准).
|
||||
|
||||
若未配置 **`WECHAT_WEBHOOK`** 或网络失败,可能只是看不到推送,不代表逻辑未执行;要紧操作请以 **交易所端持仓与挂单** 为准核对.
|
||||
|
||||
---
|
||||
|
||||
## 7. 强烈建议的风险与运维习惯
|
||||
|
||||
1. **先用 `LIVE_TRADING_ENABLED=false`** 验证页面,录入,推送,再开小资金开实盘.
|
||||
2. **API 权限**:仅开所需合约权限;勿泄露密钥;定期轮换.
|
||||
3. **单进程控盘**:同一账户避免本程序与其他机器人 **重复开仓**.
|
||||
4. **自动备份**:服务器上执行 `bash scripts/install_backup_cron.sh`(每天北京时间 0:00 → `/root/backups`,保留 30 天);升级前也可 `bash scripts/backup_data.sh` 手动跑一次.
|
||||
5. **升级代码后**:启动时会跑 **数据库迁移**(如新列 `order_monitors.monitor_type`);首次启动关注一下日志或无报错页面.
|
||||
|
||||
---
|
||||
|
||||
## 8. 常见问题(简要)
|
||||
|
||||
| 现象 | 可自查 |
|
||||
|------|--------|
|
||||
| 关键位永远不触发 | 5m 门控是否全通过(页面门控摘要),币种日成交量是否在规则内,`KLINE_TIMEFRAME`. |
|
||||
| 有信号但不自动开仓 | `LIVE_TRADING_ENABLED`,`KEY_AUTO_MIN_PLANNED_RR`,计划 RR,是否已有持仓,API/余额报错(微信或日志). |
|
||||
| 加不了箱体/收敛 | 是否已有活跃持仓;先平仓或改用「阻力/支撑位」仅提醒. |
|
||||
| 推送收不到 | `WECHAT_WEBHOOK`,企业微信机器人配额与网络. |
|
||||
|
||||
---
|
||||
|
||||
## 9. Binance 版(`crypto_monitor_binance`)差异速查
|
||||
|
||||
| 项目 | Gate 本仓库 | Binance 版 |
|
||||
|------|-------------|------------|
|
||||
| API 变量 | `GATE_API_KEY`,`GATE_API_SECRET`,`GATE_*` | `BINANCE_API_KEY`,`BINANCE_API_SECRET`,`BINANCE_*` |
|
||||
| 实盘开关 | `LIVE_TRADING_ENABLED`(通用) | 同上 |
|
||||
| 止盈止损挂载路径 | `_gate_place_tp_sl_orders` 与 `GATE_TPSL_*` | `_binance_place_tp_sl_orders`(U 本位条件单) |
|
||||
| 资金显示舍入 | 以本仓库为准 | 与 **`FUNDS_DECIMALS`** 等一致 |
|
||||
| 专门文档 | **`关键位自动下单说明.md`**(各仓库有一份,开头标明交易所) | 同左 |
|
||||
|
||||
操作流程(登录,关键位四类,手工单,单仓)**两份程序一致**:换目录,换 `.env` 即可对照使用.
|
||||
@@ -1,192 +0,0 @@
|
||||
# 关键位监控说明(自动开仓 + 人工盯盘)
|
||||
|
||||
**适用:Gate / Binance / OKX 三所实例(共用 `lib/key_monitor/key_auto_order_lib.py`)**
|
||||
|
||||
## 环境开关 `KEY_AUTO_ORDER_ENABLED`(默认 `false`)
|
||||
|
||||
| 计仓模式 | 开关 | 关键位程序自动单 |
|
||||
|----------|------|------------------|
|
||||
| `risk`(以损定仓) | `false` | **全部关闭**(含触价);支撑/阻力微信提醒仍可用 |
|
||||
| `risk` | `true` | 箱体/收敛/斐波/假突破/触价均可自动(旧行为) |
|
||||
| `full_margin`(全仓) | `false` | 全部关闭(含触价) |
|
||||
| `full_margin` | `true` | **仅触价**自动;箱体/斐波等仍禁止 |
|
||||
|
||||
**不受本开关影响:** 人工实盘下单,关键支撑/阻力提醒,**顺势加仓**(`risk` 下),趋势回调(`risk` 下).全仓模式下策略自动仍禁止.
|
||||
|
||||
修改 `.env` 后须 **重启 PM2**.复盘「开仓类型」与统计分段会随开关联动隐藏关键位选项.
|
||||
|
||||
---
|
||||
|
||||
**适用:`crypto_monitor_gate`(Gate U 本位永续)**
|
||||
Binance / OKX 见各自目录下同名文档;共享逻辑在 `lib/key_monitor/`.
|
||||
|
||||
本文档与 `.env`,`check_key_monitors`,`add_key`,`_key_hard_checks`,`_process_key_rs_level_alert` 一致.
|
||||
|
||||
---
|
||||
|
||||
## 一,监控类型总览
|
||||
|
||||
| 录入类型 | 录入时选方向 | 自动市价开仓 | 触发与结案 |
|
||||
|----------|--------------|--------------|------------|
|
||||
| **箱体突破** | **必选** 多/空 | **是**(门控 + RR) | 条件满足 → 开仓或 `rr_insufficient` / `exchange_failed` → **一次性删除** |
|
||||
| **收敛突破** | **必选** 多/空 | **是**(同上) | 同上 |
|
||||
| **关键阻力位** | **不选**(`direction=watch`) | **否** | 5m 收盘突破上/下沿 → 微信 **3 次** → `key_level_alert_done` |
|
||||
| **关键支撑位** | **不选** | **否** | 同上(与阻力位**相同规则**:填上沿+下沿,程序双向监控) |
|
||||
| 斐波回调 0.618 / 0.786 | 必选 | 限价挂单逻辑 | 见斐波说明(**不在下文展开**) |
|
||||
| **回调触价开仓** | **必选** 多/空 | **程序盯价 → 回调触 E 后市价** | 见下文 **§四** |
|
||||
| **突破触价开仓** | **必选** 多/空 | **程序盯价 → 穿越 E 立即市价** | 见下文 **§四** |
|
||||
|
||||
**添加时(箱体/收敛/斐波/触价):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30)**;上沿 **>** 下沿(触价开仓填 E/SL/TP,上下沿仅作展示占位).
|
||||
|
||||
---
|
||||
|
||||
## 二,关键阻力位 / 关键支撑位(人工盯盘)
|
||||
|
||||
### 2.1 录入
|
||||
|
||||
- 填写 **上沿 `upper`** 与 **下沿 `lower`**(程序同时监控两侧,**无法预先判定**做多还是做空).
|
||||
- 页面 **不显示,不要求** 方向;库中 `direction` 初始为 `watch`,**首次突破后** 写入 `long`(向上突破上沿)或 `short`(向下突破下沿).
|
||||
|
||||
### 2.2 触发(极简)
|
||||
|
||||
- 周期:**`KLINE_TIMEFRAME`(默认 5m)最近一根已闭合 K** 的 **收盘价**(非影线).
|
||||
- **向上突破上沿:** `收盘 > upper` → 推断方向 **多 / 向上**,本次监控任务开始按节奏提醒.
|
||||
- **向下突破下沿:** `收盘 < lower` → 推断方向 **空 / 向下**,本次任务同样开始提醒.
|
||||
- **任一侧突破即结束本条监控周期**(不会在突破后再等待另一侧;上沿,下沿谁先满足用谁,同根 K 仅可能满足一侧).
|
||||
|
||||
**不参与:** 量能,二确 K,越过幅度下限,日成交排名(运行时),计划 RR,自动开仓.
|
||||
|
||||
### 2.3 微信提醒次数
|
||||
|
||||
| 配置 | 默认 | 含义 |
|
||||
|------|------|------|
|
||||
| `KEY_ALERT_MAX_TIMES` | `3` | 突破后最多推送 3 次 |
|
||||
| `KEY_ALERT_INTERVAL_MINUTES` | `5` | 相邻两次推送至少间隔 5 分钟 |
|
||||
|
||||
- 第 1 次:首次检测到突破的当次轮询(若已闭合 5m 满足条件).
|
||||
- 第 2,3 次:仅按间隔推送(**不要求**价格仍在箱外).
|
||||
- 第 3 次推送后:写入 `key_monitor_history`,`close_reason=**key_level_alert_done**`,从 `key_monitors` **删除**.
|
||||
|
||||
### 2.4 与箱体/收敛的区别
|
||||
|
||||
| 项目 | 阻力/支撑 | 箱体/收敛 |
|
||||
|------|-----------|-----------|
|
||||
| 方向 | 程序推断 | 人工选择 |
|
||||
| K 线根数 | 1 根闭合 5m | 2 根(突破 K + 确认 K) |
|
||||
| 提醒次数 | 3 次后结案 | 自动单:触发后 1 次业务推送并结案 |
|
||||
|
||||
---
|
||||
|
||||
## 三,箱体突破 / 收敛突破(自动开仓)
|
||||
|
||||
### 3.1 K 线结构(默认索引)
|
||||
|
||||
| 角色 | 环境变量 | 默认 | 含义 |
|
||||
|------|----------|------|------|
|
||||
| 突破 K | `KEY_CONFIRM_BREAKOUT_BAR` | `-2` | 倒数第 2 根闭合 K |
|
||||
| 确认 K | `KEY_CONFIRM_BAR` | `-1` | 倒数第 1 根闭合 K |
|
||||
|
||||
### 3.2 硬门控(须全部通过)
|
||||
|
||||
1. **有效突破(收盘越界)**
|
||||
- 多:`突破 K 收盘 > upper`
|
||||
- 空:`突破 K 收盘 < lower`
|
||||
|
||||
2. **突破越过幅度(仅下限)**
|
||||
- 多:`(突破 K 收盘 − upper) / upper × 100 > KEY_BREAKOUT_AMP_MIN_PCT`(默认 **0.03%**)
|
||||
- 空:`(lower − 突破 K 收盘) / lower × 100 >` 同上
|
||||
- **无上限**;突破过猛由 **计划 RR** 过滤.
|
||||
- **不再**使用 K 线实体占开盘价比例;`KEY_BREAKOUT_AMP_MAX_PCT` **已不参与门控**.
|
||||
|
||||
3. **确认 K 不进箱体**
|
||||
- 多:确认 K 收盘 **`> upper`**(不得在 `[lower, upper]` 内)
|
||||
- 空:确认 K 收盘 **`< lower`**
|
||||
|
||||
4. **量能:** 突破 K 成交量 > 前 `KEY_VOLUME_MA_BARS`(默认 20)根均量 × `KEY_VOLUME_RATIO_MIN`(默认 1.3)
|
||||
|
||||
5. **日成交量排名:** 运行时仍须前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30)
|
||||
|
||||
6. **计划 RR(最后经济门控):** 按确认 K 收盘 **E** 计算 SL/TP 后,`RR` **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5)才市价开仓
|
||||
|
||||
### 3.3 止损 / 止盈(确认 K 收盘为 E)
|
||||
|
||||
箱体高 **H = |upper − lower|**.止损锚在 **突破 K 极值** 外侧:
|
||||
|
||||
| 方向 | 止损(标准/趋势方案) |
|
||||
|------|------------------------|
|
||||
| 多 | 突破 K **最低价** × (1 − `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
|
||||
| 空 | 突破 K **最高价** × (1 + `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
|
||||
|
||||
止盈方案见下表(与改版前一致):
|
||||
|
||||
| 方案 | `sl_tp_mode` | 多:SL / TP | 空:SL / TP |
|
||||
|------|--------------|-------------|-------------|
|
||||
| 标准突破 | `standard` | 突破 K 低外侧% / **E+H** | 突破 K 高外侧% / **E−H** |
|
||||
| 箱体 1R·止盈 1.5H | `box_1p5` | **E−H** / **E+1.5×H** | **E+H** / **E−1.5×H** |
|
||||
| 趋势单·自填止盈 | `trend_manual` | 突破 K 低 × (1−`KEY_TREND_STOP_OUTSIDE_PCT`%) / **录入止盈** | 突破 K 高外侧% / **录入止盈** |
|
||||
|
||||
### 3.4 一次性结案(`close_reason`)
|
||||
|
||||
| `close_reason` | 含义 |
|
||||
|----------------|------|
|
||||
| `box_opposite_break` | 标记价先突破反向边界(多:≤下沿;空:≥上沿) |
|
||||
| `rr_insufficient` | 门控通过但 RR 不达标或 SL/TP 几何无效 |
|
||||
| `exchange_failed` | RR 达标但实盘/交易所等原因未开仓 |
|
||||
| `auto_opened` | RR 达标且市价开仓成功 |
|
||||
| `key_level_alert_done` | 阻力/支撑 **3 次提醒** 完成 |
|
||||
|
||||
---
|
||||
|
||||
## 四,回调 / 突破触价开仓(程序触价,无交易所挂单)
|
||||
|
||||
### 4.1 录入
|
||||
|
||||
- **回调触价开仓**:方向必选多/空;填写 **计划入场价 E**,**止损 SL**,**止盈 TP**(做多须 `SL < E < TP`).
|
||||
- **突破触价开仓**:同上;添加时当前价须在突破方向一侧(做多:价低于 E;做空:价高于 E).
|
||||
- 计划 RR 以 **E** 为基准,须 **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5).
|
||||
- 可选移动保本,时间平仓;**全仓杠杆模式**下可用.
|
||||
|
||||
### 4.2 触发与结案
|
||||
|
||||
| 类型 | 触发条件(标记价) |
|
||||
|------|-------------------|
|
||||
| **回调触价** | 做多 `≤ E`;做空 `≥ E` → 下一轮询市价开仓 |
|
||||
| **突破触价** | 做多**向上穿越** E;做空**向下穿越** E → **立即**市价开仓 |
|
||||
|
||||
- 未成交前标记价先触 **TP 侧** → `trigger_tp_invalidate`.
|
||||
- **突破触价**另:未穿越 E 先触 **SL 侧** → `trigger_sl_invalidate`.
|
||||
- **24h** 未触发 → `trigger_entry_expired`.
|
||||
- 成功 → `trigger_entry_filled`;触发后开仓失败 → `trigger_exchange_failed`.
|
||||
|
||||
### 4.3 计仓与占位
|
||||
|
||||
- **以损定仓**:按 E,SL 反推保证金,触发时重算;**全仓杠杆**:可用×缓冲比例,BTC/ETH 10x,其它 5x.
|
||||
- **占当日开仓意图**(已开 + 待触发),未成交不占持仓;同币仅 1 条触价监控(含回调/突破).
|
||||
|
||||
共享逻辑:`trigger_entry_key_monitor_lib.py`;轮询:`check_trigger_entry_key_monitors`.
|
||||
|
||||
---
|
||||
|
||||
## 五,环境与参数(`.env` 摘要)
|
||||
|
||||
| 变量 | 箱体/收敛 | 阻力/支撑 |
|
||||
|------|-----------|-----------|
|
||||
| `KEY_BREAKOUT_AMP_MIN_PCT` | 突破越过下限(默认 0.03) | 不用 |
|
||||
| `KEY_BREAKOUT_AMP_MAX_PCT` | **已废弃门控** | 不用 |
|
||||
| `KEY_VOLUME_*` / `KEY_CONFIRM_*` | 用 | 不用 |
|
||||
| `KEY_AUTO_MIN_PLANNED_RR` | 用 | 不用 |
|
||||
| `KEY_ALERT_MAX_TIMES` / `KEY_ALERT_INTERVAL_MINUTES` | 不用 | 用(默认 3 次 / 5 分钟) |
|
||||
| `KEY_DAILY_VOLUME_RANK_MAX` | 添加时 + 运行时 | **仅添加时** |
|
||||
|
||||
---
|
||||
|
||||
## 六,相关代码
|
||||
|
||||
| 说明 | 位置 |
|
||||
|------|------|
|
||||
| 共享判定 | `key_monitor_lib.py` |
|
||||
| 主循环 | `check_key_monitors` |
|
||||
| 自动门控 | `_key_hard_checks` |
|
||||
| 阻力/支撑提醒 | `_process_key_rs_level_alert` |
|
||||
| 录入 | `add_key` |
|
||||
| 开仓 | `_market_open_for_key_monitor` |
|
||||
@@ -1,148 +0,0 @@
|
||||
# 界面与风控更新说明(Gate 实例)
|
||||
|
||||
## 顶栏导航(4 项)
|
||||
|
||||
| 顺序 | 名称 | 路由 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | 关键位监控 | `/key_monitor` | 关键位添加,实时门控,历史 |
|
||||
| 2 | 实盘下单 | `/trade` | 人工开仓,划转,实时持仓(**默认首页** `/` → `/trade`) |
|
||||
| 3 | 交易记录与复盘 | `/records` | 交易记录,复盘表单,AI 历史(受顶栏 UTC 时间窗筛选) |
|
||||
| 4 | 统计分析 | `/stats` | 按北京时间交易日切日 + 分品类统计块 |
|
||||
|
||||
## 关键位监控页
|
||||
|
||||
- 标题去掉「5m」;规则条从 `.env` 读取(周期,确认K,量能,自动开仓盈亏比,日成交量排名).
|
||||
- 左列:活跃关键位,**pos-card** 样式展示现价/距上沿/距下沿/门控.
|
||||
- 右列:关键位历史(失效/结案),与左列等高滚动;**受顶栏 UTC 列表时间窗筛选**(默认 UTC 当日).
|
||||
- 监控类型新增:**斐波回调0.618**,**斐波回调0.786**(与 Binance 主站同一套规则,计算逻辑见仓库根目录 `fib_key_monitor_lib.py`).
|
||||
|
||||
### 斐波关键位监控(方案 A:交易所限价)
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 同币互斥 | 每个币种只能有一条斐波监控(0.618 与 0.786 不可并存) |
|
||||
| 上下沿 | 上沿 **H**,下沿 **L**(须 H > L) |
|
||||
| 挂单价 E | **做多** `E = H − ratio × (H − L)`(自 H 向下回撤);**做空** `E = L + ratio × (H − L)`(自 L 向上反弹) |
|
||||
| 做多 | 限价 @ E,止损 L,止盈 H |
|
||||
| 做空 | 限价 @ E,止损 H,止盈 L |
|
||||
| 添加后 | **立即**在 Gate 挂限价单;卡片显示 **挂E**,限价单 ID |
|
||||
| 失效 | 以**标记价**判断:做多且标记价 ≥ H,做空且标记价 ≤ L,且限价**未成交** → 撤销该限价单并结案(不写历史开仓) |
|
||||
| 成交后 | 按仓位挂交易所 TP/SL → 写入 **实盘下单监控**(`monitor_type=关键位监控`,`key_signal_type=斐波回调0.618/0.786`)→ 从关键位列表移除 |
|
||||
| 撤单 | 仅撤本条斐波的 `fib_limit_order_id`,**不会** `cancel_all`,避免误伤其他委托 |
|
||||
| 盈亏比 | 计划 RR 须 > `KEY_AUTO_MIN_PLANNED_RR`(与箱体/收敛一致);0.618 理论约 1.6:1,0.786 约 3.7:1 |
|
||||
| 日成交量 | 与箱体/收敛相同,须在前 `KEY_DAILY_VOLUME_RANK_MAX` 名内方可添加 |
|
||||
|
||||
后台轮询:`check_fib_key_monitors()`(标记价失效 / 成交检测);箱体/收敛仍走 `check_key_monitors()`,互不干扰.
|
||||
|
||||
手动删除关键位时,若斐波限价尚未成交,会先撤交易所限价再删库记录.
|
||||
|
||||
### 箱体 / 收敛自动开仓(来源标注)
|
||||
|
||||
- 自动开仓写入 `order_monitors.key_signal_type`:`箱体突破` 或 `收敛突破`.
|
||||
- 持仓卡片,交易记录列表会显示「来源 · 信号类型」.
|
||||
|
||||
## 列表时间窗(UTC,全站顶栏)
|
||||
|
||||
共用模块:仓库根目录 `history_window_lib.py`(Gate / Binance 主站一致).
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 默认 | **UTC 当日**(`win_preset=utc_today`,从 UTC 0:00 至当前时刻) |
|
||||
| 可选 | 近 24 小时,近 7 天,自定义起止(UTC,`datetime-local`) |
|
||||
| 作用范围 | 关键位历史,交易记录列表,复盘记录 API,AI 历史 API,导出「交易记录」「关键位历史」 |
|
||||
| 与统计的关系 | **仅影响列表/导出**;**统计分析页仍按北京时间 `TRADING_DAY_RESET_HOUR`(默认 8:00)切交易日** |
|
||||
| 库内时间 | DB 存北京时间字符串;后端用 `utc_window_to_bj_sql_strings()` 换算后再 SQL 比较 |
|
||||
| 切换方式 | 顶栏「列表筛选(UTC)」→ 选预设 → **应用**(保留当前路由,如 `/records?win_preset=…`) |
|
||||
|
||||
查询参数示例:
|
||||
|
||||
- `?win_preset=utc_today`
|
||||
- `?win_preset=utc_last24h` / `utc_last7d`
|
||||
- `?win_preset=custom&from_utc=2026-05-18 00:00:00&to_utc=2026-05-19 12:00:00`
|
||||
|
||||
## 交易记录与复盘
|
||||
|
||||
- 平仓记录可同步交易所已实现盈亏(Gate 仓位历史等);列表盈亏列优先显示交易所数据,标注 **所** / **估**.
|
||||
- 记录页提供 **立即同步**(`POST /api/sync_exchange_pnl`),用于补全或刷新 `exchange_realized_pnl` 等字段.
|
||||
- 未做人工复盘时,展示以交易所盈亏为准(有同步数据时).
|
||||
- **列表默认只显示当前 UTC 时间窗内**的记录(见上节);导出 CSV 同步该时间窗.
|
||||
- 表头 **「止损(开仓)」**:展示开仓快照 `initial_stop_loss`(无则回退 `stop_loss`);核对/复盘仍可用有效止损字段.
|
||||
- 平仓写入 `trade_records` 时:`stop_loss` 与 `initial_stop_loss` 均写入**开仓时止损快照**;`key_signal_type` 保留箱体/收敛/斐波来源(`fib_key_monitor_lib.key_signal_type_for_trade_record`).
|
||||
- **开仓类型**(`entry_reason`):机器单平仓入库时,若未手填,按 `key_signal_type` 自动映射(见下表);列表/导出「开仓类型」列 = 复盘核对值优先,否则入库值,否则按信号映射.
|
||||
|
||||
| `key_signal_type` | 自动写入的 `entry_reason` |
|
||||
|-------------------|---------------------------|
|
||||
| 箱体突破 | 关键位箱体突破 |
|
||||
| 收敛突破 | 关键位收敛突破 |
|
||||
| 斐波回调0.618 | 关键位斐波0.618 |
|
||||
| 斐波回调0.786 | 关键位斐波0.786 |
|
||||
|
||||
- 复盘表单 **开仓类型** 下拉新增上述四条固定文案(与趋势/波段类并列).
|
||||
- 复盘 **离场触发** 新增 **「止盈」**;从交易记录「填入复盘」时,若结果为「止盈/保本止盈/移动止盈/止损/手动平仓」会自动选中对应触发项,并按 `key_signal_type` 预填开仓类型.
|
||||
- 勾选「保存时自动生成多周期 K 线图」时:以 **平仓时间** 为锚点,各周期向前约 `ORDER_CHART_LIMIT`(默认 100)根 K 线(`_fetch_ohlcv_ending_at`),不再固定拉「最近 100 根」.
|
||||
- `/api/journals`,`/api/reviews` 支持同一时间窗 query,与列表一致.
|
||||
|
||||
### 导出(交易记录 v3)
|
||||
|
||||
- 文件名:`trade_records_v3_YYYYMMDD.csv`
|
||||
- 相对 v2 增加:`key_signal_type`,`initial_stop_loss`(及开仓快照列),`planned_rr`,`actual_rr`,`risk_amount`,交易所盈亏与时间字段等;末列「开仓类型」为有效展示文案.
|
||||
- 「关键位历史」导出同样受 UTC 时间窗限制.
|
||||
|
||||
## 实盘下单页
|
||||
|
||||
- 左列:实盘下单监控(表单,划转,规则).
|
||||
- 右列:实时持仓(独立模块).
|
||||
- **人工开仓门控**:计划盈亏比 < `MANUAL_MIN_PLANNED_RR`(默认 **1.4**)时前端弹窗 + 后端拒绝.
|
||||
- **移动保本**(勾选启用):监控轮询达到触发 RR 后,止损阶梯上移时**同步交易所**——调用与页面「挂止盈止损」相同的 **先撤后挂**(`replace_active_monitor_tpsl_on_exchange`:撤该合约全部 TP/SL 条件单 → 按新止损 + 原止盈重挂).仅交易所成功后才写库;失败发企业微信告警,本地止损不变.未配置实盘 API 时仍只更新本地(与旧行为一致).
|
||||
|
||||
## 统计分析页(`/stats`)
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 切日 | **北京时间**;交易日边界 = 每日 `TRADING_DAY_RESET_HOUR:00`(`.env` 默认 **8**) |
|
||||
| 品类下拉 | 页顶 **「统计品类」** 下拉切换(默认「全部交易」):全部交易,下单监控,关键位箱体突破,关键位收敛结构,关键位斐波0.618,关键位斐波0.786;一次只显示所选品类的日/周/月 |
|
||||
| URL | 切换后写入 `stats_segment=`(如 `all`,`manual`,`key_box`,`key_conv`,`key_fib618`,`key_fib786`),刷新 `/stats` 可保持选项 |
|
||||
| 每块指标 | 日 / 周 / 月:开单次数,平仓笔数,胜率,净盈亏,回撤,连续亏损等(与原口径一致) |
|
||||
| 开单次数 | 人工块:`monitor_type=下单监控` 且无 `key_signal_type`;关键位块:按 `order_monitors.key_signal_type` 计数 |
|
||||
| 不受 UTC 窗影响 | 统计始终基于库内全部已平仓记录,按北京交易日归类,**不**随顶栏 UTC 列表窗切换 |
|
||||
|
||||
## 持仓与计仓
|
||||
|
||||
- `MAX_ACTIVE_POSITIONS` 默认 **1**(可在 `.env` 调大).
|
||||
- 关键位自动开仓:在已有持仓时,若 `KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT=true`,按**首笔开仓前**交易账户资金快照计仓(`trading_sessions.key_sizing_capital_snapshot`).
|
||||
|
||||
## 配置
|
||||
|
||||
详见 `.env.example` 中「关键位门控」「交易执行 / 人工风控」注释段.Gate 专用项(`GATE_*`,止盈止损触发等)保持原有段落不变.
|
||||
|
||||
## 自动备份(服务器)
|
||||
|
||||
- 脚本:`scripts/backup_data.sh`(`crypto.db` + `static/images`)
|
||||
- 定时:`scripts/install_backup_cron.sh` → 每天 **北京时间 0:00**,目录 **`/root/backups/<实例名>/YYYY-MM-DD/`**,保留 **30** 天
|
||||
- 详见 `部署文档.md` 第 5.4 节(自动备份)
|
||||
|
||||
## 数据库(启动时自动迁移)
|
||||
|
||||
`key_monitors` 新增斐波字段(示例):`fib_limit_order_id`,`fib_entry_price`,`fib_stop_loss`,`fib_take_profit`,`fib_order_amount`,`fib_margin_capital`,`fib_leverage`.
|
||||
|
||||
`trade_records` / `order_monitors` 新增或沿用:`key_signal_type`,`exchange_realized_pnl`,`exchange_opened_at`,`exchange_closed_at`,`exchange_sync_key`,`entry_reason`,`reviewed_entry_reason`,`initial_stop_loss`.
|
||||
|
||||
**历史数据**:本次**不做**旧记录的批量回填(`entry_reason` / `initial_stop_loss` / `key_signal_type` 等);仅**新产生**的平仓与复盘按新逻辑写入.旧行展示可回退已有字段.
|
||||
|
||||
## 涉及文件(便于排查)
|
||||
|
||||
| 路径 | 说明 |
|
||||
|------|------|
|
||||
| `history_window_lib.py` | UTC 时间窗解析与转北京时间 SQL 字符串 |
|
||||
| `fib_key_monitor_lib.py` | 斐波计算,`KEY_ENTRY_REASON_BY_SIGNAL`,`entry_reason_from_key_signal` |
|
||||
| `crypto_monitor_gate/app.py` | 列表筛选,统计分块,导出 v3,复盘 K 线锚点,入库逻辑 |
|
||||
| `crypto_monitor_gate/templates/index.html` | 顶栏时间窗,统计分块 UI,止损(开仓)列,复盘预填 |
|
||||
|
||||
## 升级步骤
|
||||
|
||||
1. `git pull` 后对比 `.env.example`,把新增变量合并进本地 `.env`.
|
||||
2. 在 VPS 上为 Binance / Gate / **各执行一次** `bash scripts/install_backup_cron.sh`(若尚未安装).
|
||||
3. 重启 Gate 实例服务(如 `pm2 restart crypto_gate`);首次启动会自动 `ALTER TABLE` 缺列(斐波,交易所盈亏,`entry_reason` 等).
|
||||
4. 浏览器强刷(Ctrl+F5)避免旧版 `index.html` 缓存.
|
||||
5. 打开任意页确认顶栏出现 **「列表筛选(UTC)」**;`/stats` 可见分品类统计与「北京 8:00 切日」说明.
|
||||
6. 建议在测试币上先添加一条斐波监控,确认:限价已挂出,标记价失效会撤单,成交后出现持仓监控且 TP/SL 已挂上;平仓后交易记录止损(开仓)与开仓类型是否正确.
|
||||
@@ -274,3 +274,8 @@ AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
||||
|
||||
APP_TIMEZONE=Asia/Shanghai
|
||||
# TRADING_DAY_RESET_HOUR 现在表示「北京时间」整点,默认 8 点起算新交易日;开仓整点限制见 TRADING_DAY_RESET_OPEN_GUARD_ENABLED
|
||||
|
||||
# --- 软件授权(整机共用,也可写仓库根 license.env)---
|
||||
# LICENSE_API_URL=https://sq.bz121.com
|
||||
# LICENSE_CLIENT_KEY=与授权站 CLIENT_API_KEY 相同
|
||||
# LICENSE_OFFLINE_GRACE_HOURS=72
|
||||
|
||||
@@ -1,53 +1,11 @@
|
||||
# crypto_monitor_okx
|
||||
# crypto_monitor_okx(用户版)
|
||||
|
||||
基于 **Flask** 的加密货币 **下单监控 / 关键位监控 / 交易复盘** 小系统,行情与实盘接口统一走 **OKX(USDT 永续)**,通过 **ccxt** 访问.功能与界面已与 **`crypto_monitor_binance`** 对齐(顶栏分栏,风控参数,交易所 TP/SL 管理等),差异主要在 **`.env` 的 `OKX_*` 变量** 与 OKX API(含 Passphrase).
|
||||
OKX 永续实例。
|
||||
|
||||
## 功能概要
|
||||
|
||||
- **关键位监控**:`/key_monitor`,5m 门控,企业微信,部分类型自动开仓(见 `关键位自动下单说明.md`)
|
||||
- **实盘下单**:`/trade`,以损定仓,移动保本,页面内撤挂止盈止损
|
||||
- **策略交易**:`/strategy`(趋势回调 + 顺势加仓),见 [策略交易说明.md](../策略交易说明.md)
|
||||
- **AI 复盘**:见 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)
|
||||
- **实盘(可选)**:`LIVE_TRADING_ENABLED=true` 且配置 `OKX_API_KEY` / `OKX_API_SECRET` / `OKX_API_PASSPHRASE`
|
||||
- **止盈止损(OKX)**:市价成交后通过 ccxt 挂 **止损 / 止盈** 条件单(`attachAlgoOrds` 或 reduceOnly 市价单路径,见 `app.py`)
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Python 3.10+
|
||||
- 依赖见仓库根 `requirements.txt`;经 **SSH SOCKS** 访问 OKX 时需 **`PySocks`**,并配置 `OKX_SOCKS_PROXY=socks5h://127.0.0.1:1080`
|
||||
|
||||
## 配置说明
|
||||
|
||||
| 变量 | 说明 |
|
||||
|------|------|
|
||||
| `OKX_API_KEY` / `OKX_API_SECRET` / `OKX_API_PASSPHRASE` | OKX API |
|
||||
| `OKX_TD_MODE` / `OKX_POS_MODE` | 全仓/逐仓,单向/双向 |
|
||||
| `OKX_SOCKS_PROXY` | 本机 SSH 动态转发时常用 |
|
||||
| `MAX_ACTIVE_POSITIONS` / `MANUAL_MIN_PLANNED_RR` | 与币安版一致的风控 |
|
||||
| `EXCHANGE_DISPLAY_NAME` | 页面展示名,默认 `OKX` |
|
||||
|
||||
完整模板见 **`.env.example`**.
|
||||
|
||||
## 运行
|
||||
- **部署**:[部署文档.md](./部署文档.md) · 仓库根 [docs/ubuntu-server.md](../docs/ubuntu-server.md)
|
||||
- **授权**:启动后打开 `/license`,见 [docs/license.md](../docs/license.md)
|
||||
- **配置**:复制 `.env.example` → `.env`
|
||||
|
||||
```bash
|
||||
cd /opt/crypto_monitor_user/crypto_monitor_okx
|
||||
source .venv/bin/activate
|
||||
python app.py
|
||||
```
|
||||
|
||||
生产使用 **PM2**;见 [docs/ubuntu-server.md](../docs/ubuntu-server.md).默认 **`APP_PORT`** 常为 `5004`.
|
||||
|
||||
## 部署
|
||||
|
||||
详见 **[部署文档.md](./部署文档.md)**,**[使用说明.md](./使用说明.md)**.
|
||||
|
||||
## 自检
|
||||
|
||||
```bash
|
||||
python scripts/verify_okx_funding.py
|
||||
```
|
||||
|
||||
## 风险与合规
|
||||
|
||||
实盘风险自负;请确认 API 权限,IP 白名单与 OKX 账户设置一致.
|
||||
|
||||
@@ -301,6 +301,12 @@ app.secret_key = os.getenv("FLASK_SECRET_KEY", "crypto_monitor_2026_secret_key")
|
||||
from lib.instance.instance_embed_lib import attach_embed_templates, redirect_to_embed_shell_if_enabled
|
||||
|
||||
attach_embed_templates(app, _REPO_ROOT)
|
||||
try:
|
||||
from lib.license.flask_gate import install_license_gate
|
||||
|
||||
install_license_gate(app)
|
||||
except Exception as _lic_err:
|
||||
print(f"[license] okx: {_lic_err}")
|
||||
|
||||
# ====================== 登录配置 ======================
|
||||
USERNAME = os.getenv("APP_USERNAME", "dekun")
|
||||
|
||||
Binary file not shown.
@@ -1,138 +0,0 @@
|
||||
# 使用说明
|
||||
|
||||
**本文件对应仓库:`crypto_monitor_okx`(OKX USDT 本位永续).**
|
||||
功能,界面与 **Gate.io USDT 永续版**(目录 `crypto_monitor_gate`)基本一致,差异主要在 **`.env` 里交易所密钥与部分参数名**(`OKX_*` / `GATE_*`),文末有对照.
|
||||
|
||||
**部署,代理,PM2 等**请参考本仓库说明或 **`crypto_monitor_gate`** 下的 **`部署文档.md`**(该文以 Gate + SSH SOCKS 为例;OKX 侧将 API 与密钥改为 `OKX_*` 即可类比).
|
||||
**关键位自动开仓的规则,RR,结案原因**见本目录 **`关键位自动下单说明.md`**.
|
||||
|
||||
---
|
||||
|
||||
## 1. 它能做什么
|
||||
|
||||
面向个人盘面的 **Web 控制台**,主要能力包括:
|
||||
|
||||
| 模块 | 说明 |
|
||||
|------|------|
|
||||
| **关键位监控** | 录入上/下沿与类型,按 **5m 收线** 做硬条件过滤;符合条件后 **企业微信** 提醒,部分类型可 **自动市价开仓**(见第 4 节与专门文档). |
|
||||
| **实盘下单监控** | 手工填止损/止盈,**以损定仓** 市价开单,挂上条件止盈止损,并在页面跟踪浮盈亏,保本逻辑等. |
|
||||
| **交易记录 / 复盘** | 平仓结果,盈亏,错过的单等归档与导出;可选 **AI 复盘**(见仓库根 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md)). |
|
||||
| **策略交易** | 顶栏 `/strategy`:**趋势回调**(左)与 **顺势加仓**(右)左右并列;细则见 [策略交易说明.md](../策略交易说明.md). |
|
||||
|
||||
后台按 **`MONITOR_POLL_SECONDS`**(默认几秒)轮询行情与监控逻辑.**切勿**在未理解规则时同时运行两套程序共用一个实盘账户.
|
||||
|
||||
---
|
||||
|
||||
## 2. 运行前必须配置(`.env`)
|
||||
|
||||
首次在本目录执行 **`cp .env.example .env`**,再编辑 `.env`(`.env` 勿提交 Git;`git pull` 不会改你的 `.env`,升级前建议 `cp .env .env.backup.$(date +%Y%m%d)`).
|
||||
|
||||
至少检查以下项(具体键名以 **`.env.example`** 为准):
|
||||
|
||||
| 类别 | 说明 |
|
||||
|------|------|
|
||||
| **登录网页** | `APP_PASSWORD`:打开站点后的登录口令.`FLASK_SECRET_KEY`:Session 密钥,请勿使用默认值. |
|
||||
| **企业微信** | `WECHAT_WEBHOOK`:告警与关键位推送机器人的 Webhook. |
|
||||
| **是否真下单** | `LIVE_TRADING_ENABLED=false`:**不会**向交易所发送开仓指令(适合测试流程).改为 `true` 且密钥正确才会实盘. |
|
||||
| **交易所 API** | **本仓库:** `OKX_API_KEY`,`OKX_API_SECRET`;永续相关见 `OKX_TD_MODE`,`OKX_POS_MODE`,`OKX_TRIGGER_WORKING_TYPE` 等.**勿**把 `.env` 提交到 Git. |
|
||||
| **关键位 RR / 止损外扩** | `KEY_AUTO_MIN_PLANNED_RR`,`KEY_STOP_OUTSIDE_BREAKOUT_PCT`(详见 `关键位自动下单说明.md`). |
|
||||
| **AI 复盘** | 默认 `AI_PROVIDER=openai`,`OPENAI_API_BASE=https://op.bz121.com/v1`,`OPENAI_API_KEY`,`OPENAI_MODEL=gemma4:e4b`;或 `AI_PROVIDER=ollama` + `OLLAMA_API` / `AI_MODEL`.详见 [AI复盘与模型配置说明.md](../AI复盘与模型配置说明.md). |
|
||||
|
||||
网络需要代理时可配置 **`OKX_SOCKS_PROXY` / `OKX_HTTP_PROXY`**(与 Gate 版 `GATE_*_PROXY` 用法类似).
|
||||
|
||||
---
|
||||
|
||||
## 3. 如何启动与登录
|
||||
|
||||
1. 准备 Python 虚拟环境并安装依赖(如 `flask`,`requests`,`ccxt`,按需 `Pillow`,`PySocks` 等),配置好 `.env`.
|
||||
2. 启动 Flask 应用(可用 **`ecosystem.config.cjs`** 交给 PM2,或本地 `python app.py` / `flask run`,以你当前脚本为准).
|
||||
3. 浏览器访问站点,打开 **`/login`**,使用 **`.env` 里的 `APP_PASSWORD`** 登录.
|
||||
|
||||
登录后顶栏:**关键位监控** | **实盘下单**(默认首页)| **策略交易**(`/strategy`,趋势回调 + 顺势加仓双栏)| **策略交易记录**(`/strategy/records`)| **交易记录与复盘** | **统计分析**.
|
||||
|
||||
---
|
||||
|
||||
## 4. 关键位监控(顶栏「关键位监控」→ `/key_monitor`)
|
||||
|
||||
### 4.1 添加一条关键位
|
||||
|
||||
1. **币种**:如 `BTC` 或 `BTC/USDT`(会规范成内部符号).
|
||||
2. **类型**(必选其一):
|
||||
|
||||
| 类型 | 行为摘要 |
|
||||
|------|----------|
|
||||
| **箱体突破** | 通过门控且计划 RR 达标 → **自动市价开仓**(需 `LIVE_TRADING_ENABLED=true` 且无其他持仓占位).结案后本条从列表消失并记入历史. |
|
||||
| **收敛突破** | 同上(自动开仓类). |
|
||||
| **关键阻力位** | **不自动开仓**;触发后 **发 1 次微信**,然后本条 **结案进历史**. |
|
||||
| **关键支撑位** | 同上(仅提醒). |
|
||||
| **回调触价开仓** | **不挂交易所限价**;标记价回调触达 E 后 **下一轮询市价开仓**(RR 门槛同 `KEY_AUTO_MIN_PLANNED_RR`);有效期 **24h** |
|
||||
| **突破触价开仓** | **不挂交易所限价**;标记价 **穿越 E 立即市价开仓**;先触 SL/TP 侧失效;有效期 **24h** |
|
||||
|
||||
3. **方向**:做多 / 做空(触价开仓 / 箱体 / 收敛 / 斐波必选;阻力/支撑不选).
|
||||
4. **价位**:箱体/收敛/阻力/支撑填 **上沿 / 下沿**;触价开仓填 **入场 E / 止损 SL / 止盈 TP**.
|
||||
|
||||
**限制:**
|
||||
活跃持仓数达到 **`MAX_ACTIVE_POSITIONS`**(默认 1)时,**不允许**再添加「**箱体突破** / **收敛突破**」;仍可添加「**关键阻力位 / 支撑位**」.
|
||||
若 **4h EMA55** 与你的方向逆势,页面会 **额外 Flash 提示**,**不阻挡**提交.
|
||||
|
||||
### 4.2 触发后会发生什么(简版)
|
||||
|
||||
- **箱体 / 收敛**:门控通过后算计划 SL/TP 与 RR;不达标 → 微信说明 + **`rr_insufficient`** 结案;达标 → **市价开仓**,成功 **`auto_opened`** / 失败 **`exchange_failed`**,均不重试同一关键位.
|
||||
- **阻力 / 支撑**:仅 **单次推送** → **`key_level_alert_only`** 结案.
|
||||
|
||||
详细公式与字段见 **`关键位自动下单说明.md`**.
|
||||
|
||||
### 4.3 列表与历史
|
||||
|
||||
当前条目与历史记录的用法与 Gate 版相同;结案后可在历史区查阅 **`close_reason`**.
|
||||
|
||||
---
|
||||
|
||||
## 5. 实盘下单(顶栏「实盘下单」→ `/trade`)
|
||||
|
||||
- 持仓上限由 **`MAX_ACTIVE_POSITIONS`** 控制(默认 1).
|
||||
- **人工开仓**计划盈亏比不得低于 **`MANUAL_MIN_PLANNED_RR`**(默认 1.4:1).
|
||||
- 填写币种,方向,杠杆(可选),止损/止盈(价格或百分比按表单).
|
||||
- 移动保本等选项按页面与 `.env` 默认.
|
||||
|
||||
开仓成功后卡片 **「来源」**:手工一般为 **下单监控**;关键位自动为 **关键位监控**.
|
||||
|
||||
---
|
||||
|
||||
## 6. 企业微信
|
||||
|
||||
推送逻辑与 Gate 版一致;未配置 **`WECHAT_WEBHOOK`** 时可能没有消息,请以 **交易所端** 核对持仓与挂单.
|
||||
|
||||
---
|
||||
|
||||
## 7. 强烈建议的风险与运维习惯
|
||||
|
||||
1. **先用 `LIVE_TRADING_ENABLED=false`** 熟悉流程再实盘.
|
||||
2. **API 权限**最小化,密钥勿泄露.
|
||||
3. **同一账户避免多程序重复开仓**.
|
||||
4. **自动备份**:服务器上执行 `bash scripts/install_backup_cron.sh`(每天北京时间 0:00 → `/root/backups`,保留 30 天);升级前也可 `bash scripts/backup_data.sh` 手动跑一次.
|
||||
5. 升级代码后留意 **首轮启动**有无数据库迁移报错.
|
||||
|
||||
---
|
||||
|
||||
## 8. 常见问题(简要)
|
||||
|
||||
| 现象 | 可自查 |
|
||||
|------|--------|
|
||||
| 关键位永远不触发 | 门控五项,日成交量排名,`KLINE_TIMEFRAME`. |
|
||||
| 有信号但不自动开仓 | `LIVE_TRADING_ENABLED`,RR 阈值,是否已有持仓,API/保证金错误信息. |
|
||||
| 加不了箱体/收敛 | 是否已有持仓. |
|
||||
| 推送收不到 | Webhook,网络. |
|
||||
|
||||
---
|
||||
|
||||
## 9. 与币安版(`crypto_monitor_binance`)差异速查
|
||||
|
||||
| 项目 | OKX 本仓库 | 币安版 |
|
||||
|------|------------|--------|
|
||||
| API 变量 | `OKX_API_KEY`,`OKX_API_SECRET`,`OKX_API_PASSPHRASE` | `BINANCE_API_KEY`,`BINANCE_API_SECRET` |
|
||||
| 代理 | `OKX_SOCKS_PROXY` | `BINANCE_SOCKS_PROXY` |
|
||||
| 默认端口 | 常为 `5004` | 常为 `5001` |
|
||||
| TP/SL 实现 | `_okx_place_tp_sl_orders`,页面 `/api/order/.../cancel_tpsl` | `_binance_place_tp_sl_orders` |
|
||||
|
||||
业务流程,顶栏分栏,策略交易,风控参数名已与币安版对齐;仅需更换目录与 `.env`.
|
||||
@@ -1,192 +0,0 @@
|
||||
# 关键位监控说明(自动开仓 + 人工盯盘)
|
||||
|
||||
**适用:Gate / Binance / OKX 三所实例(共用 `lib/key_monitor/key_auto_order_lib.py`)**
|
||||
|
||||
## 环境开关 `KEY_AUTO_ORDER_ENABLED`(默认 `false`)
|
||||
|
||||
| 计仓模式 | 开关 | 关键位程序自动单 |
|
||||
|----------|------|------------------|
|
||||
| `risk`(以损定仓) | `false` | **全部关闭**(含触价);支撑/阻力微信提醒仍可用 |
|
||||
| `risk` | `true` | 箱体/收敛/斐波/假突破/触价均可自动(旧行为) |
|
||||
| `full_margin`(全仓) | `false` | 全部关闭(含触价) |
|
||||
| `full_margin` | `true` | **仅触价**自动;箱体/斐波等仍禁止 |
|
||||
|
||||
**不受本开关影响:** 人工实盘下单,关键支撑/阻力提醒,**顺势加仓**(`risk` 下),趋势回调(`risk` 下).全仓模式下策略自动仍禁止.
|
||||
|
||||
修改 `.env` 后须 **重启 PM2**.复盘「开仓类型」与统计分段会随开关联动隐藏关键位选项.
|
||||
|
||||
---
|
||||
|
||||
**适用:`crypto_monitor_gate`(Gate U 本位永续)**
|
||||
Binance / OKX 见各自目录下同名文档;共享逻辑在 `lib/key_monitor/`.
|
||||
|
||||
本文档与 `.env`,`check_key_monitors`,`add_key`,`_key_hard_checks`,`_process_key_rs_level_alert` 一致.
|
||||
|
||||
---
|
||||
|
||||
## 一,监控类型总览
|
||||
|
||||
| 录入类型 | 录入时选方向 | 自动市价开仓 | 触发与结案 |
|
||||
|----------|--------------|--------------|------------|
|
||||
| **箱体突破** | **必选** 多/空 | **是**(门控 + RR) | 条件满足 → 开仓或 `rr_insufficient` / `exchange_failed` → **一次性删除** |
|
||||
| **收敛突破** | **必选** 多/空 | **是**(同上) | 同上 |
|
||||
| **关键阻力位** | **不选**(`direction=watch`) | **否** | 5m 收盘突破上/下沿 → 微信 **3 次** → `key_level_alert_done` |
|
||||
| **关键支撑位** | **不选** | **否** | 同上(与阻力位**相同规则**:填上沿+下沿,程序双向监控) |
|
||||
| 斐波回调 0.618 / 0.786 | 必选 | 限价挂单逻辑 | 见斐波说明(**不在下文展开**) |
|
||||
| **回调触价开仓** | **必选** 多/空 | **程序盯价 → 回调触 E 后市价** | 见下文 **§四** |
|
||||
| **突破触价开仓** | **必选** 多/空 | **程序盯价 → 穿越 E 立即市价** | 见下文 **§四** |
|
||||
|
||||
**添加时(箱体/收敛/斐波/触价):** 品种须 **日成交量排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30)**;上沿 **>** 下沿(触价开仓填 E/SL/TP,上下沿仅作展示占位).
|
||||
|
||||
---
|
||||
|
||||
## 二,关键阻力位 / 关键支撑位(人工盯盘)
|
||||
|
||||
### 2.1 录入
|
||||
|
||||
- 填写 **上沿 `upper`** 与 **下沿 `lower`**(程序同时监控两侧,**无法预先判定**做多还是做空).
|
||||
- 页面 **不显示,不要求** 方向;库中 `direction` 初始为 `watch`,**首次突破后** 写入 `long`(向上突破上沿)或 `short`(向下突破下沿).
|
||||
|
||||
### 2.2 触发(极简)
|
||||
|
||||
- 周期:**`KLINE_TIMEFRAME`(默认 5m)最近一根已闭合 K** 的 **收盘价**(非影线).
|
||||
- **向上突破上沿:** `收盘 > upper` → 推断方向 **多 / 向上**,本次监控任务开始按节奏提醒.
|
||||
- **向下突破下沿:** `收盘 < lower` → 推断方向 **空 / 向下**,本次任务同样开始提醒.
|
||||
- **任一侧突破即结束本条监控周期**(不会在突破后再等待另一侧;上沿,下沿谁先满足用谁,同根 K 仅可能满足一侧).
|
||||
|
||||
**不参与:** 量能,二确 K,越过幅度下限,日成交排名(运行时),计划 RR,自动开仓.
|
||||
|
||||
### 2.3 微信提醒次数
|
||||
|
||||
| 配置 | 默认 | 含义 |
|
||||
|------|------|------|
|
||||
| `KEY_ALERT_MAX_TIMES` | `3` | 突破后最多推送 3 次 |
|
||||
| `KEY_ALERT_INTERVAL_MINUTES` | `5` | 相邻两次推送至少间隔 5 分钟 |
|
||||
|
||||
- 第 1 次:首次检测到突破的当次轮询(若已闭合 5m 满足条件).
|
||||
- 第 2,3 次:仅按间隔推送(**不要求**价格仍在箱外).
|
||||
- 第 3 次推送后:写入 `key_monitor_history`,`close_reason=**key_level_alert_done**`,从 `key_monitors` **删除**.
|
||||
|
||||
### 2.4 与箱体/收敛的区别
|
||||
|
||||
| 项目 | 阻力/支撑 | 箱体/收敛 |
|
||||
|------|-----------|-----------|
|
||||
| 方向 | 程序推断 | 人工选择 |
|
||||
| K 线根数 | 1 根闭合 5m | 2 根(突破 K + 确认 K) |
|
||||
| 提醒次数 | 3 次后结案 | 自动单:触发后 1 次业务推送并结案 |
|
||||
|
||||
---
|
||||
|
||||
## 三,箱体突破 / 收敛突破(自动开仓)
|
||||
|
||||
### 3.1 K 线结构(默认索引)
|
||||
|
||||
| 角色 | 环境变量 | 默认 | 含义 |
|
||||
|------|----------|------|------|
|
||||
| 突破 K | `KEY_CONFIRM_BREAKOUT_BAR` | `-2` | 倒数第 2 根闭合 K |
|
||||
| 确认 K | `KEY_CONFIRM_BAR` | `-1` | 倒数第 1 根闭合 K |
|
||||
|
||||
### 3.2 硬门控(须全部通过)
|
||||
|
||||
1. **有效突破(收盘越界)**
|
||||
- 多:`突破 K 收盘 > upper`
|
||||
- 空:`突破 K 收盘 < lower`
|
||||
|
||||
2. **突破越过幅度(仅下限)**
|
||||
- 多:`(突破 K 收盘 − upper) / upper × 100 > KEY_BREAKOUT_AMP_MIN_PCT`(默认 **0.03%**)
|
||||
- 空:`(lower − 突破 K 收盘) / lower × 100 >` 同上
|
||||
- **无上限**;突破过猛由 **计划 RR** 过滤.
|
||||
- **不再**使用 K 线实体占开盘价比例;`KEY_BREAKOUT_AMP_MAX_PCT` **已不参与门控**.
|
||||
|
||||
3. **确认 K 不进箱体**
|
||||
- 多:确认 K 收盘 **`> upper`**(不得在 `[lower, upper]` 内)
|
||||
- 空:确认 K 收盘 **`< lower`**
|
||||
|
||||
4. **量能:** 突破 K 成交量 > 前 `KEY_VOLUME_MA_BARS`(默认 20)根均量 × `KEY_VOLUME_RATIO_MIN`(默认 1.3)
|
||||
|
||||
5. **日成交量排名:** 运行时仍须前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30)
|
||||
|
||||
6. **计划 RR(最后经济门控):** 按确认 K 收盘 **E** 计算 SL/TP 后,`RR` **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5)才市价开仓
|
||||
|
||||
### 3.3 止损 / 止盈(确认 K 收盘为 E)
|
||||
|
||||
箱体高 **H = |upper − lower|**.止损锚在 **突破 K 极值** 外侧:
|
||||
|
||||
| 方向 | 止损(标准/趋势方案) |
|
||||
|------|------------------------|
|
||||
| 多 | 突破 K **最低价** × (1 − `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
|
||||
| 空 | 突破 K **最高价** × (1 + `KEY_STOP_OUTSIDE_BREAKOUT_PCT`%) |
|
||||
|
||||
止盈方案见下表(与改版前一致):
|
||||
|
||||
| 方案 | `sl_tp_mode` | 多:SL / TP | 空:SL / TP |
|
||||
|------|--------------|-------------|-------------|
|
||||
| 标准突破 | `standard` | 突破 K 低外侧% / **E+H** | 突破 K 高外侧% / **E−H** |
|
||||
| 箱体 1R·止盈 1.5H | `box_1p5` | **E−H** / **E+1.5×H** | **E+H** / **E−1.5×H** |
|
||||
| 趋势单·自填止盈 | `trend_manual` | 突破 K 低 × (1−`KEY_TREND_STOP_OUTSIDE_PCT`%) / **录入止盈** | 突破 K 高外侧% / **录入止盈** |
|
||||
|
||||
### 3.4 一次性结案(`close_reason`)
|
||||
|
||||
| `close_reason` | 含义 |
|
||||
|----------------|------|
|
||||
| `box_opposite_break` | 标记价先突破反向边界(多:≤下沿;空:≥上沿) |
|
||||
| `rr_insufficient` | 门控通过但 RR 不达标或 SL/TP 几何无效 |
|
||||
| `exchange_failed` | RR 达标但实盘/交易所等原因未开仓 |
|
||||
| `auto_opened` | RR 达标且市价开仓成功 |
|
||||
| `key_level_alert_done` | 阻力/支撑 **3 次提醒** 完成 |
|
||||
|
||||
---
|
||||
|
||||
## 四,回调 / 突破触价开仓(程序触价,无交易所挂单)
|
||||
|
||||
### 4.1 录入
|
||||
|
||||
- **回调触价开仓**:方向必选多/空;填写 **计划入场价 E**,**止损 SL**,**止盈 TP**(做多须 `SL < E < TP`).
|
||||
- **突破触价开仓**:同上;添加时当前价须在突破方向一侧(做多:价低于 E;做空:价高于 E).
|
||||
- 计划 RR 以 **E** 为基准,须 **严格大于** `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5).
|
||||
- 可选移动保本,时间平仓;**全仓杠杆模式**下可用.
|
||||
|
||||
### 4.2 触发与结案
|
||||
|
||||
| 类型 | 触发条件(标记价) |
|
||||
|------|-------------------|
|
||||
| **回调触价** | 做多 `≤ E`;做空 `≥ E` → 下一轮询市价开仓 |
|
||||
| **突破触价** | 做多**向上穿越** E;做空**向下穿越** E → **立即**市价开仓 |
|
||||
|
||||
- 未成交前标记价先触 **TP 侧** → `trigger_tp_invalidate`.
|
||||
- **突破触价**另:未穿越 E 先触 **SL 侧** → `trigger_sl_invalidate`.
|
||||
- **24h** 未触发 → `trigger_entry_expired`.
|
||||
- 成功 → `trigger_entry_filled`;触发后开仓失败 → `trigger_exchange_failed`.
|
||||
|
||||
### 4.3 计仓与占位
|
||||
|
||||
- **以损定仓**:按 E,SL 反推保证金,触发时重算;**全仓杠杆**:可用×缓冲比例,BTC/ETH 10x,其它 5x.
|
||||
- **占当日开仓意图**(已开 + 待触发),未成交不占持仓;同币仅 1 条触价监控(含回调/突破).
|
||||
|
||||
共享逻辑:`trigger_entry_key_monitor_lib.py`;轮询:`check_trigger_entry_key_monitors`.
|
||||
|
||||
---
|
||||
|
||||
## 五,环境与参数(`.env` 摘要)
|
||||
|
||||
| 变量 | 箱体/收敛 | 阻力/支撑 |
|
||||
|------|-----------|-----------|
|
||||
| `KEY_BREAKOUT_AMP_MIN_PCT` | 突破越过下限(默认 0.03) | 不用 |
|
||||
| `KEY_BREAKOUT_AMP_MAX_PCT` | **已废弃门控** | 不用 |
|
||||
| `KEY_VOLUME_*` / `KEY_CONFIRM_*` | 用 | 不用 |
|
||||
| `KEY_AUTO_MIN_PLANNED_RR` | 用 | 不用 |
|
||||
| `KEY_ALERT_MAX_TIMES` / `KEY_ALERT_INTERVAL_MINUTES` | 不用 | 用(默认 3 次 / 5 分钟) |
|
||||
| `KEY_DAILY_VOLUME_RANK_MAX` | 添加时 + 运行时 | **仅添加时** |
|
||||
|
||||
---
|
||||
|
||||
## 六,相关代码
|
||||
|
||||
| 说明 | 位置 |
|
||||
|------|------|
|
||||
| 共享判定 | `key_monitor_lib.py` |
|
||||
| 主循环 | `check_key_monitors` |
|
||||
| 自动门控 | `_key_hard_checks` |
|
||||
| 阻力/支撑提醒 | `_process_key_rs_level_alert` |
|
||||
| 录入 | `add_key` |
|
||||
| 开仓 | `_market_open_for_key_monitor` |
|
||||
@@ -1,98 +0,0 @@
|
||||
# 界面与风控更新说明(OKX 实例)
|
||||
|
||||
与 Gate / Binance 主站对齐的列表窗,统计分品类,交易记录展示,复盘与移动保本交易所同步;OKX 仍为 **三页导航**(交易执行 / 记录复盘 / 统计),关键位监控合并在 **交易执行** 页,**无** Gate 独立「关键位监控」页与斐波限价监控.
|
||||
|
||||
## 顶栏导航(3 项)
|
||||
|
||||
| 顺序 | 名称 | 路由 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1 | 交易执行 | `/trade` | 关键位监控 + 实盘下单(**默认首页** `/` → `/trade`) |
|
||||
| 2 | 交易记录与复盘 | `/records` | 交易记录,复盘表单,AI 历史(受顶栏 UTC 时间窗筛选) |
|
||||
| 3 | 统计分析 | `/stats` | 按北京时间交易日切日 + 分品类统计块 |
|
||||
|
||||
## 列表时间窗(UTC,全站顶栏)
|
||||
|
||||
共用模块:仓库根目录 `history_window_lib.py`(与 Gate / Binance 一致).
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 默认 | **UTC 当日**(`win_preset=utc_today`) |
|
||||
| 可选 | 近 24 小时,近 7 天,自定义起止(UTC) |
|
||||
| 作用范围 | 关键位历史,交易记录列表,复盘 API,AI 历史 API,导出「交易记录」「关键位历史」 |
|
||||
| 与统计 | **仅影响列表/导出**;统计页仍按北京时间 `TRADING_DAY_RESET_HOUR`(默认 8:00)切日 |
|
||||
| 切换 | 顶栏「列表筛选(UTC)」→ 应用(保留当前路由 query) |
|
||||
|
||||
## 交易记录与复盘
|
||||
|
||||
- 列表 **止损(开仓)**:展示 `initial_stop_loss` 快照(`display_open_stop_loss`).
|
||||
- 类型列显示 `monitor_type` 与 `key_signal_type`(若有).
|
||||
- 平仓入库:`stop_loss` / `initial_stop_loss` 为开仓止损快照;机器单 `entry_reason` 可按 `key_signal_type` 自动映射(箱体突破 / 收敛突破 → 四条固定关键位开仓类型文案).
|
||||
- 复盘:开仓类型下拉含四条关键位固定文案 +「其他」;离场触发含 **「止盈」**;从交易记录填入时按结果与信号预填.
|
||||
- 复盘 K 线图:以 **平仓时间** 为锚点向前约 `ORDER_CHART_LIMIT`(默认 100)根(`_fetch_ohlcv_ending_at`).
|
||||
- `/api/journals`,`/api/reviews` 与顶栏 UTC 窗一致.
|
||||
|
||||
### 导出(交易记录 v3)
|
||||
|
||||
- 文件名:`trade_records_v3_YYYYMMDD.csv`
|
||||
- 含 `key_signal_type`,`initial_stop_loss`,计划/实际 RR,`risk_amount` 等;末列「开仓类型」为有效展示文案.
|
||||
- 受 UTC 列表窗限制;关键位历史导出同理.
|
||||
|
||||
## 实盘下单(交易执行页)
|
||||
|
||||
- **移动保本**:表单可勾选「启用移动保本」;触发阶梯上移后 **先撤后挂** 交易所 TP/SL(`replace_active_monitor_tpsl_on_exchange`),仅成功后才写库;企业微信提示含「交易所:已先撤后挂止盈止损」.未配置实盘 API 时仅更新本地止损.
|
||||
- 开仓 TP/SL 仍通过 OKX `attachAlgoOrds`(与原有逻辑一致);重挂使用 ccxt `stopLoss` / `takeProfit` 参数,触发价经 `_okx_algo_trigger_price_str` 格式化.
|
||||
|
||||
## 统计分析页(`/stats`)
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 切日 | 北京时间;边界 = `TRADING_DAY_RESET_HOUR:00`(默认 8) |
|
||||
| 品类下拉 | 全部交易,下单监控,关键位箱体突破,关键位收敛结构,关键位斐波0.618,关键位斐波0.786 |
|
||||
| URL | `stats_segment=`(`all` / `manual` / `key_box` / `key_conv` / `key_fib618` / `key_fib786`) |
|
||||
| 与 UTC 窗 | 统计 **不** 随顶栏列表窗变化 |
|
||||
|
||||
## 斐波关键位监控(与 Gate / Binance 对齐)
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 类型 | **斐波回调0.618**,**斐波回调0.786**(交易执行页关键位表单) |
|
||||
| 同币互斥 | 每币仅一条斐波监控 |
|
||||
| 挂单价 E | 做多 `E = H − ratio×(H−L)`;做空 `E = L + ratio×(H−L)`;SL/TP 为 L/H |
|
||||
| 添加后 | 立即在 OKX 挂限价单;卡片显示 **挂E**,限价单 ID |
|
||||
| 失效 | 标记价触达止盈侧且限价未成交 → 仅撤本条限价单(`cancel_fib_limit_order`) |
|
||||
| 成交后 | 挂交易所 TP/SL → 写入 `order_monitors`(`monitor_type=关键位监控`,`key_signal_type=斐波回调…`)→ 从关键位表移除 |
|
||||
| 轮询 | `check_fib_key_monitors()`(与箱体/收敛 `check_key_monitors()` 分离) |
|
||||
| 盈亏比 | 计划 RR 须 > `KEY_AUTO_MIN_PLANNED_RR`(默认 1.5) |
|
||||
| 日成交量 | 排名前 `KEY_DAILY_VOLUME_RANK_MAX`(默认 30) |
|
||||
|
||||
计算逻辑见仓库根目录 `fib_key_monitor_lib.py`.
|
||||
|
||||
## 与 Gate 的差异(其余)
|
||||
|
||||
- 无独立「关键位监控」导航页(斐波在 **交易执行** 页添加).
|
||||
- 箱体/收敛与 Gate/Binance 相同:**门控 + RR 达标后自动市价开仓**(须 `LIVE_TRADING_ENABLED=true`).
|
||||
|
||||
## 交易所已实现盈亏(与 Gate 一致)
|
||||
|
||||
- 打开 **交易执行 / 交易记录** 等主页面时,若已配置 `OKX_API_KEY` / `OKX_API_SECRET` / `OKX_API_PASSPHRASE`(只读即可),同进程约 **25 秒**内最多调用一次 OKX **历史仓位**(`fetch_positions_history`),为未写入 `exchange_sync_key` 的记录匹配并回填 `exchange_realized_pnl`.
|
||||
- 复盘列表盈亏优先展示交易所 U(旁标 **所**);本地公式估算标 **估**;人工复核优先.
|
||||
- 手动强制同步:`GET /api/sync_exchange_pnl`(需登录).
|
||||
- 可选 `.env`:`EXCHANGE_POSITION_SYNC_FROM_BJ`(北京时间起点),`EXCHANGE_POSITION_HISTORY_LIMIT`(默认 200).
|
||||
|
||||
## 企业微信推送(与 Gate 对齐)
|
||||
|
||||
- 平仓:`📉 … 平仓完成` 模板(盈亏 ±X.XX U,价位两位/按币价精度,账户资金 2 位小数).
|
||||
- 开仓成功:与 Gate 相同的 emoji 分段(条件委托状态文案,RR/张数/名义 2 位小数).
|
||||
- 移动保本:仅首次触发推送;交易所同步失败同一监控单只告警一次.
|
||||
- 斐波/关键位/划转等推送数值格式与 Gate 一致(`format_wechat_scalar_2dp`).
|
||||
|
||||
## 配置与部署
|
||||
|
||||
- 详见 `.env.example` 中 OKX(`OKX_*`)与通用风控项.
|
||||
- 代码更新后请 **重启 OKX 监控进程**;旧库行不做批量回填,展示字段有则用之,无则回退.
|
||||
|
||||
---
|
||||
|
||||
## 共享更新记录
|
||||
|
||||
自 2026-07-16 起,期权/对冲等共享逻辑的变更统一记在仓库根目录 **[docs/更新文档.md](../docs/更新文档.md)**(含原因、改动文件、目标、验收)。最新一条:期权/对冲开仓仅认真实卖一深度。
|
||||
@@ -145,3 +145,5 @@ BACKUP_ROOT=/root/backups
|
||||
|
||||
- 三个监控子项目共用根目录 **[requirements.txt](../requirements.txt)**.
|
||||
- 走 SOCKS 须 **PySocks**(已包含在 requirements 中).
|
||||
- **用户版核心包**(`lib/strategy` 等)为加密发行;本地开发若需重新加密,见 `scripts/obfuscate_release.py`。
|
||||
- **授权**:配置仓库根 `license.env`(见 `license.env.example`),对接 [https://sq.bz121.com/](https://sq.bz121.com/)。
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
# 账户冷静期 / 日冻结风控
|
||||
|
||||
三所实例(币安 / OKX / Gate / Gate)共用 `account_risk_lib.py`.
|
||||
**仅用户主动平仓**计入风控;交易所止盈/止损,空仓同步,改保本/改委托等**不触发**冷静期.
|
||||
|
||||
## 状态展示
|
||||
|
||||
实例页顶,中控监控卡片账户名旁显示风控徽章:
|
||||
|
||||
| 状态 | 含义 | 倒计时 |
|
||||
|------|------|--------|
|
||||
| 正常 | 可新开仓 | 无 |
|
||||
| 1h冻结 | 冷静期中(通常为复盘后缩短的 1 小时) | 剩余时间,如 `1h冻结 · 52m 08s` |
|
||||
| 4h冻结 | 冷静期中(默认 4 小时) | 剩余时间,如 `4h冻结 · 3h 12m` |
|
||||
| 日冻结 | 当日禁止一切新开仓 | 至下一 **交易日切点**(`TRADING_DAY_RESET_HOUR`) |
|
||||
|
||||
- 倒计时每秒刷新;到期后徽章自动恢复为 **正常**(下次轮询/API 刷新会再次对齐服务端状态).
|
||||
- 鼠标悬停徽章可见完整说明(含解除时刻,如有).
|
||||
|
||||
## 什么算「手动平仓」(计入风控)
|
||||
|
||||
以下操作通过 `close_source` 登记为 **用户主动平仓**:
|
||||
|
||||
| 来源标识 | 操作 |
|
||||
|----------|------|
|
||||
| `user_instance` | 实例页删单/手动平仓(`del_order`) |
|
||||
| `user_hub` | 中控「平仓」「全平」「紧急全平」 |
|
||||
| `user_trend_stop` | 趋势计划 **「结束计划」**(手动结束) |
|
||||
|
||||
**不算**手动平仓(不触发风控):
|
||||
|
||||
- 趋势 **「保本移交下单监控」**
|
||||
- 中控/实例修改委托,挂止盈止损,移动保本
|
||||
- 交易所止盈/止损/条件单成交
|
||||
- 后台 `reconcile_external_closes` 空仓同步(即使记账为「外部平仓」)
|
||||
- 监控轮询自动止盈/止损/保本
|
||||
|
||||
## 触发规则
|
||||
|
||||
| 事件 | 行为 |
|
||||
|------|------|
|
||||
| 第 1 次用户主动平仓 | 默认 **4h** 冷静期 |
|
||||
| 第 2 次用户主动平仓(同一交易日) | **日冻结** |
|
||||
| 复盘勾选任意情绪标签 | **日冻结** |
|
||||
| 复盘:离场=手动平仓 且说明非空 | 将当前冷静期降为 **1h**(须处于 4h 档冷静期中) |
|
||||
|
||||
情绪标签:怕踏空,报复开仓,盈利飘了,拿不住单,扛单,重仓违规.
|
||||
|
||||
### 复盘缩短为 1h
|
||||
|
||||
任选一种方式,并填写说明:
|
||||
|
||||
| 方式 | 必填 |
|
||||
|------|------|
|
||||
| **复盘表单**提交 | 离场触发 = **手动平仓**;**离场补充** 非空(不是下方「备注」) |
|
||||
| **核对修改**保存 | 结果 = **手动平仓**;**备注** 非空 |
|
||||
|
||||
说明:
|
||||
|
||||
- 中控全平 / 实例手动平仓后,只要在 4h 窗口内完成上述操作即可降为 1h.
|
||||
- 复盘保存后会同步更新 `last_close_at_ms`,倒计时以 **最后一次手动平仓 + 当前档位数** 为准,不会继续读库内旧 4h 结束时间.
|
||||
- 1h 窗口已结束后,即使库里残留旧 `cooloff_until_ms`,状态也会恢复 **正常**.
|
||||
- 若超过「平仓 + 1h」才复盘,则从 **保存复盘时刻** 起再计 1h(不延长原 4h).
|
||||
- **止盈 / 保本止盈 / 止损** 等自动平仓不触发风控,也不会刷新冷静期.
|
||||
- 代码更新后需 **重启对应实例** 并硬刷新页面.
|
||||
|
||||
### 倒计时与标签
|
||||
|
||||
- 结束时刻 = `last_close_at_ms + cooloff_hours`(`APP_TIMEZONE` 默认北京时间)
|
||||
- 1h / 4h 标签按实际剩余时长判断,与倒计时一致
|
||||
- 切交易日后,若冷静期已过期,自动清库内残留字段
|
||||
|
||||
## 环境变量
|
||||
|
||||
```env
|
||||
RISK_CONTROL_ENABLED=true
|
||||
RISK_COOLING_HOURS_MANUAL=4
|
||||
RISK_COOLING_HOURS_MANUAL_JOURNAL=1
|
||||
RISK_MANUAL_CLOSE_DAILY_LIMIT=2
|
||||
RISK_MOOD_ISSUES_DAILY_FREEZE=true
|
||||
TRADING_DAY_RESET_HOUR=8
|
||||
APP_TIMEZONE=Asia/Shanghai
|
||||
```
|
||||
|
||||
`RISK_COOLING_HOURS_EXTERNAL` 已废弃(外部平仓不再触发风控).
|
||||
|
||||
## API 与 `risk_status` 字段
|
||||
|
||||
| 接口 | 说明 |
|
||||
|------|------|
|
||||
| `GET /api/account_snapshot` | 实例页轮询,含 `risk_status` |
|
||||
| `GET /api/account_risk_status` | hub_bridge 专用 |
|
||||
| `GET /api/hub/monitor` | 中控监控板,每账户含 `risk_status` |
|
||||
| `POST /api/hub/account-risk/user-close` | 中控登记用户平仓,`body: { source, count }` |
|
||||
|
||||
`risk_status` 主要字段:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `status` | `normal` / `freeze_1h` / `freeze_4h` / `freeze_daily` / `freeze_position` |
|
||||
| `status_label` | 中文标签 |
|
||||
| `can_trade` | 是否允许新开仓(仅风控维度) |
|
||||
| `reason` | 悬停提示文案 |
|
||||
| `active_count` / `max_active_positions` | 当前活跃持仓与 `.env` 中 `MAX_ACTIVE_POSITIONS` |
|
||||
| `cooloff_until_ms` | 1h/4h 冷静期结束时间戳(毫秒) |
|
||||
| `freeze_until_ms` | 倒计时结束时间戳(日冻结为下一交易日切点) |
|
||||
| `freeze_remaining_sec` | 服务端计算的剩余秒数(供调试) |
|
||||
|
||||
**仓位上限冻结**:当 **计入上限的** 活跃持仓数(不含趋势回调)≥ 实例 `.env` 的 `MAX_ACTIVE_POSITIONS`(默认 1)且账户无时间类冻结时,徽章显示 **仓位上限冻结**;此时 **新开仓** 被禁止,但 **顺势加仓**(在已有同向监控持仓上加仓)仍可用.仅存在趋势回调持仓时不触发该冻结.时间冻结(1h/4h/日)优先展示.
|
||||
|
||||
`risk_status.can_roll`:仓位上限冻结时为 `true`,表示顺势加仓不受该冻结限制.
|
||||
|
||||
## 前端倒计时
|
||||
|
||||
- 共用脚本:`static/account_risk_badge.js?v=4`
|
||||
- 样式:`static/account_risk_badge.css`
|
||||
- 展示格式:`4h冻结 · 3h 12m`;日冻结为距下一交易日切点剩余时间
|
||||
- 倒计时优先用服务端 `freeze_remaining_sec` 推算结束时刻,避免绝对时间戳与时区/脏数据偏差
|
||||
- 服务端在冷静期**已结束**或锚点无效时**自动清库**,避免重启后误读旧 `account_risk_state` 仍显示冻结
|
||||
- 无效的未来 `last_close_at_ms` **不会**被当作「现在」重启计时
|
||||
- 若当日手动平仓**已复盘**(journal 有说明)且 1h 窗口已过,即使 risk 表被误写也会强制恢复 **正常**
|
||||
- 勿与交易记录列表中的历史平仓时间混淆:风控只看 `account_risk_state` 表内 **最后一次用户主动平仓** 及其复盘结果
|
||||
|
||||
## 相关代码
|
||||
|
||||
- `account_risk_lib.py` — 状态机,`enrich_risk_status_countdown`,`apply_position_limit_risk`,`on_user_initiated_close`
|
||||
- `hub_bridge.py` — `/api/hub/account-risk/user-close`
|
||||
- `manual_trading_hub/hub.py` — 中控平仓成功后调用 user-close
|
||||
- `strategy_trend_register.py` — `stop_trend_pullback` 结束计划时登记风控
|
||||
- `tests/test_account_risk_lib.py`
|
||||
@@ -1,45 +0,0 @@
|
||||
# 每日自动划转(三所统一)
|
||||
|
||||
## 行为
|
||||
|
||||
在 `.env` 开启 `AUTO_TRANSFER_ENABLED=true` 后,监控轮询在**北京时间 `AUTO_TRANSFER_BJ_HOUR` 整点所在小时**内(默认 8:00–8:59)执行一次(按 **UTC 自然日** 去重):
|
||||
|
||||
| 交易账户 (`AUTO_TRANSFER_TO`,默认 swap) | 动作 |
|
||||
|------------------------------------------|------|
|
||||
| 余额 **低于** `AUTO_TRANSFER_AMOUNT` | 从 `AUTO_TRANSFER_FROM`(默认 funding)划入差额 |
|
||||
| 余额 **高于** `AUTO_TRANSFER_AMOUNT` | 将多余划回 `AUTO_TRANSFER_FROM` |
|
||||
| 与目标相差 < 0.01U | 跳过,不写划转 |
|
||||
| 存在 **active** 持仓(`order_monitors`,或 Gate回调已开仓计划) | **不划转**,写账簿 `skipped`,并**企业微信**说明「持仓中,本次资金无划转」 |
|
||||
|
||||
## 配置示例(目标 50U)
|
||||
|
||||
```env
|
||||
AUTO_TRANSFER_ENABLED=true
|
||||
AUTO_TRANSFER_AMOUNT=50
|
||||
AUTO_TRANSFER_FROM=funding
|
||||
AUTO_TRANSFER_TO=swap
|
||||
AUTO_TRANSFER_BJ_HOUR=8
|
||||
```
|
||||
|
||||
`AUTO_TRANSFER_AMOUNT` 与 `DAILY_START_CAPITAL`(每日开仓基数)**独立**.
|
||||
|
||||
API Key 须具备万向划转权限(与手动划转相同).
|
||||
|
||||
## 用脚本更新三所 `.env`
|
||||
|
||||
详见 **[env-sync-scripts.md](./env-sync-scripts.md)**.常用命令:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
|
||||
# 仅补全划转相关项
|
||||
python scripts/sync_four_exchange_transfer_env.py
|
||||
|
||||
# 目标 50U 并开启自动划转
|
||||
python scripts/sync_four_exchange_transfer_env.py --set-amount 50 --enable-auto-transfer
|
||||
|
||||
# 计仓 + 划转一并补全
|
||||
python scripts/sync_four_exchange_env.py --set-transfer-amount 50 --enable-auto-transfer
|
||||
|
||||
pm2 restart crypto-monitor-binance crypto-monitor-okx crypto-monitor-gate
|
||||
```
|
||||
@@ -1,81 +0,0 @@
|
||||
# 单日开仓次数限制(三所统一)
|
||||
|
||||
各交易实例(Binance / OKX / Gate)在 `.env` 中独立配置,互不影响.
|
||||
|
||||
## 交易日口径
|
||||
|
||||
- 以 **北京时间** `TRADING_DAY_RESET_HOUR`(默认 **8:00**)切分交易日,与统计,顶栏「交易日」一致.
|
||||
- **次日恢复**:过了切日时刻后 `session_date` 变为新日期,计数自动归零,无需清库.
|
||||
|
||||
## 计数口径
|
||||
|
||||
每成功新建一条 `order_monitors` 记录计 **1 次**,包括:
|
||||
|
||||
- 人工「实盘下单」
|
||||
- 关键位自动开仓
|
||||
- 其他写入 `order_monitors` 的成功开仓
|
||||
|
||||
平仓后再开仍算新的一单.当日总次数到硬上限后 **当天不再允许新开**(即使已空仓).
|
||||
|
||||
## 环境变量
|
||||
|
||||
在「交易执行 / 人工风控」段配置:
|
||||
|
||||
```env
|
||||
# 【单日开仓 AI 提醒】本交易日开仓次数达到该值时,企业微信推送 AI 克制提醒(不拦单)
|
||||
DAILY_OPEN_ALERT_THRESHOLD=5
|
||||
|
||||
# 【单日开仓硬上限】本交易日开仓次数 >= 该值后,禁止一切新开仓直至下一交易日;0=不启用
|
||||
DAILY_OPEN_HARD_LIMIT=0
|
||||
```
|
||||
|
||||
### 配置示例
|
||||
|
||||
```env
|
||||
# 保守户:3 次提醒,5 次封死
|
||||
DAILY_OPEN_ALERT_THRESHOLD=3
|
||||
DAILY_OPEN_HARD_LIMIT=5
|
||||
|
||||
# 仅提醒,不封(与旧版行为接近)
|
||||
DAILY_OPEN_ALERT_THRESHOLD=5
|
||||
DAILY_OPEN_HARD_LIMIT=0
|
||||
|
||||
# 严格户:到 3 次即封
|
||||
DAILY_OPEN_ALERT_THRESHOLD=2
|
||||
DAILY_OPEN_HARD_LIMIT=3
|
||||
```
|
||||
|
||||
建议 `DAILY_OPEN_ALERT_THRESHOLD <= DAILY_OPEN_HARD_LIMIT`(硬上限为 0 时除外).
|
||||
|
||||
## 程序行为
|
||||
|
||||
| 次数 | 行为 |
|
||||
|------|------|
|
||||
| 未达提醒阈值 | 正常开仓 |
|
||||
| 达到 `DAILY_OPEN_ALERT_THRESHOLD` | 成功开仓后 AI 企业微信提醒 |
|
||||
| 达到 `DAILY_OPEN_HARD_LIMIT`(>0) | `precheck_risk` 拒绝人工/关键位开仓;顶栏 `can_trade=false` |
|
||||
|
||||
硬限制与以下规则 **同时生效**(取交集):
|
||||
|
||||
- `TRADING_DAY_RESET_OPEN_GUARD_ENABLED`:切日前禁止新开
|
||||
- `MAX_ACTIVE_POSITIONS`:同时持仓上限
|
||||
- Gate:`precheck_trend_pullback_start` 同样校验单日硬上限
|
||||
|
||||
## 页面与接口
|
||||
|
||||
- 顶栏 / `api/account_snapshot` 返回 `opens_today`,`daily_open_hard_limit`,`daily_open_alert_threshold`.
|
||||
- 达硬上限时提示:`本交易日开仓 N/M 已达上限,次日 8:00 后恢复`(`M` 为配置的硬上限).
|
||||
|
||||
## 部署
|
||||
|
||||
修改各实例 `.env` 后重启对应 pm2 进程,例如:
|
||||
|
||||
```bash
|
||||
pm2 restart crypto_binance crypto_okx crypto_gate
|
||||
```
|
||||
|
||||
## 实现位置
|
||||
|
||||
- 共享逻辑:`daily_open_limit_lib.py`
|
||||
- 三所 `app.py`:`precheck_risk`,`can_trade`,`api/account_snapshot`,开仓成功后的 AI 提醒文案
|
||||
- 单元测试:`tests/test_daily_open_limit_lib.py`
|
||||
@@ -1,135 +0,0 @@
|
||||
# 内照明心与永久 K 线
|
||||
|
||||
## 概述
|
||||
|
||||
「内照明心」页(`/archive`)用于 **复盘语录 + 交易记录回顾 + 按需 K 线**.左侧维护每日复盘语录(最多 100 条);右侧按日期区间列出开仓记录,展示区间统计,并可展开 K 线图表对照单笔交易.
|
||||
|
||||
与行情区 `hub_kline.db`(15 天滚动缓存)**完全独立**:档案库只增不删,从建档起永久保留.
|
||||
|
||||
## 页面布局
|
||||
|
||||
| 区域 | 说明 |
|
||||
|------|------|
|
||||
| **复盘语录** | 左栏;按日期添加/编辑/删除,一日一条 |
|
||||
| **日期与筛选** | 顶栏:本日 / 本周 / 本月 / 自选区间;盈利单,亏损单,犯病,交易所,搜索 |
|
||||
| **区间统计** | 统计栏随日期选择自动更新(见下) |
|
||||
| **K 线图表** | 默认折叠;点「图表」或展开后按需加载 |
|
||||
| **交易记录** | 默认展开;犯病行 **红色字体**(无红底);可编辑标签与备注 |
|
||||
|
||||
## 日期区间
|
||||
|
||||
交易日按北京时间 **8:00** 切日(`TRADING_DAY_RESET_HOUR`).
|
||||
|
||||
| 模式 | 范围 |
|
||||
|------|------|
|
||||
| **本日** | 可选单个交易日(默认当前交易日) |
|
||||
| **本周** | 当周周一至当前交易日 |
|
||||
| **本月** | 当月 1 日至当前交易日 |
|
||||
| **区间** | 自选 `date_from`~`date_to`(含首尾交易日) |
|
||||
|
||||
## 区间统计(统计栏)
|
||||
|
||||
基于当前 **列表筛选结果**(含盈利/亏损/犯病勾选,合约搜索;交易所下拉仍限定数据源):
|
||||
|
||||
| 指标 | 说明 |
|
||||
|------|------|
|
||||
| 总开仓次数 | 区间内开仓笔数 |
|
||||
| 盈利单 / 亏损单 | 盈亏 > 0 / < 0 的笔数(持平不计) |
|
||||
| 平均盈利 / 平均亏损 | 盈利单,亏损单各自的均值(U) |
|
||||
| 最大盈利 / 最大亏损 | 单笔最大盈利,最大亏损(U) |
|
||||
| 犯病次数 / 占比 | `behavior_tag = sick` 的笔数及占开仓比例 |
|
||||
| 盈亏 | 区间内全部已平仓盈亏合计 |
|
||||
| 剔除犯病盈亏 | 排除犯病单后的盈亏合计 |
|
||||
| 各交易所 | 每所同上分项 |
|
||||
|
||||
在搜索框输入币种(如 `BTC`)后,统计栏与下方列表同步按该条件收窄.
|
||||
|
||||
## 数据约定
|
||||
|
||||
| 项 | 约定 |
|
||||
|----|------|
|
||||
| 交易来源 | 三所 `trade_records` + 未落库的 `strategy_trade_snapshots`,经 `/api/hub/trades/archive` 拉取 |
|
||||
| 犯病标签 | 中控 `trade_overlay.behavior_tag = sick` |
|
||||
| K 线真源 | 仅 **5m** 写入 `hub_symbol_archive.db` |
|
||||
| 建档种子 | 该币 **最早开仓** 向前 **30 天** 5m |
|
||||
| 增量同步 | 默认每 **4 小时** 补新 5m 至当前 |
|
||||
| 展示周期 | Tab:**5m / 15m / 1h / 4h**,默认 **15m** |
|
||||
| 视窗模式 | **持仓过程**(锚平仓,默认)/ **进场决策**(锚开仓) |
|
||||
| 时间跳转 | 输入 `YYYY-MM-DD HH:MM` 后点「跳转」 |
|
||||
|
||||
## 存储
|
||||
|
||||
- 默认路径:`manual_trading_hub/data/hub_symbol_archive.db`
|
||||
- 环境变量:`HUB_ARCHIVE_DB_PATH`
|
||||
- 表:
|
||||
- `archive_meta` — 建档元数据
|
||||
- `archive_bars_5m` — 永久 5m K 线
|
||||
- `archive_trade_cache` — 从实例同步的交易快照
|
||||
- `trade_overlay` — 犯病标签与备注(仅中控)
|
||||
- `archive_review_quotes` — 复盘语录
|
||||
|
||||
## API(中控 FastAPI)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/archive/meta` | 周期,交易所,同步间隔等 |
|
||||
| GET | `/api/archive/daily-trades` | 区间交易列表与统计(见 query) |
|
||||
| GET | `/api/archive/quotes` | 复盘语录列表 |
|
||||
| POST | `/api/archive/quotes` | 新增语录 |
|
||||
| PATCH | `/api/archive/quotes/{id}` | 更新语录 |
|
||||
| DELETE | `/api/archive/quotes/{id}` | 删除语录 |
|
||||
| GET | `/api/archive/ohlcv` | K 线视窗(`timeframe` / `mode` / `anchor_ms` / `at`) |
|
||||
| PATCH | `/api/archive/trade/{exchange_key}/{trade_id}` | 更新标签/备注 |
|
||||
| POST | `/api/archive/sync` | 立即同步三所交易 + K 线 |
|
||||
|
||||
`GET /api/archive/daily-trades` 主要 query:
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `period` | `today` / `week` / `month` / `range` |
|
||||
| `trading_day` | 本日模式下的交易日 `YYYY-MM-DD` |
|
||||
| `date_from` / `date_to` | 区间模式起止日 |
|
||||
| `exchange_key` | 可选,按交易所筛选 |
|
||||
| `filter_profit` / `filter_loss` / `filter_sick` | 过滤列表与统计 |
|
||||
| `search` | 合约 / 交易所 / 备注搜索(同步过滤列表与统计) |
|
||||
|
||||
返回 `stats` 含 `open_count`,`win_count`,`loss_count`,`win_rate`,`avg_win`,`avg_loss`,`profit_loss_ratio`,`max_win`,`max_loss`,`sick_count`,`sick_pct`,`pnl_total`,`pnl_ex_sick`,`by_exchange`.
|
||||
|
||||
实例侧:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/hub/trades/archive` | 近 N 天已平仓(`days` / `limit`) |
|
||||
|
||||
## 后台任务
|
||||
|
||||
Hub 启动后在 lifespan 中运行 `hub-archive-sync`:
|
||||
|
||||
1. 对各启用交易所调用 `/api/hub/trades/archive`
|
||||
2. 写入 `archive_trade_cache`
|
||||
3. 未建档币种:拉 30 天 5m 种子
|
||||
4. 已建档币种:增量补 5m
|
||||
|
||||
间隔:`HUB_ARCHIVE_SYNC_INTERVAL_SEC`(默认 14400).
|
||||
|
||||
## 代码位置
|
||||
|
||||
- `hub_symbol_archive_lib.py` — 库表,区间统计,种子,增量,聚合
|
||||
- `hub_trades_lib.py` — `fetch_trades_for_archive`
|
||||
- `hub_bridge.py` — 实例 `/api/hub/trades/archive`
|
||||
- `manual_trading_hub/hub.py` — 路由与后台同步
|
||||
- `manual_trading_hub/static/archive.js` — 内照明心前端
|
||||
|
||||
## 与行情区的区别
|
||||
|
||||
| | 行情区 | 内照明心 |
|
||||
|--|--------|----------|
|
||||
| DB | `hub_kline.db` | `hub_symbol_archive.db` |
|
||||
| 保留 | 15 天滚动删除 | 建档起永久 |
|
||||
| 周期 | 多周期直存/拉取 | 仅存 5m,高周期聚合 |
|
||||
| 用途 | 实时看盘 | 复盘语录与交易回顾 |
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [中控平仓与交易记录](trend-hub-close-and-trade-records.md)
|
||||
- [中控使用说明](../manual_trading_hub/使用说明.md)
|
||||
@@ -1,147 +0,0 @@
|
||||
# lib/ 共用模块结构
|
||||
|
||||
三所实例与中控共用的 Python 库,模板与静态资源统一放在仓库根目录的 **`lib/`** 下.部署单元(`crypto_monitor_*`,`manual_trading_hub`)仍保持独立目录与 PM2 配置不变.
|
||||
|
||||
**重构前快照 Git 标签**:`pre-lib-modularization`(可用 `git checkout pre-lib-modularization` 查看旧布局).
|
||||
**移除 gate_bot 前快照 Git 标签**:`pre-remove-gate-bot`.
|
||||
|
||||
---
|
||||
|
||||
## 顶层目录
|
||||
|
||||
```
|
||||
crypto_monitor/
|
||||
├── crypto_monitor_binance/ # 三所:各自 app + .env + PM2
|
||||
├── crypto_monitor_gate/
|
||||
├── crypto_monitor_okx/
|
||||
├── manual_trading_hub/ # 中控 + 子代理 agent
|
||||
│
|
||||
├── lib/ # 共用模块(本说明)
|
||||
│ ├── strategy/
|
||||
│ ├── key_monitor/
|
||||
│ ├── trade/
|
||||
│ ├── hub/
|
||||
│ ├── ai/
|
||||
│ ├── instance/
|
||||
│ ├── exchange/
|
||||
│ ├── common/
|
||||
│ └── paths.py
|
||||
│
|
||||
├── brand/ # 各所共用图标
|
||||
├── docs/
|
||||
├── deploy/
|
||||
├── scripts/
|
||||
├── tests/
|
||||
├── requirements.txt
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## lib/ 子包说明
|
||||
|
||||
| 子包 | 职责 | 主要模块 |
|
||||
|------|------|----------|
|
||||
| **`lib/strategy/`** | 策略交易(顺势加仓,趋势回调,快照与记录) | `strategy_register.py`,`strategy_trend_register.py`,`strategy_db.py`,`strategy_roll_*`,`strategy_trend_*` |
|
||||
| **`lib/strategy/templates/`** | 策略页 Jinja 模板(原 `strategy_templates/`) | `strategy_trading_page.html`,`strategy_roll_panel.html` 等 |
|
||||
| **`lib/key_monitor/`** | 关键位监控,斐波,假突破,止盈止损方案 | `key_monitor_lib.py`,`fib_key_monitor_lib.py`,`key_sl_tp_lib.py` 等 |
|
||||
| **`lib/trade/`** | 下单监控展示,计仓,账户风控,手动 SL/TP | `order_monitor_display_lib.py`,`position_sizing_lib.py`,`account_risk_lib.py` 等 |
|
||||
| **`lib/hub/`** | 中控 API,K 线,归档,计仓器,SSO/Bridge | `hub_bridge.py`,`hub_kline_store.py`,`hub_trades_lib.py` 等 |
|
||||
| **`lib/ai/`** | AI 复盘与文本生成 | `ai_client.py`,`ai_review_lib.py` |
|
||||
| **`lib/instance/`** | 中控 iframe 嵌入,导航,复盘图表 | `instance_embed_lib.py`,`focus_chart_lib.py`,`journal_chart_lib.py` |
|
||||
| **`lib/instance/templates/`** | 嵌入页片段(原 `embed_templates/`) | `embed_page_fragment.html` |
|
||||
| **`lib/exchange/`** | 特定交易所工具 | `gate_transfer_lib.py`,`okx_orders_lib.py` 等 |
|
||||
| **`lib/common/`** | 跨功能小工具 | `form_submit_lib.py`,`wechat_notify_lib.py` 等 |
|
||||
| **`lib/common/static/`** | 三所与中控共用的 JS/CSS(原根目录 `static/`) | `instance_theme.js`,`strategy_roll.js` 等 |
|
||||
|
||||
> **说明**:`hub_*` 命名表示「中控侧能力或行情聚合」,但部分模块(如 `hub_volume_rank_lib`,`hub_market_info_lib`)三所 `app.py` 也会调用,并非中控独占.
|
||||
|
||||
---
|
||||
|
||||
## 路径辅助函数
|
||||
|
||||
`lib/paths.py` 集中维护资源目录,避免硬编码:
|
||||
|
||||
```python
|
||||
from lib.paths import strategy_templates_dir, embed_templates_dir, common_static_dir
|
||||
|
||||
strategy_templates_dir() # .../lib/strategy/templates
|
||||
embed_templates_dir() # .../lib/instance/templates
|
||||
common_static_dir() # .../lib/common/static
|
||||
```
|
||||
|
||||
可选传入 `repo_root`(字符串或 `Path`),默认使用 `lib/` 的上级目录即仓库根.
|
||||
|
||||
---
|
||||
|
||||
## Python 导入约定
|
||||
|
||||
各部署目录在启动时将 **仓库根** 加入 `sys.path`(与重构前相同):
|
||||
|
||||
```python
|
||||
_REPO_ROOT = os.path.dirname(BASE_DIR) # 或 Path(__file__).resolve().parent.parent
|
||||
if _REPO_ROOT not in sys.path:
|
||||
sys.path.insert(0, _REPO_ROOT)
|
||||
```
|
||||
|
||||
之后使用 **`lib.<子包>.<模块>`** 形式导入,例如:
|
||||
|
||||
```python
|
||||
from lib.strategy.strategy_db import init_strategy_tables
|
||||
from lib.key_monitor.key_monitor_lib import check_key_monitors
|
||||
from lib.hub.hub_bridge import install_on_app
|
||||
from lib.ai.ai_client import ai_review
|
||||
```
|
||||
|
||||
策略注册仍在各所 `app.py` 末尾:
|
||||
|
||||
```python
|
||||
from lib.strategy.strategy_register import install_strategy_trading
|
||||
from lib.strategy.strategy_trend_register import install_strategy_trend
|
||||
|
||||
install_strategy_trading(app, _REPO_ROOT, app_module=sys.modules[__name__])
|
||||
install_strategy_trend(app, _REPO_ROOT, app_module=sys.modules[__name__])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 静态资源与 URL
|
||||
|
||||
- 三所页面仍通过 **`/static/...`** 访问共用脚本;`hub_bridge.install_instance_theme_static` 从 `lib/common/static/` 提供部分根级静态路由.
|
||||
- 各所目录下 **`static/`**(图标,上传图片等)仍为实例私有,未迁入 `lib/`.
|
||||
- 中控 `manual_trading_hub/hub.py` 通过 `_REPO_ROOT / "lib" / "common" / "static"` 挂载与三所共用的 badge,复盘 JS 等.
|
||||
|
||||
---
|
||||
|
||||
## 测试
|
||||
|
||||
在仓库根执行(需将根目录置于 Python 路径,或从根目录运行):
|
||||
|
||||
```bash
|
||||
cd /opt/crypto_monitor_user
|
||||
python -m unittest discover -s tests -p "test_*.py"
|
||||
```
|
||||
|
||||
测试文件内统一 `from lib.<子包>.<模块> import ...`.使用 `@patch` 时目标写完整模块路径,例如 `lib.hub.hub_calculator_lib._resolve_market`.
|
||||
|
||||
---
|
||||
|
||||
## 迁移脚本
|
||||
|
||||
一次性迁移由 `scripts/migrate_to_lib.py` 完成(移动文件 + 批量改写 import).**不要在已迁移后的仓库上重复执行**.
|
||||
|
||||
---
|
||||
|
||||
## 后续可选整理
|
||||
|
||||
- 三所 `app.py` 体量接近,可逐步抽取公共 `exchange_app` 基座(改动面大,单独规划).
|
||||
- `manual_trading_hub/okx_orders_lib.py` 为 agent 本地副本,可与 `lib/exchange/okx_orders_lib.py` 合并去重.
|
||||
- 可引入 `pyproject.toml` + `pip install -e .`,替代 `sys.path.insert`(长期维护更规范).
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [README.md](../README.md) — 总览与部署
|
||||
- [策略交易说明.md](../策略交易说明.md)
|
||||
- [manual_trading_hub/使用说明.md](../manual_trading_hub/使用说明.md)
|
||||
@@ -0,0 +1,38 @@
|
||||
# 软件授权说明(用户版)
|
||||
|
||||
授权服务:[https://sq.bz121.com/](https://sq.bz121.com/)
|
||||
|
||||
## 激活流程
|
||||
|
||||
1. 部署并启动本系统(三所或中控任一页面)。
|
||||
2. 浏览器打开 **`/license`**(未授权时会自动跳转)。
|
||||
3. 复制页面上的 **设备 ID**。
|
||||
4. 联系微信 **dekun03** 购买套餐,获得激活码(新购 / 续费 / 换机)。
|
||||
5. 在 `/license` 粘贴激活码并兑换。
|
||||
|
||||
## 套餐(参考)
|
||||
|
||||
| 套餐 | 天数 |
|
||||
|------|------|
|
||||
| 月卡 | 30 |
|
||||
| 季卡 | 90 |
|
||||
| 年卡 | 365 |
|
||||
|
||||
续费:在原到期日基础上叠加天数。换机:旧设备立即失效,新设备需换机码。
|
||||
|
||||
## 环境变量
|
||||
|
||||
在仓库根目录创建 **`license.env`**(勿提交 Git),或写入各实例 `.env`:
|
||||
|
||||
```env
|
||||
LICENSE_API_URL=https://sq.bz121.com
|
||||
LICENSE_CLIENT_KEY=与授权站 CLIENT_API_KEY 相同
|
||||
# 可选:断网宽限小时数(默认 72)
|
||||
# LICENSE_OFFLINE_GRACE_HOURS=72
|
||||
# 本地调试可临时关闭门禁(生产勿开)
|
||||
# LICENSE_DISABLED=false
|
||||
```
|
||||
|
||||
许可状态保存在仓库根目录 **`data/license_state.json`**(三所 + 中控共用)。
|
||||
|
||||
系统约每 **3 天** 向授权站校验一次;明确过期或换机后旧设备将无法使用。
|
||||
@@ -1,83 +0,0 @@
|
||||
# 宏观关键数据 · 风控前置
|
||||
|
||||
中控 **系统设置** 手动录入 FOMC / CPI / 就业数据发布时间,在 **监控区** 发布前后各 1 小时给出风险提示.
|
||||
**不看公布结果,不解读数据**,仅作波动窗口前的行为提醒;**不拦截下单**(与账户冷静期/日冻结独立).
|
||||
|
||||
## 支持的数据类型
|
||||
|
||||
| 类型 ID | 显示名称 |
|
||||
|---------|----------|
|
||||
| `fomc` | FOMC 联邦基金利率 |
|
||||
| `cpi` | 美国 CPI 通胀 |
|
||||
| `employment` | 就业与劳工数据 |
|
||||
|
||||
每项在设置中 **名称下拉三选一**,**发布时间** 手动输入(北京时间,精确到分钟).FOMC 只录 **一条**(决议公布时刻即可).
|
||||
|
||||
## 风险窗口
|
||||
|
||||
- 默认:**发布时间 ±1 小时**
|
||||
- 发布前 **30 分钟内**:文案加强为「即将发布」
|
||||
- 窗口结束后横幅自动消失;设置列表中过期记录逐步不再展示
|
||||
|
||||
环境变量(可选):
|
||||
|
||||
```env
|
||||
HUB_MACRO_WINDOW_BEFORE_SEC=3600
|
||||
HUB_MACRO_WINDOW_AFTER_SEC=3600
|
||||
HUB_MACRO_IMMINENT_BEFORE_SEC=1800
|
||||
HUB_MACRO_LIST_FUTURE_DAYS=60
|
||||
```
|
||||
|
||||
## 监控区提示文案
|
||||
|
||||
读取当前监控板:**任意交易所有持仓 = 有仓**,否则 = 无仓.
|
||||
|
||||
| 场景 | 提示要点 |
|
||||
|------|----------|
|
||||
| 无仓 · 窗口内 | 建议等待,避免新开仓 |
|
||||
| 有仓 · 窗口内 | 注意仓位,勿加仓,检查止损/减仓 |
|
||||
| 即将发布(30 分钟内) | 在上述基础上标注剩余分钟数 |
|
||||
|
||||
## 存储
|
||||
|
||||
- SQLite:`manual_trading_hub/data/hub_macro_calendar.db`
|
||||
- 可覆盖:`HUB_MACRO_CALENDAR_DB_PATH`
|
||||
|
||||
表 `macro_events`:`event_type`, `event_at_ms`, `note`, `created_at_ms`, `updated_at_ms`
|
||||
同类型 + 同一发布时间不可重复录入.
|
||||
|
||||
## API(均需中控登录)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/macro-calendar/meta` | 类型列表与窗口说明 |
|
||||
| GET | `/api/macro-calendar/events` | 设置页列表 |
|
||||
| GET | `/api/macro-calendar/active` | 当前处于窗口内的事件(监控横幅) |
|
||||
| POST | `/api/macro-calendar/events` | 新增 |
|
||||
| PATCH | `/api/macro-calendar/events/{id}` | 更新 |
|
||||
| DELETE | `/api/macro-calendar/events/{id}` | 删除 |
|
||||
|
||||
请求体示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_type": "cpi",
|
||||
"event_at": "2026-06-18 20:30",
|
||||
"note": "可选备注"
|
||||
}
|
||||
```
|
||||
|
||||
## 使用习惯
|
||||
|
||||
1. 每月在金十/日历查看 **FOMC,CPI,非农** 公布时间
|
||||
2. 中控 **系统设置 → 宏观关键数据** 录入 1~3 条
|
||||
3. 到点前后监控区顶栏出现 **宏观风控** 横幅;无操作则窗口结束后自动消失
|
||||
|
||||
## 与账户风控的关系
|
||||
|
||||
| 模块 | 时机 | 作用 |
|
||||
|------|------|------|
|
||||
| 宏观日历 | **事前** | 已知高波动窗口,提醒等待或管仓 |
|
||||
| 账户冷静期/日冻结 | **事后** | 用户主动平仓后的惩罚性限制 |
|
||||
|
||||
宏观提醒 **不触发** 冷静期,不计入手动平仓次数.
|
||||
@@ -1,31 +0,0 @@
|
||||
# 实盘下单 · 预估盈亏比
|
||||
|
||||
## 功能
|
||||
|
||||
三所(Binance / OKX / Gate)**实盘下单监控**表单中,在「开仓」按钮前显示 **预估盈亏比**.
|
||||
|
||||
- **价格模式**:填完币种,方向,止损价,止盈价后,调用 `GET /api/order_defaults` 取标记价,按几何距离计算 RR.
|
||||
- **百分比模式**:填完币种,方向,止损%,止盈% 后拉快照校验币种,再显示 RR(`止盈% / 止损%`).
|
||||
- **固定盈亏比模式**:盈亏比由输入框直接指定;下方预览条显示预估风险/盈利/盈亏比(不再在表单行内显示预估止盈价).
|
||||
|
||||
- **以损定仓**(`POSITION_SIZING_MODE=risk`):预估风险 = 当前交易基数 × `risk%`.
|
||||
- **全仓杠杆**(`full_margin`):预估风险 = 合约可用 × 缓冲比例 × 杠杆(BTC/ETH 与山寨按 `.env` 配置)× 止损距离比例,与开仓时 `calc_risk_amount_from_plan` 一致.
|
||||
|
||||
## 前端实现
|
||||
|
||||
- 共享脚本:`static/manual_order_rr_preview.js`
|
||||
- 各所 `templates/index.html` 引入并在 `MANUAL_MIN_PLANNED_RR` 定义后执行:
|
||||
```js
|
||||
ManualOrderRrPreview.wire({ minRr: MANUAL_MIN_PLANNED_RR });
|
||||
```
|
||||
- 展示元素:`#order-rr-preview`(开仓按钮左侧)
|
||||
- 颜色:≥ 最低要求为绿色,低于为红色,无效/取价失败为红色或灰色
|
||||
|
||||
## 与提交校验
|
||||
|
||||
提交时仍走原有 `calcClientRr` / `calcClientRrFromPct` 与 `rejectManualOrderRr`;预估仅用于下单前参考,不替代服务端风控.
|
||||
|
||||
## 校验记录
|
||||
|
||||
- `node --check static/manual_order_rr_preview.js`
|
||||
- `tests/test_manual_order_rr_preview.py`:RR 公式与三所 `calc_rr_ratio` 口径一致
|
||||
@@ -1,73 +0,0 @@
|
||||
# 计仓模式(三所统一)
|
||||
|
||||
## 配置
|
||||
|
||||
在各实例 `.env` 中设置(**仅能通过 env 切换,修改后须重启进程**):
|
||||
|
||||
```env
|
||||
# risk(默认)= 以损定仓
|
||||
# full_margin = 全仓杠杆(合约可用保证金 × 比例)
|
||||
POSITION_SIZING_MODE=risk
|
||||
FULL_MARGIN_BUFFER_RATIO=0.98
|
||||
|
||||
# 关键位程序自动单(默认 false,详见各所 关键位自动下单说明.md)
|
||||
KEY_AUTO_ORDER_ENABLED=false
|
||||
```
|
||||
|
||||
切换为全仓杠杆前:**交易所须无持仓**(`MAX_ACTIVE_POSITIONS` 默认 1,全仓模式会强制单仓).
|
||||
|
||||
## 模式说明
|
||||
|
||||
| 模式 | 保证金计算 | 杠杆 | 允许入口 |
|
||||
|------|------------|------|----------|
|
||||
| `risk` | `RISK_PERCENT` × 交易资金,按止损距离反推 | 表单可选 / 同步交易所 | 实盘人工,关键位自动(须 `KEY_AUTO_ORDER_ENABLED=true`),趋势回调,顺势加仓 |
|
||||
| `full_margin` | **合约账户可用 USDT × `FULL_MARGIN_BUFFER_RATIO`**(保留 2 位小数) | BTC/ETH **10x**,其它 **5x**(与 `BTC_LEVERAGE`/`ALT_LEVERAGE` 一致) | **实盘人工下单**,**关键位触价**(须 `KEY_AUTO_ORDER_ENABLED=true`);阻力/支撑仅提醒 |
|
||||
|
||||
全仓模式下:
|
||||
|
||||
- **`KEY_AUTO_ORDER_ENABLED=false`(默认)** 时,触价程序自动单也不执行.
|
||||
- **`KEY_AUTO_ORDER_ENABLED=true`** 时,仅触价可程序自动开仓;箱体/斐波等仍禁止.
|
||||
- 仍校验 **计划盈亏比**(实盘用 `MANUAL_MIN_PLANNED_RR`;触价开仓用 `KEY_AUTO_MIN_PLANNED_RR`).
|
||||
- 下单张数由 `prepare_order_amount` + 交易所 `amount_to_precision` 决定.
|
||||
- `order_monitors.initial_stop_loss` 仍记录**开仓时**止损快照;交易记录复盘以该快照为准.
|
||||
- 已存在的 **箱体突破 / 收敛突破 / 斐波 / 假突破** 监控:进程启动时**自动撤销**并企业微信通知.
|
||||
|
||||
## 不允许(全仓模式)
|
||||
|
||||
- 关键位:箱体突破,收敛突破,斐波,假突破(添加时拒绝;已存在则启动时撤销).
|
||||
- 趋势回调,顺势加仓(策略入口返回明确错误).
|
||||
|
||||
**允许(须 `KEY_AUTO_ORDER_ENABLED=true`):** 关键位 **回调触价开仓** / **突破触价开仓**(程序盯价,触达/穿越计划入场后市价成交,无交易所挂单;全仓下仅允许一条待触发).
|
||||
|
||||
## `KEY_AUTO_ORDER_ENABLED`(三所统一,默认 `false`)
|
||||
|
||||
| 计仓 | 开关 | 效果 |
|
||||
|------|------|------|
|
||||
| `risk` | `false` | 关闭全部关键位程序自动单(含触价);顺势加仓不受影响 |
|
||||
| `risk` | `true` | 关键位全套自动(旧行为) |
|
||||
| `full_margin` | `false` | 关闭触价自动 |
|
||||
| `full_margin` | `true` | 仅触价自动 |
|
||||
|
||||
详见各实例目录 `关键位自动下单说明.md`.
|
||||
|
||||
## 用脚本更新三所 `.env`
|
||||
|
||||
详见 **[env-sync-scripts.md](./env-sync-scripts.md)**.常用命令:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
|
||||
# 仅补全计仓相关项(缺省 risk,缓冲 0.98)
|
||||
python scripts/sync_four_exchange_position_sizing_env.py
|
||||
|
||||
# 无仓后切换全仓
|
||||
python scripts/sync_four_exchange_position_sizing_env.py --set-mode full_margin
|
||||
|
||||
# 无仓后切回以损定仓
|
||||
python scripts/sync_four_exchange_position_sizing_env.py --set-mode risk
|
||||
|
||||
# 计仓 + 划转一并补全
|
||||
python scripts/sync_four_exchange_env.py
|
||||
|
||||
pm2 restart crypto-monitor-binance crypto-monitor-okx crypto-monitor-gate
|
||||
```
|
||||
@@ -1,45 +0,0 @@
|
||||
# Chrome 桌面快捷方式图标说明
|
||||
|
||||
## 图标从哪来?
|
||||
|
||||
用 Chrome **「创建快捷方式」** 或 **「安装应用」** 时,桌面/开始菜单图标**不是**操作系统自带的,而是浏览器从**你打开的网站**读取的,优先级大致为:
|
||||
|
||||
1. `manifest.webmanifest` 里的 `icons`(192×192,512×512)
|
||||
2. `link rel="apple-touch-icon"`(约 180×180)
|
||||
3. `link rel="icon"` / `favicon.ico`
|
||||
4. 若都没有 → 灰色地球或网页标题首字
|
||||
|
||||
本仓库已在 **中控** 与 **三所监控页** 配置品牌图标.PNG/ICO 由 **Pillow** 生成.
|
||||
|
||||
- **中控**:深色圆角底 + 青绿趋势线 + K 线,安装名「复盘系统中控」
|
||||
- **三所**:各所用交易所标识色与字标(币安菱形 / OKX 方块 / Gate G),安装名分别为「Binance 交易系统」「OKX 交易系统」「Gate 交易系统」
|
||||
|
||||
## 文件位置
|
||||
|
||||
| 位置 | 访问路径 |
|
||||
|------|----------|
|
||||
| 源稿 | `brand/icon.svg`,`brand/icons/*.png`,`brand/icons/{binance,okx,gate}/` |
|
||||
| Manifest | `brand/manifest.webmanifest`(中控),`brand/manifest.{binance,okx,gate}.webmanifest` |
|
||||
| 中控 | `manual_trading_hub/static/icons/` → `/assets/icons/...` |
|
||||
| 三所 | `crypto_monitor_*/static/icons/` → `/static/icons/...` |
|
||||
|
||||
## 重新生成 / 同步
|
||||
|
||||
```bash
|
||||
python scripts/generate_brand_icons.py
|
||||
python scripts/sync_brand_icons.py
|
||||
git pull # 服务器部署后
|
||||
pm2 restart …
|
||||
```
|
||||
|
||||
## 快捷方式仍显示旧图标?
|
||||
|
||||
Chrome / Windows 会**缓存** favicon:
|
||||
|
||||
1. 浏览器打开站点,**Ctrl+F5** 强刷
|
||||
2. 删除旧快捷方式,重新「创建快捷方式」
|
||||
3. 必要时清除 Chrome 站点数据(该域名)后再创建
|
||||
|
||||
## 自定义图标
|
||||
|
||||
可替换 `brand/icon.svg` 后重新运行上面两条命令;或把设计好的 `icon-192.png`,`icon-512.png` 放入 `brand/icons/` 再 `sync_brand_icons.py`.
|
||||
@@ -1,34 +0,0 @@
|
||||
# 策略文档
|
||||
|
||||
各交易实例的人工下单策略,供 UI / 复盘对齐.
|
||||
|
||||
| 文档 | 实例 | 状态 |
|
||||
|------|------|------|
|
||||
| [binance-alt-trend-long.md](./binance-alt-trend-long.md) | 币安山寨·多头趋势 | v0.4 讨论稿 |
|
||||
| [okx-trend-both.md](./okx-trend-both.md) | OKX·多空趋势 | v0.4 讨论稿 |
|
||||
| [gate-intraday.md](./gate-intraday.md) | Gate·BTC 日内 | v0.2 |
|
||||
|
||||
## 约定
|
||||
|
||||
- **不写盈亏比(趋势户)**:币安/OKX 止盈/止损随行情人工设定,趋势 MD 不量化 RR.
|
||||
- **日内例外**:Gate 日内 **最低 1:1** 才开仓,持仓目标可动态调整(见 [gate-intraday.md](./gate-intraday.md)).
|
||||
- **界面(趋势户 · v0.4)**:两级 — **反转**(启动 A/B)| **顺势**(大分歧 A/B)| **波段**(小分歧);`lib/trade/entry_model_lib.py` 三所共用.
|
||||
- **界面短标签(日内户)**:`假破` / `结构突破`.
|
||||
- **多空共用三字**:方向由「做多/做空」表达,不复用为「大分歧A多」等.
|
||||
- **自动联动**:大分歧 A/B → 趋势单;**小分歧 → 波段单**;平仓写入交易记录 `entry_reason`;复盘「填入」自动带入.
|
||||
- **杠杆默认**:BTC/ETH **10x**,其它 **5x**;与开仓类型无关(env `BTC_LEVERAGE` / `ALT_LEVERAGE`).
|
||||
- **日内 profile 独立**:env 启用 `TRADE_SYMBOL_WHITELIST=BTC,ETH` 且限制开启时,**不显示** 大分歧三项(Gate);开仓类型为 **假破 / 结构突破**(见 gate-intraday.md).
|
||||
- **日内 0 点出场**:策略称「0 点平仓」;程序为 `FORCE_CLOSE_ENABLED` + `FORCE_CLOSE_BJ_HOUR=0`,交易记录 `result=强制清仓`(与表单 1h/2h/4h `time_close` 无关).
|
||||
- **策略模块独立**:趋势回调,顺势加仓不走上述三项.
|
||||
|
||||
## 系统实现
|
||||
|
||||
- 库:`lib/trade/entry_model_lib.py`
|
||||
- 趋势户表单:`lib/instance/templates/order_entry_model_fields.html`
|
||||
- 日内判定:`is_intraday_trading_profile()`(白名单仅含 BTC/ETH)
|
||||
- 0 点强平:`force_close_before_reset()`(三所 `app.py`);env `FORCE_CLOSE_ENABLED` / `FORCE_CLOSE_BJ_HOUR`
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [计仓模式](../position-sizing-mode.md)
|
||||
- [趋势回调策略](../trend-pullback-strategy.md)
|
||||
@@ -1,194 +0,0 @@
|
||||
# 币安山寨·多头趋势账户
|
||||
|
||||
> **状态**:v0.4(反转·启动 A/B 两级 UI 已实现;策略正文 + `entry_model_lib`)
|
||||
|
||||
---
|
||||
|
||||
## 1. 账户定位
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 交易所 | 币安合约 |
|
||||
| 方向 | **仅做多**(`TRADE_DIRECTION=long_only`) |
|
||||
| 计仓 | `POSITION_SIZING_MODE=risk` |
|
||||
| 关键位自动单 | `KEY_AUTO_ORDER_ENABLED=false` |
|
||||
| UI profile | **趋势户**(非 BTC/ETH 白名单日内) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 开仓类型(两级 UI · 三所共用)
|
||||
|
||||
### 2.1 趋势户:反转 / 顺势 / 波段
|
||||
|
||||
| 第一级 | 第二级 | code | 联动 |
|
||||
|--------|--------|------|------|
|
||||
| **反转** | 启动 A / 启动 B | `launch_a` / `launch_b` | 趋势单 |
|
||||
| **顺势** | 大分歧 A / 大分歧 B | `big_div_a` / `big_div_b` | 趋势单 |
|
||||
| **波段** | 小分歧 | `small_div` | 波段单 |
|
||||
|
||||
实现:`lib/trade/entry_model_lib.py` + `order_entry_model_fields.html` + `order_entry_model.js`(币安 / OKX / Gate 趋势户共用).
|
||||
|
||||
### 2.2 杠杆(与开仓类型无关)
|
||||
|
||||
| 币种 | 默认杠杆 |
|
||||
|------|----------|
|
||||
| BTC,ETH | **10x**(`BTC_LEVERAGE`) |
|
||||
| 其它山寨 | **5x**(`ALT_LEVERAGE`) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 反转·启动(做多,讨论定稿)
|
||||
|
||||
**性质**:反转 — 在**新主升确认之前**的作战;与大分歧(顺势)不是同一行情阶段.
|
||||
|
||||
**周期**:背离与箱体以 **4h** 为主;**日线与 4h 同处筑底阶段时,结构边界以日线为准**.作战后半段可用 **5m** 节奏(启动 A2).
|
||||
|
||||
### 3.0 流程总览
|
||||
|
||||
```text
|
||||
MACD 背离(严格筛选)→ 标「参考高点」
|
||||
↓
|
||||
第 1 次到高点附近 → 不做(V 形)
|
||||
↓
|
||||
回落箱内(中间复杂形态不盯)
|
||||
↓
|
||||
第 2 次到高点附近 → 开战
|
||||
├─ 见小收敛 ──────────→ 启动 A(A1)
|
||||
└─ 不见收敛 ──────────→ 启动 B(实体突破)
|
||||
│
|
||||
├─ 成 → 主升(后续才用顺势·大分歧 / 波段·小分歧)
|
||||
└─ 败 → 止损(正常)
|
||||
↓
|
||||
跌破箱高一半 → 暂弃,等再次到高点附近
|
||||
仍在上半区 + 5m 不创新低 + N 字 → 启动 A(A2)
|
||||
```
|
||||
|
||||
### 3.1 背离:何时进入候选池(MACD · 4h)
|
||||
|
||||
全部满足才承认「有过背离」:
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 级别 | **至少 4h** MACD 底背离 |
|
||||
| 首次背离不做 | 第一段背离只观察,不交易 |
|
||||
| 通道式下跌不做 | 顺滑通道下滑中的背离,不当反转依据 |
|
||||
| 波段结构 | 有明显的波段高点,低点 |
|
||||
| 分段下跌 | **三段及以上**明显下跌之后,才开始寻找背离 |
|
||||
|
||||
背离确认后:**标注最后一个显著高点为「参考高点」**(后文「高点附近」均相对此点).
|
||||
|
||||
> 横盘**时长不量化**;关键是顺序:**跌 → 背离 → 背离后的震荡**,而非下跌中继.
|
||||
|
||||
### 3.2 参考高点:两次摸高
|
||||
|
||||
| 次序 | 规则 |
|
||||
|------|------|
|
||||
| **第 1 次**到参考高点附近 | **不关注,不做**.假突破,未突破都算「到过附近」— 视为 **V 形反弹**风险区 |
|
||||
| 回落箱内 | 箱内复杂形态**不是主战场**,不强行交易 |
|
||||
| **第 2 次**到参考高点附近 | **开始关注**,进入作战区 |
|
||||
|
||||
### 3.3 启动 B(实体突破)
|
||||
|
||||
**条件**:第二次(及以后)靠近参考高点,且**未见**再次摸高前的小收敛.
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 入场 | **实体突破**参考高点 / 箱体上沿(影线刺破不算) |
|
||||
| 成功 | 走出主升 → 后续单型转为顺势·大分歧 / 波段·小分歧 |
|
||||
| 失败 | 突破后未主升,回到箱内震荡 → **止损属正常** → 若满足 §3.4,下一笔用 **启动 A(A2)** |
|
||||
|
||||
### 3.4 启动 A(结构内 / 试仓,含 A1 与 A2)
|
||||
|
||||
**性质**:主升**确认前**在箱内找风险可控入场;**不是**小分歧.
|
||||
|
||||
#### A1 · 第二次摸高前的小收敛
|
||||
|
||||
- 在第 2 次到参考高点**之前**,出现**小的收敛结构** → 突破前企稳进场.
|
||||
- 若**看不到**小收敛,不强行做 A1,改走 **启动 B**(§3.3).
|
||||
|
||||
#### A2 · 启动 B 失败后的 5m N 字(仍记启动 A)
|
||||
|
||||
在启动 B 止损后:
|
||||
|
||||
| 暂弃 | 可试启动 A2 |
|
||||
|------|-------------|
|
||||
| 回箱后继续跌,且**跌破箱体高度一半** | **未**跌破箱一半 |
|
||||
| 暂不看,直至**再次**到参考高点附近 | 且 **5m 不创新低** |
|
||||
| | 且 **突破 5m 高点** → 按 **5m N 字形突破** 试仓 |
|
||||
|
||||
- 5m 止损一般不大;此位置**允许多次试错**(突破路径约可错 2 次;5m 试仓约可错 3 次 — 同一参考高点周期内,具体计数实操自定).
|
||||
- **5m N 字试仓不单列开仓类型**,复盘统一记 **启动 A**(可备注「A2 / 5m N」).
|
||||
|
||||
### 3.5 与大分歧,小分歧的边界
|
||||
|
||||
| | 反转·启动 | 顺势·大分歧 | 波段·小分歧 |
|
||||
|---|-----------|-------------|-------------|
|
||||
| 前端 | 跌 → 背离 → 箱 → 两次摸高 | 主升已确立 | 主升已确立 |
|
||||
| V 形 | 第 1 次摸高不做 | — | — |
|
||||
| 5m N 试仓 | **启动 A(A2)** | — | 不同于小分歧 |
|
||||
| 第三次不做 | — | — | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## 4. 顺势·大分歧(趋势单)
|
||||
|
||||
**前提**:**主升浪已确立**,上方仍有空间;前端是主升里的整理,**不要**求「跌 → 背离 → 箱 → 两次摸高」那条反转链.
|
||||
|
||||
### 4.1 大分歧 A
|
||||
|
||||
- 4h/日线大结构向上
|
||||
- 5m/15m 收敛,**不创新低** 企稳进(不等突破)
|
||||
|
||||
### 4.2 大分歧 B
|
||||
|
||||
- 同上大级别多头结构
|
||||
- **突破确认** 后入场(实体突破优先)
|
||||
|
||||
---
|
||||
|
||||
## 5. 波段·小分歧(波段单)
|
||||
|
||||
**前提**:主升**已走出**;反转链(启动 A/B)进行中**不做**小分歧.
|
||||
|
||||
- **前两次**可做,**第三次不做**
|
||||
- 低吸为主,不追突破
|
||||
- 入场:二次探底 → N 字突破;或 5m 三均线重新多头排列
|
||||
|
||||
---
|
||||
|
||||
## 6. 纪律
|
||||
|
||||
1. 不做空
|
||||
2. 反转:首次背离不做;通道跌背离不做;第 1 次摸参考高点不做(V 形)
|
||||
3. 顺势:第三次小分歧不做新单;小分歧不追突破
|
||||
4. 止盈/止损/是否手平:**随行情**,本文档不量化 RR
|
||||
|
||||
---
|
||||
|
||||
## 7. 持仓与出场(定性)
|
||||
|
||||
| 单型 | 说明 |
|
||||
|------|------|
|
||||
| 启动 A/B | 赌新主升;B 失败可转 A2;未确立主升前不做小分歧 |
|
||||
| 大分歧 | 可长持;途中两次小分歧后远目标未到,**可手平** |
|
||||
| 小分歧 | 短拿,常手平 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 系统字段
|
||||
|
||||
| 操作 | 字段 |
|
||||
|------|------|
|
||||
| 下单 | `order_monitors.entry_model`:`launch_a` / `launch_b` / `big_div_a` / `big_div_b` / `small_div` |
|
||||
| 平仓 | `trade_records.entry_reason` = 界面标签 |
|
||||
| 复盘 | 与开仓类型一致 + 策略项 +「其他」 |
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|------|------|------|
|
||||
| v0.4 | 2026-07-06 | 反转·启动 A/B 两级 UI 上线(共用 entry_model_lib) |
|
||||
| v0.2 | 2026-07-06 | 定稿 UI 短标签(大分歧/小分歧);实现代码联动 |
|
||||
| v0.1 | 2026-07-06 | 讨论稿 |
|
||||
@@ -1,68 +0,0 @@
|
||||
{
|
||||
"exchange": "binance",
|
||||
"title": "币安 · 开仓检查清单",
|
||||
"version": "v0.4",
|
||||
"groups": [
|
||||
{
|
||||
"title": "账户与方向",
|
||||
"items": [
|
||||
"本账户仅做多,不做空",
|
||||
"计仓模式为以损定仓(risk),关键位自动单已关闭",
|
||||
"已明确第一级:反转 / 顺势 / 波段(两级下拉已上线)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "反转 · 背离与箱体(启动 A/B 共同前置)",
|
||||
"items": [
|
||||
"4h MACD 底背离(非首次背离,非通道式下跌中的背离)",
|
||||
"有明显波段高/低点,且三段及以上明显下跌后才认背离",
|
||||
"背离后处于震荡箱体(时长不量化;非 V 形急跌急拉)",
|
||||
"已标注参考高点(最后一个显著高点)",
|
||||
"第 1 次到参考高点附近 → 不做;第 2 次到附近 → 才进入作战区"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "反转 · 启动 B(实体突破)",
|
||||
"items": [
|
||||
"第 2 次到参考高点附近,且未见再次摸高前的小收敛",
|
||||
"实体突破参考高点/箱体上沿(非仅影线)",
|
||||
"突破失败回箱止损属正常;跌破箱高一半则暂弃直至再次到高点附近"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "反转 · 启动 A(结构内,含 A1 / A2)",
|
||||
"items": [
|
||||
"性质:主升确认前;不是小分歧",
|
||||
"A1:第 2 次摸高前出现小收敛 → 突破前企稳",
|
||||
"A2:启动 B 止损后,未跌破箱高一半 + 5m 不创新低 + 5m N 字突破试仓",
|
||||
"5m 试仓记为启动 A(不单列类型);小止损允许多次试错"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "顺势 · 大分歧 A / B",
|
||||
"items": [
|
||||
"主升已确立,上方仍有空间(非跌后背离筑底阶段)",
|
||||
"大分歧A:5m/15m 收敛且不创新低企稳",
|
||||
"大分歧B:突破已确认,优先实体突破"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "波段 · 小分歧",
|
||||
"items": [
|
||||
"主升已确立;反转链进行中不做小分歧",
|
||||
"第三次小分歧 → 不做新单",
|
||||
"不追突破;二次探底 N 字或 5m 三均线重新多头"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "杠杆与出场",
|
||||
"items": [
|
||||
"杠杆:BTC/ETH 10x,其它山寨 5x(可选手改但须有理由)",
|
||||
"止盈止损随行情人工设定,不在此清单量化 RR"
|
||||
]
|
||||
}
|
||||
],
|
||||
"footnotes": [
|
||||
"v0.4:两级 UI 已实现;启动 A 含 A1 收敛与 A2(B 失败后 5m N 字)."
|
||||
]
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"exchange": "gate",
|
||||
"title": "Gate · BTC 日内 · 开仓检查清单",
|
||||
"version": "v0.2",
|
||||
"groups": [
|
||||
{
|
||||
"title": "方向与均线过滤",
|
||||
"items": [
|
||||
"仅交易 BTC,同时仅 1 仓",
|
||||
"15m 21/55/144 排列清晰(多或空),纠缠则不做",
|
||||
"1H 方向与 15m 不冲突",
|
||||
"21 均线关系满足:回踩支撑 / 站稳上方(多)或反弹承压 / 压在下方(空)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "开仓类型 A / B",
|
||||
"items": [
|
||||
"已选定:假破 或 结构突破(二选一)",
|
||||
"假破:扫流动性后回到结构内,5m N 字 + 15m 顶/底分型齐全",
|
||||
"结构突破:15m 收盘价站稳关键位,非仅刺破",
|
||||
"止损带宽 0.4%~1.5%,超出则不做",
|
||||
"下单前空间至少 1:1,不足则不做"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "一日节奏与笔数",
|
||||
"items": [
|
||||
"非周末;在早窗 / 晚窗计划时段内",
|
||||
"今日笔数未达上限 3,连错未达 2 笔",
|
||||
"宽幅震荡(S1)时降频或不做",
|
||||
"0 点前须了结(系统强制清仓);本清单不含手动平仓"
|
||||
]
|
||||
}
|
||||
],
|
||||
"footnotes": [
|
||||
"系统:FORCE_CLOSE_ENABLED 开启时,北京时间 0 点自动强制清仓(result=强制清仓)"
|
||||
]
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"exchange": "okx",
|
||||
"title": "OKX · 开仓检查清单",
|
||||
"version": "v0.4",
|
||||
"groups": [
|
||||
{
|
||||
"title": "账户与方向",
|
||||
"items": [
|
||||
"已选定做多或做空,且与 4H/大级别结构方向一致",
|
||||
"趋势户 profile(非 Gate 日内 BTC/ETH 白名单)",
|
||||
"计仓模式为以损定仓(risk),关键位自动单已关闭",
|
||||
"同一币种无未计划的对冲叠仓",
|
||||
"已明确第一级:反转 / 顺势 / 波段(两级下拉已上线)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "反转 · 背离与箱体(启动 A/B 共同前置)",
|
||||
"items": [
|
||||
"做多:4h MACD 底背离;做空:4h MACD 顶背离",
|
||||
"非首次背离;非通道式涨跌中的背离;明显波段高低点 + 三段及以上涨/跌后才认背离",
|
||||
"背离后处于震荡箱体(时长不量化;非 V 形急拉急杀)",
|
||||
"做多:已标参考高点;做空:已标参考低点",
|
||||
"第 1 次到参考极值附近 → 不做;第 2 次到附近 → 才进入作战区"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "反转 · 启动 B(实体突破)",
|
||||
"items": [
|
||||
"第 2 次到参考极值附近,且未见再次摸极值前的小收敛",
|
||||
"做多:实体突破参考高点/箱顶;做空:实体跌破参考低点/箱底",
|
||||
"突破失败回箱止损属正常",
|
||||
"做多:跌破箱高一半暂弃;做空:涨破箱低一半暂弃;直至再次到极值附近"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "反转 · 启动 A(结构内,含 A1 / A2)",
|
||||
"items": [
|
||||
"性质:主趋势确认前;不是小分歧",
|
||||
"A1:第 2 次摸极值前出现小收敛 → 突破前企稳",
|
||||
"A2 做多:B 止损后未跌破箱一半 + 5m 不创新低 + 5m N 字",
|
||||
"A2 做空:B 止损后未涨破箱一半 + 5m 不创新高 + 5m 倒 N 字",
|
||||
"5m 试仓记为启动 A(不单列);小止损允许多次试错"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "顺势 · 大分歧 A / B",
|
||||
"items": [
|
||||
"主趋势已确立(非涨/跌后背离筑底/筑顶阶段)",
|
||||
"做多大分歧A:5m/15m 收敛且不创新低;做空:不创新高",
|
||||
"大分歧B:突破已确认,优先实体突破"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "波段 · 小分歧",
|
||||
"items": [
|
||||
"主趋势已确立;反转链进行中不做小分歧",
|
||||
"第三次小分歧 → 不做新单",
|
||||
"做多:二次探底 N 字 / 5m 三均线多头;做空:二次探顶倒 N / 5m 空头"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "杠杆与出场",
|
||||
"items": [
|
||||
"杠杆:BTC/ETH 10x,其它山寨 5x(可选手改但须有理由)",
|
||||
"止盈止损随行情人工设定,不在此清单量化 RR"
|
||||
]
|
||||
}
|
||||
],
|
||||
"footnotes": [
|
||||
"v0.4:两级 UI 反转/顺势/波段;做多细则见 binance-alt-trend-long.md §3."
|
||||
]
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
# Gate·BTC 日内账户
|
||||
|
||||
> **状态**:v0.2(策略定稿;**0 点强平已实现**;日内 UI 隐藏平仓/委托/移动保本 **待实现**)
|
||||
|
||||
---
|
||||
|
||||
## 1. 账户定位
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 交易所 | Gate 合约 |
|
||||
| 品种 | **仅 BTC** |
|
||||
| 方向 | **多空都做**(由过滤条件决定,非手选方向) |
|
||||
| 计仓 | `POSITION_SIZING_MODE=full_margin`(全仓杠杆) |
|
||||
| UI profile | **日内户**(`TRADE_SYMBOL_WHITELIST=BTC,ETH` 且限制开启;本策略只交易 BTC) |
|
||||
| 与趋势户关系 | **不使用** 大分歧 A/B/小分歧;**不使用**「趋势单 / 波段单」手选 |
|
||||
|
||||
### 资金与杠杆(执行约定)
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 账户规模 | 约 300U(测试阶段) |
|
||||
| 日交易基数 | **50U**(早 8:00 重置为 50U,不延续前日阶梯) |
|
||||
| 单笔阶梯 | 上一笔 **+10U / −10U** 调节下一笔基数(赢 60U / 亏 40U 等) |
|
||||
| 杠杆 | **10× 全仓** |
|
||||
| 一次一单 | 同时仅 **1** 个 Gate 仓位 |
|
||||
| 止损带宽 | **0.4%~1.5%**(结构要求更宽则 **不做**) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 周期分层
|
||||
|
||||
自上而下,**先定能不能做,再做哪一类**:
|
||||
|
||||
| 层级 | 周期 | 作用 |
|
||||
|------|------|------|
|
||||
| 方向过滤 | **1H** | 大方向;**不得与 15m 排列反向** |
|
||||
| 均线 + 结构 | **15m** | 21/55/144 排列,顶底分型,结构识别,**B 类收盘突破** |
|
||||
| 触发 | **5m** | **A 类**:N 字形突破(配合 15m 分型) |
|
||||
| 方法 | 裸 K + 三均线 | 形态确认,入场与止损锚点 |
|
||||
|
||||
**不做「趋势单」概念**:持仓以 **小时** 计,当日了结;与币安/OKX 多日趋势户区分.
|
||||
|
||||
---
|
||||
|
||||
## 3. 方向过滤(必过)
|
||||
|
||||
### 3.1 15m 三均线(21 / 55 / 144)
|
||||
|
||||
| 15m 排列 | 只允许 |
|
||||
|----------|--------|
|
||||
| **多头排列**(21 > 55 > 144) | **只做多** |
|
||||
| **空头排列**(21 < 55 < 144) | **只做空** |
|
||||
| 纠缠,粘合,不符合 | **不做** |
|
||||
|
||||
### 3.2 与 1H 同向
|
||||
|
||||
- **做多**:15m 多头排列,且 **1H 不得为空头排列**(1H 均线不能与 15m 方向相反).
|
||||
- **做空**:15m 空头排列,且 **1H 不得为多头排列**.
|
||||
- 1H/15m 方向冲突 → **当日该方向不做**.
|
||||
|
||||
### 3.3 21 均线关系(才允许开仓)
|
||||
|
||||
入场须与 **21 均线** 发生有效关系,避免 distant 追单:
|
||||
|
||||
| 方向 | 要求(定性) |
|
||||
|------|----------------|
|
||||
| **做多** | 多头排列下,**回踩 21 附近获支撑** 或 **站稳 21 上方** 后再按 playbook 入场 |
|
||||
| **做空** | 空头排列下,**反弹 21 附近承压** 或 **压在 21 下方** 后再按 playbook 入场 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 开仓类型(仅两类)
|
||||
|
||||
界面日后仅两个短标签(全称见下表 hover / 本文):
|
||||
|
||||
| 界面标签 | 存储 code(建议) | 本质 |
|
||||
|----------|-------------------|------|
|
||||
| **假破** | `liquidity_false_break` | 流动性扫单 → 假突破验证 → **5m N 字** → **15m 顶/底分型** |
|
||||
| **结构突破** | `structure_breakout` | **15m 结构有效突破**(**收盘确认**) |
|
||||
|
||||
子结构 **不单独占主下拉**,可在复盘备注或二级标签中记录.
|
||||
|
||||
---
|
||||
|
||||
## 5. A 类:假破(流动性 / 假突破)
|
||||
|
||||
**适用**:关键位附近 **扫止损** 后价格 **回到结构内**,陷阱确认后再反向做.
|
||||
|
||||
### 5.1 流程
|
||||
|
||||
```text
|
||||
1H/15m 方向 + 21 均线过滤通过
|
||||
→ 假突破出现(扫高/扫低)
|
||||
→ 验证为「假」(收回结构内 / 反向裸 K 确认)
|
||||
→ 5m 走出 N 字(二次探底/探顶后,沿允许方向突破)
|
||||
→ 15m 出现底分型(多)或顶分型(空)
|
||||
→ 入场
|
||||
```
|
||||
|
||||
### 5.2 做多 / 做空(对称)
|
||||
|
||||
| 步骤 | 做多 | 做空 |
|
||||
|------|------|------|
|
||||
| 假破 | 向下扫低后快速拉回支撑/箱上 | 向上扫高后跌回阻力/箱下 |
|
||||
| 5m N 字 | 扫低 → 反弹 → 不破前低 → 向上突破 | 扫高 → 回落 → 不过前高 → 向下突破 |
|
||||
| 15m 确认 | **底分型** | **顶分型** |
|
||||
| 止损 | 假破极值或 N 字低点 **外侧**(仍须落在 0.4%~1.5%) | 对称 |
|
||||
| 目标 | **最低 1:1**;之后 **随行情动态** 部分止盈,移动止损或延伸 | 对称 |
|
||||
|
||||
### 5.3 注意
|
||||
|
||||
- **须等假破验证完成**,扫完不追.
|
||||
- **5m N + 15m 分型** 为入场必要条件,缺一不可.
|
||||
- 与大级别 **宽幅震荡(S1)** 叠加时假信号多,优先 **降频或不做**.
|
||||
|
||||
---
|
||||
|
||||
## 6. B 类:结构突破
|
||||
|
||||
**适用**:15m 上结构清晰,方向与均线排列一致,**收盘突破** 后顺势做.
|
||||
|
||||
### 6.1 子结构(均属 B 类)
|
||||
|
||||
双顶,双底,头肩顶/底,收敛(三角/楔形),箱体等——**统一记为「结构突破」**.
|
||||
|
||||
### 6.2 突破确认
|
||||
|
||||
- **以 15m K 线收盘价为准** 突破关键位(颈线,箱边,收敛边界等).
|
||||
- **仅刺破,未收盘站稳** → **不算** 有效突破,不做.
|
||||
- 可选:**收盘突破后回踩** 再进(裸 K 确认),仍须满足 21 均线关系与 **≥1:1** 空间.
|
||||
|
||||
### 6.3 止损与目标
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 止损 | 结构另一侧或突破位回退点 **外侧**(0.4%~1.5%,超出则不做) |
|
||||
| 目标 | 下单前 **至少 1:1**;到位后 **随行情动态** 调整,不写死固定 RR |
|
||||
| 空间不足 | 最近阻力/支撑导致 **达不到 1:1** → **不做** |
|
||||
|
||||
---
|
||||
|
||||
## 7. 行情状态(辅助过滤)
|
||||
|
||||
| 状态 | 特征 | Gate 动作 |
|
||||
|------|------|-----------|
|
||||
| **S0 趋势** | 1H/15m 排列清晰,高低点有序 | 正常:A/B 均可 |
|
||||
| **S1 宽幅震荡** | 大箱横盘多日,均线反复穿 | **降频或不做** |
|
||||
| **S2 末期/选边** | 贴边收敛,刚突破或假破频发 | 优先 **A 假破** 或 **B 收敛突破** |
|
||||
|
||||
---
|
||||
|
||||
## 8. 一日节奏与笔数
|
||||
|
||||
| 项 | 规则 |
|
||||
|----|------|
|
||||
| 周末 | **不开新仓** |
|
||||
| 早窗 | 约 **9:00**(8:00~12:00 内),**计划内第 1 笔** |
|
||||
| 下午 | **默认不开新仓**(持仓可保留至晚窗) |
|
||||
| 晚窗 | 约 **21:00**(20:00~23:00 内),**计划内第 2 笔** |
|
||||
| 第 3 笔 | 仅当 **未连错 2 笔**,且 **早/晚有一笔为止损出场**,可 **补 1 笔** |
|
||||
| 日上限 | **最多 3 笔** |
|
||||
| **连错 2 笔** | **当日不再开新仓**(第 3 笔名额作废) |
|
||||
|
||||
**连错计数**:
|
||||
|
||||
| 出场 | 是否算「错 1 笔」 |
|
||||
|------|------------------|
|
||||
| **计划止损**触发 | ✅ 算 |
|
||||
| **0 点强制清仓**(系统结果 `强制清仓`)且亏损 | ✅ 算 |
|
||||
| 止盈 / ≥1:1 按计划平 | ❌ 不算 |
|
||||
| 0 点强制清仓且盈利或平推 | ❌ 不算 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 出场与统计纪律
|
||||
|
||||
### 9.1 盈亏比
|
||||
|
||||
- 开仓前:**第一目标空间 ≥ 止损距离(最低 1:1)**.
|
||||
- 持仓中:目标 **随行情动态** 调整;本文档 **不量化** 固定止盈比例.
|
||||
|
||||
### 9.2 禁止「手动止损」
|
||||
|
||||
- **亏损出场** 必须来自 **开仓时设定的计划止损**(交易所或监控等价执行).
|
||||
- **禁止** 盘中亏着 **手点平仓** 充当止损(破坏统计与连错规则).
|
||||
- 若违规手动平亏:**视为当日纪律失败,建议停手**;复盘结果 **不得** 记为「止损」糊弄统计.
|
||||
|
||||
### 9.3 时间出场:仅 0 点(程序已实现)
|
||||
|
||||
- **唯一** 时间类出场:**当日 0:00(北京时间)前必须空仓**(赚赔都平).
|
||||
- **不使用** 下单表单里的 1h / 2h / 4h「开仓后 N 小时平」(`time_close`);与本策略无关.
|
||||
- **程序兜底**(三所共用,Gate 已启用):
|
||||
|
||||
| env | 说明 |
|
||||
|-----|------|
|
||||
| `FORCE_CLOSE_ENABLED=true` | 开启整点强制清仓 |
|
||||
| `FORCE_CLOSE_BJ_HOUR=0` | 北京时间 **0 点那一小时**(00:00~00:59)执行 |
|
||||
|
||||
- 实现:`force_close_before_reset()`(各实例 `app.py` 后台循环调用).
|
||||
- 行为:对该小时仍 **active** 的 `order_monitors` **市价全平**,取消交易所触发单,写交易记录.
|
||||
- **系统结果字段**:`result = 强制清仓`;备注含「北京时间 0:00 整点风控清仓」.
|
||||
- **策略口语「0 点平仓」= 系统「强制清仓」**,统计连错时按 §8 盈亏判定,不按字段名区分.
|
||||
|
||||
> **与 `TRADING_DAY_RESET_HOUR=8` 无关**:后者只切 **交易日**(统计,8 点前禁开等),**不会**自动平仓.
|
||||
|
||||
### 9.4 允许的出场类型(统计用)
|
||||
|
||||
| 策略说法 | 系统 `result` | 说明 |
|
||||
|----------|---------------|------|
|
||||
| 止盈 | 止盈 / 移动止盈 / 保本止盈 等 | 计划止盈或 ≥1:1 后按计划/动态平 |
|
||||
| 止损 | 止损 | 仅 **计划止损** 触发 |
|
||||
| 0 点平仓 | **强制清仓** | 整点风控兜底(§9.3) |
|
||||
| ~~手动平仓~~ | 手动平仓 | **策略禁止**(除极端技术故障等,须复盘说明) |
|
||||
|
||||
---
|
||||
|
||||
## 10. A / B 如何选择(当日)
|
||||
|
||||
| 盘面 | 优先 |
|
||||
|------|------|
|
||||
| 刚扫流动性,回到箱内 | **A 假破** |
|
||||
| 结构清晰,排列已顺,收敛末端 | **B 结构突破** |
|
||||
| 大箱乱扫,均线粘合 | **不做** |
|
||||
|
||||
早/晚窗 **有形态才做**,无形态 = **0 笔**,不占额度.
|
||||
|
||||
---
|
||||
|
||||
## 11. 与其它账户边界
|
||||
|
||||
| 账户 | 周期 | 持仓 | 本户勿混 |
|
||||
|------|------|------|----------|
|
||||
| 币安 | 日线/4H 事件 | 数天~数周 | 不要用 Gate 扛隔夜趋势 |
|
||||
| OKX | 4H 波段滚仓 | 数小时~数天 | 勿与 Gate 同向同结构叠隔夜 |
|
||||
| **Gate 日内** | 1H 过滤 + 15m/5m | **当日 0 点前** | 见上文 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 系统对接
|
||||
|
||||
### 12.1 已实现
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 日内 profile 判定 | `is_intraday_trading_profile()`(`lib/trade/entry_model_lib.py`) |
|
||||
| 0 点强制清仓 | `FORCE_CLOSE_ENABLED` + `FORCE_CLOSE_BJ_HOUR`;`force_close_before_reset()`;结果 **`强制清仓`** |
|
||||
| UI 标识 | 顶栏 **强制清仓 已开启** 徽章 + 持仓卡片 **倒计时**(三所 + 中控) |
|
||||
| 交易记录展示 | 三所 UI / 中控:`强制清仓` 与止损同类 badge |
|
||||
| 三所统一 | 币安 / OKX / Gate 同一函数与 env;**将来改日内只需各所 `.env` 打开,无需改代码** |
|
||||
|
||||
Gate 当前建议 env(节选):
|
||||
|
||||
```env
|
||||
FORCE_CLOSE_ENABLED=true
|
||||
FORCE_CLOSE_BJ_HOUR=0
|
||||
TRADING_DAY_RESET_HOUR=8
|
||||
```
|
||||
|
||||
### 12.2 待实现(UI / 纪律)
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 开仓类型 | 界面 **`假破` / `结构突破`**(code:`liquidity_false_break` / `structure_breakout`);**无** trend/swing 手选 |
|
||||
| 写入字段 | `trade_records.entry_model` / 复盘下拉同两项 |
|
||||
| 隐藏操作 | 日内 profile 下 **隐藏** 平仓,委托,移动保本(**实例页 + 中控**,`intraday_discipline` / `order_entry_profile=intraday`) |
|
||||
| 隐藏表单项 | 不展示 1h/2h/4h 时间平仓,移动保本勾选(避免与 §9.3 混用) |
|
||||
| 后端可选 | 严格模式下拒绝 `del_order` / 改委托 API |
|
||||
|
||||
---
|
||||
|
||||
## 13. 修订记录
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|------|------|------|
|
||||
| v0.1 | 2026-07-06 | 定稿:BTC 日内;1H+15m 均线;A 假破(5m N+15m 分型);B 结构突破(15m 收盘);早1晚1/最多3笔/连错2停;禁手动止损;仅 0 点强平 |
|
||||
| v0.2 | 2026-07-06 | §9.3/§12:对齐 `FORCE_CLOSE_*` 与系统结果「强制清仓」;区分 `time_close` / `TRADING_DAY_RESET_HOUR`;Gate 已启用说明 |
|
||||
@@ -1,157 +0,0 @@
|
||||
# OKX·多空趋势账户
|
||||
|
||||
> **状态**:v0.4(讨论稿:反转·启动 A/B;**UI 仍为 v0.2 三档**,两级下拉待实现)
|
||||
|
||||
---
|
||||
|
||||
## 1. 账户定位
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 交易所 | OKX 永续合约 |
|
||||
| 方向 | **做多 + 做空**(`TRADE_DIRECTION=both`,可按需限制) |
|
||||
| 计仓 | `POSITION_SIZING_MODE=risk` |
|
||||
| 关键位自动单 | `KEY_AUTO_ORDER_ENABLED=false` |
|
||||
| UI profile | **趋势户**(非 Gate BTC/ETH 日内白名单) |
|
||||
|
||||
**选方向原则**:开仓前确认 4H / 日线大级别与所选「做多/做空」一致;逆势单不在本策略范围内.
|
||||
|
||||
做多侧反转细则与 [binance-alt-trend-long.md §3](./binance-alt-trend-long.md) 同构;本文 **§3.2** 给出做空镜像.
|
||||
|
||||
---
|
||||
|
||||
## 2. 开仓类型(规划与现状)
|
||||
|
||||
### 2.1 规划:两级选择(与币安共用实现)
|
||||
|
||||
| 第一级 | 第二级 | 做多 | 做空 |
|
||||
|--------|--------|------|------|
|
||||
| **反转** | 启动 A / 启动 B | `launch_a` / `launch_b` | 同 code,方向在表单 |
|
||||
| **顺势** | 大分歧 A / B | `big_div_a` / `big_div_b` | 同左 |
|
||||
| **波段** | 小分歧 | `small_div` | 同左 |
|
||||
|
||||
`lib/trade/entry_model_lib.py` + 共用模板/JS,三所趋势户一致.
|
||||
|
||||
### 2.2 现状:下单监控 UI
|
||||
|
||||
两级下拉:**性质** → **类型**;提交 `entry_model` code.
|
||||
|
||||
### 2.3 杠杆
|
||||
|
||||
BTC/ETH **10x**,其它 **5x**;与方向,开仓类型无关.
|
||||
|
||||
---
|
||||
|
||||
## 3. 反转·启动(讨论定稿)
|
||||
|
||||
**性质**:反转 — 在新一轮主趋势**确认之前**作战;与顺势·大分歧不是同一阶段.
|
||||
|
||||
**周期**:背离与箱体以 **4h** 为主;**日线与 4h 同阶段时,结构边界以日线为准**;A2 可用 **5m** 节奏.
|
||||
|
||||
### 3.1 做多(跌后筑底 → 新主升)
|
||||
|
||||
与币安 [§3](./binance-alt-trend-long.md) 一致,摘要如下:
|
||||
|
||||
```text
|
||||
4h MACD 底背离(严格筛选)→ 标「参考高点」
|
||||
第 1 次到高点附近 → 不做(V 形)
|
||||
回落箱内 → 第 2 次到高点附近 → 开战
|
||||
├─ 见小收敛 → 启动 A(A1)
|
||||
└─ 不见收敛 → 启动 B(实体突破)
|
||||
B 失败回箱 → 跌破箱一半暂弃;否则 5m 不创新低 + N 字 → 启动 A(A2)
|
||||
```
|
||||
|
||||
| 环节 | 规则 |
|
||||
|------|------|
|
||||
| 背离 | 4h MACD **底背离**;**首次**不做;**通道式下跌**不做;明显波段高低点 + **三段及以上**跌后才开始找背离 |
|
||||
| 参考点 | 背离后标 **最后一个显著高点** |
|
||||
| 两次摸高 | 第 1 次到高点附近(假破/不破都算)**不做**;第 2 次才关注 |
|
||||
| 启动 B | 无小收敛 → **实体突破**参考高点/箱顶 |
|
||||
| 启动 A | A1:摸高前小收敛;A2:B 止损后,**未跌破箱一半** + **5m 不创新低** + **5m N 字** |
|
||||
|
||||
### 3.2 做空(涨后筑顶 → 新主跌,镜像)
|
||||
|
||||
| 环节 | 做多 | 做空(镜像) |
|
||||
|------|------|----------------|
|
||||
| 前端结构 | **跌** → 背离 → 箱 | **涨** → 背离 → 箱 |
|
||||
| 背离 | 4h MACD **底背离** | 4h MACD **顶背离** |
|
||||
| 背离过滤 | 首次不做;**通道式下跌**不做;三段及以上**跌** | 首次不做;**通道式上涨**不做;三段及以上**涨** |
|
||||
| 参考点 | **参考高点** | **参考低点**(最后一个显著低点) |
|
||||
| 两次摸极值 | 第 1 次到**高点**附近不做(V 形反弹) | 第 1 次到**低点**附近不做(V 形下跌) |
|
||||
| 作战区 | 第 2 次到**高点**附近 | 第 2 次到**低点**附近 |
|
||||
| 启动 B | **实体跌破**参考低点/箱底 | 同上(向下实体突破) |
|
||||
| 启动 A1 | 第二次摸高前**小收敛** | 第二次摸低前**小收敛** |
|
||||
| 启动 A2 | B 失败后**未跌破**箱一半;**5m 不创新低**;破 **5m 高** N 字 | B 失败后**未涨破**箱一半;**5m 不创新高**;破 **5m 低**倒 N 字 |
|
||||
| 成功后 | 主升 → 顺势大分歧 / 小分歧 | 主跌 → 顺势大分歧 / 小分歧 |
|
||||
|
||||
> 横盘**时长不量化**;顺序为 **涨/跌 → 背离 → 背离后震荡**,非趋势中继.
|
||||
|
||||
### 3.3 与顺势,波段的边界
|
||||
|
||||
| | 反转·启动 | 顺势·大分歧 | 波段·小分歧 |
|
||||
|---|-----------|-------------|-------------|
|
||||
| 做多前端 | 跌→背离→箱→两次摸高 | 主升已确立 | 主升已确立 |
|
||||
| 做空前端 | 涨→背离→箱→两次摸低 | 主跌已确立 | 主跌已确立 |
|
||||
| V 形 | 第 1 次摸极值不做 | — | — |
|
||||
| 5m N 试仓 | **启动 A(A2)**,非小分歧 | — | — |
|
||||
| 第三次不做 | — | — | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## 4. 顺势·大分歧(趋势单)
|
||||
|
||||
**前提**:主趋势**已确立**;上方(多)或下方(空)仍有空间;**不要**求反转链(§3).
|
||||
|
||||
| | 做多 | 做空 |
|
||||
|---|------|------|
|
||||
| **大分歧 A** | 4h/日线多头;5m/15m 收敛,**不创新低**企稳 | 4h/日线空头;5m/15m 收敛,**不创新高**企稳 |
|
||||
| **大分歧 B** | **向上突破**确认(实体优先) | **向下突破**确认(实体优先) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 波段·小分歧(波段单)
|
||||
|
||||
**前提**:主趋势**已走出**;反转链进行中**不做**小分歧.
|
||||
|
||||
- **前两次**可做,**第三次不做**;不追突破
|
||||
- **做多**:二次探底 → N 字;或 5m 三均线重新多头
|
||||
- **做空**:二次探顶 → 倒 N;或 5m 三均线重新空头
|
||||
|
||||
---
|
||||
|
||||
## 6. 纪律
|
||||
|
||||
1. 方向与大级别一致;不做顺手反向单;同一币种避免未计划对冲叠仓
|
||||
2. **反转**:首次背离不做;通道式涨跌中的背离不做;第 1 次摸参考极值不做(V 形)
|
||||
3. **顺势**:第三次小分歧不做;小分歧不追突破
|
||||
4. 止盈/止损/是否手平:**随行情**,本文档不量化 RR
|
||||
|
||||
---
|
||||
|
||||
## 7. 持仓与出场(定性)
|
||||
|
||||
| 单型 | 做多 | 做空 |
|
||||
|------|------|------|
|
||||
| 启动 A/B | 赌新主升;B 失败可 A2;确立前不做小分歧 | 赌新主跌;同上镜像 |
|
||||
| 大分歧 | 可长持;两次小分歧后远目标未到可手平 | 同左 |
|
||||
| 小分歧 | 短拿,常手平 | 短拿,常手平 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 系统字段
|
||||
|
||||
| 操作 | 字段 |
|
||||
|------|------|
|
||||
| 下单(现状) | `entry_model` + `trade_style`;方向在订单侧 |
|
||||
| 下单(规划) | `launch_a` / `launch_b` + 两级 UI |
|
||||
| 平仓 / 复盘 | `entry_reason` = 界面标签;与币安一致 |
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|------|------|------|
|
||||
| v0.4 | 2026-07-06 | 反转·启动 A/B(做多同币安 §3;做空镜像);两级 UI 规划 |
|
||||
| v0.3 | 2026-07-06 | 独立完整策略说明 |
|
||||
| v0.2 | 2026-07-06 | 定稿 UI 短标签 |
|
||||
@@ -1,184 +0,0 @@
|
||||
# 趋势回调:中控平仓与交易记录(检阅备忘)
|
||||
|
||||
本文档汇总 **中控手动结束趋势计划**,**交易记录 / 策略记录** 写入规则,以及 **三所展示统一**,**补仓表计价** 相关修复,便于自行检阅与排错.
|
||||
|
||||
适用仓库:`crypto_monitor`(Binance / OKX / + `manual_trading_hub`).
|
||||
|
||||
---
|
||||
|
||||
## 1. 中控手动平仓会不会写交易记录?
|
||||
|
||||
**会.** 在实例已部署 **`80226ee` 及之后** 代码并 **重启对应 Flask** 的前提下:
|
||||
|
||||
中控点击 **「结束计划」** → 实例执行市价平仓 + 结束计划 → **同时写入**:
|
||||
|
||||
| 目标 | 表 | 页面入口 |
|
||||
|------|-----|----------|
|
||||
| 策略记录 | `strategy_trade_snapshots` | 顶栏 **策略交易记录** → 左栏「趋势回调记录」 |
|
||||
| 交易记录 | `trade_records` | 顶栏 **交易记录与复盘** |
|
||||
|
||||
手动结束的结果字段为 **「手动平仓」**(亏损时也不会被改成「止损」).
|
||||
|
||||
---
|
||||
|
||||
## 2. 调用链(三所统一)
|
||||
|
||||
```
|
||||
manual_trading_hub
|
||||
POST /api/trend/{exchange_id}/stop
|
||||
→ 实例 POST /api/hub/trend/stop/{plan_id}
|
||||
→ stop_trend_pullback(pid)
|
||||
→ 市价平仓 + 撤单
|
||||
→ _finalize_plan(cfg, conn, row, "手动平仓", exit_price)
|
||||
```
|
||||
|
||||
共用实现:`strategy_trend_register.py`(三所同一套,各所的 `stop_trend_pullback` 也调用 `_finalize_plan`).
|
||||
|
||||
---
|
||||
|
||||
## 3. `_finalize_plan` 写入顺序(修复后)
|
||||
|
||||
1. 写 **策略快照** `save_trend_plan_snapshot` → `strategy_trade_snapshots`
|
||||
2. 撤该品种挂单
|
||||
3. 若尚无 `trade_records.trend_plan_id = 计划ID`:
|
||||
- 更新当日 session 资金
|
||||
- **`insert_trade_record`** 写入交易记录
|
||||
4. 更新 `trend_pullback_plans.status`(`stopped_manual` / `stopped_sl` / `stopped_tp`)
|
||||
5. **`conn.commit()`** 一次提交
|
||||
|
||||
要点:**先写交易记录,再结束计划**,避免「计划已结束,交易记录未写入」的半成功状态.
|
||||
|
||||
---
|
||||
|
||||
## 4. 曾出现的 Bug(#4 ONDO 漏记)
|
||||
|
||||
**现象**:策略记录有(止损 -2.71U),**交易记录没有**.
|
||||
|
||||
**原因**:各所的 `insert_trade_record` 曾 **缺少 `entry_reason` 参数**,而 `_finalize_plan` 固定传入 `entry_reason="趋势回调"`,触发:
|
||||
|
||||
```text
|
||||
TypeError: insert_trade_record() got an unexpected keyword argument 'entry_reason'
|
||||
```
|
||||
|
||||
策略快照在异常 **之前** 已插入,交易记录插入失败,故只出现在策略记录页.
|
||||
|
||||
**修复提交**:`80226ee`
|
||||
|
||||
- `insert_trade_record` 增加 `entry_reason`
|
||||
- `_call_insert_trade_record`:按各所函数 **签名过滤** 参数,避免未知字段导致失败
|
||||
- 调整写入顺序:交易记录 → 计划结束 → commit
|
||||
|
||||
---
|
||||
|
||||
## 5. 历史漏记补录
|
||||
|
||||
对已结束,策略快照在,交易记录缺的计划(如 #4):
|
||||
|
||||
```bash
|
||||
cd /opt/crypto_monitor_user # 或本机仓库根目录
|
||||
|
||||
# 先预览
|
||||
python scripts/backfill_trend_trade_records.py \
|
||||
--db crypto_monitor_gate/crypto.db --dry-run
|
||||
|
||||
# 确认后写入
|
||||
python scripts/backfill_trend_trade_records.py \
|
||||
--db crypto_monitor_gate/crypto.db --apply
|
||||
```
|
||||
|
||||
其它所将 `--db` 换成对应 `crypto.db` 路径即可.
|
||||
|
||||
---
|
||||
|
||||
## 6. 与「保本移交」的区别
|
||||
|
||||
| 操作 | 策略记录 | 交易记录 |
|
||||
|------|----------|----------|
|
||||
| 中控 **结束计划**(手动平仓) | 计划结束时写入 | **同一时刻**写入 |
|
||||
| **保本移交** | 移交时写入策略快照 | **不立即写**;持仓移交到 `order_monitors`,**后续平仓** 再写入 `trade_records` |
|
||||
|
||||
---
|
||||
|
||||
## 7. 三所展示统一(中控 ↔ 实例)
|
||||
|
||||
### 7.1 数据 enrich 入口
|
||||
|
||||
| 场景 | 函数 |
|
||||
|------|------|
|
||||
| 实例策略页 | `enrich_trend_plan` |
|
||||
| 中控 `/api/hub/monitor` | `enrich_trend_plan_for_hub` → 同上 |
|
||||
| 补仓明细表 | `attach_trend_dca_levels` → `enrich_trend_dca_levels_with_tp` |
|
||||
|
||||
在 `hub_bridge` 安装后调用 `patch_trend_hub_enrich`,与另外三所 `install_strategy_trend` 行为一致.
|
||||
|
||||
### 7.2 补仓表「触发价 / 加仓后均价」
|
||||
|
||||
**禁止**为凑均价 **反推虚构成交价**(曾错误出现做多补仓触发价 0.3941 等离谱数值).
|
||||
|
||||
**`trend_leg_display_price`(三所唯一口径)**:
|
||||
|
||||
| 列 | 规则 |
|
||||
|----|------|
|
||||
| **触发价** | `leg_fill_prices_json` 有记录 → 实际成交价;无记录 → **计划网格价** |
|
||||
| **末档已补仓的加仓后均价** | 与顶部均价一致,取 **交易所持仓 `entry_price`**(`avg_entry_price`) |
|
||||
| **顶部均价** | 优先交易所 live `entry_price`,非计划库内估算值 |
|
||||
|
||||
修复提交:`08082eb`(移除反推成交价逻辑).
|
||||
|
||||
### 7.3 中控静态页
|
||||
|
||||
`manual_trading_hub/static/app.js`:趋势浮盈亏计算 **优先** `trendPlan.avg_entry_price`,与计划卡一致.
|
||||
|
||||
---
|
||||
|
||||
## 8. 部署与自检
|
||||
|
||||
### 8.1 升级
|
||||
|
||||
```bash
|
||||
cd /opt/crypto_monitor_user
|
||||
git pull # 需含 80226ee,08082eb
|
||||
pm2 restart crypto-monitor-binance crypto-monitor-okx crypto-monitor-gate manual-trading-hub
|
||||
pm2 save
|
||||
```
|
||||
|
||||
### 8.2 手动平仓后自检
|
||||
|
||||
1. 中控结束一笔测试计划(或极小仓位)
|
||||
2. **策略交易记录**:出现对应条目
|
||||
3. **交易记录与复盘**:出现 `类型=趋势回调`,`结果=手动平仓`,且 `trend_plan_id` 与计划 ID 一致
|
||||
4. 若实例 flash / 日志出现「计划已结束但记账可能不完整」,说明 `insert_trade_record` 仍失败,需查 PM2 日志
|
||||
|
||||
### 8.3 相关代码文件
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `strategy_trend_register.py` | `_finalize_plan`,`_call_insert_trade_record`,`enrich_trend_plan` |
|
||||
| `strategy_trend_lib.py` | `trend_leg_display_price`,`enrich_trend_dca_levels_with_tp` |
|
||||
| `strategy_snapshot_lib.py` | 策略快照写入 |
|
||||
| `hub_bridge.py` | `/api/hub/trend/stop/<pid>` |
|
||||
| `crypto_monitor_gate/app.py` | `insert_trade_record`(含 `entry_reason`) |
|
||||
| `scripts/backfill_trend_trade_records.py` | 漏记交易记录补录 |
|
||||
|
||||
### 8.4 相关提交
|
||||
|
||||
| 提交 | 说明 |
|
||||
|------|------|
|
||||
| `6a4ec69` | 中控与三所趋势展示 enrich 统一 |
|
||||
| `08082eb` | 移除补仓表反推虚构成交价 |
|
||||
| `80226ee` | 修复 中控平仓漏写 `trade_records` |
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关文档
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| [策略交易说明.md](../策略交易说明.md) | 策略总览,策略交易记录页 |
|
||||
| [crypto_monitor_gate/趋势回调策略说明.md](../crypto_monitor_gate/趋势回调策略说明.md) | 趋势回调业务细则 |
|
||||
| [manual_trading_hub/使用说明.md](../manual_trading_hub/使用说明.md) | 中控监控与趋势卡布局 |
|
||||
| [hub-symbol-archive-kline.md](./hub-symbol-archive-kline.md) | 币种档案,永久 5m K 线,交易 overlay |
|
||||
|
||||
---
|
||||
|
||||
*最后整理:2026-06-07(与对话中修复项同步)*
|
||||
@@ -1,129 +0,0 @@
|
||||
# 趋势回调策略说明
|
||||
|
||||
本文描述 **「趋势回调」** 自动交易计划的业务规则与实现口径.
|
||||
|
||||
**三所主站**(Binance / Gate / OKX)均在顶栏 **策略交易 → `/strategy`** 左栏提供同一套逻辑(共用 `strategy_trend_register.py`);各所使用各自 API 与 `crypto.db`.
|
||||
|
||||
**检阅备忘**(中控平仓,交易记录,补仓展示,漏记补录):[trend-hub-close-and-trade-records.md](./trend-hub-close-and-trade-records.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 适用场景
|
||||
|
||||
- 各 **USDT 永续** 实例独立部署,使用各自 API 与 `crypto.db`.
|
||||
- 你已明确:**方向,止损价,补仓区间边界价,止盈价,杠杆**,并接受程序按风险预算拆分 **首仓 50% + 多档补仓 50%**.
|
||||
|
||||
---
|
||||
|
||||
## 2. 名词与参数
|
||||
|
||||
| 名称 | 含义 |
|
||||
|------|------|
|
||||
| **合约 USDT 可用余额** | **生成预览**时通过 API 读取的 **swap 账户 USDT `free`** 快照;**确认执行**时再次读取并与快照比对偏差. |
|
||||
| **风险比例** | 默认 **5%**:指「若整笔计划在 **补仓区间远侧边界**(做多=上沿,做空=下沿)这一侧的最坏价格结构下触及止损」,目标亏损上限约为 **可用余额快照 × 风险比例**(实现上用 `calc_risk_fraction` 与 `prepare_order_amount` 反推总张数,受交易所最小张数与精度约束). |
|
||||
| **止损价** | 用户填写;开仓后挂 **交易所仓位类止损触发单**(全平). |
|
||||
| **补仓区间边界**(库字段 `add_upper`) | 用户填写;**仅在该价位与止损价构成的区间内** 才允许程序触发剩余 50% 的市价补仓.**界面文案**:做多显示「补仓上沿」,做空显示「补仓下沿」.校验:做多 `止损 < 边界价`;做空 `止损 > 边界价`. |
|
||||
| **止盈价** | 用户填写的 **固定价格**;**不由交易所条件止盈单触发**,由应用后台 **按标记价/行情价轮询**,达到后 **市价全平**. |
|
||||
| **杠杆** | 计划内固定写入;用于 `set_leverage` 与名义换算. |
|
||||
| **补仓档位数** | 默认 **5** 档(环境变量 `TREND_PULLBACK_DCA_LEGS` 可调);程序在满足最小张数前提下可能 **自动减少档数**. |
|
||||
|
||||
---
|
||||
|
||||
## 3. 执行流程(时间顺序)
|
||||
|
||||
### 3.0 列表时间窗(交易记录 / 计划历史)
|
||||
|
||||
- **交易记录**,**计划历史**(含预览快照)列表与 **交易记录 CSV 导出** 支持 **UTC** 时间筛选(默认 UTC 当日;可选近 24h,近 7d,自定义起止).
|
||||
- 查询参数:`win_preset`(`utc_today` / `utc_last24h` / `utc_last7d` / `custom`),自定义时另传 `from_utc`,`to_utc`.
|
||||
- **统计分析**页仍按北京时间 `TRADING_DAY_RESET_HOUR` 切日,不受列表窗影响.
|
||||
|
||||
### 3.1 预览阶段(不下单)
|
||||
|
||||
1. **风控**:与「机器人下单监控」**互斥**——存在活跃机器人持仓或运行中趋势计划时,不可生成预览.
|
||||
2. **读取可用余额快照** `get_available_trading_usdt()`,失败则拒绝.
|
||||
3. **计算**(写入表 `trend_pullback_previews`,并跳转带 `preview_id`):
|
||||
- 在 **补仓区间边界 ↔ 止损** 区间内生成 `N` 个补仓触发价(做多从上沿向止损,做空从下沿向止损);
|
||||
- 将 **剩余 50% 计划张数** 拆成 `N` 份写入 `leg_amounts_json`.
|
||||
4. **预览有效期**:默认 **120 秒**(`TREND_PULLBACK_PREVIEW_TTL_SECONDS`),超时须重新点「生成预览」.
|
||||
|
||||
### 3.2 确认执行(实盘)
|
||||
|
||||
5. 再次校验:预览未过期;**当前可用余额**与预览快照相对偏差 ≤ `TREND_PREVIEW_MAX_BALANCE_DRIFT_PCT`(默认 **5%**),否则拒绝执行并要求重新预览.
|
||||
6. **首仓**:**立即市价** 开立 **总计划张数 × 50%**(不附带交易所止盈单).
|
||||
7. **止损**:撤销旧条件单后,挂 **仅止损** 的仓位触发单;之后每次补仓成交会 **刷新** 止损挂单.
|
||||
7b. **保本移交下单监控**(可选):首仓完成且交易所有持仓后,可点击「保本移交下单监控」——将止损移至 **持仓均价 ± 偏移%**(默认 **+0.3%** 多 / **−0.3%** 空),仅当新止损 **优于** 当前止损时生效;**本次趋势计划随即结束**,持仓写入 **下单监控**(备注 **趋势回调计划**),交易所在 **同一时刻挂保本止损 + 计划止盈**;后续无论中控平仓或交易所手动平仓,均经下单监控轮询 **`reconcile_external_closes` / `check_order_monitors`** 写入 **交易记录**(含 `trend_plan_id`,开仓类型「趋势回调」),供人工核对.
|
||||
8. **补仓**:当价格 **穿越** 下一档触发价(做多为自上向下穿越,做空为自下向上穿越)时,按该档张数 **市价加仓**;直至 `N` 档执行完毕或计划结束.
|
||||
9. **止盈监控**:后台线程若发现价格触及止盈,则 **市价全平**.
|
||||
10. **止损触发**:若仓位被交易所止损打光,本地检测到 **持仓为 0** 后记账为 **止损** 并结束计划.
|
||||
11. **计划结束**:任一结束路径(止盈 / 止损 / 用户手动结束)均会 **撤单**(条件单 + 普通挂单,尽力而为).
|
||||
|
||||
### 3.3 取消预览
|
||||
|
||||
用户可「取消预览」删除 `trend_pullback_previews` 中对应记录;过期记录会在新预览或页面加载时清理.
|
||||
|
||||
### 3.4 界面:计划历史与运行中浮动盈亏
|
||||
|
||||
- **计划历史(页顶卡片)**
|
||||
- 仅展示 **`trend_pullback_plans` 中已结束的计划**(`status != 'active'`,如止盈结束,止损结束,手动结束).
|
||||
- **不包含**仅存在于 `trend_pullback_previews`,从未「确认执行」的预览.
|
||||
- 每行提供 **删除**:删除该计划行,并删除 `trade_records` 中 **`trend_plan_id` 与之相同** 且类型为「趋势回调」的记录(用于与计划一一对应的新数据;历史旧行若无 `trend_plan_id` 则不会随删).
|
||||
- **运行中的计划(交易执行页)**
|
||||
- 在计划摘要下方展示 **浮盈亏(交易所)**:来自 Gate 当前持仓接口的 **未实现盈亏**(及标记价,若可得);与本地按均价估算可能略有差异,以交易所为准便于对照.
|
||||
- **补仓边界**按方向显示「补仓上沿」或「补仓下沿」(数值仍为 `add_upper` 字段).
|
||||
- **手动保本**:表单可改偏移 %(默认见 `TREND_PULLBACK_MANUAL_BREAKEVEN_OFFSET_PCT`);成功后显示「已保本」时间与原止损(若与当前不同).
|
||||
|
||||
### 3.5 交易记录与交易所「已实现盈亏」对齐
|
||||
|
||||
- 平仓时仍会写入一条 **`trade_records`**(`monitor_type=趋势回调`),其中的 **`pnl_amount` 等为本地估算**(`calc_pnl`,不含手续费,资金费等完整账单口径).
|
||||
- 打开 **「交易执行」或「交易记录」** 页面时,若已配置 **`GATE_API_KEY` / `GATE_API_SECRET`**(不要求 `LIVE_TRADING_ENABLED=true`,只读即可),应用会按节流策略(同进程约 **25 秒**内最多一次)调用 Gate **`fetch_positions_history`(平仓历史)**,为尚未写入 `exchange_sync_key` 的趋势回调记录 **匹配一条平仓记录**,并回填:
|
||||
- **`exchange_realized_pnl`**:交易所口径已实现盈亏(与 App「历史仓位」更接近);
|
||||
- **`exchange_opened_at` / `exchange_closed_at`**:换算为应用时区(默认北京)下的开,平时间字符串.
|
||||
- **交易记录表**展示列「开仓(展示) / 平仓(展示) / 盈亏U(展示)」:对「趋势回调」行,若已同步则优先显示交易所字段(界面小字 **「所」**);未同步前仍显示本地复盘字段(小字 **「估」**).
|
||||
- 匹配规则概要:同品种,同方向,平仓时间与本地 `closed_at` 接近,并结合 **`trend_plan_id`** 对应计划的 `opened_at` 收窄时间窗;极端情况下若短时间多笔同向同品种,仍存在错配可能,可对照 `exchange_sync_key` 与交易所记录.
|
||||
|
||||
---
|
||||
|
||||
## 4. 与「机器人下单监控」的差异
|
||||
|
||||
| 项目 | 机器人下单监控 | 趋势回调 |
|
||||
|------|------------------|----------|
|
||||
| 开仓 | 单次市价 + 条件止盈+止损 | 首仓 50% 市价 + 多档补仓 + **仅止损在交易所** |
|
||||
| 止盈 | 条件单 + 本地监控 | **仅本地监控市价止盈** |
|
||||
| 仓位基数 | 以损定仓(表单/会话基数) | **可用余额快照 × 风险比例** 推导 |
|
||||
| 移动保本 | 支持(按 R 自动上移) | **保本移交**(结束计划→下单监控;交易所 TP+SL;**无**自动 R 保本) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 风险声明(必读)
|
||||
|
||||
- 市价单存在 **滑点**;极端行情下实际亏损可能 **大于** 理论 5%.
|
||||
- 补仓触发依赖应用 **轮询间隔**(`MONITOR_POLL_SECONDS`),非毫秒级高频.
|
||||
- 交易所 **最小张数 / 精度** 可能导致计划张数被截断,实际风险略低于或偏离纸面计算.
|
||||
- 请使用 **单独 API Key / 子账户**,并先在 `LIVE_TRADING_ENABLED=false` 环境验证流程(若需沙盒请自行对接测试网,本仓库默认实盘接口).
|
||||
|
||||
---
|
||||
|
||||
## 6. 相关环境变量
|
||||
|
||||
| 变量 | 说明 | 默认 |
|
||||
|------|------|------|
|
||||
| `TREND_PULLBACK_MANUAL_BREAKEVEN_OFFSET_PCT` | 手动保本默认偏移(相对持仓均价,%) | `0.3` |
|
||||
| `TREND_PULLBACK_DCA_LEGS` | 剩余 50% 拆档数量上限 | `5` |
|
||||
| `TREND_PULLBACK_PREVIEW_TTL_SECONDS` | 预览有效时间(秒) | `120` |
|
||||
| `TREND_PREVIEW_MAX_BALANCE_DRIFT_PCT` | 确认执行时允许「当前可用 / 预览快照」最大相对偏差(%) | `5` |
|
||||
| `MONITOR_POLL_SECONDS` | 监控轮询间隔(秒) | `3` |
|
||||
| `LIVE_TRADING_ENABLED` | 是否允许真实下单 | `false` |
|
||||
| `FULL_MARGIN_BUFFER_RATIO` | 计划保证金相对可用余额上限比例 | `0.98` |
|
||||
| `APP_TIMEZONE` | 应用墙钟与「北京日期」同步起点时区(如 `Asia/Shanghai`) | `Asia/Shanghai` |
|
||||
| `EXCHANGE_POSITION_SYNC_FROM_BJ` | 拉取 Gate **平仓历史** 的最早日期(`YYYY-MM-DD`,按 `APP_TIMEZONE` 当日 **00:00** 起算).**留空**则从近 **90 天** 起拉取 | 空 |
|
||||
| `EXCHANGE_POSITION_HISTORY_LIMIT` | 单次拉取平仓历史条数上限(50–1000) | `200` |
|
||||
|
||||
---
|
||||
|
||||
## 7. 数据库
|
||||
|
||||
- **`trend_pullback_previews`**:未执行的预览行(含 `expires_at_ms`),执行成功或取消后删除;过期可被清理.
|
||||
- **`trend_pullback_plans`**:趋势回调计划.执行后写入一行,`status='active'` 表示运行中;止盈 / 止损 / 手动结束后变为 **`stopped_tp` / `stopped_sl` / `stopped_manual`** 等非 `active` 状态,并出现在页顶 **计划历史**.字段含快照可用余额,计划保证金,总张数,首仓张数,补仓 JSON,网格价 JSON,已补仓档数,均价,`opened_at`,`message`(结束说明)等;**`add_upper`** 存补仓区间远侧边界价(做多=上沿,做空=下沿).
|
||||
- **`trade_records`**(`monitor_type=趋势回调`):每次计划结束插入一行;含本地估算盈亏等.新写入行带 **`trend_plan_id`** 指向 `trend_pullback_plans.id`.另含 **`exchange_realized_pnl`,`exchange_opened_at`,`exchange_closed_at`,`exchange_sync_key`**,由页面触发的交易所平仓历史同步填充(见 3.5).
|
||||
|
||||
**CSV 导出**:交易记录导出为 **v3**,包含上述交易所对齐字段及 `trend_plan_id`.
|
||||
@@ -12,7 +12,8 @@
|
||||
| 运行用户 | **root**(下文命令均按 root 编写) |
|
||||
| 项目路径 | **`/opt/crypto_monitor_user`**(整仓克隆到此目录) |
|
||||
| 进程管理 | **PM2**(全局安装,见 §3) |
|
||||
| 网络 | 能 `git clone` 私有仓库;访问交易所不稳定时需 **SSH SOCKS**(见各所《部署文档》) |
|
||||
| 网络 | 能 `git clone` 私有仓库;能访问授权站 **https://sq.bz121.com**;访问交易所不稳定时需 **SSH SOCKS**(见各所《部署文档》) |
|
||||
| 授权 | 部署后配置仓库根 `license.env`,打开 `/license` 激活(见 [license.md](./license.md)) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
# 中控 AI 与部署密钥配置
|
||||
|
||||
本文档说明:**长期部署密钥**(一次生成,不轮换),**SSO 临时链接**(保持不动),以及 **AI 配置**(中控统一维护并同步三所).
|
||||
|
||||
---
|
||||
|
||||
## 1. 三类「密钥」分工
|
||||
|
||||
| 类型 | 变量 / 机制 | 谁维护 | 是否自动过期 |
|
||||
|------|-------------|--------|--------------|
|
||||
| **长期通信密钥** | `HUB_BRIDGE_TOKEN` | 部署脚本首次写入四份 `.env` | 否,不轮换 |
|
||||
| **实例 Session 签名** | `FLASK_SECRET_KEY` | 部署脚本首次写入三实例 | 否 |
|
||||
| **中控 Session 签名** | `HUB_SESSION_SECRET` | 部署脚本首次写入中控 | 否 |
|
||||
| **SSO 开门链接** | `/hub-sso?token=...` | 中控每次点「打开实例」签发 | 默认 2h + 单次 |
|
||||
| **AI 配置** | `OPENAI_*`,`AI_*` 等 | 中控系统设置 → AI 配置 | 否 |
|
||||
|
||||
**SSO 保持不动**:仍为随机 nonce,默认 `HUB_SSO_TTL_SEC=7200`,成功登录一次后链接作废.长期 `HUB_BRIDGE_TOKEN` 只用于签名,不会每 2 小时变化.
|
||||
|
||||
---
|
||||
|
||||
## 2. 首次部署自动生成
|
||||
|
||||
`bash deploy/setup_env.sh` 在复制 `.env.example` 后自动执行:
|
||||
|
||||
```bash
|
||||
python3 scripts/bootstrap_deploy_secrets.py
|
||||
```
|
||||
|
||||
| 写入项 | 位置 | 规则 |
|
||||
|--------|------|------|
|
||||
| `HUB_BRIDGE_TOKEN` | 中控 + 三实例(同值) | 仅空或占位符时写入 |
|
||||
| `FLASK_SECRET_KEY` | 三实例(同值) | 仅空或占位符时写入 |
|
||||
| `HUB_SESSION_SECRET` | 中控 | 仅空时写入 |
|
||||
| `HUB_USERNAME` / `HUB_PASSWORD` | 中控 | 默认 admin / admin123(仅空时) |
|
||||
| `APP_USERNAME` / `APP_PASSWORD` | 三实例 | 默认 admin / admin123(仅空时) |
|
||||
|
||||
**已有非空生产值不会被覆盖**(一次生成,不轮换).
|
||||
|
||||
子代理 `agent.py` 优先读取 `HUB_BRIDGE_TOKEN` 作为 `X-Control-Token` 校验;独立配置 `CONTROL_TOKEN` 已废弃.
|
||||
|
||||
---
|
||||
|
||||
## 3. 中控系统设置 → AI 配置
|
||||
|
||||
路径:**中控 Web → 系统设置 → AI 配置** Tab.
|
||||
|
||||
### 3.1 可配置项
|
||||
|
||||
| 中文名 | 环境变量 |
|
||||
|--------|----------|
|
||||
| AI 提供方 | `AI_PROVIDER` |
|
||||
| API 地址 | `OPENAI_API_BASE` |
|
||||
| API 密钥 | `OPENAI_API_KEY`(掩码,留空不修改) |
|
||||
| 云端模型 | `OPENAI_MODEL` |
|
||||
| Ollama 地址 | `OLLAMA_API` |
|
||||
| Ollama 模型 | `AI_MODEL` |
|
||||
| 请求超时(秒) | `AI_TIMEOUT_SECONDS` |
|
||||
|
||||
### 3.2 保存行为
|
||||
|
||||
1. 写入 `manual_trading_hub/.env`
|
||||
2. **强制同步** 至 `crypto_monitor_okx/binance/gate/.env` 相同键
|
||||
3. 自动 `pm2 restart` 中控 + 三实例(`--update-env`)
|
||||
|
||||
### 3.3 API(需已登录中控)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/settings/ai-env` | 读取字段与三所同步状态 |
|
||||
| POST | `/api/settings/ai-env` | body: `{ "values": {...}, "restart": true }` |
|
||||
|
||||
实现:`lib/env/shared_env_lib.py`,`manual_trading_hub/hub_env_lib.py`.
|
||||
|
||||
---
|
||||
|
||||
## 4. 实例 env 配置页变更
|
||||
|
||||
三所 **env 配置** 页已 **移除「AI 复盘」卡片**.交易所 API,企业微信,交易执行等仍各所自配.
|
||||
|
||||
实例侧若通过 API 提交已移除的 AI 键,会被白名单过滤,不会写入.
|
||||
|
||||
---
|
||||
|
||||
## 5. 与系统设置 / env 页对照
|
||||
|
||||
| 能力 | 实例 env | 实例系统设置 | 中控系统设置 |
|
||||
|------|----------|--------------|--------------|
|
||||
| 交易所 API | ✅ | ❌ | ❌ |
|
||||
| OpenAI / AI | ❌ | ❌ | ✅(同步三所) |
|
||||
| 实例登录密码 | ❌ | ✅ | ❌ |
|
||||
| 中控登录密码 | ❌ | ❌ | ✅ |
|
||||
| Bridge / Flask 长期密钥 | ❌(自动) | ❌ | ❌(自动) |
|
||||
| SSO 链接 | — | — | 每次打开实例自动签发 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 运维提示
|
||||
|
||||
- 修改 AI 后若未自动重启成功,手动:`pm2 restart manual-trading-hub crypto_okx crypto_binance crypto_gate --update-env`
|
||||
- 三所 AI 不一致时,中控 AI 配置页会提示「未完全同步」;点 **保存并同步** 即可对齐
|
||||
- 备份包可选包含各 `.env`(中控设置 → 备份恢复)
|
||||
|
||||
相关文档:[env配置说明.md](./env配置说明.md),[系统设置说明.md](./系统设置说明.md),[manual_trading_hub/局域网与反代部署说明.md](../manual_trading_hub/局域网与反代部署说明.md)(SSO 2h 说明)
|
||||
@@ -1,678 +0,0 @@
|
||||
# OKX 对冲计划 — 开发方案
|
||||
|
||||
> 状态:**方案冻结**(实现前对照本页;改需求先改本文).
|
||||
> 范围:**仅 `crypto_monitor_okx` 实例**;中控不做对冲开平.
|
||||
> 相关策略说明:[期权对冲方案分析.md](./期权对冲方案分析.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标与命名
|
||||
|
||||
在 OKX 实例增加独立模块 **「对冲计划」**,把「行情选腿 → 情景测算 →(条件满足时)自动开仓 → 规则退出 → 独立复盘统计」串成一套可状态跟踪的计划.
|
||||
|
||||
| 产品名 | 英文键 | 含义 |
|
||||
|--------|--------|------|
|
||||
| **永期对冲** | `perp_options` | 永续(子账户) + 买方期权(主账户) |
|
||||
| **期期对冲** | `options_options` | 主账户内两条买方期权腿 |
|
||||
|
||||
页面/导航展示用中文名;API/DB 用英文键.
|
||||
|
||||
**不做(本方案外):**
|
||||
|
||||
- 卖方期权、组合单原子成交
|
||||
- 中控代下单
|
||||
- 跨所对冲
|
||||
- 替代现有关键位/策略/期权页(可共存,但同标的限制见 §9)
|
||||
|
||||
---
|
||||
|
||||
## 2. 计仓模式门禁
|
||||
|
||||
沿用 `[docs/position-sizing-mode.md](./position-sizing-mode.md)`:
|
||||
|
||||
| `POSITION_SIZING_MODE` | 永期对冲 | 期期对冲 |
|
||||
|------------------------|----------|----------|
|
||||
| `risk`(以损定仓 / **非全仓**) | **仅测算**(左右行情 + 情景表);**禁止开仓/启动计划** | **允许开仓**(测算 + 启动计划) |
|
||||
| `full_margin`(全仓杠杆) | **允许开仓** | **允许开仓** |
|
||||
|
||||
说明:
|
||||
|
||||
- **永期**依赖永续全仓名义与保证金节奏,故开仓仅限全仓.
|
||||
- **期期**不占永续保证金,非全仓也允许开期权腿;非全仓下 UI 隐藏/禁用「启动永期」,仍可做永期**只读测算**.
|
||||
- 切模式后若存在 `active` 永期计划,禁止切到 `risk`,或强制要求先结束计划(实现时二选一并写死校验).
|
||||
|
||||
额外硬门:
|
||||
|
||||
- 须 `OKX_OPTIONS_ENABLED=true` 且期权 API 可用.
|
||||
- 实际开仓须实例允许实盘(`LIVE_ORDER` 等与永续/期权现有开关一致),并对冲计划自有开关见 §10.
|
||||
|
||||
---
|
||||
|
||||
## 3. 永期对冲 — 仓位与选期权
|
||||
|
||||
### 3.1 永续开仓量(全仓)
|
||||
|
||||
与现网全仓逻辑一致,对冲计划固定用于 **BTC / ETH**:
|
||||
|
||||
```
|
||||
可用保证金 = 合约账户可用 USDT
|
||||
占用保证金 = 可用 × FULL_MARGIN_BUFFER_RATIO(默认 0.98)
|
||||
杠杆 = BTC_LEVERAGE / ETH 对应杠杆(默认 10x)
|
||||
名义 ≈ 占用保证金 × 杠杆
|
||||
张数 = amount_to_precision(名义 / 价格 / 合约面值)
|
||||
```
|
||||
|
||||
页面左侧展示:**建议张数、名义、止损亏损额、止盈盈利额**(用户填止盈价/止损价后按该仓位即时算).
|
||||
|
||||
用户流程:
|
||||
|
||||
1. 选标的 ETH/BTC、方向(多/空).
|
||||
2. 系统按全仓 ×0.98×10x 算出永续张数与到止盈/止损的 U 盈亏.
|
||||
3. **再据此挑选右侧期权**(保费、张数、行权价),使「止损时保险腿」与「止盈保费损耗」可接受.
|
||||
4. 通过情景表确认后启动计划.
|
||||
|
||||
### 3.2 期权腿选取
|
||||
|
||||
- 行情自动拉 OKX 期权链(复用 `build_option_chain`).
|
||||
- **报价形态:列表式**;多仓默认筛 **Put**,空仓默认筛 **Call**.
|
||||
- 权利金默认按 **卖一 ask** 估算;开仓限价买入.
|
||||
|
||||
### 3.3 左右布局
|
||||
|
||||
```
|
||||
左:永续列表行情(标记/买卖一) + 方向/建议张数/开仓价/止盈/止损
|
||||
右:期权列表(可「选用」一条腿)
|
||||
下:情景测算 → [保存草稿] [启动计划]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 期期对冲 — 仓位与选腿
|
||||
|
||||
### 4.1 报价与选腿
|
||||
|
||||
- **T 型报价链**(复用期权页 T 型样式/数据结构).
|
||||
- 用户选 **腿 A + 腿 B**(通常 Call + Put,或主方向 + 尾部).
|
||||
- 预算受 `OKX_OPTIONS_TRADE_BUDGET_USDC` 等既有约束;可拆预算到两腿.
|
||||
|
||||
### 4.2 目标价
|
||||
|
||||
用户填 **预判价格 S\*** (「价格能到的位置」):
|
||||
|
||||
- 系统标明在 S\* 时哪条腿为 **盈利方**、哪条为 **亏损方**.
|
||||
- 到达规则见 §5.2.
|
||||
|
||||
### 4.3 左右布局
|
||||
|
||||
```
|
||||
左:指数价 + 到期日 + 预算 + 目标价 S*
|
||||
右:T 型链,依次选用两腿
|
||||
下:情景测算 → [保存草稿] [启动计划]
|
||||
```
|
||||
|
||||
非全仓模式下期期布局同上(无永续区).
|
||||
|
||||
---
|
||||
|
||||
## 5. 退出规则(冻结)
|
||||
|
||||
### 5.1 永期对冲
|
||||
|
||||
| 事件 | 永续 | 期权 | 计划是否结束 | 设计意图 |
|
||||
|------|------|------|--------------|----------|
|
||||
| **永续止盈触发** | 交易所 TP 平仓 | **不强制平**(保险腿可自生自灭/人工) | **算结束** | 对冲计划以永续兑现目标收口 |
|
||||
| **永续止损触发** | 交易所 SL 平仓 | **必须强制平仓** | **算结束** | 保护机制 |
|
||||
| 期权单独到期 | — | 结算 | 若计划已因止盈结束则只更新腿快照,不再改计划合计 | |
|
||||
| 人工结束计划 | 可选平永续 | 按选项 | **算结束** | |
|
||||
|
||||
**计划结束时盈亏口径(写入 `realized_pnl_*`,推送与统计共用):**
|
||||
|
||||
| 结束原因 | 公式(≈U,1:1) | 字段落库 |
|
||||
|----------|--------------|----------|
|
||||
| **止盈** `perp_tp` | **永续止盈已实现盈利 − 期权已付权利金** | `realized_pnl_perp` = 止盈盈利;`realized_pnl_options` = **−premium_total**(按权利金全额计成本,不论期权是否仍持仓);`realized_pnl_total` = 上两式之和 |
|
||||
| **止损** `perp_sl` | **期权平仓盈利 − 永续止损亏损额** | `realized_pnl_options` = 期权强制平后已实现;`realized_pnl_perp` = 永续止损已实现(为负或记亏损额);`realized_pnl_total` = 期权盈利 − \|永续亏损\|(即有符号相加) |
|
||||
|
||||
说明:
|
||||
|
||||
- 止盈时期权**物理上可不平**,但 **计划账** 已按「保费打掉」收口,后续期权 thrift/到期盈亏 **不再回写计划合计**(可在腿上另记备注/浮盈,不进 `realized_pnl_total`).
|
||||
- 止损时期权必须先强平再结账,用真实平仓盈亏,不是只扣权利金.
|
||||
|
||||
永续侧 TP/SL:沿用实例 **交易所条件单**,监控识别成交后触发计划结束逻辑 + 微信推送.
|
||||
|
||||
### 5.2 期期对冲
|
||||
|
||||
| 事件 | 盈利方 | 亏损方 | 计划是否结束 |
|
||||
|------|--------|--------|--------------|
|
||||
| **标的价到达用户目标价 S\*** | **自动平仓** | **不平**,持有至到期 | 平盈利腿后计划可标 `closing`;**全部腿终态后结束**(亏损腿到期后结账) |
|
||||
| **到期且整体无盈利** | — | 到期结算 | **算结束**;合计记 **总亏损**(通常 ≈ −全部权利金,或到期结算净值 < 0 的合计) |
|
||||
| 到期时组合合计仍盈利 | — | 到期结算 | **算结束**;按实际结算盈亏入账 |
|
||||
| 未达 S\* 至到期 | 两腿均到期 | | 同上,按结算合计结束 |
|
||||
|
||||
判定「整体无盈利」:到期(或计划收口)时 `realized_pnl_total ≤ 0`(含双腿权利金全损).
|
||||
|
||||
盈利方判定规则仍按前文(触达 S\* 时按浮盈较大一侧平仓;皆亏则等到期).
|
||||
|
||||
### 5.2.1 期权腿实盘平仓执行(与期权页共用)
|
||||
|
||||
对冲计划凡**必须物理平掉期权腿**时(如永期止损联动、期期平盈利腿),执行口径与独立期权模块一致:
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 禁市价 | 代码硬关闭,无市价兜底 |
|
||||
| 只锁买一 | 本轮 `min(仓位, 买一深度)` × 买一限价;`reduceOnly` |
|
||||
| 分批 | 买一不够则剩余下一轮再平再锁新买一 |
|
||||
| 有效流动性 | 残档买一禁止按买盘平 |
|
||||
| 2× 门控 | 目标位/自动类路径首次需可回收≥2×权利金并持续 hold;手动买一平只验流动性 |
|
||||
| 平仓挂单 TTL | 卖出限价超 `OKX_OPTIONS_PENDING_TTL_SECONDS`(默认 10 分钟)自动撤;UI「委托」可见 |
|
||||
|
||||
完整说明(可单独打开):**[期权开平仓与监控说明.md](./期权开平仓与监控说明.md)** · 线上 `/options/guide`.
|
||||
|
||||
---
|
||||
|
||||
### 5.3 企业微信推送(起止必发)
|
||||
|
||||
| 时机 | 是否必发 | 内容要点 |
|
||||
|------|----------|----------|
|
||||
| **计划开始**(开仓成功 → `active`) | **必发** | 类型(永期/期期)、标的方向、关键价位、张数/保费、计划 id |
|
||||
| **计划结束** | **必发** | 结束原因、`realized_pnl_total`、分项(永续/期权)、是否止盈/止损/到期亏损 |
|
||||
| 半腿失败 / 强平失败 | 必发告警 | 便于人工介入 |
|
||||
| 目标价平掉盈利腿(期期中间态) | 建议发 | 注明亏损腿仍持有 |
|
||||
|
||||
结束推送触发点与「算结束」一致:永期止盈、永期止损、期期到期收口(含无盈利总亏)、人工结束等.
|
||||
|
||||
---
|
||||
|
||||
## 6. 情景测算(开仓前必显)
|
||||
|
||||
### 6.1 永期
|
||||
|
||||
| 情景 | 含义 |
|
||||
|------|------|
|
||||
| 止盈 | 永续到 TP 的盈利 − 期权保费(期权按不强制平时的损耗估算) |
|
||||
| 宽止损 | 永续到 SL 的亏损 + 期权平仓估值(强制平,用 mark/买一估算) |
|
||||
| 到期横盘 | 永续≈0 + 期权权利金全损 |
|
||||
| 价格扫描 | index ± 若干档合计 |
|
||||
|
||||
核心输出:**宽止损合计亏损**(人工评估是否开仓).
|
||||
|
||||
### 6.2 期期
|
||||
|
||||
| 情景 | 含义 |
|
||||
|------|------|
|
||||
| 到达 S\* | 盈利腿兑现估值 − 已付总保费中亏损腿残留 |
|
||||
| 到期横盘 | 双腿权利金近似全损 |
|
||||
| 到期大涨/大跌 | 结算内在价值 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 数据来源(行情自动)
|
||||
|
||||
| 侧 | 来源 |
|
||||
|----|------|
|
||||
| 永续行情/规格 | OKX 子账户 ccxt:ticker + 现有 `/api/hub/market` 规格逻辑 |
|
||||
| 期权链 | `build_option_chain` / `/api/options/chain`(本实例直连,无需中控代理) |
|
||||
| 指数价 | 期权 `index_px`,左右对齐 |
|
||||
|
||||
报价刷新:页面手动刷新 + 计划编辑态可选 10~30s 自动刷新.
|
||||
|
||||
---
|
||||
|
||||
## 8. 自动开仓编排
|
||||
|
||||
### 8.1 永期(仅全仓)
|
||||
|
||||
建议默认顺序:**先期权、后永续**(期权失败成本低;永续失败则提示处理刚开的期权).
|
||||
|
||||
```
|
||||
校验 full_margin + LIVE + 无冲突计划
|
||||
→ 期权限价买入(卖一)
|
||||
→ 永续全仓张数市价开仓 + 挂交易所 TP/SL
|
||||
→ 写 hedge_plans / legs,状态 active
|
||||
```
|
||||
|
||||
部分失败补偿(最小集):
|
||||
|
||||
| 情况 | 动作 |
|
||||
|------|------|
|
||||
| 期权成、永续败 | 告警;建议自动平期权(可配置)或转人工 |
|
||||
| 永续成、期权败 | 告警;可选撤永续或重试期权;计划标 `partial` |
|
||||
|
||||
### 8.2 期期(全仓与非全仓均可开)
|
||||
|
||||
```
|
||||
校验 LIVE + 期权资金
|
||||
→ 腿1 限价买
|
||||
→ 腿2 限价买
|
||||
→ active;记录 target_price S*
|
||||
```
|
||||
|
||||
两腿间勿留长时间单腿敞口;第二腿失败则标 `partial` 并告警.
|
||||
|
||||
---
|
||||
|
||||
## 9. 状态机与冲突
|
||||
|
||||
```
|
||||
draft → opening → active → closing → closed
|
||||
↘ partial / failed
|
||||
draft → cancelled
|
||||
```
|
||||
|
||||
冲突规则:
|
||||
|
||||
- **同标的同时至多 1 条 active 永期计划**(全局可 `MAX_ACTIVE_HEDGE_PLANS`).
|
||||
- 永期 active 时与全仓「单仓」一致:**不与额外永续仓并存**(启动前校验无其它持仓,或本计划即为该仓).
|
||||
- 期权页对手动平「计划绑定 inst」应提示归属对冲计划.
|
||||
|
||||
---
|
||||
|
||||
## 10. 历史记录、统计与复盘(独立)
|
||||
|
||||
**入口:** OKX 顶栏 **「对冲计划」** 页内三个 Tab(不单开顶栏项):
|
||||
|
||||
| Tab | 路由建议 | 内容 |
|
||||
|-----|----------|------|
|
||||
| **计划** | `/hedge-plan` | 新建 / 草稿 / 进行中 |
|
||||
| **历史** | `/hedge-plan?tab=history` | 已结束与取消的计划列表 + 详情复盘 |
|
||||
| **统计** | `/hedge-plan?tab=stats` | 独立统计看板 |
|
||||
|
||||
**不得**并入普通「交易记录与复盘」`/records`、全站「统计分析」`/stats`、策略交易记录.
|
||||
腿可可选关联 `options_trades` / 监控 id,但 **计划合计盈亏与胜率只读本模块表**.
|
||||
|
||||
币种展示约定:永续腿 USDT、期权腿 USDC;合计列标注 **「≈U(1:1)」**,不做实时汇率换算.
|
||||
|
||||
---
|
||||
|
||||
### 10.1 数据落库
|
||||
|
||||
库文件:OKX 实例 `crypto.db`(与其它表同库).
|
||||
|
||||
#### `hedge_plans`(一条计划)
|
||||
|
||||
| 字段 | 类型建议 | 说明 |
|
||||
|------|----------|------|
|
||||
| `id` | INTEGER PK | |
|
||||
| `plan_type` | TEXT | `perp_options` 永期 / `options_options` 期期 |
|
||||
| `status` | TEXT | draft / opening / active / closing / closed / partial / failed / cancelled |
|
||||
| `underlying` | TEXT | BTC / ETH |
|
||||
| `direction` | TEXT | 永期:long/short;期期可空 |
|
||||
| `entry_mark` | REAL | 开仓参考价(标记/指数快照) |
|
||||
| `tp` | REAL | 永期止盈价;可空 |
|
||||
| `sl` | REAL | 永期止损价;可空 |
|
||||
| `target_price` | REAL | 期期目标价 S\*;可空 |
|
||||
| `sizing_mode_at_open` | TEXT | 开仓时 `risk`/`full_margin` 快照 |
|
||||
| `perp_size` | REAL | 永期张数/币量快照;期期空 |
|
||||
| `margin` | REAL | 占用保证金快照 |
|
||||
| `leverage` | REAL | 杠杆快照 |
|
||||
| `premium_total` | REAL | 期权已付权利金合计(USDC) |
|
||||
| `realized_pnl_perp` | REAL | 永续已实现(USDT);止盈为正,止损为负 |
|
||||
| `realized_pnl_options` | REAL | 期权账:止盈场景记 **−权利金**;止损场景记 **强平真实盈亏** |
|
||||
| `realized_pnl_total` | REAL | 见 §5.1 / §5.2 公式;统计与微信共用此值 |
|
||||
| `stats_bucket` | TEXT | 可选冗余:`tp` / `sl` / `oo_expiry_loss` / `oo_target` / `other` 便于统计筛选 |
|
||||
| `close_reason` | TEXT | 见下表枚举 |
|
||||
| `wechat_start_sent` | INTEGER | 开仓推送是否已发 |
|
||||
| `wechat_end_sent` | INTEGER | 结束推送是否已发 |
|
||||
| `note` | TEXT | 人工复盘短评 |
|
||||
| `created_at` | TEXT | |
|
||||
| `opened_at` | TEXT | 首次腿成交时间 |
|
||||
| `closed_at` | TEXT | **计划结束时间**(止盈/止损/到期收口等) |
|
||||
| `preview_json` | TEXT | 开仓前情景测算快照(可选) |
|
||||
|
||||
**`close_reason` 枚举**
|
||||
|
||||
| 值 | 含义 | 是否算计划结束 | 合计口径 |
|
||||
|----|------|----------------|----------|
|
||||
| `perp_tp` | 永续止盈 | **是**(立刻 closed) | **止盈盈利 − 权利金** |
|
||||
| `perp_sl` | 永续止损 + 期权强制平 | **是** | **期权盈利 − 永续亏损** |
|
||||
| `target_win_leg` | 期期已平盈利腿(中间态可暂不 closed) | 腿未齐前可不结束 | 待亏损腿到期后定合计 |
|
||||
| `oo_expiry_loss` | 期期到期且合计无盈利 | **是** | **总亏损**(settled ≤ 0,常 ≈ −保费) |
|
||||
| `oo_expiry_win` | 期期到期合计仍盈利 | **是** | 实际到期合计 |
|
||||
| `expiry` | 其它到期收口 | **是** | 实际结算 |
|
||||
| `manual` | 人工结束 | **是** | 按当时已实现 |
|
||||
| `partial_fail` | 半腿失败收尾 | **是** | 按补偿结果 |
|
||||
| `cancelled` | 未真正开仓取消 | 是(无盈亏) | 0 |
|
||||
|
||||
说明:止盈结束时期权腿可标 `hold_to_expiry`/`orphaned_after_tp`,**计划已 closed**,后续期权盈亏不回写 `realized_pnl_total`.
|
||||
|
||||
#### `hedge_plan_legs`(一条腿)
|
||||
|
||||
| 字段 | 类型建议 | 说明 |
|
||||
|------|----------|------|
|
||||
| `id` | INTEGER PK | |
|
||||
| `plan_id` | INTEGER FK | |
|
||||
| `leg_role` | TEXT | `perp` / `option_hedge` / `option_a` / `option_b` |
|
||||
| `symbol` 或 `inst_id` | TEXT | 永续符号或期权合约 id |
|
||||
| `opt_type` | TEXT | C/P;永续空 |
|
||||
| `strike` | REAL | 期权行权价 |
|
||||
| `side` | TEXT | long/short 或 buy |
|
||||
| `size` | REAL | 张数或币量 |
|
||||
| `avg_open` | REAL | 开仓均价/权利金单价 |
|
||||
| `premium` | REAL | 该腿已付权利金(期权) |
|
||||
| `status` | TEXT | open / closed / hold_to_expiry |
|
||||
| `linked_monitor_id` | INTEGER | 可选,永续监控 |
|
||||
| `options_trade_id` | INTEGER | 可选,期权成交表 |
|
||||
| `realized_pnl` | REAL | 该腿已实现 |
|
||||
| `close_reason` | TEXT | 腿级原因 |
|
||||
| `opened_at` / `closed_at` | TEXT | |
|
||||
|
||||
---
|
||||
|
||||
### 10.2 历史列表(列定义)
|
||||
|
||||
筛选:**类型**(全部/永期/期期)、**状态**、**标的**、**日期**(按 `opened_at` 或 `closed_at`).
|
||||
|
||||
| 列 | 来源 | 展示 |
|
||||
|----|------|------|
|
||||
| ID | id | `#12` |
|
||||
| 类型 | plan_type | 永期对冲 / 期期对冲 |
|
||||
| 标的 | underlying + direction | 如 `ETH 多` / `ETH 双买` |
|
||||
| 状态 | status | 中文标签 |
|
||||
| 开仓时间 | opened_at | |
|
||||
| 结束时间 | closed_at | 进行中显示 — |
|
||||
| 平仓原因 | close_reason | 中文(止盈离场/止损联动平/目标价平盈利腿/到期…) |
|
||||
| 保费 | premium_total | `x.xx USDC` |
|
||||
| 合计盈亏 | realized_pnl_total | 着色 +/- ,单位 ≈U |
|
||||
| 腿摘要 | legs | 如 `永续✓ · Put持仓` / `Call已平 · Put到期` |
|
||||
| 操作 | | 详情 |
|
||||
|
||||
行操作:**详情**(主)、可选「补写短评」.
|
||||
|
||||
---
|
||||
|
||||
### 10.3 计划详情 / 复盘页(字段)
|
||||
|
||||
从历史点进去的详情页 = **主复盘面**,分块如下.
|
||||
|
||||
#### A. 计划摘要
|
||||
|
||||
| 项 | 字段 |
|
||||
|----|------|
|
||||
| 类型 / 标的 / 方向 | plan_type, underlying, direction |
|
||||
| 状态 / 平仓原因 | status, close_reason |
|
||||
| 时间线 | created_at → opened_at → closed_at |
|
||||
| 开仓时计仓 | sizing_mode_at_open, margin, leverage, perp_size |
|
||||
| 关键价位 | entry_mark, tp, sl(永期), target_price(期期) |
|
||||
| 盈亏 | realized_pnl_perp / realized_pnl_options / realized_pnl_total |
|
||||
| 开仓情景快照 | preview_json 折叠展示(止盈合计/宽止损合计等) |
|
||||
|
||||
#### B. 腿明细表
|
||||
|
||||
| 列 | 说明 |
|
||||
|----|------|
|
||||
| 角色 | 永续 / 保险期权 / 期期腿A/B |
|
||||
| 合约 | symbol / inst_id |
|
||||
| 数量 | size |
|
||||
| 开仓价/保费 | avg_open, premium |
|
||||
| 状态 | open / closed / 持有至到期 |
|
||||
| 盈亏 | realized_pnl |
|
||||
| 平仓原因 | close_reason |
|
||||
| 关联 | 链到期权成交或监控(有则显示) |
|
||||
|
||||
#### C. 复盘短评(必做入口)
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| `note` | 多行文本,可空;保存 `PATCH /api/hedge-plan/<id>/note` |
|
||||
| 提示文案 | 建议写:开仓理由、结果是否符合情景测算、下次调整 |
|
||||
|
||||
**不做(本期):** 复盘截图上传、填入「交易记录与复盘」表单、纳入 AI 日/周复盘.
|
||||
**可后置:** 以计划摘要生成 AI 点评(独立按钮,不写进 trade_records).
|
||||
|
||||
#### D. 终态示例文案(便于复盘理解)
|
||||
|
||||
| 场景 | 详情页状态说明 | 合计 |
|
||||
|------|----------------|------|
|
||||
| 永期止盈 | 「计划已结束(止盈);期权腿可不强平,账上已扣全部权利金」 | 止盈盈利 − 权利金 |
|
||||
| 永期止损 + 期权已强平 | 「计划已结束(止损保护:期权已联动平仓)」 | 期权盈利 − 永续亏损 |
|
||||
| 期期到期无盈利 | 「计划已结束(到期无盈利)」 | 总亏损(计入统计) |
|
||||
| 期期达 S\* 后亏损腿仍持有 | 「盈利腿已平;待亏损腿到期后结账」 | 暂不入 closed 统计,或单独「收尾中」 |
|
||||
|
||||
---
|
||||
|
||||
### 10.4 统计页(独立看板)
|
||||
|
||||
**筛选:** 日期区间、类型(全部/永期/期期)、标的、结束桶(`tp`/`sl`/`oo_expiry_loss`/…).
|
||||
|
||||
**聚合规则:** 仅 `status=closed`;胜场 = `realized_pnl_total > 0`.
|
||||
|
||||
#### 永期口径(冻结)
|
||||
|
||||
| 统计桶 | `close_reason` | 单笔盈亏公式 | 汇总 |
|
||||
|--------|----------------|--------------|------|
|
||||
| **止盈统计** | `perp_tp` | **止盈盈利 − 期权权利金** | sum / 笔数 / 胜率 |
|
||||
| **止损统计** | `perp_sl` | **期权盈利 − 永续亏损** | sum / 笔数 /「保护后净亏」均值 |
|
||||
|
||||
实现校验示例:
|
||||
|
||||
```
|
||||
# 止盈
|
||||
realized_pnl_total = pnl_perp_tp - premium_total
|
||||
|
||||
# 止损(亏损额取绝对值)
|
||||
realized_pnl_total = pnl_option_close - abs(pnl_perp_sl)
|
||||
# 等价有符号: pnl_option_close + pnl_perp_sl(后者为负)
|
||||
```
|
||||
|
||||
#### 期期口径(冻结)
|
||||
|
||||
| 统计桶 | 条件 | 单笔盈亏 |
|
||||
|--------|------|----------|
|
||||
| **到期无盈利** | 到期收口且合计 ≤ 0 | **总亏损**写入 `realized_pnl_total`(负值),计入区间净亏与「到期亏损」汇总 |
|
||||
| 目标价路径 | 盈利腿已平 + 亏损腿到期后 | 两腿 realized 之和 |
|
||||
| 到期仍盈利 | 合计 > 0 | 实际结算合计 |
|
||||
|
||||
#### 总览卡片
|
||||
|
||||
| 指标 | 计算 |
|
||||
|------|------|
|
||||
| 计划笔数 | count(closed) |
|
||||
| 胜率 | 胜场 / 笔数 |
|
||||
| 区间净盈亏 | sum(realized_pnl_total) |
|
||||
| 止盈桶净盈亏 | sum where stats_bucket=tp |
|
||||
| 止损桶净盈亏 | sum where stats_bucket=sl |
|
||||
| 期期到期亏损合计 | sum where oo_expiry_loss(绝对值或带符号合计) |
|
||||
| 总保费支出 | sum(premium_total) |
|
||||
| 平均持仓时长 | avg(closed_at − opened_at) |
|
||||
|
||||
#### 分类型卡片
|
||||
|
||||
永期、期期各一套:笔数、胜率、净盈亏、平均保费;永期再拆 **止盈桶 / 止损桶**.
|
||||
|
||||
#### 退出结构
|
||||
|
||||
| 指标 | 过滤 |
|
||||
|------|------|
|
||||
| 止盈结束笔数 | `perp_tp` |
|
||||
| 止损结束笔数 | `perp_sl` |
|
||||
| 期期到期无盈利笔数 | `oo_expiry_loss` |
|
||||
| 目标价路径完结 | 含 `target_win_leg` 后收尾 |
|
||||
| 半腿/失败 | `partial_fail` / failed |
|
||||
|
||||
#### 简易表(可选)
|
||||
|
||||
最近 N 条已结束计划迷你列表,点击跳详情.
|
||||
|
||||
**导出(P5 可选):** CSV.
|
||||
|
||||
---
|
||||
|
||||
### 10.5 与现有页面关系
|
||||
|
||||
| 现有页 | 关系 |
|
||||
|--------|------|
|
||||
| 交易记录与复盘 | **不写入**;永续腿若系统仍落 `trade_records`,可标记来源「对冲计划#id」,但人工复盘以对冲详情为准 |
|
||||
| 统计分析 | **不合并**对冲净盈亏到全站数字(避免重复或口径混乱) |
|
||||
| 期权页成交/持仓 | 腿 `options_trade_id` 可跳转对照;期权页仍可看单腿 |
|
||||
| 中控 | V1 不聚合;V2 可选只读摘要 |
|
||||
|
||||
---
|
||||
|
||||
### 10.6 API(历史 / 统计 / 复盘)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/hedge-plan/list` | 进行中+草稿;`status` 过滤 |
|
||||
| GET | `/api/hedge-plan/history` | 已结束列表;类型/日期/标的 |
|
||||
| GET | `/api/hedge-plan/<id>` | 详情 = 计划 + legs + note + preview |
|
||||
| PATCH | `/api/hedge-plan/<id>/note` | 保存复盘短评 |
|
||||
| GET | `/api/hedge-plan/stats` | query:`from`,`to`,`plan_type`,`underlying` → 总览+分类型+退出结构 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 监控线程
|
||||
|
||||
在 `crypto_okx` 同进程内新增 **`hedge_plan_monitor_loop`**(可与 `options_monitor_loop` 并列):
|
||||
|
||||
| 职责 | |
|
||||
|------|--|
|
||||
| 永期 | 侦测永续 TP/SL → 按 §5.1 结束计划并结账;SL 时强制平期权 |
|
||||
| 期期 | 侦测价触 S\* → 平盈利腿;到期无盈利 → 结束并记总亏损 |
|
||||
| 微信 | **开始必推、结束必推**(§5.3);半腿/强平失败告警 |
|
||||
| 幂等 | `wechat_start_sent` / `wechat_end_sent` 防重复推送 |
|
||||
|
||||
---
|
||||
|
||||
## 12. 配置项与前端 env 页
|
||||
|
||||
对冲相关开关 **一律在 OKX 实例「env 配置」页维护**,不要求 SSH 改 `.env`.
|
||||
实现对齐现有白名单模式(`lib/env/env_ui_manifest.py` 的「期权账户」分组).
|
||||
|
||||
### 12.1 前端分组
|
||||
|
||||
在 env 配置页新增独立卡片,标题 **「对冲计划」**:
|
||||
|
||||
- **仅 OKX** 展示(Binance/Gate 不出现)
|
||||
- 放在 **「期权账户」下方**(依赖期权模块)
|
||||
- 卡片说明文案建议:
|
||||
- 永期开仓还要求「交易执行」里计仓模式为 **全仓**(`POSITION_SIZING_MODE=full_margin`)
|
||||
- 真实下单还与「交易所与实盘」→ `LIVE_TRADING_ENABLED`、本卡片 `HEDGE_PLAN_LIVE_ORDER` 同时开启
|
||||
- `HEDGE_PLAN_ENABLED` 关闭时隐藏顶栏「对冲计划」并拒绝启动计划
|
||||
|
||||
### 12.2 本分组字段(前端可配)
|
||||
|
||||
| 变量 | 前端标签 | 默认 | 控件 | 热更新 | 说明 |
|
||||
|------|----------|------|------|--------|------|
|
||||
| `HEDGE_PLAN_ENABLED` | 启用对冲计划 | false | bool | 热更优先 | 总开关:导航 + API |
|
||||
| `HEDGE_PLAN_LIVE_ORDER` | 允许对冲真实下单 | false | bool | 热更 | 关则只测算/草稿 |
|
||||
| `HEDGE_PLAN_OPEN_ORDER` | 永期开仓顺序 | options_first | select:`options_first`/`perp_first` | 热更 | 默认先期权后永续 |
|
||||
| `HEDGE_PLAN_ON_PERP_SL_CLOSE_OPTIONS` | 永期止损后强制平期权 | true | bool | 热更 | **保护机制,默认 true** |
|
||||
| `HEDGE_PLAN_ON_PERP_TP_CLOSE_OPTIONS` | 永期止盈后强制平期权 | false | bool | 热更 | **默认 false,保险腿不平** |
|
||||
| `HEDGE_PLAN_OO_CLOSE_WINNER_ONLY` | 期期只平盈利腿 | true | bool | 热更 | 达目标价只平盈利方 |
|
||||
| `MAX_ACTIVE_HEDGE_PLANS` | 最大同时活跃计划数 | 1 | number | 热更 | 建议保持 1 |
|
||||
| `HEDGE_PLAN_MONITOR_POLL_SECONDS` | 对冲监控轮询(秒) | 15 | number | 热更 | 侦测 TP/SL/目标价 |
|
||||
| `HEDGE_PLAN_PARTIAL_AUTO_CLOSE_OPTION` | 半腿失败时自动平期权 | true | bool | 热更 | 期权成、永续败时的补偿 |
|
||||
|
||||
**不放入本分组、沿用已有卡片:**
|
||||
|
||||
| 已有位置 | 键 | 对冲用途 |
|
||||
|----------|-----|----------|
|
||||
| 交易执行 | `POSITION_SIZING_MODE` | 全仓才允许永期开仓 |
|
||||
| 交易执行 | `FULL_MARGIN_BUFFER_RATIO` | 默认 0.98 |
|
||||
| 交易执行 | `BTC_LEVERAGE` | BTC/ETH 档(默认 10) |
|
||||
| 交易所与实盘 | `LIVE_TRADING_ENABLED` | 总实盘门 |
|
||||
| 期权账户 | `OKX_OPTIONS_*` | 期权 API、预算、标的 |
|
||||
|
||||
### 12.3 `.env.example` 片段(实现时写入 OKX)
|
||||
|
||||
```env
|
||||
# --- 对冲计划(仅 OKX;前端 env「对冲计划」) ---
|
||||
HEDGE_PLAN_ENABLED=false
|
||||
HEDGE_PLAN_LIVE_ORDER=false
|
||||
HEDGE_PLAN_OPEN_ORDER=options_first
|
||||
HEDGE_PLAN_ON_PERP_SL_CLOSE_OPTIONS=true
|
||||
HEDGE_PLAN_ON_PERP_TP_CLOSE_OPTIONS=false
|
||||
HEDGE_PLAN_OO_CLOSE_WINNER_ONLY=true
|
||||
MAX_ACTIVE_HEDGE_PLANS=1
|
||||
HEDGE_PLAN_MONITOR_POLL_SECONDS=15
|
||||
HEDGE_PLAN_PARTIAL_AUTO_CLOSE_OPTION=true
|
||||
```
|
||||
|
||||
### 12.4 编码触点
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `lib/env/env_ui_manifest.py` | `_HEDGE_PLAN_SECTION`,`exchanges={"okx"}`,接入 `ui_sections_for_exchange` |
|
||||
| `crypto_monitor_okx/.env.example` | 增加上表键与分组注释 |
|
||||
| `lib/env/env_schema.py` | 纳入 `HOT_RELOAD_EXACT`(及必要时重启列表) |
|
||||
| `docs/env配置说明.md` | 上线时补「对冲计划」小节 |
|
||||
|
||||
推荐分期:**P0 先做 env 白名单 + 开关可读**,页面按 `HEDGE_PLAN_ENABLED` 显隐导航.
|
||||
|
||||
---
|
||||
|
||||
## 13. 前端与路由(实现要点)
|
||||
|
||||
- 导航:OKX 实例顶栏 **「对冲计划」**(display prefs 可加 `show_nav_hedge_plan`).
|
||||
- 路由建议:`/hedge-plan`(主)、`/hedge-plan/history`、`/hedge-plan/stats`(或单页 Tab).
|
||||
- 模板/静态:`lib/options` 旁新增 `lib/hedge_plan/`(或 `lib/hedge/`),复用:
|
||||
- 期权链 CSS/T 型渲染思路(`options_panel.js` / `options-strike-table--t`)
|
||||
- `compute_full_margin_sizing` / `position_sizing_lib`
|
||||
- 永续下单 + TPSL、期权限价开平 API
|
||||
|
||||
API 草图:
|
||||
|
||||
- `GET /api/hedge-plan/market` — 永续行情 + 全仓试算
|
||||
- `GET /api/hedge-plan/options-chain` — 期权链
|
||||
- `POST /api/hedge-plan/preview` — 情景测算
|
||||
- `GET /api/hedge-plan/list` / `history` / `<id>` / `stats`
|
||||
- `PATCH /api/hedge-plan/<id>/note` — 复盘短评
|
||||
- `POST /api/hedge-plan` — 保存草稿/启动
|
||||
- `POST /api/hedge-plan/<id>/cancel|close`
|
||||
|
||||
非全仓请求启动永期 → `400` 明确:「永期对冲开仓仅全仓模式可用」.
|
||||
|
||||
---
|
||||
|
||||
## 14. 分期实施
|
||||
|
||||
| 阶段 | 交付 | 验收 |
|
||||
|------|------|------|
|
||||
| **P0** | env「对冲计划」分组 + 页框 + 行情 + 永期列表/期期 T + 情景测算 + 门禁 | env 可改开关;非全仓无法启动永期;全仓可看建议张数 |
|
||||
| **P1** | 表结构 + 草稿/列表 | DB 可查 |
|
||||
| **P2** | 永期自动开仓(全仓) | 双腿成交入计划 |
|
||||
| **P3** | 监控:止损强制平期权;止盈不平期权 | 用例测 TP/SL 分支 |
|
||||
| **P4** | 期期开仓 + 目标价只平盈利腿 | 达价仅平一侧 |
|
||||
| **P5** | 历史列表列 + 详情复盘(note) + 统计看板 + 微信 | 与 /records /stats 隔离;止损/止盈文案可区分 |
|
||||
|
||||
建议顺序严格;P3 规则错误会误平保险腿,上线前用 dry-run / paper flags.
|
||||
|
||||
---
|
||||
|
||||
## 15. 与现有文档关系
|
||||
|
||||
| 文档 | 关系 |
|
||||
|------|------|
|
||||
| [期权对冲方案分析.md](./期权对冲方案分析.md) | 策略观念;本模块是其「计划化 + 自动执行」实现 |
|
||||
| [对冲计划策略与P0校验.md](./对冲计划策略与P0校验.md) | P0 交付与口径校验 |
|
||||
| [期权方案.md](./期权方案.md) / [期权用法.md](./期权用法.md) | 期权 API、仅买方、限价规则必须遵守 |
|
||||
| [期权开平仓与监控说明.md](./期权开平仓与监控说明.md) | 买一平仓、门控、监控与风险;线上 `/options/guide` |
|
||||
| [position-sizing-mode.md](./position-sizing-mode.md) | 全仓公式与缓冲 0.98 |
|
||||
|
||||
本模块上线后,可在《期权对冲方案分析》末尾增加「系统对冲计划」链接指向本文.
|
||||
|
||||
---
|
||||
|
||||
## 16. 已拍板规则摘要(校验清单)
|
||||
|
||||
- [x] 放在 **OKX 实例**,名 **永期对冲 / 期期对冲**
|
||||
- [x] 永期止盈 → **期权不强制平**,但 **计划算结束**;统计 = **止盈盈利 − 权利金**
|
||||
- [x] 永期止损 → **期权必须强制平**,计划结束;统计 = **期权盈利 − 永续亏损**
|
||||
- [x] 期期按目标价 → **只平盈利方**;到期无盈利 → **算结束并统计总亏损**
|
||||
- [x] 对冲计划 **开始与结束均企业微信推送**
|
||||
- [x] **独立历史 + 独立统计 + 计划详情复盘**(短评 note;不进普通交易复盘)
|
||||
- [x] 永期开仓 **仅全仓**;非全仓永期只算账
|
||||
- [x] 非全仓允许 **期期**开仓;全仓永期+期期均可
|
||||
- [x] 永期张数:**ETH/BTC 10x 全仓 × 0.98**,先算盈亏再选期权
|
||||
- [x] 行情自动;永期期权 **列表式**;期期 **T 型**
|
||||
- [x] 对冲开关在前端 **env 配置 →「对冲计划」** 维护(仅 OKX);计仓/杠杆/期权 API 复用已有分组
|
||||
- [x] 期权腿实盘平仓:**禁市价、只锁买一、分批;流动性/2×门控见开平仓说明**
|
||||
|
||||
---
|
||||
|
||||
## 17. 免责与边界
|
||||
|
||||
- 双账户非原子成交,存在半腿风险.
|
||||
- 止盈结束账上按权利金全额计成本;期权若仍持仓,后续行情 **不再改计划合计**,属设计意图.
|
||||
- 止盈后期权可能继续损耗直至到期,与「计划已结束」并存.
|
||||
- 买方权利金可能全部损失;期期到期无盈利记总亏损,属设计意图.
|
||||
- 本文不构成投资建议.
|
||||
@@ -1,109 +0,0 @@
|
||||
# 对冲计划策略说明与 P0 校验
|
||||
|
||||
> 配套实现方案:[对冲计划开发方案.md](./对冲计划开发方案.md)
|
||||
> 本文记录 **策略口径** 与 **P0 代码校验**,确认可继续 P1+.
|
||||
|
||||
---
|
||||
|
||||
## 1. 策略摘要
|
||||
|
||||
| 类型 | 账户 | 作用 |
|
||||
|------|------|------|
|
||||
| **永期对冲** | 永续子账户 + 期权主账户买方 | 全仓做方向,期权买保险 |
|
||||
| **期期对冲** | 仅期权主账户双买方 | 目标价兑现盈利腿,亏损腿到期 |
|
||||
|
||||
### 永期结束与统计
|
||||
|
||||
| 事件 | 期权处理 | 计划 | 统计公式 |
|
||||
|------|----------|------|----------|
|
||||
| 止盈 | 不强平 | **结束** | **止盈盈利 − 权利金** |
|
||||
| 止损 | **强制平** | **结束** | **期权盈利 − 永续亏损**(有符号相加) |
|
||||
|
||||
### 期期结束与统计
|
||||
|
||||
| 事件 | 处理 | 统计 |
|
||||
|------|------|------|
|
||||
| 达目标价 | 只平盈利腿 | 待亏损腿到期后结账 |
|
||||
| 到期无盈利 | **结束** | **总亏损** |
|
||||
|
||||
起止均企业微信推送(P2+ 监控落地后再接).
|
||||
|
||||
### 门禁
|
||||
|
||||
- 永期开仓:**仅** `POSITION_SIZING_MODE=full_margin`
|
||||
- 非全仓:永期可测算不可开;期期 P0 起可测算(开仓后续版本)
|
||||
- env:OKX「对冲计划」分组;须 `HEDGE_PLAN_ENABLED=true` 才显示导航
|
||||
|
||||
---
|
||||
|
||||
## 2. P0 已交付
|
||||
|
||||
| 项 | 状态 |
|
||||
|----|------|
|
||||
| env 白名单「对冲计划」9 字段 | 有 |
|
||||
| OKX `.env.example` 字段 | 有 |
|
||||
| `/hedge-plan` 页(永期列表 / 期期 T) | 有 |
|
||||
| `/api/hedge-plan/market|options-chain|preview|gates` | 有 |
|
||||
| 全仓建议张数(可用×0.98×10x) | 有 |
|
||||
| 情景测算止盈/止损口径 | 有 |
|
||||
| 启动开仓 | **禁用**(明示 P0) |
|
||||
| 历史/统计/监控/微信 | **未做**(P1–P5) |
|
||||
|
||||
关键文件:
|
||||
|
||||
- `lib/hedge_plan/hedge_plan_calc_lib.py`
|
||||
- `lib/hedge_plan/hedge_plan_register.py`
|
||||
- `lib/hedge_plan/templates/hedge_plan_panel.html`
|
||||
- `lib/common/static/hedge_plan.js`
|
||||
- `tests/test_hedge_plan_calc.py`
|
||||
|
||||
---
|
||||
|
||||
## 3. 校验清单(可行性)
|
||||
|
||||
### 计算口径
|
||||
|
||||
```
|
||||
止盈: perp_pnl(tp) - premium
|
||||
止损: option_expiry_pnl(spot=sl) + perp_pnl(sl)
|
||||
期期到期无盈利: expiry_flat_total <= 0 → 记总亏损
|
||||
```
|
||||
|
||||
单测覆盖:`tests/test_hedge_plan_calc.py`.
|
||||
|
||||
### 门禁
|
||||
|
||||
- `risk` + 永期 → `can_start=false`,文案含「全仓」
|
||||
- `HEDGE_PLAN_ENABLED=false` → 导航隐藏(服务端 template 读 env)
|
||||
|
||||
### 行情
|
||||
|
||||
- 永续:`exchange.fetch_ticker` + `get_available_trading_usdt` + `compute_full_margin_sizing`
|
||||
- 期权:`build_option_chain`(与期权页同源)
|
||||
|
||||
### 已知边界(非 P0 bug)
|
||||
|
||||
1. 建议张数未强制 `amount_to_precision`(开仓阶段再对齐交易所精度).
|
||||
2. 止盈账扣全额权利金,与期权是否仍持仓无关(策略如此).
|
||||
3. 热更新 `HEDGE_PLAN_ENABLED` 后需刷新页面才显隐导航.
|
||||
4. 嵌入壳 Tab 已注册 `hedge_plan`;中控能力勾选若需显式「对冲」可后续加.
|
||||
|
||||
---
|
||||
|
||||
## 4. 服务器启用步骤
|
||||
|
||||
```bash
|
||||
# env 配置页 → 对冲计划 → HEDGE_PLAN_ENABLED=true → 保存
|
||||
# 或服务器:
|
||||
cd /opt/crypto_monitor_user/crypto_monitor_okx
|
||||
# 确保 .env 含 HEDGE_PLAN_* 字段后
|
||||
pm2 restart crypto_okx --update-env
|
||||
```
|
||||
|
||||
验收:
|
||||
|
||||
1. 顶栏出现「对冲计划」
|
||||
2. 永期可见建议张数(全仓时)
|
||||
3. 点「计算」得到止盈/止损合计
|
||||
4. 「启动计划」禁用
|
||||
5. env 页可见「对冲计划」分组
|
||||
-464
@@ -1,464 +0,0 @@
|
||||
# 更新文档(仓库级)
|
||||
|
||||
自 2026-07-16 起:**凡修改或更新功能,必须在本文件追加一条记录**,写明原因、改动位置、目标与交付验收。实例目录下旧版说明可保留,但共享逻辑(`lib/`)以本文为准。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-17 · 修复 pip>=26 部署依赖安装失败
|
||||
|
||||
### 修改原因
|
||||
|
||||
单所验证时 `setup_env` 升级到 pip 26 后,`--progress-bar ascii` 非法,依赖安装中断;腾讯源偶发空索引也会导致一次失败.
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `deploy/lib/common.sh` | `pip_progress_bar_arg` 改为 `on`(兼容 pip 26);`pip install` 加 `--retries 5` |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
非 TTY / SSH 下一键安装可顺利 `pip install -r`.
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
`bash deploy/lib/install.sh --exchange okx` 能过依赖安装并起 `crypto_okx`.
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-17 · 一键部署支持单所仅实例(不含中控)
|
||||
|
||||
### 修改原因
|
||||
|
||||
新机或专用机只需跑某一所 Flask 时,全套 7 进程过重;希望菜单可直接选「仅 OKX / Binance / Gate」,不起中控与 agent.
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `deploy/manage.sh` | 菜单增加 4/5/6 单所实例入口 |
|
||||
| `deploy/lib/install.sh` | `--exchange okx\|binance\|gate` 单所流水线 |
|
||||
| `deploy/pm2_start_all.sh` | `--only` 只启对应 ecosystem |
|
||||
| `deploy/lib/common.sh` | 单所验收 / 完成提示 / 辅助映射 |
|
||||
| `deploy/README.md` | 菜单说明 |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
1. 选 4/5/6: `setup_env --only <所>` + 仅启动该所 PM2,不含 hub/agent.
|
||||
2. 选 1: 全套行为与改前一致.
|
||||
3. 仍共用整仓 `/opt/crypto_monitor_user`,不拆仓库.
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 菜单可见 4/5/6.
|
||||
2. `bash deploy/lib/install.sh --exchange okx` 后仅 `crypto_okx` 起来,`:5004` 可访问.
|
||||
3. 全套选项 1 仍可部署三所+中控.
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-17 · 永续估算盈亏统一扣双边 taker 手续费
|
||||
|
||||
### 修改原因
|
||||
|
||||
中控/实例「盈利金额」、微信推送「本单盈亏」、交易记录 `pnl_amount` 使用价差毛利,未扣开平手续费,与交易所实际净盈亏及盈亏比体感偏差较大。
|
||||
|
||||
### 定稿口径
|
||||
|
||||
| 项 | 约定 |
|
||||
|----|------|
|
||||
| 浮盈亏 | 仍读交易所,不改 |
|
||||
| 费率 | taker 单边 **0.05%**(`PERP_TAKER_FEE_RATE`,默认 `0.0005`),开+平双边 |
|
||||
| 净盈亏 | 毛利 − 开仓名义×费率 − 平仓名义×费率(不考虑滑点) |
|
||||
| RR | 净盈利 / 原风险(风险侧加费第二步再做) |
|
||||
| 历史记录 | 不回算 |
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `lib/trade/trade_fee_lib.py` | 新增公共扣费 / 净盈亏 |
|
||||
| `lib/strategy/strategy_roll_ui_lib.py` | `reward_at_tp_usdt` → 净盈利 |
|
||||
| `lib/strategy/strategy_roll_lib.py` | 同上 |
|
||||
| `lib/strategy/strategy_trend_lib.py` | `calc_tp_profit_usdt` → 净盈利 |
|
||||
| `lib/hub/hub_calculator_lib.py` | 滚仓预览止盈盈利 / 首仓盈利扣费;RR 跟净盈利 |
|
||||
| `crypto_monitor_okx/app.py` | `calc_pnl` → 净盈亏(推送/记账) |
|
||||
| `crypto_monitor_gate/app.py` | 同上 |
|
||||
| `crypto_monitor_binance/app.py` | `calc_pnl` / 成交回退扣费;income 真费路径优先不改 |
|
||||
| `tests/test_trade_fee_lib.py` | 新增 |
|
||||
| `tests/test_strategy_roll_ui_lib.py` | 断言改净额 |
|
||||
| `tests/test_order_monitor_display_lib.py` | 断言改净额 |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
1. 中控持仓「盈利金额」、实例「盈利金额」、计算器止盈盈利、趋势/滚仓预览一致为净盈亏。
|
||||
2. 微信推送与新建交易记录的 `pnl_amount` 与上述估算口径一致。
|
||||
3. 币安若能拉到 income 净额(已含真实手续费)仍优先用交易所数。
|
||||
4. 浮盈亏展示仍跟交易所。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 同一笔持仓:中控盈利金额 ≈ 实例盈利金额(均为扣费后)。
|
||||
2. 平仓推送「本单盈亏」与新写入记录接近,不再明显大于交易所净利。
|
||||
3. 浮盈亏与交易所 App 一致(本改不动)。
|
||||
4. 单测:`python -m unittest tests.test_trade_fee_lib tests.test_strategy_roll_ui_lib tests.test_order_monitor_display_lib tests.test_trend_preview_tp -v` 通过。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 计算器左侧 Tab + 三行输入
|
||||
|
||||
### 修改原因
|
||||
|
||||
电脑端顶部横向 Tab 与纵向表单不协调,输入项行数偏多。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/index.html` | 增加计算器工作区容器,更新 CSS 缓存 |
|
||||
| `manual_trading_hub/static/app.css` | 电脑端 Tab 改为左侧竖排;≥1200px 基础输入改为五列、三行排列 |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
电脑端左侧切换计算器,右侧集中填写;宽屏基础输入压缩为三行。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 电脑端两个计算器 Tab 位于左侧。
|
||||
2. 1920px 宽屏基础输入区为三行。
|
||||
3. Tab 切换和计算功能正常;手机端保持原布局。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 电脑端计算器改为 Tab 切换
|
||||
|
||||
### 修改原因
|
||||
|
||||
电脑端同时并排显示趋势回调与滚仓计算器,横向空间利用和操作聚焦不理想。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/index.html` | 计算器 Tab 增加电脑端完整名称,更新 CSS 缓存 |
|
||||
| `manual_trading_hub/static/app.css` | 电脑端显示 Tab、单列展示当前计算器;手机端沿用原紧凑 Tab |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
电脑端通过「趋势回调计算器 / 滚仓计算器」Tab 切换,一次只显示一个计算器。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 电脑端默认显示趋势回调计算器。
|
||||
2. 点击滚仓计算器 Tab 后只显示滚仓计算器,切回正常。
|
||||
3. 手机端原有计算器 Tab 样式和交互不变。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 资金概况移除累计盈亏长条
|
||||
|
||||
### 修改原因
|
||||
|
||||
「同步快照」后方的累计盈亏长条与下方汇总卡片重复,占用横向空间。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/index.html` | 删除资金工具栏中的累计盈亏/较昨日长条 |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
资金概况工具栏仅保留「同步快照」和状态信息,累计盈亏继续由下方汇总卡展示。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 「同步快照」按钮后不再显示累计盈亏长条。
|
||||
2. 下方累计盈亏、较昨日汇总卡数据正常显示。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 今日统计默认折叠 + 交易所标题行下移
|
||||
|
||||
### 修改原因
|
||||
|
||||
今日统计常占一行挤空间;默认只需看总浮盈亏。交易所卡标题/打开实例贴顶过紧。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/app.js` | 今日统计默认折叠只露总浮盈亏;展开显示明细;状态写入 localStorage |
|
||||
| `manual_trading_hub/static/app.css` | 折叠/展开样式;展开后明细字号加大;分栏卡 `card-head` 上内边距加大 |
|
||||
| `manual_trading_hub/static/index.html` | 缓存 `20260716-hub-stats-fold` |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
监控区默认更省高;需要时一键展开更大明细;交易所标识行不再贴顶。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 默认只见「今日统计 + 总浮盈亏」与「展开明细」。
|
||||
2. 点展开后六项明细可见且数字更大。
|
||||
3. OKX/币安等卡标题与按钮相对顶边有更明显间距。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 监控芯片还原 + 底栏贴底 + 1080p 留白
|
||||
|
||||
### 修改原因
|
||||
|
||||
「监控位」本意是原先关键位/趋势回调/顺势加仓芯片,不是单独「无监控位」槽;全屏提示须贴卡片最底;1920×1080 两侧需留白,带鱼屏保持现宽。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/app.js` | 去掉错误的监控位槽;恢复策略芯片(仍隐藏期权 N仓) |
|
||||
| `manual_trading_hub/static/app.css` | 分栏卡 `card-expand-hint` `margin-top:auto` 贴底;≤2000px 加大左右留白,>2000px 保持 1860 内容宽 |
|
||||
| `manual_trading_hub/static/index.html` | 缓存 `20260716-hub-chips-margin` |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
有关键位/趋势/顺势时仍以芯片显示;提示条在卡底;1080p 两侧有留白,带鱼屏观感不变。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 无「监控位 · 0 / 无监控位」区块;有关键位等时出现原芯片样式。
|
||||
2. 「点击标题栏进入全屏…」贴在各分栏卡最底部。
|
||||
3. 1920×1080 内容两侧留白明显;带鱼屏内容宽度仍约 1860。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 监控区 2×2 细化(目标监控列/预留行/去平板专属)
|
||||
|
||||
### 修改原因
|
||||
|
||||
四卡对齐后需:左右等宽;期权表改目标监控列;去掉打开期权页与永续卡「期权 N仓」;合约卡预留仓位+监控位两行并统一全屏提示;多仓时同行左右一起长高;平板改用浏览器 80% 缩放,去掉 hub-tablet 专属样式(手机 UI 不动)。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/app.js` | 平铺 `monitor-split-2x2`;期权表删指数/到期平衡/平掉回本,加目标监控列(有=绿/无=`—`);去打开期权页;永续卡隐藏期权徽章;预留仓位/监控位槽;去掉 `isTabletLayout` |
|
||||
| `manual_trading_hub/static/app.css` | 左右 1:1;`minmax(min-content,1fr)` 同行同高可外扩;删除全部 `hub-tablet` 规则 |
|
||||
| `manual_trading_hub/static/index.html` | 缓存 `20260716-hub-2x2-slots` |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
桌面监控区四卡等宽对齐;期权看目标监控列;永续卡结构为仓位行+监控位行+提示行;≥2 仓左右一起加高;平板不再走单独 CSS。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 左右列等宽;永续卡无「期权 1仓」、无「打开期权页」。
|
||||
2. 期权表有「目标监控」列,有监控绿色、无则 `—`;无指数/到期平衡/平掉回本。
|
||||
3. 永续/币安卡可见预留仓位行、监控位行与完整全屏提示文案。
|
||||
4. 多开仓后该行变高且左右同高;`body` 无 `hub-tablet`;手机布局未改。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · OKX 拆成永续/期权双卡(2×2 对齐)
|
||||
|
||||
### 修改原因
|
||||
|
||||
OKX 单卡内嵌永续+期权过高,右侧币安/Gate 两卡对不齐,1080p 观感不协调。按产品建议改为四卡对齐。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/app.js` | 期权分栏时 OKX 渲染为「·永续」「·期权」两张独立卡;标题与操作按钮按卡片分流 |
|
||||
| `manual_trading_hub/static/app.css` | 左右列均 `1fr/1fr`,四卡 2×2 等高对齐;卡体内滚 |
|
||||
| `manual_trading_hub/static/index.html` | 静态资源缓存 `20260716-hub-okx-2x2` |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
监控区呈现:左上永续 / 左下期权 / 右上币安 / 右下 Gate,四卡对齐。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 桌面监控区可见 `OKX_趋势 · 永续` 与 `OKX_趋势 · 期权` 两张独立卡。
|
||||
2. 四卡与右侧币安、Gate 同行等高,不再一大两小。
|
||||
3. 点击任一张 OKX 卡标题仍可全屏;期权卡「全平」不重复出现。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 1080p 监控区无持仓空洞收紧
|
||||
|
||||
### 修改原因
|
||||
|
||||
1920×1080 上一屏适配用 `1fr/1fr` 把永续/分所空卡强行均分拉高,「无持仓」下方大片空洞;带鱼屏尚可,短屏观感差。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/app.css` | OKX 内卡改为 `auto + 1fr`(永续按内容、期权吃剩余);右侧 Gate/币安改为 `auto auto` 按内容收紧;1080p 短屏左侧略加宽 |
|
||||
| `manual_trading_hub/static/index.html` | CSS 缓存 `20260716-hub-1080-tight` |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
无持仓区块不再被拉成半屏空黑;有期权/持仓的区域拿到更多可视高度。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 1920×1080 监控区:永续「无持仓」仅占内容高度,期权表区域明显变高。
|
||||
2. 右侧币安/Gate 无仓时卡片贴内容,不再卡片内大片空洞。
|
||||
3. 带鱼屏布局仍可用;持仓变多时右侧列可内滚。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 电脑端误判平板导致发糊
|
||||
|
||||
### 修改原因
|
||||
|
||||
`isTabletLayout()` 曾用「高度 ≤920 即平板」;1080p 电脑有任务栏/浏览器栏时 `innerHeight` 常落在此区间,桌面被套上平板压缩字号(约 10px),观感发糊发虚。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/app.js` | 平板判定改为横屏 `721–1366 × ≤900` / 竖屏 `≤920 × ≥900`;去掉仅按高度命中 |
|
||||
| `manual_trading_hub/static/index.html` | JS 缓存 `20260716-hub-desktop-clear` |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
常规电脑端不再误加 `hub-tablet`,恢复桌面字号与清晰度;真平板视口仍走一屏密度样式。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 1080p/1440p 电脑打开中控,正文与表格清晰,非异常小字。
|
||||
2. 浏览器开发者工具确认 `body` 无 `hub-tablet`(窄窗模拟平板除外)。
|
||||
3. 平板横/竖仍为一屏密度布局。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 平板一屏密度适配(不裁切)
|
||||
|
||||
### 修改原因
|
||||
|
||||
平板字号偏大、留白空、卡片半截裁切或底部大片空黑,显得 low;需要「一屏看完」且正文不被拦腰裁掉。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/app.css` | `hub-tablet`:锁 100dvh;压缩字号/间距;监控区 flex 填满;竖屏 OKX 上 + Gate/币安并排;表体内滚兜底;资金页缩曲线、压汇总数字、分户卡完整可见 |
|
||||
| `manual_trading_hub/static/index.html` | CSS 缓存 `20260716-hub-tablet-onescreen` |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
平板监控区/资金概况一屏呈现、信息密度接近桌面;常规持仓与分户名称/余额不被裁半;持仓很多时仅表体内滚。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 平板强制刷新后,监控区三所卡片同屏,余额与持仓列完整可读。
|
||||
2. 资金概况:四格汇总 + 曲线 + 分户卡同一屏,账户名不被切半。
|
||||
3. 底部不再大片空黑;桌面大屏一屏规则不受影响。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 平板监控卡片内容裁切修复(已由一屏密度方案取代)
|
||||
|
||||
先前改为整页可滚以避免裁切;用户要求改为「一屏 + 不裁切 + 提密度」,由上一条覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 中控平板一屏适配(2560×1600)
|
||||
|
||||
### 修改原因
|
||||
|
||||
平板物理分辨率 2560×1600 在 2× 缩放下 CSS 视口约为 **1280×800**,进不了此前桌面规则 `min-width:1600px`,监控/行情/资金仍整页滚动。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/app.js` | 新增 `isTabletLayout()`;`hub-tablet` body class |
|
||||
| `manual_trading_hub/static/app.css` | 一屏适配门槛降为 `min-width:721px` + `min-height:650px`;另增平板矮/窄视口加密度规则 |
|
||||
| `manual_trading_hub/static/index.html` | 缓存版本 `20260716-hub-fit-tablet` |
|
||||
|
||||
### 达成的目标
|
||||
|
||||
平板(含 2560×1600@2x)与桌面一样:监控区 / 行情区 / 资金概况尽量一屏、无整页下拉。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. 平板横屏打开中控,强制刷新后 body 应有 `hub-tablet`(开发者工具)。
|
||||
2. 监控区(OKX 一期权 ± 永续空/一仓,另两所各 ≤1 仓):无整页纵向滚动。
|
||||
3. 行情区、资金概况同屏无整页滚动。
|
||||
4. 手机(≤720px)仍走 `hub-phone`,不受影响。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 中控三页 1920×1080 一屏显示
|
||||
|
||||
### 修改原因
|
||||
|
||||
监控区在 OKX「一永续 + 一期权」、另两所各一仓(或空仓)时,以及行情区、资金概况在 1920×1080 下出现整页纵向滚动,无法一屏看完。
|
||||
|
||||
### 修改的地方
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `manual_trading_hub/static/app.js` | `setActiveNav` 增加 `hub-page-monitor` / `hub-page-market` body class |
|
||||
| `manual_trading_hub/static/app.css` | `@media (min-width:1600px) and (min-height:900px)` 一屏适配:壳层 `100dvh` 不滚动;三页 flex 填满;监控卡片/表格压缩;行情 K 线区 flex 吃剩余高度;资金曲线与分户区压缩 |
|
||||
| `manual_trading_hub/static/index.html` | `app.css` / `app.js` 缓存版本 `20260716-hub-fit-1080` |
|
||||
|
||||
**未改:** 期权开平仓规则、实例交易页、手机端 `hub-phone` 布局。
|
||||
|
||||
### 达成的目标
|
||||
|
||||
1. **监控区**:桌面大屏下整页无纵向滚动;OKX 左右分栏(永续+期权)与 Binance/Gate 同屏可见。仓位表过长时仅卡片内部滚动。
|
||||
2. **行情区**:工具条 + K 线同屏,图表占满剩余高度。
|
||||
3. **资金概况**:统计卡 + 曲线 + 三分户同屏。
|
||||
|
||||
### 交付之后的验收(1920×1080,浏览器缩放 100%)
|
||||
|
||||
1. 打开中控监控区:服务器状态与操作栏保持折叠时,页面**无**浏览器纵向滚动条;OKX 期权 1 仓 + 另两所空仓/各 1 仓均一屏可见。
|
||||
2. 行情区:加载 BTC 日线后,OHLCV + 图在一屏内,无整页滚动。
|
||||
3. 资金概况:曲线与三分户卡片同屏,无整页滚动。
|
||||
4. 窄屏/手机(`hub-phone`)布局不受影响。
|
||||
5. 展开「服务器状态」后若内容过高,允许监控网格内部滚动,仍尽量避免整页滚动。
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-16 · 期权/对冲开仓仅认真实卖一深度
|
||||
|
||||
### 修改原因
|
||||
|
||||
此前报价在无盘口卖一时会用**标记价顶进 `ask`**,界面仍显示「限价买入 @ 卖一」,造成误以为在吃卖一;深度实值合约还容易「链上有 `~` 价、点选却失败或按估算价下单」。需要与产品规则对齐:**开仓只吃真实卖一,且必须有卖一量**。
|
||||
|
||||
### 修改的地方(明确清单)
|
||||
|
||||
| 文件 | 改动摘要 |
|
||||
|------|----------|
|
||||
| `lib/exchange/okx_options_lib.py` | 新增 `option_buy_liquidity_ok` / `cap_option_buy_sheets_to_ask_depth`;`quote_option_contract` **不再**用 mark 填充开仓 `ask`;返回 `can_open` / `ref_ask` / `open_block_msg` / `ask_source` |
|
||||
| `lib/options/options_register.py` | `/api/options/quote` 开仓 sizing 仅在 `can_open` 时计算,张数 cap 到卖一深度;`/api/options/open` 服务端再验深度并 cap 张数 |
|
||||
| `lib/common/static/options_panel.js` | 面板展示参考标记价;无深度禁用开仓按钮与说明文案;开仓前校验 `can_open` |
|
||||
| `lib/options/templates/options_panel.html` | 提示文案;增加「参考标记价」字段;脚本 `?v=37` |
|
||||
| `lib/hedge_plan/hedge_plan_orders_lib.py` | **仅** `_buy_option`(买入开仓)同步深度门禁与张数 cap;**未改** `_sell_option` 平仓 |
|
||||
| `lib/hedge_plan/templates/hedge_plan_panel.html` | 单位说明补充开仓规则 |
|
||||
| `tests/test_option_buy_liquidity.py` | 新增门禁/深度 cap 单测 |
|
||||
| `tests/test_hedge_plan_orders.py` | mock 补 `ask_sz`;无深度拒绝 / 深度 cap 用例 |
|
||||
|
||||
**铁律:未改动任何平仓规则**(期权买一平仓、`_sell_option`、close_preview / close 执行路径逻辑保持原样;报价里买一仍可用 mark 补展示,仅服务平仓读 bid)。
|
||||
|
||||
### 达成的目标
|
||||
|
||||
1. 开仓条件:`askPx` 有效 **且** `askSz > 0`。
|
||||
2. 无卖一/无深度:可展示参考标记价 `ref_ask`/`mark`,明确「不可用于开仓」,按钮禁用。
|
||||
3. 有深度时:限价买 @ 真实卖一;张数不超过卖一深度(向下取整)。
|
||||
4. 期权面板与对冲计划买入路径规则一致。
|
||||
|
||||
### 交付之后的验收
|
||||
|
||||
1. **有卖一深度**:选合约 → 卖一显示 `价/量` → 按钮「限价买入 @ 卖一」可点 → 下单张数 ≤ 卖一量。
|
||||
2. **无卖一或深度为 0**:卖一为 `—`;参考标记价显示 `~xx (不可开仓)`;红色说明含「仅供参考,不可用于开仓」;按钮为「暂无卖一深度,无法开仓」且不可点;直接调 open API 应返回失败文案。
|
||||
3. **链上 `~` 估算**:仍可浏览;点选后若无真实深度,不得用估算价成交。
|
||||
4. **平仓**:持仓「买一平仓」行为与改前一致(抽测一条即可)。
|
||||
5. **对冲计划**:执行买入腿时无深度应失败并提示;有深度 dry_run/实盘张数不超过卖一量。
|
||||
6. 单测:`python -m pytest tests/test_option_buy_liquidity.py tests/test_hedge_plan_orders.py -q` 通过。
|
||||
|
||||
### 未纳入本次(另单)
|
||||
|
||||
硬刷新链可能毁掉下单面板、限频 fallback tick、CSS `?v=` 缓存等,见会话审计清单,不在本条范围。
|
||||
@@ -1,322 +0,0 @@
|
||||
# 期权对冲方案分析
|
||||
|
||||
> 适用范围:OKX **永续子账户**(USDT 本位) + **期权主账户**(USDⓈ 本位买方).
|
||||
> 本文档为 **策略与操盘说明**,非系统自动下单功能;组合须 **人工** 在永续页与期权页分别执行.
|
||||
|
||||
---
|
||||
|
||||
## 1. 前提与账户分工
|
||||
|
||||
| 维度 | 永续合约(子账户) | 期权(主账户) |
|
||||
|------|------------------|--------------|
|
||||
| API | `OKX_API_*` | `OKX_OPTIONS_API_*` |
|
||||
| 系统页面 | 实盘下单 / 关键位 / 策略 | 期权 |
|
||||
| 保证金 | USDT | USDC / USDG |
|
||||
| 本系统能力 | 开平仓、止损、关键位 | **仅买方** 开平仓,无组合单 |
|
||||
|
||||
**重要约束:**
|
||||
|
||||
- 两套资金 **不自动合并**;对冲是逻辑上的组合,资金与下单 **分账户** 管理.
|
||||
- 系统 **不支持** 卖方开仓、跨式组合单、自动 Delta 调仓;下文操盘均为 **手工流程**.
|
||||
- 期权模块默认:≤2 日到期、轻度实值、单笔权利金预算(如 10 USDC).对冲设计需与此预算协调.
|
||||
|
||||
---
|
||||
|
||||
## 2. 期权 + 永续合约对冲
|
||||
|
||||
指用 **期权买方头寸** 去对冲或替代 **永续方向敞口**,降低单边暴跌/暴涨带来的尾部风险,或锁定部分利润.
|
||||
|
||||
### 2.1 使用场景
|
||||
|
||||
| 场景 | 永续侧状态 | 期权侧动作 | 目的 |
|
||||
|------|------------|------------|------|
|
||||
| **保护性 Put** | 子账户 **多头** 永续(策略/关键位持仓) | 主账户买入 **看跌 Put**(轻度实值、近月) | 大跌时 Put 升值,抵消永续浮亏 |
|
||||
| **看涨保险** | 子账户 **空头** 永续 | 主账户买入 **看涨 Call** | 急涨时 Call 升值,限制空头亏损 |
|
||||
| **事件前对冲** | 永续有仓,临近 FOMC/CPI/非农 | 临时买 **Put 或 Call**(方向视净敞口) | 降低数据公布瞬间波动伤害 |
|
||||
| **锁利减仓** | 永续浮盈较大,不愿全平 | 买 **反向期权** 作「便宜保险」 | 保留永续吃趋势,用权利金买回撤保护 |
|
||||
| **替代硬止损** | 永续止损易被扫或滑点大 | Put/Call 权利金 ≈ 可接受最大额外损失 | 用期权时间价值换「软止损」 |
|
||||
|
||||
**不适合:**
|
||||
|
||||
- 长期持仓 + 远期期权(本系统偏 ≤2 日 DTE,时间价值流失快).
|
||||
- 指望「完全对冲」零波动:买方期权有 **Theta 衰减**,永续有 **资金费**,不可能完美镜像.
|
||||
- 资金不足:期权需先 **USDT→USDC 兑换 + 划转到交易户**,再下单.
|
||||
|
||||
### 2.2 案例分析
|
||||
|
||||
#### 案例 A:保护性 Put(多头永续 + 买 Put)
|
||||
|
||||
**背景**
|
||||
|
||||
- 子账户 ETH 永续 **多 0.5 ETH**,均价 3,200,浮盈 +5%.
|
||||
- 当晚有宏观数据,担心 30 分钟内急跌 3%~8%.
|
||||
|
||||
**操作**
|
||||
|
||||
1. 永续:维持多单,或略减杠杆(系统「实盘下单」/策略仓).
|
||||
2. 期权主账户:USDT→USDC→划转到交易户.
|
||||
3. 期权页:ETH **看跌 Put**,行权价略低于现价(轻度实值),1~2 日到期.
|
||||
4. 按预算 10 USDC 买满(约 0.0x ETH 名义),记录权利金 **C**.
|
||||
|
||||
**结果推演**
|
||||
|
||||
| ETH 走势 | 永续盈亏(示意) | Put 盈亏(示意) | 组合效果 |
|
||||
|----------|----------------|----------------|----------|
|
||||
| 横盘 | 小亏资金费 | Put 因 Theta 贬值 | 净成本 ≈ 权利金 C |
|
||||
| 跌 5% | 永续大亏 | Put 明显升值 | 部分对冲,净亏 < 无 Put |
|
||||
| 涨 3% | 永续盈利 | Put 接近归零 | 永续盈利 − C |
|
||||
|
||||
**要点:** 买的是 **保险**,不是赚钱工具;权利金 C 是「保费」.
|
||||
|
||||
#### 案例 B:空头永续 + 买 Call
|
||||
|
||||
**背景**
|
||||
|
||||
- 子账户 BTC 永续 **空 0.02 BTC**,判断短期震荡偏空,但担心消息拉升.
|
||||
|
||||
**操作**
|
||||
|
||||
- 买 **看涨 Call**(轻度实值、近月),权利金控制在预算内.
|
||||
|
||||
**结果推演**
|
||||
|
||||
- 下跌:永续盈利,Call 损耗 → 净赚略少于裸空.
|
||||
- 急涨:永续亏损,Call 盈利 → 涨幅越大对冲越有效.
|
||||
|
||||
#### 案例 C:事件窗口「临时对冲」
|
||||
|
||||
**背景**
|
||||
|
||||
- 永续多仓持有中,事件前 2 小时不想平仓(怕踏空).
|
||||
|
||||
**操作**
|
||||
|
||||
- 事件前:买小仓位 Put,事件后 1~2 小时内无论盈亏 **平掉 Put**.
|
||||
- 永续:按原策略止损/止盈,不因期权改变永续规则.
|
||||
|
||||
**要点:** 短期 Put 的 Theta 极快;**事件结束尽快平仓**,避免「保险变持仓」.
|
||||
|
||||
### 2.3 风险评估
|
||||
|
||||
| 风险类型 | 说明 | 等级 |
|
||||
|----------|------|------|
|
||||
| **权利金损耗(Theta)** | 买方期权每天衰减;横盘即亏保费 | 高 |
|
||||
| **对冲比例不足** | 10U 预算买到的名义远小于永续仓位 | 高 |
|
||||
| **方向错配** | 多头却买 Call、空头却买 Put | 高 |
|
||||
| **双账户操作延迟** | 永续已亏,期权尚未成交 | 中 |
|
||||
| **流动性** | 限价未成交,极端行情无法对冲 | 中 |
|
||||
| **资金费 vs 保费** | 长期持仓资金费 + 反复买 Put 成本叠加 | 中 |
|
||||
| **汇率/币种** | 永续 USDT、期权 USDC,汇率波动次要 | 低 |
|
||||
| **API/权限** | 子账户与主账户密钥混用 | 低(配置隔离即可) |
|
||||
|
||||
**风险量化思路(手工估算):**
|
||||
|
||||
```
|
||||
可接受保费上限 ≈ 永续名义价值 × 愿意承担的单日额外损失比例
|
||||
例:0.5 ETH × 3,200 × 0.5% ≈ 8 USDC → 与 OKX_OPTIONS_TRADE_BUDGET_USDC 对齐
|
||||
```
|
||||
|
||||
若保费 << 永续风险敞口,属于 **部分对冲**,需心里有数.
|
||||
|
||||
### 2.4 操盘说明(期权 + 永续)
|
||||
|
||||
**准备(每次对冲前)**
|
||||
|
||||
1. 确认子账户永续:**方向、数量、均价、浮盈亏**(实盘顶栏/持仓).
|
||||
2. 确认主账户期权:**交易户 USDC 余额** 足够(兑换+划转).
|
||||
3. 明确本次是 **保护多仓(Put)** 还是 **保护空仓(Call)**.
|
||||
|
||||
**执行顺序(推荐)**
|
||||
|
||||
```
|
||||
① 永续侧:确认仓位与风控(止损/关键位)已就绪
|
||||
② 主账户:资金户 USDT → USDC(币种兑换) → 划转到交易户
|
||||
③ 期权页:选标的(ETH/BTC) → 到期日(1~2日) → Call/Put → 轻度实值行权价
|
||||
④ 限价买入(卖一),成交后记录:张数、权利金、行权价、到期时间
|
||||
⑤ 企业微信:关注翻倍提醒(浮盈≥100%权利金)作为减仓信号,非必须平永续
|
||||
```
|
||||
|
||||
**平仓/退出**
|
||||
|
||||
| 情况 | 永续 | 期权 |
|
||||
|------|------|------|
|
||||
| 趋势延续、保险未触发 | 按原策略 | 临近到期或 Theta 损耗大时 **平 Put/Call** |
|
||||
| 期权浮盈翻倍(系统提醒) | 可选:减永续仓或上移止损 | **平期权锁利**,保留永续 |
|
||||
| 永续已止损离场 | 无仓 | **立即平期权**,避免裸买权衰减 |
|
||||
| 事件结束 | 照旧 | 平期权,勿长期持有近月买方 |
|
||||
|
||||
**检查清单**
|
||||
|
||||
- [ ] 永续方向与期权类型匹配(多→Put,空→Call)
|
||||
- [ ] 权利金 ≤ 预算,且 ≤ 心理「保费」上限
|
||||
- [ ] 成交后两边持仓在各自页面可核对
|
||||
- [ ] 到期日前 24h 评估是否平仓期权
|
||||
|
||||
---
|
||||
|
||||
## 3. 期权 + 期权对冲
|
||||
|
||||
指 **仅主账户** 内,用两个(或多个)买方期权组合,表达 **波动、方向区间或尾部保护**,不直接动永续仓位.
|
||||
|
||||
> 本系统 **仅买方**;下列组合均为 **买 Call + 买 Put** 或 **不同行权价双买**,不含卖权收权利金策略.
|
||||
|
||||
### 3.1 使用场景
|
||||
|
||||
| 场景 | 组合结构 | 目的 |
|
||||
|------|----------|------|
|
||||
| **Long Straddle(双买)** | 同行权附近 **Call + Put** | 赌大波动(突破),不怕方向 |
|
||||
| **Long Strangle(宽双买)** | OTM **Call + OTM Put** | 降低成本,赌更大波动才盈利 |
|
||||
| **风险逆转(买方版)** | 轻度 ITM Call + 轻度 ITM Put(不同行权) | 同时防暴涨暴跌(保费更高) |
|
||||
| **方向 + 尾部** | 主方向 Call(或 Put) + 反向少量 Put(或 Call) | 主观点明确,反向作灾难保险 |
|
||||
| **到期滚动** | 近月双买 → 波动未出则平掉 → 换远 1 日 | 控制 Theta,需严格纪律 |
|
||||
|
||||
**不适合:**
|
||||
|
||||
- 预期 **窄幅震荡**(双买最亏 Theta).
|
||||
- 预算只够一笔 10U(双买需 **两倍保费** 或各减半张数).
|
||||
- 把双买当「稳赚」:横盘天天亏.
|
||||
|
||||
### 3.2 案例分析
|
||||
|
||||
#### 案例 D:Long Strangle(赌突破)
|
||||
|
||||
**背景**
|
||||
|
||||
- 认为 ETH 未来 24~48h 将选方向突破,但不确定涨跌.
|
||||
- 永续不想开仓,仅用期权表达波动观点.
|
||||
|
||||
**操作**
|
||||
|
||||
1. 选 1~2 日到期.
|
||||
2. 买 **轻度 OTM Call**(行权价略高于现价).
|
||||
3. 买 **轻度 OTM Put**(行权价略低于现价).
|
||||
4. 各用约一半预算(如各 5 USDC),总保费 ≈ 10U.
|
||||
|
||||
**盈亏示意**
|
||||
|
||||
| 市场 | 结果 |
|
||||
|------|------|
|
||||
| 横盘 | Call、Put 均衰减 → **最大亏全部保费** |
|
||||
| 大涨 | Call 盈利可能覆盖 Put 亏损 |
|
||||
| 大跌 | Put 盈利可能覆盖 Call 亏损 |
|
||||
|
||||
**要点:** 需要波动 **幅度** 超过「总保费对应的隐含波动门槛」才划算.
|
||||
|
||||
#### 案例 E:主多观点 + 尾部 Put(期权 + 期权)
|
||||
|
||||
**背景**
|
||||
|
||||
- 强烈看多 24h,但怕黑天鹅砸盘.
|
||||
|
||||
**操作**
|
||||
|
||||
- 70% 预算买 **Call**(进攻).
|
||||
- 30% 预算买 **Put**(尾部保险).
|
||||
|
||||
**与「永续 + Put」区别**
|
||||
|
||||
- 不占用永续保证金,无资金费.
|
||||
- 但 **无 Delta 线性收益**:涨得慢可能 Call 仍亏 Theta.
|
||||
|
||||
#### 案例 F:事件双买
|
||||
|
||||
**背景**
|
||||
|
||||
- 非农数据公布前后 2h.
|
||||
|
||||
**操作**
|
||||
|
||||
- 公布前 30min:Strangle 双买.
|
||||
- 公布后:波动释放则 **平盈利腿 + 平亏损腿**;勿持仓过夜除非仍看好波动.
|
||||
|
||||
### 3.3 风险评估
|
||||
|
||||
| 风险类型 | 说明 | 等级 |
|
||||
|----------|------|------|
|
||||
| **双倍 Theta** | 两条腿同时衰减 | 很高 |
|
||||
| **预算分裂** | 单腿名义过小,波动不够覆盖成本 | 高 |
|
||||
| **行权价选错** | Strangle 过宽,突破仍不够回本 | 高 |
|
||||
| **执行误差** | 两腿须分别下单,一腿成交一腿未成交 | 中 |
|
||||
| **IV crush** | 事件后隐含波动率骤降,双买同时贬值 | 中 |
|
||||
| **仅买方限制** | 无法做卖方收窄成本(如 Iron Condor) | 中 |
|
||||
|
||||
**与永续对冲对比**
|
||||
|
||||
| 维度 | 期权 + 永续 | 期权 + 期权 |
|
||||
|------|-------------|-------------|
|
||||
| 资金账户 | 两账户 | 仅主账户 |
|
||||
| 趋势收益 | 永续线性 | 非线性,近月衰减快 |
|
||||
| 保费成本 | 永续资金费 + 期权 | 仅期权(常更高) |
|
||||
| 适合行情 | 有主仓需保 | 无仓赌波动/事件 |
|
||||
|
||||
### 3.4 操盘说明(期权 + 期权)
|
||||
|
||||
**预算拆分建议**
|
||||
|
||||
```
|
||||
总预算 B (如 10 USDC)
|
||||
├── 腿 A(主观点):B × 60%~70%
|
||||
└── 腿 B(对冲/反向):B × 30%~40%
|
||||
|
||||
双买 Strangle:各 50%,但须接受单腿名义减半
|
||||
```
|
||||
|
||||
**执行顺序**
|
||||
|
||||
1. 先下 **流动性更好的一腿**(通常轻度 ITM 或更近 ATM),减少单腿敞口时间.
|
||||
2. 再下第二腿;若第二腿限价未成交,评估是否撤单重报或放弃组合.
|
||||
3. 记录两笔 `options_trades` 对应关系(备注:组合 ID / 事件名).
|
||||
|
||||
**平仓纪律**
|
||||
|
||||
- **时间止损:** 距到期 < 12h 且未盈利 → 考虑双平,避免 Theta 加速.
|
||||
- **盈利止损:** 组合净浮盈达保费 50%~100% → 可分批平(系统翻倍提醒可作参考).
|
||||
- **单边平仓风险:** 只平盈利腿会留下裸反向腿,除非有意转方向.
|
||||
|
||||
---
|
||||
|
||||
## 4. 方案选型简表
|
||||
|
||||
| 你的状态 | 推荐方案 | 理由 |
|
||||
|----------|----------|------|
|
||||
| 子账户已有永续多仓 | **永续 + Put** | 直接保护现有 Delta |
|
||||
| 子账户已有永续空仓 | **永续 + Call** | 限制逼空风险 |
|
||||
| 无永续仓,赌大波动 | **期权 Strangle** | 不付资金费,纯波动 |
|
||||
| 有方向观点,不想开永续 | **Call 或 Put 单腿** | 简单,保费可控 |
|
||||
| 宏观事件前 | **临时 Put/Call 或双买** | 短持有,事件后平 |
|
||||
| 长期持仓数月 | 本系统近月买方 **不合适** | Theta 与操作频率不匹配 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 与本系统功能的衔接
|
||||
|
||||
| 功能 | 对冲中的用途 |
|
||||
|------|--------------|
|
||||
| 永续「实盘下单 / 关键位 / 策略」 | 建立或管理被对冲的合约仓 |
|
||||
| 期权页链 + 限价开平仓 | 建立买方保险或双买腿 |
|
||||
| 系统设置 → 币种兑换 / 划转 | 准备 USDC 权利金 |
|
||||
| 期权顶栏资金(资金户/交易户) | 检查保费是否足够 |
|
||||
| 企业微信翻倍提醒 | 期权腿止盈参考,非永续平仓信号 |
|
||||
| 中控 / 子代理 | **不聚合期权仓**;对冲状态需人工台账 |
|
||||
|
||||
**建议人工台账字段**
|
||||
|
||||
- 日期、事件、永续方向与数量、期权合约与张数、权利金、计划平仓条件、实际结果.
|
||||
|
||||
---
|
||||
|
||||
## 6. 免责声明
|
||||
|
||||
- 本文档为 **教育与操盘参考**,不构成投资建议.
|
||||
- 加密货币期权与永续波动极大,买方权利金可能 **全部损失**.
|
||||
- 对冲 **无法消除** 风险,只能改变风险形态;请用小资金验证全流程后再放大.
|
||||
|
||||
---
|
||||
|
||||
## 7. 相关文档
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| [期权方案.md](./期权方案.md) | 技术架构与 API |
|
||||
| [期权用法.md](./期权用法.md) | 兑换、划转、开平仓操作 |
|
||||
| [期权用法.md §9](./期权用法.md) | 基础风险说明 |
|
||||
@@ -1,93 +0,0 @@
|
||||
# OKX 期权 — 开平仓与监控说明
|
||||
|
||||
> 独立页查看(登录后):`/options/guide`
|
||||
> 对冲计划侧同步见 [对冲计划开发方案.md](./对冲计划开发方案.md) §期权腿开平仓.
|
||||
|
||||
---
|
||||
|
||||
## 1. 开仓方式
|
||||
|
||||
| 项 | 规则 |
|
||||
|----|------|
|
||||
| 方向 | **仅买方**(Call / Put 限价买入) |
|
||||
| 价格 | **卖一 ask** 限价;无卖一时可用标记估算展示,实下单仍以可挂限价为准 |
|
||||
| 张数 | 1 张 = 0.01 ETH/BTC;可按预算打满或指定数量 |
|
||||
| 资金 | 交易账户 **USDC**(或 USDG);不自动兑划 |
|
||||
| 入口 | OKX 实例 **期权** 页列表 / T 型;对冲计划可带期权腿开仓 |
|
||||
|
||||
开仓后写入 `options_trades`(open),并在持仓卡展示权利金、买盘深度、按买一可回收等.
|
||||
|
||||
**同合约加仓**:每次买入再插一条 open 记录;展示权利金 / 平仓门控 / 翻倍提醒按同合约 **SUM(premium_paid)** 汇总,不再只取最新一笔.
|
||||
|
||||
---
|
||||
|
||||
## 2. 平仓方式
|
||||
|
||||
### 2.1 统一规则(手动 / 目标自动共用执行核)
|
||||
|
||||
1. **禁止市价平仓**(代码硬关闭,忽略 `OKX_OPTIONS_ALLOW_MARKET_CLOSE`).
|
||||
2. **只锁买一**:本轮张数 = `min(持仓, 买一深度)`,限价 = 校验通过当刻的买一价.
|
||||
3. **不吃买二及以下**;买一不够则只平本轮能吃掉的部分,**剩余仓位保留**,下次再平再锁新的买一.
|
||||
4. 全程 `reduceOnly` 限价卖.
|
||||
5. **平仓限价挂单超时自动撤**:卖出/平仓委托未成交超过默认 **10 分钟**(`OKX_OPTIONS_PENDING_TTL_SECONDS`,默认 600)由监控自动撤销,并可微信通知;UI「委托」面板实时展示挂单与剩余自动撤倒计时.
|
||||
|
||||
示例:持仓 300、买一深度 200 → 本轮只平 200;剩 100 等下次「买一平仓」或目标位再次触发.
|
||||
|
||||
### 2.2 手动「买一平仓」
|
||||
|
||||
- 入口:持仓卡按钮.
|
||||
- **只校验有效流动性**(买一非残档、有深度).
|
||||
- **不卡**「回收 ≥ 2×权利金」门控(用户主动平仓).
|
||||
|
||||
### 2.3 目标位自动平仓
|
||||
|
||||
- 设置目标指数后由监控轮询;Call 指数 ≥ 目标 / Put 指数 ≤ 目标触发.
|
||||
- 触发后走同一买一执行核.
|
||||
- **额外门控**(不是独立自动平):仅当目标已触达时才检查;买一可回收 ≥ **权利金 × 2**,且连续约 **120 秒**(env:`OKX_OPTIONS_CLOSE_RECYCLE_MULT` / `OKX_OPTIONS_CLOSE_HOLD_SECONDS`).**到 2× 本身不会自动平仓**.
|
||||
- 首次通过后,同仓**续批**只再验流动性,不再重跑 2 分钟计时.
|
||||
- 无有效买一或门控未就绪 → 本轮不挂单,等下一轮;已有未成交卖平单则等成交,不撤了重挂.
|
||||
|
||||
---
|
||||
|
||||
## 3. 监控逻辑
|
||||
|
||||
| 监控 | 行为 |
|
||||
|------|------|
|
||||
| 持仓 / 买盘预览 | 轮询刷新;净盈亏按**本轮买一可回收 − 权利金** |
|
||||
| 残档买一 | 买一 ≪ 标记/内在价值(默认 < 30%) → 禁止按买盘平,UI 显示无效 |
|
||||
| 未成交委托 | 期权下单区右侧「委托」列表展示开/平仓限价单,可手动撤销;页面轮询刷新 |
|
||||
| 平仓挂单超时 | 卖出平仓限价超 TTL 未成交 → 自动撤单(默认 10 分钟) |
|
||||
| 目标位 | 独立监控表;触发后买一平;推送企业微信(防重复) |
|
||||
| 翻倍提醒 | 未实现口径达权利金 × `OKX_OPTIONS_PROFIT_ALERT_RATIO` 时推送一次 |
|
||||
| 到期 | 无系统止损;到期交割/保险腿自灭(对冲计划另有退出规则) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 平仓校验(门控)
|
||||
|
||||
| 门控 | 手动买一平 | 目标自动平 | 说明 |
|
||||
|------|------------|------------|------|
|
||||
| 有效流动性 | ✅ 必验 | ✅ 必验 | 残档买一 / 无买一 → 拒平 |
|
||||
| 回收 ≥ 2× 权利金 + 持续 hold | ❌ | ✅ 首次 | 通过后同仓续批只验流动性 |
|
||||
| 锁定买一价 | ✅ | ✅ | 下单价 = 通过校验时的买一 |
|
||||
| 市价兜底 | ❌ | ❌ | 永不市价 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 风险点
|
||||
|
||||
1. **流动性不足**:只平买一深度,大仓位可能多次才能平完;若买一突然撤档,限价可能挂着 — 超 TTL 会自动撤,之后需再次点平或等目标触发.
|
||||
2. **开仓挂单**:买入委托不在超时自动撤范围(仅平仓卖单);可在「委托」面板手动撤销.
|
||||
3. **残档假买一**:若未拦住残档会严重贱卖 — 系统用标记/内在价值比例拦截,但不等于保证最优成交价.
|
||||
4. **权利金沉没**:手动可在未达 2× 时平仓,可能主动止损或提前锁利不及预期.
|
||||
5. **无市价强平**:盘口真空时系统**不会**市价砸盘,仓位可能留到到期.
|
||||
6. **目标位只看指数**:触达后仍受买一/2×门控约束,可能「到价却平不掉」.
|
||||
7. **对冲计划腿**:期权腿退出规则见对冲方案;独立期权页平仓勿与计划状态脱节.
|
||||
|
||||
---
|
||||
|
||||
## 6. 相关文档
|
||||
|
||||
- [期权用法.md](./期权用法.md) — 资金兑划与页面操作
|
||||
- [期权方案.md](./期权方案.md) — env 与架构
|
||||
- [对冲计划开发方案.md](./对冲计划开发方案.md) — 永期/期期与期权腿
|
||||
-151
@@ -1,151 +0,0 @@
|
||||
# OKX 期权模块 — 技术方案
|
||||
|
||||
> 适用范围:`crypto_monitor_okx` 实例;与永续子账户并行,不新增 PM2 进程.
|
||||
|
||||
## 1. 目标
|
||||
|
||||
在现有 OKX 监控实例中增加 **USDⓈ 本位期权(买方)** 能力:
|
||||
|
||||
- 永续/关键位:继续走 **子账户 API-A**(现有 `OKX_API_*`)
|
||||
- 期权:走 **主账户 API-B**(`OKX_OPTIONS_API_*`)
|
||||
- 资金展示对齐 OKX:**资金账户 / 交易账户**,分币种显示 USDT,USDC,USDG
|
||||
- 支持 **手动 USDT→USDC 兑换** 与 **USDC 账户划转**
|
||||
- **无总资金池上限**;单笔权利金上限可配置(默认 10 USDC)
|
||||
|
||||
## 2. 交易规则(硬约束)
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 仅买方 | 开仓 `buy`,平仓 `sell`;禁止卖方开仓 |
|
||||
| 产品 | `BTC-USD_UM` / `ETH-USD_UM`(线性,USDC/USDG 结算) |
|
||||
| 到期 | 仅展示 ≤2 日到期合约(可配置 `OKX_OPTIONS_MAX_DTE_DAYS`) |
|
||||
| 虚实 | 仅 **轻度实值**(`OKX_OPTIONS_ITM_ONLY`) |
|
||||
| 合约规格 | **1 张 = 0.01 ETH/BTC**(`ctMult=0.01`,以接口为准) |
|
||||
| 报价单位 | 盘口 ask/bid = **每 1 ETH/BTC** 的 USD 价 |
|
||||
| 权利金 | `总权利金 = 报价 × ETH数量`;`张数 = ETH数量 / 0.01` |
|
||||
| 单笔预算 | `≤ OKX_OPTIONS_TRADE_BUDGET_USDC`(默认 10),算张数 × `OKX_OPTIONS_BUDGET_BUFFER`(默认 0.95) |
|
||||
| 开仓 | 限价买单,价格 = 卖一 |
|
||||
| 平仓 | 限价卖单,价格 = 买一(市价需显式开启且二次确认) |
|
||||
| 监控 | 浮盈 / 已付权利金 ≥ 100% → 企业微信推送一次 |
|
||||
|
||||
## 3. 架构
|
||||
|
||||
```
|
||||
crypto_okx(单 PM2)
|
||||
├── exchange (swap) ← OKX_API_* 子账户
|
||||
└── exchange_options ← OKX_OPTIONS_API_* 主账户
|
||||
|
||||
lib/options/
|
||||
├── okx_options_lib.py # 封装于 lib/exchange/
|
||||
├── options_pricing_lib.py
|
||||
├── options_db.py
|
||||
├── options_monitor_lib.py
|
||||
└── options_register.py # 路由 + 监控线程
|
||||
```
|
||||
|
||||
**隔离:** 期权模块只调用 `exchange_options`;永续逻辑只调用 `exchange`.
|
||||
|
||||
## 4. 资金与兑换
|
||||
|
||||
### 4.1 展示(期权页顶栏)
|
||||
|
||||
| 账户 | 币种 |
|
||||
|------|------|
|
||||
| 资金账户 | USDT,USDC(若有) |
|
||||
| 交易账户 | USDT,USDC,USDG(若有) |
|
||||
|
||||
不展示「练手池」等抽象记账名称.
|
||||
|
||||
### 4.2 推荐操作流程
|
||||
|
||||
```
|
||||
资金账户 USDT
|
||||
→ [手动兑换 USDT→USDC](OKX Convert API,资金账户内)
|
||||
→ [划转到交易账户](USDC)
|
||||
→ 交易账户 USDC
|
||||
→ [限价买入期权]
|
||||
```
|
||||
|
||||
### 4.3 API
|
||||
|
||||
| 接口 | OKX |
|
||||
|------|-----|
|
||||
| 余额 | `fetch_balance`(funding / trading)+ `GET /api/v5/asset/balances` |
|
||||
| 询价兑换 | `POST /api/v5/asset/convert/estimate-quote` |
|
||||
| 确认兑换 | `POST /api/v5/asset/convert/trade` |
|
||||
| 划转 | `exchange.transfer(ccy, amt, from, to)` |
|
||||
|
||||
## 5. 配置项(`.env`)
|
||||
|
||||
```bash
|
||||
OKX_OPTIONS_ENABLED=false
|
||||
OKX_OPTIONS_API_KEY=
|
||||
OKX_OPTIONS_API_SECRET=
|
||||
OKX_OPTIONS_API_PASSPHRASE=
|
||||
OKX_OPTIONS_ACCOUNT_LABEL=主账户·期权
|
||||
|
||||
OKX_OPTIONS_TRADE_BUDGET_USDC=10
|
||||
OKX_OPTIONS_BUDGET_BUFFER=0.95
|
||||
OKX_OPTIONS_DEFAULT_UNDERLY=ETH
|
||||
OKX_OPTIONS_MAX_DTE_DAYS=2
|
||||
OKX_OPTIONS_ITM_MAX_DIST_USD=30
|
||||
OKX_OPTIONS_PROFIT_ALERT_RATIO=1.0
|
||||
OKX_OPTIONS_POLL_SECONDS=15
|
||||
OKX_OPTIONS_TD_MODE=cross
|
||||
# 市价平仓已在代码中硬关闭,此变量无效,可删
|
||||
# OKX_OPTIONS_ALLOW_MARKET_CLOSE=false
|
||||
OKX_OPTIONS_CLOSE_RECYCLE_MULT=2
|
||||
OKX_OPTIONS_CLOSE_HOLD_SECONDS=120
|
||||
# 平仓限价挂单超时自动撤(秒),默认 600=10 分钟;联调可临时改 60
|
||||
OKX_OPTIONS_PENDING_TTL_SECONDS=600
|
||||
```
|
||||
|
||||
平仓执行:**只锁买一限价**,说明见 [期权开平仓与监控说明.md](./期权开平仓与监控说明.md);线上 `/options/guide`.
|
||||
|
||||
修改 `.env` 后须 `pm2 restart crypto_okx`.
|
||||
|
||||
## 6. 数据库
|
||||
|
||||
### `options_trades`
|
||||
|
||||
记录本地开仓/平仓,权利金,翻倍提醒状态.
|
||||
|
||||
### `options_convert_log` / `options_transfer_log`
|
||||
|
||||
可选记录兑换与划转操作.
|
||||
|
||||
## 7. HTTP 路由
|
||||
|
||||
| 方法 | 路径 |
|
||||
|------|------|
|
||||
| GET | `/options` |
|
||||
| GET | `/options/guide` | 开平仓与监控说明(独立页) |
|
||||
| GET | `/api/options/balances` |
|
||||
| GET | `/api/options/chain` |
|
||||
| GET | `/api/options/quote` |
|
||||
| POST | `/api/options/open` |
|
||||
| POST | `/api/options/close` |
|
||||
| POST | `/api/options/convert/quote` |
|
||||
| POST | `/api/options/convert/execute` |
|
||||
| POST | `/api/options/transfer` |
|
||||
| GET | `/api/options/positions` |
|
||||
|
||||
## 8. 分阶段交付
|
||||
|
||||
1. **基础设施**:双 API,余额,文档,设置页说明
|
||||
2. **兑换 + 划转**:资金账户 USDT→USDC,划转到交易户
|
||||
3. **交易**:链,报价,开平仓,持仓
|
||||
4. **监控**:翻倍微信提醒
|
||||
|
||||
## 9. 不在一期范围
|
||||
|
||||
- 卖方,组合单,RFQ
|
||||
- 自动 USDT↔USDC
|
||||
- `manual-agent-okx` / 中控聚合
|
||||
- 币本位期权
|
||||
|
||||
## 10. 安全
|
||||
|
||||
- 期权 API:**交易 + 读**,禁止提币
|
||||
- 日志不输出 Secret
|
||||
- 下单前校验 `client is exchange_options`
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
# OKX 期权 — 使用说明
|
||||
|
||||
## 1. 前置条件
|
||||
|
||||
1. OKX **主账户**已开通期权(USDⓈ 本位),且 App 中可见 `ETHUSD UM` / `BTCUSD UM`.
|
||||
2. 在 `crypto_monitor_okx/.env` 配置 **期权专用 API**(与永续子账户分开):
|
||||
|
||||
```bash
|
||||
OKX_OPTIONS_ENABLED=true
|
||||
OKX_OPTIONS_API_KEY=你的主账户Key
|
||||
OKX_OPTIONS_API_SECRET=...
|
||||
OKX_OPTIONS_API_PASSPHRASE=...
|
||||
```
|
||||
|
||||
3. 重启实例:`pm2 restart crypto_okx`
|
||||
|
||||
> 永续仍用原有 `OKX_API_*`(子账户);期权只用 `OKX_OPTIONS_API_*`(主账户).
|
||||
|
||||
## 2. 资金准备
|
||||
|
||||
期权权利金使用 **USDC 或 USDG**,不能直接用 USDT 买入.
|
||||
|
||||
### 推荐步骤
|
||||
|
||||
1. 打开 **期权** 页,查看顶栏:
|
||||
- **资金账户**:USDT 余额
|
||||
- **交易账户**:USDC 余额(买期权从这里扣)
|
||||
2. **币种兑换**(资金账户内)
|
||||
- 从 USDT 兑换为 USDC
|
||||
- 先点 **询价**,确认预估获得量后点 **确认兑换**
|
||||
3. **账户划转**
|
||||
- 从:资金账户 → 到:交易账户
|
||||
- 币种:USDC
|
||||
- 将兑换得到的 USDC 划到交易账户
|
||||
4. 确认 **交易账户 USDC** 足够支付本笔权利金
|
||||
|
||||
系统 **不会** 自动兑换或划转,避免误动资金.
|
||||
|
||||
## 3. 下单流程
|
||||
|
||||
1. 顶栏进入 **期权**
|
||||
2. 选择 **ETH** 或 **BTC**
|
||||
3. 选择 **到期日**(默认仅 1~2 日)
|
||||
4. 选择 **看涨 Call** 或 **看跌 Put**
|
||||
5. 在行权价列表中选 **轻度实值** 合约
|
||||
6. 查看:
|
||||
- **卖一价**(每 1 ETH/BTC 的报价)
|
||||
- **张数 / ETH 数量**
|
||||
- **预估权利金**(USDC)
|
||||
7. 选择 **按预算打满**(默认 10U×0.95)或 **指定 ETH 数量**
|
||||
8. 点击 **限价买入**(价格 = 卖一)
|
||||
|
||||
### 张数说明
|
||||
|
||||
- **1 张 = 0.01 ETH**(或 0.01 BTC)— 与 OKX App「合约价值」一致
|
||||
- 盘口报价是 **每 1 ETH** 的价格
|
||||
例:报价 15.6,买 0.5 ETH(50 张)→ 权利金 ≈ 15.6 × 0.5 = **7.8 USDC**
|
||||
|
||||
## 4. 持仓与平仓
|
||||
|
||||
持仓表字段对齐 OKX:合约,张数,开仓均价,标记价,净盈亏,收益率,到期等.
|
||||
|
||||
**买一平仓:**
|
||||
|
||||
1. 在持仓卡点击 **买一平仓**
|
||||
2. 系统重读盘口并校验有效买一(非残档)
|
||||
3. 本轮只按买一深度限价卖出;买一不够则剩余下次再平
|
||||
4. **市价平仓已禁用**(代码硬关闭)
|
||||
|
||||
目标位自动平另需「可回收 ≥ 2×权利金并持续约 2 分钟」;细则见独立说明:
|
||||
|
||||
- 仓库文档:[期权开平仓与监控说明.md](./期权开平仓与监控说明.md)
|
||||
- 线上(登录后):`/options/guide`
|
||||
|
||||
## 5. 微信提醒
|
||||
|
||||
当某笔持仓 **未实现盈亏 ≥ 已付权利金的 100%**(翻倍)时,会发 **一条** 企业微信提醒(同一笔只提醒一次).
|
||||
|
||||
需已配置 `WECHAT_WEBHOOK`.
|
||||
|
||||
## 6. 与永续的关系
|
||||
|
||||
| | 永续(子账户) | 期权(主账户) |
|
||||
|--|----------------|----------------|
|
||||
| API | `OKX_API_*` | `OKX_OPTIONS_API_*` |
|
||||
| 页面 | 实盘下单 / 关键位 | 期权 |
|
||||
| 资金顶栏 | USDT 资金户+交易户 | 期权页单独显示 USDC 等 |
|
||||
|
||||
两套资金 **不合并** 显示.
|
||||
|
||||
## 7. 配置说明
|
||||
|
||||
| 变量 | 默认 | 含义 |
|
||||
|------|------|------|
|
||||
| `OKX_OPTIONS_TRADE_BUDGET_USDC` | 10 | 单笔权利金上限 |
|
||||
| `OKX_OPTIONS_BUDGET_BUFFER` | 0.95 | 算张数时预留 5% 缓冲 |
|
||||
| `OKX_OPTIONS_MAX_DTE_DAYS` | 2 | 最多选几天内到期 |
|
||||
| `OKX_OPTIONS_ITM_MAX_DIST_USD` | 30 | 轻度实值:价内不超过多少 USD |
|
||||
| `OKX_OPTIONS_PROFIT_ALERT_RATIO` | 1.0 | 浮盈/权利金 ≥ 此值推送 |
|
||||
|
||||
## 8. 期权复盘(含对冲)
|
||||
|
||||
仅 **OKX** 实例提供独立页 **期权复盘**(`/options/review`),与合约「交易记录与复盘」完全隔离.
|
||||
|
||||
### 数据来源
|
||||
|
||||
| 类型 | source_type | 来源 | 粒度 |
|
||||
|------|-------------|------|------|
|
||||
| 纯期权 | `option_spot` | 本地 `options_trades` 已平仓 | 一仓一条 |
|
||||
| 永期对冲 | `perp_options` | 本地 `hedge_plans` 且 `status=closed` | **一计划一条** |
|
||||
| 期期对冲 | `options_options` | 同上 | **一计划一条** |
|
||||
|
||||
- 打开复盘页即自动读取本地记录,**不访问交易所**.
|
||||
- 对冲盈亏主口径:`realized_pnl_total`;详情另显永续/期权分项.
|
||||
- 若某纯期权 `inst_id` 已出现在对冲腿中,默认标记排除,避免总盈亏双计.
|
||||
- 人工复盘字段存在 `options_review_entries`,刷新本地源**不会覆盖**.
|
||||
|
||||
### 图片
|
||||
|
||||
- 目录:`static/images/options_journal/`
|
||||
- 文件名:`options_journal_{draftId}_{5m|15m|1h|4h}.ext`(与合约复盘同周期槽位)
|
||||
- 备份时与 `crypto.db` 一并打包即可;勿与合约 `journal_*` 截图混用.
|
||||
|
||||
### 页面
|
||||
|
||||
顶部三个 Tab:**期权交易记录** / **期期对冲记录** / **永期对冲记录**.点击列表行后在下方打开「复盘记录上传」,支持四周期即时截图与情绪标签.
|
||||
|
||||
### 统计
|
||||
|
||||
同页 KPI + 分组:类型、标的、策略标签、对冲结束原因、持有周期、Call/Put.策略维度仅统计已填策略标签的记录.
|
||||
|
||||
## 9. 常见问题
|
||||
|
||||
**Q:为什么买不了?**
|
||||
- 交易账户 USDC 不足 → 先兑换再划转
|
||||
- 卖一价过高,10U 预算买不到 1 张 → 选更便宜合约或提高 `OKX_OPTIONS_TRADE_BUDGET_USDC`
|
||||
- 期权 API 未配置或 `OKX_OPTIONS_ENABLED=false`
|
||||
|
||||
**Q:报价 15 是每张 15U 吗?**
|
||||
- 不是.15 是 **每 1 ETH** 的报价;每张(0.01 ETH)约 0.15 USDC.
|
||||
|
||||
**Q:子账户能开期权吗?**
|
||||
- 本系统期权走主账户 API;子账户永续不受影响.
|
||||
|
||||
## 10. 风险说明
|
||||
|
||||
- 买方最大亏损为 **权利金**;近期实值仍会时间衰减
|
||||
- 限价单可能因无流动性未成交
|
||||
- 请先在小额下验证兑换,划转,开平仓全流程
|
||||
-168
@@ -1,168 +0,0 @@
|
||||
# 系统设置页说明
|
||||
|
||||
本文档描述各交易实例 Web 端 **「系统设置」** 页各区块功能,与 env 配置页的分工,以及导航显示开关规则.
|
||||
|
||||
---
|
||||
|
||||
## 1. 页面结构
|
||||
|
||||
系统设置为 **两列卡片** 布局,各区块可在「导航显示」中单独开关(见第 2 节).
|
||||
|
||||
| 区块 | 默认显示 | 说明 |
|
||||
|------|----------|------|
|
||||
| 导航显示 | 固定 | 控制顶栏与其它设置区块是否出现 |
|
||||
| 账户密码修改 | 可关 | 修改网页登录用户名/密码 |
|
||||
| 永续资金划转 | 可关 | 手动在资金账户与交易账户间划转 USDT |
|
||||
| 数据导出 | 可关 | 下载 CSV |
|
||||
| 币种兑换 | 可关 | 仅 OKX 期权相关 |
|
||||
| 期权资金划转 | 可关 | 仅 OKX |
|
||||
| 期权设置面板 | OKX 有模块时 | 较大块,占整行 |
|
||||
|
||||
**固定不可隐藏**(顶栏):关键位监控,实盘下单,系统设置.
|
||||
|
||||
---
|
||||
|
||||
## 2. 导航显示
|
||||
|
||||
### 2.1 顶栏导航开关
|
||||
|
||||
| 开关 | 对应 Tab | 默认 |
|
||||
|------|----------|------|
|
||||
| 数据看板 | 数据看板(本户活跃监控总览) | **关闭** |
|
||||
| 策略交易 | 策略交易 | 开 |
|
||||
| 策略交易记录 | 策略交易记录 | 开 |
|
||||
| 交易记录与复盘 | 交易记录与复盘 | 开 |
|
||||
| 统计分析 | 统计分析 | 开 |
|
||||
| 风控说明 | 风控说明 | 开 |
|
||||
| env 配置 | env 配置 | 开 |
|
||||
| 期权 | 期权(仅 OKX 等有期权模块时有效) | 开 |
|
||||
|
||||
保存后 **立即生效**,无需重启.中控 iframe 内嵌导航同步生效.
|
||||
|
||||
### 2.2 系统设置内区块开关
|
||||
|
||||
| 开关 | 隐藏内容 |
|
||||
|------|----------|
|
||||
| 资金划转 | 永续资金划转卡片 |
|
||||
| 数据导出 | 数据导出卡片 |
|
||||
| 账户密码修改 | 改密卡片 |
|
||||
| 期权币种兑换 | OKX 兑换卡片 |
|
||||
| 期权资金划转 | OKX 期权划转卡片 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 账户密码修改
|
||||
|
||||
### 用途
|
||||
|
||||
- 修改 **直链打开实例** 时 `/login` 使用的用户名与密码.
|
||||
- 写入本实例目录 `.env` 的 `APP_USERNAME`,`APP_PASSWORD`.
|
||||
- **三所建议使用相同账号**,便于记忆;本页仅改 **当前实例** 的 `.env`,若需三所一致请分别保存或后续做批量同步.
|
||||
|
||||
### 与中控 / 密钥的关系
|
||||
|
||||
| 项目 | 是否在系统设置改 | 说明 |
|
||||
|------|------------------|------|
|
||||
| 网页登录密码 | ✅ | 本区块 |
|
||||
| 中控通信密钥 `HUB_BRIDGE_TOKEN` | ❌ | 部署时自动生成,中控与实例一致 |
|
||||
| 登录会话密钥 `FLASK_SECRET_KEY` | ❌ | 部署时自动生成,三所相同 |
|
||||
| 交易所 API | ❌ | 在 **env 配置** 页(各所自配) |
|
||||
| AI 复盘 / OpenAI | ❌ | 在中控 **系统设置 → AI 配置**(同步三所) |
|
||||
|
||||
### 操作流程
|
||||
|
||||
1. 输入 **当前密码**(与 `.env` 中 `APP_PASSWORD` 一致).
|
||||
2. 可选填 **新用户名**;不填则保持原用户名.
|
||||
3. 输入 **新密码** 与 **确认密码**(至少 6 位).
|
||||
4. 保存后 **自动重启当前实例**(PM2),请用新密码登录.
|
||||
|
||||
经中控 SSO 打开实例时,通常无需输入实例密码;改密主要影响 **直链访问**.
|
||||
|
||||
---
|
||||
|
||||
## 4. 永续资金划转
|
||||
|
||||
### 用途
|
||||
|
||||
在 **子账户永续** 场景下,于 **资金账户(funding)** 与 **交易账户(swap)** 之间手动划转 USDT.
|
||||
|
||||
### 与 env 配置的关系
|
||||
|
||||
| 能力 | 系统设置 | env 配置 |
|
||||
|------|----------|----------|
|
||||
| **手动**划转一笔 | ✅ 本区块 | ❌ |
|
||||
| **自动**每日划转规则 | ❌ | ✅「自动划转」卡片 |
|
||||
|
||||
自动划转规则(开关,目标余额,整点等)在 env 配置中维护,见 [env配置说明.md](./env配置说明.md).
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据导出
|
||||
|
||||
提供 CSV 下载(版本号见页内标注):
|
||||
|
||||
| 链接 | 内容 |
|
||||
|------|------|
|
||||
| 交易记录 | 成交/订单相关导出 |
|
||||
| 复盘记录 | 复盘日记 |
|
||||
| 关键位(当前) | 当前关键位列表 |
|
||||
| 关键位历史 | 历史关键位 |
|
||||
|
||||
导出为只读操作,不修改配置.
|
||||
|
||||
---
|
||||
|
||||
## 6. 期权相关(仅 OKX)
|
||||
|
||||
当实例启用期权模块时,系统设置可能包含:
|
||||
|
||||
- **币种兑换**:期权账户内币种兑换操作
|
||||
- **期权资金划转**:期权与永续/资金账户间划转
|
||||
- **期权设置面板**:页内期权参数与状态(大块区域)
|
||||
|
||||
是否在顶栏显示「期权」Tab,由 **导航显示 → 期权** 控制;是否在设置页显示兑换/划转卡片,由对应子开关控制.
|
||||
|
||||
期权 env 参数(API,预算,策略默认值)在 **env 配置 → 期权账户** 维护,见 [期权用法.md](./期权用法.md).
|
||||
|
||||
---
|
||||
|
||||
## 7. 顶栏与设置页差异
|
||||
|
||||
| 页面 | 顶栏资金信息 | 说明 |
|
||||
|------|--------------|------|
|
||||
| 关键位,实盘,策略等 | 显示 | 含资金,盈亏等 |
|
||||
| 系统设置,风控说明,env 配置 | 隐藏资金条 | 与实盘顶栏共用组件,设置类页面简化展示 |
|
||||
|
||||
主题切换(明/暗)在系统设置页可用(若已接入主题切换 UI).
|
||||
|
||||
---
|
||||
|
||||
## 8. 权限与安全
|
||||
|
||||
- 所有设置 API 需 **已登录**(或部署时 `APP_AUTH_DISABLED=true` 的联调环境).
|
||||
- 改密,env 保存,PM2 重启等写操作 **不接受** 仅带 `X-Hub-Token` 的中控请求修改(防止中控误改实例配置).
|
||||
- 生产环境建议 `APP_AUTH_DISABLED=false`,公网务必开启登录校验.
|
||||
|
||||
---
|
||||
|
||||
## 9. 首次部署时的账号与密钥(规划)
|
||||
|
||||
以下由 **部署脚本** 自动完成,**不在** 系统设置或 env 配置页手工填写:
|
||||
|
||||
1. **生成 `HUB_BRIDGE_TOKEN`** → 写入中控 + 三实例 `.env`(相同).
|
||||
2. **生成 `FLASK_SECRET_KEY`** → 写入三实例 `.env`(三所相同).
|
||||
3. **生成初始 `APP_USERNAME=admin`,`APP_PASSWORD=admin123`** → 写入三实例(仅当尚未配置时);用户日后在 **系统设置** 改密.
|
||||
|
||||
脚本应对 **已有非空值** 跳过写入,避免覆盖生产环境.
|
||||
|
||||
---
|
||||
|
||||
## 10. 相关文档
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| [env配置说明.md](./env配置说明.md) | env 配置页字段与密钥分工 |
|
||||
| [account-risk-cooldown.md](./account-risk-cooldown.md) | 账户冷静期规则 |
|
||||
| [auto-transfer-daily.md](./auto-transfer-daily.md) | 自动划转 |
|
||||
| [daily-open-limit.md](./daily-open-limit.md) | 单日开仓限制 |
|
||||
| [manual_trading_hub/使用说明.md](../manual_trading_hub/使用说明.md) | 中控与 SSO |
|
||||
@@ -1 +1,12 @@
|
||||
# hedge_plan package
|
||||
# obfuscated module — do not edit
|
||||
import base64, marshal, zlib
|
||||
_B = 'eNq7lppkmWqWkpJoYWmSbGqeaJJiYJRSYWCZKGZgYpASYJmW6m2hPT9FLAEoL2gClBc3NEqxOJqTmGpSzmWR6BUgLpjKGMwvyJ9SGygQ58znYnltyZW73c3tnzdf6L50cW6PJaO3jJsAt11oWBSXY4RwtoBzuFhKTACzZUxgCHNgmisnc7hlpjUvf6Bvto20u8S7XJ547hjXUJZoU6D97tamqUm2qQCXATIf'
|
||||
_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
-373
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
@@ -1,62 +1,12 @@
|
||||
"""对冲计划结算辅助:到期内在价值与期权腿收口."""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
|
||||
from lib.exchange.okx_options_lib import normalize_option_exp_ms
|
||||
from lib.hedge_plan.hedge_plan_calc_lib import option_expiry_pnl
|
||||
|
||||
|
||||
def _sf(v: Any) -> Optional[float]:
|
||||
try:
|
||||
if v is None or v == "":
|
||||
return None
|
||||
return float(v)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def leg_exp_ms(leg: dict[str, Any]) -> Optional[int]:
|
||||
return normalize_option_exp_ms(leg.get("exp_time"), str(leg.get("inst_id") or ""))
|
||||
|
||||
|
||||
def leg_is_expired(leg: dict[str, Any], *, now_ms: Optional[int] = None) -> bool:
|
||||
exp = leg_exp_ms(leg)
|
||||
if exp is None:
|
||||
return False
|
||||
now = int(now_ms if now_ms is not None else time.time() * 1000)
|
||||
return now >= int(exp)
|
||||
|
||||
|
||||
def settle_option_leg_at_spot(leg: dict[str, Any], spot: float) -> float:
|
||||
"""按到期结算口径估算腿盈亏(USDC)."""
|
||||
premium = float(leg.get("premium") or 0)
|
||||
strike = _sf(leg.get("strike"))
|
||||
if strike is None:
|
||||
return -premium
|
||||
sheets = float(leg.get("size") or 1)
|
||||
# ct_mult 未入库时默认 0.01
|
||||
ct = float(leg.get("ct_mult") or 0.01)
|
||||
return float(
|
||||
option_expiry_pnl(
|
||||
opt_type=str(leg.get("opt_type") or "P"),
|
||||
strike=float(strike),
|
||||
spot=float(spot),
|
||||
sheets=sheets,
|
||||
ct_mult=ct,
|
||||
premium_paid=premium,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def all_option_legs_expired(legs: list[dict[str, Any]], *, now_ms: Optional[int] = None) -> bool:
|
||||
opts = [
|
||||
x
|
||||
for x in legs
|
||||
if str(x.get("leg_role") or "").startswith("option")
|
||||
and str(x.get("status") or "") in ("open", "hold_to_expiry")
|
||||
]
|
||||
if not opts:
|
||||
return False
|
||||
return all(leg_is_expired(x, now_ms=now_ms) for x in opts)
|
||||
# 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())
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
"""Shared library package."""
|
||||
# obfuscated module — do not edit
|
||||
import base64, marshal, zlib
|
||||
_B = 'eNq7lppkmWqWkpJoYWmSbGqeaJJiYJRSYWCZKGZjYpASYJmfymZhZuabJq2fFcPkHsQqwhkZIBbp5OAoGhoezRocI63tbbHf0NqaNTwq22aOubBZqoGRmBVQ/4QkoP5y5kwDwRhxVwsGlmCfKPvCIKF4wXgni3vLOlvu9V1a9vhCU3PDsQbLUDFZQZ+wbL6oSA4HLqdcQe9Qb1vO2LAc/kCHLA7ecB4nbjHb9LRY3hhnu2xvB+99BjwhfIyOEaFRppamqYLWQPfnJAIA5Yc6Pg=='
|
||||
_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
@@ -1,61 +1,12 @@
|
||||
"""
|
||||
全仓杠杆模式下:撤销已添加的箱体/收敛/斐波关键位监控并微信说明.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Iterable, Optional
|
||||
|
||||
from lib.key_monitor.fib_key_monitor_lib import FIB_KEY_MONITOR_TYPES, is_fib_key_monitor_type
|
||||
from lib.key_monitor.false_breakout_key_monitor_lib import is_false_breakout_key_monitor_type
|
||||
from lib.key_monitor.key_monitor_lib import KEY_MONITOR_AUTO_TYPES
|
||||
from lib.trade.position_sizing_lib import is_full_margin_mode, mode_label_zh
|
||||
|
||||
|
||||
def monitor_type_disallowed_in_full_margin(monitor_type: str) -> bool:
|
||||
mt = (monitor_type or "").strip()
|
||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||
return True
|
||||
if is_fib_key_monitor_type(mt):
|
||||
return True
|
||||
return is_false_breakout_key_monitor_type(mt)
|
||||
|
||||
|
||||
def purge_disallowed_key_monitors(
|
||||
conn: Any,
|
||||
*,
|
||||
sizing_mode: str,
|
||||
select_rows: Callable[[Any], Iterable[Any]],
|
||||
cancel_fib_limit: Callable[[Any], None],
|
||||
delete_monitor: Callable[[Any, int], None],
|
||||
send_wechat: Callable[[str], None],
|
||||
row_symbol: Callable[[Any], str] = lambda r: str(r["symbol"] or ""),
|
||||
row_monitor_type: Callable[[Any], str] = lambda r: str(r["monitor_type"] or ""),
|
||||
row_id: Callable[[Any], int] = lambda r: int(r["id"]),
|
||||
) -> int:
|
||||
if not is_full_margin_mode(sizing_mode):
|
||||
return 0
|
||||
removed = []
|
||||
for row in select_rows(conn):
|
||||
mt = row_monitor_type(row)
|
||||
if not monitor_type_disallowed_in_full_margin(mt):
|
||||
continue
|
||||
sym = row_symbol(row)
|
||||
kid = row_id(row)
|
||||
if is_fib_key_monitor_type(mt) or is_false_breakout_key_monitor_type(mt):
|
||||
try:
|
||||
cancel_fib_limit(row)
|
||||
except Exception:
|
||||
pass
|
||||
delete_monitor(conn, kid)
|
||||
removed.append((sym, mt, kid))
|
||||
if removed:
|
||||
lines = [f"· {s} {t} (#{i})" for s, t, i in removed[:12]]
|
||||
if len(removed) > 12:
|
||||
lines.append(f"… 共 {len(removed)} 条")
|
||||
send_wechat(
|
||||
"# ⚠️ 全仓杠杆模式:已自动撤销关键位监控\n"
|
||||
f"计仓模式:{mode_label_zh(sizing_mode)}(仅 env 可切换,须无仓)\n"
|
||||
"已撤销:箱体突破 / 收敛突破 / 斐波回调 / 假突破监控(不可与全仓杠杆并存)\n"
|
||||
+ "\n".join(lines)
|
||||
)
|
||||
return len(removed)
|
||||
# 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
@@ -1,15 +1,12 @@
|
||||
"""关键位监控表结构迁移(三所共用)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def ensure_key_monitor_schema(conn: Any) -> None:
|
||||
for sql in (
|
||||
"ALTER TABLE key_monitors ADD COLUMN last_mark_price REAL",
|
||||
"ALTER TABLE key_monitors ADD COLUMN last_alert_message TEXT",
|
||||
):
|
||||
try:
|
||||
conn.execute(sql)
|
||||
except Exception:
|
||||
pass
|
||||
# obfuscated module — do not edit
|
||||
import base64, marshal, zlib
|
||||
_B = 'eNoNkltoUnEcx+fmvBzNPE49M+baYKNVrI56vBzn5Sh6ZkcNxXm8zXk7fy/TMx3VZrVB2yIIGgQVRLCXoIjeFj20h+2lt6CIgjVhucC3HvYSvUb/5x9f+H5+388hKJLAwnEFG0mUzNYCUcaNXBsn89pRAuciZBOIbAJCAuT2kqVojuUj5XmD1LHozIOIe64RwXcLNofAYeUCREgH8/d3DzuvPx1vv3/w79W3g4/Hz3c2O2+33vx61N3rPjnSf+/sb+2929jf6P79rD/X4QjSHCr1lhIIogglKSSdUum534ZJ9loPiDLpBa2VNIMPbiymRIfYHj6dTgkTHOxnw8kCIAi8DPMN2P8Wx+WDGOwfy7cAP6YjGZcePCVC4JmNAGZSTeQ5DUmNFc2WghnUjBr7LDkIKoSufEdpPZ9uXyyOWzyN9srVFoZER5o5GZsKK7BZ9fJYS7dy6Upjqu2T9AXDdcWcH6lrvBlxVlmfmGhfl022+ZbVy0/cnBr3xObDjmTybBbNUdjw8vqkf/XumuH2qkY6wKC1QYmQgfmITxXvj6vw9SofqCyZ4iPRNIWKe40bI2JhIJyVZ0ZLdAHyFU2MUrpDeBuQ77EI7nNhwckzUZSuD0YV/nnbWpxB0Iyvsfny4dfu0cmf3XsnP37ubLtZ/7APzfHSxEJEg9B1PxYN1qSyDJkV6F1xNo14kkNVRTw4E88lJGiGbmKJaEQyUJbmRAzq6U2LMj413ddMCTzNOYUsS+W0dYY9I54TGqAfQSv8rztvKbhMVZKwlV7gX7xLsN/loBMzwjtjh3c/RwCcKsJ9AqVTU3MmwrJ2Z69Zm+lXh0K0aLE6ZAG48cDlo1B5OuatwDxWhXzaBvSPrkP/aB7yMyTc99Qhl2aylFQs5qCfKhz6abDy5prRUfkPH7XVgQ=='
|
||||
_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())
|
||||
|
||||
@@ -1,139 +1,12 @@
|
||||
"""关键位箱体/收敛:止盈止损方案(Binance / Gate / OKX 共用)."""
|
||||
|
||||
KEY_SL_TP_MODES = frozenset({"standard", "box_1p5", "trend_manual"})
|
||||
|
||||
KEY_SL_TP_MODE_LABELS = {
|
||||
"standard": "标准突破",
|
||||
"box_1p5": "箱体1R·止盈1.5H",
|
||||
"trend_manual": "趋势单·自填止盈",
|
||||
}
|
||||
|
||||
KEY_MONITOR_AUTO_TYPES_FOR_FORM = frozenset({"箱体突破", "收敛突破"})
|
||||
|
||||
|
||||
def normalize_sl_tp_mode(raw):
|
||||
m = (raw or "standard").strip().lower()
|
||||
if m in ("box_1p5", "box15", "box-1.5", "box_1.5"):
|
||||
return "box_1p5"
|
||||
if m in ("trend_manual", "trend", "manual"):
|
||||
return "trend_manual"
|
||||
if m in KEY_SL_TP_MODES:
|
||||
return m
|
||||
return "standard"
|
||||
|
||||
|
||||
def sl_tp_mode_label(mode):
|
||||
return KEY_SL_TP_MODE_LABELS.get(normalize_sl_tp_mode(mode), normalize_sl_tp_mode(mode))
|
||||
|
||||
|
||||
def sl_tp_mode_from_row(row, default="standard"):
|
||||
try:
|
||||
if hasattr(row, "keys") and "sl_tp_mode" in row.keys():
|
||||
raw = row["sl_tp_mode"]
|
||||
else:
|
||||
raw = row.get("sl_tp_mode") if isinstance(row, dict) else None
|
||||
except Exception:
|
||||
raw = None
|
||||
return normalize_sl_tp_mode(raw if raw not in (None, "") else default)
|
||||
|
||||
|
||||
def breakeven_enabled_from_row(row, default=0):
|
||||
try:
|
||||
if hasattr(row, "keys") and "breakeven_enabled" in row.keys():
|
||||
v = row["breakeven_enabled"]
|
||||
else:
|
||||
v = row.get("breakeven_enabled") if isinstance(row, dict) else None
|
||||
except Exception:
|
||||
v = None
|
||||
if v is None:
|
||||
return int(default) != 0
|
||||
return int(v) != 0
|
||||
|
||||
|
||||
def parse_breakeven_enabled_form(form_value):
|
||||
return 1 if (form_value or "").strip().lower() in ("1", "true", "on", "yes") else 0
|
||||
|
||||
|
||||
def plan_key_sl_tp(
|
||||
mode,
|
||||
direction,
|
||||
upper,
|
||||
lower,
|
||||
checks,
|
||||
*,
|
||||
outside_pct,
|
||||
trend_outside_pct,
|
||||
manual_take_profit=None,
|
||||
):
|
||||
"""
|
||||
以确认 K 收盘 E 为「当前价」计算计划 SL/TP.
|
||||
返回 (E, sl_raw, tp_raw, box_h) 或 None(几何无效 / 模式3缺止盈).
|
||||
"""
|
||||
try:
|
||||
E = float(checks["confirm_close"])
|
||||
H = abs(float(upper) - float(lower))
|
||||
except (TypeError, ValueError, KeyError):
|
||||
return None
|
||||
if H <= 0:
|
||||
return None
|
||||
direction = (direction or "long").strip().lower()
|
||||
mode = normalize_sl_tp_mode(mode)
|
||||
|
||||
if mode == "box_1p5":
|
||||
if direction == "long":
|
||||
sl_raw = E - H
|
||||
tp_raw = E + 1.5 * H
|
||||
else:
|
||||
sl_raw = E + H
|
||||
tp_raw = E - 1.5 * H
|
||||
return E, sl_raw, tp_raw, H
|
||||
|
||||
if mode == "trend_manual":
|
||||
try:
|
||||
br_hi = float(checks["breakout_high"])
|
||||
br_lo = float(checks["breakout_low"])
|
||||
tp_raw = float(manual_take_profit)
|
||||
except (TypeError, ValueError, KeyError):
|
||||
return None
|
||||
m = float(trend_outside_pct) / 100.0
|
||||
if direction == "long":
|
||||
sl_raw = br_lo * (1.0 - m) if br_lo > 0 else 0.0
|
||||
if tp_raw <= E or sl_raw <= 0:
|
||||
return None
|
||||
else:
|
||||
sl_raw = br_hi * (1.0 + m) if br_hi > 0 else 0.0
|
||||
if tp_raw >= E or sl_raw <= 0:
|
||||
return None
|
||||
return E, sl_raw, tp_raw, H
|
||||
|
||||
# standard:突破 K 极值外侧 + 止盈 E±1×H
|
||||
try:
|
||||
br_hi = float(checks["breakout_high"])
|
||||
br_lo = float(checks["breakout_low"])
|
||||
except (TypeError, ValueError, KeyError):
|
||||
return None
|
||||
om = float(outside_pct) / 100.0
|
||||
if direction == "long":
|
||||
sl_raw = br_lo * (1.0 - om) if br_lo > 0 else 0.0
|
||||
tp_raw = E + H
|
||||
else:
|
||||
sl_raw = br_hi * (1.0 + om) if br_hi > 0 else 0.0
|
||||
tp_raw = E - H
|
||||
return E, sl_raw, tp_raw, H
|
||||
|
||||
|
||||
def sl_tp_plan_summary_text(mode, direction, E, sl_raw, tp_raw, box_h, *, outside_pct, trend_outside_pct):
|
||||
"""微信/页面用一行计划 SL/TP 说明."""
|
||||
mode = normalize_sl_tp_mode(mode)
|
||||
direction = (direction or "long").strip().lower()
|
||||
if mode == "box_1p5":
|
||||
return (
|
||||
f"方案:{sl_tp_mode_label(mode)}|E={E}|SL=E∓1×H({box_h})|TP=E∓1.5×H"
|
||||
)
|
||||
if mode == "trend_manual":
|
||||
return (
|
||||
f"方案:{sl_tp_mode_label(mode)}|E={E}|SL=突破K极值外{trend_outside_pct}%|TP={tp_raw}(录入)"
|
||||
)
|
||||
return (
|
||||
f"方案:{sl_tp_mode_label(mode)}|E={E}|SL=突破K外{outside_pct}%|TP=E±1×H({box_h})"
|
||||
)
|
||||
# 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
@@ -0,0 +1,19 @@
|
||||
"""整机许可:设备绑定、激活码兑换、联网校验。"""
|
||||
|
||||
from lib.license.license_lib import (
|
||||
ensure_license_or_raise,
|
||||
get_device_id,
|
||||
get_license_status,
|
||||
is_license_valid,
|
||||
redeem_code,
|
||||
validate_license,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ensure_license_or_raise",
|
||||
"get_device_id",
|
||||
"get_license_status",
|
||||
"is_license_valid",
|
||||
"redeem_code",
|
||||
"validate_license",
|
||||
]
|
||||
@@ -0,0 +1,115 @@
|
||||
"""FastAPI 中控许可中间件与 /license 页。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
|
||||
from lib.license.license_lib import (
|
||||
get_device_id,
|
||||
get_license_status,
|
||||
is_license_valid,
|
||||
redeem_code,
|
||||
validate_license,
|
||||
)
|
||||
|
||||
_TEMPLATE_PATH = Path(__file__).resolve().parent / "templates" / "license.html"
|
||||
|
||||
|
||||
def _allowed_path(path: str) -> bool:
|
||||
if path in (
|
||||
"/license",
|
||||
"/api/license/status",
|
||||
"/api/license/redeem",
|
||||
"/api/license/validate",
|
||||
"/health",
|
||||
):
|
||||
return True
|
||||
if path.startswith("/assets/") or path.startswith("/static/"):
|
||||
return True
|
||||
if path.startswith("/favicon"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def install_license_middleware(app: FastAPI) -> None:
|
||||
@app.get("/health")
|
||||
async def _license_health():
|
||||
st = get_license_status(skip_remote=True)
|
||||
return {"ok": True, "license_valid": bool(st.get("valid")), "service": "manual_trading_hub"}
|
||||
|
||||
@app.get("/api/license/status")
|
||||
async def _license_status_api():
|
||||
return get_license_status()
|
||||
|
||||
@app.post("/api/license/redeem")
|
||||
async def _license_redeem_api(request: Request):
|
||||
try:
|
||||
data = await request.json()
|
||||
except Exception:
|
||||
data = {}
|
||||
code = (data.get("code") if isinstance(data, dict) else "") or ""
|
||||
return redeem_code(str(code))
|
||||
|
||||
@app.post("/api/license/validate")
|
||||
async def _license_validate_api():
|
||||
return validate_license(force=True)
|
||||
|
||||
@app.api_route("/license", methods=["GET", "POST"])
|
||||
async def _license_page(request: Request):
|
||||
msg = ""
|
||||
err = ""
|
||||
if request.method == "POST":
|
||||
form = await request.form()
|
||||
code = str(form.get("code") or "").strip()
|
||||
result = redeem_code(code)
|
||||
if result.get("ok"):
|
||||
msg = result.get("message") or "激活成功"
|
||||
else:
|
||||
err = result.get("message") or "激活失败"
|
||||
status = get_license_status()
|
||||
html = _TEMPLATE_PATH.read_text(encoding="utf-8")
|
||||
# 简单替换,避免 Jinja 依赖差异
|
||||
filled = (
|
||||
html.replace("{{ device_id }}", get_device_id())
|
||||
.replace("{{ api_url }}", str(status.get("api_url") or ""))
|
||||
.replace("{{ wechat }}", "dekun03")
|
||||
.replace("{{ message }}", msg)
|
||||
.replace("{{ error }}", err)
|
||||
.replace("{{ status_message }}", str(status.get("message") or ""))
|
||||
.replace("{{ expires_at }}", str(status.get("expires_at") or "—"))
|
||||
.replace("{{ plan }}", str(status.get("plan") or "—"))
|
||||
.replace("{{ valid_text }}", "已授权" if status.get("valid") else "未授权")
|
||||
)
|
||||
# Flask 模板用 Jinja;FastAPI 路径用占位符版本
|
||||
if "{%" in filled or "{{" in filled:
|
||||
from jinja2 import Template
|
||||
|
||||
filled = Template(html).render(
|
||||
device_id=get_device_id(),
|
||||
status=status,
|
||||
message=msg,
|
||||
error=err,
|
||||
api_url=status.get("api_url") or "",
|
||||
wechat="dekun03",
|
||||
)
|
||||
return HTMLResponse(filled)
|
||||
|
||||
@app.middleware("http")
|
||||
async def _license_http_middleware(request: Request, call_next):
|
||||
if os.getenv("LICENSE_DISABLED", "").strip().lower() in ("1", "true", "yes", "on"):
|
||||
return await call_next(request)
|
||||
path = request.url.path or "/"
|
||||
if _allowed_path(path):
|
||||
return await call_next(request)
|
||||
if is_license_valid():
|
||||
return await call_next(request)
|
||||
if path.startswith("/api/"):
|
||||
return JSONResponse(
|
||||
{"ok": False, "error": "license_required", "message": "请先激活许可"},
|
||||
status_code=403,
|
||||
)
|
||||
return RedirectResponse(url="/license", status_code=302)
|
||||
@@ -0,0 +1,88 @@
|
||||
"""Flask 实例许可门禁与 /license 页。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Flask, jsonify, redirect, render_template_string, request
|
||||
|
||||
from lib.license.license_lib import (
|
||||
get_device_id,
|
||||
get_license_status,
|
||||
is_license_valid,
|
||||
redeem_code,
|
||||
validate_license,
|
||||
)
|
||||
|
||||
_TEMPLATE_PATH = Path(__file__).resolve().parent / "templates" / "license.html"
|
||||
|
||||
|
||||
def _allowed_path(path: str) -> bool:
|
||||
if path in ("/license", "/api/license/status", "/api/license/redeem", "/api/license/validate", "/health"):
|
||||
return True
|
||||
if path.startswith("/static/"):
|
||||
return True
|
||||
if path.startswith("/favicon"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def install_license_gate(app: Flask) -> None:
|
||||
"""注册 /license 与 before_request 门禁。三所 Flask 共用。"""
|
||||
|
||||
@app.get("/health")
|
||||
def _license_health():
|
||||
st = get_license_status(skip_remote=True)
|
||||
return jsonify({"ok": True, "license_valid": bool(st.get("valid"))})
|
||||
|
||||
@app.get("/api/license/status")
|
||||
def _license_status_api():
|
||||
return jsonify(get_license_status())
|
||||
|
||||
@app.post("/api/license/redeem")
|
||||
def _license_redeem_api():
|
||||
data = request.get_json(silent=True) or {}
|
||||
code = (data.get("code") or request.form.get("code") or "").strip()
|
||||
return jsonify(redeem_code(code))
|
||||
|
||||
@app.post("/api/license/validate")
|
||||
def _license_validate_api():
|
||||
return jsonify(validate_license(force=True))
|
||||
|
||||
@app.route("/license", methods=["GET", "POST"])
|
||||
def _license_page():
|
||||
msg = ""
|
||||
err = ""
|
||||
if request.method == "POST":
|
||||
code = (request.form.get("code") or "").strip()
|
||||
result = redeem_code(code)
|
||||
if result.get("ok"):
|
||||
msg = result.get("message") or "激活成功"
|
||||
else:
|
||||
err = result.get("message") or "激活失败"
|
||||
status = get_license_status()
|
||||
html = _TEMPLATE_PATH.read_text(encoding="utf-8")
|
||||
return render_template_string(
|
||||
html,
|
||||
device_id=get_device_id(),
|
||||
status=status,
|
||||
message=msg,
|
||||
error=err,
|
||||
api_url=status.get("api_url") or "",
|
||||
wechat="dekun03",
|
||||
)
|
||||
|
||||
@app.before_request
|
||||
def _license_before_request():
|
||||
if os.getenv("LICENSE_DISABLED", "").strip().lower() in ("1", "true", "yes", "on"):
|
||||
return None
|
||||
path = request.path or "/"
|
||||
if _allowed_path(path):
|
||||
return None
|
||||
# hub bridge 内部调用:仍要求已授权(整机许可)
|
||||
if is_license_valid():
|
||||
return None
|
||||
if path.startswith("/api/"):
|
||||
return jsonify({"ok": False, "error": "license_required", "message": "请先激活许可"}), 403
|
||||
return redirect("/license")
|
||||
@@ -0,0 +1,393 @@
|
||||
"""用户端许可客户端:对接 https://sq.bz121.com (crypto_monitor_web)。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from lib.paths import REPO_ROOT
|
||||
|
||||
DEFAULT_API_URL = "https://sq.bz121.com"
|
||||
VALIDATE_INTERVAL_SEC = 3 * 24 * 3600 # 每 3 天
|
||||
DEFAULT_OFFLINE_GRACE_HOURS = 72
|
||||
STATE_FILENAME = "license_state.json"
|
||||
LICENSE_ENV_NAME = "license.env"
|
||||
|
||||
_lock = threading.RLock()
|
||||
_cached_device_id: str | None = None
|
||||
_env_loaded = False
|
||||
|
||||
|
||||
def _load_license_env() -> None:
|
||||
global _env_loaded
|
||||
if _env_loaded:
|
||||
return
|
||||
_env_loaded = True
|
||||
path = REPO_ROOT / LICENSE_ENV_NAME
|
||||
if not path.is_file():
|
||||
return
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
return
|
||||
for line in text.splitlines():
|
||||
raw = line.strip()
|
||||
if not raw or raw.startswith("#") or "=" not in raw:
|
||||
continue
|
||||
key, value = raw.split("=", 1)
|
||||
key = key.strip().lstrip("\ufeff")
|
||||
if not key or key in os.environ:
|
||||
continue
|
||||
os.environ[key] = value.strip().strip('"').strip("'")
|
||||
|
||||
|
||||
def api_base_url() -> str:
|
||||
_load_license_env()
|
||||
url = (os.getenv("LICENSE_API_URL") or DEFAULT_API_URL).strip().rstrip("/")
|
||||
return url or DEFAULT_API_URL
|
||||
|
||||
|
||||
def client_key() -> str:
|
||||
_load_license_env()
|
||||
return (os.getenv("LICENSE_CLIENT_KEY") or "").strip()
|
||||
|
||||
|
||||
def offline_grace_hours() -> float:
|
||||
_load_license_env()
|
||||
raw = (os.getenv("LICENSE_OFFLINE_GRACE_HOURS") or str(DEFAULT_OFFLINE_GRACE_HOURS)).strip()
|
||||
try:
|
||||
return max(0.0, float(raw))
|
||||
except ValueError:
|
||||
return float(DEFAULT_OFFLINE_GRACE_HOURS)
|
||||
|
||||
|
||||
def state_path() -> Path:
|
||||
_load_license_env()
|
||||
override = (os.getenv("LICENSE_STATE_PATH") or "").strip()
|
||||
if override:
|
||||
p = Path(override)
|
||||
if not p.is_absolute():
|
||||
p = REPO_ROOT / p
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
return p
|
||||
data = REPO_ROOT / "data"
|
||||
data.mkdir(parents=True, exist_ok=True)
|
||||
return data / STATE_FILENAME
|
||||
|
||||
|
||||
def get_device_id() -> str:
|
||||
"""稳定设备指纹,长度 16–64,供 redeem/validate 使用。"""
|
||||
global _cached_device_id
|
||||
if _cached_device_id:
|
||||
return _cached_device_id
|
||||
parts = [
|
||||
platform.node() or "",
|
||||
platform.system() or "",
|
||||
platform.machine() or "",
|
||||
hex(uuid.getnode()),
|
||||
]
|
||||
try:
|
||||
machine_id = Path("/etc/machine-id")
|
||||
if machine_id.is_file():
|
||||
parts.append(machine_id.read_text(encoding="utf-8").strip())
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
win_id = Path(os.environ.get("SystemRoot", r"C:\Windows")) / "System32" / "drivers" / "etc"
|
||||
parts.append(str(win_id.resolve()))
|
||||
except OSError:
|
||||
pass
|
||||
digest = hashlib.sha256("|".join(parts).encode("utf-8", errors="ignore")).hexdigest()
|
||||
_cached_device_id = digest[:40]
|
||||
return _cached_device_id
|
||||
|
||||
|
||||
def _read_state() -> dict[str, Any]:
|
||||
path = state_path()
|
||||
if not path.is_file():
|
||||
return {}
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
return data if isinstance(data, dict) else {}
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return {}
|
||||
|
||||
|
||||
def _write_state(data: dict[str, Any]) -> None:
|
||||
path = state_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp = path.with_suffix(".tmp")
|
||||
tmp.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
tmp.replace(path)
|
||||
|
||||
|
||||
def _now_ts() -> float:
|
||||
return time.time()
|
||||
|
||||
|
||||
def _parse_expires_at(value: str | None) -> float | None:
|
||||
if not value:
|
||||
return None
|
||||
s = value.strip()
|
||||
if not s:
|
||||
return None
|
||||
try:
|
||||
if s.endswith("Z"):
|
||||
s = s[:-1] + "+00:00"
|
||||
dt = datetime.fromisoformat(s)
|
||||
if dt.tzinfo is None:
|
||||
# 授权站按北京时间存,无 tz 时按 UTC+8
|
||||
from datetime import timedelta, timezone as _tz
|
||||
|
||||
dt = dt.replace(tzinfo=_tz(timedelta(hours=8)))
|
||||
return dt.timestamp()
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _http_json(method: str, path: str, body: dict[str, Any]) -> dict[str, Any]:
|
||||
key = client_key()
|
||||
if not key:
|
||||
return {"ok": False, "message": "未配置 LICENSE_CLIENT_KEY(与授权站 CLIENT_API_KEY 一致)"}
|
||||
url = f"{api_base_url()}{path}"
|
||||
payload = json.dumps(body).encode("utf-8")
|
||||
req = Request(
|
||||
url,
|
||||
data=payload,
|
||||
method=method.upper(),
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"X-Client-Key": key,
|
||||
"User-Agent": "crypto_monitor_user/license",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urlopen(req, timeout=20) as resp:
|
||||
raw = resp.read().decode("utf-8", errors="replace")
|
||||
data = json.loads(raw) if raw else {}
|
||||
return data if isinstance(data, dict) else {"ok": False, "message": "响应格式错误"}
|
||||
except HTTPError as e:
|
||||
try:
|
||||
detail = e.read().decode("utf-8", errors="replace")
|
||||
except Exception:
|
||||
detail = str(e)
|
||||
return {"ok": False, "message": f"HTTP {e.code}: {detail[:200]}", "network_error": True}
|
||||
except (URLError, TimeoutError, OSError) as e:
|
||||
return {"ok": False, "message": f"无法连接授权服务: {e}", "network_error": True}
|
||||
except json.JSONDecodeError:
|
||||
return {"ok": False, "message": "授权服务返回非 JSON", "network_error": True}
|
||||
|
||||
|
||||
def redeem_code(code: str) -> dict[str, Any]:
|
||||
code = (code or "").strip().upper().replace(" ", "")
|
||||
if len(code) < 8:
|
||||
return {"ok": False, "message": "激活码无效"}
|
||||
device_id = get_device_id()
|
||||
result = _http_json("POST", "/v1/redeem", {"device_id": device_id, "code": code})
|
||||
if not result.get("ok"):
|
||||
return {
|
||||
"ok": False,
|
||||
"message": result.get("message") or "兑换失败",
|
||||
"network_error": bool(result.get("network_error")),
|
||||
}
|
||||
now = _now_ts()
|
||||
with _lock:
|
||||
state = {
|
||||
"device_id": device_id,
|
||||
"subscription_id": result.get("subscription_id"),
|
||||
"plan": result.get("plan"),
|
||||
"expires_at": result.get("expires_at"),
|
||||
"last_ok_at": now,
|
||||
"last_validate_at": now,
|
||||
"last_reason": "",
|
||||
}
|
||||
_write_state(state)
|
||||
return {
|
||||
"ok": True,
|
||||
"message": result.get("message") or "激活成功",
|
||||
"subscription_id": result.get("subscription_id"),
|
||||
"plan": result.get("plan"),
|
||||
"expires_at": result.get("expires_at"),
|
||||
"device_id": device_id,
|
||||
}
|
||||
|
||||
|
||||
def validate_license(*, force: bool = False) -> dict[str, Any]:
|
||||
"""联网校验;更新本地状态。force=True 忽略 3 天间隔。"""
|
||||
with _lock:
|
||||
state = _read_state()
|
||||
sub_id = (state.get("subscription_id") or "").strip()
|
||||
if not sub_id:
|
||||
return {"ok": True, "valid": False, "reason": "not_activated", "message": "尚未激活"}
|
||||
|
||||
last_v = float(state.get("last_validate_at") or 0)
|
||||
if not force and last_v and (_now_ts() - last_v) < VALIDATE_INTERVAL_SEC:
|
||||
return get_license_status()
|
||||
|
||||
device_id = get_device_id()
|
||||
result = _http_json(
|
||||
"POST",
|
||||
"/v1/validate",
|
||||
{"device_id": device_id, "subscription_id": sub_id},
|
||||
)
|
||||
now = _now_ts()
|
||||
if result.get("network_error"):
|
||||
# 断网:在宽限期内沿用本地到期日
|
||||
status = get_license_status(skip_remote=True)
|
||||
status["message"] = result.get("message") or "网络异常,使用本地宽限"
|
||||
status["network_error"] = True
|
||||
with _lock:
|
||||
st = _read_state()
|
||||
st["last_network_error_at"] = now
|
||||
st["last_reason"] = "network_error"
|
||||
_write_state(st)
|
||||
return status
|
||||
|
||||
valid = bool(result.get("valid"))
|
||||
reason = (result.get("reason") or "").strip()
|
||||
with _lock:
|
||||
st = _read_state()
|
||||
st["device_id"] = device_id
|
||||
st["last_validate_at"] = now
|
||||
if valid:
|
||||
st["last_ok_at"] = now
|
||||
st["last_reason"] = ""
|
||||
if result.get("expires_at"):
|
||||
st["expires_at"] = result.get("expires_at")
|
||||
if result.get("plan"):
|
||||
st["plan"] = result.get("plan")
|
||||
else:
|
||||
st["last_reason"] = reason or "invalid"
|
||||
if reason in ("expired", "device_revoked", "subscription_not_found", "subscription_inactive"):
|
||||
# 明确失效:保留字段便于页面展示,但 valid=False
|
||||
pass
|
||||
_write_state(st)
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"valid": valid,
|
||||
"reason": reason if not valid else "",
|
||||
"expires_at": result.get("expires_at") or state.get("expires_at"),
|
||||
"plan": result.get("plan") or state.get("plan"),
|
||||
"device_id": device_id,
|
||||
"subscription_id": sub_id,
|
||||
"message": "有效" if valid else (reason or "无效"),
|
||||
}
|
||||
|
||||
|
||||
def get_license_status(*, skip_remote: bool = False) -> dict[str, Any]:
|
||||
"""本地状态(可触发到期判断);不主动联网除非 skip_remote=False 且到期需刷新。"""
|
||||
with _lock:
|
||||
state = _read_state()
|
||||
device_id = get_device_id()
|
||||
sub_id = (state.get("subscription_id") or "").strip()
|
||||
if not sub_id:
|
||||
return {
|
||||
"ok": True,
|
||||
"valid": False,
|
||||
"reason": "not_activated",
|
||||
"message": "尚未激活",
|
||||
"device_id": device_id,
|
||||
"api_url": api_base_url(),
|
||||
}
|
||||
|
||||
exp_ts = _parse_expires_at(state.get("expires_at"))
|
||||
now = _now_ts()
|
||||
if exp_ts is not None and now > exp_ts:
|
||||
return {
|
||||
"ok": True,
|
||||
"valid": False,
|
||||
"reason": "expired",
|
||||
"message": "许可已过期",
|
||||
"expires_at": state.get("expires_at"),
|
||||
"plan": state.get("plan"),
|
||||
"device_id": device_id,
|
||||
"subscription_id": sub_id,
|
||||
"api_url": api_base_url(),
|
||||
}
|
||||
|
||||
reason = (state.get("last_reason") or "").strip()
|
||||
if reason in ("expired", "device_revoked", "subscription_not_found", "subscription_inactive"):
|
||||
return {
|
||||
"ok": True,
|
||||
"valid": False,
|
||||
"reason": reason,
|
||||
"message": reason,
|
||||
"expires_at": state.get("expires_at"),
|
||||
"plan": state.get("plan"),
|
||||
"device_id": device_id,
|
||||
"subscription_id": sub_id,
|
||||
"api_url": api_base_url(),
|
||||
}
|
||||
|
||||
last_ok = float(state.get("last_ok_at") or 0)
|
||||
grace = offline_grace_hours() * 3600
|
||||
if last_ok and grace > 0 and (now - last_ok) > grace:
|
||||
# 超过离线宽限且长时间未成功校验
|
||||
last_v = float(state.get("last_validate_at") or 0)
|
||||
if last_v and (now - last_v) >= VALIDATE_INTERVAL_SEC and not skip_remote:
|
||||
return validate_license(force=True)
|
||||
if (now - last_ok) > grace:
|
||||
return {
|
||||
"ok": True,
|
||||
"valid": False,
|
||||
"reason": "offline_grace_exceeded",
|
||||
"message": "超过离线宽限期,请联网校验",
|
||||
"expires_at": state.get("expires_at"),
|
||||
"plan": state.get("plan"),
|
||||
"device_id": device_id,
|
||||
"subscription_id": sub_id,
|
||||
"api_url": api_base_url(),
|
||||
}
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"valid": True,
|
||||
"reason": "",
|
||||
"message": "已授权",
|
||||
"expires_at": state.get("expires_at"),
|
||||
"plan": state.get("plan"),
|
||||
"device_id": device_id,
|
||||
"subscription_id": sub_id,
|
||||
"last_ok_at": last_ok or None,
|
||||
"api_url": api_base_url(),
|
||||
}
|
||||
|
||||
|
||||
def is_license_valid() -> bool:
|
||||
status = get_license_status()
|
||||
if status.get("valid"):
|
||||
# 到期需周期性联网
|
||||
last_v = 0.0
|
||||
with _lock:
|
||||
st = _read_state()
|
||||
last_v = float(st.get("last_validate_at") or 0)
|
||||
if last_v and (_now_ts() - last_v) >= VALIDATE_INTERVAL_SEC:
|
||||
status = validate_license(force=True)
|
||||
return bool(status.get("valid"))
|
||||
# 未激活或已失效:尝试若有 subscription 则强制校验一次(避免本地过期标志陈旧)
|
||||
with _lock:
|
||||
st = _read_state()
|
||||
if (st.get("subscription_id") or "").strip() and status.get("reason") not in (
|
||||
"not_activated",
|
||||
"expired",
|
||||
"device_revoked",
|
||||
):
|
||||
status = validate_license(force=True)
|
||||
return bool(status.get("valid"))
|
||||
return False
|
||||
|
||||
|
||||
def ensure_license_or_raise() -> None:
|
||||
if not is_license_valid():
|
||||
raise RuntimeError("license_required")
|
||||
@@ -0,0 +1,100 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>软件授权</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
background: #0a0a10;
|
||||
color: #e8e8f0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
.box {
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
background: #12121a;
|
||||
border: 1px solid #2a2a3a;
|
||||
border-radius: 14px;
|
||||
padding: 28px 24px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.35rem;
|
||||
margin-bottom: 8px;
|
||||
background: linear-gradient(90deg, #4cc2ff, #7b42ff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
.muted { color: #9aa0b4; font-size: 0.9rem; margin-bottom: 18px; line-height: 1.5; }
|
||||
.row { margin-bottom: 14px; }
|
||||
label { display: block; font-size: 0.85rem; color: #b8bfd4; margin-bottom: 6px; }
|
||||
.id {
|
||||
word-break: break-all;
|
||||
background: #0c0c14;
|
||||
border: 1px solid #2a2a3a;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
font-family: ui-monospace, Consolas, monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
input[type=text] {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #2a2a3a;
|
||||
background: #0c0c14;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
padding: 11px 14px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(90deg, #3a8dff, #6b4dff);
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.ok { color: #5ddea8; margin: 10px 0; font-size: 0.9rem; }
|
||||
.err { color: #ff7b8a; margin: 10px 0; font-size: 0.9rem; }
|
||||
.meta { margin-top: 16px; font-size: 0.85rem; color: #9aa0b4; line-height: 1.6; }
|
||||
a { color: #7ec8ff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<h1>软件授权</h1>
|
||||
<p class="muted">复制下方设备 ID,联系微信 <strong>{{ wechat }}</strong> 购买激活码后粘贴兑换。授权站:{{ api_url }}</p>
|
||||
|
||||
{% if message %}<p class="ok">{{ message }}</p>{% endif %}
|
||||
{% if error %}<p class="err">{{ error }}</p>{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<label>设备 ID</label>
|
||||
<div class="id" id="deviceId">{{ device_id }}</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/license">
|
||||
<div class="row">
|
||||
<label>激活码</label>
|
||||
<input type="text" name="code" placeholder="粘贴激活码" autocomplete="off" required>
|
||||
</div>
|
||||
<button type="submit">兑换激活</button>
|
||||
</form>
|
||||
|
||||
<div class="meta">
|
||||
状态:{% if status.valid %}已授权{% else %}未授权{% endif %}
|
||||
({{ status.message or status.reason or '—' }})<br>
|
||||
套餐:{{ status.plan or '—' }} 到期:{{ status.expires_at or '—' }}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
@@ -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())
|
||||
|
||||
@@ -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 = '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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
-104
@@ -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
@@ -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
+12
-1283
File diff suppressed because one or more lines are too long
@@ -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
@@ -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 = 'eNpdV39sG+d5JmVRlEhRPkriHRlblmQr8s/Zd8e74x2PPB7JO/6mTUr8Jf6Q7sg7UhJF0o5sK5GdOE22bkATIE5lOAHaAttSBNgCtEncqLWA2EntDkPQbHGTYeuSupEQqTAaZSi2P4oNwb5zka7Z3w/e73ve533f53u/f1GrjEopikwzRI10yUQdxZXHUUaCAwSqpJiO2kPriV61312jquS0lKqXsT520S2pKTIjFbwFifYolE41eLP4AN6qk2rGqePKWP9S24spqcPpdg/5Kc61DYcrE8aJN9V2OzOoO5E58ZncpjNiaix9ZdvrY3Uhw6HKkR2s1Uk9XsOT+0SdrBL3Xvvve6+89eyX157+uw+f/vfv3H/tP+499+XH71195LmN1371xebwPujZH7z+wf3PHTH4md/8+uNnXv2Lv/nGX/31Lx37n6dRRk6Qu6yhmC9a9yT7Z0xIhNnyGDPBbr6/kk8yD9xj5uQI9vbxaNjUnbIu9JoH4/VQl392oLknUOlrpM90I9npLDJCf3HRnO8PsGZrIlWyhOZtWWM5j2w647rBEIoqxJYTMSYhwfS9drnA9/hn+9NiYvBYfrbcpRBAP4IhNX0JoC8G9L3okklYwhU6wRhUPxFcijBppY9FPDoFp5PEKHlDWlOu4nfcGdZQT6FXG1H6TA1238Xt6m2mTaCkU+YlgehyRw9IKklgSotJqxgzSCXlU5vM+1Xb55L0HAbOd3Ab9TAfeBuL+RFz9BYG662ZQr4nTMiEuujris1leuHS1Bw8U/Dfarb2h/TjQ7Fc6Cc+WVeAsitwbC4eaYScWn4E/B6DHAT8f14F/XG+NI8G83B8qdI94C8ujaVjs1ZLlPnn3334588+e++/3vjFL595eu1bdCnAW62lZsVcTPlNUB0S9bA3k8r5imFbxTTrp/N8stdUDHcQvuCHvdn+TGzYvok2hzLiYNot+DugfoMXgH7nSS+xXGMVt3pe4mjqO+FxwP8BbYxlCzZdqpmGdtFcnyWmm2INtnfJqb3FnG3YAV0257LFmvp/+kugv2ugv8dIlxR+FPR3AhXlbTKKQ4RIbNeEeqyuk5NMeDlKGAgI5S+W0UXZ4Yrgl4mXiTtVQY5JOmnaE3oy6sQJPSorVnxlSKtvkoofAuffYiGzbSbbhFKZgaI+uE+OHWZkNXRC0w+F4QISeLvRnavsSqGprtA5cH9sQsPHNZx2m3J9UyzSXcoVjaGDIL8gp9AoJrXICUwiSeJCLX4J8N/gzmT38lOD77fLJSTRnLOYsnzLSP6hvx7OLw3q4wTxy7jCBA+D+YmgCFohqWpSDah2eYPm5ahrk3wKzakb+KKcoFYDC0CfhLwlCWbr3vKWC+KLG66+bDnJBCZBfGgC6Bc+B84XzoHzt2hPxt/ThLNpcy4TuwL0DVZBvFzzKmMqLAJ9f4LP9Yatc/zIuxPZXpjvZCz5ZBuG41H4UZNdVd5vpWe7eC4Ygoehx/fuX8hjX80H4H8exC8D/1lRUScyiDNyFxHF9hCCq6sq1NOKQ51jovUp137cir6ovowKdQdxFmWSgtex+HJjSr0rldk5Oay3uRz0i/Wy8yUJ1JexeysYVIunYjX7/Biz1lhz7ecc7pw8TcYzDnyNtHMvEUo1shJbulMr+65VI54Rn54IK7GzkeVRnKfsvpuEvd1WCx1JTnB8c5oEaaBOhUEleXgyiEJe0XtXqtSvq/aFl9iUskZtUnbv6YUykZCQqQh5BxPpOzVajc3rlGlv6JEo9Q7m8HxbZVBxSY/6MTghUncb4vx1VZWjxyOdNG4ikI3T9ZdIsXWHJFE785D/bky6owjEtSrhgiat7tPqBpZsOqh1bJkQWMdS/E6gAzUFer0aZVYJPT2plJmbNTOZIh2sSCD/lFyIqTtSwhmpvkWtEmYsJ4ljYg15nvfYqZuEdV5s/Ei9IufoVCMyOUIi/xnqJDwi0C/l3qHyhH5BWInV4R8mcL7tZ0eobex1tex+eD7rOJzkrG1R+ZEKtQR2vXrQZeC28TNnBQ7wI9cxiBNd2zVxMbBoPyccWZejhw0ohPHXE6x4Ts+t4yaygjmWK3JQhb+nnR+d3CT17tfrOayiOiYjh+xjSbejcVMJNO1V0L9LUecmCfKvgvzbDnwd09Hfd1NVaD6YAf358d//2wfPf3Hv9z/48PcbH63vPL3xzY9++7cfrP089sn9F5/5n9/96oc3Yp+8fPsX77360f1X//X67fguHoSySR7+sdcU0puGTgF/viRKhIr+Zh74i6X/dDrDCvkZZBX05/M68D5+IoP+/EQG83sfvC8P/eW71BcLcM6a72W7MrlwT7o1H+yfbRnzYt7m4wpzSLieMyF562+Xhnpz2vsyxT5qC1S60kLdYgqHO3A2WTb9mOvm97BwOr/b1Oarb86PbHqSA9ZsoTMcyk33w1udgBnSZeqF/pDATpizIfZP+fnahaE02z+QqWj+w050h7LsmZk8ZykVMmy4nPMta3jvQDzWNBdLBm1+OXwu2m1u5uesxWTn/J4zXQvm4e5cvdw/M82FddP7zsecIL9d16w5uEV1GZPIGPCX2CHgL583sjY+byo/8Bj7SlkBfhTML3QczC98BcyvFQb7A2IF709gH9Bn12nIxcKS5h88qA80boUAfosQLbngVqOvFA7qw1ueQXPePWvuNcS3uL1FYbBpmZvVxR+gur5Cs2AuVEK35nO2aRPbl8+moLBe838Mzofjq0D/wBPAP3xq1FxkYd2ZLddQqbSBavExneavBuCvcSPwF8Sg8bcA/mLmYX1z2SSHpLNly+lyGrkI6gufBfUNXQT13Q1PV/oiYrLbXRIESykfcluT3QGENcZyM57K7CBvCsR57X0aAvWXKC+h+R+uShJKe2SW6pAtjFNRzCk1SA5bIlmiUSOVSYC7f3ZQ9lJNgNN/wG94sDaJEZ0a+9lkfVUao09UD5GXyGUMrXOYD8R7sLPkOIhnFUKtgvcPrzqpBll7hx8H939tvxgH+R35ar/oRbX9YlONkrHLPBNVPqUVyq6+w1TId4nrtWtqEtjKC7ShcY18oX6NidRJxuG607gN3g8LGpfW6ldxlQ4zPWoUzStRz5Q66uuiHLUeWvAg2HUFxLN293nGU4xyQjXiTNcvMdsuR8fmEzkjdlKOdDKuJ+k32f0LETqj+OnwyQPYXc4+f5uhiVFuujEnB5bsnhewkfY1ElMiZFpFaASM9zuoSL9LULKYSTA6rsQM2SLYC0sRfE2pMQ63Thlm4sIofn0hsnDVqdL+XsCPSTTOMvNPjDqtPnvb4Bkjfk3EOpFmBrfSgQ0IingTiyeYa52fMnbG3jRwU5j1lfjSWivhvctgDATxvoS0TvvbBzzbrmVl/xFxwvpisMW3E9QOkzi6qqacE0oKe6vRT+tRx4URT/wvETqurisCY/e8Sa+qRTKjRI9HJeibVtYubzJJ3yh6Qzqlzjp1dPLYiBz6Upjniaj8Ka337dT3MhXuwHhcCb0hcBAlECPSOvmmmkIfnu9xnLMRSWYUuyHx8wnXDnOYMSytU8nHIkcBP++2C+qMeG+SoxPBdmQlMb7jThwH+yMZ+C5PRld60G3CpBpo8bCRDEivKAECAs/myMV1MtNaZ0tKD+M4aD83cjI5MeoF519IEDuMcHSkuk4KnQjxlgIfdbA7qsH5fRYlD7RWR6/Rgh1y+MXhYUgYdtTHTl6erApw90y24khNc4K5fEAoBco6azrVMfA2W89ADp7WsbqEczhoQ/zDIjQI4q84jz0xPDfMp4pma90qGHTd8a/h7aNj9XOQtv9BVkcBDhot+VjaOIiMrY5DvH6mnNoH9jveVHBcpgIrx6rCMeL8Be5P4h37UO4hP5sYcIiw4Jo4f3nFZcsMpbuFvK6RTvoP1S89KZSi/n5LHl6AYF25HA5Wr0zUxyA/NCMkBsW+YcQslEuBeS1e8NsdGr+hAyqKw6eAvyMnwHyFaDBf8dNgPxkaBvMVfgT4T2A/2M/ip4H/OC6EfACvVaf367uSyTTjTXfzPYXp3swDTxfwtyzs0vxxr+aPBc0fj2v+2Kv54yltvzqm7VchzR9F4I9xGPy/hga0+73a/RC4f+Rkp2YoWxd0PaynWLKUWot58D7oy534wGC4zWj83VBuUITnNP+aA/4VKmj75YDmf2bN//Zo/rei+d8lzf8OAv/jJ4B/DD0F/AM+qvHr0/ilNH5Gjd8ejZ9F42fQ+I1r/M5q/MY0fuOA39t/Np3rt/n5LPuVvgikS8RSAB98A+BVBqcXFJTEVepnWE36qYzV1do/eNHGPx5qoA3CKTEErSoekq7T9GFlke64ltQmg5ELBPmpR3E7lxg3XfsMpWiVpr2KRC88xNkby66j0gnliPMx9zF6RaEoSsVpF4g/53pKrTEsKRMY+FxRzipD0Y210DLIf2W/DHFafqqWn1fLj9H+R76hx7T8US3/8a/jRzXcO7Sq4cMajmn44wCPYaKg6RfQ9Gtp+BDA4yzCafoeAPoeaO9WOy1dfrrO3eKanS5/kkeMdbdQ1/RzAX02cGs0YMnl4yron8VgIZWPm/NwwGDqqiAVSyioK/ZkW/rsVNngzpdmobTf7x5IGQUZxNcO7i1nz5RsQsmcCyIDNr4yZwrX00bIp/Fb+WN/DWv1u6zVb+WP+/v/qx/anxnQ+wvZovZ/sJ4C8ZJM0LTMUC1qAaMbuNs538HdZ/8XQz6u0A=='
|
||||
_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
@@ -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())
|
||||
|
||||
+12
-232
File diff suppressed because one or more lines are too long
+12
-164
File diff suppressed because one or more lines are too long
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user