react-klinecharts-ui 0.1.0 → 0.3.0
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 +528 -29
- package/dist/chunk-PIFLJKYF.cjs +2512 -0
- package/dist/chunk-PIFLJKYF.cjs.map +1 -0
- package/dist/chunk-U325AHHX.js +2468 -0
- package/dist/chunk-U325AHHX.js.map +1 -0
- package/dist/extensions.cjs +12 -4
- package/dist/extensions.d.cts +22 -1
- package/dist/extensions.d.ts +22 -1
- package/dist/extensions.js +1 -1
- package/dist/index.cjs +1936 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +441 -3
- package/dist/index.d.ts +441 -3
- package/dist/index.js +1815 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-PUULIBFN.js +0 -911
- package/dist/chunk-PUULIBFN.js.map +0 -1
- package/dist/chunk-SLTXNLKN.cjs +0 -932
- package/dist/chunk-SLTXNLKN.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkPIFLJKYF_cjs = require('./chunk-PIFLJKYF.cjs');
|
|
4
4
|
var react = require('react');
|
|
5
5
|
var reactKlinecharts = require('react-klinecharts');
|
|
6
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
@@ -125,6 +125,7 @@ function KlinechartsUIProvider({
|
|
|
125
125
|
})
|
|
126
126
|
);
|
|
127
127
|
const fullscreenContainerRef = react.useRef(null);
|
|
128
|
+
const undoRedoListenerRef = react.useRef(null);
|
|
128
129
|
const stateRef = react.useRef(state);
|
|
129
130
|
stateRef.current = state;
|
|
130
131
|
const callbacksRef = react.useRef({
|
|
@@ -176,7 +177,7 @@ function KlinechartsUIProvider({
|
|
|
176
177
|
const extraOverlaysRef = react.useRef(extraOverlays);
|
|
177
178
|
react.useEffect(() => {
|
|
178
179
|
if (shouldRegister) {
|
|
179
|
-
|
|
180
|
+
chunkPIFLJKYF_cjs.registerExtensions();
|
|
180
181
|
}
|
|
181
182
|
extraOverlaysRef.current?.forEach((overlay) => reactKlinecharts.registerOverlay(overlay));
|
|
182
183
|
}, [shouldRegister]);
|
|
@@ -190,7 +191,8 @@ function KlinechartsUIProvider({
|
|
|
190
191
|
dispatch: enhancedDispatch,
|
|
191
192
|
datafeed,
|
|
192
193
|
onSettingsChange,
|
|
193
|
-
fullscreenContainerRef
|
|
194
|
+
fullscreenContainerRef,
|
|
195
|
+
undoRedoListenerRef
|
|
194
196
|
}),
|
|
195
197
|
[enhancedDispatch, datafeed, onSettingsChange]
|
|
196
198
|
);
|
|
@@ -358,7 +360,14 @@ var MAIN_INDICATORS = [
|
|
|
358
360
|
"SMA",
|
|
359
361
|
"BOLL",
|
|
360
362
|
"SAR",
|
|
361
|
-
"BBI"
|
|
363
|
+
"BBI",
|
|
364
|
+
"BOLL_TV",
|
|
365
|
+
"Ichimoku",
|
|
366
|
+
"SuperTrend",
|
|
367
|
+
"HMA",
|
|
368
|
+
"VWAP",
|
|
369
|
+
"PivotPoints",
|
|
370
|
+
"MA_Ribbon"
|
|
362
371
|
];
|
|
363
372
|
var SUB_INDICATORS = [
|
|
364
373
|
"MA",
|
|
@@ -386,7 +395,11 @@ var SUB_INDICATORS = [
|
|
|
386
395
|
"ROC",
|
|
387
396
|
"PVT",
|
|
388
397
|
"BBI",
|
|
389
|
-
"AO"
|
|
398
|
+
"AO",
|
|
399
|
+
"MACD_TV",
|
|
400
|
+
"RSI_TV",
|
|
401
|
+
"CCI_TV",
|
|
402
|
+
"Stochastic"
|
|
390
403
|
];
|
|
391
404
|
var INDICATOR_PARAMS = {
|
|
392
405
|
SMA: {
|
|
@@ -545,12 +558,98 @@ var INDICATOR_PARAMS = {
|
|
|
545
558
|
{ label: "params_1", defaultValue: 5 },
|
|
546
559
|
{ label: "params_2", defaultValue: 34 }
|
|
547
560
|
]
|
|
561
|
+
},
|
|
562
|
+
BOLL_TV: {
|
|
563
|
+
name: "BOLL_TV",
|
|
564
|
+
localeKey: "boll_tv",
|
|
565
|
+
params: [
|
|
566
|
+
{ label: "period", defaultValue: 20 },
|
|
567
|
+
{ label: "multiplier", defaultValue: 2 }
|
|
568
|
+
]
|
|
569
|
+
},
|
|
570
|
+
Ichimoku: {
|
|
571
|
+
name: "Ichimoku",
|
|
572
|
+
localeKey: "ichimoku",
|
|
573
|
+
params: [
|
|
574
|
+
{ label: "tenkan", defaultValue: 9 },
|
|
575
|
+
{ label: "kijun", defaultValue: 26 },
|
|
576
|
+
{ label: "senkou_b", defaultValue: 52 },
|
|
577
|
+
{ label: "offset", defaultValue: 26 }
|
|
578
|
+
]
|
|
579
|
+
},
|
|
580
|
+
SuperTrend: {
|
|
581
|
+
name: "SuperTrend",
|
|
582
|
+
localeKey: "super_trend",
|
|
583
|
+
params: [
|
|
584
|
+
{ label: "period", defaultValue: 10 },
|
|
585
|
+
{ label: "factor", defaultValue: 3 }
|
|
586
|
+
]
|
|
587
|
+
},
|
|
588
|
+
HMA: {
|
|
589
|
+
name: "HMA",
|
|
590
|
+
localeKey: "hma",
|
|
591
|
+
params: [{ label: "period", defaultValue: 9 }]
|
|
592
|
+
},
|
|
593
|
+
VWAP: {
|
|
594
|
+
name: "VWAP",
|
|
595
|
+
localeKey: "vwap",
|
|
596
|
+
params: []
|
|
597
|
+
},
|
|
598
|
+
PivotPoints: {
|
|
599
|
+
name: "PivotPoints",
|
|
600
|
+
localeKey: "pivot_points",
|
|
601
|
+
params: []
|
|
602
|
+
},
|
|
603
|
+
MA_Ribbon: {
|
|
604
|
+
name: "MA_Ribbon",
|
|
605
|
+
localeKey: "ma_ribbon",
|
|
606
|
+
params: [
|
|
607
|
+
{ label: "period_1", defaultValue: 10 },
|
|
608
|
+
{ label: "period_2", defaultValue: 20 },
|
|
609
|
+
{ label: "period_3", defaultValue: 30 },
|
|
610
|
+
{ label: "period_4", defaultValue: 40 }
|
|
611
|
+
]
|
|
612
|
+
},
|
|
613
|
+
MACD_TV: {
|
|
614
|
+
name: "MACD_TV",
|
|
615
|
+
localeKey: "macd_tv",
|
|
616
|
+
params: [
|
|
617
|
+
{ label: "fast", defaultValue: 12 },
|
|
618
|
+
{ label: "slow", defaultValue: 26 },
|
|
619
|
+
{ label: "signal", defaultValue: 9 }
|
|
620
|
+
]
|
|
621
|
+
},
|
|
622
|
+
RSI_TV: {
|
|
623
|
+
name: "RSI_TV",
|
|
624
|
+
localeKey: "rsi_tv",
|
|
625
|
+
params: [
|
|
626
|
+
{ label: "rsi_period", defaultValue: 14 },
|
|
627
|
+
{ label: "ma_period", defaultValue: 14 }
|
|
628
|
+
]
|
|
629
|
+
},
|
|
630
|
+
CCI_TV: {
|
|
631
|
+
name: "CCI_TV",
|
|
632
|
+
localeKey: "cci_tv",
|
|
633
|
+
params: [{ label: "period", defaultValue: 20 }]
|
|
634
|
+
},
|
|
635
|
+
Stochastic: {
|
|
636
|
+
name: "Stochastic",
|
|
637
|
+
localeKey: "stochastic",
|
|
638
|
+
params: [
|
|
639
|
+
{ label: "period", defaultValue: 14 },
|
|
640
|
+
{ label: "smooth_k", defaultValue: 3 },
|
|
641
|
+
{ label: "smooth_d", defaultValue: 3 }
|
|
642
|
+
]
|
|
548
643
|
}
|
|
549
644
|
};
|
|
550
645
|
|
|
551
646
|
// src/hooks/useIndicators.ts
|
|
647
|
+
var COLLAPSED_HEIGHT = 30;
|
|
552
648
|
function useIndicators() {
|
|
553
649
|
const { state, dispatch } = useKlinechartsUI();
|
|
650
|
+
const { undoRedoListenerRef } = useKlinechartsUIDispatch();
|
|
651
|
+
const paneHeightsRef = react.useRef({});
|
|
652
|
+
const collapsedPanesRef = react.useRef(/* @__PURE__ */ new Set());
|
|
554
653
|
const mainIndicators = react.useMemo(() => {
|
|
555
654
|
const activeNames = state.mainIndicators;
|
|
556
655
|
const inactive = MAIN_INDICATORS.filter((n) => !activeNames.includes(n));
|
|
@@ -577,16 +676,24 @@ function useIndicators() {
|
|
|
577
676
|
);
|
|
578
677
|
const newIndicators = [...state.mainIndicators, name];
|
|
579
678
|
dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
|
|
679
|
+
undoRedoListenerRef.current?.({
|
|
680
|
+
type: "indicator_toggled",
|
|
681
|
+
data: { name, wasActive: false, isMain: true, paneId: "candle_pane" }
|
|
682
|
+
});
|
|
580
683
|
},
|
|
581
|
-
[state.chart, state.mainIndicators, dispatch]
|
|
684
|
+
[state.chart, state.mainIndicators, dispatch, undoRedoListenerRef]
|
|
582
685
|
);
|
|
583
686
|
const removeMainIndicator = react.useCallback(
|
|
584
687
|
(name) => {
|
|
585
688
|
state.chart?.removeIndicator({ id: `main_${name}` });
|
|
586
689
|
const newIndicators = state.mainIndicators.filter((n) => n !== name);
|
|
587
690
|
dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
|
|
691
|
+
undoRedoListenerRef.current?.({
|
|
692
|
+
type: "indicator_toggled",
|
|
693
|
+
data: { name, wasActive: true, isMain: true, paneId: "candle_pane" }
|
|
694
|
+
});
|
|
588
695
|
},
|
|
589
|
-
[state.chart, state.mainIndicators, dispatch]
|
|
696
|
+
[state.chart, state.mainIndicators, dispatch, undoRedoListenerRef]
|
|
590
697
|
);
|
|
591
698
|
const addSubIndicator = react.useCallback(
|
|
592
699
|
(name) => {
|
|
@@ -596,17 +703,26 @@ function useIndicators() {
|
|
|
596
703
|
const paneId = state.chart?.getIndicators({ id })?.[0]?.paneId ?? "";
|
|
597
704
|
const newIndicators = { ...state.subIndicators, [name]: paneId };
|
|
598
705
|
dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
|
|
706
|
+
undoRedoListenerRef.current?.({
|
|
707
|
+
type: "indicator_toggled",
|
|
708
|
+
data: { name, wasActive: false, isMain: false, paneId }
|
|
709
|
+
});
|
|
599
710
|
},
|
|
600
|
-
[state.chart, state.subIndicators, dispatch]
|
|
711
|
+
[state.chart, state.subIndicators, dispatch, undoRedoListenerRef]
|
|
601
712
|
);
|
|
602
713
|
const removeSubIndicator = react.useCallback(
|
|
603
714
|
(name) => {
|
|
715
|
+
const paneId = state.subIndicators[name] ?? "";
|
|
604
716
|
state.chart?.removeIndicator({ id: `sub_${name}` });
|
|
605
717
|
const newIndicators = { ...state.subIndicators };
|
|
606
718
|
delete newIndicators[name];
|
|
607
719
|
dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
|
|
720
|
+
undoRedoListenerRef.current?.({
|
|
721
|
+
type: "indicator_toggled",
|
|
722
|
+
data: { name, wasActive: true, isMain: false, paneId }
|
|
723
|
+
});
|
|
608
724
|
},
|
|
609
|
-
[state.chart, state.subIndicators, dispatch]
|
|
725
|
+
[state.chart, state.subIndicators, dispatch, undoRedoListenerRef]
|
|
610
726
|
);
|
|
611
727
|
const toggleMainIndicator = react.useCallback(
|
|
612
728
|
(name) => {
|
|
@@ -652,9 +768,15 @@ function useIndicators() {
|
|
|
652
768
|
const moveToMain = react.useCallback(
|
|
653
769
|
(name) => {
|
|
654
770
|
if (!state.chart) return;
|
|
771
|
+
const prevCalcParams = state.chart.getIndicators({ id: `sub_${name}` })?.[0]?.calcParams;
|
|
655
772
|
state.chart.removeIndicator({ id: `sub_${name}` });
|
|
656
773
|
state.chart.createIndicator(
|
|
657
|
-
{
|
|
774
|
+
{
|
|
775
|
+
name,
|
|
776
|
+
id: `main_${name}`,
|
|
777
|
+
paneId: "candle_pane",
|
|
778
|
+
...prevCalcParams ? { calcParams: prevCalcParams } : {}
|
|
779
|
+
},
|
|
658
780
|
true,
|
|
659
781
|
{ id: "candle_pane" }
|
|
660
782
|
);
|
|
@@ -671,9 +793,14 @@ function useIndicators() {
|
|
|
671
793
|
const moveToSub = react.useCallback(
|
|
672
794
|
(name) => {
|
|
673
795
|
if (!state.chart) return;
|
|
796
|
+
const prevCalcParams = state.chart.getIndicators({ id: `main_${name}` })?.[0]?.calcParams;
|
|
674
797
|
state.chart.removeIndicator({ id: `main_${name}` });
|
|
675
798
|
const subId = `sub_${name}`;
|
|
676
|
-
state.chart.createIndicator({
|
|
799
|
+
state.chart.createIndicator({
|
|
800
|
+
name,
|
|
801
|
+
id: subId,
|
|
802
|
+
...prevCalcParams ? { calcParams: prevCalcParams } : {}
|
|
803
|
+
});
|
|
677
804
|
const paneId = state.chart.getIndicators({ id: subId })?.[0]?.paneId ?? "";
|
|
678
805
|
const newMain = state.mainIndicators.filter((n) => n !== name);
|
|
679
806
|
dispatch({ type: "SET_MAIN_INDICATORS", indicators: newMain });
|
|
@@ -692,6 +819,90 @@ function useIndicators() {
|
|
|
692
819
|
(name) => name in state.subIndicators,
|
|
693
820
|
[state.subIndicators]
|
|
694
821
|
);
|
|
822
|
+
const collapseSubIndicator = react.useCallback(
|
|
823
|
+
(name) => {
|
|
824
|
+
const paneId = state.subIndicators[name];
|
|
825
|
+
if (!state.chart || !paneId) return;
|
|
826
|
+
const currentSize = state.chart.getSize?.(paneId);
|
|
827
|
+
if (currentSize?.height && currentSize.height > COLLAPSED_HEIGHT) {
|
|
828
|
+
paneHeightsRef.current[paneId] = currentSize.height;
|
|
829
|
+
}
|
|
830
|
+
collapsedPanesRef.current.add(paneId);
|
|
831
|
+
state.chart.setPaneOptions?.({
|
|
832
|
+
id: paneId,
|
|
833
|
+
height: COLLAPSED_HEIGHT
|
|
834
|
+
});
|
|
835
|
+
state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: false });
|
|
836
|
+
},
|
|
837
|
+
[state.chart, state.subIndicators]
|
|
838
|
+
);
|
|
839
|
+
const expandSubIndicator = react.useCallback(
|
|
840
|
+
(name) => {
|
|
841
|
+
const paneId = state.subIndicators[name];
|
|
842
|
+
if (!state.chart || !paneId) return;
|
|
843
|
+
const savedHeight = paneHeightsRef.current[paneId] ?? 100;
|
|
844
|
+
collapsedPanesRef.current.delete(paneId);
|
|
845
|
+
state.chart.setPaneOptions?.({
|
|
846
|
+
id: paneId,
|
|
847
|
+
height: savedHeight
|
|
848
|
+
});
|
|
849
|
+
state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: true });
|
|
850
|
+
},
|
|
851
|
+
[state.chart, state.subIndicators]
|
|
852
|
+
);
|
|
853
|
+
const isSubIndicatorCollapsed = react.useCallback(
|
|
854
|
+
(name) => {
|
|
855
|
+
const paneId = state.subIndicators[name];
|
|
856
|
+
return paneId ? collapsedPanesRef.current.has(paneId) : false;
|
|
857
|
+
},
|
|
858
|
+
[state.subIndicators]
|
|
859
|
+
);
|
|
860
|
+
const reorderSubIndicator = react.useCallback(
|
|
861
|
+
(name, direction) => {
|
|
862
|
+
if (!state.chart) return;
|
|
863
|
+
const subNames = Object.keys(state.subIndicators);
|
|
864
|
+
const idx = subNames.indexOf(name);
|
|
865
|
+
if (idx === -1) return;
|
|
866
|
+
const swapIdx = direction === "up" ? idx - 1 : idx + 1;
|
|
867
|
+
if (swapIdx < 0 || swapIdx >= subNames.length) return;
|
|
868
|
+
const subStates = subNames.map((n) => {
|
|
869
|
+
const id = `sub_${n}`;
|
|
870
|
+
const indicator = state.chart.getIndicators({ id })?.[0];
|
|
871
|
+
return {
|
|
872
|
+
name: n,
|
|
873
|
+
calcParams: indicator?.calcParams,
|
|
874
|
+
visible: indicator?.visible ?? true,
|
|
875
|
+
styles: indicator?.styles,
|
|
876
|
+
paneId: state.subIndicators[n]
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
[subStates[idx], subStates[swapIdx]] = [subStates[swapIdx], subStates[idx]];
|
|
880
|
+
for (const n of subNames) {
|
|
881
|
+
state.chart.removeIndicator({ id: `sub_${n}` });
|
|
882
|
+
}
|
|
883
|
+
const newSubIndicators = {};
|
|
884
|
+
for (const sub of subStates) {
|
|
885
|
+
const id = `sub_${sub.name}`;
|
|
886
|
+
state.chart.createIndicator({
|
|
887
|
+
name: sub.name,
|
|
888
|
+
id,
|
|
889
|
+
...sub.calcParams ? { calcParams: sub.calcParams } : {},
|
|
890
|
+
visible: sub.visible
|
|
891
|
+
});
|
|
892
|
+
const paneId = state.chart.getIndicators({ id })?.[0]?.paneId ?? "";
|
|
893
|
+
newSubIndicators[sub.name] = paneId;
|
|
894
|
+
if (sub.styles) {
|
|
895
|
+
state.chart.overrideIndicator({
|
|
896
|
+
name: sub.name,
|
|
897
|
+
paneId,
|
|
898
|
+
styles: sub.styles
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
dispatch({ type: "SET_SUB_INDICATORS", indicators: newSubIndicators });
|
|
903
|
+
},
|
|
904
|
+
[state.chart, state.subIndicators, dispatch]
|
|
905
|
+
);
|
|
695
906
|
return {
|
|
696
907
|
mainIndicators,
|
|
697
908
|
subIndicators,
|
|
@@ -711,7 +922,11 @@ function useIndicators() {
|
|
|
711
922
|
updateIndicatorParams,
|
|
712
923
|
getIndicatorParams,
|
|
713
924
|
isMainIndicatorActive,
|
|
714
|
-
isSubIndicatorActive
|
|
925
|
+
isSubIndicatorActive,
|
|
926
|
+
collapseSubIndicator,
|
|
927
|
+
expandSubIndicator,
|
|
928
|
+
isSubIndicatorCollapsed,
|
|
929
|
+
reorderSubIndicator
|
|
715
930
|
};
|
|
716
931
|
}
|
|
717
932
|
|
|
@@ -737,7 +952,9 @@ var DRAWING_CATEGORIES = [
|
|
|
737
952
|
key: "moreLine",
|
|
738
953
|
tools: [
|
|
739
954
|
{ name: "priceChannelLine", localeKey: "price_channel_line" },
|
|
740
|
-
{ name: "parallelStraightLine", localeKey: "parallel_straight_line" }
|
|
955
|
+
{ name: "parallelStraightLine", localeKey: "parallel_straight_line" },
|
|
956
|
+
{ name: "ray", localeKey: "ray" },
|
|
957
|
+
{ name: "parallelChannel", localeKey: "parallel_channel" }
|
|
741
958
|
]
|
|
742
959
|
},
|
|
743
960
|
{
|
|
@@ -761,7 +978,9 @@ var DRAWING_CATEGORIES = [
|
|
|
761
978
|
localeKey: "fibonacci_speed_resistance_fan"
|
|
762
979
|
},
|
|
763
980
|
{ name: "fibonacciExtension", localeKey: "fibonacci_extension" },
|
|
764
|
-
{ name: "
|
|
981
|
+
{ name: "fibRetracement", localeKey: "fib_retracement" },
|
|
982
|
+
{ name: "gannBox", localeKey: "gann_box" },
|
|
983
|
+
{ name: "gannFan", localeKey: "gann_fan" }
|
|
765
984
|
]
|
|
766
985
|
},
|
|
767
986
|
{
|
|
@@ -772,7 +991,27 @@ var DRAWING_CATEGORIES = [
|
|
|
772
991
|
{ name: "threeWaves", localeKey: "three_waves" },
|
|
773
992
|
{ name: "fiveWaves", localeKey: "five_waves" },
|
|
774
993
|
{ name: "eightWaves", localeKey: "eight_waves" },
|
|
775
|
-
{ name: "anyWaves", localeKey: "any_waves" }
|
|
994
|
+
{ name: "anyWaves", localeKey: "any_waves" },
|
|
995
|
+
{ name: "elliottWave", localeKey: "elliott_wave" }
|
|
996
|
+
]
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
key: "measure",
|
|
1000
|
+
tools: [
|
|
1001
|
+
{ name: "measure", localeKey: "measure" }
|
|
1002
|
+
]
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
key: "position",
|
|
1006
|
+
tools: [
|
|
1007
|
+
{ name: "longPosition", localeKey: "long_position" },
|
|
1008
|
+
{ name: "shortPosition", localeKey: "short_position" }
|
|
1009
|
+
]
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
key: "annotation",
|
|
1013
|
+
tools: [
|
|
1014
|
+
{ name: "brush", localeKey: "brush" }
|
|
776
1015
|
]
|
|
777
1016
|
}
|
|
778
1017
|
];
|
|
@@ -781,10 +1020,22 @@ var DRAWING_CATEGORIES = [
|
|
|
781
1020
|
var DRAWING_GROUP_ID = "drawing_tools";
|
|
782
1021
|
function useDrawingTools() {
|
|
783
1022
|
const { state } = useKlinechartsUI();
|
|
1023
|
+
const { undoRedoListenerRef } = useKlinechartsUIDispatch();
|
|
784
1024
|
const [activeTool, setActiveTool] = react.useState(null);
|
|
785
1025
|
const [magnetMode, setMagnetModeState] = react.useState("normal");
|
|
786
1026
|
const [isLocked, setIsLocked] = react.useState(false);
|
|
787
1027
|
const [isVisible, setIsVisible] = react.useState(true);
|
|
1028
|
+
const [autoRetrigger, setAutoRetrigger] = react.useState(true);
|
|
1029
|
+
const activeToolRef = react.useRef(activeTool);
|
|
1030
|
+
activeToolRef.current = activeTool;
|
|
1031
|
+
const autoRetriggerRef = react.useRef(autoRetrigger);
|
|
1032
|
+
autoRetriggerRef.current = autoRetrigger;
|
|
1033
|
+
const isLockedRef = react.useRef(isLocked);
|
|
1034
|
+
isLockedRef.current = isLocked;
|
|
1035
|
+
const isVisibleRef = react.useRef(isVisible);
|
|
1036
|
+
isVisibleRef.current = isVisible;
|
|
1037
|
+
const magnetModeRef = react.useRef(magnetMode);
|
|
1038
|
+
magnetModeRef.current = magnetMode;
|
|
788
1039
|
const categories = react.useMemo(
|
|
789
1040
|
() => DRAWING_CATEGORIES.map((cat) => ({
|
|
790
1041
|
key: cat.key,
|
|
@@ -795,18 +1046,45 @@ function useDrawingTools() {
|
|
|
795
1046
|
})),
|
|
796
1047
|
[]
|
|
797
1048
|
);
|
|
798
|
-
const
|
|
1049
|
+
const createOverlayForTool = react.useCallback(
|
|
799
1050
|
(name) => {
|
|
800
|
-
|
|
1051
|
+
const mode = magnetModeRef.current === "strong" ? "strong_magnet" : magnetModeRef.current === "weak" ? "weak_magnet" : "normal";
|
|
801
1052
|
state.chart?.createOverlay({
|
|
802
1053
|
name,
|
|
803
1054
|
groupId: DRAWING_GROUP_ID,
|
|
804
|
-
lock:
|
|
805
|
-
visible:
|
|
806
|
-
mode
|
|
1055
|
+
lock: isLockedRef.current,
|
|
1056
|
+
visible: isVisibleRef.current,
|
|
1057
|
+
mode,
|
|
1058
|
+
onDrawEnd: (event) => {
|
|
1059
|
+
const o = event.overlay;
|
|
1060
|
+
undoRedoListenerRef.current?.({
|
|
1061
|
+
type: "overlay_added",
|
|
1062
|
+
data: {
|
|
1063
|
+
id: o.id,
|
|
1064
|
+
overlayData: {
|
|
1065
|
+
name: o.name,
|
|
1066
|
+
points: o.points,
|
|
1067
|
+
styles: o.styles,
|
|
1068
|
+
extendData: o.extendData
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
if (autoRetriggerRef.current && activeToolRef.current === name) {
|
|
1073
|
+
requestAnimationFrame(() => {
|
|
1074
|
+
createOverlayForTool(name);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
807
1078
|
});
|
|
808
1079
|
},
|
|
809
|
-
[state.chart,
|
|
1080
|
+
[state.chart, undoRedoListenerRef]
|
|
1081
|
+
);
|
|
1082
|
+
const selectTool = react.useCallback(
|
|
1083
|
+
(name) => {
|
|
1084
|
+
setActiveTool(name);
|
|
1085
|
+
createOverlayForTool(name);
|
|
1086
|
+
},
|
|
1087
|
+
[createOverlayForTool]
|
|
810
1088
|
);
|
|
811
1089
|
const clearActiveTool = react.useCallback(() => {
|
|
812
1090
|
setActiveTool(null);
|
|
@@ -854,21 +1132,37 @@ function useDrawingTools() {
|
|
|
854
1132
|
}
|
|
855
1133
|
}, [state.chart, isVisible]);
|
|
856
1134
|
const removeAllDrawings = react.useCallback(() => {
|
|
1135
|
+
const overlays2 = state.chart?.getOverlays({ groupId: DRAWING_GROUP_ID });
|
|
1136
|
+
if (overlays2 && overlays2.length > 0) {
|
|
1137
|
+
const snapshot = overlays2.map((o) => ({
|
|
1138
|
+
name: o.name,
|
|
1139
|
+
id: o.id,
|
|
1140
|
+
points: o.points,
|
|
1141
|
+
styles: o.styles,
|
|
1142
|
+
extendData: o.extendData
|
|
1143
|
+
}));
|
|
1144
|
+
undoRedoListenerRef.current?.({
|
|
1145
|
+
type: "overlays_removed",
|
|
1146
|
+
data: { overlays: snapshot }
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
857
1149
|
state.chart?.removeOverlay({ groupId: DRAWING_GROUP_ID });
|
|
858
1150
|
setActiveTool(null);
|
|
859
|
-
}, [state.chart]);
|
|
1151
|
+
}, [state.chart, undoRedoListenerRef]);
|
|
860
1152
|
return {
|
|
861
1153
|
categories,
|
|
862
1154
|
activeTool,
|
|
863
1155
|
magnetMode,
|
|
864
1156
|
isLocked,
|
|
865
1157
|
isVisible,
|
|
1158
|
+
autoRetrigger,
|
|
866
1159
|
selectTool,
|
|
867
1160
|
clearActiveTool,
|
|
868
1161
|
setMagnetMode,
|
|
869
1162
|
toggleLock,
|
|
870
1163
|
toggleVisibility,
|
|
871
|
-
removeAllDrawings
|
|
1164
|
+
removeAllDrawings,
|
|
1165
|
+
setAutoRetrigger
|
|
872
1166
|
};
|
|
873
1167
|
}
|
|
874
1168
|
|
|
@@ -1260,12 +1554,12 @@ function useOrderLines() {
|
|
|
1260
1554
|
if (onPriceChange) {
|
|
1261
1555
|
callbacksRef.current.set(id, onPriceChange);
|
|
1262
1556
|
}
|
|
1263
|
-
const
|
|
1264
|
-
const
|
|
1557
|
+
const dataList = state.chart.getDataList();
|
|
1558
|
+
const anchorTimestamp = dataList.length > 0 ? dataList[dataList.length - 1].timestamp : Date.now();
|
|
1265
1559
|
state.chart.createOverlay({
|
|
1266
1560
|
name: "orderLine",
|
|
1267
1561
|
id,
|
|
1268
|
-
points: [{
|
|
1562
|
+
points: [{ timestamp: anchorTimestamp, value: price }],
|
|
1269
1563
|
extendData,
|
|
1270
1564
|
lock: !draggable,
|
|
1271
1565
|
mode: "normal",
|
|
@@ -1289,11 +1583,11 @@ function useOrderLines() {
|
|
|
1289
1583
|
if (onPriceChange !== void 0) {
|
|
1290
1584
|
callbacksRef.current.set(id, onPriceChange);
|
|
1291
1585
|
}
|
|
1292
|
-
const
|
|
1293
|
-
const
|
|
1586
|
+
const dataList = state.chart.getDataList();
|
|
1587
|
+
const anchorTimestamp = dataList.length > 0 ? dataList[dataList.length - 1].timestamp : Date.now();
|
|
1294
1588
|
state.chart.overrideOverlay({
|
|
1295
1589
|
id,
|
|
1296
|
-
...price != null ? { points: [{
|
|
1590
|
+
...price != null ? { points: [{ timestamp: anchorTimestamp, value: price }] } : {},
|
|
1297
1591
|
...draggable != null ? { lock: !draggable } : {},
|
|
1298
1592
|
extendData: {
|
|
1299
1593
|
...existing.extendData,
|
|
@@ -1321,6 +1615,1496 @@ function useOrderLines() {
|
|
|
1321
1615
|
removeAllOrderLines
|
|
1322
1616
|
};
|
|
1323
1617
|
}
|
|
1618
|
+
var DRAWING_GROUP_ID2 = "drawing_tools";
|
|
1619
|
+
function useUndoRedo() {
|
|
1620
|
+
const { state, dispatch } = useKlinechartsUI();
|
|
1621
|
+
const { undoRedoListenerRef } = useKlinechartsUIDispatch();
|
|
1622
|
+
const [undoStack, setUndoStack] = react.useState([]);
|
|
1623
|
+
const [redoStack, setRedoStack] = react.useState([]);
|
|
1624
|
+
const isProcessingRef = react.useRef(false);
|
|
1625
|
+
const canUndo = undoStack.length > 0;
|
|
1626
|
+
const canRedo = redoStack.length > 0;
|
|
1627
|
+
const pushAction = react.useCallback((action) => {
|
|
1628
|
+
if (isProcessingRef.current) return;
|
|
1629
|
+
setUndoStack((prev) => [...prev, action]);
|
|
1630
|
+
setRedoStack([]);
|
|
1631
|
+
}, []);
|
|
1632
|
+
react.useEffect(() => {
|
|
1633
|
+
undoRedoListenerRef.current = pushAction;
|
|
1634
|
+
return () => {
|
|
1635
|
+
undoRedoListenerRef.current = null;
|
|
1636
|
+
};
|
|
1637
|
+
}, [pushAction, undoRedoListenerRef]);
|
|
1638
|
+
const clear = react.useCallback(() => {
|
|
1639
|
+
setUndoStack([]);
|
|
1640
|
+
setRedoStack([]);
|
|
1641
|
+
}, []);
|
|
1642
|
+
const undo = react.useCallback(() => {
|
|
1643
|
+
if (undoStack.length === 0 || !state.chart || isProcessingRef.current)
|
|
1644
|
+
return;
|
|
1645
|
+
isProcessingRef.current = true;
|
|
1646
|
+
const action = undoStack[undoStack.length - 1];
|
|
1647
|
+
setUndoStack((prev) => prev.slice(0, -1));
|
|
1648
|
+
try {
|
|
1649
|
+
switch (action.type) {
|
|
1650
|
+
case "overlay_added": {
|
|
1651
|
+
const { id, overlayData } = action.data;
|
|
1652
|
+
state.chart.removeOverlay({ id });
|
|
1653
|
+
setRedoStack((prev) => [
|
|
1654
|
+
...prev,
|
|
1655
|
+
{
|
|
1656
|
+
type: "overlay_added",
|
|
1657
|
+
data: { id, overlayData }
|
|
1658
|
+
}
|
|
1659
|
+
]);
|
|
1660
|
+
break;
|
|
1661
|
+
}
|
|
1662
|
+
case "overlays_removed": {
|
|
1663
|
+
const { overlays: overlays2 } = action.data;
|
|
1664
|
+
const restoredIds = [];
|
|
1665
|
+
for (const overlay of overlays2) {
|
|
1666
|
+
const newId = state.chart.createOverlay({
|
|
1667
|
+
...overlay,
|
|
1668
|
+
groupId: DRAWING_GROUP_ID2
|
|
1669
|
+
});
|
|
1670
|
+
if (typeof newId === "string") {
|
|
1671
|
+
restoredIds.push(newId);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
setRedoStack((prev) => [
|
|
1675
|
+
...prev,
|
|
1676
|
+
{
|
|
1677
|
+
type: "overlays_removed",
|
|
1678
|
+
data: {
|
|
1679
|
+
overlays: overlays2.map((o, i) => ({
|
|
1680
|
+
...o,
|
|
1681
|
+
id: restoredIds[i]
|
|
1682
|
+
}))
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
]);
|
|
1686
|
+
break;
|
|
1687
|
+
}
|
|
1688
|
+
case "indicator_toggled": {
|
|
1689
|
+
const { name, wasActive, isMain, paneId } = action.data;
|
|
1690
|
+
if (wasActive) {
|
|
1691
|
+
if (isMain) {
|
|
1692
|
+
state.chart.createIndicator(
|
|
1693
|
+
{ name, id: `main_${name}`, paneId: "candle_pane" },
|
|
1694
|
+
true,
|
|
1695
|
+
{ id: "candle_pane" }
|
|
1696
|
+
);
|
|
1697
|
+
dispatch({
|
|
1698
|
+
type: "SET_MAIN_INDICATORS",
|
|
1699
|
+
indicators: [...state.mainIndicators, name]
|
|
1700
|
+
});
|
|
1701
|
+
} else {
|
|
1702
|
+
state.chart.createIndicator({ name, id: `sub_${name}` });
|
|
1703
|
+
const newPaneId = state.chart.getIndicators({ id: `sub_${name}` })?.[0]?.paneId ?? "";
|
|
1704
|
+
dispatch({
|
|
1705
|
+
type: "SET_SUB_INDICATORS",
|
|
1706
|
+
indicators: {
|
|
1707
|
+
...state.subIndicators,
|
|
1708
|
+
[name]: newPaneId
|
|
1709
|
+
}
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
} else {
|
|
1713
|
+
if (isMain) {
|
|
1714
|
+
state.chart.removeIndicator({ id: `main_${name}` });
|
|
1715
|
+
dispatch({
|
|
1716
|
+
type: "SET_MAIN_INDICATORS",
|
|
1717
|
+
indicators: state.mainIndicators.filter(
|
|
1718
|
+
(n) => n !== name
|
|
1719
|
+
)
|
|
1720
|
+
});
|
|
1721
|
+
} else {
|
|
1722
|
+
state.chart.removeIndicator({ id: `sub_${name}` });
|
|
1723
|
+
const newSub = { ...state.subIndicators };
|
|
1724
|
+
delete newSub[name];
|
|
1725
|
+
dispatch({
|
|
1726
|
+
type: "SET_SUB_INDICATORS",
|
|
1727
|
+
indicators: newSub
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
setRedoStack((prev) => [
|
|
1732
|
+
...prev,
|
|
1733
|
+
{
|
|
1734
|
+
type: "indicator_toggled",
|
|
1735
|
+
data: {
|
|
1736
|
+
name,
|
|
1737
|
+
wasActive: !wasActive,
|
|
1738
|
+
isMain,
|
|
1739
|
+
paneId
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
]);
|
|
1743
|
+
break;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
} finally {
|
|
1747
|
+
isProcessingRef.current = false;
|
|
1748
|
+
}
|
|
1749
|
+
}, [undoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
|
|
1750
|
+
const redo = react.useCallback(() => {
|
|
1751
|
+
if (redoStack.length === 0 || !state.chart || isProcessingRef.current)
|
|
1752
|
+
return;
|
|
1753
|
+
isProcessingRef.current = true;
|
|
1754
|
+
const action = redoStack[redoStack.length - 1];
|
|
1755
|
+
setRedoStack((prev) => prev.slice(0, -1));
|
|
1756
|
+
try {
|
|
1757
|
+
switch (action.type) {
|
|
1758
|
+
case "overlay_added": {
|
|
1759
|
+
const { overlayData } = action.data;
|
|
1760
|
+
const newId = state.chart.createOverlay({
|
|
1761
|
+
...overlayData,
|
|
1762
|
+
groupId: DRAWING_GROUP_ID2
|
|
1763
|
+
});
|
|
1764
|
+
setUndoStack((prev) => [
|
|
1765
|
+
...prev,
|
|
1766
|
+
{
|
|
1767
|
+
type: "overlay_added",
|
|
1768
|
+
data: {
|
|
1769
|
+
id: typeof newId === "string" ? newId : action.data.id,
|
|
1770
|
+
overlayData
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
]);
|
|
1774
|
+
break;
|
|
1775
|
+
}
|
|
1776
|
+
case "overlays_removed": {
|
|
1777
|
+
const { overlays: overlays2 } = action.data;
|
|
1778
|
+
const snapshotOverlays = overlays2.map((o) => {
|
|
1779
|
+
const allOverlays = state.chart?.getOverlays({ id: o.id });
|
|
1780
|
+
const actual = allOverlays?.[0];
|
|
1781
|
+
return actual ? {
|
|
1782
|
+
name: actual.name,
|
|
1783
|
+
points: actual.points,
|
|
1784
|
+
styles: actual.styles,
|
|
1785
|
+
extendData: actual.extendData
|
|
1786
|
+
} : o;
|
|
1787
|
+
});
|
|
1788
|
+
for (const overlay of overlays2) {
|
|
1789
|
+
state.chart.removeOverlay({ id: overlay.id });
|
|
1790
|
+
}
|
|
1791
|
+
setUndoStack((prev) => [
|
|
1792
|
+
...prev,
|
|
1793
|
+
{
|
|
1794
|
+
type: "overlays_removed",
|
|
1795
|
+
data: { overlays: snapshotOverlays }
|
|
1796
|
+
}
|
|
1797
|
+
]);
|
|
1798
|
+
break;
|
|
1799
|
+
}
|
|
1800
|
+
case "indicator_toggled": {
|
|
1801
|
+
const { name, wasActive, isMain } = action.data;
|
|
1802
|
+
if (wasActive) {
|
|
1803
|
+
if (isMain) {
|
|
1804
|
+
state.chart.createIndicator(
|
|
1805
|
+
{ name, id: `main_${name}`, paneId: "candle_pane" },
|
|
1806
|
+
true,
|
|
1807
|
+
{ id: "candle_pane" }
|
|
1808
|
+
);
|
|
1809
|
+
dispatch({
|
|
1810
|
+
type: "SET_MAIN_INDICATORS",
|
|
1811
|
+
indicators: [...state.mainIndicators, name]
|
|
1812
|
+
});
|
|
1813
|
+
} else {
|
|
1814
|
+
state.chart.createIndicator({ name, id: `sub_${name}` });
|
|
1815
|
+
const newPaneId = state.chart.getIndicators({ id: `sub_${name}` })?.[0]?.paneId ?? "";
|
|
1816
|
+
dispatch({
|
|
1817
|
+
type: "SET_SUB_INDICATORS",
|
|
1818
|
+
indicators: {
|
|
1819
|
+
...state.subIndicators,
|
|
1820
|
+
[name]: newPaneId
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
} else {
|
|
1825
|
+
if (isMain) {
|
|
1826
|
+
state.chart.removeIndicator({ id: `main_${name}` });
|
|
1827
|
+
dispatch({
|
|
1828
|
+
type: "SET_MAIN_INDICATORS",
|
|
1829
|
+
indicators: state.mainIndicators.filter(
|
|
1830
|
+
(n) => n !== name
|
|
1831
|
+
)
|
|
1832
|
+
});
|
|
1833
|
+
} else {
|
|
1834
|
+
state.chart.removeIndicator({ id: `sub_${name}` });
|
|
1835
|
+
const newSub = { ...state.subIndicators };
|
|
1836
|
+
delete newSub[name];
|
|
1837
|
+
dispatch({
|
|
1838
|
+
type: "SET_SUB_INDICATORS",
|
|
1839
|
+
indicators: newSub
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
setUndoStack((prev) => [
|
|
1844
|
+
...prev,
|
|
1845
|
+
{
|
|
1846
|
+
type: "indicator_toggled",
|
|
1847
|
+
data: {
|
|
1848
|
+
name,
|
|
1849
|
+
wasActive: !wasActive,
|
|
1850
|
+
isMain
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
]);
|
|
1854
|
+
break;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
} finally {
|
|
1858
|
+
isProcessingRef.current = false;
|
|
1859
|
+
}
|
|
1860
|
+
}, [redoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
|
|
1861
|
+
react.useEffect(() => {
|
|
1862
|
+
const handleKeyDown = (e) => {
|
|
1863
|
+
const isCtrlOrMeta = e.ctrlKey || e.metaKey;
|
|
1864
|
+
if (!isCtrlOrMeta) return;
|
|
1865
|
+
if (e.key === "z" && !e.shiftKey) {
|
|
1866
|
+
e.preventDefault();
|
|
1867
|
+
undo();
|
|
1868
|
+
} else if (e.key === "y" || e.key === "z" && e.shiftKey) {
|
|
1869
|
+
e.preventDefault();
|
|
1870
|
+
redo();
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1873
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1874
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1875
|
+
}, [undo, redo]);
|
|
1876
|
+
return {
|
|
1877
|
+
canUndo,
|
|
1878
|
+
canRedo,
|
|
1879
|
+
undo,
|
|
1880
|
+
redo,
|
|
1881
|
+
pushAction,
|
|
1882
|
+
clear
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
var STORAGE_KEY_PREFIX = "klinecharts_layout:";
|
|
1886
|
+
var INDEX_KEY = "klinecharts_layout_index";
|
|
1887
|
+
var STATE_VERSION = "1.0";
|
|
1888
|
+
function generateId() {
|
|
1889
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
|
|
1890
|
+
/[xy]/g,
|
|
1891
|
+
(c) => {
|
|
1892
|
+
const r = Math.random() * 16 | 0;
|
|
1893
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
1894
|
+
}
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
function getLayoutIds() {
|
|
1898
|
+
try {
|
|
1899
|
+
const raw = localStorage.getItem(INDEX_KEY);
|
|
1900
|
+
return raw ? JSON.parse(raw) : [];
|
|
1901
|
+
} catch {
|
|
1902
|
+
return [];
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
function getEntry(id) {
|
|
1906
|
+
try {
|
|
1907
|
+
const raw = localStorage.getItem(STORAGE_KEY_PREFIX + id);
|
|
1908
|
+
return raw ? JSON.parse(raw) : null;
|
|
1909
|
+
} catch {
|
|
1910
|
+
return null;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
function useLayoutManager() {
|
|
1914
|
+
const { state, dispatch } = useKlinechartsUI();
|
|
1915
|
+
const [layouts, setLayouts] = react.useState([]);
|
|
1916
|
+
const [autoSaveEnabled, setAutoSaveEnabled] = react.useState(false);
|
|
1917
|
+
const autoSaveTimerRef = react.useRef(null);
|
|
1918
|
+
const autoSaveIdRef = react.useRef(null);
|
|
1919
|
+
const refreshLayouts = react.useCallback(() => {
|
|
1920
|
+
const entries = getLayoutIds().map((id) => getEntry(id)).filter((e) => e !== null);
|
|
1921
|
+
setLayouts(entries);
|
|
1922
|
+
}, []);
|
|
1923
|
+
react.useEffect(() => {
|
|
1924
|
+
refreshLayouts();
|
|
1925
|
+
}, [refreshLayouts]);
|
|
1926
|
+
const serializeState = react.useCallback(() => {
|
|
1927
|
+
const chart = state.chart;
|
|
1928
|
+
if (!chart) return null;
|
|
1929
|
+
const indicators2 = [];
|
|
1930
|
+
const indicatorMap = chart.getIndicatorByPaneId?.();
|
|
1931
|
+
if (indicatorMap) {
|
|
1932
|
+
indicatorMap.forEach((paneIndicators, paneId) => {
|
|
1933
|
+
paneIndicators.forEach((indicator) => {
|
|
1934
|
+
indicators2.push({
|
|
1935
|
+
paneId,
|
|
1936
|
+
name: indicator.name,
|
|
1937
|
+
calcParams: indicator.calcParams,
|
|
1938
|
+
visible: indicator.visible,
|
|
1939
|
+
...indicator.styles ? { styles: indicator.styles } : {}
|
|
1940
|
+
});
|
|
1941
|
+
});
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
const drawings = [];
|
|
1945
|
+
const allOverlays = chart.getOverlays({});
|
|
1946
|
+
if (allOverlays) {
|
|
1947
|
+
for (const overlay of allOverlays) {
|
|
1948
|
+
drawings.push({
|
|
1949
|
+
name: overlay.name,
|
|
1950
|
+
points: overlay.points,
|
|
1951
|
+
styles: overlay.styles,
|
|
1952
|
+
extendData: overlay.extendData
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
return {
|
|
1957
|
+
version: STATE_VERSION,
|
|
1958
|
+
meta: {
|
|
1959
|
+
symbol: state.symbol?.ticker ?? "",
|
|
1960
|
+
period: state.period?.label ?? "",
|
|
1961
|
+
timestamp: Date.now(),
|
|
1962
|
+
lastModified: Date.now()
|
|
1963
|
+
},
|
|
1964
|
+
indicators: indicators2,
|
|
1965
|
+
drawings
|
|
1966
|
+
};
|
|
1967
|
+
}, [state.chart, state.symbol, state.period]);
|
|
1968
|
+
const saveLayout = react.useCallback(
|
|
1969
|
+
(name) => {
|
|
1970
|
+
const chartState = serializeState();
|
|
1971
|
+
if (!chartState) return null;
|
|
1972
|
+
const id = generateId();
|
|
1973
|
+
const now = Date.now();
|
|
1974
|
+
const entry = {
|
|
1975
|
+
id,
|
|
1976
|
+
name: name.trim() || `Layout ${new Date(now).toLocaleString()}`,
|
|
1977
|
+
symbol: chartState.meta.symbol,
|
|
1978
|
+
period: chartState.meta.period,
|
|
1979
|
+
timestamp: now,
|
|
1980
|
+
lastModified: now,
|
|
1981
|
+
state: chartState
|
|
1982
|
+
};
|
|
1983
|
+
localStorage.setItem(
|
|
1984
|
+
STORAGE_KEY_PREFIX + id,
|
|
1985
|
+
JSON.stringify(entry)
|
|
1986
|
+
);
|
|
1987
|
+
const ids = getLayoutIds();
|
|
1988
|
+
if (!ids.includes(id)) {
|
|
1989
|
+
ids.push(id);
|
|
1990
|
+
localStorage.setItem(INDEX_KEY, JSON.stringify(ids));
|
|
1991
|
+
}
|
|
1992
|
+
refreshLayouts();
|
|
1993
|
+
return id;
|
|
1994
|
+
},
|
|
1995
|
+
[serializeState, refreshLayouts]
|
|
1996
|
+
);
|
|
1997
|
+
const loadLayout = react.useCallback(
|
|
1998
|
+
(id) => {
|
|
1999
|
+
const entry = getEntry(id);
|
|
2000
|
+
if (!entry || !state.chart) return false;
|
|
2001
|
+
const chartState = entry.state;
|
|
2002
|
+
if (chartState.version !== STATE_VERSION) return false;
|
|
2003
|
+
const chart = state.chart;
|
|
2004
|
+
chart.removeOverlay();
|
|
2005
|
+
const indicatorMap = chart.getIndicatorByPaneId?.();
|
|
2006
|
+
if (indicatorMap) {
|
|
2007
|
+
indicatorMap.forEach((_paneIndicators, paneId) => {
|
|
2008
|
+
_paneIndicators.forEach((_, indicatorName) => {
|
|
2009
|
+
chart.removeIndicator({ paneId, name: indicatorName });
|
|
2010
|
+
});
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
const newMainIndicators = [];
|
|
2014
|
+
const newSubIndicators = {};
|
|
2015
|
+
if (chartState.indicators) {
|
|
2016
|
+
for (const ind of chartState.indicators) {
|
|
2017
|
+
chart.createIndicator(
|
|
2018
|
+
{
|
|
2019
|
+
name: ind.name,
|
|
2020
|
+
calcParams: ind.calcParams,
|
|
2021
|
+
visible: ind.visible
|
|
2022
|
+
},
|
|
2023
|
+
ind.paneId !== "candle_pane",
|
|
2024
|
+
{ id: ind.paneId }
|
|
2025
|
+
);
|
|
2026
|
+
if (ind.styles) {
|
|
2027
|
+
chart.overrideIndicator({
|
|
2028
|
+
name: ind.name,
|
|
2029
|
+
paneId: ind.paneId,
|
|
2030
|
+
styles: ind.styles
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
if (ind.paneId === "candle_pane") {
|
|
2034
|
+
newMainIndicators.push(ind.name);
|
|
2035
|
+
} else {
|
|
2036
|
+
newSubIndicators[ind.name] = ind.paneId;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
dispatch({
|
|
2041
|
+
type: "SET_MAIN_INDICATORS",
|
|
2042
|
+
indicators: newMainIndicators
|
|
2043
|
+
});
|
|
2044
|
+
dispatch({
|
|
2045
|
+
type: "SET_SUB_INDICATORS",
|
|
2046
|
+
indicators: newSubIndicators
|
|
2047
|
+
});
|
|
2048
|
+
if (chartState.drawings) {
|
|
2049
|
+
for (const drawing of chartState.drawings) {
|
|
2050
|
+
chart.createOverlay({
|
|
2051
|
+
name: drawing.name,
|
|
2052
|
+
points: drawing.points,
|
|
2053
|
+
styles: drawing.styles,
|
|
2054
|
+
extendData: drawing.extendData
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
return true;
|
|
2059
|
+
},
|
|
2060
|
+
[state.chart, dispatch]
|
|
2061
|
+
);
|
|
2062
|
+
const deleteLayout = react.useCallback(
|
|
2063
|
+
(id) => {
|
|
2064
|
+
localStorage.removeItem(STORAGE_KEY_PREFIX + id);
|
|
2065
|
+
const ids = getLayoutIds().filter((i) => i !== id);
|
|
2066
|
+
localStorage.setItem(INDEX_KEY, JSON.stringify(ids));
|
|
2067
|
+
refreshLayouts();
|
|
2068
|
+
},
|
|
2069
|
+
[refreshLayouts]
|
|
2070
|
+
);
|
|
2071
|
+
const renameLayout = react.useCallback(
|
|
2072
|
+
(id, name) => {
|
|
2073
|
+
const entry = getEntry(id);
|
|
2074
|
+
if (!entry) return false;
|
|
2075
|
+
const updated = {
|
|
2076
|
+
...entry,
|
|
2077
|
+
name: name.trim(),
|
|
2078
|
+
lastModified: Date.now()
|
|
2079
|
+
};
|
|
2080
|
+
localStorage.setItem(
|
|
2081
|
+
STORAGE_KEY_PREFIX + id,
|
|
2082
|
+
JSON.stringify(updated)
|
|
2083
|
+
);
|
|
2084
|
+
refreshLayouts();
|
|
2085
|
+
return true;
|
|
2086
|
+
},
|
|
2087
|
+
[refreshLayouts]
|
|
2088
|
+
);
|
|
2089
|
+
react.useEffect(() => {
|
|
2090
|
+
if (!autoSaveEnabled || !state.chart) return;
|
|
2091
|
+
if (autoSaveTimerRef.current) {
|
|
2092
|
+
clearTimeout(autoSaveTimerRef.current);
|
|
2093
|
+
}
|
|
2094
|
+
autoSaveTimerRef.current = setTimeout(() => {
|
|
2095
|
+
const chartState = serializeState();
|
|
2096
|
+
if (!chartState) return;
|
|
2097
|
+
if (autoSaveIdRef.current) {
|
|
2098
|
+
const entry = getEntry(autoSaveIdRef.current);
|
|
2099
|
+
if (entry) {
|
|
2100
|
+
const updated = {
|
|
2101
|
+
...entry,
|
|
2102
|
+
lastModified: Date.now(),
|
|
2103
|
+
state: chartState
|
|
2104
|
+
};
|
|
2105
|
+
localStorage.setItem(
|
|
2106
|
+
STORAGE_KEY_PREFIX + autoSaveIdRef.current,
|
|
2107
|
+
JSON.stringify(updated)
|
|
2108
|
+
);
|
|
2109
|
+
refreshLayouts();
|
|
2110
|
+
}
|
|
2111
|
+
} else {
|
|
2112
|
+
const id = generateId();
|
|
2113
|
+
const now = Date.now();
|
|
2114
|
+
const entry = {
|
|
2115
|
+
id,
|
|
2116
|
+
name: "Auto-save",
|
|
2117
|
+
symbol: chartState.meta.symbol,
|
|
2118
|
+
period: chartState.meta.period,
|
|
2119
|
+
timestamp: now,
|
|
2120
|
+
lastModified: now,
|
|
2121
|
+
state: chartState
|
|
2122
|
+
};
|
|
2123
|
+
localStorage.setItem(
|
|
2124
|
+
STORAGE_KEY_PREFIX + id,
|
|
2125
|
+
JSON.stringify(entry)
|
|
2126
|
+
);
|
|
2127
|
+
const ids = getLayoutIds();
|
|
2128
|
+
ids.push(id);
|
|
2129
|
+
localStorage.setItem(INDEX_KEY, JSON.stringify(ids));
|
|
2130
|
+
autoSaveIdRef.current = id;
|
|
2131
|
+
refreshLayouts();
|
|
2132
|
+
}
|
|
2133
|
+
}, 5e3);
|
|
2134
|
+
return () => {
|
|
2135
|
+
if (autoSaveTimerRef.current) {
|
|
2136
|
+
clearTimeout(autoSaveTimerRef.current);
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
}, [
|
|
2140
|
+
autoSaveEnabled,
|
|
2141
|
+
state.chart,
|
|
2142
|
+
state.mainIndicators,
|
|
2143
|
+
state.subIndicators,
|
|
2144
|
+
serializeState,
|
|
2145
|
+
refreshLayouts
|
|
2146
|
+
]);
|
|
2147
|
+
return {
|
|
2148
|
+
layouts,
|
|
2149
|
+
saveLayout,
|
|
2150
|
+
loadLayout,
|
|
2151
|
+
deleteLayout,
|
|
2152
|
+
renameLayout,
|
|
2153
|
+
refreshLayouts,
|
|
2154
|
+
autoSaveEnabled,
|
|
2155
|
+
setAutoSaveEnabled
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
var DEFAULT_SCRIPT = `// Custom Indicator \u2014 Pine Script Style
|
|
2159
|
+
// Available: TA, dataList, params
|
|
2160
|
+
|
|
2161
|
+
const period = params[0] ?? 14
|
|
2162
|
+
const closes = dataList.map(d => d.close)
|
|
2163
|
+
const highs = dataList.map(d => d.high)
|
|
2164
|
+
const lows = dataList.map(d => d.low)
|
|
2165
|
+
|
|
2166
|
+
// Example: RSI + Bollinger Bands
|
|
2167
|
+
const rsi = TA.rsi(closes, period)
|
|
2168
|
+
const boll = TA.bollinger(closes, period, 2)
|
|
2169
|
+
|
|
2170
|
+
// Return one object per candle \u2014 each key = one line on chart
|
|
2171
|
+
return rsi.map((v, i) => ({
|
|
2172
|
+
rsi: v,
|
|
2173
|
+
upper: boll.upper[i],
|
|
2174
|
+
mid: boll.mid[i],
|
|
2175
|
+
lower: boll.lower[i],
|
|
2176
|
+
}))`;
|
|
2177
|
+
var SERIES_COLORS = [
|
|
2178
|
+
"#2962FF",
|
|
2179
|
+
"#FF6D00",
|
|
2180
|
+
"#00BCD4",
|
|
2181
|
+
"#E91E63",
|
|
2182
|
+
"#76FF03",
|
|
2183
|
+
"#FFD600"
|
|
2184
|
+
];
|
|
2185
|
+
var SHADOW_KEYS = [
|
|
2186
|
+
"fetch",
|
|
2187
|
+
"XMLHttpRequest",
|
|
2188
|
+
"WebSocket",
|
|
2189
|
+
"Worker",
|
|
2190
|
+
"SharedWorker",
|
|
2191
|
+
"importScripts",
|
|
2192
|
+
"self",
|
|
2193
|
+
"caches",
|
|
2194
|
+
"indexedDB"
|
|
2195
|
+
];
|
|
2196
|
+
var scriptCounter = 0;
|
|
2197
|
+
function useScriptEditor() {
|
|
2198
|
+
const { state } = useKlinechartsUI();
|
|
2199
|
+
const [code, setCode] = react.useState(DEFAULT_SCRIPT);
|
|
2200
|
+
const [scriptName, setScriptName] = react.useState("");
|
|
2201
|
+
const [params, setParams] = react.useState("14");
|
|
2202
|
+
const [placement, setPlacement] = react.useState("sub");
|
|
2203
|
+
const [error, setError] = react.useState("");
|
|
2204
|
+
const [status, setStatus] = react.useState("");
|
|
2205
|
+
const [isRunning, setIsRunning] = react.useState(false);
|
|
2206
|
+
const activeIdRef = react.useRef(null);
|
|
2207
|
+
const activeNameRef = react.useRef(null);
|
|
2208
|
+
const hasActiveScript = activeNameRef.current !== null;
|
|
2209
|
+
const runScript = react.useCallback(() => {
|
|
2210
|
+
const chart = state.chart;
|
|
2211
|
+
if (!chart) {
|
|
2212
|
+
setError("Chart not initialized");
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
setError("");
|
|
2216
|
+
setStatus("Running...");
|
|
2217
|
+
setIsRunning(true);
|
|
2218
|
+
try {
|
|
2219
|
+
const parsedParams = params.split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n));
|
|
2220
|
+
const dataList = chart.getDataList?.() ?? [];
|
|
2221
|
+
const allArgs = [
|
|
2222
|
+
...SHADOW_KEYS,
|
|
2223
|
+
"TA",
|
|
2224
|
+
"dataList",
|
|
2225
|
+
"params",
|
|
2226
|
+
`"use strict";
|
|
2227
|
+
${code}`
|
|
2228
|
+
];
|
|
2229
|
+
const fn = new Function(...allArgs);
|
|
2230
|
+
const shadowValues = SHADOW_KEYS.map(() => void 0);
|
|
2231
|
+
const result = fn(...shadowValues, chunkPIFLJKYF_cjs.TA_default, dataList, parsedParams);
|
|
2232
|
+
if (!Array.isArray(result)) {
|
|
2233
|
+
throw new Error("Script must return an Array.");
|
|
2234
|
+
}
|
|
2235
|
+
const sample = result.find((v) => v !== null && v !== void 0);
|
|
2236
|
+
const seriesKeys = sample ? Object.keys(sample) : ["value"];
|
|
2237
|
+
if (seriesKeys.length === 0) {
|
|
2238
|
+
throw new Error("Returned objects have no keys.");
|
|
2239
|
+
}
|
|
2240
|
+
scriptCounter++;
|
|
2241
|
+
const indicatorName = `_custom_script_${scriptCounter}`;
|
|
2242
|
+
const figures = seriesKeys.map((key, i) => ({
|
|
2243
|
+
key,
|
|
2244
|
+
title: `${key}: `,
|
|
2245
|
+
type: "line",
|
|
2246
|
+
styles: () => ({ color: SERIES_COLORS[i % SERIES_COLORS.length] })
|
|
2247
|
+
}));
|
|
2248
|
+
reactKlinecharts.registerIndicator({
|
|
2249
|
+
name: indicatorName,
|
|
2250
|
+
shortName: (scriptName.trim() || `Script #${scriptCounter}`) + (placement === "main" ? " (Main)" : " (Sub)"),
|
|
2251
|
+
calcParams: parsedParams,
|
|
2252
|
+
figures,
|
|
2253
|
+
calc: () => result,
|
|
2254
|
+
extendData: {
|
|
2255
|
+
isCustomScript: true,
|
|
2256
|
+
code,
|
|
2257
|
+
placement
|
|
2258
|
+
}
|
|
2259
|
+
});
|
|
2260
|
+
if (activeNameRef.current) {
|
|
2261
|
+
try {
|
|
2262
|
+
chart.removeIndicator({
|
|
2263
|
+
id: activeIdRef.current === "candle_pane" ? "candle_pane" : activeIdRef.current,
|
|
2264
|
+
name: activeNameRef.current
|
|
2265
|
+
});
|
|
2266
|
+
} catch {
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
let paneId = null;
|
|
2270
|
+
if (placement === "main") {
|
|
2271
|
+
paneId = chart.createIndicator(
|
|
2272
|
+
{ name: indicatorName },
|
|
2273
|
+
true,
|
|
2274
|
+
{ id: "candle_pane" }
|
|
2275
|
+
);
|
|
2276
|
+
} else {
|
|
2277
|
+
paneId = chart.createIndicator({ name: indicatorName });
|
|
2278
|
+
}
|
|
2279
|
+
activeIdRef.current = typeof paneId === "string" ? paneId : null;
|
|
2280
|
+
activeNameRef.current = indicatorName;
|
|
2281
|
+
const title = scriptName.trim() || `Script #${scriptCounter}`;
|
|
2282
|
+
setStatus(
|
|
2283
|
+
`${title} applied to ${placement === "main" ? "Main Chart" : "Sub Pane"} \u2014 ${seriesKeys.length} series: ${seriesKeys.join(", ")}`
|
|
2284
|
+
);
|
|
2285
|
+
} catch (e) {
|
|
2286
|
+
setError(e?.message ?? String(e));
|
|
2287
|
+
} finally {
|
|
2288
|
+
setIsRunning(false);
|
|
2289
|
+
}
|
|
2290
|
+
}, [state.chart, code, scriptName, params, placement]);
|
|
2291
|
+
const removeScript = react.useCallback(() => {
|
|
2292
|
+
const chart = state.chart;
|
|
2293
|
+
if (chart && activeNameRef.current && activeIdRef.current) {
|
|
2294
|
+
try {
|
|
2295
|
+
chart.removeIndicator({
|
|
2296
|
+
id: activeIdRef.current,
|
|
2297
|
+
name: activeNameRef.current
|
|
2298
|
+
});
|
|
2299
|
+
} catch {
|
|
2300
|
+
}
|
|
2301
|
+
activeIdRef.current = null;
|
|
2302
|
+
activeNameRef.current = null;
|
|
2303
|
+
setStatus("Indicator removed.");
|
|
2304
|
+
}
|
|
2305
|
+
}, [state.chart]);
|
|
2306
|
+
const resetCode = react.useCallback(() => {
|
|
2307
|
+
setCode(DEFAULT_SCRIPT);
|
|
2308
|
+
setError("");
|
|
2309
|
+
setStatus("");
|
|
2310
|
+
}, []);
|
|
2311
|
+
const exportScript = react.useCallback(() => {
|
|
2312
|
+
const blob = new Blob([code], {
|
|
2313
|
+
type: "text/javascript;charset=utf-8"
|
|
2314
|
+
});
|
|
2315
|
+
const url = URL.createObjectURL(blob);
|
|
2316
|
+
const a = document.createElement("a");
|
|
2317
|
+
a.href = url;
|
|
2318
|
+
a.download = `${scriptName.trim() || "custom_indicator"}.js`;
|
|
2319
|
+
document.body.appendChild(a);
|
|
2320
|
+
a.click();
|
|
2321
|
+
document.body.removeChild(a);
|
|
2322
|
+
URL.revokeObjectURL(url);
|
|
2323
|
+
setStatus("Script exported.");
|
|
2324
|
+
}, [code, scriptName]);
|
|
2325
|
+
const importScript = react.useCallback(
|
|
2326
|
+
(file) => {
|
|
2327
|
+
const reader = new FileReader();
|
|
2328
|
+
reader.onload = (e) => {
|
|
2329
|
+
const content = e.target?.result;
|
|
2330
|
+
if (content) {
|
|
2331
|
+
setCode(content);
|
|
2332
|
+
if (!scriptName) {
|
|
2333
|
+
setScriptName(file.name.replace(/\.(js|ts|txt)$/i, ""));
|
|
2334
|
+
}
|
|
2335
|
+
setError("");
|
|
2336
|
+
setStatus(`Loaded ${file.name}`);
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
reader.readAsText(file);
|
|
2340
|
+
},
|
|
2341
|
+
[scriptName]
|
|
2342
|
+
);
|
|
2343
|
+
return {
|
|
2344
|
+
code,
|
|
2345
|
+
setCode,
|
|
2346
|
+
scriptName,
|
|
2347
|
+
setScriptName,
|
|
2348
|
+
params,
|
|
2349
|
+
setParams,
|
|
2350
|
+
placement,
|
|
2351
|
+
setPlacement,
|
|
2352
|
+
error,
|
|
2353
|
+
status,
|
|
2354
|
+
isRunning,
|
|
2355
|
+
hasActiveScript,
|
|
2356
|
+
runScript,
|
|
2357
|
+
removeScript,
|
|
2358
|
+
resetCode,
|
|
2359
|
+
exportScript,
|
|
2360
|
+
importScript,
|
|
2361
|
+
defaultScript: DEFAULT_SCRIPT
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
function useCrosshair() {
|
|
2365
|
+
const { state } = useKlinechartsUI();
|
|
2366
|
+
const [barData, setBarData] = react.useState(null);
|
|
2367
|
+
const rafRef = react.useRef(0);
|
|
2368
|
+
const handler = react.useCallback(
|
|
2369
|
+
(event) => {
|
|
2370
|
+
if (rafRef.current) {
|
|
2371
|
+
cancelAnimationFrame(rafRef.current);
|
|
2372
|
+
}
|
|
2373
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
2374
|
+
rafRef.current = 0;
|
|
2375
|
+
const klineData = event?.kLineData;
|
|
2376
|
+
if (!klineData) {
|
|
2377
|
+
setBarData(null);
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
const pricePrecision = state.symbol?.pricePrecision ?? 2;
|
|
2381
|
+
const open = klineData.open ?? 0;
|
|
2382
|
+
const close = klineData.close ?? 0;
|
|
2383
|
+
const change = parseFloat((close - open).toFixed(pricePrecision));
|
|
2384
|
+
const changePercent = open !== 0 ? parseFloat((change / open * 100).toFixed(2)) : 0;
|
|
2385
|
+
setBarData({
|
|
2386
|
+
open,
|
|
2387
|
+
high: klineData.high ?? 0,
|
|
2388
|
+
low: klineData.low ?? 0,
|
|
2389
|
+
close,
|
|
2390
|
+
volume: klineData.volume ?? 0,
|
|
2391
|
+
timestamp: klineData.timestamp ?? 0,
|
|
2392
|
+
change,
|
|
2393
|
+
changePercent
|
|
2394
|
+
});
|
|
2395
|
+
});
|
|
2396
|
+
},
|
|
2397
|
+
[state.symbol?.pricePrecision]
|
|
2398
|
+
);
|
|
2399
|
+
react.useEffect(() => {
|
|
2400
|
+
const chart = state.chart;
|
|
2401
|
+
if (!chart) return;
|
|
2402
|
+
chart.subscribeAction?.("onCrosshairChange", handler);
|
|
2403
|
+
return () => {
|
|
2404
|
+
if (rafRef.current) {
|
|
2405
|
+
cancelAnimationFrame(rafRef.current);
|
|
2406
|
+
rafRef.current = 0;
|
|
2407
|
+
}
|
|
2408
|
+
chart.unsubscribeAction?.("onCrosshairChange", handler);
|
|
2409
|
+
};
|
|
2410
|
+
}, [state.chart, handler]);
|
|
2411
|
+
return { barData };
|
|
2412
|
+
}
|
|
2413
|
+
var alertCounter = 0;
|
|
2414
|
+
function useAlerts() {
|
|
2415
|
+
const { state } = useKlinechartsUI();
|
|
2416
|
+
const [alerts, setAlerts] = react.useState([]);
|
|
2417
|
+
const callbackRef = react.useRef(null);
|
|
2418
|
+
const prevCloseRef = react.useRef(null);
|
|
2419
|
+
const addAlert = react.useCallback(
|
|
2420
|
+
(price, condition, message) => {
|
|
2421
|
+
const id = `alert_${++alertCounter}`;
|
|
2422
|
+
const alert = {
|
|
2423
|
+
id,
|
|
2424
|
+
price,
|
|
2425
|
+
condition,
|
|
2426
|
+
message,
|
|
2427
|
+
triggered: false
|
|
2428
|
+
};
|
|
2429
|
+
setAlerts((prev) => [...prev, alert]);
|
|
2430
|
+
if (state.chart) {
|
|
2431
|
+
state.chart.createOverlay({
|
|
2432
|
+
name: "horizontalStraightLine",
|
|
2433
|
+
id,
|
|
2434
|
+
groupId: "price_alerts",
|
|
2435
|
+
points: [{ value: price }],
|
|
2436
|
+
styles: {
|
|
2437
|
+
line: {
|
|
2438
|
+
style: "dashed",
|
|
2439
|
+
color: "#ff9800",
|
|
2440
|
+
size: 1
|
|
2441
|
+
}
|
|
2442
|
+
},
|
|
2443
|
+
lock: true
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
return id;
|
|
2447
|
+
},
|
|
2448
|
+
[state.chart]
|
|
2449
|
+
);
|
|
2450
|
+
const removeAlert = react.useCallback(
|
|
2451
|
+
(id) => {
|
|
2452
|
+
setAlerts((prev) => prev.filter((a) => a.id !== id));
|
|
2453
|
+
state.chart?.removeOverlay({ id });
|
|
2454
|
+
},
|
|
2455
|
+
[state.chart]
|
|
2456
|
+
);
|
|
2457
|
+
const clearAlerts = react.useCallback(() => {
|
|
2458
|
+
setAlerts((prev) => {
|
|
2459
|
+
for (const alert of prev) {
|
|
2460
|
+
state.chart?.removeOverlay({ id: alert.id });
|
|
2461
|
+
}
|
|
2462
|
+
return [];
|
|
2463
|
+
});
|
|
2464
|
+
}, [state.chart]);
|
|
2465
|
+
const onAlertTriggered = react.useCallback(
|
|
2466
|
+
(callback) => {
|
|
2467
|
+
callbackRef.current = callback;
|
|
2468
|
+
},
|
|
2469
|
+
[]
|
|
2470
|
+
);
|
|
2471
|
+
react.useEffect(() => {
|
|
2472
|
+
if (!state.chart) return;
|
|
2473
|
+
const interval = setInterval(() => {
|
|
2474
|
+
const dataList = state.chart?.getDataList();
|
|
2475
|
+
if (!dataList || dataList.length === 0) return;
|
|
2476
|
+
const lastBar = dataList[dataList.length - 1];
|
|
2477
|
+
const currentClose = lastBar.close;
|
|
2478
|
+
const prevClose = prevCloseRef.current;
|
|
2479
|
+
if (prevClose === null) {
|
|
2480
|
+
prevCloseRef.current = currentClose;
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
prevCloseRef.current = currentClose;
|
|
2484
|
+
setAlerts((prev) => {
|
|
2485
|
+
let changed = false;
|
|
2486
|
+
const next = prev.map((alert) => {
|
|
2487
|
+
if (alert.triggered) return alert;
|
|
2488
|
+
let shouldTrigger = false;
|
|
2489
|
+
if (alert.condition === "crossing_up") {
|
|
2490
|
+
shouldTrigger = prevClose < alert.price && currentClose >= alert.price;
|
|
2491
|
+
} else if (alert.condition === "crossing_down") {
|
|
2492
|
+
shouldTrigger = prevClose > alert.price && currentClose <= alert.price;
|
|
2493
|
+
} else if (alert.condition === "crossing") {
|
|
2494
|
+
shouldTrigger = prevClose < alert.price && currentClose >= alert.price || prevClose > alert.price && currentClose <= alert.price;
|
|
2495
|
+
}
|
|
2496
|
+
if (shouldTrigger) {
|
|
2497
|
+
changed = true;
|
|
2498
|
+
const triggered = { ...alert, triggered: true };
|
|
2499
|
+
callbackRef.current?.(triggered);
|
|
2500
|
+
return triggered;
|
|
2501
|
+
}
|
|
2502
|
+
return alert;
|
|
2503
|
+
});
|
|
2504
|
+
return changed ? next : prev;
|
|
2505
|
+
});
|
|
2506
|
+
}, 1e3);
|
|
2507
|
+
return () => clearInterval(interval);
|
|
2508
|
+
}, [state.chart]);
|
|
2509
|
+
return {
|
|
2510
|
+
alerts,
|
|
2511
|
+
addAlert,
|
|
2512
|
+
removeAlert,
|
|
2513
|
+
clearAlerts,
|
|
2514
|
+
onAlertTriggered
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
function useDataExport() {
|
|
2518
|
+
const { state } = useKlinechartsUI();
|
|
2519
|
+
const exportAll = react.useCallback(
|
|
2520
|
+
(format) => {
|
|
2521
|
+
if (!state.chart) return;
|
|
2522
|
+
const dataList = state.chart.getDataList();
|
|
2523
|
+
if (!dataList || dataList.length === 0) return;
|
|
2524
|
+
downloadData(dataList, format, state.symbol?.ticker);
|
|
2525
|
+
},
|
|
2526
|
+
[state.chart, state.symbol?.ticker]
|
|
2527
|
+
);
|
|
2528
|
+
const exportVisible = react.useCallback(
|
|
2529
|
+
(format) => {
|
|
2530
|
+
if (!state.chart) return;
|
|
2531
|
+
const dataList = state.chart.getDataList();
|
|
2532
|
+
if (!dataList || dataList.length === 0) return;
|
|
2533
|
+
const visibleRange = state.chart.getVisibleRange?.();
|
|
2534
|
+
if (!visibleRange) {
|
|
2535
|
+
downloadData(dataList, format, state.symbol?.ticker);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
const { from, to } = visibleRange;
|
|
2539
|
+
const visibleData = dataList.slice(from, to);
|
|
2540
|
+
downloadData(visibleData, format, state.symbol?.ticker);
|
|
2541
|
+
},
|
|
2542
|
+
[state.chart, state.symbol?.ticker]
|
|
2543
|
+
);
|
|
2544
|
+
return { exportAll, exportVisible };
|
|
2545
|
+
}
|
|
2546
|
+
function formatRow(bar) {
|
|
2547
|
+
return {
|
|
2548
|
+
date: new Date(bar.timestamp).toISOString(),
|
|
2549
|
+
open: bar.open,
|
|
2550
|
+
high: bar.high,
|
|
2551
|
+
low: bar.low,
|
|
2552
|
+
close: bar.close,
|
|
2553
|
+
volume: bar.volume ?? 0
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
function buildCsv(dataList) {
|
|
2557
|
+
const header = "Date,Open,High,Low,Close,Volume\n";
|
|
2558
|
+
const rows = dataList.map((bar) => {
|
|
2559
|
+
const r = formatRow(bar);
|
|
2560
|
+
return `${r.date},${r.open},${r.high},${r.low},${r.close},${r.volume}`;
|
|
2561
|
+
});
|
|
2562
|
+
return header + rows.join("\n");
|
|
2563
|
+
}
|
|
2564
|
+
function buildJson(dataList) {
|
|
2565
|
+
return JSON.stringify(dataList.map(formatRow), null, 2);
|
|
2566
|
+
}
|
|
2567
|
+
function downloadData(dataList, format, ticker) {
|
|
2568
|
+
const content = format === "csv" ? buildCsv(dataList) : buildJson(dataList);
|
|
2569
|
+
const mimeType = format === "csv" ? "text/csv" : "application/json";
|
|
2570
|
+
const blob = new Blob([content], { type: mimeType });
|
|
2571
|
+
const url = URL.createObjectURL(blob);
|
|
2572
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2573
|
+
const prefix = ticker ?? "chart";
|
|
2574
|
+
const filename = `${prefix}_${date}.${format}`;
|
|
2575
|
+
const link = document.createElement("a");
|
|
2576
|
+
link.href = url;
|
|
2577
|
+
link.download = filename;
|
|
2578
|
+
document.body.appendChild(link);
|
|
2579
|
+
link.click();
|
|
2580
|
+
document.body.removeChild(link);
|
|
2581
|
+
URL.revokeObjectURL(url);
|
|
2582
|
+
}
|
|
2583
|
+
function useWatchlist() {
|
|
2584
|
+
const { state } = useKlinechartsUI();
|
|
2585
|
+
const { dispatch, datafeed } = useKlinechartsUIDispatch();
|
|
2586
|
+
const [items, setItems] = react.useState([]);
|
|
2587
|
+
const subscriptionsRef = react.useRef(/* @__PURE__ */ new Map());
|
|
2588
|
+
const activeSymbol = state.symbol?.ticker ?? null;
|
|
2589
|
+
const addSymbol = react.useCallback(
|
|
2590
|
+
(ticker) => {
|
|
2591
|
+
setItems((prev) => {
|
|
2592
|
+
if (prev.some((item) => item.ticker === ticker)) return prev;
|
|
2593
|
+
return [
|
|
2594
|
+
...prev,
|
|
2595
|
+
{ ticker, lastPrice: null, change: null, changePercent: null }
|
|
2596
|
+
];
|
|
2597
|
+
});
|
|
2598
|
+
if (subscriptionsRef.current.has(ticker)) return;
|
|
2599
|
+
const symbolInfo = { ticker };
|
|
2600
|
+
const period = state.period;
|
|
2601
|
+
subscriptionsRef.current.set(ticker, { symbolInfo, period });
|
|
2602
|
+
datafeed.subscribe(symbolInfo, period, (bar) => {
|
|
2603
|
+
setItems(
|
|
2604
|
+
(prev) => prev.map((item) => {
|
|
2605
|
+
if (item.ticker !== ticker) return item;
|
|
2606
|
+
const change = bar.close - bar.open;
|
|
2607
|
+
const changePercent = bar.open !== 0 ? change / bar.open * 100 : null;
|
|
2608
|
+
return {
|
|
2609
|
+
...item,
|
|
2610
|
+
lastPrice: bar.close,
|
|
2611
|
+
change,
|
|
2612
|
+
changePercent
|
|
2613
|
+
};
|
|
2614
|
+
})
|
|
2615
|
+
);
|
|
2616
|
+
});
|
|
2617
|
+
},
|
|
2618
|
+
[datafeed, state.period]
|
|
2619
|
+
);
|
|
2620
|
+
const removeSymbol = react.useCallback(
|
|
2621
|
+
(ticker) => {
|
|
2622
|
+
const sub = subscriptionsRef.current.get(ticker);
|
|
2623
|
+
if (sub) {
|
|
2624
|
+
datafeed.unsubscribe(sub.symbolInfo, sub.period);
|
|
2625
|
+
subscriptionsRef.current.delete(ticker);
|
|
2626
|
+
}
|
|
2627
|
+
setItems((prev) => prev.filter((item) => item.ticker !== ticker));
|
|
2628
|
+
},
|
|
2629
|
+
[datafeed]
|
|
2630
|
+
);
|
|
2631
|
+
const switchSymbol = react.useCallback(
|
|
2632
|
+
(ticker) => {
|
|
2633
|
+
dispatch({ type: "SET_SYMBOL", symbol: { ticker } });
|
|
2634
|
+
},
|
|
2635
|
+
[dispatch]
|
|
2636
|
+
);
|
|
2637
|
+
react.useEffect(() => {
|
|
2638
|
+
return () => {
|
|
2639
|
+
subscriptionsRef.current.forEach((sub) => {
|
|
2640
|
+
datafeed.unsubscribe(sub.symbolInfo, sub.period);
|
|
2641
|
+
});
|
|
2642
|
+
subscriptionsRef.current.clear();
|
|
2643
|
+
};
|
|
2644
|
+
}, [datafeed]);
|
|
2645
|
+
return {
|
|
2646
|
+
items,
|
|
2647
|
+
addSymbol,
|
|
2648
|
+
removeSymbol,
|
|
2649
|
+
switchSymbol,
|
|
2650
|
+
activeSymbol
|
|
2651
|
+
};
|
|
2652
|
+
}
|
|
2653
|
+
function useReplay() {
|
|
2654
|
+
const { state } = useKlinechartsUI();
|
|
2655
|
+
const [isReplaying, setIsReplaying] = react.useState(false);
|
|
2656
|
+
const [isPaused, setIsPaused] = react.useState(false);
|
|
2657
|
+
const [speed, setSpeedState] = react.useState(1);
|
|
2658
|
+
const [barIndex, setBarIndex] = react.useState(0);
|
|
2659
|
+
const [totalBars, setTotalBars] = react.useState(0);
|
|
2660
|
+
const intervalRef = react.useRef(null);
|
|
2661
|
+
const savedDataRef = react.useRef([]);
|
|
2662
|
+
const currentIndexRef = react.useRef(0);
|
|
2663
|
+
const clearInterval_ = react.useCallback(() => {
|
|
2664
|
+
if (intervalRef.current !== null) {
|
|
2665
|
+
clearInterval(intervalRef.current);
|
|
2666
|
+
intervalRef.current = null;
|
|
2667
|
+
}
|
|
2668
|
+
}, []);
|
|
2669
|
+
const addNextBar = react.useCallback(() => {
|
|
2670
|
+
if (!state.chart) return;
|
|
2671
|
+
const data = savedDataRef.current;
|
|
2672
|
+
const idx = currentIndexRef.current;
|
|
2673
|
+
if (idx >= data.length) {
|
|
2674
|
+
clearInterval_();
|
|
2675
|
+
setIsPaused(true);
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
const bar = data[idx];
|
|
2679
|
+
state.chart.updateData?.(bar);
|
|
2680
|
+
currentIndexRef.current = idx + 1;
|
|
2681
|
+
setBarIndex(idx + 1);
|
|
2682
|
+
}, [state.chart, clearInterval_]);
|
|
2683
|
+
const startInterval = react.useCallback(
|
|
2684
|
+
(currentSpeed) => {
|
|
2685
|
+
clearInterval_();
|
|
2686
|
+
intervalRef.current = setInterval(addNextBar, 1e3 / currentSpeed);
|
|
2687
|
+
},
|
|
2688
|
+
[addNextBar, clearInterval_]
|
|
2689
|
+
);
|
|
2690
|
+
const startReplay = react.useCallback(() => {
|
|
2691
|
+
if (!state.chart) return;
|
|
2692
|
+
const dataList = state.chart.getDataList();
|
|
2693
|
+
if (!dataList || dataList.length === 0) return;
|
|
2694
|
+
savedDataRef.current = [...dataList];
|
|
2695
|
+
currentIndexRef.current = 0;
|
|
2696
|
+
setTotalBars(dataList.length);
|
|
2697
|
+
setBarIndex(0);
|
|
2698
|
+
setIsReplaying(true);
|
|
2699
|
+
setIsPaused(false);
|
|
2700
|
+
state.chart.clearData?.();
|
|
2701
|
+
startInterval(speed);
|
|
2702
|
+
}, [state.chart, speed, startInterval]);
|
|
2703
|
+
const stopReplay = react.useCallback(() => {
|
|
2704
|
+
if (!state.chart) return;
|
|
2705
|
+
clearInterval_();
|
|
2706
|
+
const data = savedDataRef.current;
|
|
2707
|
+
if (data.length > 0) {
|
|
2708
|
+
state.chart.clearData?.();
|
|
2709
|
+
for (const bar of data) {
|
|
2710
|
+
state.chart.updateData?.(bar);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
savedDataRef.current = [];
|
|
2714
|
+
currentIndexRef.current = 0;
|
|
2715
|
+
setIsReplaying(false);
|
|
2716
|
+
setIsPaused(false);
|
|
2717
|
+
setBarIndex(0);
|
|
2718
|
+
setTotalBars(0);
|
|
2719
|
+
}, [state.chart, clearInterval_]);
|
|
2720
|
+
const togglePause = react.useCallback(() => {
|
|
2721
|
+
if (!isReplaying) return;
|
|
2722
|
+
if (isPaused) {
|
|
2723
|
+
startInterval(speed);
|
|
2724
|
+
setIsPaused(false);
|
|
2725
|
+
} else {
|
|
2726
|
+
clearInterval_();
|
|
2727
|
+
setIsPaused(true);
|
|
2728
|
+
}
|
|
2729
|
+
}, [isReplaying, isPaused, speed, startInterval, clearInterval_]);
|
|
2730
|
+
const stepForward = react.useCallback(() => {
|
|
2731
|
+
if (!isReplaying || !isPaused) return;
|
|
2732
|
+
addNextBar();
|
|
2733
|
+
}, [isReplaying, isPaused, addNextBar]);
|
|
2734
|
+
const stepBackward = react.useCallback(() => {
|
|
2735
|
+
if (!isReplaying || !isPaused || !state.chart) return;
|
|
2736
|
+
const idx = currentIndexRef.current;
|
|
2737
|
+
if (idx <= 1) return;
|
|
2738
|
+
const data = savedDataRef.current;
|
|
2739
|
+
state.chart.clearData?.();
|
|
2740
|
+
for (let i = 0; i < idx - 1; i++) {
|
|
2741
|
+
state.chart.updateData?.(data[i]);
|
|
2742
|
+
}
|
|
2743
|
+
currentIndexRef.current = idx - 1;
|
|
2744
|
+
setBarIndex(idx - 1);
|
|
2745
|
+
}, [isReplaying, isPaused, state.chart]);
|
|
2746
|
+
const seekTo = react.useCallback(
|
|
2747
|
+
(targetIndex) => {
|
|
2748
|
+
if (!isReplaying || !state.chart) return;
|
|
2749
|
+
const data = savedDataRef.current;
|
|
2750
|
+
const clamped = Math.max(0, Math.min(targetIndex, data.length));
|
|
2751
|
+
clearInterval_();
|
|
2752
|
+
setIsPaused(true);
|
|
2753
|
+
state.chart.clearData?.();
|
|
2754
|
+
for (let i = 0; i < clamped; i++) {
|
|
2755
|
+
state.chart.updateData?.(data[i]);
|
|
2756
|
+
}
|
|
2757
|
+
currentIndexRef.current = clamped;
|
|
2758
|
+
setBarIndex(clamped);
|
|
2759
|
+
},
|
|
2760
|
+
[isReplaying, state.chart, clearInterval_]
|
|
2761
|
+
);
|
|
2762
|
+
const setSpeed = react.useCallback(
|
|
2763
|
+
(newSpeed) => {
|
|
2764
|
+
setSpeedState(newSpeed);
|
|
2765
|
+
if (isReplaying && !isPaused) {
|
|
2766
|
+
startInterval(newSpeed);
|
|
2767
|
+
}
|
|
2768
|
+
},
|
|
2769
|
+
[isReplaying, isPaused, startInterval]
|
|
2770
|
+
);
|
|
2771
|
+
react.useEffect(() => {
|
|
2772
|
+
return () => {
|
|
2773
|
+
clearInterval_();
|
|
2774
|
+
};
|
|
2775
|
+
}, [clearInterval_]);
|
|
2776
|
+
return {
|
|
2777
|
+
isReplaying,
|
|
2778
|
+
isPaused,
|
|
2779
|
+
speed,
|
|
2780
|
+
barIndex,
|
|
2781
|
+
totalBars,
|
|
2782
|
+
startReplay,
|
|
2783
|
+
stopReplay,
|
|
2784
|
+
togglePause,
|
|
2785
|
+
stepForward,
|
|
2786
|
+
stepBackward,
|
|
2787
|
+
seekTo,
|
|
2788
|
+
setSpeed
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
var DEFAULT_COLORS = [
|
|
2792
|
+
"#2196f3",
|
|
2793
|
+
"#ff9800",
|
|
2794
|
+
"#4caf50",
|
|
2795
|
+
"#e91e63",
|
|
2796
|
+
"#9c27b0",
|
|
2797
|
+
"#00bcd4",
|
|
2798
|
+
"#ff5722",
|
|
2799
|
+
"#8bc34a"
|
|
2800
|
+
];
|
|
2801
|
+
var colorIndex = 0;
|
|
2802
|
+
var compareCounter = 0;
|
|
2803
|
+
function useCompare() {
|
|
2804
|
+
const { state, datafeed } = useKlinechartsUI();
|
|
2805
|
+
const [symbols, setSymbols] = react.useState([]);
|
|
2806
|
+
const indicatorsRef = react.useRef(/* @__PURE__ */ new Map());
|
|
2807
|
+
const addSymbol = react.useCallback(
|
|
2808
|
+
async (ticker, color) => {
|
|
2809
|
+
if (!state.chart || !datafeed) return;
|
|
2810
|
+
if (indicatorsRef.current.has(ticker)) return;
|
|
2811
|
+
const assignedColor = color ?? DEFAULT_COLORS[colorIndex++ % DEFAULT_COLORS.length];
|
|
2812
|
+
const mainDataList = state.chart.getDataList();
|
|
2813
|
+
if (!mainDataList || mainDataList.length === 0) return;
|
|
2814
|
+
const from = mainDataList[0].timestamp;
|
|
2815
|
+
const to = mainDataList[mainDataList.length - 1].timestamp;
|
|
2816
|
+
const compareData = await datafeed.getHistoryKLineData(
|
|
2817
|
+
{ ticker },
|
|
2818
|
+
{ ...state.period, label: "" },
|
|
2819
|
+
from,
|
|
2820
|
+
to
|
|
2821
|
+
);
|
|
2822
|
+
if (!compareData || compareData.length === 0) return;
|
|
2823
|
+
const compareMap = /* @__PURE__ */ new Map();
|
|
2824
|
+
for (const bar of compareData) {
|
|
2825
|
+
compareMap.set(bar.timestamp, bar.close);
|
|
2826
|
+
}
|
|
2827
|
+
let basePrice = null;
|
|
2828
|
+
for (const bar of mainDataList) {
|
|
2829
|
+
const close = compareMap.get(bar.timestamp);
|
|
2830
|
+
if (close != null) {
|
|
2831
|
+
basePrice = close;
|
|
2832
|
+
break;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
if (basePrice === null || basePrice === 0) return;
|
|
2836
|
+
const bp = basePrice;
|
|
2837
|
+
const normalizedData = mainDataList.map((mainBar) => {
|
|
2838
|
+
const close = compareMap.get(mainBar.timestamp);
|
|
2839
|
+
return {
|
|
2840
|
+
pct: close != null ? (close - bp) / bp * 100 : NaN
|
|
2841
|
+
};
|
|
2842
|
+
});
|
|
2843
|
+
const indicatorName = `__cmp_${++compareCounter}_${ticker}`;
|
|
2844
|
+
reactKlinecharts.registerIndicator({
|
|
2845
|
+
name: indicatorName,
|
|
2846
|
+
shortName: ticker,
|
|
2847
|
+
figures: [
|
|
2848
|
+
{
|
|
2849
|
+
key: "pct",
|
|
2850
|
+
title: `${ticker} %: `,
|
|
2851
|
+
type: "line",
|
|
2852
|
+
styles: () => ({ color: assignedColor })
|
|
2853
|
+
}
|
|
2854
|
+
],
|
|
2855
|
+
calc: () => normalizedData
|
|
2856
|
+
});
|
|
2857
|
+
const paneId = state.chart.createIndicator(
|
|
2858
|
+
{ name: indicatorName },
|
|
2859
|
+
true,
|
|
2860
|
+
{ id: "candle_pane" }
|
|
2861
|
+
);
|
|
2862
|
+
indicatorsRef.current.set(ticker, {
|
|
2863
|
+
name: indicatorName,
|
|
2864
|
+
paneId: typeof paneId === "string" ? paneId : "candle_pane"
|
|
2865
|
+
});
|
|
2866
|
+
setSymbols((prev) => {
|
|
2867
|
+
if (prev.some((s) => s.ticker === ticker)) return prev;
|
|
2868
|
+
return [
|
|
2869
|
+
...prev,
|
|
2870
|
+
{ ticker, basePrice: bp, color: assignedColor, visible: true }
|
|
2871
|
+
];
|
|
2872
|
+
});
|
|
2873
|
+
},
|
|
2874
|
+
[state.chart, state.period, datafeed]
|
|
2875
|
+
);
|
|
2876
|
+
const removeSymbol = react.useCallback(
|
|
2877
|
+
(ticker) => {
|
|
2878
|
+
const info = indicatorsRef.current.get(ticker);
|
|
2879
|
+
if (info && state.chart) {
|
|
2880
|
+
try {
|
|
2881
|
+
state.chart.removeIndicator({ name: info.name });
|
|
2882
|
+
} catch {
|
|
2883
|
+
}
|
|
2884
|
+
indicatorsRef.current.delete(ticker);
|
|
2885
|
+
}
|
|
2886
|
+
setSymbols((prev) => prev.filter((s) => s.ticker !== ticker));
|
|
2887
|
+
},
|
|
2888
|
+
[state.chart]
|
|
2889
|
+
);
|
|
2890
|
+
const toggleSymbol = react.useCallback(
|
|
2891
|
+
(ticker) => {
|
|
2892
|
+
const info = indicatorsRef.current.get(ticker);
|
|
2893
|
+
if (!info || !state.chart) return;
|
|
2894
|
+
setSymbols((prev) => {
|
|
2895
|
+
const sym = prev.find((s) => s.ticker === ticker);
|
|
2896
|
+
if (!sym) return prev;
|
|
2897
|
+
const newVisible = !sym.visible;
|
|
2898
|
+
state.chart?.overrideIndicator({
|
|
2899
|
+
name: info.name,
|
|
2900
|
+
visible: newVisible
|
|
2901
|
+
});
|
|
2902
|
+
return prev.map(
|
|
2903
|
+
(s) => s.ticker === ticker ? { ...s, visible: newVisible } : s
|
|
2904
|
+
);
|
|
2905
|
+
});
|
|
2906
|
+
},
|
|
2907
|
+
[state.chart]
|
|
2908
|
+
);
|
|
2909
|
+
const clearAll = react.useCallback(() => {
|
|
2910
|
+
indicatorsRef.current.forEach((info) => {
|
|
2911
|
+
try {
|
|
2912
|
+
state.chart?.removeIndicator({ name: info.name });
|
|
2913
|
+
} catch {
|
|
2914
|
+
}
|
|
2915
|
+
});
|
|
2916
|
+
indicatorsRef.current.clear();
|
|
2917
|
+
setSymbols([]);
|
|
2918
|
+
}, [state.chart]);
|
|
2919
|
+
react.useEffect(() => {
|
|
2920
|
+
return () => {
|
|
2921
|
+
indicatorsRef.current.forEach((info) => {
|
|
2922
|
+
try {
|
|
2923
|
+
state.chart?.removeIndicator({ name: info.name });
|
|
2924
|
+
} catch {
|
|
2925
|
+
}
|
|
2926
|
+
});
|
|
2927
|
+
indicatorsRef.current.clear();
|
|
2928
|
+
};
|
|
2929
|
+
}, [state.chart]);
|
|
2930
|
+
return { symbols, addSymbol, removeSymbol, toggleSymbol, clearAll };
|
|
2931
|
+
}
|
|
2932
|
+
var MEASURE_OVERLAY_ID = "__measure_line__";
|
|
2933
|
+
function useMeasure() {
|
|
2934
|
+
const { state } = useKlinechartsUI();
|
|
2935
|
+
const [isActive, setIsActive] = react.useState(false);
|
|
2936
|
+
const [fromPoint, setFromPoint] = react.useState(null);
|
|
2937
|
+
const [result, setResult] = react.useState(null);
|
|
2938
|
+
const clickCountRef = react.useRef(0);
|
|
2939
|
+
const computeResult = react.useCallback(
|
|
2940
|
+
(from, to) => {
|
|
2941
|
+
const priceDiff = to.price - from.price;
|
|
2942
|
+
const pricePercent = from.price !== 0 ? priceDiff / from.price * 100 : 0;
|
|
2943
|
+
const bars = Math.abs(to.barIndex - from.barIndex);
|
|
2944
|
+
const timeDiff = Math.abs(to.timestamp - from.timestamp);
|
|
2945
|
+
return { from, to, priceDiff, pricePercent, bars, timeDiff };
|
|
2946
|
+
},
|
|
2947
|
+
[]
|
|
2948
|
+
);
|
|
2949
|
+
const cleanup = react.useCallback(() => {
|
|
2950
|
+
state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
|
|
2951
|
+
}, [state.chart]);
|
|
2952
|
+
const startMeasure = react.useCallback(() => {
|
|
2953
|
+
cleanup();
|
|
2954
|
+
setIsActive(true);
|
|
2955
|
+
setFromPoint(null);
|
|
2956
|
+
setResult(null);
|
|
2957
|
+
clickCountRef.current = 0;
|
|
2958
|
+
if (!state.chart) return;
|
|
2959
|
+
state.chart.createOverlay({
|
|
2960
|
+
name: "segment",
|
|
2961
|
+
id: MEASURE_OVERLAY_ID,
|
|
2962
|
+
groupId: "measure",
|
|
2963
|
+
styles: {
|
|
2964
|
+
line: {
|
|
2965
|
+
style: "dashed",
|
|
2966
|
+
color: "#FFD600",
|
|
2967
|
+
size: 1
|
|
2968
|
+
}
|
|
2969
|
+
},
|
|
2970
|
+
lock: true,
|
|
2971
|
+
onDrawEnd: (event) => {
|
|
2972
|
+
const overlay = event?.overlay;
|
|
2973
|
+
const points = overlay?.points;
|
|
2974
|
+
if (!points || points.length < 2) return;
|
|
2975
|
+
const chart = state.chart;
|
|
2976
|
+
if (!chart) return;
|
|
2977
|
+
const dataList = chart.getDataList();
|
|
2978
|
+
if (!dataList || dataList.length === 0) return;
|
|
2979
|
+
const makePoint = (p) => {
|
|
2980
|
+
const idx = Math.max(
|
|
2981
|
+
0,
|
|
2982
|
+
Math.min(p.dataIndex ?? 0, dataList.length - 1)
|
|
2983
|
+
);
|
|
2984
|
+
const bar = dataList[idx];
|
|
2985
|
+
return {
|
|
2986
|
+
price: p.value ?? bar?.close ?? 0,
|
|
2987
|
+
timestamp: bar?.timestamp ?? 0,
|
|
2988
|
+
barIndex: idx
|
|
2989
|
+
};
|
|
2990
|
+
};
|
|
2991
|
+
const from = makePoint(points[0]);
|
|
2992
|
+
const to = makePoint(points[1]);
|
|
2993
|
+
setFromPoint(from);
|
|
2994
|
+
setResult(computeResult(from, to));
|
|
2995
|
+
setIsActive(false);
|
|
2996
|
+
}
|
|
2997
|
+
});
|
|
2998
|
+
}, [state.chart, cleanup, computeResult]);
|
|
2999
|
+
const cancelMeasure = react.useCallback(() => {
|
|
3000
|
+
cleanup();
|
|
3001
|
+
setIsActive(false);
|
|
3002
|
+
setFromPoint(null);
|
|
3003
|
+
clickCountRef.current = 0;
|
|
3004
|
+
}, [cleanup]);
|
|
3005
|
+
const clearResult = react.useCallback(() => {
|
|
3006
|
+
cleanup();
|
|
3007
|
+
setResult(null);
|
|
3008
|
+
setFromPoint(null);
|
|
3009
|
+
}, [cleanup]);
|
|
3010
|
+
react.useEffect(() => {
|
|
3011
|
+
return () => {
|
|
3012
|
+
state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
|
|
3013
|
+
};
|
|
3014
|
+
}, [state.chart]);
|
|
3015
|
+
return {
|
|
3016
|
+
isActive,
|
|
3017
|
+
startMeasure,
|
|
3018
|
+
cancelMeasure,
|
|
3019
|
+
result,
|
|
3020
|
+
clearResult,
|
|
3021
|
+
fromPoint
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
var annotationCounter = 0;
|
|
3025
|
+
function useAnnotations() {
|
|
3026
|
+
const { state } = useKlinechartsUI();
|
|
3027
|
+
const [annotations, setAnnotations] = react.useState([]);
|
|
3028
|
+
const addAnnotation = react.useCallback(
|
|
3029
|
+
(text, price, timestamp, color) => {
|
|
3030
|
+
const id = `annotation_${++annotationCounter}`;
|
|
3031
|
+
const annotation = {
|
|
3032
|
+
id,
|
|
3033
|
+
text,
|
|
3034
|
+
price,
|
|
3035
|
+
timestamp,
|
|
3036
|
+
color
|
|
3037
|
+
};
|
|
3038
|
+
setAnnotations((prev) => [...prev, annotation]);
|
|
3039
|
+
if (state.chart) {
|
|
3040
|
+
state.chart.createOverlay({
|
|
3041
|
+
name: "simpleAnnotation",
|
|
3042
|
+
id,
|
|
3043
|
+
groupId: "annotations",
|
|
3044
|
+
points: [{ timestamp, value: price }],
|
|
3045
|
+
extendData: text,
|
|
3046
|
+
styles: color ? {
|
|
3047
|
+
text: {
|
|
3048
|
+
color
|
|
3049
|
+
}
|
|
3050
|
+
} : void 0,
|
|
3051
|
+
lock: true
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
return id;
|
|
3055
|
+
},
|
|
3056
|
+
[state.chart]
|
|
3057
|
+
);
|
|
3058
|
+
const removeAnnotation = react.useCallback(
|
|
3059
|
+
(id) => {
|
|
3060
|
+
setAnnotations((prev) => prev.filter((a) => a.id !== id));
|
|
3061
|
+
state.chart?.removeOverlay({ id });
|
|
3062
|
+
},
|
|
3063
|
+
[state.chart]
|
|
3064
|
+
);
|
|
3065
|
+
const updateAnnotation = react.useCallback(
|
|
3066
|
+
(id, updates) => {
|
|
3067
|
+
setAnnotations(
|
|
3068
|
+
(prev) => prev.map((a) => a.id === id ? { ...a, ...updates } : a)
|
|
3069
|
+
);
|
|
3070
|
+
if (state.chart) {
|
|
3071
|
+
const overrideData = { id };
|
|
3072
|
+
if (updates.text !== void 0) {
|
|
3073
|
+
overrideData.extendData = updates.text;
|
|
3074
|
+
}
|
|
3075
|
+
if (updates.color !== void 0) {
|
|
3076
|
+
overrideData.styles = {
|
|
3077
|
+
text: {
|
|
3078
|
+
color: updates.color
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
state.chart.overrideOverlay(overrideData);
|
|
3083
|
+
}
|
|
3084
|
+
},
|
|
3085
|
+
[state.chart]
|
|
3086
|
+
);
|
|
3087
|
+
const clearAnnotations = react.useCallback(() => {
|
|
3088
|
+
setAnnotations((prev) => {
|
|
3089
|
+
for (const annotation of prev) {
|
|
3090
|
+
state.chart?.removeOverlay({ id: annotation.id });
|
|
3091
|
+
}
|
|
3092
|
+
return [];
|
|
3093
|
+
});
|
|
3094
|
+
}, [state.chart]);
|
|
3095
|
+
react.useEffect(() => {
|
|
3096
|
+
return () => {
|
|
3097
|
+
state.chart?.removeOverlay({ groupId: "annotations" });
|
|
3098
|
+
};
|
|
3099
|
+
}, [state.chart]);
|
|
3100
|
+
return {
|
|
3101
|
+
annotations,
|
|
3102
|
+
addAnnotation,
|
|
3103
|
+
removeAnnotation,
|
|
3104
|
+
updateAnnotation,
|
|
3105
|
+
clearAnnotations
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
1324
3108
|
|
|
1325
3109
|
// src/utils/createDataLoader.ts
|
|
1326
3110
|
function createDataLoader(datafeed, dispatch) {
|
|
@@ -1386,85 +3170,177 @@ function createDataLoader(datafeed, dispatch) {
|
|
|
1386
3170
|
};
|
|
1387
3171
|
}
|
|
1388
3172
|
|
|
3173
|
+
Object.defineProperty(exports, "TA", {
|
|
3174
|
+
enumerable: true,
|
|
3175
|
+
get: function () { return chunkPIFLJKYF_cjs.TA_default; }
|
|
3176
|
+
});
|
|
1389
3177
|
Object.defineProperty(exports, "abcd", {
|
|
1390
3178
|
enumerable: true,
|
|
1391
|
-
get: function () { return
|
|
3179
|
+
get: function () { return chunkPIFLJKYF_cjs.abcd_default; }
|
|
1392
3180
|
});
|
|
1393
3181
|
Object.defineProperty(exports, "anyWaves", {
|
|
1394
3182
|
enumerable: true,
|
|
1395
|
-
get: function () { return
|
|
3183
|
+
get: function () { return chunkPIFLJKYF_cjs.anyWaves_default; }
|
|
1396
3184
|
});
|
|
1397
3185
|
Object.defineProperty(exports, "arrow", {
|
|
1398
3186
|
enumerable: true,
|
|
1399
|
-
get: function () { return
|
|
3187
|
+
get: function () { return chunkPIFLJKYF_cjs.arrow_default; }
|
|
3188
|
+
});
|
|
3189
|
+
Object.defineProperty(exports, "bollTv", {
|
|
3190
|
+
enumerable: true,
|
|
3191
|
+
get: function () { return chunkPIFLJKYF_cjs.bollTv_default; }
|
|
3192
|
+
});
|
|
3193
|
+
Object.defineProperty(exports, "brush", {
|
|
3194
|
+
enumerable: true,
|
|
3195
|
+
get: function () { return chunkPIFLJKYF_cjs.brush_default; }
|
|
3196
|
+
});
|
|
3197
|
+
Object.defineProperty(exports, "cci", {
|
|
3198
|
+
enumerable: true,
|
|
3199
|
+
get: function () { return chunkPIFLJKYF_cjs.cci_default; }
|
|
1400
3200
|
});
|
|
1401
3201
|
Object.defineProperty(exports, "circle", {
|
|
1402
3202
|
enumerable: true,
|
|
1403
|
-
get: function () { return
|
|
3203
|
+
get: function () { return chunkPIFLJKYF_cjs.circle_default; }
|
|
3204
|
+
});
|
|
3205
|
+
Object.defineProperty(exports, "depthOverlay", {
|
|
3206
|
+
enumerable: true,
|
|
3207
|
+
get: function () { return chunkPIFLJKYF_cjs.depthOverlay_default; }
|
|
1404
3208
|
});
|
|
1405
3209
|
Object.defineProperty(exports, "eightWaves", {
|
|
1406
3210
|
enumerable: true,
|
|
1407
|
-
get: function () { return
|
|
3211
|
+
get: function () { return chunkPIFLJKYF_cjs.eightWaves_default; }
|
|
3212
|
+
});
|
|
3213
|
+
Object.defineProperty(exports, "elliottWave", {
|
|
3214
|
+
enumerable: true,
|
|
3215
|
+
get: function () { return chunkPIFLJKYF_cjs.elliottWave_default; }
|
|
3216
|
+
});
|
|
3217
|
+
Object.defineProperty(exports, "fibRetracement", {
|
|
3218
|
+
enumerable: true,
|
|
3219
|
+
get: function () { return chunkPIFLJKYF_cjs.fibRetracement_default; }
|
|
1408
3220
|
});
|
|
1409
3221
|
Object.defineProperty(exports, "fibonacciCircle", {
|
|
1410
3222
|
enumerable: true,
|
|
1411
|
-
get: function () { return
|
|
3223
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciCircle_default; }
|
|
1412
3224
|
});
|
|
1413
3225
|
Object.defineProperty(exports, "fibonacciExtension", {
|
|
1414
3226
|
enumerable: true,
|
|
1415
|
-
get: function () { return
|
|
3227
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciExtension_default; }
|
|
1416
3228
|
});
|
|
1417
3229
|
Object.defineProperty(exports, "fibonacciSegment", {
|
|
1418
3230
|
enumerable: true,
|
|
1419
|
-
get: function () { return
|
|
3231
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciSegment_default; }
|
|
1420
3232
|
});
|
|
1421
3233
|
Object.defineProperty(exports, "fibonacciSpeedResistanceFan", {
|
|
1422
3234
|
enumerable: true,
|
|
1423
|
-
get: function () { return
|
|
3235
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciSpeedResistanceFan_default; }
|
|
1424
3236
|
});
|
|
1425
3237
|
Object.defineProperty(exports, "fibonacciSpiral", {
|
|
1426
3238
|
enumerable: true,
|
|
1427
|
-
get: function () { return
|
|
3239
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciSpiral_default; }
|
|
1428
3240
|
});
|
|
1429
3241
|
Object.defineProperty(exports, "fiveWaves", {
|
|
1430
3242
|
enumerable: true,
|
|
1431
|
-
get: function () { return
|
|
3243
|
+
get: function () { return chunkPIFLJKYF_cjs.fiveWaves_default; }
|
|
1432
3244
|
});
|
|
1433
3245
|
Object.defineProperty(exports, "gannBox", {
|
|
1434
3246
|
enumerable: true,
|
|
1435
|
-
get: function () { return
|
|
3247
|
+
get: function () { return chunkPIFLJKYF_cjs.gannBox_default; }
|
|
3248
|
+
});
|
|
3249
|
+
Object.defineProperty(exports, "gannFan", {
|
|
3250
|
+
enumerable: true,
|
|
3251
|
+
get: function () { return chunkPIFLJKYF_cjs.gannFan_default; }
|
|
3252
|
+
});
|
|
3253
|
+
Object.defineProperty(exports, "hma", {
|
|
3254
|
+
enumerable: true,
|
|
3255
|
+
get: function () { return chunkPIFLJKYF_cjs.hma_default; }
|
|
3256
|
+
});
|
|
3257
|
+
Object.defineProperty(exports, "ichimoku", {
|
|
3258
|
+
enumerable: true,
|
|
3259
|
+
get: function () { return chunkPIFLJKYF_cjs.ichimoku_default; }
|
|
3260
|
+
});
|
|
3261
|
+
Object.defineProperty(exports, "indicators", {
|
|
3262
|
+
enumerable: true,
|
|
3263
|
+
get: function () { return chunkPIFLJKYF_cjs.indicators; }
|
|
3264
|
+
});
|
|
3265
|
+
Object.defineProperty(exports, "longPosition", {
|
|
3266
|
+
enumerable: true,
|
|
3267
|
+
get: function () { return chunkPIFLJKYF_cjs.longPosition_default; }
|
|
3268
|
+
});
|
|
3269
|
+
Object.defineProperty(exports, "maRibbon", {
|
|
3270
|
+
enumerable: true,
|
|
3271
|
+
get: function () { return chunkPIFLJKYF_cjs.maRibbon_default; }
|
|
3272
|
+
});
|
|
3273
|
+
Object.defineProperty(exports, "macdTv", {
|
|
3274
|
+
enumerable: true,
|
|
3275
|
+
get: function () { return chunkPIFLJKYF_cjs.macdTv_default; }
|
|
3276
|
+
});
|
|
3277
|
+
Object.defineProperty(exports, "measure", {
|
|
3278
|
+
enumerable: true,
|
|
3279
|
+
get: function () { return chunkPIFLJKYF_cjs.measure_default; }
|
|
1436
3280
|
});
|
|
1437
3281
|
Object.defineProperty(exports, "orderLine", {
|
|
1438
3282
|
enumerable: true,
|
|
1439
|
-
get: function () { return
|
|
3283
|
+
get: function () { return chunkPIFLJKYF_cjs.orderLine_default; }
|
|
1440
3284
|
});
|
|
1441
3285
|
Object.defineProperty(exports, "overlays", {
|
|
1442
3286
|
enumerable: true,
|
|
1443
|
-
get: function () { return
|
|
3287
|
+
get: function () { return chunkPIFLJKYF_cjs.overlays; }
|
|
3288
|
+
});
|
|
3289
|
+
Object.defineProperty(exports, "parallelChannel", {
|
|
3290
|
+
enumerable: true,
|
|
3291
|
+
get: function () { return chunkPIFLJKYF_cjs.parallelChannel_default; }
|
|
1444
3292
|
});
|
|
1445
3293
|
Object.defineProperty(exports, "parallelogram", {
|
|
1446
3294
|
enumerable: true,
|
|
1447
|
-
get: function () { return
|
|
3295
|
+
get: function () { return chunkPIFLJKYF_cjs.parallelogram_default; }
|
|
3296
|
+
});
|
|
3297
|
+
Object.defineProperty(exports, "pivotPoints", {
|
|
3298
|
+
enumerable: true,
|
|
3299
|
+
get: function () { return chunkPIFLJKYF_cjs.pivotPoints_default; }
|
|
3300
|
+
});
|
|
3301
|
+
Object.defineProperty(exports, "ray", {
|
|
3302
|
+
enumerable: true,
|
|
3303
|
+
get: function () { return chunkPIFLJKYF_cjs.ray_default; }
|
|
1448
3304
|
});
|
|
1449
3305
|
Object.defineProperty(exports, "rect", {
|
|
1450
3306
|
enumerable: true,
|
|
1451
|
-
get: function () { return
|
|
3307
|
+
get: function () { return chunkPIFLJKYF_cjs.rect_default; }
|
|
1452
3308
|
});
|
|
1453
3309
|
Object.defineProperty(exports, "registerExtensions", {
|
|
1454
3310
|
enumerable: true,
|
|
1455
|
-
get: function () { return
|
|
3311
|
+
get: function () { return chunkPIFLJKYF_cjs.registerExtensions; }
|
|
3312
|
+
});
|
|
3313
|
+
Object.defineProperty(exports, "rsiTv", {
|
|
3314
|
+
enumerable: true,
|
|
3315
|
+
get: function () { return chunkPIFLJKYF_cjs.rsiTv_default; }
|
|
3316
|
+
});
|
|
3317
|
+
Object.defineProperty(exports, "shortPosition", {
|
|
3318
|
+
enumerable: true,
|
|
3319
|
+
get: function () { return chunkPIFLJKYF_cjs.shortPosition_default; }
|
|
3320
|
+
});
|
|
3321
|
+
Object.defineProperty(exports, "stochastic", {
|
|
3322
|
+
enumerable: true,
|
|
3323
|
+
get: function () { return chunkPIFLJKYF_cjs.stochastic_default; }
|
|
3324
|
+
});
|
|
3325
|
+
Object.defineProperty(exports, "superTrend", {
|
|
3326
|
+
enumerable: true,
|
|
3327
|
+
get: function () { return chunkPIFLJKYF_cjs.superTrend_default; }
|
|
1456
3328
|
});
|
|
1457
3329
|
Object.defineProperty(exports, "threeWaves", {
|
|
1458
3330
|
enumerable: true,
|
|
1459
|
-
get: function () { return
|
|
3331
|
+
get: function () { return chunkPIFLJKYF_cjs.threeWaves_default; }
|
|
1460
3332
|
});
|
|
1461
3333
|
Object.defineProperty(exports, "triangle", {
|
|
1462
3334
|
enumerable: true,
|
|
1463
|
-
get: function () { return
|
|
3335
|
+
get: function () { return chunkPIFLJKYF_cjs.triangle_default; }
|
|
3336
|
+
});
|
|
3337
|
+
Object.defineProperty(exports, "vwap", {
|
|
3338
|
+
enumerable: true,
|
|
3339
|
+
get: function () { return chunkPIFLJKYF_cjs.vwap_default; }
|
|
1464
3340
|
});
|
|
1465
3341
|
Object.defineProperty(exports, "xabcd", {
|
|
1466
3342
|
enumerable: true,
|
|
1467
|
-
get: function () { return
|
|
3343
|
+
get: function () { return chunkPIFLJKYF_cjs.xabcd_default; }
|
|
1468
3344
|
});
|
|
1469
3345
|
exports.CANDLE_TYPES = CANDLE_TYPES;
|
|
1470
3346
|
exports.COMPARE_RULES = COMPARE_RULES;
|
|
@@ -1481,6 +3357,11 @@ exports.TIMEZONES = TIMEZONES;
|
|
|
1481
3357
|
exports.TOOLTIP_SHOW_RULES = TOOLTIP_SHOW_RULES;
|
|
1482
3358
|
exports.YAXIS_POSITIONS = YAXIS_POSITIONS;
|
|
1483
3359
|
exports.createDataLoader = createDataLoader;
|
|
3360
|
+
exports.useAlerts = useAlerts;
|
|
3361
|
+
exports.useAnnotations = useAnnotations;
|
|
3362
|
+
exports.useCompare = useCompare;
|
|
3363
|
+
exports.useCrosshair = useCrosshair;
|
|
3364
|
+
exports.useDataExport = useDataExport;
|
|
1484
3365
|
exports.useDrawingTools = useDrawingTools;
|
|
1485
3366
|
exports.useFullscreen = useFullscreen;
|
|
1486
3367
|
exports.useIndicators = useIndicators;
|
|
@@ -1488,10 +3369,16 @@ exports.useKlinechartsUI = useKlinechartsUI;
|
|
|
1488
3369
|
exports.useKlinechartsUILoading = useKlinechartsUILoading;
|
|
1489
3370
|
exports.useKlinechartsUISettings = useKlinechartsUISettings;
|
|
1490
3371
|
exports.useKlinechartsUITheme = useKlinechartsUITheme;
|
|
3372
|
+
exports.useLayoutManager = useLayoutManager;
|
|
3373
|
+
exports.useMeasure = useMeasure;
|
|
1491
3374
|
exports.useOrderLines = useOrderLines;
|
|
1492
3375
|
exports.usePeriods = usePeriods;
|
|
3376
|
+
exports.useReplay = useReplay;
|
|
1493
3377
|
exports.useScreenshot = useScreenshot;
|
|
3378
|
+
exports.useScriptEditor = useScriptEditor;
|
|
1494
3379
|
exports.useSymbolSearch = useSymbolSearch;
|
|
1495
3380
|
exports.useTimezone = useTimezone;
|
|
3381
|
+
exports.useUndoRedo = useUndoRedo;
|
|
3382
|
+
exports.useWatchlist = useWatchlist;
|
|
1496
3383
|
//# sourceMappingURL=index.cjs.map
|
|
1497
3384
|
//# sourceMappingURL=index.cjs.map
|