Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a79e010c4 | |||
| 791cc750da | |||
| c8231ea194 | |||
| aaccdcfc16 | |||
| f993a89a21 | |||
| 9dc363270e | |||
| 9a83dfe209 | |||
| 32c42b8447 | |||
| a2075ba73e | |||
| 846f3de525 | |||
| a7b75895e6 | |||
| d870178b83 | |||
| 7ebe1671b2 | |||
| cb4f6aaa4b | |||
| eb175820e9 |
@@ -3,5 +3,8 @@
|
|||||||
deploy/** text eol=lf
|
deploy/** text eol=lf
|
||||||
# 文档统一 LF,避免 Windows 编辑后产生 CRLF 脏 diff
|
# 文档统一 LF,避免 Windows 编辑后产生 CRLF 脏 diff
|
||||||
docs/** text eol=lf
|
docs/** text eol=lf
|
||||||
|
# XMind 为 ZIP 二进制;须覆盖上面 docs/** 的 text/eol,否则入库会损坏打不开
|
||||||
|
*.xmind -text -diff -merge -eol
|
||||||
|
docs/**/*.xmind -text -diff -merge -eol
|
||||||
# .env 模板统一 LF,避免 Linux PM2 source 报 $'\r': command not found
|
# .env 模板统一 LF,避免 Linux PM2 source 报 $'\r': command not found
|
||||||
**/.env.example text eol=lf
|
**/.env.example text eol=lf
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ _APP_STARTED_AT = time.time()
|
|||||||
_RECONCILE_FLAT_STREAK = {}
|
_RECONCILE_FLAT_STREAK = {}
|
||||||
KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
||||||
FULL_MARGIN_BUFFER_RATIO = float(os.getenv("FULL_MARGIN_BUFFER_RATIO", "0.98"))
|
FULL_MARGIN_BUFFER_RATIO = float(os.getenv("FULL_MARGIN_BUFFER_RATIO", "0.98"))
|
||||||
TRANSFER_CCY = os.getenv("TRANSFER_CCY", "USDT")
|
TRANSFER_CCY = (os.getenv("TRANSFER_CCY", "USDT") or "USDT").strip().upper() or "USDT"
|
||||||
UPLOAD_FOLDER = resolve_path(os.getenv("UPLOAD_DIR", "static/images"))
|
UPLOAD_FOLDER = resolve_path(os.getenv("UPLOAD_DIR", "static/images"))
|
||||||
ORDER_CHART_ENABLED = os.getenv("ORDER_CHART_ENABLED", "true").lower() == "true"
|
ORDER_CHART_ENABLED = os.getenv("ORDER_CHART_ENABLED", "true").lower() == "true"
|
||||||
ORDER_CHART_TFS = [x.strip() for x in (os.getenv("ORDER_CHART_TFS", "4h,1h,15m,5m") or "").split(",") if x.strip()]
|
ORDER_CHART_TFS = [x.strip() for x in (os.getenv("ORDER_CHART_TFS", "4h,1h,15m,5m") or "").split(",") if x.strip()]
|
||||||
@@ -9870,7 +9870,7 @@ def manual_transfer():
|
|||||||
amount = float(request.form.get("amount", "0"))
|
amount = float(request.form.get("amount", "0"))
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("划转金额格式错误")
|
flash("划转金额格式错误")
|
||||||
return redirect("/settings")
|
return redirect("/settings?settings_tab=transfer")
|
||||||
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
||||||
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
||||||
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
||||||
@@ -9885,7 +9885,7 @@ def manual_transfer():
|
|||||||
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
||||||
else:
|
else:
|
||||||
flash(f"手动划转失败:{msg}")
|
flash(f"手动划转失败:{msg}")
|
||||||
return redirect("/settings")
|
return redirect("/settings?settings_tab=transfer")
|
||||||
|
|
||||||
|
|
||||||
def _journal_ai_chart_builder(row):
|
def _journal_ai_chart_builder(row):
|
||||||
|
|||||||
@@ -404,7 +404,7 @@ KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
|||||||
_APP_STARTED_AT = time.time()
|
_APP_STARTED_AT = time.time()
|
||||||
_RECONCILE_FLAT_STREAK = {}
|
_RECONCILE_FLAT_STREAK = {}
|
||||||
FULL_MARGIN_BUFFER_RATIO = float(os.getenv("FULL_MARGIN_BUFFER_RATIO", "0.98"))
|
FULL_MARGIN_BUFFER_RATIO = float(os.getenv("FULL_MARGIN_BUFFER_RATIO", "0.98"))
|
||||||
TRANSFER_CCY = os.getenv("TRANSFER_CCY", "USDT")
|
TRANSFER_CCY = (os.getenv("TRANSFER_CCY", "USDT") or "USDT").strip().upper() or "USDT"
|
||||||
UPLOAD_FOLDER = resolve_path(os.getenv("UPLOAD_DIR", "static/images"))
|
UPLOAD_FOLDER = resolve_path(os.getenv("UPLOAD_DIR", "static/images"))
|
||||||
ORDER_CHART_ENABLED = os.getenv("ORDER_CHART_ENABLED", "true").lower() == "true"
|
ORDER_CHART_ENABLED = os.getenv("ORDER_CHART_ENABLED", "true").lower() == "true"
|
||||||
ORDER_CHART_TFS = [x.strip() for x in (os.getenv("ORDER_CHART_TFS", "4h,1h,15m,5m") or "").split(",") if x.strip()]
|
ORDER_CHART_TFS = [x.strip() for x in (os.getenv("ORDER_CHART_TFS", "4h,1h,15m,5m") or "").split(",") if x.strip()]
|
||||||
@@ -9727,7 +9727,7 @@ def manual_transfer():
|
|||||||
amount = float(request.form.get("amount", "0"))
|
amount = float(request.form.get("amount", "0"))
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("划转金额格式错误")
|
flash("划转金额格式错误")
|
||||||
return redirect("/settings")
|
return redirect("/settings?settings_tab=transfer")
|
||||||
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
||||||
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
||||||
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
||||||
@@ -9742,7 +9742,7 @@ def manual_transfer():
|
|||||||
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
||||||
else:
|
else:
|
||||||
flash(f"手动划转失败:{msg}")
|
flash(f"手动划转失败:{msg}")
|
||||||
return redirect("/settings")
|
return redirect("/settings?settings_tab=transfer")
|
||||||
|
|
||||||
|
|
||||||
def _journal_ai_chart_builder(row):
|
def _journal_ai_chart_builder(row):
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ BREAKEVEN_EXCHANGE_MIN_INTERVAL_SEC = max(
|
|||||||
_BREAKEVEN_LAST_EX_SYNC: dict[int, float] = {}
|
_BREAKEVEN_LAST_EX_SYNC: dict[int, float] = {}
|
||||||
KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
KLINE_TIMEFRAME = os.getenv("KLINE_TIMEFRAME", "5m")
|
||||||
FULL_MARGIN_BUFFER_RATIO = float(os.getenv("FULL_MARGIN_BUFFER_RATIO", "0.98"))
|
FULL_MARGIN_BUFFER_RATIO = float(os.getenv("FULL_MARGIN_BUFFER_RATIO", "0.98"))
|
||||||
TRANSFER_CCY = os.getenv("TRANSFER_CCY", "USDT")
|
TRANSFER_CCY = (os.getenv("TRANSFER_CCY", "USDT") or "USDT").strip().upper() or "USDT"
|
||||||
OKX_POSITION_INST_TYPE = os.getenv("OKX_POSITION_INST_TYPE", "SWAP")
|
OKX_POSITION_INST_TYPE = os.getenv("OKX_POSITION_INST_TYPE", "SWAP")
|
||||||
EXCHANGE_POSITION_SYNC_FROM_BJ = (os.getenv("EXCHANGE_POSITION_SYNC_FROM_BJ") or "").strip()
|
EXCHANGE_POSITION_SYNC_FROM_BJ = (os.getenv("EXCHANGE_POSITION_SYNC_FROM_BJ") or "").strip()
|
||||||
EXCHANGE_POSITION_HISTORY_LIMIT = max(50, min(1000, int(os.getenv("EXCHANGE_POSITION_HISTORY_LIMIT", "200"))))
|
EXCHANGE_POSITION_HISTORY_LIMIT = max(50, min(1000, int(os.getenv("EXCHANGE_POSITION_HISTORY_LIMIT", "200"))))
|
||||||
@@ -9455,7 +9455,7 @@ def manual_transfer():
|
|||||||
amount = float(request.form.get("amount", "0"))
|
amount = float(request.form.get("amount", "0"))
|
||||||
except Exception:
|
except Exception:
|
||||||
flash("划转金额格式错误")
|
flash("划转金额格式错误")
|
||||||
return redirect("/settings")
|
return redirect("/settings?settings_tab=transfer")
|
||||||
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
from_account = (request.form.get("from_account") or AUTO_TRANSFER_FROM).strip()
|
||||||
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
to_account = (request.form.get("to_account") or AUTO_TRANSFER_TO).strip()
|
||||||
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
ok, msg, _ = execute_transfer_usdt(amount, from_account, to_account)
|
||||||
@@ -9477,7 +9477,7 @@ def manual_transfer():
|
|||||||
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
flash(f"手动划转成功:{amount}U {from_account}->{to_account}")
|
||||||
else:
|
else:
|
||||||
flash(f"手动划转失败:{msg}")
|
flash(f"手动划转失败:{msg}")
|
||||||
return redirect("/settings")
|
return redirect("/settings?settings_tab=transfer")
|
||||||
|
|
||||||
|
|
||||||
def _journal_ai_chart_builder(row):
|
def _journal_ai_chart_builder(row):
|
||||||
|
|||||||
Binary file not shown.
+3
-1
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
| 标签 | 指向提交 | 说明 |
|
| 标签 | 指向提交 | 说明 |
|
||||||
|------|----------|------|
|
|------|----------|------|
|
||||||
|
| `snapshot/20260726` | `a2075ba` | 2026-07-26:Gate划转币种大写修复、系统设置划转页签停留、自动划转账户/币种下拉默认、期权「按可用余额打满」=min(余额,单笔预算)及说明 |
|
||||||
|
| `snapshot/20260724` | `890659f` | 2026-07-24:执行手册v2(无对冲)、监控/策略页签显隐、内照明心期权档案同步、期权开平仓微信必发、实例导航显隐关键位/实盘下单等 |
|
||||||
| `snapshot/20260723-2` | `9e0591c` | 2026-07-23:策略对比页(合约/单期权/期期7:3)、监控与看板隐藏浮盈偏好、对比页卡片内边距等 |
|
| `snapshot/20260723-2` | `9e0591c` | 2026-07-23:策略对比页(合约/单期权/期期7:3)、监控与看板隐藏浮盈偏好、对比页卡片内边距等 |
|
||||||
| `snapshot/20260723-pre-amp-stats` | `40be3a5` | 2026-07-23:振幅统计开发前;含执行手册进教练、日亏损冻结、手机监控 UI、振幅统计开发方案等 |
|
| `snapshot/20260723-pre-amp-stats` | `40be3a5` | 2026-07-23:振幅统计开发前;含执行手册进教练、日亏损冻结、手机监控 UI、振幅统计开发方案等 |
|
||||||
| `snapshot/20260721-2` | `a721642` | 2026-07-21 晚:日亏损次数冻结、交易执行手册入中控策略说明、期权/Gate 执行手册文档等 |
|
| `snapshot/20260721-2` | `a721642` | 2026-07-21 晚:日亏损次数冻结、交易执行手册入中控策略说明、期权/Gate 执行手册文档等 |
|
||||||
@@ -28,7 +30,7 @@
|
|||||||
git tag -l 'snapshot/*'
|
git tag -l 'snapshot/*'
|
||||||
|
|
||||||
# 检出快照(只读查看,勿在此分支直接开发)
|
# 检出快照(只读查看,勿在此分支直接开发)
|
||||||
git checkout snapshot/20260723-2
|
git checkout snapshot/20260726
|
||||||
|
|
||||||
# 回到主线
|
# 回到主线
|
||||||
git checkout main
|
git checkout main
|
||||||
|
|||||||
@@ -235,9 +235,57 @@
|
|||||||
const parts = [];
|
const parts = [];
|
||||||
if (qs) parts.push(qs);
|
if (qs) parts.push(qs);
|
||||||
parts.push("embed=1");
|
parts.push("embed=1");
|
||||||
|
if (tab === "settings") {
|
||||||
|
try {
|
||||||
|
const st = new URLSearchParams(location.search).get("settings_tab");
|
||||||
|
if (st) parts.push("settings_tab=" + encodeURIComponent(st));
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
return url + "?" + parts.join("&");
|
return url + "?" + parts.join("&");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSettingsSubTabInUrl(key) {
|
||||||
|
if (!key) return;
|
||||||
|
try {
|
||||||
|
const q = new URLSearchParams(location.search);
|
||||||
|
q.set("tab", "settings");
|
||||||
|
q.set("settings_tab", key);
|
||||||
|
q.set("embed", "1");
|
||||||
|
history.replaceState(null, "", "/embed?" + q.toString());
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateSettingsSubTab(key) {
|
||||||
|
if (!key) return;
|
||||||
|
setSettingsSubTabInUrl(key);
|
||||||
|
const pane = tabPanes.get("settings") || document;
|
||||||
|
const radio = pane.querySelector(
|
||||||
|
'input.env-tab-radio[data-settings-tab="' + key + '"]'
|
||||||
|
);
|
||||||
|
if (radio) radio.checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formActionPath(form) {
|
||||||
|
try {
|
||||||
|
return new URL(form.action || "", location.href).pathname.replace(/\/$/, "") || "/";
|
||||||
|
} catch (_) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeKeepSettingsSubTabAfterForm(form) {
|
||||||
|
const path = formActionPath(form);
|
||||||
|
if (path === "/manual_transfer") {
|
||||||
|
setSettingsSubTabInUrl("transfer");
|
||||||
|
return "transfer";
|
||||||
|
}
|
||||||
|
if (path.indexOf("/api/options/transfer") >= 0 || path.indexOf("/api/options/cross-transfer") >= 0) {
|
||||||
|
setSettingsSubTabInUrl("options_transfer");
|
||||||
|
return "options_transfer";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchTabHtml(tab) {
|
async function fetchTabHtml(tab) {
|
||||||
const r = await fetch(embedPageUrl(tab), {
|
const r = await fetch(embedPageUrl(tab), {
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
@@ -400,14 +448,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const fd = new FormData(form);
|
const fd = new FormData(form);
|
||||||
|
const keepSub = maybeKeepSettingsSubTabAfterForm(form);
|
||||||
return fetch(form.action, {
|
return fetch(form.action, {
|
||||||
method: form.method || "POST",
|
method: form.method || "POST",
|
||||||
body: fd,
|
body: fd,
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
})
|
})
|
||||||
.then(() => reloadCurrentTab())
|
.then(() => reloadCurrentTab().then(() => activateSettingsSubTab(keepSub)))
|
||||||
.catch(() => reloadCurrentTab());
|
.catch(() => reloadCurrentTab().then(() => activateSettingsSubTab(keepSub)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchApplyListWindow() {
|
function patchApplyListWindow() {
|
||||||
@@ -466,14 +515,15 @@
|
|||||||
if (CUSTOM_SUBMIT_FORM_IDS.has(form.id)) return;
|
if (CUSTOM_SUBMIT_FORM_IDS.has(form.id)) return;
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const fd = new FormData(form);
|
const fd = new FormData(form);
|
||||||
|
const keepSub = maybeKeepSettingsSubTabAfterForm(form);
|
||||||
fetch(form.action, {
|
fetch(form.action, {
|
||||||
method: form.method || "POST",
|
method: form.method || "POST",
|
||||||
body: fd,
|
body: fd,
|
||||||
credentials: "same-origin",
|
credentials: "same-origin",
|
||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
})
|
})
|
||||||
.then(() => reloadCurrentTab())
|
.then(() => reloadCurrentTab().then(() => activateSettingsSubTab(keepSub)))
|
||||||
.catch(() => reloadCurrentTab());
|
.catch(() => reloadCurrentTab().then(() => activateSettingsSubTab(keepSub)));
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -280,8 +280,15 @@
|
|||||||
const mode = currentSizeMode();
|
const mode = currentSizeMode();
|
||||||
const sheetsEl = document.getElementById("opt-sheets-amount");
|
const sheetsEl = document.getElementById("opt-sheets-amount");
|
||||||
const ethEl = document.getElementById("opt-eth-amount");
|
const ethEl = document.getElementById("opt-eth-amount");
|
||||||
|
const hint = document.getElementById("opt-budget-full-hint");
|
||||||
|
const capEl = document.getElementById("opt-budget-full-cap");
|
||||||
if (sheetsEl) sheetsEl.style.display = mode === "sheets" ? "" : "none";
|
if (sheetsEl) sheetsEl.style.display = mode === "sheets" ? "" : "none";
|
||||||
if (ethEl) ethEl.style.display = mode === "eth_amount" ? "" : "none";
|
if (ethEl) ethEl.style.display = mode === "eth_amount" ? "" : "none";
|
||||||
|
if (hint) hint.style.display = mode === "budget_full" ? "" : "none";
|
||||||
|
if (capEl && root && root.dataset.tradeBudget) {
|
||||||
|
const n = Number(root.dataset.tradeBudget);
|
||||||
|
if (Number.isFinite(n) && n > 0) capEl.textContent = n.toFixed(2);
|
||||||
|
}
|
||||||
document.querySelectorAll(".opt-size-mode-chip").forEach(function (chip) {
|
document.querySelectorAll(".opt-size-mode-chip").forEach(function (chip) {
|
||||||
const radio = chip.querySelector('input[name="opt-size-mode"]');
|
const radio = chip.querySelector('input[name="opt-size-mode"]');
|
||||||
chip.classList.toggle("is-selected", !!(radio && radio.checked));
|
chip.classList.toggle("is-selected", !!(radio && radio.checked));
|
||||||
|
|||||||
Vendored
+18
@@ -70,7 +70,10 @@ HOT_RELOAD_EXACT = frozenset({
|
|||||||
"MONITOR_POLL_SECONDS",
|
"MONITOR_POLL_SECONDS",
|
||||||
"AUTO_TRANSFER_ENABLED",
|
"AUTO_TRANSFER_ENABLED",
|
||||||
"AUTO_TRANSFER_AMOUNT",
|
"AUTO_TRANSFER_AMOUNT",
|
||||||
|
"AUTO_TRANSFER_FROM",
|
||||||
|
"AUTO_TRANSFER_TO",
|
||||||
"AUTO_TRANSFER_BJ_HOUR",
|
"AUTO_TRANSFER_BJ_HOUR",
|
||||||
|
"TRANSFER_CCY",
|
||||||
"FORCE_CLOSE_ENABLED",
|
"FORCE_CLOSE_ENABLED",
|
||||||
"FORCE_CLOSE_BJ_HOUR",
|
"FORCE_CLOSE_BJ_HOUR",
|
||||||
"BTC_LEVERAGE",
|
"BTC_LEVERAGE",
|
||||||
@@ -126,6 +129,17 @@ SELECT_OPTIONS: dict[str, tuple[tuple[str, str], ...]] = {
|
|||||||
("long_only", "仅做多"),
|
("long_only", "仅做多"),
|
||||||
("short_only", "仅做空"),
|
("short_only", "仅做空"),
|
||||||
),
|
),
|
||||||
|
"AUTO_TRANSFER_FROM": (
|
||||||
|
("funding", "funding 资金账户"),
|
||||||
|
("swap", "swap 交易账户"),
|
||||||
|
("spot", "spot 现货"),
|
||||||
|
),
|
||||||
|
"AUTO_TRANSFER_TO": (
|
||||||
|
("swap", "swap 交易账户"),
|
||||||
|
("funding", "funding 资金账户"),
|
||||||
|
("spot", "spot 现货"),
|
||||||
|
),
|
||||||
|
"TRANSFER_CCY": (("USDT", "USDT"),),
|
||||||
"HEDGE_PLAN_OO_BIAS_SPLIT_BY": (
|
"HEDGE_PLAN_OO_BIAS_SPLIT_BY": (
|
||||||
("budget", "预算金额"),
|
("budget", "预算金额"),
|
||||||
("sheets", "张数"),
|
("sheets", "张数"),
|
||||||
@@ -136,6 +150,7 @@ _SELECT_ALIASES: dict[str, dict[str, str]] = {
|
|||||||
"OKX_TD_MODE": {"cross_margin": "cross", "isolated_margin": "isolated"},
|
"OKX_TD_MODE": {"cross_margin": "cross", "isolated_margin": "isolated"},
|
||||||
"BINANCE_MARGIN_MODE": {"cross_margin": "cross", "isolated_margin": "isolated"},
|
"BINANCE_MARGIN_MODE": {"cross_margin": "cross", "isolated_margin": "isolated"},
|
||||||
"GATE_TD_MODE": {"cross_margin": "cross", "isolated_margin": "isolated"},
|
"GATE_TD_MODE": {"cross_margin": "cross", "isolated_margin": "isolated"},
|
||||||
|
"TRANSFER_CCY": {"usdt": "USDT"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -159,10 +174,13 @@ def normalize_select_value(key: str, value: Optional[str]) -> str:
|
|||||||
if low in aliases:
|
if low in aliases:
|
||||||
return aliases[low]
|
return aliases[low]
|
||||||
allowed = {v for v, _ in (SELECT_OPTIONS.get(key) or ())}
|
allowed = {v for v, _ in (SELECT_OPTIONS.get(key) or ())}
|
||||||
|
allowed_by_lower = {v.lower(): v for v in allowed}
|
||||||
if low in allowed:
|
if low in allowed:
|
||||||
return low
|
return low
|
||||||
if raw in allowed:
|
if raw in allowed:
|
||||||
return raw
|
return raw
|
||||||
|
if low in allowed_by_lower:
|
||||||
|
return allowed_by_lower[low]
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Vendored
+9
-4
@@ -103,10 +103,10 @@ _SHARED_SECTIONS: list[dict[str, Any]] = [
|
|||||||
"fields": [
|
"fields": [
|
||||||
("AUTO_TRANSFER_ENABLED", "启用自动划转", ""),
|
("AUTO_TRANSFER_ENABLED", "启用自动划转", ""),
|
||||||
("AUTO_TRANSFER_AMOUNT", "目标余额(U)", "交易账户目标 USDT"),
|
("AUTO_TRANSFER_AMOUNT", "目标余额(U)", "交易账户目标 USDT"),
|
||||||
("AUTO_TRANSFER_FROM", "划出账户", "funding 或 swap"),
|
("AUTO_TRANSFER_FROM", "划出账户", "余额不足时从此账户划入交易账户"),
|
||||||
("AUTO_TRANSFER_TO", "划入账户", "swap 或 funding"),
|
("AUTO_TRANSFER_TO", "划入账户", "目标余额所在账户,一般为 swap"),
|
||||||
("AUTO_TRANSFER_BJ_HOUR", "执行整点(北京时间)", ""),
|
("AUTO_TRANSFER_BJ_HOUR", "执行整点(北京时间)", ""),
|
||||||
("TRANSFER_CCY", "划转币种", "默认 USDT"),
|
("TRANSFER_CCY", "划转币种", ""),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -200,6 +200,9 @@ _RUNTIME_ENV_DEFAULTS: dict[str, str] = {
|
|||||||
"RISK_MANUAL_CLOSE_DAILY_LIMIT": "2",
|
"RISK_MANUAL_CLOSE_DAILY_LIMIT": "2",
|
||||||
"RISK_DAILY_LOSS_LIMIT": "2",
|
"RISK_DAILY_LOSS_LIMIT": "2",
|
||||||
"RISK_MOOD_ISSUES_DAILY_FREEZE": "true",
|
"RISK_MOOD_ISSUES_DAILY_FREEZE": "true",
|
||||||
|
"AUTO_TRANSFER_FROM": "funding",
|
||||||
|
"AUTO_TRANSFER_TO": "swap",
|
||||||
|
"TRANSFER_CCY": "USDT",
|
||||||
"HEDGE_PLAN_SHOW_PERP_OPTIONS": "true",
|
"HEDGE_PLAN_SHOW_PERP_OPTIONS": "true",
|
||||||
"HEDGE_PLAN_SHOW_OPTIONS_OPTIONS": "true",
|
"HEDGE_PLAN_SHOW_OPTIONS_OPTIONS": "true",
|
||||||
"OKX_OPTIONS_CHAIN_ASK_LIQ_FILTER_ENABLED": "true",
|
"OKX_OPTIONS_CHAIN_ASK_LIQ_FILTER_ENABLED": "true",
|
||||||
@@ -214,7 +217,9 @@ _RUNTIME_ENV_DEFAULTS: dict[str, str] = {
|
|||||||
|
|
||||||
def _effective_env_value(key: str, file_values: dict[str, str], schema_default: str = "") -> str:
|
def _effective_env_value(key: str, file_values: dict[str, str], schema_default: str = "") -> str:
|
||||||
if key in file_values:
|
if key in file_values:
|
||||||
return file_values[key]
|
file_val = str(file_values.get(key) or "").strip()
|
||||||
|
if file_val:
|
||||||
|
return file_val
|
||||||
runtime = os.getenv(key)
|
runtime = os.getenv(key)
|
||||||
if runtime is not None and str(runtime).strip() != "":
|
if runtime is not None and str(runtime).strip() != "":
|
||||||
return str(runtime).strip()
|
return str(runtime).strip()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ def execute_transfer_usdt(
|
|||||||
) -> tuple[bool, str, Any]:
|
) -> tuple[bool, str, Any]:
|
||||||
if amount <= 0:
|
if amount <= 0:
|
||||||
return False, "划转金额必须大于0", None
|
return False, "划转金额必须大于0", None
|
||||||
|
ccy = (transfer_ccy or "USDT").strip().upper() or "USDT"
|
||||||
ok_live, reason = ensure_live_ready()
|
ok_live, reason = ensure_live_ready()
|
||||||
if not ok_live:
|
if not ok_live:
|
||||||
return False, reason, None
|
return False, reason, None
|
||||||
@@ -31,7 +32,7 @@ def execute_transfer_usdt(
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
resp = exchange.transfer(transfer_ccy, float(amount), from_account, to_account)
|
resp = exchange.transfer(ccy, float(amount), from_account, to_account)
|
||||||
return True, "划转成功", resp
|
return True, "划转成功", resp
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = str(e)
|
msg = str(e)
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ def embed_shell_enabled() -> bool:
|
|||||||
return (os.getenv("HUB_EMBED_SHELL") or "1").strip().lower() in ("1", "true", "yes", "on")
|
return (os.getenv("HUB_EMBED_SHELL") or "1").strip().lower() in ("1", "true", "yes", "on")
|
||||||
|
|
||||||
|
|
||||||
|
_SETTINGS_SUB_TABS = frozenset(
|
||||||
|
{"nav", "password", "transfer", "export", "options_swap", "options_transfer"}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def redirect_to_embed_shell_if_enabled(page: str):
|
def redirect_to_embed_shell_if_enabled(page: str):
|
||||||
"""直连 /trade 等整页路由时,重定向到 embed 壳(顶栏常驻,tab 软切换)."""
|
"""直连 /trade 等整页路由时,重定向到 embed 壳(顶栏常驻,tab 软切换)."""
|
||||||
if not embed_shell_enabled():
|
if not embed_shell_enabled():
|
||||||
@@ -93,6 +98,12 @@ def redirect_to_embed_shell_if_enabled(page: str):
|
|||||||
if (request.path or "").rstrip("/") == "/embed":
|
if (request.path or "").rstrip("/") == "/embed":
|
||||||
return None
|
return None
|
||||||
q = {k: v for k, v in request.args.items()}
|
q = {k: v for k, v in request.args.items()}
|
||||||
|
# embed 的 tab=页面名;系统设置内页签用 settings_tab,避免 /settings?tab=transfer 被覆盖成 tab=settings
|
||||||
|
if (page or "").strip() == "settings":
|
||||||
|
sub = (q.get("settings_tab") or "").strip()
|
||||||
|
legacy = (q.get("tab") or "").strip()
|
||||||
|
if not sub and legacy in _SETTINGS_SUB_TABS:
|
||||||
|
q["settings_tab"] = legacy
|
||||||
q["tab"] = page
|
q["tab"] = page
|
||||||
q["embed"] = "1"
|
q["embed"] = "1"
|
||||||
return redirect("/embed?" + urlencode(q))
|
return redirect("/embed?" + urlencode(q))
|
||||||
@@ -115,6 +126,11 @@ def rewrite_embed_dest(path: str, hub_theme: str | None = None) -> str:
|
|||||||
tab = path_to_embed_tab(split.path)
|
tab = path_to_embed_tab(split.path)
|
||||||
q = dict(parse_qsl(split.query, keep_blank_values=True))
|
q = dict(parse_qsl(split.query, keep_blank_values=True))
|
||||||
if tab:
|
if tab:
|
||||||
|
if tab == "settings":
|
||||||
|
sub = (q.get("settings_tab") or "").strip()
|
||||||
|
legacy = (q.get("tab") or "").strip()
|
||||||
|
if not sub and legacy in _SETTINGS_SUB_TABS:
|
||||||
|
q["settings_tab"] = legacy
|
||||||
q["tab"] = tab
|
q["tab"] = tab
|
||||||
q["embed"] = "1"
|
q["embed"] = "1"
|
||||||
ht = (hub_theme or q.get("hub_theme") or "").strip().lower()
|
ht = (hub_theme or q.get("hub_theme") or "").strip().lower()
|
||||||
|
|||||||
@@ -120,6 +120,6 @@ window.__INSTANCE_DISPLAY__ = {{ display | tojson }};
|
|||||||
</script>
|
</script>
|
||||||
<script src="/static/instance_settings_prefs.js?v=15"></script>
|
<script src="/static/instance_settings_prefs.js?v=15"></script>
|
||||||
<script src="/static/instance_live.js?v=6"></script>
|
<script src="/static/instance_live.js?v=6"></script>
|
||||||
<script src="/static/instance_embed.js?v=27"></script>
|
<script src="/static/instance_embed.js?v=28"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,9 +6,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if settings_tabs %}
|
{% if settings_tabs %}
|
||||||
<div class="env-config-body card settings-config-body">
|
{% set _sub = (request.args.get('settings_tab') or '').strip() %}
|
||||||
|
{% set _legacy_tab = (request.args.get('tab') or '').strip() %}
|
||||||
|
{% set ns = namespace(active_idx=0, active_key='') %}
|
||||||
{% for tab in settings_tabs %}
|
{% for tab in settings_tabs %}
|
||||||
<input type="radio" name="settings-section" id="settings-sec-{{ loop.index0 }}" class="env-tab-radio"{% if loop.first %} checked{% endif %}>
|
{% if _sub and tab.key == _sub %}
|
||||||
|
{% set ns.active_idx = loop.index0 %}
|
||||||
|
{% set ns.active_key = tab.key %}
|
||||||
|
{% elif (not _sub) and _legacy_tab and tab.key == _legacy_tab %}
|
||||||
|
{% set ns.active_idx = loop.index0 %}
|
||||||
|
{% set ns.active_key = tab.key %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="env-config-body card settings-config-body" data-settings-active-tab="{{ ns.active_key }}">
|
||||||
|
{% for tab in settings_tabs %}
|
||||||
|
<input type="radio" name="settings-section" id="settings-sec-{{ loop.index0 }}" class="env-tab-radio" data-settings-tab="{{ tab.key }}"{% if loop.index0 == ns.active_idx %} checked{% endif %}>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="env-config-tabs" role="tablist" aria-label="系统设置分类">
|
<div class="env-config-tabs" role="tablist" aria-label="系统设置分类">
|
||||||
{% for tab in settings_tabs %}
|
{% for tab in settings_tabs %}
|
||||||
@@ -52,3 +64,14 @@
|
|||||||
{% include 'options_settings_panel.html' %}
|
{% include 'options_settings_panel.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
try {
|
||||||
|
var q = new URLSearchParams(window.location.search || "");
|
||||||
|
var key = (q.get("settings_tab") || "").trim();
|
||||||
|
if (!key) return;
|
||||||
|
var radio = document.querySelector('input.env-tab-radio[data-settings-tab="' + key + '"]');
|
||||||
|
if (radio) radio.checked = true;
|
||||||
|
} catch (e) {}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -259,6 +259,11 @@ def eth_amount_from_sheets(sheets: int, ct_mult: float = 0.01) -> float:
|
|||||||
return round(int(sheets) * float(ct_mult), 8)
|
return round(int(sheets) * float(ct_mult), 8)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_budget_full_usdc(trading_usdc: float, trade_budget_usdc: float) -> float:
|
||||||
|
"""按可用余额打满:余额大于预算用预算,否则用余额."""
|
||||||
|
return min(float(trading_usdc), float(trade_budget_usdc))
|
||||||
|
|
||||||
|
|
||||||
def calc_order_size(
|
def calc_order_size(
|
||||||
*,
|
*,
|
||||||
quote_per_unit: float,
|
quote_per_unit: float,
|
||||||
|
|||||||
@@ -163,13 +163,18 @@ def _require_options_ex(cfg: dict[str, Any]):
|
|||||||
|
|
||||||
|
|
||||||
def _budget_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
|
def _budget_full_usdc(cfg: dict[str, Any], ex: Any) -> tuple[float | None, str]:
|
||||||
"""交易账户 USDC 可用余额(由 calc_order_size 再乘 budget_buffer 留余量)."""
|
"""打满可用额度 = min(交易户可用 USDC, 单笔预算);calc_order_size 再乘 budget_buffer."""
|
||||||
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
from lib.exchange.okx_options_lib import fetch_options_trading_usdc
|
||||||
|
from lib.options.options_pricing_lib import resolve_budget_full_usdc
|
||||||
|
|
||||||
raw = fetch_options_trading_usdc(ex)
|
raw = fetch_options_trading_usdc(ex)
|
||||||
if raw is None or float(raw) <= 0:
|
if raw is None or float(raw) <= 0:
|
||||||
return None, "交易账户 USDC 可用余额不足"
|
return None, "交易账户 USDC 可用余额不足"
|
||||||
return float(raw), ""
|
trading = float(raw)
|
||||||
|
cap = _env_float("OKX_OPTIONS_TRADE_BUDGET_USDC", float(cfg.get("trade_budget") or 10.0))
|
||||||
|
if cap <= 0:
|
||||||
|
return None, "单笔预算无效(OKX_OPTIONS_TRADE_BUDGET_USDC)"
|
||||||
|
return resolve_budget_full_usdc(trading, float(cap)), ""
|
||||||
|
|
||||||
|
|
||||||
def _open_premium_paid(cfg: dict[str, Any], inst_id: str) -> float | None:
|
def _open_premium_paid(cfg: dict[str, Any], inst_id: str) -> float | None:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<div class="options-page-wrap" style="grid-column:1/-1" id="options-root"
|
<div class="options-page-wrap" style="grid-column:1/-1" id="options-root"
|
||||||
data-default-underly="{{ options_default_underly | default('ETH') }}"
|
data-default-underly="{{ options_default_underly | default('ETH') }}"
|
||||||
data-budget-buffer="{{ options_budget_buffer | default(0.95) }}"
|
data-budget-buffer="{{ options_budget_buffer | default(0.95) }}"
|
||||||
|
data-trade-budget="{{ options_trade_budget | default(10) }}"
|
||||||
data-ask-liq-filter="{% if options_chain_ask_liq_filter is defined %}{{ '1' if options_chain_ask_liq_filter else '0' }}{% else %}1{% endif %}">
|
data-ask-liq-filter="{% if options_chain_ask_liq_filter is defined %}{{ '1' if options_chain_ask_liq_filter else '0' }}{% else %}1{% endif %}">
|
||||||
{% if not options_enabled %}
|
{% 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>
|
<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>
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<li>环境配置「链上仅显示有卖一」开启时,隐藏无真实卖一或深度不足 1 张的合约(估算价 <strong>~</strong> 亦不显示)。</li>
|
<li>环境配置「链上仅显示有卖一」开启时,隐藏无真实卖一或深度不足 1 张的合约(估算价 <strong>~</strong> 亦不显示)。</li>
|
||||||
<li><strong>开仓只认真实卖一价且卖一深度≥1</strong>;无深度时面板显示参考标记价并禁用买入。</li>
|
<li><strong>开仓只认真实卖一价且卖一深度≥1</strong>;无深度时面板显示参考标记价并禁用买入。</li>
|
||||||
<li>链展示近 <span id="opt-chain-dte">14</span> 日到期;<strong>T 型</strong>默认 ATM ±5 档,可展开全部。</li>
|
<li>链展示近 <span id="opt-chain-dte">14</span> 日到期;<strong>T 型</strong>默认 ATM ±5 档,可展开全部。</li>
|
||||||
<li>「按可用余额打满」可用额度 = min(交易 USDC × 预算缓冲 <strong id="opt-budget-buf">{{ '%.2f'|format(options_budget_buffer|default(0.95)|float) }}</strong>, 单笔预算);可在 env「预算缓冲比例」改。</li>
|
<li>「按可用余额打满」可用额度 = min(交易户可用 USDC, 单笔预算 <strong id="opt-trade-budget">{{ '%.2f'|format(options_trade_budget|default(10)|float) }}</strong>),再 × 预算缓冲 <strong id="opt-budget-buf">{{ '%.2f'|format(options_budget_buffer|default(0.95)|float) }}</strong> 算张数(env 可改)。</li>
|
||||||
<li>平仓仅买一限价,详见说明文档。</li>
|
<li>平仓仅买一限价,详见说明文档。</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><a href="/options/guide" target="_blank" rel="noopener">打开《期权开平仓与监控说明》</a></p>
|
<p><a href="/options/guide" target="_blank" rel="noopener">打开《期权开平仓与监控说明》</a></p>
|
||||||
@@ -133,6 +134,9 @@
|
|||||||
<input type="number" id="opt-eth-amount" min="0.01" step="0.01" placeholder="如 0.5" style="display:none"
|
<input type="number" id="opt-eth-amount" min="0.01" step="0.01" placeholder="如 0.5" style="display:none"
|
||||||
autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other">
|
autocomplete="off" inputmode="decimal" data-lpignore="true" data-1p-ignore="true" data-form-type="other">
|
||||||
</div>
|
</div>
|
||||||
|
<p class="muted opt-budget-full-hint" id="opt-budget-full-hint" style="display:none;margin:6px 0 0;font-size:.82rem;line-height:1.4">
|
||||||
|
余额 > 单笔预算(<span id="opt-budget-full-cap">{{ '%.2f'|format(options_trade_budget|default(10)|float) }}</span>U)时按预算;余额不足时按余额;再乘预算缓冲算张数。
|
||||||
|
</p>
|
||||||
<input type="text" id="opt-signal-note" name="opt_signal_note" class="opt-signal-note" placeholder="备注(关键位说明)"
|
<input type="text" id="opt-signal-note" name="opt_signal_note" class="opt-signal-note" placeholder="备注(关键位说明)"
|
||||||
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
|
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
|
||||||
data-lpignore="true" data-1p-ignore="true" data-form-type="other" readonly>
|
data-lpignore="true" data-1p-ignore="true" data-form-type="other" readonly>
|
||||||
@@ -316,4 +320,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
<script src="/static/options_expiry_countdown.js?v=1"></script>
|
||||||
<script src="/static/options_panel.js?v=49"></script>
|
<script src="/static/options_panel.js?v=50"></script>
|
||||||
|
|||||||
@@ -0,0 +1,674 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate business-style XMind (Zen/2020+) from playbook + behavior rules."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
OUT = Path(__file__).resolve().parents[1] / "docs" / "交易执行手册与行为准则.xmind"
|
||||||
|
|
||||||
|
# 商务配色:深蓝主调 + 灰蓝辅色 + 强调色
|
||||||
|
C_ROOT = "#0F2942"
|
||||||
|
C_L1 = "#1B4F72"
|
||||||
|
C_L2 = "#2E86AB"
|
||||||
|
C_PASS = "#1E8449"
|
||||||
|
C_FAIL = "#922B21"
|
||||||
|
C_WARN = "#B9770E"
|
||||||
|
C_MUTED = "#566573"
|
||||||
|
C_TEXT = "#FFFFFF"
|
||||||
|
C_TEXT_DARK = "#1C2833"
|
||||||
|
|
||||||
|
|
||||||
|
def tid() -> str:
|
||||||
|
return uuid.uuid4().hex[:26]
|
||||||
|
|
||||||
|
|
||||||
|
def style(
|
||||||
|
*,
|
||||||
|
fill: Optional[str] = None,
|
||||||
|
color: Optional[str] = None,
|
||||||
|
font_size: str = "12pt",
|
||||||
|
bold: bool = False,
|
||||||
|
shape: str = "org.xmind.topicShape.roundedRect",
|
||||||
|
line: Optional[str] = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
props: dict[str, str] = {
|
||||||
|
"shape-class": shape,
|
||||||
|
"fo:font-family": "Microsoft YaHei",
|
||||||
|
"fo:font-size": font_size,
|
||||||
|
"fo:font-weight": "bold" if bold else "normal",
|
||||||
|
"border-line-width": "0pt",
|
||||||
|
"line-width": "1.5pt",
|
||||||
|
"line-class": "org.xmind.branchConnection.roundedelbow",
|
||||||
|
}
|
||||||
|
if fill:
|
||||||
|
props["svg:fill"] = fill
|
||||||
|
if color:
|
||||||
|
props["fo:color"] = color
|
||||||
|
if line:
|
||||||
|
props["line-color"] = line
|
||||||
|
return {"id": tid(), "properties": props}
|
||||||
|
|
||||||
|
|
||||||
|
def topic(
|
||||||
|
title: str,
|
||||||
|
children: list | None = None,
|
||||||
|
*,
|
||||||
|
markers: list[str] | None = None,
|
||||||
|
labels: list[str] | None = None,
|
||||||
|
notes: str | None = None,
|
||||||
|
fill: Optional[str] = None,
|
||||||
|
color: Optional[str] = None,
|
||||||
|
font_size: str = "12pt",
|
||||||
|
bold: bool = False,
|
||||||
|
line: Optional[str] = None,
|
||||||
|
) -> dict:
|
||||||
|
node: dict[str, Any] = {
|
||||||
|
"id": tid(),
|
||||||
|
"class": "topic",
|
||||||
|
"title": title,
|
||||||
|
"style": style(
|
||||||
|
fill=fill, color=color, font_size=font_size, bold=bold, line=line
|
||||||
|
),
|
||||||
|
}
|
||||||
|
if markers:
|
||||||
|
node["markers"] = [{"markerId": m} for m in markers]
|
||||||
|
if labels:
|
||||||
|
node["labels"] = labels
|
||||||
|
if notes:
|
||||||
|
node["notes"] = {"plain": {"content": notes}}
|
||||||
|
if children:
|
||||||
|
node["children"] = {"attached": children}
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
def t1(title: str, children: list, markers: list[str], label: str) -> dict:
|
||||||
|
return topic(
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
markers=markers,
|
||||||
|
labels=[label],
|
||||||
|
fill=C_L1,
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="16pt",
|
||||||
|
bold=True,
|
||||||
|
line=C_L1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def t2(title: str, children: list | None = None, markers: list[str] | None = None) -> dict:
|
||||||
|
return topic(
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
markers=markers or ["flag-dark-blue"],
|
||||||
|
fill=C_L2,
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="13pt",
|
||||||
|
bold=True,
|
||||||
|
line=C_L2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def leaf(
|
||||||
|
title: str,
|
||||||
|
*,
|
||||||
|
markers: list[str] | None = None,
|
||||||
|
fill: Optional[str] = None,
|
||||||
|
color: Optional[str] = C_TEXT_DARK,
|
||||||
|
) -> dict:
|
||||||
|
return topic(
|
||||||
|
title,
|
||||||
|
markers=markers or ["symbol-right"],
|
||||||
|
fill=fill or "#EBF5FB",
|
||||||
|
color=color,
|
||||||
|
font_size="11pt",
|
||||||
|
line="#AED6F1",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ok(title: str) -> dict:
|
||||||
|
return leaf(title, markers=["other-yes", "symbol-right"], fill="#E8F8F5", color=C_PASS)
|
||||||
|
|
||||||
|
|
||||||
|
def no(title: str) -> dict:
|
||||||
|
return leaf(title, markers=["other-no", "flag-gray"], fill="#FDEDEC", color=C_FAIL)
|
||||||
|
|
||||||
|
|
||||||
|
def warn(title: str) -> dict:
|
||||||
|
return leaf(title, markers=["symbol-info"], fill="#FEF9E7", color=C_WARN)
|
||||||
|
|
||||||
|
|
||||||
|
def build_content() -> list:
|
||||||
|
root = topic(
|
||||||
|
"交易执行体系\n手册 v2 · 开单三检",
|
||||||
|
[
|
||||||
|
t1(
|
||||||
|
"① 设计理念",
|
||||||
|
[
|
||||||
|
t2(
|
||||||
|
"核心主张",
|
||||||
|
[
|
||||||
|
leaf("少而精,珍惜机会,样本干净", markers=["star-dark-blue"]),
|
||||||
|
leaf("不保证收益;过程可控,结果随缘", markers=["symbol-info"]),
|
||||||
|
leaf("过滤比频率重要;日更不是目标", markers=["symbol-info"]),
|
||||||
|
leaf("看不懂不做;不为开单找理由", markers=["symbol-info"]),
|
||||||
|
warn("丢掉对冲:无「有保护就能多做」幻觉"),
|
||||||
|
],
|
||||||
|
markers=["other-lightbulb"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"工具边界",
|
||||||
|
[
|
||||||
|
leaf("OKX 期权:方向单(虚值等)", markers=["flag-blue"]),
|
||||||
|
leaf("Gate 合约:结构清楚时的波段", markers=["flag-dark-blue"]),
|
||||||
|
leaf("同一时段尽量只让一边说话", markers=["symbol-equality"]),
|
||||||
|
no("不做期期对冲 / 偏置壳"),
|
||||||
|
],
|
||||||
|
markers=["symbol-info"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"文档分工",
|
||||||
|
[
|
||||||
|
leaf("行为准则:能不能动手(防火墙)", markers=["other-lock"]),
|
||||||
|
leaf("执行手册:怎么做单(玩法/仓位/离场)", markers=["other-note"]),
|
||||||
|
],
|
||||||
|
markers=["other-businesscard"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
markers=["priority-1", "other-lightbulb"],
|
||||||
|
label="理念",
|
||||||
|
),
|
||||||
|
t1(
|
||||||
|
"② 资金要求",
|
||||||
|
[
|
||||||
|
t2(
|
||||||
|
"总盘约 800U",
|
||||||
|
[
|
||||||
|
leaf("单笔约 1.25% 量级", markers=["symbol-info"]),
|
||||||
|
leaf("全错一天约 2.5% 量级——防守优先", markers=["symbol-info"]),
|
||||||
|
],
|
||||||
|
markers=["other-businesscard"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"单笔期权",
|
||||||
|
[
|
||||||
|
leaf("约 10U 权利金预算", markers=["priority-1"]),
|
||||||
|
leaf("一次只持有一个期权仓位", markers=["symbol-info"]),
|
||||||
|
leaf("打满 = min(余额, 单笔预算)", markers=["symbol-equality"]),
|
||||||
|
],
|
||||||
|
markers=["flag-blue"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"Gate 合约",
|
||||||
|
[
|
||||||
|
leaf("日内保证金约 50U · 约 10 倍", markers=["symbol-info"]),
|
||||||
|
leaf("止损一般约 5U", markers=["symbol-info"]),
|
||||||
|
leaf("单笔最大亏损不超过约 10U", markers=["flag-gray"]),
|
||||||
|
leaf("有单才用保证金,无单为 0", markers=["task-done"]),
|
||||||
|
],
|
||||||
|
markers=["flag-dark-blue"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"日损失心理框",
|
||||||
|
[
|
||||||
|
warn("都错:合计大约 ≤20U"),
|
||||||
|
ok("都对:期望可到 40U+(理想,非每日目标)"),
|
||||||
|
no("不为「好像有保护」放大仓位"),
|
||||||
|
warn("尽量少同向双开;双开按合计最坏约 20U"),
|
||||||
|
],
|
||||||
|
markers=["symbol-info"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
markers=["priority-2", "other-businesscard"],
|
||||||
|
label="资金",
|
||||||
|
),
|
||||||
|
t1(
|
||||||
|
"③ 操盘思路",
|
||||||
|
[
|
||||||
|
t2(
|
||||||
|
"行为准则 · 开单三检",
|
||||||
|
[
|
||||||
|
topic(
|
||||||
|
"一句话防火墙",
|
||||||
|
[
|
||||||
|
leaf("信号够不够清晰?", markers=["symbol-question"]),
|
||||||
|
leaf("流程有没有跑通?", markers=["symbol-question"]),
|
||||||
|
leaf("情绪是不是在证明自己?", markers=["symbol-question"]),
|
||||||
|
no("三检不过 → 不开"),
|
||||||
|
],
|
||||||
|
markers=["other-lock", "priority-1"],
|
||||||
|
fill="#154360",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
labels=["防火墙"],
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"总循环",
|
||||||
|
[
|
||||||
|
leaf("信号判断 → 流程确认 → 情绪自检", markers=["arrow-right"]),
|
||||||
|
leaf("全部通过 → 开仓", markers=["other-yes"]),
|
||||||
|
leaf("等待系统结果(止盈/止损/到期)", markers=["other-clock"]),
|
||||||
|
leaf("复盘整环 → 等待下一信号", markers=["arrow-refresh"]),
|
||||||
|
no("任一步否决 → 空仓离开"),
|
||||||
|
],
|
||||||
|
markers=["arrow-right"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"开单前三秒停顿",
|
||||||
|
[
|
||||||
|
leaf("核心信号是什么?", markers=["symbol-info"]),
|
||||||
|
leaf("安全流程跑通了吗?", markers=["task-start"]),
|
||||||
|
leaf("冷静执行,还是怕踏空/回本/证明自己?", markers=["symbol-info"]),
|
||||||
|
],
|
||||||
|
markers=["other-clock"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"检1 · 信号判断",
|
||||||
|
[
|
||||||
|
ok("一句话说清唯一核心确认"),
|
||||||
|
ok("点位/结构本身已够清楚"),
|
||||||
|
no("说不清、靠宏观故事自圆"),
|
||||||
|
no("「好像有戏」但确认模糊"),
|
||||||
|
leaf("对照:1H→空间→结构→定损盈→工具", markers=["arrow-right"]),
|
||||||
|
],
|
||||||
|
markers=["priority-1", "symbol-info"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
labels=["Signal"],
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"检2 · 流程确认",
|
||||||
|
[
|
||||||
|
ok("资金与当日额度符合"),
|
||||||
|
ok("单笔/组合敞口在预算内"),
|
||||||
|
no("资金或次数已触限"),
|
||||||
|
no("单笔或日最坏超限 → 暂停"),
|
||||||
|
no("「先开了再说」跳步"),
|
||||||
|
],
|
||||||
|
markers=["priority-2", "task-start"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
labels=["Process"],
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"检3 · 情绪自检",
|
||||||
|
[
|
||||||
|
ok("符合系统 + 账户没问题 → 开"),
|
||||||
|
ok("可接受空仓,旁观者视角"),
|
||||||
|
no("怕踏空"),
|
||||||
|
no("上回亏了要回本"),
|
||||||
|
no("必须证明我是对的"),
|
||||||
|
warn("红灯亮了,信号再好看也不开"),
|
||||||
|
],
|
||||||
|
markers=["priority-3", "smiley-smile"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
labels=["Emotion"],
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"复盘只记什么",
|
||||||
|
[
|
||||||
|
leaf("信号:是否做了?核心写了什么?", markers=["other-note"]),
|
||||||
|
leaf("流程:资金/敞口是否过关?有无跳步?", markers=["other-note"]),
|
||||||
|
leaf("情绪:当时是哪一类心态?", markers=["other-note"]),
|
||||||
|
warn("结果不推翻「三检是否完成」评分"),
|
||||||
|
],
|
||||||
|
markers=["other-note"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
markers=["other-lock", "flag-purple"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"开仓逻辑",
|
||||||
|
[
|
||||||
|
topic(
|
||||||
|
"主链条(强制)",
|
||||||
|
[
|
||||||
|
leaf("1H 方向:明显 N 字;跟 1H 波段", markers=["priority-1"]),
|
||||||
|
leaf("空间:空看支撑、多看阻力;≥约 2%", markers=["priority-2"]),
|
||||||
|
leaf("结构:15m/5m;量级约 8h+(约 48×15m)", markers=["priority-3"]),
|
||||||
|
leaf("定损盈:外沿/针尖;RR 须接受", markers=["priority-4"]),
|
||||||
|
leaf("选工具:期权 或 合约(不对冲)", markers=["priority-5"]),
|
||||||
|
no("任一步不过 → 空仓等待"),
|
||||||
|
],
|
||||||
|
markers=["arrow-right", "symbol-info"],
|
||||||
|
fill="#154360",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
labels=["主链"],
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"结构形态参考",
|
||||||
|
[
|
||||||
|
leaf("收敛", markers=["flag-blue"]),
|
||||||
|
leaf("两段式回调", markers=["flag-dark-blue"]),
|
||||||
|
leaf("箱体", markers=["flag-gray"]),
|
||||||
|
leaf("假突破", markers=["flag-orange"]),
|
||||||
|
],
|
||||||
|
markers=["symbol-image"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"期权入场",
|
||||||
|
[
|
||||||
|
ok("主链条全过;结构突破/假突破成立"),
|
||||||
|
leaf("一天期方向单;空间够优先虚值", markers=["star-blue"]),
|
||||||
|
leaf("默认先只开期权,不上合约", markers=["symbol-info"]),
|
||||||
|
leaf("尽量 16:00 后开次日到期", markers=["other-clock"]),
|
||||||
|
no("不做:期期对冲、偏置壳、为开而开"),
|
||||||
|
],
|
||||||
|
markers=["flag-blue", "symbol-plus"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
labels=["期权"],
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"合约入场(Gate)",
|
||||||
|
[
|
||||||
|
ok("主链条过关;位置极明确"),
|
||||||
|
leaf("想清进场:假突破 / 结构突破", markers=["symbol-info"]),
|
||||||
|
leaf("止损挂模型位(外沿/针尖)", markers=["symbol-info"]),
|
||||||
|
warn("独立假突破:只做合约或空仓"),
|
||||||
|
no("勿与「突破期权后再加仓」混仓"),
|
||||||
|
],
|
||||||
|
markers=["flag-dark-blue", "symbol-plus"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
labels=["合约"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
markers=["symbol-plus", "arrow-up-right"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"平仓逻辑",
|
||||||
|
[
|
||||||
|
topic(
|
||||||
|
"期权离场",
|
||||||
|
[
|
||||||
|
ok("只认:系统/规则止盈"),
|
||||||
|
ok("只认:到期"),
|
||||||
|
no("开仓后中间不手动平仓"),
|
||||||
|
warn("紧急手平 → 标记非策略样本"),
|
||||||
|
],
|
||||||
|
markers=["flag-green", "symbol-minus"],
|
||||||
|
fill="#145A32",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
labels=["期权"],
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"合约离场",
|
||||||
|
[
|
||||||
|
ok("结构止盈为准"),
|
||||||
|
ok("结构止损为准(约 5U 量级)"),
|
||||||
|
leaf("等待系统/挂单结果,不情绪手平", markers=["other-clock"]),
|
||||||
|
],
|
||||||
|
markers=["flag-dark-green", "symbol-minus"],
|
||||||
|
fill="#145A32",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
labels=["合约"],
|
||||||
|
),
|
||||||
|
topic(
|
||||||
|
"持仓期盯什么",
|
||||||
|
[
|
||||||
|
leaf("程序与纪律是否正常", markers=["task-done"]),
|
||||||
|
no("不是浮盈浮亏数字本身"),
|
||||||
|
leaf("无信号空档:空跑三检也是训练", markers=["other-lightbulb"]),
|
||||||
|
],
|
||||||
|
markers=["symbol-info"],
|
||||||
|
fill="#1A5276",
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="12pt",
|
||||||
|
bold=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
markers=["symbol-minus", "flag-green"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
markers=["priority-3", "arrow-right"],
|
||||||
|
label="操盘",
|
||||||
|
),
|
||||||
|
t1(
|
||||||
|
"④ 纪律执行",
|
||||||
|
[
|
||||||
|
t2(
|
||||||
|
"Gate 日纪律",
|
||||||
|
[
|
||||||
|
leaf("只做很明确的位置", markers=["symbol-info"]),
|
||||||
|
leaf("同一位置最多两次机会(突破/假突破)", markers=["priority-2"]),
|
||||||
|
no("两次都错 → 当日不再做单"),
|
||||||
|
ok("离场以结构止盈/止损为准"),
|
||||||
|
],
|
||||||
|
markers=["flag-dark-blue", "task-done"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"期权日纪律",
|
||||||
|
[
|
||||||
|
no("不手平;等规则止盈或到期"),
|
||||||
|
leaf("一次一仓;约 10U 权利金", markers=["symbol-info"]),
|
||||||
|
no("不对冲;不做每天默认开期权"),
|
||||||
|
leaf("损位跟模型:外沿/针尖", markers=["symbol-info"]),
|
||||||
|
],
|
||||||
|
markers=["flag-blue", "task-done"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"开仓前自检清单",
|
||||||
|
[
|
||||||
|
leaf("今日只动期权/合约?未开对冲?", markers=["task-start"]),
|
||||||
|
leaf("1H 方向清楚(含 N 字)?", markers=["task-start"]),
|
||||||
|
leaf("空间足够?结构量级够?", markers=["task-start"]),
|
||||||
|
leaf("止损/止盈与 RR 定好?", markers=["task-start"]),
|
||||||
|
leaf("工具选期权还是合约?理由写清?", markers=["task-start"]),
|
||||||
|
leaf("合约:本位置第几次?今日两次用完?", markers=["task-start"]),
|
||||||
|
],
|
||||||
|
markers=["other-yes", "task-start"],
|
||||||
|
),
|
||||||
|
t2(
|
||||||
|
"一句话版本",
|
||||||
|
[
|
||||||
|
leaf("1H→空间→结构→定损盈→期权/合约", markers=["arrow-right"]),
|
||||||
|
leaf("不对冲;期权不手平", markers=["flag-gray"]),
|
||||||
|
leaf("一位置两次,错完收工", markers=["priority-2"]),
|
||||||
|
leaf("珍惜机会,日更不是目标", markers=["star-dark-blue"]),
|
||||||
|
],
|
||||||
|
markers=["star-dark-blue", "symbol-right"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
markers=["priority-4", "task-done"],
|
||||||
|
label="纪律",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
# 中心主题保持干净:不加图标/标签/备注,避免绿人、黄便签等杂乱标识
|
||||||
|
markers=None,
|
||||||
|
labels=None,
|
||||||
|
fill=C_ROOT,
|
||||||
|
color=C_TEXT,
|
||||||
|
font_size="20pt",
|
||||||
|
bold=True,
|
||||||
|
line=C_ROOT,
|
||||||
|
)
|
||||||
|
root["structureClass"] = "org.xmind.ui.logic.right"
|
||||||
|
|
||||||
|
# XMind Zen 内置主题名;客户端可识别 business
|
||||||
|
sheet = {
|
||||||
|
"id": tid(),
|
||||||
|
"class": "sheet",
|
||||||
|
"title": "执行手册与行为准则 · 商务版",
|
||||||
|
"rootTopic": root,
|
||||||
|
"theme": {
|
||||||
|
"id": tid(),
|
||||||
|
"title": "business",
|
||||||
|
"centralTopic": {
|
||||||
|
"id": "centralTopic",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": C_ROOT,
|
||||||
|
"fo:color": C_TEXT,
|
||||||
|
"fo:font-family": "Microsoft YaHei",
|
||||||
|
"fo:font-size": "20pt",
|
||||||
|
"fo:font-weight": "bold",
|
||||||
|
"shape-class": "org.xmind.topicShape.roundedRect",
|
||||||
|
"line-color": C_L1,
|
||||||
|
"line-width": "2pt",
|
||||||
|
"line-class": "org.xmind.branchConnection.roundedelbow",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"mainTopic": {
|
||||||
|
"id": "mainTopic",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": C_L1,
|
||||||
|
"fo:color": C_TEXT,
|
||||||
|
"fo:font-family": "Microsoft YaHei",
|
||||||
|
"fo:font-size": "15pt",
|
||||||
|
"fo:font-weight": "bold",
|
||||||
|
"shape-class": "org.xmind.topicShape.roundedRect",
|
||||||
|
"line-color": C_L2,
|
||||||
|
"line-width": "1.5pt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"subTopic": {
|
||||||
|
"id": "subTopic",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": C_L2,
|
||||||
|
"fo:color": C_TEXT,
|
||||||
|
"fo:font-family": "Microsoft YaHei",
|
||||||
|
"fo:font-size": "12pt",
|
||||||
|
"shape-class": "org.xmind.topicShape.roundedRect",
|
||||||
|
"line-color": "#85C1E9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"floatingTopic": {
|
||||||
|
"id": "floatingTopic",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": C_MUTED,
|
||||||
|
"fo:color": C_TEXT,
|
||||||
|
"fo:font-family": "Microsoft YaHei",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"importantTopic": {
|
||||||
|
"id": "importantTopic",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": C_WARN,
|
||||||
|
"fo:color": C_TEXT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"minorTopic": {
|
||||||
|
"id": "minorTopic",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": "#EBF5FB",
|
||||||
|
"fo:color": C_TEXT_DARK,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"expiredTopic": {
|
||||||
|
"id": "expiredTopic",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": "#D5D8DC",
|
||||||
|
"fo:color": C_MUTED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"calloutTopic": {
|
||||||
|
"id": "calloutTopic",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": "#FEF9E7",
|
||||||
|
"fo:color": C_WARN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"summaryTopic": {
|
||||||
|
"id": "summaryTopic",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": "#145A32",
|
||||||
|
"fo:color": C_TEXT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"boundary": {
|
||||||
|
"id": "boundary",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": "#D6EAF8",
|
||||||
|
"fo:color": C_L1,
|
||||||
|
"line-color": C_L2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"id": "summary",
|
||||||
|
"properties": {
|
||||||
|
"line-color": C_L1,
|
||||||
|
"line-width": "2pt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"relationship": {
|
||||||
|
"id": "relationship",
|
||||||
|
"properties": {
|
||||||
|
"line-color": C_MUTED,
|
||||||
|
"line-pattern": "dash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"id": "map",
|
||||||
|
"properties": {
|
||||||
|
"svg:fill": "#F4F6F7",
|
||||||
|
"color-list": f"{C_L1} {C_L2} #2874A6 #1ABC9C #B9770E",
|
||||||
|
"line-tapered": "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return [sheet]
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
content = build_content()
|
||||||
|
metadata = {
|
||||||
|
"creator": {"name": "crypto_monitor", "version": "1.1"},
|
||||||
|
"activeSheetId": content[0]["id"],
|
||||||
|
}
|
||||||
|
manifest = {
|
||||||
|
"file-entries": {
|
||||||
|
"content.json": {},
|
||||||
|
"metadata.json": {},
|
||||||
|
"manifest.json": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
if OUT.exists():
|
||||||
|
OUT.unlink()
|
||||||
|
with zipfile.ZipFile(OUT, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||||
|
zf.writestr("content.json", json.dumps(content, ensure_ascii=False, indent=2))
|
||||||
|
zf.writestr("metadata.json", json.dumps(metadata, ensure_ascii=False, indent=2))
|
||||||
|
zf.writestr("manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2))
|
||||||
|
print(f"wrote {OUT}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"""按可用余额打满:min(余额, 单笔预算)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from lib.options.options_pricing_lib import resolve_budget_full_usdc
|
||||||
|
|
||||||
|
|
||||||
|
def test_balance_above_budget_uses_budget():
|
||||||
|
assert resolve_budget_full_usdc(100.0, 10.0) == 10.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_balance_below_budget_uses_balance():
|
||||||
|
assert resolve_budget_full_usdc(5.0, 10.0) == 5.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_balance_equals_budget():
|
||||||
|
assert resolve_budget_full_usdc(10.0, 10.0) == 10.0
|
||||||
Reference in New Issue
Block a user