@luxalgo/vela 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/{DataProvider-wbtJ3-Sa.d.cts → DataProvider-Bw58CjYy.d.cts} +1 -1
  2. package/dist/{DataProvider-oH78tYXL.d.ts → DataProvider-Byo8pEv0.d.ts} +1 -1
  3. package/dist/{chunk-KWI3YDBW.js → chunk-ALTGHKIM.js} +584 -25
  4. package/dist/{chunk-TO3GIK7Z.js → chunk-AUOB7A5Y.js} +2 -2
  5. package/dist/{chunk-VHGACEHO.js → chunk-I2U4NZD2.js} +7 -1
  6. package/dist/{chunk-2IPXYM4B.js → chunk-MOJ4T26N.js} +88 -5
  7. package/dist/{chunk-HMHA733L.js → chunk-UNUVPQFW.js} +1924 -734
  8. package/dist/{chunk-G7B7ZCBF.js → chunk-WY5H4S4I.js} +1 -1
  9. package/dist/{contributions-BhOBRenr.d.cts → contributions-BY3zSYiZ.d.ts} +56 -5
  10. package/dist/{contributions-HGqnNb_P.d.ts → contributions-DOm40g5-.d.cts} +56 -5
  11. package/dist/index.cjs +2583 -828
  12. package/dist/index.d.cts +9 -15
  13. package/dist/index.d.ts +9 -15
  14. package/dist/index.js +4 -4
  15. package/dist/{options-lMUwmRwh.d.cts → options-CSXbjMM5.d.cts} +64 -4
  16. package/dist/{options-lMUwmRwh.d.ts → options-CSXbjMM5.d.ts} +64 -4
  17. package/dist/{plugin-Cr2Pj8q8.d.ts → plugin-BDxUhGO3.d.ts} +3 -3
  18. package/dist/{plugin-Do5J5xuF.d.cts → plugin-CiQVyETg.d.cts} +3 -3
  19. package/dist/plugin.cjs +555 -17
  20. package/dist/plugin.d.cts +4 -4
  21. package/dist/plugin.d.ts +4 -4
  22. package/dist/plugin.js +2 -2
  23. package/dist/providers/binance.d.cts +2 -2
  24. package/dist/providers/binance.d.ts +2 -2
  25. package/dist/providers/coinbase.d.cts +2 -2
  26. package/dist/providers/coinbase.d.ts +2 -2
  27. package/dist/providers/hyperliquid.d.cts +2 -2
  28. package/dist/providers/hyperliquid.d.ts +2 -2
  29. package/dist/{statusline-model-B-2xGlB_.d.cts → statusline-model-C9tYXuux.d.ts} +19 -4
  30. package/dist/{statusline-model-CY1wzxHg.d.ts → statusline-model-DUBX6Fib.d.cts} +19 -4
  31. package/dist/ui.d.cts +1 -1
  32. package/dist/ui.d.ts +1 -1
  33. package/dist/ui.js +3 -3
  34. package/dist/vela.global.js +2590 -835
  35. package/dist/vela.global.min.js +124 -54
  36. package/dist/widget.cjs +2807 -969
  37. package/dist/widget.d.cts +6 -6
  38. package/dist/widget.d.ts +6 -6
  39. package/dist/widget.js +7 -7
  40. package/dist/workspace.cjs +2807 -969
  41. package/dist/workspace.d.cts +19 -5
  42. package/dist/workspace.d.ts +19 -5
  43. package/dist/workspace.js +6 -6
  44. package/package.json +1 -1
package/dist/plugin.cjs CHANGED
@@ -453,6 +453,28 @@ function defaultText(value = "") {
453
453
  return { value, size: "normal", hAlign: "left", vAlign: "top" };
454
454
  }
455
455
 
456
+ // src/core/drawings/visibility.ts
457
+ var MIN = 6e4;
458
+ var HOUR = 60 * MIN;
459
+ var DAY = 24 * HOUR;
460
+ var TIMEFRAME_BANDS = [
461
+ { key: "s", label: "Sec", fromMs: 0 },
462
+ { key: "1", label: "1m", fromMs: MIN },
463
+ { key: "5", label: "5m", fromMs: 5 * MIN },
464
+ { key: "15", label: "15m", fromMs: 15 * MIN },
465
+ { key: "60", label: "1H", fromMs: HOUR },
466
+ { key: "240", label: "4H", fromMs: 4 * HOUR },
467
+ { key: "D", label: "D", fromMs: DAY },
468
+ { key: "W", label: "W", fromMs: 7 * DAY },
469
+ { key: "M", label: "M", fromMs: 28 * DAY }
470
+ ];
471
+ var BAND_KEYS = new Set(TIMEFRAME_BANDS.map((b) => b.key));
472
+ function sanitizeShowOn(v) {
473
+ if (!Array.isArray(v)) return void 0;
474
+ const wanted = new Set(v.filter((k) => typeof k === "string" && BAND_KEYS.has(k)));
475
+ return TIMEFRAME_BANDS.map((b) => b.key).filter((k) => wanted.has(k));
476
+ }
477
+
456
478
  // src/core/drawings/Drawing.ts
