diff --git a/crypto_monitor_okx/.env.example b/crypto_monitor_okx/.env.example index ac8aa55..a38d5ef 100644 --- a/crypto_monitor_okx/.env.example +++ b/crypto_monitor_okx/.env.example @@ -79,11 +79,12 @@ TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true # 是否开启 OKX 实盘下单(false=只做本地流程,true=真实下单) LIVE_TRADING_ENABLED=true -# OKX API Key(实盘) +# ============================================================================= +# OKX 账户 API(永续 + 期权共用同一套密钥;修改后须重启 PM2) +# 旧键 OKX_OPTIONS_API_* 已废弃:若 OKX_API_* 为空,启动时会从 OPTIONS 键回填 +# ============================================================================= OKX_API_KEY=REPLACE_WITH_OKX_API_KEY -# OKX API Secret(实盘) OKX_API_SECRET=REPLACE_WITH_OKX_API_SECRET -# OKX API Passphrase(实盘) OKX_API_PASSPHRASE=REPLACE_WITH_OKX_API_PASSPHRASE # 保证金模式:cross=全仓,isolated=逐仓 OKX_TD_MODE=cross @@ -99,18 +100,19 @@ OKX_POSITION_INST_TYPE=SWAP EXCHANGE_DISPLAY_NAME=OKX # 企业微信推送里展示的账户备注 # OKX_ACCOUNT_LABEL= -# 顶栏是否显示永续资金账户/交易账户(热更);false 时总资金仅计期权侧 +# 顶栏是否显示 USDT 资金/交易账户(热更);false 时总资金仅计期权 USDC 侧 OKX_SHOW_PERP_FUNDS=true # ============================================================================= -# 期权(主账户 API,与永续子账户 OKX_API_* 分离;修改后须重启 PM2) +# 期权模块(与上方 OKX_API_* 同源;修改启用开关后须重启 PM2) # 详见 docs/期权方案.md 与 docs/期权用法.md # ============================================================================= OKX_OPTIONS_ENABLED=false -OKX_OPTIONS_API_KEY= -OKX_OPTIONS_API_SECRET= -OKX_OPTIONS_API_PASSPHRASE= -OKX_OPTIONS_ACCOUNT_LABEL=主账户·期权 +# 以下 OKX_OPTIONS_API_* 已废弃,请勿再配置(仅兼容旧部署回填) +# 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 # 交易模式三选一(热更):options=单独期权 / perp_options=永期对冲 / options_options=期期对冲 @@ -123,7 +125,6 @@ OKX_OPTIONS_DEFAULT_UNDERLY=ETH OKX_OPTIONS_CHAIN_ASK_LIQ_FILTER_ENABLED=true OKX_OPTIONS_MAX_DTE_DAYS=2 OKX_OPTIONS_CHAIN_MAX_DTE_DAYS=14 -OKX_SUB_ACCOUNT_NAME= OKX_OPTIONS_ITM_MAX_DIST_USD=30 OKX_OPTIONS_PROFIT_ALERT_RATIO=1.0 OKX_OPTIONS_POLL_SECONDS=15 diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index ab2cb0d..7c58d3e 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -343,16 +343,29 @@ def _resolve_app_tz(): APP_TZ = _resolve_app_tz() LIVE_TRADING_ENABLED = os.getenv("LIVE_TRADING_ENABLED", "false").lower() == "true" + + +def _promote_legacy_options_api_keys() -> None: + """1B: OKX_API_* 为空时,用废弃的 OKX_OPTIONS_API_* 回填到进程环境.""" + if (os.getenv("OKX_API_KEY") or "").strip(): + return + legacy_key = (os.getenv("OKX_OPTIONS_API_KEY") or "").strip() + legacy_secret = (os.getenv("OKX_OPTIONS_API_SECRET") or "").strip() + legacy_pass = (os.getenv("OKX_OPTIONS_API_PASSPHRASE") or "").strip() + if not (legacy_key and legacy_secret and legacy_pass): + return + os.environ["OKX_API_KEY"] = legacy_key + os.environ["OKX_API_SECRET"] = legacy_secret + os.environ["OKX_API_PASSPHRASE"] = legacy_pass + + +_promote_legacy_options_api_keys() OKX_API_KEY = os.getenv("OKX_API_KEY", "") OKX_API_SECRET = os.getenv("OKX_API_SECRET", "") OKX_API_PASSPHRASE = os.getenv("OKX_API_PASSPHRASE", "") OKX_OPTIONS_ENABLED = os.getenv("OKX_OPTIONS_ENABLED", "false").lower() in ("1", "true", "yes", "on") -OKX_OPTIONS_API_KEY = os.getenv("OKX_OPTIONS_API_KEY", "") -OKX_OPTIONS_API_SECRET = os.getenv("OKX_OPTIONS_API_SECRET", "") -OKX_OPTIONS_API_PASSPHRASE = os.getenv("OKX_OPTIONS_API_PASSPHRASE", "") OKX_OPTIONS_TRADE_BUDGET_USDC = float(os.getenv("OKX_OPTIONS_TRADE_BUDGET_USDC", "10")) OKX_OPTIONS_DEFAULT_UNDERLY = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper() -OKX_SUB_ACCOUNT_NAME = (os.getenv("OKX_SUB_ACCOUNT_NAME") or "").strip() OKX_TD_MODE = os.getenv("OKX_TD_MODE", "cross") OKX_POS_MODE = os.getenv("OKX_POS_MODE", "hedge") EXCHANGE_DISPLAY_NAME = (os.getenv("EXCHANGE_DISPLAY_NAME") or "OKX").strip() or "OKX" @@ -467,7 +480,7 @@ os.makedirs(UPLOAD_FOLDER, exist_ok=True) os.makedirs(ORDER_CHART_DIR, exist_ok=True) app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER -# 换成 OKX 永续 +# 同一套 OKX_API_*:swap 客户端跑永续,option 客户端跑期权(身份相同,defaultType 不同) exchange = ccxt.okx({ "enableRateLimit": True, "options": {"defaultType": "swap"}, # OKX 用 swap 表示永续 @@ -487,10 +500,10 @@ exchange_options = ccxt.okx( ) if OKX_CCXT_PROXIES: exchange_options.proxies = OKX_CCXT_PROXIES -if OKX_OPTIONS_API_KEY and OKX_OPTIONS_API_SECRET and OKX_OPTIONS_API_PASSPHRASE: - exchange_options.apiKey = OKX_OPTIONS_API_KEY - exchange_options.secret = OKX_OPTIONS_API_SECRET - exchange_options.password = OKX_OPTIONS_API_PASSPHRASE +if OKX_API_KEY and OKX_API_SECRET and OKX_API_PASSPHRASE: + exchange_options.apiKey = OKX_API_KEY + exchange_options.secret = OKX_API_SECRET + exchange_options.password = OKX_API_PASSPHRASE MARKETS_LOADED = False ACCOUNT_BALANCE_CACHE = { @@ -6755,8 +6768,8 @@ def render_main_page(page="trade", embed_mode=None): current_capital if _show_perp_funds else None, options_trading_usdc, options_funding_usdc, - options_funding_usdt, - options_trading_usdt, + None, + None, ), options_funding_usdc=options_funding_usdc, options_funding_usdt=options_funding_usdt, @@ -7097,8 +7110,8 @@ def api_account_snapshot(): current_capital if _show_perp_funds else None, options_trading_usdc, options_funding_usdc, - options_funding_usdt, - options_trading_usdt, + None, + None, ), "available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None, "unrealized_pnl": unrealized_pnl, diff --git a/docs/env配置说明.md b/docs/env配置说明.md index 234f511..affa601 100644 --- a/docs/env配置说明.md +++ b/docs/env配置说明.md @@ -61,14 +61,14 @@ Binance / Gate 无期权模块时,第三列最后一格不显示或显示「本 | 中文名 | 说明 | 重启 | |--------|------|------| | 开启实盘下单 | 关闭时仅走本地流程,不向交易所发单 | 需重启 | -| API Key | 永续子账户 API Key | 需重启 | -| API Secret | 永续子账户 Secret | 需重启 | +| API Key | 账户 API Key(永续+期权共用) | 需重启 | +| API Secret | 账户 API Secret | 需重启 | | API Passphrase | 仅 OKX 显示 | 需重启 | | 保证金模式 | 全仓 / 逐仓 | 需重启 | | 持仓模式 | 双向 / 单向净持仓等(按所) | 需重启 | | 仓位查询类型 | 仅 OKX:如 SWAP | 需重启 | | 账户备注 | 企业微信推送中显示的交易所备注 | 保存即生效 | -| 显示永续资金 | 仅 OKX:关闭后顶栏隐藏永续资金账户与交易账户,总资金仅计期权侧 | 保存即生效 | +| 显示永续资金 | 仅 OKX:关闭后顶栏隐藏 USDT 资金/交易账户,总资金仅计期权 USDC 侧 | 保存即生效 | **本卡片不包含**:网页登录账号密码,是否关闭登录校验,中控通信密钥. @@ -170,8 +170,7 @@ AI 相关环境变量(`AI_PROVIDER`,`OPENAI_*`,`OLLAMA_*`,`AI_MODEL`,`AI_TIMEOUT | 中文名 | 说明 | |--------|------| -| 启用期权模块 | | -| 期权 API Key / Secret / Passphrase | 主账户,与永续子账户分离 | +| 启用期权模块 | 与永续共用 `OKX_API_*`;不再单独配置期权密钥 | | 期权账户备注 | | | 单笔预算(USDC) | | | 预算缓冲比例 | | diff --git a/docs/对冲计划开发方案.md b/docs/对冲计划开发方案.md index 28da05b..4fcc41a 100644 --- a/docs/对冲计划开发方案.md +++ b/docs/对冲计划开发方案.md @@ -12,8 +12,8 @@ | 产品名 | 英文键 | 含义 | |--------|--------|------| -| **永期对冲** | `perp_options` | 永续(子账户) + 买方期权(主账户) | -| **期期对冲** | `options_options` | 主账户内两条买方期权腿 | +| **永期对冲** | `perp_options` | 同账户永续 + 买方期权 | +| **期期对冲** | `options_options` | 同账户内两条买方期权腿 | 页面/导航展示用中文名;API/DB 用英文键. @@ -217,7 +217,7 @@ | 侧 | 来源 | |----|------| -| 永续行情/规格 | OKX 子账户 ccxt:ticker + 现有 `/api/hub/market` 规格逻辑 | +| 永续行情/规格 | OKX 账户 ccxt:ticker + 现有 `/api/hub/market` 规格逻辑 | | 期权链 | `build_option_chain` / `/api/options/chain`(本实例直连,无需中控代理) | | 指数价 | 期权 `index_px`,左右对齐 | diff --git a/docs/对冲计划策略与P0校验.md b/docs/对冲计划策略与P0校验.md index 2fcb309..a40139e 100644 --- a/docs/对冲计划策略与P0校验.md +++ b/docs/对冲计划策略与P0校验.md @@ -9,8 +9,8 @@ | 类型 | 账户 | 作用 | |------|------|------| -| **永期对冲** | 永续子账户 + 期权主账户买方 | 全仓做方向,期权买保险 | -| **期期对冲** | 仅期权主账户双买方 | 目标价兑现盈利腿,亏损腿到期 | +| **永期对冲** | 同账户永续 + 期权买方 | 全仓做方向,期权买保险 | +| **期期对冲** | 同账户双买方期权 | 目标价兑现盈利腿,亏损腿到期 | ### 永期结束与统计 diff --git a/docs/期权对冲方案分析.md b/docs/期权对冲方案分析.md index 65e7c32..23c6645 100644 --- a/docs/期权对冲方案分析.md +++ b/docs/期权对冲方案分析.md @@ -1,15 +1,15 @@ # 期权对冲方案分析 -> 适用范围:OKX **永续子账户**(USDT 本位) + **期权主账户**(USDⓈ 本位买方). +> 适用范围:OKX **同一账户**(`OKX_API_*`):USDT 永续 + USDⓈ 期权买方. > 本文档为 **策略与操盘说明**,非系统自动下单功能;组合须 **人工** 在永续页与期权页分别执行. --- ## 1. 前提与账户分工 -| 维度 | 永续合约(子账户) | 期权(主账户) | -|------|------------------|--------------| -| API | `OKX_API_*` | `OKX_OPTIONS_API_*` | +| 维度 | 永续合约 | 期权 | +|------|----------|------| +| API | `OKX_API_*`(swap 客户端) | `OKX_API_*`(option 客户端) | | 系统页面 | 实盘下单 / 关键位 / 策略 | 期权 | | 保证金 | USDT | USDC / USDG | | 本系统能力 | 开平仓、止损、关键位 | **仅买方** 开平仓,无组合单 | diff --git a/docs/期权方案.md b/docs/期权方案.md index c2bb51f..d95477a 100644 --- a/docs/期权方案.md +++ b/docs/期权方案.md @@ -1,15 +1,15 @@ # OKX 期权模块 — 技术方案 -> 适用范围:`crypto_monitor_okx` 实例;与永续子账户并行,不新增 PM2 进程. +> 适用范围:`crypto_monitor_okx` 实例;永续与期权共用同一套 `OKX_API_*`,不新增 PM2 进程. ## 1. 目标 在现有 OKX 监控实例中增加 **USDⓈ 本位期权(买方)** 能力: -- 永续/关键位:继续走 **子账户 API-A**(现有 `OKX_API_*`) -- 期权:走 **主账户 API-B**(`OKX_OPTIONS_API_*`) -- 资金展示对齐 OKX:**资金账户 / 交易账户**,分币种显示 USDT,USDC,USDG -- 支持 **手动 USDT→USDC 兑换** 与 **USDC 账户划转** +- 永续/关键位与期权:**同一账户 API**(`OKX_API_*`) +- 两个 ccxt 客户端:`exchange`(defaultType=swap)与 `exchange_options`(defaultType=option),身份相同 +- 顶栏资金:**资金账户/交易账户=USDT**;**期权资金/期权交易=USDC** +- 支持 **手动 USDT→USDC 兑换** 与 **账户内划转**(无主↔子划转) - **无总资金池上限**;单笔权利金上限可配置(默认 10 USDC) ## 2. 交易规则(硬约束) @@ -32,8 +32,8 @@ ``` crypto_okx(单 PM2) -├── exchange (swap) ← OKX_API_* 子账户 -└── exchange_options ← OKX_OPTIONS_API_* 主账户 +├── exchange (swap) ← OKX_API_* +└── exchange_options (option)← 同一套 OKX_API_* lib/options/ ├── okx_options_lib.py # 封装于 lib/exchange/ @@ -43,109 +43,36 @@ lib/options/ └── options_register.py # 路由 + 监控线程 ``` -**隔离:** 期权模块只调用 `exchange_options`;永续逻辑只调用 `exchange`. +**说明:** `defaultType` 分离避免错路由;密钥身份唯一.旧 `OKX_OPTIONS_API_*` 已废弃. ## 4. 资金与兑换 -### 4.1 展示(期权页顶栏) +### 4.1 展示(实例顶栏) -| 账户 | 币种 | -|------|------| -| 资金账户 | USDT,USDC(若有) | -| 交易账户 | USDT,USDC,USDG(若有) | +- **资金账户 / 交易账户**:USDT(永续侧) +- **期权资金账户 / 期权交易账户**:USDC +- 总资金:USDT + USDC(1:1),同账户 USDT 不重复累加期权侧 USDT -不展示「练手池」等抽象记账名称. +### 4.2 兑换与划转 -### 4.2 推荐操作流程 +- 系统设置「币种兑换」:资金账户内 USDT ↔ USDC +- 「期权划转」:同账户 funding ↔ trading(USDC/USDT) +- **已移除**主↔子账户划转 -``` -资金账户 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`) +## 5. 环境变量(要点) ```bash -OKX_OPTIONS_ENABLED=false -OKX_OPTIONS_API_KEY= -OKX_OPTIONS_API_SECRET= -OKX_OPTIONS_API_PASSPHRASE= -OKX_OPTIONS_ACCOUNT_LABEL=主账户·期权 - +OKX_API_KEY= +OKX_API_SECRET= +OKX_API_PASSPHRASE= +OKX_OPTIONS_ENABLED=true 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 +OKX_OPTIONS_ACCOUNT_LABEL=账户·期权 ``` -平仓执行:**只锁买一限价**,说明见 [期权开平仓与监控说明.md](./期权开平仓与监控说明.md);线上 `/options/guide`. +详见 [env配置说明.md](./env配置说明.md) 与 `.env.example`. -修改 `.env` 后须 `pm2 restart crypto_okx`. +## 6. 相关文档 -## 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` +- [期权用法.md](./期权用法.md) +- [对冲计划开发方案.md](./对冲计划开发方案.md) diff --git a/docs/期权用法.md b/docs/期权用法.md index 6411761..d682679 100644 --- a/docs/期权用法.md +++ b/docs/期权用法.md @@ -2,37 +2,37 @@ ## 1. 前置条件 -1. OKX **主账户**已开通期权(USDⓈ 本位),且 App 中可见 `ETHUSD UM` / `BTCUSD UM`. -2. 在 `crypto_monitor_okx/.env` 配置 **期权专用 API**(与永续子账户分开): +1. OKX 账户已开通期权(USDⓈ 本位),且 App 中可见 `ETHUSD UM` / `BTCUSD UM`. +2. 在 `crypto_monitor_okx/.env` 配置 **唯一账户 API**(永续与期权共用): ```bash +OKX_API_KEY=你的账户Key +OKX_API_SECRET=... +OKX_API_PASSPHRASE=... OKX_OPTIONS_ENABLED=true -OKX_OPTIONS_API_KEY=你的主账户Key -OKX_OPTIONS_API_SECRET=... -OKX_OPTIONS_API_PASSPHRASE=... ``` -3. 重启实例:`pm2 restart crypto_okx` +3. 重启实例:`pm2 restart crypto_okx --update-env` -> 永续仍用原有 `OKX_API_*`(子账户);期权只用 `OKX_OPTIONS_API_*`(主账户). +> 旧 `OKX_OPTIONS_API_*` 已废弃.若仅残留 OPTIONS 键而 `OKX_API_*` 为空,启动会自动回填. ## 2. 资金准备 -期权权利金使用 **USDC 或 USDG**,不能直接用 USDT 买入. +期权权利金使用 **USDC**,不能直接用 USDT 买入. ### 推荐步骤 -1. 打开 **期权** 页,查看顶栏: - - **资金账户**:USDT 余额 - - **交易账户**:USDC 余额(买期权从这里扣) +1. 查看顶栏: + - **资金账户 / 交易账户**:USDT + - **期权资金账户 / 期权交易账户**:USDC 2. **币种兑换**(资金账户内) - 从 USDT 兑换为 USDC - 先点 **询价**,确认预估获得量后点 **确认兑换** -3. **账户划转** +3. **账户内划转** - 从:资金账户 → 到:交易账户 - 币种:USDC - 将兑换得到的 USDC 划到交易账户 -4. 确认 **交易账户 USDC** 足够支付本笔权利金 +4. 确认 **期权交易账户 USDC** 足够支付本笔权利金 系统 **不会** 自动兑换或划转,避免误动资金. @@ -85,82 +85,23 @@ OKX_OPTIONS_API_PASSPHRASE=... ## 6. 与永续 / 对冲计划的关系 -| | 永续(子账户) | 期权(主账户) | -|--|----------------|----------------| -| API | `OKX_API_*` | `OKX_OPTIONS_API_*` | +| | 永续 | 期权 | +|--|------|------| +| API | `OKX_API_*`(同一套) | `OKX_API_*`(同一套) | | 页面 | 实盘下单 / 关键位 | 期权 · 对冲计划 | -| 资金顶栏 | USDT 资金户+交易户 | 期权页单独显示 USDC 等 | - -两套资金 **不合并** 显示. +| 顶栏 | USDT 资金户+交易户 | USDC 期权资金+期权交易 | **期期对冲张数**(对冲计划页,与单独开期权共用预算算法): | 模式 | 说明 | |------|------| | 同张数(默认) | 两腿同 `n`,总权利金 ≤ 预算 | -| 做多 | Call:Put 按主腿占比(默认 7:3) | -| 做空 | Put:Call 按主腿占比(默认 7:3) | +| 按比例 | 主腿/次腿按 `HEDGE_PLAN_OO_BIAS_*` | -拆分口径与比例见 env:`HEDGE_PLAN_OO_BIAS_SPLIT_BY`(`budget` 默认 / `sheets`=先算同张数总张数 `2n` 再拆)、`HEDGE_PLAN_OO_BIAS_RATIO`(默认 `0.7`)。细则见 [对冲计划开发方案.md](./对冲计划开发方案.md) §4.1、[系统说明.md](./系统说明.md)。 +## 7. 常见问题 -## 7. 配置说明 +**Q:以前的期权专用密钥还要配吗?** +- 不需要.统一写到 `OKX_API_*`. -| 变量 | 默认 | 含义 | -|------|------|------| -| `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 | 浮盈/权利金 ≥ 此值推送 | -| `HEDGE_PLAN_OO_BIAS_SPLIT_BY` | budget | 期期做多/做空:按预算或按张数拆 | -| `HEDGE_PLAN_OO_BIAS_RATIO` | 0.7 | 期期做多/做空主腿占比 | - -## 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. 风险说明 - -- 买方最大亏损为 **权利金**;近期实值仍会时间衰减 -- 限价单可能因无流动性未成交 -- 请先在小额下验证兑换,划转,开平仓全流程 +**Q:还能主↔子划转吗?** +- 已移除.只保留同账户内划转与币种兑换. diff --git a/docs/系统设置说明.md b/docs/系统设置说明.md index 6ef8f2b..f7d4857 100644 --- a/docs/系统设置说明.md +++ b/docs/系统设置说明.md @@ -84,7 +84,7 @@ ### 用途 -在 **子账户永续** 场景下,于 **资金账户(funding)** 与 **交易账户(swap)** 之间手动划转 USDT. +在 **OKX 账户** 场景下,于 **资金账户(funding)** 与 **交易账户(swap)** 之间手动划转 USDT. ### 与 env 配置的关系 diff --git a/lib/common/static/instance_embed.js b/lib/common/static/instance_embed.js index cff7115..641763c 100644 --- a/lib/common/static/instance_embed.js +++ b/lib/common/static/instance_embed.js @@ -284,7 +284,7 @@ setSettingsSubTabInUrl("transfer"); return "transfer"; } - if (path.indexOf("/api/options/transfer") >= 0 || path.indexOf("/api/options/cross-transfer") >= 0) { + if (path.indexOf("/api/options/transfer") >= 0) { setSettingsSubTabInUrl("options_transfer"); return "options_transfer"; } diff --git a/lib/common/static/options_settings.js b/lib/common/static/options_settings.js index d051473..6a0895a 100644 --- a/lib/common/static/options_settings.js +++ b/lib/common/static/options_settings.js @@ -6,7 +6,6 @@ const SWAP_BTNS = ["opt-set-swap-btn", "opt-set-swap-all-btn"]; const INT_BTNS = ["opt-set-int-btn", "opt-set-int-all-btn"]; - const CROSS_BTNS = ["opt-set-cross-btn", "opt-set-cross-all-btn"]; async function apiJson(url, opts) { const r = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {})); @@ -269,80 +268,6 @@ }); } - async function submitCrossTransfer(amount) { - setButtonsBusy(CROSS_BTNS, true, "划转中…"); - setMsg("opt-set-cross-msg", "划转中…", false); - try { - const d = await apiJson("/api/options/cross-transfer", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - ccy: document.getElementById("opt-set-cross-ccy").value, - amount: amount, - from_account: document.getElementById("opt-set-cross-from").value, - to_account: document.getElementById("opt-set-cross-to").value, - direction: document.getElementById("opt-set-cross-dir").value, - }), - }); - if (d.ok) { - setMsg("opt-set-cross-msg", "划转成功", false); - refreshFundsAfterMutation(); - } else { - setMsg("opt-set-cross-msg", "划转失败:" + (d.msg || "未知错误"), true); - } - return d; - } catch (e) { - setMsg("opt-set-cross-msg", "划转失败:" + (e.message || "网络错误"), true); - return { ok: false }; - } finally { - setButtonsBusy(CROSS_BTNS, false); - } - } - - const crossBtn = document.getElementById("opt-set-cross-btn"); - if (crossBtn) { - crossBtn.addEventListener("click", async function () { - const amount = parseFloat(document.getElementById("opt-set-cross-amount").value); - if (!amount || amount <= 0) { - setMsg("opt-set-cross-msg", "请输入有效数量", true); - return; - } - await submitCrossTransfer(amount); - }); - } - - const crossAllBtn = document.getElementById("opt-set-cross-all-btn"); - if (crossAllBtn) { - crossAllBtn.addEventListener("click", async function () { - try { - const ccy = document.getElementById("opt-set-cross-ccy").value; - const from = document.getElementById("opt-set-cross-from").value; - const to = document.getElementById("opt-set-cross-to").value; - const direction = document.getElementById("opt-set-cross-dir").value; - const scope = direction === "sub_to_main" ? "sub" : "main"; - const sideLabel = direction === "sub_to_main" ? "子账户" : "主账户"; - const amount = await resolveMaxAmount(from, ccy, scope); - if (!amount) { - setMsg("opt-set-cross-msg", sideLabel + "划出账户可用余额不足", true); - return; - } - const msg = - "确认全部划转?\n\n" + - "方向:" + (direction === "main_to_sub" ? "主 → 子" : "子 → 主") + "\n" + - "币种:" + ccy + "\n" + - "划出:" + sideLabel + " · " + accountLabel(from) + "\n" + - "划入:" + (direction === "main_to_sub" ? "子账户" : "主账户") + " · " + accountLabel(to) + "\n" + - "金额:" + fmtAmt(amount, ccy) + "\n\n" + - "将划转该账户全部可用余额。"; - if (!confirmOk(msg)) return; - document.getElementById("opt-set-cross-amount").value = String(amount); - await submitCrossTransfer(amount); - } catch (e) { - setMsg("opt-set-cross-msg", "划转失败:" + (e.message || "余额拉取失败"), true); - } - }); - } - function hardenAmountAutofill(ids) { ids.forEach(function (id) { const el = document.getElementById(id); @@ -366,7 +291,7 @@ } // 全部划转/兑换前去掉 readonly,避免写不进数量 - ["opt-set-swap-all-btn", "opt-set-int-all-btn", "opt-set-cross-all-btn"].forEach(function (btnId) { + ["opt-set-swap-all-btn", "opt-set-int-all-btn"].forEach(function (btnId) { const btn = document.getElementById(btnId); if (!btn) return; btn.addEventListener( @@ -375,7 +300,6 @@ const map = { "opt-set-swap-all-btn": "opt-set-swap-amount", "opt-set-int-all-btn": "opt-set-int-amount", - "opt-set-cross-all-btn": "opt-set-cross-amount", }; const input = document.getElementById(map[btnId]); if (input) input.removeAttribute("readonly"); @@ -384,5 +308,5 @@ ); }); - hardenAmountAutofill(["opt-set-swap-amount", "opt-set-int-amount", "opt-set-cross-amount"]); + hardenAmountAutofill(["opt-set-swap-amount", "opt-set-int-amount"]); })(); diff --git a/lib/env/env_ui_manifest.py b/lib/env/env_ui_manifest.py index 8e1fc67..1912d28 100644 --- a/lib/env/env_ui_manifest.py +++ b/lib/env/env_ui_manifest.py @@ -20,8 +20,8 @@ from lib.env.env_schema import ( _EXCHANGE_LIVE_FIELDS: dict[str, list[tuple[str, str, str]]] = { "okx": [ ("LIVE_TRADING_ENABLED", "开启实盘下单", "关闭时仅走本地流程,不向交易所发单"), - ("OKX_API_KEY", "API Key", "永续子账户"), - ("OKX_API_SECRET", "API Secret", "永续子账户"), + ("OKX_API_KEY", "API Key", "账户 API(永续+期权共用)"), + ("OKX_API_SECRET", "API Secret", "账户 API(永续+期权共用)"), ("OKX_API_PASSPHRASE", "API Passphrase", "OKX 必填"), ("OKX_TD_MODE", "保证金模式", ""), ("OKX_POS_MODE", "持仓模式", ""), @@ -30,7 +30,7 @@ _EXCHANGE_LIVE_FIELDS: dict[str, list[tuple[str, str, str]]] = { ( "OKX_SHOW_PERP_FUNDS", "显示永续资金", - "默认开启;关闭后顶栏隐藏永续资金账户与交易账户,总资金仅计期权侧", + "默认开启;关闭后顶栏隐藏 USDT 资金账户与交易账户,总资金仅计期权 USDC 侧", ), ], "binance": [ @@ -140,10 +140,7 @@ _OPTIONS_SECTION: dict[str, Any] = { "title": "期权账户", "exchanges": frozenset({"okx"}), "fields": [ - ("OKX_OPTIONS_ENABLED", "启用期权模块", ""), - ("OKX_OPTIONS_API_KEY", "期权 API Key", "主账户,与永续子账户分离"), - ("OKX_OPTIONS_API_SECRET", "期权 API Secret", ""), - ("OKX_OPTIONS_API_PASSPHRASE", "期权 API Passphrase", ""), + ("OKX_OPTIONS_ENABLED", "启用期权模块", "与永续共用上方 OKX_API_*;不再单独配置期权密钥"), ("OKX_OPTIONS_ACCOUNT_LABEL", "期权账户备注", ""), ("OKX_OPTIONS_TRADE_BUDGET_USDC", "单笔预算(USDC)", ""), ("OKX_OPTIONS_BUDGET_BUFFER", "预算缓冲比例", "如 0.95"), diff --git a/lib/exchange/okx_options_lib.py b/lib/exchange/okx_options_lib.py index 58575a0..bc73ce0 100644 --- a/lib/exchange/okx_options_lib.py +++ b/lib/exchange/okx_options_lib.py @@ -72,16 +72,22 @@ def td_mode_for_option_buy(configured: str | None = None) -> str: def create_options_exchange( - api_key: str, - api_secret: str, - passphrase: str, + api_key: str = "", + api_secret: str = "", + passphrase: str = "", proxies: dict[str, str] | None = None, ) -> ccxt.okx: + """创建 option 客户端.未传密钥时读 OKX_API_*(与永续同源).""" + import os + + key = (api_key or os.getenv("OKX_API_KEY") or "").strip() + secret = (api_secret or os.getenv("OKX_API_SECRET") or "").strip() + password = (passphrase or os.getenv("OKX_API_PASSPHRASE") or "").strip() ex = ccxt.okx( { - "apiKey": api_key, - "secret": api_secret, - "password": passphrase, + "apiKey": key, + "secret": secret, + "password": password, "enableRateLimit": True, "options": {"defaultType": "option"}, } @@ -600,7 +606,10 @@ def options_header_balances( *, force: bool = False, ) -> tuple[float | None, float | None, float | None, float | None]: - """顶栏四格:交易 USDC/USDT,资金 USDC/USDT(单次拉取 + 缓存).""" + """顶栏期权两格用 USDC;顺带返回同账户 USDT(调用方勿再计入总资金,避免与永续栏重复). + + 返回:(trading_usdc, funding_usdc, funding_usdt, trading_usdt) + """ bal = fetch_options_balances(ex, force=force) def _round(v: Any) -> float | None: @@ -1562,43 +1571,6 @@ def spot_market_swap_usdt_usdc( return {"ok": False, "msg": _okx_trade_error_message(e)} -def transfer_main_sub_account( - ex: ccxt.okx, - *, - ccy: str, - amount: float, - sub_acct: str, - main_to_sub: bool, - from_account: str = "funding", - to_account: str = "funding", -) -> dict[str, Any]: - """主账户与子账户之间划转(须主账户 API).""" - if amount <= 0: - return {"ok": False, "msg": "划转金额须大于 0"} - sub = (sub_acct or "").strip() - if not sub: - return {"ok": False, "msg": "未配置子账户名称 OKX_SUB_ACCOUNT_NAME"} - from_code = _OKX_ACCT_CODE.get((from_account or "funding").lower(), "6") - to_code = _OKX_ACCT_CODE.get((to_account or "funding").lower(), "6") - try: - resp = ex.private_post_asset_transfer( - { - "type": "1" if main_to_sub else "2", - "ccy": str(ccy).upper(), - "amt": str(amount), - "from": from_code, - "to": to_code, - "subAcct": sub, - } - ) - data = (resp or {}).get("data") or [] - if data and str(data[0].get("sCode", "0")) == "0": - return {"ok": True, "data": data[0], "raw": resp} - return {"ok": False, "msg": _okx_trade_error_message(resp=resp), "raw": resp} - except Exception as e: - return {"ok": False, "msg": _okx_trade_error_message(e)} - - def format_position_row( pos: dict[str, Any], ct_mult: float = 0.01, diff --git a/lib/instance/instance_embed_context_lib.py b/lib/instance/instance_embed_context_lib.py index ca6b78e..a9c5507 100644 --- a/lib/instance/instance_embed_context_lib.py +++ b/lib/instance/instance_embed_context_lib.py @@ -104,12 +104,14 @@ def options_funding_label( funding_usdc: float | None, funding_usdt: float | None = None, ) -> str: - parts: list[str] = [] - if funding_usdc is not None and float(funding_usdc) > 0: - parts.append(f"{float(funding_usdc):.2f} USDC") - if funding_usdt is not None and float(funding_usdt) > 0: - parts.append(f"{float(funding_usdt):.2f} USDT") - return " · ".join(parts) if parts else "—" + """期权侧顶栏仅展示 USDC(USDT 归永续资金/交易账户).funding_usdt 参数保留兼容,忽略.""" + _ = funding_usdt + if funding_usdc is None: + return "—" + try: + return f"{float(funding_usdc):.2f} USDC" + except (TypeError, ValueError): + return "—" def total_funds_usdt( diff --git a/lib/instance/instance_settings_lib.py b/lib/instance/instance_settings_lib.py index 7bee01d..017b301 100644 --- a/lib/instance/instance_settings_lib.py +++ b/lib/instance/instance_settings_lib.py @@ -158,24 +158,20 @@ def build_instance_settings_view( ) if (exchange_key or "").strip().lower() == "okx" and _env_bool("OKX_OPTIONS_ENABLED", False): - opt_key = (os.getenv("OKX_OPTIONS_API_KEY") or "").strip() + api_key = (os.getenv("OKX_API_KEY") or "").strip() sections.append( { "title": "期权设置", "rows": [ _row("期权模块", "已启用"), _row( - "期权 API", - f"已配置(…{opt_key[-4:]})" if len(opt_key) >= 4 else "未配置", - ), - _row( - "子账户", - (os.getenv("OKX_SUB_ACCOUNT_NAME") or "").strip() or "未配置 OKX_SUB_ACCOUNT_NAME", - "主/子账户划转用", + "账户 API", + f"已配置(…{api_key[-4:]})" if len(api_key) >= 4 else "未配置 OKX_API_*", + "永续与期权共用 OKX_API_*", ), _row( "说明", - "币种兑换与账户划转到右侧「期权设置」卡片操作", + "币种兑换与账户内划转到右侧「期权设置」卡片操作", ), ], } @@ -196,7 +192,6 @@ def build_instance_settings_view( "show_transfer": (exchange_key or "").strip().lower() in ("gate", "binance", "okx"), "options_settings_enabled": (exchange_key or "").strip().lower() == "okx" and _env_bool("OKX_OPTIONS_ENABLED", False), - "options_sub_account": (os.getenv("OKX_SUB_ACCOUNT_NAME") or "").strip(), "auto_transfer_enabled": auto_transfer_on, "auto_transfer_bj_hour": _env_int("AUTO_TRANSFER_BJ_HOUR", 8), "auto_transfer_amount": _env_float("AUTO_TRANSFER_AMOUNT", 30), diff --git a/lib/instance/templates/embed_boot_scripts.html b/lib/instance/templates/embed_boot_scripts.html index af052d7..1d02345 100644 --- a/lib/instance/templates/embed_boot_scripts.html +++ b/lib/instance/templates/embed_boot_scripts.html @@ -1137,10 +1137,11 @@ function paintRealtimePnlFromSnapshot(data){ } function formatOptionsFundingLabel(usdc, usdt) { - const parts = []; - if (usdc !== null && usdc !== undefined && Number(usdc) > 0) parts.push(`${Number(usdc).toFixed(2)} USDC`); - if (usdt !== null && usdt !== undefined && Number(usdt) > 0) parts.push(`${Number(usdt).toFixed(2)} USDT`); - return parts.length ? parts.join(" · ") : "—"; + // 期权侧顶栏仅 USDC;usdt 参数忽略(USDT 在永续资金/交易账户) + if (usdc === null || usdc === undefined || usdc === "") return "—"; + const n = Number(usdc); + if (Number.isNaN(n)) return "—"; + return `${n.toFixed(2)} USDC`; } function setFundsFieldText(field, text){ diff --git a/lib/instance/templates/index.html b/lib/instance/templates/index.html index 8e621e1..a3836d9 100644 --- a/lib/instance/templates/index.html +++ b/lib/instance/templates/index.html @@ -1614,10 +1614,11 @@ function paintRealtimePnlFromSnapshot(data){ } function formatOptionsFundingLabel(usdc, usdt) { - const parts = []; - if (usdc !== null && usdc !== undefined && Number(usdc) > 0) parts.push(`${Number(usdc).toFixed(2)} USDC`); - if (usdt !== null && usdt !== undefined && Number(usdt) > 0) parts.push(`${Number(usdt).toFixed(2)} USDT`); - return parts.length ? parts.join(" · ") : "—"; + // 期权侧顶栏仅 USDC;usdt 参数忽略(USDT 在永续资金/交易账户) + if(usdc == null || usdc === "") return "—"; + const n = Number(usdc); + if(Number.isNaN(n)) return "—"; + return `${n.toFixed(2)} USDC`; } function setFundsFieldText(field, text){ diff --git a/lib/instance/templates/instance_header_stats.html b/lib/instance/templates/instance_header_stats.html index a553391..6c5083b 100644 --- a/lib/instance/templates/instance_header_stats.html +++ b/lib/instance/templates/instance_header_stats.html @@ -35,11 +35,11 @@ {% if options_enabled %}
子账户永续:资金账户与交易账户之间划转 USDT.
+账户内:资金账户与交易账户之间划转 USDT.
{% include 'instance_transfer_panel.html' %} {% elif tab.key == 'export' %}crypto_monitor_okx/.env 设置 OKX_OPTIONS_ENABLED=true 及主账户 OKX_OPTIONS_API_*,然后 pm2 restart crypto_okx --update-env.crypto_monitor_okx/.env 设置 OKX_OPTIONS_ENABLED=true 及 OKX_API_*(永续与期权共用),然后 pm2 restart crypto_okx --update-env.