dsh-hyperliquid 0.4.2 → 0.4.4
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 +11 -0
- package/client.js +34 -12
- package/lib/chart-drawings.d.ts +1 -1
- package/lib/chart-drawings.js +8 -1
- package/lib/remote.js +1 -1
- package/lib/tools.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.4 — 2026-10-17
|
|
4
|
+
|
|
5
|
+
- 修复:触摸屏上无法画线。lightweight-charts 处理轻点时会在 touchend 上 preventDefault,浏览器不再补发 click,而落锚点依赖原生 click;现在未拖动的触摸 pointerup 直接按点击处理(同一轻点之后若仍有 click 会被忽略)。
|
|
6
|
+
- 修复:手指拖动锚点或画线时被浏览器当作页面滚动而中途取消(pointercancel);图表区域设为 `touch-action: none`,与 TradingView 一致由图表处理全部手势。
|
|
7
|
+
- 冒烟新增 touch 阶段(点画、选中、拖锚点、单指平移)。
|
|
8
|
+
|
|
9
|
+
## 0.4.3 — 2026-10-17
|
|
10
|
+
|
|
11
|
+
- 趋势线可分别向左、向右延长(浮动工具条两个开关,agent 工具 `extendLeft` / `extendRight`);价格提醒按延长后的线判断,延长的线不参与屏幕外剔除。
|
|
12
|
+
- 「点击没反应」根因补充验证:0.1.0 两次点击间隔 300ms、450ms 同样漏画 20/20,550ms 0/20,分界与图表库 500ms 双击窗口一致;当前版本同样间隔 0 漏画。
|
|
13
|
+
|
|
3
14
|
## 0.4.2 — 2026-10-17
|
|
4
15
|
|
|
5
16
|
- 画线对象属性对齐 TradingView:按周期显示(仅本周期,水平线的轴价格标签同样隐藏)、文本与标注气泡字号(10–32px)与粗体 / 斜体、矩形 / 圆 / 三角 / 通道 / 斐波那契 / 区间等的背景填充浓淡;浮动工具条、agent 工具参数、撤销与复制都支持。限定周期的画线在其他周期不绘制但保留在画线列表。
|
package/client.js
CHANGED
|
@@ -949,6 +949,9 @@ body:has(.dhl-divider.dragging) * { cursor: col-resize !important; }
|
|
|
949
949
|
|
|
950
950
|
/* One row like TradingView's floating toolbar: a wrapped toolbar covered drawings near the top of a narrow chart. */
|
|
951
951
|
.dhl-draw-toolbar > * { flex: none; }
|
|
952
|
+
/* Touch: the chart owns every gesture inside it (anchor and drawing drags, pans), as TradingView does;
|
|
953
|
+
otherwise the browser claims a finger drag for scrolling and cancels the pointer mid-drag. */
|
|
954
|
+
.dhl-chart, .dhl-chart-drawing-overlay, .dhl-chart-drawing-overlay * { touch-action: none; }
|
|
952
955
|
`;
|
|
953
956
|
|
|
954
957
|
// src/client/locale.ts
|
|
@@ -17908,9 +17911,9 @@ function drawingLabel(kind, points2, price) {
|
|
|
17908
17911
|
const when = new Date(first.time).toLocaleString(tr("zh-CN", "en-US"), { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false });
|
|
17909
17912
|
return `${name} ${when}`;
|
|
17910
17913
|
}
|
|
17911
|
-
function clipLine(a, b, w, h, from) {
|
|
17914
|
+
function clipLine(a, b, w, h, from, to = Infinity) {
|
|
17912
17915
|
const dx = b.x - a.x, dy = b.y - a.y;
|
|
17913
|
-
let t0 = from, t1 =
|
|
17916
|
+
let t0 = from, t1 = to;
|
|
17914
17917
|
for (const [p, q] of [[-dx, a.x], [dx, w - a.x], [-dy, a.y], [dy, h - a.y]]) {
|
|
17915
17918
|
if (p === 0) {
|
|
17916
17919
|
if (q < 0) return null;
|
|
@@ -17992,7 +17995,7 @@ function guide(a, b, value, key3, text) {
|
|
|
17992
17995
|
}
|
|
17993
17996
|
var BADGE_PATHS = { alert: "M6 16V11a6 6 0 0 1 12 0v5l2 2H4zM10 20a2 2 0 0 0 4 0", shared: "M4 9h13l-3-3M20 15H7l3 3" };
|
|
17994
17997
|
var DASH = { solid: void 0, dashed: "7 5", dotted: "1.5 4" };
|
|
17995
|
-
var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, label, text: textValue, className, view, anchors = false, interactive = false, color, lineWidth, lineStyle, id, alert, shared, rows, valueArea, levels, fontSize, bold, italic, fillOpacity }) {
|
|
17998
|
+
var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, label, text: textValue, className, view, anchors = false, interactive = false, color, lineWidth, lineStyle, id, alert, shared, rows, valueArea, levels, fontSize, bold, italic, fillOpacity, extendLeft, extendRight }) {
|
|
17996
17999
|
const k = (fontSize ?? 12) / 12;
|
|
17997
18000
|
const badges = [...alert ? ["alert"] : [], ...shared ? ["shared"] : []];
|
|
17998
18001
|
const { width: w, height: h } = view;
|
|
@@ -18014,9 +18017,17 @@ var DrawingShape = React7.memo(function DrawingShape2({ kind, points: points2, l
|
|
|
18014
18017
|
body = /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, x2: p[0].x, y1: "0", y2: h });
|
|
18015
18018
|
handles = [{ x: p[0].x, y: Math.min(h - 12, Math.max(12, p[0].y)) }];
|
|
18016
18019
|
break;
|
|
18017
|
-
case "trend":
|
|
18018
|
-
|
|
18020
|
+
case "trend": {
|
|
18021
|
+
if (!p[1]) break;
|
|
18022
|
+
if (!extendLeft && !extendRight) {
|
|
18023
|
+
body = /* @__PURE__ */ React7.createElement("line", { x1: p[0].x, y1: p[0].y, x2: p[1].x, y2: p[1].y });
|
|
18024
|
+
break;
|
|
18025
|
+
}
|
|
18026
|
+
const [l, r] = p[0].x <= p[1].x ? [p[0], p[1]] : [p[1], p[0]];
|
|
18027
|
+
const seg = clipLine(l, r, w, h, extendLeft ? -Infinity : 0, extendRight ? Infinity : 1);
|
|
18028
|
+
body = seg ? /* @__PURE__ */ React7.createElement("line", { x1: seg[0].x, y1: seg[0].y, x2: seg[1].x, y2: seg[1].y }) : null;
|
|
18019
18029
|
break;
|
|
18030
|
+
}
|
|
18020
18031
|
case "ray":
|
|
18021
18032
|
case "extended": {
|
|
18022
18033
|
if (!p[1]) break;
|
|
@@ -18435,7 +18446,7 @@ function DrawToolbar({ mark, globallyLocked, onEdit, onRemove, onClose, onClone,
|
|
|
18435
18446
|
onClick: () => onEdit({ lineStyle: value })
|
|
18436
18447
|
},
|
|
18437
18448
|
/* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 18 18", width: "16", height: "16", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("line", { x1: "2", x2: "16", y1: "9", y2: "9", stroke: "currentColor", strokeWidth: "2", strokeDasharray: value === "dashed" ? "4 3" : value === "dotted" ? "1 3" : void 0, strokeLinecap: "round" }))
|
|
18438
|
-
))) : null, FONT_KINDS.includes(mark.kind) || FILL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6587\u5B57\u4E0E\u586B\u5145", "Text and fill") }, FONT_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u5B57\u53F7", "Font size"), title: tr("\u5B57\u53F7", "Font size"), value: String(mark.fontSize ?? 12), disabled: locked, onChange: (event) => onEdit({ fontSize: Number(event.target.value) }) }, [10, 12, 14, 18, 24, 32].map((size3) => /* @__PURE__ */ React8.createElement("option", { key: size3, value: size3 }, `${size3}px`))) : null, FONT_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement("button", { type: "button", className: mark.bold ? "on" : "", "aria-pressed": Boolean(mark.bold), disabled: locked, "aria-label": tr("\u7C97\u4F53", "Bold"), title: tr("\u7C97\u4F53", "Bold"), onClick: () => onEdit({ bold: !mark.bold }) }, /* @__PURE__ */ React8.createElement("b", null, "B")), /* @__PURE__ */ React8.createElement("button", { type: "button", className: mark.italic ? "on" : "", "aria-pressed": Boolean(mark.italic), disabled: locked, "aria-label": tr("\u659C\u4F53", "Italic"), title: tr("\u659C\u4F53", "Italic"), onClick: () => onEdit({ italic: !mark.italic }) }, /* @__PURE__ */ React8.createElement("i", null, "I"))) : null, FILL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u80CC\u666F\u586B\u5145", "Background fill"), title: tr("\u80CC\u666F\u586B\u5145", "Background fill"), value: String(mark.fillOpacity ?? (mark.kind === "fib" ? 0.07 : 0.08)), disabled: locked, onChange: (event) => onEdit({ fillOpacity: Number(event.target.value) }) }, [[0, "\u65E0\u586B\u5145", "No fill"], [mark.kind === "fib" ? 0.07 : 0.08, "\u6DE1", "Light"], [0.18, "\u4E2D", "Medium"], [0.32, "\u6D53", "Strong"]].map(([value, zh, en]) => /* @__PURE__ */ React8.createElement("option", { key: String(value), value: String(value) }, tr(String(zh), String(en))))) : null) : null, FIB_LEVEL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6590\u6CE2\u90A3\u5951\u5C42\u7EA7", "Fibonacci levels") }, /* @__PURE__ */ React8.createElement(
|
|
18449
|
+
))) : null, mark.kind === "trend" ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u5EF6\u957F", "Extend") }, /* @__PURE__ */ React8.createElement("button", { type: "button", className: mark.extendLeft ? "on" : "", "aria-pressed": Boolean(mark.extendLeft), disabled: locked, "aria-label": tr("\u5411\u5DE6\u5EF6\u957F", "Extend left"), title: tr("\u5411\u5DE6\u5EF6\u957F", "Extend left"), onClick: () => onEdit({ extendLeft: !mark.extendLeft }) }, /* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("path", { d: "M3 12h13", strokeDasharray: "2 2.5" }), /* @__PURE__ */ React8.createElement("path", { d: "M14 12h7" }), /* @__PURE__ */ React8.createElement("circle", { cx: "14", cy: "12", r: "1.8" }), /* @__PURE__ */ React8.createElement("path", { d: "M6 9l-3 3 3 3" }))), /* @__PURE__ */ React8.createElement("button", { type: "button", className: mark.extendRight ? "on" : "", "aria-pressed": Boolean(mark.extendRight), disabled: locked, "aria-label": tr("\u5411\u53F3\u5EF6\u957F", "Extend right"), title: tr("\u5411\u53F3\u5EF6\u957F", "Extend right"), onClick: () => onEdit({ extendRight: !mark.extendRight }) }, /* @__PURE__ */ React8.createElement("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.8", "aria-hidden": "true" }, /* @__PURE__ */ React8.createElement("path", { d: "M3 12h7" }), /* @__PURE__ */ React8.createElement("path", { d: "M10 12h11", strokeDasharray: "2 2.5" }), /* @__PURE__ */ React8.createElement("circle", { cx: "10", cy: "12", r: "1.8" }), /* @__PURE__ */ React8.createElement("path", { d: "M18 9l3 3-3 3" })))) : null, FONT_KINDS.includes(mark.kind) || FILL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6587\u5B57\u4E0E\u586B\u5145", "Text and fill") }, FONT_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u5B57\u53F7", "Font size"), title: tr("\u5B57\u53F7", "Font size"), value: String(mark.fontSize ?? 12), disabled: locked, onChange: (event) => onEdit({ fontSize: Number(event.target.value) }) }, [10, 12, 14, 18, 24, 32].map((size3) => /* @__PURE__ */ React8.createElement("option", { key: size3, value: size3 }, `${size3}px`))) : null, FONT_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement("button", { type: "button", className: mark.bold ? "on" : "", "aria-pressed": Boolean(mark.bold), disabled: locked, "aria-label": tr("\u7C97\u4F53", "Bold"), title: tr("\u7C97\u4F53", "Bold"), onClick: () => onEdit({ bold: !mark.bold }) }, /* @__PURE__ */ React8.createElement("b", null, "B")), /* @__PURE__ */ React8.createElement("button", { type: "button", className: mark.italic ? "on" : "", "aria-pressed": Boolean(mark.italic), disabled: locked, "aria-label": tr("\u659C\u4F53", "Italic"), title: tr("\u659C\u4F53", "Italic"), onClick: () => onEdit({ italic: !mark.italic }) }, /* @__PURE__ */ React8.createElement("i", null, "I"))) : null, FILL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement(Select, { "aria-label": tr("\u80CC\u666F\u586B\u5145", "Background fill"), title: tr("\u80CC\u666F\u586B\u5145", "Background fill"), value: String(mark.fillOpacity ?? (mark.kind === "fib" ? 0.07 : 0.08)), disabled: locked, onChange: (event) => onEdit({ fillOpacity: Number(event.target.value) }) }, [[0, "\u65E0\u586B\u5145", "No fill"], [mark.kind === "fib" ? 0.07 : 0.08, "\u6DE1", "Light"], [0.18, "\u4E2D", "Medium"], [0.32, "\u6D53", "Strong"]].map(([value, zh, en]) => /* @__PURE__ */ React8.createElement("option", { key: String(value), value: String(value) }, tr(String(zh), String(en))))) : null) : null, FIB_LEVEL_KINDS.includes(mark.kind) ? /* @__PURE__ */ React8.createElement("span", { className: "dhl-draw-toolbar-group", role: "group", "aria-label": tr("\u6590\u6CE2\u90A3\u5951\u5C42\u7EA7", "Fibonacci levels") }, /* @__PURE__ */ React8.createElement(
|
|
18439
18450
|
"input",
|
|
18440
18451
|
{
|
|
18441
18452
|
className: "dhl-draw-toolbar-text dhl-fib-levels",
|
|
@@ -19010,13 +19021,16 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19010
19021
|
box.addEventListener("pointermove", move, true);
|
|
19011
19022
|
box.addEventListener("pointerup", move, true);
|
|
19012
19023
|
box.addEventListener("pointercancel", cancel, true);
|
|
19024
|
+
let touchTapAt = -Infinity;
|
|
19025
|
+
const fromTouch = (event) => event.pointerType === "touch" || Boolean(event.sourceCapabilities?.firesTouchEvents);
|
|
19013
19026
|
const chartClick = (event) => {
|
|
19027
|
+
if (event.type === "click" && fromTouch(event) && performance.now() - touchTapAt < 800) return false;
|
|
19014
19028
|
const visible = chart.timeScale().getVisibleLogicalRange();
|
|
19015
19029
|
const panned = !pressStillAClick(press?.range ?? null, visible ? { from: visible.from, to: visible.to } : null);
|
|
19016
19030
|
const clicked = Boolean(press && !press.dragged && !panned && event.button === 0);
|
|
19017
19031
|
press = null;
|
|
19018
|
-
if (!clicked) return;
|
|
19019
|
-
if (!(event.target instanceof HTMLCanvasElement)) return;
|
|
19032
|
+
if (!clicked) return false;
|
|
19033
|
+
if (!(event.target instanceof HTMLCanvasElement)) return false;
|
|
19020
19034
|
const rect = box.getBoundingClientRect();
|
|
19021
19035
|
const x = event.clientX - rect.left;
|
|
19022
19036
|
const y = event.clientY - rect.top;
|
|
@@ -19067,6 +19081,11 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19067
19081
|
}
|
|
19068
19082
|
};
|
|
19069
19083
|
box.addEventListener("click", chartClick);
|
|
19084
|
+
const touchTap = (event) => {
|
|
19085
|
+
if (event.pointerType !== "touch" || press?.id !== event.pointerId) return;
|
|
19086
|
+
if (chartClick(event) !== false) touchTapAt = performance.now();
|
|
19087
|
+
};
|
|
19088
|
+
box.addEventListener("pointerup", touchTap);
|
|
19070
19089
|
let rangeTimer = null;
|
|
19071
19090
|
let viewportFrame = 0;
|
|
19072
19091
|
const refreshOverlay = () => {
|
|
@@ -19136,6 +19155,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19136
19155
|
box.removeEventListener("pointerup", move, true);
|
|
19137
19156
|
box.removeEventListener("pointercancel", cancel, true);
|
|
19138
19157
|
box.removeEventListener("click", chartClick);
|
|
19158
|
+
box.removeEventListener("pointerup", touchTap);
|
|
19139
19159
|
chart.timeScale().unsubscribeVisibleLogicalRangeChange(refreshOverlay);
|
|
19140
19160
|
box.removeEventListener("pointermove", scaleDrag);
|
|
19141
19161
|
box.removeEventListener("pointerdown", beginCanvasGesture);
|
|
@@ -19666,7 +19686,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19666
19686
|
const cloneOf = React10.useCallback((mark) => {
|
|
19667
19687
|
const line = timelineRef.current;
|
|
19668
19688
|
const shifted = markPoints(mark).map((point) => ({ time: line.toTime(line.toLogical(point.time) + 5), price: point.price }));
|
|
19669
|
-
const style = Object.fromEntries(Object.entries({ color: mark.color, lineWidth: mark.lineWidth, lineStyle: mark.lineStyle, levels: mark.levels, rows: mark.rows, valueArea: mark.valueArea, fontSize: mark.fontSize, bold: mark.bold, italic: mark.italic, fillOpacity: mark.fillOpacity, visibleIntervals: mark.visibleIntervals }).filter(([, value]) => value != null));
|
|
19689
|
+
const style = Object.fromEntries(Object.entries({ color: mark.color, lineWidth: mark.lineWidth, lineStyle: mark.lineStyle, levels: mark.levels, rows: mark.rows, valueArea: mark.valueArea, fontSize: mark.fontSize, bold: mark.bold, italic: mark.italic, extendLeft: mark.extendLeft, extendRight: mark.extendRight, fillOpacity: mark.fillOpacity, visibleIntervals: mark.visibleIntervals }).filter(([, value]) => value != null));
|
|
19670
19690
|
return { kind: mark.kind, geometry: geometryPatch(mark.kind, shifted), style, label: mark.label };
|
|
19671
19691
|
}, []);
|
|
19672
19692
|
const clipboard = React10.useRef(null);
|
|
@@ -19915,7 +19935,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19915
19935
|
const selected = selectedId === mark.id || group.includes(mark.id);
|
|
19916
19936
|
if (interval && mark.visibleIntervals?.length && !mark.visibleIntervals.includes(interval)) return null;
|
|
19917
19937
|
const clampedDecoration = !(mark.source === "user" || mark.kind === "level") || Boolean(mark.alert) || Boolean(mark.shared);
|
|
19918
|
-
if (!selected && !clampedDecoration && FINITE_KINDS.has(mark.kind) && offscreen(shown, view)) return null;
|
|
19938
|
+
if (!selected && !clampedDecoration && !mark.extendLeft && !mark.extendRight && FINITE_KINDS.has(mark.kind) && offscreen(shown, view)) return null;
|
|
19919
19939
|
return /* @__PURE__ */ React10.createElement(
|
|
19920
19940
|
DrawingShape,
|
|
19921
19941
|
{
|
|
@@ -19941,7 +19961,9 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
19941
19961
|
fontSize: mark.fontSize,
|
|
19942
19962
|
bold: mark.bold,
|
|
19943
19963
|
italic: mark.italic,
|
|
19944
|
-
fillOpacity: mark.fillOpacity
|
|
19964
|
+
fillOpacity: mark.fillOpacity,
|
|
19965
|
+
extendLeft: mark.extendLeft,
|
|
19966
|
+
extendRight: mark.extendRight
|
|
19945
19967
|
}
|
|
19946
19968
|
);
|
|
19947
19969
|
}), /* @__PURE__ */ React10.createElement("g", { className: "dhl-fills" }, fills.map((event) => {
|
|
@@ -20017,7 +20039,7 @@ function CandleChart({ candles, marks, indicators, expanded, resetToken, drawMod
|
|
|
20017
20039
|
var React11 = __toESM(require("react"), 1);
|
|
20018
20040
|
var HISTORY_LIMIT = 100;
|
|
20019
20041
|
var GEOMETRY = ["price", "fromTime", "fromPrice", "toTime", "toPrice", "points"];
|
|
20020
|
-
var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea", "levels", "visibleIntervals", "fontSize", "fillOpacity", "bold", "italic"];
|
|
20042
|
+
var STYLE = ["color", "lineWidth", "lineStyle", "locked", "hidden", "shared", "alert", "rows", "valueArea", "levels", "visibleIntervals", "fontSize", "fillOpacity", "bold", "italic", "extendLeft", "extendRight"];
|
|
20021
20043
|
var FLAGS = /* @__PURE__ */ new Set(["locked", "hidden", "shared", "alert"]);
|
|
20022
20044
|
function recreateRequest(mark) {
|
|
20023
20045
|
const out = { kind: mark.kind, label: mark.label, role: mark.role, coin: mark.coin, interval: mark.interval };
|
package/lib/chart-drawings.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type DrawingKind = 'level' | 'hray' | 'vline' | 'text' | 'trend' | 'ray' | 'extended' | 'fib' | 'rect' | 'measure' | 'channel' | 'long' | 'short' | 'fibext' | 'fibchannel' | 'fibtime' | 'gannfan' | 'pitchfork' | 'regression' | 'vprofile' | 'arrow' | 'callout' | 'circle' | 'triangle' | 'brush' | 'crossline' | 'pricelabel' | 'infoline' | 'trendangle' | 'pricerange' | 'daterange' | 'elliott' | 'elliottabc' | 'xabcd' | 'abcd' | 'headshoulders' | 'gannbox' | 'gannsquare' | 'schiff' | 'modschiff' | 'icon'
|
|
2
2
|
export type LineStyleName = 'solid' | 'dashed' | 'dotted'
|
|
3
|
-
export interface DrawingStyle { visibleIntervals?: string[]; fontSize?: number; bold?: boolean; italic?: boolean; fillOpacity?: number; levels?: number[]; rows?: number; valueArea?: number; color?: string; lineWidth?: number; lineStyle?: LineStyleName; locked?: boolean; hidden?: boolean; shared?: boolean; alert?: boolean }
|
|
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>
|
|
5
5
|
export declare const DRAWING_KINDS: DrawingKind[]
|
|
6
6
|
export declare const DRAWING_CLICKS: Record<DrawingKind, number>
|
package/lib/chart-drawings.js
CHANGED
|
@@ -106,7 +106,8 @@ export function linePriceAt(mark, time) {
|
|
|
106
106
|
if (!a) return null
|
|
107
107
|
if (mark.kind === 'hray') return time >= a.time ? a.price : null
|
|
108
108
|
if (!b || b.time === a.time) return null
|
|
109
|
-
|
|
109
|
+
// A trend line can be extended left and/or right (TradingView's per-line extend option).
|
|
110
|
+
if (mark.kind === 'trend' && ((!mark.extendLeft && time < Math.min(a.time, b.time)) || (!mark.extendRight && time > Math.max(a.time, b.time)))) return null
|
|
110
111
|
if (mark.kind === 'ray' && (time - a.time) * (b.time - a.time) < 0) return null
|
|
111
112
|
return a.price + (b.price - a.price) * (time - a.time) / (b.time - a.time)
|
|
112
113
|
}
|
|
@@ -206,6 +207,12 @@ export function drawingStyle(request) {
|
|
|
206
207
|
if (!Number.isInteger(size) || size < 10 || size > 32) throw new Error('字号必须为 10-32 的整数')
|
|
207
208
|
style.fontSize = size
|
|
208
209
|
}
|
|
210
|
+
for (const flag of ['extendLeft', 'extendRight']) {
|
|
211
|
+
if (request?.[flag] == null) continue
|
|
212
|
+
if (request?.kind != null && request.kind !== 'trend') throw new Error(`${flag} 只适用于 trend`)
|
|
213
|
+
if (typeof request[flag] !== 'boolean') throw new Error(`${flag} 必须为布尔值`)
|
|
214
|
+
style[flag] = request[flag]
|
|
215
|
+
}
|
|
209
216
|
for (const flag of ['bold', 'italic']) {
|
|
210
217
|
if (request?.[flag] == null) continue
|
|
211
218
|
if (request?.kind != null && !FONT_KINDS.includes(request.kind)) throw new Error(`${flag} 只适用于 ${FONT_KINDS.join('/')}`)
|
package/lib/remote.js
CHANGED
|
@@ -575,7 +575,7 @@ export class HyperRemote extends TypertRemoteService {
|
|
|
575
575
|
const fields = { ...drawingStyle({ ...patch, kind: current.kind }) }
|
|
576
576
|
if (patch?.alert === true) fields.alertTriggeredAt = undefined
|
|
577
577
|
// null clears a style back to its default (undo of a first restyle relies on this).
|
|
578
|
-
for (const key of ['color', 'lineWidth', 'lineStyle', 'rows', 'valueArea', 'levels', 'visibleIntervals', 'fontSize', 'fillOpacity', 'bold', 'italic']) if (key in patch && patch[key] === null) fields[key] = undefined
|
|
578
|
+
for (const key of ['color', 'lineWidth', 'lineStyle', 'rows', 'valueArea', 'levels', 'visibleIntervals', 'fontSize', 'fillOpacity', 'bold', 'italic', 'extendLeft', 'extendRight']) if (key in patch && patch[key] === null) fields[key] = undefined
|
|
579
579
|
if (geometryKeys.some((key) => key in patch)) Object.assign(fields, drawingGeometry(current.kind, merged))
|
|
580
580
|
if ('label' in patch) {
|
|
581
581
|
const label = String(patch.label ?? '').trim()
|
package/lib/tools.js
CHANGED
|
@@ -254,6 +254,7 @@ function buildTools(remote) {
|
|
|
254
254
|
lineWidth: { type: 'number', description: '画线线宽 1-4' },
|
|
255
255
|
visibleIntervals: { type: 'array', items: { type: 'string' }, description: '只在这些周期显示(如 ["4h","1d"]);省略=所有周期' },
|
|
256
256
|
fontSize: { type: 'number', description: 'kind=text/callout:字号 10-32' },
|
|
257
|
+
extendLeft: { type: 'boolean', description: 'kind=trend:向左延长' }, extendRight: { type: 'boolean', description: 'kind=trend:向右延长' },
|
|
257
258
|
bold: { type: 'boolean', description: 'kind=text/callout:粗体' }, italic: { type: 'boolean', description: 'kind=text/callout:斜体' },
|
|
258
259
|
fillOpacity: { type: 'number', description: '矩形/圆/三角形/通道/斐波那契/区间等的背景填充透明度 0-0.6(0=无填充)' },
|
|
259
260
|
levels: { type: 'array', items: { type: 'number' }, description: 'kind=fib/fibext/fibchannel:自定义斐波那契层级,如 [0,0.382,0.5,0.618,1,1.618]' },
|
|
@@ -310,7 +311,7 @@ function buildTools(remote) {
|
|
|
310
311
|
return { text: `已移除均线:${maLabel(removed)}(${removed.id})`, line: removed }
|
|
311
312
|
}
|
|
312
313
|
if (args?.action === 'update') {
|
|
313
|
-
const patch = Object.fromEntries(['points', 'price', 'fromTime', 'fromPrice', 'toTime', 'toPrice', 'label', 'color', 'lineWidth', 'lineStyle', 'locked', 'hidden', 'shared', 'alert', 'rows', 'valueArea', 'levels', 'visibleIntervals', 'fontSize', 'fillOpacity', 'bold', 'italic'].filter((key) => args?.[key] != null).map((key) => [key, args[key]]))
|
|
314
|
+
const patch = Object.fromEntries(['points', 'price', 'fromTime', 'fromPrice', 'toTime', 'toPrice', 'label', 'color', 'lineWidth', 'lineStyle', 'locked', 'hidden', 'shared', 'alert', 'rows', 'valueArea', 'levels', 'visibleIntervals', 'fontSize', 'fillOpacity', 'bold', 'italic', 'extendLeft', 'extendRight'].filter((key) => args?.[key] != null).map((key) => [key, args[key]]))
|
|
314
315
|
const mark = remote.editChartMark(args?.id, patch, sessionId)
|
|
315
316
|
return { text: `已更新${DRAWING_NAMES[mark.kind] ?? mark.kind}:${mark.label}(${mark.id})`, mark }
|
|
316
317
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-hyperliquid",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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",
|