@vesper85/technical-indicators 0.1.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 +249 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +688 -0
- package/dist/indicators/index.d.ts +41 -0
- package/dist/types.d.ts +11 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# @osiris-ai/technical-indicators
|
|
2
|
+
|
|
3
|
+
A library for calculating technical indicators used in financial analysis and trading.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @osiris-ai/technical-indicators
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Using Individual Functions
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { calculateSMA, calculateRSI, calculateMACD } from '@osiris-ai/technical-indicators';
|
|
17
|
+
|
|
18
|
+
// Simple Moving Average
|
|
19
|
+
const closes = [100, 102, 101, 105, 107, 109, 108];
|
|
20
|
+
const sma = calculateSMA(closes, { period: 5 });
|
|
21
|
+
console.log(sma); // 106.4 (single number)
|
|
22
|
+
|
|
23
|
+
// RSI
|
|
24
|
+
const rsi = calculateRSI(closes, { period: 14 });
|
|
25
|
+
console.log(rsi); // 65.5 (single number)
|
|
26
|
+
|
|
27
|
+
// MACD (returns array)
|
|
28
|
+
const macd = calculateMACD(closes, {
|
|
29
|
+
fastPeriod: 12,
|
|
30
|
+
slowPeriod: 26,
|
|
31
|
+
signalPeriod: 9
|
|
32
|
+
});
|
|
33
|
+
console.log(macd); // [0.5, 0.3, 0.2] - [MACD, signal, histogram]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Using TechnicalAnalysisService
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { TechnicalAnalysisService } from '@osiris-ai/technical-indicators';
|
|
40
|
+
import { Logger } from '@osiris-ai/logger';
|
|
41
|
+
|
|
42
|
+
const service = new TechnicalAnalysisService({ logger: new Logger() });
|
|
43
|
+
|
|
44
|
+
// OHLCV data format
|
|
45
|
+
const ohlcvData = [
|
|
46
|
+
{
|
|
47
|
+
timestamp: 1640995200000,
|
|
48
|
+
open: 100,
|
|
49
|
+
high: 105,
|
|
50
|
+
low: 99,
|
|
51
|
+
close: 103,
|
|
52
|
+
volume: 1000000
|
|
53
|
+
},
|
|
54
|
+
// ... more candles
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// Calculate RSI
|
|
58
|
+
const rsi = service.calculate('rsi_14', ohlcvData, { period: 14 });
|
|
59
|
+
console.log(rsi); // 65.5
|
|
60
|
+
|
|
61
|
+
// Calculate MACD with component selection
|
|
62
|
+
const macdSignal = service.calculate(
|
|
63
|
+
'macd_12_26_9',
|
|
64
|
+
ohlcvData,
|
|
65
|
+
{ component: 'signal' }
|
|
66
|
+
);
|
|
67
|
+
console.log(macdSignal); // 0.3 (just the signal line)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Input/Output Types
|
|
71
|
+
|
|
72
|
+
### Input Types
|
|
73
|
+
|
|
74
|
+
**Individual Functions:**
|
|
75
|
+
- **Price arrays**: `number[]` - Array of closing prices, highs, lows, etc.
|
|
76
|
+
- **Volume arrays**: `number[]` - Array of volume values
|
|
77
|
+
- **Parameters**: `TAParams` - Object containing indicator-specific parameters
|
|
78
|
+
|
|
79
|
+
**TechnicalAnalysisService:**
|
|
80
|
+
- **Data**: `OHLCV[]` - Array of OHLCV objects
|
|
81
|
+
```typescript
|
|
82
|
+
interface OHLCV {
|
|
83
|
+
timestamp: number;
|
|
84
|
+
open: number;
|
|
85
|
+
high: number;
|
|
86
|
+
low: number;
|
|
87
|
+
close: number;
|
|
88
|
+
volume: number;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
- **Indicator**: `string` - Indicator name with parameters (e.g., `"rsi_14"`, `"macd_12_26_9"`)
|
|
92
|
+
- **Params**: `TAParams & { component?: string }` - Additional parameters and optional component selector
|
|
93
|
+
|
|
94
|
+
### Output Types
|
|
95
|
+
|
|
96
|
+
**Single Value Indicators** (return `number`):
|
|
97
|
+
- RSI, EMA, SMA, ATR, ADX, OBV, CCI, MFI, VWAP, WilliamsR, ROC, PSAR, WMA
|
|
98
|
+
|
|
99
|
+
**Multi-Value Indicators** (return `number[]`):
|
|
100
|
+
- **MACD**: `[MACD, signal, histogram]`
|
|
101
|
+
- **Bollinger Bands**: `[upper, middle, lower]`
|
|
102
|
+
- **Stochastic**: `[%K, %D]`
|
|
103
|
+
- **Stochastic RSI**: `[%K, %D]`
|
|
104
|
+
- **Keltner Channels**: `[upper, lower, middle]`
|
|
105
|
+
|
|
106
|
+
### Examples
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Single value output
|
|
110
|
+
const rsi: number = calculateRSI([100, 102, 101, 105, 107], { period: 5 });
|
|
111
|
+
// Result: 65.5
|
|
112
|
+
|
|
113
|
+
// Array output - MACD
|
|
114
|
+
const macd: number[] = calculateMACD(closes, {
|
|
115
|
+
fastPeriod: 12,
|
|
116
|
+
slowPeriod: 26,
|
|
117
|
+
signalPeriod: 9
|
|
118
|
+
});
|
|
119
|
+
// Result: [0.5, 0.3, 0.2]
|
|
120
|
+
|
|
121
|
+
// Array output - Bollinger Bands
|
|
122
|
+
const bb: number[] = calculateBollingerBands(closes, {
|
|
123
|
+
period: 20,
|
|
124
|
+
stdDev: 2
|
|
125
|
+
});
|
|
126
|
+
// Result: [110.5, 105.0, 99.5] // [upper, middle, lower]
|
|
127
|
+
|
|
128
|
+
// Using component selector with service
|
|
129
|
+
const upperBand: number = service.calculate(
|
|
130
|
+
'bb_20_2',
|
|
131
|
+
ohlcvData,
|
|
132
|
+
{ component: 'upper' }
|
|
133
|
+
);
|
|
134
|
+
// Result: 110.5 (just the upper band)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Available Indicators
|
|
138
|
+
|
|
139
|
+
- **SMA** (Simple Moving Average) - `calculateSMA`
|
|
140
|
+
- **EMA** (Exponential Moving Average) - `calculateEMA`
|
|
141
|
+
- **RSI** (Relative Strength Index) - `calculateRSI`
|
|
142
|
+
- **MACD** (Moving Average Convergence Divergence) - `calculateMACD`
|
|
143
|
+
- **ATR** (Average True Range) - `calculateATR`
|
|
144
|
+
- **Bollinger Bands** - `calculateBollingerBands`
|
|
145
|
+
- **ADX** (Average Directional Index) - `calculateADX`
|
|
146
|
+
- **Stochastic** - `calculateStochastic`
|
|
147
|
+
- **OBV** (On-Balance Volume) - `calculateOBV`
|
|
148
|
+
- **CCI** (Commodity Channel Index) - `calculateCCI`
|
|
149
|
+
- **MFI** (Money Flow Index) - `calculateMFI`
|
|
150
|
+
- **VWAP** (Volume Weighted Average Price) - `calculateVWAP`
|
|
151
|
+
- **Williams %R** - `calculateWilliamsR`
|
|
152
|
+
- **Stochastic RSI** - `calculateStochasticRSI`
|
|
153
|
+
- **ROC** (Rate of Change) - `calculateROC`
|
|
154
|
+
- **Parabolic SAR** - `calculateParabolicSAR`
|
|
155
|
+
- **Keltner Channels** - `calculateKeltnerChannels`
|
|
156
|
+
- **WMA** (Weighted Moving Average) - `calculateWMA`
|
|
157
|
+
|
|
158
|
+
## Adding New Technical Indicators
|
|
159
|
+
|
|
160
|
+
Follow this checklist when adding a new technical indicator:
|
|
161
|
+
|
|
162
|
+
### 1. Update Indicator Lists
|
|
163
|
+
- [ ] Add indicator name to `supportedIndicators` array in `src/indicators/index.ts`
|
|
164
|
+
- [ ] If indicator has multiple components (e.g., MACD, Bollinger Bands), add entry to `availableComponents` object
|
|
165
|
+
|
|
166
|
+
### 2. Implement Calculation Function
|
|
167
|
+
- [ ] Create `calculate[IndicatorName]` function in `src/indicators/index.ts`
|
|
168
|
+
- [ ] Define function signature with appropriate input arrays and `TAParams`
|
|
169
|
+
- [ ] Return type: `number` for single value, `number[]` for multi-value indicators
|
|
170
|
+
- [ ] Add parameter validation (check required params, minimum data length)
|
|
171
|
+
- [ ] Use `technicalindicators` library for calculation
|
|
172
|
+
- [ ] Return the last calculated value (for single) or array of values (for multi-value)
|
|
173
|
+
|
|
174
|
+
### 3. Integrate with Service
|
|
175
|
+
- [ ] Add case in `calculate()` method switch statement in `src/index.ts`
|
|
176
|
+
- [ ] Handle component selection if indicator has multiple components
|
|
177
|
+
- [ ] Extract appropriate price arrays (closes, highs, lows, volumes) from OHLCV data
|
|
178
|
+
|
|
179
|
+
### 4. Add Parameter Extraction
|
|
180
|
+
- [ ] Add case in `extractIndicatorParams()` method if indicator uses parameterized naming (e.g., `rsi_14`)
|
|
181
|
+
- [ ] Parse parameters from indicator string format
|
|
182
|
+
- [ ] Return base indicator name and extracted parameters
|
|
183
|
+
|
|
184
|
+
### 5. Testing & Documentation
|
|
185
|
+
- [ ] Test with sample data
|
|
186
|
+
- [ ] Verify error handling for insufficient data
|
|
187
|
+
- [ ] Update this README with the new indicator
|
|
188
|
+
- [ ] Add example usage in README
|
|
189
|
+
|
|
190
|
+
### Example: Adding a New Indicator
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// 1. Add to supportedIndicators
|
|
194
|
+
export const supportedIndicators = [..., "newindicator"] as const;
|
|
195
|
+
|
|
196
|
+
// 2. Implement function
|
|
197
|
+
export function calculateNewIndicator(
|
|
198
|
+
closes: number[],
|
|
199
|
+
params: TAParams
|
|
200
|
+
): number {
|
|
201
|
+
const period = params.period;
|
|
202
|
+
if (!period) {
|
|
203
|
+
throw new Error("Period is required for NewIndicator");
|
|
204
|
+
}
|
|
205
|
+
if (closes.length < period) {
|
|
206
|
+
throw new Error(`Insufficient data: need ${period}, got ${closes.length}`);
|
|
207
|
+
}
|
|
208
|
+
const values = TI.NewIndicator.calculate({ values: closes, period });
|
|
209
|
+
const last = values[values.length - 1];
|
|
210
|
+
if (!last) {
|
|
211
|
+
throw new Error("Failed to calculate NewIndicator");
|
|
212
|
+
}
|
|
213
|
+
return last;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 3. Add to service switch
|
|
217
|
+
case "newindicator":
|
|
218
|
+
return calculateNewIndicator(closes, finalParams);
|
|
219
|
+
|
|
220
|
+
// 4. Add parameter extraction if needed
|
|
221
|
+
case "newindicator":
|
|
222
|
+
if (!parts || !parts[0])
|
|
223
|
+
throw new Error(`Period is required for newindicator`);
|
|
224
|
+
return { base, params: { period: parseInt(parts[0]) } };
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Install dependencies
|
|
231
|
+
pnpm install
|
|
232
|
+
|
|
233
|
+
# Build the package
|
|
234
|
+
pnpm build
|
|
235
|
+
|
|
236
|
+
# Watch mode
|
|
237
|
+
pnpm build:watch
|
|
238
|
+
|
|
239
|
+
# Type check
|
|
240
|
+
pnpm type-check
|
|
241
|
+
|
|
242
|
+
# Lint
|
|
243
|
+
pnpm lint
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT
|
|
249
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Logger } from "@osiris-ai/logger";
|
|
2
|
+
import { OHLCV, TAParams } from "./types";
|
|
3
|
+
export declare class TechnicalAnalysisService {
|
|
4
|
+
private logger;
|
|
5
|
+
constructor(config: {
|
|
6
|
+
logger: Logger;
|
|
7
|
+
});
|
|
8
|
+
calculate(indicator: string, data: OHLCV[], params: TAParams & {
|
|
9
|
+
component?: string;
|
|
10
|
+
}): number | number[];
|
|
11
|
+
private extractIndicatorParams;
|
|
12
|
+
}
|
|
13
|
+
export * from "./indicators/index";
|
|
14
|
+
export * from "./types";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
import * as TI from 'technicalindicators';
|
|
2
|
+
|
|
3
|
+
// @osiris-ai/technical-indicators - Technical Indicators Library
|
|
4
|
+
|
|
5
|
+
var supportedIndicators = ["rsi", "ema", "sma", "atr", "macd", "bb", "wma", "adx", "stochastic", "obv", "cci", "mfi", "vwap", "williamsr", "stochasticrsi", "roc", "psar", "keltnerchannels"];
|
|
6
|
+
var availableComponents = {
|
|
7
|
+
rsi: ["rsi"],
|
|
8
|
+
ema: ["ema"],
|
|
9
|
+
sma: ["sma"],
|
|
10
|
+
atr: ["atr"],
|
|
11
|
+
macd: ["macd", "signal", "histogram"],
|
|
12
|
+
bb: ["upper", "middle", "lower"],
|
|
13
|
+
wma: ["wma"],
|
|
14
|
+
adx: ["adx"],
|
|
15
|
+
stochastic: ["stochastic", "k", "d"],
|
|
16
|
+
obv: ["obv"],
|
|
17
|
+
cci: ["cci"],
|
|
18
|
+
mfi: ["mfi"],
|
|
19
|
+
vwap: ["vwap"],
|
|
20
|
+
williamsr: ["williamsr"],
|
|
21
|
+
stochasticrsi: ["stochasticrsi", "k", "d"],
|
|
22
|
+
roc: ["roc"],
|
|
23
|
+
psar: ["psar"],
|
|
24
|
+
keltnerchannels: ["keltnerchannels", "upper", "middle", "lower"]
|
|
25
|
+
};
|
|
26
|
+
function calculateRSI(closes, params) {
|
|
27
|
+
const period = params.period;
|
|
28
|
+
if (!period) {
|
|
29
|
+
throw new Error("Period is required for RSI");
|
|
30
|
+
}
|
|
31
|
+
if (closes.length < period) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Insufficient data for RSI: need ${period}, got ${closes.length}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
const values = TI.RSI.calculate({ values: closes, period });
|
|
37
|
+
const last = values[values.length - 1];
|
|
38
|
+
if (!last) {
|
|
39
|
+
throw new Error("Failed to calculate RSI");
|
|
40
|
+
}
|
|
41
|
+
return last;
|
|
42
|
+
}
|
|
43
|
+
function calculateEMA(closes, params) {
|
|
44
|
+
const period = params.period;
|
|
45
|
+
if (!period) {
|
|
46
|
+
throw new Error("Period is required for EMA");
|
|
47
|
+
}
|
|
48
|
+
if (closes.length < period) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Insufficient data for EMA: need ${period}, got ${closes.length}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const values = TI.EMA.calculate({ values: closes, period });
|
|
54
|
+
if (!values || values.length === 0) {
|
|
55
|
+
throw new Error(`Failed to calculate EMA: library returned empty array. Input length: ${closes.length}, period: ${period}`);
|
|
56
|
+
}
|
|
57
|
+
const last = values[values.length - 1];
|
|
58
|
+
if (last === void 0 || last === null || isNaN(last)) {
|
|
59
|
+
throw new Error(`Failed to calculate EMA: invalid result. Array length: ${values.length}, last value: ${last}`);
|
|
60
|
+
}
|
|
61
|
+
return last;
|
|
62
|
+
}
|
|
63
|
+
function calculateSMA(closes, params) {
|
|
64
|
+
const period = params.period;
|
|
65
|
+
if (!period) {
|
|
66
|
+
throw new Error("Period is required for SMA");
|
|
67
|
+
}
|
|
68
|
+
if (closes.length < period) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Insufficient data for SMA: need ${period}, got ${closes.length}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const values = TI.SMA.calculate({ values: closes, period });
|
|
74
|
+
if (!values || values.length === 0) {
|
|
75
|
+
throw new Error(`Failed to calculate SMA: library returned empty array. Input length: ${closes.length}, period: ${period}`);
|
|
76
|
+
}
|
|
77
|
+
const last = values[values.length - 1];
|
|
78
|
+
if (last === void 0 || last === null || isNaN(last)) {
|
|
79
|
+
throw new Error(`Failed to calculate SMA: invalid result. Array length: ${values.length}, last value: ${last}`);
|
|
80
|
+
}
|
|
81
|
+
return last;
|
|
82
|
+
}
|
|
83
|
+
function calculateATR(highs, lows, closes, params) {
|
|
84
|
+
const period = params.period;
|
|
85
|
+
if (!period) {
|
|
86
|
+
throw new Error("Period is required for ATR");
|
|
87
|
+
}
|
|
88
|
+
if (closes.length < period) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Insufficient data for ATR: need ${period}, got ${closes.length}`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const values = TI.ATR.calculate({
|
|
94
|
+
high: highs,
|
|
95
|
+
low: lows,
|
|
96
|
+
close: closes,
|
|
97
|
+
period
|
|
98
|
+
});
|
|
99
|
+
const last = values[values.length - 1];
|
|
100
|
+
if (!last) {
|
|
101
|
+
throw new Error("Failed to calculate ATR");
|
|
102
|
+
}
|
|
103
|
+
return last;
|
|
104
|
+
}
|
|
105
|
+
function calculateMACD(closes, params) {
|
|
106
|
+
const { fastPeriod, slowPeriod, signalPeriod } = params;
|
|
107
|
+
if (!fastPeriod) {
|
|
108
|
+
throw new Error("Fast period is required for MACD");
|
|
109
|
+
}
|
|
110
|
+
if (!slowPeriod) {
|
|
111
|
+
throw new Error("Slow period is required for MACD");
|
|
112
|
+
}
|
|
113
|
+
if (!signalPeriod) {
|
|
114
|
+
throw new Error("Signal period is required for MACD");
|
|
115
|
+
}
|
|
116
|
+
const minBars = Math.max(fastPeriod, slowPeriod) + signalPeriod;
|
|
117
|
+
if (closes.length < minBars) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Insufficient data for MACD: need ${minBars}, got ${closes.length}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const values = TI.MACD.calculate({
|
|
123
|
+
values: closes,
|
|
124
|
+
fastPeriod,
|
|
125
|
+
slowPeriod,
|
|
126
|
+
signalPeriod,
|
|
127
|
+
SimpleMAOscillator: false,
|
|
128
|
+
SimpleMASignal: false
|
|
129
|
+
});
|
|
130
|
+
if (!values || values.length === 0) {
|
|
131
|
+
throw new Error(`Failed to calculate MACD: library returned empty array. Input length: ${closes.length}, fastPeriod: ${fastPeriod}, slowPeriod: ${slowPeriod}, signalPeriod: ${signalPeriod}`);
|
|
132
|
+
}
|
|
133
|
+
const last = values[values.length - 1];
|
|
134
|
+
if (!last || !last.MACD || !last.signal || !last.histogram) {
|
|
135
|
+
throw new Error(`Failed to calculate MACD: invalid result. Array length: ${values.length}, last value: ${JSON.stringify(last)}`);
|
|
136
|
+
}
|
|
137
|
+
return [last.MACD, last.signal, last.histogram];
|
|
138
|
+
}
|
|
139
|
+
function calculateBollingerBands(closes, params) {
|
|
140
|
+
const period = params.period;
|
|
141
|
+
if (!period) {
|
|
142
|
+
throw new Error("Period is required for Bollinger Bands");
|
|
143
|
+
}
|
|
144
|
+
const stdDev = params.stdDev;
|
|
145
|
+
if (!stdDev) {
|
|
146
|
+
throw new Error("Standard deviation is required for Bollinger Bands");
|
|
147
|
+
}
|
|
148
|
+
if (closes.length < period) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Insufficient data for Bollinger Bands: need ${period}, got ${closes.length}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const values = TI.BollingerBands.calculate({
|
|
154
|
+
values: closes,
|
|
155
|
+
period,
|
|
156
|
+
stdDev
|
|
157
|
+
});
|
|
158
|
+
if (!values || values.length === 0) {
|
|
159
|
+
throw new Error(`Failed to calculate Bollinger Bands: library returned empty array. Input length: ${closes.length}, period: ${period}, stdDev: ${stdDev}`);
|
|
160
|
+
}
|
|
161
|
+
const last = values[values.length - 1];
|
|
162
|
+
if (!last || !last.upper || !last.middle || !last.lower) {
|
|
163
|
+
throw new Error(`Failed to calculate Bollinger Bands: invalid result. Array length: ${values.length}, last value: ${JSON.stringify(last)}`);
|
|
164
|
+
}
|
|
165
|
+
return [last.upper, last.middle, last.lower];
|
|
166
|
+
}
|
|
167
|
+
function calculateADX(highs, lows, closes, params) {
|
|
168
|
+
const period = params.period;
|
|
169
|
+
if (!period) {
|
|
170
|
+
throw new Error("Period is required for ADX");
|
|
171
|
+
}
|
|
172
|
+
const minBars = period * 2;
|
|
173
|
+
if (closes.length < minBars) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Insufficient data for ADX: need ${minBars}, got ${closes.length}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const values = TI.ADX.calculate({
|
|
179
|
+
high: highs,
|
|
180
|
+
low: lows,
|
|
181
|
+
close: closes,
|
|
182
|
+
period
|
|
183
|
+
});
|
|
184
|
+
const last = values[values.length - 1];
|
|
185
|
+
if (!last || !last.adx) {
|
|
186
|
+
throw new Error("Failed to calculate ADX");
|
|
187
|
+
}
|
|
188
|
+
return last.adx;
|
|
189
|
+
}
|
|
190
|
+
function calculateStochastic(highs, lows, closes, params) {
|
|
191
|
+
const period = params.period;
|
|
192
|
+
if (!period) {
|
|
193
|
+
throw new Error("Period is required for Stochastic");
|
|
194
|
+
}
|
|
195
|
+
const signalPeriod = params.signalPeriod;
|
|
196
|
+
if (!signalPeriod) {
|
|
197
|
+
throw new Error("Signal period is required for Stochastic");
|
|
198
|
+
}
|
|
199
|
+
const minBars = period + signalPeriod;
|
|
200
|
+
if (closes.length < minBars) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Insufficient data for Stochastic: need ${minBars}, got ${closes.length}`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const values = TI.Stochastic.calculate({
|
|
206
|
+
high: highs,
|
|
207
|
+
low: lows,
|
|
208
|
+
close: closes,
|
|
209
|
+
period,
|
|
210
|
+
signalPeriod
|
|
211
|
+
});
|
|
212
|
+
const last = values[values.length - 1];
|
|
213
|
+
if (!last || !last.k || !last.d) {
|
|
214
|
+
throw new Error("Failed to calculate Stochastic");
|
|
215
|
+
}
|
|
216
|
+
return [last.k, last.d];
|
|
217
|
+
}
|
|
218
|
+
function calculateOBV(closes, volumes, params) {
|
|
219
|
+
if (closes.length < 2) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Insufficient data for OBV: need at least 2 candles, got ${closes.length}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const values = TI.OBV.calculate({ close: closes, volume: volumes });
|
|
225
|
+
const last = values[values.length - 1];
|
|
226
|
+
if (!last) {
|
|
227
|
+
throw new Error("Failed to calculate OBV");
|
|
228
|
+
}
|
|
229
|
+
return last;
|
|
230
|
+
}
|
|
231
|
+
function calculateCCI(highs, lows, closes, params) {
|
|
232
|
+
const period = params.period;
|
|
233
|
+
if (!period) {
|
|
234
|
+
throw new Error("Period is required for CCI");
|
|
235
|
+
}
|
|
236
|
+
if (closes.length < period) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
`Insufficient data for CCI: need ${period}, got ${closes.length}`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const values = TI.CCI.calculate({
|
|
242
|
+
high: highs,
|
|
243
|
+
low: lows,
|
|
244
|
+
close: closes,
|
|
245
|
+
period
|
|
246
|
+
});
|
|
247
|
+
const last = values[values.length - 1];
|
|
248
|
+
if (!last) {
|
|
249
|
+
throw new Error("Failed to calculate CCI");
|
|
250
|
+
}
|
|
251
|
+
return last;
|
|
252
|
+
}
|
|
253
|
+
function calculateMFI(highs, lows, closes, volumes, params) {
|
|
254
|
+
const period = params.period;
|
|
255
|
+
if (!period) {
|
|
256
|
+
throw new Error("Period is required for MFI");
|
|
257
|
+
}
|
|
258
|
+
if (closes.length < period) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Insufficient data for MFI: need ${period}, got ${closes.length}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const values = TI.MFI.calculate({
|
|
264
|
+
high: highs,
|
|
265
|
+
low: lows,
|
|
266
|
+
close: closes,
|
|
267
|
+
volume: volumes,
|
|
268
|
+
period
|
|
269
|
+
});
|
|
270
|
+
const last = values[values.length - 1];
|
|
271
|
+
if (!last) {
|
|
272
|
+
throw new Error("Failed to calculate MFI");
|
|
273
|
+
}
|
|
274
|
+
return last;
|
|
275
|
+
}
|
|
276
|
+
function calculateVWAP(highs, lows, closes, volumes, params) {
|
|
277
|
+
if (closes.length === 0) {
|
|
278
|
+
throw new Error("Insufficient data for VWAP");
|
|
279
|
+
}
|
|
280
|
+
const values = TI.VWAP.calculate({
|
|
281
|
+
high: highs,
|
|
282
|
+
low: lows,
|
|
283
|
+
close: closes,
|
|
284
|
+
volume: volumes
|
|
285
|
+
});
|
|
286
|
+
if (!values || values.length === 0) {
|
|
287
|
+
throw new Error(`Failed to calculate VWAP: library returned empty array. Input length: ${closes.length}`);
|
|
288
|
+
}
|
|
289
|
+
const last = values[values.length - 1];
|
|
290
|
+
if (last === void 0 || last === null || isNaN(last)) {
|
|
291
|
+
throw new Error(`Failed to calculate VWAP: invalid result. Array length: ${values.length}, last value: ${last}`);
|
|
292
|
+
}
|
|
293
|
+
return last;
|
|
294
|
+
}
|
|
295
|
+
function calculateWilliamsR(highs, lows, closes, params) {
|
|
296
|
+
const period = params.period;
|
|
297
|
+
if (!period) {
|
|
298
|
+
throw new Error("Period is required for Williams %R");
|
|
299
|
+
}
|
|
300
|
+
if (closes.length < period) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`Insufficient data for Williams %R: need ${period}, got ${closes.length}`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
const values = TI.WilliamsR.calculate({
|
|
306
|
+
high: highs,
|
|
307
|
+
low: lows,
|
|
308
|
+
close: closes,
|
|
309
|
+
period
|
|
310
|
+
});
|
|
311
|
+
const last = values[values.length - 1];
|
|
312
|
+
if (!last) {
|
|
313
|
+
throw new Error("Failed to calculate Williams %R");
|
|
314
|
+
}
|
|
315
|
+
return last;
|
|
316
|
+
}
|
|
317
|
+
function calculateStochasticRSI(closes, params) {
|
|
318
|
+
const rsiPeriod = params.rsiPeriod;
|
|
319
|
+
const stochasticPeriod = params.stochasticPeriod;
|
|
320
|
+
const kPeriod = params.kPeriod;
|
|
321
|
+
const dPeriod = params.dPeriod;
|
|
322
|
+
if (!rsiPeriod) {
|
|
323
|
+
throw new Error("RSI period is required for Stochastic RSI");
|
|
324
|
+
}
|
|
325
|
+
if (!stochasticPeriod) {
|
|
326
|
+
throw new Error("Stochastic period is required for Stochastic RSI");
|
|
327
|
+
}
|
|
328
|
+
if (!kPeriod) {
|
|
329
|
+
throw new Error("K period is required for Stochastic RSI");
|
|
330
|
+
}
|
|
331
|
+
if (!dPeriod) {
|
|
332
|
+
throw new Error("D period is required for Stochastic RSI");
|
|
333
|
+
}
|
|
334
|
+
const minBars = Math.max(rsiPeriod, stochasticPeriod) + kPeriod + dPeriod;
|
|
335
|
+
if (closes.length < minBars) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`Insufficient data for Stochastic RSI: need ${minBars}, got ${closes.length}`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
const values = TI.StochasticRSI.calculate({
|
|
341
|
+
values: closes,
|
|
342
|
+
rsiPeriod,
|
|
343
|
+
stochasticPeriod,
|
|
344
|
+
kPeriod,
|
|
345
|
+
dPeriod
|
|
346
|
+
});
|
|
347
|
+
const last = values[values.length - 1];
|
|
348
|
+
if (!last || !last.k || !last.d) {
|
|
349
|
+
throw new Error("Failed to calculate Stochastic RSI");
|
|
350
|
+
}
|
|
351
|
+
return [last.k, last.d];
|
|
352
|
+
}
|
|
353
|
+
function calculateROC(closes, params) {
|
|
354
|
+
const period = params.period;
|
|
355
|
+
if (!period) {
|
|
356
|
+
throw new Error("Period is required for ROC");
|
|
357
|
+
}
|
|
358
|
+
if (closes.length < period + 1) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
`Insufficient data for ROC: need ${period + 1}, got ${closes.length}`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
const values = TI.ROC.calculate({ values: closes, period });
|
|
364
|
+
const last = values[values.length - 1];
|
|
365
|
+
if (!last) {
|
|
366
|
+
throw new Error("Failed to calculate ROC");
|
|
367
|
+
}
|
|
368
|
+
return last;
|
|
369
|
+
}
|
|
370
|
+
function calculateParabolicSAR(highs, lows, params) {
|
|
371
|
+
const step = params.step;
|
|
372
|
+
const max = params.max;
|
|
373
|
+
if (!step) {
|
|
374
|
+
throw new Error("Step is required for Parabolic SAR");
|
|
375
|
+
}
|
|
376
|
+
if (!max) {
|
|
377
|
+
throw new Error("Max is required for Parabolic SAR");
|
|
378
|
+
}
|
|
379
|
+
if (highs.length < 2) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`Insufficient data for Parabolic SAR: need at least 2 candles, got ${highs.length}`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
const values = TI.PSAR.calculate({
|
|
385
|
+
high: highs,
|
|
386
|
+
low: lows,
|
|
387
|
+
step,
|
|
388
|
+
max
|
|
389
|
+
});
|
|
390
|
+
if (!values || values.length === 0) {
|
|
391
|
+
throw new Error(`Failed to calculate Parabolic SAR: library returned empty array. Input length: ${highs.length}, step: ${step}, max: ${max}`);
|
|
392
|
+
}
|
|
393
|
+
const last = values[values.length - 1];
|
|
394
|
+
if (last === void 0 || last === null || isNaN(last)) {
|
|
395
|
+
throw new Error(`Failed to calculate Parabolic SAR: invalid result. Array length: ${values.length}, last value: ${last}`);
|
|
396
|
+
}
|
|
397
|
+
return last;
|
|
398
|
+
}
|
|
399
|
+
function calculateKeltnerChannels(highs, lows, closes, params) {
|
|
400
|
+
const maPeriod = params.maPeriod;
|
|
401
|
+
const multiplier = params.multiplier;
|
|
402
|
+
const atrPeriod = params.atrPeriod;
|
|
403
|
+
if (!maPeriod) {
|
|
404
|
+
throw new Error("MA period is required for Keltner Channels");
|
|
405
|
+
}
|
|
406
|
+
if (!multiplier) {
|
|
407
|
+
throw new Error("Multiplier is required for Keltner Channels");
|
|
408
|
+
}
|
|
409
|
+
if (!atrPeriod) {
|
|
410
|
+
throw new Error("ATR period is required for Keltner Channels");
|
|
411
|
+
}
|
|
412
|
+
const minBars = Math.max(maPeriod, atrPeriod);
|
|
413
|
+
if (closes.length < minBars) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Insufficient data for Keltner Channels: need ${minBars}, got ${closes.length}`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const values = TI.KeltnerChannels.calculate({
|
|
419
|
+
high: highs,
|
|
420
|
+
low: lows,
|
|
421
|
+
close: closes,
|
|
422
|
+
maPeriod,
|
|
423
|
+
multiplier,
|
|
424
|
+
atrPeriod,
|
|
425
|
+
useSMA: false
|
|
426
|
+
});
|
|
427
|
+
if (!values || values.length === 0) {
|
|
428
|
+
throw new Error(`Failed to calculate Keltner Channels: library returned empty array. Input length: ${closes.length}, maPeriod: ${maPeriod}, multiplier: ${multiplier}, atrPeriod: ${atrPeriod}`);
|
|
429
|
+
}
|
|
430
|
+
const last = values[values.length - 1];
|
|
431
|
+
if (!last || !last.upper || !last.lower || !last.middle) {
|
|
432
|
+
throw new Error(`Failed to calculate Keltner Channels: invalid result. Array length: ${values.length}, last value: ${JSON.stringify(last)}`);
|
|
433
|
+
}
|
|
434
|
+
return [last.upper, last.lower, last.middle];
|
|
435
|
+
}
|
|
436
|
+
function calculateWMA(closes, params) {
|
|
437
|
+
const period = params.period;
|
|
438
|
+
if (!period) {
|
|
439
|
+
throw new Error("Period is required for WMA");
|
|
440
|
+
}
|
|
441
|
+
if (closes.length < period) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`Insufficient data for WMA: need ${period}, got ${closes.length}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
const values = TI.WMA.calculate({ values: closes, period });
|
|
447
|
+
const last = values[values.length - 1];
|
|
448
|
+
if (!last) {
|
|
449
|
+
throw new Error("Failed to calculate WMA");
|
|
450
|
+
}
|
|
451
|
+
return last;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/index.ts
|
|
455
|
+
var TechnicalAnalysisService = class {
|
|
456
|
+
logger;
|
|
457
|
+
constructor(config) {
|
|
458
|
+
this.logger = config.logger;
|
|
459
|
+
}
|
|
460
|
+
calculate(indicator, data, params) {
|
|
461
|
+
try {
|
|
462
|
+
this.logger.info("Calculating technical indicator", { indicator, params });
|
|
463
|
+
if (!data || data.length === 0) {
|
|
464
|
+
throw new Error("No OHLCV data provided for TA calculation");
|
|
465
|
+
}
|
|
466
|
+
const closes = data.map((d) => Number(d.close));
|
|
467
|
+
const highs = data.map((d) => Number(d.high));
|
|
468
|
+
const lows = data.map((d) => Number(d.low));
|
|
469
|
+
const volumes = data.map((d) => Number(d.volume));
|
|
470
|
+
const component = params.component;
|
|
471
|
+
const paramsWithoutComponent = { ...params };
|
|
472
|
+
delete paramsWithoutComponent.component;
|
|
473
|
+
const { base, params: extractedParams } = this.extractIndicatorParams(indicator);
|
|
474
|
+
const finalParams = { ...extractedParams, ...paramsWithoutComponent };
|
|
475
|
+
switch (base) {
|
|
476
|
+
case "rsi":
|
|
477
|
+
return calculateRSI(closes, finalParams);
|
|
478
|
+
case "ema":
|
|
479
|
+
return calculateEMA(closes, finalParams);
|
|
480
|
+
case "sma":
|
|
481
|
+
return calculateSMA(closes, finalParams);
|
|
482
|
+
case "atr":
|
|
483
|
+
return calculateATR(highs, lows, closes, finalParams);
|
|
484
|
+
case "macd": {
|
|
485
|
+
const result = calculateMACD(closes, finalParams);
|
|
486
|
+
if (component && !availableComponents.macd.includes(component)) {
|
|
487
|
+
throw new Error(`Invalid component for MACD: ${component}`);
|
|
488
|
+
}
|
|
489
|
+
if (component === "signal") {
|
|
490
|
+
return result[1] ?? 0;
|
|
491
|
+
} else if (component === "macd") {
|
|
492
|
+
return result[0] ?? 0;
|
|
493
|
+
} else if (component === "histogram") {
|
|
494
|
+
return result[2] ?? 0;
|
|
495
|
+
}
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
case "bb": {
|
|
499
|
+
const result = calculateBollingerBands(closes, finalParams);
|
|
500
|
+
if (component && !availableComponents.bb.includes(component)) {
|
|
501
|
+
throw new Error(`Invalid component for Bollinger Bands: ${component}`);
|
|
502
|
+
}
|
|
503
|
+
if (component === "lower") {
|
|
504
|
+
return result[2] ?? 0;
|
|
505
|
+
} else if (component === "upper") {
|
|
506
|
+
return result[0] ?? 0;
|
|
507
|
+
} else if (component === "middle") {
|
|
508
|
+
return result[1] ?? 0;
|
|
509
|
+
}
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
case "adx":
|
|
513
|
+
return calculateADX(highs, lows, closes, finalParams);
|
|
514
|
+
case "stochastic": {
|
|
515
|
+
const result = calculateStochastic(highs, lows, closes, finalParams);
|
|
516
|
+
if (component && !availableComponents.stochastic.includes(component)) {
|
|
517
|
+
throw new Error(`Invalid component for Stochastic: ${component}`);
|
|
518
|
+
}
|
|
519
|
+
if (component === "d") {
|
|
520
|
+
return result[1] ?? 0;
|
|
521
|
+
} else if (component === "k") {
|
|
522
|
+
return result[0] ?? 0;
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
case "obv":
|
|
527
|
+
return calculateOBV(closes, volumes, finalParams);
|
|
528
|
+
case "cci":
|
|
529
|
+
return calculateCCI(highs, lows, closes, finalParams);
|
|
530
|
+
case "mfi":
|
|
531
|
+
return calculateMFI(highs, lows, closes, volumes, finalParams);
|
|
532
|
+
case "vwap":
|
|
533
|
+
return calculateVWAP(highs, lows, closes, volumes, finalParams);
|
|
534
|
+
case "williamsr":
|
|
535
|
+
return calculateWilliamsR(highs, lows, closes, finalParams);
|
|
536
|
+
case "stochasticrsi": {
|
|
537
|
+
const result = calculateStochasticRSI(closes, finalParams);
|
|
538
|
+
if (component && !availableComponents.stochasticrsi.includes(component)) {
|
|
539
|
+
throw new Error(`Invalid component for Stochastic RSI: ${component}`);
|
|
540
|
+
}
|
|
541
|
+
if (component === "k") {
|
|
542
|
+
return result[0] ?? 0;
|
|
543
|
+
} else if (component === "d") {
|
|
544
|
+
return result[1] ?? 0;
|
|
545
|
+
}
|
|
546
|
+
return result;
|
|
547
|
+
}
|
|
548
|
+
case "roc":
|
|
549
|
+
return calculateROC(closes, finalParams);
|
|
550
|
+
case "psar":
|
|
551
|
+
return calculateParabolicSAR(highs, lows, finalParams);
|
|
552
|
+
case "keltnerchannels": {
|
|
553
|
+
const result = calculateKeltnerChannels(highs, lows, closes, finalParams);
|
|
554
|
+
if (component && !availableComponents.keltnerchannels.includes(component)) {
|
|
555
|
+
throw new Error(`Invalid component for Keltner Channels: ${component}`);
|
|
556
|
+
}
|
|
557
|
+
if (component === "upper") {
|
|
558
|
+
return result[0] ?? 0;
|
|
559
|
+
} else if (component === "lower") {
|
|
560
|
+
return result[1] ?? 0;
|
|
561
|
+
} else if (component === "middle") {
|
|
562
|
+
return result[2] ?? 0;
|
|
563
|
+
}
|
|
564
|
+
return result;
|
|
565
|
+
}
|
|
566
|
+
case "wma":
|
|
567
|
+
return calculateWMA(closes, finalParams);
|
|
568
|
+
default:
|
|
569
|
+
throw new Error(`Unsupported technical indicator: ${indicator}`);
|
|
570
|
+
}
|
|
571
|
+
} catch (error) {
|
|
572
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
573
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
574
|
+
this.logger.error("Error calculating technical indicator", {
|
|
575
|
+
indicator,
|
|
576
|
+
params,
|
|
577
|
+
error: errorMessage,
|
|
578
|
+
stack: errorStack
|
|
579
|
+
});
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
extractIndicatorParams(indicator) {
|
|
584
|
+
const cleanIndicator = indicator.startsWith("ta_") ? indicator.substring(3) : indicator;
|
|
585
|
+
if (cleanIndicator === "vwap") {
|
|
586
|
+
return { base: "vwap", params: {} };
|
|
587
|
+
}
|
|
588
|
+
const match = cleanIndicator.match(/^([a-z]+)_(.+)$/);
|
|
589
|
+
if (!match) {
|
|
590
|
+
throw new Error(`Invalid indicator format: ${indicator}`);
|
|
591
|
+
}
|
|
592
|
+
const base = match[1];
|
|
593
|
+
if (!base || !supportedIndicators.includes(base)) {
|
|
594
|
+
throw new Error(`Base indicator not found: ${indicator}`);
|
|
595
|
+
}
|
|
596
|
+
const parts = match[2]?.split("_");
|
|
597
|
+
switch (base) {
|
|
598
|
+
case "rsi":
|
|
599
|
+
case "ema":
|
|
600
|
+
case "sma":
|
|
601
|
+
case "atr":
|
|
602
|
+
if (!parts || !parts[0])
|
|
603
|
+
throw new Error(`Period is required for ${base}`);
|
|
604
|
+
return { base, params: { period: parseInt(parts[0]) } };
|
|
605
|
+
case "macd":
|
|
606
|
+
if (!parts || (!parts[0] || !parts[1] || !parts[2]))
|
|
607
|
+
throw new Error(`MACD requires 3 params: ${indicator}`);
|
|
608
|
+
return {
|
|
609
|
+
base,
|
|
610
|
+
params: {
|
|
611
|
+
fastPeriod: parseInt(parts[0]),
|
|
612
|
+
slowPeriod: parseInt(parts[1]),
|
|
613
|
+
signalPeriod: parseInt(parts[2])
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
case "bb":
|
|
617
|
+
if (!parts || !parts[0] || !parts[1])
|
|
618
|
+
throw new Error(`BB requires 2 params: ${indicator}`);
|
|
619
|
+
return {
|
|
620
|
+
base,
|
|
621
|
+
params: {
|
|
622
|
+
period: parseInt(parts[0]),
|
|
623
|
+
stdDev: parseFloat(parts[1])
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
case "stochastic":
|
|
627
|
+
if (!parts || !parts[0] || !parts[1])
|
|
628
|
+
throw new Error(`Stoch requires 2 params: ${indicator}`);
|
|
629
|
+
return {
|
|
630
|
+
base,
|
|
631
|
+
params: {
|
|
632
|
+
period: parseInt(parts[0]),
|
|
633
|
+
signalPeriod: parseInt(parts[1])
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
case "adx":
|
|
637
|
+
if (!parts || !parts[0])
|
|
638
|
+
throw new Error(`Period is required for ${base}`);
|
|
639
|
+
return { base, params: { period: parseInt(parts[0]) } };
|
|
640
|
+
case "roc":
|
|
641
|
+
case "wma":
|
|
642
|
+
if (!parts || !parts[0])
|
|
643
|
+
throw new Error(`Period is required for ${base}`);
|
|
644
|
+
return { base, params: { period: parseInt(parts[0]) } };
|
|
645
|
+
case "stochasticrsi":
|
|
646
|
+
if (!parts || !parts[0] || !parts[1] || !parts[2] || !parts[3])
|
|
647
|
+
throw new Error(`Stochastic RSI requires 4 params: ${indicator}`);
|
|
648
|
+
return {
|
|
649
|
+
base,
|
|
650
|
+
params: {
|
|
651
|
+
rsiPeriod: parseInt(parts[0]),
|
|
652
|
+
stochasticPeriod: parseInt(parts[1]),
|
|
653
|
+
kPeriod: parseInt(parts[2]),
|
|
654
|
+
dPeriod: parseInt(parts[3])
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
case "psar":
|
|
658
|
+
if (!parts || !parts[0] || !parts[1])
|
|
659
|
+
throw new Error(`Parabolic SAR requires 2 params: ${indicator}`);
|
|
660
|
+
return {
|
|
661
|
+
base,
|
|
662
|
+
params: {
|
|
663
|
+
step: parseFloat(parts[0]),
|
|
664
|
+
max: parseFloat(parts[1])
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
case "keltnerchannels":
|
|
668
|
+
if (!parts || !parts[0] || !parts[1] || !parts[2])
|
|
669
|
+
throw new Error(`Keltner Channels requires 3 params: ${indicator}`);
|
|
670
|
+
return {
|
|
671
|
+
base,
|
|
672
|
+
params: {
|
|
673
|
+
maPeriod: parseInt(parts[0]),
|
|
674
|
+
multiplier: parseFloat(parts[1]),
|
|
675
|
+
atrPeriod: parseInt(parts[2])
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
case "vwap":
|
|
679
|
+
return { base, params: {} };
|
|
680
|
+
default:
|
|
681
|
+
throw new Error(`Unknown indicator: ${base}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
export { TechnicalAnalysisService, availableComponents, calculateADX, calculateATR, calculateBollingerBands, calculateCCI, calculateEMA, calculateKeltnerChannels, calculateMACD, calculateMFI, calculateOBV, calculateParabolicSAR, calculateROC, calculateRSI, calculateSMA, calculateStochastic, calculateStochasticRSI, calculateVWAP, calculateWMA, calculateWilliamsR, supportedIndicators };
|
|
687
|
+
//# sourceMappingURL=index.js.map
|
|
688
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TAParams } from "../types";
|
|
2
|
+
export declare const supportedIndicators: readonly ["rsi", "ema", "sma", "atr", "macd", "bb", "wma", "adx", "stochastic", "obv", "cci", "mfi", "vwap", "williamsr", "stochasticrsi", "roc", "psar", "keltnerchannels"];
|
|
3
|
+
export declare const availableComponents: {
|
|
4
|
+
rsi: string[];
|
|
5
|
+
ema: string[];
|
|
6
|
+
sma: string[];
|
|
7
|
+
atr: string[];
|
|
8
|
+
macd: string[];
|
|
9
|
+
bb: string[];
|
|
10
|
+
wma: string[];
|
|
11
|
+
adx: string[];
|
|
12
|
+
stochastic: string[];
|
|
13
|
+
obv: string[];
|
|
14
|
+
cci: string[];
|
|
15
|
+
mfi: string[];
|
|
16
|
+
vwap: string[];
|
|
17
|
+
williamsr: string[];
|
|
18
|
+
stochasticrsi: string[];
|
|
19
|
+
roc: string[];
|
|
20
|
+
psar: string[];
|
|
21
|
+
keltnerchannels: string[];
|
|
22
|
+
};
|
|
23
|
+
export declare function calculateRSI(closes: number[], params: TAParams): number;
|
|
24
|
+
export declare function calculateEMA(closes: number[], params: TAParams): number;
|
|
25
|
+
export declare function calculateSMA(closes: number[], params: TAParams): number;
|
|
26
|
+
export declare function calculateATR(highs: number[], lows: number[], closes: number[], params: TAParams): number;
|
|
27
|
+
export declare function calculateMACD(closes: number[], params: TAParams): number[];
|
|
28
|
+
export declare function calculateBollingerBands(closes: number[], params: TAParams): number[];
|
|
29
|
+
export declare function calculateADX(highs: number[], lows: number[], closes: number[], params: TAParams): number;
|
|
30
|
+
export declare function calculateStochastic(highs: number[], lows: number[], closes: number[], params: TAParams): number[];
|
|
31
|
+
export declare function calculateOBV(closes: number[], volumes: number[], params: TAParams): number;
|
|
32
|
+
export declare function calculateCCI(highs: number[], lows: number[], closes: number[], params: TAParams): number;
|
|
33
|
+
export declare function calculateMFI(highs: number[], lows: number[], closes: number[], volumes: number[], params: TAParams): number;
|
|
34
|
+
export declare function calculateVWAP(highs: number[], lows: number[], closes: number[], volumes: number[], params: TAParams): number;
|
|
35
|
+
export declare function calculateWilliamsR(highs: number[], lows: number[], closes: number[], params: TAParams): number;
|
|
36
|
+
export declare function calculateStochasticRSI(closes: number[], params: TAParams): number[];
|
|
37
|
+
export declare function calculateROC(closes: number[], params: TAParams): number;
|
|
38
|
+
export declare function calculateParabolicSAR(highs: number[], lows: number[], params: TAParams): number;
|
|
39
|
+
export declare function calculateKeltnerChannels(highs: number[], lows: number[], closes: number[], params: TAParams): number[];
|
|
40
|
+
export declare function calculateWMA(closes: number[], params: TAParams): number;
|
|
41
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/types.d.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vesper85/technical-indicators",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Technical indicators library for financial analysis",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"technical-indicators",
|
|
8
|
+
"trading",
|
|
9
|
+
"finance",
|
|
10
|
+
"indicators",
|
|
11
|
+
"ta",
|
|
12
|
+
"technical-analysis"
|
|
13
|
+
],
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"!**/*.map"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup && npm run types",
|
|
34
|
+
"types": "tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
35
|
+
"build:watch": "tsup --watch",
|
|
36
|
+
"dev": "tsup --watch",
|
|
37
|
+
"lint": "eslint \"src/**/*.ts*\"",
|
|
38
|
+
"lint:fix": "eslint \"src/**/*.ts*\" --fix",
|
|
39
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
40
|
+
"type-check": "tsc --noEmit",
|
|
41
|
+
"test:service": "tsx test-ta-service.ts",
|
|
42
|
+
"prepublishOnly": "npm run build",
|
|
43
|
+
"changeset": "changeset",
|
|
44
|
+
"version": "changeset version",
|
|
45
|
+
"release": "npm run build && changeset publish"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@changesets/cli": "^2.29.8",
|
|
49
|
+
"@osiris-ai/eslint-config": "workspace:*",
|
|
50
|
+
"@osiris-ai/typescript-config": "workspace:*",
|
|
51
|
+
"@types/node": "^24.0.8",
|
|
52
|
+
"eslint": "^8.37.0",
|
|
53
|
+
"tsup": "^8.5.0",
|
|
54
|
+
"tsx": "^4.19.2",
|
|
55
|
+
"typescript": "^5.8.3"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@osiris-ai/logger": "workspace:*",
|
|
59
|
+
"technicalindicators": "^3.1.0"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18.0.0"
|
|
63
|
+
},
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "https://github.com/FetcchX/legion-sdk.git"
|
|
67
|
+
},
|
|
68
|
+
"bugs": {
|
|
69
|
+
"url": "https://github.com/FetcchX/legion-sdk/issues"
|
|
70
|
+
},
|
|
71
|
+
"homepage": "https://github.com/FetcchX/legion-sdk#readme"
|
|
72
|
+
}
|