react-klinecharts-ui 0.2.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 +244 -5
- package/dist/{chunk-47JXSAIT.cjs → chunk-PIFLJKYF.cjs} +47 -2
- package/dist/chunk-PIFLJKYF.cjs.map +1 -0
- package/dist/{chunk-B3NZ66YA.js → chunk-U325AHHX.js} +47 -3
- package/dist/chunk-U325AHHX.js.map +1 -0
- package/dist/extensions.cjs +9 -5
- package/dist/extensions.d.cts +18 -1
- package/dist/extensions.d.ts +18 -1
- package/dist/extensions.js +1 -1
- package/dist/index.cjs +925 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +190 -2
- package/dist/index.d.ts +190 -2
- package/dist/index.js +871 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-47JXSAIT.cjs.map +0 -1
- package/dist/chunk-B3NZ66YA.js.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');
|
|
@@ -177,7 +177,7 @@ function KlinechartsUIProvider({
|
|
|
177
177
|
const extraOverlaysRef = react.useRef(extraOverlays);
|
|
178
178
|
react.useEffect(() => {
|
|
179
179
|
if (shouldRegister) {
|
|
180
|
-
|
|
180
|
+
chunkPIFLJKYF_cjs.registerExtensions();
|
|
181
181
|
}
|
|
182
182
|
extraOverlaysRef.current?.forEach((overlay) => reactKlinecharts.registerOverlay(overlay));
|
|
183
183
|
}, [shouldRegister]);
|
|
@@ -644,9 +644,12 @@ var INDICATOR_PARAMS = {
|
|
|
644
644
|
};
|
|
645
645
|
|
|
646
646
|
// src/hooks/useIndicators.ts
|
|
647
|
+
var COLLAPSED_HEIGHT = 30;
|
|
647
648
|
function useIndicators() {
|
|
648
649
|
const { state, dispatch } = useKlinechartsUI();
|
|
649
650
|
const { undoRedoListenerRef } = useKlinechartsUIDispatch();
|
|
651
|
+
const paneHeightsRef = react.useRef({});
|
|
652
|
+
const collapsedPanesRef = react.useRef(/* @__PURE__ */ new Set());
|
|
650
653
|
const mainIndicators = react.useMemo(() => {
|
|
651
654
|
const activeNames = state.mainIndicators;
|
|
652
655
|
const inactive = MAIN_INDICATORS.filter((n) => !activeNames.includes(n));
|
|
@@ -816,6 +819,90 @@ function useIndicators() {
|
|
|
816
819
|
(name) => name in state.subIndicators,
|
|
817
820
|
[state.subIndicators]
|
|
818
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
|
+
);
|
|
819
906
|
return {
|
|
820
907
|
mainIndicators,
|
|
821
908
|
subIndicators,
|
|
@@ -835,7 +922,11 @@ function useIndicators() {
|
|
|
835
922
|
updateIndicatorParams,
|
|
836
923
|
getIndicatorParams,
|
|
837
924
|
isMainIndicatorActive,
|
|
838
|
-
isSubIndicatorActive
|
|
925
|
+
isSubIndicatorActive,
|
|
926
|
+
collapseSubIndicator,
|
|
927
|
+
expandSubIndicator,
|
|
928
|
+
isSubIndicatorCollapsed,
|
|
929
|
+
reorderSubIndicator
|
|
839
930
|
};
|
|
840
931
|
}
|
|
841
932
|
|
|
@@ -934,6 +1025,17 @@ function useDrawingTools() {
|
|
|
934
1025
|
const [magnetMode, setMagnetModeState] = react.useState("normal");
|
|
935
1026
|
const [isLocked, setIsLocked] = react.useState(false);
|
|
936
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;
|
|
937
1039
|
const categories = react.useMemo(
|
|
938
1040
|
() => DRAWING_CATEGORIES.map((cat) => ({
|
|
939
1041
|
key: cat.key,
|
|
@@ -944,15 +1046,15 @@ function useDrawingTools() {
|
|
|
944
1046
|
})),
|
|
945
1047
|
[]
|
|
946
1048
|
);
|
|
947
|
-
const
|
|
1049
|
+
const createOverlayForTool = react.useCallback(
|
|
948
1050
|
(name) => {
|
|
949
|
-
|
|
1051
|
+
const mode = magnetModeRef.current === "strong" ? "strong_magnet" : magnetModeRef.current === "weak" ? "weak_magnet" : "normal";
|
|
950
1052
|
state.chart?.createOverlay({
|
|
951
1053
|
name,
|
|
952
1054
|
groupId: DRAWING_GROUP_ID,
|
|
953
|
-
lock:
|
|
954
|
-
visible:
|
|
955
|
-
mode
|
|
1055
|
+
lock: isLockedRef.current,
|
|
1056
|
+
visible: isVisibleRef.current,
|
|
1057
|
+
mode,
|
|
956
1058
|
onDrawEnd: (event) => {
|
|
957
1059
|
const o = event.overlay;
|
|
958
1060
|
undoRedoListenerRef.current?.({
|
|
@@ -967,10 +1069,22 @@ function useDrawingTools() {
|
|
|
967
1069
|
}
|
|
968
1070
|
}
|
|
969
1071
|
});
|
|
1072
|
+
if (autoRetriggerRef.current && activeToolRef.current === name) {
|
|
1073
|
+
requestAnimationFrame(() => {
|
|
1074
|
+
createOverlayForTool(name);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
970
1077
|
}
|
|
971
1078
|
});
|
|
972
1079
|
},
|
|
973
|
-
[state.chart,
|
|
1080
|
+
[state.chart, undoRedoListenerRef]
|
|
1081
|
+
);
|
|
1082
|
+
const selectTool = react.useCallback(
|
|
1083
|
+
(name) => {
|
|
1084
|
+
setActiveTool(name);
|
|
1085
|
+
createOverlayForTool(name);
|
|
1086
|
+
},
|
|
1087
|
+
[createOverlayForTool]
|
|
974
1088
|
);
|
|
975
1089
|
const clearActiveTool = react.useCallback(() => {
|
|
976
1090
|
setActiveTool(null);
|
|
@@ -1041,12 +1155,14 @@ function useDrawingTools() {
|
|
|
1041
1155
|
magnetMode,
|
|
1042
1156
|
isLocked,
|
|
1043
1157
|
isVisible,
|
|
1158
|
+
autoRetrigger,
|
|
1044
1159
|
selectTool,
|
|
1045
1160
|
clearActiveTool,
|
|
1046
1161
|
setMagnetMode,
|
|
1047
1162
|
toggleLock,
|
|
1048
1163
|
toggleVisibility,
|
|
1049
|
-
removeAllDrawings
|
|
1164
|
+
removeAllDrawings,
|
|
1165
|
+
setAutoRetrigger
|
|
1050
1166
|
};
|
|
1051
1167
|
}
|
|
1052
1168
|
|
|
@@ -2112,7 +2228,7 @@ ${code}`
|
|
|
2112
2228
|
];
|
|
2113
2229
|
const fn = new Function(...allArgs);
|
|
2114
2230
|
const shadowValues = SHADOW_KEYS.map(() => void 0);
|
|
2115
|
-
const result = fn(...shadowValues,
|
|
2231
|
+
const result = fn(...shadowValues, chunkPIFLJKYF_cjs.TA_default, dataList, parsedParams);
|
|
2116
2232
|
if (!Array.isArray(result)) {
|
|
2117
2233
|
throw new Error("Script must return an Array.");
|
|
2118
2234
|
}
|
|
@@ -2245,6 +2361,750 @@ ${code}`
|
|
|
2245
2361
|
defaultScript: DEFAULT_SCRIPT
|
|
2246
2362
|
};
|
|
2247
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
|
+
}
|
|
2248
3108
|
|
|
2249
3109
|
// src/utils/createDataLoader.ts
|
|
2250
3110
|
function createDataLoader(datafeed, dispatch) {
|
|
@@ -2312,171 +3172,175 @@ function createDataLoader(datafeed, dispatch) {
|
|
|
2312
3172
|
|
|
2313
3173
|
Object.defineProperty(exports, "TA", {
|
|
2314
3174
|
enumerable: true,
|
|
2315
|
-
get: function () { return
|
|
3175
|
+
get: function () { return chunkPIFLJKYF_cjs.TA_default; }
|
|
2316
3176
|
});
|
|
2317
3177
|
Object.defineProperty(exports, "abcd", {
|
|
2318
3178
|
enumerable: true,
|
|
2319
|
-
get: function () { return
|
|
3179
|
+
get: function () { return chunkPIFLJKYF_cjs.abcd_default; }
|
|
2320
3180
|
});
|
|
2321
3181
|
Object.defineProperty(exports, "anyWaves", {
|
|
2322
3182
|
enumerable: true,
|
|
2323
|
-
get: function () { return
|
|
3183
|
+
get: function () { return chunkPIFLJKYF_cjs.anyWaves_default; }
|
|
2324
3184
|
});
|
|
2325
3185
|
Object.defineProperty(exports, "arrow", {
|
|
2326
3186
|
enumerable: true,
|
|
2327
|
-
get: function () { return
|
|
3187
|
+
get: function () { return chunkPIFLJKYF_cjs.arrow_default; }
|
|
2328
3188
|
});
|
|
2329
3189
|
Object.defineProperty(exports, "bollTv", {
|
|
2330
3190
|
enumerable: true,
|
|
2331
|
-
get: function () { return
|
|
3191
|
+
get: function () { return chunkPIFLJKYF_cjs.bollTv_default; }
|
|
2332
3192
|
});
|
|
2333
3193
|
Object.defineProperty(exports, "brush", {
|
|
2334
3194
|
enumerable: true,
|
|
2335
|
-
get: function () { return
|
|
3195
|
+
get: function () { return chunkPIFLJKYF_cjs.brush_default; }
|
|
2336
3196
|
});
|
|
2337
3197
|
Object.defineProperty(exports, "cci", {
|
|
2338
3198
|
enumerable: true,
|
|
2339
|
-
get: function () { return
|
|
3199
|
+
get: function () { return chunkPIFLJKYF_cjs.cci_default; }
|
|
2340
3200
|
});
|
|
2341
3201
|
Object.defineProperty(exports, "circle", {
|
|
2342
3202
|
enumerable: true,
|
|
2343
|
-
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; }
|
|
2344
3208
|
});
|
|
2345
3209
|
Object.defineProperty(exports, "eightWaves", {
|
|
2346
3210
|
enumerable: true,
|
|
2347
|
-
get: function () { return
|
|
3211
|
+
get: function () { return chunkPIFLJKYF_cjs.eightWaves_default; }
|
|
2348
3212
|
});
|
|
2349
3213
|
Object.defineProperty(exports, "elliottWave", {
|
|
2350
3214
|
enumerable: true,
|
|
2351
|
-
get: function () { return
|
|
3215
|
+
get: function () { return chunkPIFLJKYF_cjs.elliottWave_default; }
|
|
2352
3216
|
});
|
|
2353
3217
|
Object.defineProperty(exports, "fibRetracement", {
|
|
2354
3218
|
enumerable: true,
|
|
2355
|
-
get: function () { return
|
|
3219
|
+
get: function () { return chunkPIFLJKYF_cjs.fibRetracement_default; }
|
|
2356
3220
|
});
|
|
2357
3221
|
Object.defineProperty(exports, "fibonacciCircle", {
|
|
2358
3222
|
enumerable: true,
|
|
2359
|
-
get: function () { return
|
|
3223
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciCircle_default; }
|
|
2360
3224
|
});
|
|
2361
3225
|
Object.defineProperty(exports, "fibonacciExtension", {
|
|
2362
3226
|
enumerable: true,
|
|
2363
|
-
get: function () { return
|
|
3227
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciExtension_default; }
|
|
2364
3228
|
});
|
|
2365
3229
|
Object.defineProperty(exports, "fibonacciSegment", {
|
|
2366
3230
|
enumerable: true,
|
|
2367
|
-
get: function () { return
|
|
3231
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciSegment_default; }
|
|
2368
3232
|
});
|
|
2369
3233
|
Object.defineProperty(exports, "fibonacciSpeedResistanceFan", {
|
|
2370
3234
|
enumerable: true,
|
|
2371
|
-
get: function () { return
|
|
3235
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciSpeedResistanceFan_default; }
|
|
2372
3236
|
});
|
|
2373
3237
|
Object.defineProperty(exports, "fibonacciSpiral", {
|
|
2374
3238
|
enumerable: true,
|
|
2375
|
-
get: function () { return
|
|
3239
|
+
get: function () { return chunkPIFLJKYF_cjs.fibonacciSpiral_default; }
|
|
2376
3240
|
});
|
|
2377
3241
|
Object.defineProperty(exports, "fiveWaves", {
|
|
2378
3242
|
enumerable: true,
|
|
2379
|
-
get: function () { return
|
|
3243
|
+
get: function () { return chunkPIFLJKYF_cjs.fiveWaves_default; }
|
|
2380
3244
|
});
|
|
2381
3245
|
Object.defineProperty(exports, "gannBox", {
|
|
2382
3246
|
enumerable: true,
|
|
2383
|
-
get: function () { return
|
|
3247
|
+
get: function () { return chunkPIFLJKYF_cjs.gannBox_default; }
|
|
2384
3248
|
});
|
|
2385
3249
|
Object.defineProperty(exports, "gannFan", {
|
|
2386
3250
|
enumerable: true,
|
|
2387
|
-
get: function () { return
|
|
3251
|
+
get: function () { return chunkPIFLJKYF_cjs.gannFan_default; }
|
|
2388
3252
|
});
|
|
2389
3253
|
Object.defineProperty(exports, "hma", {
|
|
2390
3254
|
enumerable: true,
|
|
2391
|
-
get: function () { return
|
|
3255
|
+
get: function () { return chunkPIFLJKYF_cjs.hma_default; }
|
|
2392
3256
|
});
|
|
2393
3257
|
Object.defineProperty(exports, "ichimoku", {
|
|
2394
3258
|
enumerable: true,
|
|
2395
|
-
get: function () { return
|
|
3259
|
+
get: function () { return chunkPIFLJKYF_cjs.ichimoku_default; }
|
|
2396
3260
|
});
|
|
2397
3261
|
Object.defineProperty(exports, "indicators", {
|
|
2398
3262
|
enumerable: true,
|
|
2399
|
-
get: function () { return
|
|
3263
|
+
get: function () { return chunkPIFLJKYF_cjs.indicators; }
|
|
2400
3264
|
});
|
|
2401
3265
|
Object.defineProperty(exports, "longPosition", {
|
|
2402
3266
|
enumerable: true,
|
|
2403
|
-
get: function () { return
|
|
3267
|
+
get: function () { return chunkPIFLJKYF_cjs.longPosition_default; }
|
|
2404
3268
|
});
|
|
2405
3269
|
Object.defineProperty(exports, "maRibbon", {
|
|
2406
3270
|
enumerable: true,
|
|
2407
|
-
get: function () { return
|
|
3271
|
+
get: function () { return chunkPIFLJKYF_cjs.maRibbon_default; }
|
|
2408
3272
|
});
|
|
2409
3273
|
Object.defineProperty(exports, "macdTv", {
|
|
2410
3274
|
enumerable: true,
|
|
2411
|
-
get: function () { return
|
|
3275
|
+
get: function () { return chunkPIFLJKYF_cjs.macdTv_default; }
|
|
2412
3276
|
});
|
|
2413
3277
|
Object.defineProperty(exports, "measure", {
|
|
2414
3278
|
enumerable: true,
|
|
2415
|
-
get: function () { return
|
|
3279
|
+
get: function () { return chunkPIFLJKYF_cjs.measure_default; }
|
|
2416
3280
|
});
|
|
2417
3281
|
Object.defineProperty(exports, "orderLine", {
|
|
2418
3282
|
enumerable: true,
|
|
2419
|
-
get: function () { return
|
|
3283
|
+
get: function () { return chunkPIFLJKYF_cjs.orderLine_default; }
|
|
2420
3284
|
});
|
|
2421
3285
|
Object.defineProperty(exports, "overlays", {
|
|
2422
3286
|
enumerable: true,
|
|
2423
|
-
get: function () { return
|
|
3287
|
+
get: function () { return chunkPIFLJKYF_cjs.overlays; }
|
|
2424
3288
|
});
|
|
2425
3289
|
Object.defineProperty(exports, "parallelChannel", {
|
|
2426
3290
|
enumerable: true,
|
|
2427
|
-
get: function () { return
|
|
3291
|
+
get: function () { return chunkPIFLJKYF_cjs.parallelChannel_default; }
|
|
2428
3292
|
});
|
|
2429
3293
|
Object.defineProperty(exports, "parallelogram", {
|
|
2430
3294
|
enumerable: true,
|
|
2431
|
-
get: function () { return
|
|
3295
|
+
get: function () { return chunkPIFLJKYF_cjs.parallelogram_default; }
|
|
2432
3296
|
});
|
|
2433
3297
|
Object.defineProperty(exports, "pivotPoints", {
|
|
2434
3298
|
enumerable: true,
|
|
2435
|
-
get: function () { return
|
|
3299
|
+
get: function () { return chunkPIFLJKYF_cjs.pivotPoints_default; }
|
|
2436
3300
|
});
|
|
2437
3301
|
Object.defineProperty(exports, "ray", {
|
|
2438
3302
|
enumerable: true,
|
|
2439
|
-
get: function () { return
|
|
3303
|
+
get: function () { return chunkPIFLJKYF_cjs.ray_default; }
|
|
2440
3304
|
});
|
|
2441
3305
|
Object.defineProperty(exports, "rect", {
|
|
2442
3306
|
enumerable: true,
|
|
2443
|
-
get: function () { return
|
|
3307
|
+
get: function () { return chunkPIFLJKYF_cjs.rect_default; }
|
|
2444
3308
|
});
|
|
2445
3309
|
Object.defineProperty(exports, "registerExtensions", {
|
|
2446
3310
|
enumerable: true,
|
|
2447
|
-
get: function () { return
|
|
3311
|
+
get: function () { return chunkPIFLJKYF_cjs.registerExtensions; }
|
|
2448
3312
|
});
|
|
2449
3313
|
Object.defineProperty(exports, "rsiTv", {
|
|
2450
3314
|
enumerable: true,
|
|
2451
|
-
get: function () { return
|
|
3315
|
+
get: function () { return chunkPIFLJKYF_cjs.rsiTv_default; }
|
|
2452
3316
|
});
|
|
2453
3317
|
Object.defineProperty(exports, "shortPosition", {
|
|
2454
3318
|
enumerable: true,
|
|
2455
|
-
get: function () { return
|
|
3319
|
+
get: function () { return chunkPIFLJKYF_cjs.shortPosition_default; }
|
|
2456
3320
|
});
|
|
2457
3321
|
Object.defineProperty(exports, "stochastic", {
|
|
2458
3322
|
enumerable: true,
|
|
2459
|
-
get: function () { return
|
|
3323
|
+
get: function () { return chunkPIFLJKYF_cjs.stochastic_default; }
|
|
2460
3324
|
});
|
|
2461
3325
|
Object.defineProperty(exports, "superTrend", {
|
|
2462
3326
|
enumerable: true,
|
|
2463
|
-
get: function () { return
|
|
3327
|
+
get: function () { return chunkPIFLJKYF_cjs.superTrend_default; }
|
|
2464
3328
|
});
|
|
2465
3329
|
Object.defineProperty(exports, "threeWaves", {
|
|
2466
3330
|
enumerable: true,
|
|
2467
|
-
get: function () { return
|
|
3331
|
+
get: function () { return chunkPIFLJKYF_cjs.threeWaves_default; }
|
|
2468
3332
|
});
|
|
2469
3333
|
Object.defineProperty(exports, "triangle", {
|
|
2470
3334
|
enumerable: true,
|
|
2471
|
-
get: function () { return
|
|
3335
|
+
get: function () { return chunkPIFLJKYF_cjs.triangle_default; }
|
|
2472
3336
|
});
|
|
2473
3337
|
Object.defineProperty(exports, "vwap", {
|
|
2474
3338
|
enumerable: true,
|
|
2475
|
-
get: function () { return
|
|
3339
|
+
get: function () { return chunkPIFLJKYF_cjs.vwap_default; }
|
|
2476
3340
|
});
|
|
2477
3341
|
Object.defineProperty(exports, "xabcd", {
|
|
2478
3342
|
enumerable: true,
|
|
2479
|
-
get: function () { return
|
|
3343
|
+
get: function () { return chunkPIFLJKYF_cjs.xabcd_default; }
|
|
2480
3344
|
});
|
|
2481
3345
|
exports.CANDLE_TYPES = CANDLE_TYPES;
|
|
2482
3346
|
exports.COMPARE_RULES = COMPARE_RULES;
|
|
@@ -2493,6 +3357,11 @@ exports.TIMEZONES = TIMEZONES;
|
|
|
2493
3357
|
exports.TOOLTIP_SHOW_RULES = TOOLTIP_SHOW_RULES;
|
|
2494
3358
|
exports.YAXIS_POSITIONS = YAXIS_POSITIONS;
|
|
2495
3359
|
exports.createDataLoader = createDataLoader;
|
|
3360
|
+
exports.useAlerts = useAlerts;
|
|
3361
|
+
exports.useAnnotations = useAnnotations;
|
|
3362
|
+
exports.useCompare = useCompare;
|
|
3363
|
+
exports.useCrosshair = useCrosshair;
|
|
3364
|
+
exports.useDataExport = useDataExport;
|
|
2496
3365
|
exports.useDrawingTools = useDrawingTools;
|
|
2497
3366
|
exports.useFullscreen = useFullscreen;
|
|
2498
3367
|
exports.useIndicators = useIndicators;
|
|
@@ -2501,12 +3370,15 @@ exports.useKlinechartsUILoading = useKlinechartsUILoading;
|
|
|
2501
3370
|
exports.useKlinechartsUISettings = useKlinechartsUISettings;
|
|
2502
3371
|
exports.useKlinechartsUITheme = useKlinechartsUITheme;
|
|
2503
3372
|
exports.useLayoutManager = useLayoutManager;
|
|
3373
|
+
exports.useMeasure = useMeasure;
|
|
2504
3374
|
exports.useOrderLines = useOrderLines;
|
|
2505
3375
|
exports.usePeriods = usePeriods;
|
|
3376
|
+
exports.useReplay = useReplay;
|
|
2506
3377
|
exports.useScreenshot = useScreenshot;
|
|
2507
3378
|
exports.useScriptEditor = useScriptEditor;
|
|
2508
3379
|
exports.useSymbolSearch = useSymbolSearch;
|
|
2509
3380
|
exports.useTimezone = useTimezone;
|
|
2510
3381
|
exports.useUndoRedo = useUndoRedo;
|
|
3382
|
+
exports.useWatchlist = useWatchlist;
|
|
2511
3383
|
//# sourceMappingURL=index.cjs.map
|
|
2512
3384
|
//# sourceMappingURL=index.cjs.map
|