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.
- package/CMakeLists.txt +70 -0
- package/EXPO_SETUP.md +335 -0
- package/LICENSE +22 -0
- package/README.md +275 -0
- package/index.d.ts +66 -0
- package/index.js +86 -0
- package/package.json +76 -0
- package/src/ChartModule.cpp +249 -0
- package/src/ChartModule.h +35 -0
- package/src/README.md +241 -0
- package/src/api/tier1/components/QuickIndicatorDisplay.tsx +109 -0
- package/src/api/tier1/components/index.ts +16 -0
- package/src/api/tier1/index.ts +15 -0
- package/src/api/tier1/useQuickBollinger.ts +58 -0
- package/src/api/tier1/useQuickDMI.ts +65 -0
- package/src/api/tier1/useQuickEMA.ts +48 -0
- package/src/api/tier1/useQuickKDJ.ts +55 -0
- package/src/api/tier1/useQuickMA.ts +60 -0
- package/src/api/tier1/useQuickMACD.ts +60 -0
- package/src/api/tier1/useQuickRSI.ts +38 -0
- package/src/helpers.cpp +135 -0
- package/src/helpers.h +65 -0
- package/src/hooks/useIndicators.ts +388 -0
- package/src/index.ts +51 -0
- package/src/jsi_bindings.cpp +532 -0
- package/src/types/index.ts +58 -0
|
@@ -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
|
+
|
package/src/helpers.cpp
ADDED
|
@@ -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
|
+
|