457
479
  var _Drawing = class _Drawing {
458
480
  // Public so the registry can construct concrete subclasses; `abstract` still
@@ -469,6 +491,7 @@ var _Drawing = class _Drawing {
469
491
  this.locked = init.locked ?? false;
470
492
  this.visible = init.visible ?? true;
471
493
  this.zIndex = init.zIndex ?? 0;
494
+ this.showOn = sanitizeShowOn(init.showOn);
472
495
  this.createdAt = init.createdAt ?? Date.now();
473
496
  if (init.props) this.readProps(init.props);
474
497
  }
@@ -555,7 +578,10 @@ var _Drawing = class _Drawing {
555
578
  visible: this.visible,
556
579
  zIndex: this.zIndex,
557
580
  createdAt: this.createdAt,
558
- props: this.writeProps()
581
+ props: this.writeProps(),
582
+ // Always present (undefined = every timeframe) so an edit can lift a limit; JSON
583
+ // drops the undefined value, so unlimited drawings save as before.
584
+ showOn: this.showOn ? [...this.showOn] : void 0
559
585
  };
560
586
  }
561
587
  /** Per-type extras to serialize into `props` (override in subclasses). */
@@ -690,9 +716,16 @@ var TrendLine = class extends Drawing {
690
716
  if (y1 == null || y2 == null) return null;
691
717
  return [proj.xOf(a.time), y1, proj.xOf(b.time), y2];
692
718
  }
719
+ /** Which ends run on past the anchors. */
720
+ extension() {
721
+ const { extendLeft: l, extendRight: r } = this.style;
722
+ return l && r ? "both" : l ? "left" : r ? "right" : "none";
723
+ }
693
724
  hitTest(px, py, proj, tol) {
694
725
  const p = this.pixels(proj);
695
- return p != null && distToSegment(px, py, p[0], p[1], p[2], p[3]) <= tol;
726
+ if (!p) return false;
727
+ const [x1, y1, x2, y2] = extendRay(p[0], p[1], p[2], p[3], this.extension(), proj.width, proj.height);
728
+ return distToSegment(px, py, x1, y1, x2, y2) <= tol;
696
729
  }
