@mmflow/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/dist/index.cjs ADDED
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ // src/index.ts
4
+ function sma(values, period) {
5
+ const out = new Array(values.length);
6
+ let sum = 0;
7
+ for (let i = 0; i < values.length; i++) {
8
+ sum += values[i];
9
+ if (i >= period) sum -= values[i - period];
10
+ out[i] = i >= period - 1 ? sum / period : NaN;
11
+ }
12
+ return out;
13
+ }
14
+ function ema(values, period) {
15
+ const out = new Array(values.length);
16
+ if (values.length < period) {
17
+ return out.fill(NaN);
18
+ }
19
+ const k = 2 / (period + 1);
20
+ let sum = 0;
21
+ for (let i = 0; i < period; i++) {
22
+ sum += values[i];
23
+ out[i] = NaN;
24
+ }
25
+ let prev = sum / period;
26
+ out[period - 1] = prev;
27
+ for (let i = period; i < values.length; i++) {
28
+ prev = values[i] * k + prev * (1 - k);
29
+ out[i] = prev;
30
+ }
31
+ return out;
32
+ }
33
+ function vwap(candles) {
34
+ let cumPV = 0;
35
+ let cumV = 0;
36
+ return candles.map((c) => {
37
+ const tp = (c.h + c.l + c.c) / 3;
38
+ cumPV += tp * c.v;
39
+ cumV += c.v;
40
+ return cumV > 0 ? cumPV / cumV : NaN;
41
+ });
42
+ }
43
+ function rsi(values, period = 14) {
44
+ const out = new Array(values.length).fill(NaN);
45
+ if (values.length <= period) return out;
46
+ let gainSum = 0;
47
+ let lossSum = 0;
48
+ for (let i = 1; i <= period; i++) {
49
+ const d = values[i] - values[i - 1];
50
+ if (d > 0) gainSum += d;
51
+ else lossSum += -d;
52
+ }
53
+ let avgGain = gainSum / period;
54
+ let avgLoss = lossSum / period;
55
+ out[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
56
+ for (let i = period + 1; i < values.length; i++) {
57
+ const d = values[i] - values[i - 1];
58
+ const gain = d > 0 ? d : 0;
59
+ const loss = d < 0 ? -d : 0;
60
+ avgGain = (avgGain * (period - 1) + gain) / period;
61
+ avgLoss = (avgLoss * (period - 1) + loss) / period;
62
+ out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
63
+ }
64
+ return out;
65
+ }
66
+ function macd(values, fast = 12, slow = 26, signalPeriod = 9) {
67
+ const emaFast = ema(values, fast);
68
+ const emaSlow = ema(values, slow);
69
+ const macdLine = values.map((_, i) => {
70
+ const a = emaFast[i];
71
+ const b = emaSlow[i];
72
+ return isFinite(a) && isFinite(b) ? a - b : NaN;
73
+ });
74
+ const firstValid = macdLine.findIndex((v) => isFinite(v));
75
+ const signalLine = new Array(values.length).fill(NaN);
76
+ if (firstValid >= 0) {
77
+ const tail = macdLine.slice(firstValid).filter((v) => isFinite(v));
78
+ const sig = ema(tail, signalPeriod);
79
+ for (let i = 0; i < sig.length; i++) {
80
+ signalLine[firstValid + i] = sig[i];
81
+ }
82
+ }
83
+ const histogram = macdLine.map(
84
+ (v, i) => isFinite(v) && isFinite(signalLine[i]) ? v - signalLine[i] : NaN
85
+ );
86
+ return { macd: macdLine, signal: signalLine, histogram };
87
+ }
88
+ function tpsSmoothed(series, period = 9) {
89
+ if (!Array.isArray(series) || series.length === 0) return [];
90
+ const k = 2 / (Math.max(2, period) + 1);
91
+ const out = new Array(series.length).fill(null);
92
+ let prev = null;
93
+ for (let i = 0; i < series.length; i++) {
94
+ const v = series[i];
95
+ if (v == null || !Number.isFinite(v)) {
96
+ out[i] = null;
97
+ continue;
98
+ }
99
+ if (prev == null) {
100
+ prev = v;
101
+ } else {
102
+ prev = v * k + prev * (1 - k);
103
+ }
104
+ out[i] = prev;
105
+ }
106
+ return out;
107
+ }
108
+
109
+ exports.ema = ema;
110
+ exports.macd = macd;
111
+ exports.rsi = rsi;
112
+ exports.sma = sma;
113
+ exports.tpsSmoothed = tpsSmoothed;
114
+ exports.vwap = vwap;
115
+ //# sourceMappingURL=index.cjs.map
116
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// Lightweight technical-indicator math for the chart.\n//\n// All inputs are arrays of numbers (typically closes); outputs match length\n// with NaN for the warm-up period. Lightweight-charts series will skip NaN\n// data points so the indicator only renders once it has enough history.\n//\n// We deliberately do not import a heavy TA library — these implementations\n// are exact, tested against TradingView's published formulas, and tree-shake\n// to ~1 KB.\n\n/**\n * Simple Moving Average over the last `period` samples.\n */\nexport function sma(values: number[], period: number): number[] {\n const out = new Array<number>(values.length);\n let sum = 0;\n for (let i = 0; i < values.length; i++) {\n sum += values[i];\n if (i >= period) sum -= values[i - period];\n out[i] = i >= period - 1 ? sum / period : NaN;\n }\n return out;\n}\n\n/**\n * Exponential Moving Average. Seeds with SMA over the first `period` samples.\n * k = 2 / (period + 1)\n * ema_t = close_t * k + ema_{t-1} * (1 - k)\n */\nexport function ema(values: number[], period: number): number[] {\n const out = new Array<number>(values.length);\n if (values.length < period) {\n return out.fill(NaN);\n }\n const k = 2 / (period + 1);\n // Seed with SMA over the first window.\n let sum = 0;\n for (let i = 0; i < period; i++) {\n sum += values[i];\n out[i] = NaN;\n }\n let prev = sum / period;\n out[period - 1] = prev;\n for (let i = period; i < values.length; i++) {\n prev = values[i] * k + prev * (1 - k);\n out[i] = prev;\n }\n return out;\n}\n\n/**\n * VWAP, anchored to the start of the input. Caller can slice by session.\n * vwap_t = Σ(typicalPrice_i * volume_i) / Σ(volume_i)\n * typicalPrice_i = (high_i + low_i + close_i) / 3\n */\nexport function vwap(\n candles: Array<{ h: number; l: number; c: number; v: number }>\n): number[] {\n let cumPV = 0;\n let cumV = 0;\n return candles.map((c) => {\n const tp = (c.h + c.l + c.c) / 3;\n cumPV += tp * c.v;\n cumV += c.v;\n return cumV > 0 ? cumPV / cumV : NaN;\n });\n}\n\n/**\n * Wilder's RSI(14). Uses the smoothed-average variant TradingView uses by\n * default (not the simple-average flavor — that one diverges quickly).\n *\n * gain_t = max(close_t - close_{t-1}, 0)\n * loss_t = max(close_{t-1} - close_t, 0)\n * avgGain_t = (avgGain_{t-1} * (period - 1) + gain_t) / period\n * avgLoss_t = (avgLoss_{t-1} * (period - 1) + loss_t) / period\n * rs = avgGain / avgLoss\n * rsi = 100 - 100 / (1 + rs)\n */\nexport function rsi(values: number[], period = 14): number[] {\n const out = new Array<number>(values.length).fill(NaN);\n if (values.length <= period) return out;\n\n let gainSum = 0;\n let lossSum = 0;\n for (let i = 1; i <= period; i++) {\n const d = values[i] - values[i - 1];\n if (d > 0) gainSum += d;\n else lossSum += -d;\n }\n let avgGain = gainSum / period;\n let avgLoss = lossSum / period;\n out[period] =\n avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n\n for (let i = period + 1; i < values.length; i++) {\n const d = values[i] - values[i - 1];\n const gain = d > 0 ? d : 0;\n const loss = d < 0 ? -d : 0;\n avgGain = (avgGain * (period - 1) + gain) / period;\n avgLoss = (avgLoss * (period - 1) + loss) / period;\n out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n }\n return out;\n}\n\n/**\n * MACD(12, 26, 9). Returns the MACD line, signal line, and histogram.\n * macd = ema(close, fast) - ema(close, slow)\n * signal = ema(macd, signalPeriod)\n * hist = macd - signal\n */\nexport function macd(\n values: number[],\n fast = 12,\n slow = 26,\n signalPeriod = 9\n): { macd: number[]; signal: number[]; histogram: number[] } {\n const emaFast = ema(values, fast);\n const emaSlow = ema(values, slow);\n const macdLine = values.map((_, i) => {\n const a = emaFast[i];\n const b = emaSlow[i];\n return isFinite(a) && isFinite(b) ? a - b : NaN;\n });\n // Signal EMA needs to skip the leading NaNs to seed correctly.\n const firstValid = macdLine.findIndex((v) => isFinite(v));\n const signalLine = new Array<number>(values.length).fill(NaN);\n if (firstValid >= 0) {\n const tail = macdLine.slice(firstValid).filter((v) => isFinite(v));\n const sig = ema(tail, signalPeriod);\n for (let i = 0; i < sig.length; i++) {\n signalLine[firstValid + i] = sig[i];\n }\n }\n const histogram = macdLine.map((v, i) =>\n isFinite(v) && isFinite(signalLine[i]) ? v - signalLine[i] : NaN\n );\n return { macd: macdLine, signal: signalLine, histogram };\n}\n\n/**\n * Phase 24.1 — EMA-smoothed Trades-Per-Second.\n *\n * The raw TPS series from a footprint aggregator is spiky (each bar's\n * value is `count / barDuration`). Callers that want a smoother\n * baseline read (e.g. for spotting unusual sustained activity instead\n * of single-bar bursts) can pass through this helper.\n *\n * `null` entries (degenerate bars — single-trade bar, < 250 ms\n * elapsed) bypass the EMA and stay null in the output. The smoother\n * resumes from the next non-null sample without leaking the gap into\n * the running average.\n *\n * @param series per-bar TPS values, in chart order. Use `null` for\n * bars where TPS is meaningless.\n * @param period EMA window (default 9 bars).\n */\nexport function tpsSmoothed(\n series: Array<number | null>,\n period = 9,\n): Array<number | null> {\n if (!Array.isArray(series) || series.length === 0) return [];\n const k = 2 / (Math.max(2, period) + 1);\n const out: Array<number | null> = new Array(series.length).fill(null);\n let prev: number | null = null;\n for (let i = 0; i < series.length; i++) {\n const v = series[i];\n if (v == null || !Number.isFinite(v)) {\n out[i] = null;\n continue;\n }\n if (prev == null) {\n // Seed the EMA on the first usable sample.\n prev = v;\n } else {\n prev = v * k + prev * (1 - k);\n }\n out[i] = prev;\n }\n return out;\n}\n"]}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Simple Moving Average over the last `period` samples.
3
+ */
4
+ declare function sma(values: number[], period: number): number[];
5
+ /**
6
+ * Exponential Moving Average. Seeds with SMA over the first `period` samples.
7
+ * k = 2 / (period + 1)
8
+ * ema_t = close_t * k + ema_{t-1} * (1 - k)
9
+ */
10
+ declare function ema(values: number[], period: number): number[];
11
+ /**
12
+ * VWAP, anchored to the start of the input. Caller can slice by session.
13
+ * vwap_t = Σ(typicalPrice_i * volume_i) / Σ(volume_i)
14
+ * typicalPrice_i = (high_i + low_i + close_i) / 3
15
+ */
16
+ declare function vwap(candles: Array<{
17
+ h: number;
18
+ l: number;
19
+ c: number;
20
+ v: number;
21
+ }>): number[];
22
+ /**
23
+ * Wilder's RSI(14). Uses the smoothed-average variant TradingView uses by
24
+ * default (not the simple-average flavor — that one diverges quickly).
25
+ *
26
+ * gain_t = max(close_t - close_{t-1}, 0)
27
+ * loss_t = max(close_{t-1} - close_t, 0)
28
+ * avgGain_t = (avgGain_{t-1} * (period - 1) + gain_t) / period
29
+ * avgLoss_t = (avgLoss_{t-1} * (period - 1) + loss_t) / period
30
+ * rs = avgGain / avgLoss
31
+ * rsi = 100 - 100 / (1 + rs)
32
+ */
33
+ declare function rsi(values: number[], period?: number): number[];
34
+ /**
35
+ * MACD(12, 26, 9). Returns the MACD line, signal line, and histogram.
36
+ * macd = ema(close, fast) - ema(close, slow)
37
+ * signal = ema(macd, signalPeriod)
38
+ * hist = macd - signal
39
+ */
40
+ declare function macd(values: number[], fast?: number, slow?: number, signalPeriod?: number): {
41
+ macd: number[];
42
+ signal: number[];
43
+ histogram: number[];
44
+ };
45
+ /**
46
+ * Phase 24.1 — EMA-smoothed Trades-Per-Second.
47
+ *
48
+ * The raw TPS series from a footprint aggregator is spiky (each bar's
49
+ * value is `count / barDuration`). Callers that want a smoother
50
+ * baseline read (e.g. for spotting unusual sustained activity instead
51
+ * of single-bar bursts) can pass through this helper.
52
+ *
53
+ * `null` entries (degenerate bars — single-trade bar, < 250 ms
54
+ * elapsed) bypass the EMA and stay null in the output. The smoother
55
+ * resumes from the next non-null sample without leaking the gap into
56
+ * the running average.
57
+ *
58
+ * @param series per-bar TPS values, in chart order. Use `null` for
59
+ * bars where TPS is meaningless.
60
+ * @param period EMA window (default 9 bars).
61
+ */
62
+ declare function tpsSmoothed(series: Array<number | null>, period?: number): Array<number | null>;
63
+
64
+ export { ema, macd, rsi, sma, tpsSmoothed, vwap };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Simple Moving Average over the last `period` samples.
3
+ */
4
+ declare function sma(values: number[], period: number): number[];
5
+ /**
6
+ * Exponential Moving Average. Seeds with SMA over the first `period` samples.
7
+ * k = 2 / (period + 1)
8
+ * ema_t = close_t * k + ema_{t-1} * (1 - k)
9
+ */
10
+ declare function ema(values: number[], period: number): number[];
11
+ /**
12
+ * VWAP, anchored to the start of the input. Caller can slice by session.
13
+ * vwap_t = Σ(typicalPrice_i * volume_i) / Σ(volume_i)
14
+ * typicalPrice_i = (high_i + low_i + close_i) / 3
15
+ */
16
+ declare function vwap(candles: Array<{
17
+ h: number;
18
+ l: number;
19
+ c: number;
20
+ v: number;
21
+ }>): number[];
22
+ /**
23
+ * Wilder's RSI(14). Uses the smoothed-average variant TradingView uses by
24
+ * default (not the simple-average flavor — that one diverges quickly).
25
+ *
26
+ * gain_t = max(close_t - close_{t-1}, 0)
27
+ * loss_t = max(close_{t-1} - close_t, 0)
28
+ * avgGain_t = (avgGain_{t-1} * (period - 1) + gain_t) / period
29
+ * avgLoss_t = (avgLoss_{t-1} * (period - 1) + loss_t) / period
30
+ * rs = avgGain / avgLoss
31
+ * rsi = 100 - 100 / (1 + rs)
32
+ */
33
+ declare function rsi(values: number[], period?: number): number[];
34
+ /**
35
+ * MACD(12, 26, 9). Returns the MACD line, signal line, and histogram.
36
+ * macd = ema(close, fast) - ema(close, slow)
37
+ * signal = ema(macd, signalPeriod)
38
+ * hist = macd - signal
39
+ */
40
+ declare function macd(values: number[], fast?: number, slow?: number, signalPeriod?: number): {
41
+ macd: number[];
42
+ signal: number[];
43
+ histogram: number[];
44
+ };
45
+ /**
46
+ * Phase 24.1 — EMA-smoothed Trades-Per-Second.
47
+ *
48
+ * The raw TPS series from a footprint aggregator is spiky (each bar's
49
+ * value is `count / barDuration`). Callers that want a smoother
50
+ * baseline read (e.g. for spotting unusual sustained activity instead
51
+ * of single-bar bursts) can pass through this helper.
52
+ *
53
+ * `null` entries (degenerate bars — single-trade bar, < 250 ms
54
+ * elapsed) bypass the EMA and stay null in the output. The smoother
55
+ * resumes from the next non-null sample without leaking the gap into
56
+ * the running average.
57
+ *
58
+ * @param series per-bar TPS values, in chart order. Use `null` for
59
+ * bars where TPS is meaningless.
60
+ * @param period EMA window (default 9 bars).
61
+ */
62
+ declare function tpsSmoothed(series: Array<number | null>, period?: number): Array<number | null>;
63
+
64
+ export { ema, macd, rsi, sma, tpsSmoothed, vwap };
package/dist/index.js ADDED
@@ -0,0 +1,109 @@
1
+ // src/index.ts
2
+ function sma(values, period) {
3
+ const out = new Array(values.length);
4
+ let sum = 0;
5
+ for (let i = 0; i < values.length; i++) {
6
+ sum += values[i];
7
+ if (i >= period) sum -= values[i - period];
8
+ out[i] = i >= period - 1 ? sum / period : NaN;
9
+ }
10
+ return out;
11
+ }
12
+ function ema(values, period) {
13
+ const out = new Array(values.length);
14
+ if (values.length < period) {
15
+ return out.fill(NaN);
16
+ }
17
+ const k = 2 / (period + 1);
18
+ let sum = 0;
19
+ for (let i = 0; i < period; i++) {
20
+ sum += values[i];
21
+ out[i] = NaN;
22
+ }
23
+ let prev = sum / period;
24
+ out[period - 1] = prev;
25
+ for (let i = period; i < values.length; i++) {
26
+ prev = values[i] * k + prev * (1 - k);
27
+ out[i] = prev;
28
+ }
29
+ return out;
30
+ }
31
+ function vwap(candles) {
32
+ let cumPV = 0;
33
+ let cumV = 0;
34
+ return candles.map((c) => {
35
+ const tp = (c.h + c.l + c.c) / 3;
36
+ cumPV += tp * c.v;
37
+ cumV += c.v;
38
+ return cumV > 0 ? cumPV / cumV : NaN;
39
+ });
40
+ }
41
+ function rsi(values, period = 14) {
42
+ const out = new Array(values.length).fill(NaN);
43
+ if (values.length <= period) return out;
44
+ let gainSum = 0;
45
+ let lossSum = 0;
46
+ for (let i = 1; i <= period; i++) {
47
+ const d = values[i] - values[i - 1];
48
+ if (d > 0) gainSum += d;
49
+ else lossSum += -d;
50
+ }
51
+ let avgGain = gainSum / period;
52
+ let avgLoss = lossSum / period;
53
+ out[period] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
54
+ for (let i = period + 1; i < values.length; i++) {
55
+ const d = values[i] - values[i - 1];
56
+ const gain = d > 0 ? d : 0;
57
+ const loss = d < 0 ? -d : 0;
58
+ avgGain = (avgGain * (period - 1) + gain) / period;
59
+ avgLoss = (avgLoss * (period - 1) + loss) / period;
60
+ out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);
61
+ }
62
+ return out;
63
+ }
64
+ function macd(values, fast = 12, slow = 26, signalPeriod = 9) {
65
+ const emaFast = ema(values, fast);
66
+ const emaSlow = ema(values, slow);
67
+ const macdLine = values.map((_, i) => {
68
+ const a = emaFast[i];
69
+ const b = emaSlow[i];
70
+ return isFinite(a) && isFinite(b) ? a - b : NaN;
71
+ });
72
+ const firstValid = macdLine.findIndex((v) => isFinite(v));
73
+ const signalLine = new Array(values.length).fill(NaN);
74
+ if (firstValid >= 0) {
75
+ const tail = macdLine.slice(firstValid).filter((v) => isFinite(v));
76
+ const sig = ema(tail, signalPeriod);
77
+ for (let i = 0; i < sig.length; i++) {
78
+ signalLine[firstValid + i] = sig[i];
79
+ }
80
+ }
81
+ const histogram = macdLine.map(
82
+ (v, i) => isFinite(v) && isFinite(signalLine[i]) ? v - signalLine[i] : NaN
83
+ );
84
+ return { macd: macdLine, signal: signalLine, histogram };
85
+ }
86
+ function tpsSmoothed(series, period = 9) {
87
+ if (!Array.isArray(series) || series.length === 0) return [];
88
+ const k = 2 / (Math.max(2, period) + 1);
89
+ const out = new Array(series.length).fill(null);
90
+ let prev = null;
91
+ for (let i = 0; i < series.length; i++) {
92
+ const v = series[i];
93
+ if (v == null || !Number.isFinite(v)) {
94
+ out[i] = null;
95
+ continue;
96
+ }
97
+ if (prev == null) {
98
+ prev = v;
99
+ } else {
100
+ prev = v * k + prev * (1 - k);
101
+ }
102
+ out[i] = prev;
103
+ }
104
+ return out;
105
+ }
106
+
107
+ export { ema, macd, rsi, sma, tpsSmoothed, vwap };
108
+ //# sourceMappingURL=index.js.map
109
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Lightweight technical-indicator math for the chart.\n//\n// All inputs are arrays of numbers (typically closes); outputs match length\n// with NaN for the warm-up period. Lightweight-charts series will skip NaN\n// data points so the indicator only renders once it has enough history.\n//\n// We deliberately do not import a heavy TA library — these implementations\n// are exact, tested against TradingView's published formulas, and tree-shake\n// to ~1 KB.\n\n/**\n * Simple Moving Average over the last `period` samples.\n */\nexport function sma(values: number[], period: number): number[] {\n const out = new Array<number>(values.length);\n let sum = 0;\n for (let i = 0; i < values.length; i++) {\n sum += values[i];\n if (i >= period) sum -= values[i - period];\n out[i] = i >= period - 1 ? sum / period : NaN;\n }\n return out;\n}\n\n/**\n * Exponential Moving Average. Seeds with SMA over the first `period` samples.\n * k = 2 / (period + 1)\n * ema_t = close_t * k + ema_{t-1} * (1 - k)\n */\nexport function ema(values: number[], period: number): number[] {\n const out = new Array<number>(values.length);\n if (values.length < period) {\n return out.fill(NaN);\n }\n const k = 2 / (period + 1);\n // Seed with SMA over the first window.\n let sum = 0;\n for (let i = 0; i < period; i++) {\n sum += values[i];\n out[i] = NaN;\n }\n let prev = sum / period;\n out[period - 1] = prev;\n for (let i = period; i < values.length; i++) {\n prev = values[i] * k + prev * (1 - k);\n out[i] = prev;\n }\n return out;\n}\n\n/**\n * VWAP, anchored to the start of the input. Caller can slice by session.\n * vwap_t = Σ(typicalPrice_i * volume_i) / Σ(volume_i)\n * typicalPrice_i = (high_i + low_i + close_i) / 3\n */\nexport function vwap(\n candles: Array<{ h: number; l: number; c: number; v: number }>\n): number[] {\n let cumPV = 0;\n let cumV = 0;\n return candles.map((c) => {\n const tp = (c.h + c.l + c.c) / 3;\n cumPV += tp * c.v;\n cumV += c.v;\n return cumV > 0 ? cumPV / cumV : NaN;\n });\n}\n\n/**\n * Wilder's RSI(14). Uses the smoothed-average variant TradingView uses by\n * default (not the simple-average flavor — that one diverges quickly).\n *\n * gain_t = max(close_t - close_{t-1}, 0)\n * loss_t = max(close_{t-1} - close_t, 0)\n * avgGain_t = (avgGain_{t-1} * (period - 1) + gain_t) / period\n * avgLoss_t = (avgLoss_{t-1} * (period - 1) + loss_t) / period\n * rs = avgGain / avgLoss\n * rsi = 100 - 100 / (1 + rs)\n */\nexport function rsi(values: number[], period = 14): number[] {\n const out = new Array<number>(values.length).fill(NaN);\n if (values.length <= period) return out;\n\n let gainSum = 0;\n let lossSum = 0;\n for (let i = 1; i <= period; i++) {\n const d = values[i] - values[i - 1];\n if (d > 0) gainSum += d;\n else lossSum += -d;\n }\n let avgGain = gainSum / period;\n let avgLoss = lossSum / period;\n out[period] =\n avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n\n for (let i = period + 1; i < values.length; i++) {\n const d = values[i] - values[i - 1];\n const gain = d > 0 ? d : 0;\n const loss = d < 0 ? -d : 0;\n avgGain = (avgGain * (period - 1) + gain) / period;\n avgLoss = (avgLoss * (period - 1) + loss) / period;\n out[i] = avgLoss === 0 ? 100 : 100 - 100 / (1 + avgGain / avgLoss);\n }\n return out;\n}\n\n/**\n * MACD(12, 26, 9). Returns the MACD line, signal line, and histogram.\n * macd = ema(close, fast) - ema(close, slow)\n * signal = ema(macd, signalPeriod)\n * hist = macd - signal\n */\nexport function macd(\n values: number[],\n fast = 12,\n slow = 26,\n signalPeriod = 9\n): { macd: number[]; signal: number[]; histogram: number[] } {\n const emaFast = ema(values, fast);\n const emaSlow = ema(values, slow);\n const macdLine = values.map((_, i) => {\n const a = emaFast[i];\n const b = emaSlow[i];\n return isFinite(a) && isFinite(b) ? a - b : NaN;\n });\n // Signal EMA needs to skip the leading NaNs to seed correctly.\n const firstValid = macdLine.findIndex((v) => isFinite(v));\n const signalLine = new Array<number>(values.length).fill(NaN);\n if (firstValid >= 0) {\n const tail = macdLine.slice(firstValid).filter((v) => isFinite(v));\n const sig = ema(tail, signalPeriod);\n for (let i = 0; i < sig.length; i++) {\n signalLine[firstValid + i] = sig[i];\n }\n }\n const histogram = macdLine.map((v, i) =>\n isFinite(v) && isFinite(signalLine[i]) ? v - signalLine[i] : NaN\n );\n return { macd: macdLine, signal: signalLine, histogram };\n}\n\n/**\n * Phase 24.1 — EMA-smoothed Trades-Per-Second.\n *\n * The raw TPS series from a footprint aggregator is spiky (each bar's\n * value is `count / barDuration`). Callers that want a smoother\n * baseline read (e.g. for spotting unusual sustained activity instead\n * of single-bar bursts) can pass through this helper.\n *\n * `null` entries (degenerate bars — single-trade bar, < 250 ms\n * elapsed) bypass the EMA and stay null in the output. The smoother\n * resumes from the next non-null sample without leaking the gap into\n * the running average.\n *\n * @param series per-bar TPS values, in chart order. Use `null` for\n * bars where TPS is meaningless.\n * @param period EMA window (default 9 bars).\n */\nexport function tpsSmoothed(\n series: Array<number | null>,\n period = 9,\n): Array<number | null> {\n if (!Array.isArray(series) || series.length === 0) return [];\n const k = 2 / (Math.max(2, period) + 1);\n const out: Array<number | null> = new Array(series.length).fill(null);\n let prev: number | null = null;\n for (let i = 0; i < series.length; i++) {\n const v = series[i];\n if (v == null || !Number.isFinite(v)) {\n out[i] = null;\n continue;\n }\n if (prev == null) {\n // Seed the EMA on the first usable sample.\n prev = v;\n } else {\n prev = v * k + prev * (1 - k);\n }\n out[i] = prev;\n }\n return out;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@mmflow/indicators",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
19
+ }
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup src/index.ts --format esm,cjs --dts --treeshake",
27
+ "typecheck": "tsc --noEmit"
28
+ }
29
+ }