@ch20026103/anysis 0.0.17-alpha2 → 0.0.18

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 @@
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
+ });