@luxalgo/vela 0.7.6 → 0.7.7

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.
@@ -1,7 +1,7 @@
1
- import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, SecondClock, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-JSVCWYBF.js';
1
+ import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, SecondClock, resolveTimezone, timezoneMenuRows, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, normalizeTimezone, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-RVQWJOEE.js';
2
2
  import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType, inputDeltas } from './chunk-BFA32GOU.js';
3
- import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-ZADTVRUO.js';
4
- import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-BKHSQ4YM.js';
3
+ import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-PVGWNF7V.js';
4
+ import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-WTWGEB7K.js';
5
5
  import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, icon, categoricalColor } from './chunk-CAFCLMPF.js';
6
6
  import { baseOf } from './chunk-W4EJWLEO.js';
7
7
 
@@ -1087,6 +1087,7 @@ var Bottombar = class {
1087
1087
  this.sessionButtons = /* @__PURE__ */ new Map();
1088
1088
  this.sessionEl = null;
1089
1089
  this.timezone = opts.timezone;
1090
+ this.exchangeTimezone = opts.exchangeTimezone;
1090
1091
  const doc = host.ownerDocument;
1091
1092
  injectStyles(STYLE_ID3, CSS3, doc);
1092
1093
  this.el = doc.createElement("div");
@@ -1110,7 +1111,7 @@ var Bottombar = class {
1110
1111
  this.clockEl = doc.createElement("span");
1111
1112
  this.clockEl.className = "vela-bb-clock";
1112
1113
  this.tzLabelEl = doc.createElement("span");
1113
- this.tzLabelEl.textContent = tzButtonLabel(this.timezone);
1114
+ this.tzLabelEl.textContent = tzButtonLabel(this.displayZone);
1114
1115
  this.tzButton.append(this.clockEl, this.tzLabelEl);
1115
1116
  const session = doc.createElement("span");
1116
1117
  session.className = "vela-bb-session";
@@ -1145,19 +1146,32 @@ var Bottombar = class {
1145
1146
  placement: "top-end",
1146
1147
  items: this.tzItems(),
1147
1148
  onSelect: (zone) => {
1148
- this.setTimezone(zone);
1149
+ this.setTimezone(zone, this.exchangeTimezone);
1149
1150
  opts.onTimezone(zone);
1150
1151
  }
1151
1152
  });
1152
1153
  this.tick();
1153
1154
  this.unsubClock = (opts.clock ?? new SecondClock()).onTick(() => this.tick());
1154
1155
  }
