dsh-hyperliquid 0.3.0 → 0.4.0
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 +25 -0
- package/README.md +7 -2
- package/client.js +401 -153
- package/index.js +43 -20
- package/lib/analysis.js +183 -0
- package/lib/chart-drawings.d.ts +2 -1
- package/lib/chart-drawings.js +9 -0
- package/lib/hl.js +6 -0
- package/lib/journal.js +155 -0
- package/lib/prompt.js +92 -0
- package/lib/remote.js +308 -49
- package/lib/risk-settings.js +2 -1
- package/lib/scan.js +91 -0
- package/lib/signing.js +58 -0
- package/lib/skill.js +2 -2
- package/lib/store.js +31 -1
- package/lib/tools.js +134 -23
- package/lib/turn-origin.js +46 -0
- package/lib/wake.js +85 -0
- package/package.json +3 -2
- package/skills/hyperliquid-trading/SKILL.md +119 -45
package/client.js
CHANGED
|
@@ -148,7 +148,7 @@ var styles_default = `/**
|
|
|
148
148
|
.dhl-chat-heading span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-secondary); font-size: 10.5px; }
|
|
149
149
|
.dhl-chat-ticket-toggle { flex: none; border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary); padding: 5px 9px; font: inherit; font-size: 11px; cursor: pointer; }
|
|
150
150
|
.dhl-chat-ticket-toggle:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
|
|
151
|
-
.dhl-chat-prompts { flex: none; display: flex; gap: 6px; padding: 8px 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
151
|
+
.dhl-chat-prompts { flex: none; display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 12px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
152
152
|
.dhl-chat-prompts button { border: 1px solid var(--dsw-alias-border-l1); border-radius: 999px; background: transparent; color: var(--dsw-alias-label-secondary); padding: 4px 9px; font: inherit; font-size: 10.5px; cursor: pointer; }
|
|
153
153
|
.dhl-chat-prompts button:hover { color: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
|
|
154
154
|
.dhl-chat-unavailable { margin: 18px; padding: 13px 14px; border: 1px solid var(--dsw-alias-state-warn-primary); border-radius: 8px; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 1.6; }
|
|
@@ -471,7 +471,7 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
471
471
|
.dhl-chart-tools { min-height: 42px; }
|
|
472
472
|
.dhl-chartbox { height: 410px; }
|
|
473
473
|
/* Above Lightweight Charts' own canvases (z-index 1\u20132, later in the DOM) so drawings receive the pointer. */
|
|
474
|
-
.dhl-chart-drawing-overlay { position: absolute; inset: 0; z-index: 4; width: 100%; height: 100%; overflow: hidden; pointer-events: none; color: #4c82d8; }
|
|
474
|
+
.dhl-chart-drawing-overlay { position: absolute; inset: 0; z-index: 4; width: 100%; height: 100%; overflow: hidden; pointer-events: none; color: #4c82d8; contain: layout paint; transform: translateZ(0); }
|
|
475
475
|
.dhl-chart-drawing-overlay .role-user { color: #4c82d8; }
|
|
476
476
|
.dhl-chart-drawing-overlay .role-support { color: var(--dsw-alias-state-success-primary); }
|
|
477
477
|
.dhl-chart-drawing-overlay .role-resistance { color: var(--dsw-alias-state-error-primary); }
|
|
@@ -707,7 +707,7 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
707
707
|
/* Direct manipulation: a transparent, wide hit layer on strokes (empty chart space still pans). */
|
|
708
708
|
.dhl-drawing-hit * { stroke: transparent !important; fill: transparent !important; stroke-dasharray: none !important; stroke-width: 12px !important; pointer-events: stroke; cursor: move; }
|
|
709
709
|
.dhl-drawing-hit .dhl-text-box, .dhl-drawing-hit .dhl-position rect, .dhl-drawing-hit text { pointer-events: all; }
|
|
710
|
-
.dhl-chart-drawing-overlay.drawing .dhl-drawing-hit { display: none; }
|
|
710
|
+
.dhl-chart-drawing-overlay.drawing .dhl-drawing-hit, .dhl-canvas-gesture .dhl-drawing-hit { display: none; }
|
|
711
711
|
.dhl-drawing.locked .dhl-drawing-hit * { cursor: default; }
|
|
712
712
|
.dhl-drawing:has(> .dhl-drawing-hit:hover) > line, .dhl-drawing:has(> .dhl-drawing-hit:hover) > g > line, .dhl-drawing.selected > line, .dhl-drawing.selected > g > line { stroke-width: calc(var(--dhl-line-width, 1.8) + 1); }
|
|
713
713
|
.dhl-drawing.selected { filter: drop-shadow(0 0 3px color-mix(in srgb, currentColor 55%, transparent)); }
|
|
@@ -908,7 +908,7 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
908
908
|
|
|
909
909
|
/* Fixed range volume profile: up/down volume per price row, value area brighter, POC in the alert red. */
|
|
910
910
|
.dhl-vprofile-range { fill: color-mix(in srgb, currentColor 5%, transparent); stroke: none; }
|
|
911
|
-
.dhl-vprofile
|
|
911
|
+
.dhl-vprofile .dhl-vprofile-up, .dhl-vprofile .dhl-vprofile-down { stroke: none; }
|
|
912
912
|
.dhl-vprofile-up { fill: color-mix(in srgb, #4c82d8 62%, transparent); }
|
|
913
913
|
.dhl-vprofile-down { fill: color-mix(in srgb, #e2a943 62%, transparent); }
|
|
914
914
|
.dhl-vprofile-out .dhl-vprofile-up { fill: color-mix(in srgb, #4c82d8 26%, transparent); }
|
|
@@ -919,6 +919,33 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
919
919
|
/* Magnet feedback: a ring on the O/H/L/C value the anchor will snap to. */
|
|
920
920
|
.dhl-snap-ring { fill: none; stroke: var(--dsw-alias-brand-primary, #4c82d8); stroke-width: 1.5; pointer-events: none; }
|
|
921
921
|
.dhl-draw-toolbar .dhl-select > select { height: 26px; font-size: 11px; }
|
|
922
|
+
.dhl-draw-toolbar .dhl-fib-levels { width: 190px; }
|
|
923
|
+
|
|
924
|
+
.dhl-badge.managed { color: var(--dsw-alias-brand-primary, #4c82d8); border-color: color-mix(in srgb, var(--dsw-alias-brand-primary, #4c82d8) 40%, transparent); background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4c82d8) 10%, transparent); }
|
|
925
|
+
.dhl-managed-list { margin: 6px 0 10px; padding-left: 20px; line-height: 1.7; }
|
|
926
|
+
|
|
927
|
+
/* Trading limits dialog: tabs keep it short; when the viewport is still too small the card caps at the
|
|
928
|
+
overlay's padding box (DSH Modal contract: max-height 100%) and only the body scrolls \u2014 header and
|
|
929
|
+
footer (the save button) stay in view. */
|
|
930
|
+
.dhl-limits-modal { max-height: 100%; }
|
|
931
|
+
.dhl-limits-content { flex: 1 1 auto; min-height: 0; }
|
|
932
|
+
.dhl-limits-content > :has(> .dhl-limits-scroll) { flex: 1 1 auto; min-height: 0; overflow: hidden; margin-top: 12px; }
|
|
933
|
+
.dhl-limits-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: 0 -24px; padding: 14px 24px 2px; scrollbar-gutter: stable; }
|
|
934
|
+
.dhl-limits-tabs { flex: none; display: flex; gap: 4px; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
935
|
+
.dhl-limits-tabs button { position: relative; height: 34px; padding: 0 12px; border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font: inherit; font-size: 13px; cursor: pointer; }
|
|
936
|
+
.dhl-limits-tabs button:hover { color: var(--dsw-alias-label-primary); }
|
|
937
|
+
.dhl-limits-tabs button.on { color: var(--dsw-alias-label-primary); font-weight: 600; }
|
|
938
|
+
.dhl-limits-tabs button.on::after { content: ''; position: absolute; left: 10px; right: 10px; bottom: -1px; height: 2px; border-radius: 1px; background: var(--dsw-alias-label-primary); }
|
|
939
|
+
.dhl-limits-tabs button:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; border-radius: 6px; }
|
|
940
|
+
.dhl-limits-section + .dhl-limits-section { margin-top: 0; padding-top: 0; border-top: 0; }
|
|
941
|
+
.dhl-limits-ledger-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 0 0 12px; color: var(--dsw-alias-label-secondary); font-size: 12px; }
|
|
942
|
+
.dhl-limits-scroll .dhl-limits-note { margin-top: 10px; font-size: 12px; line-height: 18px; }
|
|
943
|
+
.dhl-limits-facts { display: grid; grid-template-columns: auto 1fr; gap: 10px 14px; margin: 0; font-size: 13px; line-height: 20px; }
|
|
944
|
+
.dhl-limits-facts dt { color: var(--dsw-alias-label-secondary); white-space: nowrap; }
|
|
945
|
+
.dhl-limits-facts dd { margin: 0; color: var(--dsw-alias-label-primary); }
|
|
946
|
+
.dhl-limits-hint { display: block; margin-top: 2px; color: var(--dsw-alias-label-secondary); font-size: 12px; }
|
|
947
|
+
.dhl-limits-scroll .dhl-risk-error { margin: 10px 0 0; }
|
|
948
|
+
.dhl-wallet-form ~ .dhl-limits-facts { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
922
949
|
`;
|
|
923
950
|
|
|
924
951
|
// src/client/locale.ts
|
|
@@ -1297,8 +1324,15 @@ function t(key3, fallback) {
|
|
|
1297
1324
|
return localeT(key3, fallback);
|
|
1298
1325
|
}
|
|
1299
1326
|
function tr(zh, en) {
|
|
1300
|
-
|
|
1327
|
+
const now = Date.now();
|
|
1328
|
+
if (now - localeReadAt > 250) {
|
|
1329
|
+
localeReadAt = now;
|
|
1330
|
+
english = ctxRef?.get?.("locale")?.getLocale?.()?.active === "en";
|
|
1331
|
+
}
|
|
1332
|
+
return english ? en : zh;
|
|
1301
1333
|
}
|
|
1334
|
+
var localeReadAt = -Infinity;
|
|
1335
|
+
var english = false;
|
|
1302
1336
|
async function hyperCall(method, request = null) {
|
|
1303
1337
|
if (!conn) throw new Error(t("error.noConnection", "\u65E0\u8FDE\u63A5"));
|
|
1304
1338
|
if (typeof conn.rpc?.call !== "function") throw new Error(t("error.callFailed", "\u8FDC\u7AEF\u8C03\u7528\u5931\u8D25") + `: hyper.${method}`);
|
|
@@ -1647,23 +1681,23 @@ function selectChartCandle(candles, time, active, hit, maLines = [], settings) {
|
|
|
1647
1681
|
const priorVolumeMean20 = previous.length === 20 ? previous.reduce((sum, value) => sum + value, 0) / 20 : null;
|
|
1648
1682
|
return { candle, hit, values, priorVolumeMean20, settings };
|
|
1649
1683
|
}
|
|
1650
|
-
function formatChartReference(selection, coin, interval,
|
|
1684
|
+
function formatChartReference(selection, coin, interval, english2 = false) {
|
|
1651
1685
|
const { candle, hit, values, priorVolumeMean20 } = selection;
|
|
1652
1686
|
const number = (value) => String(value);
|
|
1653
1687
|
const computed = (value) => String(Number(value.toPrecision(10)));
|
|
1654
1688
|
const time = new Date(candle.t).toISOString().replace("T", " ").replace(".000Z", " UTC");
|
|
1655
1689
|
const hitLabel = {
|
|
1656
|
-
volume:
|
|
1690
|
+
volume: english2 ? "Volume" : "\u6210\u4EA4\u91CF",
|
|
1657
1691
|
sma20: "SMA20",
|
|
1658
1692
|
sma60: "MA60",
|
|
1659
1693
|
ema20: "EMA20",
|
|
1660
1694
|
ema50: "EMA50",
|
|
1661
|
-
bollingerUpper:
|
|
1662
|
-
bollingerLower:
|
|
1695
|
+
bollingerUpper: english2 ? "BOLL upper" : "BOLL\u4E0A\u8F68",
|
|
1696
|
+
bollingerLower: english2 ? "BOLL lower" : "BOLL\u4E0B\u8F68",
|
|
1663
1697
|
rsi14: `RSI${selection.settings?.rsi.period ?? 14}`,
|
|
1664
1698
|
macd: "MACD",
|
|
1665
|
-
macdSignal:
|
|
1666
|
-
macdHistogram:
|
|
1699
|
+
macdSignal: english2 ? "MACD signal" : "\u4FE1\u53F7\u7EBF",
|
|
1700
|
+
macdHistogram: english2 ? "MACD histogram" : "MACD\u67F1",
|
|
1667
1701
|
atr14: "ATR14",
|
|
1668
1702
|
vwap: "VWAP (UTC HLC3)",
|
|
1669
1703
|
stochastic: "Stochastic",
|
|
@@ -1671,17 +1705,17 @@ function formatChartReference(selection, coin, interval, english = false) {
|
|
|
1671
1705
|
obv: "OBV"
|
|
1672
1706
|
};
|
|
1673
1707
|
const lines = [
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1708
|
+
english2 ? `[Chart point] ${coin} ${interval} candle opened ${time}` : `\u3010\u56FE\u8868\u70B9\u4F4D\u3011${coin} ${interval} K \u7EBF\u5F00\u76D8 ${time}`,
|
|
1709
|
+
english2 ? `OHLC: ${number(candle.o)} / ${number(candle.h)} / ${number(candle.l)} / ${number(candle.c)}` : `\u5F00/\u9AD8/\u4F4E/\u6536\uFF1A${number(candle.o)} / ${number(candle.h)} / ${number(candle.l)} / ${number(candle.c)}`,
|
|
1710
|
+
english2 ? `Volume: ${number(candle.v)}` : `\u6210\u4EA4\u91CF\uFF1A${number(candle.v)}`
|
|
1677
1711
|
];
|
|
1678
1712
|
if (priorVolumeMean20 != null && priorVolumeMean20 > 0) {
|
|
1679
|
-
lines.push(
|
|
1713
|
+
lines.push(english2 ? `Previous 20-candle mean volume: ${computed(priorVolumeMean20)}; current/mean: ${(candle.v / priorVolumeMean20).toFixed(2)}x` : `\u524D 20 \u6839\u5747\u91CF\uFF1A${computed(priorVolumeMean20)}\uFF1B\u5F53\u524D/\u5747\u91CF\uFF1A${(candle.v / priorVolumeMean20).toFixed(2)} \u500D`);
|
|
1680
1714
|
}
|
|
1681
|
-
if (selection.settings) lines.push(`${
|
|
1682
|
-
if (values.some((row) => row.label === "OBV")) lines.push(
|
|
1683
|
-
if (values.length) lines.push(`${
|
|
1684
|
-
if (hit && hit !== "volume") lines.push(`${
|
|
1715
|
+
if (selection.settings) lines.push(`${english2 ? "Study parameters" : "\u6307\u6807\u53C2\u6570"}\uFF1A${JSON.stringify(selection.settings)}`);
|
|
1716
|
+
if (values.some((row) => row.label === "OBV")) lines.push(english2 ? "OBV starts at zero on the first loaded candle; its absolute value depends on the window." : "OBV \u4EE5\u5DF2\u52A0\u8F7D\u9996\u6839\u4E3A\u96F6\u70B9\uFF0C\u7EDD\u5BF9\u503C\u53D6\u51B3\u4E8E\u5386\u53F2\u7A97\u53E3\u3002");
|
|
1717
|
+
if (values.length) lines.push(`${english2 ? "Displayed studies" : "\u5DF2\u663E\u793A\u6307\u6807"}\uFF1A${values.map(({ label, value }) => `${label}=${computed(value)}`).join("\uFF0C")}`);
|
|
1718
|
+
if (hit && hit !== "volume") lines.push(`${english2 ? "Selected study" : "\u70B9\u51FB\u6307\u6807"}\uFF1A${hitLabel[hit] ?? hit}`);
|
|
1685
1719
|
return lines.join("\n");
|
|
1686
1720
|
}
|
|
1687
1721
|
var CHART_REFERENCE_SOURCE = "hyperliquid-chart";
|
|
@@ -1851,6 +1885,11 @@ function resetModeView(mode) {
|
|
|
1851
1885
|
});
|
|
1852
1886
|
void Promise.allSettled([loadStatus(), loadAccount(), loadTradeHistory(true)]);
|
|
1853
1887
|
}
|
|
1888
|
+
function resetWalletView() {
|
|
1889
|
+
networkEpoch += 1;
|
|
1890
|
+
store.patch({ account: null, accountAt: 0, accountError: null, tradeHistory: null, tradeHistoryAt: 0, tradeHistoryError: null });
|
|
1891
|
+
void Promise.allSettled([loadStatus(), loadAccount(), loadTradeHistory(true)]);
|
|
1892
|
+
}
|
|
1854
1893
|
async function switchTradingMode(mode) {
|
|
1855
1894
|
const result = await hyperCall("switchTradingMode", { mode, acknowledgeLive: mode === "live" });
|
|
1856
1895
|
if (result?.mode !== mode) throw new Error(tr("\u4EA4\u6613\u6A21\u5F0F\u5207\u6362\u7ED3\u679C\u4E0D\u4E00\u81F4", "Trading mode switch response mismatch"));
|
|
@@ -1895,6 +1934,10 @@ function mergeCandle(rows, next) {
|
|
|
1895
1934
|
if (next.t === last.t && JSON.stringify(next) !== JSON.stringify(last)) return [...rows.slice(0, -1), next];
|
|
1896
1935
|
return rows;
|
|
1897
1936
|
}
|
|
1937
|
+
async function setCustody(mode, acknowledge = false) {
|
|
1938
|
+
await hyperCall("setCustody", { mode, acknowledge });
|
|
1939
|
+
await loadStatus();
|
|
1940
|
+
}
|
|
1898
1941
|
async function loadStatus() {
|
|
1899
1942
|
const epoch = networkEpoch;
|
|
1900
1943
|
try {
|
|
@@ -2119,6 +2162,8 @@ async function pollEvents() {
|
|
|
2119
2162
|
if (ev.kind === "network.changed" && (ev.data?.to === "mainnet" || ev.data?.to === "testnet") && ev.data.to !== store.get().status?.network) resetNetworkView(ev.data.to);
|
|
2120
2163
|
if (ev.kind === "mode.changed" && (ev.data?.to === "live" || ev.data?.to === "paper") && ev.data.to !== store.get().status?.tradingMode) resetModeView(ev.data.to);
|
|
2121
2164
|
if (ev.kind === "risk.changed") void loadStatus();
|
|
2165
|
+
if (ev.kind === "wallet.changed") resetWalletView();
|
|
2166
|
+
if (ev.kind === "custody.changed") void loadStatus();
|
|
2122
2167
|
if (ev.kind === "paper.fill" || ev.kind?.startsWith("live.order.")) {
|
|
2123
2168
|
void loadAccount();
|
|
2124
2169
|
void loadTradeHistory(true);
|
|
@@ -2423,10 +2468,16 @@ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primit
|
|
|
2423
2468
|
// src/client/format.ts
|
|
2424
2469
|
var INTERVALS = ["1m", "5m", "15m", "1h", "4h", "1d"];
|
|
2425
2470
|
var MORE_INTERVALS = ["3m", "30m", "2h", "8h", "12h", "3d", "1w", "1M"];
|
|
2471
|
+
var numberFormats = /* @__PURE__ */ new Map();
|
|
2426
2472
|
function fmt(n2, d = 2) {
|
|
2427
2473
|
const v = Number(n2);
|
|
2428
2474
|
if (!Number.isFinite(v)) return "\u2014";
|
|
2429
|
-
|
|
2475
|
+
let formatter = numberFormats.get(d);
|
|
2476
|
+
if (!formatter) {
|
|
2477
|
+
formatter = new Intl.NumberFormat("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
2478
|
+
numberFormats.set(d, formatter);
|
|
2479
|
+
}
|
|
2480
|
+
return formatter.format(v);
|
|
2430
2481
|
}
|
|
2431
2482
|
function fmtPrice(n2) {
|
|
2432
2483
|
const v = Number(n2);
|
|
@@ -17651,6 +17702,7 @@ function isSingleEmoji(value) {
|
|
|
17651
17702
|
if (typeof value !== "string" || !value || value.length > 32 || !/\p{Extended_Pictographic}|\p{Regional_Indicator}/u.test(value)) return false;
|
|
17652
17703
|
return [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(value)].length === 1;
|
|
17653
17704
|
}
|
|
17705
|
+
var FIB_LEVEL_KINDS = ["fib", "fibext", "fibchannel"];
|
|
17654
17706
|
function volumeProfile(bars, { rows = 48, valueArea = 0.7 } = {}) {
|
|
17655
17707
|
const valid = bars.filter((bar) => [bar.open, bar.high, bar.low, bar.close, bar.volume].every(Number.isFinite) && bar.high >= bar.low && bar.volume >= 0);
|
|
17656
17708
|
if (!valid.length) return null;
|
|
@@ -17935,7 +17987,7 @@ function guide(a, b, value, key3, text) {
|
|
|
17935
17987
|
}
|
|
17936
17988
|
var BADGE_PATHS = { alert: "M6 16V11a6 6 0 0 1 12 0v5l2 2H4zM10 20a2 2 0 0 0 4 0", shared: "M4 9h13l-3-3M20 15H7l3 3" };
|
|
17937
17989
|
var DASH = { solid: void 0, dashed: "7 5", dotted: "1.5 4" };
|
|
17938
|
-
var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, label, text: textValue, className, view, anchors = false, interactive = false, color, lineWidth, lineStyle, id, alert, shared, rows, valueArea }) {
|
|
17990
|
+
var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, label, text: textValue, className, view, anchors = false, interactive = false, color, lineWidth, lineStyle, id, alert, shared, rows, valueArea, levels }) {
|
|
17939
17991
|
const badges = [...alert ? ["alert"] : [], ...shared ? ["shared"] : []];
|
|
17940
17992
|
const { width: w, height: h } = view;
|
|
17941
17993
|
const px = points2.map((point) => view.project(point.time, point.price));
|
|
@@ -17988,7 +18040,7 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
17988
18040
|
if (!p[1]) break;
|
|
17989
18041
|
const [start, end] = points2;
|
|
17990
18042
|
const x0 = Math.min(p[0].x, p[1].x), x1 = Math.max(p[0].x, p[1].x, x0 + 40);
|
|
17991
|
-
const rows2 = FIB_LEVELS.map((level) => ({ level, price: end.price + (start.price - end.price) * level })).map((row) => ({ ...row, y: view.project(end.time, row.price)?.y })).filter((row) => row.y != null);
|
|
18043
|
+
const rows2 = (levels ?? FIB_LEVELS).map((level) => ({ level, price: end.price + (start.price - end.price) * level })).map((row) => ({ ...row, y: view.project(end.time, row.price)?.y })).filter((row) => row.y != null);
|
|
17992
18044
|
body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, rows2.slice(1).map((row, index) => /* @__PURE__ */ React7.createElement("rect", { key: `b${row.level}`, className: `dhl-fib-band band-${index % 3}`, x: x0, width: x1 - x0, y: Math.min(row.y, rows2[index].y), height: Math.abs(row.y - rows2[index].y) })), rows2.map((row) => /* @__PURE__ */ React7.createElement("g", { key: row.level }, /* @__PURE__ */ React7.createElement("line", { className: row.level === 0.5 || row.level === 0.618 ? "dhl-fib-key" : void 0, x1: x0, x2: x1, y1: row.y, y2: row.y }), text(x0 + 4, row.y - 3, `${row.level} (${fmtPrice(row.price)})`))), /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y }));
|
|
17993
18045
|
break;
|
|
17994
18046
|
}
|
|
@@ -18070,7 +18122,7 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
18070
18122
|
}
|
|
18071
18123
|
const [a, b, c] = points2;
|
|
18072
18124
|
const x0 = p[2].x, x1 = Math.max(x0 + Math.abs(p[1].x - p[0].x), x0 + 60);
|
|
18073
|
-
const rows2 = FIB_EXT_LEVELS.map((level) => ({ level, price: c.price + (b.price - a.price) * level })).map((row) => ({ ...row, y: view.project(c.time, row.price)?.y })).filter((row) => row.y != null);
|
|
18125
|
+
const rows2 = (levels ?? FIB_EXT_LEVELS).map((level) => ({ level, price: c.price + (b.price - a.price) * level })).map((row) => ({ ...row, y: view.project(c.time, row.price)?.y })).filter((row) => row.y != null);
|
|
18074
18126
|
body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("polyline", { className: "dhl-drawing-mid", points: `${p[0].x},${p[0].y} ${p[1].x},${p[1].y} ${p[2].x},${p[2].y}` }), rows2.map((row) => /* @__PURE__ */ React7.createElement("g", { key: row.level }, /* @__PURE__ */ React7.createElement("line", { className: row.level === 1 || row.level === 1.618 ? "dhl-fib-key" : void 0, x1: x0, x2: x1, y1: row.y, y2: row.y }), text(x0 + 4, row.y - 3, `${row.level} (${fmtPrice(row.price)})`))));
|
|
18075
18127
|
break;
|
|
18076
18128
|
}
|
|
@@ -18082,7 +18134,7 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
18082
18134
|
}
|
|
18083
18135
|
const k = (points2[2].time - points2[0].time) / (points2[1].time - points2[0].time || 1);
|
|
18084
18136
|
const offset = points2[2].price - (points2[0].price + (points2[1].price - points2[0].price) * k);
|
|
18085
|
-
body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, FIB_LEVELS.map((level) => {
|
|
18137
|
+
body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, (levels ?? FIB_LEVELS).map((level) => {
|
|
18086
18138
|
const a2 = view.project(points2[0].time, points2[0].price + offset * level), b2 = view.project(points2[1].time, points2[1].price + offset * level);
|
|
18087
18139
|
const seg = a2 && b2 ? clipLine(a2, b2, w, h, 0) : null;
|
|
18088
18140
|
return seg ? /* @__PURE__ */ React7.createElement("g", { key: level }, /* @__PURE__ */ React7.createElement("line", { className: level === 0.5 || level === 0.618 ? "dhl-fib-key" : void 0, x1: seg[0].x, y1: seg[0].y, x2: seg[1].x, y2: seg[1].y }), text(a2.x + 4, a2.y - 3, String(level))) : null;
|
|
@@ -18152,9 +18204,9 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
18152
18204
|
case "gannsquare": {
|
|
18153
18205
|
if (!p[1]) break;
|
|
18154
18206
|
const x0 = Math.min(p[0].x, p[1].x), x1 = Math.max(p[0].x, p[1].x), y0 = Math.min(p[0].y, p[1].y), y1 = Math.max(p[0].y, p[1].y);
|
|
18155
|
-
const
|
|
18156
|
-
const xs =
|
|
18157
|
-
body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-drawing-fill", x: x0, y: y0, width: x1 - x0, height: y1 - y0 }), xs.slice(1, -1).map((x, i) => /* @__PURE__ */ React7.createElement("line", { key: `v${i}`, className: "dhl-drawing-mid", x1: x, x2: x, y1: y0, y2: y1 })), ys.slice(1, -1).map((y, i) => /* @__PURE__ */ React7.createElement("line", { key: `h${i}`, className: "dhl-drawing-mid", x1: x0, x2: x1, y1: y, y2: y })), /* @__PURE__ */ React7.createElement("line", { x1: x0, y1, x2: x1, y2: y0 }), /* @__PURE__ */ React7.createElement("line", { x1: x0, y1: y0, x2: x1, y2: y1 }), kind === "gannsquare" ? [[x0, y1, x1, (y0 + y1) / 2], [x0, y1, (x0 + x1) / 2, y0], [x0, y0, x1, (y0 + y1) / 2], [x0, y0, (x0 + x1) / 2, y1]].map(([ax, ay, bx, by], i) => /* @__PURE__ */ React7.createElement("line", { key: `f${i}`, className: "dhl-drawing-mid", x1: ax, y1: ay, x2: bx, y2: by })) : null, kind === "gannbox" ?
|
|
18207
|
+
const levels2 = kind === "gannbox" ? GANN_BOX_LEVELS : [0, 0.25, 0.5, 0.75, 1];
|
|
18208
|
+
const xs = levels2.map((level) => x0 + (x1 - x0) * level), ys = levels2.map((level) => y0 + (y1 - y0) * level);
|
|
18209
|
+
body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-drawing-fill", x: x0, y: y0, width: x1 - x0, height: y1 - y0 }), xs.slice(1, -1).map((x, i) => /* @__PURE__ */ React7.createElement("line", { key: `v${i}`, className: "dhl-drawing-mid", x1: x, x2: x, y1: y0, y2: y1 })), ys.slice(1, -1).map((y, i) => /* @__PURE__ */ React7.createElement("line", { key: `h${i}`, className: "dhl-drawing-mid", x1: x0, x2: x1, y1: y, y2: y })), /* @__PURE__ */ React7.createElement("line", { x1: x0, y1, x2: x1, y2: y0 }), /* @__PURE__ */ React7.createElement("line", { x1: x0, y1: y0, x2: x1, y2: y1 }), kind === "gannsquare" ? [[x0, y1, x1, (y0 + y1) / 2], [x0, y1, (x0 + x1) / 2, y0], [x0, y0, x1, (y0 + y1) / 2], [x0, y0, (x0 + x1) / 2, y1]].map(([ax, ay, bx, by], i) => /* @__PURE__ */ React7.createElement("line", { key: `f${i}`, className: "dhl-drawing-mid", x1: ax, y1: ay, x2: bx, y2: by })) : null, kind === "gannbox" ? levels2.map((level, i) => /* @__PURE__ */ React7.createElement("g", { key: `l${level}` }, text(xs[i], y0 - 4, String(level), "middle"), text(x0 - 4, ys[i] + 3, String(level), "end"))) : null);
|
|
18158
18210
|
break;
|
|
18159
18211
|
}
|
|
18160
18212
|
case "icon": {
|
|
@@ -18192,7 +18244,7 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
18192
18244
|
const x0 = Math.min(p[0].x, p[1].x), x1 = Math.max(p[0].x, p[1].x);
|
|
18193
18245
|
const settings = { rows: rows ?? 48, valueArea: valueArea ?? 0.7 };
|
|
18194
18246
|
const profile = view.profile ? view.profile(from.time, to.time, settings) : volumeProfile(view.candles?.(from.time, to.time) ?? [], settings);
|
|
18195
|
-
const partial = view.firstTime != null && view.barMs != null && view.firstTime - from.time
|
|
18247
|
+
const partial = view.firstTime != null && view.barMs != null && view.firstTime - from.time >= view.barMs;
|
|
18196
18248
|
if (!profile) {
|
|
18197
18249
|
body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-drawing-fill", x: x0, y: 0, width: Math.max(1, x1 - x0), height: h }), text(x0 + 4, 16, tr("\u533A\u95F4\u5185\u6682\u65E0\u6210\u4EA4\u91CF\u6570\u636E", "No volume in this range"), "start"));
|
|
18198
18250
|
handles = [p[0], p[1]];
|
|
@@ -18202,13 +18254,17 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
18202
18254
|
const maxRow = Math.max(...profile.rows.map((row) => row.up + row.down)) || 1;
|
|
18203
18255
|
const span = Math.max(24, (x1 - x0) * 0.3);
|
|
18204
18256
|
const yTop = y(profile.rows.at(-1).high), yBottom = y(profile.rows[0].low), yPoc = y(profile.poc), yVah = y(profile.vah), yVal = y(profile.val);
|
|
18205
|
-
|
|
18257
|
+
const paths2 = { valueUp: "", valueDown: "", outUp: "", outDown: "" };
|
|
18258
|
+
const rectPath = (x, top, width, height) => `M${x.toFixed(2)} ${top.toFixed(2)}h${width.toFixed(2)}v${height.toFixed(2)}h${(-width).toFixed(2)}z`;
|
|
18259
|
+
profile.rows.forEach((row, index) => {
|
|
18206
18260
|
const top = y(row.high), bottom = y(row.low);
|
|
18207
|
-
if (top == null || bottom == null) return
|
|
18261
|
+
if (top == null || bottom == null) return;
|
|
18208
18262
|
const height = Math.max(1, bottom - top - 0.5), inValue = index >= profile.valueLow && index <= profile.valueHigh;
|
|
18209
18263
|
const upWidth = span * row.up / maxRow, downWidth = span * row.down / maxRow;
|
|
18210
|
-
|
|
18211
|
-
|
|
18264
|
+
paths2[inValue ? "valueUp" : "outUp"] += rectPath(x0, top, upWidth, height);
|
|
18265
|
+
paths2[inValue ? "valueDown" : "outDown"] += rectPath(x0 + upWidth, top, downWidth, height);
|
|
18266
|
+
});
|
|
18267
|
+
body = /* @__PURE__ */ React7.createElement("g", { className: "dhl-vprofile", "data-rows": profile.rows.length }, yTop != null && yBottom != null ? /* @__PURE__ */ React7.createElement("rect", { className: "dhl-vprofile-range", x: x0, y: yTop, width: Math.max(1, x1 - x0), height: Math.max(1, yBottom - yTop) }) : null, /* @__PURE__ */ React7.createElement("g", { className: "dhl-vprofile-va" }, /* @__PURE__ */ React7.createElement("path", { className: "dhl-vprofile-up", d: paths2.valueUp }), /* @__PURE__ */ React7.createElement("path", { className: "dhl-vprofile-down", d: paths2.valueDown })), /* @__PURE__ */ React7.createElement("g", { className: "dhl-vprofile-out" }, /* @__PURE__ */ React7.createElement("path", { className: "dhl-vprofile-up", d: paths2.outUp }), /* @__PURE__ */ React7.createElement("path", { className: "dhl-vprofile-down", d: paths2.outDown })), yPoc != null ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-vprofile-poc", x1: x0, y1: yPoc, x2: x1, y2: yPoc }), text(x1 - 4, yPoc - 4, `POC ${fmtPrice(profile.poc)}`, "end")) : null, yVah != null ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: x0, y1: yVah, x2: x1, y2: yVah }), text(x1 - 4, yVah - 4, `VAH ${fmtPrice(profile.vah)}`, "end")) : null, yVal != null ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: x0, y1: yVal, x2: x1, y2: yVal }), text(x1 - 4, yVal + 12, `VAL ${fmtPrice(profile.val)}`, "end")) : null, yBottom != null ? text(x0 + 4, Math.min(h - 4, yBottom + 14), partial ? tr("K \u7EBF\u4F30\u7B97 \xB7 \u533A\u95F4\u524D\u6BB5\u5386\u53F2\u672A\u52A0\u8F7D", "Estimated from bars \xB7 early range not loaded") : tr("K \u7EBF\u4F30\u7B97", "Estimated from bars"), "start", partial ? "dhl-drawing-text dhl-vprofile-warn" : "dhl-drawing-text") : null);
|
|
18212
18268
|
handles = [p[0], p[1]];
|
|
18213
18269
|
break;
|
|
18214
18270
|
}
|
|
@@ -18289,6 +18345,21 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
|
|
|
18289
18345
|
setText(mark.label);
|
|
18290
18346
|
}, [mark.id, mark.label]);
|
|
18291
18347
|
const locked = Boolean(mark.locked) || globallyLocked;
|
|
18348
|
+
const defaultLevels = (mark.levels ?? (mark.kind === "fibext" ? FIB_EXT_LEVELS : FIB_LEVELS)).join(", ");
|
|
18349
|
+
const [levelsText, setLevelsText] = React8.useState(defaultLevels);
|
|
18350
|
+
React8.useEffect(() => {
|
|
18351
|
+
setLevelsText(defaultLevels);
|
|
18352
|
+
}, [mark.id, defaultLevels]);
|
|
18353
|
+
const applyLevels = () => {
|
|
18354
|
+
if (levelsText === defaultLevels) return;
|
|
18355
|
+
if (!levelsText.trim()) {
|
|
18356
|
+
onEdit({ levels: null });
|
|
18357
|
+
return;
|
|
18358
|
+
}
|
|
18359
|
+
const values = levelsText.split(/[,,\s]+/).filter(Boolean).map(Number);
|
|
18360
|
+
if (values.length && values.every(Number.isFinite)) onEdit({ levels: values });
|
|
18361
|
+
else setLevelsText(defaultLevels);
|
|
18362
|
+
};
|
|
18292
18363
|
const width = mark.lineWidth ?? 2;
|
|
18293
18364
|
const style = mark.lineStyle ?? "solid";
|
|
18294
18365
|
const tool = toolOf(mark.kind);
|
|
@@ -18348,7 +18419,22 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
|
|
|
18348
18419
|
onClick: () => onEdit({ lineStyle: value })
|
|
18349
18420
|
},
|
|
18350
18421
|
/* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 18 18", width: "16", height: "16", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("line", { x1: "2", x2: "16", y1: "9", y2: "9", stroke: "currentColor", strokeWidth: "2", strokeDasharray: value === "dashed" ? "4 3" : value === "dotted" ? "1 3" : void 0, strokeLinecap: "round" }))
|
|
18351
|
-
))) : null, mark.kind
|
|
18422
|
+
))) : null, FIB_LEVEL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6590\u6CE2\u90A3\u5951\u5C42\u7EA7", "Fibonacci levels") }, /* @__PURE__ */ React8.createElement(
|
|
18423
|
+
"input",
|
|
18424
|
+
{
|
|
18425
|
+
className: "dhl-draw-toolbar-text dhl-fib-levels",
|
|
18426
|
+
value: levelsText,
|
|
18427
|
+
disabled: locked,
|
|
18428
|
+
"aria-label": tr("\u6590\u6CE2\u90A3\u5951\u5C42\u7EA7\uFF08\u9017\u53F7\u5206\u9694\uFF09", "Fibonacci levels (comma separated)"),
|
|
18429
|
+
title: tr("\u9017\u53F7\u5206\u9694\uFF0C\u56DE\u8F66\u5E94\u7528\uFF1B\u6E05\u7A7A\u6062\u590D\u9ED8\u8BA4", "Comma separated, Enter applies; empty restores defaults"),
|
|
18430
|
+
onChange: (event) => setLevelsText(event.target.value),
|
|
18431
|
+
onKeyDown: (event) => {
|
|
18432
|
+
if (event.key === "Enter") applyLevels();
|
|
18433
|
+
if (event.key === "Escape") setLevelsText(defaultLevels);
|
|
18434
|
+
},
|
|
18435
|
+
onBlur: applyLevels
|
|
18436
|
+
}
|
|
18437
|
+
)) : null, mark.kind === "vprofile" ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6210\u4EA4\u91CF\u5206\u5E03\u8BBE\u7F6E", "Volume profile settings") }, /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u4EF7\u683C\u884C\u6570", "Rows"), title: tr("\u4EF7\u683C\u884C\u6570", "Rows"), value: String(mark.rows ?? 48), disabled: locked, onChange: (event) => onEdit({ rows: Number(event.target.value) }) }, [24, 48, 96, 150].map((value) => /* @__PURE__ */ React8.createElement("option", { key: value, value }, tr(`${value} \u884C`, `${value} rows`)))), /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u4EF7\u503C\u533A\u6BD4\u4F8B", "Value area"), title: tr("\u4EF7\u503C\u533A\u6210\u4EA4\u91CF\u5360\u6BD4", "Value area volume"), value: String(mark.valueArea ?? 0.7), disabled: locked, onChange: (event) => onEdit({ valueArea: Number(event.target.value) }) }, [0.68, 0.7, 0.8].map((value) => /* @__PURE__ */ React8.createElement("option", { key: value, value }, `VA ${Math.round(value * 100)}%`)))) : null, /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group" }, /* @__PURE__ */ React8.createElement("button", { type: "button", disabled: locked, title: tr("\u7CBE\u786E\u7F16\u8F91\u65F6\u95F4\u3001\u4EF7\u683C\u4E0E\u540D\u79F0", "Edit exact time, price and name"), "aria-label": tr("\u7F16\u8F91\u753B\u7EBF\u5750\u6807", "Edit drawing coordinates"), onClick: onCoordinates }, /* @__PURE__ */ React8.createElement(UiIcon, { name: "settings", size: 15 })), /* @__PURE__ */ React8.createElement(
|
|
18352
18438
|
"button",
|
|
18353
18439
|
{
|
|
18354
18440
|
type: "button",
|
|
@@ -18548,6 +18634,24 @@ async function chartImage(chart, box, title) {
|
|
|
18548
18634
|
return new Promise((resolve, reject) => canvas.toBlob((blob) => blob ? resolve(blob) : reject(new Error("PNG export failed")), "image/png"));
|
|
18549
18635
|
}
|
|
18550
18636
|
|
|
18637
|
+
// src/client/chart-feed.ts
|
|
18638
|
+
var fedShape = /* @__PURE__ */ new WeakMap();
|
|
18639
|
+
var fields = ["time", "value", "open", "high", "low", "close", "color"];
|
|
18640
|
+
function samePrefix(previous, data, length) {
|
|
18641
|
+
for (let i = 0; i < length; i++) for (const key3 of fields) if (previous[i][key3] !== data[i][key3]) return false;
|
|
18642
|
+
return true;
|
|
18643
|
+
}
|
|
18644
|
+
function feed(series, data, sig = "") {
|
|
18645
|
+
const prev = fedShape.get(series);
|
|
18646
|
+
const len = data.length, first = data[0]?.time, last = data.at(-1)?.time;
|
|
18647
|
+
const tailOnly = prev && prev.sig === sig && len > 0 && first === prev.data[0]?.time && (len === prev.data.length ? last === prev.data.at(-1)?.time : len === prev.data.length + 1 && data.at(-2)?.time === prev.data.at(-1)?.time) && samePrefix(prev.data, data, prev.data.length - 1);
|
|
18648
|
+
if (tailOnly) {
|
|
18649
|
+
if (len === prev.data.length + 1) series.update(data.at(-2));
|
|
18650
|
+
series.update(data.at(-1));
|
|
18651
|
+
} else series.setData(data);
|
|
18652
|
+
fedShape.set(series, { data, sig });
|
|
18653
|
+
}
|
|
18654
|
+
|
|
18551
18655
|
// src/client/components/candle-chart.tsx
|
|
18552
18656
|
var MAGNET_PX = 14;
|
|
18553
18657
|
var MA_PALETTE = ["#29b6f6", "#ff7eb6", "#c6e377", "#ffb74d", "#b39ddb", "#4dd0e1", "#f48fb1", "#aed581"];
|
|
@@ -18651,11 +18755,25 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18651
18755
|
const [selectedTime, setSelectedTime] = React10.useState(null);
|
|
18652
18756
|
const [themeVersion, setThemeVersion] = React10.useState(0);
|
|
18653
18757
|
const [viewportVersion, setViewportVersion] = React10.useState(0);
|
|
18758
|
+
const projectionStamp = React10.useRef([]);
|
|
18759
|
+
const readProjection = (r, box) => {
|
|
18760
|
+
const base = candlesRef.current[0]?.c ?? 1;
|
|
18761
|
+
return [
|
|
18762
|
+
r.chart.timeScale().logicalToCoordinate(0),
|
|
18763
|
+
r.chart.timeScale().logicalToCoordinate(1),
|
|
18764
|
+
r.candles.priceToCoordinate(base),
|
|
18765
|
+
r.candles.priceToCoordinate(base * 1.01),
|
|
18766
|
+
box.clientWidth,
|
|
18767
|
+
box.clientHeight,
|
|
18768
|
+
base
|
|
18769
|
+
];
|
|
18770
|
+
};
|
|
18654
18771
|
const [pendingPoints, setPendingPoints] = React10.useState([]);
|
|
18655
18772
|
const [drawingHover, setDrawingHover] = React10.useState(null);
|
|
18656
18773
|
const showVolume = indicators.has("volume");
|
|
18657
18774
|
const studies = React10.useMemo(() => chartIndicators(candles, settings), [candles, settings]);
|
|
18658
18775
|
const maComputed = React10.useMemo(() => maLines.map((line, index) => ({ line, color: maColor(line, index), ...maSeries(candles, line) })), [candles, maLines]);
|
|
18776
|
+
const axisKey = `${candles.length}:${candles[0]?.t ?? 0}:${candles.at(-1)?.t ?? 0}`;
|
|
18659
18777
|
const timeline = React10.useMemo(() => {
|
|
18660
18778
|
const times = [...new Set(candles.map((candle) => Math.floor(candle.t / 1e3)))].sort((a, b) => a - b);
|
|
18661
18779
|
const step = times.length > 1 ? times[times.length - 1] - times[times.length - 2] : 60;
|
|
@@ -18680,7 +18798,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18680
18798
|
return times[i] * 1e3;
|
|
18681
18799
|
};
|
|
18682
18800
|
return { toLogical, toTime };
|
|
18683
|
-
}, [
|
|
18801
|
+
}, [axisKey]);
|
|
18684
18802
|
const timelineRef = React10.useRef(timeline);
|
|
18685
18803
|
timelineRef.current = timeline;
|
|
18686
18804
|
React10.useEffect(() => {
|
|
@@ -18807,7 +18925,21 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18807
18925
|
}
|
|
18808
18926
|
}
|
|
18809
18927
|
}
|
|
18810
|
-
if (event.shiftKey && previous)
|
|
18928
|
+
if (event.shiftKey && previous) {
|
|
18929
|
+
const px = chart.timeScale().logicalToCoordinate(timelineRef.current.toLogical(previous.time));
|
|
18930
|
+
const py = candleSeries.priceToCoordinate(previous.price);
|
|
18931
|
+
const x = event.clientX - rect.left;
|
|
18932
|
+
if (px == null || py == null) return { time, price: previous.price };
|
|
18933
|
+
const dx = x - px, dy = y - py;
|
|
18934
|
+
const angle = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);
|
|
18935
|
+
if (Math.abs(Math.abs(angle) - Math.PI / 2) < 1e-6 || Math.abs(angle) < 1e-6 || Math.abs(Math.abs(angle) - Math.PI) < 1e-6) return { time, price: previous.price };
|
|
18936
|
+
const along = dx * Math.cos(angle) + dy * Math.sin(angle);
|
|
18937
|
+
const tx = px + Math.cos(angle) * along, ty = py + Math.sin(angle) * along;
|
|
18938
|
+
const snappedLogical = chart.timeScale().coordinateToLogical(tx);
|
|
18939
|
+
const snappedPrice = Number(candleSeries.coordinateToPrice(ty));
|
|
18940
|
+
if (snappedLogical == null || !Number.isFinite(snappedPrice) || snappedPrice <= 0) return { time, price: previous.price };
|
|
18941
|
+
return { time: timelineRef.current.toTime(Number(snappedLogical)), price: snappedPrice };
|
|
18942
|
+
}
|
|
18811
18943
|
return { time, price };
|
|
18812
18944
|
};
|
|
18813
18945
|
snapRef.current = snapPoint;
|
|
@@ -18877,17 +19009,31 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18877
19009
|
let viewportFrame = 0;
|
|
18878
19010
|
const refreshOverlay = () => {
|
|
18879
19011
|
if (viewportFrame) cancelAnimationFrame(viewportFrame);
|
|
18880
|
-
viewportFrame = requestAnimationFrame(() => setViewportVersion((value) => value + 1));
|
|
19012
|
+
viewportFrame = requestAnimationFrame(() => React10.startTransition(() => setViewportVersion((value) => value + 1)));
|
|
18881
19013
|
};
|
|
18882
19014
|
chart.timeScale().subscribeVisibleLogicalRangeChange(refreshOverlay);
|
|
19015
|
+
let scalingPrice = false;
|
|
19016
|
+
const endCanvasGesture = () => box.classList.remove("dhl-canvas-gesture");
|
|
19017
|
+
const beginCanvasGesture = (event) => {
|
|
19018
|
+
if (event.target instanceof HTMLCanvasElement) box.classList.add("dhl-canvas-gesture");
|
|
19019
|
+
};
|
|
19020
|
+
box.addEventListener("pointerdown", beginCanvasGesture, { passive: true });
|
|
19021
|
+
window.addEventListener("pointerup", endCanvasGesture);
|
|
19022
|
+
window.addEventListener("pointercancel", endCanvasGesture);
|
|
19023
|
+
window.addEventListener("blur", endCanvasGesture);
|
|
19024
|
+
const scaleDown = (event) => {
|
|
19025
|
+
scalingPrice = event.target instanceof HTMLCanvasElement && event.clientX >= box.getBoundingClientRect().right - chart.priceScale("right").width();
|
|
19026
|
+
};
|
|
18883
19027
|
const scaleDrag = (event) => {
|
|
18884
|
-
if (event.buttons) refreshOverlay();
|
|
19028
|
+
if (event.buttons && scalingPrice) refreshOverlay();
|
|
18885
19029
|
};
|
|
19030
|
+
box.addEventListener("pointerdown", scaleDown, { passive: true });
|
|
18886
19031
|
box.addEventListener("pointermove", scaleDrag, { passive: true });
|
|
18887
19032
|
box.addEventListener("wheel", refreshOverlay, { passive: true });
|
|
18888
19033
|
box.addEventListener("dblclick", refreshOverlay);
|
|
18889
19034
|
const sizeObserver = new ResizeObserver(refreshOverlay);
|
|
18890
19035
|
sizeObserver.observe(box);
|
|
19036
|
+
box.querySelectorAll("canvas").forEach((canvas) => sizeObserver.observe(canvas));
|
|
18891
19037
|
chart.timeScale().subscribeVisibleTimeRangeChange((range) => {
|
|
18892
19038
|
if (!fitted.current || restoring.current || !range || typeof range.from !== "number" || typeof range.to !== "number") return;
|
|
18893
19039
|
if (rangeTimer) clearTimeout(rangeTimer);
|
|
@@ -18930,6 +19076,12 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18930
19076
|
box.removeEventListener("click", chartClick);
|
|
18931
19077
|
chart.timeScale().unsubscribeVisibleLogicalRangeChange(refreshOverlay);
|
|
18932
19078
|
box.removeEventListener("pointermove", scaleDrag);
|
|
19079
|
+
box.removeEventListener("pointerdown", beginCanvasGesture);
|
|
19080
|
+
window.removeEventListener("pointerup", endCanvasGesture);
|
|
19081
|
+
window.removeEventListener("pointercancel", endCanvasGesture);
|
|
19082
|
+
window.removeEventListener("blur", endCanvasGesture);
|
|
19083
|
+
endCanvasGesture();
|
|
19084
|
+
box.removeEventListener("pointerdown", scaleDown);
|
|
18933
19085
|
box.removeEventListener("wheel", refreshOverlay);
|
|
18934
19086
|
box.removeEventListener("dblclick", refreshOverlay);
|
|
18935
19087
|
sizeObserver.disconnect();
|
|
@@ -18989,26 +19141,26 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18989
19141
|
const prevFirst = firstBarTime.current;
|
|
18990
19142
|
const prepended = prevFirst != null && bars[0].time < prevFirst ? Math.max(0, bars.findIndex((bar) => bar.time >= prevFirst)) : 0;
|
|
18991
19143
|
firstBarTime.current = bars[0].time;
|
|
18992
|
-
|
|
18993
|
-
r.
|
|
18994
|
-
r.
|
|
18995
|
-
r.
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
|
|
19001
|
-
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
|
|
19144
|
+
const studySig = JSON.stringify(settings), colorSig = `${chartType}|${th.up}|${th.down}`;
|
|
19145
|
+
feed(r.candles, chartType === "line" || chartType === "area" ? bars.map((bar) => ({ time: bar.time, value: bar.close })) : bars, chartType);
|
|
19146
|
+
if (showVolume) feed(r.volume, vols.map((v) => ({ ...v, color: v.color === FALLBACK_UP ? th.up : th.down })), colorSig);
|
|
19147
|
+
if (r.volume.options().visible !== showVolume) r.volume.applyOptions({ visible: showVolume });
|
|
19148
|
+
for (const [series, key3, indicator] of [
|
|
19149
|
+
[r.sma, "sma20", "sma20"],
|
|
19150
|
+
[r.sma60, "sma60", "sma60"],
|
|
19151
|
+
[r.ema20, "ema20", "ema20"],
|
|
19152
|
+
[r.ema50, "ema50", "ema50"],
|
|
19153
|
+
[r.basis, "basis", "bollinger20"],
|
|
19154
|
+
[r.upper, "upper", "bollinger20"],
|
|
19155
|
+
[r.lower, "lower", "bollinger20"]
|
|
19156
|
+
]) {
|
|
19157
|
+
const visible = indicators.has(indicator);
|
|
19158
|
+
if (visible) feed(series, chartData(studies[key3]), studySig);
|
|
19159
|
+
if (series.options().visible !== visible) series.applyOptions({ visible });
|
|
19160
|
+
}
|
|
19009
19161
|
if (indicators.has("vwap")) {
|
|
19010
19162
|
r.vwap ??= r.chart.addSeries(lineSeries, { color: "#e89a56", lineWidth: 2, lastValueVisible: false, priceLineVisible: false });
|
|
19011
|
-
r.vwap
|
|
19163
|
+
feed(r.vwap, chartData(studies.vwap), studySig);
|
|
19012
19164
|
} else if (r.vwap) {
|
|
19013
19165
|
r.chart.removeSeries(r.vwap);
|
|
19014
19166
|
r.vwap = void 0;
|
|
@@ -19019,7 +19171,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19019
19171
|
pane.setStretchFactor(0.28);
|
|
19020
19172
|
r.atr = pane.addSeries(lineSeries, { color: "#56bca7", lineWidth: 2, priceLineVisible: false });
|
|
19021
19173
|
}
|
|
19022
|
-
r.atr
|
|
19174
|
+
feed(r.atr, chartData(studies.atr14), studySig);
|
|
19023
19175
|
} else if (r.atr) {
|
|
19024
19176
|
r.chart.removeSeries(r.atr);
|
|
19025
19177
|
r.atr = void 0;
|
|
@@ -19032,7 +19184,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19032
19184
|
r.rsiBounds = [settings.rsi.lower, settings.rsi.upper].map((price) => r.rsi.createPriceLine({ price, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false }));
|
|
19033
19185
|
}
|
|
19034
19186
|
r.rsiBounds?.forEach((line, index) => line.applyOptions({ price: index ? settings.rsi.upper : settings.rsi.lower }));
|
|
19035
|
-
r.rsi
|
|
19187
|
+
feed(r.rsi, chartData(studies.rsi14), studySig);
|
|
19036
19188
|
} else if (r.rsi) {
|
|
19037
19189
|
r.chart.removeSeries(r.rsi);
|
|
19038
19190
|
r.rsi = void 0;
|
|
@@ -19045,9 +19197,9 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19045
19197
|
r.macd = pane.addSeries(lineSeries, { color: "#55a6ce", lineWidth: 2, priceLineVisible: false, lastValueVisible: false });
|
|
19046
19198
|
r.signal = pane.addSeries(lineSeries, { color: "#e2a943", lineWidth: 2, priceLineVisible: false, lastValueVisible: false });
|
|
19047
19199
|
}
|
|
19048
|
-
r.macd
|
|
19049
|
-
r.signal
|
|
19050
|
-
r.histogram
|
|
19200
|
+
feed(r.macd, chartData(studies.macd), studySig);
|
|
19201
|
+
feed(r.signal, chartData(studies.signal), studySig);
|
|
19202
|
+
feed(r.histogram, studies.histogram.map((point) => ({ time: point.time, value: point.value, color: point.value >= 0 ? th.up : th.down })), studySig + colorSig);
|
|
19051
19203
|
} else if (r.macd) {
|
|
19052
19204
|
r.chart.removeSeries(r.macd);
|
|
19053
19205
|
r.chart.removeSeries(r.signal);
|
|
@@ -19071,7 +19223,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19071
19223
|
if (group2.id === "stochastic") r.stochBounds = [settings.stochastic.lower, settings.stochastic.upper].map((price) => r.extra.stochK.createPriceLine({ price, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false }));
|
|
19072
19224
|
if (group2.id === "adx") r.extra.adx.createPriceLine({ price: 25, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false });
|
|
19073
19225
|
}
|
|
19074
|
-
for (const key3 of group2.keys) r.extra[key3]
|
|
19226
|
+
for (const key3 of group2.keys) feed(r.extra[key3], chartData(studies[key3]), studySig);
|
|
19075
19227
|
if (group2.id === "stochastic") r.stochBounds?.forEach((line, index) => line.applyOptions({ price: index ? settings.stochastic.upper : settings.stochastic.lower }));
|
|
19076
19228
|
} else for (const key3 of group2.keys) {
|
|
19077
19229
|
if (r.extra[key3]) {
|
|
@@ -19112,11 +19264,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19112
19264
|
maRefs.current.set(item.line.id, entry);
|
|
19113
19265
|
}
|
|
19114
19266
|
entry.main.applyOptions({ color: item.color });
|
|
19115
|
-
entry.main
|
|
19267
|
+
feed(entry.main, toPoints(item.values), JSON.stringify(item.line));
|
|
19116
19268
|
if (item.smoothed) {
|
|
19117
19269
|
entry.smooth ??= r.chart.addSeries(lineSeries, { lineWidth: 1, lineStyle: LineStyle.Dashed, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: false });
|
|
19118
19270
|
entry.smooth.applyOptions({ color: item.color });
|
|
19119
|
-
entry.smooth
|
|
19271
|
+
feed(entry.smooth, toPoints(item.smoothed), JSON.stringify(item.line));
|
|
19120
19272
|
} else if (entry.smooth) {
|
|
19121
19273
|
r.chart.removeSeries(entry.smooth);
|
|
19122
19274
|
entry.smooth = void 0;
|
|
@@ -19125,7 +19277,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19125
19277
|
if (!fitted.current) {
|
|
19126
19278
|
restoring.current = true;
|
|
19127
19279
|
r.chart.timeScale().fitContent();
|
|
19128
|
-
const
|
|
19280
|
+
const linked = linkRef.current?.range && linkRef.current.range.source !== linkRef.current.id ? linkRef.current.range : null;
|
|
19281
|
+
const saved = linked ?? initialRange.current;
|
|
19129
19282
|
if (saved && saved.from < saved.to) {
|
|
19130
19283
|
try {
|
|
19131
19284
|
r.chart.timeScale().setVisibleRange({ from: saved.from, to: saved.to });
|
|
@@ -19137,6 +19290,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19137
19290
|
restoring.current = false;
|
|
19138
19291
|
}, 100);
|
|
19139
19292
|
}
|
|
19293
|
+
const overlayFrame = requestAnimationFrame(() => {
|
|
19294
|
+
const box2 = boxRef.current;
|
|
19295
|
+
if (box2 && readProjection(r, box2).some((value, index) => value !== projectionStamp.current[index])) React10.startTransition(() => setViewportVersion((value) => value + 1));
|
|
19296
|
+
});
|
|
19297
|
+
return () => cancelAnimationFrame(overlayFrame);
|
|
19140
19298
|
}, [candles, studies, indicators, showVolume, maComputed, chartType, settings]);
|
|
19141
19299
|
React10.useEffect(() => {
|
|
19142
19300
|
const r = refs.current;
|
|
@@ -19296,22 +19454,28 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19296
19454
|
const view = React10.useMemo(() => {
|
|
19297
19455
|
const chartApi = refs.current, box = boxRef.current;
|
|
19298
19456
|
if (!chartApi || !box) return null;
|
|
19457
|
+
const stamp = readProjection(chartApi, box);
|
|
19458
|
+
projectionStamp.current = stamp;
|
|
19459
|
+
const [x0, x1, y0, y1] = stamp;
|
|
19460
|
+
const base = candlesRef.current[0]?.c ?? 1;
|
|
19461
|
+
const ys = /* @__PURE__ */ new Map();
|
|
19299
19462
|
return {
|
|
19300
19463
|
project: (time, price) => {
|
|
19301
|
-
const x =
|
|
19302
|
-
|
|
19464
|
+
const x = x0 == null || x1 == null ? null : x0 + (x1 - x0) * timeline.toLogical(time);
|
|
19465
|
+
if (!ys.has(price)) ys.set(price, priceScale === "log" ? chartApi.candles.priceToCoordinate(price) : y0 == null || y1 == null ? null : y0 + (y1 - y0) * (price - base) / (base * 0.01));
|
|
19466
|
+
const y = ys.get(price);
|
|
19303
19467
|
return x == null || y == null || !Number.isFinite(x) || !Number.isFinite(y) ? null : { x, y };
|
|
19304
19468
|
},
|
|
19305
19469
|
width: Math.max(0, box.clientWidth - chartApi.chart.priceScale("right").width()),
|
|
19306
19470
|
height: chartApi.chart.panes()[0]?.getHeight() ?? box.clientHeight,
|
|
19307
19471
|
bars: (from, to) => timeline.toLogical(to) - timeline.toLogical(from),
|
|
19308
|
-
closes: (from, to) =>
|
|
19309
|
-
candles: (from, to) =>
|
|
19310
|
-
profile: (from, to, options) => cachedProfile(
|
|
19311
|
-
firstTime:
|
|
19312
|
-
barMs:
|
|
19472
|
+
closes: (from, to) => candlesRef.current.filter((candle) => candle.t >= from && candle.t <= to).map((candle) => ({ time: candle.t, price: candle.c })),
|
|
19473
|
+
candles: (from, to) => candlesRef.current.filter((candle) => candle.t >= from && candle.t <= to).map((candle) => ({ time: candle.t, open: candle.o, high: candle.h, low: candle.l, close: candle.c, volume: candle.v })),
|
|
19474
|
+
profile: (from, to, options) => cachedProfile(candlesRef.current, from, to, options),
|
|
19475
|
+
firstTime: candlesRef.current[0]?.t,
|
|
19476
|
+
barMs: candlesRef.current.length > 1 ? candlesRef.current[1].t - candlesRef.current[0].t : void 0
|
|
19313
19477
|
};
|
|
19314
|
-
}, [viewportVersion, timeline, themeVersion, expanded, indicators]);
|
|
19478
|
+
}, [viewportVersion, timeline, themeVersion, expanded, indicators, priceScale]);
|
|
19315
19479
|
React10.useEffect(() => {
|
|
19316
19480
|
if (selectedId && !markById.has(selectedId)) setSelectedId(null);
|
|
19317
19481
|
}, [markById, selectedId]);
|
|
@@ -19426,7 +19590,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19426
19590
|
const cloneOf = React10.useCallback((mark) => {
|
|
19427
19591
|
const line = timelineRef.current;
|
|
19428
19592
|
const shifted = markPoints(mark).map((point) => ({ time: line.toTime(line.toLogical(point.time) + 5), price: point.price }));
|
|
19429
|
-
const style = Object.fromEntries(Object.entries({ color: mark.color, lineWidth: mark.lineWidth, lineStyle: mark.lineStyle }).filter(([, value]) => value != null));
|
|
19593
|
+
const style = Object.fromEntries(Object.entries({ color: mark.color, lineWidth: mark.lineWidth, lineStyle: mark.lineStyle, levels: mark.levels, rows: mark.rows, valueArea: mark.valueArea }).filter(([, value]) => value != null));
|
|
19430
19594
|
return { kind: mark.kind, geometry: geometryPatch(mark.kind, shifted), style, label: mark.label };
|
|
19431
19595
|
}, []);
|
|
19432
19596
|
const clipboard = React10.useRef(null);
|
|
@@ -19526,6 +19690,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19526
19690
|
}
|
|
19527
19691
|
latest2 = next;
|
|
19528
19692
|
setGroupDraft(next);
|
|
19693
|
+
for (const [memberId, member] of originals) if (member.kind === "level") priceLines.current.get(memberId)?.applyOptions({ price: next.get(memberId)[0].price });
|
|
19529
19694
|
};
|
|
19530
19695
|
const move2 = (ev) => {
|
|
19531
19696
|
if (!moved2 && Math.hypot(ev.clientX - origin2.x, ev.clientY - origin2.y) < 3) return;
|
|
@@ -19543,6 +19708,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19543
19708
|
};
|
|
19544
19709
|
const cancel2 = () => {
|
|
19545
19710
|
detach2();
|
|
19711
|
+
for (const [memberId, member] of originals) if (member.kind === "level") priceLines.current.get(memberId)?.applyOptions({ price: Number(member.price) });
|
|
19546
19712
|
setGroupDraft(null);
|
|
19547
19713
|
};
|
|
19548
19714
|
const onKey2 = (ev) => {
|
|
@@ -19582,7 +19748,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19582
19748
|
const compute = (ev) => {
|
|
19583
19749
|
let next;
|
|
19584
19750
|
if (anchor != null) {
|
|
19585
|
-
const point = snap(ev, original[anchor === 0 ? 1 :
|
|
19751
|
+
const point = snap(ev, original[anchor === 0 ? 1 : anchor - 1]);
|
|
19586
19752
|
if (!point) return;
|
|
19587
19753
|
next = original.map((item) => ({ ...item }));
|
|
19588
19754
|
if (mark.kind === "level") next[0] = { ...next[0], price: point.price };
|
|
@@ -19680,6 +19846,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19680
19846
|
points: shown,
|
|
19681
19847
|
view,
|
|
19682
19848
|
text: mark.label,
|
|
19849
|
+
dataVersion: mark.kind === "vprofile" || mark.kind === "regression" ? candles : void 0,
|
|
19683
19850
|
className: `dhl-drawing ${roleClass(mark)}${mark.kind === "level" ? " is-level" : ""}${selected ? " selected" : ""}${mark.locked || drawingsLocked ? " locked" : ""}`,
|
|
19684
19851
|
label: mark.source === "user" || mark.kind === "level" ? void 0 : mark.label,
|
|
19685
19852
|
anchors: selected,
|
|
@@ -19690,7 +19857,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19690
19857
|
alert: mark.alert,
|
|
19691
19858
|
shared: mark.shared,
|
|
19692
19859
|
rows: mark.rows,
|
|
19693
|
-
valueArea: mark.valueArea
|
|
19860
|
+
valueArea: mark.valueArea,
|
|
19861
|
+
levels: mark.levels
|
|
19694
19862
|
}
|
|
19695
19863
|
);
|
|
19696
19864
|
}), /* @__PURE__ */ React10.createElement("g", { className: "dhl-fills" }, fills.map((event) => {
|
|
@@ -19766,7 +19934,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19766
19934
|
var React11 = __toESM(require("react"), 1);
|
|
19767
19935
|
var HISTORY_LIMIT = 100;
|
|
19768
19936
|
var GEOMETRY = ["price", "fromTime", "fromPrice", "toTime", "toPrice", "points"];
|
|
19769
|
-
var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea"];
|
|
19937
|
+
var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea", "levels"];
|
|
19770
19938
|
var FLAGS = /* @__PURE__ */ new Set(["locked", "hidden", "shared", "alert"]);
|
|
19771
19939
|
function recreateRequest(mark) {
|
|
19772
19940
|
const out = { kind: mark.kind, label: mark.label, role: mark.role, coin: mark.coin, interval: mark.interval };
|
|
@@ -19878,6 +20046,28 @@ function useDrawingHistory(scope) {
|
|
|
19878
20046
|
}
|
|
19879
20047
|
return apiDelete(ctx, op.mark.id);
|
|
19880
20048
|
};
|
|
20049
|
+
const step = async (ctx, forward) => {
|
|
20050
|
+
const from = forward ? redoStack : undoStack, to = forward ? undoStack : redoStack;
|
|
20051
|
+
const op = from.current.pop();
|
|
20052
|
+
if (!op) return;
|
|
20053
|
+
bump();
|
|
20054
|
+
let done = null, left = null;
|
|
20055
|
+
if (op.type === "batch") {
|
|
20056
|
+
const ok = [], failed = [];
|
|
20057
|
+
for (const inner of forward ? op.ops : [...op.ops].reverse()) (await apply2(ctx, inner, forward) ? ok : failed).push(inner);
|
|
20058
|
+
if (!forward) {
|
|
20059
|
+
ok.reverse();
|
|
20060
|
+
failed.reverse();
|
|
20061
|
+
}
|
|
20062
|
+
if (ok.length) done = { type: "batch", ops: ok };
|
|
20063
|
+
if (failed.length) left = { type: "batch", ops: failed };
|
|
20064
|
+
} else if (await apply2(ctx, op, forward)) done = op;
|
|
20065
|
+
else left = op;
|
|
20066
|
+
if (ctx.scope !== scopeRef.current) return;
|
|
20067
|
+
if (done) to.current.push(done);
|
|
20068
|
+
if (left) from.current.push(left);
|
|
20069
|
+
bump();
|
|
20070
|
+
};
|
|
19881
20071
|
return {
|
|
19882
20072
|
create: (request) => serial(async (ctx) => {
|
|
19883
20073
|
const saved = await apiCreate(ctx, request);
|
|
@@ -19919,29 +20109,8 @@ function useDrawingHistory(scope) {
|
|
|
19919
20109
|
}
|
|
19920
20110
|
if (done.length) record({ type: "batch", ops: done }, ctx);
|
|
19921
20111
|
}, void 0),
|
|
19922
|
-
|
|
19923
|
-
|
|
19924
|
-
const op = undoStack.current.pop();
|
|
19925
|
-
if (!op) return;
|
|
19926
|
-
redoStack.current.push(op);
|
|
19927
|
-
bump();
|
|
19928
|
-
if (!await apply2(ctx, op, false)) {
|
|
19929
|
-
redoStack.current.pop();
|
|
19930
|
-
undoStack.current.push(op);
|
|
19931
|
-
bump();
|
|
19932
|
-
}
|
|
19933
|
-
}, void 0),
|
|
19934
|
-
redo: () => serial(async (ctx) => {
|
|
19935
|
-
const op = redoStack.current.pop();
|
|
19936
|
-
if (!op) return;
|
|
19937
|
-
undoStack.current.push(op);
|
|
19938
|
-
bump();
|
|
19939
|
-
if (!await apply2(ctx, op, true)) {
|
|
19940
|
-
undoStack.current.pop();
|
|
19941
|
-
redoStack.current.push(op);
|
|
19942
|
-
bump();
|
|
19943
|
-
}
|
|
19944
|
-
}, void 0),
|
|
20112
|
+
undo: () => serial((ctx) => step(ctx, false), void 0),
|
|
20113
|
+
redo: () => serial((ctx) => step(ctx, true), void 0),
|
|
19945
20114
|
canUndo: undoStack.current.length > 0,
|
|
19946
20115
|
canRedo: redoStack.current.length > 0
|
|
19947
20116
|
};
|
|
@@ -19950,7 +20119,7 @@ function useDrawingHistory(scope) {
|
|
|
19950
20119
|
// src/client/components/drawing-list.tsx
|
|
19951
20120
|
var React12 = __toESM(require("react"), 1);
|
|
19952
20121
|
var OPEN_KEY = "dsh-hyperliquid.draw.listOpen";
|
|
19953
|
-
|
|
20122
|
+
var DrawingList = React12.memo(function DrawingList2({ marks, locked, onSelect, onEdit, onRemove, language }) {
|
|
19954
20123
|
const [open, setOpen] = React12.useState(() => {
|
|
19955
20124
|
try {
|
|
19956
20125
|
return localStorage.getItem(OPEN_KEY) !== "0";
|
|
@@ -19966,11 +20135,11 @@ function DrawingList({ marks, locked, onSelect, onEdit, onRemove }) {
|
|
|
19966
20135
|
return !value;
|
|
19967
20136
|
});
|
|
19968
20137
|
const hidden = marks.filter((mark) => mark.hidden).length;
|
|
19969
|
-
return /* @__PURE__ */ React12.createElement("section", { className: "dhl-drawing-list", "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) => {
|
|
20138
|
+
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) => {
|
|
19970
20139
|
const tool = toolOf(mark.kind);
|
|
19971
20140
|
return /* @__PURE__ */ React12.createElement("li", { key: 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: () => onSelect(mark.id), 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.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 })));
|
|
19972
20141
|
})) : null);
|
|
19973
|
-
}
|
|
20142
|
+
});
|
|
19974
20143
|
|
|
19975
20144
|
// src/client/components/sticker-picker.tsx
|
|
19976
20145
|
var React13 = __toESM(require("react"), 1);
|
|
@@ -20426,7 +20595,7 @@ function MarketPicker({ activeCoin, onClose }) {
|
|
|
20426
20595
|
const marketRows = React21.useMemo(() => {
|
|
20427
20596
|
const livePerps = new Map(s.markets.map((market) => [market.name, market]));
|
|
20428
20597
|
const catalog = s.marketCatalog.length ? s.marketCatalog.map((row) => {
|
|
20429
|
-
const latest = row.
|
|
20598
|
+
const latest = row.kind === "perp" ? livePerps.get(row.name) : null;
|
|
20430
20599
|
return latest ? { ...row, ctx: latest.ctx } : row;
|
|
20431
20600
|
}) : s.markets.map((row) => ({
|
|
20432
20601
|
name: row.name,
|
|
@@ -20540,7 +20709,7 @@ function MarketPicker({ activeCoin, onClose }) {
|
|
|
20540
20709
|
}
|
|
20541
20710
|
};
|
|
20542
20711
|
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);
|
|
20543
|
-
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\
|
|
20712
|
+
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: () => {
|
|
20544
20713
|
setCategory(key3);
|
|
20545
20714
|
if (["spot", "hip3", "outcome", "crypto", "tradfi", "preipo"].includes(key3)) setStrict(false);
|
|
20546
20715
|
} }, 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) => {
|
|
@@ -20615,8 +20784,8 @@ function atr14(rows) {
|
|
|
20615
20784
|
for (const item of ranges.slice(14)) value = (value * 13 + item) / 14;
|
|
20616
20785
|
return value;
|
|
20617
20786
|
}
|
|
20618
|
-
function evaluateStrategyStudies(candles, coin, interval, now = Date.now(),
|
|
20619
|
-
const say = (zh, en) =>
|
|
20787
|
+
function evaluateStrategyStudies(candles, coin, interval, now = Date.now(), english2 = false) {
|
|
20788
|
+
const say = (zh, en) => english2 ? en : zh;
|
|
20620
20789
|
const valid = candles.filter((row) => Number.isFinite(row.t) && Number.isFinite(row.c) && row.c > 0 && Number.isFinite(row.h) && Number.isFinite(row.l) && row.h >= row.l && Number.isFinite(row.v));
|
|
20621
20790
|
const rows = [...new Map(valid.map((row) => [row.t, row])).values()].sort((a, b) => a.t - b.t).filter((row) => closedAt(row, interval) <= now);
|
|
20622
20791
|
const latest = rows.at(-1);
|
|
@@ -20712,8 +20881,8 @@ var STATE_LABELS = {
|
|
|
20712
20881
|
};
|
|
20713
20882
|
function StrategyMenu({ candles, coin, interval }) {
|
|
20714
20883
|
const menu = React22.useRef(null);
|
|
20715
|
-
const
|
|
20716
|
-
const studies = React22.useMemo(() => evaluateStrategyStudies(candles, coin, interval, Date.now(),
|
|
20884
|
+
const english2 = tr("zh", "en") === "en";
|
|
20885
|
+
const studies = React22.useMemo(() => evaluateStrategyStudies(candles, coin, interval, Date.now(), english2), [candles, coin, interval, english2]);
|
|
20717
20886
|
return /* @__PURE__ */ React22.createElement("details", { className: "dhl-strategy-menu", ref: menu }, /* @__PURE__ */ React22.createElement("summary", null, tr("\u7B56\u7565\u6846\u67B6", "Strategies"), /* @__PURE__ */ React22.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React22.createElement("div", { className: "dhl-strategy-popover", role: "group", "aria-label": tr("\u4EA4\u6613\u7B56\u7565\u7814\u7A76", "Trading strategy studies") }, /* @__PURE__ */ React22.createElement("header", null, /* @__PURE__ */ React22.createElement("strong", null, tr("\u7B56\u7565\u7814\u7A76 \xB7 \u5DF2\u6536\u76D8 K \u7EBF", "Strategy studies \xB7 closed candles")), /* @__PURE__ */ React22.createElement("small", null, coin, " \xB7 ", interval)), /* @__PURE__ */ React22.createElement("p", { className: "dhl-strategy-caveat" }, tr(
|
|
20718
20887
|
"\u8FD9\u4E9B\u6761\u4EF6\u7528\u4E8E\u7EC4\u7EC7\u5206\u6790\uFF0C\u672A\u5728\u672C\u5E02\u573A\u548C\u5468\u671F\u9A8C\u8BC1\u6536\u76CA\uFF1B\u9700\u8981\u6838\u5BF9\u66F4\u9AD8\u5468\u671F\u3001\u76D8\u53E3\u3001\u8D44\u91D1\u8D39\u548C\u4EA4\u6613\u6210\u672C\u3002",
|
|
20719
20888
|
"These conditions structure analysis; returns are unverified for this market and interval. Check higher timeframe, book, funding and costs."
|
|
@@ -20803,7 +20972,7 @@ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
|
|
|
20803
20972
|
const [name, setName] = React23.useState("");
|
|
20804
20973
|
const [error, setError] = React23.useState("");
|
|
20805
20974
|
const [busy, setBusy] = React23.useState(false);
|
|
20806
|
-
const populate = (value) => setDraft(Object.fromEntries(Object.entries(normalizeIndicatorSettings(value)).map(([section,
|
|
20975
|
+
const populate = (value) => setDraft(Object.fromEntries(Object.entries(normalizeIndicatorSettings(value)).map(([section, fields2]) => [section, Object.fromEntries(Object.entries(fields2).map(([key3, n2]) => [key3, String(n2)]))])));
|
|
20807
20976
|
React23.useEffect(() => {
|
|
20808
20977
|
if (!open) return;
|
|
20809
20978
|
populate(settings);
|
|
@@ -20822,7 +20991,7 @@ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
|
|
|
20822
20991
|
cancelled = true;
|
|
20823
20992
|
};
|
|
20824
20993
|
}, [open]);
|
|
20825
|
-
const values = () => normalizeIndicatorSettings(Object.fromEntries(Object.entries(draft).map(([section,
|
|
20994
|
+
const values = () => normalizeIndicatorSettings(Object.fromEntries(Object.entries(draft).map(([section, fields2]) => [section, Object.fromEntries(Object.entries(fields2).map(([key3, n2]) => [key3, n2.trim() ? Number(n2) : NaN]))])));
|
|
20826
20995
|
const apply2 = () => {
|
|
20827
20996
|
try {
|
|
20828
20997
|
onApply({ indicatorSettings: values(), indicators: active, maLines: lines });
|
|
@@ -20871,7 +21040,7 @@ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
|
|
|
20871
21040
|
setTemplates(result.templates);
|
|
20872
21041
|
setSelected("");
|
|
20873
21042
|
}).catch((cause) => setError(String(cause.message))).finally(() => setBusy(false));
|
|
20874
|
-
} }, tr("\u5220\u9664\u6A21\u677F", "Delete")))), /* @__PURE__ */ React23.createElement("div", { className: "dhl-study-parameter-grid" }, SECTIONS.map(([section, title,
|
|
21043
|
+
} }, tr("\u5220\u9664\u6A21\u677F", "Delete")))), /* @__PURE__ */ React23.createElement("div", { className: "dhl-study-parameter-grid" }, SECTIONS.map(([section, title, fields2]) => /* @__PURE__ */ React23.createElement("fieldset", { key: section }, /* @__PURE__ */ React23.createElement("legend", null, title), fields2.map(([key3, zh, en]) => /* @__PURE__ */ React23.createElement("label", { key: key3 }, tr(zh, en), /* @__PURE__ */ React23.createElement(import_dsh_client_ui_primitives4.Input, { type: "number", step: key3 === "multiplier" ? "any" : 1, "aria-label": `${title} ${tr(zh, en)}`, value: draft[section]?.[key3] ?? "", onChange: (event) => setDraft((current2) => ({ ...current2, [section]: { ...current2[section], [key3]: event.target.value } })) })))))), error ? /* @__PURE__ */ React23.createElement("p", { className: "dhl-inline-error", role: "alert" }, error) : null)
|
|
20875
21044
|
));
|
|
20876
21045
|
}
|
|
20877
21046
|
|
|
@@ -20885,7 +21054,7 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20885
21054
|
const [state, setState] = React24.useState({ scope: "", candles: [], error: "", older: "idle", at: 0 });
|
|
20886
21055
|
const currentScope = React24.useRef(scope);
|
|
20887
21056
|
currentScope.current = scope;
|
|
20888
|
-
const
|
|
21057
|
+
const feed2 = state.scope === scope ? state : { scope, candles: [], error: "", older: "idle", at: 0 };
|
|
20889
21058
|
React24.useEffect(() => {
|
|
20890
21059
|
let alive = true, busy = false;
|
|
20891
21060
|
setState({ scope, candles: [], error: "", older: "idle", at: 0 });
|
|
@@ -20910,11 +21079,11 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20910
21079
|
}, [scope]);
|
|
20911
21080
|
const olderBusy = React24.useRef(false);
|
|
20912
21081
|
const needOlder = async () => {
|
|
20913
|
-
if (olderBusy.current ||
|
|
21082
|
+
if (olderBusy.current || feed2.older !== "idle" || !feed2.candles.length) return;
|
|
20914
21083
|
olderBusy.current = true;
|
|
20915
21084
|
setState((old) => ({ ...old, older: "loading" }));
|
|
20916
21085
|
try {
|
|
20917
|
-
const first =
|
|
21086
|
+
const first = feed2.candles[0].t;
|
|
20918
21087
|
const result = await hyperCall("candles", { coin, interval, endMs: first - 1, startMs: first - durations[RESEARCH_INTERVALS.indexOf(interval)] * 500 });
|
|
20919
21088
|
if (currentScope.current === scope) setState((old) => {
|
|
20920
21089
|
const rows = merge2(result.candles, old.candles);
|
|
@@ -20927,12 +21096,12 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20927
21096
|
}
|
|
20928
21097
|
};
|
|
20929
21098
|
const layers = { orders: true, fills: true, reasons: false, ...s.workspace?.chartLayers };
|
|
20930
|
-
const events = React24.useMemo(() => chartTradeEvents(
|
|
20931
|
-
return /* @__PURE__ */ React24.createElement("section", { className: "dhl-research-pane", "aria-label": `${coin} ${interval} ${tr("\u7814\u7A76\u56FE\u8868", "research chart")}` }, /* @__PURE__ */ React24.createElement("header", null, /* @__PURE__ */ React24.createElement("b", null, coin), /* @__PURE__ */ React24.createElement(Select, { "aria-label": tr("\u7814\u7A76\u5468\u671F", "Research interval"), value: interval, onChange: (e) => onInterval(e.target.value) }, RESEARCH_INTERVALS.map((iv) => /* @__PURE__ */ React24.createElement("option", { key: iv, value: iv }, iv))), /* @__PURE__ */ React24.createElement("small", null,
|
|
21099
|
+
const events = React24.useMemo(() => chartTradeEvents(feed2.candles, coin, layers.orders ? s.tradeHistory?.orders ?? [] : [], layers.fills ? s.tradeHistory?.fills ?? [] : []), [feed2.candles, s.tradeHistory, layers.orders, layers.fills, coin]);
|
|
21100
|
+
return /* @__PURE__ */ React24.createElement("section", { className: "dhl-research-pane", "aria-label": `${coin} ${interval} ${tr("\u7814\u7A76\u56FE\u8868", "research chart")}` }, /* @__PURE__ */ React24.createElement("header", null, /* @__PURE__ */ React24.createElement("b", null, coin), /* @__PURE__ */ React24.createElement(Select, { "aria-label": tr("\u7814\u7A76\u5468\u671F", "Research interval"), value: interval, onChange: (e) => onInterval(e.target.value) }, RESEARCH_INTERVALS.map((iv) => /* @__PURE__ */ React24.createElement("option", { key: iv, value: iv }, iv))), /* @__PURE__ */ React24.createElement("small", null, feed2.at ? `${tr("\u66F4\u65B0", "Updated")} ${new Date(feed2.at).toLocaleTimeString()}` : tr("\u52A0\u8F7D\u4E2D\u2026", "Loading\u2026"))), /* @__PURE__ */ React24.createElement(
|
|
20932
21101
|
CandleChart,
|
|
20933
21102
|
{
|
|
20934
21103
|
key: scope,
|
|
20935
|
-
candles:
|
|
21104
|
+
candles: feed2.candles,
|
|
20936
21105
|
marks,
|
|
20937
21106
|
indicators,
|
|
20938
21107
|
expanded: false,
|
|
@@ -20953,7 +21122,7 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20953
21122
|
},
|
|
20954
21123
|
onSelect: (selection) => insertChartReference(selection, coin, interval),
|
|
20955
21124
|
onNeedOlder: () => void needOlder(),
|
|
20956
|
-
olderState:
|
|
21125
|
+
olderState: feed2.older,
|
|
20957
21126
|
tradeEvents: events,
|
|
20958
21127
|
showReasons: layers.fills && layers.reasons,
|
|
20959
21128
|
onAnalyzeFill,
|
|
@@ -20968,7 +21137,7 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20968
21137
|
selectRequest: null,
|
|
20969
21138
|
focus: null
|
|
20970
21139
|
}
|
|
20971
|
-
),
|
|
21140
|
+
), feed2.error ? /* @__PURE__ */ React24.createElement("button", { type: "button", className: "dhl-inline-error", onClick: () => setState((old) => ({ ...old, older: "idle" })) }, feed2.error, " \xB7 ", tr("\u81EA\u52A8\u91CD\u8BD5\u4E2D\uFF1B\u70B9\u51FB\u91CD\u8BD5\u5386\u53F2", "Retrying; click to retry history")) : null);
|
|
20972
21141
|
}
|
|
20973
21142
|
|
|
20974
21143
|
// lib/workspace-settings.js
|
|
@@ -21180,12 +21349,22 @@ function DeskColumn({ onCollapse }) {
|
|
|
21180
21349
|
}, []);
|
|
21181
21350
|
const editMark = React26.useCallback((id, patch) => historyRef.current.edit(id, patch), []);
|
|
21182
21351
|
const erase = React26.useCallback((id) => historyRef.current.remove(id), []);
|
|
21352
|
+
const selectMark = React26.useCallback((id) => setSelectRequest({ id, at: Date.now() }), []);
|
|
21183
21353
|
const cloneMark = React26.useCallback((copy) => {
|
|
21184
21354
|
void historyRef.current.create({ kind: copy.kind, label: copy.label, ...copy.geometry, ...copy.style });
|
|
21185
21355
|
}, []);
|
|
21356
|
+
const onSecondaryChart = React26.useRef(false);
|
|
21357
|
+
React26.useEffect(() => {
|
|
21358
|
+
const track = (event) => {
|
|
21359
|
+
const target = event.target;
|
|
21360
|
+
onSecondaryChart.current = Boolean(target?.closest?.(".dhl-research-pane") && !target.closest(".dhl-primary-pane"));
|
|
21361
|
+
};
|
|
21362
|
+
window.addEventListener("pointerdown", track, true);
|
|
21363
|
+
return () => window.removeEventListener("pointerdown", track, true);
|
|
21364
|
+
}, []);
|
|
21186
21365
|
React26.useEffect(() => {
|
|
21187
21366
|
const onKey = (event) => {
|
|
21188
|
-
if (!(event.metaKey || event.ctrlKey) || event.altKey) return;
|
|
21367
|
+
if (!(event.metaKey || event.ctrlKey) || event.altKey || onSecondaryChart.current) return;
|
|
21189
21368
|
const element = event.target;
|
|
21190
21369
|
if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
|
|
21191
21370
|
const key3 = event.key.toLowerCase();
|
|
@@ -21225,7 +21404,7 @@ function DeskColumn({ onCollapse }) {
|
|
|
21225
21404
|
React26.useEffect(() => {
|
|
21226
21405
|
const keys = { KeyT: "trend", KeyH: "level", KeyJ: "hray", KeyV: "vline", KeyC: "crossline", KeyF: "fib" };
|
|
21227
21406
|
const onKey = (event) => {
|
|
21228
|
-
if (!event.altKey || event.ctrlKey || event.metaKey || chartTab !== "chart") return;
|
|
21407
|
+
if (!event.altKey || event.ctrlKey || event.metaKey || chartTab !== "chart" || onSecondaryChart.current) return;
|
|
21229
21408
|
const element = event.target;
|
|
21230
21409
|
if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
|
|
21231
21410
|
const kind = event.shiftKey ? event.code === "KeyR" ? "rect" : void 0 : keys[event.code];
|
|
@@ -21421,9 +21600,10 @@ function DeskColumn({ onCollapse }) {
|
|
|
21421
21600
|
{
|
|
21422
21601
|
marks,
|
|
21423
21602
|
locked: drawingsLocked,
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21603
|
+
language: tr("zh", "en"),
|
|
21604
|
+
onSelect: selectMark,
|
|
21605
|
+
onEdit: editMark,
|
|
21606
|
+
onRemove: erase
|
|
21427
21607
|
}
|
|
21428
21608
|
) : null, /* @__PURE__ */ React26.createElement(
|
|
21429
21609
|
ChartNavigation,
|
|
@@ -21638,8 +21818,8 @@ var React28 = __toESM(require("react"), 1);
|
|
|
21638
21818
|
function TradeTicket({ onSubmitted }) {
|
|
21639
21819
|
const s = useStore();
|
|
21640
21820
|
const coin = s.activeCoin;
|
|
21641
|
-
const market = s.markets.find((row) => row.name === coin);
|
|
21642
21821
|
const catalogMarket = s.marketCatalog.find((row) => row.name === coin);
|
|
21822
|
+
const market = s.markets.find((row) => row.name === coin) ?? (catalogMarket?.tradable ? catalogMarket : void 0);
|
|
21643
21823
|
const liveMid = s.snapshot?.coin === coin && Date.now() - Number(s.snapshot?.observedAt?.mids) < 1e4 ? Number(s.snapshot?.mids?.mids?.[coin] ?? s.snapshot?.mids?.[coin]) : 0;
|
|
21644
21824
|
const mark = liveMid || Number(s.selectedMarket?.name === coin ? s.selectedMarket.ctx?.markPx : 0) || Number(market?.ctx?.markPx);
|
|
21645
21825
|
const position = s.account?.positions?.find((p) => p.coin === coin);
|
|
@@ -21874,20 +22054,34 @@ function ChatColumn({ width, fill = false, onCollapse, SessionProvider, renderFa
|
|
|
21874
22054
|
requestAnimationFrame(() => input.focus?.());
|
|
21875
22055
|
}
|
|
21876
22056
|
}, [activeId, readyRef, showTicket, s.chartReferences]);
|
|
21877
|
-
const
|
|
21878
|
-
|
|
21879
|
-
|
|
21880
|
-
|
|
21881
|
-
|
|
21882
|
-
|
|
21883
|
-
|
|
21884
|
-
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
|
|
22057
|
+
const QUICK = [
|
|
22058
|
+
[
|
|
22059
|
+
"\u4EA4\u6613\u4F53\u68C0",
|
|
22060
|
+
"Trading check-up",
|
|
22061
|
+
"\u8BF7\u7528 hyper_account action=analyze \u7ED9\u6211\u505A\u4E00\u6B21\u4EA4\u6613\u4F53\u68C0\uFF08\u8FD1 90 \u5929\uFF09\uFF1A\u5148\u8BB2\u6700\u503C\u94B1\u7684 1\u20132 \u4E2A\u53D1\u73B0\u548C\u5EFA\u8BAE\u89C4\u5219\uFF0C\u518D\u95EE\u6211\u662F\u5426\u91C7\u7528\u3002\u4E0D\u8981\u4E0B\u5355\u3002",
|
|
22062
|
+
"Run a trading check-up with hyper_account action=analyze (last 90 days): lead with the 1\u20132 most valuable findings and suggested rules, then ask whether I want to adopt them. Do not trade."
|
|
22063
|
+
],
|
|
22064
|
+
[
|
|
22065
|
+
"\u4ECA\u5929\u6709\u4EC0\u4E48\u673A\u4F1A",
|
|
22066
|
+
"Today's opportunities",
|
|
22067
|
+
"\u8BF7\u7528 hyper_market action=scan \u770B\u770B\u4ECA\u5929\u7684\u673A\u4F1A\uFF0C\u7ED3\u5408\u6211\u7684\u6863\u6848\u6311 1 \u4E2A\u6700\u503C\u5F97\u5173\u6CE8\u7684\uFF0C\u7528 hyper_market candles/indicators \u6838\u5B9E\uFF0C\u518D\u7528 hyper_chart \u753B\u51FA\u5173\u952E\u4F4D\u548C\u5934\u5BF8\u6846\uFF0C\u6309\u4EA4\u6613\u5361\u683C\u5F0F\u7ED9\u51FA\u8BA1\u5212\u3002\u4E0D\u8981\u4E0B\u5355\u3002",
|
|
22068
|
+
"Use hyper_market action=scan for today's opportunities, pick the one that best fits my profile, verify it with hyper_market candles/indicators, draw the levels and a position box with hyper_chart, and give the plan as a trade card. Do not trade."
|
|
22069
|
+
],
|
|
22070
|
+
[
|
|
22071
|
+
"\u5E2E\u6211\u76EF\u7740\u4ED3\u4F4D",
|
|
22072
|
+
"Watch my positions",
|
|
22073
|
+
"\u8BF7\u7528 hyper_context \u68C0\u67E5\u6211\u7684\u4ED3\u4F4D\u548C\u6302\u5355\uFF1A\u6BCF\u4E2A\u4ED3\u4F4D\u662F\u5426\u6709\u8DB3\u91CF\u6B62\u635F\u3001\u79BB\u6B62\u635F\u548C\u76EE\u6807\u591A\u8FDC\u3001\u8D44\u91D1\u8D39\u6210\u672C\uFF1B\u5728\u5173\u952E\u4EF7\u4F4D\u7528 hyper_chart \u8BBE\u4EF7\u683C\u63D0\u9192\uFF08alert=true\uFF09\uFF1B\u9700\u8981\u5B9A\u671F\u68C0\u67E5\u7684\u8BDD\u5148\u95EE\u6211\u4E00\u6B21\uFF0C\u518D\u7528 schedule_create \u5B89\u6392\u3002\u4E0D\u8981\u4E0B\u5355\u6216\u64A4\u5355\u3002",
|
|
22074
|
+
"Use hyper_context to check my positions and orders: is each one covered by a stop, how far are stop and target, what does funding cost; set price alerts (alert=true) at key levels with hyper_chart; if periodic checks would help, ask me once, then use schedule_create. Do not place or cancel orders."
|
|
22075
|
+
],
|
|
22076
|
+
[
|
|
22077
|
+
"\u628A\u56FE\u4E0A\u753B\u7EBF\u53D8\u6210\u7B56\u7565",
|
|
22078
|
+
"Turn my drawings into a strategy",
|
|
22079
|
+
`\u8BF7\u7528 hyper_chart action=list \u8BFB\u53D6 ${s.activeCoin} \u56FE\u4E0A\u7684\u753B\u7EBF\uFF0C\u7ED3\u5408 hyper_market candles \u7406\u89E3\u6211\u7684\u601D\u8DEF\uFF0C\u6574\u7406\u6210\u4E00\u6761\u53EF\u6838\u5BF9\u7684\u7B56\u7565\uFF08\u4F9D\u636E\u3001\u5165\u573A\u3001\u79BB\u573A\u3001\u4F5C\u5E9F\u3001\u4ED3\u4F4D\uFF09\uFF0C\u7528 hyper_journal action=strategy \u5B58\u4E3A idea\uFF0C\u5E76\u8BF4\u660E\u8FD8\u9700\u8981\u9A8C\u8BC1\u4EC0\u4E48\u3002\u4E0D\u8981\u4E0B\u5355\u3002`,
|
|
22080
|
+
`Read my drawings on ${s.activeCoin} with hyper_chart action=list, use hyper_market candles to understand the idea, write it up as a checkable strategy (thesis, entry, exit, invalidation, sizing), save it with hyper_journal action=strategy as an idea, and say what still needs verifying. Do not trade.`
|
|
22081
|
+
]
|
|
22082
|
+
];
|
|
21889
22083
|
const content = !activeId ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-empty" }, t("chat.noSession", "\u9009\u62E9\u5DE6\u4FA7\u4EA4\u6613\u4F1A\u8BDD\uFF0C\u5728\u8FD9\u91CC\u76F4\u63A5\u4E0E\u4EA4\u6613 agent \u5BF9\u8BDD\u3002")) : bindError ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-inline-error", role: "alert" }, t("chat.bindError", "\u4F1A\u8BDD\u7ED1\u5B9A\u5931\u8D25\uFF1A") + bindError) : legacy ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-unavailable", role: "alert" }, t("chat.upgrade", "\u5F53\u524D DSH \u7248\u672C\u4E0D\u652F\u6301\u5D4C\u5165\u539F\u751F\u4F1A\u8BDD\uFF1B\u8BF7\u4F7F\u7528 DSH 0.2 \u6216\u66F4\u65B0\u7248\u672C\u3002")) : !readyRef ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-empty" }, t("chat.linking", "\u6B63\u5728\u63A5\u5165\u4F1A\u8BDD\u2026")) : !SessionProvider || !renderFactorySlot ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-inline-error", role: "alert" }, t("chat.noFactory", "DSH \u539F\u751F\u4F1A\u8BDD\u7EC4\u4EF6\u4E0D\u53EF\u7528\u3002")) : /* @__PURE__ */ React29.createElement("div", { className: "dhl-chatmount", "data-trading-session": activeId }, /* @__PURE__ */ React29.createElement(SessionProvider, { session: readyRef }, renderFactorySlot("conversation.content", { variant: "embedded", phase: "active", hero: false }, { slots: { views: FixedChatView } })));
|
|
21890
|
-
return /* @__PURE__ */ React29.createElement("div", { id: "dhl-panel-chat", className: "dhl-col-chat", style: fill ? { flex: "1 1 0", width: "auto" } : width ? { width } : void 0 }, /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-header" }, /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-heading" }, /* @__PURE__ */ React29.createElement("strong", null, t("chat.title", "\u4EA4\u6613\u4F1A\u8BDD")), activeId ? /* @__PURE__ */ React29.createElement("span", { title }, title) : null), /* @__PURE__ */ React29.createElement("button", { type: "button", className: "dhl-chat-ticket-toggle", "aria-expanded": showTicket, onClick: () => setShowTicket((value) => !value) }, showTicket ? t("chat.back", "\u8FD4\u56DE\u4F1A\u8BDD") : t("chat.ticket", "\u4EA4\u6613\u7968\u636E")), /* @__PURE__ */ React29.createElement("button", { className: "dhl-panel-iconbtn", type: "button", onClick: onCollapse, "aria-label": tr("\u6536\u8D77\u5BF9\u8BDD\u680F", "Collapse chat"), title: tr("\u6536\u8D77\u5BF9\u8BDD\u680F", "Collapse chat") }, /* @__PURE__ */ React29.createElement(FoldIcon, { direction: "right" }))), activeId && !showTicket && !legacy ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-prompts", role: "group", "aria-label": t("chat.prompts", "\u5FEB\u6377\u63D0\u95EE") },
|
|
22084
|
+
return /* @__PURE__ */ React29.createElement("div", { id: "dhl-panel-chat", className: "dhl-col-chat", style: fill ? { flex: "1 1 0", width: "auto" } : width ? { width } : void 0 }, /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-header" }, /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-heading" }, /* @__PURE__ */ React29.createElement("strong", null, t("chat.title", "\u4EA4\u6613\u4F1A\u8BDD")), activeId ? /* @__PURE__ */ React29.createElement("span", { title }, title) : null), /* @__PURE__ */ React29.createElement("button", { type: "button", className: "dhl-chat-ticket-toggle", "aria-expanded": showTicket, onClick: () => setShowTicket((value) => !value) }, showTicket ? t("chat.back", "\u8FD4\u56DE\u4F1A\u8BDD") : t("chat.ticket", "\u4EA4\u6613\u7968\u636E")), /* @__PURE__ */ React29.createElement("button", { className: "dhl-panel-iconbtn", type: "button", onClick: onCollapse, "aria-label": tr("\u6536\u8D77\u5BF9\u8BDD\u680F", "Collapse chat"), title: tr("\u6536\u8D77\u5BF9\u8BDD\u680F", "Collapse chat") }, /* @__PURE__ */ React29.createElement(FoldIcon, { direction: "right" }))), activeId && !showTicket && !legacy ? /* @__PURE__ */ React29.createElement("div", { className: "dhl-chat-prompts", role: "group", "aria-label": t("chat.prompts", "\u5FEB\u6377\u63D0\u95EE") }, QUICK.map(([zh, en, askZh, askEn]) => /* @__PURE__ */ React29.createElement("button", { key: zh, type: "button", onClick: () => queueToAgent(tr(askZh, askEn)) }, tr(zh, en)))) : null, showTicket ? /* @__PURE__ */ React29.createElement(TradeTicket, { key: activeId ?? "anonymous", onSubmitted: () => setShowTicket(false) }) : content);
|
|
21891
22085
|
}
|
|
21892
22086
|
|
|
21893
22087
|
// src/client/components/wallet-details.tsx
|
|
@@ -22015,22 +22209,60 @@ function TradingLimits({ open, onClose }) {
|
|
|
22015
22209
|
setBusy(false);
|
|
22016
22210
|
}
|
|
22017
22211
|
};
|
|
22018
|
-
const
|
|
22019
|
-
const
|
|
22020
|
-
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
|
|
22212
|
+
const [tab, setTab] = React31.useState("limits");
|
|
22213
|
+
const [wallet, setWallet] = React31.useState({ signer: "local", web3Account: "main", tradingAddress: "" });
|
|
22214
|
+
const [web3Accounts, setWeb3Accounts] = React31.useState({ available: false, accounts: [] });
|
|
22215
|
+
React31.useEffect(() => {
|
|
22216
|
+
if (!open) return;
|
|
22217
|
+
setWallet({ signer: st?.signer ?? "local", web3Account: st?.web3Account ?? "main", tradingAddress: st?.tradingAddress ?? "" });
|
|
22218
|
+
void hyperCall("walletAccounts").then(setWeb3Accounts).catch(() => setWeb3Accounts({ available: false, accounts: [] }));
|
|
22219
|
+
}, [open]);
|
|
22220
|
+
const saveWallet = () => {
|
|
22221
|
+
setBusy(true);
|
|
22222
|
+
setError("");
|
|
22223
|
+
void hyperCall("updateWalletSettings", wallet).then(() => resetWalletView()).then(() => notify(tr("\u94B1\u5305\u8BBE\u7F6E\u5DF2\u751F\u6548", "Wallet settings applied"))).catch((cause) => setError(String(cause.message))).finally(() => setBusy(false));
|
|
22224
|
+
};
|
|
22225
|
+
React31.useEffect(() => {
|
|
22226
|
+
if (open) setTab("limits");
|
|
22227
|
+
}, [open]);
|
|
22228
|
+
const saveCosts = () => {
|
|
22024
22229
|
setBusy(true);
|
|
22025
22230
|
setError("");
|
|
22026
22231
|
void hyperCall("updatePaperCosts", { costs: { feeBps: costs.feeBps.trim() ? Number(costs.feeBps) : NaN, slippageBps: costs.slippageBps.trim() ? Number(costs.slippageBps) : NaN } }).then(() => loadStatus()).then(() => notify(tr("\u6A21\u62DF\u6210\u672C\u5DF2\u4FDD\u5B58", "Paper costs saved"))).catch((cause) => setError(String(cause.message))).finally(() => setBusy(false));
|
|
22027
|
-
}
|
|
22028
|
-
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
|
|
22032
|
-
"
|
|
22033
|
-
|
|
22232
|
+
};
|
|
22233
|
+
const field = (key3, label, props, wide = false) => /* @__PURE__ */ React31.createElement("label", { className: wide ? "dhl-risk-wide" : void 0 }, /* @__PURE__ */ React31.createElement("span", null, label), /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Input, { className: "dhl-risk-input", value: draft[key3], onChange: (event) => change(key3, event.target.value), required: true, ...props }));
|
|
22234
|
+
const footer = tab === "limits" ? limits ? /* @__PURE__ */ React31.createElement(React31.Fragment, null, st?.riskSettingsSource === "desk" ? /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "outline", disabled: busy, onClick: () => void reset() }, tr("\u6062\u590D\u63D2\u4EF6\u914D\u7F6E", "Restore plugin config")) : null, /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "primary", type: "submit", form: "dhl-risk-form", disabled: busy }, busy ? tr("\u4FDD\u5B58\u4E2D\u2026", "Saving\u2026") : tr("\u4FDD\u5B58\u9650\u989D", "Save limits"))) : null : tab === "costs" ? /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "primary", disabled: busy, onClick: saveCosts }, busy ? tr("\u4FDD\u5B58\u4E2D\u2026", "Saving\u2026") : tr("\u4FDD\u5B58\u6A21\u62DF\u6210\u672C", "Save paper costs")) : /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Button, { variant: "primary", disabled: busy, onClick: saveWallet }, busy ? tr("\u8FDE\u63A5\u4E2D\u2026", "Connecting\u2026") : tr("\u4FDD\u5B58\u5E76\u8FDE\u63A5", "Save & connect"));
|
|
22235
|
+
const tabs = [["limits", "\u4EA4\u6613\u9650\u989D", "Limits"], ["costs", "\u6A21\u62DF\u6210\u672C", "Paper costs"], ["signing", "\u7B7E\u540D\u4E0E\u6258\u7BA1", "Signing & custody"]];
|
|
22236
|
+
return /* @__PURE__ */ React31.createElement(import_dsh_client_ui_primitives9.Modal, { open, onClose, title: tr("\u4EA4\u6613\u9650\u989D\u4E0E\u7B7E\u540D\u89C4\u5219", "Trading limits & signing rules"), closeLabel: tr("\u5173\u95ED", "Close"), className: "dhl-limits-modal", contentClassName: "dhl-limits-content", footer }, /* @__PURE__ */ React31.createElement("div", { className: "dhl-limits-tabs", role: "tablist", "aria-label": tr("\u8BBE\u7F6E\u5206\u7EC4", "Settings sections") }, tabs.map(([key3, zh, en]) => /* @__PURE__ */ React31.createElement(
|
|
22237
|
+
"button",
|
|
22238
|
+
{
|
|
22239
|
+
key: key3,
|
|
22240
|
+
type: "button",
|
|
22241
|
+
role: "tab",
|
|
22242
|
+
id: `dhl-limits-tab-${key3}`,
|
|
22243
|
+
"aria-controls": `dhl-limits-panel-${key3}`,
|
|
22244
|
+
"aria-selected": tab === key3,
|
|
22245
|
+
className: tab === key3 ? "on" : "",
|
|
22246
|
+
onClick: () => {
|
|
22247
|
+
setTab(key3);
|
|
22248
|
+
setError("");
|
|
22249
|
+
}
|
|
22250
|
+
},
|
|
22251
|
+
tr(zh, en)
|
|
22252
|
+
))), /* @__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) }, field("allowedCoins", tr("\u5141\u8BB8\u5F00\u4ED3\u5E01\u5BF9\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u5982 BTC, xyz:XYZ100\uFF09", "Coins allowed to open (comma separated)"), { placeholder: "BTC, ETH, xyz:XYZ100", maxLength: 1e3 }, true), 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(
|
|
22253
|
+
"\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",
|
|
22254
|
+
"Applies to paper and live at once and survives restart; each mode has its own daily budget; reducing and closing are never blocked."
|
|
22255
|
+
))) : 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(
|
|
22256
|
+
import_dsh_client_ui_primitives9.Input,
|
|
22257
|
+
{
|
|
22258
|
+
"aria-label": tr("\u4E3B\u8D26\u6237\u5730\u5740", "Main account address"),
|
|
22259
|
+
placeholder: "0x\u2026",
|
|
22260
|
+
value: wallet.tradingAddress,
|
|
22261
|
+
maxLength: 42,
|
|
22262
|
+
spellCheck: false,
|
|
22263
|
+
onChange: (event) => setWallet((old) => ({ ...old, tradingAddress: event.target.value.trim() }))
|
|
22264
|
+
}
|
|
22265
|
+
))), wallet.signer === "web3" && !web3Accounts.available ? /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr("\u6CA1\u6709\u68C0\u6D4B\u5230 dsh-web3 \u94B1\u5305\u63D2\u4EF6\u3002", "The dsh-web3 wallet plugin is not available.")) : null, /* @__PURE__ */ React31.createElement("p", { className: "dhl-limits-note" }, tr("\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF0C\u65E0\u9700\u91CD\u542F\uFF1B\u79C1\u94A5\u4ECD\u53EA\u5728\u63D2\u4EF6\u914D\u7F6E\u91CC\u7BA1\u7406\u3002\u65B0\u5EFA\u7684 dsh-web3 \u8D26\u6237\u82E5\u4E0D\u5728\u5217\u8868\u4E2D\uFF0C\u9700\u91CD\u542F DSH \u5237\u65B0\u94B1\u5305\u3002", "Applies immediately, no restart; the private key stays in plugin config. A dsh-web3 account created elsewhere may need a DSH restart to appear.")), /* @__PURE__ */ React31.createElement("dl", { className: "dhl-limits-facts" }, /* @__PURE__ */ React31.createElement("dt", null, tr("\u5F53\u524D", "Current")), /* @__PURE__ */ React31.createElement("dd", null, paper ? tr("\u6A21\u62DF\u76D8\u672C\u5730\u64AE\u5408\uFF0C\u4E0D\u7B7E\u540D\u3002", "Paper fills locally without signing.") : st?.signer === "web3" ? tr("dsh-web3 \u94B1\u5305\uFF0C\u6309\u5176\u7B7E\u540D\u7B56\u7565\u5904\u7406\uFF08DSH \u8BBE\u7F6E \u2192 Web3 \u94B1\u5305\uFF09\u3002", "dsh-web3 wallet, under its signing policy (DSH Settings \u2192 Web3 wallet).") : tr("\u63D2\u4EF6\u914D\u7F6E\u4E2D\u7684\u79C1\u94A5\uFF08DSH \u63D2\u4EF6\u914D\u7F6E\uFF09\u3002", "Private key from plugin configuration.")), /* @__PURE__ */ React31.createElement("dt", null, tr("\u6258\u7BA1\u6A21\u5F0F", "Custody")), /* @__PURE__ */ React31.createElement("dd", null, st?.custody === "managed" ? tr("\u5168\u6258\u7BA1\uFF1Aagent \u5728\u9650\u989D\u5185\u76F4\u63A5\u6267\u884C\uFF0C\u4E5F\u53EF\u8C03\u6574\u8FD9\u4E9B\u9650\u989D\u5E76\u5212\u8F6C HIP-3 \u4FDD\u8BC1\u91D1\uFF0C\u6BCF\u6B21\u90FD\u4F1A\u5728\u4F1A\u8BDD\u91CC\u8BF4\u660E\u3002", "Managed: the agent trades within limits, may adjust them and move HIP-3 margin, and says so each time.") : tr("\u534A\u6258\u7BA1\uFF1Aagent \u6BCF\u7B14\u5B9E\u76D8\u4E0B\u5355\u3001\u6539\u6760\u6746\u548C\u5212\u8F6C\u90FD\u9700\u8981\u4F60\u6279\u51C6\uFF0C\u9650\u989D\u53EA\u7531\u4F60\u4FEE\u6539\u3002", "Assisted: you approve each live order, leverage change and transfer; only you change limits."), /* @__PURE__ */ React31.createElement("span", { className: "dhl-limits-hint" }, tr("\u5728\u53F3\u4E0A\u89D2 \u8BBE\u7F6E \u2192 \u6258\u7BA1\u6A21\u5F0F \u5207\u6362\u3002", "Switch in Settings \u2192 Custody."))))) : null, error ? /* @__PURE__ */ React31.createElement("p", { className: "dhl-risk-error", role: "alert" }, error) : null));
|
|
22034
22266
|
}
|
|
22035
22267
|
|
|
22036
22268
|
// src/client/app.tsx
|
|
@@ -22043,6 +22275,22 @@ function App(props) {
|
|
|
22043
22275
|
const [walletOpen, setWalletOpen] = React32.useState(false);
|
|
22044
22276
|
const [moreOpen, setMoreOpen] = React32.useState(false);
|
|
22045
22277
|
const [limitsOpen, setLimitsOpen] = React32.useState(false);
|
|
22278
|
+
const [confirmManaged, setConfirmManaged] = React32.useState(false);
|
|
22279
|
+
const custody = st?.custody === "managed" ? "managed" : "assisted";
|
|
22280
|
+
const chooseCustody = React32.useCallback(async (next, acknowledge = false) => {
|
|
22281
|
+
setMoreOpen(false);
|
|
22282
|
+
if (next === "managed" && store.get().status?.tradingMode === "live" && !acknowledge) {
|
|
22283
|
+
setConfirmManaged(true);
|
|
22284
|
+
return;
|
|
22285
|
+
}
|
|
22286
|
+
try {
|
|
22287
|
+
await setCustody(next, acknowledge);
|
|
22288
|
+
} catch (error) {
|
|
22289
|
+
notify(tr("\u5207\u6362\u6258\u7BA1\u6A21\u5F0F\u5931\u8D25", "Could not switch custody") + "\uFF1A" + String(error?.message ?? error));
|
|
22290
|
+
} finally {
|
|
22291
|
+
setConfirmManaged(false);
|
|
22292
|
+
}
|
|
22293
|
+
}, []);
|
|
22046
22294
|
const moreRef = React32.useRef(null);
|
|
22047
22295
|
const network = st?.network === "testnet" ? "testnet" : "mainnet";
|
|
22048
22296
|
const mode = st?.tradingMode === "paper" ? "paper" : "live";
|
|
@@ -22150,12 +22398,12 @@ function App(props) {
|
|
|
22150
22398
|
const showSessions = !layout.sessionsCollapsed;
|
|
22151
22399
|
const showDesk = !layout.deskCollapsed;
|
|
22152
22400
|
const showChat = !layout.chatCollapsed;
|
|
22153
|
-
return /* @__PURE__ */ React32.createElement("div", { className: "dhl-root" }, /* @__PURE__ */ React32.createElement("div", { className: "dhl-topbar" }, /* @__PURE__ */ React32.createElement("span", { className: "dhl-brand" }, /* @__PURE__ */ React32.createElement(BrandMark, null), "Hyperliquid ", /* @__PURE__ */ React32.createElement("span", { style: { fontWeight: 400, color: "var(--dsw-alias-label-secondary)" } }, t("app.suffix", "\u4EA4\u6613\u53F0"))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-spacer" }), s.statusError ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge err" }, t("status.unavailable", "\u8FDC\u7AEF\u4E0D\u53EF\u7528")) : null, st && network === "testnet" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge paper" }, t("network.testnet", "\u6D4B\u8BD5\u7F51")) : null, st && mode === "paper" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge paper" }, t("mode.paper", "\u6A21\u62DF\u76D8")) : null, wsBadge, /* @__PURE__ */ React32.createElement("button", { type: "button", className: "dhl-badge dhl-wallet-trigger", onClick: () => setWalletOpen(true), "aria-haspopup": "dialog", "aria-label": tr("\u67E5\u770B\u94B1\u5305\u8BE6\u60C5\u548C\u6536\u76CA", "View wallet and P&L") }, mode === "paper" ? tr("\u6A21\u62DF\u8D26\u6237", "Paper account") : st?.address ? st.address.slice(0, 6) + "\u2026" + st.address.slice(-4) : t("account.none", "\u672A\u8FDE\u63A5\u94B1\u5305"), /* @__PURE__ */ React32.createElement(UiIcon, { name: "chevronDown", size: 14 })), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more", ref: moreRef }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: "dhl-top-control dhl-more-trigger", "aria-haspopup": "menu", "aria-expanded": moreOpen, "aria-label": tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings"), title: tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings"), onClick: () => setMoreOpen((value) => !value) }, /* @__PURE__ */ React32.createElement(UiIcon, { name: "settings", size: 17 })), moreOpen ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-popover", role: "menu" }, /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, t("network.label", "\u7F51\u7EDC")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: network === "mainnet" ? "on" : "", disabled: networkBusy || !st, onClick: () => network !== "mainnet" && requestEnv({ network: "mainnet", mode }) }, t("network.mainnet", "\u4E3B\u7F51")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: network === "testnet" ? "on" : "", disabled: networkBusy || !st, onClick: () => network !== "testnet" && requestEnv({ network: "testnet", mode }) }, t("network.testnet", "\u6D4B\u8BD5\u7F51")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u8D26\u6237", "Account")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: mode === "live" ? "on" : "", disabled: networkBusy || !st, onClick: () => mode !== "live" && requestEnv({ network, mode: "live" }) }, t("mode.live", "\u5B9E\u76D8")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: mode === "paper" ? "on" : "", disabled: networkBusy || !st, onClick: () => mode !== "paper" && requestEnv({ network, mode: "paper" }) }, t("mode.paper", "\u6A21\u62DF\u76D8")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u76D8\u53E3 / \u6210\u4EA4\u4F4D\u7F6E", "Book / trades position")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: s.tapePlacement === "auto" ? "on" : "", onClick: () => setTapePlacement("auto") }, tr("\u81EA\u9002\u5E94", "Auto")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: s.tapePlacement === "below" ? "on" : "", onClick: () => setTapePlacement("below") }, tr("\u56FE\u8868\u4E0B\u65B9", "Below chart")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", onClick: () => {
|
|
22401
|
+
return /* @__PURE__ */ React32.createElement("div", { className: "dhl-root" }, /* @__PURE__ */ React32.createElement("div", { className: "dhl-topbar" }, /* @__PURE__ */ React32.createElement("span", { className: "dhl-brand" }, /* @__PURE__ */ React32.createElement(BrandMark, null), "Hyperliquid ", /* @__PURE__ */ React32.createElement("span", { style: { fontWeight: 400, color: "var(--dsw-alias-label-secondary)" } }, t("app.suffix", "\u4EA4\u6613\u53F0"))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-spacer" }), s.statusError ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge err" }, t("status.unavailable", "\u8FDC\u7AEF\u4E0D\u53EF\u7528")) : null, st && network === "testnet" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge paper" }, t("network.testnet", "\u6D4B\u8BD5\u7F51")) : null, st && mode === "paper" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge paper" }, t("mode.paper", "\u6A21\u62DF\u76D8")) : null, st && custody === "managed" ? /* @__PURE__ */ React32.createElement("span", { className: "dhl-badge managed", title: tr("\u5168\u6258\u7BA1\uFF1Aagent \u53EF\u81EA\u4E3B\u6267\u884C\u5B9E\u76D8\u3001\u8C03\u6574\u9650\u989D\u4E0E\u5212\u8F6C\u4FDD\u8BC1\u91D1", "Managed: the agent trades, adjusts limits and moves margin on its own") }, tr("\u5168\u6258\u7BA1", "Managed")) : null, wsBadge, /* @__PURE__ */ React32.createElement("button", { type: "button", className: "dhl-badge dhl-wallet-trigger", onClick: () => setWalletOpen(true), "aria-haspopup": "dialog", "aria-label": tr("\u67E5\u770B\u94B1\u5305\u8BE6\u60C5\u548C\u6536\u76CA", "View wallet and P&L") }, mode === "paper" ? tr("\u6A21\u62DF\u8D26\u6237", "Paper account") : st?.address ? st.address.slice(0, 6) + "\u2026" + st.address.slice(-4) : t("account.none", "\u672A\u8FDE\u63A5\u94B1\u5305"), /* @__PURE__ */ React32.createElement(UiIcon, { name: "chevronDown", size: 14 })), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more", ref: moreRef }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: "dhl-top-control dhl-more-trigger", "aria-haspopup": "menu", "aria-expanded": moreOpen, "aria-label": tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings"), title: tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings"), onClick: () => setMoreOpen((value) => !value) }, /* @__PURE__ */ React32.createElement(UiIcon, { name: "settings", size: 17 })), moreOpen ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-popover", role: "menu" }, /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, t("network.label", "\u7F51\u7EDC")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: network === "mainnet" ? "on" : "", disabled: networkBusy || !st, onClick: () => network !== "mainnet" && requestEnv({ network: "mainnet", mode }) }, t("network.mainnet", "\u4E3B\u7F51")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: network === "testnet" ? "on" : "", disabled: networkBusy || !st, onClick: () => network !== "testnet" && requestEnv({ network: "testnet", mode }) }, t("network.testnet", "\u6D4B\u8BD5\u7F51")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u8D26\u6237", "Account")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: mode === "live" ? "on" : "", disabled: networkBusy || !st, onClick: () => mode !== "live" && requestEnv({ network, mode: "live" }) }, t("mode.live", "\u5B9E\u76D8")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: mode === "paper" ? "on" : "", disabled: networkBusy || !st, onClick: () => mode !== "paper" && requestEnv({ network, mode: "paper" }) }, t("mode.paper", "\u6A21\u62DF\u76D8")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u6258\u7BA1\u6A21\u5F0F", "Custody")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment", role: "group", "aria-label": tr("\u6258\u7BA1\u6A21\u5F0F", "Custody") }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: custody === "assisted" ? "on" : "", "aria-pressed": custody === "assisted", disabled: !st, title: tr("\u5B9E\u76D8\u4E0B\u5355\u3001\u5212\u8F6C\u9700\u4F60\u6279\u51C6\uFF1B\u9650\u989D\u53EA\u7531\u4F60\u4FEE\u6539", "You approve live orders and transfers; only you change limits"), onClick: () => custody !== "assisted" && void chooseCustody("assisted") }, tr("\u534A\u6258\u7BA1", "Assisted")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: custody === "managed" ? "on" : "", "aria-pressed": custody === "managed", disabled: !st, title: tr("agent \u81EA\u4E3B\u6267\u884C\u5B9E\u76D8\u3001\u8C03\u6574\u9650\u989D\u4E0E\u5212\u8F6C\u4FDD\u8BC1\u91D1", "The agent trades, adjusts limits and moves margin on its own"), onClick: () => custody !== "managed" && void chooseCustody("managed") }, tr("\u5168\u6258\u7BA1", "Managed")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u76D8\u53E3 / \u6210\u4EA4\u4F4D\u7F6E", "Book / trades position")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", className: s.tapePlacement === "auto" ? "on" : "", onClick: () => setTapePlacement("auto") }, tr("\u81EA\u9002\u5E94", "Auto")), /* @__PURE__ */ React32.createElement("button", { type: "button", className: s.tapePlacement === "below" ? "on" : "", onClick: () => setTapePlacement("below") }, tr("\u56FE\u8868\u4E0B\u65B9", "Below chart")))), /* @__PURE__ */ React32.createElement("div", { className: "dhl-more-group" }, /* @__PURE__ */ React32.createElement("span", null, tr("\u4EA4\u6613\u8BBE\u7F6E", "Trading settings")), /* @__PURE__ */ React32.createElement("div", { className: "dhl-segment" }, /* @__PURE__ */ React32.createElement("button", { type: "button", onClick: () => {
|
|
22154
22402
|
setMoreOpen(false);
|
|
22155
22403
|
setLimitsOpen(true);
|
|
22156
22404
|
} }, tr("\u4EA4\u6613\u9650\u989D\u4E0E\u7B7E\u540D\u89C4\u5219", "Limits & signing rules"))))) : null)), /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Modal, { open: Boolean(confirmLive), onClose: () => setConfirmLive(null), title: tr("\u5207\u6362\u5230\u4E3B\u7F51\u5B9E\u76D8", "Switch to mainnet live"), closeLabel: t("modal.close", "\u5173\u95ED"), className: "dhl-network-modal", footer: /* @__PURE__ */ React32.createElement(React32.Fragment, null, /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Button, { variant: "outline", onClick: () => setConfirmLive(null) }, t("modal.cancel", "\u53D6\u6D88")), /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Button, { variant: "primary", disabled: networkBusy, onClick: () => {
|
|
22157
22405
|
if (confirmLive) void applyEnv(confirmLive);
|
|
22158
|
-
} }, tr("\u786E\u8BA4\u5207\u6362", "Switch"))) }, /* @__PURE__ */ React32.createElement("p", null, tr("\u4E3B\u7F51\u5B9E\u76D8\u4F1A\u663E\u793A\u771F\u5B9E\u884C\u60C5\u548C\u771F\u5B9E\u8D26\u6237\uFF0C\u540E\u7EED\u4EA4\u6613\u5C06\u4F7F\u7528\u771F\u5B9E\u8D44\u91D1\u3002", "Mainnet live shows real markets and your real account; trades use real funds."))), /* @__PURE__ */ React32.createElement(WalletDetails, { open: walletOpen, onClose: () => setWalletOpen(false) }), /* @__PURE__ */ React32.createElement(TradingLimits, { open: limitsOpen, onClose: () => setLimitsOpen(false) }), /* @__PURE__ */ React32.createElement("div", { className: "dhl-toasts" }, marketStale && !resyncing ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-data-warning", role: "status" }, tr("\u884C\u60C5\u6216\u5BBF\u4E3B\u72B6\u6001\u672A\u53CA\u65F6\u66F4\u65B0\uFF1B\u5F53\u524D\u4EF7\u683C\u53EF\u80FD\u8FC7\u671F\u3002\u4EA4\u6613\u7968\u636E\u5DF2\u6682\u505C\u63D0\u4EA4\uFF0C\u7B49\u5F85\u6570\u636E\u6062\u590D\u3002", "Market or host data is stale. Ticket submission is paused until data recovers.")) : null, s.notice ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-notice", onClick: () => notify(null) }, /* @__PURE__ */ React32.createElement(UiIcon, { name: "info", size: 16 }), /* @__PURE__ */ React32.createElement("span", { style: { flex: 1 } }, s.notice.text), /* @__PURE__ */ React32.createElement(UiIcon, { name: "close", size: 14 })) : null), /* @__PURE__ */ React32.createElement("div", { className: "dhl-body" }, showSessions ? /* @__PURE__ */ React32.createElement(SessionsColumn, { width: layout.sessionsWidth, fill: !showDesk && !showChat, onCollapse: toggleSessions }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleSessions, "aria-label": tr("\u5C55\u5F00\u4F1A\u8BDD\u680F", "Expand sessions"), title: tr("\u5C55\u5F00\u4F1A\u8BDD\u680F", "Expand sessions") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "sessions" })), showSessions && (showDesk || showChat) ? /* @__PURE__ */ React32.createElement(Divider, { side: "left", onResize: resizeSessions, onCommit: commitLayout, onReset: resetSessions }) : null, showDesk ? /* @__PURE__ */ React32.createElement(DeskColumn, { onCollapse: toggleDesk }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleDesk, "aria-label": tr("\u5C55\u5F00\u884C\u60C5\u533A", "Expand market panel"), title: tr("\u5C55\u5F00\u884C\u60C5\u533A", "Expand market panel") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "desk" })), showDesk && showChat ? /* @__PURE__ */ React32.createElement(Divider, { side: "right", onResize: resizeChat, onCommit: commitLayout, onReset: resetChat }) : null, showChat ? /* @__PURE__ */ React32.createElement(ChatColumn, { width: layout.chatWidth, fill: !showDesk, onCollapse: toggleChat, SessionProvider: props.SessionProvider ?? props.sessionProvider, renderFactorySlot: props.renderFactorySlot }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleChat, "aria-label": tr("\u5C55\u5F00\u5BF9\u8BDD\u680F", "Expand chat"), title: tr("\u5C55\u5F00\u5BF9\u8BDD\u680F", "Expand chat") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "chat" }))));
|
|
22406
|
+
} }, tr("\u786E\u8BA4\u5207\u6362", "Switch"))) }, /* @__PURE__ */ React32.createElement("p", null, tr("\u4E3B\u7F51\u5B9E\u76D8\u4F1A\u663E\u793A\u771F\u5B9E\u884C\u60C5\u548C\u771F\u5B9E\u8D26\u6237\uFF0C\u540E\u7EED\u4EA4\u6613\u5C06\u4F7F\u7528\u771F\u5B9E\u8D44\u91D1\u3002", "Mainnet live shows real markets and your real account; trades use real funds."))), /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Modal, { open: confirmManaged, onClose: () => setConfirmManaged(false), title: tr("\u5F00\u542F\u5168\u6258\u7BA1", "Turn on managed mode"), closeLabel: t("modal.close", "\u5173\u95ED"), className: "dhl-network-modal", footer: /* @__PURE__ */ React32.createElement(React32.Fragment, null, /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Button, { variant: "outline", onClick: () => setConfirmManaged(false) }, t("modal.cancel", "\u53D6\u6D88")), /* @__PURE__ */ React32.createElement(import_dsh_client_ui_primitives10.Button, { variant: "primary", onClick: () => void chooseCustody("managed", true) }, tr("\u5F00\u542F\u5168\u6258\u7BA1", "Turn on"))) }, /* @__PURE__ */ React32.createElement("p", null, tr("\u5168\u6258\u7BA1\u540E\uFF0Cagent \u5728\u5F53\u524D\u5B9E\u76D8\u8D26\u6237\u4E2D\u53EF\u4EE5\uFF1A", "In managed mode the agent can, on this live account:")), /* @__PURE__ */ React32.createElement("ul", { className: "dhl-managed-list" }, /* @__PURE__ */ React32.createElement("li", null, tr("\u4E0D\u7ECF\u9010\u7B14\u6279\u51C6\u76F4\u63A5\u4E0B\u5355\u3001\u64A4\u5355\u548C\u8C03\u6574\u6760\u6746\uFF1B", "place, cancel and re-lever orders without per-trade approval;")), /* @__PURE__ */ React32.createElement("li", null, tr("\u81EA\u884C\u4FEE\u6539\u5141\u8BB8\u4EA4\u6613\u7684\u5E01\u5BF9\u4E0E\u5355\u7B14\u3001\u6BCF\u65E5\u3001\u4ED3\u4F4D\u3001\u6760\u6746\u4E0A\u9650\uFF1B", "change the allowed coins and the order, daily, position and leverage limits;")), /* @__PURE__ */ React32.createElement("li", null, tr("\u5728\u4E3B\u6C38\u7EED\u8D26\u6237\u4E0E HIP-3 \u4EA4\u6613\u6240\u4E4B\u95F4\u5212\u8F6C USDC \u4FDD\u8BC1\u91D1\u3002", "move USDC margin between the core perp account and HIP-3 dexes."))), /* @__PURE__ */ React32.createElement("p", null, tr("dsh-web3 \u94B1\u5305\u81EA\u8EAB\u7684\u7B7E\u540D\u7B56\u7565\u4ECD\u7136\u751F\u6548\u3002\u4F60\u53EF\u4EE5\u968F\u65F6\u5207\u56DE\u534A\u6258\u7BA1\u3002", "The dsh-web3 wallet signing policy still applies. You can switch back to assisted at any time."))), /* @__PURE__ */ React32.createElement(WalletDetails, { open: walletOpen, onClose: () => setWalletOpen(false) }), /* @__PURE__ */ React32.createElement(TradingLimits, { open: limitsOpen, onClose: () => setLimitsOpen(false) }), /* @__PURE__ */ React32.createElement("div", { className: "dhl-toasts" }, marketStale && !resyncing ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-data-warning", role: "status" }, tr("\u884C\u60C5\u6216\u5BBF\u4E3B\u72B6\u6001\u672A\u53CA\u65F6\u66F4\u65B0\uFF1B\u5F53\u524D\u4EF7\u683C\u53EF\u80FD\u8FC7\u671F\u3002\u4EA4\u6613\u7968\u636E\u5DF2\u6682\u505C\u63D0\u4EA4\uFF0C\u7B49\u5F85\u6570\u636E\u6062\u590D\u3002", "Market or host data is stale. Ticket submission is paused until data recovers.")) : null, s.notice ? /* @__PURE__ */ React32.createElement("div", { className: "dhl-notice", onClick: () => notify(null) }, /* @__PURE__ */ React32.createElement(UiIcon, { name: "info", size: 16 }), /* @__PURE__ */ React32.createElement("span", { style: { flex: 1 } }, s.notice.text), /* @__PURE__ */ React32.createElement(UiIcon, { name: "close", size: 14 })) : null), /* @__PURE__ */ React32.createElement("div", { className: "dhl-body" }, showSessions ? /* @__PURE__ */ React32.createElement(SessionsColumn, { width: layout.sessionsWidth, fill: !showDesk && !showChat, onCollapse: toggleSessions }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleSessions, "aria-label": tr("\u5C55\u5F00\u4F1A\u8BDD\u680F", "Expand sessions"), title: tr("\u5C55\u5F00\u4F1A\u8BDD\u680F", "Expand sessions") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "sessions" })), showSessions && (showDesk || showChat) ? /* @__PURE__ */ React32.createElement(Divider, { side: "left", onResize: resizeSessions, onCommit: commitLayout, onReset: resetSessions }) : null, showDesk ? /* @__PURE__ */ React32.createElement(DeskColumn, { onCollapse: toggleDesk }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleDesk, "aria-label": tr("\u5C55\u5F00\u884C\u60C5\u533A", "Expand market panel"), title: tr("\u5C55\u5F00\u884C\u60C5\u533A", "Expand market panel") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "desk" })), showDesk && showChat ? /* @__PURE__ */ React32.createElement(Divider, { side: "right", onResize: resizeChat, onCommit: commitLayout, onReset: resetChat }) : null, showChat ? /* @__PURE__ */ React32.createElement(ChatColumn, { width: layout.chatWidth, fill: !showDesk, onCollapse: toggleChat, SessionProvider: props.SessionProvider ?? props.sessionProvider, renderFactorySlot: props.renderFactorySlot }) : /* @__PURE__ */ React32.createElement("button", { className: "dhl-panel-rail", type: "button", onClick: toggleChat, "aria-label": tr("\u5C55\u5F00\u5BF9\u8BDD\u680F", "Expand chat"), title: tr("\u5C55\u5F00\u5BF9\u8BDD\u680F", "Expand chat") }, /* @__PURE__ */ React32.createElement(PanelIcon, { kind: "chat" }))));
|
|
22159
22407
|
}
|
|
22160
22408
|
|
|
22161
22409
|
// src/client/components/panel-entry.tsx
|