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.
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.71",
3
+ "version": "0.1.73",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",