market-data-analyzer 2.0.1 → 2.1.1

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/src/utils/math.ts DELETED
@@ -1,342 +0,0 @@
1
- /**
2
- * Financial math utilities -- technical indicators, risk metrics, etc.
3
- * Pure functions with no external dependencies.
4
- */
5
-
6
- // ---------------------------------------------------------------------------
7
- // Basic statistics
8
- // ---------------------------------------------------------------------------
9
-
10
- export function mean(values: number[]): number {
11
- if (values.length === 0) return 0;
12
- return values.reduce((s, v) => s + v, 0) / values.length;
13
- }
14
-
15
- export function stdDev(values: number[]): number {
16
- if (values.length < 2) return 0;
17
- const m = mean(values);
18
- // Use sample variance (N-1) for unbiased estimation from finite samples
19
- const variance = values.reduce((s, v) => s + (v - m) ** 2, 0) / (values.length - 1);
20
- return Math.sqrt(variance);
21
- }
22
-
23
- export function percentile(sorted: number[], p: number): number {
24
- if (sorted.length === 0) return 0;
25
- if (sorted.length === 1) return sorted[0]!;
26
- // Clamp p to [0, 100] to avoid out-of-bounds access
27
- const clampedP = Math.max(0, Math.min(100, p));
28
- const idx = (clampedP / 100) * (sorted.length - 1);
29
- const lower = Math.floor(idx);
30
- const upper = Math.ceil(idx);
31
- if (lower === upper) return sorted[lower]!;
32
- const frac = idx - lower;
33
- return sorted[lower]! * (1 - frac) + sorted[upper]! * frac;
34
- }
35
-
36
- export function pricesToReturns(prices: number[]): number[] {
37
- const returns: number[] = [];
38
- for (let i = 1; i < prices.length; i++) {
39
- const prev = prices[i - 1]!;
40
- if (prev !== 0) {
41
- returns.push((prices[i]! - prev) / prev);
42
- }
43
- }
44
- return returns;
45
- }
46
-
47
- export function correlation(a: number[], b: number[]): number {
48
- const n = Math.min(a.length, b.length);
49
- if (n < 2) return 0;
50
- const ma = mean(a.slice(0, n));
51
- const mb = mean(b.slice(0, n));
52
- let sumAB = 0, sumA2 = 0, sumB2 = 0;
53
- for (let i = 0; i < n; i++) {
54
- const da = a[i]! - ma;
55
- const db = b[i]! - mb;
56
- sumAB += da * db;
57
- sumA2 += da * da;
58
- sumB2 += db * db;
59
- }
60
- const denom = Math.sqrt(sumA2 * sumB2);
61
- return denom > 0 ? sumAB / denom : 0;
62
- }
63
-
64
- // ---------------------------------------------------------------------------
65
- // Moving averages
66
- // ---------------------------------------------------------------------------
67
-
68
- export function sma(values: number[], period: number): (number | null)[] {
69
- if (period <= 0) return values.map(() => null);
70
- const result: (number | null)[] = [];
71
- for (let i = 0; i < values.length; i++) {
72
- if (i < period - 1) {
73
- result.push(null);
74
- } else {
75
- let sum = 0;
76
- for (let j = i - period + 1; j <= i; j++) sum += values[j]!;
77
- result.push(sum / period);
78
- }
79
- }
80
- return result;
81
- }
82
-
83
- export function ema(values: number[], period: number): (number | null)[] {
84
- if (period <= 0) return values.map(() => null);
85
- const result: (number | null)[] = [];
86
- const multiplier = 2 / (period + 1);
87
- let prev: number | null = null;
88
-
89
- for (let i = 0; i < values.length; i++) {
90
- if (i < period - 1) {
91
- result.push(null);
92
- } else if (prev === null) {
93
- let sum = 0;
94
- for (let j = i - period + 1; j <= i; j++) sum += values[j]!;
95
- prev = sum / period;
96
- result.push(prev);
97
- } else {
98
- prev = (values[i]! - prev) * multiplier + prev;
99
- result.push(prev);
100
- }
101
- }
102
- return result;
103
- }
104
-
105
- /** Get the latest non-null value from an indicator series. */
106
- export function lastValue(series: (number | null)[]): number | null {
107
- for (let i = series.length - 1; i >= 0; i--) {
108
- if (series[i] !== null) return series[i];
109
- }
110
- return null;
111
- }
112
-
113
- // ---------------------------------------------------------------------------
114
- // RSI (Relative Strength Index)
115
- // ---------------------------------------------------------------------------
116
-
117
- export function rsi(closes: number[], period: number = 14): (number | null)[] {
118
- if (closes.length < period + 1) return closes.map(() => null);
119
-
120
- const result: (number | null)[] = [null];
121
- const gains: number[] = [];
122
- const losses: number[] = [];
123
-
124
- for (let i = 1; i < closes.length; i++) {
125
- const change = closes[i]! - closes[i - 1]!;
126
- gains.push(change > 0 ? change : 0);
127
- losses.push(change < 0 ? -change : 0);
128
- }
129
-
130
- let avgGain = 0;
131
- let avgLoss = 0;
132
-
133
- // Initial averages
134
- for (let i = 0; i < period; i++) {
135
- avgGain += gains[i]!;
136
- avgLoss += losses[i]!;
137
- }
138
- avgGain /= period;
139
- avgLoss /= period;
140
-
141
- for (let i = 0; i < gains.length; i++) {
142
- if (i < period - 1) {
143
- result.push(null);
144
- } else if (i === period - 1) {
145
- if (avgLoss === 0) {
146
- result.push(100);
147
- } else {
148
- result.push(100 - 100 / (1 + avgGain / avgLoss));
149
- }
150
- } else {
151
- // Wilder smoothing
152
- avgGain = (avgGain * (period - 1) + gains[i]!) / period;
153
- avgLoss = (avgLoss * (period - 1) + losses[i]!) / period;
154
- if (avgLoss === 0) {
155
- result.push(100);
156
- } else {
157
- result.push(100 - 100 / (1 + avgGain / avgLoss));
158
- }
159
- }
160
- }
161
-
162
- return result;
163
- }
164
-
165
- // ---------------------------------------------------------------------------
166
- // MACD
167
- // ---------------------------------------------------------------------------
168
-
169
- export interface MACDResult {
170
- line: (number | null)[];
171
- signal: (number | null)[];
172
- histogram: (number | null)[];
173
- }
174
-
175
- export function macd(
176
- closes: number[],
177
- fastPeriod = 12,
178
- slowPeriod = 26,
179
- signalPeriod = 9,
180
- ): MACDResult {
181
- const fastEma = ema(closes, fastPeriod);
182
- const slowEma = ema(closes, slowPeriod);
183
-
184
- const macdLine: (number | null)[] = [];
185
- for (let i = 0; i < closes.length; i++) {
186
- if (fastEma[i] !== null && slowEma[i] !== null) {
187
- macdLine.push(fastEma[i]! - slowEma[i]!);
188
- } else {
189
- macdLine.push(null);
190
- }
191
- }
192
-
193
- // Signal line = EMA of MACD line
194
- const nonNull: number[] = [];
195
- const nonNullIdx: number[] = [];
196
- for (let i = 0; i < macdLine.length; i++) {
197
- if (macdLine[i] !== null) {
198
- nonNull.push(macdLine[i]!);
199
- nonNullIdx.push(i);
200
- }
201
- }
202
-
203
- const sigEma = ema(nonNull, signalPeriod);
204
- const signalLine: (number | null)[] = new Array(closes.length).fill(null);
205
- for (let i = 0; i < nonNullIdx.length; i++) {
206
- signalLine[nonNullIdx[i]!] = sigEma[i] ?? null;
207
- }
208
-
209
- const histogram: (number | null)[] = [];
210
- for (let i = 0; i < closes.length; i++) {
211
- if (macdLine[i] !== null && signalLine[i] !== null) {
212
- histogram.push(macdLine[i]! - signalLine[i]!);
213
- } else {
214
- histogram.push(null);
215
- }
216
- }
217
-
218
- return { line: macdLine, signal: signalLine, histogram };
219
- }
220
-
221
- // ---------------------------------------------------------------------------
222
- // Support / Resistance (pivot-based)
223
- // ---------------------------------------------------------------------------
224
-
225
- export function findSupportResistance(
226
- highs: number[],
227
- lows: number[],
228
- closes: number[],
229
- lookback: number = 20,
230
- ): { support: number[]; resistance: number[] } {
231
- const support: number[] = [];
232
- const resistance: number[] = [];
233
-
234
- if (closes.length < lookback) {
235
- return { support: [], resistance: [] };
236
- }
237
-
238
- const currentPrice = closes[closes.length - 1]!;
239
-
240
- // Find local minima (support) and maxima (resistance)
241
- const windowSize = Math.max(5, Math.floor(lookback / 4));
242
- for (let i = windowSize; i < closes.length - windowSize; i++) {
243
- let isLocalMin = true;
244
- let isLocalMax = true;
245
-
246
- for (let j = i - windowSize; j <= i + windowSize; j++) {
247
- if (j === i) continue;
248
- if (lows[j]! < lows[i]!) isLocalMin = false;
249
- if (highs[j]! > highs[i]!) isLocalMax = false;
250
- }
251
-
252
- if (isLocalMin && lows[i]! < currentPrice) {
253
- support.push(Math.round(lows[i]! * 100) / 100);
254
- }
255
- if (isLocalMax && highs[i]! > currentPrice) {
256
- resistance.push(Math.round(highs[i]! * 100) / 100);
257
- }
258
- }
259
-
260
- // Deduplicate: merge levels within 1% of each other
261
- const dedup = (levels: number[]): number[] => {
262
- if (levels.length === 0) return [];
263
- levels.sort((a, b) => a - b);
264
- const merged: number[] = [levels[0]!];
265
- for (let i = 1; i < levels.length; i++) {
266
- const last = merged[merged.length - 1]!;
267
- if (last > 0 && Math.abs(levels[i]! - last) / last < 0.01) {
268
- merged[merged.length - 1] = (last + levels[i]!) / 2;
269
- } else {
270
- merged.push(levels[i]!);
271
- }
272
- }
273
- return merged;
274
- };
275
-
276
- // Return closest 3 levels each
277
- const dedupedSupport = dedup(support).sort((a, b) => b - a).slice(0, 3);
278
- const dedupedResistance = dedup(resistance).sort((a, b) => a - b).slice(0, 3);
279
-
280
- return { support: dedupedSupport, resistance: dedupedResistance };
281
- }
282
-
283
- // ---------------------------------------------------------------------------
284
- // Max drawdown
285
- // ---------------------------------------------------------------------------
286
-
287
- export function maxDrawdown(prices: number[]): number {
288
- if (prices.length < 2) return 0;
289
- let peak = prices[0]!;
290
- let worst = 0;
291
- for (const p of prices) {
292
- if (p > peak) peak = p;
293
- // Guard against division by zero if peak is somehow 0
294
- if (peak > 0) {
295
- const dd = (peak - p) / peak;
296
- if (dd > worst) worst = dd;
297
- }
298
- }
299
- return worst;
300
- }
301
-
302
- // ---------------------------------------------------------------------------
303
- // Sharpe ratio approximation
304
- // ---------------------------------------------------------------------------
305
-
306
- const TRADING_DAYS = 252;
307
- const DEFAULT_RISK_FREE_RATE = 0.045; // ~4.5% annualized
308
-
309
- export function sharpeRatio(
310
- returns: number[],
311
- riskFreeRate: number = DEFAULT_RISK_FREE_RATE,
312
- ): number {
313
- if (returns.length < 2) return 0;
314
- const dailyRfr = riskFreeRate / TRADING_DAYS;
315
- const avgReturn = mean(returns);
316
- const vol = stdDev(returns);
317
- if (vol === 0) return 0;
318
- return ((avgReturn - dailyRfr) / vol) * Math.sqrt(TRADING_DAYS);
319
- }
320
-
321
- // ---------------------------------------------------------------------------
322
- // Formatting helpers
323
- // ---------------------------------------------------------------------------
324
-
325
- export function formatCurrency(n: number): string {
326
- if (Math.abs(n) >= 1e12) return `$${(n / 1e12).toFixed(2)}T`;
327
- if (Math.abs(n) >= 1e9) return `$${(n / 1e9).toFixed(2)}B`;
328
- if (Math.abs(n) >= 1e6) return `$${(n / 1e6).toFixed(2)}M`;
329
- return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
330
- }
331
-
332
- export function formatVolume(n: number): string {
333
- if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
334
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
335
- if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
336
- return n.toString();
337
- }
338
-
339
- export function formatPct(n: number, decimals: number = 2): string {
340
- const sign = n >= 0 ? "+" : "";
341
- return `${sign}${(n * 100).toFixed(decimals)}%`;
342
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "Node16",
5
- "moduleResolution": "Node16",
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "resolveJsonModule": true,
13
- "declaration": true,
14
- "declarationMap": true,
15
- "sourceMap": true
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["node_modules", "dist"]
19
- }