币本位买币改为按最大可开张数×权利金×可配缓冲,不全额兑换USDT
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -122,6 +122,8 @@ OKX_OPTIONS_COIN_COMPOUND=true
|
||||
OKX_OPTIONS_COIN_BUDGET_USDT=10
|
||||
OKX_OPTIONS_COIN_MAX_USDT_ENABLED=false
|
||||
OKX_OPTIONS_COIN_MAX_USDT=50
|
||||
# 现货买入相对权利金缓冲:1.10=多买10%;也可写 0.10。按最大可开张数×权利金×缓冲买币,不全额兑换
|
||||
OKX_OPTIONS_COIN_SPOT_BUY_BUFFER=1.10
|
||||
# 全仓复利:开启时隐藏单笔预算且不可用打满;关闭后恢复单笔预算
|
||||
OKX_OPTIONS_COMPOUND_FULL_ENABLED=true
|
||||
OKX_OPTIONS_COMPOUND_FULL_CAP_ENABLED=false
|
||||
|
||||
@@ -57,6 +57,9 @@
|
||||
| `OKX_OPTIONS_COIN_BUDGET_USDT` | 复利关闭时的固定 USDT 预算;或作展示参考 | `10` |
|
||||
| `OKX_OPTIONS_COIN_MAX_USDT_ENABLED` | 单笔不超过 N U 开关 | `false`(**默认关**) |
|
||||
| `OKX_OPTIONS_COIN_MAX_USDT` | 上限 N(仅开关开启时生效) | 如 `50`(可改) |
|
||||
| `OKX_OPTIONS_COIN_SPOT_BUY_BUFFER` | 现货买入相对权利金倍数(也可写 `0.10`=+10%) | `1.10` |
|
||||
|
||||
开仓买币:**先按预算估最大可开张数 → 买币 USDT ≈ 张数×卖一权利金×现货缓冲**,不全额把预算换成币。
|
||||
|
||||
说明:
|
||||
|
||||
|
||||
@@ -1344,8 +1344,11 @@
|
||||
? ((d && d.msg) || (sz && sz.msg) || "无法开仓")
|
||||
: "暂无卖一深度,无法开仓";
|
||||
} else if (coinMode) {
|
||||
const buyU = sz && sz.buy_usdt != null ? sz.buy_usdt : null;
|
||||
openBtn.textContent =
|
||||
bud != null ? "买币并开仓(预算 ≈ " + Number(bud).toFixed(2) + " USDT)" : "买币并开仓 @ 卖一";
|
||||
buyU != null
|
||||
? ("买币并开仓(约 " + Number(buyU).toFixed(2) + " USDT)")
|
||||
: (bud != null ? "买币并开仓(预算上限 ≈ " + Number(bud).toFixed(2) + " USDT)" : "买币并开仓 @ 卖一");
|
||||
} else {
|
||||
openBtn.textContent = "限价买入 @ 卖一";
|
||||
}
|
||||
@@ -1577,8 +1580,13 @@
|
||||
? ((latest && (latest.msg || (latest.sizing && latest.sizing.msg))) || "无法开仓")
|
||||
: "暂无卖一深度,无法开仓";
|
||||
} else if (coinMode) {
|
||||
const buyU = latest && latest.sizing && latest.sizing.buy_usdt != null
|
||||
? latest.sizing.buy_usdt
|
||||
: null;
|
||||
btn.textContent =
|
||||
bud != null ? "买币并开仓(预算 ≈ " + Number(bud).toFixed(2) + " USDT)" : "买币并开仓 @ 卖一";
|
||||
buyU != null
|
||||
? ("买币并开仓(约 " + Number(buyU).toFixed(2) + " USDT)")
|
||||
: (bud != null ? "买币并开仓(预算上限 ≈ " + Number(bud).toFixed(2) + " USDT)" : "买币并开仓 @ 卖一");
|
||||
} else {
|
||||
btn.textContent = "限价买入 @ 卖一";
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -104,6 +104,7 @@ HOT_RELOAD_EXACT = frozenset({
|
||||
"OKX_OPTIONS_COIN_BUDGET_USDT",
|
||||
"OKX_OPTIONS_COIN_MAX_USDT_ENABLED",
|
||||
"OKX_OPTIONS_COIN_MAX_USDT",
|
||||
"OKX_OPTIONS_COIN_SPOT_BUY_BUFFER",
|
||||
"OKX_TRADE_MODE",
|
||||
"MAX_ACTIVE_HEDGE_PLANS",
|
||||
"HEDGE_PLAN_LIVE_ORDER",
|
||||
|
||||
Vendored
+5
@@ -174,6 +174,11 @@ _OPTIONS_SECTION: dict[str, Any] = {
|
||||
"币本位单笔上限(USDT)",
|
||||
"仅上限开关开启时生效",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COIN_SPOT_BUY_BUFFER",
|
||||
"币本位现货买入缓冲",
|
||||
"相对权利金倍数,默认 1.10(=多买10%);也可写 0.10 表示+10%。按最大可开张数×卖一权利金×本缓冲买币,不全额兑换",
|
||||
),
|
||||
(
|
||||
"OKX_OPTIONS_COMPOUND_FULL_ENABLED",
|
||||
"全仓复利开关",
|
||||
|
||||
@@ -17,6 +17,7 @@ from lib.options.options_margin_mode_lib import (
|
||||
is_coin_margin_mode,
|
||||
margin_mode_from_inst_id,
|
||||
normalize_options_margin_mode,
|
||||
plan_coin_open_by_budget,
|
||||
premium_ccy_for_mode,
|
||||
)
|
||||
from lib.options.options_spot_bridge_lib import (
|
||||
@@ -56,8 +57,9 @@ def open_coin_option_buy_full(
|
||||
target_index: float | None = None,
|
||||
profit_exit_enabled: bool = False,
|
||||
profit_exit_mult: float = 1.0,
|
||||
target_sheets: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""先买满 USDT 预算对应的币,再按卖一尽量开满."""
|
||||
"""先按最大可开张数估权利金×现货缓冲买币,再开对应张数(不全额兑换预算)."""
|
||||
from lib.options.options_db import init_options_tables
|
||||
from lib.options.options_position_limit_lib import (
|
||||
compound_full_single_position_block_msg,
|
||||
@@ -111,16 +113,56 @@ def open_coin_option_buy_full(
|
||||
"can_open": False,
|
||||
}
|
||||
|
||||
# 1) 买币(用买入前后可用差作为本轮币量,避免叠加原有现货)
|
||||
ct_mult = float(q.get("ct_mult") or 0.01)
|
||||
min_sz = int(q.get("min_sz") or 1)
|
||||
idx = None
|
||||
try:
|
||||
idx = float(q.get("index_px") or q.get("idxPx") or 0)
|
||||
except (TypeError, ValueError):
|
||||
idx = 0.0
|
||||
if idx <= 0:
|
||||
try:
|
||||
from lib.exchange.okx_options_lib import fetch_index_price
|
||||
|
||||
idx = float(fetch_index_price(ex, f"{underlying}-USD") or 0)
|
||||
except Exception:
|
||||
idx = 0.0
|
||||
|
||||
plan = plan_coin_open_by_budget(
|
||||
quote_per_unit=float(ask),
|
||||
ct_mult=ct_mult,
|
||||
min_sz=min_sz,
|
||||
budget_usdt=budget_usdt,
|
||||
index_px=float(idx),
|
||||
ask_sz=ask_sz,
|
||||
target_sheets=target_sheets,
|
||||
)
|
||||
if not plan.get("ok"):
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": plan.get("msg") or "无法规划买币张数",
|
||||
"plan": plan,
|
||||
"budget": budget_info,
|
||||
"can_open": False,
|
||||
}
|
||||
buy_usdt = float(plan["buy_usdt"])
|
||||
sheets = int(plan["sheets"])
|
||||
|
||||
# 1) 仅买「权利金×现货缓冲」所需 USDT,不全额兑换预算
|
||||
coin_before = fetch_trading_coin_available(ex, underlying) or 0.0
|
||||
buy = spot_market_buy_coin_with_usdt(ex, underlying=underlying, usdt_amount=budget_usdt)
|
||||
buy = spot_market_buy_coin_with_usdt(ex, underlying=underlying, usdt_amount=buy_usdt)
|
||||
if not buy.get("ok"):
|
||||
return {"ok": False, "msg": f"现货买入 {underlying} 失败: {buy.get('msg')}", "budget": budget_info}
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": f"现货买入 {underlying} 失败: {buy.get('msg')}",
|
||||
"budget": budget_info,
|
||||
"plan": plan,
|
||||
}
|
||||
bridge_id = insert_bridge(
|
||||
conn,
|
||||
underlying=underlying,
|
||||
status=BRIDGE_BOUGHT,
|
||||
budget_usdt=budget_usdt,
|
||||
budget_usdt=buy_usdt,
|
||||
buy_ord_id=str(buy.get("ord_id") or ""),
|
||||
inst_id=inst_id,
|
||||
message="已买币,待开期权",
|
||||
@@ -143,6 +185,7 @@ def open_coin_option_buy_full(
|
||||
"msg": "买币后读不到可用余额,已尝试卖回 USDT",
|
||||
"rollback": rb,
|
||||
"budget": budget_info,
|
||||
"plan": plan,
|
||||
}
|
||||
coin_bought = max(0.0, float(coin_after) - float(coin_before or 0))
|
||||
if coin_bought <= 0:
|
||||
@@ -155,8 +198,6 @@ def open_coin_option_buy_full(
|
||||
return {"ok": False, "msg": "买币后可用增量无效", "rollback": rb, "budget": budget_info}
|
||||
update_bridge(conn, bridge_id, coin_bought=float(coin_bought))
|
||||
|
||||
ct_mult = float(q.get("ct_mult") or 0.01)
|
||||
min_sz = int(q.get("min_sz") or 1)
|
||||
sizing = calc_sheets_from_coin_balance(
|
||||
quote_per_unit=float(ask),
|
||||
ct_mult=ct_mult,
|
||||
@@ -172,9 +213,10 @@ def open_coin_option_buy_full(
|
||||
reason=sizing.get("msg") or "张数不足",
|
||||
coin_amount=float(coin_bought),
|
||||
)
|
||||
return {"ok": False, "msg": sizing.get("msg"), "sizing": sizing, "rollback": rb, "budget": budget_info}
|
||||
return {"ok": False, "msg": sizing.get("msg"), "sizing": sizing, "rollback": rb, "budget": budget_info, "plan": plan}
|
||||
|
||||
sheets = int(sizing["sheets"])
|
||||
# 实盘以买到的币为准,但不超过规划张数
|
||||
sheets = min(int(sizing["sheets"]), int(plan["sheets"]))
|
||||
capped, cap_msg = cap_option_buy_sheets_to_ask_depth(sheets, ask_sz, min_sz=min_sz)
|
||||
if capped is None:
|
||||
rb = rollback_bought_coin_to_usdt(
|
||||
@@ -190,6 +232,13 @@ def open_coin_option_buy_full(
|
||||
"coin_premium": round(sheets * float(ask) * ct_mult, 8),
|
||||
"ask_depth_capped": True,
|
||||
}
|
||||
else:
|
||||
sizing = {
|
||||
"ok": True,
|
||||
"sheets": sheets,
|
||||
"eth_amount": round(sheets * ct_mult, 8),
|
||||
"coin_premium": round(sheets * float(ask) * ct_mult, 8),
|
||||
}
|
||||
|
||||
tick_sz = q.get("tick_sz")
|
||||
order = cfg["place_option_limit_order"](
|
||||
@@ -292,7 +341,7 @@ def open_coin_option_buy_full(
|
||||
signal_note=signal_note,
|
||||
exchange_ord_id=ord_id,
|
||||
bridge_id=bridge_id,
|
||||
budget_usdt=budget_usdt,
|
||||
budget_usdt=buy_usdt,
|
||||
premium_ccy=premium_ccy,
|
||||
profit_exit_enabled=profit_exit_enabled,
|
||||
profit_exit_mult=profit_exit_mult,
|
||||
|
||||
@@ -134,6 +134,156 @@ def compute_coin_budget_usdt(
|
||||
}
|
||||
|
||||
|
||||
def normalize_coin_spot_buy_buffer(raw: Any = None) -> float:
|
||||
"""
|
||||
现货买入相对权利金的倍数缓冲.
|
||||
env OKX_OPTIONS_COIN_SPOT_BUY_BUFFER 默认 1.10(=多买 10%).
|
||||
也可写 0.10 表示 +10%.
|
||||
"""
|
||||
if raw is None:
|
||||
v = _env_float("OKX_OPTIONS_COIN_SPOT_BUY_BUFFER", 1.10)
|
||||
else:
|
||||
try:
|
||||
v = float(raw)
|
||||
except (TypeError, ValueError):
|
||||
v = 1.10
|
||||
if v <= 0:
|
||||
return 1.10
|
||||
if v < 1.0:
|
||||
return 1.0 + v
|
||||
return v
|
||||
|
||||
|
||||
def plan_coin_open_by_budget(
|
||||
*,
|
||||
quote_per_unit: float,
|
||||
ct_mult: float,
|
||||
min_sz: int,
|
||||
budget_usdt: float,
|
||||
index_px: float,
|
||||
ask_sz: float | None = None,
|
||||
spot_buy_buffer: float | None = None,
|
||||
target_sheets: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
先按预算/卖一估最大可开张数,再按「权利金 × 现货缓冲」算应买现货 USDT.
|
||||
不全额把预算换成币.
|
||||
"""
|
||||
import math
|
||||
|
||||
from lib.exchange.okx_options_lib import cap_option_buy_sheets_to_ask_depth
|
||||
|
||||
ask = float(quote_per_unit or 0)
|
||||
mult = float(ct_mult or 0.01)
|
||||
need = max(1, int(min_sz or 1))
|
||||
budget = max(0.0, float(budget_usdt or 0))
|
||||
idx = float(index_px or 0)
|
||||
buf = normalize_coin_spot_buy_buffer(spot_buy_buffer)
|
||||
if ask <= 0 or mult <= 0:
|
||||
return {"ok": False, "msg": "卖一价无效", "sheets": 0, "buy_usdt": 0.0}
|
||||
if idx <= 0:
|
||||
return {"ok": False, "msg": "缺少指数价,无法估算买币 USDT", "sheets": 0, "buy_usdt": 0.0}
|
||||
if budget <= 0:
|
||||
return {"ok": False, "msg": "USDT 预算无效", "sheets": 0, "buy_usdt": 0.0}
|
||||
|
||||
per_sheet_coin = ask * mult
|
||||
# 每张开仓需买的币(含缓冲)及其约合 USDT
|
||||
per_sheet_buy_coin = per_sheet_coin * buf
|
||||
per_sheet_usdt = per_sheet_buy_coin * idx
|
||||
if per_sheet_usdt <= 0:
|
||||
return {"ok": False, "msg": "无法计算单张买币成本", "sheets": 0, "buy_usdt": 0.0}
|
||||
|
||||
max_by_budget = int(math.floor((budget / per_sheet_usdt) + 1e-12))
|
||||
if target_sheets is not None:
|
||||
try:
|
||||
want = int(target_sheets)
|
||||
except (TypeError, ValueError):
|
||||
want = 0
|
||||
if want < need:
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": f"指定张数无效(需≥{need})",
|
||||
"sheets": 0,
|
||||
"buy_usdt": 0.0,
|
||||
"max_by_budget": max_by_budget,
|
||||
}
|
||||
sheets = min(want, max_by_budget)
|
||||
if sheets < want:
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": (
|
||||
f"预算约可开 {max_by_budget} 张(含现货缓冲×{buf:g}),"
|
||||
f"不足指定 {want} 张"
|
||||
),
|
||||
"sheets": 0,
|
||||
"buy_usdt": 0.0,
|
||||
"max_by_budget": max_by_budget,
|
||||
"spot_buy_buffer": buf,
|
||||
}
|
||||
else:
|
||||
sheets = max_by_budget
|
||||
|
||||
capped, cap_msg = cap_option_buy_sheets_to_ask_depth(sheets, ask_sz, min_sz=need)
|
||||
ask_depth_capped = False
|
||||
if capped is None:
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": cap_msg or "卖一深度不足",
|
||||
"sheets": 0,
|
||||
"buy_usdt": 0.0,
|
||||
"spot_buy_buffer": buf,
|
||||
}
|
||||
if int(capped) < sheets:
|
||||
sheets = int(capped)
|
||||
ask_depth_capped = True
|
||||
|
||||
if sheets < need:
|
||||
return {
|
||||
"ok": False,
|
||||
"msg": (
|
||||
f"预算不足,无法买入 {need} 张"
|
||||
f"(单张约需 {per_sheet_usdt:.4f} USDT,含现货缓冲×{buf:g})"
|
||||
),
|
||||
"sheets": sheets,
|
||||
"buy_usdt": 0.0,
|
||||
"per_sheet_usdt": round(per_sheet_usdt, 8),
|
||||
"spot_buy_buffer": buf,
|
||||
"max_by_budget": max_by_budget,
|
||||
}
|
||||
|
||||
premium_coin = sheets * per_sheet_coin
|
||||
buy_coin = premium_coin * buf
|
||||
buy_usdt = min(budget, buy_coin * idx)
|
||||
# 再保险:向下对齐,避免浮点导致略超预算
|
||||
buy_usdt = min(budget, round(buy_usdt, 8))
|
||||
out = {
|
||||
"ok": True,
|
||||
"msg": "" if not ask_depth_capped else (cap_msg or f"已按卖一深度限制为 {sheets} 张"),
|
||||
"sheets": sheets,
|
||||
"eth_amount": round(sheets * mult, 8),
|
||||
"coin_premium": round(premium_coin, 8),
|
||||
"total_premium": round(premium_coin, 8),
|
||||
"per_sheet_coin": per_sheet_coin,
|
||||
"buy_coin": round(buy_coin, 8),
|
||||
"buy_usdt": round(buy_usdt, 8),
|
||||
"budget_usdt": round(budget, 8),
|
||||
"spot_buy_buffer": buf,
|
||||
"index_px": idx,
|
||||
"max_by_budget": max_by_budget,
|
||||
"ask_depth_capped": ask_depth_capped,
|
||||
"est_note": (
|
||||
f"按最大可开 {sheets} 张×卖一权利金×现货缓冲{buf:g}估买币;"
|
||||
f"不全额兑换预算"
|
||||
),
|
||||
}
|
||||
if target_sheets is not None:
|
||||
out["est_note"] = (
|
||||
f"指定 {sheets} 张×卖一权利金×现货缓冲{buf:g}估买币;不全额兑换"
|
||||
)
|
||||
out["target_sheets"] = int(target_sheets)
|
||||
return out
|
||||
|
||||
|
||||
def calc_sheets_from_coin_balance(
|
||||
*,
|
||||
quote_per_unit: float,
|
||||
|
||||
@@ -553,12 +553,11 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
# 币本位:报价预览走 USDT 预算→估币→张数,禁止再查 USDC
|
||||
try:
|
||||
from lib.options.options_margin_mode_lib import (
|
||||
calc_sheets_from_coin_balance,
|
||||
is_coin_margin_mode,
|
||||
margin_mode_from_inst_id,
|
||||
)
|
||||
from lib.options.options_coin_open_lib import coin_budget_preview
|
||||
from lib.exchange.okx_options_lib import cap_option_buy_sheets_to_ask_depth, option_buy_liquidity_ok
|
||||
from lib.exchange.okx_options_lib import option_buy_liquidity_ok
|
||||
|
||||
if is_coin_margin_mode():
|
||||
ask = q.get("ask")
|
||||
@@ -637,61 +636,31 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
)
|
||||
idx = _safe_float(q.get("index_px")) or _safe_float(q.get("idxPx"))
|
||||
budget_usdt = float(budget_info["budget_usdt"])
|
||||
est_coin = (budget_usdt / float(idx)) if idx and float(idx) > 0 else None
|
||||
if est_coin is None or est_coin <= 0:
|
||||
return jsonify(
|
||||
{
|
||||
**q,
|
||||
"ok": True,
|
||||
"can_open": False,
|
||||
"msg": "无法用指数估算可买币量",
|
||||
"options_margin_mode": "coin",
|
||||
"coin_budget": budget_info,
|
||||
"sizing": {
|
||||
"ok": False,
|
||||
"msg": "缺少指数价,无法预估张数",
|
||||
"sheets": 0,
|
||||
"eth_amount": 0.0,
|
||||
"total_premium": 0.0,
|
||||
},
|
||||
}
|
||||
)
|
||||
sizing = calc_sheets_from_coin_balance(
|
||||
target_sheets = sheet_count if mode == "sheets" and sheet_count is not None else None
|
||||
if mode == "eth" and request.args.get("eth"):
|
||||
# 指定币量:按币量反推张数后再走统一规划
|
||||
try:
|
||||
eth_want = float(request.args.get("eth"))
|
||||
except (TypeError, ValueError):
|
||||
eth_want = 0.0
|
||||
if eth_want > 0 and float(ct_mult) > 0:
|
||||
import math
|
||||
|
||||
target_sheets = max(int(min_sz), int(math.floor(eth_want / float(ct_mult) + 1e-12)))
|
||||
from lib.options.options_margin_mode_lib import plan_coin_open_by_budget
|
||||
|
||||
sizing = plan_coin_open_by_budget(
|
||||
quote_per_unit=float(ask),
|
||||
ct_mult=float(ct_mult),
|
||||
min_sz=int(min_sz),
|
||||
coin_available=float(est_coin),
|
||||
budget_usdt=budget_usdt,
|
||||
index_px=float(idx or 0),
|
||||
ask_sz=ask_sz,
|
||||
target_sheets=target_sheets,
|
||||
)
|
||||
if sizing.get("ok"):
|
||||
capped, cap_msg = cap_option_buy_sheets_to_ask_depth(
|
||||
int(sizing.get("sheets") or 0),
|
||||
ask_sz,
|
||||
min_sz=int(min_sz),
|
||||
)
|
||||
if capped is None:
|
||||
sizing = {
|
||||
"ok": False,
|
||||
"msg": cap_msg,
|
||||
"sheets": 0,
|
||||
"eth_amount": 0.0,
|
||||
"total_premium": 0.0,
|
||||
}
|
||||
elif capped < int(sizing.get("sheets") or 0):
|
||||
sizing = calc_sheets_from_coin_balance(
|
||||
quote_per_unit=float(ask),
|
||||
ct_mult=float(ct_mult),
|
||||
min_sz=int(min_sz),
|
||||
coin_available=float(capped) * float(ask) * float(ct_mult),
|
||||
)
|
||||
if sizing.get("ok"):
|
||||
sizing["ask_depth_capped"] = True
|
||||
sizing["ask_sz"] = ask_sz
|
||||
sizing["msg"] = f"已按卖一深度限制为 {capped} 张"
|
||||
if sizing.get("ok"):
|
||||
sizing["total_premium"] = sizing.get("coin_premium")
|
||||
sizing["premium_ccy"] = (inst_id.split("-")[0] if inst_id else "ETH").upper()
|
||||
sizing["est_coin"] = round(float(est_coin), 8)
|
||||
sizing["est_note"] = "张数为预算估币预览;实盘按买币后可用量开满"
|
||||
sizing["est_coin"] = sizing.get("buy_coin")
|
||||
q = _attach_close_preview(
|
||||
cfg,
|
||||
ex,
|
||||
@@ -1025,6 +994,25 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
from lib.options.options_coin_open_lib import open_coin_option_buy_full
|
||||
|
||||
if is_coin_margin_mode():
|
||||
want_sheets = None
|
||||
if mode == "sheets":
|
||||
try:
|
||||
want_sheets = int(data.get("sheets") or 0) or None
|
||||
except (TypeError, ValueError):
|
||||
want_sheets = None
|
||||
elif mode == "eth":
|
||||
try:
|
||||
eth_want = float(data.get("eth") or 0)
|
||||
except (TypeError, ValueError):
|
||||
eth_want = 0.0
|
||||
if eth_want > 0:
|
||||
q0 = cfg["quote_option_contract"](ex, inst_id)
|
||||
ct0 = float((q0 or {}).get("ct_mult") or 0.01)
|
||||
min0 = int((q0 or {}).get("min_sz") or 1)
|
||||
if ct0 > 0:
|
||||
import math
|
||||
|
||||
want_sheets = max(min0, int(math.floor(eth_want / ct0 + 1e-12)))
|
||||
result = open_coin_option_buy_full(
|
||||
cfg,
|
||||
ex,
|
||||
@@ -1033,6 +1021,7 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
target_index=target_index,
|
||||
profit_exit_enabled=profit_exit_enabled,
|
||||
profit_exit_mult=profit_exit_mult,
|
||||
target_sheets=want_sheets,
|
||||
)
|
||||
if result.get("ok"):
|
||||
from lib.exchange.okx_options_lib import invalidate_option_positions_cache
|
||||
|
||||
@@ -82,6 +82,45 @@ class TestOptionsMarginMode(unittest.TestCase):
|
||||
self.assertTrue(r["ok"])
|
||||
self.assertEqual(r["sheets"], 97)
|
||||
|
||||
def test_spot_buy_buffer_normalize(self):
|
||||
from lib.options.options_margin_mode_lib import normalize_coin_spot_buy_buffer
|
||||
|
||||
self.assertAlmostEqual(normalize_coin_spot_buy_buffer(1.10), 1.10)
|
||||
self.assertAlmostEqual(normalize_coin_spot_buy_buffer(0.10), 1.10)
|
||||
self.assertAlmostEqual(normalize_coin_spot_buy_buffer(1.25), 1.25)
|
||||
|
||||
def test_plan_coin_open_by_budget(self):
|
||||
from lib.options.options_margin_mode_lib import plan_coin_open_by_budget
|
||||
|
||||
# ask 0.01, ct 0.1 → 单张权利金 0.001 ETH;×1.1=0.0011;×指数 2000 → 2.2 USDT/张
|
||||
r = plan_coin_open_by_budget(
|
||||
quote_per_unit=0.01,
|
||||
ct_mult=0.1,
|
||||
min_sz=1,
|
||||
budget_usdt=10.0,
|
||||
index_px=2000.0,
|
||||
ask_sz=100,
|
||||
spot_buy_buffer=1.10,
|
||||
)
|
||||
self.assertTrue(r["ok"], r.get("msg"))
|
||||
self.assertEqual(r["sheets"], 4) # floor(10/2.2)=4
|
||||
self.assertAlmostEqual(r["buy_usdt"], 4 * 0.01 * 0.1 * 1.10 * 2000, places=4)
|
||||
self.assertLess(r["buy_usdt"], 10.0)
|
||||
|
||||
one = plan_coin_open_by_budget(
|
||||
quote_per_unit=0.01,
|
||||
ct_mult=0.1,
|
||||
min_sz=1,
|
||||
budget_usdt=10.0,
|
||||
index_px=2000.0,
|
||||
ask_sz=100,
|
||||
spot_buy_buffer=1.10,
|
||||
target_sheets=1,
|
||||
)
|
||||
self.assertTrue(one["ok"], one.get("msg"))
|
||||
self.assertEqual(one["sheets"], 1)
|
||||
self.assertAlmostEqual(one["buy_usdt"], 0.01 * 0.1 * 1.10 * 2000, places=4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user