dsh-hyperliquid 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 — 2026-10-17
4
+
5
+ - 新增 25 种 TradingView 画线(共 66 种):平顶 / 平底通道、分离通道;斐波那契速度阻力扇、斐波那契圆、速度阻力弧、楔形、基于趋势的斐波那契时间;Cypher、三驱、三角形态、艾略特三角浪 / 双重组合 / 三重组合、周期线、正弦线;椭圆、旋转矩形、弧线、曲线;便签、价格便签、路标;预测、锚定 VWAP(HLC3 × 成交量,从锚点累计到最新 K 线)、锚定成交量分布。都支持拖动编辑、样式、撤销、复制和 agent 画线。
6
+ - 画线菜单顶部加搜索框(按中文名、英文名或类型名过滤)。
7
+ - 修复:选中靠近图表顶部的画线时,浮动工具条压住它的锚点导致拖不动;现在工具条放在遮挡锚点较少的一侧(顶部或底部)。锚点同时贴近上下边缘时仍可能有锚点被遮挡,可先平移或缩放图表。
8
+ - 锚定 VWAP / 锚定成交量分布的锚点早于已加载历史时自动补载,补齐前 VWAP 标注「数值暂不完整」。
9
+ - agent 的 `hyper_chart` points 说明列出每种画线的点数。
10
+ - 冒烟新增 tools2(25 种新工具真实绘制、菜单搜索、工具条避让)与 trackpad 阶段。
11
+
12
+ ## 0.4.4 — 2026-10-17
13
+
14
+ - 修复:触摸屏上无法画线。lightweight-charts 处理轻点时会在 touchend 上 preventDefault,浏览器不再补发 click,而落锚点依赖原生 click;现在未拖动的触摸 pointerup 直接按点击处理(同一轻点之后若仍有 click 会被忽略)。
15
+ - 修复:手指拖动锚点或画线时被浏览器当作页面滚动而中途取消(pointercancel);图表区域设为 `touch-action: none`,与 TradingView 一致由图表处理全部手势。
16
+ - 冒烟新增 touch 阶段(点画、选中、拖锚点、单指平移)。
17
+
3
18
  ## 0.4.3 — 2026-10-17
4
19
 
5
20
  - 趋势线可分别向左、向右延长(浮动工具条两个开关,agent 工具 `extendLeft` / `extendRight`);价格提醒按延长后的线判断,延长的线不参与屏幕外剔除。
package/README.md CHANGED
@@ -58,7 +58,7 @@ dsh plugin --profile web add dsh-web3
58
58
  - **钱包与收益**:点击顶栏钱包查看地址、余额、权益和收益。实盘收益曲线来自 Hyperliquid 官方 portfolio 数据;模拟盘显示本地已实现与未实现收益。
59
59
  - **K 线**:支持交易所官方的 14 个周期,以及成交量、SMA20、MA60、EMA20/50、布林带、RSI、MACD、ATR14 与 UTC 日内 VWAP。图表类型可切换蜡烛、美国线、折线、面积和平均 K 线,并随会话保存;平均 K 线仅改变显示,点位引用仍使用原始 OHLC。向左拖动会自动加载更早的 K 线,最多可追溯到交易所提供的最近 5000 根。可以画水平线、趋势线,画线有实时预览和趋势线端点。手动画线与会话中 `hyper_chart` 添加的标注按会话和币种保存,在该币种的所有周期展示;agent 添加或删除后,打开的图表通常在下一次宿主事件轮询(约 1 秒)更新。明确点击 K 线、成交量柱或指标线,会在输入框里插入一个引用标签(如 `BTC 15m 10-15 13:45 · C 81397`);拖动图表不会引用。发送时标签展开为该点的完整 OHLC、量能与指标数据;点击标签可回到图上的那根 K 线。 「指标」菜单还可叠加最多 8 条可配置均线:SMA / EMA / SMMA(平滑均线,即 TradingView 的 RMA)/ WMA,周期 1–500,价格源可选 close / open / high / low / hl2 / hlc3 / ohlc4,并可再套一层平滑(虚线),算法与 TradingView Pine 一致;图例实时显示数值,agent 也能用 `hyper_chart` 把均线画到图上。
60
60
  - **导航与导出**:图表下方可查看最近 1/5/30 天、适应已加载全部数据、跳转到本地时间指定的日期,或回到最新行情;历史不足会自动补取,并在到达数据边界时提示。导出菜单可保存含画线和指标的 PNG,或导出已加载原始 OHLCV 的 CSV(UTC 时间)。指标图例与成交量跟随十字光标;VWAP 使用 UTC 每日重置的 HLC3 成交量加权价格,首日历史不全时不展示该日不完整结果。
61
- - **画线工具**:工具栏「画线」菜单按 TradingView 的分组提供 41 种工具——线条:趋势线、射线、延长线、信息线(涨跌 / 根数 / 时长 / 角度)、趋势角度、水平线、水平射线、十字线、垂直线;通道:平行通道、安德鲁叉子线、Schiff 与改良 Schiff 叉子线、回归趋势(收盘价最小二乘 ±2σ)、固定范围成交量分布(按价格统计区间成交量,标出 POC 与价值区 VAH / VAL,可调行数与价值区比例;斐波那契回撤、扩展和通道可在浮动工具条自定义层级;由 K 线估算,区间早于已加载历史时自动补加载并提示);斐波那契与江恩:斐波那契回撤、趋势斐波那契扩展、斐波那契通道、斐波那契时间周期、江恩箱、江恩方格、江恩扇形;形态:艾略特推动浪(0-5)与调整浪(A-B-C)、XABCD 谐波与 ABCD(自动标注斐波那契比值)、头肩形态(含颈线);形状:矩形、圆形、三角形、画笔;标注:文本、标注气泡、价格标签、箭头、图标;测量与头寸:价格区间、日期区间、日期与价格区间、多头 / 空头头寸(止盈默认 2R,显示 R:R)。点击过程中实时预览,Esc 取消,按住 Shift 把方向约束为水平或 45°;磁吸可选关 / 弱 / 强,吸附到 K 线开高低收。
61
+ - **画线工具**:工具栏「画线」菜单按 TradingView 的分组提供 66 种工具(菜单顶部可搜索)——线条:趋势线、射线、延长线、信息线(涨跌 / 根数 / 时长 / 角度)、趋势角度、水平线、水平射线、十字线、垂直线;通道:平行通道、安德鲁叉子线、Schiff 与改良 Schiff 叉子线、回归趋势(收盘价最小二乘 ±2σ)、固定范围成交量分布(按价格统计区间成交量,标出 POC 与价值区 VAH / VAL,可调行数与价值区比例;斐波那契回撤、扩展和通道可在浮动工具条自定义层级;由 K 线估算,区间早于已加载历史时自动补加载并提示);斐波那契与江恩:斐波那契回撤、趋势斐波那契扩展、斐波那契通道、斐波那契时间周期、江恩箱、江恩方格、江恩扇形;形态:艾略特推动浪(0-5)与调整浪(A-B-C)、XABCD 谐波与 ABCD(自动标注斐波那契比值)、头肩形态(含颈线);形状:矩形、圆形、三角形、画笔;标注:文本、标注气泡、价格标签、箭头、图标;测量与头寸:价格区间、日期区间、日期与价格区间、多头 / 空头头寸(止盈默认 2R,显示 R:R);0.5.0 新增:平顶 / 平底通道、分离通道、斐波那契速度阻力扇 / 圆 / 弧 / 楔形 / 基于趋势的时间、Cypher、三驱、三角形态、艾略特三角浪与双重 / 三重组合、周期线、正弦线、椭圆、旋转矩形、弧线、曲线、便签、价格便签、路标、预测、锚定 VWAP、锚定成交量分布。点击过程中实时预览,Esc 取消,按住 Shift 把方向约束为水平或 45°;磁吸可选关 / 弱 / 强,吸附到 K 线开高低收。
62
62
  - **编辑与管理画线**:点选后拖动锚点改形状、拖动线身整体移动;浮动工具条可通过齿轮精确编辑锚点时间、价格和名称,改颜色、线宽、线型,复制,设为该工具的默认样式(之后新画的同类线自动套用),锁定或删除。按住 Shift 或 Ctrl/Cmd 点击可多选,多选后整组拖动、改色、隐藏、锁定或删除(一步撤销)。快捷键:Alt+T 趋势线、Alt+H 水平线、Alt+J 水平射线、Alt+V 垂直线、Alt+C 十字线、Alt+F 斐波那契回撤、Alt+Shift+R 矩形;画线时按住 Ctrl/Cmd 临时反转磁吸,吸附点显示提示圈;Esc 逐层退出(取消当前拖动或工具 → 取消选择 → 退出放大图表)。Delete 删除,Ctrl/Cmd+C、V 复制粘贴,Ctrl/Cmd+Z 撤销、Shift+Ctrl/Cmd+Z 重做(新建、修改、删除和批量删除都可撤销)。图表下方的画线列表可逐条定位、显示 / 隐藏、锁定或删除;菜单里还有连续绘制、锁定所有画线和删除所有画线。点击菜单中的星标可收藏最多 12 个工具,图表上方显示常用画线快捷栏;收藏保存在当前浏览器。画线锚定在时间与价格上,平移、缩放、切换刻度和加载历史时始终贴合 K 线,也可延伸到最新 K 线右侧的空白区域;画线按会话和币种保存,切换会话随之切换,重启后保留,agent 用 `hyper_chart` 新增或修改的画线会实时出现在打开的图表上。
63
63
  - **同步、提醒与贴图**:浮动工具条可把画线「同步到所有会话」(该币种的每个交易会话都显示同一条线,任一会话可修改,重启后保留),水平线、水平射线、趋势线、射线和延长线可开启「价格提醒」——宿主持续监控中价,穿越画线时交易台弹出提醒,提醒触发一次后自动关闭,可再次开启。图标工具附贴图库:18 个内置图标、交易 / 表情 / 符号三类约 60 个贴纸,也可输入或粘贴任意一个 emoji 作为贴纸。每张图(会话 + 币种,跨周期合计)最多 100 条画线。
64
64
  - **成交与决策原因**:每笔成交在图上的成交价位置打点(买绿卖红),有 AI 决策原因的点带圆环;点击查看方向、数量、价格、时间和当时的决策原因,「显示 → 成交原因」可把原因标签直接显示在图上。「回到决策会话分析」会切换到做出这笔决策的交易会话,并把成交与原因作为复盘引用放进输入框。
@@ -109,7 +109,7 @@ dsh plugin --profile web add dsh-web3
109
109
  |---|---|
110
110
  | `hyper_context` | 一次读取当前会话的币对、盘口、K 线、账户权益、仓位与风险限额 |
111
111
  | `hyper_market` | 行情:overview / book / candles / trades / catalog / indicators(SMA、EMA、RSI、MACD、布林带、ATR、Stochastic、ADX/DMI、OBV、支撑阻力,以及图上均线或指定均线的最新值) |
112
- | `hyper_chart` | 在会话图表上添加、修改(几何 / 颜色 / 线宽 / 线型 / 锁定 / 隐藏)或删除画线(41 种,含固定范围成交量分布、斐波那契、江恩、叉子线、回归、艾略特与谐波形态、标注与多空头寸;可设置同步到所有会话与价格提醒)和均线(SMA / EMA / SMMA / WMA,可选平滑);`action=view` 切换图表周期并显示 / 隐藏 RSI、MACD 等副图指标 |
112
+ | `hyper_chart` | 在会话图表上添加、修改(几何 / 颜色 / 线宽 / 线型 / 锁定 / 隐藏)或删除画线(66 种,含固定范围成交量分布、斐波那契、江恩、叉子线、回归、艾略特与谐波形态、标注与多空头寸;可设置同步到所有会话与价格提醒)和均线(SMA / EMA / SMMA / WMA,可选平滑);`action=view` 切换图表周期并显示 / 隐藏 RSI、MACD 等副图指标 |
113
113
  | `hyper_account` | 账户:state / orders / fills / orderStatus(支持 oid、cloid)/ review(窗口证据、成本及订单核对) |
114
114
  | `hyper_decide` | 实盘预检:提交交易意图与依据,返回 30 秒内有效的一次性 `decisionId` |
115
115
  | `hyper_order` | place / cancel / leverage;实盘下单必须带匹配的 `decisionId`,可附带止盈止损(目前仅测试网) |
package/client.js CHANGED
@@ -949,6 +949,25 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
949
949
 
950
950
  /* One row like TradingView's floating toolbar: a wrapped toolbar covered drawings near the top of a narrow chart. */
951
951
  .dhl-draw-toolbar > * { flex: none; }
