feat: restructure OKX options UI with header funds, settings card, and dual-column layout
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2458,4 +2458,62 @@ html[data-theme="light"] .settings-export-link {
|
||||
.options-order-mode-row input[type="number"] {
|
||||
width: 88px;
|
||||
}
|
||||
.options-dual-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.15fr 0.85fr;
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.options-dual-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.options-order-card h2,
|
||||
.options-pos-card-wrap h2 {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
.options-pos-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.options-pos-head h2 {
|
||||
margin: 0;
|
||||
}
|
||||
.options-pos-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.opt-pos-tab.active {
|
||||
border-color: #5b8cff;
|
||||
color: #cfe0ff;
|
||||
background: rgba(74, 124, 255, 0.28);
|
||||
}
|
||||
.options-pos-pane {
|
||||
min-height: 200px;
|
||||
}
|
||||
.opt-pos-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.options-settings-block {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.options-settings-row {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.options-settings-hint {
|
||||
font-size: 0.82rem;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.pos-pnl-profit {
|
||||
color: #7ee787;
|
||||
}
|
||||
.pos-pnl-loss {
|
||||
color: #ff8b8b;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
optType: "C",
|
||||
chain: null,
|
||||
selectedInst: null,
|
||||
convertQuoteId: null,
|
||||
posTab: "live",
|
||||
};
|
||||
|
||||
function fmt(v, d) {
|
||||
@@ -55,20 +55,17 @@
|
||||
if (!panel || !instId) return;
|
||||
const row = document.querySelector('#opt-strike-tbody tr.opt-strike-row[data-inst="' + CSS.escape(instId) + '"]');
|
||||
if (!row) return;
|
||||
|
||||
document.querySelectorAll(".opt-strike-row").forEach(function (r) {
|
||||
r.classList.toggle("opt-row-selected", r === row);
|
||||
});
|
||||
document.querySelectorAll(".opt-pick-btn").forEach(function (b) {
|
||||
const on = b.getAttribute("data-inst") === instId;
|
||||
b.classList.toggle("active", on);
|
||||
if (!b.dataset.origText) b.dataset.origText = b.textContent;
|
||||
if (on) b.textContent = "已选";
|
||||
document.querySelectorAll(".opt-pick-btn").forEach(function (btn) {
|
||||
const on = btn.getAttribute("data-inst") === instId;
|
||||
btn.classList.toggle("active", on);
|
||||
if (!btn.dataset.origText) btn.dataset.origText = btn.textContent;
|
||||
if (on) btn.textContent = "已选";
|
||||
});
|
||||
|
||||
const oldInline = document.querySelector(".opt-order-inline-row");
|
||||
if (oldInline) oldInline.remove();
|
||||
|
||||
const tr = document.createElement("tr");
|
||||
tr.className = "opt-order-inline-row";
|
||||
const td = document.createElement("td");
|
||||
@@ -113,15 +110,8 @@
|
||||
return '<span class="opt-moneyness opt-moneyness-' + m + '">' + label + "</span>";
|
||||
}
|
||||
|
||||
async function refreshBalances() {
|
||||
const d = await apiJson("/api/options/balances");
|
||||
if (!d.ok) return;
|
||||
document.getElementById("opt-funding-usdt").textContent = fmt(d.funding_usdt) + " U";
|
||||
document.getElementById("opt-funding-usdc").textContent = fmt(d.funding_usdc) + " U";
|
||||
document.getElementById("opt-trading-usdt").textContent = fmt(d.trading_usdt) + " U";
|
||||
document.getElementById("opt-trading-usdc").textContent = fmt(d.trading_usdc) + " U";
|
||||
document.getElementById("opt-trading-usdg").textContent = fmt(d.trading_usdg) + " U";
|
||||
document.getElementById("opt-trade-budget").textContent = fmt(d.trade_budget) + " USDC";
|
||||
function optTypeLabel(t) {
|
||||
return (t || "").toUpperCase() === "P" ? "看跌 Put" : "看涨 Call";
|
||||
}
|
||||
|
||||
function expLabel(ms) {
|
||||
@@ -299,8 +289,8 @@
|
||||
msgEl.textContent = d.ok ? "下单已提交,可在 OKX 委托中查看" : (d.msg || "失败");
|
||||
msgEl.classList.toggle("opt-error", !d.ok);
|
||||
if (d.ok) {
|
||||
refreshBalances();
|
||||
refreshPositions();
|
||||
if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
|
||||
} else {
|
||||
alert(d.msg || "下单失败");
|
||||
}
|
||||
@@ -309,6 +299,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
function renderPositionCard(p) {
|
||||
const upl = p.upl;
|
||||
const uplCls = upl > 0 ? "pos-pnl-profit" : upl < 0 ? "pos-pnl-loss" : "";
|
||||
const sideCls = (p.opt_type || "").toUpperCase() === "P" ? "pos-side-short" : "pos-side-long";
|
||||
return (
|
||||
'<div class="pos-card opt-pos-card" data-inst="' + (p.inst_id || "") + '">' +
|
||||
'<div class="pos-card-head">' +
|
||||
'<div class="pos-card-symbol"><strong>' + (p.inst_id || "") + '</strong>' +
|
||||
'<span class="pos-side-badge ' + sideCls + '">' + optTypeLabel(p.opt_type) + "</span></div>" +
|
||||
'<div class="pos-head-actions">' +
|
||||
'<button type="button" class="btn-primary opt-close-btn" data-inst="' + p.inst_id + '">限价平仓</button>' +
|
||||
"</div></div>" +
|
||||
'<div class="pos-meta">' +
|
||||
'<span class="pos-meta-item">行权价: ' + fmt(p.strike, 0) + "</span>" +
|
||||
'<span class="pos-meta-item">张数: ' + fmt(p.pos, 0) + " · 币量 " + fmt(p.eth_amount, 4) + "</span>" +
|
||||
"</div>" +
|
||||
'<div class="pos-grid">' +
|
||||
'<div class="pos-cell"><span class="pos-label">开仓均价</span><span class="pos-value">' + fmt(p.avg_px, 4) + "</span></div>" +
|
||||
'<div class="pos-cell"><span class="pos-label">标记价</span><span class="pos-value">' + fmt(p.mark_px, 4) + "</span></div>" +
|
||||
'<div class="pos-cell"><span class="pos-label">浮盈亏</span><span class="pos-value ' + uplCls + '">' + fmt(p.upl, 4) + "</span></div>" +
|
||||
'<div class="pos-cell"><span class="pos-label">收益率</span><span class="pos-value ' + uplCls + '">' +
|
||||
(p.upl_ratio_pct != null ? p.upl_ratio_pct + "%" : "—") + "</span></div>" +
|
||||
"</div></div>"
|
||||
);
|
||||
}
|
||||
|
||||
async function closePosition(inst, btn) {
|
||||
const q = await apiJson("/api/options/quote?inst_id=" + encodeURIComponent(inst) + "&mode=sheets&sheets=1");
|
||||
if (!q.ok) {
|
||||
@@ -334,7 +350,8 @@
|
||||
alert(r.msg || "平仓失败");
|
||||
}
|
||||
refreshPositions();
|
||||
refreshBalances();
|
||||
refreshHistory();
|
||||
if (typeof refreshAccountSnapshot === "function") refreshAccountSnapshot();
|
||||
} finally {
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
@@ -342,33 +359,62 @@
|
||||
|
||||
async function refreshPositions() {
|
||||
const d = await apiJson("/api/options/positions");
|
||||
const tbody = document.getElementById("opt-positions-tbody");
|
||||
tbody.innerHTML = "";
|
||||
const wrap = document.getElementById("opt-pos-cards");
|
||||
const empty = document.getElementById("opt-pos-empty");
|
||||
const list = (d.ok && d.positions) || [];
|
||||
wrap.innerHTML = "";
|
||||
if (!list.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="muted">暂无持仓</td></tr>';
|
||||
empty.style.display = "";
|
||||
return;
|
||||
}
|
||||
empty.style.display = "none";
|
||||
list.forEach(function (p) {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML =
|
||||
"<td><code>" + (p.inst_id || "") + "</code></td>" +
|
||||
"<td>" + fmt(p.pos, 0) + "</td>" +
|
||||
"<td>" + fmt(p.eth_amount, 4) + "</td>" +
|
||||
"<td>" + fmt(p.avg_px, 4) + "</td>" +
|
||||
"<td>" + fmt(p.mark_px, 4) + "</td>" +
|
||||
"<td>" + fmt(p.upl, 4) + "</td>" +
|
||||
"<td>" + (p.upl_ratio_pct != null ? p.upl_ratio_pct + "%" : "—") + "</td>" +
|
||||
'<td><button type="button" class="btn-primary opt-close-btn" data-inst="' + p.inst_id + '">限价平仓</button></td>';
|
||||
tbody.appendChild(tr);
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = renderPositionCard(p);
|
||||
wrap.appendChild(div.firstChild);
|
||||
});
|
||||
tbody.querySelectorAll(".opt-close-btn").forEach(function (btn) {
|
||||
wrap.querySelectorAll(".opt-close-btn").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
closePosition(btn.getAttribute("data-inst"), btn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshHistory() {
|
||||
const d = await apiJson("/api/options/history");
|
||||
const tbody = document.getElementById("opt-history-tbody");
|
||||
tbody.innerHTML = "";
|
||||
const list = (d.ok && d.history) || [];
|
||||
if (!list.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="muted">暂无历史记录</td></tr>';
|
||||
return;
|
||||
}
|
||||
list.forEach(function (h) {
|
||||
const tr = document.createElement("tr");
|
||||
const prem = h.status === "closed" ? h.premium_received : h.premium_paid;
|
||||
const pnl = h.realized_pnl;
|
||||
const pnlTxt = pnl != null ? fmt(pnl, 4) : "—";
|
||||
tr.innerHTML =
|
||||
"<td><code>" + (h.inst_id || "") + "</code></td>" +
|
||||
"<td>" + fmt(h.sheets, 0) + "</td>" +
|
||||
"<td>" + fmt(prem, 4) + "</td>" +
|
||||
"<td>" + (h.status === "closed" ? "已平" : "持仓中") + "</td>" +
|
||||
"<td>" + pnlTxt + "</td>" +
|
||||
"<td>" + (h.closed_at || h.created_at || "—") + "</td>";
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function switchPosTab(tab) {
|
||||
state.posTab = tab;
|
||||
document.querySelectorAll(".opt-pos-tab").forEach(function (b) {
|
||||
b.classList.toggle("active", b.getAttribute("data-tab") === tab);
|
||||
});
|
||||
document.getElementById("opt-pos-live").hidden = tab !== "live";
|
||||
document.getElementById("opt-pos-history").hidden = tab !== "history";
|
||||
if (tab === "history") refreshHistory();
|
||||
}
|
||||
|
||||
document.querySelectorAll(".opt-uly-btn").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
document.querySelectorAll(".opt-uly-btn").forEach(function (b) { b.classList.remove("active"); });
|
||||
@@ -387,10 +433,18 @@
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll(".opt-pos-tab").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
switchPosTab(btn.getAttribute("data-tab"));
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("opt-exp-select").addEventListener("change", renderStrikes);
|
||||
document.getElementById("opt-load-chain").addEventListener("click", loadChain);
|
||||
document.getElementById("opt-refresh-balances").addEventListener("click", refreshBalances);
|
||||
document.getElementById("opt-refresh-positions").addEventListener("click", refreshPositions);
|
||||
document.getElementById("opt-refresh-positions").addEventListener("click", function () {
|
||||
refreshPositions();
|
||||
if (state.posTab === "history") refreshHistory();
|
||||
});
|
||||
document.getElementById("opt-open-btn").addEventListener("click", openPosition);
|
||||
|
||||
document.querySelectorAll('input[name="opt-size-mode"]').forEach(function (r) {
|
||||
@@ -408,61 +462,7 @@
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("opt-convert-quote-btn").addEventListener("click", async function () {
|
||||
const amount = parseFloat(document.getElementById("opt-convert-amount").value);
|
||||
if (!amount || amount <= 0) {
|
||||
alert("请输入 USDT 数量");
|
||||
return;
|
||||
}
|
||||
const d = await apiJson("/api/options/convert/quote", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ amount: amount }),
|
||||
});
|
||||
const prev = document.getElementById("opt-convert-preview");
|
||||
if (!d.ok) {
|
||||
prev.textContent = d.msg || "询价失败";
|
||||
state.convertQuoteId = null;
|
||||
document.getElementById("opt-convert-exec-btn").disabled = true;
|
||||
return;
|
||||
}
|
||||
state.convertQuoteId = d.quote_id;
|
||||
prev.textContent =
|
||||
"预估获得 " + fmt(d.base_sz, 6) + " USDC,汇率 " + fmt(d.cnvt_px, 6);
|
||||
document.getElementById("opt-convert-exec-btn").disabled = false;
|
||||
});
|
||||
|
||||
document.getElementById("opt-convert-exec-btn").addEventListener("click", async function () {
|
||||
if (!state.convertQuoteId) return;
|
||||
const amount = parseFloat(document.getElementById("opt-convert-amount").value);
|
||||
const d = await apiJson("/api/options/convert/execute", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ quote_id: state.convertQuoteId, rfq_sz: amount }),
|
||||
});
|
||||
document.getElementById("opt-convert-preview").textContent = d.ok ? "兑换成功" : (d.msg || "失败");
|
||||
state.convertQuoteId = null;
|
||||
document.getElementById("opt-convert-exec-btn").disabled = true;
|
||||
refreshBalances();
|
||||
});
|
||||
|
||||
document.getElementById("opt-transfer-btn").addEventListener("click", async function () {
|
||||
const d = await apiJson("/api/options/transfer", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
ccy: document.getElementById("opt-transfer-ccy").value,
|
||||
from: document.getElementById("opt-transfer-from").value,
|
||||
to: document.getElementById("opt-transfer-to").value,
|
||||
amount: parseFloat(document.getElementById("opt-transfer-amount").value),
|
||||
}),
|
||||
});
|
||||
document.getElementById("opt-transfer-msg").textContent = d.ok ? "划转成功" : (d.msg || "失败");
|
||||
refreshBalances();
|
||||
});
|
||||
|
||||
updateSizeInputs();
|
||||
refreshBalances();
|
||||
loadChain();
|
||||
refreshPositions();
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const root = document.getElementById("options-settings-root");
|
||||
if (!root) return;
|
||||
|
||||
async function apiJson(url, opts) {
|
||||
const r = await fetch(url, Object.assign({ credentials: "same-origin" }, opts || {}));
|
||||
return r.json();
|
||||
}
|
||||
|
||||
function setMsg(id, text, isErr) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.textContent = text || "";
|
||||
el.classList.toggle("opt-error", !!isErr);
|
||||
}
|
||||
|
||||
document.getElementById("opt-set-swap-btn").addEventListener("click", async function () {
|
||||
const amount = parseFloat(document.getElementById("opt-set-swap-amount").value);
|
||||
if (!amount || amount <= 0) {
|
||||
setMsg("opt-set-swap-msg", "请输入有效数量", true);
|
||||
return;
|
||||
}
|
||||
const d = await apiJson("/api/options/spot/swap", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
direction: document.getElementById("opt-set-swap-dir").value,
|
||||
amount: amount,
|
||||
}),
|
||||
});
|
||||
setMsg("opt-set-swap-msg", d.ok ? "兑换单已提交" : (d.msg || "失败"), !d.ok);
|
||||
});
|
||||
|
||||
document.getElementById("opt-set-int-btn").addEventListener("click", async function () {
|
||||
const amount = parseFloat(document.getElementById("opt-set-int-amount").value);
|
||||
if (!amount || amount <= 0) {
|
||||
setMsg("opt-set-int-msg", "请输入有效数量", true);
|
||||
return;
|
||||
}
|
||||
const d = await apiJson("/api/options/transfer", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
ccy: document.getElementById("opt-set-int-ccy").value,
|
||||
from: document.getElementById("opt-set-int-from").value,
|
||||
to: document.getElementById("opt-set-int-to").value,
|
||||
amount: amount,
|
||||
}),
|
||||
});
|
||||
setMsg("opt-set-int-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok);
|
||||
});
|
||||
|
||||
document.getElementById("opt-set-cross-btn").addEventListener("click", async function () {
|
||||
const amount = parseFloat(document.getElementById("opt-set-cross-amount").value);
|
||||
if (!amount || amount <= 0) {
|
||||
setMsg("opt-set-cross-msg", "请输入有效数量", true);
|
||||
return;
|
||||
}
|
||||
const d = await apiJson("/api/options/cross-transfer", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
ccy: document.getElementById("opt-set-cross-ccy").value,
|
||||
amount: amount,
|
||||
account: document.getElementById("opt-set-cross-acct").value,
|
||||
direction: document.getElementById("opt-set-cross-dir").value,
|
||||
}),
|
||||
});
|
||||
setMsg("opt-set-cross-msg", d.ok ? "划转成功" : (d.msg || "失败"), !d.ok);
|
||||
});
|
||||
})();
|
||||
@@ -478,6 +478,95 @@ def transfer_ccy(
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
_OKX_ACCT_CODE = {"funding": "6", "trading": "18", "spot": "18"}
|
||||
|
||||
|
||||
def fetch_options_trading_usdc(ex: ccxt.okx) -> float | None:
|
||||
bal = fetch_options_balances(ex)
|
||||
v = bal.get("trading_usdc")
|
||||
if v is None:
|
||||
return None
|
||||
return round(float(v), 2)
|
||||
|
||||
|
||||
def spot_market_swap_usdt_usdc(
|
||||
ex: ccxt.okx,
|
||||
*,
|
||||
direction: str,
|
||||
amount: float,
|
||||
) -> dict[str, Any]:
|
||||
"""现货市价兑换 USDC-USDT。direction: usdt_to_usdc | usdc_to_usdt。"""
|
||||
if amount <= 0:
|
||||
return {"ok": False, "msg": "数量须大于 0"}
|
||||
d = (direction or "").lower()
|
||||
inst_id = "USDC-USDT"
|
||||
try:
|
||||
if d == "usdt_to_usdc":
|
||||
body = {
|
||||
"instId": inst_id,
|
||||
"tdMode": "cash",
|
||||
"side": "buy",
|
||||
"ordType": "market",
|
||||
"sz": str(amount),
|
||||
"tgtCcy": "quote",
|
||||
}
|
||||
elif d == "usdc_to_usdt":
|
||||
body = {
|
||||
"instId": inst_id,
|
||||
"tdMode": "cash",
|
||||
"side": "sell",
|
||||
"ordType": "market",
|
||||
"sz": str(amount),
|
||||
"tgtCcy": "base",
|
||||
}
|
||||
else:
|
||||
return {"ok": False, "msg": "direction 须为 usdt_to_usdc 或 usdc_to_usdt"}
|
||||
resp = ex.private_post_trade_order(body)
|
||||
data = (resp or {}).get("data") or []
|
||||
if data and str(data[0].get("sCode")) == "0":
|
||||
return {"ok": True, "data": data[0], "raw": resp}
|
||||
msg = data[0].get("sMsg") if data else str(resp)
|
||||
return {"ok": False, "msg": msg or "现货兑换失败", "raw": resp}
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
def transfer_main_sub_account(
|
||||
ex: ccxt.okx,
|
||||
*,
|
||||
ccy: str,
|
||||
amount: float,
|
||||
sub_acct: str,
|
||||
main_to_sub: bool,
|
||||
account: str = "funding",
|
||||
) -> dict[str, Any]:
|
||||
"""主账户与子账户之间划转(须主账户 API)。"""
|
||||
if amount <= 0:
|
||||
return {"ok": False, "msg": "划转金额须大于 0"}
|
||||
sub = (sub_acct or "").strip()
|
||||
if not sub:
|
||||
return {"ok": False, "msg": "未配置子账户名称 OKX_SUB_ACCOUNT_NAME"}
|
||||
acct_code = _OKX_ACCT_CODE.get((account or "funding").lower(), "6")
|
||||
try:
|
||||
resp = ex.private_post_asset_transfer(
|
||||
{
|
||||
"type": "1" if main_to_sub else "2",
|
||||
"ccy": str(ccy).upper(),
|
||||
"amt": str(amount),
|
||||
"from": acct_code,
|
||||
"to": acct_code,
|
||||
"subAcct": sub,
|
||||
}
|
||||
)
|
||||
data = (resp or {}).get("data") or []
|
||||
if data and str(data[0].get("sCode", "0")) == "0":
|
||||
return {"ok": True, "data": data[0], "raw": resp}
|
||||
msg = data[0].get("sMsg") if data else str(resp)
|
||||
return {"ok": False, "msg": msg or "主/子账户划转失败", "raw": resp}
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
def format_position_row(pos: dict[str, Any], ct_mult: float = 0.01) -> dict[str, Any]:
|
||||
sheets = _safe_float(pos.get("pos")) or 0.0
|
||||
avg = _safe_float(pos.get("avgPx"))
|
||||
|
||||
@@ -85,11 +85,18 @@ def profit_loss_ratio_from_trades(trades: list[dict[str, Any]] | None) -> float
|
||||
return profit_loss_ratio_from_averages(avg_win, avg_loss)
|
||||
|
||||
|
||||
def total_funds_usdt(funding_usdt: float | None, trading_usdt: float | None) -> float | None:
|
||||
def total_funds_usdt(
|
||||
funding_usdt: float | None,
|
||||
trading_usdt: float | None,
|
||||
options_trading_usdc: float | None = None,
|
||||
) -> float | None:
|
||||
if funding_usdt is None:
|
||||
return None
|
||||
try:
|
||||
return round(float(funding_usdt) + float(trading_usdt or 0), 2)
|
||||
total = float(funding_usdt) + float(trading_usdt or 0)
|
||||
if options_trading_usdc is not None:
|
||||
total += float(options_trading_usdc)
|
||||
return round(total, 2)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
@@ -139,18 +139,21 @@ def build_instance_settings_view(
|
||||
opt_key = (os.getenv("OKX_OPTIONS_API_KEY") or "").strip()
|
||||
sections.append(
|
||||
{
|
||||
"title": "期权账户(主账户)",
|
||||
"title": "期权设置",
|
||||
"rows": [
|
||||
_row("期权模块", "已启用"),
|
||||
_row(
|
||||
"期权 API",
|
||||
f"已配置(…{opt_key[-4:]})" if len(opt_key) >= 4 else "未配置",
|
||||
),
|
||||
_row("单笔权利金上限", f"{_env_float('OKX_OPTIONS_TRADE_BUDGET_USDC', 10):g} USDC"),
|
||||
_row(
|
||||
"资金说明",
|
||||
"资金账户 USDT 兑换 USDC 后划转到交易账户",
|
||||
"期权页操作;与永续子账户资金分开",
|
||||
"子账户",
|
||||
(os.getenv("OKX_SUB_ACCOUNT_NAME") or "").strip() or "未配置 OKX_SUB_ACCOUNT_NAME",
|
||||
"主/子账户划转用",
|
||||
),
|
||||
_row(
|
||||
"说明",
|
||||
"币种兑换与账户划转到右侧「期权设置」卡片操作",
|
||||
),
|
||||
],
|
||||
}
|
||||
@@ -169,6 +172,9 @@ def build_instance_settings_view(
|
||||
"sections": sections,
|
||||
"data_export_version": int(data_export_version),
|
||||
"show_transfer": (exchange_key or "").strip().lower() in ("gate", "binance", "okx"),
|
||||
"options_settings_enabled": (exchange_key or "").strip().lower() == "okx"
|
||||
and _env_bool("OKX_OPTIONS_ENABLED", False),
|
||||
"options_sub_account": (os.getenv("OKX_SUB_ACCOUNT_NAME") or "").strip(),
|
||||
"auto_transfer_enabled": auto_transfer_on,
|
||||
"auto_transfer_bj_hour": _env_int("AUTO_TRANSFER_BJ_HOUR", 8),
|
||||
"auto_transfer_amount": _env_float("AUTO_TRANSFER_AMOUNT", 30),
|
||||
|
||||
@@ -1061,6 +1061,11 @@ function refreshAccountSnapshot(){
|
||||
const el = document.getElementById("current-capital");
|
||||
if(el) el.innerText = `${Number(data.current_capital).toFixed(2)}U`;
|
||||
}
|
||||
if (typeof data.options_trading_usdc !== "undefined") {
|
||||
const el = document.getElementById("options-trading-usdc");
|
||||
if (el) el.innerText = (data.options_trading_usdc === null || data.options_trading_usdc === undefined)
|
||||
? "—" : `${Number(data.options_trading_usdc).toFixed(2)} USDC`;
|
||||
}
|
||||
if (typeof data.unrealized_pnl !== "undefined") {
|
||||
paintRealtimePnl(data.unrealized_pnl);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</div>
|
||||
{% with msg=get_flashed_messages() %}{% if msg %}<div class="flash">{{ msg[0] }}</div>{% endif %}{% endwith %}
|
||||
|
||||
{% if page != 'settings' and page != 'options' %}
|
||||
{% if page != 'settings' %}
|
||||
{% include 'instance_header_panel.html' %}
|
||||
{% endif %}
|
||||
{% if page != 'settings' and page != 'options' %}
|
||||
@@ -1612,6 +1612,11 @@ function refreshAccountSnapshot(){
|
||||
const el = document.getElementById("current-capital");
|
||||
if(el) el.innerText = `${Number(data.current_capital).toFixed(2)}U`;
|
||||
}
|
||||
if (typeof data.options_trading_usdc !== "undefined") {
|
||||
const el = document.getElementById("options-trading-usdc");
|
||||
if (el) el.innerText = (data.options_trading_usdc === null || data.options_trading_usdc === undefined)
|
||||
? "—" : `${Number(data.options_trading_usdc).toFixed(2)} USDC`;
|
||||
}
|
||||
if (typeof data.unrealized_pnl !== "undefined") {
|
||||
paintRealtimePnl(data.unrealized_pnl);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,12 @@
|
||||
<div class="label">交易账户</div>
|
||||
<div class="value" id="current-capital">{{ funds_fmt(current_capital) }}U</div>
|
||||
</div>
|
||||
{% if options_enabled %}
|
||||
<div class="stat-strip-item">
|
||||
<div class="label">期权交易账户</div>
|
||||
<div class="value" id="options-trading-usdc">{% if options_trading_usdc is not none %}{{ funds_fmt(options_trading_usdc) }} USDC{% else %}—{% endif %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="stat-strip-item stat-strip-item--pnl">
|
||||
<div class="label">实时盈亏</div>
|
||||
<div class="value" id="realtime-pnl">—</div>
|
||||
|
||||
@@ -39,6 +39,13 @@
|
||||
</div>
|
||||
|
||||
<div class="settings-side-col">
|
||||
{% if instance_settings.options_settings_enabled %}
|
||||
<div class="card settings-card">
|
||||
<h2>期权设置</h2>
|
||||
<p class="settings-card-desc">主账户期权资金:现货市价兑换 USDT/USDC;主账户与子账户(永续)之间划转。</p>
|
||||
{% include 'options_settings_panel.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if instance_settings.show_transfer %}
|
||||
<div class="card settings-card">
|
||||
<h2>资金划转</h2>
|
||||
|
||||
@@ -70,11 +70,14 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
||||
place_option_limit_order,
|
||||
place_option_market_order,
|
||||
quote_option_contract,
|
||||
spot_market_swap_usdt_usdc,
|
||||
transfer_ccy,
|
||||
transfer_main_sub_account,
|
||||
)
|
||||
|
||||
return {
|
||||
"enabled": _env_bool("OKX_OPTIONS_ENABLED", False),
|
||||
"sub_account_name": (os.getenv("OKX_SUB_ACCOUNT_NAME") or "").strip(),
|
||||
"get_db": app_module.get_db,
|
||||
"login_required": app_module.login_required,
|
||||
"exchange_options": getattr(app_module, "exchange_options", None),
|
||||
@@ -101,6 +104,8 @@ def _build_cfg(app_module: Any) -> dict[str, Any]:
|
||||
"estimate_usdt_to_usdc": estimate_usdt_to_usdc,
|
||||
"execute_convert": execute_convert,
|
||||
"transfer_ccy": transfer_ccy,
|
||||
"spot_market_swap_usdt_usdc": spot_market_swap_usdt_usdc,
|
||||
"transfer_main_sub_account": transfer_main_sub_account,
|
||||
"options_api_ready": options_api_ready,
|
||||
}
|
||||
|
||||
@@ -452,6 +457,89 @@ def register_options_routes(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
conn.close()
|
||||
return jsonify(result)
|
||||
|
||||
@app.route("/api/options/spot/swap", methods=["POST"])
|
||||
@lr
|
||||
def api_options_spot_swap():
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
data = request.get_json(silent=True) or {}
|
||||
direction = (data.get("direction") or "usdt_to_usdc").strip()
|
||||
try:
|
||||
amount = float(data.get("amount"))
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({"ok": False, "msg": "数量无效"})
|
||||
return jsonify(cfg["spot_market_swap_usdt_usdc"](ex, direction=direction, amount=amount))
|
||||
|
||||
@app.route("/api/options/cross-transfer", methods=["POST"])
|
||||
@lr
|
||||
def api_options_cross_transfer():
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
data = request.get_json(silent=True) or {}
|
||||
ccy = (data.get("ccy") or "USDT").upper()
|
||||
account = (data.get("account") or "funding").strip()
|
||||
direction = (data.get("direction") or "sub_to_main").strip()
|
||||
try:
|
||||
amount = float(data.get("amount"))
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({"ok": False, "msg": "数量无效"})
|
||||
main_to_sub = direction == "main_to_sub"
|
||||
result = cfg["transfer_main_sub_account"](
|
||||
ex,
|
||||
ccy=ccy,
|
||||
amount=amount,
|
||||
sub_acct=cfg.get("sub_account_name") or "",
|
||||
main_to_sub=main_to_sub,
|
||||
account=account,
|
||||
)
|
||||
if result.get("ok"):
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
init_options_tables(conn)
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO options_transfer_log (ccy, amount, from_account, to_account, status, message)
|
||||
VALUES (?, ?, ?, ?, 'ok', ?)
|
||||
""",
|
||||
(
|
||||
ccy,
|
||||
amount,
|
||||
"main" if main_to_sub else "sub",
|
||||
"sub" if main_to_sub else "main",
|
||||
f"cross:{account}",
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
return jsonify(result)
|
||||
|
||||
@app.route("/api/options/history")
|
||||
@lr
|
||||
def api_options_history():
|
||||
ex, err = _require_options_ex(cfg)
|
||||
if ex is None:
|
||||
return jsonify({"ok": False, "msg": err})
|
||||
conn = cfg["get_db"]()
|
||||
try:
|
||||
init_options_tables(conn)
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT id, inst_id, underlying, opt_type, strike, sheets, eth_amount,
|
||||
open_quote, premium_paid, close_quote, premium_received,
|
||||
realized_pnl, status, signal_note, created_at, closed_at
|
||||
FROM options_trades
|
||||
ORDER BY id DESC
|
||||
LIMIT 200
|
||||
"""
|
||||
).fetchall()
|
||||
items = [dict(r) for r in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
return jsonify({"ok": True, "history": items})
|
||||
|
||||
|
||||
def _start_monitor_thread(app: Flask, cfg: dict[str, Any]) -> None:
|
||||
if app.extensions.get("options_monitor_started"):
|
||||
|
||||
@@ -1,135 +1,96 @@
|
||||
<div class="card options-page-card" style="grid-column:1/-1" id="options-root"
|
||||
data-trade-budget="{{ options_trade_budget | default(10) }}"
|
||||
<div class="options-page-wrap" style="grid-column:1/-1" id="options-root"
|
||||
data-default-underly="{{ options_default_underly | default('ETH') }}">
|
||||
<h2>期权(USDⓈ 本位 · 仅买方)</h2>
|
||||
{% if not options_enabled %}
|
||||
<div class="flash" style="margin-bottom:12px">期权 API 未启用:请在 <code>crypto_monitor_okx/.env</code> 设置 <code>OKX_OPTIONS_ENABLED=true</code> 及主账户 <code>OKX_OPTIONS_API_*</code>,然后 <code>pm2 restart crypto_okx --update-env</code>。</div>
|
||||
{% endif %}
|
||||
<p class="muted options-hint">资金账户兑换 USDT→USDC 后,划转到交易账户即可买入。报价单位为每 1 ETH/BTC;1 张 = 0.01 ETH/BTC。期权链展示近 <span id="opt-chain-dte">14</span> 日到期合约,标注实值/虚值;下单可指定张数。</p>
|
||||
|
||||
<div class="options-funds-grid">
|
||||
<div class="options-funds-col">
|
||||
<h3>资金账户</h3>
|
||||
<div class="options-fund-row"><span>USDT</span><strong id="opt-funding-usdt">—</strong></div>
|
||||
<div class="options-fund-row"><span>USDC</span><strong id="opt-funding-usdc">—</strong></div>
|
||||
</div>
|
||||
<div class="options-funds-col">
|
||||
<h3>交易账户</h3>
|
||||
<div class="options-fund-row"><span>USDT</span><strong id="opt-trading-usdt">—</strong></div>
|
||||
<div class="options-fund-row"><span>USDC</span><strong id="opt-trading-usdc">—</strong></div>
|
||||
<div class="options-fund-row"><span>USDG</span><strong id="opt-trading-usdg">—</strong></div>
|
||||
</div>
|
||||
<div class="options-funds-col options-funds-meta">
|
||||
<div class="options-fund-row"><span>单笔权利金上限</span><strong id="opt-trade-budget">—</strong></div>
|
||||
<button type="button" class="btn-secondary" id="opt-refresh-balances">刷新余额</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="options-section card-nested">
|
||||
<h3>币种兑换(资金账户 USDT → USDC)</h3>
|
||||
<div class="form-row options-convert-row">
|
||||
<input type="number" id="opt-convert-amount" min="0" step="0.01" placeholder="USDT 数量">
|
||||
<button type="button" class="btn-secondary" id="opt-convert-quote-btn">询价</button>
|
||||
<button type="button" class="btn-primary" id="opt-convert-exec-btn" disabled>确认兑换</button>
|
||||
</div>
|
||||
<div id="opt-convert-preview" class="muted"></div>
|
||||
</div>
|
||||
|
||||
<div class="options-section card-nested">
|
||||
<h3>账户划转</h3>
|
||||
<div class="form-row options-transfer-row">
|
||||
<select id="opt-transfer-ccy">
|
||||
<option value="USDC" selected>USDC</option>
|
||||
<option value="USDT">USDT</option>
|
||||
</select>
|
||||
<select id="opt-transfer-from">
|
||||
<option value="funding" selected>资金账户</option>
|
||||
<option value="trading">交易账户</option>
|
||||
</select>
|
||||
<span>→</span>
|
||||
<select id="opt-transfer-to">
|
||||
<option value="trading" selected>交易账户</option>
|
||||
<option value="funding">资金账户</option>
|
||||
</select>
|
||||
<input type="number" id="opt-transfer-amount" min="0" step="0.01" placeholder="数量">
|
||||
<button type="button" class="btn-primary" id="opt-transfer-btn">确认划转</button>
|
||||
</div>
|
||||
<div id="opt-transfer-msg" class="muted"></div>
|
||||
</div>
|
||||
|
||||
<div class="options-section">
|
||||
<div class="form-row options-chain-toolbar">
|
||||
<button type="button" class="btn-secondary opt-uly-btn active" data-uly="ETH">ETH</button>
|
||||
<button type="button" class="btn-secondary opt-uly-btn" data-uly="BTC">BTC</button>
|
||||
<select id="opt-exp-select"><option value="">选择到期日</option></select>
|
||||
<button type="button" class="btn-secondary opt-type-btn active" data-type="C">看涨 Call</button>
|
||||
<button type="button" class="btn-secondary opt-type-btn" data-type="P">看跌 Put</button>
|
||||
<button type="button" class="btn-secondary" id="opt-load-chain">刷新链</button>
|
||||
</div>
|
||||
<div id="opt-index-line" class="muted"></div>
|
||||
<div class="options-strike-table-wrap">
|
||||
<table class="options-strike-table" id="opt-strike-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>行权价</th>
|
||||
<th>类型</th>
|
||||
<th>合约</th>
|
||||
<th>卖一</th>
|
||||
<th>买一</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="opt-strike-tbody">
|
||||
<tr><td colspan="6" class="muted">请选择到期日</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="opt-order-panel-host" class="opt-order-panel-host" hidden aria-hidden="true">
|
||||
<div id="opt-order-panel" class="opt-order-panel-inner" style="display:none">
|
||||
<h3 class="opt-order-title">下单</h3>
|
||||
<div id="opt-order-inst" class="options-order-inst"></div>
|
||||
<div class="options-order-grid">
|
||||
<div><span class="k">卖一(每1币)</span><span id="opt-order-ask" class="v">—</span></div>
|
||||
<div><span class="k">张数</span><span id="opt-order-sheets" class="v">—</span></div>
|
||||
<div><span class="k">ETH/BTC 数量</span><span id="opt-order-eth" class="v">—</span></div>
|
||||
<div><span class="k">预估权利金</span><span id="opt-order-premium" class="v">—</span></div>
|
||||
<div class="options-dual-grid">
|
||||
<div class="card options-order-card">
|
||||
<h2>期权下单</h2>
|
||||
<p class="muted options-hint">报价单位为每 1 ETH/BTC;1 张 = 0.01。链展示近 <span id="opt-chain-dte">14</span> 日到期,标注实值/虚值。资金划转与 USDT/USDC 兑换见「系统设置 → 期权设置」。</p>
|
||||
<div class="form-row options-chain-toolbar">
|
||||
<button type="button" class="btn-secondary opt-uly-btn active" data-uly="ETH">ETH</button>
|
||||
<button type="button" class="btn-secondary opt-uly-btn" data-uly="BTC">BTC</button>
|
||||
<select id="opt-exp-select"><option value="">选择到期日</option></select>
|
||||
<button type="button" class="btn-secondary opt-type-btn active" data-type="C">看涨 Call</button>
|
||||
<button type="button" class="btn-secondary opt-type-btn" data-type="P">看跌 Put</button>
|
||||
<button type="button" class="btn-secondary" id="opt-load-chain">刷新链</button>
|
||||
</div>
|
||||
<div id="opt-index-line" class="muted"></div>
|
||||
<div class="options-strike-table-wrap">
|
||||
<table class="options-strike-table" id="opt-strike-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>行权价</th>
|
||||
<th>类型</th>
|
||||
<th>合约</th>
|
||||
<th>卖一</th>
|
||||
<th>买一</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="opt-strike-tbody">
|
||||
<tr><td colspan="6" class="muted">请选择到期日</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="opt-order-panel-host" class="opt-order-panel-host" hidden aria-hidden="true">
|
||||
<div id="opt-order-panel" class="opt-order-panel-inner" style="display:none">
|
||||
<h3 class="opt-order-title">下单</h3>
|
||||
<div id="opt-order-inst" class="options-order-inst"></div>
|
||||
<div class="options-order-grid">
|
||||
<div><span class="k">卖一(每1币)</span><span id="opt-order-ask" class="v">—</span></div>
|
||||
<div><span class="k">张数</span><span id="opt-order-sheets" class="v">—</span></div>
|
||||
<div><span class="k">ETH/BTC 数量</span><span id="opt-order-eth" class="v">—</span></div>
|
||||
<div><span class="k">预估权利金</span><span id="opt-order-premium" class="v">—</span></div>
|
||||
</div>
|
||||
<div class="form-row options-order-mode-row">
|
||||
<label><input type="radio" name="opt-size-mode" value="sheets" checked> 指定张数</label>
|
||||
<input type="number" id="opt-sheets-amount" min="1" step="1" value="1" placeholder="张数">
|
||||
<label><input type="radio" name="opt-size-mode" value="budget_full"> 按单笔上限打满</label>
|
||||
<label><input type="radio" name="opt-size-mode" value="eth_amount"> 指定币数量</label>
|
||||
<input type="number" id="opt-eth-amount" min="0.01" step="0.01" placeholder="如 0.5" style="display:none">
|
||||
<input type="text" id="opt-signal-note" placeholder="备注(关键位说明)">
|
||||
<button type="button" class="btn-primary" id="opt-open-btn">限价买入 @ 卖一</button>
|
||||
</div>
|
||||
<div id="opt-order-msg" class="muted"></div>
|
||||
</div>
|
||||
<div class="form-row options-order-mode-row">
|
||||
<label><input type="radio" name="opt-size-mode" value="sheets" checked> 指定张数</label>
|
||||
<input type="number" id="opt-sheets-amount" min="1" step="1" value="1" placeholder="张数">
|
||||
<label><input type="radio" name="opt-size-mode" value="budget_full"> 按单笔上限打满</label>
|
||||
<label><input type="radio" name="opt-size-mode" value="eth_amount"> 指定币数量</label>
|
||||
<input type="number" id="opt-eth-amount" min="0.01" step="0.01" placeholder="如 0.5" style="display:none">
|
||||
<input type="text" id="opt-signal-note" placeholder="备注(关键位说明)">
|
||||
<button type="button" class="btn-primary" id="opt-open-btn">限价买入 @ 卖一</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card options-pos-card-wrap">
|
||||
<div class="options-pos-head">
|
||||
<h2>持仓</h2>
|
||||
<button type="button" class="btn-secondary" id="opt-refresh-positions">刷新</button>
|
||||
</div>
|
||||
<div class="options-pos-tabs" role="tablist">
|
||||
<button type="button" class="btn-secondary opt-pos-tab active" data-tab="live">当前持仓</button>
|
||||
<button type="button" class="btn-secondary opt-pos-tab" data-tab="history">期权历史</button>
|
||||
</div>
|
||||
<div id="opt-pos-live" class="panel-scroll pos-list options-pos-pane">
|
||||
<div class="pos-empty" id="opt-pos-empty">暂无持仓</div>
|
||||
<div id="opt-pos-cards"></div>
|
||||
</div>
|
||||
<div id="opt-pos-history" class="options-pos-pane" hidden>
|
||||
<div class="options-history-table-wrap">
|
||||
<table class="options-strike-table" id="opt-history-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>合约</th>
|
||||
<th>张数</th>
|
||||
<th>权利金</th>
|
||||
<th>状态</th>
|
||||
<th>盈亏</th>
|
||||
<th>时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="opt-history-tbody">
|
||||
<tr><td colspan="6" class="muted">加载中…</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="opt-order-msg" class="muted"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="options-section">
|
||||
<h3>持仓</h3>
|
||||
<button type="button" class="btn-secondary" id="opt-refresh-positions">刷新持仓</button>
|
||||
<div class="options-strike-table-wrap">
|
||||
<table class="options-strike-table" id="opt-positions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>合约</th>
|
||||
<th>张数</th>
|
||||
<th>币量</th>
|
||||
<th>开仓均价</th>
|
||||
<th>标记价</th>
|
||||
<th>浮盈</th>
|
||||
<th>收益率</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="opt-positions-tbody">
|
||||
<tr><td colspan="8" class="muted">暂无持仓</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/options_panel.js?v=4"></script>
|
||||
<script src="/static/options_panel.js?v=5"></script>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<div class="options-settings-panel" id="options-settings-root"
|
||||
data-sub-account="{{ instance_settings.options_sub_account | default('', true) }}">
|
||||
<div class="options-settings-block card-nested">
|
||||
<h3>币种兑换(现货市价)</h3>
|
||||
<p class="muted options-settings-hint">主账户资金账户:USDT ↔ USDC,走 USDC-USDT 现货市价单。</p>
|
||||
<div class="form-row options-settings-row">
|
||||
<select id="opt-set-swap-dir">
|
||||
<option value="usdt_to_usdc" selected>USDT → USDC</option>
|
||||
<option value="usdc_to_usdt">USDC → USDT</option>
|
||||
</select>
|
||||
<input type="number" id="opt-set-swap-amount" min="0.01" step="0.01" placeholder="数量">
|
||||
<button type="button" class="btn-primary" id="opt-set-swap-btn">市价兑换</button>
|
||||
</div>
|
||||
<div id="opt-set-swap-msg" class="muted"></div>
|
||||
</div>
|
||||
|
||||
<div class="options-settings-block card-nested">
|
||||
<h3>主账户内划转</h3>
|
||||
<div class="form-row options-settings-row">
|
||||
<select id="opt-set-int-ccy">
|
||||
<option value="USDC" selected>USDC</option>
|
||||
<option value="USDT">USDT</option>
|
||||
</select>
|
||||
<select id="opt-set-int-from">
|
||||
<option value="funding" selected>资金账户</option>
|
||||
<option value="trading">交易账户</option>
|
||||
</select>
|
||||
<span>→</span>
|
||||
<select id="opt-set-int-to">
|
||||
<option value="trading" selected>交易账户</option>
|
||||
<option value="funding">资金账户</option>
|
||||
</select>
|
||||
<input type="number" id="opt-set-int-amount" min="0.01" step="0.01" placeholder="数量">
|
||||
<button type="button" class="btn-primary" id="opt-set-int-btn">确认划转</button>
|
||||
</div>
|
||||
<div id="opt-set-int-msg" class="muted"></div>
|
||||
</div>
|
||||
|
||||
<div class="options-settings-block card-nested">
|
||||
<h3>主账户 ↔ 子账户划转</h3>
|
||||
<p class="muted options-settings-hint">子账户:{{ instance_settings.options_sub_account or '未配置 OKX_SUB_ACCOUNT_NAME' }}</p>
|
||||
<div class="form-row options-settings-row">
|
||||
<select id="opt-set-cross-ccy">
|
||||
<option value="USDT" selected>USDT</option>
|
||||
<option value="USDC">USDC</option>
|
||||
</select>
|
||||
<select id="opt-set-cross-acct">
|
||||
<option value="funding" selected>资金账户</option>
|
||||
<option value="trading">交易账户</option>
|
||||
</select>
|
||||
<select id="opt-set-cross-dir">
|
||||
<option value="sub_to_main" selected>子账户 → 主账户</option>
|
||||
<option value="main_to_sub">主账户 → 子账户</option>
|
||||
</select>
|
||||
<input type="number" id="opt-set-cross-amount" min="0.01" step="0.01" placeholder="数量">
|
||||
<button type="button" class="btn-primary" id="opt-set-cross-btn">确认划转</button>
|
||||
</div>
|
||||
<div id="opt-set-cross-msg" class="muted"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/options_settings.js?v=1"></script>
|
||||
Reference in New Issue
Block a user