单独期权增加全仓复利模式:可选上限开关,仅允许一仓
用期权户全部可用×缓冲开仓,默认不设上限;开启上限后按 env 封顶,全仓时禁止已有持仓再开。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6875,6 +6875,13 @@ def render_main_page(page="trade", embed_mode=None):
|
||||
hedge_plan_budget_buffer=float(os.getenv("HEDGE_PLAN_BUDGET_BUFFER") or "0.95"),
|
||||
options_trade_budget=OKX_OPTIONS_TRADE_BUDGET_USDC,
|
||||
options_budget_buffer=float(os.getenv("OKX_OPTIONS_BUDGET_BUFFER") or "0.95"),
|
||||
options_compound_full_cap_enabled=os.getenv(
|
||||
"OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", "false"
|
||||
).lower()
|
||||
in ("1", "true", "yes", "on"),
|
||||
options_compound_full_cap_usdc=float(
|
||||
os.getenv("OKX_OPTIONS_COMPOUND_FULL_CAP_USDC") or "300"
|
||||
),
|
||||
options_default_underly=OKX_OPTIONS_DEFAULT_UNDERLY,
|
||||
options_chain_ask_liq_filter=os.getenv(
|
||||
"OKX_OPTIONS_CHAIN_ASK_LIQ_FILTER_ENABLED", "true"
|
||||
|
||||
@@ -281,14 +281,26 @@
|
||||
const sheetsEl = document.getElementById("opt-sheets-amount");
|
||||
const ethEl = document.getElementById("opt-eth-amount");
|
||||
const hint = document.getElementById("opt-budget-full-hint");
|
||||
const compoundHint = document.getElementById("opt-compound-full-hint");
|
||||
const capEl = document.getElementById("opt-budget-full-cap");
|
||||
const compoundCapLine = document.getElementById("opt-compound-cap-line");
|
||||
if (sheetsEl) sheetsEl.style.display = mode === "sheets" ? "" : "none";
|
||||
if (ethEl) ethEl.style.display = mode === "eth_amount" ? "" : "none";
|
||||
if (hint) hint.style.display = mode === "budget_full" ? "" : "none";
|
||||
if (compoundHint) compoundHint.style.display = mode === "compound_full" ? "" : "none";
|
||||
if (capEl && root && root.dataset.tradeBudget) {
|
||||
const n = Number(root.dataset.tradeBudget);
|
||||
if (Number.isFinite(n) && n > 0) capEl.textContent = n.toFixed(2);
|
||||
}
|
||||
if (compoundCapLine && root) {
|
||||
const on = String(root.dataset.compoundCapEnabled || "") === "1";
|
||||
const cap = Number(root.dataset.compoundCapUsdc);
|
||||
if (on && Number.isFinite(cap) && cap > 0) {
|
||||
compoundCapLine.textContent = "全仓上限已开启:" + cap.toFixed(2) + "U";
|
||||
} else {
|
||||
compoundCapLine.textContent = "全仓上限关闭(env可开)";
|
||||
}
|
||||
}
|
||||
document.querySelectorAll(".opt-size-mode-chip").forEach(function (chip) {
|
||||
const radio = chip.querySelector('input[name="opt-size-mode"]');
|
||||
chip.classList.toggle("is-selected", !!(radio && radio.checked));
|
||||
|
||||
Vendored
+11
-1
@@ -144,7 +144,17 @@ _OPTIONS_SECTION: dict[str, Any] = {
|
||||
("OKX_OPTIONS_ENABLED", "启用期权模块", "与永续共用上方 OKX_API_*;不再单独配置期权密钥"),
|
||||
("OKX_OPTIONS_ACCOUNT_LABEL", "期权账户备注", ""),
|
||||
("OKX_OPTIONS_TRADE_BUDGET_USDC", "单笔预算(USDC)", ""),
|
||||
("OKX_OPTIONS_BUDGET_BUFFER", "预算缓冲比例", "如 0.95"),
|
||||
("OKX_OPTIONS_BUDGET_BUFFER", "预算缓冲比例", "如 0.95;打满/全仓复利共用"),
|
||||
(
|
||||
"OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED",
|
||||
"全仓复利上限开关",
|
||||
"默认 false=关闭上限,用期权户全部可用;true 时按下方上限封顶",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COMPOUND_FULL_CAP_USDC",
|
||||
"全仓复利上限(USDC)",
|
||||
"仅上限开关开启时生效;例如 300",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_MAX_ACTIVE_POSITIONS",
|
||||
"期权持仓上限(笔)",
|
||||
|
||||
@@ -119,3 +119,26 @@ def option_position_limit_block_msg(
|
||||
f"请将 OKX_OPTIONS_MAX_ACTIVE_POSITIONS 设为 0(不限制)或不小于 {active + need},或先平仓"
|
||||
)
|
||||
return f"期权持仓已达上限({active}/{mx}),请先平仓后再开"
|
||||
|
||||
|
||||
def compound_full_single_position_block_msg(
|
||||
ex: Any,
|
||||
*,
|
||||
fetch_positions=None,
|
||||
) -> Optional[str]:
|
||||
"""全仓复利:账户内已有任意期权持仓则禁止再开(仅允许 1 笔)."""
|
||||
fetch = fetch_positions
|
||||
if fetch is None:
|
||||
from lib.exchange.okx_options_lib import fetch_option_positions
|
||||
|
||||
fetch = fetch_option_positions
|
||||
try:
|
||||
rows = fetch(ex)
|
||||
except Exception:
|
||||
rows = None
|
||||
if rows is None:
|
||||
return "无法获取期权持仓,全仓复利模式暂不可开仓"
|
||||
active = count_live_option_positions(rows)
|
||||
if active >= 1:
|
||||
return f"全仓复利模式仅允许同时持有 1 笔仓位(当前 {active} 笔),请先平仓"
|
||||
return None
|
||||
|
||||
@@ -264,6 +264,25 @@ def resolve_budget_full_usdc(trading_usdc: float, trade_budget_usdc: float) -> f
|
||||
return min(float(trading_usdc), float(trade_budget_usdc))
|
||||
|
||||
|
||||
def resolve_compound_full_usdc(
|
||||
trading_usdc: float,
|
||||
*,
|
||||
cap_enabled: bool = False,
|
||||
cap_usdc: float | None = None,
|
||||
) -> float:
|
||||
"""全仓复利:默认用期权交易户全部可用;上限开关开启时再封顶."""
|
||||
bal = max(0.0, float(trading_usdc or 0))
|
||||
if not cap_enabled:
|
||||
return bal
|
||||
try:
|
||||
cap = float(cap_usdc) if cap_usdc is not None else 0.0
|
||||
except (TypeError, ValueError):
|
||||
cap = 0.0
|
||||
if cap <= 0:
|
||||
return bal
|
||||
return min(bal, cap)
|
||||
|
||||
|
||||
def calc_order_size(
|
||||
*,
|
||||
quote_per_unit: float,
|
||||
|
||||
@@ -103,6 +103,8 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
||||
"render_main_page": app_module.render_main_page,
|
||||
"trade_budget": _env_float("OKX_OPTIONS_TRADE_BUDGET_USDC", 10.0),
|
||||
"budget_buffer": _env_float("OKX_OPTIONS_BUDGET_BUFFER", 0.95),
|
||||
"compound_full_cap_enabled": _env_bool("OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", False),
|
||||
"compound_full_cap_usdc": _env_float("OKX_OPTIONS_COMPOUND_FULL_CAP_USDC", 300.0),
|
||||
"default_underly": (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper(),
|
||||
"max_dte_days": _env_float("OKX_OPTIONS_MAX_DTE_DAYS", 2.0),
|
||||
"chain_max_dte_days": _env_float("OKX_OPTIONS_CHAIN_MAX_DTE_DAYS", 14.0),
|
||||
@@ -174,6 +176,34 @@ def _budget_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
|
||||
return resolve_budget_full_usdc(trading, float(cap)), ""
|
||||
|
||||
|
||||
def _compound_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
|
||||
"""全仓复利 = 期权交易户可用(可选上限封顶);再由 calc_order_size × budget_buffer."""
|
||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||
from lib.options.options_pricing_lib import resolve_compound_full_usdc
|
||||
|
||||
raw = fetch_options_trading_usdc(ex)
|
||||
if raw is None or float(raw) <= 0:
|
||||
return None, "交易账户 USDC 可用余额不足"
|
||||
trading = float(raw)
|
||||
# 额度热更读 env(与模板启动值无关)
|
||||
cap_on = _env_bool("OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED", False)
|
||||
cap_v = _env_float("OKX_OPTIONS_COMPOUND_FULL_CAP_USDC", 300.0)
|
||||
if cap_on and cap_v <= 0:
|
||||
return None, "全仓上限无效(OKX_OPTIONS_COMPOUND_FULL_CAP_USDC)"
|
||||
return (
|
||||
resolve_compound_full_usdc(
|
||||
trading,
|
||||
cap_enabled=cap_on,
|
||||
cap_usdc=cap_v,
|
||||
),
|
||||
"",
|
||||
)
|
||||
|
||||
|
||||
def _is_budget_mode(mode: str) -> bool:
|
||||
return mode in ("budget_full", "compound_full")
|
||||
|
||||
|
||||
def _open_premium_paid(cfg: dict[str, Any], inst_id: str) -> float | None:
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
@@ -440,6 +470,14 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
budget_cap = budget
|
||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||
|
||||
available_usdc = fetch_options_trading_usdc(ex)
|
||||
elif mode == "compound_full":
|
||||
budget, budget_err = _compound_full_usdc(cfg, ex)
|
||||
if budget is None:
|
||||
return jsonify({"ok": False, "msg": budget_err})
|
||||
budget_cap = budget
|
||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||
|
||||
available_usdc = fetch_options_trading_usdc(ex)
|
||||
eth_amount = None
|
||||
try:
|
||||
@@ -471,6 +509,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
},
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": budget if mode == "budget_full" else None,
|
||||
"compound_full_usdc": budget if mode == "compound_full" else None,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
@@ -507,6 +546,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
},
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": budget if mode == "budget_full" else None,
|
||||
"compound_full_usdc": budget if mode == "compound_full" else None,
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
@@ -531,9 +571,39 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
},
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": budget if mode == "budget_full" else None,
|
||||
"compound_full_usdc": budget if mode == "compound_full" else None,
|
||||
}
|
||||
)
|
||||
from lib.options.options_position_limit_lib import option_position_limit_block_msg
|
||||
from lib.options.options_position_limit_lib import (
|
||||
compound_full_single_position_block_msg,
|
||||
option_position_limit_block_msg,
|
||||
)
|
||||
|
||||
if mode == "compound_full":
|
||||
compound_block = compound_full_single_position_block_msg(
|
||||
ex, fetch_positions=cfg.get("fetch_option_positions")
|
||||
)
|
||||
if compound_block:
|
||||
return jsonify(
|
||||
{
|
||||
**q,
|
||||
"ok": True,
|
||||
"can_open": False,
|
||||
"msg": compound_block,
|
||||
"quote_per_unit": ask,
|
||||
"premium_per_sheet": None,
|
||||
"sizing": {
|
||||
"ok": False,
|
||||
"msg": compound_block,
|
||||
"sheets": 0,
|
||||
"eth_amount": 0.0,
|
||||
"total_premium": 0.0,
|
||||
},
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": None,
|
||||
"compound_full_usdc": budget,
|
||||
}
|
||||
)
|
||||
|
||||
pos_limit_msg = option_position_limit_block_msg(
|
||||
ex,
|
||||
@@ -558,17 +628,18 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
},
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": budget if mode == "budget_full" else None,
|
||||
"compound_full_usdc": budget if mode == "compound_full" else None,
|
||||
}
|
||||
)
|
||||
sizing = calc_order_size(
|
||||
quote_per_unit=float(ask),
|
||||
ct_mult=float(ct_mult),
|
||||
min_sz=int(min_sz),
|
||||
budget_usdc=budget if mode == "budget_full" else None,
|
||||
budget_usdc=budget if _is_budget_mode(mode) else None,
|
||||
budget_buffer=cfg["budget_buffer"],
|
||||
eth_amount=eth_amount if mode == "eth_amount" else None,
|
||||
sheets=sheet_count if mode == "sheets" else None,
|
||||
budget_cap=budget_cap if mode in ("budget_full", "sheets", "eth_amount") else None,
|
||||
budget_cap=budget_cap if mode in ("budget_full", "compound_full", "sheets", "eth_amount") else None,
|
||||
)
|
||||
if sizing.get("ok"):
|
||||
capped, cap_msg = cap_option_buy_sheets_to_ask_depth(
|
||||
@@ -590,7 +661,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
ct_mult=float(ct_mult),
|
||||
min_sz=int(min_sz),
|
||||
sheets=capped,
|
||||
budget_cap=budget_cap if mode in ("budget_full", "sheets", "eth_amount") else None,
|
||||
budget_cap=budget_cap if mode in ("budget_full", "compound_full", "sheets", "eth_amount") else None,
|
||||
)
|
||||
if sizing.get("ok"):
|
||||
sizing["ask_depth_capped"] = True
|
||||
@@ -612,6 +683,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
"sizing": sizing,
|
||||
"available_usdc": available_usdc,
|
||||
"budget_full_usdc": budget if mode == "budget_full" else None,
|
||||
"compound_full_usdc": budget if mode == "compound_full" else None,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -672,7 +744,17 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
"ref_ask": q.get("ref_ask"),
|
||||
}
|
||||
)
|
||||
from lib.options.options_position_limit_lib import option_position_limit_block_msg
|
||||
from lib.options.options_position_limit_lib import (
|
||||
compound_full_single_position_block_msg,
|
||||
option_position_limit_block_msg,
|
||||
)
|
||||
|
||||
if mode == "compound_full":
|
||||
compound_block = compound_full_single_position_block_msg(
|
||||
ex, fetch_positions=cfg.get("fetch_option_positions")
|
||||
)
|
||||
if compound_block:
|
||||
return jsonify({"ok": False, "msg": compound_block, "can_open": False})
|
||||
|
||||
pos_limit_msg = option_position_limit_block_msg(
|
||||
ex,
|
||||
@@ -702,15 +784,20 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
if budget is None:
|
||||
return jsonify({"ok": False, "msg": budget_err})
|
||||
budget_cap = budget
|
||||
elif mode == "compound_full":
|
||||
budget, budget_err = _compound_full_usdc(cfg, ex)
|
||||
if budget is None:
|
||||
return jsonify({"ok": False, "msg": budget_err})
|
||||
budget_cap = budget
|
||||
sizing = calc_order_size(
|
||||
quote_per_unit=float(ask),
|
||||
ct_mult=ct_mult,
|
||||
min_sz=min_sz,
|
||||
budget_usdc=budget if mode == "budget_full" else None,
|
||||
budget_usdc=budget if _is_budget_mode(mode) else None,
|
||||
budget_buffer=cfg["budget_buffer"],
|
||||
eth_amount=eth_amount,
|
||||
sheets=sheet_count,
|
||||
budget_cap=budget_cap if mode in ("budget_full", "sheets", "eth_amount") else None,
|
||||
budget_cap=budget_cap if mode in ("budget_full", "compound_full", "sheets", "eth_amount") else None,
|
||||
)
|
||||
if not sizing.get("ok"):
|
||||
return jsonify({"ok": False, "msg": sizing.get("msg") or "张数计算失败", "sizing": sizing})
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
data-default-underly="{{ options_default_underly | default('ETH') }}"
|
||||
data-budget-buffer="{{ options_budget_buffer | default(0.95) }}"
|
||||
data-trade-budget="{{ options_trade_budget | default(10) }}"
|
||||
data-compound-cap-enabled="{% if options_compound_full_cap_enabled %}1{% else %}0{% endif %}"
|
||||
data-compound-cap-usdc="{{ '%.2f'|format(options_compound_full_cap_usdc|default(300)|float) }}"
|
||||
data-ask-liq-filter="{% if options_chain_ask_liq_filter is defined %}{{ '1' if options_chain_ask_liq_filter else '0' }}{% else %}1{% endif %}">
|
||||
{% if not options_enabled %}
|
||||
<div class="flash" style="margin-bottom:12px">期权未启用:请在 <code>crypto_monitor_okx/.env</code> 设置 <code>OKX_OPTIONS_ENABLED=true</code> 及 <code>OKX_API_*</code>(永续与期权共用),然后 <code>pm2 restart crypto_okx --update-env</code>.</div>
|
||||
@@ -23,6 +25,7 @@
|
||||
<li><strong>开仓只认真实卖一价且卖一深度≥1</strong>;无深度时面板显示参考标记价并禁用买入。</li>
|
||||
<li>链展示近 <span id="opt-chain-dte">14</span> 日到期;列表与 T 型默认<strong>平值 + 实值3档 + 虚值3档</strong>,勾选「展开全部」看全部行权价(若当前为实值/虚值筛选会自动切回「全部」)。</li>
|
||||
<li>「按可用余额打满」可用额度 = min(交易户可用 USDC, 单笔预算 <strong id="opt-trade-budget">{{ '%.2f'|format(options_trade_budget|default(10)|float) }}</strong>),再 × 预算缓冲 <strong id="opt-budget-buf">{{ '%.2f'|format(options_budget_buffer|default(0.95)|float) }}</strong> 算张数(env 可改)。</li>
|
||||
<li>「全仓复利」用期权交易户<strong>全部可用</strong>×缓冲开仓(不受单笔预算限制);可选开启全仓上限;该模式下仅允许同时 1 笔持仓。</li>
|
||||
<li>平仓仅买一限价,详见说明文档。</li>
|
||||
</ul>
|
||||
<p><a href="/options/guide" target="_blank" rel="noopener">打开《期权开平仓与监控说明》</a></p>
|
||||
@@ -131,6 +134,10 @@
|
||||
<input type="radio" name="opt-size-mode" value="budget_full">
|
||||
<span>按可用余额打满</span>
|
||||
</label>
|
||||
<label class="btn-secondary opt-order-chip opt-size-mode-chip">
|
||||
<input type="radio" name="opt-size-mode" value="compound_full">
|
||||
<span>全仓复利</span>
|
||||
</label>
|
||||
<label class="btn-secondary opt-order-chip opt-size-mode-chip">
|
||||
<input type="radio" name="opt-size-mode" value="eth_amount" id="opt-size-mode-eth">
|
||||
<span>指定币数量</span>
|
||||
@@ -141,6 +148,9 @@
|
||||
<p class="muted opt-budget-full-hint" id="opt-budget-full-hint" style="display:none;margin:6px 0 0;font-size:.82rem;line-height:1.4">
|
||||
余额 > 单笔预算(<span id="opt-budget-full-cap">{{ '%.2f'|format(options_trade_budget|default(10)|float) }}</span>U)时按预算;余额不足时按余额;再乘预算缓冲算张数。
|
||||
</p>
|
||||
<p class="muted opt-compound-full-hint" id="opt-compound-full-hint" style="display:none;margin:6px 0 0;font-size:.82rem;line-height:1.4">
|
||||
用期权交易户全部可用×缓冲开仓;不受单笔预算限制。<span id="opt-compound-cap-line">全仓上限关闭</span>。仅允许同时持有 1 笔仓位。
|
||||
</p>
|
||||
<input type="text" id="opt-signal-note" name="opt_signal_note" class="opt-signal-note" placeholder="备注(关键位说明)"
|
||||
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||
data-lpignore="true" data-1p-ignore="true" data-form-type="other" readonly>
|
||||
@@ -324,4 +334,4 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||
<script src="/static/options_panel.js?v=56"></script>
|
||||
<script src="/static/options_panel.js?v=57"></script>
|
||||
|
||||
@@ -1,16 +1,69 @@
|
||||
"""按可用余额打满:min(余额, 单笔预算)."""
|
||||
"""按可用余额打满 / 全仓复利定仓."""
|
||||
from __future__ import annotations
|
||||
|
||||
from lib.options.options_pricing_lib import resolve_budget_full_usdc
|
||||
import unittest
|
||||
|
||||
from lib.options.options_pricing_lib import (
|
||||
resolve_budget_full_usdc,
|
||||
resolve_compound_full_usdc,
|
||||
)
|
||||
from lib.options.options_position_limit_lib import (
|
||||
compound_full_single_position_block_msg,
|
||||
count_live_option_positions,
|
||||
)
|
||||
|
||||
|
||||
def test_balance_above_budget_uses_budget():
|
||||
assert resolve_budget_full_usdc(100.0, 10.0) == 10.0
|
||||
class TestOptionsBudgetModes(unittest.TestCase):
|
||||
def test_balance_above_budget_uses_budget(self):
|
||||
self.assertEqual(resolve_budget_full_usdc(100.0, 10.0), 10.0)
|
||||
|
||||
def test_balance_below_budget_uses_balance(self):
|
||||
self.assertEqual(resolve_budget_full_usdc(5.0, 10.0), 5.0)
|
||||
|
||||
def test_balance_equals_budget(self):
|
||||
self.assertEqual(resolve_budget_full_usdc(10.0, 10.0), 10.0)
|
||||
|
||||
def test_compound_full_no_cap_uses_all(self):
|
||||
self.assertEqual(
|
||||
resolve_compound_full_usdc(200.0, cap_enabled=False, cap_usdc=50.0),
|
||||
200.0,
|
||||
)
|
||||
|
||||
def test_compound_full_cap_on(self):
|
||||
self.assertEqual(
|
||||
resolve_compound_full_usdc(200.0, cap_enabled=True, cap_usdc=50.0),
|
||||
50.0,
|
||||
)
|
||||
self.assertEqual(
|
||||
resolve_compound_full_usdc(30.0, cap_enabled=True, cap_usdc=50.0),
|
||||
30.0,
|
||||
)
|
||||
|
||||
def test_compound_full_cap_invalid_falls_back_to_balance(self):
|
||||
self.assertEqual(
|
||||
resolve_compound_full_usdc(80.0, cap_enabled=True, cap_usdc=0),
|
||||
80.0,
|
||||
)
|
||||
self.assertEqual(
|
||||
resolve_compound_full_usdc(80.0, cap_enabled=True, cap_usdc=None),
|
||||
80.0,
|
||||
)
|
||||
|
||||
def test_compound_full_blocks_when_position_open(self):
|
||||
rows = [{"instId": "ETH-USD_UM-260812-1870-P", "pos": "1"}]
|
||||
msg = compound_full_single_position_block_msg(
|
||||
object(), fetch_positions=lambda _ex: rows
|
||||
)
|
||||
self.assertIsNotNone(msg)
|
||||
self.assertIn("1 笔", msg or "")
|
||||
|
||||
def test_compound_full_allows_when_flat(self):
|
||||
msg = compound_full_single_position_block_msg(
|
||||
object(), fetch_positions=lambda _ex: []
|
||||
)
|
||||
self.assertIsNone(msg)
|
||||
self.assertEqual(count_live_option_positions([]), 0)
|
||||
|
||||
|
||||
def test_balance_below_budget_uses_balance():
|
||||
assert resolve_budget_full_usdc(5.0, 10.0) == 5.0
|
||||
|
||||
|
||||
def test_balance_equals_budget():
|
||||
assert resolve_budget_full_usdc(10.0, 10.0) == 10.0
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user