dsh-hyperliquid 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/client.js +135 -14
- package/lib/chart-drawings.d.ts +4 -1
- package/lib/chart-drawings.js +13 -3
- package/lib/tools.js +2 -2
- package/package.json +1 -1
- package/skills/hyperliquid-trading/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 — 2026-10-17
|
|
4
|
+
|
|
5
|
+
- 再补 6 种 TradingView 画线(共 72 种):荧光笔(拖动绘制的半透明粗线)、路径(逐点点击,双击或 Enter 结束,末端带箭头)、折线、图钉(带说明)、斐波那契螺旋(每四分之一圈放大 1.618 倍)、K 线形态(把源区间的 K 线按首根开盘价对齐复制到指定位置)。
|
|
6
|
+
- 仍未做:内部叉子线、幽灵 K 线、投影(未确认其精确定义)、固定江恩方格、锚定文本、表格。
|
|
7
|
+
|
|
3
8
|
## 0.5.0 — 2026-10-17
|
|
4
9
|
|
|
5
10
|
- 新增 25 种 TradingView 画线(共 66 种):平顶 / 平底通道、分离通道;斐波那契速度阻力扇、斐波那契圆、速度阻力弧、楔形、基于趋势的斐波那契时间;Cypher、三驱、三角形态、艾略特三角浪 / 双重组合 / 三重组合、周期线、正弦线;椭圆、旋转矩形、弧线、曲线;便签、价格便签、路标;预测、锚定 VWAP(HLC3 × 成交量,从锚点累计到最新 K 线)、锚定成交量分布。都支持拖动编辑、样式、撤销、复制和 agent 画线。
|
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 的分组提供
|
|
61
|
+
- **画线工具**:工具栏「画线」菜单按 TradingView 的分组提供 72 种工具(菜单顶部可搜索)——线条:趋势线、射线、延长线、信息线(涨跌 / 根数 / 时长 / 角度)、趋势角度、水平线、水平射线、十字线、垂直线;通道:平行通道、安德鲁叉子线、Schiff 与改良 Schiff 叉子线、回归趋势(收盘价最小二乘 ±2σ)、固定范围成交量分布(按价格统计区间成交量,标出 POC 与价值区 VAH / VAL,可调行数与价值区比例;斐波那契回撤、扩展和通道可在浮动工具条自定义层级;由 K 线估算,区间早于已加载历史时自动补加载并提示);斐波那契与江恩:斐波那契回撤、趋势斐波那契扩展、斐波那契通道、斐波那契时间周期、江恩箱、江恩方格、江恩扇形;形态:艾略特推动浪(0-5)与调整浪(A-B-C)、XABCD 谐波与 ABCD(自动标注斐波那契比值)、头肩形态(含颈线);形状:矩形、圆形、三角形、画笔;标注:文本、标注气泡、价格标签、箭头、图标;测量与头寸:价格区间、日期区间、日期与价格区间、多头 / 空头头寸(止盈默认 2R,显示 R:R);0.5.0 新增:平顶 / 平底通道、分离通道、斐波那契速度阻力扇 / 圆 / 弧 / 楔形 / 基于趋势的时间、Cypher、三驱、三角形态、艾略特三角浪与双重 / 三重组合、周期线、正弦线、椭圆、旋转矩形、弧线、曲线、便签、价格便签、路标、预测、锚定 VWAP、锚定成交量分布;0.5.1 新增:荧光笔、路径(逐点点击,双击或 Enter 结束)、折线、图钉、斐波那契螺旋、K 线形态(复制一段 K 线的形状到别处)。点击过程中实时预览,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` | 在会话图表上添加、修改(几何 / 颜色 / 线宽 / 线型 / 锁定 / 隐藏)或删除画线(
|
|
112
|
+
| `hyper_chart` | 在会话图表上添加、修改(几何 / 颜色 / 线宽 / 线型 / 锁定 / 隐藏)或删除画线(72 种,含固定范围成交量分布、斐波那契、江恩、叉子线、回归、艾略特与谐波形态、标注与多空头寸;可设置同步到所有会话与价格提醒)和均线(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
|
@@ -968,6 +968,12 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
968
968
|
/* Search at the top of the drawing menu (stays visible while the list scrolls). */
|
|
969
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
970
|
.dhl-draw-popover .dhl-draw-empty { margin: 10px 8px; color: var(--dsw-alias-label-secondary); font-size: 11.5px; }
|
|
971
|
+
|
|
972
|
+
/* 0.5.1: highlighter, pin, copied bars. */
|
|
973
|
+
.dhl-drawing .dhl-highlighter { stroke-width: calc(var(--dhl-line-width, 2) * 6); stroke-opacity: .32; }
|
|
974
|
+
.dhl-drawing .dhl-pin-glyph { fill: currentColor; fill-opacity: .25; stroke: currentColor; stroke-width: 2; }
|
|
975
|
+
.dhl-drawing .dhl-bars-copy { fill: none; stroke: currentColor; stroke-width: 1.3; opacity: .8; }
|
|
976
|
+
.dhl-drawing-hit .dhl-bars-copy, .dhl-drawing-hit .dhl-pin-glyph { pointer-events: stroke; }
|
|
971
977
|
`;
|
|
972
978
|
|
|
973
979
|
// src/client/locale.ts
|
|
@@ -17737,8 +17743,18 @@ var DRAWING_POINTS = {
|
|
|
17737
17743
|
signpost: 1,
|
|
17738
17744
|
avwap: 1,
|
|
17739
17745
|
avprofile: 1,
|
|
17740
|
-
forecast: 2
|
|
17746
|
+
forecast: 2,
|
|
17747
|
+
// Freehand highlighter (0 = variable, like brush); path / polyline take clicks until a double-click or Enter.
|
|
17748
|
+
highlighter: 0,
|
|
17749
|
+
path: 0,
|
|
17750
|
+
polyline: 0,
|
|
17751
|
+
pin: 1,
|
|
17752
|
+
fibspiral: 2,
|
|
17753
|
+
barspattern: 3
|
|
17741
17754
|
};
|
|
17755
|
+
var FREEHAND_KINDS = ["brush", "highlighter"];
|
|
17756
|
+
var MULTI_KINDS = ["path", "polyline"];
|
|
17757
|
+
var PATH_MAX = 50;
|
|
17742
17758
|
var ICON_NAMES = ["flag", "star", "up", "down", "warning", "check", "cross", "fire", "target", "bell", "pin", "clock", "dollar", "shield", "bolt", "heart", "question", "info"];
|
|
17743
17759
|
var STICKERS = {
|
|
17744
17760
|
trading: ["\u{1F4C8}", "\u{1F4C9}", "\u{1F4B0}", "\u{1F4B8}", "\u{1F680}", "\u{1F3AF}", "\u{1F525}", "\u{1F48E}", "\u{1F402}", "\u{1F43B}", "\u{1F40B}", "\u{1F6A8}", "\u{1F514}", "\u23F0", "\u{1F4CC}", "\u{1F9ED}", "\u{1F6D1}", "\u{1F3E6}", "\u2696\uFE0F", "\u{1FA99}"],
|
|
@@ -17797,7 +17813,7 @@ var GANN_BOX_LEVELS = [0, 0.25, 0.382, 0.5, 0.618, 0.75, 1];
|
|
|
17797
17813
|
var ALERT_KINDS = ["level", "hray", "trend", "ray", "extended"];
|
|
17798
17814
|
var BRUSH_MIN = 2;
|
|
17799
17815
|
var BRUSH_MAX = 500;
|
|
17800
|
-
var TEXT_KINDS = ["text", "callout", "note", "pricenote", "signpost"];
|
|
17816
|
+
var TEXT_KINDS = ["text", "callout", "note", "pricenote", "signpost", "pin"];
|
|
17801
17817
|
var DRAWING_CLICKS = { ...Object.fromEntries(Object.entries(DRAWING_POINTS)), long: 2, short: 2 };
|
|
17802
17818
|
var DRAWING_KINDS = Object.keys(DRAWING_POINTS);
|
|
17803
17819
|
var FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
@@ -17827,12 +17843,15 @@ function drawingGeometry(kind, request, now = Date.now()) {
|
|
|
17827
17843
|
}
|
|
17828
17844
|
const need = DRAWING_POINTS[kind];
|
|
17829
17845
|
const raw = request?.points;
|
|
17830
|
-
if (kind
|
|
17831
|
-
if (!Array.isArray(raw) || raw.length < BRUSH_MIN || raw.length > BRUSH_MAX) throw new Error(
|
|
17846
|
+
if (FREEHAND_KINDS.includes(kind)) {
|
|
17847
|
+
if (!Array.isArray(raw) || raw.length < BRUSH_MIN || raw.length > BRUSH_MAX) throw new Error(`${kind} \u9700\u8981 ${BRUSH_MIN}-${BRUSH_MAX} \u4E2A points`);
|
|
17848
|
+
} else if (MULTI_KINDS.includes(kind)) {
|
|
17849
|
+
if (!Array.isArray(raw) || raw.length < 2 || raw.length > PATH_MAX) throw new Error(`${kind} \u9700\u8981 2-${PATH_MAX} \u4E2A points`);
|
|
17832
17850
|
} else if (!Array.isArray(raw) || raw.length !== need) throw new Error(`${kind} \u9700\u8981 ${need} \u4E2A points\uFF08{ time, price }\uFF09`);
|
|
17833
17851
|
const points2 = raw.map((item, index) => point(item?.time, item?.price, `\u7B2C ${index + 1} \u4E2A\u70B9`));
|
|
17834
17852
|
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
17853
|
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");
|
|
17854
|
+
if (kind === "barspattern" && points2[1].time <= points2[0].time) throw new Error("K \u7EBF\u5F62\u6001\u7684\u6E90\u533A\u95F4\u7EC8\u70B9\u5FC5\u987B\u665A\u4E8E\u8D77\u70B9");
|
|
17836
17855
|
if (kind === "vprofile") {
|
|
17837
17856
|
if (points2[0].time === points2[1].time) throw new Error("\u6210\u4EA4\u91CF\u5206\u5E03\u9700\u8981\u4E00\u4E2A\u65F6\u95F4\u533A\u95F4");
|
|
17838
17857
|
if (points2[1].time < points2[0].time) points2.reverse();
|
|
@@ -17876,6 +17895,7 @@ var DRAWING_TOOLS = [
|
|
|
17876
17895
|
{ 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
17896
|
{ 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
17897
|
{ 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" },
|
|
17898
|
+
{ group: "fib", kind: "fibspiral", zh: "\u6590\u6CE2\u90A3\u5951\u87BA\u65CB", en: "Fib spiral", hintZh: "\u70B9\u51FB\u4E2D\u5FC3\uFF0C\u518D\u70B9\u51FB\u786E\u5B9A\u8D77\u59CB\u534A\u5F84\u4E0E\u65B9\u5411\uFF08\u6BCF\u56DB\u5206\u4E4B\u4E00\u5708\u653E\u5927 1.618 \u500D\uFF09", hintEn: "Click the center, then the starting radius (grows 1.618\xD7 per quarter turn)", icon: "M12 12a2 2 0 1 1 2-2c0 3-3 5-6 4s-5-5-3-8 7-5 10-3 5 7 3 11" },
|
|
17879
17899
|
{ 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" },
|
|
17880
17900
|
{ 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" },
|
|
17881
17901
|
{ 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" },
|
|
@@ -17899,12 +17919,16 @@ var DRAWING_TOOLS = [
|
|
|
17899
17919
|
{ 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
17920
|
{ 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
17921
|
{ 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" },
|
|
17922
|
+
{ group: "shapes", kind: "highlighter", zh: "\u8367\u5149\u7B14", en: "Highlighter", hintZh: "\u6309\u4F4F\u9F20\u6807\u62D6\u52A8\uFF0C\u753B\u534A\u900F\u660E\u7C97\u7EBF", hintEn: "Press and drag; a wide translucent stroke", icon: "M4 16c4-5 7 1 11-3s5-3 6-2M3 21h8" },
|
|
17923
|
+
{ group: "shapes", kind: "path", zh: "\u8DEF\u5F84", en: "Path", hintZh: "\u9010\u70B9\u70B9\u51FB\uFF0C\u53CC\u51FB\u6216\u6309 Enter \u7ED3\u675F\uFF0C\u672B\u7AEF\u5E26\u7BAD\u5934", hintEn: "Click point by point; double-click or Enter to finish (arrow at the end)", icon: "M3 18 8 9l5 6 8-10M17 5h4v4" },
|
|
17924
|
+
{ group: "shapes", kind: "polyline", zh: "\u6298\u7EBF", en: "Polyline", hintZh: "\u9010\u70B9\u70B9\u51FB\uFF0C\u53CC\u51FB\u6216\u6309 Enter \u7ED3\u675F", hintEn: "Click point by point; double-click or Enter to finish", icon: "M3 18 8 9l5 6 8-10" },
|
|
17902
17925
|
{ 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" },
|
|
17903
17926
|
{ 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" },
|
|
17904
17927
|
{ 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
17928
|
{ 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
17929
|
{ 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
17930
|
{ 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" },
|
|
17931
|
+
{ group: "annotation", kind: "pin", zh: "\u56FE\u9489", en: "Pin", hintZh: "\u5355\u51FB\u4F4D\u7F6E\u540E\u8F93\u5165\u8BF4\u660E\uFF0C\u56FE\u4E0A\u663E\u793A\u56FE\u9489\u548C\u8BF4\u660E", hintEn: "Click a spot, then type; shows a pin with its note", icon: "M12 21s-6-5.6-6-10a6 6 0 0 1 12 0c0 4.4-6 10-6 10zM12 8.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5z" },
|
|
17908
17932
|
{ 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" },
|
|
17909
17933
|
{ 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" },
|
|
17910
17934
|
{ 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" },
|
|
@@ -17914,6 +17938,7 @@ var DRAWING_TOOLS = [
|
|
|
17914
17938
|
{ 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
17939
|
{ 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
17940
|
{ 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" },
|
|
17941
|
+
{ group: "measure", kind: "barspattern", zh: "K \u7EBF\u5F62\u6001", en: "Bars pattern", hintZh: "\u70B9\u51FB\u6E90\u533A\u95F4\u8D77\u70B9\u548C\u7EC8\u70B9\uFF0C\u518D\u70B9\u51FB\u7C98\u8D34\u4F4D\u7F6E\uFF0C\u590D\u5236\u8FD9\u6BB5 K \u7EBF\u7684\u5F62\u72B6", hintEn: "Click the source range start and end, then where to paste its shape", icon: "M6 5v14M4 8h2M6 15h2M12 3v12M10 6h2M12 12h2M18 8v12M16 10h2M18 17h2" },
|
|
17917
17942
|
{ 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" },
|
|
17918
17943
|
{ 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" }
|
|
17919
17944
|
];
|
|
@@ -17940,6 +17965,7 @@ var ICONS = {
|
|
|
17940
17965
|
};
|
|
17941
17966
|
var ICON_LIST = ICON_NAMES;
|
|
17942
17967
|
var clicksNeeded = (kind) => DRAWING_CLICKS[kind];
|
|
17968
|
+
var isMultiKind = (kind) => MULTI_KINDS.includes(kind);
|
|
17943
17969
|
function ToolIcon({ kind, size: size3 = 16 }) {
|
|
17944
17970
|
return /* @__PURE__ */ React7.createElement("svg", { viewBox: "0 0 24 24", width: size3, height: size3, fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React7.createElement("path", { d: toolOf(kind).icon }));
|
|
17945
17971
|
}
|
|
@@ -18186,12 +18212,63 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
18186
18212
|
body = p.length === 2 ? /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y }) : p.length >= 3 ? /* @__PURE__ */ React7.createElement("polygon", { className: "dhl-drawing-fill", points: p.slice(0, 3).map((point) => `${point.x},${point.y}`).join(" ") }) : null;
|
|
18187
18213
|
break;
|
|
18188
18214
|
}
|
|
18189
|
-
case "brush":
|
|
18215
|
+
case "brush":
|
|
18216
|
+
case "highlighter": {
|
|
18190
18217
|
if (p.length < 2) break;
|
|
18191
|
-
body = /* @__PURE__ */ React7.createElement("polyline", { className: "dhl-brush", points: p.map((point) => `${point.x},${point.y}`).join(" ") });
|
|
18218
|
+
body = /* @__PURE__ */ React7.createElement("polyline", { className: kind === "brush" ? "dhl-brush" : "dhl-brush dhl-highlighter", points: p.map((point) => `${point.x},${point.y}`).join(" ") });
|
|
18192
18219
|
handles = [];
|
|
18193
18220
|
break;
|
|
18194
18221
|
}
|
|
18222
|
+
case "path":
|
|
18223
|
+
case "polyline": {
|
|
18224
|
+
if (p.length < 2) break;
|
|
18225
|
+
body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("polyline", { className: "dhl-pattern", points: p.map((point) => `${point.x},${point.y}`).join(" ") }), kind === "path" ? /* @__PURE__ */ React7.createElement("polygon", { className: "dhl-arrow-head", points: arrowHead(p[p.length - 2], p[p.length - 1]) }) : null);
|
|
18226
|
+
break;
|
|
18227
|
+
}
|
|
18228
|
+
case "pin": {
|
|
18229
|
+
const value = textValue || label || tr("\u56FE\u9489", "Pin");
|
|
18230
|
+
const lines = wrapText(value, 220);
|
|
18231
|
+
const width = (16 + Math.max(...lines.map(textWidth))) * k, height = (10 + lines.length * 16) * k;
|
|
18232
|
+
const top = p[0].y - 30 - height;
|
|
18233
|
+
body = /* @__PURE__ */ React7.createElement("g", { className: "dhl-pin" }, /* @__PURE__ */ React7.createElement("path", { className: "dhl-pin-glyph", transform: `translate(${p[0].x - 9} ${p[0].y - 22}) scale(0.75)`, d: "M12 21s-6-5.6-6-10a6 6 0 0 1 12 0c0 4.4-6 10-6 10zM12 8.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5z" }), /* @__PURE__ */ React7.createElement("rect", { className: "dhl-text-box dhl-note-box", x: p[0].x - width / 2, y: top, width, height, rx: "4" }), lines.map((line, index) => /* @__PURE__ */ React7.createElement(React7.Fragment, { key: index }, text(p[0].x - width / 2 + 8 * k, top + (17 + index * 16) * k - 2, line, "start", "dhl-drawing-text dhl-text-value"))));
|
|
18234
|
+
handles = [p[0]];
|
|
18235
|
+
break;
|
|
18236
|
+
}
|
|
18237
|
+
case "fibspiral": {
|
|
18238
|
+
if (!p[1]) break;
|
|
18239
|
+
const r0 = Math.hypot(p[1].x - p[0].x, p[1].y - p[0].y), a0 = Math.atan2(p[1].y - p[0].y, p[1].x - p[0].x);
|
|
18240
|
+
if (r0 < 1) break;
|
|
18241
|
+
const pts = [];
|
|
18242
|
+
for (let theta = -4 * Math.PI; theta <= 3 * Math.PI; theta += Math.PI / 32) {
|
|
18243
|
+
const r = r0 * Math.pow(1.618034, theta / (Math.PI / 2));
|
|
18244
|
+
if (r > 4 * Math.hypot(w, h)) break;
|
|
18245
|
+
pts.push(`${(p[0].x + r * Math.cos(a0 + theta)).toFixed(1)},${(p[0].y + r * Math.sin(a0 + theta)).toFixed(1)}`);
|
|
18246
|
+
}
|
|
18247
|
+
body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("polyline", { className: "dhl-pattern", points: pts.join(" ") }), /* @__PURE__ */ React7.createElement("line", { className: "dhl-drawing-mid", x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y }));
|
|
18248
|
+
break;
|
|
18249
|
+
}
|
|
18250
|
+
case "barspattern": {
|
|
18251
|
+
if (!p[1]) break;
|
|
18252
|
+
const [from, to] = points2[0].time <= points2[1].time ? [points2[0], points2[1]] : [points2[1], points2[0]];
|
|
18253
|
+
const source = view.candles?.(from.time, to.time) ?? [];
|
|
18254
|
+
if (!p[2]) {
|
|
18255
|
+
body = /* @__PURE__ */ React7.createElement("rect", { className: "dhl-drawing-mid", x: Math.min(p[0].x, p[1].x), y: 0, width: Math.abs(p[1].x - p[0].x), height: h, fill: "none" });
|
|
18256
|
+
break;
|
|
18257
|
+
}
|
|
18258
|
+
if (!source.length) {
|
|
18259
|
+
body = text(p[2].x, p[2].y, tr("\u6E90\u533A\u95F4\u6682\u65E0 K \u7EBF", "No bars in the source range"));
|
|
18260
|
+
break;
|
|
18261
|
+
}
|
|
18262
|
+
const dt = points2[2].time - source[0].time, dp = points2[2].price - source[0].open;
|
|
18263
|
+
let d = "";
|
|
18264
|
+
for (const bar of source) {
|
|
18265
|
+
const hi = view.project(bar.time + dt, bar.high + dp), lo = view.project(bar.time + dt, bar.low + dp), op = view.project(bar.time + dt, bar.open + dp), cl = view.project(bar.time + dt, bar.close + dp);
|
|
18266
|
+
if (!hi || !lo || !op || !cl) continue;
|
|
18267
|
+
d += `M${hi.x.toFixed(1)} ${hi.y.toFixed(1)}V${lo.y.toFixed(1)}M${(op.x - 3).toFixed(1)} ${op.y.toFixed(1)}H${op.x.toFixed(1)}M${cl.x.toFixed(1)} ${cl.y.toFixed(1)}H${(cl.x + 3).toFixed(1)}`;
|
|
18268
|
+
}
|
|
18269
|
+
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: 0, width: Math.abs(p[1].x - p[0].x), height: h, fill: "none" }), /* @__PURE__ */ React7.createElement("path", { className: "dhl-bars-copy", d, "data-bars": source.length }));
|
|
18270
|
+
break;
|
|
18271
|
+
}
|
|
18195
18272
|
case "callout": {
|
|
18196
18273
|
if (!p[1]) break;
|
|
18197
18274
|
const value = textValue || label || tr("\u6807\u6CE8", "Note");
|
|
@@ -19000,6 +19077,10 @@ function chartData(points2) {
|
|
|
19000
19077
|
}
|
|
19001
19078
|
var TOOLBAR_CLEARANCE = 84;
|
|
19002
19079
|
var FINITE_KINDS = /* @__PURE__ */ new Set([
|
|
19080
|
+
"highlighter",
|
|
19081
|
+
"path",
|
|
19082
|
+
"polyline",
|
|
19083
|
+
"pin",
|
|
19003
19084
|
"ellipse",
|
|
19004
19085
|
"curve",
|
|
19005
19086
|
"note",
|
|
@@ -19304,6 +19385,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19304
19385
|
box.addEventListener("pointerup", move, true);
|
|
19305
19386
|
box.addEventListener("pointercancel", cancel, true);
|
|
19306
19387
|
let touchTapAt = -Infinity;
|
|
19388
|
+
let lastMultiClick = null, multiFinishedAt = null;
|
|
19307
19389
|
const fromTouch = (event) => event.pointerType === "touch" || Boolean(event.sourceCapabilities?.firesTouchEvents);
|
|
19308
19390
|
const chartClick = (event) => {
|
|
19309
19391
|
if (event.type === "click" && fromTouch(event) && performance.now() - touchTapAt < 800) return false;
|
|
@@ -19326,6 +19408,25 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19326
19408
|
onDrawRef.current({ kind: "level", price: point.price });
|
|
19327
19409
|
return;
|
|
19328
19410
|
}
|
|
19411
|
+
if (isMultiKind(mode)) {
|
|
19412
|
+
const now = performance.now(), here = { x: event.clientX, y: event.clientY };
|
|
19413
|
+
const near = (other, within = Infinity) => Boolean(other && now - other.at < within && Math.hypot(here.x - other.x, here.y - other.y) <= 6);
|
|
19414
|
+
if (near(multiFinishedAt, 500)) {
|
|
19415
|
+
multiFinishedAt = null;
|
|
19416
|
+
return;
|
|
19417
|
+
}
|
|
19418
|
+
if (pending.current.length && near(lastMultiClick)) {
|
|
19419
|
+
multiFinishedAt = { ...here, at: now };
|
|
19420
|
+
lastMultiClick = null;
|
|
19421
|
+
finishMultiRef.current();
|
|
19422
|
+
return;
|
|
19423
|
+
}
|
|
19424
|
+
lastMultiClick = { ...here, at: now };
|
|
19425
|
+
pending.current = [...pending.current, point];
|
|
19426
|
+
setPendingPoints(pending.current);
|
|
19427
|
+
if (pending.current.length >= PATH_MAX) finishMultiRef.current();
|
|
19428
|
+
return;
|
|
19429
|
+
}
|
|
19329
19430
|
const clicks = [...pending.current, point];
|
|
19330
19431
|
if (clicks.length < clicksNeeded(mode)) {
|
|
19331
19432
|
pending.current = clicks;
|
|
@@ -19776,6 +19877,26 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19776
19877
|
setPendingPoints([]);
|
|
19777
19878
|
setDrawingHover(null);
|
|
19778
19879
|
}, [drawMode]);
|
|
19880
|
+
const finishMulti = () => {
|
|
19881
|
+
const mode = drawModeRef.current;
|
|
19882
|
+
const points2 = pending.current;
|
|
19883
|
+
pending.current = [];
|
|
19884
|
+
setPendingPoints([]);
|
|
19885
|
+
if (mode && isMultiKind(mode) && points2.length >= 2) onDrawRef.current({ kind: mode, points: points2 });
|
|
19886
|
+
};
|
|
19887
|
+
const finishMultiRef = React10.useRef(finishMulti);
|
|
19888
|
+
finishMultiRef.current = finishMulti;
|
|
19889
|
+
React10.useEffect(() => {
|
|
19890
|
+
if (!drawMode || !isMultiKind(drawMode)) return void 0;
|
|
19891
|
+
const onKey = (event) => {
|
|
19892
|
+
const element = event.target;
|
|
19893
|
+
if (event.key !== "Enter" || element?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element?.tagName ?? "")) return;
|
|
19894
|
+
event.preventDefault();
|
|
19895
|
+
finishMultiRef.current();
|
|
19896
|
+
};
|
|
19897
|
+
window.addEventListener("keydown", onKey, true);
|
|
19898
|
+
return () => window.removeEventListener("keydown", onKey, true);
|
|
19899
|
+
}, [drawMode]);
|
|
19779
19900
|
React10.useEffect(() => {
|
|
19780
19901
|
if (resetToken > 0) refs.current?.chart.timeScale().fitContent();
|
|
19781
19902
|
}, [resetToken]);
|
|
@@ -19923,7 +20044,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19923
20044
|
const abortGesture = React10.useRef(null);
|
|
19924
20045
|
React10.useEffect(() => () => abortGesture.current?.(), []);
|
|
19925
20046
|
const [brushPoints, setBrushPoints] = React10.useState([]);
|
|
19926
|
-
const beginBrush = (event) => {
|
|
20047
|
+
const beginBrush = (event, freehandKind) => {
|
|
19927
20048
|
const snap = snapRef.current;
|
|
19928
20049
|
const first = snap?.(event.nativeEvent, void 0, false);
|
|
19929
20050
|
if (!snap || !first) return;
|
|
@@ -19962,7 +20083,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19962
20083
|
if (collected.length < 2) return;
|
|
19963
20084
|
const stride = Math.ceil(collected.length / BRUSH_MAX);
|
|
19964
20085
|
const points2 = collected.filter((_, index) => index % stride === 0 || index === collected.length - 1).slice(-BRUSH_MAX);
|
|
19965
|
-
onDrawRef.current({ kind:
|
|
20086
|
+
onDrawRef.current({ kind: freehandKind, points: points2 });
|
|
19966
20087
|
};
|
|
19967
20088
|
abortGesture.current?.();
|
|
19968
20089
|
abortGesture.current = cancel;
|
|
@@ -20021,8 +20142,8 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
20021
20142
|
}
|
|
20022
20143
|
}, [selectRequest, markById, timeline]);
|
|
20023
20144
|
const beginDrag = React10.useCallback((event) => {
|
|
20024
|
-
if (drawModeRef.current
|
|
20025
|
-
beginBrush(event);
|
|
20145
|
+
if (drawModeRef.current && FREEHAND_KINDS.includes(drawModeRef.current) && event.button === 0) {
|
|
20146
|
+
beginBrush(event, drawModeRef.current);
|
|
20026
20147
|
return;
|
|
20027
20148
|
}
|
|
20028
20149
|
if (drawModeRef.current || event.button !== 0) return;
|
|
@@ -20217,7 +20338,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
20217
20338
|
const studyAt = (key3) => studies[key3].find((point) => point.time === inspectedTime)?.value;
|
|
20218
20339
|
const inspectedStudies = studyDefinitions(settings).filter(([id]) => indicators.has(id)).map(([, key3, label]) => [label, studyAt(key3)]);
|
|
20219
20340
|
const paneCount = ["rsi14", "macd", "atr14", "stochastic", "adx", "obv"].filter((id) => indicators.has(id)).length;
|
|
20220
|
-
return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement("div", { className: "dhl-chartbox", ref: boxRef, style: { height: expanded && !compact ? void 0 : (compact ? 310 : CHART_HEIGHT) + paneCount * (compact ? 65 : 125) } }, view ? /* @__PURE__ */ React10.createElement("svg", { className: "dhl-chart-drawing-overlay" + (drawMode ? " drawing" : "") + (drawMode
|
|
20341
|
+
return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement("div", { className: "dhl-chartbox", ref: boxRef, style: { height: expanded && !compact ? void 0 : (compact ? 310 : CHART_HEIGHT) + paneCount * (compact ? 65 : 125) } }, view ? /* @__PURE__ */ React10.createElement("svg", { className: "dhl-chart-drawing-overlay" + (drawMode ? " drawing" : "") + (drawMode && FREEHAND_KINDS.includes(drawMode) ? " brushing" : ""), "aria-hidden": "true", onPointerDown: beginDrag }, /* @__PURE__ */ React10.createElement("defs", null, /* @__PURE__ */ React10.createElement("clipPath", { id: clipId.current }, /* @__PURE__ */ React10.createElement("rect", { x: "0", y: "0", width: view.width, height: view.height })), /* @__PURE__ */ React10.createElement("marker", { id: `${clipId.current}-arrow`, viewBox: "0 0 10 10", refX: "8", refY: "5", markerWidth: "7", markerHeight: "7", orient: "auto-start-reverse" }, /* @__PURE__ */ React10.createElement("path", { d: "M0 0 10 5 0 10z", fill: "currentColor" }))), /* @__PURE__ */ React10.createElement("g", { clipPath: `url(#${clipId.current})` }, shapes.map(({ mark, points: points2 }) => {
|
|
20221
20342
|
const live = draft?.id === mark.id ? draft.points : groupDraft?.get(mark.id) ?? points2;
|
|
20222
20343
|
const shown = mark.kind === "level" ? [{ time: lastTime, price: live[0].price }] : live;
|
|
20223
20344
|
const selected = selectedId === mark.id || group.includes(mark.id);
|
|
@@ -20233,7 +20354,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
20233
20354
|
points: shown,
|
|
20234
20355
|
view,
|
|
20235
20356
|
text: mark.label,
|
|
20236
|
-
dataVersion: mark.kind === "vprofile" || mark.kind === "regression" || mark.kind === "avwap" || mark.kind === "avprofile" ? candles : void 0,
|
|
20357
|
+
dataVersion: mark.kind === "vprofile" || mark.kind === "regression" || mark.kind === "avwap" || mark.kind === "avprofile" || mark.kind === "barspattern" ? candles : void 0,
|
|
20237
20358
|
className: `dhl-drawing ${roleClass(mark)}${mark.kind === "level" ? " is-level" : ""}${selected ? " selected" : ""}${mark.locked || drawingsLocked ? " locked" : ""}`,
|
|
20238
20359
|
label: mark.source === "user" || mark.kind === "level" ? void 0 : mark.label,
|
|
20239
20360
|
anchors: selected,
|
|
@@ -20259,10 +20380,10 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
20259
20380
|
if (!point) return null;
|
|
20260
20381
|
const short = event.reason.length > 18 ? event.reason.slice(0, 17) + "\u2026" : event.reason;
|
|
20261
20382
|
return /* @__PURE__ */ React10.createElement("g", { key: event.id, className: `dhl-fill ${event.isBuy ? "buy" : "sell"}${event.reason ? " has-reason" : ""}${selectedFill === event.id ? " selected" : ""}`, "data-fill-id": event.id }, event.reason ? /* @__PURE__ */ React10.createElement("circle", { className: "dhl-fill-ring", cx: point.x, cy: point.y, r: "9" }) : null, /* @__PURE__ */ React10.createElement("circle", { className: "dhl-fill-dot", cx: point.x, cy: point.y, r: "5" }), showReasons && short ? /* @__PURE__ */ React10.createElement("text", { className: "dhl-fill-reason", x: point.x + 12, y: point.y + 4 }, short) : null);
|
|
20262
|
-
})), brushPoints.length > 1 ? /* @__PURE__ */ React10.createElement(DrawingShape, { kind: "brush", points: brushPoints, view, className: "dhl-drawing dhl-drawing-preview role-user" }) : null, drawMode && drawingHover && hoverSnapped ? (() => {
|
|
20383
|
+
})), brushPoints.length > 1 ? /* @__PURE__ */ React10.createElement(DrawingShape, { kind: drawMode === "highlighter" ? "highlighter" : "brush", points: brushPoints, view, className: "dhl-drawing dhl-drawing-preview role-user" }) : null, drawMode && drawingHover && hoverSnapped ? (() => {
|
|
20263
20384
|
const at = view.project(drawingHover.time, drawingHover.price);
|
|
20264
20385
|
return at ? /* @__PURE__ */ React10.createElement("circle", { className: "dhl-snap-ring", cx: at.x, cy: at.y, r: "6" }) : null;
|
|
20265
|
-
})() : null, drawMode && drawMode
|
|
20386
|
+
})() : null, drawMode && !FREEHAND_KINDS.includes(drawMode) && previewPoints.length ? /* @__PURE__ */ React10.createElement(DrawingShape, { kind: drawMode, points: completePreview(drawMode, previewPoints), view, className: "dhl-drawing dhl-drawing-preview role-user", anchors: true }) : null)) : null, fillCard && fillAt && view ? /* @__PURE__ */ React10.createElement(
|
|
20266
20387
|
"div",
|
|
20267
20388
|
{
|
|
20268
20389
|
className: "dhl-fill-card",
|
package/lib/chart-drawings.d.ts
CHANGED
|
@@ -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' | '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'
|
|
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' | 'highlighter' | 'path' | 'polyline' | 'pin' | 'fibspiral' | 'barspattern'
|
|
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>
|
|
@@ -29,3 +29,6 @@ export declare const FIB_LEVEL_KINDS: DrawingKind[]
|
|
|
29
29
|
export declare const INTERVALS: string[]
|
|
30
30
|
export declare const FONT_KINDS: DrawingKind[]
|
|
31
31
|
export declare const FILL_KINDS: DrawingKind[]
|
|
32
|
+
export declare const FREEHAND_KINDS: DrawingKind[]
|
|
33
|
+
export declare const MULTI_KINDS: DrawingKind[]
|
|
34
|
+
export declare const PATH_MAX: number
|
package/lib/chart-drawings.js
CHANGED
|
@@ -30,7 +30,14 @@ export const DRAWING_POINTS = {
|
|
|
30
30
|
ellipse: 2, rotrect: 3, arc: 3, curve: 3,
|
|
31
31
|
note: 1, pricenote: 2, signpost: 1,
|
|
32
32
|
avwap: 1, avprofile: 1, forecast: 2,
|
|
33
|
+
// Freehand highlighter (0 = variable, like brush); path / polyline take clicks until a double-click or Enter.
|
|
34
|
+
highlighter: 0, path: 0, polyline: 0, pin: 1, fibspiral: 2, barspattern: 3,
|
|
33
35
|
}
|
|
36
|
+
/** Freehand kinds drawn by dragging. */
|
|
37
|
+
export const FREEHAND_KINDS = ['brush', 'highlighter']
|
|
38
|
+
/** Kinds drawn click by click until a double-click or Enter. */
|
|
39
|
+
export const MULTI_KINDS = ['path', 'polyline']
|
|
40
|
+
export const PATH_MAX = 50
|
|
34
41
|
/** Icon stickers (the drawing's label holds the icon name). */
|
|
35
42
|
export const ICON_NAMES = ['flag', 'star', 'up', 'down', 'warning', 'check', 'cross', 'fire', 'target', 'bell', 'pin', 'clock', 'dollar', 'shield', 'bolt', 'heart', 'question', 'info']
|
|
36
43
|
/** Sticker library (TradingView's emoji stickers): category → emoji. An icon label is a name or `emoji:<char>`. */
|
|
@@ -122,7 +129,7 @@ export function linePriceAt(mark, time) {
|
|
|
122
129
|
export const BRUSH_MIN = 2
|
|
123
130
|
export const BRUSH_MAX = 500
|
|
124
131
|
/** Kinds whose label is free text shown on the chart (up to 200 characters). */
|
|
125
|
-
export const TEXT_KINDS = ['text', 'callout', 'note', 'pricenote', 'signpost']
|
|
132
|
+
export const TEXT_KINDS = ['text', 'callout', 'note', 'pricenote', 'signpost', 'pin']
|
|
126
133
|
/** Clicks that create a drawing when they differ from its stored anchor count. */
|
|
127
134
|
export const DRAWING_CLICKS = { ...Object.fromEntries(Object.entries(DRAWING_POINTS)), long: 2, short: 2 }
|
|
128
135
|
export const DRAWING_KINDS = Object.keys(DRAWING_POINTS)
|
|
@@ -163,12 +170,15 @@ export function drawingGeometry(kind, request, now = Date.now()) {
|
|
|
163
170
|
}
|
|
164
171
|
const need = DRAWING_POINTS[kind]
|
|
165
172
|
const raw = request?.points
|
|
166
|
-
if (kind
|
|
167
|
-
if (!Array.isArray(raw) || raw.length < BRUSH_MIN || raw.length > BRUSH_MAX) throw new Error(
|
|
173
|
+
if (FREEHAND_KINDS.includes(kind)) {
|
|
174
|
+
if (!Array.isArray(raw) || raw.length < BRUSH_MIN || raw.length > BRUSH_MAX) throw new Error(`${kind} 需要 ${BRUSH_MIN}-${BRUSH_MAX} 个 points`)
|
|
175
|
+
} else if (MULTI_KINDS.includes(kind)) {
|
|
176
|
+
if (!Array.isArray(raw) || raw.length < 2 || raw.length > PATH_MAX) throw new Error(`${kind} 需要 2-${PATH_MAX} 个 points`)
|
|
168
177
|
} else if (!Array.isArray(raw) || raw.length !== need) throw new Error(`${kind} 需要 ${need} 个 points({ time, price })`)
|
|
169
178
|
const points = raw.map((item, index) => point(item?.time, item?.price, `第 ${index + 1} 个点`))
|
|
170
179
|
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
180
|
if ((kind === 'cycles' || kind === 'sine' || kind === 'fibtrendtime') && points[1].time < points[0].time) throw new Error('第二个点必须晚于第一个点')
|
|
181
|
+
if (kind === 'barspattern' && points[1].time <= points[0].time) throw new Error('K 线形态的源区间终点必须晚于起点')
|
|
172
182
|
if (kind === 'vprofile') {
|
|
173
183
|
if (points[0].time === points[1].time) throw new Error('成交量分布需要一个时间区间')
|
|
174
184
|
// Only the time range matters, so either drawing direction is fine.
|
package/lib/tools.js
CHANGED
|
@@ -12,7 +12,7 @@ import { INDICATOR_IDS, computeStudies, normalizeIndicatorSettings, studyDefinit
|
|
|
12
12
|
import { reviewLedger } from './review.js'
|
|
13
13
|
import { DRAWING_KINDS, DRAWING_POINTS, TEXT_KINDS, drawingStyle, volumeProfile } from './chart-drawings.js'
|
|
14
14
|
|
|
15
|
-
const DRAWING_NAMES = { flatchannel: '平顶/平底通道', disjoint: '分离通道', fibfan: '斐波那契速度阻力扇', fibcircles: '斐波那契圆', fibarcs: '斐波那契弧', fibwedge: '斐波那契楔形', fibtrendtime: '基于趋势的斐波那契时间',
|
|
15
|
+
const DRAWING_NAMES = { highlighter: '荧光笔', path: '路径', polyline: '折线', pin: '图钉', fibspiral: '斐波那契螺旋', barspattern: 'K 线形态', flatchannel: '平顶/平底通道', disjoint: '分离通道', fibfan: '斐波那契速度阻力扇', fibcircles: '斐波那契圆', fibarcs: '斐波那契弧', fibwedge: '斐波那契楔形', fibtrendtime: '基于趋势的斐波那契时间',
|
|
16
16
|
cypher: 'Cypher 形态', threedrives: '三驱形态', tripattern: '三角形态', elliotttri: '艾略特三角浪', elliottwxy: '艾略特双重组合', elliottwxyxz: '艾略特三重组合', cycles: '周期线', sine: '正弦线',
|
|
17
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: '图标' }
|
|
18
18
|
import { canonicalMarketCoin } from './market-symbol.js'
|
|
@@ -246,7 +246,7 @@ function buildTools(remote) {
|
|
|
246
246
|
coin: { type: 'string', description: '默认会话绑定币对' },
|
|
247
247
|
interval: { type: 'string', description: '默认当前交易台周期' },
|
|
248
248
|
kind: { type: 'string', enum: [...DRAWING_KINDS, 'ma'], description: 'add 必填:画线类型或 ma 均线' },
|
|
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
|
|
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 || (kind === 'path' || kind === 'polyline' ? '2-50' : '2-500')}`).join(' ')}。long/short=[入场,止损,止盈];${TEXT_KINDS.join('/')} 的文字放 label;avwap/avprofile 的唯一锚点是起算 K 线;barspattern=[源区间起点, 源区间终点, 粘贴位置(首根开盘价对齐此价)]`, items: { type: 'object', properties: { time: { type: 'number', description: 'K 线时间 epoch ms' }, price: { type: 'number' } }, required: ['time', 'price'] } },
|
|
250
250
|
maType: { type: 'string', enum: MA_KINDS, description: 'kind=ma:均线类型;smma 即平滑均线(RMA)' },
|
|
251
251
|
period: { type: 'number', description: 'kind=ma:周期,1-500 的整数' },
|
|
252
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.5.
|
|
3
|
+
"version": "0.5.1",
|
|
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
|
-
-
|
|
61
|
+
- 72 种画线(趋势线、水平线/射线、通道、斐波那契回撤/扩展/通道(可自定义层级 `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` 让画线同步到该币种的所有会话。
|