对齐币本位期权:现货缓冲开仓、页头 ETH/BTC 余额与默认 coin 模式。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+120
-22
@@ -19,7 +19,7 @@ from lib.options.options_pricing_lib import (
|
||||
)
|
||||
|
||||
_OKX_OPTION_ERR_ZH: dict[str, str] = {
|
||||
"51008": "可用余额或保证金不足(期权买入请确认交易账户 USDC 足够)",
|
||||
"51008": "可用余额或保证金不足(币本位请确认交易账户 ETH/BTC 足够;USDC 模式请确认 USDC 足够)",
|
||||
"51018": "期权账户不能持有净空头头寸",
|
||||
"51019": "期权买入须使用逐仓模式(全仓模式下不能持有多头净头寸)",
|
||||
}
|
||||
@@ -47,11 +47,18 @@ def _okx_trade_error_message(exc: BaseException | None = None, resp: Any = None)
|
||||
msg = str(row.get("sMsg") or "").strip()
|
||||
low = msg.lower()
|
||||
if code == "51008":
|
||||
# 勿写死「资金账户 USDT」:期权开仓常因交易户 USDC 不足
|
||||
# 勿写死「资金账户 USDT」:USDC 模式常因交易户 USDC 不足;币本位则是标的币不足
|
||||
if "usdc" in low:
|
||||
return "交易账户 USDC 可用余额不足"
|
||||
if "usdt" in low:
|
||||
return "USDT 可用余额不足(期权请先兑成 USDC 并划入交易账户)"
|
||||
return "USDT 可用余额不足"
|
||||
try:
|
||||
from lib.options.options_margin_mode_lib import is_coin_margin_mode
|
||||
|
||||
if is_coin_margin_mode():
|
||||
return "可用余额或保证金不足(币本位请确认交易账户 ETH/BTC 足够,或减少张数)"
|
||||
except Exception:
|
||||
pass
|
||||
return _OKX_OPTION_ERR_ZH["51008"]
|
||||
zh = _OKX_OPTION_ERR_ZH.get(code)
|
||||
if zh:
|
||||
@@ -164,6 +171,21 @@ def format_usdc_amount(v: float | None) -> str | None:
|
||||
return f"{float(v):.2f}"
|
||||
|
||||
|
||||
def format_premium_amount(v: float | None, *, ccy: str | None = "USDC") -> str | None:
|
||||
"""权利金/回收金额文案:USDC 2 位;币本位 ETH/BTC 最多 8 位去尾零."""
|
||||
if v is None:
|
||||
return None
|
||||
try:
|
||||
n = float(v)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
unit = (ccy or "USDC").strip().upper() or "USDC"
|
||||
if unit in ("ETH", "BTC"):
|
||||
txt = f"{n:.8f}".rstrip("0").rstrip(".")
|
||||
return txt or "0"
|
||||
return f"{n:.2f}"
|
||||
|
||||
|
||||
def is_option_full_close_history(raw: dict[str, Any]) -> bool:
|
||||
"""仅保留 OKX 历史仓位中的「全部平仓/强平/ADL 全平」记录,排除部分平仓."""
|
||||
close_type = str(raw.get("type") or "").strip()
|
||||
@@ -509,8 +531,8 @@ def fetch_account_balances_by_type(
|
||||
ex: ccxt.okx,
|
||||
account_type: str,
|
||||
) -> tuple[dict[str, float | None], dict[str, float | None]]:
|
||||
out: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None}
|
||||
avail: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None}
|
||||
out: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None, "ETH": None, "BTC": None}
|
||||
avail: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None, "ETH": None, "BTC": None}
|
||||
try:
|
||||
bal = ex.fetch_balance(params={"type": account_type})
|
||||
for c in out:
|
||||
@@ -525,8 +547,8 @@ def fetch_funding_balances_via_asset_api(
|
||||
ex: ccxt.okx,
|
||||
) -> tuple[dict[str, float | None], dict[str, float | None]]:
|
||||
"""OKX 资金账户余额(GET /api/v5/asset/balances),比 ccxt fetch_balance 更准确."""
|
||||
out: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None}
|
||||
avail: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None}
|
||||
out: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None, "ETH": None, "BTC": None}
|
||||
avail: dict[str, float | None] = {"USDT": None, "USDC": None, "USDG": None, "ETH": None, "BTC": None}
|
||||
try:
|
||||
resp = ex.private_get_asset_balances({})
|
||||
for row in (resp or {}).get("data") or []:
|
||||
@@ -609,24 +631,34 @@ def fetch_options_balances(
|
||||
funding = _merge_balance_maps(funding, asset_funding)
|
||||
funding_avail = _merge_balance_maps(funding_avail, asset_funding_avail)
|
||||
trading, trading_avail = fetch_account_balances_by_type(ex, "trading")
|
||||
if trading.get("USDC") is None:
|
||||
# OKX 统一账户:option 客户端拉 type=trading 常缺 USDT/币;用 swap 补齐缺失项
|
||||
if any(trading.get(c) is None for c in ("USDT", "USDC", "ETH", "BTC")):
|
||||
swap_bal, swap_avail = fetch_account_balances_by_type(ex, "swap")
|
||||
if swap_bal.get("USDC") is not None:
|
||||
trading["USDC"] = swap_bal["USDC"]
|
||||
if trading_avail.get("USDC") is None and swap_avail.get("USDC") is not None:
|
||||
trading_avail["USDC"] = swap_avail["USDC"]
|
||||
for ccy in ("USDT", "USDC", "USDG", "ETH", "BTC"):
|
||||
if trading.get(ccy) is None and swap_bal.get(ccy) is not None:
|
||||
trading[ccy] = swap_bal[ccy]
|
||||
if trading_avail.get(ccy) is None and swap_avail.get(ccy) is not None:
|
||||
trading_avail[ccy] = swap_avail[ccy]
|
||||
result = {
|
||||
"scope": "main",
|
||||
"funding_usdt": funding.get("USDT"),
|
||||
"funding_usdc": funding.get("USDC"),
|
||||
"funding_usdg": funding.get("USDG"),
|
||||
"funding_eth": funding.get("ETH"),
|
||||
"funding_btc": funding.get("BTC"),
|
||||
"funding_usdt_avail": funding_avail.get("USDT"),
|
||||
"funding_usdc_avail": funding_avail.get("USDC"),
|
||||
"funding_eth_avail": funding_avail.get("ETH"),
|
||||
"funding_btc_avail": funding_avail.get("BTC"),
|
||||
"trading_usdt": trading.get("USDT"),
|
||||
"trading_usdc": trading.get("USDC"),
|
||||
"trading_usdg": trading.get("USDG"),
|
||||
"trading_eth": trading.get("ETH"),
|
||||
"trading_btc": trading.get("BTC"),
|
||||
"trading_usdt_avail": trading_avail.get("USDT"),
|
||||
"trading_usdc_avail": trading_avail.get("USDC"),
|
||||
"trading_eth_avail": trading_avail.get("ETH"),
|
||||
"trading_btc_avail": trading_avail.get("BTC"),
|
||||
}
|
||||
_OPTIONS_BALANCE_CACHE["updated_at"] = now
|
||||
_OPTIONS_BALANCE_CACHE["data"] = result
|
||||
@@ -642,22 +674,63 @@ def options_header_balances(
|
||||
|
||||
返回:(trading_usdc, funding_usdc, funding_usdt, trading_usdt)
|
||||
"""
|
||||
pack = options_header_balance_pack(ex, force=force)
|
||||
return (
|
||||
pack.get("trading_usdc"),
|
||||
pack.get("funding_usdc"),
|
||||
pack.get("funding_usdt"),
|
||||
pack.get("trading_usdt"),
|
||||
)
|
||||
|
||||
|
||||
def options_header_balance_pack(
|
||||
ex: ccxt.okx,
|
||||
*,
|
||||
force: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""顶栏/快照用期权资金包(含币本位 ETH/BTC)."""
|
||||
import os
|
||||
|
||||
bal = fetch_options_balances(ex, force=force)
|
||||
|
||||
def _round(v: Any) -> float | None:
|
||||
def _round(v: Any, nd: int = 2) -> float | None:
|
||||
if v is None:
|
||||
return None
|
||||
try:
|
||||
return round(float(v), 2)
|
||||
return round(float(v), nd)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
return (
|
||||
_round(bal.get("trading_usdc")),
|
||||
_round(bal.get("funding_usdc")),
|
||||
_round(bal.get("funding_usdt")),
|
||||
_round(bal.get("trading_usdt")),
|
||||
)
|
||||
def _round_coin(v: Any) -> float | None:
|
||||
if v is None:
|
||||
return None
|
||||
try:
|
||||
return round(float(v), 8)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
try:
|
||||
from lib.options.options_margin_mode_lib import normalize_options_margin_mode
|
||||
|
||||
margin_mode = normalize_options_margin_mode()
|
||||
except Exception:
|
||||
margin_mode = "usdc"
|
||||
underly = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper() or "ETH"
|
||||
coin_key = "btc" if underly == "BTC" else "eth"
|
||||
return {
|
||||
"trading_usdc": _round(bal.get("trading_usdc")),
|
||||
"funding_usdc": _round(bal.get("funding_usdc")),
|
||||
"funding_usdt": _round(bal.get("funding_usdt")),
|
||||
"trading_usdt": _round(bal.get("trading_usdt")),
|
||||
"funding_eth": _round_coin(bal.get("funding_eth")),
|
||||
"trading_eth": _round_coin(bal.get("trading_eth")),
|
||||
"funding_btc": _round_coin(bal.get("funding_btc")),
|
||||
"trading_btc": _round_coin(bal.get("trading_btc")),
|
||||
"options_margin_mode": margin_mode,
|
||||
"options_underly": underly,
|
||||
"funding_coin": _round_coin(bal.get(f"funding_{coin_key}")),
|
||||
"trading_coin": _round_coin(bal.get(f"trading_{coin_key}")),
|
||||
}
|
||||
|
||||
|
||||
def fetch_index_price(ex: ccxt.okx, uly: str) -> float | None:
|
||||
@@ -734,9 +807,19 @@ def build_option_chain(
|
||||
itm_only: bool = True,
|
||||
itm_max_dist_usd: float = 30.0,
|
||||
index_px: float | None = None,
|
||||
margin_mode: str | None = None,
|
||||
inst_family: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
u = (underlying or "ETH").upper()
|
||||
family = f"{u}-USD_UM"
|
||||
if inst_family:
|
||||
family = str(inst_family).strip()
|
||||
else:
|
||||
try:
|
||||
from lib.options.options_margin_mode_lib import inst_family_for_underlying
|
||||
|
||||
family = inst_family_for_underlying(u, margin_mode=margin_mode)
|
||||
except Exception:
|
||||
family = f"{u}-USD_UM"
|
||||
uly = f"{u}-USD"
|
||||
idx = index_px if index_px is not None else fetch_index_price(ex, uly)
|
||||
now_ms = time.time() * 1000
|
||||
@@ -838,6 +921,8 @@ def build_option_chain(
|
||||
"underlying": u,
|
||||
"index_px": idx,
|
||||
"inst_family": family,
|
||||
"margin_mode": "usdc" if "_UM" in family.upper() else "coin",
|
||||
"premium_ccy": "USDC" if "_UM" in family.upper() else u,
|
||||
"expiries": exp_list,
|
||||
"instruments_count": len(instruments),
|
||||
}
|
||||
@@ -1727,6 +1812,16 @@ def format_position_row(
|
||||
ct_mult=ct_mult,
|
||||
)
|
||||
exp_time_ms = normalize_option_exp_ms(pos.get("expTime"), inst_id)
|
||||
try:
|
||||
from lib.options.options_margin_mode_lib import margin_mode_from_inst_id, premium_ccy_for_mode
|
||||
|
||||
row_mode = margin_mode_from_inst_id(inst_id) if inst_id else "usdc"
|
||||
underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH"
|
||||
premium_ccy = premium_ccy_for_mode(row_mode, underly)
|
||||
except Exception:
|
||||
row_mode = "usdc"
|
||||
underly = (inst_id.split("-")[0] if inst_id else "ETH") or "ETH"
|
||||
premium_ccy = "USDC"
|
||||
return {
|
||||
"inst_id": inst_id or pos.get("instId"),
|
||||
"pos": sheets,
|
||||
@@ -1735,11 +1830,14 @@ def format_position_row(
|
||||
"mark_px": mark,
|
||||
"avg_px_fmt": format_option_px(avg, tick_sz) if avg is not None else None,
|
||||
"mark_px_fmt": format_option_px(mark, tick_sz) if mark is not None else None,
|
||||
"premium_paid_fmt": format_usdc_amount(premium_paid),
|
||||
"premium_paid_fmt": format_premium_amount(premium_paid, ccy=premium_ccy),
|
||||
"tick_sz": tick_sz,
|
||||
"ct_mult": ct_mult,
|
||||
"idx_px": idx_px,
|
||||
"premium_paid": premium_paid,
|
||||
"margin_mode": row_mode,
|
||||
"premium_ccy": premium_ccy,
|
||||
"underlying": underly,
|
||||
"upl": upl,
|
||||
"upl_ratio_pct": round(upl_ratio * 100, 2) if upl_ratio is not None else None,
|
||||
"exp_time": exp_time_ms,
|
||||
|
||||
Reference in New Issue
Block a user