acttrader-charts 1.3.0-beta.1 → 1.3.0-beta.11

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/README.md CHANGED
@@ -152,6 +152,9 @@ const dealingChart = new ChartEngine({
152
152
  container,
153
153
  // "Show chart by" toggle: 'ltp' | 'ask' | 'bid' (candle close/high/low source)
154
154
  tickClosePriceSource: 'ltp',
155
+ // Header dropdown so the user can switch the source on the chart itself
156
+ // (rendered next to the timeframe dropdown; omit to hide):
157
+ priceSourceSelector: ['ltp', 'bid'],
155
158
  // Chart line checkboxes — each price line is independent:
156
159
  showAskLine: true, // Show AskLine
157
160
  showBidLine: true, // Show BidLine
@@ -161,6 +164,11 @@ const dealingChart = new ChartEngine({
161
164
  dealingChart.setShowAskLine(false); // hide the ask line
162
165
  dealingChart.setShowBidLine(true); // show the bid line
163
166
  dealingChart.setShowLtpPrice(true); // show the LTP line
167
+ // Switch the candle source programmatically (e.g. from a trade-settings page);
168
+ // the header dropdown label follows automatically:
169
+ dealingChart.setTickClosePriceSource('bid');
170
+ // A user pick from the dropdown emits `priceSourceChange` — persist it:
171
+ dealingChart.on('priceSourceChange', ({ source }) => saveTradeSetting(source));
164
172
  ```
165
173
 
166
174
  ### Compare symbols
@@ -378,6 +386,9 @@ chart.on('orderLineMoved', ({ label, fromTimestamp, toTimestamp, data }) => {
378
386
  | `timeframe` | | `"1D"` | Initial timeframe |
379
387
  | `duration` | | — | Initial active duration button |
380
388
  | `symbol` | | — | Symbol name shown in the top bar |
389
+ | `features` | | `{}` | Opt-in feature gates. See [Feature flags](#feature-flags) |
390
+ | `instrument` | | — | Contract specs for `symbol` — pip size, contract size, money conversion. See [`InstrumentSpec`](#instrumentspec) |
391
+ | `account` | | — | Account equity and per-trade risk used to size the position tools. See [`AccountSpec`](#accountspec) |
381
392
  | `isins` | | — | Symbol list for the picker modal |
382
393
  | `onIsinSelect` | | — | Called when user picks a symbol from the picker modal |
383
394
  | `padding` | | `{top:8,right:0,bottom:0,left:0}` | Canvas padding (px) |
@@ -403,6 +414,7 @@ chart.on('orderLineMoved', ({ label, fromTimestamp, toTimestamp, data }) => {
403
414
  | `labels` | | `DEFAULT_LABELS` | Deep-partial string overrides for i18n/translation |
404
415
  | `tickClosePriceSource` | | `"bid"` | Which price drives live tick close/high/low: `"bid"`, `"ask"`, or `"ltp"` (build candles from the last traded price — exchange/dealing feeds; ticks without a valid LTP fall back to the bid) |
405
416
  | `showLtpPrice` | | unset | Show the LTP marker (dashed price line + axis tag). Unset: shown only in `"ltp"` mode. `true`: always shown when the feed supplies an LTP. `false`: hidden even in `"ltp"` mode (candles still build from the LTP). Toggle at runtime with `chart.setShowLtpPrice(show)` |
417
+ | `priceSourceSelector` | | unset | Show a price-source dropdown in the chart header listing the given sources, e.g. `["ltp", "bid"]` (dealing feeds). A user pick switches the live candle source and emits `priceSourceChange`; sync it programmatically with `chart.setTickClosePriceSource(source)`. Hidden when unset/empty. Default header layout only |
406
418
  | `showBidAskLines` | | `false` | **Deprecated** — show both bid and ask as dashed lines during a live stream. Prefer the per-line checks `showAskLine` / `showBidLine`, which override it when set |
407
419
  | `showAskLine` | | unset | Show the Ask price line (dashed line + axis tag) independently. Unset: legacy `showBidAskLines` behavior. Toggle at runtime with `chart.setShowAskLine(show)` |
408
420
  | `showBidLine` | | unset | Show the Bid price line (dashed line + axis tag) independently. Unset: legacy `showBidAskLines` behavior. Toggle at runtime with `chart.setShowBidLine(show)` |
@@ -437,6 +449,111 @@ chart.on('orderLineMoved', ({ label, fromTimestamp, toTimestamp, data }) => {
437
449
  | `onLotsChange` | | — | Called when user changes qty on a draft order |
438
450
  | `onLevelDragEnd` | | — | *(deprecated — use `onLevelEdit`)* |
439
451
 
452
+ ### `InstrumentSpec`
453
+
454
+ The chart reads prices, never contract specs — so a tool that reports a distance
455
+ in **pips**, or converts one to money, has to be told how. Pass `instrument` at
456
+ construction and swap it with `setInstrument()` whenever the symbol changes.
457
+
458
+ ```ts
459
+ const chart = new ChartEngine({
460
+ container,
461
+ symbol: 'EURUSD',
462
+ instrument: {
463
+ pipSize: 0.0001, // 0.01 for JPY crosses
464
+ contractSize: 100_000, // units per lot
465
+ valuePerPoint: 1, // account-currency value of one price unit per unit
466
+ currencySymbol: '$',
467
+ },
468
+ });
469
+
470
+ chart.setSymbol('USDJPY').setInstrument({ pipSize: 0.01, contractSize: 100_000 });
471
+ ```
472
+
473
+ | Field | Default | Description |
474
+ |---|---|---|
475
+ | `pipSize` | inferred | Price distance counted as one pip |
476
+ | `contractSize` | `1` | Units per lot (`100` for XAUUSD, `100000` for most FX pairs) |
477
+ | `valuePerPoint` | `1` | Account-currency value of one price unit per contract unit |
478
+ | `currencySymbol` | `'$'` | Prefixed to money figures |
479
+
480
+ **Every field is optional and the whole block can be omitted** — tools fall back
481
+ to price-only readouts, so existing integrations keep working untouched.
482
+
483
+ When `pipSize` is absent it is inferred from how many decimals the feed quotes:
484
+ five-decimal (`1.08531`) and three-decimal (`151.234`) feeds carry fractional
485
+ pips, so the pip is the second-to-last digit; two- and four-decimal feeds quote
486
+ whole pips. **That convention is wrong for metals, indices and crypto** — pass
487
+ `pipSize` explicitly if pips matter.
488
+
489
+ The same fallbacks are exported for hosts that want to compute alongside the
490
+ chart:
491
+
492
+ ```ts
493
+ import { resolveInstrument, inferPipSize, toPips, toMoney } from 'acttrader-charts';
494
+ ```
495
+
496
+ > Per-position values still travel on the individual `TradeLevel`
497
+ > (`contractSize` / `valuePerPoint`). `InstrumentSpec` is the chart-wide default
498
+ > for tools that aren't attached to a position.
499
+
500
+ ### Feature flags
501
+
502
+ Newer tools ship behind `ChartConfig.features` so each host opts in. **Every flag
503
+ defaults to `false`** — a chart that sets none behaves exactly as it did before
504
+ the flag existed, including its toolbar contents.
505
+
506
+ ```ts
507
+ const chart = new ChartEngine({
508
+ container,
509
+ features: { enableForecasting: true },
510
+ });
511
+ ```
512
+
513
+ | Flag | Default | What it changes |
514
+ |---|---|---|
515
+ | `enableForecasting` | `false` | The reworked drawing tools, as one switch — see below |
516
+ | `orderLineTimeDrag` | `false` | Horizontal (time-axis) order-line dragging — see [Horizontal order-line dragging](#horizontal-time-axis-order-line-dragging) |
517
+
518
+ `enableForecasting` turns on three things together:
519
+
520
+ - **Long Position** / **Short Position** in a new **Forecasting** group in the
521
+ drawing toolbar. With the flag off the group is not rendered at all.
522
+ - **Brush** and **Highlighter** draw freehand — press, drag, release. Off, they
523
+ keep the click-per-point gesture ended by a double-click.
524
+ - **Ruler** reports percent, pips, calendar duration and volume. Off, it reports
525
+ the bar count and raw price delta as before.
526
+
527
+ They share one flag because they ship and roll back together: a broker either has
528
+ the reworked drawing tools or it doesn't.
529
+
530
+ Related config, independent of the flag: the position tools show quantity and
531
+ money only when [`account`](#accountspec) is supplied, and pips on both the
532
+ position tools and the ruler come from [`instrument`](#instrumentspec) — without
533
+ it, pip size is inferred from the feed's decimal count.
534
+
535
+ ### `AccountSpec`
536
+
537
+ What the **Long / Short position** tools size themselves against.
538
+
539
+ TradingView asks the user to type these because it has no broker connection. We
540
+ do have one — so pass real equity and keep it current.
541
+
542
+ ```ts
543
+ chart.setAccount({ size: 10_000, riskPercent: 1 });
544
+ ```
545
+
546
+ | Field | Default | Description |
547
+ |---|---|---|
548
+ | `size` | — | Account equity in the account currency |
549
+ | `riskPercent` | `1` | Percent of the account risked per trade |
550
+
551
+ Omit the block and the position tools still draw — price, percent, pips and
552
+ risk/reward all render; only the quantity and money amounts are left out.
553
+
554
+ > A sketch drawn against a stale balance reports the wrong quantity rather than
555
+ > failing visibly, so push `setAccount()` whenever equity moves.
556
+
440
557
  ### `OHLCVBar`
441
558
 
442
559
  | Field | Required | Type | Description |
@@ -673,7 +790,7 @@ chart.resetData(): this
673
790
 
674
791
  **Symbol switch pattern:**
675
792
  ```ts
676
- chart.setSymbol('GBPUSD').resetData();
793
+ chart.setSymbol('GBPUSD').setInstrument({ pipSize: 0.0001 }).resetData();
677
794
  // … fetch new bars …
678
795
  chart.loadData(newBars);
679
796
  ```
@@ -682,8 +799,15 @@ chart.loadData(newBars);
682
799
 
683
800
  ```ts
684
801
  chart.setSeries(series: SeriesType): this
802
+ chart.setInstrument(spec: InstrumentSpec | undefined): this // contract specs for pips / money readouts
803
+ chart.getInstrument(): ResolvedInstrument // specs with fallbacks applied
804
+ chart.setAccount(account: AccountSpec | undefined): this // equity + risk for the position tools
805
+ chart.getAccount(): AccountSpec | undefined
685
806
  chart.setTheme('dark' | 'light'): this
686
807
  chart.setTimeframe(tf: Timeframe): this // change timeframe and reload data
808
+ chart.setDuration(d: Duration, timeframe?: Timeframe): this // select a duration; pairs the timeframe and reloads
809
+ chart.getDuration(): Duration | null // currently selected duration
810
+ chart.setBracketLabelMode(mode, currencySymbol?): this // SL/TP pills: 'price' (default) or 'amount'
687
811
  chart.setThemeOverrides(overrides: ThemeOverrides): this // update per-theme color overrides at runtime
688
812
  chart.setTimezone(tz: string): this // change display timezone at runtime
689
813
  chart.setVolume(show: boolean): this
@@ -888,6 +1012,37 @@ Sub-pane heights are user-resizable by dragging the separator between panes.
888
1012
  | `flatChannel` | Flat Channel | 2 clicks |
889
1013
  | `disjointChannel` | Disjoint Channel | 3 clicks |
890
1014
 
1015
+ ### SL/TP bracket labels — price or amount
1016
+
1017
+ By default an SL/TP pill reads `SL 4159.00`. With `bracketLabelMode: 'amount'`
1018
+ it reads `SL -$290.80` — the money the position gains or loses if that bracket
1019
+ is hit, currency symbol in front.
1020
+
1021
+ The chart has no access to contract specs or the account currency, so each level
1022
+ supplies what the maths needs:
1023
+
1024
+ ```ts
1025
+ new ChartEngine({ container, bracketLabelMode: 'amount', currencySymbol: '$' });
1026
+
1027
+ chart.setLevels([{
1028
+ label: 'POS-1', price: 4173.54, side: 'buy', lots: 0.20,
1029
+ stopLossPrice: 4159.00, takeProfitPrice: 4183.00,
1030
+ contractSize: 100, // units per lot (100 for XAUUSD, 100000 for most FX)
1031
+ valuePerPoint: 1, // account-currency value of one price unit
1032
+ currencySymbol: '$', // optional per-level override
1033
+ }], 'label', 'price', 'position');
1034
+ // pills render: SL -$290.80 TP +$189.20
1035
+ ```
1036
+
1037
+ ```
1038
+ amount = (bracket − entry) × direction × lots × contractSize × valuePerPoint
1039
+ ```
1040
+
1041
+ `valuePerPoint` is where a quote → account currency conversion goes. A level
1042
+ missing `lots` or `contractSize` keeps showing its price, so a partial rollout
1043
+ degrades level by level rather than rendering `NaN`. Toggle at runtime with
1044
+ `chart.setBracketLabelMode('amount')`.
1045
+
891
1046
  ### Fibonacci & Gann
892
1047
 
893
1048
  `fibRetracement` · `fibExtension` · `fibChannel` · `fibTimezone` · `fibCircles` · `fibSpiral` · `fibFan` · `fibProjection` · `gannFan` · `gannSquare` · `gannBox`
@@ -920,10 +1075,78 @@ In a **per-drawing** style (`SerializedDrawing.style.levels`), a level may also
920
1075
 
921
1076
  `rectangle` · `rotatedRectangle` · `ellipse` · `triangle` · `circle` · `arc` · `polyline` · `path` · `brush` · `highlighter`
922
1077
 
1078
+ **`brush` and `highlighter` draw freehand** when `features.enableForecasting` is on
1079
+ (see [Feature flags](#feature-flags)): press, drag, release — the pointer
1080
+ path is sampled continuously and the finished stroke is thinned to the points
1081
+ that carry its shape. (`polyline` and `path` remain click-per-point, ended with a
1082
+ double-click.)
1083
+
923
1084
  ### Measurements & Annotations
924
1085
 
925
1086
  `priceRange` · `dateRange` · `datePriceRange` · `ruler` · `priceLabel` · `priceNote` · `priceProjection` · `projection` · `arrowUp` · `arrowDown` · `arrowMarker` · `text` · `callout` · `anchoredNote` · `flag` · `sineLine` · `regressionTrend` · `ghostFeed`
926
1087
 
1088
+ **`ruler`**, with `features.enableForecasting` on (see [Feature flags](#feature-flags)),
1089
+ draws a direction-tinted box over the span it measures and reports:
1090
+
1091
+ ```
1092
+ 0.00455 (0.80%) 45.5
1093
+ 43 bars, 9d 4h
1094
+ Vol 102.27K
1095
+ ```
1096
+
1097
+ Price delta, percent, and **pips**; bar count and calendar duration; volume
1098
+ traded across the span.
1099
+
1100
+ A pip figure is always shown — but it is only *correct* when the host supplies
1101
+ [`instrument.pipSize`](#instrumentspec). Without it the chart infers pip size
1102
+ from how many decimals the feed quotes, which follows the usual FX convention
1103
+ and is wrong for metals, indices and crypto.
1104
+
1105
+ Duration needs bar timestamps, and the volume line is omitted entirely when the
1106
+ bars carry no volume, rather than showing a `0` that would read as "no volume
1107
+ traded".
1108
+
1109
+ The box takes its colour from `candle.up` / `candle.down` so a measurement never
1110
+ disagrees with the candles underneath it; picking a colour in the style popover
1111
+ overrides that. All four corners resize.
1112
+
1113
+ With the flag off it reports the bar count and raw price delta, as it always has.
1114
+
1115
+ ### Forecasting
1116
+
1117
+ `longPosition` · `shortPosition`
1118
+
1119
+ > Requires `features.enableForecasting` — see [Feature flags](#feature-flags).
1120
+ > With the flag off the group is not rendered, on the desktop toolbar or in the
1121
+ > mobile tools sheet.
1122
+ >
1123
+ > `ghostFeed` and `projection` stay in **Advanced Tools** and are ungated — they
1124
+ > predate this group.
1125
+
1126
+ **`longPosition` / `shortPosition`** sketch a trade that hasn't been placed: a
1127
+ green profit zone from entry to target, a red risk zone from entry to stop, and
1128
+ live readouts.
1129
+
1130
+ ```
1131
+ Target: 0.00313 (0.549%) 31.3, Amount: 5622.13
1132
+ Open PnL: 0.00146, Qty: 11323
1133
+ Risk/reward ratio: 1.84
1134
+ Stop: 0.00170 (0.298%) 17.0, Amount: 2887.5
1135
+ ```
1136
+
1137
+ Two clicks place it — entry, then target — and the stop lands at a 2:1
1138
+ reward:risk for you to drag. All three prices have handles.
1139
+
1140
+ Quantity is sized so that hitting the stop costs exactly `riskPercent` of the
1141
+ account, which needs [`account`](#accountspec); the money amounts and pips also
1142
+ need [`instrument`](#instrumentspec). Without them the tool still draws and still
1143
+ reports price, percent and risk/reward.
1144
+
1145
+ > **This is a drawing, not an order.** Trade-From-Chart (`setLevels`) is what
1146
+ > puts real broker orders on the chart — see [Trade From Chart
1147
+ > (TFC)](#trade-from-chart-tfc--setlevels). Nothing on a position tool reaches
1148
+ > the broker.
1149
+
927
1150
  ### Volume Profile
928
1151
 
929
1152
  `volumeProfile` · `anchoredVP` · `fixedRangeVP`
@@ -932,6 +1155,10 @@ In a **per-drawing** style (`SerializedDrawing.style.levels`), a level may also
932
1155
 
933
1156
  `cyclicLines` · `timeCycles`
934
1157
 
1158
+ > `ghostFeed` and `projection` remain here and are ungated. TradingView files
1159
+ > them under Forecasting, but moving them would have reshuffled the toolbar for
1160
+ > hosts that never enabled the new group.
1161
+
935
1162
  ### Harmonic & Elliott Patterns
936
1163
 
937
1164
  `abcdPattern` · `headShoulders` · `trianglePattern` · `batPattern` · `butterflyPattern` · `crabPattern` · `gartleyPattern` · `cypherPattern` · `sharkPattern` · `threeDrives` · `elliottImpulse` · `elliottCorrective` · `elliottTriangle` · `elliottCombination` · `elliottWxy`
@@ -952,6 +1179,7 @@ chart.on('pan', ({ viewport }) => {});
952
1179
  chart.on('timeframeChange',({ timeframe }) => {});
953
1180
  chart.on('durationChange', ({ duration, timeframe }) => {});
954
1181
  chart.on('seriesChange', ({ series }) => {});
1182
+ chart.on('priceSourceChange', ({ source }) => {}); // header BID/ASK/LTP dropdown pick
955
1183
  chart.on('streamStatus', ({ status }) => {}); // 'connected' | 'reconnecting' | 'disconnected'
956
1184
  chart.on('dataLoaded', ({ timeframe, interval, start, end }) => {});
957
1185
  chart.on('newBar', ({ completedBar, openingBar, intervalMs }) => {});
@@ -1,3 +1,13 @@
1
+ declare class ScaleManager {
2
+ private static readonly VERTICAL_PADDING_PX;
3
+ xToPixel(index: number, viewport: Viewport, chartWidth: number): number;
4
+ pixelToBarIndex(x: number, viewport: Viewport, chartWidth: number): number;
5
+ candleBodyWidth(viewport: Viewport, chartWidth: number): number;
6
+ yToPixel(price: number, range: PriceRange, chartHeight: number): number;
7
+ pixelToPrice(y: number, range: PriceRange, chartHeight: number): number;
8
+ volumeToPixel(volume: number, maxVolume: number, volumePanelHeight: number): number;
9
+ }
10
+
1
11
  /**
2
12
  * Definition of a multi-chart layout preset (grid template).
3
13
  * Used by the chart-owned LayoutPopover and emitted via the `layoutChange` event.
@@ -45,16 +55,6 @@ declare const PRESET_LABEL: Record<string, string>;
45
55
  /** Default sync state — matches the host's current defaults in useChartLayout.ts. */
46
56
  declare const DEFAULT_LAYOUT_SYNC: LayoutSyncState;
47
57
 
48
- declare class ScaleManager {
49
- private static readonly VERTICAL_PADDING_PX;
50
- xToPixel(index: number, viewport: Viewport, chartWidth: number): number;
51
- pixelToBarIndex(x: number, viewport: Viewport, chartWidth: number): number;
52
- candleBodyWidth(viewport: Viewport, chartWidth: number): number;
53
- yToPixel(price: number, range: PriceRange, chartHeight: number): number;
54
- pixelToPrice(y: number, range: PriceRange, chartHeight: number): number;
55
- volumeToPixel(volume: number, maxVolume: number, volumePanelHeight: number): number;
56
- }
57
-
58
58
  interface DrawingToolbarUiConfig {
59
59
  /** Icon button size (width = height) in px */
60
60
  iconBtnSize: number;
@@ -262,6 +262,7 @@ interface DrawingToolbarLabels {
262
262
  elliott: string;
263
263
  brushes: string;
264
264
  advanced: string;
265
+ forecasting: string;
265
266
  priceProjection: string;
266
267
  };
267
268
  /** Individual drawing tool names (used in flyout rows). */
@@ -618,6 +619,50 @@ type ThemeOverrides = {
618
619
  dark?: DeepPartialChartTheme;
619
620
  light?: DeepPartialChartTheme;
620
621
  };
622
+ /**
623
+ * What a price *means* for this instrument.
624
+ *
625
+ * The chart reads prices, never contract specs — so a tool that wants to report
626
+ * a distance in pips, or convert it to money, has to be told how. Every field is
627
+ * optional and every one degrades to a documented fallback, so an existing
628
+ * integration that omits the block keeps working and simply shows price-only
629
+ * readouts.
630
+ *
631
+ * Per-position values still travel on the individual `TradeLevel`; this block is
632
+ * the instrument-wide default for tools that aren't attached to a position.
633
+ */
634
+ interface InstrumentSpec {
635
+ /**
636
+ * Price distance counted as one pip — `0.0001` for most FX pairs, `0.01` for
637
+ * JPY crosses. Inferred from the feed's decimal count when omitted, which
638
+ * follows the usual FX convention and is wrong for metals, indices and
639
+ * crypto. Pass it explicitly if pips matter.
640
+ */
641
+ pipSize?: number;
642
+ /** Units per lot (e.g. `100` for XAUUSD, `100000` for most FX pairs). Default `1`. */
643
+ contractSize?: number;
644
+ /** Account-currency value of one price unit per contract unit. Default `1`. */
645
+ valuePerPoint?: number;
646
+ /** Symbol prefixed to money figures. Default `'$'`. */
647
+ currencySymbol?: string;
648
+ }
649
+ /**
650
+ * Account figures the position tools size themselves against.
651
+ *
652
+ * TradingView asks the user for these because it has no broker connection. A
653
+ * host that does know the live account should pass them here instead, and keep
654
+ * them current — the tool then sizes against real equity rather than a number
655
+ * the user typed once and forgot.
656
+ *
657
+ * Omit the block and the position tools still draw, showing price, percent,
658
+ * pips and risk/reward but no quantity or money amounts.
659
+ */
660
+ interface AccountSpec {
661
+ /** Account equity in the account currency. */
662
+ size?: number;
663
+ /** Percent of the account risked per trade. Default `1`. */
664
+ riskPercent?: number;
665
+ }
621
666
  interface ChartConfig {
622
667
  container: HTMLElement;
623
668
  theme?: Theme;
@@ -633,6 +678,17 @@ interface ChartConfig {
633
678
  timeframe?: Timeframe;
634
679
  duration?: Duration;
635
680
  symbol?: string;
681
+ /**
682
+ * Contract specs for the current symbol. Lets measurement and position tools
683
+ * report pips and money instead of bare price distances. Swap it alongside
684
+ * the symbol via `setInstrument()`.
685
+ */
686
+ instrument?: InstrumentSpec;
687
+ /**
688
+ * Account equity and per-trade risk used to size the Long/Short position
689
+ * tools. Keep it current via `setAccount()`.
690
+ */
691
+ account?: AccountSpec;
636
692
  /** List of symbols/ISINs the user can switch between via the symbol picker modal. */
637
693
  isins?: string[];
638
694
  /** Called when the user picks a symbol from the picker modal. Parent should call setSymbol() + setData(). */
@@ -856,6 +912,14 @@ interface ChartConfig {
856
912
  * mode (candles still build from the LTP).
857
913
  */
858
914
  showLtpPrice?: boolean;
915
+ /**
916
+ * Show a price-source dropdown in the chart header listing the given
917
+ * sources (e.g. `['ltp', 'bid']` for dealing feeds). Selecting an option
918
+ * switches `tickClosePriceSource` at runtime and emits `priceSourceChange`
919
+ * so the host can persist the choice. Hidden when unset or empty.
920
+ * Rendered by the default header layout.
921
+ */
922
+ priceSourceSelector?: Array<'bid' | 'ask' | 'ltp'>;
859
923
  /**
860
924
  * When the combined count of open positions and pending orders exceeds this
861
925
  * number, the position render style auto-defaults to `'dot'` (compact).
@@ -1001,6 +1065,24 @@ interface ChartConfig {
1001
1065
  * Clamped to `[1, 3]`. Default: `1`.
1002
1066
  */
1003
1067
  tradeLevelButtonScale?: number;
1068
+ /**
1069
+ * What the SL/TP bracket pills show.
1070
+ *
1071
+ * `'price'` (default) renders the bracket price — `SL 4159.00`.
1072
+ * `'amount'` renders the money the position gains or loses if that bracket
1073
+ * is hit — `SL -$290.80` — computed from the level's `lots`, `contractSize`
1074
+ * and `valuePerPoint`. Falls back to the price for any level lacking the
1075
+ * data needed to compute it, so a partial rollout degrades gracefully.
1076
+ * `'priceAndAmount'` keeps the price but puts the currency symbol in front
1077
+ * of it — `SL $1.27859` — and appends the P/L while the bracket is being
1078
+ * dragged: `SL $1.27859 -$29.08`.
1079
+ */
1080
+ bracketLabelMode?: 'price' | 'amount' | 'priceAndAmount';
1081
+ /**
1082
+ * Currency symbol for SL/TP amounts, used when `bracketLabelMode: 'amount'`
1083
+ * and the level does not carry its own `currencySymbol`. Default `'$'`.
1084
+ */
1085
+ currencySymbol?: string;
1004
1086
  /**
1005
1087
  * When `true`, a theme toggle (Dark / Light) is shown in the Settings dialog.
1006
1088
  * Default: `false` — the toggle is hidden.
@@ -1123,6 +1205,27 @@ interface ChartConfig {
1123
1205
  * Default: false.
1124
1206
  */
1125
1207
  orderLineTimeDrag?: boolean;
1208
+ /**
1209
+ * Enables the AWT-2642 forecasting/drawing work as one switch:
1210
+ *
1211
+ * - **Long Position** / **Short Position** in a new **Forecasting** group
1212
+ * in the drawing toolbar. Drawings only — nothing reaches the broker.
1213
+ * - **Brush** and **Highlighter** draw freehand (press, drag, release)
1214
+ * instead of dropping one point per click.
1215
+ * - **Ruler** reports percent, pips, calendar duration and volume instead
1216
+ * of just a bar count and raw price delta.
1217
+ *
1218
+ * One flag rather than three because they ship and roll back together —
1219
+ * a broker either has the reworked drawing tools or it doesn't.
1220
+ *
1221
+ * Off by default: enabling it changes the toolbar and the gesture of two
1222
+ * existing tools, so hosts opt in. With it off the chart behaves exactly
1223
+ * as it did before these tools existed. Default: false.
1224
+ *
1225
+ * Position quantity and money amounts additionally need `account`, and
1226
+ * pips on both the position tools and the ruler come from `instrument`.
1227
+ */
1228
+ enableForecasting?: boolean;
1126
1229
  };
1127
1230
  /**
1128
1231
  * Fetches historical OHLC bars for a compare symbol. When provided, the
@@ -1215,7 +1318,7 @@ interface IIndicator {
1215
1318
  getValueRange?(results: IndicatorResult[]): PriceRange;
1216
1319
  render(ctx: CanvasRenderingContext2D, results: IndicatorResult[], scaleManager: ScaleManager, viewport: Viewport, chartHeight: number, rangeOverride?: PriceRange): void;
1217
1320
  }
1218
- type DrawingToolType = "horizontalLine" | "verticalLine" | "trendLine" | "rectangle" | "ellipse" | "circle" | "triangle" | "fibRetracement" | "fibExtension" | "fibChannel" | "fibProjection" | "fibFan" | "fibCircles" | "fibSpiral" | "fibTimezone" | "flatChannel" | "parallelChannel" | "disjointChannel" | "pitchfork" | "schiffPitchfork" | "modifiedSchiff" | "insidePitchfork" | "crossLine" | "trendAngle" | "arc" | "sineLine" | "rotatedRectangle" | "path" | "polyline" | "brush" | "highlighter" | "text" | "callout" | "priceLabel" | "priceNote" | "anchoredNote" | "arrowUp" | "arrowDown" | "arrowMarker" | "flag" | "volumeProfile" | "anchoredVP" | "fixedRangeVP" | "abcdPattern" | "gartleyPattern" | "batPattern" | "butterflyPattern" | "crabPattern" | "sharkPattern" | "cypherPattern" | "headShoulders" | "trianglePattern" | "threeDrives" | "elliottImpulse" | "elliottCorrective" | "elliottTriangle" | "elliottWxy" | "elliottCombination" | "gannFan" | "gannSquare" | "gannBox" | "priceRange" | "dateRange" | "datePriceRange" | "ruler" | "regressionTrend" | "projection" | "cyclicLines" | "timeCycles" | "priceProjection" | "ghostFeed";
1321
+ type DrawingToolType = "horizontalLine" | "verticalLine" | "trendLine" | "rectangle" | "ellipse" | "circle" | "triangle" | "fibRetracement" | "fibExtension" | "fibChannel" | "fibProjection" | "fibFan" | "fibCircles" | "fibSpiral" | "fibTimezone" | "flatChannel" | "parallelChannel" | "disjointChannel" | "pitchfork" | "schiffPitchfork" | "modifiedSchiff" | "insidePitchfork" | "crossLine" | "trendAngle" | "arc" | "sineLine" | "rotatedRectangle" | "path" | "polyline" | "brush" | "highlighter" | "text" | "callout" | "priceLabel" | "priceNote" | "anchoredNote" | "arrowUp" | "arrowDown" | "arrowMarker" | "flag" | "volumeProfile" | "anchoredVP" | "fixedRangeVP" | "abcdPattern" | "gartleyPattern" | "batPattern" | "butterflyPattern" | "crabPattern" | "sharkPattern" | "cypherPattern" | "headShoulders" | "trianglePattern" | "threeDrives" | "elliottImpulse" | "elliottCorrective" | "elliottTriangle" | "elliottWxy" | "elliottCombination" | "gannFan" | "gannSquare" | "gannBox" | "priceRange" | "dateRange" | "datePriceRange" | "ruler" | "regressionTrend" | "projection" | "cyclicLines" | "timeCycles" | "priceProjection" | "ghostFeed" | "longPosition" | "shortPosition";
1219
1322
  interface DrawingPoint {
1220
1323
  barIndex: number;
1221
1324
  price: number;
@@ -1226,6 +1329,15 @@ interface DrawingPoint {
1226
1329
  interface DrawingHandle {
1227
1330
  pointIndex: number;
1228
1331
  cursor: string;
1332
+ /**
1333
+ * Corners that are a mixture of two stored points — a rectangle's top-right
1334
+ * is `(p2.barIndex, p1.price)` — set these instead of relying on
1335
+ * `pointIndex` alone. The drag then writes the x to `points[xPointIndex]`
1336
+ * and the y to `points[yPointIndex]`, which is what makes all four corners
1337
+ * of a two-point shape draggable rather than just the two stored ones.
1338
+ */
1339
+ xPointIndex?: number;
1340
+ yPointIndex?: number;
1229
1341
  }
1230
1342
  /** Base per-instance style fields applicable to every drawing. */
1231
1343
  interface DrawingStyle {
@@ -1266,6 +1378,23 @@ interface DrawingFibStyle extends DrawingStyle {
1266
1378
  }
1267
1379
  /** Union of all drawing style shapes. */
1268
1380
  type AnyDrawingStyle = DrawingStyle | DrawingTextStyle | DrawingFibStyle;
1381
+ /**
1382
+ * Extra context a drawing may need at render time, beyond geometry.
1383
+ *
1384
+ * Passed as one optional object rather than more positional parameters: almost
1385
+ * no drawing needs any of it, and the ones that do (measurement and position
1386
+ * tools) need several fields at once.
1387
+ */
1388
+ interface DrawingRenderContext {
1389
+ /** Resolved contract specs — pip size, contract size, money conversion. */
1390
+ instrument?: ResolvedInstrument;
1391
+ /** Bars currently loaded, for tools that summarise a range. */
1392
+ bars?: readonly OHLCVBar[];
1393
+ /** Account equity and per-trade risk, for tools that size a position. */
1394
+ account?: AccountSpec;
1395
+ /** Host feature gates, for tools whose output is flagged. */
1396
+ features?: ChartConfig['features'];
1397
+ }
1269
1398
  interface IDrawing {
1270
1399
  id: string;
1271
1400
  type: DrawingToolType;
@@ -1276,8 +1405,17 @@ interface IDrawing {
1276
1405
  text?: string;
1277
1406
  /** Optional per-instance style override; falls back to theme defaults when absent. */
1278
1407
  style?: AnyDrawingStyle;
1279
- render(ctx: CanvasRenderingContext2D, scale: ScaleManager, viewport: Viewport, priceRange: PriceRange, chartW: number, chartH: number, isSelected: boolean, pricePrecision?: number, theme?: ChartTheme): void;
1408
+ render(ctx: CanvasRenderingContext2D, scale: ScaleManager, viewport: Viewport, priceRange: PriceRange, chartW: number, chartH: number, isSelected: boolean, pricePrecision?: number, theme?: ChartTheme, context?: DrawingRenderContext): void;
1280
1409
  hitTest(x: number, y: number, scale: ScaleManager, viewport: Viewport, priceRange: PriceRange, chartW: number, chartH: number): boolean;
1410
+ /**
1411
+ * Called once, immediately after the drawing is completed.
1412
+ *
1413
+ * Lets a tool derive anchors it could not collect during placement — the
1414
+ * position tools take entry and target from two clicks, then materialise a
1415
+ * stop from the default risk/reward here rather than making the user click
1416
+ * a third time.
1417
+ */
1418
+ onPlaced?(): void;
1281
1419
  /** Returns the handle under (x, y), or null if none */
1282
1420
  getHandle(x: number, y: number, scale: ScaleManager, viewport: Viewport, priceRange: PriceRange, chartW: number, chartH: number): DrawingHandle | null;
1283
1421
  }
@@ -1429,6 +1567,12 @@ type ChartEventMap = {
1429
1567
  seriesChange: {
1430
1568
  series: SeriesType;
1431
1569
  };
1570
+ /** Emitted when the user picks a price source (BID / ASK / LTP) from the
1571
+ * header dropdown (`priceSourceSelector`). Not emitted for programmatic
1572
+ * `setTickClosePriceSource()` calls. */
1573
+ priceSourceChange: {
1574
+ source: 'bid' | 'ask' | 'ltp';
1575
+ };
1432
1576
  streamStatus: {
1433
1577
  status: StreamStatus;
1434
1578
  };
@@ -1748,6 +1892,23 @@ interface PositionLevel {
1748
1892
  takeProfitPrice?: number;
1749
1893
  /** Data object for the TP bracket — returned in TP-related events instead of the parent's `data`. */
1750
1894
  takeProfitData?: unknown;
1895
+ /**
1896
+ * Units per lot for this instrument (e.g. `100` for XAUUSD, `100000` for most
1897
+ * FX pairs). Only used when `bracketLabelMode: 'amount'`, to convert an SL/TP
1898
+ * price distance into money. Default `1`.
1899
+ */
1900
+ contractSize?: number;
1901
+ /**
1902
+ * Account-currency value of one price unit per contract unit — folds in any
1903
+ * quote → account conversion. Only used when `bracketLabelMode: 'amount'`.
1904
+ * Default `1`.
1905
+ */
1906
+ valuePerPoint?: number;
1907
+ /**
1908
+ * Currency symbol for this level's SL/TP amounts. Falls back to
1909
+ * `ChartConfig.currencySymbol`, then `'$'`.
1910
+ */
1911
+ currencySymbol?: string;
1751
1912
  /**
1752
1913
  * When true, the entry price line becomes draggable — renders a grip handle and emits
1753
1914
  * `tradeLevelEdit` with `field: 'main'` on confirm. Use this for pending entry orders
@@ -1840,6 +2001,61 @@ interface PendingOrderLevel {
1840
2001
  /** Union of all level types — use this wherever a level is accepted or stored. */
1841
2002
  type AnyLevel = TradeLevel | PositionLevel | PendingOrderLevel;
1842
2003
 
2004
+ /**
2005
+ * Instrument specs, with every gap filled in.
2006
+ *
2007
+ * The chart has never known what a price *means* — see `tradeMath.ts`, which
2008
+ * takes contract specs per trade level for exactly this reason. Tools that
2009
+ * report a distance in pips or in money need the same information at chart
2010
+ * scope, so hosts supply an `instrument` block on `ChartConfig` and this module
2011
+ * resolves it against sensible fallbacks.
2012
+ */
2013
+ interface ResolvedInstrument {
2014
+ /** Price distance counted as one pip. */
2015
+ pipSize: number;
2016
+ /** Units per lot (e.g. `100` for XAUUSD, `100000` for most FX pairs). */
2017
+ contractSize: number;
2018
+ /** Account-currency value of one price unit per contract unit. */
2019
+ valuePerPoint: number;
2020
+ /** Symbol prefixed to money figures. */
2021
+ currencySymbol: string;
2022
+ /** True when `pipSize` was guessed from price precision rather than supplied. */
2023
+ pipSizeInferred: boolean;
2024
+ }
2025
+ /**
2026
+ * Pip size guessed from how many decimals the feed quotes.
2027
+ *
2028
+ * Feeds that quote fractional pips carry one digit more than the pip itself —
2029
+ * five decimals on EURUSD (1.08531) and three on USDJPY (151.234) both mean the
2030
+ * pip is the second-to-last digit. Two- and four-decimal feeds quote whole pips,
2031
+ * so the pip is the last digit.
2032
+ *
2033
+ * This is a convention, not a fact about the instrument: metals, indices and
2034
+ * crypto all break it. Hosts that care should pass `pipSize` explicitly.
2035
+ */
2036
+ declare function inferPipSize(pricePrecision: number): number;
2037
+ /** Fills in whatever the host left out. */
2038
+ declare function resolveInstrument(spec: InstrumentSpec | undefined, pricePrecision: number): ResolvedInstrument;
2039
+ /** A price distance expressed in pips. Sign follows the distance. */
2040
+ declare function toPips(priceDelta: number, pipSize: number): number;
2041
+ /**
2042
+ * A price distance expressed in account currency for a given size, or `null`
2043
+ * when the host hasn't supplied enough to convert. Callers fall back to showing
2044
+ * the price rather than rendering a number that isn't money.
2045
+ */
2046
+ declare function toMoney(priceDelta: number, lots: number, instrument: ResolvedInstrument): number | null;
2047
+ /**
2048
+ * Compact volume for a readout — `102.27K`, `4.5M`. Full precision would push
2049
+ * the label wider than the box it sits in.
2050
+ */
2051
+ declare function formatVolume(volume: number): string;
2052
+ /**
2053
+ * Calendar span between two timestamps, in the largest two units that apply —
2054
+ * `9d 4h`, `4h 30m`, `45m`. Bar counts alone don't tell a trader how long a move
2055
+ * actually took once weekends and session gaps are in the data.
2056
+ */
2057
+ declare function formatDuration(ms: number): string;
2058
+
1843
2059
  type PriceSource = 'open' | 'high' | 'low' | 'close';
1844
2060
 
1845
2061
  declare class SMA implements IIndicator {
@@ -1901,4 +2117,4 @@ declare class MACD implements IIndicator {
1901
2117
  render(ctx: CanvasRenderingContext2D, results: IndicatorResult[], scale: ScaleManager, viewport: Viewport, paneHeight: number, rangeOverride?: PriceRange): void;
1902
2118
  }
1903
2119
 
1904
- export { type DrawingToolbarUiConfig as $, type AnyDrawingStyle as A, BollingerBands as B, type ChartConfig as C, type DrawingToolType as D, DEFAULT_CROSSHAIR_CONFIG as E, DEFAULT_DRAWING_TOOLBAR_CONFIG as F, DEFAULT_INDICATOR_OVERLAY_CONFIG as G, DEFAULT_LABELS as H, type IIndicator as I, DEFAULT_LAYOUT_SYNC as J, DEFAULT_PRICE_AXIS_CONFIG as K, type LayoutSyncState as L, DEFAULT_TIME_AXIS_CONFIG as M, DEFAULT_TOP_BAR_CONFIG as N, type OHLCVBar as O, type PriceRange as P, DEFAULT_TRADE_BUTTON_CONFIG as Q, DEFAULT_UI_CONFIG as R, type SeriesType as S, type Timeframe as T, type DataLoaderParams as U, type Viewport as V, type DeepPartial$1 as W, type DeepPartialChartTheme as X, type DialogLabels as Y, type DrawingToolbarColors as Z, type DrawingToolbarLabels as _, type IndicatorParams as a, type Duration as a0, EMA as a1, type IndicatorOverlayColors as a2, type IndicatorOverlayUiConfig as a3, type IndicatorResult as a4, type IndicatorStateEntry as a5, LAYOUT_PRESETS as a6, LIGHT_THEME as a7, type LayoutPreset as a8, MACD as a9, getAllPresets as aA, getCustomPresets as aB, getPreset as aC, registerCustomPreset as aD, resolveCssVarsInTheme as aE, resolveLabels as aF, resolveUiConfig as aG, unregisterCustomPreset as aH, type PriceSource as aI, type OhlcLabels as aa, type OrderSubmit as ab, PRESETS_BY_COUNT as ac, PRESET_COUNTS as ad, PRESET_LABEL as ae, type Padding as af, type PendingOrderLevel as ag, type PositionLevel as ah, type PositionRenderStyle as ai, type PriceAxisUiConfig as aj, RSI as ak, SMA as al, type Theme as am, type ThemeOverrides as an, type TimeAxisUiConfig as ao, type TopBarColors as ap, type TopBarLabels as aq, type TopBarUiConfig as ar, type TradeButtonUiConfig as as, type TradeDisplayFilter as at, type TradeLabels as au, type TradeLevel as av, type TradeLevelColors as aw, type TradePanelColors as ax, type UiConfig as ay, deepMergeTheme as az, type ChartEventMap as b, type ChartState as c, type IDrawing as d, type IWebSocketAdapter as e, type TradeLevelType as f, type ChartTheme as g, type AnyLevel as h, type DrawingPoint as i, ScaleManager as j, type DrawingHandle as k, type SerializedDrawing as l, type Tick as m, type StreamStatus as n, type ActiveIndicatorState as o, type BottomBarColors as p, type BottomBarLabels as q, type BottomBarUiConfig as r, type CanvasColorSettings as s, type ChartLabels as t, type ChartMiscLabels as u, type ChartThemeUi as v, type CrosshairPosition as w, type CrosshairUiConfig as x, DARK_THEME as y, DEFAULT_BOTTOM_BAR_CONFIG as z };
2120
+ export { type DeepPartial$1 as $, type AccountSpec as A, BollingerBands as B, type ChartConfig as C, type Duration as D, type CrosshairPosition as E, type CrosshairUiConfig as F, DARK_THEME as G, DEFAULT_BOTTOM_BAR_CONFIG as H, type IIndicator as I, DEFAULT_CROSSHAIR_CONFIG as J, DEFAULT_DRAWING_TOOLBAR_CONFIG as K, type LayoutSyncState as L, DEFAULT_INDICATOR_OVERLAY_CONFIG as M, DEFAULT_LABELS as N, type OHLCVBar as O, type PriceRange as P, DEFAULT_LAYOUT_SYNC as Q, type ResolvedInstrument as R, type SeriesType as S, type Timeframe as T, DEFAULT_PRICE_AXIS_CONFIG as U, type Viewport as V, DEFAULT_TIME_AXIS_CONFIG as W, DEFAULT_TOP_BAR_CONFIG as X, DEFAULT_TRADE_BUTTON_CONFIG as Y, DEFAULT_UI_CONFIG as Z, type DataLoaderParams as _, type IndicatorParams as a, type DeepPartialChartTheme as a0, type DialogLabels as a1, type DrawingToolbarColors as a2, type DrawingToolbarLabels as a3, type DrawingToolbarUiConfig as a4, EMA as a5, type IndicatorOverlayColors as a6, type IndicatorOverlayUiConfig as a7, type IndicatorResult as a8, type IndicatorStateEntry as a9, type TradeLevelColors as aA, type TradePanelColors as aB, type UiConfig as aC, deepMergeTheme as aD, formatDuration as aE, formatVolume as aF, getAllPresets as aG, getCustomPresets as aH, getPreset as aI, inferPipSize as aJ, registerCustomPreset as aK, resolveCssVarsInTheme as aL, resolveInstrument as aM, resolveLabels as aN, resolveUiConfig as aO, toMoney as aP, toPips as aQ, unregisterCustomPreset as aR, type PriceSource as aS, LAYOUT_PRESETS as aa, LIGHT_THEME as ab, type LayoutPreset as ac, MACD as ad, type OhlcLabels as ae, type OrderSubmit as af, PRESETS_BY_COUNT as ag, PRESET_COUNTS as ah, PRESET_LABEL as ai, type Padding as aj, type PendingOrderLevel as ak, type PositionLevel as al, type PositionRenderStyle as am, type PriceAxisUiConfig as an, RSI as ao, SMA as ap, type Theme as aq, type ThemeOverrides as ar, type TimeAxisUiConfig as as, type TopBarColors as at, type TopBarLabels as au, type TopBarUiConfig as av, type TradeButtonUiConfig as aw, type TradeDisplayFilter as ax, type TradeLabels as ay, type TradeLevel as az, type ChartEventMap as b, type ChartState as c, type InstrumentSpec as d, type DrawingToolType as e, type IDrawing as f, type AnyDrawingStyle as g, type IWebSocketAdapter as h, type TradeLevelType as i, type ChartTheme as j, type AnyLevel as k, type DrawingPoint as l, ScaleManager as m, type DrawingHandle as n, type DrawingRenderContext as o, type SerializedDrawing as p, type Tick as q, type StreamStatus as r, type ActiveIndicatorState as s, type BottomBarColors as t, type BottomBarLabels as u, type BottomBarUiConfig as v, type CanvasColorSettings as w, type ChartLabels as x, type ChartMiscLabels as y, type ChartThemeUi as z };