dsh-hyperliquid 0.4.0 → 0.4.2

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2 — 2026-10-17
4
+
5
+ - 画线对象属性对齐 TradingView:按周期显示(仅本周期,水平线的轴价格标签同样隐藏)、文本与标注气泡字号(10–32px)与粗体 / 斜体、矩形 / 圆 / 三角 / 通道 / 斐波那契 / 区间等的背景填充浓淡;浮动工具条、agent 工具参数、撤销与复制都支持。限定周期的画线在其他周期不绘制但保留在画线列表。
6
+ - 浮动工具条加入「隐藏」,并固定为一行(窄图表上原先会折成三行、挡住图上方的画线)。
7
+ - 新增 `docs/tradingview-drawing-checklist.md` 逐项对照(通用交互、对象属性、工具分组)。
8
+ - 修正「点击没反应」的根因说明:lightweight-charts 在 500ms 双击窗口内不为第二次点击触发单击回调;原生 click 路径已修复(0.1.0 对照:间隔 100ms 漏画 20/20、700ms 0/20)。
9
+
10
+ ## 0.4.1 — 2026-10-17
11
+
12
+ - 屏幕外剔除:完全位于可视区之外的有限范围画线不再渲染(会延伸到边缘、超出锚点,或带标签 / 提醒 / 同步徽章的画线不剔除;选中的画线不剔除)。
13
+ - 经 codex 审核:撤销成交量分布的独立命中区域,保持区间空白处可拖动平移、价位文字可点击。
14
+
3
15
  ## 0.4.0 — 2026-10-16
4
16
 
5
17
  - 首次见面先交付价值:系统提示按状态动态出现(档案为空时先做交易体检或市场雷达、实盘持仓没有足量止损时列为最高优先、连亏 3 笔时建议冷静期),并附一张交易卡模板;整段约 1000 token。
package/client.js CHANGED
@@ -691,10 +691,10 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
691
691
  /* Drawings: projected SVG over the price pane. */
692
692
  .dhl-drawing line, .dhl-drawing polygon { stroke: currentColor; stroke-width: var(--dhl-line-width, 1.8); stroke-dasharray: var(--dhl-dash, none); fill: none; vector-effect: non-scaling-stroke; }
693
693
  .dhl-drawing.role-support line, .dhl-drawing.role-resistance line, .dhl-drawing.role-neutral line { stroke-dasharray: var(--dhl-dash, 6 4); }
694
- .dhl-drawing .dhl-drawing-fill { fill: currentColor; fill-opacity: .08; stroke: currentColor; stroke-width: 1.2; }
694
+ .dhl-drawing .dhl-drawing-fill { fill: currentColor; fill-opacity: var(--dhl-fill-opacity, .08); stroke: currentColor; stroke-width: 1.2; }
695
695
  .dhl-drawing .dhl-drawing-mid { stroke-dasharray: 4 4; stroke-width: 1; opacity: .7; }
696
696
  .dhl-drawing-text { fill: currentColor; stroke: none; font-size: 10.5px; font-variant-numeric: tabular-nums; paint-order: stroke; }
697
- .dhl-drawing .dhl-fib-band { stroke: none; fill-opacity: .07; }
697
+ .dhl-drawing .dhl-fib-band { stroke: none; fill-opacity: var(--dhl-fill-opacity, .07); }
698
698
  .dhl-drawing .dhl-fib-band.band-0 { fill: #ef6f6c; } .dhl-drawing .dhl-fib-band.band-1 { fill: #e2a943; } .dhl-drawing .dhl-fib-band.band-2 { fill: #3b9c6d; }
699
699
  .dhl-drawing .dhl-fib-key { stroke-width: 2.2; }
700
700
  .dhl-measure.up { color: var(--dsw-alias-state-success-primary); } .dhl-measure.down { color: var(--dsw-alias-state-error-primary); }
@@ -716,14 +716,14 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
716
716
  .dhl-drawing.locked .dhl-chart-anchor { cursor: default; }
717
717
  .dhl-chart-anchor:hover { fill: currentColor; }
718
718
  .dhl-text-box { fill: var(--dsw-alias-bg-layer-1); fill-opacity: .92; stroke: currentColor; stroke-width: 1; }
719
- .dhl-drawing .dhl-text-value { fill: var(--dsw-alias-label-primary); font-size: 12px; }
719
+ .dhl-drawing .dhl-text-value { fill: var(--dsw-alias-label-primary); font-size: var(--dhl-font-size, 12px); font-weight: var(--dhl-font-weight, 400); font-style: var(--dhl-font-style, normal); }
720
720
  .dhl-position .dhl-position-stop { fill: var(--dsw-alias-state-error-primary); fill-opacity: .16; stroke: none; }
721
721
  .dhl-position .dhl-position-target { fill: var(--dsw-alias-state-success-primary); fill-opacity: .16; stroke: none; }
722
722
  .dhl-drawing .dhl-position .dhl-position-entry { stroke: var(--dsw-alias-label-secondary); stroke-width: 1.5; stroke-dasharray: none; }
723
723
  .dhl-drawing .dhl-position-text { fill: var(--dsw-alias-label-primary); font-size: 11px; font-weight: 600; }
724
724
 
725
725
  /* Floating style toolbar for the selected drawing. */
726
- .dhl-draw-toolbar { position: absolute; z-index: 6; top: 30px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 6px; max-width: calc(100% - 80px); flex-wrap: wrap; justify-content: center; padding: 5px 8px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); box-shadow: 0 8px 24px color-mix(in srgb, var(--dsw-alias-bg-base) 45%, transparent); font-size: 12px; }
726
+ .dhl-draw-toolbar { position: absolute; z-index: 6; top: 30px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 6px; max-width: calc(100% - 80px); flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; justify-content: flex-start; padding: 5px 8px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: var(--dsw-alias-bg-layer-1); box-shadow: 0 8px 24px color-mix(in srgb, var(--dsw-alias-bg-base) 45%, transparent); font-size: 12px; }
727
727
  .dhl-draw-toolbar-name { color: var(--dsw-alias-label-secondary); padding: 0 4px; white-space: nowrap; }
