@adrienhobbs/candlekit 0.2.3 → 0.2.5
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 +19 -2
- package/dist/index.js +81 -31
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
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
|
|
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,11 +347,11 @@ function ChartComponent({
|
|
|
346
347
|
vertLines: { color: "#1e293b" },
|
|
347
348
|
horzLines: { color: "#1e293b" }
|
|
348
349
|
},
|
|
349
|
-
// Render axis ticks + crosshair in the viewer's
|
|
350
|
-
// charts otherwise renders numeric times as UTC,
|
|
351
|
-
// local-time
|
|
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.
|
|
352
353
|
localization: {
|
|
353
|
-
timeFormatter:
|
|
354
|
+
timeFormatter: makeCrosshairTimeFormatter(timeZone)
|
|
354
355
|
},
|
|
355
356
|
width: chartContainerRef.current.clientWidth,
|
|
356
357
|
// Auto-fill the container's height unless an explicit `height` is given.
|
|
@@ -361,7 +362,7 @@ function ChartComponent({
|
|
|
361
362
|
timeVisible: true,
|
|
362
363
|
secondsVisible: true,
|
|
363
364
|
borderColor: "#334155",
|
|
364
|
-
tickMarkFormatter:
|
|
365
|
+
tickMarkFormatter: makeTickMarkFormatter(timeZone)
|
|
365
366
|
},
|
|
366
367
|
rightPriceScale: {
|
|
367
368
|
borderColor: "#334155"
|
|
@@ -523,6 +524,9 @@ function ChartComponent({
|
|
|
523
524
|
container.removeEventListener("mouseup", handleMouseUp, true);
|
|
524
525
|
container.removeEventListener("click", handleClick, true);
|
|
525
526
|
chart.remove();
|
|
527
|
+
indicatorSeriesRef.current.clear();
|
|
528
|
+
indicatorPaneIndexRef.current.clear();
|
|
529
|
+
nextPaneIndexRef.current = 2;
|
|
526
530
|
};
|
|
527
531
|
}, []);
|
|
528
532
|
useEffect(() => {
|
|
@@ -687,6 +691,36 @@ function ChartComponent({
|
|
|
687
691
|
window.removeEventListener("resize", updatePositions);
|
|
688
692
|
};
|
|
689
693
|
}, [lines]);
|
|
694
|
+
useEffect(() => {
|
|
695
|
+
const series = candlestickSeriesRef.current;
|
|
696
|
+
if (!chartRef.current || !series) return;
|
|
697
|
+
if (priceBands.length === 0) {
|
|
698
|
+
setBandRects([]);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const recompute = () => {
|
|
702
|
+
if (!candlestickSeriesRef.current) return;
|
|
703
|
+
const rects = [];
|
|
704
|
+
for (const band of priceBands) {
|
|
705
|
+
const yTop = candlestickSeriesRef.current.priceToCoordinate(band.top);
|
|
706
|
+
const yBottom = candlestickSeriesRef.current.priceToCoordinate(band.bottom);
|
|
707
|
+
if (yTop == null || yBottom == null) continue;
|
|
708
|
+
const top = Math.min(yTop, yBottom);
|
|
709
|
+
const height2 = Math.abs(yBottom - yTop);
|
|
710
|
+
rects.push({ id: band.id, top, height: height2, color: band.color });
|
|
711
|
+
}
|
|
712
|
+
setBandRects(rects);
|
|
713
|
+
};
|
|
714
|
+
const raf = requestAnimationFrame(recompute);
|
|
715
|
+
const timeScale = chartRef.current.timeScale();
|
|
716
|
+
timeScale.subscribeVisibleLogicalRangeChange(recompute);
|
|
717
|
+
window.addEventListener("resize", recompute);
|
|
718
|
+
return () => {
|
|
719
|
+
cancelAnimationFrame(raf);
|
|
720
|
+
chartRef.current?.timeScale().unsubscribeVisibleLogicalRangeChange(recompute);
|
|
721
|
+
window.removeEventListener("resize", recompute);
|
|
722
|
+
};
|
|
723
|
+
}, [priceBands]);
|
|
690
724
|
useEffect(() => {
|
|
691
725
|
if (!chartRef.current) return;
|
|
692
726
|
indicatorSeriesRef.current.forEach((series) => {
|
|
@@ -714,7 +748,7 @@ function ChartComponent({
|
|
|
714
748
|
lineWidth: indicator.settings.lineWidth || 2,
|
|
715
749
|
title: indicator.name
|
|
716
750
|
}, paneIndex);
|
|
717
|
-
const lineData = data.map((d) => ({ time: d.time, value: d.value }));
|
|
751
|
+
const lineData = data.map((d) => ({ time: d.time, value: d.value })).filter((d) => Number.isFinite(d.value));
|
|
718
752
|
series.setData(lineData);
|
|
719
753
|
indicatorSeriesRef.current.set(indicator.id, series);
|
|
720
754
|
} else if (definition.renderConfig.hasBandFill && definition.renderConfig.fillBands) {
|
|
@@ -757,7 +791,7 @@ function ChartComponent({
|
|
|
757
791
|
color: indicator.settings.color || "#8b5cf6",
|
|
758
792
|
title: indicator.name
|
|
759
793
|
}, paneIndex);
|
|
760
|
-
const histData = data.map((d) => ({ time: d.time, value: d.value }));
|
|
794
|
+
const histData = data.map((d) => ({ time: d.time, value: d.value })).filter((d) => Number.isFinite(d.value));
|
|
761
795
|
series.setData(histData);
|
|
762
796
|
indicatorSeriesRef.current.set(indicator.id, series);
|
|
763
797
|
} else if (seriesType === "area") {
|
|
@@ -768,7 +802,7 @@ function ChartComponent({
|
|
|
768
802
|
lineWidth: indicator.settings.lineWidth || 2,
|
|
769
803
|
title: indicator.name
|
|
770
804
|
}, paneIndex);
|
|
771
|
-
const areaData = data.map((d) => ({ time: d.time, value: d.value }));
|
|
805
|
+
const areaData = data.map((d) => ({ time: d.time, value: d.value })).filter((d) => Number.isFinite(d.value));
|
|
772
806
|
series.setData(areaData);
|
|
773
807
|
indicatorSeriesRef.current.set(indicator.id, series);
|
|
774
808
|
}
|
|
@@ -784,7 +818,7 @@ function ChartComponent({
|
|
|
784
818
|
if (definition.renderConfig.outputCount === 1) {
|
|
785
819
|
const series = indicatorSeriesRef.current.get(indicator.id);
|
|
786
820
|
if (series) {
|
|
787
|
-
const lineData = data.map((d) => ({ time: d.time, value: d.value }));
|
|
821
|
+
const lineData = data.map((d) => ({ time: d.time, value: d.value })).filter((d) => Number.isFinite(d.value));
|
|
788
822
|
series.setData(lineData);
|
|
789
823
|
}
|
|
790
824
|
} else if (definition.renderConfig.hasBandFill && definition.renderConfig.fillBands) {
|
|
@@ -887,6 +921,14 @@ function ChartComponent({
|
|
|
887
921
|
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..." }),
|
|
888
922
|
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
889
923
|
/* @__PURE__ */ jsx("div", { ref: chartContainerRef, className: "w-full" }),
|
|
924
|
+
bandRects.map((b) => /* @__PURE__ */ jsx(
|
|
925
|
+
"div",
|
|
926
|
+
{
|
|
927
|
+
className: "absolute left-0 right-0 pointer-events-none z-4",
|
|
928
|
+
style: { top: `${b.top}px`, height: `${b.height}px`, background: b.color }
|
|
929
|
+
},
|
|
930
|
+
b.id
|
|
931
|
+
)),
|
|
890
932
|
enableBarSelection && spotlightPosition && /* @__PURE__ */ jsx(
|
|
891
933
|
"div",
|
|
892
934
|
{
|
|
@@ -1014,30 +1056,38 @@ function getLineStyle(style) {
|
|
|
1014
1056
|
return LineStyle.Solid;
|
|
1015
1057
|
}
|
|
1016
1058
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1059
|
+
function makeTickMarkFormatter(timeZone) {
|
|
1060
|
+
const time = new Intl.DateTimeFormat("en-US", { timeZone, hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
|
|
1061
|
+
const timeSec = new Intl.DateTimeFormat("en-US", { timeZone, hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23" });
|
|
1062
|
+
const month = new Intl.DateTimeFormat("en-US", { timeZone, month: "short" });
|
|
1063
|
+
const day = new Intl.DateTimeFormat("en-US", { timeZone, month: "short", day: "numeric" });
|
|
1064
|
+
const year = new Intl.DateTimeFormat("en-US", { timeZone, year: "numeric" });
|
|
1065
|
+
return (t, tickMarkType) => {
|
|
1066
|
+
const d = new Date(t * 1e3);
|
|
1067
|
+
switch (tickMarkType) {
|
|
1068
|
+
case 0:
|
|
1069
|
+
return year.format(d);
|
|
1070
|
+
case 1:
|
|
1071
|
+
return month.format(d);
|
|
1072
|
+
case 2:
|
|
1073
|
+
return day.format(d);
|
|
1074
|
+
case 4:
|
|
1075
|
+
return timeSec.format(d);
|
|
1076
|
+
default:
|
|
1077
|
+
return time.format(d);
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1032
1080
|
}
|
|
1033
|
-
function
|
|
1034
|
-
|
|
1081
|
+
function makeCrosshairTimeFormatter(timeZone) {
|
|
1082
|
+
const fmt = new Intl.DateTimeFormat("en-US", {
|
|
1083
|
+
timeZone,
|
|
1035
1084
|
month: "short",
|
|
1036
1085
|
day: "2-digit",
|
|
1037
1086
|
hour: "2-digit",
|
|
1038
1087
|
minute: "2-digit",
|
|
1039
|
-
|
|
1088
|
+
hourCycle: "h23"
|
|
1040
1089
|
});
|
|
1090
|
+
return (t) => fmt.format(new Date(t * 1e3));
|
|
1041
1091
|
}
|
|
1042
1092
|
var IndicatorCategory = /* @__PURE__ */ ((IndicatorCategory2) => {
|
|
1043
1093
|
IndicatorCategory2["TREND"] = "Trend";
|