Align hedge UI/fees: drop 未启动, premium coin/U, equity incl perp margin, OKX option fees.
Option net uses Min(0.03%×coin, 7%×premium) with bid/ask fills (no slip); live PnL colors fixed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -726,7 +726,7 @@
|
|||||||
el.textContent = "持仓中" + idPart;
|
el.textContent = "持仓中" + idPart;
|
||||||
el.classList.add("is-holding");
|
el.classList.add("is-holding");
|
||||||
} else {
|
} else {
|
||||||
el.textContent = "未启动";
|
el.textContent = "";
|
||||||
el.classList.add("is-idle");
|
el.classList.add("is-idle");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -990,6 +990,10 @@
|
|||||||
"<span>标记 <strong>" +
|
"<span>标记 <strong>" +
|
||||||
fmt(opt.mark_px, 4) +
|
fmt(opt.mark_px, 4) +
|
||||||
"</strong></span>" +
|
"</strong></span>" +
|
||||||
|
'<span title="权利金(币/U,U按买币参考指数)">权利金 <strong>' +
|
||||||
|
(opt.premium_coin != null ? fmt(opt.premium_coin, 4) + " 币" : "—") +
|
||||||
|
(opt.premium_usdt != null ? " / " + fmt(opt.premium_usdt, 2) + " U" : "") +
|
||||||
|
"</strong></span>" +
|
||||||
'<span class="' +
|
'<span class="' +
|
||||||
(liqWarn ? "hp-liq-warn" : "") +
|
(liqWarn ? "hp-liq-warn" : "") +
|
||||||
'" title="' +
|
'" title="' +
|
||||||
@@ -1268,8 +1272,16 @@
|
|||||||
if (acct.trading_usdc != null && acct.trading_usdc !== "") {
|
if (acct.trading_usdc != null && acct.trading_usdc !== "") {
|
||||||
state.tradingUsdc = Number(acct.trading_usdc);
|
state.tradingUsdc = Number(acct.trading_usdc);
|
||||||
}
|
}
|
||||||
|
// 展示权益(含永续全仓保证金);定仓用可用余额
|
||||||
if (acct.trading_usdt != null && acct.trading_usdt !== "") {
|
if (acct.trading_usdt != null && acct.trading_usdt !== "") {
|
||||||
state.tradingUsdt = Number(acct.trading_usdt);
|
state.tradingUsdtEquity = Number(acct.trading_usdt);
|
||||||
|
} else if (acct.trading_usdc != null && acct.trading_usdc !== "") {
|
||||||
|
state.tradingUsdtEquity = Number(acct.trading_usdc);
|
||||||
|
}
|
||||||
|
if (acct.trading_usdt_avail != null && acct.trading_usdt_avail !== "") {
|
||||||
|
state.tradingUsdt = Number(acct.trading_usdt_avail);
|
||||||
|
} else if (state.tradingUsdtEquity != null) {
|
||||||
|
state.tradingUsdt = state.tradingUsdtEquity;
|
||||||
} else if (acct.trading_usdc != null && acct.trading_usdc !== "") {
|
} else if (acct.trading_usdc != null && acct.trading_usdc !== "") {
|
||||||
state.tradingUsdt = Number(acct.trading_usdc);
|
state.tradingUsdt = Number(acct.trading_usdc);
|
||||||
}
|
}
|
||||||
@@ -1282,18 +1294,27 @@
|
|||||||
if (chain && chain.budget_buffer != null && chain.budget_buffer !== "") {
|
if (chain && chain.budget_buffer != null && chain.budget_buffer !== "") {
|
||||||
applyBudgetBuffer(chain.budget_buffer);
|
applyBudgetBuffer(chain.budget_buffer);
|
||||||
}
|
}
|
||||||
|
const equityTxt = fmt(
|
||||||
|
state.tradingUsdtEquity != null
|
||||||
|
? state.tradingUsdtEquity
|
||||||
|
: acct.trading_usdt != null
|
||||||
|
? acct.trading_usdt
|
||||||
|
: acct.trading_usdc,
|
||||||
|
2
|
||||||
|
);
|
||||||
const line =
|
const line =
|
||||||
label +
|
label +
|
||||||
" · 交易 USDT " +
|
" · 交易 USDT " +
|
||||||
fmt(acct.trading_usdt != null ? acct.trading_usdt : acct.trading_usdc, 2) +
|
equityTxt +
|
||||||
" · 资金 USDT " +
|
" · 资金 USDT " +
|
||||||
fmt(acct.funding_usdt != null ? acct.funding_usdt : acct.funding_usdc, 2);
|
fmt(acct.funding_usdt != null ? acct.funding_usdt : acct.funding_usdc, 2) +
|
||||||
|
" · 永续全仓/期权逐仓";
|
||||||
const el = $("hp-opt-bal-line");
|
const el = $("hp-opt-bal-line");
|
||||||
if (el) el.textContent = line;
|
if (el) el.textContent = line;
|
||||||
const fundEl = $("hp-oo-funding-usdc");
|
const fundEl = $("hp-oo-funding-usdc");
|
||||||
const tradeEl = $("hp-oo-trading-usdc");
|
const tradeEl = $("hp-oo-trading-usdc");
|
||||||
if (fundEl) fundEl.textContent = fmt(acct.funding_usdt != null ? acct.funding_usdt : acct.funding_usdc, 2);
|
if (fundEl) fundEl.textContent = fmt(acct.funding_usdt != null ? acct.funding_usdt : acct.funding_usdc, 2);
|
||||||
if (tradeEl) tradeEl.textContent = fmt(acct.trading_usdt != null ? acct.trading_usdt : acct.trading_usdc, 2);
|
if (tradeEl) tradeEl.textContent = equityTxt;
|
||||||
autoFillOoSheets();
|
autoFillOoSheets();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1937,7 +1958,19 @@
|
|||||||
const ct = Number(state.selected.ct_mult || 0.01);
|
const ct = Number(state.selected.ct_mult || 0.01);
|
||||||
const ask = Number(state.selected.ask || 0);
|
const ask = Number(state.selected.ask || 0);
|
||||||
const prem = ask * sheets * ct;
|
const prem = ask * sheets * ct;
|
||||||
line.textContent = "权利金 ≈ " + fmt(prem, 4) + " 币 · 卖一 " + fmt(ask, 4);
|
// U 以买币参考价(指数)为准,与开仓买币口径一致
|
||||||
|
const buyPx = Number(
|
||||||
|
(state.market && (state.market.index_px || state.market.mark)) || indexPx() || 0
|
||||||
|
);
|
||||||
|
const premU = buyPx > 0 ? prem * buyPx : null;
|
||||||
|
line.textContent =
|
||||||
|
"权利金 ≈ " +
|
||||||
|
fmt(prem, 4) +
|
||||||
|
" 币" +
|
||||||
|
(premU != null ? " / " + fmt(premU, 2) + " U" : "") +
|
||||||
|
" · 卖一 " +
|
||||||
|
fmt(ask, 4) +
|
||||||
|
(buyPx > 0 ? " · 买币参考 " + fmt(buyPx, 2) : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildStraddleRows(contracts) {
|
function buildStraddleRows(contracts) {
|
||||||
|
|||||||
@@ -4246,6 +4246,14 @@ html[data-theme="light"] .opt-be-dist-down {
|
|||||||
color: #e8ecf4;
|
color: #e8ecf4;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
.hedge-plan-page-wrap .hp-live-leg-grid strong.hp-pnl-pos,
|
||||||
|
.hedge-plan-page-wrap .hp-live-combo strong.hp-pnl-pos {
|
||||||
|
color: #7ee787 !important;
|
||||||
|
}
|
||||||
|
.hedge-plan-page-wrap .hp-live-leg-grid strong.hp-pnl-neg,
|
||||||
|
.hedge-plan-page-wrap .hp-live-combo strong.hp-pnl-neg {
|
||||||
|
color: #ff8a8a !important;
|
||||||
|
}
|
||||||
.hedge-plan-page-wrap .hp-live-combo {
|
.hedge-plan-page-wrap .hp-live-combo {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -706,11 +706,17 @@ def options_header_balance_pack(
|
|||||||
margin_mode = "usdc"
|
margin_mode = "usdc"
|
||||||
underly = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper() or "ETH"
|
underly = (os.getenv("OKX_OPTIONS_DEFAULT_UNDERLY") or "ETH").strip().upper() or "ETH"
|
||||||
coin_key = "btc" if underly == "BTC" else "eth"
|
coin_key = "btc" if underly == "BTC" else "eth"
|
||||||
|
trading_usdt = _round(bal.get("trading_usdt"))
|
||||||
|
trading_usdt_avail = _round(bal.get("trading_usdt_avail"))
|
||||||
|
if trading_usdt_avail is None:
|
||||||
|
trading_usdt_avail = trading_usdt
|
||||||
return {
|
return {
|
||||||
"trading_usdc": _round(bal.get("trading_usdc")),
|
"trading_usdc": _round(bal.get("trading_usdc")),
|
||||||
"funding_usdc": _round(bal.get("funding_usdc")),
|
"funding_usdc": _round(bal.get("funding_usdc")),
|
||||||
"funding_usdt": _round(bal.get("funding_usdt")),
|
"funding_usdt": _round(bal.get("funding_usdt")),
|
||||||
"trading_usdt": _round(bal.get("trading_usdt")),
|
"trading_usdt": trading_usdt,
|
||||||
|
"trading_usdt_avail": trading_usdt_avail,
|
||||||
|
"perp_margin_usdt": _round(bal.get("perp_margin_usdt")),
|
||||||
"funding_eth": _round_coin(bal.get("funding_eth")),
|
"funding_eth": _round_coin(bal.get("funding_eth")),
|
||||||
"trading_eth": _round_coin(bal.get("trading_eth")),
|
"trading_eth": _round_coin(bal.get("trading_eth")),
|
||||||
"funding_btc": _round_coin(bal.get("funding_btc")),
|
"funding_btc": _round_coin(bal.get("funding_btc")),
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ from __future__ import annotations
|
|||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from lib.hedge_plan.hedge_plan_option_primary_lib import (
|
from lib.hedge_plan.hedge_plan_option_primary_lib import (
|
||||||
estimate_combo_net_pnl,
|
estimate_combo_net_pnl,
|
||||||
fee_rate,
|
fee_rate,
|
||||||
|
option_fee_rate,
|
||||||
opt_type_for_view,
|
opt_type_for_view,
|
||||||
option_bid_liquidity_ok,
|
option_bid_liquidity_ok,
|
||||||
perp_direction_for_view,
|
perp_direction_for_view,
|
||||||
@@ -129,6 +130,7 @@ def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any
|
|||||||
get_cs = cfg.get("get_contract_size")
|
get_cs = cfg.get("get_contract_size")
|
||||||
fetch_idx = cfg.get("fetch_index_price")
|
fetch_idx = cfg.get("fetch_index_price")
|
||||||
fr = fee_rate()
|
fr = fee_rate()
|
||||||
|
fr_opt = option_fee_rate()
|
||||||
now_ms = int(datetime.now(tz=_BJ).timestamp() * 1000)
|
now_ms = int(datetime.now(tz=_BJ).timestamp() * 1000)
|
||||||
out: list[dict[str, Any]] = []
|
out: list[dict[str, Any]] = []
|
||||||
|
|
||||||
@@ -226,11 +228,15 @@ def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any
|
|||||||
contracts=float(contracts or 0),
|
contracts=float(contracts or 0),
|
||||||
contract_size=float(cs or 0.01),
|
contract_size=float(cs or 0.01),
|
||||||
fee=fr,
|
fee=fr,
|
||||||
|
option_fee=fr_opt,
|
||||||
)
|
)
|
||||||
opt_net_coin = float(net.get("opt_net") or 0)
|
opt_net_coin = float(net.get("opt_net") or 0)
|
||||||
|
# 币→U:用当前指数(与买币参考价同口径)
|
||||||
opt_net_usdt = round(opt_net_coin * float(idx or 0), 4) if idx else None
|
opt_net_usdt = round(opt_net_coin * float(idx or 0), 4) if idx else None
|
||||||
perp_net = float(net.get("perp_net") or 0)
|
perp_net = float(net.get("perp_net") or 0)
|
||||||
combo_usdt = round((opt_net_usdt or 0) + perp_net, 4) if opt_net_usdt is not None else round(perp_net, 4)
|
combo_usdt = round((opt_net_usdt or 0) + perp_net, 4) if opt_net_usdt is not None else round(perp_net, 4)
|
||||||
|
prem_coin = float(net.get("premium") or (float(avg_open or 0) * float(sheets or 0) * float(ct or 0.01)))
|
||||||
|
prem_usdt = round(prem_coin * float(idx or 0), 2) if idx else None
|
||||||
|
|
||||||
opened_at = plan.get("opened_at")
|
opened_at = plan.get("opened_at")
|
||||||
opened_ms = _parse_opened_ms(opened_at)
|
opened_ms = _parse_opened_ms(opened_at)
|
||||||
@@ -253,6 +259,7 @@ def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any
|
|||||||
"remain_sec": remain_sec,
|
"remain_sec": remain_sec,
|
||||||
"index_px": idx,
|
"index_px": idx,
|
||||||
"fee_rate": fr,
|
"fee_rate": fr,
|
||||||
|
"option_fee_rate": fr_opt,
|
||||||
"option": {
|
"option": {
|
||||||
"inst_id": inst_id or "—",
|
"inst_id": inst_id or "—",
|
||||||
"opt_type": opt_type,
|
"opt_type": opt_type,
|
||||||
@@ -266,6 +273,8 @@ def build_live_hedge_cards(cfg: dict[str, Any], conn: Any) -> list[dict[str, Any
|
|||||||
"leverage": opt_leverage,
|
"leverage": opt_leverage,
|
||||||
"liq_ok": bool(liq_ok),
|
"liq_ok": bool(liq_ok),
|
||||||
"liq_msg": liq_msg or "",
|
"liq_msg": liq_msg or "",
|
||||||
|
"premium_coin": round(prem_coin, 4),
|
||||||
|
"premium_usdt": prem_usdt,
|
||||||
"net_coin": opt_net_coin,
|
"net_coin": opt_net_coin,
|
||||||
"net_usdt": opt_net_usdt,
|
"net_usdt": opt_net_usdt,
|
||||||
"live": bool(opt_raw),
|
"live": bool(opt_raw),
|
||||||
|
|||||||
@@ -38,12 +38,29 @@ def is_option_primary(body_or_plan: dict[str, Any] | None) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def fee_rate() -> float:
|
def fee_rate() -> float:
|
||||||
|
"""永续 taker 费率(默认 0.05%)."""
|
||||||
try:
|
try:
|
||||||
return max(0.0, float(os.getenv("HEDGE_PLAN_FEE_RATE") or os.getenv("OKX_TAKER_FEE") or "0.0005"))
|
return max(0.0, float(os.getenv("HEDGE_PLAN_FEE_RATE") or os.getenv("OKX_TAKER_FEE") or "0.0005"))
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return 0.0005
|
return 0.0005
|
||||||
|
|
||||||
|
|
||||||
|
def option_fee_rate() -> float:
|
||||||
|
"""币本位期权 taker 费率(OKX 普通档默认 0.03%)."""
|
||||||
|
try:
|
||||||
|
return max(
|
||||||
|
0.0,
|
||||||
|
float(
|
||||||
|
os.getenv("HEDGE_PLAN_OPTION_FEE_RATE")
|
||||||
|
or os.getenv("OKX_OPTIONS_TAKER_FEE")
|
||||||
|
or os.getenv("SIM_OPTION_FEE_RATE")
|
||||||
|
or "0.0003"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return 0.0003
|
||||||
|
|
||||||
|
|
||||||
def floor2(v: float) -> float:
|
def floor2(v: float) -> float:
|
||||||
"""ETH 数量向下取两位小数."""
|
"""ETH 数量向下取两位小数."""
|
||||||
if v <= 0:
|
if v <= 0:
|
||||||
@@ -201,17 +218,30 @@ def estimate_combo_net_pnl(
|
|||||||
contracts: float,
|
contracts: float,
|
||||||
contract_size: float,
|
contract_size: float,
|
||||||
fee: Optional[float] = None,
|
fee: Optional[float] = None,
|
||||||
|
option_fee: Optional[float] = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""组合净利(扣费);平仓/卖出手续费按买入费率估算."""
|
"""组合净利(扣开平双边手续费).
|
||||||
fr = fee if fee is not None else fee_rate()
|
|
||||||
|
期权:吃卖一开/买一平(无滑点);费=Min(费率×标的币, 权利金×7%).
|
||||||
|
永续:名义×费率(开+平).
|
||||||
|
"""
|
||||||
|
from lib.sim.pricing_lib import option_fee_coin
|
||||||
|
|
||||||
|
fr_perp = fee if fee is not None else fee_rate()
|
||||||
|
fr_opt = option_fee if option_fee is not None else option_fee_rate()
|
||||||
ct = float(ct_mult or 0.01)
|
ct = float(ct_mult or 0.01)
|
||||||
sh = float(sheets or 0)
|
sh = float(sheets or 0)
|
||||||
a = float(ask_open or 0)
|
a = float(ask_open or 0)
|
||||||
b = float(bid or 0)
|
b = float(bid or 0)
|
||||||
premium = a * sh * ct
|
underlying = sh * ct
|
||||||
opt_proceeds = b * sh * ct
|
premium = a * underlying
|
||||||
opt_open_fee = premium * fr
|
opt_proceeds = b * underlying
|
||||||
opt_close_fee = opt_proceeds * fr # 卖出费用按买入费率
|
opt_open_fee = option_fee_coin(
|
||||||
|
underlying_coin=underlying, premium_coin=premium, fee_rate=fr_opt
|
||||||
|
)
|
||||||
|
opt_close_fee = option_fee_coin(
|
||||||
|
underlying_coin=underlying, premium_coin=opt_proceeds, fee_rate=fr_opt
|
||||||
|
)
|
||||||
opt_net = opt_proceeds - premium - opt_open_fee - opt_close_fee
|
opt_net = opt_proceeds - premium - opt_open_fee - opt_close_fee
|
||||||
|
|
||||||
coins = float(contracts or 0) * float(contract_size or 0.01)
|
coins = float(contracts or 0) * float(contract_size or 0.01)
|
||||||
@@ -224,15 +254,20 @@ def estimate_combo_net_pnl(
|
|||||||
perp_gross = (mark - entry) * coins
|
perp_gross = (mark - entry) * coins
|
||||||
perp_notional_open = abs(entry * coins)
|
perp_notional_open = abs(entry * coins)
|
||||||
perp_notional_close = abs(mark * coins)
|
perp_notional_close = abs(mark * coins)
|
||||||
perp_open_fee = perp_notional_open * fr
|
perp_open_fee = perp_notional_open * fr_perp
|
||||||
perp_close_fee = perp_notional_close * fr
|
perp_close_fee = perp_notional_close * fr_perp
|
||||||
perp_net = perp_gross - perp_open_fee - perp_close_fee
|
perp_net = perp_gross - perp_open_fee - perp_close_fee
|
||||||
total = opt_net + perp_net
|
total = opt_net + perp_net
|
||||||
return {
|
return {
|
||||||
"opt_net": round(opt_net, 4),
|
"opt_net": round(opt_net, 4),
|
||||||
"perp_net": round(perp_net, 4),
|
"perp_net": round(perp_net, 4),
|
||||||
"net": round(total, 4),
|
"net": round(total, 4),
|
||||||
"fee_rate": fr,
|
"fee_rate": fr_perp,
|
||||||
|
"option_fee_rate": fr_opt,
|
||||||
|
"opt_open_fee": round(opt_open_fee, 8),
|
||||||
|
"opt_close_fee": round(opt_close_fee, 8),
|
||||||
|
"perp_open_fee": round(perp_open_fee, 4),
|
||||||
|
"perp_close_fee": round(perp_close_fee, 4),
|
||||||
"premium": round(premium, 4),
|
"premium": round(premium, 4),
|
||||||
"opt_proceeds": round(opt_proceeds, 4),
|
"opt_proceeds": round(opt_proceeds, 4),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1431,18 +1431,40 @@ def _fetch_perp_market(cfg: dict[str, Any], base: str) -> tuple[dict[str, Any],
|
|||||||
|
|
||||||
|
|
||||||
def _options_account_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
def _options_account_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""期权账户资金快照(与期权页同源: exchange_options)."""
|
"""期权账户资金快照(与期权页同源: exchange_options).
|
||||||
|
|
||||||
|
trading_usdt=展示权益(含永续全仓占用);trading_usdt_avail=可开仓余额.
|
||||||
|
"""
|
||||||
out: dict[str, Any] = {
|
out: dict[str, Any] = {
|
||||||
"label": cfg.get("options_account_label") or "期权账户",
|
"label": cfg.get("options_account_label") or "期权账户",
|
||||||
"trading_usdc": None,
|
"trading_usdc": None,
|
||||||
"funding_usdc": None,
|
"funding_usdc": None,
|
||||||
"trading_usdt": None,
|
"trading_usdt": None,
|
||||||
"funding_usdt": None,
|
"funding_usdt": None,
|
||||||
|
"trading_usdt_avail": None,
|
||||||
|
"perp_margin_usdt": None,
|
||||||
|
"margin_note": "永续全仓 · 期权逐仓",
|
||||||
}
|
}
|
||||||
ex = cfg.get("exchange_options")
|
ex = cfg.get("exchange_options")
|
||||||
hdr = cfg.get("options_header_balances")
|
if ex is None:
|
||||||
if ex is None or not callable(hdr):
|
|
||||||
return out
|
return out
|
||||||
|
try:
|
||||||
|
from lib.exchange.okx_options_lib import options_header_balance_pack
|
||||||
|
|
||||||
|
pack = options_header_balance_pack(ex, force=False) or {}
|
||||||
|
out.update(
|
||||||
|
{
|
||||||
|
"trading_usdc": pack.get("trading_usdc"),
|
||||||
|
"funding_usdc": pack.get("funding_usdc"),
|
||||||
|
"trading_usdt": pack.get("trading_usdt"),
|
||||||
|
"funding_usdt": pack.get("funding_usdt"),
|
||||||
|
"trading_usdt_avail": pack.get("trading_usdt_avail", pack.get("trading_usdt")),
|
||||||
|
"perp_margin_usdt": pack.get("perp_margin_usdt"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
hdr = cfg.get("options_header_balances")
|
||||||
|
if callable(hdr):
|
||||||
try:
|
try:
|
||||||
trading_usdc, funding_usdc, funding_usdt, trading_usdt = hdr(ex, force=False)
|
trading_usdc, funding_usdc, funding_usdt, trading_usdt = hdr(ex, force=False)
|
||||||
out.update(
|
out.update(
|
||||||
@@ -1451,6 +1473,7 @@ def _options_account_snapshot(cfg: dict[str, Any]) -> dict[str, Any]:
|
|||||||
"funding_usdc": funding_usdc,
|
"funding_usdc": funding_usdc,
|
||||||
"trading_usdt": trading_usdt,
|
"trading_usdt": trading_usdt,
|
||||||
"funding_usdt": funding_usdt,
|
"funding_usdt": funding_usdt,
|
||||||
|
"trading_usdt_avail": trading_usdt,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -431,4 +431,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/hedge_plan.js?v=58"></script>
|
<script src="/static/hedge_plan.js?v=59"></script>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=13">
|
<link rel="stylesheet" href="/static/instance_page.css?v=13">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=118">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=119">
|
||||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||||
<script src="/static/open_submit_gate.js?v=1"></script>
|
<script src="/static/open_submit_gate.js?v=1"></script>
|
||||||
<meta name="theme-color" content="#0b0d14">
|
<meta name="theme-color" content="#0b0d14">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||||
<title>{{ pwa_app_name }}</title>
|
<title>{{ pwa_app_name }}</title>
|
||||||
<link rel="stylesheet" href="/static/instance_page.css?v=13">
|
<link rel="stylesheet" href="/static/instance_page.css?v=13">
|
||||||
<link rel="stylesheet" href="/static/instance_theme.css?v=118">
|
<link rel="stylesheet" href="/static/instance_theme.css?v=119">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
|
|||||||
+17
-4
@@ -7,7 +7,7 @@ import uuid
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from lib.sim.pricing_lib import option_fill, perp_fill, sim_fee_rate
|
from lib.sim.pricing_lib import option_fill, perp_fill, sim_fee_rate, sim_option_fee_rate
|
||||||
from lib.sim.wallets_lib import InsufficientFunds, SimWallets
|
from lib.sim.wallets_lib import InsufficientFunds, SimWallets
|
||||||
|
|
||||||
|
|
||||||
@@ -109,7 +109,20 @@ class SimBroker:
|
|||||||
self.wallets = SimWallets(get_db)
|
self.wallets = SimWallets(get_db)
|
||||||
|
|
||||||
def balances_header(self) -> dict[str, float]:
|
def balances_header(self) -> dict[str, float]:
|
||||||
return self.wallets.view()
|
"""资金快照.trading_usdt=全仓权益(可用+永续占用保证金);trading_usdt_free=可开仓余额."""
|
||||||
|
w = dict(self.wallets.view())
|
||||||
|
free = float(w.get("trading_usdt") or 0)
|
||||||
|
margin = 0.0
|
||||||
|
try:
|
||||||
|
for p in self.list_perp_positions():
|
||||||
|
margin += float(p.get("margin_usdt") or 0)
|
||||||
|
except Exception:
|
||||||
|
margin = 0.0
|
||||||
|
w["trading_usdt_free"] = free
|
||||||
|
w["perp_margin_usdt"] = margin
|
||||||
|
# 永续全仓:展示权益含占用保证金;期权逐仓权利金在币余额,不并入 USDT
|
||||||
|
w["trading_usdt"] = round(free + margin, 8)
|
||||||
|
return w
|
||||||
|
|
||||||
def list_perp_positions(self) -> list[dict[str, Any]]:
|
def list_perp_positions(self) -> list[dict[str, Any]]:
|
||||||
conn = self.get_db()
|
conn = self.get_db()
|
||||||
@@ -431,7 +444,7 @@ class SimBroker:
|
|||||||
n = int(sheets)
|
n = int(sheets)
|
||||||
if n < 1:
|
if n < 1:
|
||||||
return {"ok": False, "msg": "张数至少为 1"}
|
return {"ok": False, "msg": "张数至少为 1"}
|
||||||
fr = sim_fee_rate(fee_rate)
|
fr = sim_option_fee_rate(fee_rate)
|
||||||
bid, ask, ct_mult = _option_bid_ask(exchange_options, inst_id)
|
bid, ask, ct_mult = _option_bid_ask(exchange_options, inst_id)
|
||||||
if price is not None and float(price) > 0:
|
if price is not None and float(price) > 0:
|
||||||
# 限价: 用 min(limit, ask) 作为基准卖一近似, 仍走 option_fill 滑点
|
# 限价: 用 min(limit, ask) 作为基准卖一近似, 仍走 option_fill 滑点
|
||||||
@@ -540,7 +553,7 @@ class SimBroker:
|
|||||||
close_n = min(pos_s, float(n))
|
close_n = min(pos_s, float(n))
|
||||||
if close_n <= 0:
|
if close_n <= 0:
|
||||||
return {"ok": False, "msg": "平仓张数无效"}
|
return {"ok": False, "msg": "平仓张数无效"}
|
||||||
fr = sim_fee_rate(fee_rate)
|
fr = sim_option_fee_rate(fee_rate)
|
||||||
qty = close_n * ct_mult
|
qty = close_n * ct_mult
|
||||||
pr = option_fill(action="close", bid=bid, ask=ask, qty=qty, fee_rate=fr)
|
pr = option_fill(action="close", bid=bid, ask=ask, qty=qty, fee_rate=fr)
|
||||||
credit = pr.notional - pr.fee
|
credit = pr.notional - pr.fee
|
||||||
|
|||||||
+8
-2
@@ -55,7 +55,11 @@ def apply_sim_hooks(app_module: Any) -> None:
|
|||||||
|
|
||||||
def get_available_trading_usdt():
|
def get_available_trading_usdt():
|
||||||
if is_sim_mode(app_module.get_db):
|
if is_sim_mode(app_module.get_db):
|
||||||
return float(broker().balances_header()["trading_usdt"])
|
w = broker().balances_header()
|
||||||
|
# 开仓可用=未占用保证金的 USDT(勿用全仓权益)
|
||||||
|
if w.get("trading_usdt_free") is not None:
|
||||||
|
return float(w.get("trading_usdt_free") or 0)
|
||||||
|
return float(w.get("trading_usdt") or 0)
|
||||||
return _orig_avail()
|
return _orig_avail()
|
||||||
|
|
||||||
def get_live_position_contracts(exchange_symbol, direction):
|
def get_live_position_contracts(exchange_symbol, direction):
|
||||||
@@ -215,6 +219,7 @@ def _patch_okx_options_lib(app_module: Any) -> None:
|
|||||||
te = float(w.get("trading_eth") or 0)
|
te = float(w.get("trading_eth") or 0)
|
||||||
fb = float(w.get("funding_btc") or 0)
|
fb = float(w.get("funding_btc") or 0)
|
||||||
tb = float(w.get("trading_btc") or 0)
|
tb = float(w.get("trading_btc") or 0)
|
||||||
|
tu_free = float(w.get("trading_usdt_free") if w.get("trading_usdt_free") is not None else tu)
|
||||||
return {
|
return {
|
||||||
"scope": "main",
|
"scope": "main",
|
||||||
"funding_usdt": fu,
|
"funding_usdt": fu,
|
||||||
@@ -227,12 +232,13 @@ def _patch_okx_options_lib(app_module: Any) -> None:
|
|||||||
"trading_btc": tb,
|
"trading_btc": tb,
|
||||||
"funding_usdt_avail": fu,
|
"funding_usdt_avail": fu,
|
||||||
"funding_usdc_avail": fc,
|
"funding_usdc_avail": fc,
|
||||||
"trading_usdt_avail": tu,
|
"trading_usdt_avail": tu_free,
|
||||||
"trading_usdc_avail": tc,
|
"trading_usdc_avail": tc,
|
||||||
"funding_eth_avail": fe,
|
"funding_eth_avail": fe,
|
||||||
"trading_eth_avail": te,
|
"trading_eth_avail": te,
|
||||||
"funding_btc_avail": fb,
|
"funding_btc_avail": fb,
|
||||||
"trading_btc_avail": tb,
|
"trading_btc_avail": tb,
|
||||||
|
"perp_margin_usdt": float(w.get("perp_margin_usdt") or 0),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|||||||
+22
-10
@@ -1,8 +1,8 @@
|
|||||||
"""成交价与手续费.
|
"""成交价与手续费.
|
||||||
|
|
||||||
永续:滑点=1×fee_rate,手续费=名义×fee_rate.
|
永续:滑点=1×fee_rate,手续费=名义×fee_rate.
|
||||||
期权(币本位):滑点仍按权利金价×fee_rate;手续费对齐 OKX=
|
期权(币本位):吃卖一/买一按报价成交(无额外滑点);手续费对齐 OKX=
|
||||||
Min(fee_rate×标的币数量, 权利金×7%).
|
Min(fee_rate×标的币数量, 权利金×7%);普通用户 taker 默认 0.03%.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -32,6 +32,20 @@ def sim_fee_rate(override: float | None = None) -> float:
|
|||||||
return 0.0005
|
return 0.0005
|
||||||
|
|
||||||
|
|
||||||
|
def sim_option_fee_rate(override: float | None = None) -> float:
|
||||||
|
"""OKX 币本位期权 taker 费率(默认 0.03%)."""
|
||||||
|
if override is not None:
|
||||||
|
return float(override)
|
||||||
|
try:
|
||||||
|
return float(
|
||||||
|
os.getenv("SIM_OPTION_FEE_RATE")
|
||||||
|
or os.getenv("OKX_OPTIONS_TAKER_FEE")
|
||||||
|
or "0.0003"
|
||||||
|
)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return 0.0003
|
||||||
|
|
||||||
|
|
||||||
def perp_fill(
|
def perp_fill(
|
||||||
*,
|
*,
|
||||||
side: str,
|
side: str,
|
||||||
@@ -74,7 +88,7 @@ def option_fee_coin(
|
|||||||
Min(费率 × 张数对应标的币数量, 权利金×封顶比例).
|
Min(费率 × 张数对应标的币数量, 权利金×封顶比例).
|
||||||
注意:费率作用在标的币面值上,不是权利金名义上.
|
注意:费率作用在标的币面值上,不是权利金名义上.
|
||||||
"""
|
"""
|
||||||
f = sim_fee_rate(fee_rate)
|
f = sim_option_fee_rate(fee_rate)
|
||||||
qty = abs(float(underlying_coin or 0.0))
|
qty = abs(float(underlying_coin or 0.0))
|
||||||
prem = abs(float(premium_coin or 0.0))
|
prem = abs(float(premium_coin or 0.0))
|
||||||
if qty <= 0 and prem <= 0:
|
if qty <= 0 and prem <= 0:
|
||||||
@@ -92,20 +106,19 @@ def option_fill(
|
|||||||
bid: float,
|
bid: float,
|
||||||
ask: float,
|
ask: float,
|
||||||
qty: float,
|
qty: float,
|
||||||
fee_rate: float,
|
fee_rate: float | None = None,
|
||||||
) -> PriceResult:
|
) -> PriceResult:
|
||||||
"""开仓买入吃卖一; 平仓卖出吃买一.
|
"""开仓买入吃卖一; 平仓卖出吃买一.
|
||||||
|
|
||||||
qty = sheets × ct_mult(标的币数量).
|
qty = sheets × ct_mult(标的币数量).
|
||||||
滑点仍按权利金价×费率;手续费按 OKX 币本位公式(标的币×费率,权利金7%封顶).
|
成交价=买卖一(无额外滑点);手续费=OKX 币本位公式(标的币×费率,权利金7%封顶).
|
||||||
"""
|
"""
|
||||||
f = float(fee_rate)
|
f = sim_option_fee_rate(fee_rate)
|
||||||
if action == "open":
|
if action == "open":
|
||||||
base = float(ask)
|
base = float(ask)
|
||||||
fill = base * (1.0 + f)
|
|
||||||
else:
|
else:
|
||||||
base = float(bid)
|
base = float(bid)
|
||||||
fill = base * (1.0 - f)
|
fill = base
|
||||||
qty_f = abs(float(qty))
|
qty_f = abs(float(qty))
|
||||||
notional = abs(fill * qty_f)
|
notional = abs(fill * qty_f)
|
||||||
fee = option_fee_coin(
|
fee = option_fee_coin(
|
||||||
@@ -113,8 +126,7 @@ def option_fill(
|
|||||||
premium_coin=notional,
|
premium_coin=notional,
|
||||||
fee_rate=f,
|
fee_rate=f,
|
||||||
)
|
)
|
||||||
slip = abs(fill - base) * qty_f
|
return PriceResult(base_px=base, fill_px=fill, fee=fee, slip=0.0, notional=notional)
|
||||||
return PriceResult(base_px=base, fill_px=fill, fee=fee, slip=slip, notional=notional)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user