dsh-hyperliquid 0.3.0 → 0.4.1
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 +30 -0
- package/README.md +7 -2
- package/client.js +434 -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"];
|
|
@@ -18582,6 +18686,37 @@ function cssVar(el, name, fallback) {
|
|
|
18582
18686
|
function chartData(points2) {
|
|
18583
18687
|
return points2.map((point) => ({ time: point.time, value: point.value }));
|
|
18584
18688
|
}
|
|
18689
|
+
var FINITE_KINDS = /* @__PURE__ */ new Set([
|
|
18690
|
+
"trend",
|
|
18691
|
+
"rect",
|
|
18692
|
+
"measure",
|
|
18693
|
+
"fib",
|
|
18694
|
+
"long",
|
|
18695
|
+
"short",
|
|
18696
|
+
"text",
|
|
18697
|
+
"arrow",
|
|
18698
|
+
"triangle",
|
|
18699
|
+
"brush",
|
|
18700
|
+
"infoline",
|
|
18701
|
+
"trendangle",
|
|
18702
|
+
"pricerange",
|
|
18703
|
+
"daterange",
|
|
18704
|
+
"elliott",
|
|
18705
|
+
"elliottabc",
|
|
18706
|
+
"xabcd",
|
|
18707
|
+
"abcd",
|
|
18708
|
+
"gannbox",
|
|
18709
|
+
"gannsquare",
|
|
18710
|
+
"icon",
|
|
18711
|
+
"vprofile",
|
|
18712
|
+
"regression"
|
|
18713
|
+
]);
|
|
18714
|
+
function offscreen(points2, view) {
|
|
18715
|
+
if (!points2.length) return false;
|
|
18716
|
+
const xs = points2.map((point) => view.project(point.time, point.price)?.x);
|
|
18717
|
+
if (xs.some((x) => x == null)) return false;
|
|
18718
|
+
return xs.every((x) => x < -320) || xs.every((x) => x > view.width + 320);
|
|
18719
|
+
}
|
|
18585
18720
|
var profileCache = /* @__PURE__ */ new WeakMap();
|
|
18586
18721
|
function cachedProfile(candles, from, to, options = {}) {
|
|
18587
18722
|
let byRange = profileCache.get(candles);
|
|
@@ -18651,11 +18786,25 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18651
18786
|
const [selectedTime, setSelectedTime] = React10.useState(null);
|
|
18652
18787
|
const [themeVersion, setThemeVersion] = React10.useState(0);
|
|
18653
18788
|
const [viewportVersion, setViewportVersion] = React10.useState(0);
|
|
18789
|
+
const projectionStamp = React10.useRef([]);
|
|
18790
|
+
const readProjection = (r, box) => {
|
|
18791
|
+
const base = candlesRef.current[0]?.c ?? 1;
|
|
18792
|
+
return [
|
|
18793
|
+
r.chart.timeScale().logicalToCoordinate(0),
|
|
18794
|
+
r.chart.timeScale().logicalToCoordinate(1),
|
|
18795
|
+
r.candles.priceToCoordinate(base),
|
|
18796
|
+
r.candles.priceToCoordinate(base * 1.01),
|
|
18797
|
+
box.clientWidth,
|
|
18798
|
+
box.clientHeight,
|
|
18799
|
+
base
|
|
18800
|
+
];
|
|
18801
|
+
};
|
|
18654
18802
|
const [pendingPoints, setPendingPoints] = React10.useState([]);
|
|
18655
18803
|
const [drawingHover, setDrawingHover] = React10.useState(null);
|
|
18656
18804
|
const showVolume = indicators.has("volume");
|
|
18657
18805
|
const studies = React10.useMemo(() => chartIndicators(candles, settings), [candles, settings]);
|
|
18658
18806
|
const maComputed = React10.useMemo(() => maLines.map((line, index) => ({ line, color: maColor(line, index), ...maSeries(candles, line) })), [candles, maLines]);
|
|
18807
|
+
const axisKey = `${candles.length}:${candles[0]?.t ?? 0}:${candles.at(-1)?.t ?? 0}`;
|
|
18659
18808
|
const timeline = React10.useMemo(() => {
|
|
18660
18809
|
const times = [...new Set(candles.map((candle) => Math.floor(candle.t / 1e3)))].sort((a, b) => a - b);
|
|
18661
18810
|
const step = times.length > 1 ? times[times.length - 1] - times[times.length - 2] : 60;
|
|
@@ -18680,7 +18829,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18680
18829
|
return times[i] * 1e3;
|
|
18681
18830
|
};
|
|
18682
18831
|
return { toLogical, toTime };
|
|
18683
|
-
}, [
|
|
18832
|
+
}, [axisKey]);
|
|
18684
18833
|
const timelineRef = React10.useRef(timeline);
|
|
18685
18834
|
timelineRef.current = timeline;
|
|
18686
18835
|
React10.useEffect(() => {
|
|
@@ -18807,7 +18956,21 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18807
18956
|
}
|
|
18808
18957
|
}
|
|
18809
18958
|
}
|
|
18810
|
-
if (event.shiftKey && previous)
|
|
18959
|
+
if (event.shiftKey && previous) {
|
|
18960
|
+
const px = chart.timeScale().logicalToCoordinate(timelineRef.current.toLogical(previous.time));
|
|
18961
|
+
const py = candleSeries.priceToCoordinate(previous.price);
|
|
18962
|
+
const x = event.clientX - rect.left;
|
|
18963
|
+
if (px == null || py == null) return { time, price: previous.price };
|
|
18964
|
+
const dx = x - px, dy = y - py;
|
|
18965
|
+
const angle = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);
|
|
18966
|
+
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 };
|
|
18967
|
+
const along = dx * Math.cos(angle) + dy * Math.sin(angle);
|
|
18968
|
+
const tx = px + Math.cos(angle) * along, ty = py + Math.sin(angle) * along;
|
|
18969
|
+
const snappedLogical = chart.timeScale().coordinateToLogical(tx);
|
|
18970
|
+
const snappedPrice = Number(candleSeries.coordinateToPrice(ty));
|
|
18971
|
+
if (snappedLogical == null || !Number.isFinite(snappedPrice) || snappedPrice <= 0) return { time, price: previous.price };
|
|
18972
|
+
return { time: timelineRef.current.toTime(Number(snappedLogical)), price: snappedPrice };
|
|
18973
|
+
}
|
|
18811
18974
|
return { time, price };
|
|
18812
18975
|
};
|
|
18813
18976
|
snapRef.current = snapPoint;
|
|
@@ -18877,17 +19040,31 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18877
19040
|
let viewportFrame = 0;
|
|
18878
19041
|
const refreshOverlay = () => {
|
|
18879
19042
|
if (viewportFrame) cancelAnimationFrame(viewportFrame);
|
|
18880
|
-
viewportFrame = requestAnimationFrame(() => setViewportVersion((value) => value + 1));
|
|
19043
|
+
viewportFrame = requestAnimationFrame(() => React10.startTransition(() => setViewportVersion((value) => value + 1)));
|
|
18881
19044
|
};
|
|
18882
19045
|
chart.timeScale().subscribeVisibleLogicalRangeChange(refreshOverlay);
|
|
19046
|
+
let scalingPrice = false;
|
|
19047
|
+
const endCanvasGesture = () => box.classList.remove("dhl-canvas-gesture");
|
|
19048
|
+
const beginCanvasGesture = (event) => {
|
|
19049
|
+
if (event.target instanceof HTMLCanvasElement) box.classList.add("dhl-canvas-gesture");
|
|
19050
|
+
};
|
|
19051
|
+
box.addEventListener("pointerdown", beginCanvasGesture, { passive: true });
|
|
19052
|
+
window.addEventListener("pointerup", endCanvasGesture);
|
|
19053
|
+
window.addEventListener("pointercancel", endCanvasGesture);
|
|
19054
|
+
window.addEventListener("blur", endCanvasGesture);
|
|
19055
|
+
const scaleDown = (event) => {
|
|
19056
|
+
scalingPrice = event.target instanceof HTMLCanvasElement && event.clientX >= box.getBoundingClientRect().right - chart.priceScale("right").width();
|
|
19057
|
+
};
|
|
18883
19058
|
const scaleDrag = (event) => {
|
|
18884
|
-
if (event.buttons) refreshOverlay();
|
|
19059
|
+
if (event.buttons && scalingPrice) refreshOverlay();
|
|
18885
19060
|
};
|
|
19061
|
+
box.addEventListener("pointerdown", scaleDown, { passive: true });
|
|
18886
19062
|
box.addEventListener("pointermove", scaleDrag, { passive: true });
|
|
18887
19063
|
box.addEventListener("wheel", refreshOverlay, { passive: true });
|
|
18888
19064
|
box.addEventListener("dblclick", refreshOverlay);
|
|
18889
19065
|
const sizeObserver = new ResizeObserver(refreshOverlay);
|
|
18890
19066
|
sizeObserver.observe(box);
|
|
19067
|
+
box.querySelectorAll("canvas").forEach((canvas) => sizeObserver.observe(canvas));
|
|
18891
19068
|
chart.timeScale().subscribeVisibleTimeRangeChange((range) => {
|
|
18892
19069
|
if (!fitted.current || restoring.current || !range || typeof range.from !== "number" || typeof range.to !== "number") return;
|
|
18893
19070
|
if (rangeTimer) clearTimeout(rangeTimer);
|
|
@@ -18930,6 +19107,12 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18930
19107
|
box.removeEventListener("click", chartClick);
|
|
18931
19108
|
chart.timeScale().unsubscribeVisibleLogicalRangeChange(refreshOverlay);
|
|
18932
19109
|
box.removeEventListener("pointermove", scaleDrag);
|
|
19110
|
+
box.removeEventListener("pointerdown", beginCanvasGesture);
|
|
19111
|
+
window.removeEventListener("pointerup", endCanvasGesture);
|
|
19112
|
+
window.removeEventListener("pointercancel", endCanvasGesture);
|
|
19113
|
+
window.removeEventListener("blur", endCanvasGesture);
|
|
19114
|
+
endCanvasGesture();
|
|
19115
|
+
box.removeEventListener("pointerdown", scaleDown);
|
|
18933
19116
|
box.removeEventListener("wheel", refreshOverlay);
|
|
18934
19117
|
box.removeEventListener("dblclick", refreshOverlay);
|
|
18935
19118
|
sizeObserver.disconnect();
|
|
@@ -18989,26 +19172,26 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
18989
19172
|
const prevFirst = firstBarTime.current;
|
|
18990
19173
|
const prepended = prevFirst != null && bars[0].time < prevFirst ? Math.max(0, bars.findIndex((bar) => bar.time >= prevFirst)) : 0;
|
|
18991
19174
|
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
|
-
|
|
19175
|
+
const studySig = JSON.stringify(settings), colorSig = `${chartType}|${th.up}|${th.down}`;
|
|
19176
|
+
feed(r.candles, chartType === "line" || chartType === "area" ? bars.map((bar) => ({ time: bar.time, value: bar.close })) : bars, chartType);
|
|
19177
|
+
if (showVolume) feed(r.volume, vols.map((v) => ({ ...v, color: v.color === FALLBACK_UP ? th.up : th.down })), colorSig);
|
|
19178
|
+
if (r.volume.options().visible !== showVolume) r.volume.applyOptions({ visible: showVolume });
|
|
19179
|
+
for (const [series, key3, indicator] of [
|
|
19180
|
+
[r.sma, "sma20", "sma20"],
|
|
19181
|
+
[r.sma60, "sma60", "sma60"],
|
|
19182
|
+
[r.ema20, "ema20", "ema20"],
|
|
19183
|
+
[r.ema50, "ema50", "ema50"],
|
|
19184
|
+
[r.basis, "basis", "bollinger20"],
|
|
19185
|
+
[r.upper, "upper", "bollinger20"],
|
|
19186
|
+
[r.lower, "lower", "bollinger20"]
|
|
19187
|
+
]) {
|
|
19188
|
+
const visible = indicators.has(indicator);
|
|
19189
|
+
if (visible) feed(series, chartData(studies[key3]), studySig);
|
|
19190
|
+
if (series.options().visible !== visible) series.applyOptions({ visible });
|
|
19191
|
+
}
|
|
19009
19192
|
if (indicators.has("vwap")) {
|
|
19010
19193
|
r.vwap ??= r.chart.addSeries(lineSeries, { color: "#e89a56", lineWidth: 2, lastValueVisible: false, priceLineVisible: false });
|
|
19011
|
-
r.vwap
|
|
19194
|
+
feed(r.vwap, chartData(studies.vwap), studySig);
|
|
19012
19195
|
} else if (r.vwap) {
|
|
19013
19196
|
r.chart.removeSeries(r.vwap);
|
|
19014
19197
|
r.vwap = void 0;
|
|
@@ -19019,7 +19202,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19019
19202
|
pane.setStretchFactor(0.28);
|
|
19020
19203
|
r.atr = pane.addSeries(lineSeries, { color: "#56bca7", lineWidth: 2, priceLineVisible: false });
|
|
19021
19204
|
}
|
|
19022
|
-
r.atr
|
|
19205
|
+
feed(r.atr, chartData(studies.atr14), studySig);
|
|
19023
19206
|
} else if (r.atr) {
|
|
19024
19207
|
r.chart.removeSeries(r.atr);
|
|
19025
19208
|
r.atr = void 0;
|
|
@@ -19032,7 +19215,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19032
19215
|
r.rsiBounds = [settings.rsi.lower, settings.rsi.upper].map((price) => r.rsi.createPriceLine({ price, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false }));
|
|
19033
19216
|
}
|
|
19034
19217
|
r.rsiBounds?.forEach((line, index) => line.applyOptions({ price: index ? settings.rsi.upper : settings.rsi.lower }));
|
|
19035
|
-
r.rsi
|
|
19218
|
+
feed(r.rsi, chartData(studies.rsi14), studySig);
|
|
19036
19219
|
} else if (r.rsi) {
|
|
19037
19220
|
r.chart.removeSeries(r.rsi);
|
|
19038
19221
|
r.rsi = void 0;
|
|
@@ -19045,9 +19228,9 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19045
19228
|
r.macd = pane.addSeries(lineSeries, { color: "#55a6ce", lineWidth: 2, priceLineVisible: false, lastValueVisible: false });
|
|
19046
19229
|
r.signal = pane.addSeries(lineSeries, { color: "#e2a943", lineWidth: 2, priceLineVisible: false, lastValueVisible: false });
|
|
19047
19230
|
}
|
|
19048
|
-
r.macd
|
|
19049
|
-
r.signal
|
|
19050
|
-
r.histogram
|
|
19231
|
+
feed(r.macd, chartData(studies.macd), studySig);
|
|
19232
|
+
feed(r.signal, chartData(studies.signal), studySig);
|
|
19233
|
+
feed(r.histogram, studies.histogram.map((point) => ({ time: point.time, value: point.value, color: point.value >= 0 ? th.up : th.down })), studySig + colorSig);
|
|
19051
19234
|
} else if (r.macd) {
|
|
19052
19235
|
r.chart.removeSeries(r.macd);
|
|
19053
19236
|
r.chart.removeSeries(r.signal);
|
|
@@ -19071,7 +19254,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19071
19254
|
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
19255
|
if (group2.id === "adx") r.extra.adx.createPriceLine({ price: 25, color: "#8d8d8d", lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false });
|
|
19073
19256
|
}
|
|
19074
|
-
for (const key3 of group2.keys) r.extra[key3]
|
|
19257
|
+
for (const key3 of group2.keys) feed(r.extra[key3], chartData(studies[key3]), studySig);
|
|
19075
19258
|
if (group2.id === "stochastic") r.stochBounds?.forEach((line, index) => line.applyOptions({ price: index ? settings.stochastic.upper : settings.stochastic.lower }));
|
|
19076
19259
|
} else for (const key3 of group2.keys) {
|
|
19077
19260
|
if (r.extra[key3]) {
|
|
@@ -19112,11 +19295,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19112
19295
|
maRefs.current.set(item.line.id, entry);
|
|
19113
19296
|
}
|
|
19114
19297
|
entry.main.applyOptions({ color: item.color });
|
|
19115
|
-
entry.main
|
|
19298
|
+
feed(entry.main, toPoints(item.values), JSON.stringify(item.line));
|
|
19116
19299
|
if (item.smoothed) {
|
|
19117
19300
|
entry.smooth ??= r.chart.addSeries(lineSeries, { lineWidth: 1, lineStyle: LineStyle.Dashed, lastValueVisible: false, priceLineVisible: false, crosshairMarkerVisible: false });
|
|
19118
19301
|
entry.smooth.applyOptions({ color: item.color });
|
|
19119
|
-
entry.smooth
|
|
19302
|
+
feed(entry.smooth, toPoints(item.smoothed), JSON.stringify(item.line));
|
|
19120
19303
|
} else if (entry.smooth) {
|
|
19121
19304
|
r.chart.removeSeries(entry.smooth);
|
|
19122
19305
|
entry.smooth = void 0;
|
|
@@ -19125,7 +19308,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19125
19308
|
if (!fitted.current) {
|
|
19126
19309
|
restoring.current = true;
|
|
19127
19310
|
r.chart.timeScale().fitContent();
|
|
19128
|
-
const
|
|
19311
|
+
const linked = linkRef.current?.range && linkRef.current.range.source !== linkRef.current.id ? linkRef.current.range : null;
|
|
19312
|
+
const saved = linked ?? initialRange.current;
|
|
19129
19313
|
if (saved && saved.from < saved.to) {
|
|
19130
19314
|
try {
|
|
19131
19315
|
r.chart.timeScale().setVisibleRange({ from: saved.from, to: saved.to });
|
|
@@ -19137,6 +19321,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19137
19321
|
restoring.current = false;
|
|
19138
19322
|
}, 100);
|
|
19139
19323
|
}
|
|
19324
|
+
const overlayFrame = requestAnimationFrame(() => {
|
|
19325
|
+
const box2 = boxRef.current;
|
|
19326
|
+
if (box2 && readProjection(r, box2).some((value, index) => value !== projectionStamp.current[index])) React10.startTransition(() => setViewportVersion((value) => value + 1));
|
|
19327
|
+
});
|
|
19328
|
+
return () => cancelAnimationFrame(overlayFrame);
|
|
19140
19329
|
}, [candles, studies, indicators, showVolume, maComputed, chartType, settings]);
|
|
19141
19330
|
React10.useEffect(() => {
|
|
19142
19331
|
const r = refs.current;
|
|
@@ -19296,22 +19485,28 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19296
19485
|
const view = React10.useMemo(() => {
|
|
19297
19486
|
const chartApi = refs.current, box = boxRef.current;
|
|
19298
19487
|
if (!chartApi || !box) return null;
|
|
19488
|
+
const stamp = readProjection(chartApi, box);
|
|
19489
|
+
projectionStamp.current = stamp;
|
|
19490
|
+
const [x0, x1, y0, y1] = stamp;
|
|
19491
|
+
const base = candlesRef.current[0]?.c ?? 1;
|
|
19492
|
+
const ys = /* @__PURE__ */ new Map();
|
|
19299
19493
|
return {
|
|
19300
19494
|
project: (time, price) => {
|
|
19301
|
-
const x =
|
|
19302
|
-
|
|
19495
|
+
const x = x0 == null || x1 == null ? null : x0 + (x1 - x0) * timeline.toLogical(time);
|
|
19496
|
+
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));
|
|
19497
|
+
const y = ys.get(price);
|
|
19303
19498
|
return x == null || y == null || !Number.isFinite(x) || !Number.isFinite(y) ? null : { x, y };
|
|
19304
19499
|
},
|
|
19305
19500
|
width: Math.max(0, box.clientWidth - chartApi.chart.priceScale("right").width()),
|
|
19306
19501
|
height: chartApi.chart.panes()[0]?.getHeight() ?? box.clientHeight,
|
|
19307
19502
|
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:
|
|
19503
|
+
closes: (from, to) => candlesRef.current.filter((candle) => candle.t >= from && candle.t <= to).map((candle) => ({ time: candle.t, price: candle.c })),
|
|
19504
|
+
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 })),
|
|
19505
|
+
profile: (from, to, options) => cachedProfile(candlesRef.current, from, to, options),
|
|
19506
|
+
firstTime: candlesRef.current[0]?.t,
|
|
19507
|
+
barMs: candlesRef.current.length > 1 ? candlesRef.current[1].t - candlesRef.current[0].t : void 0
|
|
19313
19508
|
};
|
|
19314
|
-
}, [viewportVersion, timeline, themeVersion, expanded, indicators]);
|
|
19509
|
+
}, [viewportVersion, timeline, themeVersion, expanded, indicators, priceScale]);
|
|
19315
19510
|
React10.useEffect(() => {
|
|
19316
19511
|
if (selectedId && !markById.has(selectedId)) setSelectedId(null);
|
|
19317
19512
|
}, [markById, selectedId]);
|
|
@@ -19426,7 +19621,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19426
19621
|
const cloneOf = React10.useCallback((mark) => {
|
|
19427
19622
|
const line = timelineRef.current;
|
|
19428
19623
|
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));
|
|
19624
|
+
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
19625
|
return { kind: mark.kind, geometry: geometryPatch(mark.kind, shifted), style, label: mark.label };
|
|
19431
19626
|
}, []);
|
|
19432
19627
|
const clipboard = React10.useRef(null);
|
|
@@ -19526,6 +19721,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19526
19721
|
}
|
|
19527
19722
|
latest2 = next;
|
|
19528
19723
|
setGroupDraft(next);
|
|
19724
|
+
for (const [memberId, member] of originals) if (member.kind === "level") priceLines.current.get(memberId)?.applyOptions({ price: next.get(memberId)[0].price });
|
|
19529
19725
|
};
|
|
19530
19726
|
const move2 = (ev) => {
|
|
19531
19727
|
if (!moved2 && Math.hypot(ev.clientX - origin2.x, ev.clientY - origin2.y) < 3) return;
|
|
@@ -19543,6 +19739,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19543
19739
|
};
|
|
19544
19740
|
const cancel2 = () => {
|
|
19545
19741
|
detach2();
|
|
19742
|
+
for (const [memberId, member] of originals) if (member.kind === "level") priceLines.current.get(memberId)?.applyOptions({ price: Number(member.price) });
|
|
19546
19743
|
setGroupDraft(null);
|
|
19547
19744
|
};
|
|
19548
19745
|
const onKey2 = (ev) => {
|
|
@@ -19582,7 +19779,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19582
19779
|
const compute = (ev) => {
|
|
19583
19780
|
let next;
|
|
19584
19781
|
if (anchor != null) {
|
|
19585
|
-
const point = snap(ev, original[anchor === 0 ? 1 :
|
|
19782
|
+
const point = snap(ev, original[anchor === 0 ? 1 : anchor - 1]);
|
|
19586
19783
|
if (!point) return;
|
|
19587
19784
|
next = original.map((item) => ({ ...item }));
|
|
19588
19785
|
if (mark.kind === "level") next[0] = { ...next[0], price: point.price };
|
|
@@ -19671,6 +19868,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19671
19868
|
const live = draft?.id === mark.id ? draft.points : groupDraft?.get(mark.id) ?? points2;
|
|
19672
19869
|
const shown = mark.kind === "level" ? [{ time: lastTime, price: live[0].price }] : live;
|
|
19673
19870
|
const selected = selectedId === mark.id || group.includes(mark.id);
|
|
19871
|
+
const clampedDecoration = !(mark.source === "user" || mark.kind === "level") || Boolean(mark.alert) || Boolean(mark.shared);
|
|
19872
|
+
if (!selected && !clampedDecoration && FINITE_KINDS.has(mark.kind) && offscreen(shown, view)) return null;
|
|
19674
19873
|
return /* @__PURE__ */ React10.createElement(
|
|
19675
19874
|
DrawingShape,
|
|
19676
19875
|
{
|
|
@@ -19680,6 +19879,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19680
19879
|
points: shown,
|
|
19681
19880
|
view,
|
|
19682
19881
|
text: mark.label,
|
|
19882
|
+
dataVersion: mark.kind === "vprofile" || mark.kind === "regression" ? candles : void 0,
|
|
19683
19883
|
className: `dhl-drawing ${roleClass(mark)}${mark.kind === "level" ? " is-level" : ""}${selected ? " selected" : ""}${mark.locked || drawingsLocked ? " locked" : ""}`,
|
|
19684
19884
|
label: mark.source === "user" || mark.kind === "level" ? void 0 : mark.label,
|
|
19685
19885
|
anchors: selected,
|
|
@@ -19690,7 +19890,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19690
19890
|
alert: mark.alert,
|
|
19691
19891
|
shared: mark.shared,
|
|
19692
19892
|
rows: mark.rows,
|
|
19693
|
-
valueArea: mark.valueArea
|
|
19893
|
+
valueArea: mark.valueArea,
|
|
19894
|
+
levels: mark.levels
|
|
19694
19895
|
}
|
|
19695
19896
|
);
|
|
19696
19897
|
}), /* @__PURE__ */ React10.createElement("g", { className: "dhl-fills" }, fills.map((event) => {
|
|
@@ -19766,7 +19967,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19766
19967
|
var React11 = __toESM(require("react"), 1);
|
|
19767
19968
|
var HISTORY_LIMIT = 100;
|
|
19768
19969
|
var GEOMETRY = ["price", "fromTime", "fromPrice", "toTime", "toPrice", "points"];
|
|
19769
|
-
var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea"];
|
|
19970
|
+
var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea", "levels"];
|
|
19770
19971
|
var FLAGS = /* @__PURE__ */ new Set(["locked", "hidden", "shared", "alert"]);
|
|
19771
19972
|
function recreateRequest(mark) {
|
|
19772
19973
|
const out = { kind: mark.kind, label: mark.label, role: mark.role, coin: mark.coin, interval: mark.interval };
|
|
@@ -19878,6 +20079,28 @@ function useDrawingHistory(scope) {
|
|
|
19878
20079
|
}
|
|
19879
20080
|
return apiDelete(ctx, op.mark.id);
|
|
19880
20081
|
};
|
|
20082
|
+
const step = async (ctx, forward) => {
|
|
20083
|
+
const from = forward ? redoStack : undoStack, to = forward ? undoStack : redoStack;
|
|
20084
|
+
const op = from.current.pop();
|
|
20085
|
+
if (!op) return;
|
|
20086
|
+
bump();
|
|
20087
|
+
let done = null, left = null;
|
|
20088
|
+
if (op.type === "batch") {
|
|
20089
|
+
const ok = [], failed = [];
|
|
20090
|
+
for (const inner of forward ? op.ops : [...op.ops].reverse()) (await apply2(ctx, inner, forward) ? ok : failed).push(inner);
|
|
20091
|
+
if (!forward) {
|
|
20092
|
+
ok.reverse();
|
|
20093
|
+
failed.reverse();
|
|
20094
|
+
}
|
|
20095
|
+
if (ok.length) done = { type: "batch", ops: ok };
|
|
20096
|
+
if (failed.length) left = { type: "batch", ops: failed };
|
|
20097
|
+
} else if (await apply2(ctx, op, forward)) done = op;
|
|
20098
|
+
else left = op;
|
|
20099
|
+
if (ctx.scope !== scopeRef.current) return;
|
|
20100
|
+
if (done) to.current.push(done);
|
|
20101
|
+
if (left) from.current.push(left);
|
|
20102
|
+
bump();
|
|
20103
|
+
};
|
|
19881
20104
|
return {
|
|
19882
20105
|
create: (request) => serial(async (ctx) => {
|
|
19883
20106
|
const saved = await apiCreate(ctx, request);
|
|
@@ -19919,29 +20142,8 @@ function useDrawingHistory(scope) {
|
|
|
19919
20142
|
}
|
|
19920
20143
|
if (done.length) record({ type: "batch", ops: done }, ctx);
|
|
19921
20144
|
}, 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),
|
|
20145
|
+
undo: () => serial((ctx) => step(ctx, false), void 0),
|
|
20146
|
+
redo: () => serial((ctx) => step(ctx, true), void 0),
|
|
19945
20147
|
canUndo: undoStack.current.length > 0,
|
|
19946
20148
|
canRedo: redoStack.current.length > 0
|
|
19947
20149
|
};
|
|
@@ -19950,7 +20152,7 @@ function useDrawingHistory(scope) {
|
|
|
19950
20152
|
// src/client/components/drawing-list.tsx
|
|
19951
20153
|
var React12 = __toESM(require("react"), 1);
|
|
19952
20154
|
var OPEN_KEY = "dsh-hyperliquid.draw.listOpen";
|
|
19953
|
-
|
|
20155
|
+
var DrawingList = React12.memo(function DrawingList2({ marks, locked, onSelect, onEdit, onRemove, language }) {
|
|
19954
20156
|
const [open, setOpen] = React12.useState(() => {
|
|
19955
20157
|
try {
|
|
19956
20158
|
return localStorage.getItem(OPEN_KEY) !== "0";
|
|
@@ -19966,11 +20168,11 @@ function DrawingList({ marks, locked, onSelect, onEdit, onRemove }) {
|
|
|
19966
20168
|
return !value;
|
|
19967
20169
|
});
|
|
19968
20170
|
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) => {
|
|
20171
|
+
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
20172
|
const tool = toolOf(mark.kind);
|
|
19971
20173
|
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
20174
|
})) : null);
|
|
19973
|
-
}
|
|
20175
|
+
});
|
|
19974
20176
|
|
|
19975
20177
|
// src/client/components/sticker-picker.tsx
|
|
19976
20178
|
var React13 = __toESM(require("react"), 1);
|
|
@@ -20426,7 +20628,7 @@ function MarketPicker({ activeCoin, onClose }) {
|
|
|
20426
20628
|
const marketRows = React21.useMemo(() => {
|
|
20427
20629
|
const livePerps = new Map(s.markets.map((market) => [market.name, market]));
|
|
20428
20630
|
const catalog = s.marketCatalog.length ? s.marketCatalog.map((row) => {
|
|
20429
|
-
const latest = row.
|
|
20631
|
+
const latest = row.kind === "perp" ? livePerps.get(row.name) : null;
|
|
20430
20632
|
return latest ? { ...row, ctx: latest.ctx } : row;
|
|
20431
20633
|
}) : s.markets.map((row) => ({
|
|
20432
20634
|
name: row.name,
|
|
@@ -20540,7 +20742,7 @@ function MarketPicker({ activeCoin, onClose }) {
|
|
|
20540
20742
|
}
|
|
20541
20743
|
};
|
|
20542
20744
|
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\
|
|
20745
|
+
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
20746
|
setCategory(key3);
|
|
20545
20747
|
if (["spot", "hip3", "outcome", "crypto", "tradfi", "preipo"].includes(key3)) setStrict(false);
|
|
20546
20748
|
} }, 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 +20817,8 @@ function atr14(rows) {
|
|
|
20615
20817
|
for (const item of ranges.slice(14)) value = (value * 13 + item) / 14;
|
|
20616
20818
|
return value;
|
|
20617
20819
|
}
|
|
20618
|
-
function evaluateStrategyStudies(candles, coin, interval, now = Date.now(),
|
|
20619
|
-
const say = (zh, en) =>
|
|
20820
|
+
function evaluateStrategyStudies(candles, coin, interval, now = Date.now(), english2 = false) {
|
|
20821
|
+
const say = (zh, en) => english2 ? en : zh;
|
|
20620
20822
|
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
20823
|
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
20824
|
const latest = rows.at(-1);
|
|
@@ -20712,8 +20914,8 @@ var STATE_LABELS = {
|
|
|
20712
20914
|
};
|
|
20713
20915
|
function StrategyMenu({ candles, coin, interval }) {
|
|
20714
20916
|
const menu = React22.useRef(null);
|
|
20715
|
-
const
|
|
20716
|
-
const studies = React22.useMemo(() => evaluateStrategyStudies(candles, coin, interval, Date.now(),
|
|
20917
|
+
const english2 = tr("zh", "en") === "en";
|
|
20918
|
+
const studies = React22.useMemo(() => evaluateStrategyStudies(candles, coin, interval, Date.now(), english2), [candles, coin, interval, english2]);
|
|
20717
20919
|
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
20920
|
"\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
20921
|
"These conditions structure analysis; returns are unverified for this market and interval. Check higher timeframe, book, funding and costs."
|
|
@@ -20803,7 +21005,7 @@ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
|
|
|
20803
21005
|
const [name, setName] = React23.useState("");
|
|
20804
21006
|
const [error, setError] = React23.useState("");
|
|
20805
21007
|
const [busy, setBusy] = React23.useState(false);
|
|
20806
|
-
const populate = (value) => setDraft(Object.fromEntries(Object.entries(normalizeIndicatorSettings(value)).map(([section,
|
|
21008
|
+
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
21009
|
React23.useEffect(() => {
|
|
20808
21010
|
if (!open) return;
|
|
20809
21011
|
populate(settings);
|
|
@@ -20822,7 +21024,7 @@ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
|
|
|
20822
21024
|
cancelled = true;
|
|
20823
21025
|
};
|
|
20824
21026
|
}, [open]);
|
|
20825
|
-
const values = () => normalizeIndicatorSettings(Object.fromEntries(Object.entries(draft).map(([section,
|
|
21027
|
+
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
21028
|
const apply2 = () => {
|
|
20827
21029
|
try {
|
|
20828
21030
|
onApply({ indicatorSettings: values(), indicators: active, maLines: lines });
|
|
@@ -20871,7 +21073,7 @@ function IndicatorEditor({ settings, indicators, maLines, onApply }) {
|
|
|
20871
21073
|
setTemplates(result.templates);
|
|
20872
21074
|
setSelected("");
|
|
20873
21075
|
}).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,
|
|
21076
|
+
} }, 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
21077
|
));
|
|
20876
21078
|
}
|
|
20877
21079
|
|
|
@@ -20885,7 +21087,7 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20885
21087
|
const [state, setState] = React24.useState({ scope: "", candles: [], error: "", older: "idle", at: 0 });
|
|
20886
21088
|
const currentScope = React24.useRef(scope);
|
|
20887
21089
|
currentScope.current = scope;
|
|
20888
|
-
const
|
|
21090
|
+
const feed2 = state.scope === scope ? state : { scope, candles: [], error: "", older: "idle", at: 0 };
|
|
20889
21091
|
React24.useEffect(() => {
|
|
20890
21092
|
let alive = true, busy = false;
|
|
20891
21093
|
setState({ scope, candles: [], error: "", older: "idle", at: 0 });
|
|
@@ -20910,11 +21112,11 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20910
21112
|
}, [scope]);
|
|
20911
21113
|
const olderBusy = React24.useRef(false);
|
|
20912
21114
|
const needOlder = async () => {
|
|
20913
|
-
if (olderBusy.current ||
|
|
21115
|
+
if (olderBusy.current || feed2.older !== "idle" || !feed2.candles.length) return;
|
|
20914
21116
|
olderBusy.current = true;
|
|
20915
21117
|
setState((old) => ({ ...old, older: "loading" }));
|
|
20916
21118
|
try {
|
|
20917
|
-
const first =
|
|
21119
|
+
const first = feed2.candles[0].t;
|
|
20918
21120
|
const result = await hyperCall("candles", { coin, interval, endMs: first - 1, startMs: first - durations[RESEARCH_INTERVALS.indexOf(interval)] * 500 });
|
|
20919
21121
|
if (currentScope.current === scope) setState((old) => {
|
|
20920
21122
|
const rows = merge2(result.candles, old.candles);
|
|
@@ -20927,12 +21129,12 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20927
21129
|
}
|
|
20928
21130
|
};
|
|
20929
21131
|
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,
|
|
21132
|
+
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]);
|
|
21133
|
+
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
21134
|
CandleChart,
|
|
20933
21135
|
{
|
|
20934
21136
|
key: scope,
|
|
20935
|
-
candles:
|
|
21137
|
+
candles: feed2.candles,
|
|
20936
21138
|
marks,
|
|
20937
21139
|
indicators,
|
|
20938
21140
|
expanded: false,
|
|
@@ -20953,7 +21155,7 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20953
21155
|
},
|
|
20954
21156
|
onSelect: (selection) => insertChartReference(selection, coin, interval),
|
|
20955
21157
|
onNeedOlder: () => void needOlder(),
|
|
20956
|
-
olderState:
|
|
21158
|
+
olderState: feed2.older,
|
|
20957
21159
|
tradeEvents: events,
|
|
20958
21160
|
showReasons: layers.fills && layers.reasons,
|
|
20959
21161
|
onAnalyzeFill,
|
|
@@ -20968,7 +21170,7 @@ function ResearchPane({ coin, interval, onInterval, indicators, marks, link, cha
|
|
|
20968
21170
|
selectRequest: null,
|
|
20969
21171
|
focus: null
|
|
20970
21172
|
}
|
|
20971
|
-
),
|
|
21173
|
+
), 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
21174
|
}
|
|
20973
21175
|
|
|
20974
21176
|
// lib/workspace-settings.js
|
|
@@ -21180,12 +21382,22 @@ function DeskColumn({ onCollapse }) {
|
|
|
21180
21382
|
}, []);
|
|
21181
21383
|
const editMark = React26.useCallback((id, patch) => historyRef.current.edit(id, patch), []);
|
|
21182
21384
|
const erase = React26.useCallback((id) => historyRef.current.remove(id), []);
|
|
21385
|
+
const selectMark = React26.useCallback((id) => setSelectRequest({ id, at: Date.now() }), []);
|
|
21183
21386
|
const cloneMark = React26.useCallback((copy) => {
|
|
21184
21387
|
void historyRef.current.create({ kind: copy.kind, label: copy.label, ...copy.geometry, ...copy.style });
|
|
21185
21388
|
}, []);
|
|
21389
|
+
const onSecondaryChart = React26.useRef(false);
|
|
21390
|
+
React26.useEffect(() => {
|
|
21391
|
+
const track = (event) => {
|
|
21392
|
+
const target = event.target;
|
|
21393
|
+
onSecondaryChart.current = Boolean(target?.closest?.(".dhl-research-pane") && !target.closest(".dhl-primary-pane"));
|
|
21394
|
+
};
|
|
21395
|
+
window.addEventListener("pointerdown", track, true);
|
|
21396
|
+
return () => window.removeEventListener("pointerdown", track, true);
|
|
21397
|
+
}, []);
|
|
21186
21398
|
React26.useEffect(() => {
|
|
21187
21399
|
const onKey = (event) => {
|
|
21188
|
-
if (!(event.metaKey || event.ctrlKey) || event.altKey) return;
|
|
21400
|
+
if (!(event.metaKey || event.ctrlKey) || event.altKey || onSecondaryChart.current) return;
|
|
21189
21401
|
const element = event.target;
|
|
21190
21402
|
if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
|
|
21191
21403
|
const key3 = event.key.toLowerCase();
|
|
@@ -21225,7 +21437,7 @@ function DeskColumn({ onCollapse }) {
|
|
|
21225
21437
|
React26.useEffect(() => {
|
|
21226
21438
|
const keys = { KeyT: "trend", KeyH: "level", KeyJ: "hray", KeyV: "vline", KeyC: "crossline", KeyF: "fib" };
|
|
21227
21439
|
const onKey = (event) => {
|
|
21228
|
-
if (!event.altKey || event.ctrlKey || event.metaKey || chartTab !== "chart") return;
|
|
21440
|
+
if (!event.altKey || event.ctrlKey || event.metaKey || chartTab !== "chart" || onSecondaryChart.current) return;
|
|
21229
21441
|
const element = event.target;
|
|
21230
21442
|
if (element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
|
|
21231
21443
|
const kind = event.shiftKey ? event.code === "KeyR" ? "rect" : void 0 : keys[event.code];
|
|
@@ -21421,9 +21633,10 @@ function DeskColumn({ onCollapse }) {
|
|
|
21421
21633
|
{
|
|
21422
21634
|
marks,
|
|
21423
21635
|
locked: drawingsLocked,
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21636
|
+
language: tr("zh", "en"),
|
|
21637
|
+
onSelect: selectMark,
|
|
21638
|
+
onEdit: editMark,
|
|
21639
|
+
onRemove: erase
|
|
21427
21640
|
}
|
|
21428
21641
|
) : null, /* @__PURE__ */ React26.createElement(
|
|
21429
21642
|
ChartNavigation,
|
|
@@ -21638,8 +21851,8 @@ var React28 = __toESM(require("react"), 1);
|
|
|
21638
21851
|
function TradeTicket({ onSubmitted }) {
|
|
21639
21852
|
const s = useStore();
|
|
21640
21853
|
const coin = s.activeCoin;
|
|
21641
|
-
const market = s.markets.find((row) => row.name === coin);
|
|
21642
21854
|
const catalogMarket = s.marketCatalog.find((row) => row.name === coin);
|
|
21855
|
+
const market = s.markets.find((row) => row.name === coin) ?? (catalogMarket?.tradable ? catalogMarket : void 0);
|
|
21643
21856
|
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
21857
|
const mark = liveMid || Number(s.selectedMarket?.name === coin ? s.selectedMarket.ctx?.markPx : 0) || Number(market?.ctx?.markPx);
|
|
21645
21858
|
const position = s.account?.positions?.find((p) => p.coin === coin);
|
|
@@ -21874,20 +22087,34 @@ function ChatColumn({ width, fill = false, onCollapse, SessionProvider, renderFa
|
|
|
21874
22087
|
requestAnimationFrame(() => input.focus?.());
|
|
21875
22088
|
}
|
|
21876
22089
|
}, [activeId, readyRef, showTicket, s.chartReferences]);
|
|
21877
|
-
const
|
|
21878
|
-
|
|
21879
|
-
|
|
21880
|
-
|
|
21881
|
-
|
|
21882
|
-
|
|
21883
|
-
|
|
21884
|
-
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
|
|
22090
|
+
const QUICK = [
|
|
22091
|
+
[
|
|
22092
|
+
"\u4EA4\u6613\u4F53\u68C0",
|
|
22093
|
+
"Trading check-up",
|
|
22094
|
+
"\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",
|
|
22095
|
+
"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."
|
|
22096
|
+
],
|
|
22097
|
+
[
|
|
22098
|
+
"\u4ECA\u5929\u6709\u4EC0\u4E48\u673A\u4F1A",
|
|
22099
|
+
"Today's opportunities",
|
|
22100
|
+
"\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",
|
|
22101
|
+
"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."
|
|
22102
|
+
],
|
|
22103
|
+
[
|
|
22104
|
+
"\u5E2E\u6211\u76EF\u7740\u4ED3\u4F4D",
|
|
22105
|
+
"Watch my positions",
|
|
22106
|
+
"\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",
|
|
22107
|
+
"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."
|
|
22108
|
+
],
|
|
22109
|
+
[
|
|
22110
|
+
"\u628A\u56FE\u4E0A\u753B\u7EBF\u53D8\u6210\u7B56\u7565",
|
|
22111
|
+
"Turn my drawings into a strategy",
|
|
22112
|
+
`\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`,
|
|
22113
|
+
`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.`
|
|
22114
|
+
]
|
|
22115
|
+
];
|
|
21889
22116
|
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") },
|
|
22117
|
+
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
22118
|
}
|
|
21892
22119
|
|
|
21893
22120
|
// src/client/components/wallet-details.tsx
|
|
@@ -22015,22 +22242,60 @@ function TradingLimits({ open, onClose }) {
|
|
|
22015
22242
|
setBusy(false);
|
|
22016
22243
|
}
|
|
22017
22244
|
};
|
|
22018
|
-
const
|
|
22019
|
-
const
|
|
22020
|
-
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
|
|
22245
|
+
const [tab, setTab] = React31.useState("limits");
|
|
22246
|
+
const [wallet, setWallet] = React31.useState({ signer: "local", web3Account: "main", tradingAddress: "" });
|
|
22247
|
+
const [web3Accounts, setWeb3Accounts] = React31.useState({ available: false, accounts: [] });
|
|
22248
|
+
React31.useEffect(() => {
|
|
22249
|
+
if (!open) return;
|
|
22250
|
+
setWallet({ signer: st?.signer ?? "local", web3Account: st?.web3Account ?? "main", tradingAddress: st?.tradingAddress ?? "" });
|
|
22251
|
+
void hyperCall("walletAccounts").then(setWeb3Accounts).catch(() => setWeb3Accounts({ available: false, accounts: [] }));
|
|
22252
|
+
}, [open]);
|
|
22253
|
+
const saveWallet = () => {
|
|
22254
|
+
setBusy(true);
|
|
22255
|
+
setError("");
|
|
22256
|
+
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));
|
|
22257
|
+
};
|
|
22258
|
+
React31.useEffect(() => {
|
|
22259
|
+
if (open) setTab("limits");
|
|
22260
|
+
}, [open]);
|
|
22261
|
+
const saveCosts = () => {
|
|
22024
22262
|
setBusy(true);
|
|
22025
22263
|
setError("");
|
|
22026
22264
|
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
|
-
|
|
22265
|
+
};
|
|
22266
|
+
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 }));
|
|
22267
|
+
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"));
|
|
22268
|
+
const tabs = [["limits", "\u4EA4\u6613\u9650\u989D", "Limits"], ["costs", "\u6A21\u62DF\u6210\u672C", "Paper costs"], ["signing", "\u7B7E\u540D\u4E0E\u6258\u7BA1", "Signing & custody"]];
|
|
22269
|
+
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(
|
|
22270
|
+
"button",
|
|
22271
|
+
{
|
|
22272
|
+
key: key3,
|
|
22273
|
+
type: "button",
|
|
22274
|
+
role: "tab",
|
|
22275
|
+
id: `dhl-limits-tab-${key3}`,
|
|
22276
|
+
"aria-controls": `dhl-limits-panel-${key3}`,
|
|
22277
|
+
"aria-selected": tab === key3,
|
|
22278
|
+
className: tab === key3 ? "on" : "",
|
|
22279
|
+
onClick: () => {
|
|
22280
|
+
setTab(key3);
|
|
22281
|
+
setError("");
|
|
22282
|
+
}
|
|
22283
|
+
},
|
|
22284
|
+
tr(zh, en)
|
|
22285
|
+
))), /* @__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(
|
|
22286
|
+
"\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",
|
|
22287
|
+
"Applies to paper and live at once and survives restart; each mode has its own daily budget; reducing and closing are never blocked."
|
|
22288
|
+
))) : 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(
|
|
22289
|
+
import_dsh_client_ui_primitives9.Input,
|
|
22290
|
+
{
|
|
22291
|
+
"aria-label": tr("\u4E3B\u8D26\u6237\u5730\u5740", "Main account address"),
|
|
22292
|
+
placeholder: "0x\u2026",
|
|
22293
|
+
value: wallet.tradingAddress,
|
|
22294
|
+
maxLength: 42,
|
|
22295
|
+
spellCheck: false,
|
|
22296
|
+
onChange: (event) => setWallet((old) => ({ ...old, tradingAddress: event.target.value.trim() }))
|
|
22297
|
+
}
|
|
22298
|
+
))), 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
22299
|
}
|
|
22035
22300
|
|
|
22036
22301
|
// src/client/app.tsx
|
|
@@ -22043,6 +22308,22 @@ function App(props) {
|
|
|
22043
22308
|
const [walletOpen, setWalletOpen] = React32.useState(false);
|
|
22044
22309
|
const [moreOpen, setMoreOpen] = React32.useState(false);
|
|
22045
22310
|
const [limitsOpen, setLimitsOpen] = React32.useState(false);
|
|
22311
|
+
const [confirmManaged, setConfirmManaged] = React32.useState(false);
|
|
22312
|
+
const custody = st?.custody === "managed" ? "managed" : "assisted";
|
|
22313
|
+
const chooseCustody = React32.useCallback(async (next, acknowledge = false) => {
|
|
22314
|
+
setMoreOpen(false);
|
|
22315
|
+
if (next === "managed" && store.get().status?.tradingMode === "live" && !acknowledge) {
|
|
22316
|
+
setConfirmManaged(true);
|
|
22317
|
+
return;
|
|
22318
|
+
}
|
|
22319
|
+
try {
|
|
22320
|
+
await setCustody(next, acknowledge);
|
|
22321
|
+
} catch (error) {
|
|
22322
|
+
notify(tr("\u5207\u6362\u6258\u7BA1\u6A21\u5F0F\u5931\u8D25", "Could not switch custody") + "\uFF1A" + String(error?.message ?? error));
|
|
22323
|
+
} finally {
|
|
22324
|
+
setConfirmManaged(false);
|
|
22325
|
+
}
|
|
22326
|
+
}, []);
|
|
22046
22327
|
const moreRef = React32.useRef(null);
|
|
22047
22328
|
const network = st?.network === "testnet" ? "testnet" : "mainnet";
|
|
22048
22329
|
const mode = st?.tradingMode === "paper" ? "paper" : "live";
|
|
@@ -22150,12 +22431,12 @@ function App(props) {
|
|
|
22150
22431
|
const showSessions = !layout.sessionsCollapsed;
|
|
22151
22432
|
const showDesk = !layout.deskCollapsed;
|
|
22152
22433
|
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: () => {
|
|
22434
|
+
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
22435
|
setMoreOpen(false);
|
|
22155
22436
|
setLimitsOpen(true);
|
|
22156
22437
|
} }, 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
22438
|
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" }))));
|
|
22439
|
+
} }, 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
22440
|
}
|
|
22160
22441
|
|
|
22161
22442
|
// src/client/components/panel-entry.tsx
|