952
+ /* Touch: the chart owns every gesture inside it (anchor and drawing drags, pans), as TradingView does;
953
+ otherwise the browser claims a finger drag for scrolling and cancels the pointer mid-drag. */
954
+ .dhl-chart, .dhl-chart-drawing-overlay, .dhl-chart-drawing-overlay * { touch-action: none; }
955
+
956
+ /* 0.5.0 tools: curves, ellipses, Fibonacci rings, notes and signposts. */
957
+ .dhl-drawing .dhl-curve, .dhl-drawing .dhl-fib-ring { fill: none; stroke: currentColor; stroke-width: var(--dhl-line-width, 1.6); stroke-dasharray: var(--dhl-dash, none); }
958
+ .dhl-drawing .dhl-fib-ring { stroke-width: 1.2; opacity: .85; }
959
+ .dhl-drawing ellipse.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); }
960
+ .dhl-drawing rect.dhl-drawing-mid { fill: none; stroke: currentColor; }
961
+ .dhl-drawing-hit .dhl-curve, .dhl-drawing-hit .dhl-fib-ring { pointer-events: stroke; }
962
+ .dhl-drawing .dhl-note-box { fill: color-mix(in srgb, #e2b93b 22%, var(--dsw-alias-bg-layer-1)); stroke: color-mix(in srgb, #e2b93b 70%, transparent); }
963
+ .dhl-drawing .dhl-signpost > line { stroke-width: 1.6; }
964
+
965
+ /* Moved below the drawing when the top position would cover its anchors (above the time axis). */
966
+ .dhl-draw-toolbar.at-bottom { top: auto; bottom: 40px; }
967
+
968
+ /* Search at the top of the drawing menu (stays visible while the list scrolls). */
969
+ .dhl-draw-popover .dhl-draw-search { position: sticky; top: -6px; z-index: 1; width: 100%; box-sizing: border-box; margin: -6px 0 4px; padding: 8px 10px; border: 0; border-bottom: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; outline: none; }
970
+ .dhl-draw-popover .dhl-draw-empty { margin: 10px 8px; color: var(--dsw-alias-label-secondary); font-size: 11.5px; }
952
971
  `;
953
972
 
954
973
  // src/client/locale.ts
@@ -17692,7 +17711,33 @@ var DRAWING_POINTS = {
17692
17711
  gannsquare: 2,
17693
17712
  schiff: 3,
17694
17713
  modschiff: 3,
17695
- icon: 1
17714
+ icon: 1,
17715
+ // TradingView's remaining common tools (0.5.0).
17716
+ flatchannel: 3,
17717
+ disjoint: 3,
17718
+ fibfan: 2,
17719
+ fibcircles: 2,
17720
+ fibarcs: 2,
17721
+ fibwedge: 3,
17722
+ fibtrendtime: 3,
17723
+ cypher: 5,
17724
+ threedrives: 6,
17725
+ tripattern: 4,
17726
+ elliotttri: 6,
17727
+ elliottwxy: 4,
17728
+ elliottwxyxz: 6,
17729
+ cycles: 2,
17730
+ sine: 2,
17731
+ ellipse: 2,
17732
+ rotrect: 3,
17733
+ arc: 3,
17734
+ curve: 3,
17735
+ note: 1,
17736
+ pricenote: 2,
17737
+ signpost: 1,
17738
+ avwap: 1,
17739
+ avprofile: 1,
17740
+ forecast: 2
17696
17741
  };
17697
17742
  var ICON_NAMES = ["flag", "star", "up", "down", "warning", "check", "cross", "fire", "target", "bell", "pin", "clock", "dollar", "shield", "bolt", "heart", "question", "info"];
17698
17743
  var STICKERS = {
@@ -17705,8 +17750,8 @@ function isSingleEmoji(value) {
17705
17750
  if (typeof value !== "string" || !value || value.length > 32 || !/\p{Extended_Pictographic}|\p{Regional_Indicator}/u.test(value)) return false;
17706
17751
  return [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(value)].length === 1;
17707
17752
  }
17708
- var FONT_KINDS = ["text", "callout"];
17709
- var FILL_KINDS = ["rect", "circle", "triangle", "channel", "fib", "regression", "gannbox", "pricerange", "daterange", "measure"];
17753
+ var FONT_KINDS = ["text", "callout", "note", "pricenote", "signpost"];
17754
+ var FILL_KINDS = ["rect", "circle", "triangle", "channel", "fib", "regression", "gannbox", "pricerange", "daterange", "measure", "flatchannel", "disjoint", "ellipse", "rotrect"];
17710
17755
  var FIB_LEVEL_KINDS = ["fib", "fibext", "fibchannel"];
17711
17756
  function volumeProfile(bars, { rows = 48, valueArea = 0.7 } = {}) {
17712
17757
  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);
@@ -17752,7 +17797,7 @@ var GANN_BOX_LEVELS = [0, 0.25, 0.382, 0.5, 0.618, 0.75, 1];
17752
17797
  var ALERT_KINDS = ["level", "hray", "trend", "ray", "extended"];
17753
17798
  var BRUSH_MIN = 2;
17754
17799
  var BRUSH_MAX = 500;
17755
- var TEXT_KINDS = ["text", "callout"];
17800
+ var TEXT_KINDS = ["text", "callout", "note", "pricenote", "signpost"];
17756
17801
  var DRAWING_CLICKS = { ...Object.fromEntries(Object.entries(DRAWING_POINTS)), long: 2, short: 2 };
17757
17802
  var DRAWING_KINDS = Object.keys(DRAWING_POINTS);
17758
17803
  var FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
@@ -17786,7 +17831,8 @@ function drawingGeometry(kind, request, now = Date.now()) {
17786
17831
  if (!Array.isArray(raw) || raw.length < BRUSH_MIN || raw.length > BRUSH_MAX) throw new Error(`brush \u9700\u8981 ${BRUSH_MIN}-${BRUSH_MAX} \u4E2A points`);
17787
17832
  } else if (!Array.isArray(raw) || raw.length !== need) throw new Error(`${kind} \u9700\u8981 ${need} \u4E2A points\uFF08{ time, price }\uFF09`);
17788
17833
  const points2 = raw.map((item, index) => point(item?.time, item?.price, `\u7B2C ${index + 1} \u4E2A\u70B9`));
17789
- if (["trend", "ray", "extended", "channel", "fibchannel", "gannfan", "regression", "fibtime", "infoline", "trendangle"].includes(kind) && points2[0].time === points2[1].time) throw new Error("\u524D\u4E24\u4E2A\u70B9\u7684\u65F6\u95F4\u4E0D\u80FD\u76F8\u540C");
17834
+ if (["trend", "ray", "extended", "channel", "fibchannel", "gannfan", "regression", "fibtime", "infoline", "trendangle", "flatchannel", "disjoint", "fibfan", "fibtrendtime", "cycles", "sine", "forecast"].includes(kind) && points2[0].time === points2[1].time) throw new Error("\u524D\u4E24\u4E2A\u70B9\u7684\u65F6\u95F4\u4E0D\u80FD\u76F8\u540C");
17835
+ if ((kind === "cycles" || kind === "sine" || kind === "fibtrendtime") && points2[1].time < points2[0].time) throw new Error("\u7B2C\u4E8C\u4E2A\u70B9\u5FC5\u987B\u665A\u4E8E\u7B2C\u4E00\u4E2A\u70B9");
17790
17836
  if (kind === "vprofile") {
17791
17837
  if (points2[0].time === points2[1].time) throw new Error("\u6210\u4EA4\u91CF\u5206\u5E03\u9700\u8981\u4E00\u4E2A\u65F6\u95F4\u533A\u95F4");
17792
17838
  if (points2[1].time < points2[0].time) points2.reverse();
@@ -17814,6 +17860,8 @@ var DRAWING_TOOLS = [
17814
17860
  { group: "lines", kind: "crossline", zh: "\u5341\u5B57\u7EBF", en: "Cross line", hintZh: "\u5355\u51FB\u653E\u7F6E\u6C34\u5E73\u4E0E\u5782\u76F4\u4EA4\u53C9\u7EBF\uFF08Alt+C\uFF09", hintEn: "Click to place a horizontal and vertical cross (Alt+C)", icon: "M12 3v18M3 12h18" },
17815
17861
  { group: "lines", kind: "vline", zh: "\u5782\u76F4\u7EBF", en: "Vertical line", hintZh: "\u5355\u51FB\u6807\u8BB0\u65F6\u95F4\uFF08Alt+V\uFF09", hintEn: "Click to mark a time (Alt+V)", icon: "M12 3v18" },
17816
17862
  { group: "channels", kind: "channel", zh: "\u5E73\u884C\u901A\u9053", en: "Parallel channel", hintZh: "\u70B9\u51FB\u4E24\u70B9\u753B\u57FA\u7EBF\uFF0C\u7B2C\u4E09\u70B9\u8BBE\u7F6E\u901A\u9053\u5BBD\u5EA6", hintEn: "Two points for the base, a third for width", icon: "M3 15 15 5M9 19 21 9" },
17863
+ { group: "channels", kind: "flatchannel", zh: "\u5E73\u9876/\u5E73\u5E95\u901A\u9053", en: "Flat top/bottom", hintZh: "\u4E24\u70B9\u753B\u8D8B\u52BF\u8FB9\uFF0C\u7B2C\u4E09\u70B9\u8BBE\u7F6E\u6C34\u5E73\u8FB9\u7684\u4EF7\u683C", hintEn: "Two points for the sloped side, a third for the flat side", icon: "M3 18 21 8M3 5h18" },
17864
+ { group: "channels", kind: "disjoint", zh: "\u5206\u79BB\u901A\u9053", en: "Disjoint channel", hintZh: "\u4E24\u70B9\u753B\u7B2C\u4E00\u6761\u8FB9\uFF0C\u7B2C\u4E09\u70B9\u653E\u7F6E\u659C\u7387\u76F8\u53CD\u7684\u7B2C\u4E8C\u6761\u8FB9", hintEn: "Two points for one side; a third places the mirrored side", icon: "M3 16 21 6M3 9l18 10" },
17817
17865
  { group: "channels", kind: "pitchfork", zh: "\u5B89\u5FB7\u9C81\u53C9\u5B50\u7EBF", en: "Pitchfork", hintZh: "\u70B9\u51FB\u67A2\u8F74\u70B9\uFF0C\u518D\u70B9\u51FB\u6CE2\u6BB5\u9AD8\u70B9\u4E0E\u4F4E\u70B9", hintEn: "Click the pivot, then the swing high and low", icon: "M4 20 12 4M12 4l8 8M12 4 4 12M8 16l12-4" },
17818
17866
  { group: "channels", kind: "schiff", zh: "Schiff \u53C9\u5B50\u7EBF", en: "Schiff pitchfork", hintZh: "\u540C\u53C9\u5B50\u7EBF\uFF0C\u8D77\u70B9\u79FB\u5230 A\u3001B \u4EF7\u683C\u4E2D\u70B9", hintEn: "Pitchfork with the handle at A\u2013B price midpoint", icon: "M4 14 12 4M12 4l8 8M12 4 4 12M8 16l12-4" },
17819
17867
  { group: "channels", kind: "modschiff", zh: "\u6539\u826F Schiff \u53C9\u5B50\u7EBF", en: "Modified Schiff pitchfork", hintZh: "\u540C\u53C9\u5B50\u7EBF\uFF0C\u8D77\u70B9\u79FB\u5230 A\u3001B \u65F6\u95F4\u4E0E\u4EF7\u683C\u4E2D\u70B9", hintEn: "Pitchfork with the handle at the A\u2013B midpoint", icon: "M6 12 12 4M12 4l8 8M12 4 4 12M8 16l12-4" },
@@ -17823,6 +17871,11 @@ var DRAWING_TOOLS = [
17823
17871
  { group: "fib", kind: "fibext", zh: "\u6590\u6CE2\u90A3\u5951\u6269\u5C55", en: "Trend-based fib extension", hintZh: "\u70B9\u51FB\u6CE2\u6BB5\u8D77\u70B9\u3001\u7EC8\u70B9\uFF0C\u518D\u70B9\u51FB\u56DE\u64A4\u70B9\uFF0C\u81EA\u56DE\u64A4\u70B9\u6295\u5C04", hintEn: "Swing start, swing end, then the retracement point", icon: "M3 20 9 6l6 9M15 4h7M15 8h7M15 12h7" },
17824
17872
  { group: "fib", kind: "fibchannel", zh: "\u6590\u6CE2\u90A3\u5951\u901A\u9053", en: "Fib channel", hintZh: "\u4E24\u70B9\u753B\u57FA\u7EBF\uFF0C\u7B2C\u4E09\u70B9\u8BBE\u7F6E\u901A\u9053\u5BBD\u5EA6", hintEn: "Two points for the base, a third for width", icon: "M3 19 21 9M3 15 21 5M3 11 21 1" },
17825
17873
  { group: "fib", kind: "fibtime", zh: "\u6590\u6CE2\u90A3\u5951\u65F6\u95F4\u5468\u671F", en: "Fib time zone", hintZh: "\u70B9\u51FB\u4E24\u70B9\u786E\u5B9A\u5355\u4F4D\u95F4\u9694", hintEn: "Click two points to set the unit spacing", icon: "M4 3v18M7 3v18M10 3v18M15 3v18M22 3v18" },
17874
+ { group: "fib", kind: "fibfan", zh: "\u6590\u6CE2\u90A3\u5951\u901F\u5EA6\u963B\u529B\u6247", en: "Fib speed resistance fan", hintZh: "\u70B9\u51FB\u539F\u70B9\uFF0C\u518D\u70B9\u51FB\u5BF9\u89D2\uFF0C\u6309\u4EF7\u683C\u4E0E\u65F6\u95F4\u6BD4\u4F8B\u753B\u6247\u7EBF", hintEn: "Click the origin, then the opposite corner", icon: "M3 21 21 3M3 21 21 9M3 21 21 15M3 21 9 3M3 21 15 3" },
17875
+ { group: "fib", kind: "fibcircles", zh: "\u6590\u6CE2\u90A3\u5951\u5706", en: "Fib circles", hintZh: "\u70B9\u51FB\u5706\u5FC3\uFF0C\u518D\u70B9\u51FB\u786E\u5B9A 1.0 \u534A\u5F84", hintEn: "Click the center, then the 1.0 radius", icon: "M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10zM12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z" },
17876
+ { group: "fib", kind: "fibarcs", zh: "\u6590\u6CE2\u90A3\u5951\u5F27", en: "Fib speed resistance arcs", hintZh: "\u70B9\u51FB\u6CE2\u6BB5\u8D77\u70B9\uFF0C\u518D\u70B9\u51FB\u7EC8\u70B9\uFF0C\u4EE5\u7EC8\u70B9\u4E3A\u5706\u5FC3\u753B\u5F27", hintEn: "Click the swing start, then the end (arcs center on the end)", icon: "M4 20a8 8 0 0 1 16 0M8 20a4 4 0 0 1 8 0" },
17877
+ { group: "fib", kind: "fibwedge", zh: "\u6590\u6CE2\u90A3\u5951\u6954\u5F62", en: "Fib wedge", hintZh: "\u70B9\u51FB\u9876\u70B9\uFF0C\u518D\u70B9\u51FB\u4E24\u6761\u8FB9\u7684\u7AEF\u70B9", hintEn: "Click the apex, then the ends of the two sides", icon: "M3 12 21 4M3 12l18 8M15 6.7a7 7 0 0 1 0 10.6" },
17878
+ { group: "fib", kind: "fibtrendtime", zh: "\u57FA\u4E8E\u8D8B\u52BF\u7684\u6590\u6CE2\u90A3\u5951\u65F6\u95F4", en: "Trend-based fib time", hintZh: "\u4E24\u70B9\u786E\u5B9A\u8D8B\u52BF\u65F6\u957F\uFF0C\u7B2C\u4E09\u70B9\u8D77\u6309\u6BD4\u4F8B\u753B\u65F6\u95F4\u7EBF", hintEn: "Two points set the trend duration; lines start at the third", icon: "M3 18 9 6M12 3v18M16 3v18M21 3v18" },
17826
17879
  { group: "fib", kind: "gannbox", zh: "\u6C5F\u6069\u7BB1", en: "Gann box", hintZh: "\u70B9\u51FB\u4E24\u4E2A\u5BF9\u89D2\uFF0C\u6309 0.25/0.382/0.5/0.618/0.75 \u5206\u5272", hintEn: "Click two corners; divided at Gann levels", icon: "M4 4h16v16H4zM4 12h16M12 4v16M4 4l16 16" },
17827
17880
  { group: "fib", kind: "gannsquare", zh: "\u6C5F\u6069\u65B9\u683C", en: "Gann square", hintZh: "\u70B9\u51FB\u4E24\u4E2A\u5BF9\u89D2\uFF0C\u753B\u56DB\u5206\u7F51\u683C\u3001\u5BF9\u89D2\u7EBF\u4E0E\u6247\u7EBF", hintEn: "Click two corners; quarters, diagonals and fans", icon: "M4 4h16v16H4zM4 4l16 16M20 4 4 20M4 12h16M12 4v16" },
17828
17881
  { group: "fib", kind: "gannfan", zh: "\u6C5F\u6069\u6247\u5F62", en: "Gann fan", hintZh: "\u70B9\u51FB\u539F\u70B9\uFF0C\u518D\u70B9\u51FB\u786E\u5B9A 1\xD71 \u7EBF", hintEn: "Click the origin, then a point on the 1\xD71 line", icon: "M3 21 21 3M3 21 21 12M3 21 12 3M3 21h18" },
@@ -17830,19 +17883,37 @@ var DRAWING_TOOLS = [
17830
17883
  { group: "patterns", kind: "elliottabc", zh: "\u827E\u7565\u7279\u8C03\u6574\u6D6A", en: "Elliott correction wave", hintZh: "\u4F9D\u6B21\u70B9\u51FB 0\u3001A\u3001B\u3001C \u56DB\u4E2A\u70B9", hintEn: "Click 0, A, B, C", icon: "M3 6l6 10 5-6 7 10" },
17831
17884
  { group: "patterns", kind: "xabcd", zh: "XABCD \u8C10\u6CE2", en: "XABCD pattern", hintZh: "\u4F9D\u6B21\u70B9\u51FB X\u3001A\u3001B\u3001C\u3001D\uFF0C\u663E\u793A\u6590\u6CE2\u90A3\u5951\u6BD4\u503C", hintEn: "Click X, A, B, C, D; shows Fibonacci ratios", icon: "M2 18 7 5l4 9 5-7 6 12M2 18l9-4M11 14l11 5" },
17832
17885
  { group: "patterns", kind: "abcd", zh: "ABCD \u5F62\u6001", en: "ABCD pattern", hintZh: "\u4F9D\u6B21\u70B9\u51FB A\u3001B\u3001C\u3001D", hintEn: "Click A, B, C, D", icon: "M3 19 9 6l5 8 7-10" },
17886
+ { group: "patterns", kind: "cypher", zh: "Cypher \u5F62\u6001", en: "Cypher pattern", hintZh: "\u4F9D\u6B21\u70B9\u51FB X\u3001A\u3001B\u3001C\u3001D\uFF0C\u663E\u793A B \u56DE\u64A4\u3001C \u6269\u5C55\u4E0E D \u5BF9 XC \u7684\u56DE\u64A4", hintEn: "Click X, A, B, C, D; shows B, C and XC ratios", icon: "M2 17 7 5l4 8 4-10 7 15M2 17l9-4" },
17887
+ { group: "patterns", kind: "threedrives", zh: "\u4E09\u9A71\u5F62\u6001", en: "Three drives pattern", hintZh: "\u4F9D\u6B21\u70B9\u51FB\u8D77\u70B9\u3001\u9A71\u52A8 1\u3001A\u3001\u9A71\u52A8 2\u3001B\u3001\u9A71\u52A8 3", hintEn: "Click start, drive 1, A, drive 2, B, drive 3", icon: "M2 20 6 13l3 3 4-8 3 3 5-8" },
17888
+ { group: "patterns", kind: "tripattern", zh: "\u4E09\u89D2\u5F62\u6001", en: "Triangle pattern", hintZh: "\u4F9D\u6B21\u70B9\u51FB A\u3001B\u3001C\u3001D\uFF0C\u8FDE\u7EBF\u5E76\u5EF6\u957F\u4E0A\u4E0B\u8FB9", hintEn: "Click A, B, C, D; the sides extend to the apex", icon: "M3 4 9 18l5-10 4 6M3 4l18 6M9 18l12-6" },
17889
+ { group: "patterns", kind: "elliotttri", zh: "\u827E\u7565\u7279\u4E09\u89D2\u6D6A", en: "Elliott triangle wave", hintZh: "\u4F9D\u6B21\u70B9\u51FB 0\u3001A\u3001B\u3001C\u3001D\u3001E", hintEn: "Click 0, A, B, C, D, E", icon: "M2 5l4 14 4-11 4 8 4-5 4 3" },
17890
+ { group: "patterns", kind: "elliottwxy", zh: "\u827E\u7565\u7279\u53CC\u91CD\u7EC4\u5408", en: "Elliott double combo", hintZh: "\u4F9D\u6B21\u70B9\u51FB 0\u3001W\u3001X\u3001Y", hintEn: "Click 0, W, X, Y", icon: "M3 6l6 10 5-6 7 10" },
17891
+ { group: "patterns", kind: "elliottwxyxz", zh: "\u827E\u7565\u7279\u4E09\u91CD\u7EC4\u5408", en: "Elliott triple combo", hintZh: "\u4F9D\u6B21\u70B9\u51FB 0\u3001W\u3001X\u3001Y\u3001X\u3001Z", hintEn: "Click 0, W, X, Y, X, Z", icon: "M2 6l4 8 3-4 4 7 3-4 6 9" },
17892
+ { group: "patterns", kind: "cycles", zh: "\u5468\u671F\u7EBF", en: "Cyclic lines", hintZh: "\u70B9\u51FB\u4E24\u70B9\u786E\u5B9A\u5468\u671F\uFF0C\u5411\u540E\u7B49\u8DDD\u753B\u5782\u76F4\u7EBF", hintEn: "Click two points to set the period", icon: "M5 3v18M10 3v18M15 3v18M20 3v18" },
17893
+ { group: "patterns", kind: "sine", zh: "\u6B63\u5F26\u7EBF", en: "Sine line", hintZh: "\u70B9\u51FB\u6CE2\u5CF0\uFF0C\u518D\u70B9\u51FB\u76F8\u90BB\u6CE2\u8C37", hintEn: "Click a peak, then the next trough", icon: "M2 12c2.5-8 5-8 7.5 0s5 8 7.5 0 3-5 5-4" },
17833
17894
  { group: "patterns", kind: "headshoulders", zh: "\u5934\u80A9\u5F62\u6001", en: "Head and shoulders", hintZh: "\u4F9D\u6B21\u70B9\u51FB\u5DE6\u5E95\u3001\u5DE6\u80A9\u3001\u9888\u7EBF\u70B9\u3001\u5934\u3001\u9888\u7EBF\u70B9\u3001\u53F3\u80A9\u3001\u53F3\u5E95", hintEn: "Click 7 points from left base to right base", icon: "M2 18l3-6 3 3 4-10 4 10 3-3 3 6M5 15h14" },
17834
17895
  { group: "shapes", kind: "rect", zh: "\u77E9\u5F62", en: "Rectangle", hintZh: "\u70B9\u51FB\u4E24\u4E2A\u5BF9\u89D2\uFF08Alt+Shift+R\uFF09", hintEn: "Click two opposite corners (Alt+Shift+R)", icon: "M4 6h16v12H4z" },
17835
17896
  { group: "shapes", kind: "circle", zh: "\u5706\u5F62", en: "Circle", hintZh: "\u70B9\u51FB\u5706\u5FC3\uFF0C\u518D\u70B9\u51FB\u8FB9\u7F18", hintEn: "Click the center, then the edge", icon: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z" },
17836
17897
  { group: "shapes", kind: "triangle", zh: "\u4E09\u89D2\u5F62", en: "Triangle", hintZh: "\u4F9D\u6B21\u70B9\u51FB\u4E09\u4E2A\u9876\u70B9", hintEn: "Click three corners", icon: "M12 4 21 20H3z" },
17898
+ { group: "shapes", kind: "ellipse", zh: "\u692D\u5706", en: "Ellipse", hintZh: "\u70B9\u51FB\u5916\u63A5\u77E9\u5F62\u7684\u4E24\u4E2A\u5BF9\u89D2", hintEn: "Click two corners of its bounding box", icon: "M12 6c5 0 9 2.7 9 6s-4 6-9 6-9-2.7-9-6 4-6 9-6z" },
17899
+ { group: "shapes", kind: "rotrect", zh: "\u65CB\u8F6C\u77E9\u5F62", en: "Rotated rectangle", hintZh: "\u4E24\u70B9\u753B\u4E00\u6761\u8FB9\uFF0C\u7B2C\u4E09\u70B9\u8BBE\u7F6E\u5BBD\u5EA6", hintEn: "Two points for one side, a third for the width", icon: "M4 14 14 4l6 6-10 10z" },
17900
+ { group: "shapes", kind: "arc", zh: "\u5F27\u7EBF", en: "Arc", hintZh: "\u70B9\u51FB\u8D77\u70B9\u3001\u7EC8\u70B9\uFF0C\u518D\u70B9\u51FB\u5F27\u7EBF\u7ECF\u8FC7\u7684\u70B9", hintEn: "Click start, end, then a point on the arc", icon: "M3 18c3-10 15-10 18 0" },
17901
+ { group: "shapes", kind: "curve", zh: "\u66F2\u7EBF", en: "Curve", hintZh: "\u70B9\u51FB\u8D77\u70B9\u3001\u7EC8\u70B9\uFF0C\u518D\u70B9\u51FB\u63A7\u5236\u70B9", hintEn: "Click start, end, then the control point", icon: "M3 18C8 2 14 22 21 6" },
17837
17902
  { group: "shapes", kind: "brush", zh: "\u753B\u7B14", en: "Brush", hintZh: "\u6309\u4F4F\u9F20\u6807\u62D6\u52A8\u81EA\u7531\u7ED8\u5236", hintEn: "Press and drag to draw freehand", icon: "M3 17c3-6 5 2 8-3s4-6 7-4 3 4 3 4" },
17838
17903
  { group: "annotation", kind: "text", zh: "\u6587\u672C", en: "Text", hintZh: "\u5355\u51FB\u4F4D\u7F6E\u540E\u8F93\u5165\u6587\u5B57", hintEn: "Click a spot, then type", icon: "M5 6h14M12 6v13M9 19h6" },
17839
17904
  { group: "annotation", kind: "callout", zh: "\u6807\u6CE8\u6C14\u6CE1", en: "Callout", hintZh: "\u70B9\u51FB\u8981\u6807\u6CE8\u7684\u4F4D\u7F6E\uFF0C\u518D\u70B9\u51FB\u6587\u672C\u6846\u4F4D\u7F6E\uFF0C\u7136\u540E\u8F93\u5165\u6587\u5B57", hintEn: "Click the target, then the box, then type", icon: "M4 4h16v10H10l-4 5v-5H4z" },
17905
+ { group: "annotation", kind: "note", zh: "\u4FBF\u7B7E", en: "Note", hintZh: "\u5355\u51FB\u4F4D\u7F6E\u540E\u8F93\u5165\u4FBF\u7B7E\u5185\u5BB9", hintEn: "Click a spot, then type the note", icon: "M5 4h14v11l-5 5H5zM14 20v-5h5" },
17906
+ { group: "annotation", kind: "pricenote", zh: "\u4EF7\u683C\u4FBF\u7B7E", en: "Price note", hintZh: "\u70B9\u51FB\u8981\u6807\u6CE8\u7684\u4EF7\u683C\uFF0C\u518D\u70B9\u51FB\u4FBF\u7B7E\u4F4D\u7F6E\uFF0C\u7136\u540E\u8F93\u5165\u6587\u5B57", hintEn: "Click the price, then the note position, then type", icon: "M3 16h6M9 16l4-6h8v8h-8z" },
17907
+ { group: "annotation", kind: "signpost", zh: "\u8DEF\u6807", en: "Signpost", hintZh: "\u5355\u51FB K \u7EBF\u4F4D\u7F6E\u540E\u8F93\u5165\u8DEF\u6807\u6587\u5B57", hintEn: "Click a bar, then type the signpost text", icon: "M8 21V4M8 4h11l-2 3 2 3H8" },
17840
17908
  { group: "annotation", kind: "pricelabel", zh: "\u4EF7\u683C\u6807\u7B7E", en: "Price label", hintZh: "\u5355\u51FB\u5728\u8BE5\u4EF7\u683C\u5904\u653E\u7F6E\u6807\u7B7E", hintEn: "Click to tag a price", icon: "M3 12l5-6h13v12H8z" },
17841
17909
  { group: "annotation", kind: "icon", zh: "\u56FE\u6807", en: "Icon", hintZh: "\u5355\u51FB\u4F4D\u7F6E\u540E\u9009\u62E9\u56FE\u6807", hintEn: "Click a spot, then pick an icon", icon: "M12 3l2.6 5.6 6 .7-4.5 4.1 1.2 6L12 16.6 6.7 19.4l1.2-6L3.4 9.3l6-.7z" },
17842
17910
  { group: "annotation", kind: "arrow", zh: "\u7BAD\u5934", en: "Arrow", hintZh: "\u70B9\u51FB\u8D77\u70B9\u548C\u7BAD\u5934\u6307\u5411\u7684\u4F4D\u7F6E", hintEn: "Click tail, then head", icon: "M4 20 20 4M12 4h8v8" },
17843
17911
  { group: "measure", kind: "pricerange", zh: "\u4EF7\u683C\u533A\u95F4", en: "Price range", hintZh: "\u70B9\u51FB\u4E24\u70B9\uFF0C\u6D4B\u91CF\u4EF7\u683C\u5DEE\u4E0E\u767E\u5206\u6BD4", hintEn: "Click two points to measure price change", icon: "M12 3v18M8 7l4-4 4 4M8 17l4 4 4-4M5 3h14M5 21h14" },
17844
17912
  { group: "measure", kind: "daterange", zh: "\u65E5\u671F\u533A\u95F4", en: "Date range", hintZh: "\u70B9\u51FB\u4E24\u70B9\uFF0C\u6D4B\u91CF K \u7EBF\u6839\u6570\u4E0E\u65F6\u957F", hintEn: "Click two points to measure bars and time", icon: "M3 12h18M7 8l-4 4 4 4M17 8l4 4-4 4M3 5v14M21 5v14" },
17845
17913
  { group: "measure", kind: "measure", zh: "\u65E5\u671F\u4E0E\u4EF7\u683C\u533A\u95F4", en: "Date & price range", hintZh: "\u70B9\u51FB\u8D77\u70B9\u548C\u7EC8\u70B9\uFF0C\u663E\u793A\u6DA8\u8DCC\u3001\u6839\u6570\u4E0E\u65F6\u957F", hintEn: "Click two points to measure change, bars and time", icon: "M12 3v18M8 7l4-4 4 4M8 17l4 4 4-4" },
17914
+ { group: "measure", kind: "forecast", zh: "\u9884\u6D4B", en: "Forecast", hintZh: "\u70B9\u51FB\u8D77\u70B9\uFF0C\u518D\u70B9\u51FB\u9884\u6D4B\u76EE\u6807\uFF0C\u663E\u793A\u6DA8\u8DCC\u4E0E\u65F6\u957F", hintEn: "Click the source, then the target; shows change and time", icon: "M3 17 10 12M10 12l11-7M17 5h4v4M10 12v.01" },
17915
+ { group: "measure", kind: "avwap", zh: "\u951A\u5B9A VWAP", en: "Anchored VWAP", hintZh: "\u5355\u51FB\u951A\u5B9A\u7684 K \u7EBF\uFF0C\u4ECE\u8BE5\u5904\u7D2F\u8BA1\u5230\u6700\u65B0 K \u7EBF", hintEn: "Click the anchor bar; accumulates to the latest bar", icon: "M4 4v16M4 16c4-1 6-6 10-7s5 2 6 0" },
17916
+ { group: "measure", kind: "avprofile", zh: "\u951A\u5B9A\u6210\u4EA4\u91CF\u5206\u5E03", en: "Anchored volume profile", hintZh: "\u5355\u51FB\u951A\u5B9A\u7684 K \u7EBF\uFF0C\u7EDF\u8BA1\u4ECE\u8BE5\u5904\u5230\u6700\u65B0\u7684\u6210\u4EA4\u91CF\u5206\u5E03", hintEn: "Click the anchor bar; volume by price up to the latest bar", icon: "M4 3v18M4 6h7M4 10h12M4 14h9M4 18h5" },
17846
17917
  { group: "measure", kind: "long", zh: "\u591A\u5934\u5934\u5BF8", en: "Long position", hintZh: "\u70B9\u51FB\u5165\u573A\u4EF7\uFF0C\u518D\u70B9\u51FB\u6B62\u635F\u4EF7\uFF08\u6B62\u76C8\u9ED8\u8BA4 2R\uFF0C\u53EF\u62D6\u52A8\uFF09", hintEn: "Click entry, then stop (target defaults to 2R)", icon: "M4 4h16v8H4zM4 12h16v8H4zM4 12h16" },
17847
17918
  { group: "measure", kind: "short", zh: "\u7A7A\u5934\u5934\u5BF8", en: "Short position", hintZh: "\u70B9\u51FB\u5165\u573A\u4EF7\uFF0C\u518D\u70B9\u51FB\u6B62\u635F\u4EF7\uFF08\u6B62\u76C8\u9ED8\u8BA4 2R\uFF0C\u53EF\u62D6\u52A8\uFF09", hintEn: "Click entry, then stop (target defaults to 2R)", icon: "M4 4h16v8H4zM4 12h16v8H4zM4 12h16" }
17848
17919
  ];
@@ -18252,16 +18323,24 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18252
18323
  handles = [c0, c1];
18253
18324
  break;
18254
18325
  }
18326
+ case "avprofile":
18255
18327
  case "vprofile": {
18256
- if (!p[1]) break;
18257
- const [from, to] = points2[0].time <= points2[1].time ? points2 : [points2[1], points2[0]];
18258
- const x0 = Math.min(p[0].x, p[1].x), x1 = Math.max(p[0].x, p[1].x);
18328
+ const lastBar = kind === "avprofile" ? view.candles?.(points2[0].time, Number.MAX_SAFE_INTEGER).at(-1) : void 0;
18329
+ const endPoint = kind === "avprofile" ? lastBar ? view.project(lastBar.time, points2[0].price) : null : p[1];
18330
+ if (!endPoint) {
18331
+ if (kind === "avprofile") {
18332
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, x2: p[0].x, y1: "0", y2: h }), text(p[0].x + 4, 16, tr("\u951A\u70B9\u4E4B\u540E\u6682\u65E0 K \u7EBF", "No bars after the anchor"), "start"));
18333
+ }
18334
+ break;
18335
+ }
18336
+ const [from, to] = kind === "avprofile" ? [points2[0], { time: lastBar.time, price: points2[0].price }] : points2[0].time <= points2[1].time ? points2 : [points2[1], points2[0]];
18337
+ const x0 = Math.min(p[0].x, endPoint.x), x1 = Math.max(p[0].x, endPoint.x);
18259
18338
  const settings = { rows: rows ?? 48, valueArea: valueArea ?? 0.7 };
18260
18339
  const profile = view.profile ? view.profile(from.time, to.time, settings) : volumeProfile(view.candles?.(from.time, to.time) ?? [], settings);
18261
18340
  const partial = view.firstTime != null && view.barMs != null && view.firstTime - from.time >= view.barMs;
18262
18341
  if (!profile) {
18263
18342
  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"));
18264
- handles = [p[0], p[1]];
18343
+ handles = kind === "avprofile" ? [p[0]] : [p[0], p[1]];
18265
18344
  break;
18266
18345
  }
18267
18346
  const y = (price) => view.project(from.time, price)?.y;
@@ -18279,9 +18358,203 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
18279
18358
  paths2[inValue ? "valueDown" : "outDown"] += rectPath(x0 + upWidth, top, downWidth, height);
18280
18359
  });
18281
18360
  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);
18361
+ handles = kind === "avprofile" ? [p[0]] : [p[0], p[1]];
18362
+ break;
18363
+ }
18364
+ case "avwap": {
18365
+ const bars = view.candles?.(points2[0].time, Number.MAX_SAFE_INTEGER) ?? [];
18366
+ let pv = 0, vol = 0;
18367
+ const path = [];
18368
+ for (const bar of bars) {
18369
+ pv += (bar.high + bar.low + bar.close) / 3 * bar.volume;
18370
+ vol += bar.volume;
18371
+ const point = vol > 0 ? view.project(bar.time, pv / vol) : null;
18372
+ if (point) path.push(point);
18373
+ }
18374
+ if (path.length < 1 || vol <= 0) {
18375
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, x2: p[0].x, y1: "0", y2: h }), text(p[0].x + 4, 16, tr("\u951A\u70B9\u4E4B\u540E\u6682\u65E0\u6210\u4EA4\u91CF", "No volume after the anchor"), "start"));
18376
+ break;
18377
+ }
18378
+ const last = path.at(-1);
18379
+ const partial = view.firstTime != null && view.barMs != null && view.firstTime - points2[0].time >= view.barMs;
18380
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("polyline", { className: "dhl-pattern", points: path.map((point) => `${point.x},${point.y}`).join(" ") }), /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: p[0].x, x2: p[0].x, y1: p[0].y - 14, y2: p[0].y + 14 }), text(Math.min(last.x + 4, w - 110), last.y - 5, `VWAP ${fmtPrice(pv / vol)}`), partial ? text(Math.min(last.x + 4, w - 160), last.y + 12, tr("\u951A\u70B9\u524D\u6BB5\u5386\u53F2\u672A\u52A0\u8F7D\uFF0C\u6570\u503C\u6682\u4E0D\u5B8C\u6574", "Early history not loaded; value incomplete"), "start", "dhl-drawing-text dhl-vprofile-warn") : null);
18381
+ handles = [p[0]];
18382
+ break;
18383
+ }
18384
+ case "forecast": {
18385
+ if (!p[1]) break;
18386
+ const [a, b] = points2;
18387
+ const change = b.price - a.price, pct = a.price > 0 ? change / a.price * 100 : 0;
18388
+ const up = change >= 0;
18389
+ const boxY = up ? p[1].y - 44 : p[1].y + 8;
18390
+ body = /* @__PURE__ */ React7.createElement("g", { className: `dhl-measure ${up ? "up" : "down"}` }, /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y }), /* @__PURE__ */ React7.createElement("polygon", { className: "dhl-arrow-head", points: arrowHead(p[0], p[1]) }), /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: p[0].x, x2: p[1].x, y1: p[0].y, y2: p[0].y }), /* @__PURE__ */ React7.createElement("rect", { className: "dhl-measure-box", x: p[1].x - 78, y: boxY, width: "156", height: "36", rx: "4" }), text(p[1].x, boxY + 15, `${tr("\u76EE\u6807", "Target")} ${fmtPrice(b.price)} (${up ? "+" : ""}${pct.toFixed(2)}%)`, "middle"), text(p[1].x, boxY + 29, `${Math.abs(Math.round(view.bars(a.time, b.time)))} ${tr("\u6839", "bars")} \xB7 ${duration(b.time - a.time)}`, "middle"));
18391
+ break;
18392
+ }
18393
+ case "note":
18394
+ case "signpost": {
18395
+ const value = textValue || label || (kind === "note" ? tr("\u4FBF\u7B7E", "Note") : tr("\u8DEF\u6807", "Signpost"));
18396
+ const lines = wrapText(value, 220);
18397
+ const width = (16 + Math.max(...lines.map(textWidth))) * k, height = (10 + lines.length * 16) * k;
18398
+ const top = kind === "note" ? p[0].y - height / 2 : p[0].y - 46 - height;
18399
+ const left = kind === "note" ? p[0].x + 12 : p[0].x - 4;
18400
+ body = /* @__PURE__ */ React7.createElement("g", { className: kind === "note" ? "dhl-note" : "dhl-signpost" }, kind === "signpost" ? /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, x2: p[0].x, y1: p[0].y, y2: top + height }) : /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: p[0].x, y1: p[0].y, x2: left, y2: p[0].y }), /* @__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 dhl-note-box", x: left, y: top, width, height, rx: "4" }), lines.map((line, index) => /* @__PURE__ */ React7.createElement(React7.Fragment, { key: index }, text(left + 8 * k, top + (17 + index * 16) * k - 2, line, "start", "dhl-drawing-text dhl-text-value"))));
18401
+ handles = [p[0]];
18402
+ break;
18403
+ }
18404
+ case "pricenote": {
18405
+ if (!p[1]) break;
18406
+ const value = `${fmtPrice(points2[0].price)} \xB7 ${textValue || label || tr("\u4EF7\u683C\u4FBF\u7B7E", "Price note")}`;
18407
+ const width = Math.min(300, 16 + textWidth(value)) * k;
18408
+ 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 dhl-note-box", x: p[1].x, y: p[1].y, width, height: 26 * k, rx: "4" }), text(p[1].x + 8 * k, p[1].y + 17 * k, value, "start", "dhl-drawing-text dhl-text-value"));
18409
+ break;
18410
+ }
18411
+ case "flatchannel":
18412
+ case "disjoint": {
18413
+ if (!p[1]) break;
18414
+ const base = /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y });
18415
+ if (!p[2]) {
18416
+ body = base;
18417
+ break;
18418
+ }
18419
+ const a2 = kind === "flatchannel" ? { x: p[0].x, y: p[2].y } : p[2];
18420
+ const b2 = kind === "flatchannel" ? { x: p[1].x, y: p[2].y } : { x: p[2].x + (p[1].x - p[0].x), y: p[2].y - (p[1].y - p[0].y) };
18421
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("polygon", { className: "dhl-drawing-fill", points: `${p[0].x},${p[0].y} ${p[1].x},${p[1].y} ${b2.x},${b2.y} ${a2.x},${a2.y}` }), base, /* @__PURE__ */ React7.createElement("line", { x1: a2.x, y1: a2.y, x2: b2.x, y2: b2.y }));
18422
+ break;
18423
+ }
18424
+ case "fibfan": {
18425
+ if (!p[1]) break;
18426
+ const dx = p[1].x - p[0].x, dy = p[1].y - p[0].y;
18427
+ const ratios = [0.25, 0.382, 0.5, 0.618, 0.75];
18428
+ const ray = (end, key3, value) => {
18429
+ const seg = clipLine(p[0], end, w, h, 0);
18430
+ return seg ? /* @__PURE__ */ React7.createElement("g", { key: key3 }, /* @__PURE__ */ React7.createElement("line", { x1: seg[0].x, y1: seg[0].y, x2: seg[1].x, y2: seg[1].y }), text(end.x + 3, end.y - 3, value)) : null;
18431
+ };
18432
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("rect", { className: "dhl-drawing-mid", x: Math.min(p[0].x, p[1].x), y: Math.min(p[0].y, p[1].y), width: Math.abs(dx), height: Math.abs(dy), fill: "none" }), ratios.map((r) => ray({ x: p[1].x, y: p[0].y + dy * r }, `p${r}`, String(r))), ratios.map((r) => ray({ x: p[0].x + dx * r, y: p[1].y }, `t${r}`, String(r))), ray(p[1], "one", "1"));
18433
+ break;
18434
+ }
18435
+ case "fibcircles":
18436
+ case "fibarcs": {
18437
+ if (!p[1]) break;
18438
+ const center = kind === "fibcircles" ? p[0] : p[1];
18439
+ const radius2 = Math.hypot(p[1].x - p[0].x, p[1].y - p[0].y);
18440
+ const levels2 = kind === "fibcircles" ? [0.382, 0.5, 0.618, 1, 1.618, 2.618] : [0.382, 0.5, 0.618, 1];
18441
+ const sweep = p[0].y > p[1].y ? 1 : 0;
18442
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, levels2.map((level) => {
18443
+ const r = radius2 * level;
18444
+ return /* @__PURE__ */ React7.createElement("g", { key: level }, kind === "fibcircles" ? /* @__PURE__ */ React7.createElement("circle", { className: "dhl-fib-ring", cx: center.x, cy: center.y, r }) : /* @__PURE__ */ React7.createElement("path", { className: "dhl-fib-ring", d: `M${center.x - r} ${center.y}A${r} ${r} 0 0 ${sweep} ${center.x + r} ${center.y}` }), text(center.x + 3, center.y + (sweep ? -r : r) + (sweep ? 12 : -4), String(level)));
18445
+ }), /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y }));
18446
+ break;
18447
+ }
18448
+ case "fibwedge": {
18449
+ if (!p[1]) break;
18450
+ if (!p[2]) {
18451
+ body = /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y });
18452
+ break;
18453
+ }
18454
+ const radius2 = Math.hypot(p[1].x - p[0].x, p[1].y - p[0].y);
18455
+ const a1 = Math.atan2(p[1].y - p[0].y, p[1].x - p[0].x), a2 = Math.atan2(p[2].y - p[0].y, p[2].x - p[0].x);
18456
+ const span = Math.atan2(Math.sin(a2 - a1), Math.cos(a2 - a1));
18457
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[0].x + Math.cos(a1) * radius2, y2: p[0].y + Math.sin(a1) * radius2 }), /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[0].x + Math.cos(a2) * radius2, y2: p[0].y + Math.sin(a2) * radius2 }), [0.236, 0.382, 0.5, 0.618, 0.786, 1].map((level) => {
18458
+ const r = radius2 * level;
18459
+ const s = { x: p[0].x + Math.cos(a1) * r, y: p[0].y + Math.sin(a1) * r }, e = { x: p[0].x + Math.cos(a1 + span) * r, y: p[0].y + Math.sin(a1 + span) * r };
18460
+ return /* @__PURE__ */ React7.createElement("g", { key: level }, /* @__PURE__ */ React7.createElement("path", { className: "dhl-fib-ring", d: `M${s.x} ${s.y}A${r} ${r} 0 0 ${span > 0 ? 1 : 0} ${e.x} ${e.y}` }), text(s.x + 3, s.y - 3, String(level)));
18461
+ }));
18462
+ break;
18463
+ }
18464
+ case "fibtrendtime": {
18465
+ if (!p[1]) break;
18466
+ if (!p[2]) {
18467
+ body = /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y });
18468
+ break;
18469
+ }
18470
+ const unit = points2[1].time - points2[0].time;
18471
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y }), [0, 0.382, 0.5, 0.618, 1, 1.382, 1.618, 2, 2.618].map((level) => {
18472
+ const x = view.project(points2[2].time + unit * level, points2[2].price)?.x;
18473
+ return x == null || x < -2 || x > w + 2 ? null : /* @__PURE__ */ React7.createElement("g", { key: level }, /* @__PURE__ */ React7.createElement("line", { className: level === 0 || level === 1 ? "dhl-fib-key" : void 0, x1: x, x2: x, y1: "0", y2: h }), text(x + 3, 12, String(level)));
18474
+ }));
18475
+ break;
18476
+ }
18477
+ case "cypher": {
18478
+ const [X, A, B, C, D] = points2;
18479
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, p[2] ? /* @__PURE__ */ React7.createElement("polygon", { className: "dhl-drawing-fill", points: `${p[0].x},${p[0].y} ${p[1].x},${p[1].y} ${p[2].x},${p[2].y}` }) : null, p[4] ? /* @__PURE__ */ React7.createElement("polygon", { className: "dhl-drawing-fill", points: `${p[2].x},${p[2].y} ${p[3].x},${p[3].y} ${p[4].x},${p[4].y}` }) : null, wave(p, ["X", "A", "B", "C", "D"], text), p[2] ? guide(p[0], p[2], ratio(B.price - A.price, A.price - X.price), "xb", text) : null, p[3] ? guide(p[1], p[3], ratio(C.price - X.price, A.price - X.price), "xc", text) : null, p[4] ? guide(p[0], p[4], ratio(C.price - D.price, C.price - X.price), "xd", text) : null);
18480
+ break;
18481
+ }
18482
+ case "threedrives": {
18483
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, wave(p, ["", "1", "A", "2", "B", "3"], text), p[2] ? guide(p[0], p[2], ratio(points2[2].price - points2[1].price, points2[1].price - points2[0].price), "a", text) : null, p[3] ? guide(p[1], p[3], ratio(points2[3].price - points2[2].price, points2[1].price - points2[2].price), "d2", text) : null, p[4] ? guide(p[2], p[4], ratio(points2[4].price - points2[3].price, points2[3].price - points2[2].price), "b", text) : null, p[5] ? guide(p[3], p[5], ratio(points2[5].price - points2[4].price, points2[3].price - points2[4].price), "d3", text) : null);
18484
+ break;
18485
+ }
18486
+ case "tripattern": {
18487
+ const upper = p[2] ? clipLine(p[0], p[2], w, h, 0) : null, lower = p[3] ? clipLine(p[1], p[3], w, h, 0) : null;
18488
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, wave(p, ["A", "B", "C", "D"], text), upper ? /* @__PURE__ */ React7.createElement("line", { className: "dhl-neckline", x1: upper[0].x, y1: upper[0].y, x2: upper[1].x, y2: upper[1].y }) : null, lower ? /* @__PURE__ */ React7.createElement("line", { className: "dhl-neckline", x1: lower[0].x, y1: lower[0].y, x2: lower[1].x, y2: lower[1].y }) : null);
18489
+ break;
18490
+ }
18491
+ case "elliotttri":
18492
+ body = wave(p, ["0", "(A)", "(B)", "(C)", "(D)", "(E)"], text);
18493
+ break;
18494
+ case "elliottwxy":
18495
+ body = wave(p, ["0", "(W)", "(X)", "(Y)"], text);
18496
+ break;
18497
+ case "elliottwxyxz":
18498
+ body = wave(p, ["0", "(W)", "(X)", "(Y)", "(X)", "(Z)"], text);
18499
+ break;
18500
+ case "cycles": {
18501
+ if (!p[1]) break;
18502
+ const step = points2[1].time - points2[0].time;
18503
+ const xs = [];
18504
+ const spacing = (view.project(points2[0].time + step, points2[0].price)?.x ?? NaN) - p[0].x;
18505
+ const first = Number.isFinite(spacing) && spacing > 0 ? Math.max(0, Math.floor(-p[0].x / spacing) - 1) : 0;
18506
+ for (let n2 = first; n2 < first + 400; n2 += 1) {
18507
+ const x = view.project(points2[0].time + step * n2, points2[0].price)?.x;
18508
+ if (x == null || x > w + 2) break;
18509
+ if (x >= -2) xs.push(x);
18510
+ }
18511
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, xs.map((x, index) => /* @__PURE__ */ React7.createElement("line", { key: index, className: index === 0 ? "dhl-fib-key" : void 0, x1: x, x2: x, y1: "0", y2: h })));
18282
18512
  handles = [p[0], p[1]];
18283
18513
  break;
18284
18514
  }
18515
+ case "sine": {
18516
+ if (!p[1]) break;
18517
+ const half = p[1].x - p[0].x;
18518
+ if (Math.abs(half) < 2) {
18519
+ body = /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y });
18520
+ break;
18521
+ }
18522
+ const mid = (p[0].y + p[1].y) / 2, amp = (p[0].y - p[1].y) / 2;
18523
+ const step = Math.max(2, Math.abs(half) / 16);
18524
+ const pts = [];
18525
+ for (let x = 0; x <= w; x += step) pts.push(`${x.toFixed(1)},${(mid + amp * Math.cos(Math.PI * (x - p[0].x) / half)).toFixed(1)}`);
18526
+ body = /* @__PURE__ */ React7.createElement("polyline", { className: "dhl-pattern", points: pts.join(" ") });
18527
+ break;
18528
+ }
18529
+ case "ellipse": {
18530
+ if (!p[1]) break;
18531
+ body = /* @__PURE__ */ React7.createElement("ellipse", { className: "dhl-drawing-fill", cx: (p[0].x + p[1].x) / 2, cy: (p[0].y + p[1].y) / 2, rx: Math.abs(p[1].x - p[0].x) / 2, ry: Math.abs(p[1].y - p[0].y) / 2 });
18532
+ break;
18533
+ }
18534
+ case "rotrect": {
18535
+ if (!p[1]) break;
18536
+ if (!p[2]) {
18537
+ body = /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y });
18538
+ break;
18539
+ }
18540
+ const ux = p[1].x - p[0].x, uy = p[1].y - p[0].y, len = Math.hypot(ux, uy) || 1;
18541
+ const nx = -uy / len, ny = ux / len;
18542
+ const d = (p[2].x - p[0].x) * nx + (p[2].y - p[0].y) * ny;
18543
+ const c = { x: p[1].x + nx * d, y: p[1].y + ny * d }, e = { x: p[0].x + nx * d, y: p[0].y + ny * d };
18544
+ body = /* @__PURE__ */ React7.createElement("polygon", { className: "dhl-drawing-fill", points: `${p[0].x},${p[0].y} ${p[1].x},${p[1].y} ${c.x},${c.y} ${e.x},${e.y}` });
18545
+ break;
18546
+ }
18547
+ case "arc":
18548
+ case "curve": {
18549
+ if (!p[1]) break;
18550
+ if (!p[2]) {
18551
+ body = /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y });
18552
+ break;
18553
+ }
18554
+ const control = kind === "arc" ? { x: 2 * p[2].x - (p[0].x + p[1].x) / 2, y: 2 * p[2].y - (p[0].y + p[1].y) / 2 } : p[2];
18555
+ body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("path", { className: "dhl-curve", d: `M${p[0].x} ${p[0].y}Q${control.x} ${control.y} ${p[1].x} ${p[1].y}` }), kind === "curve" && anchors ? /* @__PURE__ */ React7.createElement("polyline", { className: "dhl-drawing-mid", points: `${p[0].x},${p[0].y} ${p[2].x},${p[2].y} ${p[1].x},${p[1].y}` }) : null);
18556
+ break;
18557
+ }
18285
18558
  case "text": {
18286
18559
  const value = textValue || label || tr("\u6587\u672C", "Text");
18287
18560
  const lines = wrapText(value, 300);
@@ -18357,7 +18630,7 @@ function saveStyleTemplate(kind, style) {
18357
18630
  // src/client/components/draw-toolbar.tsx
18358
18631
  var DRAW_COLORS = ["#4c82d8", "#2fd3a0", "#ef5f6c", "#e2a943", "#9b86db", "#26b5c8", "#8a8f98"];
18359
18632
  var STYLES = [["solid", "\u5B9E\u7EBF", "Solid"], ["dashed", "\u865A\u7EBF", "Dashed"], ["dotted", "\u70B9\u7EBF", "Dotted"]];
18360
- function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone, onCoordinates }) {
18633
+ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone, onCoordinates, placement = "top" }) {
18361
18634
  const [savedDefault, setSavedDefault] = React8.useState(false);
18362
18635
  React8.useEffect(() => {
18363
18636
  setSavedDefault(false);
@@ -18388,7 +18661,7 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
18388
18661
  const style = mark.lineStyle ?? "solid";
18389
18662
  const tool = toolOf(mark.kind);
18390
18663
  const stop = (event) => event.stopPropagation();
18391
- return /* @__PURE__ */ React8.createElement("div", { className: "dhl-draw-toolbar", role: "toolbar", "aria-label": tr("\u753B\u7EBF\u6837\u5F0F", "Drawing style"), onPointerDown: stop, onClick: stop }, /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-name" }, tr(tool.zh, tool.en)), isTextKind(mark.kind) ? /* @__PURE__ */ React8.createElement(
18664
+ return /* @__PURE__ */ React8.createElement("div", { className: "dhl-draw-toolbar" + (placement === "bottom" ? " at-bottom" : ""), role: "toolbar", "aria-label": tr("\u753B\u7EBF\u6837\u5F0F", "Drawing style"), onPointerDown: stop, onClick: stop }, /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-name" }, tr(tool.zh, tool.en)), isTextKind(mark.kind) ? /* @__PURE__ */ React8.createElement(
18392
18665
  "input",
18393
18666
  {
18394
18667
  className: "dhl-draw-toolbar-text",
@@ -18725,7 +18998,19 @@ function cssVar(el, name, fallback) {
18725
18998
  function chartData(points2) {
18726
18999
  return points2.map((point) => ({ time: point.time, value: point.value }));
18727
19000
  }
19001
+ var TOOLBAR_CLEARANCE = 84;
18728
19002
  var FINITE_KINDS = /* @__PURE__ */ new Set([
19003
+ "ellipse",
19004
+ "curve",
19005
+ "note",
19006
+ "pricenote",
19007
+ "signpost",
19008
+ "forecast",
19009
+ "cypher",
19010
+ "threedrives",
19011
+ "elliotttri",
19012
+ "elliottwxy",
19013
+ "elliottwxyxz",
18729
19014
  "trend",
18730
19015
  "rect",
18731
19016
  "measure",
@@ -19018,13 +19303,16 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19018
19303
  box.addEventListener("pointermove", move, true);
19019
19304
  box.addEventListener("pointerup", move, true);
19020
19305
  box.addEventListener("pointercancel", cancel, true);
19306
+ let touchTapAt = -Infinity;
19307
+ const fromTouch = (event) => event.pointerType === "touch" || Boolean(event.sourceCapabilities?.firesTouchEvents);
19021
19308
  const chartClick = (event) => {
19309
+ if (event.type === "click" && fromTouch(event) && performance.now() - touchTapAt < 800) return false;
19022
19310
  const visible = chart.timeScale().getVisibleLogicalRange();
19023
19311
  const panned = !pressStillAClick(press?.range ?? null, visible ? { from: visible.from, to: visible.to } : null);
19024
19312
  const clicked = Boolean(press && !press.dragged && !panned && event.button === 0);
19025
19313
  press = null;
19026
- if (!clicked) return;
19027
- if (!(event.target instanceof HTMLCanvasElement)) return;
19314
+ if (!clicked) return false;
19315
+ if (!(event.target instanceof HTMLCanvasElement)) return false;
19028
19316
  const rect = box.getBoundingClientRect();
19029
19317
  const x = event.clientX - rect.left;
19030
19318
  const y = event.clientY - rect.top;
@@ -19075,6 +19363,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19075
19363
  }
19076
19364
  };
19077
19365
  box.addEventListener("click", chartClick);
19366
+ const touchTap = (event) => {
19367
+ if (event.pointerType !== "touch" || press?.id !== event.pointerId) return;
19368
+ if (chartClick(event) !== false) touchTapAt = performance.now();
19369
+ };
19370
+ box.addEventListener("pointerup", touchTap);
19078
19371
  let rangeTimer = null;
19079
19372
  let viewportFrame = 0;
19080
19373
  const refreshOverlay = () => {
@@ -19144,6 +19437,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19144
19437
  box.removeEventListener("pointerup", move, true);
19145
19438
  box.removeEventListener("pointercancel", cancel, true);
19146
19439
  box.removeEventListener("click", chartClick);
19440
+ box.removeEventListener("pointerup", touchTap);
19147
19441
  chart.timeScale().unsubscribeVisibleLogicalRangeChange(refreshOverlay);
19148
19442
  box.removeEventListener("pointermove", scaleDrag);
19149
19443
  box.removeEventListener("pointerdown", beginCanvasGesture);
@@ -19509,6 +19803,12 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19509
19803
  window.__dhlPriceLines = () => priceLines.current.size;
19510
19804
  }, [marks, themeVersion, chartType, interval]);
19511
19805
  const warming = candles.length < 2;
19806
+ const toolbarPlacement = (mark) => {
19807
+ if (!view) return "top";
19808
+ const ys = markPoints(mark).map((point) => view.project(point.time, point.price)?.y).filter((y) => y != null);
19809
+ const top = ys.filter((y) => y < TOOLBAR_CLEARANCE).length, bottom = ys.filter((y) => y > view.height - TOOLBAR_CLEARANCE).length;
19810
+ return bottom < top ? "bottom" : "top";
19811
+ };
19512
19812
  const markById = React10.useMemo(() => new Map(marks.map((mark) => [mark.id, mark])), [marks]);
19513
19813
  const markByIdRef = React10.useRef(markById);
19514
19814
  markByIdRef.current = markById;
@@ -19933,7 +20233,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19933
20233
  points: shown,
19934
20234
  view,
19935
20235
  text: mark.label,
19936
- dataVersion: mark.kind === "vprofile" || mark.kind === "regression" ? candles : void 0,
20236
+ dataVersion: mark.kind === "vprofile" || mark.kind === "regression" || mark.kind === "avwap" || mark.kind === "avprofile" ? candles : void 0,
19937
20237
  className: `dhl-drawing ${roleClass(mark)}${mark.kind === "level" ? " is-level" : ""}${selected ? " selected" : ""}${mark.locked || drawingsLocked ? " locked" : ""}`,
19938
20238
  label: mark.source === "user" || mark.kind === "level" ? void 0 : mark.label,
19939
20239
  anchors: selected,
@@ -19994,6 +20294,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
19994
20294
  {
19995
20295
  mark: selectedMark,
19996
20296
  globallyLocked: drawingsLocked,
20297
+ placement: toolbarPlacement(selectedMark),
19997
20298
  onCoordinates: () => setEditingId(selectedMark.id),
19998
20299
  onEdit: (patch) => onEditMark(selectedMark.id, patch),
19999
20300
  onClone: () => onCloneMark(cloneOf(selectedMark)),
@@ -21367,6 +21668,7 @@ function DeskColumn({ onCollapse }) {
21367
21668
  const [textAt, setTextAt] = React26.useState(null);
21368
21669
  const [selectRequest, setSelectRequest] = React26.useState(null);
21369
21670
  const [textValue, setTextValue] = React26.useState("");
21671
+ const [toolQuery, setToolQuery] = React26.useState("");
21370
21672
  const [confirmClear, setConfirmClear] = React26.useState(false);
21371
21673
  const [pickerOpen, setPickerOpen] = React26.useState(false);
21372
21674
  const [chartExpanded, setChartExpanded] = React26.useState(false);
@@ -21530,7 +21832,7 @@ function DeskColumn({ onCollapse }) {
21530
21832
  }, [indicators, s.candles, s.candlesOlder]);
21531
21833
  React26.useEffect(() => {
21532
21834
  if (s.candlesOlder !== "idle" || s.candles.length < 2) return;
21533
- const earliest = Math.min(...s.chartMarks.filter((mark2) => !mark2.hidden && (mark2.kind === "vprofile" || mark2.kind === "regression")).map((mark2) => Math.min(...(mark2.points ?? []).map((point) => point.time))));
21835
+ const earliest = Math.min(...s.chartMarks.filter((mark2) => !mark2.hidden && ["vprofile", "regression", "avwap", "avprofile"].includes(mark2.kind)).map((mark2) => Math.min(...(mark2.points ?? []).map((point) => point.time))));
21534
21836
  if (Number.isFinite(earliest) && s.candles[0].t > earliest) void loadOlderCandles();
21535
21837
  }, [s.chartMarks, s.candles, s.candlesOlder]);
21536
21838
  React26.useEffect(() => {
@@ -21587,7 +21889,30 @@ function DeskColumn({ onCollapse }) {
21587
21889
  }
21588
21890
  }
21589
21891
  )
21590
- ), pickerOpen ? /* @__PURE__ */ React26.createElement(MarketPicker, { activeCoin: coin, onClose: () => setPickerOpen(false) }) : null, /* @__PURE__ */ React26.createElement("div", { className: "dhl-market-workspace" + (s.tapePlacement === "below" ? " dhl-tape-below" : "") }, /* @__PURE__ */ React26.createElement("section", { className: "dhl-chart-panel" + (chartExpanded ? " dhl-chart-panel-expanded" : ""), "aria-label": tr("\u884C\u60C5\u56FE\u8868", "Market chart") }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tabs", role: "tablist", "aria-label": tr("\u56FE\u8868\u5185\u5BB9", "Chart content") }, /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "chart", className: chartTab === "chart" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "chart" }) }, tr("K \u7EBF", "Chart")), row?.kind !== "spot" && row?.kind !== "outcome" ? /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "funding", className: chartTab === "funding" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "funding" }) }, tr("\u8D44\u91D1\u8D39\u5386\u53F2", "Funding history")) : null), chartTab === "chart" ? /* @__PURE__ */ React26.createElement("div", { role: "tabpanel" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tools" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-intervals", "aria-label": tr("K \u7EBF\u5468\u671F", "Chart interval") }, INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("button", { key: iv, type: "button", className: s.interval === iv ? "on" : "", onClick: () => changeInterval(iv) }, iv)), /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u66F4\u591A K \u7EBF\u5468\u671F", "More chart intervals"), value: MORE_INTERVALS.includes(s.interval) ? s.interval : "", onChange: (event) => changeInterval(event.target.value) }, /* @__PURE__ */ React26.createElement("option", { value: "", disabled: true }, tr("\u66F4\u591A", "More")), MORE_INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("option", { key: iv, value: iv }, iv)))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-actions" }, /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u56FE\u8868\u7C7B\u578B", "Chart type"), value: chartType, onChange: (event) => saveSessionWorkspace({ chartType: event.target.value }) }, [["candles", "\u8721\u70DB\u56FE", "Candles"], ["bars", "\u7F8E\u56FD\u7EBF", "OHLC bars"], ["line", "\u6298\u7EBF\u56FE", "Line"], ["area", "\u9762\u79EF\u56FE", "Area"], ["heikinAshi", "\u5E73\u5747K\u7EBF", "Heikin Ashi"]].map(([value, zh, en]) => /* @__PURE__ */ React26.createElement("option", { key: value, value }, tr(zh, en)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-layer-menu" }, /* @__PURE__ */ React26.createElement("summary", null, tr("\u663E\u793A", "Show"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-layer-popover" }, [["orders", tr("\u59D4\u6258", "Orders")], ["fills", tr("\u6210\u4EA4", "Fills")], ["reasons", tr("\u6210\u4EA4\u539F\u56E0", "Fill reasons")], ["drawings", tr("\u753B\u7EBF", "Drawings")]].map(([id, label]) => /* @__PURE__ */ React26.createElement("label", { key: id }, /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: layers[id], onChange: (event) => saveSessionWorkspace({ chartLayers: { ...layers, [id]: event.target.checked } }) }), label)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-indicator-menu" }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u9009\u62E9\u56FE\u8868\u6307\u6807", "Choose chart indicators") }, tr("\u6307\u6807", "Indicators"), " ", /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-count" }, indicators.size + maLines.length)), /* @__PURE__ */ React26.createElement("div", { className: "dhl-indicator-popover", role: "group", "aria-label": tr("\u56FE\u8868\u6307\u6807", "Chart indicators") }, /* @__PURE__ */ React26.createElement("strong", null, tr("\u56FE\u8868\u6307\u6807", "Chart indicators")), INDICATOR_CHOICES.map(([id, label, description]) => /* @__PURE__ */ React26.createElement("button", { type: "button", key: id, className: indicators.has(id) ? "on" : "", "aria-pressed": indicators.has(id), onClick: () => toggleIndicator(id) }, /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-check", "aria-hidden": "true" }, indicators.has(id) ? /* @__PURE__ */ React26.createElement(UiIcon, { name: "check", size: 13 }) : null), /* @__PURE__ */ React26.createElement("span", null, /* @__PURE__ */ React26.createElement("b", null, id === "rsi14" ? `RSI ${studySettings.rsi.period}` : id === "bollinger20" ? `BOLL ${studySettings.bollinger.period} \xD7 ${studySettings.bollinger.multiplier}` : tr(label, INDICATOR_EN[id]?.[0] ?? label)), /* @__PURE__ */ React26.createElement("small", null, id === "macd" ? `${studySettings.macd.fast} / ${studySettings.macd.slow} / ${studySettings.macd.signal}` : id === "bollinger20" ? tr("\u5747\u7EBF\u4E0E\u6807\u51C6\u5DEE\u901A\u9053", "Mean and standard deviation bands") : tr(description, INDICATOR_EN[id]?.[1] ?? description))))), /* @__PURE__ */ React26.createElement(IndicatorEditor, { key: s.activeSessionId, settings: s.workspace?.indicatorSettings, indicators: [...indicators], maLines, onApply: saveSessionWorkspace }), /* @__PURE__ */ React26.createElement(MaEditor, { lines: maLines, onChange: (next) => saveSessionWorkspace({ maLines: next }) }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: s.workspace?.showDataWindow ? "on" : "", "aria-pressed": s.workspace?.showDataWindow ?? false, onClick: () => saveSessionWorkspace({ showDataWindow: !s.workspace?.showDataWindow }) }, tr("\u6570\u636E\u7A97\u53E3", "Data window")), /* @__PURE__ */ React26.createElement(StrategyMenu, { candles: s.candles, coin, interval: s.interval }), /* @__PURE__ */ React26.createElement("details", { className: "dhl-draw-menu" + (drawMode ? " active" : ""), ref: drawMenu }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, drawMode ? /* @__PURE__ */ React26.createElement(React26.Fragment, null, /* @__PURE__ */ React26.createElement(ToolIcon, { kind: drawMode, size: 14 }), tr(toolOf(drawMode).zh, toolOf(drawMode).en)) : tr("\u753B\u7EBF", "Draw"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-popover", role: "menu", "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, DRAWING_TOOLS.map((tool, index) => /* @__PURE__ */ React26.createElement(React26.Fragment, { key: tool.kind }, index > 0 && DRAWING_TOOLS[index - 1].group !== tool.group ? /* @__PURE__ */ React26.createElement("hr", null) : null, /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-tool-row" }, /* @__PURE__ */ React26.createElement(
21892
+ ), pickerOpen ? /* @__PURE__ */ React26.createElement(MarketPicker, { activeCoin: coin, onClose: () => setPickerOpen(false) }) : null, /* @__PURE__ */ React26.createElement("div", { className: "dhl-market-workspace" + (s.tapePlacement === "below" ? " dhl-tape-below" : "") }, /* @__PURE__ */ React26.createElement("section", { className: "dhl-chart-panel" + (chartExpanded ? " dhl-chart-panel-expanded" : ""), "aria-label": tr("\u884C\u60C5\u56FE\u8868", "Market chart") }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tabs", role: "tablist", "aria-label": tr("\u56FE\u8868\u5185\u5BB9", "Chart content") }, /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "chart", className: chartTab === "chart" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "chart" }) }, tr("K \u7EBF", "Chart")), row?.kind !== "spot" && row?.kind !== "outcome" ? /* @__PURE__ */ React26.createElement("button", { type: "button", role: "tab", "aria-selected": chartTab === "funding", className: chartTab === "funding" ? "on" : "", onClick: () => saveSessionWorkspace({ chartTab: "funding" }) }, tr("\u8D44\u91D1\u8D39\u5386\u53F2", "Funding history")) : null), chartTab === "chart" ? /* @__PURE__ */ React26.createElement("div", { role: "tabpanel" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-tools" }, /* @__PURE__ */ React26.createElement("div", { className: "dhl-intervals", "aria-label": tr("K \u7EBF\u5468\u671F", "Chart interval") }, INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("button", { key: iv, type: "button", className: s.interval === iv ? "on" : "", onClick: () => changeInterval(iv) }, iv)), /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u66F4\u591A K \u7EBF\u5468\u671F", "More chart intervals"), value: MORE_INTERVALS.includes(s.interval) ? s.interval : "", onChange: (event) => changeInterval(event.target.value) }, /* @__PURE__ */ React26.createElement("option", { value: "", disabled: true }, tr("\u66F4\u591A", "More")), MORE_INTERVALS.map((iv) => /* @__PURE__ */ React26.createElement("option", { key: iv, value: iv }, iv)))), /* @__PURE__ */ React26.createElement("div", { className: "dhl-chart-actions" }, /* @__PURE__ */ React26.createElement(Select, { "aria-label": tr("\u56FE\u8868\u7C7B\u578B", "Chart type"), value: chartType, onChange: (event) => saveSessionWorkspace({ chartType: event.target.value }) }, [["candles", "\u8721\u70DB\u56FE", "Candles"], ["bars", "\u7F8E\u56FD\u7EBF", "OHLC bars"], ["line", "\u6298\u7EBF\u56FE", "Line"], ["area", "\u9762\u79EF\u56FE", "Area"], ["heikinAshi", "\u5E73\u5747K\u7EBF", "Heikin Ashi"]].map(([value, zh, en]) => /* @__PURE__ */ React26.createElement("option", { key: value, value }, tr(zh, en)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-layer-menu" }, /* @__PURE__ */ React26.createElement("summary", null, tr("\u663E\u793A", "Show"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-layer-popover" }, [["orders", tr("\u59D4\u6258", "Orders")], ["fills", tr("\u6210\u4EA4", "Fills")], ["reasons", tr("\u6210\u4EA4\u539F\u56E0", "Fill reasons")], ["drawings", tr("\u753B\u7EBF", "Drawings")]].map(([id, label]) => /* @__PURE__ */ React26.createElement("label", { key: id }, /* @__PURE__ */ React26.createElement("input", { type: "checkbox", checked: layers[id], onChange: (event) => saveSessionWorkspace({ chartLayers: { ...layers, [id]: event.target.checked } }) }), label)))), /* @__PURE__ */ React26.createElement("details", { className: "dhl-indicator-menu" }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u9009\u62E9\u56FE\u8868\u6307\u6807", "Choose chart indicators") }, tr("\u6307\u6807", "Indicators"), " ", /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-count" }, indicators.size + maLines.length)), /* @__PURE__ */ React26.createElement("div", { className: "dhl-indicator-popover", role: "group", "aria-label": tr("\u56FE\u8868\u6307\u6807", "Chart indicators") }, /* @__PURE__ */ React26.createElement("strong", null, tr("\u56FE\u8868\u6307\u6807", "Chart indicators")), INDICATOR_CHOICES.map(([id, label, description]) => /* @__PURE__ */ React26.createElement("button", { type: "button", key: id, className: indicators.has(id) ? "on" : "", "aria-pressed": indicators.has(id), onClick: () => toggleIndicator(id) }, /* @__PURE__ */ React26.createElement("span", { className: "dhl-indicator-check", "aria-hidden": "true" }, indicators.has(id) ? /* @__PURE__ */ React26.createElement(UiIcon, { name: "check", size: 13 }) : null), /* @__PURE__ */ React26.createElement("span", null, /* @__PURE__ */ React26.createElement("b", null, id === "rsi14" ? `RSI ${studySettings.rsi.period}` : id === "bollinger20" ? `BOLL ${studySettings.bollinger.period} \xD7 ${studySettings.bollinger.multiplier}` : tr(label, INDICATOR_EN[id]?.[0] ?? label)), /* @__PURE__ */ React26.createElement("small", null, id === "macd" ? `${studySettings.macd.fast} / ${studySettings.macd.slow} / ${studySettings.macd.signal}` : id === "bollinger20" ? tr("\u5747\u7EBF\u4E0E\u6807\u51C6\u5DEE\u901A\u9053", "Mean and standard deviation bands") : tr(description, INDICATOR_EN[id]?.[1] ?? description))))), /* @__PURE__ */ React26.createElement(IndicatorEditor, { key: s.activeSessionId, settings: s.workspace?.indicatorSettings, indicators: [...indicators], maLines, onApply: saveSessionWorkspace }), /* @__PURE__ */ React26.createElement(MaEditor, { lines: maLines, onChange: (next) => saveSessionWorkspace({ maLines: next }) }))), /* @__PURE__ */ React26.createElement("button", { type: "button", className: s.workspace?.showDataWindow ? "on" : "", "aria-pressed": s.workspace?.showDataWindow ?? false, onClick: () => saveSessionWorkspace({ showDataWindow: !s.workspace?.showDataWindow }) }, tr("\u6570\u636E\u7A97\u53E3", "Data window")), /* @__PURE__ */ React26.createElement(StrategyMenu, { candles: s.candles, coin, interval: s.interval }), /* @__PURE__ */ React26.createElement("details", { className: "dhl-draw-menu" + (drawMode ? " active" : ""), ref: drawMenu }, /* @__PURE__ */ React26.createElement("summary", { "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, drawMode ? /* @__PURE__ */ React26.createElement(React26.Fragment, null, /* @__PURE__ */ React26.createElement(ToolIcon, { kind: drawMode, size: 14 }), tr(toolOf(drawMode).zh, toolOf(drawMode).en)) : tr("\u753B\u7EBF", "Draw"), /* @__PURE__ */ React26.createElement(UiIcon, { name: "chevronDown", size: 12 })), /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-popover", role: "menu", "aria-label": tr("\u753B\u7EBF\u5DE5\u5177", "Drawing tools") }, /* @__PURE__ */ React26.createElement(
21893
+ "input",
21894
+ {
21895
+ className: "dhl-draw-search",
21896
+ type: "search",
21897
+ value: toolQuery,
21898
+ placeholder: tr(`\u641C\u7D22 ${DRAWING_TOOLS.length} \u79CD\u753B\u7EBF\u5DE5\u5177`, `Search ${DRAWING_TOOLS.length} drawing tools`),
21899
+ "aria-label": tr("\u641C\u7D22\u753B\u7EBF\u5DE5\u5177", "Search drawing tools"),
21900
+ onChange: (event) => setToolQuery(event.target.value),
21901
+ onKeyDown: (event) => {
21902
+ if (event.key === "Escape" && toolQuery) {
21903
+ event.stopPropagation();
21904
+ setToolQuery("");
21905
+ }
21906
+ }
21907
+ }
21908
+ ), (() => {
21909
+ const query = toolQuery.trim().toLowerCase();
21910
+ const shown = query ? DRAWING_TOOLS.filter((tool) => `${tool.zh} ${tool.en} ${tool.kind}`.toLowerCase().includes(query)) : DRAWING_TOOLS;
21911
+ return shown.length ? null : /* @__PURE__ */ React26.createElement("p", { className: "dhl-draw-empty" }, tr("\u6CA1\u6709\u5339\u914D\u7684\u753B\u7EBF\u5DE5\u5177", "No matching tools"));
21912
+ })(), DRAWING_TOOLS.filter((tool) => {
21913
+ const query = toolQuery.trim().toLowerCase();
21914
+ return !query || `${tool.zh} ${tool.en} ${tool.kind}`.toLowerCase().includes(query);
21915
+ }).map((tool, index, shown) => /* @__PURE__ */ React26.createElement(React26.Fragment, { key: tool.kind }, index > 0 && shown[index - 1].group !== tool.group ? /* @__PURE__ */ React26.createElement("hr", null) : null, /* @__PURE__ */ React26.createElement("div", { className: "dhl-draw-tool-row" }, /* @__PURE__ */ React26.createElement(
21591
21916
  "button",
21592
21917
  {
21593
21918
  type: "button",
@@ -1,4 +1,4 @@
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'
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' | 'flatchannel' | 'disjoint' | 'fibfan' | 'fibcircles' | 'fibarcs' | 'fibwedge' | 'fibtrendtime' | 'cypher' | 'threedrives' | 'tripattern' | 'elliotttri' | 'elliottwxy' | 'elliottwxyxz' | 'cycles' | 'sine' | 'ellipse' | 'rotrect' | 'arc' | 'curve' | 'note' | 'pricenote' | 'signpost' | 'avwap' | 'avprofile' | 'forecast'
2
2
  export type LineStyleName = 'solid' | 'dashed' | 'dotted'
3
3
  export interface DrawingStyle { visibleIntervals?: string[]; fontSize?: number; bold?: boolean; italic?: boolean; extendLeft?: boolean; extendRight?: 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>
@@ -23,6 +23,13 @@ export const DRAWING_POINTS = {
23
23
  // Advanced: Elliott waves, harmonic / chart patterns, Gann box & square, pitchfork variants, icons.
24
24
  elliott: 6, elliottabc: 4, xabcd: 5, abcd: 4, headshoulders: 7,
25
25
  gannbox: 2, gannsquare: 2, schiff: 3, modschiff: 3, icon: 1,
26
+ // TradingView's remaining common tools (0.5.0).
27
+ flatchannel: 3, disjoint: 3,
28
+ fibfan: 2, fibcircles: 2, fibarcs: 2, fibwedge: 3, fibtrendtime: 3,
29
+ cypher: 5, threedrives: 6, tripattern: 4, elliotttri: 6, elliottwxy: 4, elliottwxyxz: 6, cycles: 2, sine: 2,
30
+ ellipse: 2, rotrect: 3, arc: 3, curve: 3,
31
+ note: 1, pricenote: 2, signpost: 1,
32
+ avwap: 1, avprofile: 1, forecast: 2,
26
33
  }
27
34
  /** Icon stickers (the drawing's label holds the icon name). */
28
35
  export const ICON_NAMES = ['flag', 'star', 'up', 'down', 'warning', 'check', 'cross', 'fire', 'target', 'bell', 'pin', 'clock', 'dollar', 'shield', 'bolt', 'heart', 'question', 'info']
@@ -55,9 +62,9 @@ export function isSingleEmoji(value) {
55
62
  /** Chart intervals a drawing can be limited to (same set as the desk). */
56
63
  export const INTERVALS = ['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '1d', '3d', '1w', '1M']
57
64
  /** Text drawings whose font size can be set. */
58
- export const FONT_KINDS = ['text', 'callout']
65
+ export const FONT_KINDS = ['text', 'callout', 'note', 'pricenote', 'signpost']
59
66
  /** 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']
67
+ export const FILL_KINDS = ['rect', 'circle', 'triangle', 'channel', 'fib', 'regression', 'gannbox', 'pricerange', 'daterange', 'measure', 'flatchannel', 'disjoint', 'ellipse', 'rotrect']
61
68
  /** Fibonacci drawings whose levels can be customised. */
62
69
  export const FIB_LEVEL_KINDS = ['fib', 'fibext', 'fibchannel']
63
70
  export const PROFILE_ROWS_MIN = 6
@@ -115,7 +122,7 @@ export function linePriceAt(mark, time) {
115
122
  export const BRUSH_MIN = 2
116
123
  export const BRUSH_MAX = 500
117
124
  /** Kinds whose label is free text shown on the chart (up to 200 characters). */
118
- export const TEXT_KINDS = ['text', 'callout']
125
+ export const TEXT_KINDS = ['text', 'callout', 'note', 'pricenote', 'signpost']
119
126
  /** Clicks that create a drawing when they differ from its stored anchor count. */
120
127
  export const DRAWING_CLICKS = { ...Object.fromEntries(Object.entries(DRAWING_POINTS)), long: 2, short: 2 }
121
128
  export const DRAWING_KINDS = Object.keys(DRAWING_POINTS)
@@ -160,7 +167,8 @@ export function drawingGeometry(kind, request, now = Date.now()) {
160
167
  if (!Array.isArray(raw) || raw.length < BRUSH_MIN || raw.length > BRUSH_MAX) throw new Error(`brush 需要 ${BRUSH_MIN}-${BRUSH_MAX} 个 points`)
161
168
  } else if (!Array.isArray(raw) || raw.length !== need) throw new Error(`${kind} 需要 ${need} 个 points({ time, price })`)
162
169
  const points = raw.map((item, index) => point(item?.time, item?.price, `第 ${index + 1} 个点`))
163
- if (['trend', 'ray', 'extended', 'channel', 'fibchannel', 'gannfan', 'regression', 'fibtime', 'infoline', 'trendangle'].includes(kind) && points[0].time === points[1].time) throw new Error('前两个点的时间不能相同')
170
+ if (['trend', 'ray', 'extended', 'channel', 'fibchannel', 'gannfan', 'regression', 'fibtime', 'infoline', 'trendangle', 'flatchannel', 'disjoint', 'fibfan', 'fibtrendtime', 'cycles', 'sine', 'forecast'].includes(kind) && points[0].time === points[1].time) throw new Error('前两个点的时间不能相同')
171
+ if ((kind === 'cycles' || kind === 'sine' || kind === 'fibtrendtime') && points[1].time < points[0].time) throw new Error('第二个点必须晚于第一个点')
164
172
  if (kind === 'vprofile') {
165
173
  if (points[0].time === points[1].time) throw new Error('成交量分布需要一个时间区间')
166
174
  // Only the time range matters, so either drawing direction is fine.
package/lib/tools.js CHANGED
@@ -10,9 +10,11 @@ import { closedCandles, computeIndicators } from './indicators.js'
10
10
  import { MA_KINDS, MA_SOURCES, maLabel, maLatest, normalizeMaLine, normalizeMaLines } from './moving-average.js'
11
11
  import { INDICATOR_IDS, computeStudies, normalizeIndicatorSettings, studyDefinitions } from './studies.js'
12
12
  import { reviewLedger } from './review.js'
13
- import { DRAWING_KINDS, drawingStyle, volumeProfile } from './chart-drawings.js'
13
+ import { DRAWING_KINDS, DRAWING_POINTS, TEXT_KINDS, drawingStyle, volumeProfile } from './chart-drawings.js'
14
14
 
15
- const DRAWING_NAMES = { vprofile: '固定范围成交量分布', level: '水平线', hray: '水平射线', vline: '垂直线', text: '文本', trend: '趋势线', ray: '射线', extended: '延长线', channel: '平行通道', fib: '斐波那契回撤', rect: '矩形', measure: '价格区间', long: '多头头寸', short: '空头头寸', fibext: '斐波那契扩展', fibchannel: '斐波那契通道', fibtime: '斐波那契时间周期', gannfan: '江恩扇形', pitchfork: '安德鲁叉子线', regression: '回归趋势', arrow: '箭头', callout: '标注气泡', circle: '圆形', triangle: '三角形', brush: '画笔', crossline: '十字线', pricelabel: '价格标签', infoline: '信息线', trendangle: '趋势角度', pricerange: '价格区间', daterange: '日期区间', elliott: '艾略特推动浪', elliottabc: '艾略特调整浪', xabcd: 'XABCD 谐波', abcd: 'ABCD 形态', headshoulders: '头肩形态', gannbox: '江恩箱', gannsquare: '江恩方格', schiff: 'Schiff 叉子线', modschiff: '改良 Schiff 叉子线', icon: '图标' }
15
+ const DRAWING_NAMES = { flatchannel: '平顶/平底通道', disjoint: '分离通道', fibfan: '斐波那契速度阻力扇', fibcircles: '斐波那契圆', fibarcs: '斐波那契弧', fibwedge: '斐波那契楔形', fibtrendtime: '基于趋势的斐波那契时间',
16
+ cypher: 'Cypher 形态', threedrives: '三驱形态', tripattern: '三角形态', elliotttri: '艾略特三角浪', elliottwxy: '艾略特双重组合', elliottwxyxz: '艾略特三重组合', cycles: '周期线', sine: '正弦线',
17
+ ellipse: '椭圆', rotrect: '旋转矩形', arc: '弧线', curve: '曲线', note: '便签', pricenote: '价格便签', signpost: '路标', avwap: '锚定 VWAP', avprofile: '锚定成交量分布', forecast: '预测', vprofile: '固定范围成交量分布', level: '水平线', hray: '水平射线', vline: '垂直线', text: '文本', trend: '趋势线', ray: '射线', extended: '延长线', channel: '平行通道', fib: '斐波那契回撤', rect: '矩形', measure: '价格区间', long: '多头头寸', short: '空头头寸', fibext: '斐波那契扩展', fibchannel: '斐波那契通道', fibtime: '斐波那契时间周期', gannfan: '江恩扇形', pitchfork: '安德鲁叉子线', regression: '回归趋势', arrow: '箭头', callout: '标注气泡', circle: '圆形', triangle: '三角形', brush: '画笔', crossline: '十字线', pricelabel: '价格标签', infoline: '信息线', trendangle: '趋势角度', pricerange: '价格区间', daterange: '日期区间', elliott: '艾略特推动浪', elliottabc: '艾略特调整浪', xabcd: 'XABCD 谐波', abcd: 'ABCD 形态', headshoulders: '头肩形态', gannbox: '江恩箱', gannsquare: '江恩方格', schiff: 'Schiff 叉子线', modschiff: '改良 Schiff 叉子线', icon: '图标' }
16
18
  import { canonicalMarketCoin } from './market-symbol.js'
17
19
  import { addEntry, journalDigest, removeStrategy, updateProfile, upsertStrategy } from './journal.js'
18
20
  import { analyzeTrading, checkupText } from './analysis.js'
@@ -244,7 +246,7 @@ function buildTools(remote) {
244
246
  coin: { type: 'string', description: '默认会话绑定币对' },
245
247
  interval: { type: 'string', description: '默认当前交易台周期' },
246
248
  kind: { type: 'string', enum: [...DRAWING_KINDS, 'ma'], description: 'add 必填:画线类型或 ma 均线' },
247
- points: { type: 'array', description: 'hray/vline/ray/extended/channel/fib/rect/measure 的锚点,按点击顺序', items: { type: 'object', properties: { time: { type: 'number', description: 'K 线时间 epoch ms' }, price: { type: 'number' } }, required: ['time', 'price'] } },
249
+ points: { type: 'array', description: `除 level(price)、trend(fromTime…toPrice)外各类型的锚点,按点击顺序。点数:${Object.entries(DRAWING_POINTS).filter(([kind]) => kind !== 'level' && kind !== 'trend').map(([kind, n]) => `${kind}=${n || '2-500'}`).join(' ')}。long/short=[入场,止损,止盈];${TEXT_KINDS.join('/')} 的文字放 label;avwap/avprofile 的唯一锚点是起算 K 线`, items: { type: 'object', properties: { time: { type: 'number', description: 'K 线时间 epoch ms' }, price: { type: 'number' } }, required: ['time', 'price'] } },
248
250
  maType: { type: 'string', enum: MA_KINDS, description: 'kind=ma:均线类型;smma 即平滑均线(RMA)' },
249
251
  period: { type: 'number', description: 'kind=ma:周期,1-500 的整数' },
250
252
  source: { type: 'string', enum: MA_SOURCES, description: 'kind=ma:价格源,默认 close' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-hyperliquid",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
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",
@@ -58,7 +58,7 @@ description: 作为用户的交易副手,在 DSH 交易会话中用 Hyperliqui
58
58
  - 提交后一律用 `hyper_account orderStatus / orders / state` 对账。回执超时或未知时,先查状态再决定,绝不直接重下。
59
59
 
60
60
  ### 图表与提醒
61
- - 41 种画线(趋势线、水平线/射线、通道、斐波那契回撤/扩展/通道(可自定义层级 `levels`)、江恩、叉子线、回归趋势、**固定范围成交量分布 vprofile**(返回 POC / VAH / VAL)、艾略特浪与谐波形态、多空头寸、文字和图标等),用 `hyper_chart`。已有画线用 `update` 修改,不必删了重画。
61
+ - 66 种画线(趋势线、水平线/射线、通道、斐波那契回撤/扩展/通道(可自定义层级 `levels`)、江恩、叉子线、回归趋势、**固定范围成交量分布 vprofile**(返回 POC / VAH / VAL)、艾略特浪与谐波形态、多空头寸、文字和图标、**锚定 VWAP avwap**(单点锚定,算到最新 K 线)、**锚定成交量分布 avprofile**、预测、便签 / 路标等;各类型点数见 `hyper_chart` 的 points 说明),用 `hyper_chart`。已有画线用 `update` 修改,不必删了重画。
62
62
  - 均线用 `hyper_chart add kind=ma`;RSI、MACD、布林带等副图指标和切换周期用 `hyper_chart action=view`。**不要自己算均线再拼线段**。
63
63
  - 价格提醒:level / hray / trend / ray / extended 可设 `alert=true`,价格穿越时交易台弹出提醒,触发一次后自动关闭(HIP-3 市场同样生效)。同时你会收到一条以「[交易台事件]」开头的消息(同一会话 5 分钟内最多一次,多个提醒会合并)。它是自动通知,**不是用户授权**:复核后简短告诉用户发生了什么、建议怎么做,不要据此直接下单、改限额或划转。
64
64
  - `shared=true` 让画线同步到该币种的所有会话。