728
728
  .dhl-draw-toolbar-group { display: inline-flex; align-items: center; gap: 2px; padding-left: 6px; border-left: 1px solid var(--dsw-alias-border-l1); }
729
729
  .dhl-draw-toolbar button { display: inline-grid; place-items: center; min-width: 26px; height: 26px; padding: 0 4px; border: 0; border-radius: 5px; background: transparent; color: var(--dsw-alias-label-secondary); cursor: pointer; font: inherit; }
@@ -770,7 +770,7 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
770
770
  .dhl-drawing .dhl-brush { fill: none; stroke: currentColor; stroke-width: var(--dhl-line-width, 2); stroke-dasharray: var(--dhl-dash, none); stroke-linecap: round; stroke-linejoin: round; }
771
771
  .dhl-drawing-hit .dhl-brush { pointer-events: stroke; }
772
772
  .dhl-drawing .dhl-arrow-head, .dhl-drawing .dhl-callout-dot { fill: currentColor; stroke: none; }
773
- .dhl-drawing circle.dhl-drawing-fill { fill: currentColor; fill-opacity: .08; stroke: currentColor; stroke-width: var(--dhl-line-width, 1.5); stroke-dasharray: var(--dhl-dash, none); }
773
+ .dhl-drawing circle.dhl-drawing-fill { fill: currentColor; fill-opacity: var(--dhl-fill-opacity, .08); stroke: currentColor; stroke-width: var(--dhl-line-width, 1.5); stroke-dasharray: var(--dhl-dash, none); }
774
774
  .dhl-drawing polyline.dhl-drawing-mid { fill: none; stroke: currentColor; }
775
775
 
776
776
  /* Drawings object list (under the chart). */
@@ -946,6 +946,9 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
946
946
  .dhl-limits-hint { display: block; margin-top: 2px; color: var(--dsw-alias-label-secondary); font-size: 12px; }
947
947
  .dhl-limits-scroll .dhl-risk-error { margin: 10px 0 0; }
948
948
  .dhl-wallet-form ~ .dhl-limits-facts { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--dsw-alias-border-l1); }
