chart-library-native 1.0.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.
@@ -0,0 +1,60 @@
1
+ // ============================================================================
2
+ // 📄 bindings/react-native/src/api/tier1/useQuickMACD.ts
3
+ // Tier 1 API: MACD (Standard settings)
4
+ // ============================================================================
5
+
6
+ import { useMACD } from '../../hooks/useIndicators';
7
+
8
+ export interface UseQuickMACDResult {
9
+ macd: (number | null)[];
10
+ signal: (number | null)[];
11
+ histogram: (number | null)[];
12
+ loading: boolean;
13
+ error?: string;
14
+ currentSignal: {
15
+ macdValue: number | null;
16
+ signalValue: number | null;
17
+ histogramValue: number | null;
18
+ signal: 'buy' | 'sell' | 'neutral';
19
+ };
20
+ status: 'success' | 'error';
21
+ }
22
+
23
+ /**
24
+ * Quick MACD Hook - Industry standard settings
25
+ *
26
+ * Default: fast=12, slow=26, signal=9 (most common)
27
+ *
28
+ * @param closes - Array of close prices
29
+ * @returns { macd, signal, histogram, currentSignal, loading, error, status }
30
+ */
31
+ export const useQuickMACD = (closes: number[]): UseQuickMACDResult => {
32
+ const { macd, signal, histogram, loading, status, error } = useMACD(closes, {
33
+ fast: 12,
34
+ slow: 26,
35
+ signal: 9
36
+ });
37
+
38
+ // Calculate trade signal
39
+ const validMacd = macd.filter(v => v !== null && Number.isFinite(v)) as number[];
40
+ const validSignal = signal.filter(v => v !== null && Number.isFinite(v)) as number[];
41
+ const validHist = histogram.filter(v => v !== null && Number.isFinite(v)) as number[];
42
+
43
+ const currentSignal = {
44
+ macdValue: validMacd.length > 0 ? validMacd[validMacd.length - 1] : null,
45
+ signalValue: validSignal.length > 0 ? validSignal[validSignal.length - 1] : null,
46
+ histogramValue: validHist.length > 0 ? validHist[validHist.length - 1] : null,
47
+ signal: ((): 'buy' | 'sell' | 'neutral' => {
48
+ if (validMacd.length === 0 || validSignal.length === 0 || validHist.length === 0) {
49
+ return 'neutral';
50
+ }
51
+ const h = validHist[validHist.length - 1];
52
+ const m = validMacd[validMacd.length - 1];
53
+ const s = validSignal[validSignal.length - 1];
54
+ return m > s && h > 0 ? 'buy' : m < s && h < 0 ? 'sell' : 'neutral';
55
+ })()
56
+ };
57
+
58
+ return { macd, signal, histogram, loading, error, currentSignal, status };
59
+ };
60
+
@@ -0,0 +1,38 @@
1
+ // ============================================================================
2
+ // 📄 bindings/react-native/src/api/tier1/useQuickRSI.ts
3
+ // Tier 1 API: RSI (Relative Strength Index)
4
+ // ============================================================================
5
+
6
+ import { useRSI } from '../../hooks/useIndicators';
7
+
8
+ export interface UseQuickRSIResult {
9
+ data: (number | null)[];
10
+ loading: boolean;
11
+ error?: string;
12
+ currentLevel: {
13
+ value: number | null;
14
+ level: 'overbought' | 'oversold' | 'neutral';
15
+ };
16
+ status: 'success' | 'error';
17
+ }
18
+
19
+ /**
20
+ * Quick RSI Hook - Standard RSI14
21
+ *
22
+ * @param closes - Array of close prices
23
+ * @returns { data, loading, error, currentLevel, status }
24
+ */
25
+ export const useQuickRSI = (closes: number[]): UseQuickRSIResult => {
26
+ const { data, loading, status, error } = useRSI(closes, { period: 14 });
27
+
28
+ const validData = data.filter(v => v !== null && Number.isFinite(v)) as number[];
29
+ const value = validData.length > 0 ? validData[validData.length - 1] : null;
30
+
31
+ const currentLevel = {
32
+ value,
33
+ level: (value === null ? 'neutral' : value > 70 ? 'overbought' : value < 30 ? 'oversold' : 'neutral') as 'overbought' | 'oversold' | 'neutral'
34
+ };
35
+
36
+ return { data, loading, error, currentLevel, status };
37
+ };
38
+
@@ -0,0 +1,135 @@
1
+ // ============================================================================
2
+ // 📄 bindings/react-native/src/helpers.cpp
3
+ // Mục đích: Helper functions implementation for JS <-> C++ type conversion
4
+ // ============================================================================
5
+
6
+ #include "helpers.h"
7
+ #include <cmath>
8
+
9
+ using namespace facebook;
10
+
11
+ namespace chart {
12
+ namespace reactnative {
13
+ namespace helpers {
14
+
15
+ // ======================================================================
16
+ // Convert JavaScript Array to C++ vector<double>
17
+ // ======================================================================
18
+ std::vector<double> jsArrayToVector(jsi::Runtime& runtime, const jsi::Value& array) {
19
+ if (!array.isObject()) {
20
+ throw jsi::JSError(runtime, "Expected array");
21
+ }
22
+
23
+ jsi::Object obj = array.asObject(runtime);
24
+ if (!obj.isArray(runtime)) {
25
+ throw jsi::JSError(runtime, "Expected array");
26
+ }
27
+
28
+ jsi::Array jsArray = obj.getArray(runtime);
29
+ size_t length = jsArray.length(runtime);
30
+
31
+ std::vector<double> result;
32
+ result.reserve(length);
33
+
34
+ for (size_t i = 0; i < length; i++) {
35
+ jsi::Value element = jsArray.getValueAtIndex(runtime, i);
36
+ if (element.isNumber()) {
37
+ result.push_back(element.asNumber());
38
+ } else if (element.isNull() || (element.isNumber() && std::isnan(element.asNumber()))) {
39
+ result.push_back(std::nan(""));
40
+ } else {
41
+ throw jsi::JSError(runtime, "Array element must be a number");
42
+ }
43
+ }
44
+
45
+ return result;
46
+ }
47
+
48
+ // ======================================================================
49
+ // Convert C++ vector<double> to JavaScript Array
50
+ // ======================================================================
51
+ jsi::Array vectorToJSArray(jsi::Runtime& runtime, const std::vector<double>& vec) {
52
+ jsi::Array result = jsi::Array(runtime, vec.size());
53
+
54
+ for (size_t i = 0; i < vec.size(); i++) {
55
+ if (std::isnan(vec[i])) {
56
+ result.setValueAtIndex(runtime, i, jsi::Value::null());
57
+ } else {
58
+ result.setValueAtIndex(runtime, i, jsi::Value(vec[i]));
59
+ }
60
+ }
61
+
62
+ return result;
63
+ }
64
+
65
+ // ======================================================================
66
+ // Convert MACDResult to JavaScript Object
67
+ // ======================================================================
68
+ jsi::Object macdResultToJSObject(jsi::Runtime& runtime, const common::MACDResult& result) {
69
+ jsi::Object obj = jsi::Object(runtime);
70
+ obj.setProperty(runtime, "macd", vectorToJSArray(runtime, result.macd));
71
+ obj.setProperty(runtime, "signal", vectorToJSArray(runtime, result.signal));
72
+ obj.setProperty(runtime, "histogram", vectorToJSArray(runtime, result.histogram));
73
+ return obj;
74
+ }
75
+
76
+ // ======================================================================
77
+ // Convert BollingerResult to JavaScript Object
78
+ // ======================================================================
79
+ jsi::Object bollingerResultToJSObject(jsi::Runtime& runtime, const common::BollingerResult& result) {
80
+ jsi::Object obj = jsi::Object(runtime);
81
+ obj.setProperty(runtime, "upper", vectorToJSArray(runtime, result.upper));
82
+ obj.setProperty(runtime, "mid", vectorToJSArray(runtime, result.mid));
83
+ obj.setProperty(runtime, "lower", vectorToJSArray(runtime, result.lower));
84
+ return obj;
85
+ }
86
+
87
+ // ======================================================================
88
+ // Convert KDJResult to JavaScript Object
89
+ // ======================================================================
90
+ jsi::Object kdjResultToJSObject(jsi::Runtime& runtime, const common::KDJResult& result) {
91
+ jsi::Object obj = jsi::Object(runtime);
92
+ obj.setProperty(runtime, "k", vectorToJSArray(runtime, result.k));
93
+ obj.setProperty(runtime, "d", vectorToJSArray(runtime, result.d));
94
+ obj.setProperty(runtime, "j", vectorToJSArray(runtime, result.j));
95
+ return obj;
96
+ }
97
+
98
+ // ======================================================================
99
+ // Convert DMIResult to JavaScript Object
100
+ // ======================================================================
101
+ jsi::Object dmiResultToJSObject(jsi::Runtime& runtime, const common::DMIResult& result) {
102
+ jsi::Object obj = jsi::Object(runtime);
103
+ obj.setProperty(runtime, "plusDI", vectorToJSArray(runtime, result.plusDI));
104
+ obj.setProperty(runtime, "minusDI", vectorToJSArray(runtime, result.minusDI));
105
+ obj.setProperty(runtime, "adx", vectorToJSArray(runtime, result.adx));
106
+ return obj;
107
+ }
108
+
109
+ // ======================================================================
110
+ // Convert IchimokuResult to JavaScript Object
111
+ // ======================================================================
112
+ jsi::Object ichimokuResultToJSObject(jsi::Runtime& runtime, const common::IchimokuResult& result) {
113
+ jsi::Object obj = jsi::Object(runtime);
114
+ obj.setProperty(runtime, "tenkan", vectorToJSArray(runtime, result.tenkan));
115
+ obj.setProperty(runtime, "kijun", vectorToJSArray(runtime, result.kijun));
116
+ obj.setProperty(runtime, "senkouA", vectorToJSArray(runtime, result.senkouA));
117
+ obj.setProperty(runtime, "senkouB", vectorToJSArray(runtime, result.senkouB));
118
+ obj.setProperty(runtime, "chikou", vectorToJSArray(runtime, result.chikou));
119
+ return obj;
120
+ }
121
+
122
+ // ======================================================================
123
+ // Convert StochasticResult to JavaScript Object
124
+ // ======================================================================
125
+ jsi::Object stochasticResultToJSObject(jsi::Runtime& runtime, const common::StochasticResult& result) {
126
+ jsi::Object obj = jsi::Object(runtime);
127
+ obj.setProperty(runtime, "k", vectorToJSArray(runtime, result.k));
128
+ obj.setProperty(runtime, "d", vectorToJSArray(runtime, result.d));
129
+ return obj;
130
+ }
131
+
132
+ } // namespace helpers
133
+ } // namespace reactnative
134
+ } // namespace chart
135
+
package/src/helpers.h ADDED
@@ -0,0 +1,65 @@
1
+ // ============================================================================
2
+ // 📄 bindings/react-native/src/helpers.h
3
+ // Mục đích: Helper functions for JS <-> C++ type conversion
4
+ // ============================================================================
5
+
6
+ #ifndef HELPERS_H
7
+ #define HELPERS_H
8
+
9
+ #include <jsi/jsi.h>
10
+ #include <vector>
11
+ #include "../../core/indicators/indicators.h"
12
+ #include "../../core/common/types.h"
13
+
14
+ using namespace facebook;
15
+
16
+ namespace chart {
17
+ namespace reactnative {
18
+ namespace helpers {
19
+
20
+ // ======================================================================
21
+ // Convert JavaScript Array to C++ vector<double>
22
+ // ======================================================================
23
+ std::vector<double> jsArrayToVector(jsi::Runtime& runtime, const jsi::Value& array);
24
+
25
+ // ======================================================================
26
+ // Convert C++ vector<double> to JavaScript Array
27
+ // ======================================================================
28
+ jsi::Array vectorToJSArray(jsi::Runtime& runtime, const std::vector<double>& vec);
29
+
30
+ // ======================================================================
31
+ // Convert MACDResult to JavaScript Object
32
+ // ======================================================================
33
+ jsi::Object macdResultToJSObject(jsi::Runtime& runtime, const common::MACDResult& result);
34
+
35
+ // ======================================================================
36
+ // Convert BollingerResult to JavaScript Object
37
+ // ======================================================================
38
+ jsi::Object bollingerResultToJSObject(jsi::Runtime& runtime, const common::BollingerResult& result);
39
+
40
+ // ======================================================================
41
+ // Convert KDJResult to JavaScript Object
42
+ // ======================================================================
43
+ jsi::Object kdjResultToJSObject(jsi::Runtime& runtime, const common::KDJResult& result);
44
+
45
+ // ======================================================================
46
+ // Convert DMIResult to JavaScript Object
47
+ // ======================================================================
48
+ jsi::Object dmiResultToJSObject(jsi::Runtime& runtime, const common::DMIResult& result);
49
+
50
+ // ======================================================================
51
+ // Convert IchimokuResult to JavaScript Object
52
+ // ======================================================================
53
+ jsi::Object ichimokuResultToJSObject(jsi::Runtime& runtime, const common::IchimokuResult& result);
54
+
55
+ // ======================================================================
56
+ // Convert StochasticResult to JavaScript Object
57
+ // ======================================================================
58
+ jsi::Object stochasticResultToJSObject(jsi::Runtime& runtime, const common::StochasticResult& result);
59
+
60
+ } // namespace helpers
61
+ } // namespace reactnative
62
+ } // namespace chart
63
+
64
+ #endif // HELPERS_H
65
+
@@ -0,0 +1,388 @@
1
+ // ============================================================================
2
+ // 📄 bindings/react-native/src/hooks/useIndicators.ts
3
+ // Tier 2: Advanced hooks with full customization
4
+ // ============================================================================
5
+
6
+ import { useState, useEffect, useMemo } from 'react';
7
+ import ChartLibrary from '../../index';
8
+ import type { CandleData, MACDResult, KDJResult, BOLLResult, DMIResult } from '../types';
9
+
10
+ // ============================================================================
11
+ // MA Hook (Tier 2)
12
+ // ============================================================================
13
+
14
+ export interface MAConfig {
15
+ period: number;
16
+ type: 'simple' | 'exponential' | 'weighted';
17
+ }
18
+
19
+ export const useMA = (closes: number[], config: MAConfig) => {
20
+ const [data, setData] = useState<(number | null)[]>([]);
21
+ const [loading, setLoading] = useState(true);
22
+ const [error, setError] = useState<string | undefined>();
23
+ const [status, setStatus] = useState<'success' | 'error'>('success');
24
+
25
+ useEffect(() => {
26
+ if (!closes || closes.length === 0) {
27
+ setData([]);
28
+ setLoading(false);
29
+ return;
30
+ }
31
+
32
+ try {
33
+ setLoading(true);
34
+ let result: (number | null)[];
35
+
36
+ if (config.type === 'simple') {
37
+ result = ChartLibrary.calculateMA(closes, config.period);
38
+ } else if (config.type === 'exponential') {
39
+ result = ChartLibrary.calculateEMA(closes, config.period);
40
+ } else {
41
+ // Weighted - fallback to simple for now
42
+ result = ChartLibrary.calculateMA(closes, config.period);
43
+ }
44
+
45
+ setData(result);
46
+ setStatus('success');
47
+ setError(undefined);
48
+ } catch (err) {
49
+ setError(err instanceof Error ? err.message : 'Unknown error');
50
+ setStatus('error');
51
+ setData([]);
52
+ } finally {
53
+ setLoading(false);
54
+ }
55
+ }, [closes, config.period, config.type]);
56
+
57
+ return { data, loading, error, status };
58
+ };
59
+
60
+ // ============================================================================
61
+ // EMA Hook (Tier 2)
62
+ // ============================================================================
63
+
64
+ export interface EMAConfig {
65
+ period: number;
66
+ type: 'exponential';
67
+ }
68
+
69
+ export const useEMA = (closes: number[], config: EMAConfig) => {
70
+ const [data, setData] = useState<(number | null)[]>([]);
71
+ const [loading, setLoading] = useState(true);
72
+ const [error, setError] = useState<string | undefined>();
73
+ const [status, setStatus] = useState<'success' | 'error'>('success');
74
+
75
+ useEffect(() => {
76
+ if (!closes || closes.length === 0) {
77
+ setData([]);
78
+ setLoading(false);
79
+ return;
80
+ }
81
+
82
+ try {
83
+ setLoading(true);
84
+ const result = ChartLibrary.calculateEMA(closes, config.period);
85
+ setData(result);
86
+ setStatus('success');
87
+ setError(undefined);
88
+ } catch (err) {
89
+ setError(err instanceof Error ? err.message : 'Unknown error');
90
+ setStatus('error');
91
+ setData([]);
92
+ } finally {
93
+ setLoading(false);
94
+ }
95
+ }, [closes, config.period]);
96
+
97
+ return { data, loading, error, status };
98
+ };
99
+
100
+ // ============================================================================
101
+ // WMA Hook (Tier 2) - Placeholder
102
+ // ============================================================================
103
+
104
+ export interface WMAConfig {
105
+ period: number;
106
+ type: 'weighted';
107
+ }
108
+
109
+ export const useWMA = (closes: number[], config: WMAConfig) => {
110
+ // WMA not yet in C API, fallback to MA
111
+ return useMA(closes, { period: config.period, type: 'simple' });
112
+ };
113
+
114
+ // ============================================================================
115
+ // MACD Hook (Tier 2)
116
+ // ============================================================================
117
+
118
+ export interface MACDConfig {
119
+ fast: number;
120
+ slow: number;
121
+ signal: number;
122
+ }
123
+
124
+ export const useMACD = (closes: number[], config: MACDConfig): MACDResult => {
125
+ const [macd, setMacd] = useState<(number | null)[]>([]);
126
+ const [signal, setSignal] = useState<(number | null)[]>([]);
127
+ const [histogram, setHistogram] = useState<(number | null)[]>([]);
128
+ const [loading, setLoading] = useState(true);
129
+ const [error, setError] = useState<string | undefined>();
130
+ const [status, setStatus] = useState<'success' | 'error'>('success');
131
+
132
+ useEffect(() => {
133
+ if (!closes || closes.length === 0) {
134
+ setMacd([]);
135
+ setSignal([]);
136
+ setHistogram([]);
137
+ setLoading(false);
138
+ return;
139
+ }
140
+
141
+ try {
142
+ setLoading(true);
143
+ const result = ChartLibrary.calculateMACD(closes, config.fast, config.slow, config.signal);
144
+ setMacd(result.macd);
145
+ setSignal(result.signal);
146
+ setHistogram(result.histogram);
147
+ setStatus('success');
148
+ setError(undefined);
149
+ } catch (err) {
150
+ setError(err instanceof Error ? err.message : 'Unknown error');
151
+ setStatus('error');
152
+ setMacd([]);
153
+ setSignal([]);
154
+ setHistogram([]);
155
+ } finally {
156
+ setLoading(false);
157
+ }
158
+ }, [closes, config.fast, config.slow, config.signal]);
159
+
160
+ return { macd, signal, histogram, loading, error, status };
161
+ };
162
+
163
+ // ============================================================================
164
+ // RSI Hook (Tier 2)
165
+ // ============================================================================
166
+
167
+ export interface RSIConfig {
168
+ period: number;
169
+ }
170
+
171
+ export const useRSI = (closes: number[], config: RSIConfig) => {
172
+ const [data, setData] = useState<(number | null)[]>([]);
173
+ const [loading, setLoading] = useState(true);
174
+ const [error, setError] = useState<string | undefined>();
175
+ const [status, setStatus] = useState<'success' | 'error'>('success');
176
+
177
+ useEffect(() => {
178
+ if (!closes || closes.length === 0) {
179
+ setData([]);
180
+ setLoading(false);
181
+ return;
182
+ }
183
+
184
+ try {
185
+ setLoading(true);
186
+ const result = ChartLibrary.calculateRSI(closes, config.period);
187
+ setData(result);
188
+ setStatus('success');
189
+ setError(undefined);
190
+ } catch (err) {
191
+ setError(err instanceof Error ? err.message : 'Unknown error');
192
+ setStatus('error');
193
+ setData([]);
194
+ } finally {
195
+ setLoading(false);
196
+ }
197
+ }, [closes, config.period]);
198
+
199
+ return { data, loading, error, status };
200
+ };
201
+
202
+ // ============================================================================
203
+ // KDJ Hook (Tier 2)
204
+ // ============================================================================
205
+
206
+ export interface KDJConfig {
207
+ n: number;
208
+ m1: number;
209
+ m2: number;
210
+ }
211
+
212
+ export const useKDJ = (candleData: CandleData[], config: KDJConfig): KDJResult => {
213
+ const [k, setK] = useState<(number | null)[]>([]);
214
+ const [d, setD] = useState<(number | null)[]>([]);
215
+ const [j, setJ] = useState<(number | null)[]>([]);
216
+ const [loading, setLoading] = useState(true);
217
+ const [error, setError] = useState<string | undefined>();
218
+ const [status, setStatus] = useState<'success' | 'error'>('success');
219
+
220
+ useEffect(() => {
221
+ if (!candleData || candleData.length === 0) {
222
+ setK([]);
223
+ setD([]);
224
+ setJ([]);
225
+ setLoading(false);
226
+ return;
227
+ }
228
+
229
+ try {
230
+ setLoading(true);
231
+ const highs = candleData.map(c => c.high);
232
+ const lows = candleData.map(c => c.low);
233
+ const closes = candleData.map(c => c.close);
234
+
235
+ const result = ChartLibrary.calculateKDJ(highs, lows, closes, config.n, config.m1, config.m2);
236
+ setK(result.k);
237
+ setD(result.d);
238
+ setJ(result.j);
239
+ setStatus('success');
240
+ setError(undefined);
241
+ } catch (err) {
242
+ setError(err instanceof Error ? err.message : 'Unknown error');
243
+ setStatus('error');
244
+ setK([]);
245
+ setD([]);
246
+ setJ([]);
247
+ } finally {
248
+ setLoading(false);
249
+ }
250
+ }, [candleData, config.n, config.m1, config.m2]);
251
+
252
+ return { k, d, j, loading, error, status };
253
+ };
254
+
255
+ // ============================================================================
256
+ // Bollinger Bands Hook (Tier 2)
257
+ // ============================================================================
258
+
259
+ export interface BollingerConfig {
260
+ period: number;
261
+ k: number;
262
+ }
263
+
264
+ export const useBollinger = (closes: number[], config: BollingerConfig): BOLLResult => {
265
+ const [upper, setUpper] = useState<(number | null)[]>([]);
266
+ const [middle, setMiddle] = useState<(number | null)[]>([]);
267
+ const [lower, setLower] = useState<(number | null)[]>([]);
268
+ const [loading, setLoading] = useState(true);
269
+ const [error, setError] = useState<string | undefined>();
270
+ const [status, setStatus] = useState<'success' | 'error'>('success');
271
+
272
+ useEffect(() => {
273
+ if (!closes || closes.length === 0) {
274
+ setUpper([]);
275
+ setMiddle([]);
276
+ setLower([]);
277
+ setLoading(false);
278
+ return;
279
+ }
280
+
281
+ try {
282
+ setLoading(true);
283
+ const result = ChartLibrary.calculateBOLL(closes, config.period, config.k);
284
+ setUpper(result.upper);
285
+ setMiddle(result.middle);
286
+ setLower(result.lower);
287
+ setStatus('success');
288
+ setError(undefined);
289
+ } catch (err) {
290
+ setError(err instanceof Error ? err.message : 'Unknown error');
291
+ setStatus('error');
292
+ setUpper([]);
293
+ setMiddle([]);
294
+ setLower([]);
295
+ } finally {
296
+ setLoading(false);
297
+ }
298
+ }, [closes, config.period, config.k]);
299
+
300
+ return { upper, middle, lower, loading, error, status };
301
+ };
302
+
303
+ // ============================================================================
304
+ // DMI Hook (Tier 2)
305
+ // ============================================================================
306
+
307
+ export interface DMIConfig {
308
+ period: number;
309
+ }
310
+
311
+ export const useDMI = (candleData: CandleData[], config: DMIConfig): DMIResult => {
312
+ const [pdi, setPdi] = useState<(number | null)[]>([]);
313
+ const [mdi, setMdi] = useState<(number | null)[]>([]);
314
+ const [adx, setAdx] = useState<(number | null)[]>([]);
315
+ const [adxr, setAdxr] = useState<(number | null)[]>([]);
316
+ const [loading, setLoading] = useState(true);
317
+ const [error, setError] = useState<string | undefined>();
318
+ const [status, setStatus] = useState<'success' | 'error'>('success');
319
+
320
+ useEffect(() => {
321
+ if (!candleData || candleData.length === 0) {
322
+ setPdi([]);
323
+ setMdi([]);
324
+ setAdx([]);
325
+ setAdxr([]);
326
+ setLoading(false);
327
+ return;
328
+ }
329
+
330
+ try {
331
+ setLoading(true);
332
+ const opens = candleData.map(c => c.open);
333
+ const highs = candleData.map(c => c.high);
334
+ const lows = candleData.map(c => c.low);
335
+ const closes = candleData.map(c => c.close);
336
+
337
+ const result = ChartLibrary.calculateDMI(opens, highs, lows, closes, config.period);
338
+ setPdi(result.pdi);
339
+ setMdi(result.mdi);
340
+ setAdx(result.adx);
341
+ setAdxr(result.adxr);
342
+ setStatus('success');
343
+ setError(undefined);
344
+ } catch (err) {
345
+ setError(err instanceof Error ? err.message : 'Unknown error');
346
+ setStatus('error');
347
+ setPdi([]);
348
+ setMdi([]);
349
+ setAdx([]);
350
+ setAdxr([]);
351
+ } finally {
352
+ setLoading(false);
353
+ }
354
+ }, [candleData, config.period]);
355
+
356
+ return { pdi, mdi, adx, adxr, loading, error, status };
357
+ };
358
+
359
+ // ============================================================================
360
+ // Multiple Indicators Hook (Tier 2)
361
+ // ============================================================================
362
+
363
+ export const useMultipleIndicators = (
364
+ closes: number[],
365
+ configs: {
366
+ ma?: MAConfig;
367
+ ema?: EMAConfig;
368
+ macd?: MACDConfig;
369
+ rsi?: RSIConfig;
370
+ bollinger?: BollingerConfig;
371
+ }
372
+ ) => {
373
+ const ma = configs.ma ? useMA(closes, configs.ma) : null;
374
+ const ema = configs.ema ? useEMA(closes, configs.ema) : null;
375
+ const macd = configs.macd ? useMACD(closes, configs.macd) : null;
376
+ const rsi = configs.rsi ? useRSI(closes, configs.rsi) : null;
377
+ const bollinger = configs.bollinger ? useBollinger(closes, configs.bollinger) : null;
378
+
379
+ return {
380
+ ma,
381
+ ema,
382
+ macd,
383
+ rsi,
384
+ bollinger,
385
+ loading: ma?.loading || ema?.loading || macd?.loading || rsi?.loading || bollinger?.loading || false
386
+ };
387
+ };
388
+