react-klinecharts-ui 0.3.0 → 0.5.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 +317 -8
- package/dist/{chunk-U325AHHX.js → chunk-DTBNTO4M.js} +8 -19
- package/dist/chunk-DTBNTO4M.js.map +1 -0
- package/dist/{chunk-PIFLJKYF.cjs → chunk-UJNJH3BS.cjs} +8 -19
- package/dist/chunk-UJNJH3BS.cjs.map +1 -0
- package/dist/extensions.cjs +6 -6
- package/dist/extensions.js +1 -1
- package/dist/index.cjs +476 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +198 -33
- package/dist/index.d.ts +198 -33
- package/dist/index.js +432 -229
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/dist/chunk-PIFLJKYF.cjs.map +0 -1
- package/dist/chunk-U325AHHX.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { registerExtensions, TA_default } from './chunk-
|
|
2
|
-
export { TA_default as TA, abcd_default as abcd, anyWaves_default as anyWaves, arrow_default as arrow, bollTv_default as bollTv, brush_default as brush, cci_default as cci, circle_default as circle, depthOverlay_default as depthOverlay, eightWaves_default as eightWaves, elliottWave_default as elliottWave, fibRetracement_default as fibRetracement, fibonacciCircle_default as fibonacciCircle, fibonacciExtension_default as fibonacciExtension, fibonacciSegment_default as fibonacciSegment, fibonacciSpeedResistanceFan_default as fibonacciSpeedResistanceFan, fibonacciSpiral_default as fibonacciSpiral, fiveWaves_default as fiveWaves, gannBox_default as gannBox, gannFan_default as gannFan, hma_default as hma, ichimoku_default as ichimoku, indicators, longPosition_default as longPosition, maRibbon_default as maRibbon, macdTv_default as macdTv, measure_default as measure, orderLine_default as orderLine, overlays, parallelChannel_default as parallelChannel, parallelogram_default as parallelogram, pivotPoints_default as pivotPoints, ray_default as ray, rect_default as rect, registerExtensions, rsiTv_default as rsiTv, shortPosition_default as shortPosition, stochastic_default as stochastic, superTrend_default as superTrend, threeWaves_default as threeWaves, triangle_default as triangle, vwap_default as vwap, xabcd_default as xabcd } from './chunk-
|
|
1
|
+
import { registerExtensions, TA_default } from './chunk-DTBNTO4M.js';
|
|
2
|
+
export { TA_default as TA, abcd_default as abcd, anyWaves_default as anyWaves, arrow_default as arrow, bollTv_default as bollTv, brush_default as brush, cci_default as cci, circle_default as circle, depthOverlay_default as depthOverlay, eightWaves_default as eightWaves, elliottWave_default as elliottWave, fibRetracement_default as fibRetracement, fibonacciCircle_default as fibonacciCircle, fibonacciExtension_default as fibonacciExtension, fibonacciSegment_default as fibonacciSegment, fibonacciSpeedResistanceFan_default as fibonacciSpeedResistanceFan, fibonacciSpiral_default as fibonacciSpiral, fiveWaves_default as fiveWaves, gannBox_default as gannBox, gannFan_default as gannFan, hma_default as hma, ichimoku_default as ichimoku, indicators, longPosition_default as longPosition, maRibbon_default as maRibbon, macdTv_default as macdTv, measure_default as measure, orderLine_default as orderLine, overlays, parallelChannel_default as parallelChannel, parallelogram_default as parallelogram, pivotPoints_default as pivotPoints, ray_default as ray, rect_default as rect, registerExtensions, rsiTv_default as rsiTv, shortPosition_default as shortPosition, stochastic_default as stochastic, superTrend_default as superTrend, threeWaves_default as threeWaves, triangle_default as triangle, vwap_default as vwap, xabcd_default as xabcd } from './chunk-DTBNTO4M.js';
|
|
3
3
|
import { createContext, useContext, useReducer, useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import { registerOverlay, registerIndicator } from 'react-klinecharts';
|
|
5
5
|
import { jsx } from 'react/jsx-runtime';
|
|
@@ -57,6 +57,16 @@ function reducer(state, action) {
|
|
|
57
57
|
return { ...state, mainIndicators: action.indicators };
|
|
58
58
|
case "SET_SUB_INDICATORS":
|
|
59
59
|
return { ...state, subIndicators: action.indicators };
|
|
60
|
+
case "SET_INDICATOR_AXES":
|
|
61
|
+
return { ...state, indicatorAxes: action.axes };
|
|
62
|
+
case "SET_INDICATOR_VISIBILITY":
|
|
63
|
+
return { ...state, indicatorVisibility: action.visibility };
|
|
64
|
+
case "SET_ALERTS":
|
|
65
|
+
return { ...state, alerts: action.alerts };
|
|
66
|
+
case "SET_MEASURE":
|
|
67
|
+
return { ...state, measure: { ...state.measure, ...action.measure } };
|
|
68
|
+
case "SET_REPLAY":
|
|
69
|
+
return { ...state, replay: { ...state.replay, ...action.replay } };
|
|
60
70
|
case "SET_STYLES":
|
|
61
71
|
return { ...state, styles: action.styles };
|
|
62
72
|
case "SET_LOCALE":
|
|
@@ -119,12 +129,28 @@ function KlinechartsUIProvider({
|
|
|
119
129
|
(acc, name) => ({ ...acc, [name]: "" }),
|
|
120
130
|
{}
|
|
121
131
|
),
|
|
132
|
+
indicatorAxes: {},
|
|
133
|
+
indicatorVisibility: {},
|
|
134
|
+
alerts: [],
|
|
135
|
+
measure: { isActive: false, fromPoint: null, result: null },
|
|
136
|
+
replay: {
|
|
137
|
+
isReplaying: false,
|
|
138
|
+
isPaused: false,
|
|
139
|
+
speed: 1,
|
|
140
|
+
barIndex: 0,
|
|
141
|
+
totalBars: 0
|
|
142
|
+
},
|
|
122
143
|
styles: opts.styles,
|
|
123
144
|
screenshotUrl: null
|
|
124
145
|
})
|
|
125
146
|
);
|
|
126
147
|
const fullscreenContainerRef = useRef(null);
|
|
127
148
|
const undoRedoListenerRef = useRef(null);
|
|
149
|
+
const alertTriggeredListenerRef = useRef(null);
|
|
150
|
+
const replayIntervalRef = useRef(null);
|
|
151
|
+
const replaySavedDataRef = useRef([]);
|
|
152
|
+
const replayIndexRef = useRef(0);
|
|
153
|
+
const alertPrevCloseRef = useRef(null);
|
|
128
154
|
const stateRef = useRef(state);
|
|
129
155
|
stateRef.current = state;
|
|
130
156
|
const callbacksRef = useRef({
|
|
@@ -185,13 +211,58 @@ function KlinechartsUIProvider({
|
|
|
185
211
|
state.chart.setStyles(state.styles);
|
|
186
212
|
}
|
|
187
213
|
}, [state.chart, state.styles]);
|
|
214
|
+
const hasAlerts = state.alerts.length > 0;
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
const chart = state.chart;
|
|
217
|
+
if (!chart || !hasAlerts) return;
|
|
218
|
+
alertPrevCloseRef.current = null;
|
|
219
|
+
const interval = setInterval(() => {
|
|
220
|
+
const dataList = chart.getDataList();
|
|
221
|
+
if (!dataList || dataList.length === 0) return;
|
|
222
|
+
const currentClose = dataList[dataList.length - 1].close;
|
|
223
|
+
const prevClose = alertPrevCloseRef.current;
|
|
224
|
+
alertPrevCloseRef.current = currentClose;
|
|
225
|
+
if (prevClose === null) return;
|
|
226
|
+
const alerts = stateRef.current.alerts;
|
|
227
|
+
let changed = false;
|
|
228
|
+
const next = alerts.map((alert) => {
|
|
229
|
+
if (alert.triggered) return alert;
|
|
230
|
+
const crossedUp = prevClose < alert.price && currentClose >= alert.price;
|
|
231
|
+
const crossedDown = prevClose > alert.price && currentClose <= alert.price;
|
|
232
|
+
const shouldTrigger = alert.condition === "crossing_up" ? crossedUp : alert.condition === "crossing_down" ? crossedDown : crossedUp || crossedDown;
|
|
233
|
+
if (shouldTrigger) {
|
|
234
|
+
changed = true;
|
|
235
|
+
const triggered = { ...alert, triggered: true };
|
|
236
|
+
alertTriggeredListenerRef.current?.(triggered);
|
|
237
|
+
return triggered;
|
|
238
|
+
}
|
|
239
|
+
return alert;
|
|
240
|
+
});
|
|
241
|
+
if (changed) {
|
|
242
|
+
enhancedDispatch({ type: "SET_ALERTS", alerts: next });
|
|
243
|
+
}
|
|
244
|
+
}, 1e3);
|
|
245
|
+
return () => clearInterval(interval);
|
|
246
|
+
}, [state.chart, hasAlerts, enhancedDispatch]);
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
return () => {
|
|
249
|
+
if (replayIntervalRef.current !== null) {
|
|
250
|
+
clearInterval(replayIntervalRef.current);
|
|
251
|
+
replayIntervalRef.current = null;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}, []);
|
|
188
255
|
const dispatchValue = useMemo(
|
|
189
256
|
() => ({
|
|
190
257
|
dispatch: enhancedDispatch,
|
|
191
258
|
datafeed,
|
|
192
259
|
onSettingsChange,
|
|
193
260
|
fullscreenContainerRef,
|
|
194
|
-
undoRedoListenerRef
|
|
261
|
+
undoRedoListenerRef,
|
|
262
|
+
alertTriggeredListenerRef,
|
|
263
|
+
replayIntervalRef,
|
|
264
|
+
replaySavedDataRef,
|
|
265
|
+
replayIndexRef
|
|
195
266
|
}),
|
|
196
267
|
[enhancedDispatch, datafeed, onSettingsChange]
|
|
197
268
|
);
|
|
@@ -653,75 +724,140 @@ function useIndicators() {
|
|
|
653
724
|
const activeNames = state.mainIndicators;
|
|
654
725
|
const inactive = MAIN_INDICATORS.filter((n) => !activeNames.includes(n));
|
|
655
726
|
return [
|
|
656
|
-
...activeNames.map((name) => ({
|
|
657
|
-
|
|
727
|
+
...activeNames.map((name) => ({
|
|
728
|
+
name,
|
|
729
|
+
isActive: true,
|
|
730
|
+
visible: state.indicatorVisibility[`main_${name}`] ?? true
|
|
731
|
+
})),
|
|
732
|
+
...inactive.map((name) => ({ name, isActive: false, visible: true }))
|
|
658
733
|
];
|
|
659
|
-
}, [state.mainIndicators]);
|
|
734
|
+
}, [state.mainIndicators, state.indicatorVisibility]);
|
|
660
735
|
const subIndicators = useMemo(() => {
|
|
661
736
|
const activeNames = Object.keys(state.subIndicators);
|
|
662
737
|
const inactive = SUB_INDICATORS.filter((n) => !activeNames.includes(n));
|
|
663
738
|
return [
|
|
664
|
-
...activeNames.map((name) => ({
|
|
665
|
-
|
|
739
|
+
...activeNames.map((name) => ({
|
|
740
|
+
name,
|
|
741
|
+
isActive: true,
|
|
742
|
+
visible: state.indicatorVisibility[`sub_${name}`] ?? true
|
|
743
|
+
})),
|
|
744
|
+
...inactive.map((name) => ({ name, isActive: false, visible: true }))
|
|
666
745
|
];
|
|
667
|
-
}, [state.subIndicators]);
|
|
746
|
+
}, [state.subIndicators, state.indicatorVisibility]);
|
|
668
747
|
const addMainIndicator = useCallback(
|
|
669
|
-
(name) => {
|
|
748
|
+
(name, options) => {
|
|
670
749
|
if (state.mainIndicators.includes(name)) return;
|
|
750
|
+
const yAxis = options?.yAxis;
|
|
671
751
|
state.chart?.createIndicator(
|
|
672
|
-
{ name, id: `main_${name}
|
|
673
|
-
true,
|
|
674
|
-
{ id: "candle_pane" }
|
|
752
|
+
{ name, id: `main_${name}` },
|
|
753
|
+
{ isStack: true, pane: { id: "candle_pane" }, yAxis }
|
|
675
754
|
);
|
|
676
755
|
const newIndicators = [...state.mainIndicators, name];
|
|
677
756
|
dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
|
|
757
|
+
if (yAxis?.id) {
|
|
758
|
+
dispatch({
|
|
759
|
+
type: "SET_INDICATOR_AXES",
|
|
760
|
+
axes: { ...state.indicatorAxes, [`main_${name}`]: yAxis.id }
|
|
761
|
+
});
|
|
762
|
+
}
|
|
678
763
|
undoRedoListenerRef.current?.({
|
|
679
764
|
type: "indicator_toggled",
|
|
680
|
-
data: {
|
|
765
|
+
data: {
|
|
766
|
+
name,
|
|
767
|
+
wasActive: false,
|
|
768
|
+
isMain: true,
|
|
769
|
+
paneId: "candle_pane",
|
|
770
|
+
yAxisId: yAxis?.id
|
|
771
|
+
}
|
|
681
772
|
});
|
|
682
773
|
},
|
|
683
|
-
[state.chart, state.mainIndicators, dispatch, undoRedoListenerRef]
|
|
774
|
+
[state.chart, state.mainIndicators, state.indicatorAxes, dispatch, undoRedoListenerRef]
|
|
684
775
|
);
|
|
685
776
|
const removeMainIndicator = useCallback(
|
|
686
777
|
(name) => {
|
|
687
|
-
|
|
778
|
+
const id = `main_${name}`;
|
|
779
|
+
const yAxisId = state.indicatorAxes[id];
|
|
780
|
+
state.chart?.removeIndicator({ id });
|
|
688
781
|
const newIndicators = state.mainIndicators.filter((n) => n !== name);
|
|
689
782
|
dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
|
|
783
|
+
if (yAxisId) {
|
|
784
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
785
|
+
delete nextAxes[id];
|
|
786
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
787
|
+
}
|
|
788
|
+
if (id in state.indicatorVisibility) {
|
|
789
|
+
const nextVisibility = { ...state.indicatorVisibility };
|
|
790
|
+
delete nextVisibility[id];
|
|
791
|
+
dispatch({ type: "SET_INDICATOR_VISIBILITY", visibility: nextVisibility });
|
|
792
|
+
}
|
|
690
793
|
undoRedoListenerRef.current?.({
|
|
691
794
|
type: "indicator_toggled",
|
|
692
|
-
data: { name, wasActive: true, isMain: true, paneId: "candle_pane" }
|
|
795
|
+
data: { name, wasActive: true, isMain: true, paneId: "candle_pane", yAxisId }
|
|
693
796
|
});
|
|
694
797
|
},
|
|
695
|
-
[
|
|
798
|
+
[
|
|
799
|
+
state.chart,
|
|
800
|
+
state.mainIndicators,
|
|
801
|
+
state.indicatorAxes,
|
|
802
|
+
state.indicatorVisibility,
|
|
803
|
+
dispatch,
|
|
804
|
+
undoRedoListenerRef
|
|
805
|
+
]
|
|
696
806
|
);
|
|
697
807
|
const addSubIndicator = useCallback(
|
|
698
|
-
(name) => {
|
|
808
|
+
(name, options) => {
|
|
699
809
|
if (name in state.subIndicators) return;
|
|
700
810
|
const id = `sub_${name}`;
|
|
701
|
-
|
|
811
|
+
const yAxis = options?.yAxis;
|
|
812
|
+
state.chart?.createIndicator({ name, id }, { yAxis });
|
|
702
813
|
const paneId = state.chart?.getIndicators({ id })?.[0]?.paneId ?? "";
|
|
703
814
|
const newIndicators = { ...state.subIndicators, [name]: paneId };
|
|
704
815
|
dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
|
|
816
|
+
if (yAxis?.id) {
|
|
817
|
+
dispatch({
|
|
818
|
+
type: "SET_INDICATOR_AXES",
|
|
819
|
+
axes: { ...state.indicatorAxes, [id]: yAxis.id }
|
|
820
|
+
});
|
|
821
|
+
}
|
|
705
822
|
undoRedoListenerRef.current?.({
|
|
706
823
|
type: "indicator_toggled",
|
|
707
|
-
data: { name, wasActive: false, isMain: false, paneId }
|
|
824
|
+
data: { name, wasActive: false, isMain: false, paneId, yAxisId: yAxis?.id }
|
|
708
825
|
});
|
|
709
826
|
},
|
|
710
|
-
[state.chart, state.subIndicators, dispatch, undoRedoListenerRef]
|
|
827
|
+
[state.chart, state.subIndicators, state.indicatorAxes, dispatch, undoRedoListenerRef]
|
|
711
828
|
);
|
|
712
829
|
const removeSubIndicator = useCallback(
|
|
713
830
|
(name) => {
|
|
831
|
+
const id = `sub_${name}`;
|
|
714
832
|
const paneId = state.subIndicators[name] ?? "";
|
|
715
|
-
state.
|
|
833
|
+
const yAxisId = state.indicatorAxes[id];
|
|
834
|
+
state.chart?.removeIndicator({ id });
|
|
716
835
|
const newIndicators = { ...state.subIndicators };
|
|
717
836
|
delete newIndicators[name];
|
|
718
837
|
dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
|
|
838
|
+
if (yAxisId) {
|
|
839
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
840
|
+
delete nextAxes[id];
|
|
841
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
842
|
+
}
|
|
843
|
+
if (id in state.indicatorVisibility) {
|
|
844
|
+
const nextVisibility = { ...state.indicatorVisibility };
|
|
845
|
+
delete nextVisibility[id];
|
|
846
|
+
dispatch({ type: "SET_INDICATOR_VISIBILITY", visibility: nextVisibility });
|
|
847
|
+
}
|
|
719
848
|
undoRedoListenerRef.current?.({
|
|
720
849
|
type: "indicator_toggled",
|
|
721
|
-
data: { name, wasActive: true, isMain: false, paneId }
|
|
850
|
+
data: { name, wasActive: true, isMain: false, paneId, yAxisId }
|
|
722
851
|
});
|
|
723
852
|
},
|
|
724
|
-
[
|
|
853
|
+
[
|
|
854
|
+
state.chart,
|
|
855
|
+
state.subIndicators,
|
|
856
|
+
state.indicatorAxes,
|
|
857
|
+
state.indicatorVisibility,
|
|
858
|
+
dispatch,
|
|
859
|
+
undoRedoListenerRef
|
|
860
|
+
]
|
|
725
861
|
);
|
|
726
862
|
const toggleMainIndicator = useCallback(
|
|
727
863
|
(name) => {
|
|
@@ -747,12 +883,22 @@ function useIndicators() {
|
|
|
747
883
|
(name, isMain, visible) => {
|
|
748
884
|
const id = isMain ? `main_${name}` : `sub_${name}`;
|
|
749
885
|
state.chart?.overrideIndicator({ name, id, visible });
|
|
886
|
+
const nextVisibility = { ...state.indicatorVisibility };
|
|
887
|
+
if (visible) delete nextVisibility[id];
|
|
888
|
+
else nextVisibility[id] = false;
|
|
889
|
+
dispatch({ type: "SET_INDICATOR_VISIBILITY", visibility: nextVisibility });
|
|
750
890
|
},
|
|
751
|
-
[state.chart]
|
|
891
|
+
[state.chart, state.indicatorVisibility, dispatch]
|
|
892
|
+
);
|
|
893
|
+
const isIndicatorVisible = useCallback(
|
|
894
|
+
(name, isMain) => state.indicatorVisibility[isMain ? `main_${name}` : `sub_${name}`] ?? true,
|
|
895
|
+
[state.indicatorVisibility]
|
|
752
896
|
);
|
|
753
897
|
const updateIndicatorParams = useCallback(
|
|
754
|
-
|
|
755
|
-
|
|
898
|
+
// `paneId` is kept in the signature for API stability; klinecharts v10
|
|
899
|
+
// `overrideIndicator` no longer accepts it and targets by name/id instead.
|
|
900
|
+
(name, _paneId, params) => {
|
|
901
|
+
state.chart?.overrideIndicator({ name, calcParams: params });
|
|
756
902
|
},
|
|
757
903
|
[state.chart]
|
|
758
904
|
);
|
|
@@ -773,11 +919,9 @@ function useIndicators() {
|
|
|
773
919
|
{
|
|
774
920
|
name,
|
|
775
921
|
id: `main_${name}`,
|
|
776
|
-
paneId: "candle_pane",
|
|
777
922
|
...prevCalcParams ? { calcParams: prevCalcParams } : {}
|
|
778
923
|
},
|
|
779
|
-
true,
|
|
780
|
-
{ id: "candle_pane" }
|
|
924
|
+
{ isStack: true, pane: { id: "candle_pane" } }
|
|
781
925
|
);
|
|
782
926
|
const newSub = { ...state.subIndicators };
|
|
783
927
|
delete newSub[name];
|
|
@@ -831,9 +975,14 @@ function useIndicators() {
|
|
|
831
975
|
id: paneId,
|
|
832
976
|
height: COLLAPSED_HEIGHT
|
|
833
977
|
});
|
|
834
|
-
|
|
978
|
+
const id = `sub_${name}`;
|
|
979
|
+
state.chart.overrideIndicator({ name, id, visible: false });
|
|
980
|
+
dispatch({
|
|
981
|
+
type: "SET_INDICATOR_VISIBILITY",
|
|
982
|
+
visibility: { ...state.indicatorVisibility, [id]: false }
|
|
983
|
+
});
|
|
835
984
|
},
|
|
836
|
-
[state.chart, state.subIndicators]
|
|
985
|
+
[state.chart, state.subIndicators, state.indicatorVisibility, dispatch]
|
|
837
986
|
);
|
|
838
987
|
const expandSubIndicator = useCallback(
|
|
839
988
|
(name) => {
|
|
@@ -845,9 +994,13 @@ function useIndicators() {
|
|
|
845
994
|
id: paneId,
|
|
846
995
|
height: savedHeight
|
|
847
996
|
});
|
|
848
|
-
|
|
997
|
+
const id = `sub_${name}`;
|
|
998
|
+
state.chart.overrideIndicator({ name, id, visible: true });
|
|
999
|
+
const nextVisibility = { ...state.indicatorVisibility };
|
|
1000
|
+
delete nextVisibility[id];
|
|
1001
|
+
dispatch({ type: "SET_INDICATOR_VISIBILITY", visibility: nextVisibility });
|
|
849
1002
|
},
|
|
850
|
-
[state.chart, state.subIndicators]
|
|
1003
|
+
[state.chart, state.subIndicators, state.indicatorVisibility, dispatch]
|
|
851
1004
|
);
|
|
852
1005
|
const isSubIndicatorCollapsed = useCallback(
|
|
853
1006
|
(name) => {
|
|
@@ -893,7 +1046,7 @@ function useIndicators() {
|
|
|
893
1046
|
if (sub.styles) {
|
|
894
1047
|
state.chart.overrideIndicator({
|
|
895
1048
|
name: sub.name,
|
|
896
|
-
|
|
1049
|
+
id,
|
|
897
1050
|
styles: sub.styles
|
|
898
1051
|
});
|
|
899
1052
|
}
|
|
@@ -902,6 +1055,44 @@ function useIndicators() {
|
|
|
902
1055
|
},
|
|
903
1056
|
[state.chart, state.subIndicators, dispatch]
|
|
904
1057
|
);
|
|
1058
|
+
const getIndicatorAxis = useCallback(
|
|
1059
|
+
(name, isMain) => state.indicatorAxes[isMain ? `main_${name}` : `sub_${name}`],
|
|
1060
|
+
[state.indicatorAxes]
|
|
1061
|
+
);
|
|
1062
|
+
const bindIndicatorToNewAxis = useCallback(
|
|
1063
|
+
(name, isMain, yAxis) => {
|
|
1064
|
+
if (!state.chart) return;
|
|
1065
|
+
const id = isMain ? `main_${name}` : `sub_${name}`;
|
|
1066
|
+
const current = state.chart.getIndicators({ id })?.[0];
|
|
1067
|
+
if (!current) return;
|
|
1068
|
+
const calcParams = current.calcParams;
|
|
1069
|
+
const styles = current.styles;
|
|
1070
|
+
const visible = current.visible ?? true;
|
|
1071
|
+
const paneId = isMain ? "candle_pane" : current.paneId ?? state.subIndicators[name] ?? "";
|
|
1072
|
+
state.chart.removeIndicator({ id });
|
|
1073
|
+
state.chart.createIndicator(
|
|
1074
|
+
{
|
|
1075
|
+
name,
|
|
1076
|
+
id,
|
|
1077
|
+
...calcParams ? { calcParams } : {},
|
|
1078
|
+
visible
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
isStack: isMain,
|
|
1082
|
+
...paneId ? { pane: { id: paneId } } : {},
|
|
1083
|
+
yAxis
|
|
1084
|
+
}
|
|
1085
|
+
);
|
|
1086
|
+
if (styles) {
|
|
1087
|
+
state.chart.overrideIndicator({ name, id, styles });
|
|
1088
|
+
}
|
|
1089
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
1090
|
+
if (yAxis?.id) nextAxes[id] = yAxis.id;
|
|
1091
|
+
else delete nextAxes[id];
|
|
1092
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
1093
|
+
},
|
|
1094
|
+
[state.chart, state.indicatorAxes, state.subIndicators, dispatch]
|
|
1095
|
+
);
|
|
905
1096
|
return {
|
|
906
1097
|
mainIndicators,
|
|
907
1098
|
subIndicators,
|
|
@@ -918,6 +1109,7 @@ function useIndicators() {
|
|
|
918
1109
|
moveToMain,
|
|
919
1110
|
moveToSub,
|
|
920
1111
|
setIndicatorVisible,
|
|
1112
|
+
isIndicatorVisible,
|
|
921
1113
|
updateIndicatorParams,
|
|
922
1114
|
getIndicatorParams,
|
|
923
1115
|
isMainIndicatorActive,
|
|
@@ -925,7 +1117,11 @@ function useIndicators() {
|
|
|
925
1117
|
collapseSubIndicator,
|
|
926
1118
|
expandSubIndicator,
|
|
927
1119
|
isSubIndicatorCollapsed,
|
|
928
|
-
reorderSubIndicator
|
|
1120
|
+
reorderSubIndicator,
|
|
1121
|
+
indicatorAxes: state.indicatorAxes,
|
|
1122
|
+
getIndicatorAxis,
|
|
1123
|
+
indicatorVisibility: state.indicatorVisibility,
|
|
1124
|
+
bindIndicatorToNewAxis
|
|
929
1125
|
};
|
|
930
1126
|
}
|
|
931
1127
|
|
|
@@ -1235,20 +1431,23 @@ function useKlinechartsUISettings() {
|
|
|
1235
1431
|
() => TOOLTIP_SHOW_RULES.map((r) => ({ key: r, localeKey: r })),
|
|
1236
1432
|
[]
|
|
1237
1433
|
);
|
|
1434
|
+
const applyPaneAxis = useCallback(
|
|
1435
|
+
(axis) => {
|
|
1436
|
+
state.chart?.overrideYAxis?.({ paneId: "candle_pane", ...axis });
|
|
1437
|
+
},
|
|
1438
|
+
[state.chart]
|
|
1439
|
+
);
|
|
1238
1440
|
const hasAppliedInitial = useRef(false);
|
|
1239
1441
|
useEffect(() => {
|
|
1240
1442
|
if (!state.chart || hasAppliedInitial.current) return;
|
|
1241
1443
|
hasAppliedInitial.current = true;
|
|
1242
|
-
const
|
|
1243
|
-
if (
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
...settings.yAxisPosition !== "right" && { position: settings.yAxisPosition },
|
|
1250
|
-
...settings.yAxisInside && { inside: true }
|
|
1251
|
-
}
|
|
1444
|
+
const needsAxisOverride = settings.reverseCoordinate || settings.priceAxisType !== "normal" || settings.yAxisPosition !== "right" || settings.yAxisInside;
|
|
1445
|
+
if (needsAxisOverride) {
|
|
1446
|
+
applyPaneAxis({
|
|
1447
|
+
...settings.priceAxisType !== "normal" && { name: settings.priceAxisType },
|
|
1448
|
+
...settings.reverseCoordinate && { reverse: true },
|
|
1449
|
+
...settings.yAxisPosition !== "right" && { position: settings.yAxisPosition },
|
|
1450
|
+
...settings.yAxisInside && { inside: true }
|
|
1252
1451
|
});
|
|
1253
1452
|
}
|
|
1254
1453
|
if (settings.showIndicatorLastValue) {
|
|
@@ -1271,12 +1470,6 @@ function useKlinechartsUISettings() {
|
|
|
1271
1470
|
},
|
|
1272
1471
|
[state.chart]
|
|
1273
1472
|
);
|
|
1274
|
-
const applyPaneAxis = useCallback(
|
|
1275
|
-
(axis) => {
|
|
1276
|
-
state.chart?.setPaneOptions({ id: "candle_pane", axis });
|
|
1277
|
-
},
|
|
1278
|
-
[state.chart]
|
|
1279
|
-
);
|
|
1280
1473
|
const setCandleType = useCallback(
|
|
1281
1474
|
(type) => {
|
|
1282
1475
|
applyStyle("candle.type", type);
|
|
@@ -1424,11 +1617,8 @@ function useKlinechartsUISettings() {
|
|
|
1424
1617
|
const resetToDefaults = useCallback(() => {
|
|
1425
1618
|
setSettings(defaultSettings);
|
|
1426
1619
|
state.chart?.setStyles(state.theme);
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
axis: { name: "normal", reverse: false, position: "right", inside: false }
|
|
1430
|
-
});
|
|
1431
|
-
}, [state.chart, state.theme]);
|
|
1620
|
+
applyPaneAxis({ name: "normal", reverse: false, position: "right", inside: false });
|
|
1621
|
+
}, [state.chart, state.theme, applyPaneAxis]);
|
|
1432
1622
|
return {
|
|
1433
1623
|
...settings,
|
|
1434
1624
|
candleTypes,
|
|
@@ -1685,21 +1875,28 @@ function useUndoRedo() {
|
|
|
1685
1875
|
break;
|
|
1686
1876
|
}
|
|
1687
1877
|
case "indicator_toggled": {
|
|
1688
|
-
const { name, wasActive, isMain, paneId } = action.data;
|
|
1878
|
+
const { name, wasActive, isMain, paneId, yAxisId } = action.data;
|
|
1879
|
+
const id = isMain ? `main_${name}` : `sub_${name}`;
|
|
1689
1880
|
if (wasActive) {
|
|
1690
1881
|
if (isMain) {
|
|
1691
1882
|
state.chart.createIndicator(
|
|
1692
|
-
{ name, id
|
|
1693
|
-
|
|
1694
|
-
|
|
1883
|
+
{ name, id },
|
|
1884
|
+
{
|
|
1885
|
+
isStack: true,
|
|
1886
|
+
pane: { id: "candle_pane" },
|
|
1887
|
+
...yAxisId ? { yAxis: { id: yAxisId } } : {}
|
|
1888
|
+
}
|
|
1695
1889
|
);
|
|
1696
1890
|
dispatch({
|
|
1697
1891
|
type: "SET_MAIN_INDICATORS",
|
|
1698
1892
|
indicators: [...state.mainIndicators, name]
|
|
1699
1893
|
});
|
|
1700
1894
|
} else {
|
|
1701
|
-
state.chart.createIndicator(
|
|
1702
|
-
|
|
1895
|
+
state.chart.createIndicator(
|
|
1896
|
+
{ name, id },
|
|
1897
|
+
yAxisId ? { yAxis: { id: yAxisId } } : void 0
|
|
1898
|
+
);
|
|
1899
|
+
const newPaneId = state.chart.getIndicators({ id })?.[0]?.paneId ?? "";
|
|
1703
1900
|
dispatch({
|
|
1704
1901
|
type: "SET_SUB_INDICATORS",
|
|
1705
1902
|
indicators: {
|
|
@@ -1708,9 +1905,15 @@ function useUndoRedo() {
|
|
|
1708
1905
|
}
|
|
1709
1906
|
});
|
|
1710
1907
|
}
|
|
1908
|
+
if (yAxisId) {
|
|
1909
|
+
dispatch({
|
|
1910
|
+
type: "SET_INDICATOR_AXES",
|
|
1911
|
+
axes: { ...state.indicatorAxes, [id]: yAxisId }
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1711
1914
|
} else {
|
|
1915
|
+
state.chart.removeIndicator({ id });
|
|
1712
1916
|
if (isMain) {
|
|
1713
|
-
state.chart.removeIndicator({ id: `main_${name}` });
|
|
1714
1917
|
dispatch({
|
|
1715
1918
|
type: "SET_MAIN_INDICATORS",
|
|
1716
1919
|
indicators: state.mainIndicators.filter(
|
|
@@ -1718,7 +1921,6 @@ function useUndoRedo() {
|
|
|
1718
1921
|
)
|
|
1719
1922
|
});
|
|
1720
1923
|
} else {
|
|
1721
|
-
state.chart.removeIndicator({ id: `sub_${name}` });
|
|
1722
1924
|
const newSub = { ...state.subIndicators };
|
|
1723
1925
|
delete newSub[name];
|
|
1724
1926
|
dispatch({
|
|
@@ -1726,6 +1928,11 @@ function useUndoRedo() {
|
|
|
1726
1928
|
indicators: newSub
|
|
1727
1929
|
});
|
|
1728
1930
|
}
|
|
1931
|
+
if (state.indicatorAxes[id]) {
|
|
1932
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
1933
|
+
delete nextAxes[id];
|
|
1934
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
1935
|
+
}
|
|
1729
1936
|
}
|
|
1730
1937
|
setRedoStack((prev) => [
|
|
1731
1938
|
...prev,
|
|
@@ -1735,7 +1942,8 @@ function useUndoRedo() {
|
|
|
1735
1942
|
name,
|
|
1736
1943
|
wasActive: !wasActive,
|
|
1737
1944
|
isMain,
|
|
1738
|
-
paneId
|
|
1945
|
+
paneId,
|
|
1946
|
+
yAxisId
|
|
1739
1947
|
}
|
|
1740
1948
|
}
|
|
1741
1949
|
]);
|
|
@@ -1745,7 +1953,7 @@ function useUndoRedo() {
|
|
|
1745
1953
|
} finally {
|
|
1746
1954
|
isProcessingRef.current = false;
|
|
1747
1955
|
}
|
|
1748
|
-
}, [undoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
|
|
1956
|
+
}, [undoStack, state.chart, state.mainIndicators, state.subIndicators, state.indicatorAxes, dispatch]);
|
|
1749
1957
|
const redo = useCallback(() => {
|
|
1750
1958
|
if (redoStack.length === 0 || !state.chart || isProcessingRef.current)
|
|
1751
1959
|
return;
|
|
@@ -1797,21 +2005,28 @@ function useUndoRedo() {
|
|
|
1797
2005
|
break;
|
|
1798
2006
|
}
|
|
1799
2007
|
case "indicator_toggled": {
|
|
1800
|
-
const { name, wasActive, isMain } = action.data;
|
|
2008
|
+
const { name, wasActive, isMain, paneId, yAxisId } = action.data;
|
|
2009
|
+
const id = isMain ? `main_${name}` : `sub_${name}`;
|
|
1801
2010
|
if (wasActive) {
|
|
1802
2011
|
if (isMain) {
|
|
1803
2012
|
state.chart.createIndicator(
|
|
1804
|
-
{ name, id
|
|
1805
|
-
|
|
1806
|
-
|
|
2013
|
+
{ name, id },
|
|
2014
|
+
{
|
|
2015
|
+
isStack: true,
|
|
2016
|
+
pane: { id: "candle_pane" },
|
|
2017
|
+
...yAxisId ? { yAxis: { id: yAxisId } } : {}
|
|
2018
|
+
}
|
|
1807
2019
|
);
|
|
1808
2020
|
dispatch({
|
|
1809
2021
|
type: "SET_MAIN_INDICATORS",
|
|
1810
2022
|
indicators: [...state.mainIndicators, name]
|
|
1811
2023
|
});
|
|
1812
2024
|
} else {
|
|
1813
|
-
state.chart.createIndicator(
|
|
1814
|
-
|
|
2025
|
+
state.chart.createIndicator(
|
|
2026
|
+
{ name, id },
|
|
2027
|
+
yAxisId ? { yAxis: { id: yAxisId } } : void 0
|
|
2028
|
+
);
|
|
2029
|
+
const newPaneId = state.chart.getIndicators({ id })?.[0]?.paneId ?? "";
|
|
1815
2030
|
dispatch({
|
|
1816
2031
|
type: "SET_SUB_INDICATORS",
|
|
1817
2032
|
indicators: {
|
|
@@ -1820,9 +2035,15 @@ function useUndoRedo() {
|
|
|
1820
2035
|
}
|
|
1821
2036
|
});
|
|
1822
2037
|
}
|
|
2038
|
+
if (yAxisId) {
|
|
2039
|
+
dispatch({
|
|
2040
|
+
type: "SET_INDICATOR_AXES",
|
|
2041
|
+
axes: { ...state.indicatorAxes, [id]: yAxisId }
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
1823
2044
|
} else {
|
|
2045
|
+
state.chart.removeIndicator({ id });
|
|
1824
2046
|
if (isMain) {
|
|
1825
|
-
state.chart.removeIndicator({ id: `main_${name}` });
|
|
1826
2047
|
dispatch({
|
|
1827
2048
|
type: "SET_MAIN_INDICATORS",
|
|
1828
2049
|
indicators: state.mainIndicators.filter(
|
|
@@ -1830,7 +2051,6 @@ function useUndoRedo() {
|
|
|
1830
2051
|
)
|
|
1831
2052
|
});
|
|
1832
2053
|
} else {
|
|
1833
|
-
state.chart.removeIndicator({ id: `sub_${name}` });
|
|
1834
2054
|
const newSub = { ...state.subIndicators };
|
|
1835
2055
|
delete newSub[name];
|
|
1836
2056
|
dispatch({
|
|
@@ -1838,6 +2058,11 @@ function useUndoRedo() {
|
|
|
1838
2058
|
indicators: newSub
|
|
1839
2059
|
});
|
|
1840
2060
|
}
|
|
2061
|
+
if (state.indicatorAxes[id]) {
|
|
2062
|
+
const nextAxes = { ...state.indicatorAxes };
|
|
2063
|
+
delete nextAxes[id];
|
|
2064
|
+
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
2065
|
+
}
|
|
1841
2066
|
}
|
|
1842
2067
|
setUndoStack((prev) => [
|
|
1843
2068
|
...prev,
|
|
@@ -1846,7 +2071,9 @@ function useUndoRedo() {
|
|
|
1846
2071
|
data: {
|
|
1847
2072
|
name,
|
|
1848
2073
|
wasActive: !wasActive,
|
|
1849
|
-
isMain
|
|
2074
|
+
isMain,
|
|
2075
|
+
paneId,
|
|
2076
|
+
yAxisId
|
|
1850
2077
|
}
|
|
1851
2078
|
}
|
|
1852
2079
|
]);
|
|
@@ -1856,7 +2083,7 @@ function useUndoRedo() {
|
|
|
1856
2083
|
} finally {
|
|
1857
2084
|
isProcessingRef.current = false;
|
|
1858
2085
|
}
|
|
1859
|
-
}, [redoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
|
|
2086
|
+
}, [redoStack, state.chart, state.mainIndicators, state.subIndicators, state.indicatorAxes, dispatch]);
|
|
1860
2087
|
useEffect(() => {
|
|
1861
2088
|
const handleKeyDown = (e) => {
|
|
1862
2089
|
const isCtrlOrMeta = e.ctrlKey || e.metaKey;
|
|
@@ -1930,12 +2157,14 @@ function useLayoutManager() {
|
|
|
1930
2157
|
if (indicatorMap) {
|
|
1931
2158
|
indicatorMap.forEach((paneIndicators, paneId) => {
|
|
1932
2159
|
paneIndicators.forEach((indicator) => {
|
|
2160
|
+
const yAxisId = state.indicatorAxes[indicator.id];
|
|
1933
2161
|
indicators2.push({
|
|
1934
2162
|
paneId,
|
|
1935
2163
|
name: indicator.name,
|
|
1936
2164
|
calcParams: indicator.calcParams,
|
|
1937
2165
|
visible: indicator.visible,
|
|
1938
|
-
...indicator.styles ? { styles: indicator.styles } : {}
|
|
2166
|
+
...indicator.styles ? { styles: indicator.styles } : {},
|
|
2167
|
+
...yAxisId ? { yAxisId } : {}
|
|
1939
2168
|
});
|
|
1940
2169
|
});
|
|
1941
2170
|
});
|
|
@@ -1963,7 +2192,7 @@ function useLayoutManager() {
|
|
|
1963
2192
|
indicators: indicators2,
|
|
1964
2193
|
drawings
|
|
1965
2194
|
};
|
|
1966
|
-
}, [state.chart, state.symbol, state.period]);
|
|
2195
|
+
}, [state.chart, state.symbol, state.period, state.indicatorAxes]);
|
|
1967
2196
|
const saveLayout = useCallback(
|
|
1968
2197
|
(name) => {
|
|
1969
2198
|
const chartState = serializeState();
|
|
@@ -2011,25 +2240,39 @@ function useLayoutManager() {
|
|
|
2011
2240
|
}
|
|
2012
2241
|
const newMainIndicators = [];
|
|
2013
2242
|
const newSubIndicators = {};
|
|
2243
|
+
const restoredAxes = {};
|
|
2244
|
+
const restoredVisibility = {};
|
|
2014
2245
|
if (chartState.indicators) {
|
|
2015
2246
|
for (const ind of chartState.indicators) {
|
|
2247
|
+
const isMain = ind.paneId === "candle_pane";
|
|
2248
|
+
const id2 = isMain ? `main_${ind.name}` : `sub_${ind.name}`;
|
|
2016
2249
|
chart.createIndicator(
|
|
2017
2250
|
{
|
|
2018
2251
|
name: ind.name,
|
|
2252
|
+
id: id2,
|
|
2019
2253
|
calcParams: ind.calcParams,
|
|
2020
2254
|
visible: ind.visible
|
|
2021
2255
|
},
|
|
2022
|
-
|
|
2023
|
-
|
|
2256
|
+
{
|
|
2257
|
+
isStack: ind.paneId !== "candle_pane",
|
|
2258
|
+
pane: { id: ind.paneId },
|
|
2259
|
+
...ind.yAxisId ? { yAxis: { id: ind.yAxisId } } : {}
|
|
2260
|
+
}
|
|
2024
2261
|
);
|
|
2025
2262
|
if (ind.styles) {
|
|
2026
2263
|
chart.overrideIndicator({
|
|
2027
2264
|
name: ind.name,
|
|
2028
|
-
|
|
2265
|
+
id: id2,
|
|
2029
2266
|
styles: ind.styles
|
|
2030
2267
|
});
|
|
2031
2268
|
}
|
|
2032
|
-
if (ind.
|
|
2269
|
+
if (ind.yAxisId) {
|
|
2270
|
+
restoredAxes[id2] = ind.yAxisId;
|
|
2271
|
+
}
|
|
2272
|
+
if (ind.visible === false) {
|
|
2273
|
+
restoredVisibility[id2] = false;
|
|
2274
|
+
}
|
|
2275
|
+
if (isMain) {
|
|
2033
2276
|
newMainIndicators.push(ind.name);
|
|
2034
2277
|
} else {
|
|
2035
2278
|
newSubIndicators[ind.name] = ind.paneId;
|
|
@@ -2044,6 +2287,14 @@ function useLayoutManager() {
|
|
|
2044
2287
|
type: "SET_SUB_INDICATORS",
|
|
2045
2288
|
indicators: newSubIndicators
|
|
2046
2289
|
});
|
|
2290
|
+
dispatch({
|
|
2291
|
+
type: "SET_INDICATOR_AXES",
|
|
2292
|
+
axes: restoredAxes
|
|
2293
|
+
});
|
|
2294
|
+
dispatch({
|
|
2295
|
+
type: "SET_INDICATOR_VISIBILITY",
|
|
2296
|
+
visibility: restoredVisibility
|
|
2297
|
+
});
|
|
2047
2298
|
if (chartState.drawings) {
|
|
2048
2299
|
for (const drawing of chartState.drawings) {
|
|
2049
2300
|
chart.createOverlay({
|
|
@@ -2269,8 +2520,7 @@ ${code}`
|
|
|
2269
2520
|
if (placement === "main") {
|
|
2270
2521
|
paneId = chart.createIndicator(
|
|
2271
2522
|
{ name: indicatorName },
|
|
2272
|
-
true,
|
|
2273
|
-
{ id: "candle_pane" }
|
|
2523
|
+
{ isStack: true, pane: { id: "candle_pane" } }
|
|
2274
2524
|
);
|
|
2275
2525
|
} else {
|
|
2276
2526
|
paneId = chart.createIndicator({ name: indicatorName });
|
|
@@ -2411,10 +2661,9 @@ function useCrosshair() {
|
|
|
2411
2661
|
}
|
|
2412
2662
|
var alertCounter = 0;
|
|
2413
2663
|
function useAlerts() {
|
|
2414
|
-
const { state } = useKlinechartsUI();
|
|
2415
|
-
const
|
|
2416
|
-
const
|
|
2417
|
-
const prevCloseRef = useRef(null);
|
|
2664
|
+
const { state, dispatch } = useKlinechartsUI();
|
|
2665
|
+
const { alertTriggeredListenerRef } = useKlinechartsUIDispatch();
|
|
2666
|
+
const alerts = state.alerts;
|
|
2418
2667
|
const addAlert = useCallback(
|
|
2419
2668
|
(price, condition, message) => {
|
|
2420
2669
|
const id = `alert_${++alertCounter}`;
|
|
@@ -2425,7 +2674,7 @@ function useAlerts() {
|
|
|
2425
2674
|
message,
|
|
2426
2675
|
triggered: false
|
|
2427
2676
|
};
|
|
2428
|
-
|
|
2677
|
+
dispatch({ type: "SET_ALERTS", alerts: [...state.alerts, alert] });
|
|
2429
2678
|
if (state.chart) {
|
|
2430
2679
|
state.chart.createOverlay({
|
|
2431
2680
|
name: "horizontalStraightLine",
|
|
@@ -2444,67 +2693,30 @@ function useAlerts() {
|
|
|
2444
2693
|
}
|
|
2445
2694
|
return id;
|
|
2446
2695
|
},
|
|
2447
|
-
[state.chart]
|
|
2696
|
+
[state.chart, state.alerts, dispatch]
|
|
2448
2697
|
);
|
|
2449
2698
|
const removeAlert = useCallback(
|
|
2450
2699
|
(id) => {
|
|
2451
|
-
|
|
2700
|
+
dispatch({
|
|
2701
|
+
type: "SET_ALERTS",
|
|
2702
|
+
alerts: state.alerts.filter((a) => a.id !== id)
|
|
2703
|
+
});
|
|
2452
2704
|
state.chart?.removeOverlay({ id });
|
|
2453
2705
|
},
|
|
2454
|
-
[state.chart]
|
|
2706
|
+
[state.chart, state.alerts, dispatch]
|
|
2455
2707
|
);
|
|
2456
2708
|
const clearAlerts = useCallback(() => {
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
});
|
|
2463
|
-
}, [state.chart]);
|
|
2709
|
+
for (const alert of state.alerts) {
|
|
2710
|
+
state.chart?.removeOverlay({ id: alert.id });
|
|
2711
|
+
}
|
|
2712
|
+
dispatch({ type: "SET_ALERTS", alerts: [] });
|
|
2713
|
+
}, [state.chart, state.alerts, dispatch]);
|
|
2464
2714
|
const onAlertTriggered = useCallback(
|
|
2465
2715
|
(callback) => {
|
|
2466
|
-
|
|
2716
|
+
alertTriggeredListenerRef.current = callback;
|
|
2467
2717
|
},
|
|
2468
|
-
[]
|
|
2718
|
+
[alertTriggeredListenerRef]
|
|
2469
2719
|
);
|
|
2470
|
-
useEffect(() => {
|
|
2471
|
-
if (!state.chart) return;
|
|
2472
|
-
const interval = setInterval(() => {
|
|
2473
|
-
const dataList = state.chart?.getDataList();
|
|
2474
|
-
if (!dataList || dataList.length === 0) return;
|
|
2475
|
-
const lastBar = dataList[dataList.length - 1];
|
|
2476
|
-
const currentClose = lastBar.close;
|
|
2477
|
-
const prevClose = prevCloseRef.current;
|
|
2478
|
-
if (prevClose === null) {
|
|
2479
|
-
prevCloseRef.current = currentClose;
|
|
2480
|
-
return;
|
|
2481
|
-
}
|
|
2482
|
-
prevCloseRef.current = currentClose;
|
|
2483
|
-
setAlerts((prev) => {
|
|
2484
|
-
let changed = false;
|
|
2485
|
-
const next = prev.map((alert) => {
|
|
2486
|
-
if (alert.triggered) return alert;
|
|
2487
|
-
let shouldTrigger = false;
|
|
2488
|
-
if (alert.condition === "crossing_up") {
|
|
2489
|
-
shouldTrigger = prevClose < alert.price && currentClose >= alert.price;
|
|
2490
|
-
} else if (alert.condition === "crossing_down") {
|
|
2491
|
-
shouldTrigger = prevClose > alert.price && currentClose <= alert.price;
|
|
2492
|
-
} else if (alert.condition === "crossing") {
|
|
2493
|
-
shouldTrigger = prevClose < alert.price && currentClose >= alert.price || prevClose > alert.price && currentClose <= alert.price;
|
|
2494
|
-
}
|
|
2495
|
-
if (shouldTrigger) {
|
|
2496
|
-
changed = true;
|
|
2497
|
-
const triggered = { ...alert, triggered: true };
|
|
2498
|
-
callbackRef.current?.(triggered);
|
|
2499
|
-
return triggered;
|
|
2500
|
-
}
|
|
2501
|
-
return alert;
|
|
2502
|
-
});
|
|
2503
|
-
return changed ? next : prev;
|
|
2504
|
-
});
|
|
2505
|
-
}, 1e3);
|
|
2506
|
-
return () => clearInterval(interval);
|
|
2507
|
-
}, [state.chart]);
|
|
2508
2720
|
return {
|
|
2509
2721
|
alerts,
|
|
2510
2722
|
addAlert,
|
|
@@ -2650,128 +2862,124 @@ function useWatchlist() {
|
|
|
2650
2862
|
};
|
|
2651
2863
|
}
|
|
2652
2864
|
function useReplay() {
|
|
2653
|
-
const { state } = useKlinechartsUI();
|
|
2654
|
-
const
|
|
2655
|
-
const
|
|
2656
|
-
const [speed, setSpeedState] = useState(1);
|
|
2657
|
-
const [barIndex, setBarIndex] = useState(0);
|
|
2658
|
-
const [totalBars, setTotalBars] = useState(0);
|
|
2659
|
-
const intervalRef = useRef(null);
|
|
2660
|
-
const savedDataRef = useRef([]);
|
|
2661
|
-
const currentIndexRef = useRef(0);
|
|
2865
|
+
const { state, dispatch } = useKlinechartsUI();
|
|
2866
|
+
const { replayIntervalRef, replaySavedDataRef, replayIndexRef } = useKlinechartsUIDispatch();
|
|
2867
|
+
const { isReplaying, isPaused, speed, barIndex, totalBars } = state.replay;
|
|
2662
2868
|
const clearInterval_ = useCallback(() => {
|
|
2663
|
-
if (
|
|
2664
|
-
clearInterval(
|
|
2665
|
-
|
|
2869
|
+
if (replayIntervalRef.current !== null) {
|
|
2870
|
+
clearInterval(replayIntervalRef.current);
|
|
2871
|
+
replayIntervalRef.current = null;
|
|
2666
2872
|
}
|
|
2667
|
-
}, []);
|
|
2873
|
+
}, [replayIntervalRef]);
|
|
2668
2874
|
const addNextBar = useCallback(() => {
|
|
2669
2875
|
if (!state.chart) return;
|
|
2670
|
-
const data =
|
|
2671
|
-
const idx =
|
|
2876
|
+
const data = replaySavedDataRef.current;
|
|
2877
|
+
const idx = replayIndexRef.current;
|
|
2672
2878
|
if (idx >= data.length) {
|
|
2673
2879
|
clearInterval_();
|
|
2674
|
-
|
|
2880
|
+
dispatch({ type: "SET_REPLAY", replay: { isPaused: true } });
|
|
2675
2881
|
return;
|
|
2676
2882
|
}
|
|
2677
2883
|
const bar = data[idx];
|
|
2678
2884
|
state.chart.updateData?.(bar);
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
}, [state.chart, clearInterval_]);
|
|
2885
|
+
replayIndexRef.current = idx + 1;
|
|
2886
|
+
dispatch({ type: "SET_REPLAY", replay: { barIndex: idx + 1 } });
|
|
2887
|
+
}, [state.chart, clearInterval_, replaySavedDataRef, replayIndexRef, dispatch]);
|
|
2682
2888
|
const startInterval = useCallback(
|
|
2683
2889
|
(currentSpeed) => {
|
|
2684
2890
|
clearInterval_();
|
|
2685
|
-
|
|
2891
|
+
replayIntervalRef.current = setInterval(addNextBar, 1e3 / currentSpeed);
|
|
2686
2892
|
},
|
|
2687
|
-
[addNextBar, clearInterval_]
|
|
2893
|
+
[addNextBar, clearInterval_, replayIntervalRef]
|
|
2688
2894
|
);
|
|
2689
2895
|
const startReplay = useCallback(() => {
|
|
2690
2896
|
if (!state.chart) return;
|
|
2691
2897
|
const dataList = state.chart.getDataList();
|
|
2692
2898
|
if (!dataList || dataList.length === 0) return;
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2899
|
+
replaySavedDataRef.current = [...dataList];
|
|
2900
|
+
replayIndexRef.current = 0;
|
|
2901
|
+
dispatch({
|
|
2902
|
+
type: "SET_REPLAY",
|
|
2903
|
+
replay: {
|
|
2904
|
+
totalBars: dataList.length,
|
|
2905
|
+
barIndex: 0,
|
|
2906
|
+
isReplaying: true,
|
|
2907
|
+
isPaused: false
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2699
2910
|
state.chart.clearData?.();
|
|
2700
2911
|
startInterval(speed);
|
|
2701
|
-
}, [state.chart, speed, startInterval]);
|
|
2912
|
+
}, [state.chart, speed, startInterval, replaySavedDataRef, replayIndexRef, dispatch]);
|
|
2702
2913
|
const stopReplay = useCallback(() => {
|
|
2703
2914
|
if (!state.chart) return;
|
|
2704
2915
|
clearInterval_();
|
|
2705
|
-
const data =
|
|
2916
|
+
const data = replaySavedDataRef.current;
|
|
2706
2917
|
if (data.length > 0) {
|
|
2707
2918
|
state.chart.clearData?.();
|
|
2708
2919
|
for (const bar of data) {
|
|
2709
2920
|
state.chart.updateData?.(bar);
|
|
2710
2921
|
}
|
|
2711
2922
|
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
}, [state.chart, clearInterval_]);
|
|
2923
|
+
replaySavedDataRef.current = [];
|
|
2924
|
+
replayIndexRef.current = 0;
|
|
2925
|
+
dispatch({
|
|
2926
|
+
type: "SET_REPLAY",
|
|
2927
|
+
replay: { isReplaying: false, isPaused: false, barIndex: 0, totalBars: 0 }
|
|
2928
|
+
});
|
|
2929
|
+
}, [state.chart, clearInterval_, replaySavedDataRef, replayIndexRef, dispatch]);
|
|
2719
2930
|
const togglePause = useCallback(() => {
|
|
2720
2931
|
if (!isReplaying) return;
|
|
2721
2932
|
if (isPaused) {
|
|
2722
2933
|
startInterval(speed);
|
|
2723
|
-
|
|
2934
|
+
dispatch({ type: "SET_REPLAY", replay: { isPaused: false } });
|
|
2724
2935
|
} else {
|
|
2725
2936
|
clearInterval_();
|
|
2726
|
-
|
|
2937
|
+
dispatch({ type: "SET_REPLAY", replay: { isPaused: true } });
|
|
2727
2938
|
}
|
|
2728
|
-
}, [isReplaying, isPaused, speed, startInterval, clearInterval_]);
|
|
2939
|
+
}, [isReplaying, isPaused, speed, startInterval, clearInterval_, dispatch]);
|
|
2729
2940
|
const stepForward = useCallback(() => {
|
|
2730
2941
|
if (!isReplaying || !isPaused) return;
|
|
2731
2942
|
addNextBar();
|
|
2732
2943
|
}, [isReplaying, isPaused, addNextBar]);
|
|
2733
2944
|
const stepBackward = useCallback(() => {
|
|
2734
2945
|
if (!isReplaying || !isPaused || !state.chart) return;
|
|
2735
|
-
const idx =
|
|
2946
|
+
const idx = replayIndexRef.current;
|
|
2736
2947
|
if (idx <= 1) return;
|
|
2737
|
-
const data =
|
|
2948
|
+
const data = replaySavedDataRef.current;
|
|
2738
2949
|
state.chart.clearData?.();
|
|
2739
2950
|
for (let i = 0; i < idx - 1; i++) {
|
|
2740
2951
|
state.chart.updateData?.(data[i]);
|
|
2741
2952
|
}
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
}, [isReplaying, isPaused, state.chart]);
|
|
2953
|
+
replayIndexRef.current = idx - 1;
|
|
2954
|
+
dispatch({ type: "SET_REPLAY", replay: { barIndex: idx - 1 } });
|
|
2955
|
+
}, [isReplaying, isPaused, state.chart, replaySavedDataRef, replayIndexRef, dispatch]);
|
|
2745
2956
|
const seekTo = useCallback(
|
|
2746
2957
|
(targetIndex) => {
|
|
2747
2958
|
if (!isReplaying || !state.chart) return;
|
|
2748
|
-
const data =
|
|
2959
|
+
const data = replaySavedDataRef.current;
|
|
2749
2960
|
const clamped = Math.max(0, Math.min(targetIndex, data.length));
|
|
2750
2961
|
clearInterval_();
|
|
2751
|
-
setIsPaused(true);
|
|
2752
2962
|
state.chart.clearData?.();
|
|
2753
2963
|
for (let i = 0; i < clamped; i++) {
|
|
2754
2964
|
state.chart.updateData?.(data[i]);
|
|
2755
2965
|
}
|
|
2756
|
-
|
|
2757
|
-
|
|
2966
|
+
replayIndexRef.current = clamped;
|
|
2967
|
+
dispatch({
|
|
2968
|
+
type: "SET_REPLAY",
|
|
2969
|
+
replay: { isPaused: true, barIndex: clamped }
|
|
2970
|
+
});
|
|
2758
2971
|
},
|
|
2759
|
-
[isReplaying, state.chart, clearInterval_]
|
|
2972
|
+
[isReplaying, state.chart, clearInterval_, replaySavedDataRef, replayIndexRef, dispatch]
|
|
2760
2973
|
);
|
|
2761
2974
|
const setSpeed = useCallback(
|
|
2762
2975
|
(newSpeed) => {
|
|
2763
|
-
|
|
2976
|
+
dispatch({ type: "SET_REPLAY", replay: { speed: newSpeed } });
|
|
2764
2977
|
if (isReplaying && !isPaused) {
|
|
2765
2978
|
startInterval(newSpeed);
|
|
2766
2979
|
}
|
|
2767
2980
|
},
|
|
2768
|
-
[isReplaying, isPaused, startInterval]
|
|
2981
|
+
[isReplaying, isPaused, startInterval, dispatch]
|
|
2769
2982
|
);
|
|
2770
|
-
useEffect(() => {
|
|
2771
|
-
return () => {
|
|
2772
|
-
clearInterval_();
|
|
2773
|
-
};
|
|
2774
|
-
}, [clearInterval_]);
|
|
2775
2983
|
return {
|
|
2776
2984
|
isReplaying,
|
|
2777
2985
|
isPaused,
|
|
@@ -2855,8 +3063,7 @@ function useCompare() {
|
|
|
2855
3063
|
});
|
|
2856
3064
|
const paneId = state.chart.createIndicator(
|
|
2857
3065
|
{ name: indicatorName },
|
|
2858
|
-
true,
|
|
2859
|
-
{ id: "candle_pane" }
|
|
3066
|
+
{ isStack: true, pane: { id: "candle_pane" } }
|
|
2860
3067
|
);
|
|
2861
3068
|
indicatorsRef.current.set(ticker, {
|
|
2862
3069
|
name: indicatorName,
|
|
@@ -2929,31 +3136,25 @@ function useCompare() {
|
|
|
2929
3136
|
return { symbols, addSymbol, removeSymbol, toggleSymbol, clearAll };
|
|
2930
3137
|
}
|
|
2931
3138
|
var MEASURE_OVERLAY_ID = "__measure_line__";
|
|
3139
|
+
function computeResult(from, to) {
|
|
3140
|
+
const priceDiff = to.price - from.price;
|
|
3141
|
+
const pricePercent = from.price !== 0 ? priceDiff / from.price * 100 : 0;
|
|
3142
|
+
const bars = Math.abs(to.barIndex - from.barIndex);
|
|
3143
|
+
const timeDiff = Math.abs(to.timestamp - from.timestamp);
|
|
3144
|
+
return { from, to, priceDiff, pricePercent, bars, timeDiff };
|
|
3145
|
+
}
|
|
2932
3146
|
function useMeasure() {
|
|
2933
|
-
const { state } = useKlinechartsUI();
|
|
2934
|
-
const
|
|
2935
|
-
const [fromPoint, setFromPoint] = useState(null);
|
|
2936
|
-
const [result, setResult] = useState(null);
|
|
2937
|
-
const clickCountRef = useRef(0);
|
|
2938
|
-
const computeResult = useCallback(
|
|
2939
|
-
(from, to) => {
|
|
2940
|
-
const priceDiff = to.price - from.price;
|
|
2941
|
-
const pricePercent = from.price !== 0 ? priceDiff / from.price * 100 : 0;
|
|
2942
|
-
const bars = Math.abs(to.barIndex - from.barIndex);
|
|
2943
|
-
const timeDiff = Math.abs(to.timestamp - from.timestamp);
|
|
2944
|
-
return { from, to, priceDiff, pricePercent, bars, timeDiff };
|
|
2945
|
-
},
|
|
2946
|
-
[]
|
|
2947
|
-
);
|
|
3147
|
+
const { state, dispatch } = useKlinechartsUI();
|
|
3148
|
+
const { isActive, fromPoint, result } = state.measure;
|
|
2948
3149
|
const cleanup = useCallback(() => {
|
|
2949
3150
|
state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
|
|
2950
3151
|
}, [state.chart]);
|
|
2951
3152
|
const startMeasure = useCallback(() => {
|
|
2952
3153
|
cleanup();
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
3154
|
+
dispatch({
|
|
3155
|
+
type: "SET_MEASURE",
|
|
3156
|
+
measure: { isActive: true, fromPoint: null, result: null }
|
|
3157
|
+
});
|
|
2957
3158
|
if (!state.chart) return;
|
|
2958
3159
|
state.chart.createOverlay({
|
|
2959
3160
|
name: "segment",
|
|
@@ -2989,23 +3190,25 @@ function useMeasure() {
|
|
|
2989
3190
|
};
|
|
2990
3191
|
const from = makePoint(points[0]);
|
|
2991
3192
|
const to = makePoint(points[1]);
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3193
|
+
dispatch({
|
|
3194
|
+
type: "SET_MEASURE",
|
|
3195
|
+
measure: {
|
|
3196
|
+
isActive: false,
|
|
3197
|
+
fromPoint: from,
|
|
3198
|
+
result: computeResult(from, to)
|
|
3199
|
+
}
|
|
3200
|
+
});
|
|
2995
3201
|
}
|
|
2996
3202
|
});
|
|
2997
|
-
}, [state.chart, cleanup,
|
|
3203
|
+
}, [state.chart, cleanup, dispatch]);
|
|
2998
3204
|
const cancelMeasure = useCallback(() => {
|
|
2999
3205
|
cleanup();
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
clickCountRef.current = 0;
|
|
3003
|
-
}, [cleanup]);
|
|
3206
|
+
dispatch({ type: "SET_MEASURE", measure: { isActive: false, fromPoint: null } });
|
|
3207
|
+
}, [cleanup, dispatch]);
|
|
3004
3208
|
const clearResult = useCallback(() => {
|
|
3005
3209
|
cleanup();
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
}, [cleanup]);
|
|
3210
|
+
dispatch({ type: "SET_MEASURE", measure: { result: null, fromPoint: null } });
|
|
3211
|
+
}, [cleanup, dispatch]);
|
|
3009
3212
|
useEffect(() => {
|
|
3010
3213
|
return () => {
|
|
3011
3214
|
state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
|