@adrienhobbs/candlekit 0.2.2 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -21,6 +21,16 @@ interface ChartLine {
21
21
  title?: string;
22
22
  type?: 'entry' | 'stopLoss' | 'takeProfit' | 'mfe' | 'mae';
23
23
  }
24
+ /** A shaded horizontal price band (e.g. an MFE↔MAE excursion zone). */
25
+ interface PriceBand {
26
+ id: string;
27
+ /** Upper price bound of the band. */
28
+ top: number;
29
+ /** Lower price bound of the band. */
30
+ bottom: number;
31
+ /** CSS color (use an rgba/low-opacity fill so candles show through). */
32
+ color: string;
33
+ }
24
34
  interface ChartTrade {
25
35
  id: string;
26
36
  entryTime: number;
@@ -177,8 +187,15 @@ interface ChartComponentProps {
177
187
  * view. Pairs with `selectedTradeId` to drive both selection and focus.
178
188
  */
179
189
  focusTradeId?: string | null;
190
+ /**
191
+ * IANA timezone (e.g. "America/New_York") for the axis ticks + crosshair
192
+ * labels. Omit to use the viewer's local timezone.
193
+ */
194
+ timeZone?: string;
195
+ /** Shaded horizontal price bands (e.g. an MFE↔MAE excursion zone). */
196
+ priceBands?: PriceBand[];
180
197
  }
181
- declare function ChartComponent({ bars, onLoadMoreData, indicators, lines, onBarUpdate, onNewBar, onDeleteLine, onAddLine, onClearAllLines, enableBarSelection, onBarClick, trades, selectedTradeId, renderTradePopup, height, focusTradeId, }: ChartComponentProps): react_jsx_runtime.JSX.Element;
198
+ declare function ChartComponent({ bars, onLoadMoreData, indicators, lines, onBarUpdate, onNewBar, onDeleteLine, onAddLine, onClearAllLines, enableBarSelection, onBarClick, trades, selectedTradeId, renderTradePopup, height, focusTradeId, timeZone, priceBands, }: ChartComponentProps): react_jsx_runtime.JSX.Element;
182
199
 
183
200
  interface IndicatorBrowserProps {
184
201
  isOpen: boolean;
@@ -445,4 +462,4 @@ declare function getOldestBar(bars: OHLCVBar[]): OHLCVBar | null;
445
462
  declare function getNewestBar(bars: OHLCVBar[]): OHLCVBar | null;
446
463
  declare function updateCurrentBar(bars: OHLCVBar[], tradePrice: number, tradeVolume: number): OHLCVBar[];
447
464
 
448
- export { ADXIndicator, ATRIndicator, AlpacaBarAdapter, type BarDataAdapter, type BarDataAdapterOptions, BollingerBandsIndicator, CCIIndicator, ChartComponent, type ChartLine, ChartSeriesType, type ChartTrade, DonchianChannelsIndicator, EMAIndicator, ForceIndexIndicator, type HistoricalDataParams, IchimokuIndicator, IndicatorBrowser, type IndicatorCalculation, IndicatorCategory, type IndicatorDefinition, type IndicatorInstance, IndicatorInstanceSchema, type IndicatorMetadata, IndicatorMetadataSchema, type IndicatorOutput, type IndicatorPanel, type IndicatorSettings$1 as IndicatorSettings, IndicatorSettingsForm, IndicatorSettingsSchema, KeltnerChannelsIndicator, type LineStyle, LineStyleSchema, LocalStoragePersistenceAdapter, MACDIndicator, MFIIndicator, MockAdapter, NoOpPersistenceAdapter, OBVIndicator, type OHLCVBar, PSARIndicator, type PersistenceAdapter, ROCIndicator, RSIIndicator, type RealtimeHandlers, type RealtimeSubscription, type RenderConfig, RenderConfigSchema, SMAIndicator, type SettingField, SettingFieldSchema, type SettingFieldType, SettingFieldTypeSchema, SettingsDialog, StochRSIIndicator, StochasticIndicator, SuperTrendIndicator, VWAPIndicator, WMAIndicator, WilliamsRIndicator, appendBar, calculateBollingerBands, calculateEMA, calculateRSI, calculateSMA, calculateStandardDeviation, createPersistenceAdapter, deduplicateBars, displaceArray, getNewestBar, getOldestBar, indicatorCalculator, indicatorRegistry, isValidBar, mergeBars, normalizeTimestamp, padIndicatorArray, prependBars, registerBuiltInIndicators, sortBars, updateBarInArray, updateCurrentBar, useBarsData, useChartAPI, useRealtimeUpdates, validateAndNormalizeBars, validateBar };
465
+ export { ADXIndicator, ATRIndicator, AlpacaBarAdapter, type BarDataAdapter, type BarDataAdapterOptions, BollingerBandsIndicator, CCIIndicator, ChartComponent, type ChartLine, ChartSeriesType, type ChartTrade, DonchianChannelsIndicator, EMAIndicator, ForceIndexIndicator, type HistoricalDataParams, IchimokuIndicator, IndicatorBrowser, type IndicatorCalculation, IndicatorCategory, type IndicatorDefinition, type IndicatorInstance, IndicatorInstanceSchema, type IndicatorMetadata, IndicatorMetadataSchema, type IndicatorOutput, type IndicatorPanel, type IndicatorSettings$1 as IndicatorSettings, IndicatorSettingsForm, IndicatorSettingsSchema, KeltnerChannelsIndicator, type LineStyle, LineStyleSchema, LocalStoragePersistenceAdapter, MACDIndicator, MFIIndicator, MockAdapter, NoOpPersistenceAdapter, OBVIndicator, type OHLCVBar, PSARIndicator, type PersistenceAdapter, type PriceBand, ROCIndicator, RSIIndicator, type RealtimeHandlers, type RealtimeSubscription, type RenderConfig, RenderConfigSchema, SMAIndicator, type SettingField, SettingFieldSchema, type SettingFieldType, SettingFieldTypeSchema, SettingsDialog, StochRSIIndicator, StochasticIndicator, SuperTrendIndicator, VWAPIndicator, WMAIndicator, WilliamsRIndicator, appendBar, calculateBollingerBands, calculateEMA, calculateRSI, calculateSMA, calculateStandardDeviation, createPersistenceAdapter, deduplicateBars, displaceArray, getNewestBar, getOldestBar, indicatorCalculator, indicatorRegistry, isValidBar, mergeBars, normalizeTimestamp, padIndicatorArray, prependBars, registerBuiltInIndicators, sortBars, updateBarInArray, updateCurrentBar, useBarsData, useChartAPI, useRealtimeUpdates, validateAndNormalizeBars, validateBar };
package/dist/index.js CHANGED
@@ -260,13 +260,11 @@ var BandsPrimitive = class {
260
260
  // src/components/trade-markers.ts
261
261
  var WIN = "#10b981";
262
262
  var LOSS = "#ef4444";
263
- var SEL = "#3b82f6";
264
263
  function buildTradeMarkers(trades, selectedTradeId) {
265
264
  const markers = [];
266
265
  for (const t of trades) {
267
266
  const selected = t.id === selectedTradeId;
268
- const base = t.outcome === "win" ? WIN : LOSS;
269
- const color = selected ? SEL : base;
267
+ const color = t.outcome === "win" ? WIN : LOSS;
270
268
  markers.push({
271
269
  time: t.entryTime / 1e3,
272
270
  position: "belowBar",
@@ -301,7 +299,9 @@ function ChartComponent({
301
299
  selectedTradeId = null,
302
300
  renderTradePopup,
303
301
  height,
304
- focusTradeId = null
302
+ focusTradeId = null,
303
+ timeZone,
304
+ priceBands = []
305
305
  }) {
306
306
  const chartContainerRef = useRef(null);
307
307
  const chartRef = useRef(null);
@@ -315,6 +315,7 @@ function ChartComponent({
315
315
  const [isLoadingMore, setIsLoadingMore] = useState(false);
316
316
  const [contextMenu, setContextMenu] = useState(null);
317
317
  const [linePositions, setLinePositions] = useState(/* @__PURE__ */ new Map());
318
+ const [bandRects, setBandRects] = useState([]);
318
319
  const [selectedBar, setSelectedBar] = useState(null);
319
320
  const selectedBarRef = useRef(null);
320
321
  const [spotlightPosition, setSpotlightPosition] = useState(null);
@@ -346,6 +347,12 @@ function ChartComponent({
346
347
  vertLines: { color: "#1e293b" },
347
348
  horzLines: { color: "#1e293b" }
348
349
  },
350
+ // Render axis ticks + crosshair in `timeZone` (default: the viewer's local
351
+ // timezone). lightweight-charts otherwise renders numeric times as UTC,
352
+ // which mismatches local/exchange-time labels alongside the chart.
353
+ localization: {
354
+ timeFormatter: makeCrosshairTimeFormatter(timeZone)
355
+ },
349
356
  width: chartContainerRef.current.clientWidth,
350
357
  // Auto-fill the container's height unless an explicit `height` is given.
351
358
  // Fall back to 600 only when the container hasn't been laid out yet (a
@@ -354,7 +361,8 @@ function ChartComponent({
354
361
  timeScale: {
355
362
  timeVisible: true,
356
363
  secondsVisible: true,
357
- borderColor: "#334155"
364
+ borderColor: "#334155",
365
+ tickMarkFormatter: makeTickMarkFormatter(timeZone)
358
366
  },
359
367
  rightPriceScale: {
360
368
  borderColor: "#334155"
@@ -680,6 +688,36 @@ function ChartComponent({
680
688
  window.removeEventListener("resize", updatePositions);
681
689
  };
682
690
  }, [lines]);
691
+ useEffect(() => {
692
+ const series = candlestickSeriesRef.current;
693
+ if (!chartRef.current || !series) return;
694
+ if (priceBands.length === 0) {
695
+ setBandRects([]);
696
+ return;
697
+ }
698
+ const recompute = () => {
699
+ if (!candlestickSeriesRef.current) return;
700
+ const rects = [];
701
+ for (const band of priceBands) {
702
+ const yTop = candlestickSeriesRef.current.priceToCoordinate(band.top);
703
+ const yBottom = candlestickSeriesRef.current.priceToCoordinate(band.bottom);
704
+ if (yTop == null || yBottom == null) continue;
705
+ const top = Math.min(yTop, yBottom);
706
+ const height2 = Math.abs(yBottom - yTop);
707
+ rects.push({ id: band.id, top, height: height2, color: band.color });
708
+ }
709
+ setBandRects(rects);
710
+ };
711
+ const raf = requestAnimationFrame(recompute);
712
+ const timeScale = chartRef.current.timeScale();
713
+ timeScale.subscribeVisibleLogicalRangeChange(recompute);
714
+ window.addEventListener("resize", recompute);
715
+ return () => {
716
+ cancelAnimationFrame(raf);
717
+ chartRef.current?.timeScale().unsubscribeVisibleLogicalRangeChange(recompute);
718
+ window.removeEventListener("resize", recompute);
719
+ };
720
+ }, [priceBands]);
683
721
  useEffect(() => {
684
722
  if (!chartRef.current) return;
685
723
  indicatorSeriesRef.current.forEach((series) => {
@@ -880,6 +918,14 @@ function ChartComponent({
880
918
  isLoadingMore && /* @__PURE__ */ jsx("div", { className: "absolute top-4 left-1/2 transform -translate-x-1/2 bg-slate-800 text-slate-200 px-4 py-2 rounded-lg shadow-lg z-10", children: "Loading more data..." }),
881
919
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
882
920
  /* @__PURE__ */ jsx("div", { ref: chartContainerRef, className: "w-full" }),
921
+ bandRects.map((b) => /* @__PURE__ */ jsx(
922
+ "div",
923
+ {
924
+ className: "absolute left-0 right-0 pointer-events-none z-4",
925
+ style: { top: `${b.top}px`, height: `${b.height}px`, background: b.color }
926
+ },
927
+ b.id
928
+ )),
883
929
  enableBarSelection && spotlightPosition && /* @__PURE__ */ jsx(
884
930
  "div",
885
931
  {
@@ -1007,6 +1053,39 @@ function getLineStyle(style) {
1007
1053
  return LineStyle.Solid;
1008
1054
  }
1009
1055
  }
1056
+ function makeTickMarkFormatter(timeZone) {
1057
+ const time = new Intl.DateTimeFormat("en-US", { timeZone, hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
1058
+ const timeSec = new Intl.DateTimeFormat("en-US", { timeZone, hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23" });
1059
+ const month = new Intl.DateTimeFormat("en-US", { timeZone, month: "short" });
1060
+ const day = new Intl.DateTimeFormat("en-US", { timeZone, month: "short", day: "numeric" });
1061
+ const year = new Intl.DateTimeFormat("en-US", { timeZone, year: "numeric" });
1062
+ return (t, tickMarkType) => {
1063
+ const d = new Date(t * 1e3);
1064
+ switch (tickMarkType) {
1065
+ case 0:
1066
+ return year.format(d);
1067
+ case 1:
1068
+ return month.format(d);
1069
+ case 2:
1070
+ return day.format(d);
1071
+ case 4:
1072
+ return timeSec.format(d);
1073
+ default:
1074
+ return time.format(d);
1075
+ }
1076
+ };
1077
+ }
1078
+ function makeCrosshairTimeFormatter(timeZone) {
1079
+ const fmt = new Intl.DateTimeFormat("en-US", {
1080
+ timeZone,
1081
+ month: "short",
1082
+ day: "2-digit",
1083
+ hour: "2-digit",
1084
+ minute: "2-digit",
1085
+ hourCycle: "h23"
1086
+ });
1087
+ return (t) => fmt.format(new Date(t * 1e3));
1088
+ }
1010
1089
  var IndicatorCategory = /* @__PURE__ */ ((IndicatorCategory2) => {
1011
1090
  IndicatorCategory2["TREND"] = "Trend";
1012
1091
  IndicatorCategory2["MOMENTUM"] = "Momentum";