hyperprop-charting-library 0.1.80 → 0.1.82
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 +97 -4
- package/dist/hyperprop-charting-library.d.ts +9 -1
- package/dist/hyperprop-charting-library.js +97 -4
- package/dist/index.cjs +97 -4
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +97 -4
- package/docs/API.md +2 -1
- package/package.json +1 -1
|
@@ -961,6 +961,13 @@ function createChart(element, options = {}) {
|
|
|
961
961
|
style: drawing.style ?? "dotted",
|
|
962
962
|
width: Math.max(1, Number(drawing.width) || 1),
|
|
963
963
|
locked: drawing.locked ?? false,
|
|
964
|
+
accountSize: Number(drawing.accountSize) || 0,
|
|
965
|
+
lotSize: Number(drawing.lotSize) || 1,
|
|
966
|
+
risk: Number(drawing.risk) || 0,
|
|
967
|
+
riskMode: drawing.riskMode === "amount" ? "amount" : "percent",
|
|
968
|
+
leverage: Number(drawing.leverage) || 1,
|
|
969
|
+
pointValue: Number(drawing.pointValue) || 1,
|
|
970
|
+
qtyPrecision: Number.isFinite(drawing.qtyPrecision) ? Math.max(0, Math.floor(Number(drawing.qtyPrecision))) : 0,
|
|
964
971
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
965
972
|
});
|
|
966
973
|
const serializeDrawing = (drawing) => ({
|
|
@@ -973,6 +980,13 @@ function createChart(element, options = {}) {
|
|
|
973
980
|
style: drawing.style,
|
|
974
981
|
width: drawing.width,
|
|
975
982
|
locked: drawing.locked,
|
|
983
|
+
accountSize: drawing.accountSize,
|
|
984
|
+
lotSize: drawing.lotSize,
|
|
985
|
+
risk: drawing.risk,
|
|
986
|
+
riskMode: drawing.riskMode,
|
|
987
|
+
leverage: drawing.leverage,
|
|
988
|
+
pointValue: drawing.pointValue,
|
|
989
|
+
qtyPrecision: drawing.qtyPrecision,
|
|
976
990
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
977
991
|
});
|
|
978
992
|
let indicators = (options.indicators ?? []).map((indicator) => normalizeIndicatorState(indicator));
|
|
@@ -982,6 +996,7 @@ function createChart(element, options = {}) {
|
|
|
982
996
|
let drawingDragState = null;
|
|
983
997
|
let drawingsChangeHandler = null;
|
|
984
998
|
let drawingSelectHandler = null;
|
|
999
|
+
let drawingDoubleClickHandler = null;
|
|
985
1000
|
let drawingHoverHandler = null;
|
|
986
1001
|
let lastHoveredDrawingId = null;
|
|
987
1002
|
let selectedDrawingId = null;
|
|
@@ -2429,6 +2444,40 @@ function createChart(element, options = {}) {
|
|
|
2429
2444
|
ctx.fillStyle = lossFill;
|
|
2430
2445
|
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2431
2446
|
ctx.restore();
|
|
2447
|
+
const isLongPosition = drawing.type === "long-position";
|
|
2448
|
+
const simStart = Math.max(0, Math.round(Math.min(entry.index, right.index)));
|
|
2449
|
+
const simEnd = Math.min(data.length - 1, Math.round(Math.max(entry.index, right.index)));
|
|
2450
|
+
let positionHit = null;
|
|
2451
|
+
for (let i = simStart; i <= simEnd; i += 1) {
|
|
2452
|
+
const bar = data[i];
|
|
2453
|
+
if (!bar) continue;
|
|
2454
|
+
const targetTouched = isLongPosition ? bar.h >= target.price : bar.l <= target.price;
|
|
2455
|
+
const stopTouched = isLongPosition ? bar.l <= stop.price : bar.h >= stop.price;
|
|
2456
|
+
if (stopTouched) {
|
|
2457
|
+
positionHit = { index: i, price: stop.price, profit: false };
|
|
2458
|
+
break;
|
|
2459
|
+
}
|
|
2460
|
+
if (targetTouched) {
|
|
2461
|
+
positionHit = { index: i, price: target.price, profit: true };
|
|
2462
|
+
break;
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
if (positionHit) {
|
|
2466
|
+
const hitX = clamp(xFromDrawingPoint({ index: positionHit.index, price: positionHit.price }), boxX0, boxX1);
|
|
2467
|
+
const exitY = yFromPrice(positionHit.price);
|
|
2468
|
+
ctx.save();
|
|
2469
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2470
|
+
ctx.fillStyle = hexToRgba(positionHit.profit ? profitColor : lossColor, 0.2);
|
|
2471
|
+
ctx.fillRect(boxX0, Math.min(entryY, exitY), Math.max(0, hitX - boxX0), Math.abs(exitY - entryY));
|
|
2472
|
+
ctx.setLineDash([5, 4]);
|
|
2473
|
+
ctx.lineWidth = 1;
|
|
2474
|
+
ctx.strokeStyle = hexToRgba("#787b86", 0.9);
|
|
2475
|
+
ctx.beginPath();
|
|
2476
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2477
|
+
ctx.lineTo(crisp(hitX), crisp(exitY));
|
|
2478
|
+
ctx.stroke();
|
|
2479
|
+
ctx.restore();
|
|
2480
|
+
}
|
|
2432
2481
|
if (isSelected) {
|
|
2433
2482
|
ctx.save();
|
|
2434
2483
|
ctx.setLineDash([]);
|
|
@@ -2463,6 +2512,14 @@ function createChart(element, options = {}) {
|
|
|
2463
2512
|
const stopDist = Math.abs(entry.price - stop.price);
|
|
2464
2513
|
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2465
2514
|
const cx = (boxX0 + boxX1) / 2;
|
|
2515
|
+
const effectivePointValue = (drawing.pointValue > 0 ? drawing.pointValue : 1) * (drawing.lotSize > 0 ? drawing.lotSize : 1);
|
|
2516
|
+
const riskAmount = drawing.riskMode === "amount" ? drawing.risk : drawing.accountSize * (drawing.risk / 100);
|
|
2517
|
+
const qtyRaw = riskAmount > 0 && stopDist > 0 ? riskAmount / (stopDist * effectivePointValue) : 0;
|
|
2518
|
+
const hasMoney = qtyRaw > 0 && Number.isFinite(qtyRaw);
|
|
2519
|
+
const qtyText = hasMoney ? qtyRaw.toFixed(Math.max(0, drawing.qtyPrecision)) : "";
|
|
2520
|
+
const formatAmount = (value) => Math.abs(value) >= 1e3 ? value.toFixed(0) : value.toFixed(2);
|
|
2521
|
+
const targetAmountText = hasMoney ? `, Amount: ${formatAmount(qtyRaw * targetDist * effectivePointValue)}` : "";
|
|
2522
|
+
const stopAmountText = hasMoney ? `, Amount: ${formatAmount(qtyRaw * stopDist * effectivePointValue)}` : "";
|
|
2466
2523
|
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2467
2524
|
const prevFont = ctx.font;
|
|
2468
2525
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
@@ -2480,9 +2537,20 @@ function createChart(element, options = {}) {
|
|
|
2480
2537
|
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2481
2538
|
ctx.font = prevFont;
|
|
2482
2539
|
};
|
|
2483
|
-
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2484
|
-
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2485
|
-
|
|
2540
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}${targetAmountText}`, cx, targetY, profitLine);
|
|
2541
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}${stopAmountText}`, cx, stopY, lossLine);
|
|
2542
|
+
let centerText;
|
|
2543
|
+
let centerBg;
|
|
2544
|
+
if (positionHit) {
|
|
2545
|
+
const pnl = positionHit.profit ? qtyRaw * targetDist * effectivePointValue : -(qtyRaw * stopDist * effectivePointValue);
|
|
2546
|
+
const pnlText = hasMoney ? `Closed P&L: ${formatAmount(pnl)}` : `Closed ${positionHit.profit ? "+" : "\u2212"}${formatPrice(Math.abs(positionHit.price - entry.price))}`;
|
|
2547
|
+
centerText = hasMoney ? `${pnlText}, Qty: ${qtyText} RR ${rr.toFixed(2)}` : `${pnlText} RR ${rr.toFixed(2)}`;
|
|
2548
|
+
centerBg = positionHit.profit ? profitLine : lossLine;
|
|
2549
|
+
} else {
|
|
2550
|
+
centerText = hasMoney ? `Entry ${formatPrice(entry.price)} Qty ${qtyText} RR ${rr.toFixed(2)}` : `Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`;
|
|
2551
|
+
centerBg = hexToRgba(drawing.color, 0.92);
|
|
2552
|
+
}
|
|
2553
|
+
drawPositionPill(centerText, cx, entryY, centerBg);
|
|
2486
2554
|
if (drawing.label) {
|
|
2487
2555
|
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2488
2556
|
}
|
|
@@ -3945,7 +4013,14 @@ function createChart(element, options = {}) {
|
|
|
3945
4013
|
color: defaults.color ?? (isLong ? "#26a69a" : "#ef5350"),
|
|
3946
4014
|
colors: defaults.colors ?? POSITION_DEFAULT_COLORS,
|
|
3947
4015
|
style: defaults.style ?? "solid",
|
|
3948
|
-
width: defaults.width ?? 1
|
|
4016
|
+
width: defaults.width ?? 1,
|
|
4017
|
+
...defaults.accountSize === void 0 ? {} : { accountSize: defaults.accountSize },
|
|
4018
|
+
...defaults.lotSize === void 0 ? {} : { lotSize: defaults.lotSize },
|
|
4019
|
+
...defaults.risk === void 0 ? {} : { risk: defaults.risk },
|
|
4020
|
+
...defaults.riskMode === void 0 ? {} : { riskMode: defaults.riskMode },
|
|
4021
|
+
...defaults.leverage === void 0 ? {} : { leverage: defaults.leverage },
|
|
4022
|
+
...defaults.pointValue === void 0 ? {} : { pointValue: defaults.pointValue },
|
|
4023
|
+
...defaults.qtyPrecision === void 0 ? {} : { qtyPrecision: defaults.qtyPrecision }
|
|
3949
4024
|
})
|
|
3950
4025
|
);
|
|
3951
4026
|
emitDrawingsChange();
|
|
@@ -4515,6 +4590,20 @@ function createChart(element, options = {}) {
|
|
|
4515
4590
|
if (region === "outside") {
|
|
4516
4591
|
return;
|
|
4517
4592
|
}
|
|
4593
|
+
if (region === "plot" && !activeDrawingTool) {
|
|
4594
|
+
const drawingHit = getDrawingHit(point.x, point.y);
|
|
4595
|
+
if (drawingHit) {
|
|
4596
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
4597
|
+
drawingDoubleClickHandler?.({
|
|
4598
|
+
drawing: serializeDrawing(drawingHit.drawing),
|
|
4599
|
+
target: drawingHit.target,
|
|
4600
|
+
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
|
|
4601
|
+
x: point.x,
|
|
4602
|
+
y: point.y
|
|
4603
|
+
});
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4518
4607
|
if (doubleClickAction === "placeLimitOrder") {
|
|
4519
4608
|
if (region !== "plot") {
|
|
4520
4609
|
return;
|
|
@@ -4825,6 +4914,9 @@ function createChart(element, options = {}) {
|
|
|
4825
4914
|
const onDrawingSelect = (handler) => {
|
|
4826
4915
|
drawingSelectHandler = handler;
|
|
4827
4916
|
};
|
|
4917
|
+
const onDrawingDoubleClick = (handler) => {
|
|
4918
|
+
drawingDoubleClickHandler = handler;
|
|
4919
|
+
};
|
|
4828
4920
|
const onDrawingHover = (handler) => {
|
|
4829
4921
|
drawingHoverHandler = handler;
|
|
4830
4922
|
};
|
|
@@ -4882,6 +4974,7 @@ function createChart(element, options = {}) {
|
|
|
4882
4974
|
clearDrawings,
|
|
4883
4975
|
onDrawingsChange,
|
|
4884
4976
|
onDrawingSelect,
|
|
4977
|
+
onDrawingDoubleClick,
|
|
4885
4978
|
setSelectedDrawing,
|
|
4886
4979
|
onDrawingHover,
|
|
4887
4980
|
setDrawingDefaults,
|
|
@@ -66,6 +66,13 @@ interface DrawingObjectOptions {
|
|
|
66
66
|
width?: number;
|
|
67
67
|
label?: string;
|
|
68
68
|
locked?: boolean;
|
|
69
|
+
accountSize?: number;
|
|
70
|
+
lotSize?: number;
|
|
71
|
+
risk?: number;
|
|
72
|
+
riskMode?: "percent" | "amount";
|
|
73
|
+
leverage?: number;
|
|
74
|
+
pointValue?: number;
|
|
75
|
+
qtyPrecision?: number;
|
|
69
76
|
}
|
|
70
77
|
/** Default colors for position tools: [profit, loss, label text]. */
|
|
71
78
|
declare const POSITION_DEFAULT_COLORS: string[];
|
|
@@ -84,7 +91,7 @@ interface DrawingHoverEvent {
|
|
|
84
91
|
x: number;
|
|
85
92
|
y: number;
|
|
86
93
|
}
|
|
87
|
-
type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "colors" | "style" | "width">>;
|
|
94
|
+
type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "colors" | "style" | "width" | "accountSize" | "lotSize" | "risk" | "riskMode" | "leverage" | "pointValue" | "qtyPrecision">>;
|
|
88
95
|
interface IndicatorInstanceOptions<TInputs extends Record<string, unknown> = Record<string, unknown>> {
|
|
89
96
|
id?: string;
|
|
90
97
|
type: string;
|
|
@@ -420,6 +427,7 @@ interface ChartInstance {
|
|
|
420
427
|
clearDrawings: () => void;
|
|
421
428
|
onDrawingsChange: (handler: ((drawings: DrawingObjectOptions[]) => void) | null) => void;
|
|
422
429
|
onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
430
|
+
onDrawingDoubleClick: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
423
431
|
onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
|
|
424
432
|
setDrawingDefaults: (tool: DrawingToolType, defaults: DrawingDefaults | null) => void;
|
|
425
433
|
setSelectedDrawing: (id: string | null) => void;
|
|
@@ -935,6 +935,13 @@ function createChart(element, options = {}) {
|
|
|
935
935
|
style: drawing.style ?? "dotted",
|
|
936
936
|
width: Math.max(1, Number(drawing.width) || 1),
|
|
937
937
|
locked: drawing.locked ?? false,
|
|
938
|
+
accountSize: Number(drawing.accountSize) || 0,
|
|
939
|
+
lotSize: Number(drawing.lotSize) || 1,
|
|
940
|
+
risk: Number(drawing.risk) || 0,
|
|
941
|
+
riskMode: drawing.riskMode === "amount" ? "amount" : "percent",
|
|
942
|
+
leverage: Number(drawing.leverage) || 1,
|
|
943
|
+
pointValue: Number(drawing.pointValue) || 1,
|
|
944
|
+
qtyPrecision: Number.isFinite(drawing.qtyPrecision) ? Math.max(0, Math.floor(Number(drawing.qtyPrecision))) : 0,
|
|
938
945
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
939
946
|
});
|
|
940
947
|
const serializeDrawing = (drawing) => ({
|
|
@@ -947,6 +954,13 @@ function createChart(element, options = {}) {
|
|
|
947
954
|
style: drawing.style,
|
|
948
955
|
width: drawing.width,
|
|
949
956
|
locked: drawing.locked,
|
|
957
|
+
accountSize: drawing.accountSize,
|
|
958
|
+
lotSize: drawing.lotSize,
|
|
959
|
+
risk: drawing.risk,
|
|
960
|
+
riskMode: drawing.riskMode,
|
|
961
|
+
leverage: drawing.leverage,
|
|
962
|
+
pointValue: drawing.pointValue,
|
|
963
|
+
qtyPrecision: drawing.qtyPrecision,
|
|
950
964
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
951
965
|
});
|
|
952
966
|
let indicators = (options.indicators ?? []).map((indicator) => normalizeIndicatorState(indicator));
|
|
@@ -956,6 +970,7 @@ function createChart(element, options = {}) {
|
|
|
956
970
|
let drawingDragState = null;
|
|
957
971
|
let drawingsChangeHandler = null;
|
|
958
972
|
let drawingSelectHandler = null;
|
|
973
|
+
let drawingDoubleClickHandler = null;
|
|
959
974
|
let drawingHoverHandler = null;
|
|
960
975
|
let lastHoveredDrawingId = null;
|
|
961
976
|
let selectedDrawingId = null;
|
|
@@ -2403,6 +2418,40 @@ function createChart(element, options = {}) {
|
|
|
2403
2418
|
ctx.fillStyle = lossFill;
|
|
2404
2419
|
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2405
2420
|
ctx.restore();
|
|
2421
|
+
const isLongPosition = drawing.type === "long-position";
|
|
2422
|
+
const simStart = Math.max(0, Math.round(Math.min(entry.index, right.index)));
|
|
2423
|
+
const simEnd = Math.min(data.length - 1, Math.round(Math.max(entry.index, right.index)));
|
|
2424
|
+
let positionHit = null;
|
|
2425
|
+
for (let i = simStart; i <= simEnd; i += 1) {
|
|
2426
|
+
const bar = data[i];
|
|
2427
|
+
if (!bar) continue;
|
|
2428
|
+
const targetTouched = isLongPosition ? bar.h >= target.price : bar.l <= target.price;
|
|
2429
|
+
const stopTouched = isLongPosition ? bar.l <= stop.price : bar.h >= stop.price;
|
|
2430
|
+
if (stopTouched) {
|
|
2431
|
+
positionHit = { index: i, price: stop.price, profit: false };
|
|
2432
|
+
break;
|
|
2433
|
+
}
|
|
2434
|
+
if (targetTouched) {
|
|
2435
|
+
positionHit = { index: i, price: target.price, profit: true };
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
if (positionHit) {
|
|
2440
|
+
const hitX = clamp(xFromDrawingPoint({ index: positionHit.index, price: positionHit.price }), boxX0, boxX1);
|
|
2441
|
+
const exitY = yFromPrice(positionHit.price);
|
|
2442
|
+
ctx.save();
|
|
2443
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2444
|
+
ctx.fillStyle = hexToRgba(positionHit.profit ? profitColor : lossColor, 0.2);
|
|
2445
|
+
ctx.fillRect(boxX0, Math.min(entryY, exitY), Math.max(0, hitX - boxX0), Math.abs(exitY - entryY));
|
|
2446
|
+
ctx.setLineDash([5, 4]);
|
|
2447
|
+
ctx.lineWidth = 1;
|
|
2448
|
+
ctx.strokeStyle = hexToRgba("#787b86", 0.9);
|
|
2449
|
+
ctx.beginPath();
|
|
2450
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2451
|
+
ctx.lineTo(crisp(hitX), crisp(exitY));
|
|
2452
|
+
ctx.stroke();
|
|
2453
|
+
ctx.restore();
|
|
2454
|
+
}
|
|
2406
2455
|
if (isSelected) {
|
|
2407
2456
|
ctx.save();
|
|
2408
2457
|
ctx.setLineDash([]);
|
|
@@ -2437,6 +2486,14 @@ function createChart(element, options = {}) {
|
|
|
2437
2486
|
const stopDist = Math.abs(entry.price - stop.price);
|
|
2438
2487
|
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2439
2488
|
const cx = (boxX0 + boxX1) / 2;
|
|
2489
|
+
const effectivePointValue = (drawing.pointValue > 0 ? drawing.pointValue : 1) * (drawing.lotSize > 0 ? drawing.lotSize : 1);
|
|
2490
|
+
const riskAmount = drawing.riskMode === "amount" ? drawing.risk : drawing.accountSize * (drawing.risk / 100);
|
|
2491
|
+
const qtyRaw = riskAmount > 0 && stopDist > 0 ? riskAmount / (stopDist * effectivePointValue) : 0;
|
|
2492
|
+
const hasMoney = qtyRaw > 0 && Number.isFinite(qtyRaw);
|
|
2493
|
+
const qtyText = hasMoney ? qtyRaw.toFixed(Math.max(0, drawing.qtyPrecision)) : "";
|
|
2494
|
+
const formatAmount = (value) => Math.abs(value) >= 1e3 ? value.toFixed(0) : value.toFixed(2);
|
|
2495
|
+
const targetAmountText = hasMoney ? `, Amount: ${formatAmount(qtyRaw * targetDist * effectivePointValue)}` : "";
|
|
2496
|
+
const stopAmountText = hasMoney ? `, Amount: ${formatAmount(qtyRaw * stopDist * effectivePointValue)}` : "";
|
|
2440
2497
|
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2441
2498
|
const prevFont = ctx.font;
|
|
2442
2499
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
@@ -2454,9 +2511,20 @@ function createChart(element, options = {}) {
|
|
|
2454
2511
|
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2455
2512
|
ctx.font = prevFont;
|
|
2456
2513
|
};
|
|
2457
|
-
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2458
|
-
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2459
|
-
|
|
2514
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}${targetAmountText}`, cx, targetY, profitLine);
|
|
2515
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}${stopAmountText}`, cx, stopY, lossLine);
|
|
2516
|
+
let centerText;
|
|
2517
|
+
let centerBg;
|
|
2518
|
+
if (positionHit) {
|
|
2519
|
+
const pnl = positionHit.profit ? qtyRaw * targetDist * effectivePointValue : -(qtyRaw * stopDist * effectivePointValue);
|
|
2520
|
+
const pnlText = hasMoney ? `Closed P&L: ${formatAmount(pnl)}` : `Closed ${positionHit.profit ? "+" : "\u2212"}${formatPrice(Math.abs(positionHit.price - entry.price))}`;
|
|
2521
|
+
centerText = hasMoney ? `${pnlText}, Qty: ${qtyText} RR ${rr.toFixed(2)}` : `${pnlText} RR ${rr.toFixed(2)}`;
|
|
2522
|
+
centerBg = positionHit.profit ? profitLine : lossLine;
|
|
2523
|
+
} else {
|
|
2524
|
+
centerText = hasMoney ? `Entry ${formatPrice(entry.price)} Qty ${qtyText} RR ${rr.toFixed(2)}` : `Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`;
|
|
2525
|
+
centerBg = hexToRgba(drawing.color, 0.92);
|
|
2526
|
+
}
|
|
2527
|
+
drawPositionPill(centerText, cx, entryY, centerBg);
|
|
2460
2528
|
if (drawing.label) {
|
|
2461
2529
|
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2462
2530
|
}
|
|
@@ -3919,7 +3987,14 @@ function createChart(element, options = {}) {
|
|
|
3919
3987
|
color: defaults.color ?? (isLong ? "#26a69a" : "#ef5350"),
|
|
3920
3988
|
colors: defaults.colors ?? POSITION_DEFAULT_COLORS,
|
|
3921
3989
|
style: defaults.style ?? "solid",
|
|
3922
|
-
width: defaults.width ?? 1
|
|
3990
|
+
width: defaults.width ?? 1,
|
|
3991
|
+
...defaults.accountSize === void 0 ? {} : { accountSize: defaults.accountSize },
|
|
3992
|
+
...defaults.lotSize === void 0 ? {} : { lotSize: defaults.lotSize },
|
|
3993
|
+
...defaults.risk === void 0 ? {} : { risk: defaults.risk },
|
|
3994
|
+
...defaults.riskMode === void 0 ? {} : { riskMode: defaults.riskMode },
|
|
3995
|
+
...defaults.leverage === void 0 ? {} : { leverage: defaults.leverage },
|
|
3996
|
+
...defaults.pointValue === void 0 ? {} : { pointValue: defaults.pointValue },
|
|
3997
|
+
...defaults.qtyPrecision === void 0 ? {} : { qtyPrecision: defaults.qtyPrecision }
|
|
3923
3998
|
})
|
|
3924
3999
|
);
|
|
3925
4000
|
emitDrawingsChange();
|
|
@@ -4489,6 +4564,20 @@ function createChart(element, options = {}) {
|
|
|
4489
4564
|
if (region === "outside") {
|
|
4490
4565
|
return;
|
|
4491
4566
|
}
|
|
4567
|
+
if (region === "plot" && !activeDrawingTool) {
|
|
4568
|
+
const drawingHit = getDrawingHit(point.x, point.y);
|
|
4569
|
+
if (drawingHit) {
|
|
4570
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
4571
|
+
drawingDoubleClickHandler?.({
|
|
4572
|
+
drawing: serializeDrawing(drawingHit.drawing),
|
|
4573
|
+
target: drawingHit.target,
|
|
4574
|
+
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
|
|
4575
|
+
x: point.x,
|
|
4576
|
+
y: point.y
|
|
4577
|
+
});
|
|
4578
|
+
return;
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4492
4581
|
if (doubleClickAction === "placeLimitOrder") {
|
|
4493
4582
|
if (region !== "plot") {
|
|
4494
4583
|
return;
|
|
@@ -4799,6 +4888,9 @@ function createChart(element, options = {}) {
|
|
|
4799
4888
|
const onDrawingSelect = (handler) => {
|
|
4800
4889
|
drawingSelectHandler = handler;
|
|
4801
4890
|
};
|
|
4891
|
+
const onDrawingDoubleClick = (handler) => {
|
|
4892
|
+
drawingDoubleClickHandler = handler;
|
|
4893
|
+
};
|
|
4802
4894
|
const onDrawingHover = (handler) => {
|
|
4803
4895
|
drawingHoverHandler = handler;
|
|
4804
4896
|
};
|
|
@@ -4856,6 +4948,7 @@ function createChart(element, options = {}) {
|
|
|
4856
4948
|
clearDrawings,
|
|
4857
4949
|
onDrawingsChange,
|
|
4858
4950
|
onDrawingSelect,
|
|
4951
|
+
onDrawingDoubleClick,
|
|
4859
4952
|
setSelectedDrawing,
|
|
4860
4953
|
onDrawingHover,
|
|
4861
4954
|
setDrawingDefaults,
|
package/dist/index.cjs
CHANGED
|
@@ -961,6 +961,13 @@ function createChart(element, options = {}) {
|
|
|
961
961
|
style: drawing.style ?? "dotted",
|
|
962
962
|
width: Math.max(1, Number(drawing.width) || 1),
|
|
963
963
|
locked: drawing.locked ?? false,
|
|
964
|
+
accountSize: Number(drawing.accountSize) || 0,
|
|
965
|
+
lotSize: Number(drawing.lotSize) || 1,
|
|
966
|
+
risk: Number(drawing.risk) || 0,
|
|
967
|
+
riskMode: drawing.riskMode === "amount" ? "amount" : "percent",
|
|
968
|
+
leverage: Number(drawing.leverage) || 1,
|
|
969
|
+
pointValue: Number(drawing.pointValue) || 1,
|
|
970
|
+
qtyPrecision: Number.isFinite(drawing.qtyPrecision) ? Math.max(0, Math.floor(Number(drawing.qtyPrecision))) : 0,
|
|
964
971
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
965
972
|
});
|
|
966
973
|
const serializeDrawing = (drawing) => ({
|
|
@@ -973,6 +980,13 @@ function createChart(element, options = {}) {
|
|
|
973
980
|
style: drawing.style,
|
|
974
981
|
width: drawing.width,
|
|
975
982
|
locked: drawing.locked,
|
|
983
|
+
accountSize: drawing.accountSize,
|
|
984
|
+
lotSize: drawing.lotSize,
|
|
985
|
+
risk: drawing.risk,
|
|
986
|
+
riskMode: drawing.riskMode,
|
|
987
|
+
leverage: drawing.leverage,
|
|
988
|
+
pointValue: drawing.pointValue,
|
|
989
|
+
qtyPrecision: drawing.qtyPrecision,
|
|
976
990
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
977
991
|
});
|
|
978
992
|
let indicators = (options.indicators ?? []).map((indicator) => normalizeIndicatorState(indicator));
|
|
@@ -982,6 +996,7 @@ function createChart(element, options = {}) {
|
|
|
982
996
|
let drawingDragState = null;
|
|
983
997
|
let drawingsChangeHandler = null;
|
|
984
998
|
let drawingSelectHandler = null;
|
|
999
|
+
let drawingDoubleClickHandler = null;
|
|
985
1000
|
let drawingHoverHandler = null;
|
|
986
1001
|
let lastHoveredDrawingId = null;
|
|
987
1002
|
let selectedDrawingId = null;
|
|
@@ -2429,6 +2444,40 @@ function createChart(element, options = {}) {
|
|
|
2429
2444
|
ctx.fillStyle = lossFill;
|
|
2430
2445
|
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2431
2446
|
ctx.restore();
|
|
2447
|
+
const isLongPosition = drawing.type === "long-position";
|
|
2448
|
+
const simStart = Math.max(0, Math.round(Math.min(entry.index, right.index)));
|
|
2449
|
+
const simEnd = Math.min(data.length - 1, Math.round(Math.max(entry.index, right.index)));
|
|
2450
|
+
let positionHit = null;
|
|
2451
|
+
for (let i = simStart; i <= simEnd; i += 1) {
|
|
2452
|
+
const bar = data[i];
|
|
2453
|
+
if (!bar) continue;
|
|
2454
|
+
const targetTouched = isLongPosition ? bar.h >= target.price : bar.l <= target.price;
|
|
2455
|
+
const stopTouched = isLongPosition ? bar.l <= stop.price : bar.h >= stop.price;
|
|
2456
|
+
if (stopTouched) {
|
|
2457
|
+
positionHit = { index: i, price: stop.price, profit: false };
|
|
2458
|
+
break;
|
|
2459
|
+
}
|
|
2460
|
+
if (targetTouched) {
|
|
2461
|
+
positionHit = { index: i, price: target.price, profit: true };
|
|
2462
|
+
break;
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
if (positionHit) {
|
|
2466
|
+
const hitX = clamp(xFromDrawingPoint({ index: positionHit.index, price: positionHit.price }), boxX0, boxX1);
|
|
2467
|
+
const exitY = yFromPrice(positionHit.price);
|
|
2468
|
+
ctx.save();
|
|
2469
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2470
|
+
ctx.fillStyle = hexToRgba(positionHit.profit ? profitColor : lossColor, 0.2);
|
|
2471
|
+
ctx.fillRect(boxX0, Math.min(entryY, exitY), Math.max(0, hitX - boxX0), Math.abs(exitY - entryY));
|
|
2472
|
+
ctx.setLineDash([5, 4]);
|
|
2473
|
+
ctx.lineWidth = 1;
|
|
2474
|
+
ctx.strokeStyle = hexToRgba("#787b86", 0.9);
|
|
2475
|
+
ctx.beginPath();
|
|
2476
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2477
|
+
ctx.lineTo(crisp(hitX), crisp(exitY));
|
|
2478
|
+
ctx.stroke();
|
|
2479
|
+
ctx.restore();
|
|
2480
|
+
}
|
|
2432
2481
|
if (isSelected) {
|
|
2433
2482
|
ctx.save();
|
|
2434
2483
|
ctx.setLineDash([]);
|
|
@@ -2463,6 +2512,14 @@ function createChart(element, options = {}) {
|
|
|
2463
2512
|
const stopDist = Math.abs(entry.price - stop.price);
|
|
2464
2513
|
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2465
2514
|
const cx = (boxX0 + boxX1) / 2;
|
|
2515
|
+
const effectivePointValue = (drawing.pointValue > 0 ? drawing.pointValue : 1) * (drawing.lotSize > 0 ? drawing.lotSize : 1);
|
|
2516
|
+
const riskAmount = drawing.riskMode === "amount" ? drawing.risk : drawing.accountSize * (drawing.risk / 100);
|
|
2517
|
+
const qtyRaw = riskAmount > 0 && stopDist > 0 ? riskAmount / (stopDist * effectivePointValue) : 0;
|
|
2518
|
+
const hasMoney = qtyRaw > 0 && Number.isFinite(qtyRaw);
|
|
2519
|
+
const qtyText = hasMoney ? qtyRaw.toFixed(Math.max(0, drawing.qtyPrecision)) : "";
|
|
2520
|
+
const formatAmount = (value) => Math.abs(value) >= 1e3 ? value.toFixed(0) : value.toFixed(2);
|
|
2521
|
+
const targetAmountText = hasMoney ? `, Amount: ${formatAmount(qtyRaw * targetDist * effectivePointValue)}` : "";
|
|
2522
|
+
const stopAmountText = hasMoney ? `, Amount: ${formatAmount(qtyRaw * stopDist * effectivePointValue)}` : "";
|
|
2466
2523
|
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2467
2524
|
const prevFont = ctx.font;
|
|
2468
2525
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
@@ -2480,9 +2537,20 @@ function createChart(element, options = {}) {
|
|
|
2480
2537
|
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2481
2538
|
ctx.font = prevFont;
|
|
2482
2539
|
};
|
|
2483
|
-
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2484
|
-
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2485
|
-
|
|
2540
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}${targetAmountText}`, cx, targetY, profitLine);
|
|
2541
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}${stopAmountText}`, cx, stopY, lossLine);
|
|
2542
|
+
let centerText;
|
|
2543
|
+
let centerBg;
|
|
2544
|
+
if (positionHit) {
|
|
2545
|
+
const pnl = positionHit.profit ? qtyRaw * targetDist * effectivePointValue : -(qtyRaw * stopDist * effectivePointValue);
|
|
2546
|
+
const pnlText = hasMoney ? `Closed P&L: ${formatAmount(pnl)}` : `Closed ${positionHit.profit ? "+" : "\u2212"}${formatPrice(Math.abs(positionHit.price - entry.price))}`;
|
|
2547
|
+
centerText = hasMoney ? `${pnlText}, Qty: ${qtyText} RR ${rr.toFixed(2)}` : `${pnlText} RR ${rr.toFixed(2)}`;
|
|
2548
|
+
centerBg = positionHit.profit ? profitLine : lossLine;
|
|
2549
|
+
} else {
|
|
2550
|
+
centerText = hasMoney ? `Entry ${formatPrice(entry.price)} Qty ${qtyText} RR ${rr.toFixed(2)}` : `Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`;
|
|
2551
|
+
centerBg = hexToRgba(drawing.color, 0.92);
|
|
2552
|
+
}
|
|
2553
|
+
drawPositionPill(centerText, cx, entryY, centerBg);
|
|
2486
2554
|
if (drawing.label) {
|
|
2487
2555
|
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2488
2556
|
}
|
|
@@ -3945,7 +4013,14 @@ function createChart(element, options = {}) {
|
|
|
3945
4013
|
color: defaults.color ?? (isLong ? "#26a69a" : "#ef5350"),
|
|
3946
4014
|
colors: defaults.colors ?? POSITION_DEFAULT_COLORS,
|
|
3947
4015
|
style: defaults.style ?? "solid",
|
|
3948
|
-
width: defaults.width ?? 1
|
|
4016
|
+
width: defaults.width ?? 1,
|
|
4017
|
+
...defaults.accountSize === void 0 ? {} : { accountSize: defaults.accountSize },
|
|
4018
|
+
...defaults.lotSize === void 0 ? {} : { lotSize: defaults.lotSize },
|
|
4019
|
+
...defaults.risk === void 0 ? {} : { risk: defaults.risk },
|
|
4020
|
+
...defaults.riskMode === void 0 ? {} : { riskMode: defaults.riskMode },
|
|
4021
|
+
...defaults.leverage === void 0 ? {} : { leverage: defaults.leverage },
|
|
4022
|
+
...defaults.pointValue === void 0 ? {} : { pointValue: defaults.pointValue },
|
|
4023
|
+
...defaults.qtyPrecision === void 0 ? {} : { qtyPrecision: defaults.qtyPrecision }
|
|
3949
4024
|
})
|
|
3950
4025
|
);
|
|
3951
4026
|
emitDrawingsChange();
|
|
@@ -4515,6 +4590,20 @@ function createChart(element, options = {}) {
|
|
|
4515
4590
|
if (region === "outside") {
|
|
4516
4591
|
return;
|
|
4517
4592
|
}
|
|
4593
|
+
if (region === "plot" && !activeDrawingTool) {
|
|
4594
|
+
const drawingHit = getDrawingHit(point.x, point.y);
|
|
4595
|
+
if (drawingHit) {
|
|
4596
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
4597
|
+
drawingDoubleClickHandler?.({
|
|
4598
|
+
drawing: serializeDrawing(drawingHit.drawing),
|
|
4599
|
+
target: drawingHit.target,
|
|
4600
|
+
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
|
|
4601
|
+
x: point.x,
|
|
4602
|
+
y: point.y
|
|
4603
|
+
});
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4518
4607
|
if (doubleClickAction === "placeLimitOrder") {
|
|
4519
4608
|
if (region !== "plot") {
|
|
4520
4609
|
return;
|
|
@@ -4825,6 +4914,9 @@ function createChart(element, options = {}) {
|
|
|
4825
4914
|
const onDrawingSelect = (handler) => {
|
|
4826
4915
|
drawingSelectHandler = handler;
|
|
4827
4916
|
};
|
|
4917
|
+
const onDrawingDoubleClick = (handler) => {
|
|
4918
|
+
drawingDoubleClickHandler = handler;
|
|
4919
|
+
};
|
|
4828
4920
|
const onDrawingHover = (handler) => {
|
|
4829
4921
|
drawingHoverHandler = handler;
|
|
4830
4922
|
};
|
|
@@ -4882,6 +4974,7 @@ function createChart(element, options = {}) {
|
|
|
4882
4974
|
clearDrawings,
|
|
4883
4975
|
onDrawingsChange,
|
|
4884
4976
|
onDrawingSelect,
|
|
4977
|
+
onDrawingDoubleClick,
|
|
4885
4978
|
setSelectedDrawing,
|
|
4886
4979
|
onDrawingHover,
|
|
4887
4980
|
setDrawingDefaults,
|
package/dist/index.d.cts
CHANGED
|
@@ -66,6 +66,13 @@ interface DrawingObjectOptions {
|
|
|
66
66
|
width?: number;
|
|
67
67
|
label?: string;
|
|
68
68
|
locked?: boolean;
|
|
69
|
+
accountSize?: number;
|
|
70
|
+
lotSize?: number;
|
|
71
|
+
risk?: number;
|
|
72
|
+
riskMode?: "percent" | "amount";
|
|
73
|
+
leverage?: number;
|
|
74
|
+
pointValue?: number;
|
|
75
|
+
qtyPrecision?: number;
|
|
69
76
|
}
|
|
70
77
|
/** Default colors for position tools: [profit, loss, label text]. */
|
|
71
78
|
declare const POSITION_DEFAULT_COLORS: string[];
|
|
@@ -84,7 +91,7 @@ interface DrawingHoverEvent {
|
|
|
84
91
|
x: number;
|
|
85
92
|
y: number;
|
|
86
93
|
}
|
|
87
|
-
type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "colors" | "style" | "width">>;
|
|
94
|
+
type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "colors" | "style" | "width" | "accountSize" | "lotSize" | "risk" | "riskMode" | "leverage" | "pointValue" | "qtyPrecision">>;
|
|
88
95
|
interface IndicatorInstanceOptions<TInputs extends Record<string, unknown> = Record<string, unknown>> {
|
|
89
96
|
id?: string;
|
|
90
97
|
type: string;
|
|
@@ -420,6 +427,7 @@ interface ChartInstance {
|
|
|
420
427
|
clearDrawings: () => void;
|
|
421
428
|
onDrawingsChange: (handler: ((drawings: DrawingObjectOptions[]) => void) | null) => void;
|
|
422
429
|
onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
430
|
+
onDrawingDoubleClick: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
423
431
|
onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
|
|
424
432
|
setDrawingDefaults: (tool: DrawingToolType, defaults: DrawingDefaults | null) => void;
|
|
425
433
|
setSelectedDrawing: (id: string | null) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -66,6 +66,13 @@ interface DrawingObjectOptions {
|
|
|
66
66
|
width?: number;
|
|
67
67
|
label?: string;
|
|
68
68
|
locked?: boolean;
|
|
69
|
+
accountSize?: number;
|
|
70
|
+
lotSize?: number;
|
|
71
|
+
risk?: number;
|
|
72
|
+
riskMode?: "percent" | "amount";
|
|
73
|
+
leverage?: number;
|
|
74
|
+
pointValue?: number;
|
|
75
|
+
qtyPrecision?: number;
|
|
69
76
|
}
|
|
70
77
|
/** Default colors for position tools: [profit, loss, label text]. */
|
|
71
78
|
declare const POSITION_DEFAULT_COLORS: string[];
|
|
@@ -84,7 +91,7 @@ interface DrawingHoverEvent {
|
|
|
84
91
|
x: number;
|
|
85
92
|
y: number;
|
|
86
93
|
}
|
|
87
|
-
type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "colors" | "style" | "width">>;
|
|
94
|
+
type DrawingDefaults = Partial<Pick<DrawingObjectOptions, "color" | "colors" | "style" | "width" | "accountSize" | "lotSize" | "risk" | "riskMode" | "leverage" | "pointValue" | "qtyPrecision">>;
|
|
88
95
|
interface IndicatorInstanceOptions<TInputs extends Record<string, unknown> = Record<string, unknown>> {
|
|
89
96
|
id?: string;
|
|
90
97
|
type: string;
|
|
@@ -420,6 +427,7 @@ interface ChartInstance {
|
|
|
420
427
|
clearDrawings: () => void;
|
|
421
428
|
onDrawingsChange: (handler: ((drawings: DrawingObjectOptions[]) => void) | null) => void;
|
|
422
429
|
onDrawingSelect: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
430
|
+
onDrawingDoubleClick: (handler: ((event: DrawingSelectEvent) => void) | null) => void;
|
|
423
431
|
onDrawingHover: (handler: ((event: DrawingHoverEvent) => void) | null) => void;
|
|
424
432
|
setDrawingDefaults: (tool: DrawingToolType, defaults: DrawingDefaults | null) => void;
|
|
425
433
|
setSelectedDrawing: (id: string | null) => void;
|
package/dist/index.js
CHANGED
|
@@ -935,6 +935,13 @@ function createChart(element, options = {}) {
|
|
|
935
935
|
style: drawing.style ?? "dotted",
|
|
936
936
|
width: Math.max(1, Number(drawing.width) || 1),
|
|
937
937
|
locked: drawing.locked ?? false,
|
|
938
|
+
accountSize: Number(drawing.accountSize) || 0,
|
|
939
|
+
lotSize: Number(drawing.lotSize) || 1,
|
|
940
|
+
risk: Number(drawing.risk) || 0,
|
|
941
|
+
riskMode: drawing.riskMode === "amount" ? "amount" : "percent",
|
|
942
|
+
leverage: Number(drawing.leverage) || 1,
|
|
943
|
+
pointValue: Number(drawing.pointValue) || 1,
|
|
944
|
+
qtyPrecision: Number.isFinite(drawing.qtyPrecision) ? Math.max(0, Math.floor(Number(drawing.qtyPrecision))) : 0,
|
|
938
945
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
939
946
|
});
|
|
940
947
|
const serializeDrawing = (drawing) => ({
|
|
@@ -947,6 +954,13 @@ function createChart(element, options = {}) {
|
|
|
947
954
|
style: drawing.style,
|
|
948
955
|
width: drawing.width,
|
|
949
956
|
locked: drawing.locked,
|
|
957
|
+
accountSize: drawing.accountSize,
|
|
958
|
+
lotSize: drawing.lotSize,
|
|
959
|
+
risk: drawing.risk,
|
|
960
|
+
riskMode: drawing.riskMode,
|
|
961
|
+
leverage: drawing.leverage,
|
|
962
|
+
pointValue: drawing.pointValue,
|
|
963
|
+
qtyPrecision: drawing.qtyPrecision,
|
|
950
964
|
...drawing.label === void 0 ? {} : { label: drawing.label }
|
|
951
965
|
});
|
|
952
966
|
let indicators = (options.indicators ?? []).map((indicator) => normalizeIndicatorState(indicator));
|
|
@@ -956,6 +970,7 @@ function createChart(element, options = {}) {
|
|
|
956
970
|
let drawingDragState = null;
|
|
957
971
|
let drawingsChangeHandler = null;
|
|
958
972
|
let drawingSelectHandler = null;
|
|
973
|
+
let drawingDoubleClickHandler = null;
|
|
959
974
|
let drawingHoverHandler = null;
|
|
960
975
|
let lastHoveredDrawingId = null;
|
|
961
976
|
let selectedDrawingId = null;
|
|
@@ -2403,6 +2418,40 @@ function createChart(element, options = {}) {
|
|
|
2403
2418
|
ctx.fillStyle = lossFill;
|
|
2404
2419
|
ctx.fillRect(boxX0, Math.min(entryY, stopY), boxW, Math.abs(stopY - entryY));
|
|
2405
2420
|
ctx.restore();
|
|
2421
|
+
const isLongPosition = drawing.type === "long-position";
|
|
2422
|
+
const simStart = Math.max(0, Math.round(Math.min(entry.index, right.index)));
|
|
2423
|
+
const simEnd = Math.min(data.length - 1, Math.round(Math.max(entry.index, right.index)));
|
|
2424
|
+
let positionHit = null;
|
|
2425
|
+
for (let i = simStart; i <= simEnd; i += 1) {
|
|
2426
|
+
const bar = data[i];
|
|
2427
|
+
if (!bar) continue;
|
|
2428
|
+
const targetTouched = isLongPosition ? bar.h >= target.price : bar.l <= target.price;
|
|
2429
|
+
const stopTouched = isLongPosition ? bar.l <= stop.price : bar.h >= stop.price;
|
|
2430
|
+
if (stopTouched) {
|
|
2431
|
+
positionHit = { index: i, price: stop.price, profit: false };
|
|
2432
|
+
break;
|
|
2433
|
+
}
|
|
2434
|
+
if (targetTouched) {
|
|
2435
|
+
positionHit = { index: i, price: target.price, profit: true };
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
if (positionHit) {
|
|
2440
|
+
const hitX = clamp(xFromDrawingPoint({ index: positionHit.index, price: positionHit.price }), boxX0, boxX1);
|
|
2441
|
+
const exitY = yFromPrice(positionHit.price);
|
|
2442
|
+
ctx.save();
|
|
2443
|
+
ctx.globalAlpha = draft ? 0.6 : 1;
|
|
2444
|
+
ctx.fillStyle = hexToRgba(positionHit.profit ? profitColor : lossColor, 0.2);
|
|
2445
|
+
ctx.fillRect(boxX0, Math.min(entryY, exitY), Math.max(0, hitX - boxX0), Math.abs(exitY - entryY));
|
|
2446
|
+
ctx.setLineDash([5, 4]);
|
|
2447
|
+
ctx.lineWidth = 1;
|
|
2448
|
+
ctx.strokeStyle = hexToRgba("#787b86", 0.9);
|
|
2449
|
+
ctx.beginPath();
|
|
2450
|
+
ctx.moveTo(crisp(boxX0), crisp(entryY));
|
|
2451
|
+
ctx.lineTo(crisp(hitX), crisp(exitY));
|
|
2452
|
+
ctx.stroke();
|
|
2453
|
+
ctx.restore();
|
|
2454
|
+
}
|
|
2406
2455
|
if (isSelected) {
|
|
2407
2456
|
ctx.save();
|
|
2408
2457
|
ctx.setLineDash([]);
|
|
@@ -2437,6 +2486,14 @@ function createChart(element, options = {}) {
|
|
|
2437
2486
|
const stopDist = Math.abs(entry.price - stop.price);
|
|
2438
2487
|
const rr = stopDist > 0 ? targetDist / stopDist : 0;
|
|
2439
2488
|
const cx = (boxX0 + boxX1) / 2;
|
|
2489
|
+
const effectivePointValue = (drawing.pointValue > 0 ? drawing.pointValue : 1) * (drawing.lotSize > 0 ? drawing.lotSize : 1);
|
|
2490
|
+
const riskAmount = drawing.riskMode === "amount" ? drawing.risk : drawing.accountSize * (drawing.risk / 100);
|
|
2491
|
+
const qtyRaw = riskAmount > 0 && stopDist > 0 ? riskAmount / (stopDist * effectivePointValue) : 0;
|
|
2492
|
+
const hasMoney = qtyRaw > 0 && Number.isFinite(qtyRaw);
|
|
2493
|
+
const qtyText = hasMoney ? qtyRaw.toFixed(Math.max(0, drawing.qtyPrecision)) : "";
|
|
2494
|
+
const formatAmount = (value) => Math.abs(value) >= 1e3 ? value.toFixed(0) : value.toFixed(2);
|
|
2495
|
+
const targetAmountText = hasMoney ? `, Amount: ${formatAmount(qtyRaw * targetDist * effectivePointValue)}` : "";
|
|
2496
|
+
const stopAmountText = hasMoney ? `, Amount: ${formatAmount(qtyRaw * stopDist * effectivePointValue)}` : "";
|
|
2440
2497
|
const drawPositionPill = (text, centerX, centerY, bg) => {
|
|
2441
2498
|
const prevFont = ctx.font;
|
|
2442
2499
|
ctx.font = `500 11px ${mergedOptions.fontFamily}`;
|
|
@@ -2454,9 +2511,20 @@ function createChart(element, options = {}) {
|
|
|
2454
2511
|
ctx.fillText(text, centerX, pillY + pillH / 2);
|
|
2455
2512
|
ctx.font = prevFont;
|
|
2456
2513
|
};
|
|
2457
|
-
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}`, cx, targetY, profitLine);
|
|
2458
|
-
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}`, cx, stopY, lossLine);
|
|
2459
|
-
|
|
2514
|
+
drawPositionPill(`Target ${formatPrice(target.price)} (${pctOf(target.price)})${ticksOf(target.price)}${targetAmountText}`, cx, targetY, profitLine);
|
|
2515
|
+
drawPositionPill(`Stop ${formatPrice(stop.price)} (${pctOf(stop.price)})${ticksOf(stop.price)}${stopAmountText}`, cx, stopY, lossLine);
|
|
2516
|
+
let centerText;
|
|
2517
|
+
let centerBg;
|
|
2518
|
+
if (positionHit) {
|
|
2519
|
+
const pnl = positionHit.profit ? qtyRaw * targetDist * effectivePointValue : -(qtyRaw * stopDist * effectivePointValue);
|
|
2520
|
+
const pnlText = hasMoney ? `Closed P&L: ${formatAmount(pnl)}` : `Closed ${positionHit.profit ? "+" : "\u2212"}${formatPrice(Math.abs(positionHit.price - entry.price))}`;
|
|
2521
|
+
centerText = hasMoney ? `${pnlText}, Qty: ${qtyText} RR ${rr.toFixed(2)}` : `${pnlText} RR ${rr.toFixed(2)}`;
|
|
2522
|
+
centerBg = positionHit.profit ? profitLine : lossLine;
|
|
2523
|
+
} else {
|
|
2524
|
+
centerText = hasMoney ? `Entry ${formatPrice(entry.price)} Qty ${qtyText} RR ${rr.toFixed(2)}` : `Entry ${formatPrice(entry.price)} RR ${rr.toFixed(2)}`;
|
|
2525
|
+
centerBg = hexToRgba(drawing.color, 0.92);
|
|
2526
|
+
}
|
|
2527
|
+
drawPositionPill(centerText, cx, entryY, centerBg);
|
|
2460
2528
|
if (drawing.label) {
|
|
2461
2529
|
drawDrawingLabel(drawing.label, cx, Math.min(targetY, stopY) - 4, drawing.color);
|
|
2462
2530
|
}
|
|
@@ -3919,7 +3987,14 @@ function createChart(element, options = {}) {
|
|
|
3919
3987
|
color: defaults.color ?? (isLong ? "#26a69a" : "#ef5350"),
|
|
3920
3988
|
colors: defaults.colors ?? POSITION_DEFAULT_COLORS,
|
|
3921
3989
|
style: defaults.style ?? "solid",
|
|
3922
|
-
width: defaults.width ?? 1
|
|
3990
|
+
width: defaults.width ?? 1,
|
|
3991
|
+
...defaults.accountSize === void 0 ? {} : { accountSize: defaults.accountSize },
|
|
3992
|
+
...defaults.lotSize === void 0 ? {} : { lotSize: defaults.lotSize },
|
|
3993
|
+
...defaults.risk === void 0 ? {} : { risk: defaults.risk },
|
|
3994
|
+
...defaults.riskMode === void 0 ? {} : { riskMode: defaults.riskMode },
|
|
3995
|
+
...defaults.leverage === void 0 ? {} : { leverage: defaults.leverage },
|
|
3996
|
+
...defaults.pointValue === void 0 ? {} : { pointValue: defaults.pointValue },
|
|
3997
|
+
...defaults.qtyPrecision === void 0 ? {} : { qtyPrecision: defaults.qtyPrecision }
|
|
3923
3998
|
})
|
|
3924
3999
|
);
|
|
3925
4000
|
emitDrawingsChange();
|
|
@@ -4489,6 +4564,20 @@ function createChart(element, options = {}) {
|
|
|
4489
4564
|
if (region === "outside") {
|
|
4490
4565
|
return;
|
|
4491
4566
|
}
|
|
4567
|
+
if (region === "plot" && !activeDrawingTool) {
|
|
4568
|
+
const drawingHit = getDrawingHit(point.x, point.y);
|
|
4569
|
+
if (drawingHit) {
|
|
4570
|
+
selectedDrawingId = drawingHit.drawing.id;
|
|
4571
|
+
drawingDoubleClickHandler?.({
|
|
4572
|
+
drawing: serializeDrawing(drawingHit.drawing),
|
|
4573
|
+
target: drawingHit.target,
|
|
4574
|
+
...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
|
|
4575
|
+
x: point.x,
|
|
4576
|
+
y: point.y
|
|
4577
|
+
});
|
|
4578
|
+
return;
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4492
4581
|
if (doubleClickAction === "placeLimitOrder") {
|
|
4493
4582
|
if (region !== "plot") {
|
|
4494
4583
|
return;
|
|
@@ -4799,6 +4888,9 @@ function createChart(element, options = {}) {
|
|
|
4799
4888
|
const onDrawingSelect = (handler) => {
|
|
4800
4889
|
drawingSelectHandler = handler;
|
|
4801
4890
|
};
|
|
4891
|
+
const onDrawingDoubleClick = (handler) => {
|
|
4892
|
+
drawingDoubleClickHandler = handler;
|
|
4893
|
+
};
|
|
4802
4894
|
const onDrawingHover = (handler) => {
|
|
4803
4895
|
drawingHoverHandler = handler;
|
|
4804
4896
|
};
|
|
@@ -4856,6 +4948,7 @@ function createChart(element, options = {}) {
|
|
|
4856
4948
|
clearDrawings,
|
|
4857
4949
|
onDrawingsChange,
|
|
4858
4950
|
onDrawingSelect,
|
|
4951
|
+
onDrawingDoubleClick,
|
|
4859
4952
|
setSelectedDrawing,
|
|
4860
4953
|
onDrawingHover,
|
|
4861
4954
|
setDrawingDefaults,
|
package/docs/API.md
CHANGED
|
@@ -422,7 +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
|
+
- `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`). Sizing inputs `accountSize`, `lotSize`, `risk`, `riskMode` (`"percent"|"amount"`), `leverage`, `pointValue`, `qtyPrecision` drive the Qty/Amount labels — set `pointValue` (contract $/point) from your app for real money values. The box also runs a trade simulation across the bars it covers: it shades the traversed region and draws a diagonal to the first bar that touches the target or stop, and the center label switches to "Closed P&L" (green if the target was hit first, red if the stop was hit first).
|
|
426
426
|
- `points: DrawingPoint[]`
|
|
427
427
|
- `visible?: boolean`
|
|
428
428
|
- `color?: string`
|
|
@@ -482,6 +482,7 @@ Use `getDrawings()` / `setDrawings()` for persistence.
|
|
|
482
482
|
- `fitContent(): void` (x-only fit, keeps y zoom)
|
|
483
483
|
- `resetViewport(): void` (fit x + reset y auto-scale)
|
|
484
484
|
- `setSelectedDrawing(id: string | null): void` (marks a drawing selected; only the selected/drafted drawing renders its handles, and position tools render their lines/labels only while selected)
|
|
485
|
+
- `onDrawingDoubleClick(handler): void` (fires when a drawing is double-clicked; use it to open a settings dialog, e.g. for position tools)
|
|
485
486
|
- `setActiveDrawingTool(tool: DrawingToolType | null): void` (`DrawingToolType` = `"horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement"`)
|
|
486
487
|
- `getActiveDrawingTool(): DrawingToolType | null`
|
|
487
488
|
- `setDrawings(drawings: DrawingObjectOptions[]): void`
|