@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.
package/demo/main.js CHANGED
@@ -1,24 +1,55 @@
1
1
  /* eslint @typescript-eslint/no-var-requires: "off" */
2
2
  const axios = require("axios");
3
- const { Kd } = require("../dist/cjs/index.js");
3
+ const { Mfi } = require("../dist/cjs/index.js");
4
4
 
5
5
  // 使用示例
6
- const kd = new Kd();
6
+ const mfi = new Mfi();
7
7
  function DemoDay(stockId) {
8
8
  axios
9
9
  .get(
10
- `https://tw.quote.finance.yahoo.net/quote/q?type=ta&perd=d&mkt=10&sym=${stockId}&v=1&callback=`
10
+ `https://tw.stock.yahoo.com/_td-stock/api/resource/FinanceChartService.ApacLibraCharts;period=d;symbols=[%22${stockId}%22]`
11
11
  )
12
12
  .then((res) => {
13
- res = res.data.replace(/^\(|\);$/g, "");
14
- let parse = JSON.parse(res);
15
- let data = parse.ta;
13
+ const json = res.data;
14
+ const opens = json[0].chart.indicators.quote[0].open;
15
+ const closes = json[0].chart.indicators.quote[0].close;
16
+ const highs = json[0].chart.indicators.quote[0].high;
17
+ const lows = json[0].chart.indicators.quote[0].low;
18
+ const volumes = json[0].chart.indicators.quote[0].volume;
19
+ const ts = json[0].chart.timestamp.map((item) => {
20
+ const date = new Date(item * 1000);
21
+ const formatDateTime = () => {
22
+ const year = date.getFullYear();
23
+ const month = String(date.getMonth() + 1).padStart(2, "0");
24
+ const day = String(date.getDate()).padStart(2, "0");
25
+ const hours = String(date.getHours()).padStart(2, "0");
26
+ const minutes = String(date.getMinutes()).padStart(2, "0");
27
+ const seconds = String(date.getSeconds()).padStart(2, "0");
28
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
29
+ };
30
+ return formatDateTime();
31
+ });
16
32
 
17
- let kdData = kd.init(data[0], 9);
18
- for (let i = 1; i < data.length; i++) {
19
- kdData = kd.next(data[i], kdData, 9);
33
+ const response = [];
34
+ for (let i = 0; i < opens.length; i++) {
35
+ if (opens[i] !== null) {
36
+ response.push({
37
+ t: ts[i],
38
+ o: opens[i],
39
+ c: closes[i],
40
+ h: highs[i],
41
+ l: lows[i],
42
+ v: volumes[i],
43
+ });
44
+ }
20
45
  }
21
- console.log(kdData);
46
+
47
+
48
+ let mfiData = mfi.init(response[0], 14);
49
+ for (let i = 1; i < response.length; i++) {
50
+ mfiData = mfi.next(response[i], mfiData, 14);
51
+ }
52
+ console.log(mfiData);
22
53
  })
23
54
  .catch((error) => {
24
55
  console.error(error);
@@ -28,4 +59,4 @@ function DemoDay(stockId) {
28
59
  });
29
60
  }
30
61
 
31
- DemoDay("2385");
62
+ DemoDay("1618");
@@ -19,6 +19,7 @@ export { default as Week } from "./stockSkills/week.js";
19
19
  export { default as Vma } from "./stockSkills/vma.js";
20
20
  export { default as Williams } from "./stockSkills/williams.js";
21
21
  export { default as ObvEma } from "./stockSkills/obv_ema.js";
22
+ export { default as Mfi } from "./stockSkills/mfi.js";
22
23
  export { add } from "./test/add.js";
23
24
  export { minus } from "./test/minus.js";
24
25
  export { default as calculateDivisionFactor } from "./utils/calculateDivisionFactor.js";
package/dist/cjs/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseLsusbOutput = exports.isJSON = exports.calculateDivisionFactor = exports.minus = exports.add = exports.ObvEma = exports.Williams = exports.Vma = exports.Week = exports.dateFormat = exports.Rsi = exports.Obv = exports.Macd = exports.Ma = exports.Kd = exports.Gold = exports.Ema = exports.Boll = exports.calcSeasonalIndicesNoTrend = exports.weightMovingAverages = exports.movingAverages = exports.exponentialSmoothing = exports.findTroughByGradient = exports.findPeaksByGradient = exports.SwingExtremesType = exports.SwingExtremes = exports.slope = exports.simpleRegressionModel = exports.Angle = void 0;
3
+ exports.parseLsusbOutput = exports.isJSON = exports.calculateDivisionFactor = exports.minus = exports.add = exports.Mfi = exports.ObvEma = exports.Williams = exports.Vma = exports.Week = exports.dateFormat = exports.Rsi = exports.Obv = exports.Macd = exports.Ma = exports.Kd = exports.Gold = exports.Ema = exports.Boll = exports.calcSeasonalIndicesNoTrend = exports.weightMovingAverages = exports.movingAverages = exports.exponentialSmoothing = exports.findTroughByGradient = exports.findPeaksByGradient = exports.SwingExtremesType = exports.SwingExtremes = exports.slope = exports.simpleRegressionModel = exports.Angle = void 0;
4
4
  /*
5
5
  請注意,在 src/index.ts 中,我的導入包含文件擴展名(.js)。
6
6
  如果需要支持 Node.js 和構建工具(ex: webpack),則不需要這樣做。 **因為commonJs默認js副檔名**
@@ -49,6 +49,8 @@ var williams_js_1 = require("./stockSkills/williams.js");
49
49
  Object.defineProperty(exports, "Williams", { enumerable: true, get: function () { return williams_js_1.default; } });
50
50
  var obv_ema_js_1 = require("./stockSkills/obv_ema.js");
51
51
  Object.defineProperty(exports, "ObvEma", { enumerable: true, get: function () { return obv_ema_js_1.default; } });
52
+ var mfi_js_1 = require("./stockSkills/mfi.js");
53
+ Object.defineProperty(exports, "Mfi", { enumerable: true, get: function () { return mfi_js_1.default; } });
52
54
  var add_js_1 = require("./test/add.js");
53
55
  Object.defineProperty(exports, "add", { enumerable: true, get: function () { return add_js_1.add; } });
54
56
  var minus_js_1 = require("./test/minus.js");
@@ -0,0 +1,15 @@
1
+ import { StockListType, StockType } from "./types";
2
+ export type MfiResType = {
3
+ dataset: StockListType;
4
+ mfi: number | null;
5
+ type: number;
6
+ sumPositiveMF: number;
7
+ sumNegativeMF: number;
8
+ };
9
+ export default class Mfi {
10
+ private getTypicalPrice;
11
+ private getRawMoneyFlow;
12
+ init(data: StockType, type: number): MfiResType;
13
+ next(data: StockType, preList: MfiResType, type: number): MfiResType;
14
+ calculateMFI(prices: StockListType, period?: number): number[];
15
+ }
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ var Mfi = /** @class */ (function () {
15
+ function Mfi() {
16
+ }
17
+ // 計算典型價格 (Typical Price)
18
+ Object.defineProperty(Mfi.prototype, "getTypicalPrice", {
19
+ enumerable: false,
20
+ configurable: true,
21
+ writable: true,
22
+ value: function (data) {
23
+ return (data.h + data.l + data.c) / 3;
24
+ }
25
+ });
26
+ // 計算原始資金流量 (Raw Money Flow)
27
+ Object.defineProperty(Mfi.prototype, "getRawMoneyFlow", {
28
+ enumerable: false,
29
+ configurable: true,
30
+ writable: true,
31
+ value: function (data) {
32
+ return this.getTypicalPrice(data) * data.v;
33
+ }
34
+ });
35
+ Object.defineProperty(Mfi.prototype, "init", {
36
+ enumerable: false,
37
+ configurable: true,
38
+ writable: true,
39
+ value: function (data, type) {
40
+ return {
41
+ dataset: [data],
42
+ mfi: null,
43
+ type: type,
44
+ sumPositiveMF: 0,
45
+ sumNegativeMF: 0,
46
+ };
47
+ }
48
+ });
49
+ Object.defineProperty(Mfi.prototype, "next", {
50
+ enumerable: false,
51
+ configurable: true,
52
+ writable: true,
53
+ value: function (data, preList, type) {
54
+ preList.dataset.push(data);
55
+ // 資料不足,不計算 MFI
56
+ if (preList.dataset.length < type + 1) {
57
+ return __assign(__assign({}, preList), { mfi: null, type: type, sumPositiveMF: 0, sumNegativeMF: 0 });
58
+ }
59
+ var sumPositiveMF = preList.sumPositiveMF;
60
+ var sumNegativeMF = preList.sumNegativeMF;
61
+ if (preList.dataset.length === type + 1) {
62
+ // 第一次達到計算條件,初始化計算
63
+ sumPositiveMF = 0;
64
+ sumNegativeMF = 0;
65
+ for (var i = 1; i <= type; i++) {
66
+ var currentTP = this.getTypicalPrice(preList.dataset[i]);
67
+ var prevTP = this.getTypicalPrice(preList.dataset[i - 1]);
68
+ var rawMF = this.getRawMoneyFlow(preList.dataset[i]);
69
+ if (currentTP > prevTP) {
70
+ sumPositiveMF += rawMF;
71
+ }
72
+ else if (currentTP < prevTP) {
73
+ sumNegativeMF += rawMF;
74
+ }
75
+ }
76
+ }
77
+ else {
78
+ // 增量更新:移除最舊的影響,加上最新的影響
79
+ // 移除最舊的資料影響
80
+ var oldestTP = this.getTypicalPrice(preList.dataset[0]);
81
+ var secondOldestTP = this.getTypicalPrice(preList.dataset[1]);
82
+ var oldestRawMF = this.getRawMoneyFlow(preList.dataset[1]);
83
+ if (secondOldestTP > oldestTP) {
84
+ sumPositiveMF -= oldestRawMF;
85
+ }
86
+ else if (secondOldestTP < oldestTP) {
87
+ sumNegativeMF -= oldestRawMF;
88
+ }
89
+ // 移除最舊的資料
90
+ preList.dataset.shift();
91
+ // 加上最新的資料影響
92
+ var newTP = this.getTypicalPrice(preList.dataset[preList.dataset.length - 1]);
93
+ var prevTP = this.getTypicalPrice(preList.dataset[preList.dataset.length - 2]);
94
+ var newRawMF = this.getRawMoneyFlow(preList.dataset[preList.dataset.length - 1]);
95
+ if (newTP > prevTP) {
96
+ sumPositiveMF += newRawMF;
97
+ }
98
+ else if (newTP < prevTP) {
99
+ sumNegativeMF += newRawMF;
100
+ }
101
+ }
102
+ // 計算 MFI
103
+ var mfi;
104
+ if (sumNegativeMF === 0) {
105
+ mfi = 100;
106
+ }
107
+ else {
108
+ var moneyFlowRatio = sumPositiveMF / sumNegativeMF;
109
+ mfi = 100 - (100 / (1 + moneyFlowRatio));
110
+ }
111
+ return {
112
+ dataset: preList.dataset,
113
+ type: type,
114
+ mfi: mfi,
115
+ sumPositiveMF: sumPositiveMF,
116
+ sumNegativeMF: sumNegativeMF
117
+ };
118
+ }
119
+ });
120
+ Object.defineProperty(Mfi.prototype, "calculateMFI", {
121
+ enumerable: false,
122
+ configurable: true,
123
+ writable: true,
124
+ value: function (prices, period) {
125
+ if (period === void 0) { period = 14; }
126
+ if (prices.length < period + 1) {
127
+ return [];
128
+ }
129
+ var mfis = [];
130
+ // 從 period 開始,確保有足夠的資料進行比較
131
+ for (var endIdx = period; endIdx < prices.length; endIdx++) {
132
+ var sumPositiveMF = 0;
133
+ var sumNegativeMF = 0;
134
+ // 計算 period 個交易日的資金流量
135
+ // 從 endIdx - period + 1 到 endIdx,共 period 個資料點
136
+ // 但比較是從 endIdx - period + 2 開始(因為要跟前一天比)
137
+ for (var i = endIdx - period + 1; i <= endIdx; i++) {
138
+ // 確保 i-1 >= 0,避免溢位
139
+ if (i === 0)
140
+ continue;
141
+ var currentTP = this.getTypicalPrice(prices[i]);
142
+ var prevTP = this.getTypicalPrice(prices[i - 1]);
143
+ var rawMF = this.getRawMoneyFlow(prices[i]);
144
+ if (currentTP > prevTP) {
145
+ sumPositiveMF += rawMF;
146
+ }
147
+ else if (currentTP < prevTP) {
148
+ sumNegativeMF += rawMF;
149
+ }
150
+ }
151
+ // 計算 MFI
152
+ if (sumNegativeMF === 0) {
153
+ mfis.push(100);
154
+ }
155
+ else {
156
+ var moneyFlowRatio = sumPositiveMF / sumNegativeMF;
157
+ var mfi = 100 - (100 / (1 + moneyFlowRatio));
158
+ mfis.push(mfi);
159
+ }
160
+ }
161
+ return mfis;
162
+ }
163
+ });
164
+ return Mfi;
165
+ }());
166
+ exports.default = Mfi;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var mfi_1 = require("./mfi");
4
+ describe('MFI (Money Flow Index) Algorithm', function () {
5
+ var mfi;
6
+ beforeEach(function () {
7
+ mfi = new mfi_1.default();
8
+ });
9
+ // 測試用的股票資料
10
+ var createStockData = function (high, low, close, volume) { return ({
11
+ h: high,
12
+ l: low,
13
+ c: close,
14
+ v: volume,
15
+ o: close,
16
+ t: Date.now()
17
+ }); };
18
+ describe('init method', function () {
19
+ it('should initialize with correct default values', function () {
20
+ var stockData = createStockData(100, 95, 98, 1000);
21
+ var result = mfi.init(stockData, 14);
22
+ expect(result).toEqual({
23
+ dataset: [stockData],
24
+ mfi: null,
25
+ type: 14,
26
+ sumPositiveMF: 0,
27
+ sumNegativeMF: 0,
28
+ });
29
+ });
30
+ it('should work with different period types', function () {
31
+ var stockData = createStockData(100, 95, 98, 1000);
32
+ var result = mfi.init(stockData, 5);
33
+ expect(result.type).toBe(5);
34
+ expect(result.dataset).toHaveLength(1);
35
+ });
36
+ });
37
+ describe('next method - insufficient data', function () {
38
+ it('should return null MFI when data is insufficient', function () {
39
+ var result = mfi.init(createStockData(100, 95, 98, 1000), 3);
40
+ // 第二個資料點
41
+ result = mfi.next(createStockData(102, 97, 100, 1200), result, 3);
42
+ expect(result.mfi).toBeNull();
43
+ expect(result.dataset).toHaveLength(2);
44
+ // 第三個資料點
45
+ result = mfi.next(createStockData(105, 99, 103, 1500), result, 3);
46
+ expect(result.mfi).toBeNull();
47
+ expect(result.dataset).toHaveLength(3);
48
+ });
49
+ });
50
+ describe('next method - sufficient data', function () {
51
+ it('should calculate MFI correctly when reaching minimum period', function () {
52
+ var period = 3;
53
+ var result = mfi.init(createStockData(100, 95, 98, 1000), period);
54
+ // 添加資料直到滿足計算條件
55
+ result = mfi.next(createStockData(102, 97, 100, 1200), result, period); // TP上升
56
+ result = mfi.next(createStockData(105, 99, 103, 1500), result, period); // TP上升
57
+ result = mfi.next(createStockData(103, 98, 101, 1300), result, period); // TP下降
58
+ expect(result.mfi).not.toBeNull();
59
+ expect(typeof result.mfi).toBe('number');
60
+ expect(result.mfi).toBeGreaterThanOrEqual(0);
61
+ expect(result.mfi).toBeLessThanOrEqual(100);
62
+ expect(result.dataset).toHaveLength(period + 1);
63
+ });
64
+ it('should handle all positive money flows (MFI = 100)', function () {
65
+ var period = 2;
66
+ var result = mfi.init(createStockData(100, 95, 98, 1000), period);
67
+ // 所有後續價格都上升
68
+ result = mfi.next(createStockData(102, 97, 100, 1200), result, period);
69
+ result = mfi.next(createStockData(105, 99, 103, 1500), result, period);
70
+ expect(result.mfi).toBe(100);
71
+ });
72
+ it('should maintain sliding window correctly', function () {
73
+ var period = 2;
74
+ var result = mfi.init(createStockData(100, 95, 98, 1000), period);
75
+ result = mfi.next(createStockData(102, 97, 100, 1200), result, period);
76
+ result = mfi.next(createStockData(105, 99, 103, 1500), result, period);
77
+ // 新增第五個資料點,應該移除第一個
78
+ var oldFirstData = result.dataset[0];
79
+ result = mfi.next(createStockData(103, 98, 101, 1300), result, period);
80
+ expect(result.dataset).toHaveLength(period + 1);
81
+ expect(result.dataset[0]).not.toEqual(oldFirstData);
82
+ });
83
+ });
84
+ describe('calculateMFI method', function () {
85
+ it('should return empty array for insufficient data', function () {
86
+ var prices = [
87
+ createStockData(100, 95, 98, 1000),
88
+ createStockData(102, 97, 100, 1200),
89
+ ];
90
+ var result = mfi.calculateMFI(prices, 14);
91
+ expect(result).toEqual([]);
92
+ });
93
+ it('should calculate MFI for sufficient data', function () {
94
+ // 創建測試資料:期間為3,需要4個資料點
95
+ var prices = [
96
+ createStockData(100, 95, 98, 1000),
97
+ createStockData(102, 97, 100, 1200),
98
+ createStockData(105, 99, 103, 1500),
99
+ createStockData(103, 98, 101, 1300), // TP = 100.67 (下降)
100
+ ];
101
+ var result = mfi.calculateMFI(prices, 3);
102
+ expect(result).toHaveLength(1); // 只能計算一個MFI值
103
+ expect(result[0]).toBeGreaterThanOrEqual(0);
104
+ expect(result[0]).toBeLessThanOrEqual(100);
105
+ });
106
+ it('should calculate multiple MFI values for longer data series', function () {
107
+ var prices = [];
108
+ // 創建6個資料點,期間為3,應該能計算3個MFI值
109
+ for (var i = 0; i < 6; i++) {
110
+ prices.push(createStockData(100 + i * 2, 95 + i * 2, 98 + i * 2 + (i % 2 === 0 ? 1 : -1), // 交替上下
111
+ 1000 + i * 100));
112
+ }
113
+ var result = mfi.calculateMFI(prices, 3);
114
+ expect(result).toHaveLength(3); // 6 - 3 = 3個MFI值
115
+ result.forEach(function (mfiValue) {
116
+ expect(mfiValue).toBeGreaterThanOrEqual(0);
117
+ expect(mfiValue).toBeLessThanOrEqual(100);
118
+ });
119
+ });
120
+ it('should handle edge case with no negative money flow', function () {
121
+ // 創建持續上升的價格資料
122
+ var prices = [];
123
+ for (var i = 0; i < 5; i++) {
124
+ prices.push(createStockData(100 + i * 5, 95 + i * 5, 98 + i * 5, 1000));
125
+ }
126
+ var result = mfi.calculateMFI(prices, 3);
127
+ expect(result).toHaveLength(2);
128
+ result.forEach(function (mfiValue) {
129
+ expect(mfiValue).toBe(100); // 全部正向資金流量
130
+ });
131
+ });
132
+ it('should handle equal typical prices correctly', function () {
133
+ var prices = [
134
+ createStockData(100, 95, 98, 1000),
135
+ createStockData(102, 97, 99, 1200),
136
+ createStockData(101, 96, 99, 1500),
137
+ createStockData(103, 98, 100, 1300),
138
+ createStockData(102, 97, 99, 1100), // TP = 99.33 (相等)
139
+ ];
140
+ var result = mfi.calculateMFI(prices, 3);
141
+ expect(result).toHaveLength(2);
142
+ result.forEach(function (mfiValue) {
143
+ expect(mfiValue).toBeGreaterThanOrEqual(0);
144
+ expect(mfiValue).toBeLessThanOrEqual(100);
145
+ expect(Number.isNaN(mfiValue)).toBe(false);
146
+ });
147
+ });
148
+ });
149
+ describe('typical price calculation', function () {
150
+ it('should calculate typical price correctly', function () {
151
+ var stockData = createStockData(105, 95, 100, 1000);
152
+ var mfiInstance = new mfi_1.default();
153
+ // 使用反射來測試私有方法(僅用於測試)
154
+ var typicalPrice = mfiInstance.getTypicalPrice(stockData);
155
+ expect(typicalPrice).toBeCloseTo((105 + 95 + 100) / 3, 2);
156
+ });
157
+ });
158
+ describe('raw money flow calculation', function () {
159
+ it('should calculate raw money flow correctly', function () {
160
+ var stockData = createStockData(105, 95, 100, 1500);
161
+ var mfiInstance = new mfi_1.default();
162
+ var typicalPrice = (105 + 95 + 100) / 3;
163
+ var expectedMF = typicalPrice * 1500;
164
+ var rawMF = mfiInstance.getRawMoneyFlow(stockData);
165
+ expect(rawMF).toBeCloseTo(expectedMF, 2);
166
+ });
167
+ });
168
+ describe('consistency between methods', function () {
169
+ it('should produce same results using next() and calculateMFI()', function () {
170
+ var prices = [];
171
+ for (var i = 0; i < 8; i++) {
172
+ prices.push(createStockData(100 + i * 3, 95 + i * 2, 98 + i * 2.5, 1000 + i * 50));
173
+ }
174
+ var period = 3;
175
+ // 使用 calculateMFI
176
+ var batchResult = mfi.calculateMFI(prices, period);
177
+ // 使用 next 方法逐步計算
178
+ var result = mfi.init(prices[0], period);
179
+ var nextResults = [];
180
+ for (var i = 1; i < prices.length; i++) {
181
+ result = mfi.next(prices[i], result, period);
182
+ nextResults.push(result.mfi);
183
+ }
184
+ // 過濾掉 null 值
185
+ var validNextResults = nextResults.filter(function (val) { return val !== null; });
186
+ expect(validNextResults).toHaveLength(batchResult.length);
187
+ // 比較結果(允許小的浮點誤差)
188
+ for (var i = 0; i < batchResult.length; i++) {
189
+ expect(validNextResults[i]).toBeCloseTo(batchResult[i], 6);
190
+ }
191
+ });
192
+ });
193
+ });
@@ -19,6 +19,7 @@ export { default as Week } from "./stockSkills/week.js";
19
19
  export { default as Vma } from "./stockSkills/vma.js";
20
20
  export { default as Williams } from "./stockSkills/williams.js";
21
21
  export { default as ObvEma } from "./stockSkills/obv_ema.js";
22
+ export { default as Mfi } from "./stockSkills/mfi.js";
22
23
  export { add } from "./test/add.js";
23
24
  export { minus } from "./test/minus.js";
24
25
  export { default as calculateDivisionFactor } from "./utils/calculateDivisionFactor.js";
package/dist/esm/index.js CHANGED
@@ -22,6 +22,7 @@ export { default as Week } from "./stockSkills/week.js";
22
22
  export { default as Vma } from "./stockSkills/vma.js";
23
23
  export { default as Williams } from "./stockSkills/williams.js";
24
24
  export { default as ObvEma } from "./stockSkills/obv_ema.js";
25
+ export { default as Mfi } from "./stockSkills/mfi.js";
25
26
  export { add } from "./test/add.js";
26
27
  export { minus } from "./test/minus.js";
27
28
  export { default as calculateDivisionFactor } from "./utils/calculateDivisionFactor.js";
@@ -0,0 +1,15 @@
1
+ import { StockListType, StockType } from "./types";
2
+ export type MfiResType = {
3
+ dataset: StockListType;
4
+ mfi: number | null;
5
+ type: number;
6
+ sumPositiveMF: number;
7
+ sumNegativeMF: number;
8
+ };
9
+ export default class Mfi {
10
+ private getTypicalPrice;
11
+ private getRawMoneyFlow;
12
+ init(data: StockType, type: number): MfiResType;
13
+ next(data: StockType, preList: MfiResType, type: number): MfiResType;
14
+ calculateMFI(prices: StockListType, period?: number): number[];
15
+ }
@@ -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;