697
730
  handlePoints(proj) {
698
731
  const p = this.pixels(proj);
@@ -719,6 +752,8 @@ var TrendLine = class extends Drawing {
719
752
  fields: [
720
753
  ...LINE_FIELDS,
721
754
  { path: "style.arrowRight", label: "Arrow", kind: "boolean", group: "line" },
755
+ { path: "style.extendLeft", label: "Extend left", kind: "boolean", group: "line" },
756
+ { path: "style.extendRight", label: "Extend right", kind: "boolean", group: "line" },
722
757
  ...TEXT_FIELDS
723
758
  ]
724
759
  };
@@ -3178,6 +3213,14 @@ var FibRatios = class extends Drawing {
3178
3213
 
3179
3214
  // src/core/drawings/types/FibLevels.ts
3180
3215
  var FibLevels = class extends FibRatios {
3216
+ // `declare`d: the base constructor reads saved props before these could be initialized.
3217
+ constructor(init) {
3218
+ super(init);
3219
+ if (this.showRatios === void 0) this.showRatios = true;
3220
+ if (this.showPrices === void 0) this.showPrices = true;
3221
+ if (this.labelSide === void 0) this.labelSide = "left";
3222
+ if (this.background === void 0) this.background = true;
3223
+ }
3181
3224
  /** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
3182
3225
  levelPrice(ratio, p1, p2) {
3183
3226
  return p1 + ratio * (p2 - p1);
@@ -3189,8 +3232,8 @@ var FibLevels = class extends FibRatios {
3189
3232
  if (!a || !b) return null;
3190
3233
  const xa = proj.xOf(a.time);
3191
3234
  const xb = proj.xOf(b.time);
3192
- const x1 = Math.min(xa, xb);
3193
- const x2 = Math.max(xa, xb);
3235
+ const x1 = this.style.extendLeft ? 0 : Math.min(xa, xb);
3236
+ const x2 = this.style.extendRight ? proj.width : Math.max(xa, xb);
3194
3237
  const out = [];
3195
3238
  for (const lv of this.levels) {
3196
3239
  if (!lv.enabled) continue;
@@ -3204,6 +3247,7 @@ var FibLevels = class extends FibRatios {
3204
3247
  entryLines(proj) {
3205
3248
  const lines = this.levelLines(proj);
3206
3249
  if (!lines) return null;
3250
+ const right = this.labelSide === "right";
3207
3251
  return lines.map((l) => ({
3208
3252
  color: l.color,
3209
3253
  label: l.label,
@@ -3211,16 +3255,24 @@ var FibLevels = class extends FibRatios {
3211
3255
  y1: l.y,
3212
3256
  x2: l.x2,
3213
3257
  y2: l.y,
3214
- numberText: `${l.ratio} (${l.price.toFixed(2)})`,
3215
- numberX: l.x1 + 4,
3258
+ numberText: this.levelText(l.ratio, l.price),
3259
+ numberX: right ? l.x2 - 4 : l.x1 + 4,
3216
3260
  numberY: l.y - 7,
3217
- numberAlign: "left",
3261
+ numberAlign: right ? "right" : "left",
3218
3262
  labelX: (l.x1 + l.x2) / 2,
3219
3263
  labelY: l.y - 7
3220
3264
  }));
3221
3265
  }
3266
+ /** The text beside a level: its ratio, its price, both, or nothing. */
3267
+ levelText(ratio, price) {
3268
+ const p = price.toFixed(2);
3269
+ if (this.showRatios && this.showPrices) return `${ratio} (${p})`;
3270
+ if (this.showRatios) return String(ratio);
3271
+ return this.showPrices ? p : "";
3272
+ }
3222
3273
  fillBands(proj) {
3223
- const lines = this.levelLines(proj);
3274
+ if (!this.background) return [];
3275
+ const lines = this.levelLines(proj)?.sort((p, q) => p.ratio - q.ratio);
3224
3276
  if (!lines) return [];
3225
3277
  const bands = [];
3226
3278
  for (let i = 1; i < lines.length; i += 1) {
@@ -3238,6 +3290,26 @@ var FibLevels = class extends FibRatios {
3238
3290
  if (prices.length === 0) return null;
3239
3291
  return { min: Math.min(...prices), max: Math.max(...prices) };
3240
3292
  }
3293
+ schema() {
3294
+ const base = super.schema();
3295
+ return {
3296
+ fields: [
3297
+ ...base.fields,
3298
+ { path: "style.extendLeft", label: "Extend left", kind: "boolean", group: "line" },
3299
+ { path: "style.extendRight", label: "Extend right", kind: "boolean", group: "line" }
3300
+ ]
3301
+ };
3302
+ }
3303
+ writeProps() {
3304
+ return { ...super.writeProps(), showRatios: this.showRatios, showPrices: this.showPrices, labelSide: this.labelSide, background: this.background };
3305
+ }
3306
+ readProps(props) {
3307
+ super.readProps(props);
3308
+ if (typeof props.showRatios === "boolean") this.showRatios = props.showRatios;
3309
+ if (typeof props.showPrices === "boolean") this.showPrices = props.showPrices;
3310
+ if (props.labelSide === "left" || props.labelSide === "right") this.labelSide = props.labelSide;
3311
+ if (typeof props.background === "boolean") this.background = props.background;
3312
+ }
3241
3313
  };
3242
3314
 
3243
3315
  // src/core/drawings/levelPalette.ts
@@ -3283,14 +3355,31 @@ var RATIO_COLORS = [
3283
3355
  [0.75, LEVEL_BLUE],
3284
3356
  [0.786, LEVEL_BLUE],
3285
3357
  [1, LEVEL_ANCHOR],
3358
+ [0.705, LEVEL_AMBER],
3359
+ [0.886, LEVEL_PURPLE],
3286
3360
  [1.272, LEVEL_BLUE],
3287
3361
  [1.382, LEVEL_ORANGE],
3362
+ [1.414, LEVEL_AQUA],
3288
3363
  [1.618, LEVEL_RED],
3289
3364
  [2, LEVEL_ANCHOR],
3365
+ [2.272, LEVEL_VIOLET],
3290
3366
  [2.382, LEVEL_ORANGE],
3367
+ [2.414, LEVEL_PINK],
3291
3368
  [2.618, LEVEL_ORANGE],
3292
3369
  [3, LEVEL_ANCHOR],
3370
+ [3.272, LEVEL_CORAL],
3371
+ [3.618, LEVEL_PURPLE],
3293
3372
  [4.236, LEVEL_GREEN],
3373
+ [4.618, LEVEL_PINK],
3374
+ // Beyond the swing's end (targets): blues, deepening with distance.
3375
+ [-0.27, LEVEL_CYAN],
3376
+ [-0.272, LEVEL_CYAN],
3377
+ [-0.62, LEVEL_BLUE],
3378
+ [-0.618, LEVEL_BLUE],
3379
+ [-1, LEVEL_AQUA],
3380
+ [-1.5, LEVEL_VIOLET],
3381
+ [-2, LEVEL_PURPLE],
3382
+ [-2.5, LEVEL_PINK],
3294
3383
  [6.854, LEVEL_TEAL],
3295
3384
  [11.09, LEVEL_BLUE]
3296
3385
  ];
@@ -3317,8 +3406,36 @@ function cycleLevels(count, enabledCount = count) {
3317
3406
  }));
3318
3407
  }
3319
3408
 
3409
+ // src/core/drawings/fibPresets.ts
3410
+ var LADDER = [0, 0.236, 0.382, 0.5, 0.618, 0.705, 0.786, 0.886, 1, 1.272, 1.414, 1.618, 2, 2.272, 2.414, 2.618, 3, 3.272, 3.618, 4.236, 4.618, -0.272, -0.618, -1];
3411
+ var OTE_LADDER = [0, 0.236, 0.382, 0.5, 0.62, 0.705, 0.79, 0.886, 1, 1.272, 1.414, 1.618, 2, 2.618, 3, 3.618, 4.236, 4.618, -0.27, -0.62, -1, -1.5, -2, -2.5];
3412
+ function ladder(ratios, on, colors = {}) {
3413
+ return fibLevels(ratios.map((ratio) => ({ ratio, enabled: on.includes(ratio), color: colors[String(ratio)] })));
3414
+ }
3415
+ var FIB_PRESETS = [
3416
+ {
3417
+ key: "classic",
3418
+ label: "Classic",
3419
+ reverse: false,
3420
+ levels: ladder(LADDER, [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1])
3421
+ },
3422
+ {
3423
+ key: "extension",
3424
+ label: "Extension",
3425
+ reverse: true,
3426
+ levels: ladder(LADDER, [0, 1, 1.272, 1.414, 1.618, 2, 2.618, 3.618, 4.236])
3427
+ },
3428
+ {
3429
+ key: "ote",
3430
+ label: "OTE",
3431
+ reverse: false,
3432
+ // The entry zone reads as one band.
3433
+ levels: ladder(OTE_LADDER, [0, 0.5, 0.62, 0.705, 0.79, 1, -0.27, -0.62], { "0.62": LEVEL_AMBER, "0.79": LEVEL_AMBER })
3434
+ }
3435
+ ];
3436
+
3320
3437
  // src/core/drawings/types/FibRetracement.ts
3321
- var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
3438
+ var LEVELS = FIB_PRESETS[0].levels;
3322
3439
  var FibRetracement = class extends FibLevels {
3323
3440
  constructor(init) {
3324
3441
  super(init);
@@ -3341,7 +3458,11 @@ var FibRetracement = class extends FibLevels {
3341
3458
  };
3342
3459
 
3343
3460
  // src/core/drawings/types/FibExtension.ts
3344
- var LEVELS2 = fibLevels([0, 0.382, 0.618, 1, 1.272, 1.618, { ratio: 2.618, color: LEVEL_PURPLE }]);
3461
+ var LEVELS2 = ladder(
3462
+ [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.272, 1.414, 1.618, 2, 2.272, 2.414, 2.618, 3, 3.272, 3.618, 4.236, 4.618],
3463
+ [0, 0.382, 0.618, 1, 1.272, 1.618, 2.618],
3464
+ { "2.618": LEVEL_PURPLE }
3465
+ );
3345
3466
  var FibExtension = class extends FibLevels {
3346
3467
  constructor() {
3347
3468
  super(...arguments);
@@ -4919,20 +5040,20 @@ function signed(n) {
4919
5040
  }
4920
5041
  function formatDuration(ms) {
4921
5042
  const m = Math.abs(ms);
4922
- const DAY = 864e5;
5043
+ const DAY2 = 864e5;
4923
5044
  const HR = 36e5;
4924
- const MIN = 6e4;
4925
- if (m >= DAY) {
4926
- const d = Math.floor(m / DAY);
4927
- const h = Math.round(m % DAY / HR);
5045
+ const MIN2 = 6e4;
5046
+ if (m >= DAY2) {
5047
+ const d = Math.floor(m / DAY2);
5048
+ const h = Math.round(m % DAY2 / HR);
4928
5049
  return h ? `${d}d ${h}h` : `${d}d`;
4929
5050
  }
4930
5051
  if (m >= HR) {
4931
5052
  const h = Math.floor(m / HR);
4932
- const mn = Math.round(m % HR / MIN);
5053
+ const mn = Math.round(m % HR / MIN2);
4933
5054
  return mn ? `${h}h ${mn}m` : `${h}h`;
4934
5055
  }
4935
- if (m >= MIN) return `${Math.round(m / MIN)}m`;
5056
+ if (m >= MIN2) return `${Math.round(m / MIN2)}m`;
4936
5057
  return `${Math.round(m / 1e3)}s`;
4937
5058
  }
4938
5059
  var MeasureBox = class extends Drawing {
@@ -5495,6 +5616,393 @@ var Magnifier = class extends Drawing {
5495
5616
  }
5496
5617
  };
5497
5618
 
5619
+ // src/core/drawings/priceAction.ts
5620
+ var GAP_REACH = 3;
5621
+ function nearestBarIndex(bars, time) {
5622
+ if (bars.length === 0) return -1;
5623
+ let lo = 0;
5624
+ let hi = bars.length - 1;
5625
+ while (lo < hi) {
5626
+ const mid = lo + hi >> 1;
5627
+ if (bars[mid].time < time) lo = mid + 1;
5628
+ else hi = mid;
5629
+ }
5630
+ if (lo > 0 && Math.abs(bars[lo - 1].time - time) <= Math.abs(bars[lo].time - time)) return lo - 1;
5631
+ return lo;
5632
+ }
5633
+ function gapAt(bars, i) {
5634
+ const prev = bars[i - 1];
5635
+ const next = bars[i + 1];
5636
+ if (!prev || !next || !bars[i]) return null;
5637
+ if (next.low > prev.high) return { bull: true, top: next.low, bottom: prev.high };
5638
+ if (next.high < prev.low) return { bull: false, top: prev.low, bottom: next.high };
5639
+ return null;
5640
+ }
5641
+ function nearestGap(bars, i) {
5642
+ for (let k = 0; k <= GAP_REACH; k += 1) {
5643
+ for (const j of k === 0 ? [i] : [i - k, i + k]) {
5644
+ const g = gapAt(bars, j);
5645
+ if (g) return { index: j, ...g };
5646
+ }
5647
+ }
5648
+ return null;
5649
+ }
5650
+ function gapState(bars, i) {
5651
+ const g = nearestGap(bars, i);
5652
+ if (!g) return null;
5653
+ const { index, bull, top, bottom } = g;
5654
+ const mid = (top + bottom) / 2;
5655
+ let tested = null;
5656
+ let half = null;
5657
+ let filled = null;
5658
+ let inverted = null;
5659
+ for (let j = index + 2; j < bars.length && inverted === null; j += 1) {
5660
+ const b = bars[j];
5661
+ const reach = bull ? b.low : b.high;
5662
+ const into = (v) => bull ? reach <= v : reach >= v;
5663
+ if (tested === null && into(bull ? top : bottom)) tested = j;
5664
+ if (half === null && into(mid)) half = j;
5665
+ if (filled === null && into(bull ? bottom : top)) filled = j;
5666
+ if (filled !== null && (bull ? b.close < bottom : b.close > top)) inverted = j;
5667
+ }
5668
+ return { index, bull, top, bottom, mid, tested, half, filled, inverted };
5669
+ }
5670
+ function blockState(bars, i, range) {
5671
+ const c = bars[i];
5672
+ if (!c) return null;
5673
+ const bull = c.close < c.open;
5674
+ const top = range === "body" ? Math.max(c.open, c.close) : c.high;
5675
+ const bottom = range === "body" ? Math.min(c.open, c.close) : c.low;
5676
+ const mid = (top + bottom) / 2;
5677
+ let left = false;
5678
+ let tested = null;
5679
+ let half = null;
5680
+ let broken = null;
5681
+ let leftAfterBreak = false;
5682
+ let retest = null;
5683
+ for (let j = i + 1; j < bars.length && retest === null; j += 1) {
5684
+ const b = bars[j];
5685
+ if (broken === null) {
5686
+ if (!left) left = bull ? b.low > top : b.high < bottom;
5687
+ else {
5688
+ if (tested === null && (bull ? b.low <= top : b.high >= bottom)) tested = j;
5689
+ if (half === null && (bull ? b.low <= mid : b.high >= mid)) half = j;
5690
+ }
5691
+ if (bull ? b.close < bottom : b.close > top) broken = j;
5692
+ } else if (!leftAfterBreak) leftAfterBreak = bull ? b.high < bottom : b.low > top;
5693
+ else if (bull ? b.high >= bottom : b.low <= top) retest = j;
5694
+ }
5695
+ return { index: i, bull, top, bottom, mid, tested, half, broken, retest };
5696
+ }
5697
+ function liquidityState(bars, i, side) {
5698
+ const c = bars[i];
5699
+ if (!c) return null;
5700
+ const high = side === "high";
5701
+ const price = high ? c.high : c.low;
5702
+ let swept = null;
5703
+ let broken = null;
5704
+ for (let j = i + 1; j < bars.length && broken === null; j += 1) {
5705
+ const b = bars[j];
5706
+ if (high ? b.close > price : b.close < price) broken = j;
5707
+ else if (swept === null && (high ? b.high > price : b.low < price)) swept = j;
5708
+ }
5709
+ return { index: i, high, price, swept, broken };
5710
+ }
5711
+
5712
+ // src/core/drawings/types/PriceActionZone.ts
5713
+ var candlesSeen = /* @__PURE__ */ new WeakMap();
5714
+ var PriceActionTool = class extends Drawing {
5715
+ anchorSchema() {
5716
+ return { min: 1, max: 1, slots: [{ role: "candle", free: "x" }] };
5717
+ }
5718
+ /** The chart's candles, and the one nearest the anchor. */
5719
+ candles(proj) {
5720
+ const a = this.anchors[0];
5721
+ if (!a) return null;
5722
+ let bars = candlesSeen.get(proj);
5723
+ if (!bars) {
5724
+ bars = proj.barsInRange?.(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY) ?? [];
5725
+ candlesSeen.set(proj, bars);
5726
+ }
5727
+ return bars.length > 0 ? { bars, at: nearestBarIndex(bars, a.time) } : null;
5728
+ }
5729
+ /** Half a candle's width at bar `i`, in pixels. */
5730
+ halfBar(proj, bars, i) {
5731
+ const a = bars[Math.max(0, i - 1)];
5732
+ const b = bars[Math.min(bars.length - 1, Math.max(1, i))];
5733
+ if (!a || !b || a === b) return 3;
5734
+ return Math.max(1, Math.abs(proj.xOf(b.time) - proj.xOf(a.time)) / 2);
5735
+ }
5736
+ /** Pixel x for the right edge of a zone that stops at bar `end`, or runs on to the chart's edge. */
5737
+ rightEdge(proj, bars, end) {
5738
+ if (end === null) return Math.max(proj.width, proj.xOf(bars[bars.length - 1].time));
5739
+ return proj.xOf(bars[end].time) + this.halfBar(proj, bars, end);
5740
+ }
5741
+ /** The anchor itself, for a tool with nothing on the candles to hang its handle on. */
5742
+ anchorHandle(proj) {
5743
+ const a = this.anchors[0];
5744
+ const y = a ? proj.yOf(a.price, this.paneId) : null;
5745
+ return a && y != null ? [[proj.xOf(a.time), y]] : [];
5746
+ }
5747
+ hitHandle(px, py, proj, tol) {
5748
+ return handleAt(px, py, this.handlePoints(proj), tol + 3);
5749
+ }
5750
+ priceRange() {
5751
+ return null;
5752
+ }
5753
+ };
5754
+ var PriceActionZone = class extends PriceActionTool {
5755
+ /** The zone's pixel layout, or null when the candles don't form one. */
5756
+ zone(proj) {
5757
+ const c = this.candles(proj);
5758
+ const f = c && this.formation(c.bars, c.at);
5759
+ if (!c || !f) return null;
5760
+ const yt = proj.yOf(f.top, this.paneId);
5761
+ const yb = proj.yOf(f.bottom, this.paneId);
5762
+ if (yt == null || yb == null) return null;
5763
+ const { bars } = c;
5764
+ const s = this.settings();
5765
+ const left = (i) => proj.xOf(bars[i].time) - this.halfBar(proj, bars, i);
5766
+ const yTop = Math.min(yt, yb);
5767
+ const yBottom = Math.max(yt, yb);
5768
+ const midY = proj.yOf(f.mid, this.paneId) ?? (yTop + yBottom) / 2;
5769
+ const flip = f.flip && { x1: left(f.flip.from), x2: this.rightEdge(proj, bars, f.flip.to), fill: f.bull ? s.downColor : s.upColor };
5770
+ const edge = s.extend === "tested" ? f.bull ? f.top : f.bottom : s.extend === "half" ? f.mid : f.bull ? f.bottom : f.top;
5771
+ const edgeY = proj.yOf(edge, this.paneId);
5772
+ return {
5773
+ x1: left(f.start),
5774
+ x2: this.rightEdge(proj, bars, f.end),
5775
+ yTop,
5776
+ yBottom,
5777
+ fill: f.bull ? s.upColor : s.downColor,
5778
+ border: s.border,
5779
+ midY: s.midline ? midY : null,
5780
+ faded: f.end !== null && s.fade && !flip,
5781
+ marker: f.end !== null && !flip && edgeY != null ? { x: proj.xOf(bars[f.end].time), y: edgeY } : null,
5782
+ flip,
5783
+ handle: [proj.xOf(bars[f.index].time), midY]
5784
+ };
5785
+ }
5786
+ hitTest(px, py, proj, tol) {
5787
+ const z = this.zone(proj);
5788
+ if (!z) {
5789
+ const a = this.anchors[0];
5790
+ return a != null && Math.abs(px - proj.xOf(a.time)) <= tol;
5791
+ }
5792
+ const inY = py >= z.yTop - tol && py <= z.yBottom + tol;
5793
+ if (inY && px >= z.x1 - tol && px <= z.x2 + tol) return true;
5794
+ return z.flip != null && inY && px >= z.flip.x1 - tol && px <= z.flip.x2 + tol;
5795
+ }
5796
+ handlePoints(proj) {
5797
+ const z = this.zone(proj);
5798
+ return z ? [z.handle] : this.anchorHandle(proj);
5799
+ }
5800
+ bounds(proj) {
5801
+ const z = this.zone(proj);
5802
+ if (!z) return null;
5803
+ const right = Math.min(proj.width, z.flip ? z.flip.x2 : z.x2);
5804
+ return { x: z.x1, y: z.yTop, w: Math.max(1, right - z.x1), h: Math.max(1, z.yBottom - z.yTop) };
5805
+ }
5806
+ };
5807
+
5808
+ // src/core/drawings/types/FairValueGap.ts
5809
+ var EXTEND_OPTIONS = [
5810
+ { value: "tested", label: "Until tested" },
5811
+ { value: "half", label: "Until 50%" },
5812
+ { value: "filled", label: "Until filled" },
5813
+ { value: "always", label: "Always" }
5814
+ ];
5815
+ function defaults2() {
5816
+ return { extend: "filled", upColor: `${BULLISH}33`, downColor: `${BEARISH}33`, midline: true, border: false, fade: true, inverse: false };
5817
+ }
5818
+ var FairValueGap = class extends PriceActionZone {
5819
+ constructor(init) {
5820
+ super(init);
5821
+ this.type = "fairvaluegap";
5822
+ if (!this.gap) this.gap = defaults2();
5823
+ }
5824
+ settings() {
5825
+ return this.gap;
5826
+ }
5827
+ formation(bars, at) {
5828
+ const s = gapState(bars, at);
5829
+ if (!s) return null;
5830
+ const { extend, inverse } = this.gap;
5831
+ const flip = inverse && extend === "filled" && s.inverted !== null ? { from: s.inverted, to: null } : null;
5832
+ return { ...s, start: s.index - 1, end: extend === "always" ? null : s[extend], flip };
5833
+ }
5834
+ schema() {
5835
+ return {
5836
+ fields: [
5837
+ { path: "gap.extend", label: "Extend right", kind: "select", options: EXTEND_OPTIONS, group: "behavior" },
5838
+ { path: "gap.upColor", label: "Bullish", kind: "color", group: "fill" },
5839
+ { path: "gap.downColor", label: "Bearish", kind: "color", group: "fill" },
5840
+ { path: "gap.midline", label: "Midline", kind: "boolean", group: "line" },
5841
+ { path: "gap.border", label: "Border", kind: "boolean", group: "line" },
5842
+ { path: "gap.fade", label: "Fade at end", kind: "boolean", group: "behavior", when: { path: "gap.extend", in: ["tested", "half", "filled"] } },
5843
+ { path: "gap.inverse", label: "Flip on close", kind: "boolean", group: "behavior", when: { path: "gap.extend", in: ["filled"] } }
5844
+ ]
5845
+ };
5846
+ }
5847
+ writeProps() {
5848
+ return { ...this.gap };
5849
+ }
5850
+ readProps(props) {
5851
+ const d = defaults2();
5852
+ const p = props;
5853
+ this.gap = {
5854
+ extend: EXTEND_OPTIONS.some((o) => o.value === p.extend) ? p.extend : d.extend,
5855
+ upColor: typeof p.upColor === "string" ? p.upColor : d.upColor,
5856
+ downColor: typeof p.downColor === "string" ? p.downColor : d.downColor,
5857
+ midline: typeof p.midline === "boolean" ? p.midline : d.midline,
5858
+ border: typeof p.border === "boolean" ? p.border : d.border,
5859
+ fade: typeof p.fade === "boolean" ? p.fade : d.fade,
5860
+ inverse: typeof p.inverse === "boolean" ? p.inverse : d.inverse
5861
+ };
5862
+ }
5863
+ };
5864
+
5865
+ // src/core/drawings/types/OrderBlock.ts
5866
+ var RANGE_OPTIONS = [
5867
+ { value: "body", label: "Body" },
5868
+ { value: "wick", label: "Full range" }
5869
+ ];
5870
+ var EXTEND_OPTIONS2 = [
5871
+ { value: "tested", label: "Until tested" },
5872
+ { value: "half", label: "Until 50%" },
5873
+ { value: "broken", label: "Until broken" },
5874
+ { value: "always", label: "Always" }
5875
+ ];
5876
+ function defaults3() {
5877
+ return { range: "body", extend: "broken", upColor: `${BULLISH}29`, downColor: `${BEARISH}29`, midline: true, border: true, fade: true, breaker: true };
5878
+ }
5879
+ var OrderBlock = class extends PriceActionZone {
5880
+ constructor(init) {
5881
+ super(init);
5882
+ this.type = "orderblock";
5883
+ if (!this.block) this.block = defaults3();
5884
+ }
5885
+ settings() {
5886
+ return this.block;
5887
+ }
5888
+ formation(bars, at) {
5889
+ const s = blockState(bars, at, this.block.range);
5890
+ if (!s) return null;
5891
+ const { extend, breaker } = this.block;
5892
+ const flip = breaker && extend === "broken" && s.broken !== null ? { from: s.broken, to: s.retest } : null;
5893
+ return { ...s, start: s.index, end: extend === "always" ? null : s[extend], flip };
5894
+ }
5895
+ schema() {
5896
+ return {
5897
+ fields: [
5898
+ { path: "block.range", label: "Zone", kind: "select", options: RANGE_OPTIONS, group: "behavior" },
5899
+ { path: "block.extend", label: "Extend right", kind: "select", options: EXTEND_OPTIONS2, group: "behavior" },
5900
+ { path: "block.upColor", label: "Bullish", kind: "color", group: "fill" },
5901
+ { path: "block.downColor", label: "Bearish", kind: "color", group: "fill" },
5902
+ { path: "block.midline", label: "Midline", kind: "boolean", group: "line" },
5903
+ { path: "block.border", label: "Border", kind: "boolean", group: "line" },
5904
+ { path: "block.fade", label: "Fade at end", kind: "boolean", group: "behavior", when: { path: "block.extend", in: ["tested", "half", "broken"] } },
5905
+ { path: "block.breaker", label: "Breaker", kind: "boolean", group: "behavior", when: { path: "block.extend", in: ["broken"] } }
5906
+ ]
5907
+ };
5908
+ }
5909
+ writeProps() {
5910
+ return { ...this.block };
5911
+ }
5912
+ readProps(props) {
5913
+ const d = defaults3();
5914
+ const p = props;
5915
+ this.block = {
5916
+ range: p.range === "body" || p.range === "wick" ? p.range : d.range,
5917
+ extend: EXTEND_OPTIONS2.some((o) => o.value === p.extend) ? p.extend : d.extend,
5918
+ upColor: typeof p.upColor === "string" ? p.upColor : d.upColor,
5919
+ downColor: typeof p.downColor === "string" ? p.downColor : d.downColor,
5920
+ midline: typeof p.midline === "boolean" ? p.midline : d.midline,
5921
+ border: typeof p.border === "boolean" ? p.border : d.border,
5922
+ fade: typeof p.fade === "boolean" ? p.fade : d.fade,
5923
+ breaker: typeof p.breaker === "boolean" ? p.breaker : d.breaker
5924
+ };
5925
+ }
5926
+ };
5927
+
5928
+ // src/core/drawings/types/Liquidity.ts
5929
+ var EXTEND_OPTIONS3 = [
5930
+ { value: "swept", label: "Until swept" },
5931
+ { value: "broken", label: "Until broken" },
5932
+ { value: "always", label: "Always" }
5933
+ ];
5934
+ function defaults4() {
5935
+ return { extend: "swept", markSweep: true };
5936
+ }
5937
+ var Liquidity = class extends PriceActionTool {
5938
+ constructor(init) {
5939
+ super(init);
5940
+ this.type = "liquidity";
5941
+ if (!this.liq) this.liq = defaults4();
5942
+ }
5943
+ anchorSchema() {
5944
+ return { min: 1, max: 1, slots: [{ role: "candle", free: "both" }] };
5945
+ }
5946
+ state(proj) {
5947
+ const c = this.candles(proj);
5948
+ const a = this.anchors[0];
5949
+ if (!c || !a) return null;
5950
+ const bar = c.bars[c.at];
5951
+ return liquidityState(c.bars, c.at, a.price >= (bar.high + bar.low) / 2 ? "high" : "low");
5952
+ }
5953
+ line(proj) {
5954
+ const c = this.candles(proj);
5955
+ const s = this.state(proj);
5956
+ if (!c || !s) return null;
5957
+ const y = proj.yOf(s.price, this.paneId);
5958
+ if (y == null) return null;
5959
+ const { bars } = c;
5960
+ const { extend, markSweep } = this.liq;
5961
+ const end = extend === "always" ? null : extend === "broken" ? s.broken : s.swept ?? s.broken;
5962
+ const x1 = proj.xOf(bars[s.index].time);
5963
+ let sweep = null;
5964
+ if (markSweep && s.swept !== null && (end === null || s.swept <= end)) {
5965
+ const b = bars[s.swept];
5966
+ const wick = proj.yOf(s.high ? b.high : b.low, this.paneId);
5967
+ if (wick != null) sweep = { x: proj.xOf(b.time), y: wick + (s.high ? -9 : 9) };
5968
+ }
5969
+ return { x1, x2: this.rightEdge(proj, bars, end), y, sweep, handle: [x1, y] };
5970
+ }
5971
+ hitTest(px, py, proj, tol) {
5972
+ const l = this.line(proj);
5973
+ return l != null && px >= l.x1 - tol && px <= l.x2 + tol && Math.abs(py - l.y) <= tol;
5974
+ }
5975
+ handlePoints(proj) {
5976
+ const l = this.line(proj);
5977
+ return l ? [l.handle] : this.anchorHandle(proj);
5978
+ }
5979
+ bounds(proj) {
5980
+ const l = this.line(proj);
5981
+ if (!l) return null;
5982
+ return { x: l.x1, y: l.y - 4, w: Math.max(1, Math.min(proj.width, l.x2) - l.x1), h: 8 };
5983
+ }
5984
+ schema() {
5985
+ return {
5986
+ fields: [
5987
+ ...LINE_FIELDS,
5988
+ { path: "liq.extend", label: "Extend right", kind: "select", options: EXTEND_OPTIONS3, group: "behavior" },
5989
+ { path: "liq.markSweep", label: "Mark sweep", kind: "boolean", group: "behavior" }
5990
+ ]
5991
+ };
5992
+ }
5993
+ writeProps() {
5994
+ return { ...this.liq };
5995
+ }
5996
+ readProps(props) {
5997
+ const d = defaults4();
5998
+ const p = props;
5999
+ this.liq = {
6000
+ extend: EXTEND_OPTIONS3.some((o) => o.value === p.extend) ? p.extend : d.extend,
6001
+ markSweep: typeof p.markSweep === "boolean" ? p.markSweep : d.markSweep
6002
+ };
6003
+ }
6004
+ };
6005
+
5498
6006
  // src/core/drawings/registry.ts
5499
6007
  var REGISTRY2 = /* @__PURE__ */ new Map();
5500
6008
  function registerDrawingType(meta) {
@@ -6164,6 +6672,36 @@ registerDrawingType({
6164
6672
  defaultStyle: { lineColor: BULLISH, lineWidth: 1, lineStyle: "solid" },
6165
6673
  create: (init) => new FixedRangeVolumeProfile(init)
6166
6674
  });
6675
+ var FVG_ICON = svg24('<path d="M6 15v6M12 3v18M18 2v6"/><rect x="3.5" y="9.5" width="17" height="5" rx="1" fill="currentColor" fill-opacity="0.28" stroke-width="1.4"/>');
6676
+ var ORDER_BLOCK_ICON = svg24('<rect x="4" y="7.5" width="5" height="8" rx="1"/><path d="M6.5 4v3.5M6.5 15.5V19"/><rect x="9" y="7.5" width="11.5" height="8" rx="1" fill="currentColor" fill-opacity="0.22" stroke-width="1.4"/>');
6677
+ var LIQUIDITY_ICON = svg24('<path d="M3 8h12" stroke-dasharray="2.5 2.5"/><path d="m16.5 4.5 4 4m0-4-4 4"/><path d="M3.5 20 8 13.5l4 3.5 5-8"/>');
6678
+ registerDrawingType({
6679
+ type: "fairvaluegap",
6680
+ group: "priceaction",
6681
+ label: "Fair Value Gap",
6682
+ icon: FVG_ICON,
6683
+ defaultStyle: { lineColor: BULLISH, lineWidth: 1, lineStyle: "solid" },
6684
+ placementHint: "Click the middle candle of a gap",
6685
+ create: (init) => new FairValueGap(init)
6686
+ });
6687
+ registerDrawingType({
6688
+ type: "orderblock",
6689
+ group: "priceaction",
6690
+ label: "Order Block",
6691
+ icon: ORDER_BLOCK_ICON,
6692
+ defaultStyle: { lineColor: BULLISH, lineWidth: 1, lineStyle: "solid" },
6693
+ placementHint: "Click the candle that forms the block",
6694
+ create: (init) => new OrderBlock(init)
6695
+ });
6696
+ registerDrawingType({
6697
+ type: "liquidity",
6698
+ group: "priceaction",
6699
+ label: "Liquidity",
6700
+ icon: LIQUIDITY_ICON,
6701
+ defaultStyle: { lineColor: WARNING, lineWidth: 1, lineStyle: "solid" },
6702
+ placementHint: "Click near a candle's high or low",
6703
+ create: (init) => new Liquidity(init)
6704
+ });
6167
6705
 
6168
6706
  // src/core/model/inputs.ts
6169
6707
  function inputVisible(when, values) {