@ch20026103/anysis 0.0.17-alpha2 → 0.0.19-alpha

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.
@@ -0,0 +1,164 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var Mfi = /** @class */ (function () {
13
+ function Mfi() {
14
+ }
15
+ // 計算典型價格 (Typical Price)
16
+ Object.defineProperty(Mfi.prototype, "getTypicalPrice", {
17
+ enumerable: false,
18
+ configurable: true,
19
+ writable: true,
20
+ value: function (data) {
21
+ return (data.h + data.l + data.c) / 3;
22
+ }
23
+ });
24
+ // 計算原始資金流量 (Raw Money Flow)
25
+ Object.defineProperty(Mfi.prototype, "getRawMoneyFlow", {
26
+ enumerable: false,
27
+ configurable: true,
28
+ writable: true,
29
+ value: function (data) {
30
+ return this.getTypicalPrice(data) * data.v;
31
+ }
32
+ });
33
+ Object.defineProperty(Mfi.prototype, "init", {
34
+ enumerable: false,
35
+ configurable: true,
36
+ writable: true,
37
+ value: function (data, type) {
38
+ return {
39
+ dataset: [data],
40
+ mfi: null,
41
+ type: type,
42
+ sumPositiveMF: 0,
43
+ sumNegativeMF: 0,
44
+ };
45
+ }
46
+ });
47
+ Object.defineProperty(Mfi.prototype, "next", {
48
+ enumerable: false,
49
+ configurable: true,
50
+ writable: true,
51
+ value: function (data, preList, type) {
52
+ preList.dataset.push(data);
53
+ // 資料不足,不計算 MFI
54
+ if (preList.dataset.length < type + 1) {
55
+ return __assign(__assign({}, preList), { mfi: null, type: type, sumPositiveMF: 0, sumNegativeMF: 0 });
56
+ }
57
+ var sumPositiveMF = preList.sumPositiveMF;
58
+ var sumNegativeMF = preList.sumNegativeMF;
59
+ if (preList.dataset.length === type + 1) {
60
+ // 第一次達到計算條件,初始化計算
61
+ sumPositiveMF = 0;
62
+ sumNegativeMF = 0;
63
+ for (var i = 1; i <= type; i++) {
64
+ var currentTP = this.getTypicalPrice(preList.dataset[i]);
65
+ var prevTP = this.getTypicalPrice(preList.dataset[i - 1]);
66
+ var rawMF = this.getRawMoneyFlow(preList.dataset[i]);
67
+ if (currentTP > prevTP) {
68
+ sumPositiveMF += rawMF;
69
+ }
70
+ else if (currentTP < prevTP) {
71
+ sumNegativeMF += rawMF;
72
+ }
73
+ }
74
+ }
75
+ else {
76
+ // 增量更新:移除最舊的影響,加上最新的影響
77
+ // 移除最舊的資料影響
78
+ var oldestTP = this.getTypicalPrice(preList.dataset[0]);
79
+ var secondOldestTP = this.getTypicalPrice(preList.dataset[1]);
80
+ var oldestRawMF = this.getRawMoneyFlow(preList.dataset[1]);
81
+ if (secondOldestTP > oldestTP) {
82
+ sumPositiveMF -= oldestRawMF;
83
+ }
84
+ else if (secondOldestTP < oldestTP) {
85
+ sumNegativeMF -= oldestRawMF;
86
+ }
87
+ // 移除最舊的資料
88
+ preList.dataset.shift();
89
+ // 加上最新的資料影響
90
+ var newTP = this.getTypicalPrice(preList.dataset[preList.dataset.length - 1]);
91
+ var prevTP = this.getTypicalPrice(preList.dataset[preList.dataset.length - 2]);
92
+ var newRawMF = this.getRawMoneyFlow(preList.dataset[preList.dataset.length - 1]);
93
+ if (newTP > prevTP) {
94
+ sumPositiveMF += newRawMF;
95
+ }
96
+ else if (newTP < prevTP) {
97
+ sumNegativeMF += newRawMF;
98
+ }
99
+ }
100
+ // 計算 MFI
101
+ var mfi;
102
+ if (sumNegativeMF === 0) {
103
+ mfi = 100;
104
+ }
105
+ else {
106
+ var moneyFlowRatio = sumPositiveMF / sumNegativeMF;
107
+ mfi = 100 - (100 / (1 + moneyFlowRatio));
108
+ }
109
+ return {
110
+ dataset: preList.dataset,
111
+ type: type,
112
+ mfi: mfi,
113
+ sumPositiveMF: sumPositiveMF,
114
+ sumNegativeMF: sumNegativeMF
115
+ };
116
+ }
117
+ });
118
+ Object.defineProperty(Mfi.prototype, "calculateMFI", {
119
+ enumerable: false,
120
+ configurable: true,
121
+ writable: true,
122
+ value: function (prices, period) {
123
+ if (period === void 0) { period = 14; }
124
+ if (prices.length < period + 1) {
125
+ return [];
126
+ }
127
+ var mfis = [];
128
+ // 從 period 開始,確保有足夠的資料進行比較
129
+ for (var endIdx = period; endIdx < prices.length; endIdx++) {
130
+ var sumPositiveMF = 0;
131
+ var sumNegativeMF = 0;
132
+ // 計算 period 個交易日的資金流量
133
+ // 從 endIdx - period + 1 到 endIdx,共 period 個資料點
134
+ // 但比較是從 endIdx - period + 2 開始(因為要跟前一天比)
135
+ for (var i = endIdx - period + 1; i <= endIdx; i++) {
136
+ // 確保 i-1 >= 0,避免溢位
137
+ if (i === 0)
138
+ continue;
139
+ var currentTP = this.getTypicalPrice(prices[i]);
140
+ var prevTP = this.getTypicalPrice(prices[i - 1]);
141
+ var rawMF = this.getRawMoneyFlow(prices[i]);
142
+ if (currentTP > prevTP) {
143
+ sumPositiveMF += rawMF;
144
+ }
145
+ else if (currentTP < prevTP) {
146
+ sumNegativeMF += rawMF;
147
+ }
148
+ }
149
+ // 計算 MFI
150
+ if (sumNegativeMF === 0) {
151
+ mfis.push(100);
152
+ }
153
+ else {
154
+ var moneyFlowRatio = sumPositiveMF / sumNegativeMF;
155
+ var mfi = 100 - (100 / (1 + moneyFlowRatio));
156
+ mfis.push(mfi);
157
+ }
158
+ }
159
+ return mfis;
160
+ }
161
+ });
162
+ return Mfi;
163
+ }());
164
+ export default Mfi;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,191 @@
1
+ import Mfi from './mfi';
2
+ describe('MFI (Money Flow Index) Algorithm', function () {
3
+ var mfi;
4
+ beforeEach(function () {
5
+ mfi = new Mfi();
6
+ });
7
+ // 測試用的股票資料
8
+ var createStockData = function (high, low, close, volume) { return ({
9
+ h: high,
10
+ l: low,
11
+ c: close,
12
+ v: volume,
13
+ o: close,
14
+ t: Date.now()
15
+ }); };
16
+ describe('init method', function () {
17
+ it('should initialize with correct default values', function () {
18
+ var stockData = createStockData(100, 95, 98, 1000);
19
+ var result = mfi.init(stockData, 14);
20
+ expect(result).toEqual({
21
+ dataset: [stockData],
22
+ mfi: null,
23
+ type: 14,
24
+ sumPositiveMF: 0,
25
+ sumNegativeMF: 0,
26
+ });
27
+ });
28
+ it('should work with different period types', function () {
29
+ var stockData = createStockData(100, 95, 98, 1000);
30
+ var result = mfi.init(stockData, 5);
31
+ expect(result.type).toBe(5);
32
+ expect(result.dataset).toHaveLength(1);
33
+ });
34
+ });
35
+ describe('next method - insufficient data', function () {
36
+ it('should return null MFI when data is insufficient', function () {
37
+ var result = mfi.init(createStockData(100, 95, 98, 1000), 3);
38
+ // 第二個資料點
39
+ result = mfi.next(createStockData(102, 97, 100, 1200), result, 3);
40
+ expect(result.mfi).toBeNull();
41
+ expect(result.dataset).toHaveLength(2);
42
+ // 第三個資料點
43
+ result = mfi.next(createStockData(105, 99, 103, 1500), result, 3);
44
+ expect(result.mfi).toBeNull();
45
+ expect(result.dataset).toHaveLength(3);
46
+ });
47
+ });
48
+ describe('next method - sufficient data', function () {
49
+ it('should calculate MFI correctly when reaching minimum period', function () {
50
+ var period = 3;
51
+ var result = mfi.init(createStockData(100, 95, 98, 1000), period);
52
+ // 添加資料直到滿足計算條件
53
+ result = mfi.next(createStockData(102, 97, 100, 1200), result, period); // TP上升
54
+ result = mfi.next(createStockData(105, 99, 103, 1500), result, period); // TP上升
55
+ result = mfi.next(createStockData(103, 98, 101, 1300), result, period); // TP下降
56
+ expect(result.mfi).not.toBeNull();
57
+ expect(typeof result.mfi).toBe('number');
58
+ expect(result.mfi).toBeGreaterThanOrEqual(0);
59
+ expect(result.mfi).toBeLessThanOrEqual(100);
60
+ expect(result.dataset).toHaveLength(period + 1);
61
+ });
62
+ it('should handle all positive money flows (MFI = 100)', function () {
63
+ var period = 2;
64
+ var result = mfi.init(createStockData(100, 95, 98, 1000), period);
65
+ // 所有後續價格都上升
66
+ result = mfi.next(createStockData(102, 97, 100, 1200), result, period);
67
+ result = mfi.next(createStockData(105, 99, 103, 1500), result, period);
68
+ expect(result.mfi).toBe(100);
69
+ });
70
+ it('should maintain sliding window correctly', function () {
71
+ var period = 2;
72
+ var result = mfi.init(createStockData(100, 95, 98, 1000), period);
73
+ result = mfi.next(createStockData(102, 97, 100, 1200), result, period);
74
+ result = mfi.next(createStockData(105, 99, 103, 1500), result, period);
75
+ // 新增第五個資料點,應該移除第一個
76
+ var oldFirstData = result.dataset[0];
77
+ result = mfi.next(createStockData(103, 98, 101, 1300), result, period);
78
+ expect(result.dataset).toHaveLength(period + 1);
79
+ expect(result.dataset[0]).not.toEqual(oldFirstData);
80
+ });
81
+ });
82
+ describe('calculateMFI method', function () {
83
+ it('should return empty array for insufficient data', function () {
84
+ var prices = [
85
+ createStockData(100, 95, 98, 1000),
86
+ createStockData(102, 97, 100, 1200),
87
+ ];
88
+ var result = mfi.calculateMFI(prices, 14);
89
+ expect(result).toEqual([]);
90
+ });
91
+ it('should calculate MFI for sufficient data', function () {
92
+ // 創建測試資料:期間為3,需要4個資料點
93
+ var prices = [
94
+ createStockData(100, 95, 98, 1000),
95
+ createStockData(102, 97, 100, 1200),
96
+ createStockData(105, 99, 103, 1500),
97
+ createStockData(103, 98, 101, 1300), // TP = 100.67 (下降)
98
+ ];
99
+ var result = mfi.calculateMFI(prices, 3);
100
+ expect(result).toHaveLength(1); // 只能計算一個MFI值
101
+ expect(result[0]).toBeGreaterThanOrEqual(0);
102
+ expect(result[0]).toBeLessThanOrEqual(100);
103
+ });
104
+ it('should calculate multiple MFI values for longer data series', function () {
105
+ var prices = [];
106
+ // 創建6個資料點,期間為3,應該能計算3個MFI值
107
+ for (var i = 0; i < 6; i++) {
108
+ prices.push(createStockData(100 + i * 2, 95 + i * 2, 98 + i * 2 + (i % 2 === 0 ? 1 : -1), // 交替上下
109
+ 1000 + i * 100));
110
+ }
111
+ var result = mfi.calculateMFI(prices, 3);
112
+ expect(result).toHaveLength(3); // 6 - 3 = 3個MFI值
113
+ result.forEach(function (mfiValue) {
114
+ expect(mfiValue).toBeGreaterThanOrEqual(0);
115
+ expect(mfiValue).toBeLessThanOrEqual(100);
116
+ });
117
+ });
118
+ it('should handle edge case with no negative money flow', function () {
119
+ // 創建持續上升的價格資料
120
+ var prices = [];
121
+ for (var i = 0; i < 5; i++) {
122
+ prices.push(createStockData(100 + i * 5, 95 + i * 5, 98 + i * 5, 1000));
123
+ }
124
+ var result = mfi.calculateMFI(prices, 3);
125
+ expect(result).toHaveLength(2);
126
+ result.forEach(function (mfiValue) {
127
+ expect(mfiValue).toBe(100); // 全部正向資金流量
128
+ });
129
+ });
130
+ it('should handle equal typical prices correctly', function () {
131
+ var prices = [
132
+ createStockData(100, 95, 98, 1000),
133
+ createStockData(102, 97, 99, 1200),
134
+ createStockData(101, 96, 99, 1500),
135
+ createStockData(103, 98, 100, 1300),
136
+ createStockData(102, 97, 99, 1100), // TP = 99.33 (相等)
137
+ ];
138
+ var result = mfi.calculateMFI(prices, 3);
139
+ expect(result).toHaveLength(2);
140
+ result.forEach(function (mfiValue) {
141
+ expect(mfiValue).toBeGreaterThanOrEqual(0);
142
+ expect(mfiValue).toBeLessThanOrEqual(100);
143
+ expect(Number.isNaN(mfiValue)).toBe(false);
144
+ });
145
+ });
146
+ });
147
+ describe('typical price calculation', function () {
148
+ it('should calculate typical price correctly', function () {
149
+ var stockData = createStockData(105, 95, 100, 1000);
150
+ var mfiInstance = new Mfi();
151
+ // 使用反射來測試私有方法(僅用於測試)
152
+ var typicalPrice = mfiInstance.getTypicalPrice(stockData);
153
+ expect(typicalPrice).toBeCloseTo((105 + 95 + 100) / 3, 2);
154
+ });
155
+ });
156
+ describe('raw money flow calculation', function () {
157
+ it('should calculate raw money flow correctly', function () {
158
+ var stockData = createStockData(105, 95, 100, 1500);
159
+ var mfiInstance = new Mfi();
160
+ var typicalPrice = (105 + 95 + 100) / 3;
161
+ var expectedMF = typicalPrice * 1500;
162
+ var rawMF = mfiInstance.getRawMoneyFlow(stockData);
163
+ expect(rawMF).toBeCloseTo(expectedMF, 2);
164
+ });
165
+ });
166
+ describe('consistency between methods', function () {
167
+ it('should produce same results using next() and calculateMFI()', function () {
168
+ var prices = [];
169
+ for (var i = 0; i < 8; i++) {
170
+ prices.push(createStockData(100 + i * 3, 95 + i * 2, 98 + i * 2.5, 1000 + i * 50));
171
+ }
172
+ var period = 3;
173
+ // 使用 calculateMFI
174
+ var batchResult = mfi.calculateMFI(prices, period);
175
+ // 使用 next 方法逐步計算
176
+ var result = mfi.init(prices[0], period);
177
+ var nextResults = [];
178
+ for (var i = 1; i < prices.length; i++) {
179
+ result = mfi.next(prices[i], result, period);
180
+ nextResults.push(result.mfi);
181
+ }
182
+ // 過濾掉 null 值
183
+ var validNextResults = nextResults.filter(function (val) { return val !== null; });
184
+ expect(validNextResults).toHaveLength(batchResult.length);
185
+ // 比較結果(允許小的浮點誤差)
186
+ for (var i = 0; i < batchResult.length; i++) {
187
+ expect(validNextResults[i]).toBeCloseTo(batchResult[i], 6);
188
+ }
189
+ });
190
+ });
191
+ });