dsh-hyperliquid 0.5.8 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/client.js +153 -32
- package/index.js +1 -1
- package/lib/remote.js +9 -5
- package/lib/risk-settings.js +11 -2
- package/lib/tools.js +2 -2
- package/lib/trading-guard.js +2 -1
- package/package.json +1 -1
- package/skills/hyperliquid-trading/SKILL.md +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.9 — 2026-10-17
|
|
4
|
+
|
|
5
|
+
- 允许开仓币对增加「全部 / 手动设置」:全部 = 所有可交易市场(核心永续与以 USDC 为保证金的 HIP-3)都能开仓,单笔 / 每日 / 仓位 / 杠杆上限照常生效;手动设置 = 原来的逗号列表,从全部切回时预填上次的列表。宿主以 `allowedCoins: ["*"]` 表示全部,插件配置也可写 `*`;全托管 agent 用 `addCoins: ["*"]` 切到全部。
|
|
6
|
+
- 市场选择器的「可交易 / 全部」保持用户的选择,不再随下方分类(现货、HIP-3、Crypto 等)自动切换,并在下次打开时记住;「可交易」下的只读分类为空时提示并提供「查看全部」。
|
|
7
|
+
- 修复:市场目录后台刷新期间结果为空时列表一片空白、没有提示。
|
|
8
|
+
- 修复:深色主题下画线菜单的搜索框聚焦时被白色焦点环框住;改为底边加粗提示焦点。
|
|
9
|
+
- 画线列表改为浮层:展开时浮在内容之上(下方空间不足时向上弹出),不再把图表下方的导航与下单区域往下推;默认收起;点击外部、按 Esc 或从列表选中一条画线后收起(不遮挡刚选中的线),显示 / 锁定 / 删除操作保持展开。
|
|
10
|
+
- 「全屏图表」改为真正的浏览器全屏(整页全屏,图表铺满屏幕,弹窗照常显示);支持键盘锁的浏览器里 Esc 仍按「取消画线 → 取消选择 → 退出全屏」逐层退出;从浏览器侧退出全屏时同步退出;浏览器不允许全屏时退回页面内放大。
|
|
11
|
+
|
|
3
12
|
## 0.5.8 — 2026-10-17
|
|
4
13
|
|
|
5
14
|
- 账户互转补全:现货、主永续、各 HIP-3 交易所之间任意两两划转。现货 ↔ HIP-3、HIP-3 ↔ HIP-3 自动经主永续分两步签名提交,半托管只批准一次;第二步前重新确认主永续可划余额;第二步被拒、被取消或余额不足时报错说明资金在主永续;第二步已提交但未收到确认时报「结果未知,先对账,不要直接重试」,防止重复划转。两端都校验 USDC 保证金,非 USDC 交易所在签名前拒绝。
|
package/client.js
CHANGED
|
@@ -776,7 +776,11 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
776
776
|
/* Drawings object list (under the chart). */
|
|
777
777
|
.dhl-drawing-list { margin: 6px 10px 0; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; font-size: 12px; }
|
|
778
778
|
.dhl-drawing-list-head { display: flex; justify-content: space-between; width: 100%; padding: 6px 10px; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; cursor: pointer; }
|
|
779
|
-
.dhl-drawing-list
|
|
779
|
+
.dhl-drawing-list { position: relative; z-index: 9; }
|
|
780
|
+
/* The list floats over what is below (or above when there is no room) instead of pushing it down. */
|
|
781
|
+
.dhl-drawing-list ul { position: absolute; left: -1px; right: -1px; top: calc(100% + 4px); z-index: 1; margin: 0; padding: 4px; list-style: none; max-height: min(320px, 45vh); overflow-y: auto;
|
|
782
|
+
border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); box-shadow: 0 12px 32px color-mix(in srgb, #000 16%, transparent); }
|
|
783
|
+
.dhl-drawing-list.up ul { top: auto; bottom: calc(100% + 4px); }
|
|
780
784
|
.dhl-drawing-list li { display: flex; align-items: center; gap: 2px; border-radius: 6px; }
|
|
781
785
|
.dhl-drawing-list li:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
782
786
|
.dhl-drawing-list li.is-hidden { opacity: .5; }
|
|
@@ -859,7 +863,7 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
859
863
|
.dhl-chart-panel-expanded .dhl-chart-tools { flex-wrap: wrap; flex-shrink: 0; }
|
|
860
864
|
.dhl-chart-panel-expanded .dhl-intervals, .dhl-chart-panel-expanded .dhl-chart-actions { flex-wrap: wrap; }
|
|
861
865
|
.dhl-chart-panel-expanded .dhl-favorite-tools, .dhl-chart-panel-expanded .dhl-chart-click-hint, .dhl-chart-panel-expanded .dhl-chart-navigation { flex-shrink: 0; }
|
|
862
|
-
.dhl-chart-panel-expanded .dhl-drawing-list {
|
|
866
|
+
.dhl-chart-panel-expanded .dhl-drawing-list { flex-shrink: 0; }
|
|
863
867
|
.dhl-draw-popover .dhl-draw-favorite[aria-pressed="true"] svg { color: var(--dsw-alias-brand-primary); }
|
|
864
868
|
|
|
865
869
|
/* Research and evidence share the existing desk tokens. */
|
|
@@ -966,7 +970,9 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
966
970
|
.dhl-draw-toolbar.at-bottom { top: auto; bottom: 40px; }
|
|
967
971
|
|
|
968
972
|
/* Search at the top of the drawing menu (stays visible while the list scrolls). */
|
|
969
|
-
.dhl-draw-popover .dhl-draw-search { position: sticky; top: -6px; z-index: 1; width: 100%; box-sizing: border-box; margin: -6px 0 4px; padding: 8px 10px; border: 0; border-bottom: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; outline: none; }
|
|
973
|
+
.dhl-draw-popover .dhl-draw-search { position: sticky; top: -6px; z-index: 1; width: 100%; box-sizing: border-box; margin: -6px 0 4px; padding: 8px 10px; border: 0; border-bottom: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; outline: none; -webkit-appearance: none; appearance: none; box-shadow: none; border-radius: 0; }
|
|
974
|
+
/* Focus shows as a stronger bottom rule instead of the global ring, which boxed the search in white on dark themes. */
|
|
975
|
+
.dhl-root .dhl-draw-popover .dhl-draw-search:focus-visible { outline: none; border-bottom: 2px solid var(--dsw-alias-brand-primary); padding-bottom: 7px; }
|
|
970
976
|
.dhl-draw-popover .dhl-draw-empty { margin: 10px 8px; color: var(--dsw-alias-label-secondary); font-size: 11.5px; }
|
|
971
977
|
|
|
972
978
|
/* 0.5.1: highlighter, pin, copied bars. */
|
|
@@ -988,6 +994,19 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
988
994
|
|
|
989
995
|
/* 0.5.4: Gann square fixed grid. */
|
|
990
996
|
.dhl-drawing .dhl-gann-grid { stroke: currentColor; stroke-width: 1; opacity: .35; }
|
|
997
|
+
|
|
998
|
+
/* Allowed coins: \u5168\u90E8 / \u624B\u52A8\u8BBE\u7F6E. */
|
|
999
|
+
.dhl-risk-form .dhl-coins-mode { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 6px; }
|
|
1000
|
+
.dhl-coins-mode > span:first-child { color: var(--dsw-alias-label-secondary); font-size: 13px; line-height: 20px; }
|
|
1001
|
+
.dhl-coins-seg { display: inline-flex; align-self: flex-start; gap: 2px; padding: 2px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 7px; }
|
|
1002
|
+
.dhl-coins-seg button { padding: 5px 14px; border: 0; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; cursor: pointer; }
|
|
1003
|
+
.dhl-coins-seg button:hover:not(.on) { color: var(--dsw-alias-label-primary); }
|
|
1004
|
+
.dhl-coins-seg button.on { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent); color: var(--dsw-alias-brand-primary); font-weight: 600; }
|
|
1005
|
+
.dhl-coins-mode small { color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 1.45; }
|
|
1006
|
+
.dhl-picker-empty-action { display: block; margin: 10px auto 0; padding: 4px 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; background: transparent; color: var(--dsw-alias-brand-primary); font: inherit; cursor: pointer; }
|
|
1007
|
+
|
|
1008
|
+
/* Real fullscreen: the page fills the screen; the chart panel fills the page edge to edge. */
|
|
1009
|
+
.dhl-chart-panel-expanded.is-fullscreen { inset: 0; border: 0; border-radius: 0; box-shadow: none; }
|
|
991
1010
|
`;
|
|
992
1011
|
|
|
993
1012
|
// src/client/locale.ts
|
|
@@ -20872,26 +20891,43 @@ function useDrawingHistory(scope) {
|
|
|
20872
20891
|
|
|
20873
20892
|
// src/client/components/drawing-list.tsx
|
|
20874
20893
|
var React12 = __toESM(require("react"), 1);
|
|
20875
|
-
var OPEN_KEY = "dsh-hyperliquid.draw.listOpen";
|
|
20876
20894
|
var DrawingList = React12.memo(function DrawingList2({ marks, locked, onSelect, onEdit, onRemove, language }) {
|
|
20877
|
-
const [open, setOpen] = React12.useState(
|
|
20878
|
-
|
|
20879
|
-
|
|
20880
|
-
} catch {
|
|
20881
|
-
return true;
|
|
20882
|
-
}
|
|
20883
|
-
});
|
|
20895
|
+
const [open, setOpen] = React12.useState(false);
|
|
20896
|
+
const [up, setUp] = React12.useState(false);
|
|
20897
|
+
const root = React12.useRef(null);
|
|
20884
20898
|
const toggle = () => setOpen((value) => {
|
|
20885
|
-
|
|
20886
|
-
|
|
20887
|
-
|
|
20899
|
+
if (!value && root.current) {
|
|
20900
|
+
const rect = root.current.getBoundingClientRect(), below = window.innerHeight - rect.bottom;
|
|
20901
|
+
setUp(below < 240 && rect.top > below);
|
|
20888
20902
|
}
|
|
20889
20903
|
return !value;
|
|
20890
20904
|
});
|
|
20905
|
+
React12.useEffect(() => {
|
|
20906
|
+
if (!open) return void 0;
|
|
20907
|
+
const onPointer = (event) => {
|
|
20908
|
+
if (!root.current?.contains(event.target)) setOpen(false);
|
|
20909
|
+
};
|
|
20910
|
+
const onKey = (event) => {
|
|
20911
|
+
if (event.key === "Escape") {
|
|
20912
|
+
event.preventDefault();
|
|
20913
|
+
event.stopPropagation();
|
|
20914
|
+
setOpen(false);
|
|
20915
|
+
}
|
|
20916
|
+
};
|
|
20917
|
+
window.addEventListener("pointerdown", onPointer, true);
|
|
20918
|
+
window.addEventListener("keydown", onKey, true);
|
|
20919
|
+
return () => {
|
|
20920
|
+
window.removeEventListener("pointerdown", onPointer, true);
|
|
20921
|
+
window.removeEventListener("keydown", onKey, true);
|
|
20922
|
+
};
|
|
20923
|
+
}, [open]);
|
|
20891
20924
|
const hidden = marks.filter((mark) => mark.hidden).length;
|
|
20892
|
-
return /* @__PURE__ */ React12.createElement("section", { className: "dhl-drawing-list", lang: language, "aria-label": tr("\u753B\u7EBF\u5217\u8868", "Drawings") }, /* @__PURE__ */ React12.createElement("button", { type: "button", className: "dhl-drawing-list-head", "aria-expanded": open, onClick: toggle }, /* @__PURE__ */ React12.createElement("span", null, tr("\u753B\u7EBF", "Drawings"), " ", marks.length, hidden ? tr(` \xB7 \u9690\u85CF ${hidden}`, ` \xB7 ${hidden} hidden`) : ""), /* @__PURE__ */ React12.createElement(UiIcon, { name: open ? "chevronDown" : "chevronRight", size: 14 })), open ? /* @__PURE__ */ React12.createElement("ul", null, marks.map((mark) => {
|
|
20925
|
+
return /* @__PURE__ */ React12.createElement("section", { ref: root, className: "dhl-drawing-list" + (open ? " open" : "") + (open && up ? " up" : ""), lang: language, "aria-label": tr("\u753B\u7EBF\u5217\u8868", "Drawings") }, /* @__PURE__ */ React12.createElement("button", { type: "button", className: "dhl-drawing-list-head", "aria-expanded": open, onClick: toggle }, /* @__PURE__ */ React12.createElement("span", null, tr("\u753B\u7EBF", "Drawings"), " ", marks.length, hidden ? tr(` \xB7 \u9690\u85CF ${hidden}`, ` \xB7 ${hidden} hidden`) : ""), /* @__PURE__ */ React12.createElement(UiIcon, { name: open ? "chevronDown" : "chevronRight", size: 14 })), open ? /* @__PURE__ */ React12.createElement("ul", null, marks.map((mark) => {
|
|
20893
20926
|
const tool = toolOf(mark.kind);
|
|
20894
|
-
return /* @__PURE__ */ React12.createElement("li", { key: mark.id, "data-mark-id": mark.id, className: mark.hidden ? "is-hidden" : "" }, /* @__PURE__ */ React12.createElement("button", { type: "button", className: "dhl-drawing-list-name", title: tr("\u5728\u56FE\u4E0A\u5B9A\u4F4D\u5E76\u9009\u4E2D", "Find and select on the chart"), onClick: () =>
|
|
20927
|
+
return /* @__PURE__ */ React12.createElement("li", { key: mark.id, "data-mark-id": mark.id, className: mark.hidden ? "is-hidden" : "" }, /* @__PURE__ */ React12.createElement("button", { type: "button", className: "dhl-drawing-list-name", title: tr("\u5728\u56FE\u4E0A\u5B9A\u4F4D\u5E76\u9009\u4E2D", "Find and select on the chart"), onClick: () => {
|
|
20928
|
+
onSelect(mark.id);
|
|
20929
|
+
setOpen(false);
|
|
20930
|
+
}, disabled: mark.hidden }, /* @__PURE__ */ React12.createElement("span", { style: { color: mark.color } }, /* @__PURE__ */ React12.createElement(ToolIcon, { kind: mark.kind, size: 14 })), /* @__PURE__ */ React12.createElement("b", null, mark.label), /* @__PURE__ */ React12.createElement("small", null, mark.alert ? /* @__PURE__ */ React12.createElement("span", { className: "dhl-list-flag", title: tr("\u4EF7\u683C\u63D0\u9192\u5DF2\u5F00\u542F", "Price alert armed") }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "bell", size: 12 })) : null, mark.shared ? /* @__PURE__ */ React12.createElement("span", { className: "dhl-list-flag", title: tr("\u5DF2\u540C\u6B65\u5230\u6240\u6709\u4F1A\u8BDD", "Synced to all sessions") }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "sync", size: 12 })) : null, tr(tool.zh, tool.en), " \xB7 ", mark.interval, mark.visibleIntervals?.length ? tr(` \xB7 \u4EC5 ${mark.visibleIntervals.join("/")} \u663E\u793A`, ` \xB7 ${mark.visibleIntervals.join("/")} only`) : "", mark.source === "agent" ? " \xB7 agent" : "")), /* @__PURE__ */ React12.createElement("button", { type: "button", "aria-pressed": !mark.hidden, title: mark.hidden ? tr("\u663E\u793A", "Show") : tr("\u9690\u85CF", "Hide"), "aria-label": `${mark.hidden ? tr("\u663E\u793A", "Show") : tr("\u9690\u85CF", "Hide")} ${mark.label}`, onClick: () => onEdit(mark.id, { hidden: !mark.hidden }) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: mark.hidden ? "eyeOff" : "eye", size: 14 })), /* @__PURE__ */ React12.createElement("button", { type: "button", className: mark.locked ? "on" : "", "aria-pressed": Boolean(mark.locked), disabled: locked, title: mark.locked ? tr("\u89E3\u9501", "Unlock") : tr("\u9501\u5B9A", "Lock"), "aria-label": `${mark.locked ? tr("\u89E3\u9501", "Unlock") : tr("\u9501\u5B9A", "Lock")} ${mark.label}`, onClick: () => onEdit(mark.id, { locked: !mark.locked }) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: mark.locked ? "lock" : "unlock", size: 14 })), /* @__PURE__ */ React12.createElement("button", { type: "button", disabled: locked || mark.locked, title: tr("\u5220\u9664", "Delete"), "aria-label": `${tr("\u5220\u9664\u753B\u7EBF", "Remove line")} ${mark.label}`, onClick: () => onRemove(mark.id) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "trash", size: 14 })));
|
|
20895
20931
|
})) : null);
|
|
20896
20932
|
});
|
|
20897
20933
|
|
|
@@ -21302,6 +21338,14 @@ var CATEGORIES = [
|
|
|
21302
21338
|
];
|
|
21303
21339
|
var FAV_KEY = "dsh-hyperliquid.favorites.v1";
|
|
21304
21340
|
var TRADFI_TAGS = /* @__PURE__ */ new Set(["stocks", "stock", "equities", "equity", "commodities", "indices", "fx", "rates", "preipo"]);
|
|
21341
|
+
var STRICT_KEY = "dhl.marketPicker.tradableOnly";
|
|
21342
|
+
function readStrict() {
|
|
21343
|
+
try {
|
|
21344
|
+
return localStorage.getItem(STRICT_KEY) !== "0";
|
|
21345
|
+
} catch {
|
|
21346
|
+
return true;
|
|
21347
|
+
}
|
|
21348
|
+
}
|
|
21305
21349
|
function readFavorites() {
|
|
21306
21350
|
try {
|
|
21307
21351
|
const value = JSON.parse(localStorage.getItem(FAV_KEY) || "[]");
|
|
@@ -21326,7 +21370,14 @@ function MarketPicker({ activeCoin, onClose }) {
|
|
|
21326
21370
|
const s = useStore();
|
|
21327
21371
|
const [query, setQuery] = React21.useState("");
|
|
21328
21372
|
const [category, setCategory] = React21.useState("all");
|
|
21329
|
-
const [strict,
|
|
21373
|
+
const [strict, setStrictState] = React21.useState(readStrict);
|
|
21374
|
+
const setStrict = (value) => {
|
|
21375
|
+
setStrictState(value);
|
|
21376
|
+
try {
|
|
21377
|
+
localStorage.setItem(STRICT_KEY, value ? "1" : "0");
|
|
21378
|
+
} catch {
|
|
21379
|
+
}
|
|
21380
|
+
};
|
|
21330
21381
|
const [sort, setSort] = React21.useState("volume");
|
|
21331
21382
|
const [descending, setDescending] = React21.useState(true);
|
|
21332
21383
|
const [favorites, setFavorites] = React21.useState(readFavorites);
|
|
@@ -21463,16 +21514,13 @@ function MarketPicker({ activeCoin, onClose }) {
|
|
|
21463
21514
|
}
|
|
21464
21515
|
};
|
|
21465
21516
|
const header = (label, key3) => /* @__PURE__ */ React21.createElement("button", { type: "button", onClick: () => sortBy(key3), "aria-label": tr(`\u6309${label}\u6392\u5E8F`, `Sort by ${label}`) }, label, sort === key3 ? /* @__PURE__ */ React21.createElement(UiIcon, { name: descending ? "arrowDown" : "arrowUp", size: 12 }) : null);
|
|
21466
|
-
return /* @__PURE__ */ React21.createElement(import_dsh_client_ui_primitives3.Modal, { open: true, onClose, title: t("market.choose", "\u9009\u62E9\u5E02\u573A"), headless: true, className: "dhl-picker-modal", backdropBlur: true, onKeyDownCapture: onKeyDown }, /* @__PURE__ */ React21.createElement("section", { className: "dhl-picker" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-top" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-search" }, /* @__PURE__ */ React21.createElement(SearchIcon, null), /* @__PURE__ */ React21.createElement("input", { ref: searchRef, "data-modal-autofocus": true, value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("market.search", "\u641C\u7D22\u5E02\u573A"), "aria-label": t("market.search", "\u641C\u7D22\u5E02\u573A") }), /* @__PURE__ */ React21.createElement("kbd", null, "/")), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-mode", role: "group", "aria-label": tr("\u5E02\u573A\u8303\u56F4", "Market scope") }, /* @__PURE__ */ React21.createElement("button", { type: "button", className: strict ? "on" : "", "aria-pressed": strict, onClick: () => setStrict(true) }, t("market.tradable", "\u53EF\u4EA4\u6613")), /* @__PURE__ */ React21.createElement("button", { type: "button", className: !strict ? "on" : "", "aria-pressed": !strict, onClick: () => setStrict(false) }, t("market.all", "\u5168\u90E8"))), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-picker-close", onClick: onClose, "aria-label": t("modal.close", "\u5173\u95ED") }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "close", size: 18 }))), !strict ? /* @__PURE__ */ React21.createElement("p", { className: "dhl-picker-readonly-note" }, tr("\u201C\u53EA\u8BFB\u201D\u8868\u793A\u672C\u63D2\u4EF6\u4EC5\u63D0\u4F9B\u884C\u60C5\uFF0C\u5C1A\u672A\u63A5\u5165\u8BE5\u5E02\u573A\u7684\u4E0B\u5355\u4E0E\u98CE\u63A7\uFF1B\u73B0\u8D27\u4E0E Outcome \u5728 Hyperliquid \u672C\u8EAB\u53EF\u4EA4\u6613\uFF1B\u4EE5 USDC \u4E3A\u4FDD\u8BC1\u91D1\u7684 HIP-3 \u6C38\u7EED\u5DF2\u53EF\u5728\u8FD9\u91CC\u4EA4\u6613\u3002", "\u201CView only\u201D means this plugin has market data but no order and risk support for that market. Hyperliquid itself supports spot and outcome trading; USDC-collateral HIP-3 perps are tradable here.")) : null, /* @__PURE__ */ React21.createElement("nav", { className: "dhl-picker-tabs", "aria-label": tr("\u5E02\u573A\u5206\u7C7B", "Market categories") }, CATEGORIES.map(([key3, label]) => /* @__PURE__ */ React21.createElement("button", { type: "button", key: key3, className: category === key3 ? "on" : "", "aria-current": category === key3 ? "page" : void 0, onClick: () => {
|
|
21467
|
-
setCategory(key3);
|
|
21468
|
-
if (["spot", "hip3", "outcome", "crypto", "tradfi", "preipo"].includes(key3)) setStrict(false);
|
|
21469
|
-
} }, t(`market.category.${key3}`, label)))), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-tablehead" }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "star", size: 15 }), header(t("market.name", "\u5E02\u573A"), "name"), header(t("market.price", "\u6700\u65B0\u4EF7"), "price"), header(t("market.change", "24h \u6DA8\u8DCC"), "change"), header(t("market.funding", "\u8D44\u91D1\u8D39"), "funding"), header(t("market.volume", "24h \u6210\u4EA4\u989D"), "volume"), header(t("market.openInterest", "\u672A\u5E73\u4ED3\u91CF"), "openInterest")), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-list", role: "listbox", "aria-label": tr("\u5E02\u573A\u5217\u8868", "Market list"), ref: listRef, onScroll: (event) => {
|
|
21517
|
+
return /* @__PURE__ */ React21.createElement(import_dsh_client_ui_primitives3.Modal, { open: true, onClose, title: t("market.choose", "\u9009\u62E9\u5E02\u573A"), headless: true, className: "dhl-picker-modal", backdropBlur: true, onKeyDownCapture: onKeyDown }, /* @__PURE__ */ React21.createElement("section", { className: "dhl-picker" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-top" }, /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-search" }, /* @__PURE__ */ React21.createElement(SearchIcon, null), /* @__PURE__ */ React21.createElement("input", { ref: searchRef, "data-modal-autofocus": true, value: query, onChange: (event) => setQuery(event.target.value), placeholder: t("market.search", "\u641C\u7D22\u5E02\u573A"), "aria-label": t("market.search", "\u641C\u7D22\u5E02\u573A") }), /* @__PURE__ */ React21.createElement("kbd", null, "/")), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-mode", role: "group", "aria-label": tr("\u5E02\u573A\u8303\u56F4", "Market scope") }, /* @__PURE__ */ React21.createElement("button", { type: "button", className: strict ? "on" : "", "aria-pressed": strict, onClick: () => setStrict(true) }, t("market.tradable", "\u53EF\u4EA4\u6613")), /* @__PURE__ */ React21.createElement("button", { type: "button", className: !strict ? "on" : "", "aria-pressed": !strict, onClick: () => setStrict(false) }, t("market.all", "\u5168\u90E8"))), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-picker-close", onClick: onClose, "aria-label": t("modal.close", "\u5173\u95ED") }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "close", size: 18 }))), !strict ? /* @__PURE__ */ React21.createElement("p", { className: "dhl-picker-readonly-note" }, tr("\u201C\u53EA\u8BFB\u201D\u8868\u793A\u672C\u63D2\u4EF6\u4EC5\u63D0\u4F9B\u884C\u60C5\uFF0C\u5C1A\u672A\u63A5\u5165\u8BE5\u5E02\u573A\u7684\u4E0B\u5355\u4E0E\u98CE\u63A7\uFF1B\u73B0\u8D27\u4E0E Outcome \u5728 Hyperliquid \u672C\u8EAB\u53EF\u4EA4\u6613\uFF1B\u4EE5 USDC \u4E3A\u4FDD\u8BC1\u91D1\u7684 HIP-3 \u6C38\u7EED\u5DF2\u53EF\u5728\u8FD9\u91CC\u4EA4\u6613\u3002", "\u201CView only\u201D means this plugin has market data but no order and risk support for that market. Hyperliquid itself supports spot and outcome trading; USDC-collateral HIP-3 perps are tradable here.")) : null, /* @__PURE__ */ React21.createElement("nav", { className: "dhl-picker-tabs", "aria-label": tr("\u5E02\u573A\u5206\u7C7B", "Market categories") }, CATEGORIES.map(([key3, label]) => /* @__PURE__ */ React21.createElement("button", { type: "button", key: key3, className: category === key3 ? "on" : "", "aria-current": category === key3 ? "page" : void 0, onClick: () => setCategory(key3) }, t(`market.category.${key3}`, label)))), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-tablehead" }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "star", size: 15 }), header(t("market.name", "\u5E02\u573A"), "name"), header(t("market.price", "\u6700\u65B0\u4EF7"), "price"), header(t("market.change", "24h \u6DA8\u8DCC"), "change"), header(t("market.funding", "\u8D44\u91D1\u8D39"), "funding"), header(t("market.volume", "24h \u6210\u4EA4\u989D"), "volume"), header(t("market.openInterest", "\u672A\u5E73\u4ED3\u91CF"), "openInterest")), /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-list", role: "listbox", "aria-label": tr("\u5E02\u573A\u5217\u8868", "Market list"), ref: listRef, onScroll: (event) => {
|
|
21470
21518
|
const element = event.currentTarget;
|
|
21471
21519
|
if (element.scrollTop + element.clientHeight >= element.scrollHeight - 200) setRenderCount((count) => Math.min(marketRows.length, count + 150));
|
|
21472
21520
|
} }, marketRows.slice(0, renderCount).map((row, index) => {
|
|
21473
21521
|
const change = changeOf(row);
|
|
21474
21522
|
return /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-row" + (row.name === activeCoin ? " active" : "") + (cursor === index ? " cursor" : ""), role: "option", "aria-selected": row.name === activeCoin, key: `${row.kind}:${row.name}`, "data-market-index": index, onMouseEnter: () => setCursor(index) }, /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-picker-star" + (favorites.includes(row.name) ? " on" : ""), "aria-label": `${favorites.includes(row.name) ? tr("\u53D6\u6D88\u81EA\u9009", "Remove favorite") : tr("\u52A0\u5165\u81EA\u9009", "Add favorite")} ${row.label}`, onClick: () => toggleFavorite(row.name) }, /* @__PURE__ */ React21.createElement(UiIcon, { name: "star", size: 17, filled: favorites.includes(row.name) })), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-picker-market", onClick: () => choose(row), title: `${row.label} \xB7 ${row.name} \xB7 ${row.venue}${row.category ? ` \xB7 ${row.category}` : ""}` }, /* @__PURE__ */ React21.createElement(MarketIcon, { market: row, small: true }), /* @__PURE__ */ React21.createElement("strong", null, row.label), /* @__PURE__ */ React21.createElement("small", null, row.kind === "perp" ? "PERP" : row.kind === "hip3" ? "HIP-3" : row.kind === "outcome" ? "OUTCOME" : "SPOT", row.name.startsWith("@") ? ` ${row.name}` : "", row.category ? ` \xB7 ${row.category}` : "", row.tradable ? "" : ` \xB7 ${tr("\u53EA\u8BFB", "View only")}`)), /* @__PURE__ */ React21.createElement("span", null, n(row.ctx?.markPx) > 0 ? fmtPrice(row.ctx.markPx) : "\u2014"), /* @__PURE__ */ React21.createElement("span", { className: change > 0 ? "dhl-up" : change < 0 ? "dhl-down" : "" }, n(row.ctx?.prevDayPx) > 0 ? `${change >= 0 ? "+" : ""}${change.toFixed(2)}%` : "\u2014"), /* @__PURE__ */ React21.createElement("span", null, row.ctx?.funding != null ? `${(n(row.ctx.funding) * 100).toFixed(4)}%` : "\u2014"), /* @__PURE__ */ React21.createElement("span", null, row.ctx?.dayNtlVlm != null ? "$" + fmt(row.ctx.dayNtlVlm, 0) : "\u2014"), /* @__PURE__ */ React21.createElement("span", null, row.ctx?.openInterest != null ? "$" + fmt(interestOf(row), 0) : "\u2014"));
|
|
21475
|
-
}), !loading
|
|
21523
|
+
}), marketRows.length === 0 && (!loading || s.marketCatalog.length > 0) ? /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-empty" }, category === "outcome" && s.status?.network === "mainnet" ? t("market.outcomeTestnet", "Outcome \u76EE\u5F55\u76EE\u524D\u4EC5\u5728 testnet \u63D0\u4F9B\u3002") : strict ? /* @__PURE__ */ React21.createElement(React21.Fragment, null, tr("\u8FD9\u4E2A\u5206\u7C7B\u91CC\u6CA1\u6709\u53EF\u4EA4\u6613\u7684\u5E02\u573A\uFF08\u73B0\u8D27\u3001Outcome \u7B49\u76EE\u524D\u53EA\u8BFB\uFF09\u3002", "No tradable markets in this category (spot, outcome and some others are view only)."), /* @__PURE__ */ React21.createElement("button", { type: "button", className: "dhl-picker-empty-action", onClick: () => setStrict(false) }, tr("\u67E5\u770B\u5168\u90E8", "Show all"))) : t("market.empty", "\u6CA1\u6709\u5339\u914D\u7684\u5E02\u573A")) : null, loading && !s.marketCatalog.length ? /* @__PURE__ */ React21.createElement("div", { className: "dhl-picker-empty" }, t("market.loading", "\u6B63\u5728\u8BFB\u53D6\u5E02\u573A\u76EE\u5F55\u2026")) : null), /* @__PURE__ */ React21.createElement("footer", { className: "dhl-picker-foot" }, /* @__PURE__ */ React21.createElement("span", null, t("market.shortcuts", "\u2191\u2193 \u9009\u62E9\u3000Enter \u6253\u5F00\u3000\u2318/Ctrl+S \u81EA\u9009\u3000Esc \u5173\u95ED")), /* @__PURE__ */ React21.createElement("span", null, marketRows.length, " ", t("market.count", "\u4E2A\u5E02\u573A"), " \xB7 Hyperliquid \xB7 ", s.status?.network ?? "Hyperliquid"))));
|
|
21476
21524
|
}
|
|
21477
21525
|
|
|
21478
21526
|
// src/client/components/strategy-menu.tsx
|
|
@@ -22056,6 +22104,46 @@ function DeskColumn({ onCollapse }) {
|
|
|
22056
22104
|
const [confirmClear, setConfirmClear] = React26.useState(false);
|
|
22057
22105
|
const [pickerOpen, setPickerOpen] = React26.useState(false);
|
|
22058
22106
|
const [chartExpanded, setChartExpanded] = React26.useState(false);
|
|
22107
|
+
const [fullscreen, setFullscreen] = React26.useState(false);
|
|
22108
|
+
const keyboard = () => navigator.keyboard;
|
|
22109
|
+
const fullscreenRequest = React26.useRef(0);
|
|
22110
|
+
const enterExpanded = React26.useCallback(() => {
|
|
22111
|
+
setChartExpanded(true);
|
|
22112
|
+
const root = document.documentElement;
|
|
22113
|
+
if (!document.fullscreenElement && root.requestFullscreen) {
|
|
22114
|
+
const request = ++fullscreenRequest.current;
|
|
22115
|
+
void root.requestFullscreen({ navigationUI: "hide" }).then(async () => {
|
|
22116
|
+
if (request !== fullscreenRequest.current) {
|
|
22117
|
+
await document.exitFullscreen().catch(() => {
|
|
22118
|
+
});
|
|
22119
|
+
return;
|
|
22120
|
+
}
|
|
22121
|
+
await keyboard()?.lock?.(["Escape"]);
|
|
22122
|
+
if (request !== fullscreenRequest.current) keyboard()?.unlock?.();
|
|
22123
|
+
}).catch(() => {
|
|
22124
|
+
});
|
|
22125
|
+
}
|
|
22126
|
+
}, []);
|
|
22127
|
+
const leaveExpanded = React26.useCallback(() => {
|
|
22128
|
+
fullscreenRequest.current += 1;
|
|
22129
|
+
setChartExpanded(false);
|
|
22130
|
+
keyboard()?.unlock?.();
|
|
22131
|
+
if (document.fullscreenElement) void document.exitFullscreen().catch(() => {
|
|
22132
|
+
});
|
|
22133
|
+
}, []);
|
|
22134
|
+
React26.useEffect(() => {
|
|
22135
|
+
const onChange = () => {
|
|
22136
|
+
const on = Boolean(document.fullscreenElement);
|
|
22137
|
+
setFullscreen(on);
|
|
22138
|
+
if (!on) {
|
|
22139
|
+
fullscreenRequest.current += 1;
|
|
22140
|
+
keyboard()?.unlock?.();
|
|
22141
|
+
setChartExpanded(false);
|
|
22142
|
+
}
|
|
22143
|
+
};
|
|
22144
|
+
document.addEventListener("fullscreenchange", onChange);
|
|
22145
|
+
return () => document.removeEventListener("fullscreenchange", onChange);
|
|
22146
|
+
}, []);
|
|
22059
22147
|
const [chartResetToken, setChartResetToken] = React26.useState(0);
|
|
22060
22148
|
const sessionMaLines = s.workspaceSessionId === s.activeSessionId ? s.workspace?.maLines : void 0;
|
|
22061
22149
|
const maLines = React26.useMemo(() => sessionMaLines ?? [], [sessionMaLines]);
|
|
@@ -22067,8 +22155,8 @@ function DeskColumn({ onCollapse }) {
|
|
|
22067
22155
|
};
|
|
22068
22156
|
React26.useEffect(() => {
|
|
22069
22157
|
setDrawMode(null);
|
|
22070
|
-
|
|
22071
|
-
}, [s.activeSessionId]);
|
|
22158
|
+
leaveExpanded();
|
|
22159
|
+
}, [s.activeSessionId, leaveExpanded]);
|
|
22072
22160
|
const marks = s.chartMarksKey === `${s.activeSessionId ?? ""}:${coin}` ? s.chartMarks : [];
|
|
22073
22161
|
const layers = { orders: true, fills: true, drawings: true, reasons: false, ...s.workspace?.chartLayers };
|
|
22074
22162
|
const tradeEvents = React26.useMemo(
|
|
@@ -22174,12 +22262,12 @@ function DeskColumn({ onCollapse }) {
|
|
|
22174
22262
|
const onEscape = (event) => {
|
|
22175
22263
|
if (event.key === "Escape" && !pickerOpen && !event.defaultPrevented) {
|
|
22176
22264
|
event.preventDefault();
|
|
22177
|
-
|
|
22265
|
+
leaveExpanded();
|
|
22178
22266
|
}
|
|
22179
22267
|
};
|
|
22180
22268
|
window.addEventListener("keydown", onEscape);
|
|
22181
22269
|
return () => window.removeEventListener("keydown", onEscape);
|
|
22182
|
-
}, [chartExpanded, pickerOpen]);
|
|
22270
|
+
}, [chartExpanded, pickerOpen, leaveExpanded]);
|
|
22183
22271
|
React26.useEffect(() => {
|
|
22184
22272
|
const keys = { KeyT: "trend", KeyH: "level", KeyJ: "hray", KeyV: "vline", KeyC: "crossline", KeyF: "fib" };
|
|
22185
22273
|
const onKey = (event) => {
|
|
@@ -22273,7 +22361,7 @@ function DeskColumn({ onCollapse }) {
|
|
|
22273
22361
|
}
|
|
22274
22362
|
}
|
|
22275
22363
|
)
|
|
22276
|
-
), pickerOpen ? /* @__PURE__ */ React26.createElement(MarketPicker, { activeCoin: coin, onClose: () => setPickerOpen(false) }) : null, /* @__PURE__ */ React26.createElement("div", { className: "dhl-market-workspace" + (s.tapePlacement === "below" ? " dhl-tape-below" : "") }, /* @__PURE__ */ React26.createElement("section", { className: "dhl-chart-panel" + (chartExpanded ? " dhl-chart-panel-expanded" : ""), "aria-label": tr("\u884C\u60C5\u56FE\u8868", "Market chart") }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tabs", role: "tablist", "aria-label": tr("\u56FE\u8868\u5185\u5BB9", "Chart content") }, /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "chart", className: chartTab === "chart" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "chart" }) }, tr("K \u7EBF", "Chart")), row?.kind !== "spot" && row?.kind !== "outcome" ? /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "funding", className: chartTab === "funding" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "funding" }) }, tr("\u8D44\u91D1\u8D39\u5386\u53F2", "Funding history")) : null), chartTab === "chart" ? /* @__PURE__ */ React26.createElement("div", { role: "tabpanel" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tools" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-intervals", "aria-label": tr("K \u7EBF\u5468\u671F", "Chart interval") }, INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("button", { key: iv, type: "button", className: s.interval === iv ? "on" : "", onClick: () => changeInterval(iv) }, iv)), /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u66F4\u591A K \u7EBF\u5468\u671F", "More chart intervals"), value: MORE_INTERVALS.includes(s.interval) ? s.interval : "", onChange: (event) => changeInterval(event.target.value) }, /* @__PURE__ */ React26.createElement("option", { value: "", disabled: true }, tr("\u66F4\u591A", "More")), MORE_INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("option", { key: iv, value: iv }, iv)))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-actions" }, /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u56FE\u8868\u7C7B\u578B", "Chart type"), value: chartType, onChange: (event) => saveSessionWorkspace({ chartType: event.target.value }) }, [["candles", "\u8721\u70DB\u56FE", "Candles"], ["bars", "\u7F8E\u56FD\u7EBF", "OHLC bars"], ["line", "\u6298\u7EBF\u56FE", "Line"], ["area", "\u9762\u79EF\u56FE", "Area"], ["heikinAshi", "\u5E73\u5747K\u7EBF", "Heikin Ashi"]].map(([value, zh, en]) => /* @__PURE__ */ React26.createElement("option", { key: value, value }, tr(zh, en)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-layer-menu" }, /* @__PURE__ */ React26.createElement("summary", null, tr("\u663E\u793A", "Show"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-layer-popover" }, [["orders", tr("\u59D4\u6258", "Orders")], ["fills", tr("\u6210\u4EA4", "Fills")], ["reasons", tr("\u6210\u4EA4\u539F\u56E0", "Fill reasons")], ["drawings", tr("\u753B\u7EBF", "Drawings")]].map(([id, label]) => /* @__PURE__ */ React26.createElement("label", { key: id }, /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: layers[id], onChange: (event) => saveSessionWorkspace({ chartLayers: { ...layers, [id]: event.target.checked } }) }), label)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-indicator-menu" }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u9009\u62E9\u56FE\u8868\u6307\u6807", "Choose chart indicators") }, tr("\u6307\u6807", "Indicators"), " ", /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-count" }, indicators.size + maLines.length)), /* @__PURE__ */ React26.createElement("div", { className: "dhl-indicator-popover", role: "group", "aria-label": tr("\u56FE\u8868\u6307\u6807", "Chart indicators") }, /* @__PURE__ */ React26.createElement("strong", null, tr("\u56FE\u8868\u6307\u6807", "Chart indicators")), INDICATOR_CHOICES.map(([id, label, description]) => /* @__PURE__ */ React26.createElement("button", { type: "button", key: id, className: indicators.has(id) ? "on" : "", "aria-pressed": indicators.has(id), onClick: () => toggleIndicator(id) }, /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-check", "aria-hidden": "true" }, indicators.has(id) ? /* @__PURE__ */ React26.createElement(UiIcon, { name: "check", size: 13 }) : null), /* @__PURE__ */ React26.createElement("span", null, /* @__PURE__ */ React26.createElement("b", null, id === "rsi14" ? `RSI ${studySettings.rsi.period}` : id === "bollinger20" ? `BOLL ${studySettings.bollinger.period} \xD7 ${studySettings.bollinger.multiplier}` : tr(label, INDICATOR_EN[id]?.[0] ?? label)), /* @__PURE__ */ React26.createElement("small", null, id === "macd" ? `${studySettings.macd.fast} / ${studySettings.macd.slow} / ${studySettings.macd.signal}` : id === "bollinger20" ? tr("\u5747\u7EBF\u4E0E\u6807\u51C6\u5DEE\u901A\u9053", "Mean and standard deviation bands") : tr(description, INDICATOR_EN[id]?.[1] ?? description))))), /* @__PURE__ */ React26.createElement(IndicatorEditor, { key: s.activeSessionId, settings: s.workspace?.indicatorSettings, indicators: [...indicators], maLines, onApply: saveSessionWorkspace }), /* @__PURE__ */ React26.createElement(MaEditor, { lines: maLines, onChange: (next) => saveSessionWorkspace({ maLines: next }) }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: s.workspace?.showDataWindow ? "on" : "", "aria-pressed": s.workspace?.showDataWindow ?? false, onClick: () => saveSessionWorkspace({ showDataWindow: !s.workspace?.showDataWindow }) }, tr("\u6570\u636E\u7A97\u53E3", "Data window")), /* @__PURE__ */ React26.createElement(StrategyMenu, { candles: s.candles, coin, interval: s.interval }), /* @__PURE__ */ React26.createElement("details", { className: "dhl-draw-menu" + (drawMode ? " active" : ""), ref: drawMenu, onToggle: (event) => fitPopover(event.currentTarget) }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, drawMode ? /* @__PURE__ */ React26.createElement(React26.Fragment, null, /* @__PURE__ */ React26.createElement(ToolIcon, { kind: drawMode, size: 14 }), tr(toolOf(drawMode).zh, toolOf(drawMode).en)) : tr("\u753B\u7EBF", "Draw"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-popover", role: "menu", "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, /* @__PURE__ */ React26.createElement(
|
|
22364
|
+
), pickerOpen ? /* @__PURE__ */ React26.createElement(MarketPicker, { activeCoin: coin, onClose: () => setPickerOpen(false) }) : null, /* @__PURE__ */ React26.createElement("div", { className: "dhl-market-workspace" + (s.tapePlacement === "below" ? " dhl-tape-below" : "") }, /* @__PURE__ */ React26.createElement("section", { className: "dhl-chart-panel" + (chartExpanded ? " dhl-chart-panel-expanded" : "") + (chartExpanded && fullscreen ? " is-fullscreen" : ""), "aria-label": tr("\u884C\u60C5\u56FE\u8868", "Market chart") }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tabs", role: "tablist", "aria-label": tr("\u56FE\u8868\u5185\u5BB9", "Chart content") }, /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "chart", className: chartTab === "chart" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "chart" }) }, tr("K \u7EBF", "Chart")), row?.kind !== "spot" && row?.kind !== "outcome" ? /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "funding", className: chartTab === "funding" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "funding" }) }, tr("\u8D44\u91D1\u8D39\u5386\u53F2", "Funding history")) : null), chartTab === "chart" ? /* @__PURE__ */ React26.createElement("div", { role: "tabpanel" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tools" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-intervals", "aria-label": tr("K \u7EBF\u5468\u671F", "Chart interval") }, INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("button", { key: iv, type: "button", className: s.interval === iv ? "on" : "", onClick: () => changeInterval(iv) }, iv)), /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u66F4\u591A K \u7EBF\u5468\u671F", "More chart intervals"), value: MORE_INTERVALS.includes(s.interval) ? s.interval : "", onChange: (event) => changeInterval(event.target.value) }, /* @__PURE__ */ React26.createElement("option", { value: "", disabled: true }, tr("\u66F4\u591A", "More")), MORE_INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("option", { key: iv, value: iv }, iv)))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-actions" }, /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u56FE\u8868\u7C7B\u578B", "Chart type"), value: chartType, onChange: (event) => saveSessionWorkspace({ chartType: event.target.value }) }, [["candles", "\u8721\u70DB\u56FE", "Candles"], ["bars", "\u7F8E\u56FD\u7EBF", "OHLC bars"], ["line", "\u6298\u7EBF\u56FE", "Line"], ["area", "\u9762\u79EF\u56FE", "Area"], ["heikinAshi", "\u5E73\u5747K\u7EBF", "Heikin Ashi"]].map(([value, zh, en]) => /* @__PURE__ */ React26.createElement("option", { key: value, value }, tr(zh, en)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-layer-menu" }, /* @__PURE__ */ React26.createElement("summary", null, tr("\u663E\u793A", "Show"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-layer-popover" }, [["orders", tr("\u59D4\u6258", "Orders")], ["fills", tr("\u6210\u4EA4", "Fills")], ["reasons", tr("\u6210\u4EA4\u539F\u56E0", "Fill reasons")], ["drawings", tr("\u753B\u7EBF", "Drawings")]].map(([id, label]) => /* @__PURE__ */ React26.createElement("label", { key: id }, /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: layers[id], onChange: (event) => saveSessionWorkspace({ chartLayers: { ...layers, [id]: event.target.checked } }) }), label)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-indicator-menu" }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u9009\u62E9\u56FE\u8868\u6307\u6807", "Choose chart indicators") }, tr("\u6307\u6807", "Indicators"), " ", /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-count" }, indicators.size + maLines.length)), /* @__PURE__ */ React26.createElement("div", { className: "dhl-indicator-popover", role: "group", "aria-label": tr("\u56FE\u8868\u6307\u6807", "Chart indicators") }, /* @__PURE__ */ React26.createElement("strong", null, tr("\u56FE\u8868\u6307\u6807", "Chart indicators")), INDICATOR_CHOICES.map(([id, label, description]) => /* @__PURE__ */ React26.createElement("button", { type: "button", key: id, className: indicators.has(id) ? "on" : "", "aria-pressed": indicators.has(id), onClick: () => toggleIndicator(id) }, /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-check", "aria-hidden": "true" }, indicators.has(id) ? /* @__PURE__ */ React26.createElement(UiIcon, { name: "check", size: 13 }) : null), /* @__PURE__ */ React26.createElement("span", null, /* @__PURE__ */ React26.createElement("b", null, id === "rsi14" ? `RSI ${studySettings.rsi.period}` : id === "bollinger20" ? `BOLL ${studySettings.bollinger.period} \xD7 ${studySettings.bollinger.multiplier}` : tr(label, INDICATOR_EN[id]?.[0] ?? label)), /* @__PURE__ */ React26.createElement("small", null, id === "macd" ? `${studySettings.macd.fast} / ${studySettings.macd.slow} / ${studySettings.macd.signal}` : id === "bollinger20" ? tr("\u5747\u7EBF\u4E0E\u6807\u51C6\u5DEE\u901A\u9053", "Mean and standard deviation bands") : tr(description, INDICATOR_EN[id]?.[1] ?? description))))), /* @__PURE__ */ React26.createElement(IndicatorEditor, { key: s.activeSessionId, settings: s.workspace?.indicatorSettings, indicators: [...indicators], maLines, onApply: saveSessionWorkspace }), /* @__PURE__ */ React26.createElement(MaEditor, { lines: maLines, onChange: (next) => saveSessionWorkspace({ maLines: next }) }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: s.workspace?.showDataWindow ? "on" : "", "aria-pressed": s.workspace?.showDataWindow ?? false, onClick: () => saveSessionWorkspace({ showDataWindow: !s.workspace?.showDataWindow }) }, tr("\u6570\u636E\u7A97\u53E3", "Data window")), /* @__PURE__ */ React26.createElement(StrategyMenu, { candles: s.candles, coin, interval: s.interval }), /* @__PURE__ */ React26.createElement("details", { className: "dhl-draw-menu" + (drawMode ? " active" : ""), ref: drawMenu, onToggle: (event) => fitPopover(event.currentTarget) }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, drawMode ? /* @__PURE__ */ React26.createElement(React26.Fragment, null, /* @__PURE__ */ React26.createElement(ToolIcon, { kind: drawMode, size: 14 }), tr(toolOf(drawMode).zh, toolOf(drawMode).en)) : tr("\u753B\u7EBF", "Draw"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-popover", role: "menu", "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, /* @__PURE__ */ React26.createElement(
|
|
22277
22365
|
"input",
|
|
22278
22366
|
{
|
|
22279
22367
|
className: "dhl-draw-search",
|
|
@@ -22339,7 +22427,7 @@ function DeskColumn({ onCollapse }) {
|
|
|
22339
22427
|
onBlur: () => setConfirmClear(false)
|
|
22340
22428
|
},
|
|
22341
22429
|
confirmClear ? tr(`\u786E\u8BA4\u5220\u9664 ${marks.filter((item) => !item.locked).length} \u6761\u753B\u7EBF`, `Delete ${marks.filter((item) => !item.locked).length} drawings`) : tr("\u5220\u9664\u6240\u6709\u753B\u7EBF", "Remove all drawings")
|
|
22342
|
-
)))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", disabled: !history.canUndo, "aria-label": tr("\u64A4\u9500", "Undo"), title: tr("\u64A4\u9500\u753B\u7EBF\u64CD\u4F5C\uFF08Ctrl/Cmd+Z\uFF09", "Undo drawing change (Ctrl/Cmd+Z)"), onClick: () => void history.undo() }, /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M9 14 4 9l5-5" }), /* @__PURE__ */ React26.createElement("path", { d: "M4 9h10a6 6 0 0 1 0 12h-3" }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", disabled: !history.canRedo, "aria-label": tr("\u91CD\u505A", "Redo"), title: tr("\u91CD\u505A\uFF08Shift+Ctrl/Cmd+Z\uFF09", "Redo (Shift+Ctrl/Cmd+Z)"), onClick: () => void history.redo() }, /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "m15 14 5-5-5-5" }), /* @__PURE__ */ React26.createElement("path", { d: "M20 9H10a6 6 0 0 0 0 12h3" }))), /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u4EF7\u683C\u523B\u5EA6", "Price scale"), value: s.workspace?.priceScale ?? "normal", onChange: (event) => saveSessionWorkspace({ priceScale: event.target.value }) }, /* @__PURE__ */ React26.createElement("option", { value: "normal" }, tr("\u7EBF\u6027", "Linear")), /* @__PURE__ */ React26.createElement("option", { value: "log" }, tr("\u5BF9\u6570", "Log")), /* @__PURE__ */ React26.createElement("option", { value: "percentage" }, "%")), /* @__PURE__ */ React26.createElement("span", { className: "dhl-chart-action-separator", "aria-hidden": "true" }), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", "aria-label": tr("\u590D\u4F4D\u56FE\u8868\u89C6\u56FE", "Reset chart view"), title: tr("\u590D\u4F4D\u56FE\u8868\u89C6\u56FE", "Reset chart view"), onClick: () => setChartResetToken((value) => value + 1) }, /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M3 11a9 9 0 1 0 2.6-6.4M3 4v6h6" }), /* @__PURE__ */ React26.createElement("path", { d: "M12 7v5l3 2" }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", "aria-label": chartExpanded ? tr("\u9000\u51FA\
|
|
22430
|
+
)))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", disabled: !history.canUndo, "aria-label": tr("\u64A4\u9500", "Undo"), title: tr("\u64A4\u9500\u753B\u7EBF\u64CD\u4F5C\uFF08Ctrl/Cmd+Z\uFF09", "Undo drawing change (Ctrl/Cmd+Z)"), onClick: () => void history.undo() }, /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M9 14 4 9l5-5" }), /* @__PURE__ */ React26.createElement("path", { d: "M4 9h10a6 6 0 0 1 0 12h-3" }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", disabled: !history.canRedo, "aria-label": tr("\u91CD\u505A", "Redo"), title: tr("\u91CD\u505A\uFF08Shift+Ctrl/Cmd+Z\uFF09", "Redo (Shift+Ctrl/Cmd+Z)"), onClick: () => void history.redo() }, /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "m15 14 5-5-5-5" }), /* @__PURE__ */ React26.createElement("path", { d: "M20 9H10a6 6 0 0 0 0 12h3" }))), /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u4EF7\u683C\u523B\u5EA6", "Price scale"), value: s.workspace?.priceScale ?? "normal", onChange: (event) => saveSessionWorkspace({ priceScale: event.target.value }) }, /* @__PURE__ */ React26.createElement("option", { value: "normal" }, tr("\u7EBF\u6027", "Linear")), /* @__PURE__ */ React26.createElement("option", { value: "log" }, tr("\u5BF9\u6570", "Log")), /* @__PURE__ */ React26.createElement("option", { value: "percentage" }, "%")), /* @__PURE__ */ React26.createElement("span", { className: "dhl-chart-action-separator", "aria-hidden": "true" }), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", "aria-label": tr("\u590D\u4F4D\u56FE\u8868\u89C6\u56FE", "Reset chart view"), title: tr("\u590D\u4F4D\u56FE\u8868\u89C6\u56FE", "Reset chart view"), onClick: () => setChartResetToken((value) => value + 1) }, /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M3 11a9 9 0 1 0 2.6-6.4M3 4v6h6" }), /* @__PURE__ */ React26.createElement("path", { d: "M12 7v5l3 2" }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: "dhl-chart-iconbtn", "aria-label": chartExpanded ? tr("\u9000\u51FA\u5168\u5C4F\u56FE\u8868", "Exit fullscreen chart") : tr("\u5168\u5C4F\u56FE\u8868", "Fullscreen chart"), "aria-pressed": chartExpanded, title: chartExpanded ? tr("\u9000\u51FA\u5168\u5C4F (Esc)", "Exit fullscreen (Esc)") : tr("\u5168\u5C4F\u56FE\u8868", "Fullscreen chart"), onClick: () => chartExpanded ? leaveExpanded() : enterExpanded() }, chartExpanded ? /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M9 3v6H3m18 6h-6v6M3 9h6V3m12 12h-6v6" })) : /* @__PURE__ */ React26.createElement("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React26.createElement("path", { d: "M9 3H3v6m12-6h6v6M3 15v6h6m12-6v6h-6" }))))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-favorite-tools", role: "toolbar", "aria-label": tr("\u5E38\u7528\u753B\u7EBF", "Favorite drawing tools") }, /* @__PURE__ */ React26.createElement("button", { type: "button", "aria-label": tr("\u5149\u6807\uFF08\u53D6\u6D88\u7ED8\u5236\uFF09", "Cursor (cancel drawing)"), title: tr("\u5149\u6807 \xB7 Esc \u53D6\u6D88\u7ED8\u5236", "Cursor \xB7 Esc cancels drawing"), "aria-pressed": !drawMode, className: !drawMode ? "on" : "", onClick: () => setDrawMode(null) }, /* @__PURE__ */ React26.createElement(UiIcon, { name: "cursor", size: 16 })), /* @__PURE__ */ React26.createElement("span", { className: "dhl-chart-action-separator", "aria-hidden": "true" }), favorites.map((kind) => /* @__PURE__ */ React26.createElement("button", { key: kind, type: "button", title: tr(toolOf(kind).zh, toolOf(kind).en), "aria-label": `${tr("\u5E38\u7528", "Favorite")} ${tr(toolOf(kind).zh, toolOf(kind).en)}`, "aria-pressed": drawMode === kind, className: drawMode === kind ? "on" : "", onClick: () => setDrawMode(drawMode === kind ? null : kind) }, /* @__PURE__ */ React26.createElement(ToolIcon, { kind, size: 16 }))), /* @__PURE__ */ React26.createElement("small", null, tr("\u5728\u753B\u7EBF\u83DC\u5355\u4E2D\u6536\u85CF\u5E38\u7528\u5DE5\u5177", "Star tools in the Draw menu"))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-research-controls" }, /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u591A\u5468\u671F\u5E03\u5C40", "Research layout"), value: research.layout, onChange: (e) => saveSessionWorkspace({ research: { ...research, layout: e.target.value } }) }, /* @__PURE__ */ React26.createElement("option", { value: "single" }, tr("\u5355\u56FE", "Single chart")), /* @__PURE__ */ React26.createElement("option", { value: "dual" }, tr("\u53CC\u56FE\u7814\u7A76", "Two charts")), /* @__PURE__ */ React26.createElement("option", { value: "quad" }, tr("\u56DB\u56FE\u7814\u7A76", "Four charts"))), /* @__PURE__ */ React26.createElement("label", null, /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: research.syncTime, onChange: (e) => {
|
|
22343
22431
|
setRangeEvent(null);
|
|
22344
22432
|
saveSessionWorkspace({ research: { ...research, syncTime: e.target.checked } });
|
|
22345
22433
|
} }), tr("\u65F6\u95F4\u8054\u52A8", "Sync time")), /* @__PURE__ */ React26.createElement("label", null, /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: research.syncCursor, onChange: (e) => {
|
|
@@ -22950,10 +23038,28 @@ function WalletDetails({ open, onClose }) {
|
|
|
22950
23038
|
// src/client/components/trading-limits.tsx
|
|
22951
23039
|
var React31 = __toESM(require("react"), 1);
|
|
22952
23040
|
var import_dsh_client_ui_primitives9 = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
22953
|
-
var EMPTY = { allowedCoins: "", maxOrderUsd: "", maxDailyUsd: "", maxPositionUsd: "", maxLeverage: "" };
|
|
23041
|
+
var EMPTY = { coinsMode: "manual", allowedCoins: "", maxOrderUsd: "", maxDailyUsd: "", maxPositionUsd: "", maxLeverage: "" };
|
|
23042
|
+
var MANUAL_KEY = "dhl.allowedCoins.manual";
|
|
23043
|
+
function readManualCoins() {
|
|
23044
|
+
try {
|
|
23045
|
+
return localStorage.getItem(MANUAL_KEY) ?? "";
|
|
23046
|
+
} catch {
|
|
23047
|
+
return "";
|
|
23048
|
+
}
|
|
23049
|
+
}
|
|
23050
|
+
function rememberManualCoins(value) {
|
|
23051
|
+
try {
|
|
23052
|
+
localStorage.setItem(MANUAL_KEY, value);
|
|
23053
|
+
} catch {
|
|
23054
|
+
}
|
|
23055
|
+
}
|
|
22954
23056
|
function fromLimits(limits) {
|
|
23057
|
+
const coins = limits?.allowedCoins ?? [];
|
|
23058
|
+
const all = coins.includes("*");
|
|
22955
23059
|
return {
|
|
22956
|
-
|
|
23060
|
+
coinsMode: all ? "all" : "manual",
|
|
23061
|
+
// Switching from 全部 back to 手动设置 starts from the last manual list this desk saw (or an empty field).
|
|
23062
|
+
allowedCoins: all ? readManualCoins() : coins.join(", "),
|
|
22957
23063
|
maxOrderUsd: String(limits?.maxOrderUsd ?? ""),
|
|
22958
23064
|
maxDailyUsd: String(limits?.maxDailyUsd ?? ""),
|
|
22959
23065
|
maxPositionUsd: String(limits?.maxPositionUsd ?? ""),
|
|
@@ -22983,8 +23089,9 @@ function TradingLimits({ open, onClose }) {
|
|
|
22983
23089
|
setBusy(true);
|
|
22984
23090
|
setError("");
|
|
22985
23091
|
try {
|
|
23092
|
+
if (draft.allowedCoins.trim()) rememberManualCoins(draft.allowedCoins);
|
|
22986
23093
|
await hyperCall("updateRiskSettings", { limits: {
|
|
22987
|
-
allowedCoins: draft.allowedCoins,
|
|
23094
|
+
allowedCoins: draft.coinsMode === "all" ? "*" : draft.allowedCoins,
|
|
22988
23095
|
maxOrderUsd: Number(draft.maxOrderUsd),
|
|
22989
23096
|
maxDailyUsd: Number(draft.maxDailyUsd),
|
|
22990
23097
|
maxPositionUsd: Number(draft.maxPositionUsd),
|
|
@@ -23052,7 +23159,21 @@ function TradingLimits({ open, onClose }) {
|
|
|
23052
23159
|
}
|
|
23053
23160
|
},
|
|
23054
23161
|
tr(zh, en)
|
|
23055
|
-
))), /* @__PURE__ */ React31.createElement("div", { className: "dhl-limits-scroll" }, tab === "limits" ? /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section", role: "tabpanel", id: "dhl-limits-panel-limits", "aria-labelledby": "dhl-limits-tab-limits" }, /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-ledger-row" }, /* @__PURE__ */ React31.createElement("span", { className: "dhl-limits-ledger" }, paper ? tr("\u6A21\u62DF\u76D8\u8D26\u672C", "Paper ledger") : tr("\u5B9E\u76D8\u8D26\u672C", "Live ledger")), limits ? /* @__PURE__ */ React31.createElement("span", null, tr("\u4ECA\u65E5\u5DF2\u7528\uFF08UTC\uFF09", "Used today (UTC)"), " ", fmt(limits.usedTodayUsd ?? 0), " / ", fmt(limits.maxDailyUsd), " USD") : null), limits ? /* @__PURE__ */ React31.createElement("form", { id: "dhl-risk-form", className: "dhl-risk-form", onSubmit: (event) => void save(event) },
|
|
23162
|
+
))), /* @__PURE__ */ React31.createElement("div", { className: "dhl-limits-scroll" }, tab === "limits" ? /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section", role: "tabpanel", id: "dhl-limits-panel-limits", "aria-labelledby": "dhl-limits-tab-limits" }, /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-ledger-row" }, /* @__PURE__ */ React31.createElement("span", { className: "dhl-limits-ledger" }, paper ? tr("\u6A21\u62DF\u76D8\u8D26\u672C", "Paper ledger") : tr("\u5B9E\u76D8\u8D26\u672C", "Live ledger")), limits ? /* @__PURE__ */ React31.createElement("span", null, tr("\u4ECA\u65E5\u5DF2\u7528\uFF08UTC\uFF09", "Used today (UTC)"), " ", fmt(limits.usedTodayUsd ?? 0), " / ", fmt(limits.maxDailyUsd), " USD") : null), limits ? /* @__PURE__ */ React31.createElement("form", { id: "dhl-risk-form", className: "dhl-risk-form", onSubmit: (event) => void save(event) }, /* @__PURE__ */ React31.createElement("div", { className: "dhl-risk-wide dhl-coins-mode" }, /* @__PURE__ */ React31.createElement("span", { id: "dhl-coins-mode-label" }, tr("\u5141\u8BB8\u5F00\u4ED3\u5E01\u5BF9", "Coins allowed to open")), /* @__PURE__ */ React31.createElement("span", { className: "dhl-coins-seg", role: "radiogroup", "aria-labelledby": "dhl-coins-mode-label" }, [["all", "\u5168\u90E8", "All markets"], ["manual", "\u624B\u52A8\u8BBE\u7F6E", "Choose coins"]].map(([mode, zh, en]) => /* @__PURE__ */ React31.createElement(
|
|
23163
|
+
"button",
|
|
23164
|
+
{
|
|
23165
|
+
key: mode,
|
|
23166
|
+
type: "button",
|
|
23167
|
+
role: "radio",
|
|
23168
|
+
"aria-checked": draft.coinsMode === mode,
|
|
23169
|
+
className: draft.coinsMode === mode ? "on" : "",
|
|
23170
|
+
onClick: () => setDraft((current2) => {
|
|
23171
|
+
if (mode === "all" && current2.allowedCoins.trim()) rememberManualCoins(current2.allowedCoins);
|
|
23172
|
+
return { ...current2, coinsMode: mode };
|
|
23173
|
+
})
|
|
23174
|
+
},
|
|
23175
|
+
tr(zh, en)
|
|
23176
|
+
))), /* @__PURE__ */ React31.createElement("small", null, draft.coinsMode === "all" ? tr("\u53EF\u4EE5\u5728\u6240\u6709\u53EF\u4EA4\u6613\u5E02\u573A\u5F00\u4ED3\uFF08\u6838\u5FC3\u6C38\u7EED\u4E0E\u4EE5 USDC \u4E3A\u4FDD\u8BC1\u91D1\u7684 HIP-3\uFF09\uFF1B\u5355\u7B14\u3001\u6BCF\u65E5\u3001\u4ED3\u4F4D\u548C\u6760\u6746\u4E0A\u9650\u7167\u5E38\u751F\u6548\u3002", "Opening is allowed on every tradable market; the order, daily, position and leverage limits still apply.") : tr("\u53EA\u80FD\u5728\u4E0B\u9762\u5217\u51FA\u7684\u5E01\u5BF9\u5F00\u4ED3\uFF1B\u51CF\u4ED3\u548C\u5E73\u4ED3\u4E0D\u53D7\u9650\u3002", "Only the listed coins can be opened; reducing and closing are never blocked."))), draft.coinsMode === "manual" ? field("allowedCoins", tr("\u5E01\u5BF9\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u5982 BTC, xyz:XYZ100\uFF09", "Coins (comma separated, e.g. BTC, xyz:XYZ100)"), { placeholder: "BTC, ETH, xyz:XYZ100", maxLength: 1e3 }, true) : null, field("maxOrderUsd", tr("\u5355\u7B14\u4E0A\u9650 \xB7 USD", "Per order \xB7 USD"), { type: "number", min: "0.01", step: "any" }), field("maxDailyUsd", tr("\u6BCF\u65E5\u5F00\u4ED3\u989D\u5EA6 \xB7 USD", "Daily budget \xB7 USD"), { type: "number", min: "0.01", step: "any" }), field("maxPositionUsd", tr("\u5355\u5E01\u4ED3\u4F4D\u4E0A\u9650 \xB7 USD", "Per coin position \xB7 USD"), { type: "number", min: "0.01", step: "any" }), field("maxLeverage", tr("\u6700\u5927\u6760\u6746 \xB7 \u500D", "Max leverage \xB7 x"), { type: "number", min: "1", max: "50", step: "1" })) : /* @__PURE__ */ React31.createElement("div", { className: "dhl-empty" }, tr("\u6B63\u5728\u8BFB\u53D6\u2026", "Loading\u2026")), /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr(
|
|
23056
23177
|
"\u7ACB\u5373\u4F5C\u7528\u4E8E\u6A21\u62DF\u76D8\u548C\u5B9E\u76D8\uFF0C\u91CD\u542F\u540E\u4FDD\u7559\uFF1B\u4E24\u79CD\u6A21\u5F0F\u7684\u6BCF\u65E5\u989D\u5EA6\u5206\u5F00\u8BB0\u8D26\uFF0C\u51CF\u4ED3\u548C\u5E73\u4ED3\u4E0D\u53D7\u9650\u3002",
|
|
23057
23178
|
"Applies to paper and live at once and survives restart; each mode has its own daily budget; reducing and closing are never blocked."
|
|
23058
23179
|
))) : null, tab === "costs" ? /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section", role: "tabpanel", id: "dhl-limits-panel-costs", "aria-labelledby": "dhl-limits-tab-costs" }, /* @__PURE__ */ React31.createElement("div", { className: "dhl-risk-form" }, ["feeBps", "slippageBps"].map((key3) => /* @__PURE__ */ React31.createElement("label", { key: key3 }, /* @__PURE__ */ React31.createElement("span", null, key3 === "feeBps" ? tr("\u624B\u7EED\u8D39 \xB7 bps", "Fee \xB7 bps") : tr("\u4E0D\u5229\u6ED1\u70B9 \xB7 bps", "Adverse slippage \xB7 bps")), /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Input, { type: "number", "aria-label": key3 === "feeBps" ? tr("\u6A21\u62DF\u624B\u7EED\u8D39 bps", "Paper fee bps") : tr("\u6A21\u62DF\u6ED1\u70B9 bps", "Paper slippage bps"), min: "0", max: "100", step: "any", value: costs[key3], onChange: (e) => setCosts((old) => ({ ...old, [key3]: e.target.value })) })))), /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr("1 bps = 0.01%\u3002\u4EC5\u5F71\u54CD\u4E4B\u540E\u7684\u6A21\u62DF\u6210\u4EA4\uFF1B\u4E0D\u6A21\u62DF\u76D8\u53E3\u6DF1\u5EA6\u3001\u8D44\u91D1\u8D39\u548C\u5F3A\u5E73\u3002", "1 bps = 0.01%. Future paper fills only; no depth, funding or liquidation simulation."))) : null, tab === "signing" ? /* @__PURE__ */ React31.createElement("section", { className: "dhl-limits-section", role: "tabpanel", id: "dhl-limits-panel-signing", "aria-labelledby": "dhl-limits-tab-signing" }, /* @__PURE__ */ React31.createElement("div", { className: "dhl-risk-form dhl-wallet-form" }, /* @__PURE__ */ React31.createElement("label", null, /* @__PURE__ */ React31.createElement("span", null, tr("\u7B7E\u540D\u65B9\u5F0F", "Signer")), /* @__PURE__ */ React31.createElement(Select, { "aria-label": tr("\u7B7E\u540D\u65B9\u5F0F", "Signer"), value: wallet.signer, onChange: (event) => setWallet((old) => ({ ...old, signer: event.target.value })) }, /* @__PURE__ */ React31.createElement("option", { value: "web3" }, tr("dsh-web3 \u94B1\u5305", "dsh-web3 wallet")), /* @__PURE__ */ React31.createElement("option", { value: "local" }, tr("\u63D2\u4EF6\u914D\u7F6E\u7684\u79C1\u94A5", "Plugin private key")))), wallet.signer === "web3" ? /* @__PURE__ */ React31.createElement("label", null, /* @__PURE__ */ React31.createElement("span", null, tr("\u94B1\u5305\u8D26\u6237", "Wallet account")), /* @__PURE__ */ React31.createElement(Select, { "aria-label": tr("\u94B1\u5305\u8D26\u6237", "Wallet account"), value: wallet.web3Account, onChange: (event) => setWallet((old) => ({ ...old, web3Account: event.target.value })) }, web3Accounts.accounts.some((row) => row.id === wallet.web3Account || row.label === wallet.web3Account) ? null : /* @__PURE__ */ React31.createElement("option", { value: wallet.web3Account }, wallet.web3Account), web3Accounts.accounts.map((row) => /* @__PURE__ */ React31.createElement("option", { key: row.id, value: row.id }, `${row.label} \xB7 ${row.address.slice(0, 6)}\u2026${row.address.slice(-4)}`)))) : null, /* @__PURE__ */ React31.createElement("label", { className: "dhl-risk-wide" }, /* @__PURE__ */ React31.createElement("span", null, tr("Hyperliquid \u4E3B\u8D26\u6237\u5730\u5740\uFF08\u4EA4\u6613\u4E0E\u67E5\u8BE2\u7528\uFF09", "Hyperliquid main account address")), /* @__PURE__ */ React31.createElement(
|
package/index.js
CHANGED
|
@@ -48,7 +48,7 @@ export const Config = z.object({
|
|
|
48
48
|
web3Account: z.string().description('signer=web3 时使用的 dsh-web3 账户 id 或名称').default('main'),
|
|
49
49
|
tradingAddress: z.string().description('Hyperliquid 主账户地址;使用 dsh-web3 API wallet 签名时必填,账户查询必须用主账户地址').default(''),
|
|
50
50
|
autonomousLive: z.boolean().description('初始托管模式:true=全托管(agent 自主执行实盘、可调整限额与划转保证金),false=半托管;之后以交易台 设置 → 托管模式 为准。仅支持 dsh-web3 签名').default(false),
|
|
51
|
-
allowedCoins: z.string().description('交易限额(paper/live
|
|
51
|
+
allowedCoins: z.string().description('交易限额(paper/live 均生效):允许新开仓的币对,逗号分隔;* 表示全部可交易市场;减仓不受限').default('BTC,ETH'),
|
|
52
52
|
maxOrderUsd: z.number().description('交易限额(paper/live 均生效):单笔名义价值上限 USD').default(100),
|
|
53
53
|
maxDailyUsd: z.number().description('交易限额(paper/live 均生效):每 UTC 日累计开仓名义价值上限 USD,paper 与 live 分账').default(500),
|
|
54
54
|
maxPositionUsd: z.number().description('交易限额(paper/live 均生效):每币最大仓位名义价值 USD').default(500),
|
package/lib/remote.js
CHANGED
|
@@ -15,7 +15,7 @@ import { addressFromPrivateKey, buildL1TypedData, buildSendAsset, buildUsdClassT
|
|
|
15
15
|
import { TradingGuard } from './trading-guard.js'
|
|
16
16
|
import { normalizeMaLines } from './moving-average.js'
|
|
17
17
|
import { ALERT_KINDS, ICON_NAMES, TEXT_KINDS, drawingGeometry, drawingStyle, isIconLabel, linePriceAt } from './chart-drawings.js'
|
|
18
|
-
import { normalizeRiskSettings } from './risk-settings.js'
|
|
18
|
+
import { normalizeRiskSettings, coinAllowed, ALL_COINS } from './risk-settings.js'
|
|
19
19
|
import { INDICATOR_IDS, normalizeIndicatorSettings } from './studies.js'
|
|
20
20
|
import { normalizeResearch } from './workspace-settings.js'
|
|
21
21
|
import { fundingWindow } from './review.js'
|
|
@@ -890,7 +890,11 @@ export class HyperRemote extends TypertRemoteService {
|
|
|
890
890
|
const current = { allowedCoins: [...this.cfg.allowedCoins], maxOrderUsd: this.cfg.maxOrderUsd, maxDailyUsd: this.cfg.maxDailyUsd, maxPositionUsd: this.cfg.maxPositionUsd, maxLeverage: this.cfg.maxLeverage }
|
|
891
891
|
const add = (request?.addCoins ?? []).map((coin) => canonicalMarketCoin(coin))
|
|
892
892
|
const drop = new Set((request?.removeCoins ?? []).map((coin) => canonicalMarketCoin(coin)))
|
|
893
|
-
|
|
893
|
+
// '*' = all markets. Removing one coin from "all" needs an explicit list first (the trader's call).
|
|
894
|
+
const allNow = current.allowedCoins.includes(ALL_COINS)
|
|
895
|
+
if (allNow && drop.size && !drop.has(ALL_COINS)) throw new Error('当前允许全部币对;要排除某个币对,请先用 removeCoins ["*"] 并 addCoins 列出要允许的币对')
|
|
896
|
+
const base = allNow && drop.has(ALL_COINS) ? [] : current.allowedCoins.filter((coin) => !drop.has(coin))
|
|
897
|
+
const next = { ...current, allowedCoins: add.includes(ALL_COINS) ? [ALL_COINS] : [...new Set([...base, ...add])] }
|
|
894
898
|
for (const key of ['maxOrderUsd', 'maxDailyUsd', 'maxPositionUsd', 'maxLeverage']) if (request?.[key] != null) next[key] = request[key]
|
|
895
899
|
if (this.activeTradeOps > 0) throw new Error('交易操作仍在进行,暂不能修改限额')
|
|
896
900
|
const limits = normalizeRiskSettings(next)
|
|
@@ -1649,7 +1653,7 @@ export class HyperRemote extends TypertRemoteService {
|
|
|
1649
1653
|
if (this.cfg.tradingMode === 'paper') throw new Error('paper 模式没有挂单')
|
|
1650
1654
|
const exec = request?.[TOOL_CALL]?.exec
|
|
1651
1655
|
if (!exec?.agent) throw new Error('live 撤单仅允许从 DSH agent 工具调用')
|
|
1652
|
-
if (!this.isManaged() || !this.cfg.allowedCoins
|
|
1656
|
+
if (!this.isManaged() || !coinAllowed(this.cfg.allowedCoins, coin)) await this.authorizeToolAction(`Hyperliquid ${this.cfg.network} 撤销 ${coin} 订单 oid=${oid}?`, exec, `Hyperliquid ${this.cfg.network}: cancel ${coin} order oid=${oid}?`)
|
|
1653
1657
|
const { asset: idx } = await this.tradeAsset(coin)
|
|
1654
1658
|
const res = await this.submitAction({ type: 'cancel', cancels: [{ a: idx, o: oid }] }, exec)
|
|
1655
1659
|
assertExchangeAccepted(res)
|
|
@@ -1668,7 +1672,7 @@ export class HyperRemote extends TypertRemoteService {
|
|
|
1668
1672
|
const isCross = request?.marginMode !== 'isolated' && !market.onlyIsolated
|
|
1669
1673
|
if (this.cfg.tradingMode === 'paper') {
|
|
1670
1674
|
if (leverage > this.cfg.maxLeverage) throw new Error(`杠杆超过配置上限 ${this.cfg.maxLeverage}x`)
|
|
1671
|
-
if (!this.cfg.allowedCoins
|
|
1675
|
+
if (!coinAllowed(this.cfg.allowedCoins, coin)) throw new Error(`${coin} 不在允许交易币对内`)
|
|
1672
1676
|
const paper = this.store.paperAccount()
|
|
1673
1677
|
if (paper.positions[coin]) {
|
|
1674
1678
|
const risk = paperRiskState(paper, await safeMids(this, Object.keys(paper.positions)), true)
|
|
@@ -1682,7 +1686,7 @@ export class HyperRemote extends TypertRemoteService {
|
|
|
1682
1686
|
return { mode: 'paper', ok: true }
|
|
1683
1687
|
}
|
|
1684
1688
|
if (leverage > this.cfg.maxLeverage) throw new Error(`杠杆超过配置上限 ${this.cfg.maxLeverage}x`)
|
|
1685
|
-
if (!this.cfg.allowedCoins
|
|
1689
|
+
if (!coinAllowed(this.cfg.allowedCoins, coin)) throw new Error(`交易限额:${coin} 不在允许交易币对内(allowedCoins)`)
|
|
1686
1690
|
const exec = request?.[TOOL_CALL]?.exec
|
|
1687
1691
|
if (!exec?.agent) throw new Error('live 杠杆变更仅允许从 DSH agent 工具调用')
|
|
1688
1692
|
if (!this.isManaged()) await this.authorizeToolAction(`Hyperliquid ${this.cfg.network} 将 ${coin} 杠杆设为 ${leverage}x?`, exec, `Hyperliquid ${this.cfg.network}: set ${coin} leverage to ${leverage}x?`)
|
package/lib/risk-settings.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { canonicalMarketCoin } from './market-symbol.js'
|
|
2
|
+
|
|
3
|
+
/** `allowedCoins: ['*']` allows opening on every tradable market; otherwise only the listed ones. */
|
|
4
|
+
export const ALL_COINS = '*'
|
|
5
|
+
/** Whether opening a position on `coin` is allowed by the allow-list. */
|
|
6
|
+
export function coinAllowed(allowedCoins, coin) {
|
|
7
|
+
return Array.isArray(allowedCoins) && (allowedCoins.includes(ALL_COINS) || allowedCoins.includes(coin))
|
|
8
|
+
}
|
|
2
9
|
/** Validate the user-editable trading limits before they reach either guard. */
|
|
3
10
|
export function normalizeRiskSettings(input) {
|
|
4
11
|
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('交易设置无效')
|
|
@@ -6,8 +13,10 @@ export function normalizeRiskSettings(input) {
|
|
|
6
13
|
if (Object.keys(input).some((key) => !allowed.includes(key)) || allowed.some((key) => !(key in input))) throw new Error('交易设置字段无效')
|
|
7
14
|
const coins = typeof input.allowedCoins === 'string' ? input.allowedCoins.split(',') : input.allowedCoins
|
|
8
15
|
if (!Array.isArray(coins)) throw new Error('允许交易的币对无效')
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
let allowedCoins = [...new Set(coins.map((coin) => canonicalMarketCoin(coin)).filter(Boolean))]
|
|
17
|
+
// "All markets" stands alone: '*' with anything else is just '*'.
|
|
18
|
+
if (allowedCoins.includes(ALL_COINS)) allowedCoins = [ALL_COINS]
|
|
19
|
+
else if (!allowedCoins.length || allowedCoins.length > 100 || allowedCoins.some((coin) => coin.length > 32 || !/^[A-Za-z0-9:_-]+$/.test(coin))) {
|
|
11
20
|
throw new Error('允许交易的币对无效')
|
|
12
21
|
}
|
|
13
22
|
const values = {}
|
package/lib/tools.js
CHANGED
|
@@ -120,7 +120,7 @@ function buildTools(remote) {
|
|
|
120
120
|
? '当前账户数据不代表该只读市场的余额、仓位或可交易额度。'
|
|
121
121
|
: `当前市场仅可分析;${context.positionsCoverage === 'all-dexs' ? '仓位来自全部永续 DEX 快照' : '账户仓位仅覆盖所选 DEX'}${position ? `;当前仓位=${fmt(position.size, 6)} ${coin}` : ''},不能据此在该市场下单。`
|
|
122
122
|
: position ? `仓位 ${position.size > 0 ? 'LONG' : 'SHORT'} ${fmt(Math.abs(position.size), 6)} ${coin} entry=${fmt(position.entryPx)} uPnL=${fmt(position.uPnl)}` : '当前币对无持仓',
|
|
123
|
-
`风险:允许币对=${(context.riskLimits?.allowedCoins ?? []).join(',')} 单笔≤${fmt(context.riskLimits?.maxOrderUsd)} USD 每日≤${fmt(context.riskLimits?.maxDailyUsd)} USD 杠杆≤${fmt(context.riskLimits?.maxLeverage, 0)}x · 托管模式=${context.custody === 'managed' ? '全托管(可自主执行、调整限额与划转)' : '半托管(实盘与划转需用户批准,限额由用户修改)'}${account.mode === 'paper' ? ' · 模拟盘配置的滑点与手续费,未模拟盘口深度、资金费或强平' : ''}`,
|
|
123
|
+
`风险:允许币对=${(context.riskLimits?.allowedCoins ?? []).includes('*') ? '全部' : (context.riskLimits?.allowedCoins ?? []).join(',')} 单笔≤${fmt(context.riskLimits?.maxOrderUsd)} USD 每日≤${fmt(context.riskLimits?.maxDailyUsd)} USD 杠杆≤${fmt(context.riskLimits?.maxLeverage, 0)}x · 托管模式=${context.custody === 'managed' ? '全托管(可自主执行、调整限额与划转)' : '半托管(实盘与划转需用户批准,限额由用户修改)'}${account.mode === 'paper' ? ' · 模拟盘配置的滑点与手续费,未模拟盘口深度、资金费或强平' : ''}`,
|
|
124
124
|
`近 ${candles.length} 根 K 线价格(末根可能未收盘): ${candles.map((c) => fmt(c.c)).join(', ')}`,
|
|
125
125
|
].join('\n')
|
|
126
126
|
return { text, context }
|
|
@@ -355,7 +355,7 @@ function buildTools(remote) {
|
|
|
355
355
|
name: 'hyper_account',
|
|
356
356
|
description: '查询交易账户。action=state 返回权益/持仓;orders 返回挂单;fills 返回成交;orderStatus 用 oid 或 cloid 核对某笔 live 订单,尤其在网络超时后。review 返回最近 7/30/90 天成交复盘、订单数量核对、费用和资金费覆盖缺口;不是完整账户收益率。' +
|
|
357
357
|
'action=analyze 交易体检(默认 90 天):按完整往返统计胜率、盈亏比、期望、持仓时长、按币种/时段/星期的表现、手续费与资金费,并给出按金额排序的习惯问题和建议规则——第一次见面或用户问“我做得怎么样”时用它,不要自己从成交里手算。' +
|
|
358
|
-
'action=limits 修改允许币对(addCoins/removeCoins
|
|
358
|
+
'action=limits 修改允许币对(addCoins/removeCoins;addCoins ["*"] 表示允许全部币对)与单笔/每日/仓位/杠杆上限——仅全托管模式可用,半托管时由用户在 设置 → 交易限额与签名规则 修改。' +
|
|
359
359
|
'action=transfer 在自己的任意两个账户之间划转 USDC:from / to 取 spot(现货)、perp(主永续)或 HIP-3 交易所名(如 xyz),amount 为数量;spot↔perp、perp↔HIP-3 一步完成,spot↔HIP-3 与 HIP-3↔HIP-3 自动经 perp 两步完成(只需批准一次)。下单前永续保证金不足而现货有 USDC 时,先 spot→perp。统一账户 / 组合保证金模式无需 spot↔perp 划转。全托管直接执行,半托管每次需用户批准;仅 live,需交易主钱包签名。旧参数 dex + direction(in/out)仍可用。',
|
|
360
360
|
parameters: {
|
|
361
361
|
type: 'object',
|
package/lib/trading-guard.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { coinAllowed } from './risk-settings.js'
|
|
2
3
|
import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync } from 'node:fs'
|
|
3
4
|
import { join } from 'node:path'
|
|
4
5
|
|
|
@@ -28,7 +29,7 @@ export class TradingGuard {
|
|
|
28
29
|
if (reduceOnly && (positionSize === 0 || Math.sign(signed) === Math.sign(positionSize) || sz > Math.abs(positionSize) + 1e-10)) throw new Error('只减仓订单会增加或翻转仓位')
|
|
29
30
|
const riskReducing = positionSize !== 0 && Math.sign(signed) !== Math.sign(positionSize) &&
|
|
30
31
|
(nextSize === 0 || Math.sign(nextSize) === Math.sign(positionSize)) && Math.abs(nextSize) < Math.abs(positionSize)
|
|
31
|
-
if (!riskReducing && !l.allowedCoins
|
|
32
|
+
if (!riskReducing && !coinAllowed(l.allowedCoins, coin)) throw new Error(`交易限额:${coin} 不在允许交易币对内(allowedCoins)`)
|
|
32
33
|
if (!riskReducing && leverage > l.maxLeverage) throw new Error(`交易限额:杠杆 ${leverage}x 超过上限 ${l.maxLeverage}x(maxLeverage)`)
|
|
33
34
|
const notionalUsd = sz * price
|
|
34
35
|
if (!riskReducing && accountValue <= 0) throw new Error('账户权益不足,拒绝开仓')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-hyperliquid",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Hyperliquid trading desk for the DeepSeek Harness (dsh) - market workspace, native agent conversation and guarded live / paper trading over the Hyperliquid API.",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"type": "module",
|
|
@@ -33,11 +33,12 @@ description: 作为用户的交易副手,在 DSH 交易会话中用 Hyperliqui
|
|
|
33
33
|
| | 半托管(默认) | 全托管 |
|
|
34
34
|
|---|---|---|
|
|
35
35
|
| 实盘下单、撤单、改杠杆 | 每笔都需要用户批准 | 在限额内直接执行 |
|
|
36
|
-
| 允许币对与单笔/每日/仓位/杠杆上限 | 只能由用户在 **设置 → 交易限额与签名规则** 修改 | 你可以用 `hyper_account action=limits` 调整(`addCoins` / `removeCoins` / `maxOrderUsd` / `maxDailyUsd` / `maxPositionUsd` / `maxLeverage
|
|
36
|
+
| 允许币对与单笔/每日/仓位/杠杆上限 | 只能由用户在 **设置 → 交易限额与签名规则** 修改 | 你可以用 `hyper_account action=limits` 调整(`addCoins` / `removeCoins` / `maxOrderUsd` / `maxDailyUsd` / `maxPositionUsd` / `maxLeverage`;`addCoins: ["*"]` = 允许全部币对) |
|
|
37
37
|
| USDC 划转(现货、主永续、各 HIP-3 交易所之间任意互转) | `hyper_account action=transfer`,每次需要用户批准 | 直接执行 |
|
|
38
38
|
|
|
39
39
|
两种模式下 dsh-web3 钱包自己的签名策略都仍然生效:钱包拒签就停止,不要绕过。
|
|
40
40
|
|
|
41
|
+
- 允许币对为 `*`(交易台里的「全部」)时,所有可交易市场都能开仓,单笔、每日、仓位、杠杆上限照常生效。
|
|
41
42
|
- **半托管**:预检因币对不在白名单被拒时,直接告诉用户:“请在 设置 → 交易限额与签名规则 → 允许开仓币对 里加上 `xyz:XYZ100`;也可以在 设置 → 托管模式 切换到全托管,由我来调整。”给出准确的合约名。不要只回一句“我做不到”。
|
|
42
43
|
- **全托管**:放宽限额是为了执行交易员的计划,不是为了绕开风控。每次调整都在回复里写清改了什么、为什么。不要为了做一笔不符合策略的交易去放宽限额;不要把单笔或仓位上限放到超出档案里的风险偏好。拿不准时,先按现有限额执行,再建议调整。
|
|
43
44
|
- 不要拆单来绕过任何限额。
|