react-klinecharts-ui 0.4.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 +284 -6
- 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 +282 -201
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +155 -29
- package/dist/index.d.ts +155 -29
- package/dist/index.js +238 -157
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- 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';
|
|
@@ -59,6 +59,14 @@ function reducer(state, action) {
|
|
|
59
59
|
return { ...state, subIndicators: action.indicators };
|
|
60
60
|
case "SET_INDICATOR_AXES":
|
|
61
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 } };
|
|
62
70
|
case "SET_STYLES":
|
|
63
71
|
return { ...state, styles: action.styles };
|
|
64
72
|
case "SET_LOCALE":
|
|
@@ -122,12 +130,27 @@ function KlinechartsUIProvider({
|
|
|
122
130
|
{}
|
|
123
131
|
),
|
|
124
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
|
+
},
|
|
125
143
|
styles: opts.styles,
|
|
126
144
|
screenshotUrl: null
|
|
127
145
|
})
|
|
128
146
|
);
|
|
129
147
|
const fullscreenContainerRef = useRef(null);
|
|
130
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);
|
|
131
154
|
const stateRef = useRef(state);
|
|
132
155
|
stateRef.current = state;
|
|
133
156
|
const callbacksRef = useRef({
|
|
@@ -188,13 +211,58 @@ function KlinechartsUIProvider({
|
|
|
188
211
|
state.chart.setStyles(state.styles);
|
|
189
212
|
}
|
|
190
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
|
+
}, []);
|
|
191
255
|
const dispatchValue = useMemo(
|
|
192
256
|
() => ({
|
|
193
257
|
dispatch: enhancedDispatch,
|
|
194
258
|
datafeed,
|
|
195
259
|
onSettingsChange,
|
|
196
260
|
fullscreenContainerRef,
|
|
197
|
-
undoRedoListenerRef
|
|
261
|
+
undoRedoListenerRef,
|
|
262
|
+
alertTriggeredListenerRef,
|
|
263
|
+
replayIntervalRef,
|
|
264
|
+
replaySavedDataRef,
|
|
265
|
+
replayIndexRef
|
|
198
266
|
}),
|
|
199
267
|
[enhancedDispatch, datafeed, onSettingsChange]
|
|
200
268
|
);
|
|
@@ -656,18 +724,26 @@ function useIndicators() {
|
|
|
656
724
|
const activeNames = state.mainIndicators;
|
|
657
725
|
const inactive = MAIN_INDICATORS.filter((n) => !activeNames.includes(n));
|
|
658
726
|
return [
|
|
659
|
-
...activeNames.map((name) => ({
|
|
660
|
-
|
|
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 }))
|
|
661
733
|
];
|
|
662
|
-
}, [state.mainIndicators]);
|
|
734
|
+
}, [state.mainIndicators, state.indicatorVisibility]);
|
|
663
735
|
const subIndicators = useMemo(() => {
|
|
664
736
|
const activeNames = Object.keys(state.subIndicators);
|
|
665
737
|
const inactive = SUB_INDICATORS.filter((n) => !activeNames.includes(n));
|
|
666
738
|
return [
|
|
667
|
-
...activeNames.map((name) => ({
|
|
668
|
-
|
|
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 }))
|
|
669
745
|
];
|
|
670
|
-
}, [state.subIndicators]);
|
|
746
|
+
}, [state.subIndicators, state.indicatorVisibility]);
|
|
671
747
|
const addMainIndicator = useCallback(
|
|
672
748
|
(name, options) => {
|
|
673
749
|
if (state.mainIndicators.includes(name)) return;
|
|
@@ -709,12 +785,24 @@ function useIndicators() {
|
|
|
709
785
|
delete nextAxes[id];
|
|
710
786
|
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
711
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
|
+
}
|
|
712
793
|
undoRedoListenerRef.current?.({
|
|
713
794
|
type: "indicator_toggled",
|
|
714
795
|
data: { name, wasActive: true, isMain: true, paneId: "candle_pane", yAxisId }
|
|
715
796
|
});
|
|
716
797
|
},
|
|
717
|
-
[
|
|
798
|
+
[
|
|
799
|
+
state.chart,
|
|
800
|
+
state.mainIndicators,
|
|
801
|
+
state.indicatorAxes,
|
|
802
|
+
state.indicatorVisibility,
|
|
803
|
+
dispatch,
|
|
804
|
+
undoRedoListenerRef
|
|
805
|
+
]
|
|
718
806
|
);
|
|
719
807
|
const addSubIndicator = useCallback(
|
|
720
808
|
(name, options) => {
|
|
@@ -752,12 +840,24 @@ function useIndicators() {
|
|
|
752
840
|
delete nextAxes[id];
|
|
753
841
|
dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
|
|
754
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
|
+
}
|
|
755
848
|
undoRedoListenerRef.current?.({
|
|
756
849
|
type: "indicator_toggled",
|
|
757
850
|
data: { name, wasActive: true, isMain: false, paneId, yAxisId }
|
|
758
851
|
});
|
|
759
852
|
},
|
|
760
|
-
[
|
|
853
|
+
[
|
|
854
|
+
state.chart,
|
|
855
|
+
state.subIndicators,
|
|
856
|
+
state.indicatorAxes,
|
|
857
|
+
state.indicatorVisibility,
|
|
858
|
+
dispatch,
|
|
859
|
+
undoRedoListenerRef
|
|
860
|
+
]
|
|
761
861
|
);
|
|
762
862
|
const toggleMainIndicator = useCallback(
|
|
763
863
|
(name) => {
|
|
@@ -783,8 +883,16 @@ function useIndicators() {
|
|
|
783
883
|
(name, isMain, visible) => {
|
|
784
884
|
const id = isMain ? `main_${name}` : `sub_${name}`;
|
|
785
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 });
|
|
786
890
|
},
|
|
787
|
-
[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]
|
|
788
896
|
);
|
|
789
897
|
const updateIndicatorParams = useCallback(
|
|
790
898
|
// `paneId` is kept in the signature for API stability; klinecharts v10
|
|
@@ -867,9 +975,14 @@ function useIndicators() {
|
|
|
867
975
|
id: paneId,
|
|
868
976
|
height: COLLAPSED_HEIGHT
|
|
869
977
|
});
|
|
870
|
-
|
|
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
|
+
});
|
|
871
984
|
},
|
|
872
|
-
[state.chart, state.subIndicators]
|
|
985
|
+
[state.chart, state.subIndicators, state.indicatorVisibility, dispatch]
|
|
873
986
|
);
|
|
874
987
|
const expandSubIndicator = useCallback(
|
|
875
988
|
(name) => {
|
|
@@ -881,9 +994,13 @@ function useIndicators() {
|
|
|
881
994
|
id: paneId,
|
|
882
995
|
height: savedHeight
|
|
883
996
|
});
|
|
884
|
-
|
|
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 });
|
|
885
1002
|
},
|
|
886
|
-
[state.chart, state.subIndicators]
|
|
1003
|
+
[state.chart, state.subIndicators, state.indicatorVisibility, dispatch]
|
|
887
1004
|
);
|
|
888
1005
|
const isSubIndicatorCollapsed = useCallback(
|
|
889
1006
|
(name) => {
|
|
@@ -992,6 +1109,7 @@ function useIndicators() {
|
|
|
992
1109
|
moveToMain,
|
|
993
1110
|
moveToSub,
|
|
994
1111
|
setIndicatorVisible,
|
|
1112
|
+
isIndicatorVisible,
|
|
995
1113
|
updateIndicatorParams,
|
|
996
1114
|
getIndicatorParams,
|
|
997
1115
|
isMainIndicatorActive,
|
|
@@ -1002,6 +1120,7 @@ function useIndicators() {
|
|
|
1002
1120
|
reorderSubIndicator,
|
|
1003
1121
|
indicatorAxes: state.indicatorAxes,
|
|
1004
1122
|
getIndicatorAxis,
|
|
1123
|
+
indicatorVisibility: state.indicatorVisibility,
|
|
1005
1124
|
bindIndicatorToNewAxis
|
|
1006
1125
|
};
|
|
1007
1126
|
}
|
|
@@ -2122,6 +2241,7 @@ function useLayoutManager() {
|
|
|
2122
2241
|
const newMainIndicators = [];
|
|
2123
2242
|
const newSubIndicators = {};
|
|
2124
2243
|
const restoredAxes = {};
|
|
2244
|
+
const restoredVisibility = {};
|
|
2125
2245
|
if (chartState.indicators) {
|
|
2126
2246
|
for (const ind of chartState.indicators) {
|
|
2127
2247
|
const isMain = ind.paneId === "candle_pane";
|
|
@@ -2149,6 +2269,9 @@ function useLayoutManager() {
|
|
|
2149
2269
|
if (ind.yAxisId) {
|
|
2150
2270
|
restoredAxes[id2] = ind.yAxisId;
|
|
2151
2271
|
}
|
|
2272
|
+
if (ind.visible === false) {
|
|
2273
|
+
restoredVisibility[id2] = false;
|
|
2274
|
+
}
|
|
2152
2275
|
if (isMain) {
|
|
2153
2276
|
newMainIndicators.push(ind.name);
|
|
2154
2277
|
} else {
|
|
@@ -2168,6 +2291,10 @@ function useLayoutManager() {
|
|
|
2168
2291
|
type: "SET_INDICATOR_AXES",
|
|
2169
2292
|
axes: restoredAxes
|
|
2170
2293
|
});
|
|
2294
|
+
dispatch({
|
|
2295
|
+
type: "SET_INDICATOR_VISIBILITY",
|
|
2296
|
+
visibility: restoredVisibility
|
|
2297
|
+
});
|
|
2171
2298
|
if (chartState.drawings) {
|
|
2172
2299
|
for (const drawing of chartState.drawings) {
|
|
2173
2300
|
chart.createOverlay({
|
|
@@ -2534,10 +2661,9 @@ function useCrosshair() {
|
|
|
2534
2661
|
}
|
|
2535
2662
|
var alertCounter = 0;
|
|
2536
2663
|
function useAlerts() {
|
|
2537
|
-
const { state } = useKlinechartsUI();
|
|
2538
|
-
const
|
|
2539
|
-
const
|
|
2540
|
-
const prevCloseRef = useRef(null);
|
|
2664
|
+
const { state, dispatch } = useKlinechartsUI();
|
|
2665
|
+
const { alertTriggeredListenerRef } = useKlinechartsUIDispatch();
|
|
2666
|
+
const alerts = state.alerts;
|
|
2541
2667
|
const addAlert = useCallback(
|
|
2542
2668
|
(price, condition, message) => {
|
|
2543
2669
|
const id = `alert_${++alertCounter}`;
|
|
@@ -2548,7 +2674,7 @@ function useAlerts() {
|
|
|
2548
2674
|
message,
|
|
2549
2675
|
triggered: false
|
|
2550
2676
|
};
|
|
2551
|
-
|
|
2677
|
+
dispatch({ type: "SET_ALERTS", alerts: [...state.alerts, alert] });
|
|
2552
2678
|
if (state.chart) {
|
|
2553
2679
|
state.chart.createOverlay({
|
|
2554
2680
|
name: "horizontalStraightLine",
|
|
@@ -2567,67 +2693,30 @@ function useAlerts() {
|
|
|
2567
2693
|
}
|
|
2568
2694
|
return id;
|
|
2569
2695
|
},
|
|
2570
|
-
[state.chart]
|
|
2696
|
+
[state.chart, state.alerts, dispatch]
|
|
2571
2697
|
);
|
|
2572
2698
|
const removeAlert = useCallback(
|
|
2573
2699
|
(id) => {
|
|
2574
|
-
|
|
2700
|
+
dispatch({
|
|
2701
|
+
type: "SET_ALERTS",
|
|
2702
|
+
alerts: state.alerts.filter((a) => a.id !== id)
|
|
2703
|
+
});
|
|
2575
2704
|
state.chart?.removeOverlay({ id });
|
|
2576
2705
|
},
|
|
2577
|
-
[state.chart]
|
|
2706
|
+
[state.chart, state.alerts, dispatch]
|
|
2578
2707
|
);
|
|
2579
2708
|
const clearAlerts = useCallback(() => {
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
});
|
|
2586
|
-
}, [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]);
|
|
2587
2714
|
const onAlertTriggered = useCallback(
|
|
2588
2715
|
(callback) => {
|
|
2589
|
-
|
|
2716
|
+
alertTriggeredListenerRef.current = callback;
|
|
2590
2717
|
},
|
|
2591
|
-
[]
|
|
2718
|
+
[alertTriggeredListenerRef]
|
|
2592
2719
|
);
|
|
2593
|
-
useEffect(() => {
|
|
2594
|
-
if (!state.chart) return;
|
|
2595
|
-
const interval = setInterval(() => {
|
|
2596
|
-
const dataList = state.chart?.getDataList();
|
|
2597
|
-
if (!dataList || dataList.length === 0) return;
|
|
2598
|
-
const lastBar = dataList[dataList.length - 1];
|
|
2599
|
-
const currentClose = lastBar.close;
|
|
2600
|
-
const prevClose = prevCloseRef.current;
|
|
2601
|
-
if (prevClose === null) {
|
|
2602
|
-
prevCloseRef.current = currentClose;
|
|
2603
|
-
return;
|
|
2604
|
-
}
|
|
2605
|
-
prevCloseRef.current = currentClose;
|
|
2606
|
-
setAlerts((prev) => {
|
|
2607
|
-
let changed = false;
|
|
2608
|
-
const next = prev.map((alert) => {
|
|
2609
|
-
if (alert.triggered) return alert;
|
|
2610
|
-
let shouldTrigger = false;
|
|
2611
|
-
if (alert.condition === "crossing_up") {
|
|
2612
|
-
shouldTrigger = prevClose < alert.price && currentClose >= alert.price;
|
|
2613
|
-
} else if (alert.condition === "crossing_down") {
|
|
2614
|
-
shouldTrigger = prevClose > alert.price && currentClose <= alert.price;
|
|
2615
|
-
} else if (alert.condition === "crossing") {
|
|
2616
|
-
shouldTrigger = prevClose < alert.price && currentClose >= alert.price || prevClose > alert.price && currentClose <= alert.price;
|
|
2617
|
-
}
|
|
2618
|
-
if (shouldTrigger) {
|
|
2619
|
-
changed = true;
|
|
2620
|
-
const triggered = { ...alert, triggered: true };
|
|
2621
|
-
callbackRef.current?.(triggered);
|
|
2622
|
-
return triggered;
|
|
2623
|
-
}
|
|
2624
|
-
return alert;
|
|
2625
|
-
});
|
|
2626
|
-
return changed ? next : prev;
|
|
2627
|
-
});
|
|
2628
|
-
}, 1e3);
|
|
2629
|
-
return () => clearInterval(interval);
|
|
2630
|
-
}, [state.chart]);
|
|
2631
2720
|
return {
|
|
2632
2721
|
alerts,
|
|
2633
2722
|
addAlert,
|
|
@@ -2773,128 +2862,124 @@ function useWatchlist() {
|
|
|
2773
2862
|
};
|
|
2774
2863
|
}
|
|
2775
2864
|
function useReplay() {
|
|
2776
|
-
const { state } = useKlinechartsUI();
|
|
2777
|
-
const
|
|
2778
|
-
const
|
|
2779
|
-
const [speed, setSpeedState] = useState(1);
|
|
2780
|
-
const [barIndex, setBarIndex] = useState(0);
|
|
2781
|
-
const [totalBars, setTotalBars] = useState(0);
|
|
2782
|
-
const intervalRef = useRef(null);
|
|
2783
|
-
const savedDataRef = useRef([]);
|
|
2784
|
-
const currentIndexRef = useRef(0);
|
|
2865
|
+
const { state, dispatch } = useKlinechartsUI();
|
|
2866
|
+
const { replayIntervalRef, replaySavedDataRef, replayIndexRef } = useKlinechartsUIDispatch();
|
|
2867
|
+
const { isReplaying, isPaused, speed, barIndex, totalBars } = state.replay;
|
|
2785
2868
|
const clearInterval_ = useCallback(() => {
|
|
2786
|
-
if (
|
|
2787
|
-
clearInterval(
|
|
2788
|
-
|
|
2869
|
+
if (replayIntervalRef.current !== null) {
|
|
2870
|
+
clearInterval(replayIntervalRef.current);
|
|
2871
|
+
replayIntervalRef.current = null;
|
|
2789
2872
|
}
|
|
2790
|
-
}, []);
|
|
2873
|
+
}, [replayIntervalRef]);
|
|
2791
2874
|
const addNextBar = useCallback(() => {
|
|
2792
2875
|
if (!state.chart) return;
|
|
2793
|
-
const data =
|
|
2794
|
-
const idx =
|
|
2876
|
+
const data = replaySavedDataRef.current;
|
|
2877
|
+
const idx = replayIndexRef.current;
|
|
2795
2878
|
if (idx >= data.length) {
|
|
2796
2879
|
clearInterval_();
|
|
2797
|
-
|
|
2880
|
+
dispatch({ type: "SET_REPLAY", replay: { isPaused: true } });
|
|
2798
2881
|
return;
|
|
2799
2882
|
}
|
|
2800
2883
|
const bar = data[idx];
|
|
2801
2884
|
state.chart.updateData?.(bar);
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
}, [state.chart, clearInterval_]);
|
|
2885
|
+
replayIndexRef.current = idx + 1;
|
|
2886
|
+
dispatch({ type: "SET_REPLAY", replay: { barIndex: idx + 1 } });
|
|
2887
|
+
}, [state.chart, clearInterval_, replaySavedDataRef, replayIndexRef, dispatch]);
|
|
2805
2888
|
const startInterval = useCallback(
|
|
2806
2889
|
(currentSpeed) => {
|
|
2807
2890
|
clearInterval_();
|
|
2808
|
-
|
|
2891
|
+
replayIntervalRef.current = setInterval(addNextBar, 1e3 / currentSpeed);
|
|
2809
2892
|
},
|
|
2810
|
-
[addNextBar, clearInterval_]
|
|
2893
|
+
[addNextBar, clearInterval_, replayIntervalRef]
|
|
2811
2894
|
);
|
|
2812
2895
|
const startReplay = useCallback(() => {
|
|
2813
2896
|
if (!state.chart) return;
|
|
2814
2897
|
const dataList = state.chart.getDataList();
|
|
2815
2898
|
if (!dataList || dataList.length === 0) return;
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
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
|
+
});
|
|
2822
2910
|
state.chart.clearData?.();
|
|
2823
2911
|
startInterval(speed);
|
|
2824
|
-
}, [state.chart, speed, startInterval]);
|
|
2912
|
+
}, [state.chart, speed, startInterval, replaySavedDataRef, replayIndexRef, dispatch]);
|
|
2825
2913
|
const stopReplay = useCallback(() => {
|
|
2826
2914
|
if (!state.chart) return;
|
|
2827
2915
|
clearInterval_();
|
|
2828
|
-
const data =
|
|
2916
|
+
const data = replaySavedDataRef.current;
|
|
2829
2917
|
if (data.length > 0) {
|
|
2830
2918
|
state.chart.clearData?.();
|
|
2831
2919
|
for (const bar of data) {
|
|
2832
2920
|
state.chart.updateData?.(bar);
|
|
2833
2921
|
}
|
|
2834
2922
|
}
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
}, [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]);
|
|
2842
2930
|
const togglePause = useCallback(() => {
|
|
2843
2931
|
if (!isReplaying) return;
|
|
2844
2932
|
if (isPaused) {
|
|
2845
2933
|
startInterval(speed);
|
|
2846
|
-
|
|
2934
|
+
dispatch({ type: "SET_REPLAY", replay: { isPaused: false } });
|
|
2847
2935
|
} else {
|
|
2848
2936
|
clearInterval_();
|
|
2849
|
-
|
|
2937
|
+
dispatch({ type: "SET_REPLAY", replay: { isPaused: true } });
|
|
2850
2938
|
}
|
|
2851
|
-
}, [isReplaying, isPaused, speed, startInterval, clearInterval_]);
|
|
2939
|
+
}, [isReplaying, isPaused, speed, startInterval, clearInterval_, dispatch]);
|
|
2852
2940
|
const stepForward = useCallback(() => {
|
|
2853
2941
|
if (!isReplaying || !isPaused) return;
|
|
2854
2942
|
addNextBar();
|
|
2855
2943
|
}, [isReplaying, isPaused, addNextBar]);
|
|
2856
2944
|
const stepBackward = useCallback(() => {
|
|
2857
2945
|
if (!isReplaying || !isPaused || !state.chart) return;
|
|
2858
|
-
const idx =
|
|
2946
|
+
const idx = replayIndexRef.current;
|
|
2859
2947
|
if (idx <= 1) return;
|
|
2860
|
-
const data =
|
|
2948
|
+
const data = replaySavedDataRef.current;
|
|
2861
2949
|
state.chart.clearData?.();
|
|
2862
2950
|
for (let i = 0; i < idx - 1; i++) {
|
|
2863
2951
|
state.chart.updateData?.(data[i]);
|
|
2864
2952
|
}
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
}, [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]);
|
|
2868
2956
|
const seekTo = useCallback(
|
|
2869
2957
|
(targetIndex) => {
|
|
2870
2958
|
if (!isReplaying || !state.chart) return;
|
|
2871
|
-
const data =
|
|
2959
|
+
const data = replaySavedDataRef.current;
|
|
2872
2960
|
const clamped = Math.max(0, Math.min(targetIndex, data.length));
|
|
2873
2961
|
clearInterval_();
|
|
2874
|
-
setIsPaused(true);
|
|
2875
2962
|
state.chart.clearData?.();
|
|
2876
2963
|
for (let i = 0; i < clamped; i++) {
|
|
2877
2964
|
state.chart.updateData?.(data[i]);
|
|
2878
2965
|
}
|
|
2879
|
-
|
|
2880
|
-
|
|
2966
|
+
replayIndexRef.current = clamped;
|
|
2967
|
+
dispatch({
|
|
2968
|
+
type: "SET_REPLAY",
|
|
2969
|
+
replay: { isPaused: true, barIndex: clamped }
|
|
2970
|
+
});
|
|
2881
2971
|
},
|
|
2882
|
-
[isReplaying, state.chart, clearInterval_]
|
|
2972
|
+
[isReplaying, state.chart, clearInterval_, replaySavedDataRef, replayIndexRef, dispatch]
|
|
2883
2973
|
);
|
|
2884
2974
|
const setSpeed = useCallback(
|
|
2885
2975
|
(newSpeed) => {
|
|
2886
|
-
|
|
2976
|
+
dispatch({ type: "SET_REPLAY", replay: { speed: newSpeed } });
|
|
2887
2977
|
if (isReplaying && !isPaused) {
|
|
2888
2978
|
startInterval(newSpeed);
|
|
2889
2979
|
}
|
|
2890
2980
|
},
|
|
2891
|
-
[isReplaying, isPaused, startInterval]
|
|
2981
|
+
[isReplaying, isPaused, startInterval, dispatch]
|
|
2892
2982
|
);
|
|
2893
|
-
useEffect(() => {
|
|
2894
|
-
return () => {
|
|
2895
|
-
clearInterval_();
|
|
2896
|
-
};
|
|
2897
|
-
}, [clearInterval_]);
|
|
2898
2983
|
return {
|
|
2899
2984
|
isReplaying,
|
|
2900
2985
|
isPaused,
|
|
@@ -3051,31 +3136,25 @@ function useCompare() {
|
|
|
3051
3136
|
return { symbols, addSymbol, removeSymbol, toggleSymbol, clearAll };
|
|
3052
3137
|
}
|
|
3053
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
|
+
}
|
|
3054
3146
|
function useMeasure() {
|
|
3055
|
-
const { state } = useKlinechartsUI();
|
|
3056
|
-
const
|
|
3057
|
-
const [fromPoint, setFromPoint] = useState(null);
|
|
3058
|
-
const [result, setResult] = useState(null);
|
|
3059
|
-
const clickCountRef = useRef(0);
|
|
3060
|
-
const computeResult = useCallback(
|
|
3061
|
-
(from, to) => {
|
|
3062
|
-
const priceDiff = to.price - from.price;
|
|
3063
|
-
const pricePercent = from.price !== 0 ? priceDiff / from.price * 100 : 0;
|
|
3064
|
-
const bars = Math.abs(to.barIndex - from.barIndex);
|
|
3065
|
-
const timeDiff = Math.abs(to.timestamp - from.timestamp);
|
|
3066
|
-
return { from, to, priceDiff, pricePercent, bars, timeDiff };
|
|
3067
|
-
},
|
|
3068
|
-
[]
|
|
3069
|
-
);
|
|
3147
|
+
const { state, dispatch } = useKlinechartsUI();
|
|
3148
|
+
const { isActive, fromPoint, result } = state.measure;
|
|
3070
3149
|
const cleanup = useCallback(() => {
|
|
3071
3150
|
state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
|
|
3072
3151
|
}, [state.chart]);
|
|
3073
3152
|
const startMeasure = useCallback(() => {
|
|
3074
3153
|
cleanup();
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3154
|
+
dispatch({
|
|
3155
|
+
type: "SET_MEASURE",
|
|
3156
|
+
measure: { isActive: true, fromPoint: null, result: null }
|
|
3157
|
+
});
|
|
3079
3158
|
if (!state.chart) return;
|
|
3080
3159
|
state.chart.createOverlay({
|
|
3081
3160
|
name: "segment",
|
|
@@ -3111,23 +3190,25 @@ function useMeasure() {
|
|
|
3111
3190
|
};
|
|
3112
3191
|
const from = makePoint(points[0]);
|
|
3113
3192
|
const to = makePoint(points[1]);
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3193
|
+
dispatch({
|
|
3194
|
+
type: "SET_MEASURE",
|
|
3195
|
+
measure: {
|
|
3196
|
+
isActive: false,
|
|
3197
|
+
fromPoint: from,
|
|
3198
|
+
result: computeResult(from, to)
|
|
3199
|
+
}
|
|
3200
|
+
});
|
|
3117
3201
|
}
|
|
3118
3202
|
});
|
|
3119
|
-
}, [state.chart, cleanup,
|
|
3203
|
+
}, [state.chart, cleanup, dispatch]);
|
|
3120
3204
|
const cancelMeasure = useCallback(() => {
|
|
3121
3205
|
cleanup();
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
clickCountRef.current = 0;
|
|
3125
|
-
}, [cleanup]);
|
|
3206
|
+
dispatch({ type: "SET_MEASURE", measure: { isActive: false, fromPoint: null } });
|
|
3207
|
+
}, [cleanup, dispatch]);
|
|
3126
3208
|
const clearResult = useCallback(() => {
|
|
3127
3209
|
cleanup();
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
}, [cleanup]);
|
|
3210
|
+
dispatch({ type: "SET_MEASURE", measure: { result: null, fromPoint: null } });
|
|
3211
|
+
}, [cleanup, dispatch]);
|
|
3131
3212
|
useEffect(() => {
|
|
3132
3213
|
return () => {
|
|
3133
3214
|
state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
|