diff --git a/crypto_monitor_okx/.env.example b/crypto_monitor_okx/.env.example
index 20a7890..36607f7 100644
--- a/crypto_monitor_okx/.env.example
+++ b/crypto_monitor_okx/.env.example
@@ -135,7 +135,7 @@ HEDGE_PLAN_ON_PERP_TP_CLOSE_OPTIONS=false
HEDGE_PLAN_OO_CLOSE_WINNER_ONLY=true
# 方案C:期期页面显示「平仓模式」(到期平/全平);关则固定到期平.默认开启,页面默认选全平
HEDGE_PLAN_OO_CLOSE_MODE_ENABLED=true
-# 期期「做多/做空」拆分口径:budget=按权利金预算(默认);sheets=先算同张数 n 再按比例拆
+# 期期「做多/做空」拆分口径:budget=按权利金预算(默认);sheets=先算同张数总张数(2n)再按比例拆
HEDGE_PLAN_OO_BIAS_SPLIT_BY=budget
# 期期「做多/做空」主腿占比(0~1,默认 0.7=7:3);做多主腿=Call,做空主腿=Put
HEDGE_PLAN_OO_BIAS_RATIO=0.7
diff --git a/docs/对冲计划开发方案.md b/docs/对冲计划开发方案.md
index 219c1fa..28da05b 100644
--- a/docs/对冲计划开发方案.md
+++ b/docs/对冲计划开发方案.md
@@ -98,7 +98,7 @@
- **同张数**(默认):最大 `n` 使 `n×(cost_A+cost_B) ≤ B`,两腿均填 `n`
- **做多 / 做空**:须一 Call 一 Put;主:次默认 **7:3**(`HEDGE_PLAN_OO_BIAS_RATIO`,可改)
- 做多:主腿=Call;做空:主腿=Put
- - 拆分口径 `HEDGE_PLAN_OO_BIAS_SPLIT_BY`:`budget`(默认,按权利金预算拆) / `sheets`(先算同张数 `n` 再按比例拆张数)
+ - 拆分口径 `HEDGE_PLAN_OO_BIAS_SPLIT_BY`:`budget`(默认,按权利金预算拆) / `sheets`(先按同张数得每腿 `n`,总张数 `2n` 再按比例拆到 Call/Put)
- 另受各自卖一深度上限约束
- 已移除页面「均分」;后端仍兼容旧 `split_budget` 入参(预算对半)
diff --git a/docs/更新文档.md b/docs/更新文档.md
index ce895cf..590733d 100644
--- a/docs/更新文档.md
+++ b/docs/更新文档.md
@@ -4,6 +4,25 @@
---
+## 2026-07-19 · 修复期期「按张数」拆分:用同张数总张数 2n
+
+### 修改原因
+
+`sheets` 口径误把同张数每腿 `n` 当总张数拆,规模偏小;应对齐「先算完同张数两侧合计总张数 `2n`,再按比例拆」。
+
+### 修改的地方
+
+| 文件 | 改动摘要 |
+|------|----------|
+| `hedge_plan_calc_lib.py` / `hedge_plan.js` | `total = n_same * 2` 再拆 |
+| 相关 docs / env 文案 | 口径说明改为总张数 `2n` |
+
+### 交付之后的验收
+
+同张数 `n=5` 时,`sheets`+做空(0.7) → Put 7 / Call 3(合计 10)。
+
+---
+
## 2026-07-19 · 期期张数:做多/做空替代均分 + env 拆分口径
### 修改原因
@@ -25,7 +44,7 @@
1. 默认仍为同张数。
2. 做多=Call 主占比、做空=Put 主占比;默认比例 0.7,口径默认预算金额。
-3. `sheets` 口径:先算同张数 `n` 再拆。
+3. `sheets` 口径:先算同张数每腿 `n`,总张数 `2n` 再拆(见上一条修正)。
### 交付之后的验收
diff --git a/docs/期权用法.md b/docs/期权用法.md
index 8466b2a..7faec5d 100644
--- a/docs/期权用法.md
+++ b/docs/期权用法.md
@@ -96,7 +96,7 @@ OKX_OPTIONS_API_PASSPHRASE=...
| 做多 | Call:Put 按主腿占比(默认 7:3) |
| 做空 | Put:Call 按主腿占比(默认 7:3) |
-拆分口径与比例见 env:`HEDGE_PLAN_OO_BIAS_SPLIT_BY`(`budget` 默认 / `sheets`)、`HEDGE_PLAN_OO_BIAS_RATIO`(默认 `0.7`)。细则见 [对冲计划开发方案.md](./对冲计划开发方案.md) §4.1、[系统说明.md](./系统说明.md)。
+拆分口径与比例见 env:`HEDGE_PLAN_OO_BIAS_SPLIT_BY`(`budget` 默认 / `sheets`=先算同张数总张数 `2n` 再拆)、`HEDGE_PLAN_OO_BIAS_RATIO`(默认 `0.7`)。细则见 [对冲计划开发方案.md](./对冲计划开发方案.md) §4.1、[系统说明.md](./系统说明.md)。
## 7. 配置说明
diff --git a/docs/系统说明.md b/docs/系统说明.md
index f6e0b26..c116bf3 100644
--- a/docs/系统说明.md
+++ b/docs/系统说明.md
@@ -129,7 +129,7 @@
逻辑摘要:
- **同张数**:最大 `n` 使两腿各 `n` 张且总权利金 ≤ 预算。
-- **做多 / 做空**:主腿与次腿按 env 比例(默认 7:3)分配;做多主腿=Call,做空主腿=Put。拆分口径由 `HEDGE_PLAN_OO_BIAS_SPLIT_BY` 决定:`budget`(默认,按权利金预算拆)或 `sheets`(先算同张数 `n` 再拆张数)。
+- **做多 / 做空**:主腿与次腿按 env 比例(默认 7:3)分配;做多主腿=Call,做空主腿=Put。拆分口径由 `HEDGE_PLAN_OO_BIAS_SPLIT_BY` 决定:`budget`(默认,按权利金预算拆)或 `sheets`(先按同张数算出每腿 `n`,总张数 `2n` 再按比例拆到 Call/Put)。
- 达目标价:通常只平盈利腿。
- **全平**:盈利腿平掉后立刻尝试清另一腿(无 2× 权利金门控,失败会重试)。
- **到期平**:残腿持有至到期再结。
diff --git a/lib/common/static/hedge_plan.js b/lib/common/static/hedge_plan.js
index adc8404..b7a8f45 100644
--- a/lib/common/static/hedge_plan.js
+++ b/lib/common/static/hedge_plan.js
@@ -364,9 +364,11 @@
if (!(ratio > 0) || !(ratio < 1)) ratio = 0.7;
const splitBy = state.ooBiasSplitBy === "sheets" ? "sheets" : "budget";
const pair = costA + costB;
- let nSame = pair > 0 ? Math.floor(budget / pair + 1e-12) : 0;
- nSame = capByDepth(nSame, state.legA && state.legA.ask_sz);
- nSame = capByDepth(nSame, state.legB && state.legB.ask_sz);
+ const nPair = pair > 0 ? Math.floor(budget / pair + 1e-12) : 0;
+ const nSame = Math.min(
+ capByDepth(nPair, state.legA && state.legA.ask_sz),
+ capByDepth(nPair, state.legB && state.legB.ask_sz)
+ );
let nA = 0;
let nB = 0;
@@ -381,12 +383,14 @@
let nCall = 0;
let nPut = 0;
if (splitBy === "sheets") {
- if (nSame < 2) {
- return { sheetsA: 0, sheetsB: 0, premium: 0, ok: false, msg: "同张数规模不足 2,无法按比例拆分" };
+ // 总张数 = 同张数两侧合计(每腿 n → 共 2n),再按比例拆
+ const total = nSame * 2;
+ if (total < 2) {
+ return { sheetsA: 0, sheetsB: 0, premium: 0, ok: false, msg: "同张数总规模不足 2,无法按比例拆分" };
}
- let majorN = Math.round(nSame * ratio);
- majorN = Math.max(1, Math.min(majorN, nSame - 1));
- const minorN = nSame - majorN;
+ let majorN = Math.round(total * ratio);
+ majorN = Math.max(1, Math.min(majorN, total - 1));
+ const minorN = total - majorN;
nCall = majorIsCall ? majorN : minorN;
nPut = majorIsCall ? minorN : majorN;
} else {
diff --git a/lib/env/env_ui_manifest.py b/lib/env/env_ui_manifest.py
index 14134d4..dc599cd 100644
--- a/lib/env/env_ui_manifest.py
+++ b/lib/env/env_ui_manifest.py
@@ -149,7 +149,7 @@ _HEDGE_PLAN_SECTION: dict[str, Any] = {
(
"HEDGE_PLAN_OO_BIAS_SPLIT_BY",
"期期做多做空拆分口径",
- "默认预算金额;budget=按权利金预算按比例分两腿;sheets=先算同张数 n 再按比例拆张数",
+ "默认预算金额;budget=按权利金预算按比例分两腿;sheets=先算同张数总张数(2n)再按比例拆",
),
(
"HEDGE_PLAN_OO_BIAS_RATIO",
diff --git a/lib/hedge_plan/hedge_plan_calc_lib.py b/lib/hedge_plan/hedge_plan_calc_lib.py
index 44a114b..2d8d91d 100644
--- a/lib/hedge_plan/hedge_plan_calc_lib.py
+++ b/lib/hedge_plan/hedge_plan_calc_lib.py
@@ -243,9 +243,12 @@ def suggest_oo_sheets(
return _fail("缺少有效卖一价,无法建议张数")
pair = cost_a + cost_b
- n_same = int(math.floor(budget / pair + 1e-12)) if pair > 0 else 0
- n_same = _cap_sheets_by_ask_depth(n_same, ask_sz_a)
- n_same = _cap_sheets_by_ask_depth(n_same, ask_sz_b)
+ n_pair = int(math.floor(budget / pair + 1e-12)) if pair > 0 else 0
+ # 与同张数一致:先按预算得 n,再各自深度封顶后取 min
+ n_same = min(
+ _cap_sheets_by_ask_depth(n_pair, ask_sz_a),
+ _cap_sheets_by_ask_depth(n_pair, ask_sz_b),
+ )
if m == "same_sheets":
n_a = n_same
@@ -262,11 +265,13 @@ def suggest_oo_sheets(
return _fail(err)
major_is_call = m == "long_bias"
if split_by == "sheets":
- if n_same < 2:
- return _fail("同张数规模不足 2,无法按比例拆分")
- major_n = int(round(n_same * ratio))
- major_n = max(1, min(major_n, n_same - 1))
- minor_n = n_same - major_n
+ # 总张数 = 同张数两侧合计(每腿 n → 共 2n),再按比例拆到 Call/Put
+ total = int(n_same) * 2
+ if total < 2:
+ return _fail("同张数总规模不足 2,无法按比例拆分")
+ major_n = int(round(total * ratio))
+ major_n = max(1, min(major_n, total - 1))
+ minor_n = total - major_n
n_call = major_n if major_is_call else minor_n
n_put = minor_n if major_is_call else major_n
else:
diff --git a/lib/hedge_plan/templates/hedge_plan_panel.html b/lib/hedge_plan/templates/hedge_plan_panel.html
index 79f2d85..c59694f 100644
--- a/lib/hedge_plan/templates/hedge_plan_panel.html
+++ b/lib/hedge_plan/templates/hedge_plan_panel.html
@@ -301,4 +301,4 @@
-
+
diff --git a/tests/test_hedge_plan_calc.py b/tests/test_hedge_plan_calc.py
index 982827a..6bf1724 100644
--- a/tests/test_hedge_plan_calc.py
+++ b/tests/test_hedge_plan_calc.py
@@ -173,7 +173,7 @@ class TestHedgePlanCalc(unittest.TestCase):
self.assertTrue(s["ok"])
def test_suggest_oo_short_bias_sheets(self):
- # same n=5; short → put major 4 / call minor 1 (round 5*0.7=4)
+ # 同张数 n=5 → 总张数 10; short → put 7 / call 3
s = suggest_oo_sheets(
mode="short_bias",
budget_usdc=10,
@@ -187,8 +187,8 @@ class TestHedgePlanCalc(unittest.TestCase):
bias_ratio=0.7,
)
self.assertEqual(s["mode"], "short_bias")
- self.assertEqual(s["sheets_a"], 1)
- self.assertEqual(s["sheets_b"], 4)
+ self.assertEqual(s["sheets_a"], 3)
+ self.assertEqual(s["sheets_b"], 7)
def test_suggest_oo_bias_requires_call_put(self):
s = suggest_oo_sheets(