1155
- setTimezone(zone) {
1156
+ /**
1157
+ * Reflect the stored choice AND the active chart's market zone. The clock and the
1158
+ * offset label read the RESOLVED zone, so a workspace on the exchange rule re-labels
1159
+ * when the active cell (or its symbol) changes market — the host re-projects on
1160
+ * both. Idempotent: unchanged inputs leave the menu alone.
1161
+ */
1162
+ setTimezone(zone, exchangeTimezone) {
1163
+ if (zone === this.timezone && exchangeTimezone === this.exchangeTimezone) return;
1164
+ const choiceChanged = zone !== this.timezone;
1156
1165
  this.timezone = zone;
1157
- this.tzLabelEl.textContent = tzButtonLabel(zone);
1158
- this.tzMenu.setItems(this.tzItems());
1166
+ this.exchangeTimezone = exchangeTimezone;
1167
+ this.tzLabelEl.textContent = tzButtonLabel(this.displayZone);
1168
+ if (choiceChanged) this.tzMenu.setItems(this.tzItems());
1159
1169
  this.tick();
1160
1170
  }
1171
+ /** The zone the bar's clock and label render in. */
1172
+ get displayZone() {
1173
+ return resolveTimezone(this.timezone, this.exchangeTimezone);
1174
+ }
1161
1175
  /** Highlight (or clear with null) the active range chip — cleared on manual tf changes. */
1162
1176
  setActiveRange(id) {
1163
1177
  for (const [key, b] of this.rangeButtons) {
@@ -1185,11 +1199,7 @@ var Bottombar = class {
1185
1199
  this.el.remove();
1186
1200
  }
1187
1201
  tzItems() {
1188
- return TIMEZONES.map((t) => ({
1189
- id: t.value,
1190
- label: tzMenuLabel(t.value, t.label),
1191
- checked: t.value === normalizeTimezone(this.timezone)
1192
- }));
1202
+ return timezoneMenuRows(this.timezone).map((r) => ({ id: r.value, label: r.label, checked: r.checked }));
1193
1203
  }
1194
1204
  tick() {
1195
1205
  try {
@@ -1198,7 +1208,7 @@ var Bottombar = class {
1198
1208
  minute: "2-digit",
1199
1209
  second: "2-digit",
1200
1210
  hour12: false,
1201
- timeZone: this.timezone
1211
+ timeZone: this.displayZone
1202
1212
  }).format(/* @__PURE__ */ new Date());
1203
1213
  } catch {
1204
1214
  this.clockEl.textContent = "";
@@ -4756,12 +4766,11 @@ function priceAxisItems(s) {
4756
4766
  ];
4757
4767
  }
4758
4768
  function timeAxisItems(timezone) {
4759
- const active = timezone === "UTC" ? "Etc/UTC" : timezone;
4760
4769
  return [
4761
4770
  {
4762
4771
  id: "timezone",
4763
4772
  label: "Time zone",
4764
- submenu: TIMEZONES.map((t) => ({ id: `tz:${t.value}`, label: tzMenuLabel(t.value, t.label), checked: t.value === active }))
4773
+ submenu: timezoneMenuRows(timezone).map((r) => ({ id: `tz:${r.value}`, label: r.label, checked: r.checked }))
4765
4774
  },
4766
4775
  settingsItem("time-axis", "More settings\u2026")
4767
4776
  ];
@@ -4882,7 +4891,7 @@ var ChartContextMenu = class {
4882
4891
  } else if (id.startsWith("tz:")) {
4883
4892
  const zone = id.slice("tz:".length);
4884
4893
  if (this.cbs.setTimezone) this.cbs.setTimezone(zone);
4885
- else chart.renderer.set("timezone", zone);
4894
+ else chart.renderer.set("timezone", resolveTimezone(zone, void 0));
4886
4895
  } else if (id === "auto") {
4887
4896
  chart.renderer.set("autoScale", chart.renderer.get("autoScale") === false);
4888
4897
  } else if (id === "invert") {
@@ -5641,7 +5650,7 @@ var ChartCell = class {
5641
5650
  this.history.onChart(this.inner);
5642
5651
  this.inner.renderer.onConfigChanged(() => {
5643
5652
  const zone = this.inner?.renderer.get("timezone");
5644
- if (typeof zone === "string" && normalizeTimezone(zone) !== normalizeTimezone(this.deps.timezone())) {
5653
+ if (typeof zone === "string" && normalizeTimezone(zone) !== normalizeTimezone(this.displayTimezone)) {
5645
5654
  this.deps.setTimezone(normalizeTimezone(zone));
5646
5655
  }
5647
5656
  const style = this.priceStyle;
@@ -5683,8 +5692,7 @@ var ChartCell = class {
5683
5692
  this.refreshSessionShading();
5684
5693
  });
5685
5694
  this.inner.on("viewport:changed", (range) => this.sessionShading.updateRange(range));
5686
- const tz = deps.timezone();
5687
- if (tz !== "Etc/UTC") this.inner.renderer.set("timezone", tz);
5695
+ this.applyTimezone();
5688
5696
  this.indicatorTitlesOn = seed.indicatorTitles ?? true;
5689
5697
  if (!this.indicatorTitlesOn) this.inner.renderer.set("indicatorTitles", false);
5690
5698
  this.indicatorValuesOn = seed.indicatorValues ?? true;
@@ -5706,7 +5714,7 @@ var ChartCell = class {
5706
5714
  this.statusline?.setSymbol(this.state.symbol);
5707
5715
  this.statusline?.setMeta(this.state.timeframe ?? "60", this.inner.data.displayPrefix(this.state.symbol) ?? this.state.provider ?? "");
5708
5716
  }
5709
- this.refreshSessionAvailable();
5717
+ this.refreshSymbolMetadata();
5710
5718
  if (this.inner && this.state.symbol) this.marketStatus?.track(this.inner.data, this.state.symbol);
5711
5719
  });
5712
5720
  this.syncStatuslineColors();
@@ -5780,7 +5788,7 @@ var ChartCell = class {
5780
5788
  this.offMarket = this.inner.on("market:changed", ({ symbol: symbol2, timeframe }) => {
5781
5789
  this.projectMarket(symbol2, timeframe);
5782
5790
  this.refreshNativeCatalog();
5783
- this.refreshSessionAvailable();
5791
+ this.refreshSymbolMetadata();
5784
5792
  if (this.inner) this.marketStatus?.track(this.inner.data, symbol2);
5785
5793
  this.deps.onMarketChanged(this.id);
5786
5794
  });
@@ -5821,7 +5829,35 @@ var ChartCell = class {
5821
5829
  this.deps.onStateDirty();
5822
5830
  void this.inner?.setMarket({ session });
5823
5831
  }
5824
- refreshSessionAvailable() {
5832
+ /**
5833
+ * The symbol's own trading zone, once its metadata has landed — what the exchange
5834
+ * rule resolves to on this cell (the workspace labels the shared bottom bar with the
5835
+ * ACTIVE cell's). Undefined = unknown or undeclared (the rule then renders UTC).
5836
+ */
5837
+ get exchangeTimezone() {
5838
+ return this.exchangeZone;
5839
+ }
5840
+ /** The IANA zone this cell's axis renders in: the workspace choice, resolved. */
5841
+ get displayTimezone() {
5842
+ return resolveTimezone(this.deps.timezone(), this.exchangeZone);
5843
+ }
5844
+ /**
5845
+ * Push the resolved zone to the renderer — on the workspace choice changing, and on
5846
+ * this cell's market zone changing under the exchange rule. Skips the write when the
5847
+ * renderer already holds it (its config default is the bare `'UTC'` alias).
5848
+ */
5849
+ applyTimezone() {
5850
+ const chart = this.inner;
5851
+ if (!chart) return;
5852
+ const zone = this.displayTimezone;
5853
+ const current = chart.renderer.get("timezone");
5854
+ const held = typeof current === "string" && current ? current : "UTC";
5855
+ if (normalizeTimezone(held) === normalizeTimezone(zone)) return;
5856
+ chart.renderer.set("timezone", zone);
5857
+ }
5858
+ /** Re-read the symbol's metadata: session posture (RTH/ETH toggle, shading) and its
5859
+ * trading zone (the exchange rule). Async — the workspace re-projects when a verdict lands. */
5860
+ refreshSymbolMetadata() {
5825
5861
  const chart = this.inner;
5826
5862
  const symbol = this.state.symbol;
5827
5863
  if (!chart || !symbol) return;
@@ -5829,7 +5865,13 @@ var ChartCell = class {
5829
5865
  if (this.inner !== chart) return;
5830
5866
  const available = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
5831
5867
  const overnight = parseSessionSpec(si)?.overnight === true;
5832
- if (available !== this.sessionAvailableFlag || overnight !== this.sessionOvernightFlag) {
5868
+ const zone = typeof si?.timezone === "string" && si.timezone !== "" ? si.timezone : void 0;
5869
+ const zoneChanged = zone !== this.exchangeZone;
5870
+ if (zoneChanged) {
5871
+ this.exchangeZone = zone;
5872
+ this.applyTimezone();
5873
+ }
5874
+ if (available !== this.sessionAvailableFlag || overnight !== this.sessionOvernightFlag || zoneChanged) {
5833
5875
  this.sessionAvailableFlag = available;
5834
5876
  this.sessionOvernightFlag = overnight;
5835
5877
  this.deps.onMarketChanged(this.id);
@@ -7735,15 +7777,14 @@ var TimezoneDrawer = class {
7735
7777
  this.drawer.body.replaceChildren();
7736
7778
  const list = doc.createElement("div");
7737
7779
  list.className = "vela-tzd-list";
7738
- const current = normalizeTimezone(this.opts.timezone());
7739
- for (const tz of TIMEZONES) {
7780
+ for (const tz of timezoneMenuRows(this.opts.timezone())) {
7740
7781
  const row = doc.createElement("div");
7741
7782
  row.className = "vela-tzd-row";
7742
7783
  const label = doc.createElement("span");
7743
7784
  label.className = "vela-tzd-row-label";
7744
- label.textContent = tzMenuLabel(tz.value, tz.label);
7785
+ label.textContent = tz.label;
7745
7786
  row.appendChild(label);
7746
- if (tz.value === current) row.appendChild(iconEl("check", doc));
7787
+ if (tz.checked) row.appendChild(iconEl("check", doc));
7747
7788
  row.addEventListener("click", () => {
7748
7789
  this.opts.onTimezone(tz.value);
7749
7790
  this.drawer.hide();
@@ -8851,13 +8892,23 @@ var VelaWorkspace = class {
8851
8892
  }
8852
8893
  }
8853
8894
  }
8854
- /** Set the workspace-global display timezone — applied to EVERY cell. */
8895
+ /**
8896
+ * Set the workspace-global display timezone — applied to EVERY cell. An IANA zone,
8897
+ * or `'exchange'` (the exchange rule): each cell then renders in its OWN market's
8898
+ * zone (Chicago for a CME future, New York for a US equity, UTC for crypto), and the
8899
+ * bottom bar follows the active cell's.
8900
+ */
8855
8901
  setTimezone(zone) {
8856
8902
  this.timezone = zone;
8857
- this.bottombar?.setTimezone(zone);
8858
- for (const cell of this.cellsById.values()) cell.chart.renderer.set("timezone", zone);
8903
+ this.projectTimezone();
8904
+ for (const cell of this.cellsById.values()) cell.applyTimezone();
8859
8905
  this.markStateDirty();
8860
8906
  }
8907
+ /** Bottom bar ⇐ the stored choice + the ACTIVE cell's market zone (labels the exchange row). */
8908
+ projectTimezone() {
8909
+ const active = this.activeId ? this.cellsById.get(this.activeId) : void 0;
8910
+ this.bottombar?.setTimezone(this.timezone, active?.exchangeTimezone);
8911
+ }
8861
8912
  /**
8862
8913
  * Swap the workspace theme at runtime — `'dark'`, `'light'`, or a full custom theme,
8863
8914
  * applied to the shared chrome (topbar, panels, drawing toolbar) and EVERY cell.
@@ -9062,6 +9113,7 @@ var VelaWorkspace = class {
9062
9113
  this.dock.onChart(cell.chart);
9063
9114
  this.bottombar?.setActiveRange(cell.activeRangeId);
9064
9115
  this.bottombar?.setSession({ session: cell.session, enabled: cell.sessionAvailable });
9116
+ this.projectTimezone();
9065
9117
  this.indicatorPicker?.sync();
9066
9118
  this.glider.stop();
9067
9119
  const d = cell.chart.drawings;
@@ -9646,6 +9698,7 @@ var VelaWorkspace = class {
9646
9698
  this.mobileBar?.setTimeframe(cell.timeframe);
9647
9699
  this.objectTree.setSymbol(cell.symbol);
9648
9700
  this.bottombar?.setSession({ session: cell.session, enabled: cell.sessionAvailable });
9701
+ this.projectTimezone();
9649
9702
  this.bottombar?.setActiveRange(cell.activeRangeId);
9650
9703
  }
9651
9704
  /** Trigger ② — a cell's price style changed: the topbar button/menu only if active.
@@ -47,6 +47,23 @@ interface StrategyTrade {
47
47
  exit?: StrategyFill;
48
48
  /** Still open at the last computed bar. */
49
49
  open: boolean;
50
+ /**
51
+ * Realized P&L of this round trip in account currency, net of commission. Absent
52
+ * while the trade is open, and for an engine that reports no per-trade ledger.
53
+ */
54
+ pnl?: number;
55
+ /** Commission charged on this trade, in account currency. */
56
+ commission?: number;
57
+ /**
58
+ * Worst / best unrealized excursion of THIS trade from its entry, in account
59
+ * currency (positive magnitudes). Read `maxDrawdown` as the adverse excursion and
60
+ * `maxRunup` as the favorable one — the per-trade counterparts of the account-level
61
+ * {@link StrategyState.maxDrawdown} / {@link StrategyState.maxRunup}. An engine
62
+ * defines the exact convention (intrabar extremes, commission handling); absent when
63
+ * it does not track them.
64
+ */
65
+ maxDrawdown?: number;
66
+ maxRunup?: number;
50
67
  }
51
68
  /** One side of a {@link StrategyTrade}. */
52
69
  interface StrategyFill {
@@ -47,6 +47,23 @@ interface StrategyTrade {
47
47
  exit?: StrategyFill;
48
48
  /** Still open at the last computed bar. */
49
49
  open: boolean;
50
+ /**
51
+ * Realized P&L of this round trip in account currency, net of commission. Absent
52
+ * while the trade is open, and for an engine that reports no per-trade ledger.
53
+ */
54
+ pnl?: number;
55
+ /** Commission charged on this trade, in account currency. */
56
+ commission?: number;
57
+ /**
58
+ * Worst / best unrealized excursion of THIS trade from its entry, in account
59
+ * currency (positive magnitudes). Read `maxDrawdown` as the adverse excursion and
60
+ * `maxRunup` as the favorable one — the per-trade counterparts of the account-level
61
+ * {@link StrategyState.maxDrawdown} / {@link StrategyState.maxRunup}. An engine
62
+ * defines the exact convention (intrabar extremes, commission handling); absent when
63
+ * it does not track them.
64
+ */
65
+ maxDrawdown?: number;
66
+ maxRunup?: number;
50
67
  }
51
68
  /** One side of a {@link StrategyTrade}. */
52
69
  interface StrategyFill {