hyperprop-charting-library 0.1.71 → 0.1.72
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 +147 -0
- package/dist/hyperprop-charting-library.d.ts +1 -1
- package/dist/hyperprop-charting-library.js +147 -0
- package/dist/index.cjs +147 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +147 -0
- package/docs/API.md +3 -0
- package/package.json +1 -1
|
@@ -2391,6 +2391,88 @@ function createChart(element, options = {}) {
|
|
|
2391
2391
|
}
|
|
2392
2392
|
}
|
|
2393
2393
|
}
|
|
2394
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
2395
|
+
const entry = drawing.points[0];
|
|
2396
|
+
const target = drawing.points[1];
|
|
2397
|
+
const stop = drawing.points[2];
|
|
2398
|
+
const right = drawing.points[3];
|
|
2399
|
+
if (entry && target && stop && right) {
|
|
2400
|
+
const leftX = xFromDrawingPoint(entry);
|
|
2401
|
+
const rightX = xFromDrawingPoint(right);
|
|
2402
|
+
const boxX0 = Math.min(leftX, rightX);
|
|
2403
|
+
const boxX1 = Math.max(leftX, rightX);
|
|
2404
|
+
const boxW = Math.max(1, boxX1 - boxX0);
|
|
2405
|
+
const entryY = yFromPrice(entry.price);
|
|
2406
|
+
const targetY = yFromPrice(target.price);
|
|
2407
|
+
const stopY = yFromPrice(stop.price);
|
|
2408
|
+
const profitFill = "rgba(38,166,154,0.16)";
|
|
2409
|
+
const lossFill = "rgba(239,83,80,0.16)";
|
|
2410
|
+
const profitLine = "rgba(38,166,154,0.9)";
|
|
2411
|
+
const lossLine = "rgba(239,83,80,0.9)";
|
|
2412
|
+
ctx.save();
|
|
2413
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2414
|
+
ctx.fillStyle = profitFill;
|
|
2415
|
+
ctx.fillRect(boxX0, Math.min(entryY, targetY), boxW, Math.abs(targetY - entryY));
|
|
2416
|
+
ctx.fillStyle = lossFill;
|
|
2417
|
+
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2418
|
+
ctx.restore();
|
|
2419
|
+
ctx.save();
|
|
2420
|
+
ctx.setLineDash([]);
|
|
2421
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2422
|
+
ctx.strokeStyle = profitLine;
|
|
2423
|
+
ctx.beginPath();
|
|
2424
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2425
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2426
|
+
ctx.stroke();
|
|
2427
|
+
ctx.strokeStyle = lossLine;
|
|
2428
|
+
ctx.beginPath();
|
|
2429
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2430
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2431
|
+
ctx.stroke();
|
|
2432
|
+
ctx.strokeStyle = drawing.color;
|
|
2433
|
+
ctx.beginPath();
|
|
2434
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2435
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2436
|
+
ctx.stroke();
|
|
2437
|
+
ctx.restore();
|
|
2438
|
+
drawDrawingHandle(leftX, targetY, drawing.color);
|
|
2439
|
+
drawDrawingHandle(leftX, entryY, drawing.color);
|
|
2440
|
+
drawDrawingHandle(leftX, stopY, drawing.color);
|
|
2441
|
+
drawDrawingHandle(rightX, entryY, drawing.color);
|
|
2442
|
+
const tick = getConfiguredTickSize();
|
|
2443
|
+
const pctOf = (price) => {
|
|
2444
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2445
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2446
|
+
};
|
|
2447
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2448
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2449
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2450
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2451
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2452
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2453
|
+
const prevFont = ctx.font;
|
|
2454
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2455
|
+
const padding = 6;
|
|
2456
|
+
const textW = ctx.measureText(text).width;
|
|
2457
|
+
const pillW = textW + padding * 2;
|
|
2458
|
+
const pillH = 18;
|
|
2459
|
+
const pillX = centerX - pillW / 2;
|
|
2460
|
+
const pillY = centerY - pillH / 2;
|
|
2461
|
+
ctx.fillStyle = bg;
|
|
2462
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2463
|
+
ctx.fillStyle = "#ffffff";
|
|
2464
|
+
ctx.textAlign = "center";
|
|
2465
|
+
ctx.textBaseline = "middle";
|
|
2466
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2467
|
+
ctx.font = prevFont;
|
|
2468
|
+
};
|
|
2469
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2470
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2471
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2472
|
+
if (drawing.label) {
|
|
2473
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2394
2476
|
}
|
|
2395
2477
|
ctx.restore();
|
|
2396
2478
|
};
|
|
@@ -3587,6 +3669,28 @@ function createChart(element, options = {}) {
|
|
|
3587
3669
|
} else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
|
|
3588
3670
|
return { drawing, target: "line" };
|
|
3589
3671
|
}
|
|
3672
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
3673
|
+
const entry = drawing.points[0];
|
|
3674
|
+
const target = drawing.points[1];
|
|
3675
|
+
const stop = drawing.points[2];
|
|
3676
|
+
const right = drawing.points[3];
|
|
3677
|
+
if (!entry || !target || !stop || !right) continue;
|
|
3678
|
+
const leftX = canvasXFromDrawingPoint(entry);
|
|
3679
|
+
const rightX = canvasXFromDrawingPoint(right);
|
|
3680
|
+
const entryY = canvasYFromDrawingPrice(entry.price);
|
|
3681
|
+
const targetY = canvasYFromDrawingPrice(target.price);
|
|
3682
|
+
const stopY = canvasYFromDrawingPrice(stop.price);
|
|
3683
|
+
if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
3684
|
+
if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
3685
|
+
if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
3686
|
+
if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
3687
|
+
const x0 = Math.min(leftX, rightX);
|
|
3688
|
+
const x1 = Math.max(leftX, rightX);
|
|
3689
|
+
const yTop = Math.min(targetY, stopY, entryY);
|
|
3690
|
+
const yBot = Math.max(targetY, stopY, entryY);
|
|
3691
|
+
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
3692
|
+
return { drawing, target: "line" };
|
|
3693
|
+
}
|
|
3590
3694
|
}
|
|
3591
3695
|
}
|
|
3592
3696
|
return null;
|
|
@@ -3787,6 +3891,33 @@ function createChart(element, options = {}) {
|
|
|
3787
3891
|
draw();
|
|
3788
3892
|
return true;
|
|
3789
3893
|
}
|
|
3894
|
+
if (activeDrawingTool === "long-position" || activeDrawingTool === "short-position") {
|
|
3895
|
+
const isLong = activeDrawingTool === "long-position";
|
|
3896
|
+
const tick = getConfiguredTickSize();
|
|
3897
|
+
const priceOffset = tick > 0 ? tick * 20 : Math.abs(point.price) * 0.01 || 1;
|
|
3898
|
+
const width2 = Math.max(5, Math.round(xSpan * 0.15));
|
|
3899
|
+
const entryPrice = point.price;
|
|
3900
|
+
const targetPrice = isLong ? entryPrice + priceOffset : entryPrice - priceOffset;
|
|
3901
|
+
const stopPrice = isLong ? entryPrice - priceOffset : entryPrice + priceOffset;
|
|
3902
|
+
const defaults = getDrawingToolDefaults(activeDrawingTool);
|
|
3903
|
+
drawings.push(
|
|
3904
|
+
normalizeDrawingState({
|
|
3905
|
+
type: activeDrawingTool,
|
|
3906
|
+
points: [
|
|
3907
|
+
point,
|
|
3908
|
+
normalizeDrawingPoint(point.index, targetPrice),
|
|
3909
|
+
normalizeDrawingPoint(point.index, stopPrice),
|
|
3910
|
+
normalizeDrawingPoint(point.index + width2, entryPrice)
|
|
3911
|
+
],
|
|
3912
|
+
color: defaults.color ?? "#2563eb",
|
|
3913
|
+
style: defaults.style ?? "solid",
|
|
3914
|
+
width: defaults.width ?? 1
|
|
3915
|
+
})
|
|
3916
|
+
);
|
|
3917
|
+
emitDrawingsChange();
|
|
3918
|
+
draw();
|
|
3919
|
+
return true;
|
|
3920
|
+
}
|
|
3790
3921
|
return false;
|
|
3791
3922
|
};
|
|
3792
3923
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3810,6 +3941,22 @@ function createChart(element, options = {}) {
|
|
|
3810
3941
|
if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
|
|
3811
3942
|
return drawing;
|
|
3812
3943
|
}
|
|
3944
|
+
if (drawingDragState.target === "handle" && (drawing.type === "long-position" || drawing.type === "short-position")) {
|
|
3945
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
3946
|
+
const entry = pts[0];
|
|
3947
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
3948
|
+
if (pointIndex === 0) {
|
|
3949
|
+
pts[0] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3950
|
+
if (pts[3]) pts[3] = normalizeDrawingPoint(pts[3].index, currentPoint.price);
|
|
3951
|
+
} else if (pointIndex === 1 && pts[1]) {
|
|
3952
|
+
pts[1] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3953
|
+
} else if (pointIndex === 2 && pts[2]) {
|
|
3954
|
+
pts[2] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3955
|
+
} else if (pointIndex === 3 && pts[3]) {
|
|
3956
|
+
pts[3] = normalizeDrawingPoint(currentPoint.index, entry.price);
|
|
3957
|
+
}
|
|
3958
|
+
return { ...drawing, points: pts };
|
|
3959
|
+
}
|
|
3813
3960
|
if (drawingDragState.target === "handle") {
|
|
3814
3961
|
return {
|
|
3815
3962
|
...drawing,
|
|
@@ -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;
|
|
@@ -2366,6 +2366,88 @@ function createChart(element, options = {}) {
|
|
|
2366
2366
|
}
|
|
2367
2367
|
}
|
|
2368
2368
|
}
|
|
2369
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
2370
|
+
const entry = drawing.points[0];
|
|
2371
|
+
const target = drawing.points[1];
|
|
2372
|
+
const stop = drawing.points[2];
|
|
2373
|
+
const right = drawing.points[3];
|
|
2374
|
+
if (entry && target && stop && right) {
|
|
2375
|
+
const leftX = xFromDrawingPoint(entry);
|
|
2376
|
+
const rightX = xFromDrawingPoint(right);
|
|
2377
|
+
const boxX0 = Math.min(leftX, rightX);
|
|
2378
|
+
const boxX1 = Math.max(leftX, rightX);
|
|
2379
|
+
const boxW = Math.max(1, boxX1 - boxX0);
|
|
2380
|
+
const entryY = yFromPrice(entry.price);
|
|
2381
|
+
const targetY = yFromPrice(target.price);
|
|
2382
|
+
const stopY = yFromPrice(stop.price);
|
|
2383
|
+
const profitFill = "rgba(38,166,154,0.16)";
|
|
2384
|
+
const lossFill = "rgba(239,83,80,0.16)";
|
|
2385
|
+
const profitLine = "rgba(38,166,154,0.9)";
|
|
2386
|
+
const lossLine = "rgba(239,83,80,0.9)";
|
|
2387
|
+
ctx.save();
|
|
2388
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2389
|
+
ctx.fillStyle = profitFill;
|
|
2390
|
+
ctx.fillRect(boxX0, Math.min(entryY, targetY), boxW, Math.abs(targetY - entryY));
|
|
2391
|
+
ctx.fillStyle = lossFill;
|
|
2392
|
+
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2393
|
+
ctx.restore();
|
|
2394
|
+
ctx.save();
|
|
2395
|
+
ctx.setLineDash([]);
|
|
2396
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2397
|
+
ctx.strokeStyle = profitLine;
|
|
2398
|
+
ctx.beginPath();
|
|
2399
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2400
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2401
|
+
ctx.stroke();
|
|
2402
|
+
ctx.strokeStyle = lossLine;
|
|
2403
|
+
ctx.beginPath();
|
|
2404
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2405
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2406
|
+
ctx.stroke();
|
|
2407
|
+
ctx.strokeStyle = drawing.color;
|
|
2408
|
+
ctx.beginPath();
|
|
2409
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2410
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2411
|
+
ctx.stroke();
|
|
2412
|
+
ctx.restore();
|
|
2413
|
+
drawDrawingHandle(leftX, targetY, drawing.color);
|
|
2414
|
+
drawDrawingHandle(leftX, entryY, drawing.color);
|
|
2415
|
+
drawDrawingHandle(leftX, stopY, drawing.color);
|
|
2416
|
+
drawDrawingHandle(rightX, entryY, drawing.color);
|
|
2417
|
+
const tick = getConfiguredTickSize();
|
|
2418
|
+
const pctOf = (price) => {
|
|
2419
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2420
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2421
|
+
};
|
|
2422
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2423
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2424
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2425
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2426
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2427
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2428
|
+
const prevFont = ctx.font;
|
|
2429
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2430
|
+
const padding = 6;
|
|
2431
|
+
const textW = ctx.measureText(text).width;
|
|
2432
|
+
const pillW = textW + padding * 2;
|
|
2433
|
+
const pillH = 18;
|
|
2434
|
+
const pillX = centerX - pillW / 2;
|
|
2435
|
+
const pillY = centerY - pillH / 2;
|
|
2436
|
+
ctx.fillStyle = bg;
|
|
2437
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2438
|
+
ctx.fillStyle = "#ffffff";
|
|
2439
|
+
ctx.textAlign = "center";
|
|
2440
|
+
ctx.textBaseline = "middle";
|
|
2441
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2442
|
+
ctx.font = prevFont;
|
|
2443
|
+
};
|
|
2444
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2445
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2446
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2447
|
+
if (drawing.label) {
|
|
2448
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2369
2451
|
}
|
|
2370
2452
|
ctx.restore();
|
|
2371
2453
|
};
|
|
@@ -3562,6 +3644,28 @@ function createChart(element, options = {}) {
|
|
|
3562
3644
|
} else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
|
|
3563
3645
|
return { drawing, target: "line" };
|
|
3564
3646
|
}
|
|
3647
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
3648
|
+
const entry = drawing.points[0];
|
|
3649
|
+
const target = drawing.points[1];
|
|
3650
|
+
const stop = drawing.points[2];
|
|
3651
|
+
const right = drawing.points[3];
|
|
3652
|
+
if (!entry || !target || !stop || !right) continue;
|
|
3653
|
+
const leftX = canvasXFromDrawingPoint(entry);
|
|
3654
|
+
const rightX = canvasXFromDrawingPoint(right);
|
|
3655
|
+
const entryY = canvasYFromDrawingPrice(entry.price);
|
|
3656
|
+
const targetY = canvasYFromDrawingPrice(target.price);
|
|
3657
|
+
const stopY = canvasYFromDrawingPrice(stop.price);
|
|
3658
|
+
if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
3659
|
+
if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
3660
|
+
if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
3661
|
+
if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
3662
|
+
const x0 = Math.min(leftX, rightX);
|
|
3663
|
+
const x1 = Math.max(leftX, rightX);
|
|
3664
|
+
const yTop = Math.min(targetY, stopY, entryY);
|
|
3665
|
+
const yBot = Math.max(targetY, stopY, entryY);
|
|
3666
|
+
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
3667
|
+
return { drawing, target: "line" };
|
|
3668
|
+
}
|
|
3565
3669
|
}
|
|
3566
3670
|
}
|
|
3567
3671
|
return null;
|
|
@@ -3762,6 +3866,33 @@ function createChart(element, options = {}) {
|
|
|
3762
3866
|
draw();
|
|
3763
3867
|
return true;
|
|
3764
3868
|
}
|
|
3869
|
+
if (activeDrawingTool === "long-position" || activeDrawingTool === "short-position") {
|
|
3870
|
+
const isLong = activeDrawingTool === "long-position";
|
|
3871
|
+
const tick = getConfiguredTickSize();
|
|
3872
|
+
const priceOffset = tick > 0 ? tick * 20 : Math.abs(point.price) * 0.01 || 1;
|
|
3873
|
+
const width2 = Math.max(5, Math.round(xSpan * 0.15));
|
|
3874
|
+
const entryPrice = point.price;
|
|
3875
|
+
const targetPrice = isLong ? entryPrice + priceOffset : entryPrice - priceOffset;
|
|
3876
|
+
const stopPrice = isLong ? entryPrice - priceOffset : entryPrice + priceOffset;
|
|
3877
|
+
const defaults = getDrawingToolDefaults(activeDrawingTool);
|
|
3878
|
+
drawings.push(
|
|
3879
|
+
normalizeDrawingState({
|
|
3880
|
+
type: activeDrawingTool,
|
|
3881
|
+
points: [
|
|
3882
|
+
point,
|
|
3883
|
+
normalizeDrawingPoint(point.index, targetPrice),
|
|
3884
|
+
normalizeDrawingPoint(point.index, stopPrice),
|
|
3885
|
+
normalizeDrawingPoint(point.index + width2, entryPrice)
|
|
3886
|
+
],
|
|
3887
|
+
color: defaults.color ?? "#2563eb",
|
|
3888
|
+
style: defaults.style ?? "solid",
|
|
3889
|
+
width: defaults.width ?? 1
|
|
3890
|
+
})
|
|
3891
|
+
);
|
|
3892
|
+
emitDrawingsChange();
|
|
3893
|
+
draw();
|
|
3894
|
+
return true;
|
|
3895
|
+
}
|
|
3765
3896
|
return false;
|
|
3766
3897
|
};
|
|
3767
3898
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3785,6 +3916,22 @@ function createChart(element, options = {}) {
|
|
|
3785
3916
|
if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
|
|
3786
3917
|
return drawing;
|
|
3787
3918
|
}
|
|
3919
|
+
if (drawingDragState.target === "handle" && (drawing.type === "long-position" || drawing.type === "short-position")) {
|
|
3920
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
3921
|
+
const entry = pts[0];
|
|
3922
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
3923
|
+
if (pointIndex === 0) {
|
|
3924
|
+
pts[0] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3925
|
+
if (pts[3]) pts[3] = normalizeDrawingPoint(pts[3].index, currentPoint.price);
|
|
3926
|
+
} else if (pointIndex === 1 && pts[1]) {
|
|
3927
|
+
pts[1] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3928
|
+
} else if (pointIndex === 2 && pts[2]) {
|
|
3929
|
+
pts[2] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3930
|
+
} else if (pointIndex === 3 && pts[3]) {
|
|
3931
|
+
pts[3] = normalizeDrawingPoint(currentPoint.index, entry.price);
|
|
3932
|
+
}
|
|
3933
|
+
return { ...drawing, points: pts };
|
|
3934
|
+
}
|
|
3788
3935
|
if (drawingDragState.target === "handle") {
|
|
3789
3936
|
return {
|
|
3790
3937
|
...drawing,
|
package/dist/index.cjs
CHANGED
|
@@ -2391,6 +2391,88 @@ function createChart(element, options = {}) {
|
|
|
2391
2391
|
}
|
|
2392
2392
|
}
|
|
2393
2393
|
}
|
|
2394
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
2395
|
+
const entry = drawing.points[0];
|
|
2396
|
+
const target = drawing.points[1];
|
|
2397
|
+
const stop = drawing.points[2];
|
|
2398
|
+
const right = drawing.points[3];
|
|
2399
|
+
if (entry && target && stop && right) {
|
|
2400
|
+
const leftX = xFromDrawingPoint(entry);
|
|
2401
|
+
const rightX = xFromDrawingPoint(right);
|
|
2402
|
+
const boxX0 = Math.min(leftX, rightX);
|
|
2403
|
+
const boxX1 = Math.max(leftX, rightX);
|
|
2404
|
+
const boxW = Math.max(1, boxX1 - boxX0);
|
|
2405
|
+
const entryY = yFromPrice(entry.price);
|
|
2406
|
+
const targetY = yFromPrice(target.price);
|
|
2407
|
+
const stopY = yFromPrice(stop.price);
|
|
2408
|
+
const profitFill = "rgba(38,166,154,0.16)";
|
|
2409
|
+
const lossFill = "rgba(239,83,80,0.16)";
|
|
2410
|
+
const profitLine = "rgba(38,166,154,0.9)";
|
|
2411
|
+
const lossLine = "rgba(239,83,80,0.9)";
|
|
2412
|
+
ctx.save();
|
|
2413
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2414
|
+
ctx.fillStyle = profitFill;
|
|
2415
|
+
ctx.fillRect(boxX0, Math.min(entryY, targetY), boxW, Math.abs(targetY - entryY));
|
|
2416
|
+
ctx.fillStyle = lossFill;
|
|
2417
|
+
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2418
|
+
ctx.restore();
|
|
2419
|
+
ctx.save();
|
|
2420
|
+
ctx.setLineDash([]);
|
|
2421
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2422
|
+
ctx.strokeStyle = profitLine;
|
|
2423
|
+
ctx.beginPath();
|
|
2424
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2425
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2426
|
+
ctx.stroke();
|
|
2427
|
+
ctx.strokeStyle = lossLine;
|
|
2428
|
+
ctx.beginPath();
|
|
2429
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2430
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2431
|
+
ctx.stroke();
|
|
2432
|
+
ctx.strokeStyle = drawing.color;
|
|
2433
|
+
ctx.beginPath();
|
|
2434
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2435
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2436
|
+
ctx.stroke();
|
|
2437
|
+
ctx.restore();
|
|
2438
|
+
drawDrawingHandle(leftX, targetY, drawing.color);
|
|
2439
|
+
drawDrawingHandle(leftX, entryY, drawing.color);
|
|
2440
|
+
drawDrawingHandle(leftX, stopY, drawing.color);
|
|
2441
|
+
drawDrawingHandle(rightX, entryY, drawing.color);
|
|
2442
|
+
const tick = getConfiguredTickSize();
|
|
2443
|
+
const pctOf = (price) => {
|
|
2444
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2445
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2446
|
+
};
|
|
2447
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2448
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2449
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2450
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2451
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2452
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2453
|
+
const prevFont = ctx.font;
|
|
2454
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2455
|
+
const padding = 6;
|
|
2456
|
+
const textW = ctx.measureText(text).width;
|
|
2457
|
+
const pillW = textW + padding * 2;
|
|
2458
|
+
const pillH = 18;
|
|
2459
|
+
const pillX = centerX - pillW / 2;
|
|
2460
|
+
const pillY = centerY - pillH / 2;
|
|
2461
|
+
ctx.fillStyle = bg;
|
|
2462
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2463
|
+
ctx.fillStyle = "#ffffff";
|
|
2464
|
+
ctx.textAlign = "center";
|
|
2465
|
+
ctx.textBaseline = "middle";
|
|
2466
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2467
|
+
ctx.font = prevFont;
|
|
2468
|
+
};
|
|
2469
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2470
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2471
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2472
|
+
if (drawing.label) {
|
|
2473
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2394
2476
|
}
|
|
2395
2477
|
ctx.restore();
|
|
2396
2478
|
};
|
|
@@ -3587,6 +3669,28 @@ function createChart(element, options = {}) {
|
|
|
3587
3669
|
} else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
|
|
3588
3670
|
return { drawing, target: "line" };
|
|
3589
3671
|
}
|
|
3672
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
3673
|
+
const entry = drawing.points[0];
|
|
3674
|
+
const target = drawing.points[1];
|
|
3675
|
+
const stop = drawing.points[2];
|
|
3676
|
+
const right = drawing.points[3];
|
|
3677
|
+
if (!entry || !target || !stop || !right) continue;
|
|
3678
|
+
const leftX = canvasXFromDrawingPoint(entry);
|
|
3679
|
+
const rightX = canvasXFromDrawingPoint(right);
|
|
3680
|
+
const entryY = canvasYFromDrawingPrice(entry.price);
|
|
3681
|
+
const targetY = canvasYFromDrawingPrice(target.price);
|
|
3682
|
+
const stopY = canvasYFromDrawingPrice(stop.price);
|
|
3683
|
+
if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
3684
|
+
if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
3685
|
+
if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
3686
|
+
if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
3687
|
+
const x0 = Math.min(leftX, rightX);
|
|
3688
|
+
const x1 = Math.max(leftX, rightX);
|
|
3689
|
+
const yTop = Math.min(targetY, stopY, entryY);
|
|
3690
|
+
const yBot = Math.max(targetY, stopY, entryY);
|
|
3691
|
+
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
3692
|
+
return { drawing, target: "line" };
|
|
3693
|
+
}
|
|
3590
3694
|
}
|
|
3591
3695
|
}
|
|
3592
3696
|
return null;
|
|
@@ -3787,6 +3891,33 @@ function createChart(element, options = {}) {
|
|
|
3787
3891
|
draw();
|
|
3788
3892
|
return true;
|
|
3789
3893
|
}
|
|
3894
|
+
if (activeDrawingTool === "long-position" || activeDrawingTool === "short-position") {
|
|
3895
|
+
const isLong = activeDrawingTool === "long-position";
|
|
3896
|
+
const tick = getConfiguredTickSize();
|
|
3897
|
+
const priceOffset = tick > 0 ? tick * 20 : Math.abs(point.price) * 0.01 || 1;
|
|
3898
|
+
const width2 = Math.max(5, Math.round(xSpan * 0.15));
|
|
3899
|
+
const entryPrice = point.price;
|
|
3900
|
+
const targetPrice = isLong ? entryPrice + priceOffset : entryPrice - priceOffset;
|
|
3901
|
+
const stopPrice = isLong ? entryPrice - priceOffset : entryPrice + priceOffset;
|
|
3902
|
+
const defaults = getDrawingToolDefaults(activeDrawingTool);
|
|
3903
|
+
drawings.push(
|
|
3904
|
+
normalizeDrawingState({
|
|
3905
|
+
type: activeDrawingTool,
|
|
3906
|
+
points: [
|
|
3907
|
+
point,
|
|
3908
|
+
normalizeDrawingPoint(point.index, targetPrice),
|
|
3909
|
+
normalizeDrawingPoint(point.index, stopPrice),
|
|
3910
|
+
normalizeDrawingPoint(point.index + width2, entryPrice)
|
|
3911
|
+
],
|
|
3912
|
+
color: defaults.color ?? "#2563eb",
|
|
3913
|
+
style: defaults.style ?? "solid",
|
|
3914
|
+
width: defaults.width ?? 1
|
|
3915
|
+
})
|
|
3916
|
+
);
|
|
3917
|
+
emitDrawingsChange();
|
|
3918
|
+
draw();
|
|
3919
|
+
return true;
|
|
3920
|
+
}
|
|
3790
3921
|
return false;
|
|
3791
3922
|
};
|
|
3792
3923
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3810,6 +3941,22 @@ function createChart(element, options = {}) {
|
|
|
3810
3941
|
if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
|
|
3811
3942
|
return drawing;
|
|
3812
3943
|
}
|
|
3944
|
+
if (drawingDragState.target === "handle" && (drawing.type === "long-position" || drawing.type === "short-position")) {
|
|
3945
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
3946
|
+
const entry = pts[0];
|
|
3947
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
3948
|
+
if (pointIndex === 0) {
|
|
3949
|
+
pts[0] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3950
|
+
if (pts[3]) pts[3] = normalizeDrawingPoint(pts[3].index, currentPoint.price);
|
|
3951
|
+
} else if (pointIndex === 1 && pts[1]) {
|
|
3952
|
+
pts[1] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3953
|
+
} else if (pointIndex === 2 && pts[2]) {
|
|
3954
|
+
pts[2] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3955
|
+
} else if (pointIndex === 3 && pts[3]) {
|
|
3956
|
+
pts[3] = normalizeDrawingPoint(currentPoint.index, entry.price);
|
|
3957
|
+
}
|
|
3958
|
+
return { ...drawing, points: pts };
|
|
3959
|
+
}
|
|
3813
3960
|
if (drawingDragState.target === "handle") {
|
|
3814
3961
|
return {
|
|
3815
3962
|
...drawing,
|
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;
|
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;
|
package/dist/index.js
CHANGED
|
@@ -2366,6 +2366,88 @@ function createChart(element, options = {}) {
|
|
|
2366
2366
|
}
|
|
2367
2367
|
}
|
|
2368
2368
|
}
|
|
2369
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
2370
|
+
const entry = drawing.points[0];
|
|
2371
|
+
const target = drawing.points[1];
|
|
2372
|
+
const stop = drawing.points[2];
|
|
2373
|
+
const right = drawing.points[3];
|
|
2374
|
+
if (entry && target && stop && right) {
|
|
2375
|
+
const leftX = xFromDrawingPoint(entry);
|
|
2376
|
+
const rightX = xFromDrawingPoint(right);
|
|
2377
|
+
const boxX0 = Math.min(leftX, rightX);
|
|
2378
|
+
const boxX1 = Math.max(leftX, rightX);
|
|
2379
|
+
const boxW = Math.max(1, boxX1 - boxX0);
|
|
2380
|
+
const entryY = yFromPrice(entry.price);
|
|
2381
|
+
const targetY = yFromPrice(target.price);
|
|
2382
|
+
const stopY = yFromPrice(stop.price);
|
|
2383
|
+
const profitFill = "rgba(38,166,154,0.16)";
|
|
2384
|
+
const lossFill = "rgba(239,83,80,0.16)";
|
|
2385
|
+
const profitLine = "rgba(38,166,154,0.9)";
|
|
2386
|
+
const lossLine = "rgba(239,83,80,0.9)";
|
|
2387
|
+
ctx.save();
|
|
2388
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2389
|
+
ctx.fillStyle = profitFill;
|
|
2390
|
+
ctx.fillRect(boxX0, Math.min(entryY, targetY), boxW, Math.abs(targetY - entryY));
|
|
2391
|
+
ctx.fillStyle = lossFill;
|
|
2392
|
+
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2393
|
+
ctx.restore();
|
|
2394
|
+
ctx.save();
|
|
2395
|
+
ctx.setLineDash([]);
|
|
2396
|
+
ctx.lineWidth = Math.max(1, drawing.width);
|
|
2397
|
+
ctx.strokeStyle = profitLine;
|
|
2398
|
+
ctx.beginPath();
|
|
2399
|
+
ctx.moveTo(crisp(boxX0), crisp(targetY));
|
|
2400
|
+
ctx.lineTo(crisp(boxX1), crisp(targetY));
|
|
2401
|
+
ctx.stroke();
|
|
2402
|
+
ctx.strokeStyle = lossLine;
|
|
2403
|
+
ctx.beginPath();
|
|
2404
|
+
ctx.moveTo(crisp(boxX0), crisp(stopY));
|
|
2405
|
+
ctx.lineTo(crisp(boxX1), crisp(stopY));
|
|
2406
|
+
ctx.stroke();
|
|
2407
|
+
ctx.strokeStyle = drawing.color;
|
|
2408
|
+
ctx.beginPath();
|
|
2409
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2410
|
+
ctx.lineTo(crisp(boxX1), crisp(entryY));
|
|
2411
|
+
ctx.stroke();
|
|
2412
|
+
ctx.restore();
|
|
2413
|
+
drawDrawingHandle(leftX, targetY, drawing.color);
|
|
2414
|
+
drawDrawingHandle(leftX, entryY, drawing.color);
|
|
2415
|
+
drawDrawingHandle(leftX, stopY, drawing.color);
|
|
2416
|
+
drawDrawingHandle(rightX, entryY, drawing.color);
|
|
2417
|
+
const tick = getConfiguredTickSize();
|
|
2418
|
+
const pctOf = (price) => {
|
|
2419
|
+
if (!Number.isFinite(entry.price) || entry.price === 0) return "0.00%";
|
|
2420
|
+
return `${((price - entry.price) / Math.abs(entry.price) * 100).toFixed(2)}%`;
|
|
2421
|
+
};
|
|
2422
|
+
const ticksOf = (price) => tick > 0 ? ` ${Math.round(Math.abs(price - entry.price) / tick)}t` : "";
|
|
2423
|
+
const targetDist = Math.abs(target.price - entry.price);
|
|
2424
|
+
const stopDist = Math.abs(entry.price - stop.price);
|
|
2425
|
+
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2426
|
+
const cx = (boxX0 + boxX1) / 2;
|
|
2427
|
+
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2428
|
+
const prevFont = ctx.font;
|
|
2429
|
+
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
2430
|
+
const padding = 6;
|
|
2431
|
+
const textW = ctx.measureText(text).width;
|
|
2432
|
+
const pillW = textW + padding * 2;
|
|
2433
|
+
const pillH = 18;
|
|
2434
|
+
const pillX = centerX - pillW / 2;
|
|
2435
|
+
const pillY = centerY - pillH / 2;
|
|
2436
|
+
ctx.fillStyle = bg;
|
|
2437
|
+
fillRoundedRect(pillX, pillY, pillW, pillH, 4);
|
|
2438
|
+
ctx.fillStyle = "#ffffff";
|
|
2439
|
+
ctx.textAlign = "center";
|
|
2440
|
+
ctx.textBaseline = "middle";
|
|
2441
|
+
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2442
|
+
ctx.font = prevFont;
|
|
2443
|
+
};
|
|
2444
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2445
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2446
|
+
drawPositionPill(`Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`, cx, entryY, hexToRgba(drawing.color, 0.92));
|
|
2447
|
+
if (drawing.label) {
|
|
2448
|
+
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2369
2451
|
}
|
|
2370
2452
|
ctx.restore();
|
|
2371
2453
|
};
|
|
@@ -3562,6 +3644,28 @@ function createChart(element, options = {}) {
|
|
|
3562
3644
|
} else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
|
|
3563
3645
|
return { drawing, target: "line" };
|
|
3564
3646
|
}
|
|
3647
|
+
} else if (drawing.type === "long-position" || drawing.type === "short-position") {
|
|
3648
|
+
const entry = drawing.points[0];
|
|
3649
|
+
const target = drawing.points[1];
|
|
3650
|
+
const stop = drawing.points[2];
|
|
3651
|
+
const right = drawing.points[3];
|
|
3652
|
+
if (!entry || !target || !stop || !right) continue;
|
|
3653
|
+
const leftX = canvasXFromDrawingPoint(entry);
|
|
3654
|
+
const rightX = canvasXFromDrawingPoint(right);
|
|
3655
|
+
const entryY = canvasYFromDrawingPrice(entry.price);
|
|
3656
|
+
const targetY = canvasYFromDrawingPrice(target.price);
|
|
3657
|
+
const stopY = canvasYFromDrawingPrice(stop.price);
|
|
3658
|
+
if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
|
|
3659
|
+
if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
|
|
3660
|
+
if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
|
|
3661
|
+
if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
|
|
3662
|
+
const x0 = Math.min(leftX, rightX);
|
|
3663
|
+
const x1 = Math.max(leftX, rightX);
|
|
3664
|
+
const yTop = Math.min(targetY, stopY, entryY);
|
|
3665
|
+
const yBot = Math.max(targetY, stopY, entryY);
|
|
3666
|
+
if (x >= x0 && x <= x1 && y >= yTop && y <= yBot) {
|
|
3667
|
+
return { drawing, target: "line" };
|
|
3668
|
+
}
|
|
3565
3669
|
}
|
|
3566
3670
|
}
|
|
3567
3671
|
return null;
|
|
@@ -3762,6 +3866,33 @@ function createChart(element, options = {}) {
|
|
|
3762
3866
|
draw();
|
|
3763
3867
|
return true;
|
|
3764
3868
|
}
|
|
3869
|
+
if (activeDrawingTool === "long-position" || activeDrawingTool === "short-position") {
|
|
3870
|
+
const isLong = activeDrawingTool === "long-position";
|
|
3871
|
+
const tick = getConfiguredTickSize();
|
|
3872
|
+
const priceOffset = tick > 0 ? tick * 20 : Math.abs(point.price) * 0.01 || 1;
|
|
3873
|
+
const width2 = Math.max(5, Math.round(xSpan * 0.15));
|
|
3874
|
+
const entryPrice = point.price;
|
|
3875
|
+
const targetPrice = isLong ? entryPrice + priceOffset : entryPrice - priceOffset;
|
|
3876
|
+
const stopPrice = isLong ? entryPrice - priceOffset : entryPrice + priceOffset;
|
|
3877
|
+
const defaults = getDrawingToolDefaults(activeDrawingTool);
|
|
3878
|
+
drawings.push(
|
|
3879
|
+
normalizeDrawingState({
|
|
3880
|
+
type: activeDrawingTool,
|
|
3881
|
+
points: [
|
|
3882
|
+
point,
|
|
3883
|
+
normalizeDrawingPoint(point.index, targetPrice),
|
|
3884
|
+
normalizeDrawingPoint(point.index, stopPrice),
|
|
3885
|
+
normalizeDrawingPoint(point.index + width2, entryPrice)
|
|
3886
|
+
],
|
|
3887
|
+
color: defaults.color ?? "#2563eb",
|
|
3888
|
+
style: defaults.style ?? "solid",
|
|
3889
|
+
width: defaults.width ?? 1
|
|
3890
|
+
})
|
|
3891
|
+
);
|
|
3892
|
+
emitDrawingsChange();
|
|
3893
|
+
draw();
|
|
3894
|
+
return true;
|
|
3895
|
+
}
|
|
3765
3896
|
return false;
|
|
3766
3897
|
};
|
|
3767
3898
|
const setDrawingDefaults = (tool, defaults) => {
|
|
@@ -3785,6 +3916,22 @@ function createChart(element, options = {}) {
|
|
|
3785
3916
|
if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
|
|
3786
3917
|
return drawing;
|
|
3787
3918
|
}
|
|
3919
|
+
if (drawingDragState.target === "handle" && (drawing.type === "long-position" || drawing.type === "short-position")) {
|
|
3920
|
+
const pts = drawing.points.map((point) => ({ ...point }));
|
|
3921
|
+
const entry = pts[0];
|
|
3922
|
+
const pointIndex = drawingDragState.pointIndex ?? 0;
|
|
3923
|
+
if (pointIndex === 0) {
|
|
3924
|
+
pts[0] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3925
|
+
if (pts[3]) pts[3] = normalizeDrawingPoint(pts[3].index, currentPoint.price);
|
|
3926
|
+
} else if (pointIndex === 1 && pts[1]) {
|
|
3927
|
+
pts[1] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3928
|
+
} else if (pointIndex === 2 && pts[2]) {
|
|
3929
|
+
pts[2] = normalizeDrawingPoint(entry.index, currentPoint.price);
|
|
3930
|
+
} else if (pointIndex === 3 && pts[3]) {
|
|
3931
|
+
pts[3] = normalizeDrawingPoint(currentPoint.index, entry.price);
|
|
3932
|
+
}
|
|
3933
|
+
return { ...drawing, points: pts };
|
|
3934
|
+
}
|
|
3788
3935
|
if (drawingDragState.target === "handle") {
|
|
3789
3936
|
return {
|
|
3790
3937
|
...drawing,
|
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.
|
|
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
|
|