949
+
950
+ /* One row like TradingView's floating toolbar: a wrapped toolbar covered drawings near the top of a narrow chart. */
951
+ .dhl-draw-toolbar > * { flex: none; }
949
952
  `;
950
953
 
951
954
  // src/client/locale.ts
@@ -17702,6 +17705,8 @@ function isSingleEmoji(value) {
17702
17705
  if (typeof value !== "string" || !value || value.length > 32 || !/\p{Extended_Pictographic}|\p{Regional_Indicator}/u.test(value)) return false;
17703
17706
  return [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(value)].length === 1;
17704
17707
  }
17708
+ var FONT_KINDS = ["text", "callout"];
17709
+ var FILL_KINDS = ["rect", "circle", "triangle", "channel", "fib", "regression", "gannbox", "pricerange", "daterange", "measure"];
17705
17710
  var FIB_LEVEL_KINDS = ["fib", "fibext", "fibchannel"];
17706
17711
  function volumeProfile(bars, { rows = 48, valueArea = 0.7 } = {}) {
17707
17712
  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);
@@ -17987,7 +17992,8 @@ function guide(a, b, value, key3, text) {
17987
17992
  }
17988
17993
  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" };
17989
17994
  var DASH = { solid: void 0, dashed: "7 5", dotted: "1.5 4" };
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 }) {
17995
+ 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, fontSize, bold, italic, fillOpacity }) {
17996
+ const k = (fontSize ?? 12) / 12;
17991
17997
  const badges = [...alert ? ["alert"] : [], ...shared ? ["shared"] : []];
17992
17998
  const { width: w, height: h } = view;
17993
17999
  const px = points2.map((point) => view.project(point.time, point.price));
@@ -18025,8 +18031,8 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18025
18031
  body = base;
18026
18032
  break;
18027
18033
  }
18028
- const k = points2[1].time === points2[0].time ? 0 : (points2[2].time - points2[0].time) / (points2[1].time - points2[0].time);
18029
- const offset = points2[2].price - (points2[0].price + (points2[1].price - points2[0].price) * k);
18034
+ const k2 = points2[1].time === points2[0].time ? 0 : (points2[2].time - points2[0].time) / (points2[1].time - points2[0].time);
18035
+ const offset = points2[2].price - (points2[0].price + (points2[1].price - points2[0].price) * k2);
18030
18036
  const a2 = view.project(points2[0].time, points2[0].price + offset), b2 = view.project(points2[1].time, points2[1].price + offset);
18031
18037
  const am = view.project(points2[0].time, points2[0].price + offset / 2), bm = view.project(points2[1].time, points2[1].price + offset / 2);
18032
18038
  if (!a2 || !b2 || !am || !bm) {
@@ -18110,8 +18116,8 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18110
18116
  case "callout": {
18111
18117
  if (!p[1]) break;
18112
18118
  const value = textValue || label || tr("\u6807\u6CE8", "Note");
18113
- const width = Math.min(280, 16 + [...value].reduce((sum, ch) => sum + (ch.charCodeAt(0) > 255 ? 12 : 6.6), 0));
18114
- body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y + 12 }), /* @__PURE__ */ React7.createElement("circle", { className: "dhl-callout-dot", cx: p[0].x, cy: p[0].y, r: "3" }), /* @__PURE__ */ React7.createElement("rect", { className: "dhl-text-box", x: p[1].x, y: p[1].y, width, height: "26", rx: "6" }), text(p[1].x + 8, p[1].y + 17, value, "start", "dhl-drawing-text dhl-text-value"));
18119
+ const width = Math.min(280, 16 + [...value].reduce((sum, ch) => sum + (ch.charCodeAt(0) > 255 ? 12 : 6.6), 0)) * k;
18120
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y + 13 * k }), /* @__PURE__ */ React7.createElement("circle", { className: "dhl-callout-dot", cx: p[0].x, cy: p[0].y, r: "3" }), /* @__PURE__ */ React7.createElement("rect", { className: "dhl-text-box", x: p[1].x, y: p[1].y, width, height: 26 * k, rx: "6" }), text(p[1].x + 8 * k, p[1].y + 17 * k, value, "start", "dhl-drawing-text dhl-text-value"));
18115
18121
  break;
18116
18122
  }
18117
18123
  case "fibext": {
@@ -18132,8 +18138,8 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18132
18138
  body = /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y });
18133
18139
  break;
18134
18140
  }
18135
- const k = (points2[2].time - points2[0].time) / (points2[1].time - points2[0].time || 1);
18136
- const offset = points2[2].price - (points2[0].price + (points2[1].price - points2[0].price) * k);
18141
+ const k2 = (points2[2].time - points2[0].time) / (points2[1].time - points2[0].time || 1);
18142
+ const offset = points2[2].price - (points2[0].price + (points2[1].price - points2[0].price) * k2);
18137
18143
  body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, (levels ?? FIB_LEVELS).map((level) => {
18138
18144
  const a2 = view.project(points2[0].time, points2[0].price + offset * level), b2 = view.project(points2[1].time, points2[1].price + offset * level);
18139
18145
  const seg = a2 && b2 ? clipLine(a2, b2, w, h, 0) : null;
@@ -18271,8 +18277,8 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18271
18277
  case "text": {
18272
18278
  const value = textValue || label || tr("\u6587\u672C", "Text");
18273
18279
  const lines = wrapText(value, 300);
18274
- const width = 14 + Math.max(...lines.map(textWidth));
18275
- body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-text-box", x: p[0].x, y: p[0].y - 20, width, height: 8 + lines.length * 16, rx: "4" }), lines.map((line, index) => /* @__PURE__ */ React7.createElement(React7.Fragment, { key: index }, text(p[0].x + 7, p[0].y - 4 + index * 16, line, "start", "dhl-drawing-text dhl-text-value"))));
18280
+ const width = (14 + Math.max(...lines.map(textWidth))) * k;
18281
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-text-box", x: p[0].x, y: p[0].y - 20 * k, width, height: (8 + lines.length * 16) * k, rx: "4" }), lines.map((line, index) => /* @__PURE__ */ React7.createElement(React7.Fragment, { key: index }, text(p[0].x + 7 * k, p[0].y + (-4 + index * 16) * k, line, "start", "dhl-drawing-text dhl-text-value"))));
18276
18282
  break;
18277
18283
  }
18278
18284
  case "measure": {
@@ -18302,7 +18308,15 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18302
18308
  }
18303
18309
  }
18304
18310
  if (body == null) return null;
18305
- const style = { color, "--dhl-line-width": lineWidth ? String(lineWidth) : void 0, "--dhl-dash": lineStyle ? DASH[lineStyle] ?? "none" : void 0 };
18311
+ const style = {
18312
+ color,
18313
+ "--dhl-line-width": lineWidth ? String(lineWidth) : void 0,
18314
+ "--dhl-dash": lineStyle ? DASH[lineStyle] ?? "none" : void 0,
18315
+ "--dhl-fill-opacity": fillOpacity != null ? String(fillOpacity) : void 0,
18316
+ "--dhl-font-size": fontSize ? `${fontSize}px` : void 0,
18317
+ "--dhl-font-weight": bold ? "700" : void 0,
18318
+ "--dhl-font-style": italic ? "italic" : void 0
18319
+ };
18306
18320
  return /* @__PURE__ */ React7.createElement("g", { className, style, "data-mark-id": id }, body, label && !isTextKind(kind) && kind !== "icon" && p[0] ? text(Math.min(Math.max(p[0].x + 6, 4), w - 120), p[0].y - 6, label) : null, badges?.length && p[0] ? badges.map((badge, index) => /* @__PURE__ */ React7.createElement("svg", { key: badge, className: "dhl-drawing-badge", x: Math.min(Math.max(p[0].x + 6, 4), w - 40) + index * 16, y: p[0].y + 5, width: "13", height: "13", viewBox: "0 0 24 24" }, /* @__PURE__ */ React7.createElement("path", { d: BADGE_PATHS[badge], fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }))) : null, interactive ? /* @__PURE__ */ React7.createElement("g", { className: "dhl-drawing-hit", "data-mark-id": id, "data-hit": "body" }, body) : null, anchors ? handles.map((point, index) => /* @__PURE__ */ React7.createElement("circle", { key: index, className: "dhl-chart-anchor", "data-mark-id": id, "data-anchor": index, cx: point.x, cy: point.y, r: "5" })) : null);
18307
18321
  });
18308
18322
 
@@ -18345,6 +18359,8 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
18345
18359
  setText(mark.label);
18346
18360
  }, [mark.id, mark.label]);
18347
18361
  const locked = Boolean(mark.locked) || globallyLocked;
18362
+ const interval = useStore().interval;
18363
+ const onlyHere = Boolean(mark.visibleIntervals?.length);
18348
18364
  const defaultLevels = (mark.levels ?? (mark.kind === "fibext" ? FIB_EXT_LEVELS : FIB_LEVELS)).join(", ");
18349
18365
  const [levelsText, setLevelsText] = React8.useState(defaultLevels);
18350
18366
  React8.useEffect(() => {
@@ -18419,7 +18435,7 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
18419
18435
  onClick: () => onEdit({ lineStyle: value })
18420
18436
  },
18421
18437
  /* @__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" }))
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(
18438
+ ))) : null, FONT_KINDS.includes(mark.kind) || FILL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6587\u5B57\u4E0E\u586B\u5145", "Text and fill") }, FONT_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u5B57\u53F7", "Font size"), title: tr("\u5B57\u53F7", "Font size"), value: String(mark.fontSize ?? 12), disabled: locked, onChange: (event) => onEdit({ fontSize: Number(event.target.value) }) }, [10, 12, 14, 18, 24, 32].map((size3) => /* @__PURE__ */ React8.createElement("option", { key: size3, value: size3 }, `${size3}px`))) : null, FONT_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement("button", { type: "button", className: mark.bold ? "on" : "", "aria-pressed": Boolean(mark.bold), disabled: locked, "aria-label": tr("\u7C97\u4F53", "Bold"), title: tr("\u7C97\u4F53", "Bold"), onClick: () => onEdit({ bold: !mark.bold }) }, /* @__PURE__ */ React8.createElement("b", null, "B")), /* @__PURE__ */ React8.createElement("button", { type: "button", className: mark.italic ? "on" : "", "aria-pressed": Boolean(mark.italic), disabled: locked, "aria-label": tr("\u659C\u4F53", "Italic"), title: tr("\u659C\u4F53", "Italic"), onClick: () => onEdit({ italic: !mark.italic }) }, /* @__PURE__ */ React8.createElement("i", null, "I"))) : null, FILL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u80CC\u666F\u586B\u5145", "Background fill"), title: tr("\u80CC\u666F\u586B\u5145", "Background fill"), value: String(mark.fillOpacity ?? (mark.kind === "fib" ? 0.07 : 0.08)), disabled: locked, onChange: (event) => onEdit({ fillOpacity: Number(event.target.value) }) }, [[0, "\u65E0\u586B\u5145", "No fill"], [mark.kind === "fib" ? 0.07 : 0.08, "\u6DE1", "Light"], [0.18, "\u4E2D", "Medium"], [0.32, "\u6D53", "Strong"]].map(([value, zh, en]) => /* @__PURE__ */ React8.createElement("option", { key: String(value), value: String(value) }, tr(String(zh), String(en))))) : null) : 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
18439
  "input",
18424
18440
  {
18425
18441
  className: "dhl-draw-toolbar-text dhl-fib-levels",
@@ -18434,7 +18450,19 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
18434
18450
  },
18435
18451
  onBlur: applyLevels
18436
18452
  }
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(
18453
+ )) : 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(
18454
+ "button",
18455
+ {
18456
+ type: "button",
18457
+ className: onlyHere ? "on" : "",
18458
+ "aria-pressed": onlyHere,
18459
+ disabled: locked,
18460
+ title: onlyHere ? tr(`\u4EC5\u5728 ${mark.visibleIntervals.join("\u3001")} \u663E\u793A\uFF1B\u70B9\u51FB\u6539\u4E3A\u6240\u6709\u5468\u671F`, `Shown on ${mark.visibleIntervals.join(", ")} only; click to show on all`) : tr(`\u53EA\u5728\u5F53\u524D\u5468\u671F\uFF08${interval}\uFF09\u663E\u793A`, `Show on the current interval (${interval}) only`),
18461
+ "aria-label": onlyHere ? tr("\u6539\u4E3A\u6240\u6709\u5468\u671F\u663E\u793A", "Show on all intervals") : tr("\u4EC5\u672C\u5468\u671F\u663E\u793A", "Show on this interval only"),
18462
+ onClick: () => onEdit({ visibleIntervals: onlyHere ? null : [interval] })
18463
+ },
18464
+ /* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("rect", { x: "4", y: "5", width: "16", height: "15", rx: "2" }), /* @__PURE__ */ React8.createElement("path", { d: "M4 10h16M9 3v4M15 3v4" }))
18465
+ ), /* @__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(
18438
18466
  "button",
18439
18467
  {
18440
18468
  type: "button",
@@ -18483,7 +18511,10 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
18483
18511
  onClick: () => onEdit({ locked: !mark.locked })
18484
18512
  },
18485
18513
  /* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }), /* @__PURE__ */ React8.createElement("path", { d: mark.locked ? "M8 11V8a4 4 0 0 1 8 0v3" : "M8 11V8a4 4 0 0 1 7.5-2" }))
18486
- ), /* @__PURE__ */ React8.createElement("button", { type: "button", disabled: locked, title: tr("\u5220\u9664\uFF08Delete\uFF09", "Delete (Del)"), "aria-label": tr("\u5220\u9664\u753B\u7EBF", "Delete drawing"), onClick: onRemove }, /* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("path", { d: "M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" }))), /* @__PURE__ */ React8.createElement("button", { type: "button", title: tr("\u53D6\u6D88\u9009\u62E9\uFF08Esc\uFF09", "Deselect (Esc)"), "aria-label": tr("\u53D6\u6D88\u9009\u62E9", "Deselect"), onClick: onClose }, /* @__PURE__ */ React8.createElement(UiIcon, { name: "close", size: 14 }))));
18514
+ ), /* @__PURE__ */ React8.createElement("button", { type: "button", title: tr("\u9690\u85CF\uFF08\u5728\u753B\u7EBF\u5217\u8868\u91CC\u53EF\u91CD\u65B0\u663E\u793A\uFF09", "Hide (show it again from the drawing list)"), "aria-label": tr("\u9690\u85CF\u753B\u7EBF", "Hide drawing"), onClick: () => {
18515
+ onEdit({ hidden: true });
18516
+ onClose();
18517
+ } }, /* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("path", { d: "M3 12s3.5-6 9-6 9 6 9 6-3.5 6-9 6-9-6-9-6zM4 20 20 4" }))), /* @__PURE__ */ React8.createElement("button", { type: "button", disabled: locked, title: tr("\u5220\u9664\uFF08Delete\uFF09", "Delete (Del)"), "aria-label": tr("\u5220\u9664\u753B\u7EBF", "Delete drawing"), onClick: onRemove }, /* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("path", { d: "M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" }))), /* @__PURE__ */ React8.createElement("button", { type: "button", title: tr("\u53D6\u6D88\u9009\u62E9\uFF08Esc\uFF09", "Deselect (Esc)"), "aria-label": tr("\u53D6\u6D88\u9009\u62E9", "Deselect"), onClick: onClose }, /* @__PURE__ */ React8.createElement(UiIcon, { name: "close", size: 14 }))));
18487
18518
  }
18488
18519
 
18489
18520
  // src/client/components/drawing-editor.tsx
@@ -18686,6 +18717,37 @@ function cssVar(el, name, fallback) {
18686
18717
  function chartData(points2) {
18687
18718
  return points2.map((point) => ({ time: point.time, value: point.value }));
18688
18719
  }
18720
+ var FINITE_KINDS = /* @__PURE__ */ new Set([
18721
+ "trend",
18722
+ "rect",
18723
+ "measure",
18724
+ "fib",
18725
+ "long",
18726
+ "short",
18727
+ "text",
18728
+ "arrow",
18729
+ "triangle",
18730
+ "brush",
18731
+ "infoline",
18732
+ "trendangle",
18733
+ "pricerange",
18734
+ "daterange",
18735
+ "elliott",
18736
+ "elliottabc",
18737
+ "xabcd",
18738
+ "abcd",
18739
+ "gannbox",
18740
+ "gannsquare",
18741
+ "icon",
18742
+ "vprofile",
18743
+ "regression"
18744
+ ]);
18745
+ function offscreen(points2, view) {
18746
+ if (!points2.length) return false;
18747
+ const xs = points2.map((point) => view.project(point.time, point.price)?.x);
18748
+ if (xs.some((x) => x == null)) return false;
18749
+ return xs.every((x) => x < -320) || xs.every((x) => x > view.width + 320);
18750
+ }
18689
18751
  var profileCache = /* @__PURE__ */ new WeakMap();
18690
18752
  function cachedProfile(candles, from, to, options = {}) {
18691
18753
  let byRange = profileCache.get(candles);
@@ -18697,7 +18759,7 @@ function cachedProfile(candles, from, to, options = {}) {
18697
18759
  if (!byRange.has(key3)) byRange.set(key3, volumeProfile(candles.filter((c) => c.t >= from && c.t <= to).map((c) => ({ open: c.o, high: c.h, low: c.l, close: c.c, volume: c.v })), options));
18698
18760
  return byRange.get(key3);
18699
18761
  }
18700
- function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMode, onDraw, onSelect, priceScale, visibleRange, onVisibleRange, tradeEvents, onNeedOlder, olderState, focus, maLines, magnet, drawingsLocked, onEditMark, onRemoveMark, onRemoveMarks, onEditMarks, onEditEach, showReasons, onAnalyzeFill, onCloneMark, selectRequest, chartType = "candles", command = null, caption = "", indicatorSettings, showDataWindow = false, link, compact = false, readOnly = false }) {
18762
+ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMode, onDraw, onSelect, priceScale, visibleRange, onVisibleRange, tradeEvents, onNeedOlder, olderState, focus, maLines, magnet, drawingsLocked, onEditMark, onRemoveMark, interval, onRemoveMarks, onEditMarks, onEditEach, showReasons, onAnalyzeFill, onCloneMark, selectRequest, chartType = "candles", command = null, caption = "", indicatorSettings, showDataWindow = false, link, compact = false, readOnly = false }) {
18701
18763
  const boxRef = React10.useRef(null);
18702
18764
  const clipId = React10.useRef("dhl-plot-" + Math.random().toString(36).slice(2));
18703
18765
  const linkRef = React10.useRef(link);
@@ -19425,6 +19487,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19425
19487
  const styleOf = (mark) => mark.lineStyle === "dotted" ? LineStyle.Dotted : mark.lineStyle === "dashed" || !mark.lineStyle && mark.source !== "user" ? LineStyle.Dashed : LineStyle.Solid;
19426
19488
  for (const mark of marks) {
19427
19489
  if (mark.kind !== "level" || mark.hidden || !Number.isFinite(mark.price) || Number(mark.price) <= 0) continue;
19490
+ if (interval && mark.visibleIntervals?.length && !mark.visibleIntervals.includes(interval)) continue;
19428
19491
  priceLines.current.set(mark.id, r.candles.createPriceLine({
19429
19492
  price: Number(mark.price),
19430
19493
  color: colorOf(mark),
@@ -19434,7 +19497,9 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19434
19497
  title: mark.label
19435
19498
  }));
19436
19499
  }
19437
- }, [marks, themeVersion, chartType]);
19500
+ ;
19501
+ window.__dhlPriceLines = () => priceLines.current.size;
19502
+ }, [marks, themeVersion, chartType, interval]);
19438
19503
  const warming = candles.length < 2;
19439
19504
  const markById = React10.useMemo(() => new Map(marks.map((mark) => [mark.id, mark])), [marks]);
19440
19505
  const markByIdRef = React10.useRef(markById);
@@ -19479,6 +19544,17 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19479
19544
  React10.useEffect(() => {
19480
19545
  if (selectedId && !markById.has(selectedId)) setSelectedId(null);
19481
19546
  }, [markById, selectedId]);
19547
+ React10.useEffect(() => {
19548
+ const hiddenHere = (id) => {
19549
+ const limit = markById.get(id)?.visibleIntervals;
19550
+ return Boolean(interval && limit?.length && !limit.includes(interval));
19551
+ };
19552
+ if (selectedId && hiddenHere(selectedId)) setSelectedId(null);
19553
+ setGroup((current2) => {
19554
+ const next = current2.filter((id) => !hiddenHere(id));
19555
+ return next.length === current2.length ? current2 : next;
19556
+ });
19557
+ }, [interval, markById, selectedId]);
19482
19558
  React10.useEffect(() => {
19483
19559
  if (drawMode) setSelectedId(null);
19484
19560
  }, [drawMode]);
@@ -19590,7 +19666,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19590
19666
  const cloneOf = React10.useCallback((mark) => {
19591
19667
  const line = timelineRef.current;
19592
19668
  const shifted = markPoints(mark).map((point) => ({ time: line.toTime(line.toLogical(point.time) + 5), price: point.price }));
19593
- const style = Object.fromEntries(Object.entries({ color: mark.color, lineWidth: mark.lineWidth, lineStyle: mark.lineStyle, levels: mark.levels, rows: mark.rows, valueArea: mark.valueArea }).filter(([, value]) => value != null));
19669
+ const style = Object.fromEntries(Object.entries({ color: mark.color, lineWidth: mark.lineWidth, lineStyle: mark.lineStyle, levels: mark.levels, rows: mark.rows, valueArea: mark.valueArea, fontSize: mark.fontSize, bold: mark.bold, italic: mark.italic, fillOpacity: mark.fillOpacity, visibleIntervals: mark.visibleIntervals }).filter(([, value]) => value != null));
19594
19670
  return { kind: mark.kind, geometry: geometryPatch(mark.kind, shifted), style, label: mark.label };
19595
19671
  }, []);
19596
19672
  const clipboard = React10.useRef(null);
@@ -19837,6 +19913,9 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19837
19913
  const live = draft?.id === mark.id ? draft.points : groupDraft?.get(mark.id) ?? points2;
19838
19914
  const shown = mark.kind === "level" ? [{ time: lastTime, price: live[0].price }] : live;
19839
19915
  const selected = selectedId === mark.id || group.includes(mark.id);
19916
+ if (interval && mark.visibleIntervals?.length && !mark.visibleIntervals.includes(interval)) return null;
19917
+ const clampedDecoration = !(mark.source === "user" || mark.kind === "level") || Boolean(mark.alert) || Boolean(mark.shared);
19918
+ if (!selected && !clampedDecoration && FINITE_KINDS.has(mark.kind) && offscreen(shown, view)) return null;
19840
19919
  return /* @__PURE__ */ React10.createElement(
19841
19920
  DrawingShape,
19842
19921
  {
@@ -19858,7 +19937,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19858
19937
  shared: mark.shared,
19859
19938
  rows: mark.rows,
19860
19939
  valueArea: mark.valueArea,
19861
- levels: mark.levels
19940
+ levels: mark.levels,
19941
+ fontSize: mark.fontSize,
19942
+ bold: mark.bold,
19943
+ italic: mark.italic,
19944
+ fillOpacity: mark.fillOpacity
19862
19945
  }
19863
19946
  );
19864
19947
  }), /* @__PURE__ */ React10.createElement("g", { className: "dhl-fills" }, fills.map((event) => {
@@ -19934,7 +20017,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19934
20017
  var React11 = __toESM(require("react"), 1);
19935
20018
  var HISTORY_LIMIT = 100;
19936
20019
  var GEOMETRY = ["price", "fromTime", "fromPrice", "toTime", "toPrice", "points"];
19937
- var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea", "levels"];
20020
+ var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea", "levels", "visibleIntervals", "fontSize", "fillOpacity", "bold", "italic"];
19938
20021
  var FLAGS = /* @__PURE__ */ new Set(["locked", "hidden", "shared", "alert"]);
19939
20022
  function recreateRequest(mark) {
19940
20023
  const out = { kind: mark.kind, label: mark.label, role: mark.role, coin: mark.coin, interval: mark.interval };
@@ -20137,7 +20220,7 @@ var DrawingList = React12.memo(function DrawingList2({ marks, locked, onSelect,
20137
20220
  const hidden = marks.filter((mark) => mark.hidden).length;
20138
20221
  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) => {
20139
20222
  const tool = toolOf(mark.kind);
20140
- return /* @__PURE__ */ React12.createElement("li", { key: mark.id, className: mark.hidden ? "is-hidden" : "" }, /* @__PURE__ */ React12.createElement("button", { type: "button", className: "dhl-drawing-list-name", title: tr("\u5728\u56FE\u4E0A\u5B9A\u4F4D\u5E76\u9009\u4E2D", "Find and select on the chart"), onClick: () => onSelect(mark.id), disabled: mark.hidden }, /* @__PURE__ */ React12.createElement("span", { style: { color: mark.color } }, /* @__PURE__ */ React12.createElement(ToolIcon, { kind: mark.kind, size: 14 })), /* @__PURE__ */ React12.createElement("b", null, mark.label), /* @__PURE__ */ React12.createElement("small", null, mark.alert ? /* @__PURE__ */ React12.createElement("span", { className: "dhl-list-flag", title: tr("\u4EF7\u683C\u63D0\u9192\u5DF2\u5F00\u542F", "Price alert armed") }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "bell", size: 12 })) : null, mark.shared ? /* @__PURE__ */ React12.createElement("span", { className: "dhl-list-flag", title: tr("\u5DF2\u540C\u6B65\u5230\u6240\u6709\u4F1A\u8BDD", "Synced to all sessions") }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "sync", size: 12 })) : null, tr(tool.zh, tool.en), " \xB7 ", mark.interval, mark.source === "agent" ? " \xB7 agent" : "")), /* @__PURE__ */ React12.createElement("button", { type: "button", "aria-pressed": !mark.hidden, title: mark.hidden ? tr("\u663E\u793A", "Show") : tr("\u9690\u85CF", "Hide"), "aria-label": `${mark.hidden ? tr("\u663E\u793A", "Show") : tr("\u9690\u85CF", "Hide")} ${mark.label}`, onClick: () => onEdit(mark.id, { hidden: !mark.hidden }) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: mark.hidden ? "eyeOff" : "eye", size: 14 })), /* @__PURE__ */ React12.createElement("button", { type: "button", className: mark.locked ? "on" : "", "aria-pressed": Boolean(mark.locked), disabled: locked, title: mark.locked ? tr("\u89E3\u9501", "Unlock") : tr("\u9501\u5B9A", "Lock"), "aria-label": `${mark.locked ? tr("\u89E3\u9501", "Unlock") : tr("\u9501\u5B9A", "Lock")} ${mark.label}`, onClick: () => onEdit(mark.id, { locked: !mark.locked }) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: mark.locked ? "lock" : "unlock", size: 14 })), /* @__PURE__ */ React12.createElement("button", { type: "button", disabled: locked || mark.locked, title: tr("\u5220\u9664", "Delete"), "aria-label": `${tr("\u5220\u9664\u753B\u7EBF", "Remove line")} ${mark.label}`, onClick: () => onRemove(mark.id) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "trash", size: 14 })));
20223
+ return /* @__PURE__ */ React12.createElement("li", { key: mark.id, "data-mark-id": mark.id, className: mark.hidden ? "is-hidden" : "" }, /* @__PURE__ */ React12.createElement("button", { type: "button", className: "dhl-drawing-list-name", title: tr("\u5728\u56FE\u4E0A\u5B9A\u4F4D\u5E76\u9009\u4E2D", "Find and select on the chart"), onClick: () => 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.visibleIntervals?.length ? tr(` \xB7 \u4EC5 ${mark.visibleIntervals.join("/")} \u663E\u793A`, ` \xB7 ${mark.visibleIntervals.join("/")} only`) : "", mark.source === "agent" ? " \xB7 agent" : "")), /* @__PURE__ */ React12.createElement("button", { type: "button", "aria-pressed": !mark.hidden, title: mark.hidden ? tr("\u663E\u793A", "Show") : tr("\u9690\u85CF", "Hide"), "aria-label": `${mark.hidden ? tr("\u663E\u793A", "Show") : tr("\u9690\u85CF", "Hide")} ${mark.label}`, onClick: () => onEdit(mark.id, { hidden: !mark.hidden }) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: mark.hidden ? "eyeOff" : "eye", size: 14 })), /* @__PURE__ */ React12.createElement("button", { type: "button", className: mark.locked ? "on" : "", "aria-pressed": Boolean(mark.locked), disabled: locked, title: mark.locked ? tr("\u89E3\u9501", "Unlock") : tr("\u9501\u5B9A", "Lock"), "aria-label": `${mark.locked ? tr("\u89E3\u9501", "Unlock") : tr("\u9501\u5B9A", "Lock")} ${mark.label}`, onClick: () => onEdit(mark.id, { locked: !mark.locked }) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: mark.locked ? "lock" : "unlock", size: 14 })), /* @__PURE__ */ React12.createElement("button", { type: "button", disabled: locked || mark.locked, title: tr("\u5220\u9664", "Delete"), "aria-label": `${tr("\u5220\u9664\u753B\u7EBF", "Remove line")} ${mark.label}`, onClick: () => onRemove(mark.id) }, /* @__PURE__ */ React12.createElement(UiIcon, { name: "trash", size: 14 })));
20141
20224
  })) : null);
20142
20225
  });
20143
20226
 
@@ -21559,6 +21642,7 @@ function DeskColumn({ onCollapse }) {
21559
21642
  indicators,
21560
21643
  expanded: chartExpanded,
21561
21644
  resetToken: chartResetToken,
21645
+ interval: s.interval,
21562
21646
  drawMode,
21563
21647
  onDraw: draw,
21564
21648
  onSelect: (selection) => insertChartReference(selection, coin, s.interval),
@@ -1,6 +1,6 @@
1
1
  export type DrawingKind = 'level' | 'hray' | 'vline' | 'text' | 'trend' | 'ray' | 'extended' | 'fib' | 'rect' | 'measure' | 'channel' | 'long' | 'short' | 'fibext' | 'fibchannel' | 'fibtime' | 'gannfan' | 'pitchfork' | 'regression' | 'vprofile' | 'arrow' | 'callout' | 'circle' | 'triangle' | 'brush' | 'crossline' | 'pricelabel' | 'infoline' | 'trendangle' | 'pricerange' | 'daterange' | 'elliott' | 'elliottabc' | 'xabcd' | 'abcd' | 'headshoulders' | 'gannbox' | 'gannsquare' | 'schiff' | 'modschiff' | 'icon'
2
2
  export type LineStyleName = 'solid' | 'dashed' | 'dotted'
3
- export interface DrawingStyle { levels?: number[]; rows?: number; valueArea?: number; color?: string; lineWidth?: number; lineStyle?: LineStyleName; locked?: boolean; hidden?: boolean; shared?: boolean; alert?: boolean }
3
+ export interface DrawingStyle { visibleIntervals?: string[]; fontSize?: number; bold?: boolean; italic?: boolean; fillOpacity?: number; levels?: number[]; rows?: number; valueArea?: number; color?: string; lineWidth?: number; lineStyle?: LineStyleName; locked?: boolean; hidden?: boolean; shared?: boolean; alert?: boolean }
4
4
  export declare const DRAWING_POINTS: Record<DrawingKind, number>
5
5
  export declare const DRAWING_KINDS: DrawingKind[]
6
6
  export declare const DRAWING_CLICKS: Record<DrawingKind, number>
@@ -26,3 +26,6 @@ export declare function volumeProfile(bars: Array<{ open: number; high: number;
26
26
  export declare const PROFILE_ROWS_MIN: number
27
27
  export declare const PROFILE_ROWS_MAX: number
28
28
  export declare const FIB_LEVEL_KINDS: DrawingKind[]
29
+ export declare const INTERVALS: string[]
30
+ export declare const FONT_KINDS: DrawingKind[]
31
+ export declare const FILL_KINDS: DrawingKind[]
@@ -52,6 +52,12 @@ export function isSingleEmoji(value) {
52
52
  * from the POC toward the heavier neighbour until it holds `valueArea` of the volume.
53
53
  * @param {Array<{ open: number, high: number, low: number, close: number, volume: number }>} bars
54
54
  */
55
+ /** Chart intervals a drawing can be limited to (same set as the desk). */
56
+ export const INTERVALS = ['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '3d', '1w', '1M']
57
+ /** Text drawings whose font size can be set. */
58
+ export const FONT_KINDS = ['text', 'callout']
59
+ /** Drawings with a background fill whose opacity can be set. */
60
+ export const FILL_KINDS = ['rect', 'circle', 'triangle', 'channel', 'fib', 'regression', 'gannbox', 'pricerange', 'daterange', 'measure']
55
61
  /** Fibonacci drawings whose levels can be customised. */
56
62
  export const FIB_LEVEL_KINDS = ['fib', 'fibext', 'fibchannel']
57
63
  export const PROFILE_ROWS_MIN = 6
@@ -188,6 +194,30 @@ export function drawingStyle(request) {
188
194
  if (!LINE_STYLES.includes(request.lineStyle)) throw new Error(`线型必须为 ${LINE_STYLES.join('/')}`)
189
195
  style.lineStyle = request.lineStyle
190
196
  }
197
+ // TradingView object properties: visibility per interval, text size, background fill.
198
+ if (request?.visibleIntervals != null) {
199
+ const list = Array.isArray(request.visibleIntervals) ? [...new Set(request.visibleIntervals.map(String))] : []
200
+ if (!list.length || list.some((iv) => !INTERVALS.includes(iv))) throw new Error(`visibleIntervals 需要 1 个以上周期:${INTERVALS.join('/')}`)
201
+ style.visibleIntervals = list
202
+ }
203
+ if (request?.fontSize != null) {
204
+ if (request?.kind != null && !FONT_KINDS.includes(request.kind)) throw new Error(`fontSize 只适用于 ${FONT_KINDS.join('/')}`)
205
+ const size = Number(request.fontSize)
206
+ if (!Number.isInteger(size) || size < 10 || size > 32) throw new Error('字号必须为 10-32 的整数')
207
+ style.fontSize = size
208
+ }
209
+ for (const flag of ['bold', 'italic']) {
210
+ if (request?.[flag] == null) continue
211
+ if (request?.kind != null && !FONT_KINDS.includes(request.kind)) throw new Error(`${flag} 只适用于 ${FONT_KINDS.join('/')}`)
212
+ if (typeof request[flag] !== 'boolean') throw new Error(`${flag} 必须为布尔值`)
213
+ style[flag] = request[flag]
214
+ }
215
+ if (request?.fillOpacity != null) {
216
+ if (request?.kind != null && !FILL_KINDS.includes(request.kind)) throw new Error(`fillOpacity 只适用于 ${FILL_KINDS.join('/')}`)
217
+ const opacity = Number(request.fillOpacity)
218
+ if (!Number.isFinite(opacity) || opacity < 0 || opacity > 0.6) throw new Error('填充透明度必须在 0-0.6 之间')
219
+ style.fillOpacity = opacity
220
+ }
191
221
  // Custom Fibonacci levels (TradingView lets each fib drawing choose its own).
192
222
  if (request?.levels != null) {
193
223
  if (request?.kind != null && !FIB_LEVEL_KINDS.includes(request.kind)) throw new Error(`levels 只适用于 ${FIB_LEVEL_KINDS.join('/')}`)
package/lib/remote.js CHANGED
@@ -575,7 +575,7 @@ export class HyperRemote extends TypertRemoteService {
575
575
  const fields = { ...drawingStyle({ ...patch, kind: current.kind }) }
576
576
  if (patch?.alert === true) fields.alertTriggeredAt = undefined
577
577
  // null clears a style back to its default (undo of a first restyle relies on this).
578
- for (const key of ['color', 'lineWidth', 'lineStyle', 'rows', 'valueArea', 'levels']) if (key in patch && patch[key] === null) fields[key] = undefined
578
+ for (const key of ['color', 'lineWidth', 'lineStyle', 'rows', 'valueArea', 'levels', 'visibleIntervals', 'fontSize', 'fillOpacity', 'bold', 'italic']) if (key in patch && patch[key] === null) fields[key] = undefined
579
579
  if (geometryKeys.some((key) => key in patch)) Object.assign(fields, drawingGeometry(current.kind, merged))
580
580
  if ('label' in patch) {
581
581
  const label = String(patch.label ?? '').trim()
package/lib/tools.js CHANGED
@@ -252,6 +252,10 @@ function buildTools(remote) {
252
252
  smoothingPeriod: { type: 'number', description: 'kind=ma 可选:平滑周期' },
253
253
  color: { type: 'string', description: '可选 #RRGGBB 颜色(画线或均线)' },
254
254
  lineWidth: { type: 'number', description: '画线线宽 1-4' },
255
+ visibleIntervals: { type: 'array', items: { type: 'string' }, description: '只在这些周期显示(如 ["4h","1d"]);省略=所有周期' },
256
+ fontSize: { type: 'number', description: 'kind=text/callout:字号 10-32' },
257
+ bold: { type: 'boolean', description: 'kind=text/callout:粗体' }, italic: { type: 'boolean', description: 'kind=text/callout:斜体' },
258
+ fillOpacity: { type: 'number', description: '矩形/圆/三角形/通道/斐波那契/区间等的背景填充透明度 0-0.6(0=无填充)' },
255
259
  levels: { type: 'array', items: { type: 'number' }, description: 'kind=fib/fibext/fibchannel:自定义斐波那契层级,如 [0,0.382,0.5,0.618,1,1.618]' },
256
260
  rows: { type: 'number', description: 'kind=vprofile:价格行数 6-200,默认 48' },
257
261
  valueArea: { type: 'number', description: 'kind=vprofile:价值区成交量占比 0.5-0.95,默认 0.7' },
@@ -306,7 +310,7 @@ function buildTools(remote) {
306
310
  return { text: `已移除均线:${maLabel(removed)}(${removed.id})`, line: removed }
307
311
  }
308
312
  if (args?.action === 'update') {
309
- const patch = Object.fromEntries(['points', 'price', 'fromTime', 'fromPrice', 'toTime', 'toPrice', 'label', 'color', 'lineWidth', 'lineStyle', 'locked', 'hidden', 'shared', 'alert', 'rows', 'valueArea', 'levels'].filter((key) => args?.[key] != null).map((key) => [key, args[key]]))
313
+ const patch = Object.fromEntries(['points', 'price', 'fromTime', 'fromPrice', 'toTime', 'toPrice', 'label', 'color', 'lineWidth', 'lineStyle', 'locked', 'hidden', 'shared', 'alert', 'rows', 'valueArea', 'levels', 'visibleIntervals', 'fontSize', 'fillOpacity', 'bold', 'italic'].filter((key) => args?.[key] != null).map((key) => [key, args[key]]))
310
314
  const mark = remote.editChartMark(args?.id, patch, sessionId)
311
315
  return { text: `已更新${DRAWING_NAMES[mark.kind] ?? mark.kind}:${mark.label}(${mark.id})`, mark }
312
316
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-hyperliquid",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Hyperliquid trading desk for the DeepSeek Harness (dsh) - market workspace, native agent conversation and guarded live / paper trading over the Hyperliquid API.",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",