@vulcan-js/indicators 0.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/dist/index.js ADDED
@@ -0,0 +1,683 @@
1
+ import { assert, createSignal } from "@vulcan-js/core";
2
+ import { abs, add, div, divide, eq, equal, from, gt, lt, mul, multiply, sub, subtract } from "dnum";
3
+
4
+ //#region src/trend/exponentialMovingAverage.ts
5
+ const defaultExponentialMovingAverageOptions = { period: 12 };
6
+ /**
7
+ * Exponential Moving Average (EMA)
8
+ *
9
+ * EMA = Price * k + PrevEMA * (1 - k)
10
+ * Where k = 2 / (period + 1)
11
+ */
12
+ const ema = createSignal(({ period }) => {
13
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
14
+ const k = divide(from(2, 18), from(1 + period, 18), 18);
15
+ const m = subtract(from(1, 18), k);
16
+ let prev;
17
+ return (value) => {
18
+ if (prev === void 0) {
19
+ prev = from(value, 18);
20
+ return prev;
21
+ }
22
+ prev = add(mul(value, k, 18), mul(prev, m, 18));
23
+ return prev;
24
+ };
25
+ }, defaultExponentialMovingAverageOptions);
26
+
27
+ //#endregion
28
+ //#region src/momentum/absolutePriceOscillator.ts
29
+ const defaultAbsolutePriceOscillatorOptions = {
30
+ fastPeriod: 12,
31
+ slowPeriod: 26
32
+ };
33
+ const apo = createSignal(({ fastPeriod, slowPeriod }) => {
34
+ assert(Number.isInteger(fastPeriod) && fastPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected fastPeriod to be a positive integer, got ${fastPeriod}`));
35
+ assert(Number.isInteger(slowPeriod) && slowPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected slowPeriod to be a positive integer, got ${slowPeriod}`));
36
+ const fastProc = ema.create({ period: fastPeriod });
37
+ const slowProc = ema.create({ period: slowPeriod });
38
+ return (value) => sub(fastProc(value), slowProc(value));
39
+ }, defaultAbsolutePriceOscillatorOptions);
40
+
41
+ //#endregion
42
+ //#region src/trend/simpleMovingAverage.ts
43
+ const defaultSMAOptions = { period: 2 };
44
+ /**
45
+ * Simple Moving Average (SMA)
46
+ *
47
+ * Calculates the arithmetic mean of a set of values over a specified period.
48
+ * The SMA is calculated by summing all values in the period and dividing by the period length.
49
+ *
50
+ * Formula: SMA = (P1 + P2 + ... + Pn) / n
51
+ * Where: P = Price values, n = Period
52
+ *
53
+ * @param source - Iterable of price values
54
+ * @param options - Configuration options
55
+ * @param options.period - The period for calculating the moving average (default: 2)
56
+ * @returns Generator yielding SMA values
57
+ */
58
+ const sma = createSignal(({ period }) => {
59
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
60
+ const buffer = Array.from({ length: period });
61
+ let head = 0;
62
+ let count = 0;
63
+ let runningSum = from(0, 18);
64
+ return (value) => {
65
+ const v = from(value, 18);
66
+ if (count < period) {
67
+ buffer[count] = v;
68
+ runningSum = add(runningSum, v);
69
+ count++;
70
+ } else {
71
+ runningSum = subtract(runningSum, buffer[head]);
72
+ runningSum = add(runningSum, v);
73
+ buffer[head] = v;
74
+ head = (head + 1) % period;
75
+ }
76
+ return div(runningSum, count, 18);
77
+ };
78
+ }, defaultSMAOptions);
79
+
80
+ //#endregion
81
+ //#region src/momentum/awesomeOscillator.ts
82
+ const defaultAwesomeOscillatorOptions = {
83
+ fastPeriod: 5,
84
+ slowPeriod: 34
85
+ };
86
+ /**
87
+ * Awesome Oscillator (AO)
88
+ *
89
+ * AO = SMA(median, fastPeriod) - SMA(median, slowPeriod)
90
+ * Where median = (high + low) / 2
91
+ */
92
+ const ao = createSignal(({ fastPeriod, slowPeriod }) => {
93
+ assert(Number.isInteger(fastPeriod) && fastPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected fastPeriod to be a positive integer, got ${fastPeriod}`));
94
+ assert(Number.isInteger(slowPeriod) && slowPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected slowPeriod to be a positive integer, got ${slowPeriod}`));
95
+ const fastProc = sma.create({ period: fastPeriod });
96
+ const slowProc = sma.create({ period: slowPeriod });
97
+ return (bar) => {
98
+ const median = div(add(from(bar.h, 18), from(bar.l, 18)), 2, 18);
99
+ return sub(fastProc(median), slowProc(median));
100
+ };
101
+ }, defaultAwesomeOscillatorOptions);
102
+
103
+ //#endregion
104
+ //#region src/volume/accumulationDistribution.ts
105
+ /**
106
+ * Accumulation/Distribution Indicator (A/D). Cumulative indicator
107
+ * that uses volume and price to assess whether a stock is
108
+ * being accumulated or distributed.
109
+ *
110
+ * MFM = ((Closing - Low) - (High - Closing)) / (High - Low)
111
+ * MFV = MFM * Period Volume
112
+ * AD = Previous AD + CMFV
113
+ */
114
+ const ad = createSignal(() => {
115
+ let prevAD = from(0, 18);
116
+ return (bar) => {
117
+ const h = from(bar.h, 18);
118
+ const l = from(bar.l, 18);
119
+ const c = from(bar.c, 18);
120
+ const v = from(bar.v, 18);
121
+ const range = subtract(h, l);
122
+ prevAD = add(multiply(equal(range, 0) ? from(0, 18) : divide(subtract(subtract(c, l), subtract(h, c)), range, 18), v), prevAD);
123
+ return prevAD;
124
+ };
125
+ });
126
+
127
+ //#endregion
128
+ //#region src/momentum/chaikinOscillator.ts
129
+ const defaultChaikinOscillatorOptions = {
130
+ fastPeriod: 3,
131
+ slowPeriod: 10
132
+ };
133
+ /**
134
+ * The ChaikinOscillator function measures the momentum of the
135
+ * Accumulation/Distribution (A/D) using the Moving Average
136
+ * Convergence Divergence (MACD) formula. It takes the
137
+ * difference between fast and slow periods EMA of the A/D.
138
+ * Cross above the A/D line indicates bullish.
139
+ *
140
+ * CO = Ema(fastPeriod, AD) - Ema(slowPeriod, AD)
141
+ */
142
+ const cmo = createSignal(({ fastPeriod, slowPeriod }) => {
143
+ assert(Number.isInteger(fastPeriod) && fastPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected fastPeriod to be a positive integer, got ${fastPeriod}`));
144
+ assert(Number.isInteger(slowPeriod) && slowPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected slowPeriod to be a positive integer, got ${slowPeriod}`));
145
+ const adProc = ad.create();
146
+ const fastProc = ema.create({ period: fastPeriod });
147
+ const slowProc = ema.create({ period: slowPeriod });
148
+ return (bar) => {
149
+ const adVal = adProc(bar);
150
+ return sub(fastProc(adVal), slowProc(adVal));
151
+ };
152
+ }, defaultChaikinOscillatorOptions);
153
+
154
+ //#endregion
155
+ //#region src/trend/movingMax.ts
156
+ const defaultMovingMaxOptions = { period: 4 };
157
+ /**
158
+ * Moving Maximum (MovingMax)
159
+ */
160
+ const mmax = createSignal(({ period }) => {
161
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
162
+ const buffer = [];
163
+ return (value) => {
164
+ buffer.push(from(value, 18));
165
+ if (buffer.length > period) buffer.shift();
166
+ return buffer.reduce((max, cur) => gt(max, cur) ? max : cur);
167
+ };
168
+ }, defaultMovingMaxOptions);
169
+
170
+ //#endregion
171
+ //#region src/trend/movingMin.ts
172
+ const defaultMovingMinOptions = { period: 4 };
173
+ /**
174
+ * Moving Minimum (MovingMin)
175
+ */
176
+ const mmin = createSignal(({ period }) => {
177
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
178
+ const buffer = [];
179
+ return (value) => {
180
+ buffer.push(from(value, 18));
181
+ if (buffer.length > period) buffer.shift();
182
+ return buffer.reduce((min, cur) => lt(min, cur) ? min : cur);
183
+ };
184
+ }, defaultMovingMinOptions);
185
+
186
+ //#endregion
187
+ //#region src/momentum/ichimokuCloud.ts
188
+ const defaultIchimokuCloudOptions = {
189
+ conversionPeriod: 9,
190
+ basePeriod: 26,
191
+ leadingBPeriod: 52
192
+ };
193
+ /**
194
+ * Ichimoku Cloud (Ichimoku Kinko Hyo)
195
+ *
196
+ * Computes raw values for each component. Displacement (shifting
197
+ * Leading Spans forward and Lagging Span backward on the chart)
198
+ * is a presentation concern left to the consumer.
199
+ *
200
+ * - Conversion (Tenkan-sen): (highest high + lowest low) / 2 over conversionPeriod
201
+ * - Base (Kijun-sen): (highest high + lowest low) / 2 over basePeriod
202
+ * - Leading Span A (Senkou A): (conversion + base) / 2
203
+ * - Leading Span B (Senkou B): (highest high + lowest low) / 2 over leadingBPeriod
204
+ * - Lagging (Chikou): current close price
205
+ */
206
+ const ichimokuCloud = createSignal(({ conversionPeriod, basePeriod, leadingBPeriod }) => {
207
+ assert(Number.isInteger(conversionPeriod) && conversionPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected conversionPeriod to be a positive integer, got ${conversionPeriod}`));
208
+ assert(Number.isInteger(basePeriod) && basePeriod >= 1, /* @__PURE__ */ new RangeError(`Expected basePeriod to be a positive integer, got ${basePeriod}`));
209
+ assert(Number.isInteger(leadingBPeriod) && leadingBPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected leadingBPeriod to be a positive integer, got ${leadingBPeriod}`));
210
+ const convHighProc = mmax.create({ period: conversionPeriod });
211
+ const convLowProc = mmin.create({ period: conversionPeriod });
212
+ const baseHighProc = mmax.create({ period: basePeriod });
213
+ const baseLowProc = mmin.create({ period: basePeriod });
214
+ const leadBHighProc = mmax.create({ period: leadingBPeriod });
215
+ const leadBLowProc = mmin.create({ period: leadingBPeriod });
216
+ return (bar) => {
217
+ const h = from(bar.h, 18);
218
+ const l = from(bar.l, 18);
219
+ const conversion = div(add(convHighProc(h), convLowProc(l)), 2, 18);
220
+ const base = div(add(baseHighProc(h), baseLowProc(l)), 2, 18);
221
+ return {
222
+ conversion,
223
+ base,
224
+ leadingA: div(add(conversion, base), 2, 18),
225
+ leadingB: div(add(leadBHighProc(h), leadBLowProc(l)), 2, 18),
226
+ lagging: from(bar.c, 18)
227
+ };
228
+ };
229
+ }, defaultIchimokuCloudOptions);
230
+
231
+ //#endregion
232
+ //#region src/momentum/percentagePriceOscillator.ts
233
+ const defaultPercentagePriceOscillatorOptions = {
234
+ fastPeriod: 12,
235
+ slowPeriod: 26,
236
+ signalPeriod: 9
237
+ };
238
+ /**
239
+ * Percentage Price Oscillator (PPO)
240
+ *
241
+ * The Percentage Price Oscillator (PPO) is a momentum oscillator that measures the difference
242
+ * between two moving averages as a percentage of the slower moving average. It consists of three components:
243
+ * - PPO Line: ((Fast EMA - Slow EMA) / Slow EMA) * 100
244
+ * - Signal Line: EMA of the PPO line
245
+ * - Histogram: PPO - Signal
246
+ *
247
+ * Formula:
248
+ * - PPO = ((EMA(fastPeriod, prices) - EMA(slowPeriod, prices)) / EMA(slowPeriod, prices)) * 100
249
+ * - Signal = EMA(signalPeriod, PPO)
250
+ * - Histogram = PPO - Signal
251
+ *
252
+ * @param source - Iterable of price values
253
+ * @param options - Configuration options
254
+ * @param options.fastPeriod - Period for the fast EMA (default: 12)
255
+ * @param options.slowPeriod - Period for the slow EMA (default: 26)
256
+ * @param options.signalPeriod - Period for the signal EMA (default: 9)
257
+ * @returns Generator yielding PPO point objects
258
+ */
259
+ const ppo = createSignal(({ fastPeriod, slowPeriod, signalPeriod }) => {
260
+ assert(Number.isInteger(fastPeriod) && fastPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected fastPeriod to be a positive integer, got ${fastPeriod}`));
261
+ assert(Number.isInteger(slowPeriod) && slowPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected slowPeriod to be a positive integer, got ${slowPeriod}`));
262
+ assert(Number.isInteger(signalPeriod) && signalPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected signalPeriod to be a positive integer, got ${signalPeriod}`));
263
+ const fastProc = ema.create({ period: fastPeriod });
264
+ const slowProc = ema.create({ period: slowPeriod });
265
+ const signalProc = ema.create({ period: signalPeriod });
266
+ return (value) => {
267
+ const fast = fastProc(from(value, 18));
268
+ const slow = slowProc(from(value, 18));
269
+ const ppoVal = eq(slow, 0) ? from(0, 18) : mul(div(sub(fast, slow), slow, 18), 100, 18);
270
+ const sig = signalProc(ppoVal);
271
+ return {
272
+ ppo: ppoVal,
273
+ signal: sig,
274
+ histogram: sub(ppoVal, sig)
275
+ };
276
+ };
277
+ }, defaultPercentagePriceOscillatorOptions);
278
+
279
+ //#endregion
280
+ //#region src/trend/rollingMovingAverage.ts
281
+ const defaultRMAOptions = { period: 4 };
282
+ /**
283
+ * Rolling moving average (RMA).
284
+ *
285
+ * R[0] to R[p-1] is SMA(values)
286
+ *
287
+ * R[p] and after is R[i] = ((R[i-1]*(p-1)) + v[i]) / p
288
+ */
289
+ const rma = createSignal(({ period }) => {
290
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
291
+ let count = 0;
292
+ let sum = from(0, 18);
293
+ let prev = from(0, 18);
294
+ return (value) => {
295
+ if (count < period) {
296
+ sum = add(sum, value);
297
+ count++;
298
+ prev = div(sum, count, 18);
299
+ return prev;
300
+ }
301
+ prev = div(add(mul(prev, period - 1, 18), value), period, 18);
302
+ return prev;
303
+ };
304
+ }, defaultRMAOptions);
305
+
306
+ //#endregion
307
+ //#region src/momentum/relativeStrengthIndex.ts
308
+ const defaultRSIOptions = { period: 14 };
309
+ /**
310
+ * Relative Strength Index (RSI). It is a momentum indicator that measures the magnitude of
311
+ * recent price changes to evaluate overbought and oversold conditions
312
+ * using the given window period.
313
+ *
314
+ * RS = Average Gain / Average Loss
315
+ *
316
+ * RSI = 100 - (100 / (1 + RS))
317
+ */
318
+ const rsi = createSignal(({ period }) => {
319
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
320
+ const gainProc = rma.create({ period });
321
+ const lossProc = rma.create({ period });
322
+ let prev;
323
+ return (value) => {
324
+ const price = from(value, 18);
325
+ if (prev === void 0) {
326
+ prev = price;
327
+ gainProc(from(0, 18));
328
+ lossProc(from(0, 18));
329
+ return from(0, 18);
330
+ }
331
+ const change = sub(price, prev);
332
+ prev = price;
333
+ const gain = gt(change, 0) ? change : from(0, 18);
334
+ const loss = gt(change, 0) ? from(0, 18) : mul(change, -1, 18);
335
+ const avgGain = gainProc(gain);
336
+ const avgLoss = lossProc(loss);
337
+ if (eq(avgLoss, 0)) return from(100, 18);
338
+ return sub(100, div(100, add(1, div(avgGain, avgLoss, 18)), 18));
339
+ };
340
+ }, defaultRSIOptions);
341
+
342
+ //#endregion
343
+ //#region src/momentum/stochasticOscillator.ts
344
+ const defaultStochasticOscillatorOptions = {
345
+ kPeriod: 14,
346
+ slowingPeriod: 1,
347
+ dPeriod: 3
348
+ };
349
+ /**
350
+ * Stochastic Oscillator
351
+ *
352
+ * %K = ((Close - Lowest Low) / (Highest High - Lowest Low)) * 100
353
+ * %D = SMA(%K, dPeriod)
354
+ */
355
+ const stoch = createSignal(({ kPeriod, slowingPeriod, dPeriod }) => {
356
+ assert(Number.isInteger(kPeriod) && kPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected kPeriod to be a positive integer, got ${kPeriod}`));
357
+ assert(Number.isInteger(slowingPeriod) && slowingPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected slowingPeriod to be a positive integer, got ${slowingPeriod}`));
358
+ assert(Number.isInteger(dPeriod) && dPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected dPeriod to be a positive integer, got ${dPeriod}`));
359
+ const mmaxProc = mmax.create({ period: kPeriod });
360
+ const mminProc = mmin.create({ period: kPeriod });
361
+ const slowingProc = slowingPeriod > 1 ? sma.create({ period: slowingPeriod }) : null;
362
+ const dProc = sma.create({ period: dPeriod });
363
+ return (bar) => {
364
+ const h = from(bar.h, 18);
365
+ const l = from(bar.l, 18);
366
+ const c = from(bar.c, 18);
367
+ const highestHigh = mmaxProc(h);
368
+ const lowestLow = mminProc(l);
369
+ const range = sub(highestHigh, lowestLow, 18);
370
+ const rawK = eq(range, 0) ? from(0, 18) : mul(div(sub(c, lowestLow, 18), range, 18), 100, 18);
371
+ const k = slowingProc ? slowingProc(rawK) : rawK;
372
+ return {
373
+ k,
374
+ d: dProc(k)
375
+ };
376
+ };
377
+ }, defaultStochasticOscillatorOptions);
378
+
379
+ //#endregion
380
+ //#region src/trend/aroon.ts
381
+ const defaultAroonOptions = { period: 25 };
382
+ /**
383
+ * Aroon Indicator
384
+ *
385
+ * Aroon Up = ((period - days since highest high) / period) * 100
386
+ * Aroon Down = ((period - days since lowest low) / period) * 100
387
+ * Oscillator = Aroon Up - Aroon Down
388
+ */
389
+ const aroon = createSignal(({ period }) => {
390
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
391
+ const highBuffer = [];
392
+ const lowBuffer = [];
393
+ return (bar) => {
394
+ const h = from(bar.h, 18);
395
+ const l = from(bar.l, 18);
396
+ highBuffer.push(h);
397
+ lowBuffer.push(l);
398
+ if (highBuffer.length > period + 1) highBuffer.shift();
399
+ if (lowBuffer.length > period + 1) lowBuffer.shift();
400
+ let highestIdx = 0;
401
+ let lowestIdx = 0;
402
+ for (let j = 1; j < highBuffer.length; j++) {
403
+ if (!gt(highBuffer[highestIdx], highBuffer[j])) highestIdx = j;
404
+ if (!lt(lowBuffer[lowestIdx], lowBuffer[j])) lowestIdx = j;
405
+ }
406
+ const daysSinceHigh = highBuffer.length - 1 - highestIdx;
407
+ const daysSinceLow = lowBuffer.length - 1 - lowestIdx;
408
+ const periodDnum = from(period, 18);
409
+ const up = divide(multiply(from(period - daysSinceHigh, 18), 100, 18), periodDnum, 18);
410
+ const down = divide(multiply(from(period - daysSinceLow, 18), 100, 18), periodDnum, 18);
411
+ return {
412
+ up,
413
+ down,
414
+ oscillator: subtract(up, down)
415
+ };
416
+ };
417
+ }, defaultAroonOptions);
418
+
419
+ //#endregion
420
+ //#region src/trend/balanceOfPower.ts
421
+ const bop = createSignal(() => {
422
+ return (bar) => {
423
+ const o = from(bar.o, 18);
424
+ const h = from(bar.h, 18);
425
+ const l = from(bar.l, 18);
426
+ const c = from(bar.c, 18);
427
+ const range = subtract(h, l);
428
+ if (equal(range, 0)) return from(0, 18);
429
+ return divide(subtract(c, o), range, 18);
430
+ };
431
+ });
432
+
433
+ //#endregion
434
+ //#region src/trend/chandeForecastOscillator.ts
435
+ const defaultCFOOptions = { period: 14 };
436
+ /**
437
+ * Chande Forecast Oscillator (CFO)
438
+ *
439
+ * Measures the percentage difference between the actual close price and the
440
+ * n-period linear regression forecast price. Positive values indicate bullish
441
+ * momentum (price above forecast), negative values indicate bearish momentum.
442
+ *
443
+ * Formula: CFO = ((Close - Forecast) / Close) * 100
444
+ * Where: Forecast = Linear regression value at current point
445
+ *
446
+ * @param source - Iterable of price values
447
+ * @param options - Configuration options
448
+ * @param options.period - The period for linear regression (default: 14)
449
+ * @returns Generator yielding CFO values as percentages
450
+ */
451
+ const cfo = createSignal(({ period }) => {
452
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
453
+ const buffer = [];
454
+ return (value) => {
455
+ buffer.push(from(value, 18));
456
+ if (buffer.length > period) buffer.shift();
457
+ const n = buffer.length;
458
+ if (n < 2) return from(0, 18);
459
+ const xSum = n * (n + 1) / 2;
460
+ const denom = n * (n * (n + 1) * (2 * n + 1) / 6) - xSum * xSum;
461
+ let sumY = from(0, 18);
462
+ let sumXY = from(0, 18);
463
+ for (let i = 0; i < n; i++) {
464
+ sumY = add(sumY, buffer[i]);
465
+ sumXY = add(sumXY, multiply(buffer[i], i + 1));
466
+ }
467
+ const slope = divide(subtract(multiply(sumXY, n), multiply(sumY, xSum)), denom, 18);
468
+ const intercept = divide(subtract(sumY, multiply(slope, xSum)), n, 18);
469
+ const forecast = add(multiply(slope, n), intercept);
470
+ const close = buffer[n - 1];
471
+ if (equal(close, 0)) return from(0, 18);
472
+ return divide(multiply(subtract(close, forecast), 100), close, 18);
473
+ };
474
+ }, defaultCFOOptions);
475
+
476
+ //#endregion
477
+ //#region src/trend/commodityChannelIndex.ts
478
+ const defaultCCIOptions = { period: 20 };
479
+ /**
480
+ * Commodity Channel Index (CCI)
481
+ *
482
+ * Developed by Donald Lambert in 1980, CCI measures the deviation of the
483
+ * typical price from its simple moving average, normalized by mean deviation.
484
+ * The constant 0.015 ensures approximately 70-80% of CCI values fall between
485
+ * +100 and -100.
486
+ *
487
+ * Formula:
488
+ * TP = (High + Low + Close) / 3
489
+ * CCI = (TP - SMA(TP, period)) / (0.015 * Mean Deviation)
490
+ * Mean Deviation = SUM(|TP_i - SMA|) / period
491
+ *
492
+ * @param source - Iterable of OHLC candle data
493
+ * @param options - Configuration options
494
+ * @param options.period - The period for CCI calculation (default: 20)
495
+ * @returns Generator yielding CCI values
496
+ */
497
+ const cci = createSignal(({ period }) => {
498
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
499
+ const buffer = [];
500
+ return (bar) => {
501
+ const h = from(bar.h, 18);
502
+ const l = from(bar.l, 18);
503
+ const c = from(bar.c, 18);
504
+ const tp = divide(add(add(h, l), c), 3, 18);
505
+ buffer.push(tp);
506
+ if (buffer.length > period) buffer.shift();
507
+ const n = buffer.length;
508
+ if (n < period) return from(0, 18);
509
+ let sum = from(0, 18);
510
+ for (const v of buffer) sum = add(sum, v);
511
+ const smaVal = divide(sum, n, 18);
512
+ let devSum = from(0, 18);
513
+ for (const v of buffer) devSum = add(devSum, abs(subtract(v, smaVal)));
514
+ const meanDev = divide(devSum, n, 18);
515
+ if (equal(meanDev, 0)) return from(0, 18);
516
+ const currentTP = buffer[n - 1];
517
+ return divide(subtract(currentTP, smaVal), divide([meanDev[0] * 15n, meanDev[1]], [1000n, 0], 18), 18);
518
+ };
519
+ }, defaultCCIOptions);
520
+
521
+ //#endregion
522
+ //#region src/trend/doubleExponentialMovingAverage.ts
523
+ const defaultDoubleExponentialMovingAverageOptions = { period: 12 };
524
+ /**
525
+ * Double Exponential Moving Average (DEMA)
526
+ *
527
+ * DEMA reduces lag compared to a traditional EMA by applying the formula:
528
+ * DEMA = 2 * EMA(data, period) - EMA(EMA(data, period), period)
529
+ *
530
+ * @param source - Iterable of input values
531
+ * @param options - Configuration options
532
+ * @param options.period - The lookback period (default: 12)
533
+ * @returns Generator yielding DEMA values
534
+ */
535
+ const dema = createSignal(({ period }) => {
536
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
537
+ const ema1 = ema.create({ period });
538
+ const ema2 = ema.create({ period });
539
+ return (value) => {
540
+ const e1 = ema1(value);
541
+ const e2 = ema2(e1);
542
+ return sub(mul(e1, 2, 18), e2);
543
+ };
544
+ }, defaultDoubleExponentialMovingAverageOptions);
545
+
546
+ //#endregion
547
+ //#region src/trend/macd.ts
548
+ const defaultMACDOptions = {
549
+ fastPeriod: 12,
550
+ slowPeriod: 26,
551
+ signalPeriod: 9
552
+ };
553
+ /**
554
+ * Moving Average Convergence Divergence (MACD)
555
+ *
556
+ * MACD is a trend-following momentum indicator that shows the relationship
557
+ * between two exponential moving averages of prices. It consists of three components:
558
+ * - MACD Line: Fast EMA - Slow EMA
559
+ * - Signal Line: EMA of the MACD line
560
+ * - Histogram: MACD - Signal
561
+ *
562
+ * Formula:
563
+ * - MACD = EMA(fastPeriod, prices) - EMA(slowPeriod, prices)
564
+ * - Signal = EMA(signalPeriod, MACD)
565
+ * - Histogram = MACD - Signal
566
+ *
567
+ * @param source - Iterable of price values
568
+ * @param options - Configuration options
569
+ * @param options.fastPeriod - Period for the fast EMA (default: 12)
570
+ * @param options.slowPeriod - Period for the slow EMA (default: 26)
571
+ * @param options.signalPeriod - Period for the signal EMA (default: 9)
572
+ * @returns Generator yielding MACDPoint objects
573
+ */
574
+ const macd = createSignal(({ fastPeriod, slowPeriod, signalPeriod }) => {
575
+ assert(Number.isInteger(fastPeriod) && fastPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected fastPeriod to be a positive integer, got ${fastPeriod}`));
576
+ assert(Number.isInteger(slowPeriod) && slowPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected slowPeriod to be a positive integer, got ${slowPeriod}`));
577
+ assert(Number.isInteger(signalPeriod) && signalPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected signalPeriod to be a positive integer, got ${signalPeriod}`));
578
+ const fastProc = ema.create({ period: fastPeriod });
579
+ const slowProc = ema.create({ period: slowPeriod });
580
+ const signalProc = ema.create({ period: signalPeriod });
581
+ return (value) => {
582
+ const m = sub(fastProc(value), slowProc(value));
583
+ const sig = signalProc(m);
584
+ return {
585
+ macd: m,
586
+ signal: sig,
587
+ histogram: sub(m, sig)
588
+ };
589
+ };
590
+ }, defaultMACDOptions);
591
+
592
+ //#endregion
593
+ //#region src/trend/movingSum.ts
594
+ const defaultMovingSumOptions = { period: 4 };
595
+ /**
596
+ * Moving Sum
597
+ *
598
+ * Calculates the sum of values in a sliding window of the specified period.
599
+ */
600
+ const msum = createSignal(({ period }) => {
601
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
602
+ const buffer = Array.from({ length: period });
603
+ let head = 0;
604
+ let count = 0;
605
+ let runningSum = from(0, 18);
606
+ return (value) => {
607
+ const v = from(value, 18);
608
+ if (count < period) {
609
+ buffer[count] = v;
610
+ runningSum = add(runningSum, v);
611
+ count++;
612
+ } else {
613
+ runningSum = subtract(runningSum, buffer[head]);
614
+ runningSum = add(runningSum, v);
615
+ buffer[head] = v;
616
+ head = (head + 1) % period;
617
+ }
618
+ return runningSum;
619
+ };
620
+ }, defaultMovingSumOptions);
621
+
622
+ //#endregion
623
+ //#region src/trend/massIndex.ts
624
+ const defaultMassIndexOptions = {
625
+ emaPeriod: 9,
626
+ miPeriod: 25
627
+ };
628
+ /**
629
+ * Mass Index (MI)
630
+ *
631
+ * Developed by Donald Dorsey, the Mass Index uses the high-low range
632
+ * to identify trend reversals based on range expansions. A "reversal bulge"
633
+ * occurs when the Mass Index rises above 27 and then falls below 26.5.
634
+ *
635
+ * Formula:
636
+ * Range = High - Low
637
+ * EMA1 = EMA(Range, emaPeriod)
638
+ * EMA2 = EMA(EMA1, emaPeriod)
639
+ * Ratio = EMA1 / EMA2
640
+ * MI = MovingSum(Ratio, miPeriod)
641
+ *
642
+ * @param source - Iterable of OHLC candle data (requires high and low)
643
+ * @param options - Configuration options
644
+ * @param options.emaPeriod - The EMA smoothing period (default: 9)
645
+ * @param options.miPeriod - The moving sum period (default: 25)
646
+ * @returns Generator yielding Mass Index values
647
+ */
648
+ const mi = createSignal(({ emaPeriod, miPeriod }) => {
649
+ assert(Number.isInteger(emaPeriod) && emaPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected emaPeriod to be a positive integer, got ${emaPeriod}`));
650
+ assert(Number.isInteger(miPeriod) && miPeriod >= 1, /* @__PURE__ */ new RangeError(`Expected miPeriod to be a positive integer, got ${miPeriod}`));
651
+ const ema1Proc = ema.create({ period: emaPeriod });
652
+ const ema2Proc = ema.create({ period: emaPeriod });
653
+ const msumProc = msum.create({ period: miPeriod });
654
+ return (bar) => {
655
+ const e1 = ema1Proc(subtract(from(bar.h, 18), from(bar.l, 18)));
656
+ return msumProc(divide(e1, ema2Proc(e1), 18));
657
+ };
658
+ }, defaultMassIndexOptions);
659
+
660
+ //#endregion
661
+ //#region src/trend/triangularMovingAverage.ts
662
+ const defaultTriangularMovingAverageOptions = { period: 4 };
663
+ const trima = createSignal(({ period }) => {
664
+ assert(Number.isInteger(period) && period >= 1, /* @__PURE__ */ new RangeError(`Expected period to be a positive integer, got ${period}`));
665
+ let n1;
666
+ let n2;
667
+ if (period % 2 === 0) {
668
+ n1 = period / 2;
669
+ n2 = n1 + 1;
670
+ } else {
671
+ n1 = (period + 1) / 2;
672
+ n2 = n1;
673
+ }
674
+ const sma1 = sma.create({ period: n2 });
675
+ const sma2 = sma.create({ period: n1 });
676
+ return (value) => {
677
+ return sma2(sma1(value));
678
+ };
679
+ }, defaultTriangularMovingAverageOptions);
680
+
681
+ //#endregion
682
+ export { apo as absolutePriceOscillator, apo, ad as accumulationDistribution, ad, ao, ao as awesomeOscillator, aroon, bop as balanceOfPower, bop, cci, cci as commodityChannelIndex, cfo, cfo as chandeForecastOscillator, cmo as chaikinOscillator, cmo, defaultAbsolutePriceOscillatorOptions, defaultAroonOptions, defaultAwesomeOscillatorOptions, defaultCCIOptions, defaultCFOOptions, defaultChaikinOscillatorOptions, defaultDoubleExponentialMovingAverageOptions, defaultExponentialMovingAverageOptions, defaultIchimokuCloudOptions, defaultMACDOptions, defaultMassIndexOptions, defaultMovingMaxOptions, defaultMovingMinOptions, defaultMovingSumOptions, defaultPercentagePriceOscillatorOptions, defaultRMAOptions, defaultRSIOptions, defaultSMAOptions, defaultStochasticOscillatorOptions, defaultTriangularMovingAverageOptions, dema, dema as doubleExponentialMovingAverage, ema, ema as exponentialMovingAverage, ichimokuCloud, macd, macd as movingAverageConvergenceDivergence, mi as massIndex, mi, mmax, mmax as movingMax, mmin, mmin as movingMin, msum, ppo as percentagePriceOscillator, ppo, rsi as relativeStrengthIndex, rsi, rma, rma as rollingMovingAverage, sma as simpleMovingAverage, sma, stoch, stoch as stochasticOscillator, trima as triangularMovingAverage, trima };
683
+ //# sourceMappingURL=index.js.map