dsh-hyperliquid 0.4.4 → 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 +9 -0
- package/README.md +2 -2
- package/client.js +327 -14
- package/lib/chart-drawings.d.ts +1 -1
- package/lib/chart-drawings.js +12 -4
- package/lib/tools.js +5 -3
- package/package.json +1 -1
- package/skills/hyperliquid-trading/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
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
|
+
|
|
3
12
|
## 0.4.4 — 2026-10-17
|
|
4
13
|
|
|
5
14
|
- 修复:触摸屏上无法画线。lightweight-charts 处理轻点时会在 touchend 上 preventDefault,浏览器不再补发 click,而落锚点依赖原生 click;现在未拖动的触摸 pointerup 直接按点击处理(同一轻点之后若仍有 click 会被忽略)。
|
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 的分组提供 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` | 在会话图表上添加、修改(几何 / 颜色 / 线宽 / 线型 / 锁定 / 隐藏)或删除画线(
|
|
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
|
@@ -952,6 +952,22 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
952
952
|
/* Touch: the chart owns every gesture inside it (anchor and drawing drags, pans), as TradingView does;
|
|
953
953
|
otherwise the browser claims a finger drag for scrolling and cancels the pointer mid-drag. */
|
|
954
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; }
|
|
955
971
|
`;
|
|
956
972
|
|
|
957
973
|
// src/client/locale.ts
|
|
@@ -17695,7 +17711,33 @@ var DRAWING_POINTS = {
|
|
|
17695
17711
|
gannsquare: 2,
|
|
17696
17712
|
schiff: 3,
|
|
17697
17713
|
modschiff: 3,
|
|
17698
|
-
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
|
|
17699
17741
|
};
|
|
17700
17742
|
var ICON_NAMES = ["flag", "star", "up", "down", "warning", "check", "cross", "fire", "target", "bell", "pin", "clock", "dollar", "shield", "bolt", "heart", "question", "info"];
|
|
17701
17743
|
var STICKERS = {
|
|
@@ -17708,8 +17750,8 @@ function isSingleEmoji(value) {
|
|
|
17708
17750
|
if (typeof value !== "string" || !value || value.length > 32 || !/\p{Extended_Pictographic}|\p{Regional_Indicator}/u.test(value)) return false;
|
|
17709
17751
|
return [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(value)].length === 1;
|
|
17710
17752
|
}
|
|
17711
|
-
var FONT_KINDS = ["text", "callout"];
|
|
17712
|
-
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"];
|
|
17713
17755
|
var FIB_LEVEL_KINDS = ["fib", "fibext", "fibchannel"];
|
|
17714
17756
|
function volumeProfile(bars, { rows = 48, valueArea = 0.7 } = {}) {
|
|
17715
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);
|
|
@@ -17755,7 +17797,7 @@ var GANN_BOX_LEVELS = [0, 0.25, 0.382, 0.5, 0.618, 0.75, 1];
|
|
|
17755
17797
|
var ALERT_KINDS = ["level", "hray", "trend", "ray", "extended"];
|
|
17756
17798
|
var BRUSH_MIN = 2;
|
|
17757
17799
|
var BRUSH_MAX = 500;
|
|
17758
|
-
var TEXT_KINDS = ["text", "callout"];
|
|
17800
|
+
var TEXT_KINDS = ["text", "callout", "note", "pricenote", "signpost"];
|
|
17759
17801
|
var DRAWING_CLICKS = { ...Object.fromEntries(Object.entries(DRAWING_POINTS)), long: 2, short: 2 };
|
|
17760
17802
|
var DRAWING_KINDS = Object.keys(DRAWING_POINTS);
|
|
17761
17803
|
var FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
@@ -17789,7 +17831,8 @@ function drawingGeometry(kind, request, now = Date.now()) {
|
|
|
17789
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`);
|
|
17790
17832
|
} else if (!Array.isArray(raw) || raw.length !== need) throw new Error(`${kind} \u9700\u8981 ${need} \u4E2A points\uFF08{ time, price }\uFF09`);
|
|
17791
17833
|
const points2 = raw.map((item, index) => point(item?.time, item?.price, `\u7B2C ${index + 1} \u4E2A\u70B9`));
|
|
17792
|
-
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");
|
|
17793
17836
|
if (kind === "vprofile") {
|
|
17794
17837
|
if (points2[0].time === points2[1].time) throw new Error("\u6210\u4EA4\u91CF\u5206\u5E03\u9700\u8981\u4E00\u4E2A\u65F6\u95F4\u533A\u95F4");
|
|
17795
17838
|
if (points2[1].time < points2[0].time) points2.reverse();
|
|
@@ -17817,6 +17860,8 @@ var DRAWING_TOOLS = [
|
|
|
17817
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" },
|
|
17818
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" },
|
|
17819
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" },
|
|
17820
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" },
|
|
17821
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" },
|
|
17822
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" },
|
|
@@ -17826,6 +17871,11 @@ var DRAWING_TOOLS = [
|
|
|
17826
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" },
|
|
17827
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" },
|
|
17828
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" },
|
|
17829
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" },
|
|
17830
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" },
|
|
17831
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" },
|
|
@@ -17833,19 +17883,37 @@ var DRAWING_TOOLS = [
|
|
|
17833
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" },
|
|
17834
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" },
|
|
17835
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" },
|
|
17836
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" },
|
|
17837
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" },
|
|
17838
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" },
|
|
17839
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" },
|
|
17840
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" },
|
|
17841
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" },
|
|
17842
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" },
|
|
17843
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" },
|
|
17844
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" },
|
|
17845
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" },
|
|
17846
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" },
|
|
17847
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" },
|
|
17848
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" },
|
|
17849
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" },
|
|
17850
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" }
|
|
17851
17919
|
];
|
|
@@ -18255,16 +18323,24 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
18255
18323
|
handles = [c0, c1];
|
|
18256
18324
|
break;
|
|
18257
18325
|
}
|
|
18326
|
+
case "avprofile":
|
|
18258
18327
|
case "vprofile": {
|
|
18259
|
-
|
|
18260
|
-
const
|
|
18261
|
-
|
|
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);
|
|
18262
18338
|
const settings = { rows: rows ?? 48, valueArea: valueArea ?? 0.7 };
|
|
18263
18339
|
const profile = view.profile ? view.profile(from.time, to.time, settings) : volumeProfile(view.candles?.(from.time, to.time) ?? [], settings);
|
|
18264
18340
|
const partial = view.firstTime != null && view.barMs != null && view.firstTime - from.time >= view.barMs;
|
|
18265
18341
|
if (!profile) {
|
|
18266
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"));
|
|
18267
|
-
handles = [p[0], p[1]];
|
|
18343
|
+
handles = kind === "avprofile" ? [p[0]] : [p[0], p[1]];
|
|
18268
18344
|
break;
|
|
18269
18345
|
}
|
|
18270
18346
|
const y = (price) => view.project(from.time, price)?.y;
|
|
@@ -18282,9 +18358,203 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
18282
18358
|
paths2[inValue ? "valueDown" : "outDown"] += rectPath(x0 + upWidth, top, downWidth, height);
|
|
18283
18359
|
});
|
|
18284
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 })));
|
|
18285
18512
|
handles = [p[0], p[1]];
|
|
18286
18513
|
break;
|
|
18287
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
|
+
}
|
|
18288
18558
|
case "text": {
|
|
18289
18559
|
const value = textValue || label || tr("\u6587\u672C", "Text");
|
|
18290
18560
|
const lines = wrapText(value, 300);
|
|
@@ -18360,7 +18630,7 @@ function saveStyleTemplate(kind, style) {
|
|
|
18360
18630
|
// src/client/components/draw-toolbar.tsx
|
|
18361
18631
|
var DRAW_COLORS = ["#4c82d8", "#2fd3a0", "#ef5f6c", "#e2a943", "#9b86db", "#26b5c8", "#8a8f98"];
|
|
18362
18632
|
var STYLES = [["solid", "\u5B9E\u7EBF", "Solid"], ["dashed", "\u865A\u7EBF", "Dashed"], ["dotted", "\u70B9\u7EBF", "Dotted"]];
|
|
18363
|
-
function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone, onCoordinates }) {
|
|
18633
|
+
function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone, onCoordinates, placement = "top" }) {
|
|
18364
18634
|
const [savedDefault, setSavedDefault] = React8.useState(false);
|
|
18365
18635
|
React8.useEffect(() => {
|
|
18366
18636
|
setSavedDefault(false);
|
|
@@ -18391,7 +18661,7 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
|
|
|
18391
18661
|
const style = mark.lineStyle ?? "solid";
|
|
18392
18662
|
const tool = toolOf(mark.kind);
|
|
18393
18663
|
const stop = (event) => event.stopPropagation();
|
|
18394
|
-
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(
|
|
18395
18665
|
"input",
|
|
18396
18666
|
{
|
|
18397
18667
|
className: "dhl-draw-toolbar-text",
|
|
@@ -18728,7 +18998,19 @@ function cssVar(el, name, fallback) {
|
|
|
18728
18998
|
function chartData(points2) {
|
|
18729
18999
|
return points2.map((point) => ({ time: point.time, value: point.value }));
|
|
18730
19000
|
}
|
|
19001
|
+
var TOOLBAR_CLEARANCE = 84;
|
|
18731
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",
|
|
18732
19014
|
"trend",
|
|
18733
19015
|
"rect",
|
|
18734
19016
|
"measure",
|
|
@@ -19521,6 +19803,12 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19521
19803
|
window.__dhlPriceLines = () => priceLines.current.size;
|
|
19522
19804
|
}, [marks, themeVersion, chartType, interval]);
|
|
19523
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
|
+
};
|
|
19524
19812
|
const markById = React10.useMemo(() => new Map(marks.map((mark) => [mark.id, mark])), [marks]);
|
|
19525
19813
|
const markByIdRef = React10.useRef(markById);
|
|
19526
19814
|
markByIdRef.current = markById;
|
|
@@ -19945,7 +20233,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19945
20233
|
points: shown,
|
|
19946
20234
|
view,
|
|
19947
20235
|
text: mark.label,
|
|
19948
|
-
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,
|
|
19949
20237
|
className: `dhl-drawing ${roleClass(mark)}${mark.kind === "level" ? " is-level" : ""}${selected ? " selected" : ""}${mark.locked || drawingsLocked ? " locked" : ""}`,
|
|
19950
20238
|
label: mark.source === "user" || mark.kind === "level" ? void 0 : mark.label,
|
|
19951
20239
|
anchors: selected,
|
|
@@ -20006,6 +20294,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
20006
20294
|
{
|
|
20007
20295
|
mark: selectedMark,
|
|
20008
20296
|
globallyLocked: drawingsLocked,
|
|
20297
|
+
placement: toolbarPlacement(selectedMark),
|
|
20009
20298
|
onCoordinates: () => setEditingId(selectedMark.id),
|
|
20010
20299
|
onEdit: (patch) => onEditMark(selectedMark.id, patch),
|
|
20011
20300
|
onClone: () => onCloneMark(cloneOf(selectedMark)),
|
|
@@ -21379,6 +21668,7 @@ function DeskColumn({ onCollapse }) {
|
|
|
21379
21668
|
const [textAt, setTextAt] = React26.useState(null);
|
|
21380
21669
|
const [selectRequest, setSelectRequest] = React26.useState(null);
|
|
21381
21670
|
const [textValue, setTextValue] = React26.useState("");
|
|
21671
|
+
const [toolQuery, setToolQuery] = React26.useState("");
|
|
21382
21672
|
const [confirmClear, setConfirmClear] = React26.useState(false);
|
|
21383
21673
|
const [pickerOpen, setPickerOpen] = React26.useState(false);
|
|
21384
21674
|
const [chartExpanded, setChartExpanded] = React26.useState(false);
|
|
@@ -21542,7 +21832,7 @@ function DeskColumn({ onCollapse }) {
|
|
|
21542
21832
|
}, [indicators, s.candles, s.candlesOlder]);
|
|
21543
21833
|
React26.useEffect(() => {
|
|
21544
21834
|
if (s.candlesOlder !== "idle" || s.candles.length < 2) return;
|
|
21545
|
-
const earliest = Math.min(...s.chartMarks.filter((mark2) => !mark2.hidden &&
|
|
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))));
|
|
21546
21836
|
if (Number.isFinite(earliest) && s.candles[0].t > earliest) void loadOlderCandles();
|
|
21547
21837
|
}, [s.chartMarks, s.candles, s.candlesOlder]);
|
|
21548
21838
|
React26.useEffect(() => {
|
|
@@ -21599,7 +21889,30 @@ function DeskColumn({ onCollapse }) {
|
|
|
21599
21889
|
}
|
|
21600
21890
|
}
|
|
21601
21891
|
)
|
|
21602
|
-
), 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") },
|
|
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(
|
|
21603
21916
|
"button",
|
|
21604
21917
|
{
|
|
21605
21918
|
type: "button",
|
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'
|
|
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>
|
package/lib/chart-drawings.js
CHANGED
|
@@ -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 = {
|
|
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: '
|
|
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.
|
|
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
|
-
-
|
|
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` 让画线同步到该币种的所有会话。
|