hyperprop-charting-library 0.1.71 → 0.1.73
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/dist/hyperprop-charting-library.cjs +155 -0
- package/dist/hyperprop-charting-library.d.ts +4 -2
- package/dist/hyperprop-charting-library.js +154 -0
- package/dist/index.cjs +155 -0
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +154 -0
- package/docs/API.md +3 -0
- package/package.json +1 -1
|
@@ -21,9 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
FIB_DEFAULT_PALETTE: () => FIB_DEFAULT_PALETTE,
|
|
24
|
+
POSITION_DEFAULT_COLORS: () => POSITION_DEFAULT_COLORS,
|
|
24
25
|
createChart: () => createChart
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var POSITION_DEFAULT_COLORS = ["#26a69a", "#ef5350", "#ffffff"];
|
|
27
29
|
var FIB_DEFAULT_PALETTE = [
|
|
28
30
|
"#787b86",
|
|
29
31
|
"#f23645",
|
|
@@ -2391,6 +2393,92 @@ function createChart(element, options = {}) {
|
|
|
2391
2393
|
}
|
|
2392
2394
|
}
|
|
2393
2395
|
}
|
|
2396
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
2397
|
+
const entry = drawing.points[0];
|
|
2398
|
+
const target = drawing.points[1];
|
|
2399
|
+
const stop = drawing.points[2];
|
|
2400
|
+
const right = drawing.points[3];
|
|
2401
|
+
if (entry && target && stop && right) {
|
|
2402
|
+
const leftX = xFromDrawingPoint(entry);
|
|
2403
|
+
const rightX = xFromDrawingPoint(right);
|
|
2404
|
+
const boxX0 = Math.min(leftX, rightX);
|
|
2405
|
+
const boxX1 = Math.max(leftX, rightX);
|
|
2406
|
+
const boxW = Math.max(1, boxX1 - boxX0);
|
|
2407
|
+
const entryY = yFromPrice(entry.price);
|
|
2408
|
+
const targetY = yFromPrice(target.price);
|
|
2409
|
+
const stopY = yFromPrice(stop.price);
|
|
2410
|
+
const isHex6 = (value) => !!value && /^#[0-9a-fA-F]{6}$/.test(value);
|
|
2411
|
+
const profitColor = isHex6(drawing.colors[0]) ? drawing.colors[0] : "#26a69a";
|
|
2412
|
+
const lossColor = isHex6(drawing.colors[1]) ? drawing.colors[1] : "#ef5350";
|
|
2413
|
+
const labelTextColor = isHex6(drawing.colors[2]) ? drawing.colors[2] : "#ffffff";
|
|
2414
|
+
const profitFill = hexToRgba(profitColor, 0.16);
|
|
2415
|
+
const lossFill = hexToRgba(lossColor, 0.16);
|
|
2416
|
+
const profitLine = profitColor;
|
|
2417
|
+
const lossLine = lossColor;
|
|
2418
|
+
ctx.save();
|
|
2419
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2420
|
+
ctx.fillStyle = profitFill;
|
|
2421
|
+
ctx.fillRect(boxX0, Math.min(entryY, targetY), boxW, Math.abs(targetY - entryY));
|
|
2422
|
+
ctx.fillStyle = lossFill;
|
|
2423
|
+
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2424
|
+
ctx.restore();
|
|
2425
|
+
ctx.save();
|
|
2426
|
+
ctx.setLineDash([]);
|
|
2427
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2428
|
+
ctx.strokeStyle = profitLine;
|
|
2429
|
+
ctx.beginPath();
|
|
2430
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2431
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2432
|
+
ctx.stroke();
|
|
2433
|
+
ctx.strokeStyle = lossLine;
|
|
2434
|
+
ctx.beginPath();
|
|
2435
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2436
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2437
|
+
ctx.stroke();
|
|
2438
|
+
ctx.strokeStyle = drawing.color;
|
|
2439
|
+
ctx.beginPath();
|
|
2440
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2441
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2442
|
+
ctx.stroke();
|
|
2443
|
+
ctx.restore();
|
|
2444
|
+
drawDrawingHandle(leftX, targetY, drawing.color);
|
|
2445
|
+
drawDrawingHandle(leftX, entryY, drawing.color);
|
|
2446
|
+
drawDrawingHandle(leftX, stopY, drawing.color);
|
|
2447
|
+
drawDrawingHandle(rightX, entryY, drawing.color);
|
|
2448
|
+
const tick = getConfiguredTickSize();
|
|
2449
|
+
const pctOf = (price) => {
|
|
2450
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2451
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2452
|
+
};
|
|
2453
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2454
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2455
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2456
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2457
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2458
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2459
|
+
const prevFont = ctx.font;
|
|
2460
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2461
|
+
const padding = 6;
|
|
2462
|
+
const textW = ctx.measureText(text).width;
|
|
2463
|
+
const pillW = textW + padding * 2;
|
|
2464
|
+
const pillH = 18;
|
|
2465
|
+
const pillX = centerX - pillW / 2;
|
|
2466
|
+
const pillY = centerY - pillH / 2;
|
|
2467
|
+
ctx.fillStyle = bg;
|
|
2468
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2469
|
+
ctx.fillStyle = labelTextColor;
|
|
2470
|
+
ctx.textAlign = "center";
|
|
2471
|
+
ctx.textBaseline = "middle";
|
|
2472
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2473
|
+
ctx.font = prevFont;
|
|
2474
|
+
};
|
|
2475
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2476
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2477
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2478
|
+
if (drawing.label) {
|
|
2479
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2394
2482
|
}
|
|
2395
2483
|
ctx.restore();
|
|
2396
2484
|
};
|
|
@@ -3587,6 +3675,28 @@ function createChart(element, options = {}) {
|
|
|
3587
3675
|
} else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
|
|
3588
3676
|
return { drawing, target: "line" };
|
|
3589
3677
|
}
|
|
3678
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
3679
|
+
const entry = drawing.points[0];
|
|
3680
|
+
const target = drawing.points[1];
|
|
3681
|
+
const stop = drawing.points[2];
|
|
3682
|
+
const right = drawing.points[3];
|
|
3683
|
+
if (!entry || !target || !stop || !right) continue;
|
|
3684
|
+
const leftX = canvasXFromDrawingPoint(entry);
|
|
3685
|
+
const rightX = canvasXFromDrawingPoint(right);
|
|
3686
|
+
const entryY = canvasYFromDrawingPrice(entry.price);
|
|
3687
|
+
const targetY = canvasYFromDrawingPrice(target.price);
|
|
3688
|
+
const stopY = canvasYFromDrawingPrice(stop.price);
|
|
3689
|
+
if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
3690
|
+
if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
3691
|
+
if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
3692
|
+
if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
3693
|
+
const x0 = Math.min(leftX, rightX);
|
|
3694
|
+
const x1 = Math.max(leftX, rightX);
|
|
3695
|
+
const yTop = Math.min(targetY, stopY, entryY);
|
|
3696
|
+
const yBot = Math.max(targetY, stopY, entryY);
|
|
3697
|
+
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
3698
|
+
return { drawing, target: "line" };
|
|
3699
|
+
}
|
|
3590
3700
|
}
|
|
3591
3701
|
}
|
|
3592
3702
|
return null;
|
|
@@ -3787,6 +3897,34 @@ function createChart(element, options = {}) {
|
|
|
3787
3897
|
draw();
|
|
3788
3898
|
return true;
|
|
3789
3899
|
}
|
|
3900
|
+
if (activeDrawingTool === "long-position" || activeDrawingTool === "short-position") {
|
|
3901
|
+
const isLong = activeDrawingTool === "long-position";
|
|
3902
|
+
const tick = getConfiguredTickSize();
|
|
3903
|
+
const priceOffset = tick > 0 ? tick * 20 : Math.abs(point.price) * 0.01 || 1;
|
|
3904
|
+
const width2 = Math.max(5, Math.round(xSpan * 0.15));
|
|
3905
|
+
const entryPrice = point.price;
|
|
3906
|
+
const targetPrice = isLong ? entryPrice + priceOffset : entryPrice - priceOffset;
|
|
3907
|
+
const stopPrice = isLong ? entryPrice - priceOffset : entryPrice + priceOffset;
|
|
3908
|
+
const defaults = getDrawingToolDefaults(activeDrawingTool);
|
|
3909
|
+
drawings.push(
|
|
3910
|
+
normalizeDrawingState({
|
|
3911
|
+
type: activeDrawingTool,
|
|
3912
|
+
points: [
|
|
3913
|
+
point,
|
|
3914
|
+
normalizeDrawingPoint(point.index, targetPrice),
|
|
3915
|
+
normalizeDrawingPoint(point.index, stopPrice),
|
|
3916
|
+
normalizeDrawingPoint(point.index + width2, entryPrice)
|
|
3917
|
+
],
|
|
3918
|
+
color: defaults.color ?? (isLong ? "#26a69a" : "#ef5350"),
|
|
3919
|
+
colors: defaults.colors ?? POSITION_DEFAULT_COLORS,
|
|
3920
|
+
style: defaults.style ?? "solid",
|
|
3921
|
+
width: defaults.width ?? 1
|
|
3922
|
+
})
|
|
3923
|
+
);
|
|
3924
|
+
emitDrawingsChange();
|
|
3925
|
+
draw();
|
|
3926
|
+
return true;
|
|
3927
|
+
}
|
|
3790
3928
|
return false;
|
|
3791
3929
|
};
|
|
3792
3930
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3810,6 +3948,22 @@ function createChart(element, options = {}) {
|
|
|
3810
3948
|
if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
|
|
3811
3949
|
return drawing;
|
|
3812
3950
|
}
|
|
3951
|
+
if (drawingDragState.target === "handle" && (drawing.type === "long-position" || drawing.type === "short-position")) {
|
|
3952
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
3953
|
+
const entry = pts[0];
|
|
3954
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
3955
|
+
if (pointIndex === 0) {
|
|
3956
|
+
pts[0] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3957
|
+
if (pts[3]) pts[3] = normalizeDrawingPoint(pts[3].index, currentPoint.price);
|
|
3958
|
+
} else if (pointIndex === 1 && pts[1]) {
|
|
3959
|
+
pts[1] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3960
|
+
} else if (pointIndex === 2 && pts[2]) {
|
|
3961
|
+
pts[2] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3962
|
+
} else if (pointIndex === 3 && pts[3]) {
|
|
3963
|
+
pts[3] = normalizeDrawingPoint(currentPoint.index, entry.price);
|
|
3964
|
+
}
|
|
3965
|
+
return { ...drawing, points: pts };
|
|
3966
|
+
}
|
|
3813
3967
|
if (drawingDragState.target === "handle") {
|
|
3814
3968
|
return {
|
|
3815
3969
|
...drawing,
|
|
@@ -4711,5 +4865,6 @@ function createChart(element, options = {}) {
|
|
|
4711
4865
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4712
4866
|
0 && (module.exports = {
|
|
4713
4867
|
FIB_DEFAULT_PALETTE,
|
|
4868
|
+
POSITION_DEFAULT_COLORS,
|
|
4714
4869
|
createChart
|
|
4715
4870
|
});
|
|
@@ -44,7 +44,7 @@ interface ChartOptions {
|
|
|
44
44
|
drawings?: DrawingObjectOptions[];
|
|
45
45
|
}
|
|
46
46
|
type IndicatorPane = "overlay" | "separate";
|
|
47
|
-
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension";
|
|
47
|
+
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position";
|
|
48
48
|
interface DrawingPoint {
|
|
49
49
|
index: number;
|
|
50
50
|
price: number;
|
|
@@ -67,6 +67,8 @@ interface DrawingObjectOptions {
|
|
|
67
67
|
label?: string;
|
|
68
68
|
locked?: boolean;
|
|
69
69
|
}
|
|
70
|
+
/** Default colors for position tools: [profit, loss, label text]. */
|
|
71
|
+
declare const POSITION_DEFAULT_COLORS: string[];
|
|
70
72
|
/** Default multi-color palette for fib-retracement levels. */
|
|
71
73
|
declare const FIB_DEFAULT_PALETTE: string[];
|
|
72
74
|
interface DrawingSelectEvent {
|
|
@@ -455,4 +457,4 @@ interface ViewportState {
|
|
|
455
457
|
}
|
|
456
458
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
457
459
|
|
|
458
|
-
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
|
460
|
+
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
var POSITION_DEFAULT_COLORS = ["#26a69a", "#ef5350", "#ffffff"];
|
|
2
3
|
var FIB_DEFAULT_PALETTE = [
|
|
3
4
|
"#787b86",
|
|
4
5
|
"#f23645",
|
|
@@ -2366,6 +2367,92 @@ function createChart(element, options = {}) {
|
|
|
2366
2367
|
}
|
|
2367
2368
|
}
|
|
2368
2369
|
}
|
|
2370
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
2371
|
+
const entry = drawing.points[0];
|
|
2372
|
+
const target = drawing.points[1];
|
|
2373
|
+
const stop = drawing.points[2];
|
|
2374
|
+
const right = drawing.points[3];
|
|
2375
|
+
if (entry && target && stop && right) {
|
|
2376
|
+
const leftX = xFromDrawingPoint(entry);
|
|
2377
|
+
const rightX = xFromDrawingPoint(right);
|
|
2378
|
+
const boxX0 = Math.min(leftX, rightX);
|
|
2379
|
+
const boxX1 = Math.max(leftX, rightX);
|
|
2380
|
+
const boxW = Math.max(1, boxX1 - boxX0);
|
|
2381
|
+
const entryY = yFromPrice(entry.price);
|
|
2382
|
+
const targetY = yFromPrice(target.price);
|
|
2383
|
+
const stopY = yFromPrice(stop.price);
|
|
2384
|
+
const isHex6 = (value) => !!value && /^#[0-9a-fA-F]{6}$/.test(value);
|
|
2385
|
+
const profitColor = isHex6(drawing.colors[0]) ? drawing.colors[0] : "#26a69a";
|
|
2386
|
+
const lossColor = isHex6(drawing.colors[1]) ? drawing.colors[1] : "#ef5350";
|
|
2387
|
+
const labelTextColor = isHex6(drawing.colors[2]) ? drawing.colors[2] : "#ffffff";
|
|
2388
|
+
const profitFill = hexToRgba(profitColor, 0.16);
|
|
2389
|
+
const lossFill = hexToRgba(lossColor, 0.16);
|
|
2390
|
+
const profitLine = profitColor;
|
|
2391
|
+
const lossLine = lossColor;
|
|
2392
|
+
ctx.save();
|
|
2393
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2394
|
+
ctx.fillStyle = profitFill;
|
|
2395
|
+
ctx.fillRect(boxX0, Math.min(entryY, targetY), boxW, Math.abs(targetY - entryY));
|
|
2396
|
+
ctx.fillStyle = lossFill;
|
|
2397
|
+
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2398
|
+
ctx.restore();
|
|
2399
|
+
ctx.save();
|
|
2400
|
+
ctx.setLineDash([]);
|
|
2401
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2402
|
+
ctx.strokeStyle = profitLine;
|
|
2403
|
+
ctx.beginPath();
|
|
2404
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2405
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2406
|
+
ctx.stroke();
|
|
2407
|
+
ctx.strokeStyle = lossLine;
|
|
2408
|
+
ctx.beginPath();
|
|
2409
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2410
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2411
|
+
ctx.stroke();
|
|
2412
|
+
ctx.strokeStyle = drawing.color;
|
|
2413
|
+
ctx.beginPath();
|
|
2414
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2415
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2416
|
+
ctx.stroke();
|
|
2417
|
+
ctx.restore();
|
|
2418
|
+
drawDrawingHandle(leftX, targetY, drawing.color);
|
|
2419
|
+
drawDrawingHandle(leftX, entryY, drawing.color);
|
|
2420
|
+
drawDrawingHandle(leftX, stopY, drawing.color);
|
|
2421
|
+
drawDrawingHandle(rightX, entryY, drawing.color);
|
|
2422
|
+
const tick = getConfiguredTickSize();
|
|
2423
|
+
const pctOf = (price) => {
|
|
2424
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2425
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2426
|
+
};
|
|
2427
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2428
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2429
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2430
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2431
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2432
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2433
|
+
const prevFont = ctx.font;
|
|
2434
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2435
|
+
const padding = 6;
|
|
2436
|
+
const textW = ctx.measureText(text).width;
|
|
2437
|
+
const pillW = textW + padding * 2;
|
|
2438
|
+
const pillH = 18;
|
|
2439
|
+
const pillX = centerX - pillW / 2;
|
|
2440
|
+
const pillY = centerY - pillH / 2;
|
|
2441
|
+
ctx.fillStyle = bg;
|
|
2442
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2443
|
+
ctx.fillStyle = labelTextColor;
|
|
2444
|
+
ctx.textAlign = "center";
|
|
2445
|
+
ctx.textBaseline = "middle";
|
|
2446
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2447
|
+
ctx.font = prevFont;
|
|
2448
|
+
};
|
|
2449
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2450
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2451
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2452
|
+
if (drawing.label) {
|
|
2453
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2369
2456
|
}
|
|
2370
2457
|
ctx.restore();
|
|
2371
2458
|
};
|
|
@@ -3562,6 +3649,28 @@ function createChart(element, options = {}) {
|
|
|
3562
3649
|
} else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
|
|
3563
3650
|
return { drawing, target: "line" };
|
|
3564
3651
|
}
|
|
3652
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
3653
|
+
const entry = drawing.points[0];
|
|
3654
|
+
const target = drawing.points[1];
|
|
3655
|
+
const stop = drawing.points[2];
|
|
3656
|
+
const right = drawing.points[3];
|
|
3657
|
+
if (!entry || !target || !stop || !right) continue;
|
|
3658
|
+
const leftX = canvasXFromDrawingPoint(entry);
|
|
3659
|
+
const rightX = canvasXFromDrawingPoint(right);
|
|
3660
|
+
const entryY = canvasYFromDrawingPrice(entry.price);
|
|
3661
|
+
const targetY = canvasYFromDrawingPrice(target.price);
|
|
3662
|
+
const stopY = canvasYFromDrawingPrice(stop.price);
|
|
3663
|
+
if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
3664
|
+
if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
3665
|
+
if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
3666
|
+
if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
3667
|
+
const x0 = Math.min(leftX, rightX);
|
|
3668
|
+
const x1 = Math.max(leftX, rightX);
|
|
3669
|
+
const yTop = Math.min(targetY, stopY, entryY);
|
|
3670
|
+
const yBot = Math.max(targetY, stopY, entryY);
|
|
3671
|
+
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
3672
|
+
return { drawing, target: "line" };
|
|
3673
|
+
}
|
|
3565
3674
|
}
|
|
3566
3675
|
}
|
|
3567
3676
|
return null;
|
|
@@ -3762,6 +3871,34 @@ function createChart(element, options = {}) {
|
|
|
3762
3871
|
draw();
|
|
3763
3872
|
return true;
|
|
3764
3873
|
}
|
|
3874
|
+
if (activeDrawingTool === "long-position" || activeDrawingTool === "short-position") {
|
|
3875
|
+
const isLong = activeDrawingTool === "long-position";
|
|
3876
|
+
const tick = getConfiguredTickSize();
|
|
3877
|
+
const priceOffset = tick > 0 ? tick * 20 : Math.abs(point.price) * 0.01 || 1;
|
|
3878
|
+
const width2 = Math.max(5, Math.round(xSpan * 0.15));
|
|
3879
|
+
const entryPrice = point.price;
|
|
3880
|
+
const targetPrice = isLong ? entryPrice + priceOffset : entryPrice - priceOffset;
|
|
3881
|
+
const stopPrice = isLong ? entryPrice - priceOffset : entryPrice + priceOffset;
|
|
3882
|
+
const defaults = getDrawingToolDefaults(activeDrawingTool);
|
|
3883
|
+
drawings.push(
|
|
3884
|
+
normalizeDrawingState({
|
|
3885
|
+
type: activeDrawingTool,
|
|
3886
|
+
points: [
|
|
3887
|
+
point,
|
|
3888
|
+
normalizeDrawingPoint(point.index, targetPrice),
|
|
3889
|
+
normalizeDrawingPoint(point.index, stopPrice),
|
|
3890
|
+
normalizeDrawingPoint(point.index + width2, entryPrice)
|
|
3891
|
+
],
|
|
3892
|
+
color: defaults.color ?? (isLong ? "#26a69a" : "#ef5350"),
|
|
3893
|
+
colors: defaults.colors ?? POSITION_DEFAULT_COLORS,
|
|
3894
|
+
style: defaults.style ?? "solid",
|
|
3895
|
+
width: defaults.width ?? 1
|
|
3896
|
+
})
|
|
3897
|
+
);
|
|
3898
|
+
emitDrawingsChange();
|
|
3899
|
+
draw();
|
|
3900
|
+
return true;
|
|
3901
|
+
}
|
|
3765
3902
|
return false;
|
|
3766
3903
|
};
|
|
3767
3904
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3785,6 +3922,22 @@ function createChart(element, options = {}) {
|
|
|
3785
3922
|
if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
|
|
3786
3923
|
return drawing;
|
|
3787
3924
|
}
|
|
3925
|
+
if (drawingDragState.target === "handle" && (drawing.type === "long-position" || drawing.type === "short-position")) {
|
|
3926
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
3927
|
+
const entry = pts[0];
|
|
3928
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
3929
|
+
if (pointIndex === 0) {
|
|
3930
|
+
pts[0] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3931
|
+
if (pts[3]) pts[3] = normalizeDrawingPoint(pts[3].index, currentPoint.price);
|
|
3932
|
+
} else if (pointIndex === 1 && pts[1]) {
|
|
3933
|
+
pts[1] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3934
|
+
} else if (pointIndex === 2 && pts[2]) {
|
|
3935
|
+
pts[2] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3936
|
+
} else if (pointIndex === 3 && pts[3]) {
|
|
3937
|
+
pts[3] = normalizeDrawingPoint(currentPoint.index, entry.price);
|
|
3938
|
+
}
|
|
3939
|
+
return { ...drawing, points: pts };
|
|
3940
|
+
}
|
|
3788
3941
|
if (drawingDragState.target === "handle") {
|
|
3789
3942
|
return {
|
|
3790
3943
|
...drawing,
|
|
@@ -4685,5 +4838,6 @@ function createChart(element, options = {}) {
|
|
|
4685
4838
|
}
|
|
4686
4839
|
export {
|
|
4687
4840
|
FIB_DEFAULT_PALETTE,
|
|
4841
|
+
POSITION_DEFAULT_COLORS,
|
|
4688
4842
|
createChart
|
|
4689
4843
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -21,9 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
FIB_DEFAULT_PALETTE: () => FIB_DEFAULT_PALETTE,
|
|
24
|
+
POSITION_DEFAULT_COLORS: () => POSITION_DEFAULT_COLORS,
|
|
24
25
|
createChart: () => createChart
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var POSITION_DEFAULT_COLORS = ["#26a69a", "#ef5350", "#ffffff"];
|
|
27
29
|
var FIB_DEFAULT_PALETTE = [
|
|
28
30
|
"#787b86",
|
|
29
31
|
"#f23645",
|
|
@@ -2391,6 +2393,92 @@ function createChart(element, options = {}) {
|
|
|
2391
2393
|
}
|
|
2392
2394
|
}
|
|
2393
2395
|
}
|
|
2396
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
2397
|
+
const entry = drawing.points[0];
|
|
2398
|
+
const target = drawing.points[1];
|
|
2399
|
+
const stop = drawing.points[2];
|
|
2400
|
+
const right = drawing.points[3];
|
|
2401
|
+
if (entry && target && stop && right) {
|
|
2402
|
+
const leftX = xFromDrawingPoint(entry);
|
|
2403
|
+
const rightX = xFromDrawingPoint(right);
|
|
2404
|
+
const boxX0 = Math.min(leftX, rightX);
|
|
2405
|
+
const boxX1 = Math.max(leftX, rightX);
|
|
2406
|
+
const boxW = Math.max(1, boxX1 - boxX0);
|
|
2407
|
+
const entryY = yFromPrice(entry.price);
|
|
2408
|
+
const targetY = yFromPrice(target.price);
|
|
2409
|
+
const stopY = yFromPrice(stop.price);
|
|
2410
|
+
const isHex6 = (value) => !!value && /^#[0-9a-fA-F]{6}$/.test(value);
|
|
2411
|
+
const profitColor = isHex6(drawing.colors[0]) ? drawing.colors[0] : "#26a69a";
|
|
2412
|
+
const lossColor = isHex6(drawing.colors[1]) ? drawing.colors[1] : "#ef5350";
|
|
2413
|
+
const labelTextColor = isHex6(drawing.colors[2]) ? drawing.colors[2] : "#ffffff";
|
|
2414
|
+
const profitFill = hexToRgba(profitColor, 0.16);
|
|
2415
|
+
const lossFill = hexToRgba(lossColor, 0.16);
|
|
2416
|
+
const profitLine = profitColor;
|
|
2417
|
+
const lossLine = lossColor;
|
|
2418
|
+
ctx.save();
|
|
2419
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2420
|
+
ctx.fillStyle = profitFill;
|
|
2421
|
+
ctx.fillRect(boxX0, Math.min(entryY, targetY), boxW, Math.abs(targetY - entryY));
|
|
2422
|
+
ctx.fillStyle = lossFill;
|
|
2423
|
+
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2424
|
+
ctx.restore();
|
|
2425
|
+
ctx.save();
|
|
2426
|
+
ctx.setLineDash([]);
|
|
2427
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2428
|
+
ctx.strokeStyle = profitLine;
|
|
2429
|
+
ctx.beginPath();
|
|
2430
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2431
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2432
|
+
ctx.stroke();
|
|
2433
|
+
ctx.strokeStyle = lossLine;
|
|
2434
|
+
ctx.beginPath();
|
|
2435
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2436
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2437
|
+
ctx.stroke();
|
|
2438
|
+
ctx.strokeStyle = drawing.color;
|
|
2439
|
+
ctx.beginPath();
|
|
2440
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2441
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2442
|
+
ctx.stroke();
|
|
2443
|
+
ctx.restore();
|
|
2444
|
+
drawDrawingHandle(leftX, targetY, drawing.color);
|
|
2445
|
+
drawDrawingHandle(leftX, entryY, drawing.color);
|
|
2446
|
+
drawDrawingHandle(leftX, stopY, drawing.color);
|
|
2447
|
+
drawDrawingHandle(rightX, entryY, drawing.color);
|
|
2448
|
+
const tick = getConfiguredTickSize();
|
|
2449
|
+
const pctOf = (price) => {
|
|
2450
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2451
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2452
|
+
};
|
|
2453
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2454
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2455
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2456
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2457
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2458
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2459
|
+
const prevFont = ctx.font;
|
|
2460
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2461
|
+
const padding = 6;
|
|
2462
|
+
const textW = ctx.measureText(text).width;
|
|
2463
|
+
const pillW = textW + padding * 2;
|
|
2464
|
+
const pillH = 18;
|
|
2465
|
+
const pillX = centerX - pillW / 2;
|
|
2466
|
+
const pillY = centerY - pillH / 2;
|
|
2467
|
+
ctx.fillStyle = bg;
|
|
2468
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2469
|
+
ctx.fillStyle = labelTextColor;
|
|
2470
|
+
ctx.textAlign = "center";
|
|
2471
|
+
ctx.textBaseline = "middle";
|
|
2472
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2473
|
+
ctx.font = prevFont;
|
|
2474
|
+
};
|
|
2475
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2476
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2477
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2478
|
+
if (drawing.label) {
|
|
2479
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2394
2482
|
}
|
|
2395
2483
|
ctx.restore();
|
|
2396
2484
|
};
|
|
@@ -3587,6 +3675,28 @@ function createChart(element, options = {}) {
|
|
|
3587
3675
|
} else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
|
|
3588
3676
|
return { drawing, target: "line" };
|
|
3589
3677
|
}
|
|
3678
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
3679
|
+
const entry = drawing.points[0];
|
|
3680
|
+
const target = drawing.points[1];
|
|
3681
|
+
const stop = drawing.points[2];
|
|
3682
|
+
const right = drawing.points[3];
|
|
3683
|
+
if (!entry || !target || !stop || !right) continue;
|
|
3684
|
+
const leftX = canvasXFromDrawingPoint(entry);
|
|
3685
|
+
const rightX = canvasXFromDrawingPoint(right);
|
|
3686
|
+
const entryY = canvasYFromDrawingPrice(entry.price);
|
|
3687
|
+
const targetY = canvasYFromDrawingPrice(target.price);
|
|
3688
|
+
const stopY = canvasYFromDrawingPrice(stop.price);
|
|
3689
|
+
if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
3690
|
+
if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
3691
|
+
if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
3692
|
+
if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
3693
|
+
const x0 = Math.min(leftX, rightX);
|
|
3694
|
+
const x1 = Math.max(leftX, rightX);
|
|
3695
|
+
const yTop = Math.min(targetY, stopY, entryY);
|
|
3696
|
+
const yBot = Math.max(targetY, stopY, entryY);
|
|
3697
|
+
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
3698
|
+
return { drawing, target: "line" };
|
|
3699
|
+
}
|
|
3590
3700
|
}
|
|
3591
3701
|
}
|
|
3592
3702
|
return null;
|
|
@@ -3787,6 +3897,34 @@ function createChart(element, options = {}) {
|
|
|
3787
3897
|
draw();
|
|
3788
3898
|
return true;
|
|
3789
3899
|
}
|
|
3900
|
+
if (activeDrawingTool === "long-position" || activeDrawingTool === "short-position") {
|
|
3901
|
+
const isLong = activeDrawingTool === "long-position";
|
|
3902
|
+
const tick = getConfiguredTickSize();
|
|
3903
|
+
const priceOffset = tick > 0 ? tick * 20 : Math.abs(point.price) * 0.01 || 1;
|
|
3904
|
+
const width2 = Math.max(5, Math.round(xSpan * 0.15));
|
|
3905
|
+
const entryPrice = point.price;
|
|
3906
|
+
const targetPrice = isLong ? entryPrice + priceOffset : entryPrice - priceOffset;
|
|
3907
|
+
const stopPrice = isLong ? entryPrice - priceOffset : entryPrice + priceOffset;
|
|
3908
|
+
const defaults = getDrawingToolDefaults(activeDrawingTool);
|
|
3909
|
+
drawings.push(
|
|
3910
|
+
normalizeDrawingState({
|
|
3911
|
+
type: activeDrawingTool,
|
|
3912
|
+
points: [
|
|
3913
|
+
point,
|
|
3914
|
+
normalizeDrawingPoint(point.index, targetPrice),
|
|
3915
|
+
normalizeDrawingPoint(point.index, stopPrice),
|
|
3916
|
+
normalizeDrawingPoint(point.index + width2, entryPrice)
|
|
3917
|
+
],
|
|
3918
|
+
color: defaults.color ?? (isLong ? "#26a69a" : "#ef5350"),
|
|
3919
|
+
colors: defaults.colors ?? POSITION_DEFAULT_COLORS,
|
|
3920
|
+
style: defaults.style ?? "solid",
|
|
3921
|
+
width: defaults.width ?? 1
|
|
3922
|
+
})
|
|
3923
|
+
);
|
|
3924
|
+
emitDrawingsChange();
|
|
3925
|
+
draw();
|
|
3926
|
+
return true;
|
|
3927
|
+
}
|
|
3790
3928
|
return false;
|
|
3791
3929
|
};
|
|
3792
3930
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3810,6 +3948,22 @@ function createChart(element, options = {}) {
|
|
|
3810
3948
|
if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
|
|
3811
3949
|
return drawing;
|
|
3812
3950
|
}
|
|
3951
|
+
if (drawingDragState.target === "handle" && (drawing.type === "long-position" || drawing.type === "short-position")) {
|
|
3952
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
3953
|
+
const entry = pts[0];
|
|
3954
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
3955
|
+
if (pointIndex === 0) {
|
|
3956
|
+
pts[0] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3957
|
+
if (pts[3]) pts[3] = normalizeDrawingPoint(pts[3].index, currentPoint.price);
|
|
3958
|
+
} else if (pointIndex === 1 && pts[1]) {
|
|
3959
|
+
pts[1] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3960
|
+
} else if (pointIndex === 2 && pts[2]) {
|
|
3961
|
+
pts[2] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3962
|
+
} else if (pointIndex === 3 && pts[3]) {
|
|
3963
|
+
pts[3] = normalizeDrawingPoint(currentPoint.index, entry.price);
|
|
3964
|
+
}
|
|
3965
|
+
return { ...drawing, points: pts };
|
|
3966
|
+
}
|
|
3813
3967
|
if (drawingDragState.target === "handle") {
|
|
3814
3968
|
return {
|
|
3815
3969
|
...drawing,
|
|
@@ -4711,5 +4865,6 @@ function createChart(element, options = {}) {
|
|
|
4711
4865
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4712
4866
|
0 && (module.exports = {
|
|
4713
4867
|
FIB_DEFAULT_PALETTE,
|
|
4868
|
+
POSITION_DEFAULT_COLORS,
|
|
4714
4869
|
createChart
|
|
4715
4870
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -44,7 +44,7 @@ interface ChartOptions {
|
|
|
44
44
|
drawings?: DrawingObjectOptions[];
|
|
45
45
|
}
|
|
46
46
|
type IndicatorPane = "overlay" | "separate";
|
|
47
|
-
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension";
|
|
47
|
+
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position";
|
|
48
48
|
interface DrawingPoint {
|
|
49
49
|
index: number;
|
|
50
50
|
price: number;
|
|
@@ -67,6 +67,8 @@ interface DrawingObjectOptions {
|
|
|
67
67
|
label?: string;
|
|
68
68
|
locked?: boolean;
|
|
69
69
|
}
|
|
70
|
+
/** Default colors for position tools: [profit, loss, label text]. */
|
|
71
|
+
declare const POSITION_DEFAULT_COLORS: string[];
|
|
70
72
|
/** Default multi-color palette for fib-retracement levels. */
|
|
71
73
|
declare const FIB_DEFAULT_PALETTE: string[];
|
|
72
74
|
interface DrawingSelectEvent {
|
|
@@ -455,4 +457,4 @@ interface ViewportState {
|
|
|
455
457
|
}
|
|
456
458
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
457
459
|
|
|
458
|
-
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
|
460
|
+
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
package/dist/index.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ interface ChartOptions {
|
|
|
44
44
|
drawings?: DrawingObjectOptions[];
|
|
45
45
|
}
|
|
46
46
|
type IndicatorPane = "overlay" | "separate";
|
|
47
|
-
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension";
|
|
47
|
+
type DrawingToolType = "horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position";
|
|
48
48
|
interface DrawingPoint {
|
|
49
49
|
index: number;
|
|
50
50
|
price: number;
|
|
@@ -67,6 +67,8 @@ interface DrawingObjectOptions {
|
|
|
67
67
|
label?: string;
|
|
68
68
|
locked?: boolean;
|
|
69
69
|
}
|
|
70
|
+
/** Default colors for position tools: [profit, loss, label text]. */
|
|
71
|
+
declare const POSITION_DEFAULT_COLORS: string[];
|
|
70
72
|
/** Default multi-color palette for fib-retracement levels. */
|
|
71
73
|
declare const FIB_DEFAULT_PALETTE: string[];
|
|
72
74
|
interface DrawingSelectEvent {
|
|
@@ -455,4 +457,4 @@ interface ViewportState {
|
|
|
455
457
|
}
|
|
456
458
|
declare function createChart(element: HTMLElement, options?: ChartOptions): ChartInstance;
|
|
457
459
|
|
|
458
|
-
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
|
460
|
+
export { type AxisOptions, type BuiltInIndicatorInfo, type ChartClickEvent, type ChartInstance, type ChartOptions, type CrosshairMoveEvent, type CrosshairOptions, type CrosshairPriceActionEvent, type DashPatternOptions, type DrawingDefaults, type DrawingHoverEvent, type DrawingObjectOptions, type DrawingPoint, type DrawingSelectEvent, type DrawingToolType, FIB_DEFAULT_PALETTE, type GridOptions, type IndicatorInstanceOptions, type IndicatorPane, type IndicatorPaneAxisOptions, type IndicatorPaneGuideLine, type IndicatorPaneRenderInfo, type IndicatorPaneValue, type IndicatorPaneValueLabel, type IndicatorPlugin, type IndicatorRenderContext, type LabelsOptions, type OhlcDataPoint, type OrderActionButton, type OrderActionEvent, type OrderLineOptions, POSITION_DEFAULT_COLORS, type PriceLineOptions, type TickerLineOptions, type ViewportState, type WatermarkOptions, createChart };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
var POSITION_DEFAULT_COLORS = ["#26a69a", "#ef5350", "#ffffff"];
|
|
2
3
|
var FIB_DEFAULT_PALETTE = [
|
|
3
4
|
"#787b86",
|
|
4
5
|
"#f23645",
|
|
@@ -2366,6 +2367,92 @@ function createChart(element, options = {}) {
|
|
|
2366
2367
|
}
|
|
2367
2368
|
}
|
|
2368
2369
|
}
|
|
2370
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
2371
|
+
const entry = drawing.points[0];
|
|
2372
|
+
const target = drawing.points[1];
|
|
2373
|
+
const stop = drawing.points[2];
|
|
2374
|
+
const right = drawing.points[3];
|
|
2375
|
+
if (entry && target && stop && right) {
|
|
2376
|
+
const leftX = xFromDrawingPoint(entry);
|
|
2377
|
+
const rightX = xFromDrawingPoint(right);
|
|
2378
|
+
const boxX0 = Math.min(leftX, rightX);
|
|
2379
|
+
const boxX1 = Math.max(leftX, rightX);
|
|
2380
|
+
const boxW = Math.max(1, boxX1 - boxX0);
|
|
2381
|
+
const entryY = yFromPrice(entry.price);
|
|
2382
|
+
const targetY = yFromPrice(target.price);
|
|
2383
|
+
const stopY = yFromPrice(stop.price);
|
|
2384
|
+
const isHex6 = (value) => !!value && /^#[0-9a-fA-F]{6}$/.test(value);
|
|
2385
|
+
const profitColor = isHex6(drawing.colors[0]) ? drawing.colors[0] : "#26a69a";
|
|
2386
|
+
const lossColor = isHex6(drawing.colors[1]) ? drawing.colors[1] : "#ef5350";
|
|
2387
|
+
const labelTextColor = isHex6(drawing.colors[2]) ? drawing.colors[2] : "#ffffff";
|
|
2388
|
+
const profitFill = hexToRgba(profitColor, 0.16);
|
|
2389
|
+
const lossFill = hexToRgba(lossColor, 0.16);
|
|
2390
|
+
const profitLine = profitColor;
|
|
2391
|
+
const lossLine = lossColor;
|
|
2392
|
+
ctx.save();
|
|
2393
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2394
|
+
ctx.fillStyle = profitFill;
|
|
2395
|
+
ctx.fillRect(boxX0, Math.min(entryY, targetY), boxW, Math.abs(targetY - entryY));
|
|
2396
|
+
ctx.fillStyle = lossFill;
|
|
2397
|
+
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2398
|
+
ctx.restore();
|
|
2399
|
+
ctx.save();
|
|
2400
|
+
ctx.setLineDash([]);
|
|
2401
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2402
|
+
ctx.strokeStyle = profitLine;
|
|
2403
|
+
ctx.beginPath();
|
|
2404
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2405
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2406
|
+
ctx.stroke();
|
|
2407
|
+
ctx.strokeStyle = lossLine;
|
|
2408
|
+
ctx.beginPath();
|
|
2409
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2410
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2411
|
+
ctx.stroke();
|
|
2412
|
+
ctx.strokeStyle = drawing.color;
|
|
2413
|
+
ctx.beginPath();
|
|
2414
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2415
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2416
|
+
ctx.stroke();
|
|
2417
|
+
ctx.restore();
|
|
2418
|
+
drawDrawingHandle(leftX, targetY, drawing.color);
|
|
2419
|
+
drawDrawingHandle(leftX, entryY, drawing.color);
|
|
2420
|
+
drawDrawingHandle(leftX, stopY, drawing.color);
|
|
2421
|
+
drawDrawingHandle(rightX, entryY, drawing.color);
|
|
2422
|
+
const tick = getConfiguredTickSize();
|
|
2423
|
+
const pctOf = (price) => {
|
|
2424
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2425
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2426
|
+
};
|
|
2427
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2428
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2429
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2430
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2431
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2432
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2433
|
+
const prevFont = ctx.font;
|
|
2434
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2435
|
+
const padding = 6;
|
|
2436
|
+
const textW = ctx.measureText(text).width;
|
|
2437
|
+
const pillW = textW + padding * 2;
|
|
2438
|
+
const pillH = 18;
|
|
2439
|
+
const pillX = centerX - pillW / 2;
|
|
2440
|
+
const pillY = centerY - pillH / 2;
|
|
2441
|
+
ctx.fillStyle = bg;
|
|
2442
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2443
|
+
ctx.fillStyle = labelTextColor;
|
|
2444
|
+
ctx.textAlign = "center";
|
|
2445
|
+
ctx.textBaseline = "middle";
|
|
2446
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2447
|
+
ctx.font = prevFont;
|
|
2448
|
+
};
|
|
2449
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2450
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2451
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2452
|
+
if (drawing.label) {
|
|
2453
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2369
2456
|
}
|
|
2370
2457
|
ctx.restore();
|
|
2371
2458
|
};
|
|
@@ -3562,6 +3649,28 @@ function createChart(element, options = {}) {
|
|
|
3562
3649
|
} else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
|
|
3563
3650
|
return { drawing, target: "line" };
|
|
3564
3651
|
}
|
|
3652
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
3653
|
+
const entry = drawing.points[0];
|
|
3654
|
+
const target = drawing.points[1];
|
|
3655
|
+
const stop = drawing.points[2];
|
|
3656
|
+
const right = drawing.points[3];
|
|
3657
|
+
if (!entry || !target || !stop || !right) continue;
|
|
3658
|
+
const leftX = canvasXFromDrawingPoint(entry);
|
|
3659
|
+
const rightX = canvasXFromDrawingPoint(right);
|
|
3660
|
+
const entryY = canvasYFromDrawingPrice(entry.price);
|
|
3661
|
+
const targetY = canvasYFromDrawingPrice(target.price);
|
|
3662
|
+
const stopY = canvasYFromDrawingPrice(stop.price);
|
|
3663
|
+
if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
3664
|
+
if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
3665
|
+
if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
3666
|
+
if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
3667
|
+
const x0 = Math.min(leftX, rightX);
|
|
3668
|
+
const x1 = Math.max(leftX, rightX);
|
|
3669
|
+
const yTop = Math.min(targetY, stopY, entryY);
|
|
3670
|
+
const yBot = Math.max(targetY, stopY, entryY);
|
|
3671
|
+
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
3672
|
+
return { drawing, target: "line" };
|
|
3673
|
+
}
|
|
3565
3674
|
}
|
|
3566
3675
|
}
|
|
3567
3676
|
return null;
|
|
@@ -3762,6 +3871,34 @@ function createChart(element, options = {}) {
|
|
|
3762
3871
|
draw();
|
|
3763
3872
|
return true;
|
|
3764
3873
|
}
|
|
3874
|
+
if (activeDrawingTool === "long-position" || activeDrawingTool === "short-position") {
|
|
3875
|
+
const isLong = activeDrawingTool === "long-position";
|
|
3876
|
+
const tick = getConfiguredTickSize();
|
|
3877
|
+
const priceOffset = tick > 0 ? tick * 20 : Math.abs(point.price) * 0.01 || 1;
|
|
3878
|
+
const width2 = Math.max(5, Math.round(xSpan * 0.15));
|
|
3879
|
+
const entryPrice = point.price;
|
|
3880
|
+
const targetPrice = isLong ? entryPrice + priceOffset : entryPrice - priceOffset;
|
|
3881
|
+
const stopPrice = isLong ? entryPrice - priceOffset : entryPrice + priceOffset;
|
|
3882
|
+
const defaults = getDrawingToolDefaults(activeDrawingTool);
|
|
3883
|
+
drawings.push(
|
|
3884
|
+
normalizeDrawingState({
|
|
3885
|
+
type: activeDrawingTool,
|
|
3886
|
+
points: [
|
|
3887
|
+
point,
|
|
3888
|
+
normalizeDrawingPoint(point.index, targetPrice),
|
|
3889
|
+
normalizeDrawingPoint(point.index, stopPrice),
|
|
3890
|
+
normalizeDrawingPoint(point.index + width2, entryPrice)
|
|
3891
|
+
],
|
|
3892
|
+
color: defaults.color ?? (isLong ? "#26a69a" : "#ef5350"),
|
|
3893
|
+
colors: defaults.colors ?? POSITION_DEFAULT_COLORS,
|
|
3894
|
+
style: defaults.style ?? "solid",
|
|
3895
|
+
width: defaults.width ?? 1
|
|
3896
|
+
})
|
|
3897
|
+
);
|
|
3898
|
+
emitDrawingsChange();
|
|
3899
|
+
draw();
|
|
3900
|
+
return true;
|
|
3901
|
+
}
|
|
3765
3902
|
return false;
|
|
3766
3903
|
};
|
|
3767
3904
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3785,6 +3922,22 @@ function createChart(element, options = {}) {
|
|
|
3785
3922
|
if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
|
|
3786
3923
|
return drawing;
|
|
3787
3924
|
}
|
|
3925
|
+
if (drawingDragState.target === "handle" && (drawing.type === "long-position" || drawing.type === "short-position")) {
|
|
3926
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
3927
|
+
const entry = pts[0];
|
|
3928
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
3929
|
+
if (pointIndex === 0) {
|
|
3930
|
+
pts[0] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3931
|
+
if (pts[3]) pts[3] = normalizeDrawingPoint(pts[3].index, currentPoint.price);
|
|
3932
|
+
} else if (pointIndex === 1 && pts[1]) {
|
|
3933
|
+
pts[1] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3934
|
+
} else if (pointIndex === 2 && pts[2]) {
|
|
3935
|
+
pts[2] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3936
|
+
} else if (pointIndex === 3 && pts[3]) {
|
|
3937
|
+
pts[3] = normalizeDrawingPoint(currentPoint.index, entry.price);
|
|
3938
|
+
}
|
|
3939
|
+
return { ...drawing, points: pts };
|
|
3940
|
+
}
|
|
3788
3941
|
if (drawingDragState.target === "handle") {
|
|
3789
3942
|
return {
|
|
3790
3943
|
...drawing,
|
|
@@ -4685,5 +4838,6 @@ function createChart(element, options = {}) {
|
|
|
4685
4838
|
}
|
|
4686
4839
|
export {
|
|
4687
4840
|
FIB_DEFAULT_PALETTE,
|
|
4841
|
+
POSITION_DEFAULT_COLORS,
|
|
4688
4842
|
createChart
|
|
4689
4843
|
};
|
package/docs/API.md
CHANGED
|
@@ -422,6 +422,7 @@ Drawings are user-created chart tools, separate from indicators. They are intera
|
|
|
422
422
|
- `ray`: two-point line that extends infinitely past the second point
|
|
423
423
|
- `fib-retracement`: two-point retracement with levels/bands
|
|
424
424
|
- `fib-extension`: trend-based three-point extension (click trend start, trend end, then the projection origin); levels project from the third point by the first→second move
|
|
425
|
+
- `long-position` / `short-position`: risk/reward forecasting box (single click drops a default box). Points are `[entry, target, stop, rightEdge]`; anchors are target/entry/stop on the left and a time-width handle on the right. Labels show price, % move, ticks, and risk/reward ratio. `color` sets the entry line; `colors` is `[profitColor, lossColor, labelTextColor]` (defaults `POSITION_DEFAULT_COLORS`).
|
|
425
426
|
- `points: DrawingPoint[]`
|
|
426
427
|
- `visible?: boolean`
|
|
427
428
|
- `color?: string`
|
|
@@ -449,6 +450,8 @@ chart.setActiveDrawingTool("trendline"); // first click starts, second cli
|
|
|
449
450
|
chart.setActiveDrawingTool("ray"); // first click starts, second click commits (extends past p2)
|
|
450
451
|
chart.setActiveDrawingTool("fib-retracement"); // first click starts, second click commits
|
|
451
452
|
chart.setActiveDrawingTool("fib-extension"); // three clicks: trend start, trend end, projection origin
|
|
453
|
+
chart.setActiveDrawingTool("long-position"); // single click drops a long risk/reward box
|
|
454
|
+
chart.setActiveDrawingTool("short-position"); // single click drops a short risk/reward box
|
|
452
455
|
chart.setActiveDrawingTool(null); // back to normal cursor/pan
|
|
453
456
|
```
|
|
454
457
|
|