@ch20026103/anysis 0.0.19-alpha → 0.0.19-alpha1
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 +2 -2
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +3 -3
- package/dist/cjs/stockSkills/ichimoku.d.ts +35 -0
- package/dist/cjs/stockSkills/ichimoku.js +96 -0
- package/dist/cjs/stockSkills/ichimoku.test.d.ts +1 -0
- package/dist/cjs/stockSkills/ichimoku.test.js +158 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/stockSkills/ichimoku.d.ts +35 -0
- package/dist/esm/stockSkills/ichimoku.js +94 -0
- package/dist/esm/stockSkills/ichimoku.test.d.ts +1 -0
- package/dist/esm/stockSkills/ichimoku.test.js +156 -0
- package/dist/umd/index.js +73 -34
- package/package.json +1 -1
- package/dist/cjs/stockSkills/ad.d.ts +0 -17
- package/dist/cjs/stockSkills/ad.js +0 -57
- package/dist/esm/stockSkills/ad.d.ts +0 -17
- package/dist/esm/stockSkills/ad.js +0 -55
package/demo/main.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* eslint @typescript-eslint/no-var-requires: "off" */
|
|
2
2
|
const axios = require("axios");
|
|
3
|
-
const {
|
|
3
|
+
const { Ichimoku } = require("../dist/cjs/index.js");
|
|
4
4
|
|
|
5
5
|
// 使用示例
|
|
6
|
-
const ad = new
|
|
6
|
+
const ad = new Ichimoku();
|
|
7
7
|
function DemoDay(stockId) {
|
|
8
8
|
axios
|
|
9
9
|
.get(
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ 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
22
|
export { default as Mfi } from "./stockSkills/mfi.js";
|
|
23
|
-
export { default as
|
|
23
|
+
export { default as Ichimoku } from "./stockSkills/ichimoku.js";
|
|
24
24
|
export { add } from "./test/add.js";
|
|
25
25
|
export { minus } from "./test/minus.js";
|
|
26
26
|
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.
|
|
3
|
+
exports.parseLsusbOutput = exports.isJSON = exports.calculateDivisionFactor = exports.minus = exports.add = exports.Ichimoku = 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副檔名**
|
|
@@ -51,8 +51,8 @@ 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
52
|
var mfi_js_1 = require("./stockSkills/mfi.js");
|
|
53
53
|
Object.defineProperty(exports, "Mfi", { enumerable: true, get: function () { return mfi_js_1.default; } });
|
|
54
|
-
var
|
|
55
|
-
Object.defineProperty(exports, "
|
|
54
|
+
var ichimoku_js_1 = require("./stockSkills/ichimoku.js");
|
|
55
|
+
Object.defineProperty(exports, "Ichimoku", { enumerable: true, get: function () { return ichimoku_js_1.default; } });
|
|
56
56
|
var add_js_1 = require("./test/add.js");
|
|
57
57
|
Object.defineProperty(exports, "add", { enumerable: true, get: function () { return add_js_1.add; } });
|
|
58
58
|
var minus_js_1 = require("./test/minus.js");
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { StockListType, StockType } from "./types";
|
|
2
|
+
type NewStockType = Required<Pick<StockType, "v" | "h" | "l" | "c">> & StockType;
|
|
3
|
+
export type IchimokuValType = {
|
|
4
|
+
tenkan: number | null;
|
|
5
|
+
kijun: number | null;
|
|
6
|
+
senkouA: number | null;
|
|
7
|
+
senkouB: number | null;
|
|
8
|
+
chikou: number | null;
|
|
9
|
+
};
|
|
10
|
+
export type IchimokuResType = {
|
|
11
|
+
dataset: Readonly<StockListType>;
|
|
12
|
+
ichimoku: IchimokuValType;
|
|
13
|
+
};
|
|
14
|
+
interface IchimokuType {
|
|
15
|
+
init: (data: NewStockType) => IchimokuResType;
|
|
16
|
+
next: (data: NewStockType, preList: IchimokuResType) => IchimokuResType;
|
|
17
|
+
}
|
|
18
|
+
export default class IchimokuCloud implements IchimokuType {
|
|
19
|
+
init(data: NewStockType): IchimokuResType;
|
|
20
|
+
/**
|
|
21
|
+
* 優化說明:
|
|
22
|
+
* 如果 preList.dataset 是一個可變陣列,我們直接 push 以達到最佳效能 O(1)。
|
|
23
|
+
* 如果你的框架 (如 React state) 強制要求 immutable,則需要改回 [...prev, data] 的寫法。
|
|
24
|
+
* 下面的寫法假設可以 Mutation (這在 Class 內部運算或 Backend 處理很常見)。
|
|
25
|
+
*/
|
|
26
|
+
next(data: NewStockType, preList: IchimokuResType): IchimokuResType;
|
|
27
|
+
private calcIchimoku;
|
|
28
|
+
/**
|
|
29
|
+
* 優化:
|
|
30
|
+
* 1. 移除 .slice(),避免產生 Garbage Collection。
|
|
31
|
+
* 2. 使用反向迴圈 (i--),通常在 JS 引擎中微幅快一點,且語意上是「從現在往回看」。
|
|
32
|
+
*/
|
|
33
|
+
private getMidPrice;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
var IchimokuCloud = /** @class */ (function () {
|
|
4
|
+
function IchimokuCloud() {
|
|
5
|
+
}
|
|
6
|
+
Object.defineProperty(IchimokuCloud.prototype, "init", {
|
|
7
|
+
enumerable: false,
|
|
8
|
+
configurable: true,
|
|
9
|
+
writable: true,
|
|
10
|
+
value: function (data) {
|
|
11
|
+
var dataset = [data];
|
|
12
|
+
return {
|
|
13
|
+
dataset: dataset,
|
|
14
|
+
ichimoku: this.calcIchimoku(dataset, dataset.length - 1),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* 優化說明:
|
|
20
|
+
* 如果 preList.dataset 是一個可變陣列,我們直接 push 以達到最佳效能 O(1)。
|
|
21
|
+
* 如果你的框架 (如 React state) 強制要求 immutable,則需要改回 [...prev, data] 的寫法。
|
|
22
|
+
* 下面的寫法假設可以 Mutation (這在 Class 內部運算或 Backend 處理很常見)。
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(IchimokuCloud.prototype, "next", {
|
|
25
|
+
enumerable: false,
|
|
26
|
+
configurable: true,
|
|
27
|
+
writable: true,
|
|
28
|
+
value: function (data, preList) {
|
|
29
|
+
// 強制轉型以進行 push (避免 typescript 報錯 readonly)
|
|
30
|
+
var mutableDataset = preList.dataset;
|
|
31
|
+
mutableDataset.push(data);
|
|
32
|
+
// 只需要計算最後一筆
|
|
33
|
+
var currentResult = this.calcIchimoku(mutableDataset, mutableDataset.length - 1);
|
|
34
|
+
return {
|
|
35
|
+
dataset: mutableDataset,
|
|
36
|
+
ichimoku: currentResult,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
// 核心計算邏輯
|
|
41
|
+
Object.defineProperty(IchimokuCloud.prototype, "calcIchimoku", {
|
|
42
|
+
enumerable: false,
|
|
43
|
+
configurable: true,
|
|
44
|
+
writable: true,
|
|
45
|
+
value: function (dataList, i) {
|
|
46
|
+
var currentData = dataList[i];
|
|
47
|
+
// 優化:直接傳入 index 與 list,不產生新陣列
|
|
48
|
+
var tenkanVal = this.getMidPrice(dataList, i, 9);
|
|
49
|
+
var kijunVal = this.getMidPrice(dataList, i, 26);
|
|
50
|
+
var senkouBVal = this.getMidPrice(dataList, i, 52);
|
|
51
|
+
var senkouAVal = null;
|
|
52
|
+
if (tenkanVal !== null && kijunVal !== null) {
|
|
53
|
+
senkouAVal = (tenkanVal + kijunVal) / 2;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
tenkan: tenkanVal,
|
|
57
|
+
kijun: kijunVal,
|
|
58
|
+
senkouA: senkouAVal,
|
|
59
|
+
senkouB: senkouBVal,
|
|
60
|
+
chikou: currentData.c,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* 優化:
|
|
66
|
+
* 1. 移除 .slice(),避免產生 Garbage Collection。
|
|
67
|
+
* 2. 使用反向迴圈 (i--),通常在 JS 引擎中微幅快一點,且語意上是「從現在往回看」。
|
|
68
|
+
*/
|
|
69
|
+
Object.defineProperty(IchimokuCloud.prototype, "getMidPrice", {
|
|
70
|
+
enumerable: false,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: function (list, currentIndex, periods) {
|
|
74
|
+
if (currentIndex < periods - 1) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
// 計算視窗的起始 index
|
|
78
|
+
var start = currentIndex - (periods - 1);
|
|
79
|
+
// 初始化最大最小
|
|
80
|
+
// 小技巧:直接拿第一個值當初始值,避免 Infinity 的比較
|
|
81
|
+
var maxH = list[start].h;
|
|
82
|
+
var minL = list[start].l;
|
|
83
|
+
// 從 start + 1 開始遍歷到 currentIndex
|
|
84
|
+
for (var j = start + 1; j <= currentIndex; j++) {
|
|
85
|
+
var _a = list[j], h = _a.h, l = _a.l;
|
|
86
|
+
if (h > maxH)
|
|
87
|
+
maxH = h;
|
|
88
|
+
if (l < minL)
|
|
89
|
+
minL = l;
|
|
90
|
+
}
|
|
91
|
+
return (maxH + minL) / 2;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return IchimokuCloud;
|
|
95
|
+
}());
|
|
96
|
+
exports.default = IchimokuCloud;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
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 vitest_1 = require("vitest");
|
|
15
|
+
var ichimoku_1 = require("./ichimoku");
|
|
16
|
+
// 輔助函式:快速產生測試用的假資料
|
|
17
|
+
var createStock = function (idx, override) {
|
|
18
|
+
return __assign({ t: idx, o: 100, c: 100, h: 100, l: 100, v: 1000 }, override);
|
|
19
|
+
};
|
|
20
|
+
(0, vitest_1.describe)("IchimokuCloud Algo", function () {
|
|
21
|
+
var ichimoku;
|
|
22
|
+
(0, vitest_1.beforeEach)(function () {
|
|
23
|
+
ichimoku = new ichimoku_1.default();
|
|
24
|
+
});
|
|
25
|
+
(0, vitest_1.describe)("init", function () {
|
|
26
|
+
(0, vitest_1.it)("should initialize with correct structure", function () {
|
|
27
|
+
var data = createStock(0, { c: 150 });
|
|
28
|
+
var res = ichimoku.init(data);
|
|
29
|
+
(0, vitest_1.expect)(res.dataset).toHaveLength(1);
|
|
30
|
+
(0, vitest_1.expect)(res.dataset[0]).toEqual(data);
|
|
31
|
+
// 第一筆資料,除了 chikou (收盤價) 外,其他應為 null
|
|
32
|
+
(0, vitest_1.expect)(res.ichimoku.chikou).toBe(150);
|
|
33
|
+
(0, vitest_1.expect)(res.ichimoku.tenkan).toBeNull();
|
|
34
|
+
(0, vitest_1.expect)(res.ichimoku.kijun).toBeNull();
|
|
35
|
+
(0, vitest_1.expect)(res.ichimoku.senkouA).toBeNull();
|
|
36
|
+
(0, vitest_1.expect)(res.ichimoku.senkouB).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.describe)("next & Logic Calculation", function () {
|
|
40
|
+
(0, vitest_1.it)("should accumulate dataset correctly", function () {
|
|
41
|
+
var res = ichimoku.init(createStock(0));
|
|
42
|
+
res = ichimoku.next(createStock(1), res);
|
|
43
|
+
res = ichimoku.next(createStock(2), res);
|
|
44
|
+
(0, vitest_1.expect)(res.dataset).toHaveLength(3);
|
|
45
|
+
(0, vitest_1.expect)(res.dataset[2].t).toBe(2);
|
|
46
|
+
});
|
|
47
|
+
(0, vitest_1.it)("should calculate Tenkan-sen (9 periods) correctly", function () {
|
|
48
|
+
// 策略:建立 8 筆資料,確認 Tenkan 為 null
|
|
49
|
+
// 再加第 9 筆,確認 Tenkan 有值
|
|
50
|
+
// 設定數據:High 逐漸增加 (10~90),Low 保持 0
|
|
51
|
+
// 預期結果:(MaxHigh(90) + MinLow(0)) / 2 = 45
|
|
52
|
+
var res = ichimoku.init(createStock(0, { h: 10, l: 0 }));
|
|
53
|
+
// 加入第 2 到第 8 筆 (共 8 筆)
|
|
54
|
+
for (var i = 1; i < 8; i++) {
|
|
55
|
+
res = ichimoku.next(createStock(i, { h: (i + 1) * 10, l: 0 }), res);
|
|
56
|
+
(0, vitest_1.expect)(res.ichimoku.tenkan).toBeNull(); // 資料不足 9 筆
|
|
57
|
+
}
|
|
58
|
+
// 加入第 9 筆
|
|
59
|
+
res = ichimoku.next(createStock(8, { h: 90, l: 0 }), res);
|
|
60
|
+
// 驗證
|
|
61
|
+
(0, vitest_1.expect)(res.dataset).toHaveLength(9);
|
|
62
|
+
(0, vitest_1.expect)(res.ichimoku.tenkan).toBe(45); // (90 + 0) / 2
|
|
63
|
+
});
|
|
64
|
+
(0, vitest_1.it)("should calculate Kijun-sen (26 periods) correctly", function () {
|
|
65
|
+
// 策略:直接灌入 25 筆資料
|
|
66
|
+
// 第 1 筆 Low = 10,第 26 筆 High = 110,中間平穩
|
|
67
|
+
// Kijun = (110 + 10) / 2 = 60
|
|
68
|
+
var res = ichimoku.init(createStock(0, { h: 50, l: 10 })); // 最低點在 index 0
|
|
69
|
+
// 填補中間資料 (index 1 ~ 24)
|
|
70
|
+
for (var i = 1; i < 25; i++) {
|
|
71
|
+
res = ichimoku.next(createStock(i, { h: 50, l: 50 }), res);
|
|
72
|
+
(0, vitest_1.expect)(res.ichimoku.kijun).toBeNull(); // 資料不足 26 筆
|
|
73
|
+
}
|
|
74
|
+
// 加入第 26 筆 (index 25)
|
|
75
|
+
res = ichimoku.next(createStock(25, { h: 110, l: 50 }), res); // 最高點在 index 25
|
|
76
|
+
(0, vitest_1.expect)(res.dataset).toHaveLength(26);
|
|
77
|
+
(0, vitest_1.expect)(res.ichimoku.kijun).toBe(60); // (110 + 10) / 2
|
|
78
|
+
});
|
|
79
|
+
(0, vitest_1.it)("should calculate Senkou Span A correctly", function () {
|
|
80
|
+
// Senkou A = (Tenkan + Kijun) / 2
|
|
81
|
+
// 我們需要至少 26 筆資料讓 Kijun 有值 (此時 Tenkan 也有值)
|
|
82
|
+
var res = ichimoku.init(createStock(0));
|
|
83
|
+
// 我們設計一個場景:
|
|
84
|
+
// 過去 9 天 (Tenkan window): Max=20, Min=10 -> Tenkan = 15
|
|
85
|
+
// 過去 26 天 (Kijun window): Max=30, Min=0 -> Kijun = 15
|
|
86
|
+
// 預期 Senkou A = (15 + 15) / 2 = 15
|
|
87
|
+
// 先塞入前段資料
|
|
88
|
+
for (var i = 1; i < 26; i++) {
|
|
89
|
+
// 為了方便控制,我們讓最後一筆資料決定極值
|
|
90
|
+
// 這裡隨便塞,只要最後一筆能控制範圍即可,因為我們邏輯是找區間最大最小
|
|
91
|
+
// 但為了確保 Kijun 區間的 Min 是 0,我們在 index 0 設 l=0
|
|
92
|
+
// 為了確保 Kijun 區間的 Max 是 30,我們在 index 0 設 h=30 (如果它是最大)
|
|
93
|
+
// 簡單化:讓資料全部平躺,最後一瞬間拉高拉低
|
|
94
|
+
// 這裡不用迴圈邏輯太複雜,直接用輔助函式慢慢疊
|
|
95
|
+
res = ichimoku.next(createStock(i), res);
|
|
96
|
+
}
|
|
97
|
+
// 重新建立一個乾淨的測試流程,比較好控制數學
|
|
98
|
+
// Reset
|
|
99
|
+
res = ichimoku.init(createStock(0, { h: 100, l: 100 }));
|
|
100
|
+
// 填充 24 筆 (共 25 筆)
|
|
101
|
+
for (var i = 1; i < 25; i++) {
|
|
102
|
+
res = ichimoku.next(createStock(i, { h: 100, l: 100 }), res);
|
|
103
|
+
}
|
|
104
|
+
// 第 26 筆 (Index 25) 進來,決定生死
|
|
105
|
+
// Kijun 看過去 26 筆 (idx 0~25)
|
|
106
|
+
// Tenkan 看過去 9 筆 (idx 17~25)
|
|
107
|
+
// 設定目標:
|
|
108
|
+
// Tenkan: Max=110, Min=90 => Avg=100
|
|
109
|
+
// Kijun: Max=120, Min=80 => Avg=100
|
|
110
|
+
// SenkouA => 100
|
|
111
|
+
// 修改 dataset 裡的值 (模擬真實波動)
|
|
112
|
+
// 讓 index 0 (很久以前) 有個極低值 80 (影響 Kijun 不影響 Tenkan)
|
|
113
|
+
// 讓 index 0 有個極高值 120 (影響 Kijun 不影響 Tenkan)
|
|
114
|
+
// 注意:上面的 init 和 next 已經把資料寫死為 100 了,這在單元測試有點難搞
|
|
115
|
+
// 所以我們用更簡單的方法:在 next 過程中精準控制
|
|
116
|
+
// --- 重來:精準控制版 ---
|
|
117
|
+
var ichi = new ichimoku_1.default();
|
|
118
|
+
var r = ichi.init(createStock(0, { h: 120, l: 80 })); // Kijun Range: H=120, L=80
|
|
119
|
+
// 填滿中間 16 筆 (Index 1~16),數值平穩不影響極值,且不讓 Tenkan 抓到
|
|
120
|
+
for (var i = 1; i <= 16; i++) {
|
|
121
|
+
r = ichi.next(createStock(i, { h: 100, l: 100 }), r);
|
|
122
|
+
}
|
|
123
|
+
// 接下來 8 筆 (Index 17~24),準備進入 Tenkan 範圍
|
|
124
|
+
// 我們讓 Tenkan 範圍 (包含下一筆 index 25) 為 H=110, L=90
|
|
125
|
+
for (var i = 17; i <= 24; i++) {
|
|
126
|
+
r = ichi.next(createStock(i, { h: 110, l: 90 }), r);
|
|
127
|
+
}
|
|
128
|
+
// 第 26 筆 (Index 25)
|
|
129
|
+
r = ichi.next(createStock(25, { h: 110, l: 90 }), r);
|
|
130
|
+
// 驗證 Tenkan (看 index 17~25)
|
|
131
|
+
// MaxH = 110, MinL = 90 => 100
|
|
132
|
+
(0, vitest_1.expect)(r.ichimoku.tenkan).toBe(100);
|
|
133
|
+
// 驗證 Kijun (看 index 0~25)
|
|
134
|
+
// MaxH = 120 (from index 0), MinL = 80 (from index 0) => 100
|
|
135
|
+
(0, vitest_1.expect)(r.ichimoku.kijun).toBe(100);
|
|
136
|
+
// 驗證 Senkou A
|
|
137
|
+
(0, vitest_1.expect)(r.ichimoku.senkouA).toBe(100);
|
|
138
|
+
});
|
|
139
|
+
(0, vitest_1.it)("should calculate Senkou Span B (52 periods) correctly", function () {
|
|
140
|
+
var res = ichimoku.init(createStock(0, { h: 200, l: 0 })); // 極值在最開始
|
|
141
|
+
// 填到 50 筆
|
|
142
|
+
for (var i = 1; i < 51; i++) {
|
|
143
|
+
res = ichimoku.next(createStock(i, { h: 100, l: 100 }), res);
|
|
144
|
+
(0, vitest_1.expect)(res.ichimoku.senkouB).toBeNull();
|
|
145
|
+
}
|
|
146
|
+
// 第 52 筆
|
|
147
|
+
res = ichimoku.next(createStock(51, { h: 100, l: 100 }), res);
|
|
148
|
+
// 52 期間 (0~51): Max=200, Min=0 => 100
|
|
149
|
+
(0, vitest_1.expect)(res.ichimoku.senkouB).toBe(100);
|
|
150
|
+
});
|
|
151
|
+
(0, vitest_1.it)("should handle Chikou Span (just close price)", function () {
|
|
152
|
+
var res = ichimoku.init(createStock(0, { c: 555 }));
|
|
153
|
+
(0, vitest_1.expect)(res.ichimoku.chikou).toBe(555);
|
|
154
|
+
res = ichimoku.next(createStock(1, { c: 888 }), res);
|
|
155
|
+
(0, vitest_1.expect)(res.ichimoku.chikou).toBe(888);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ 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
22
|
export { default as Mfi } from "./stockSkills/mfi.js";
|
|
23
|
-
export { default as
|
|
23
|
+
export { default as Ichimoku } from "./stockSkills/ichimoku.js";
|
|
24
24
|
export { add } from "./test/add.js";
|
|
25
25
|
export { minus } from "./test/minus.js";
|
|
26
26
|
export { default as calculateDivisionFactor } from "./utils/calculateDivisionFactor.js";
|
package/dist/esm/index.js
CHANGED
|
@@ -23,7 +23,7 @@ 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
25
|
export { default as Mfi } from "./stockSkills/mfi.js";
|
|
26
|
-
export { default as
|
|
26
|
+
export { default as Ichimoku } from "./stockSkills/ichimoku.js";
|
|
27
27
|
export { add } from "./test/add.js";
|
|
28
28
|
export { minus } from "./test/minus.js";
|
|
29
29
|
export { default as calculateDivisionFactor } from "./utils/calculateDivisionFactor.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { StockListType, StockType } from "./types";
|
|
2
|
+
type NewStockType = Required<Pick<StockType, "v" | "h" | "l" | "c">> & StockType;
|
|
3
|
+
export type IchimokuValType = {
|
|
4
|
+
tenkan: number | null;
|
|
5
|
+
kijun: number | null;
|
|
6
|
+
senkouA: number | null;
|
|
7
|
+
senkouB: number | null;
|
|
8
|
+
chikou: number | null;
|
|
9
|
+
};
|
|
10
|
+
export type IchimokuResType = {
|
|
11
|
+
dataset: Readonly<StockListType>;
|
|
12
|
+
ichimoku: IchimokuValType;
|
|
13
|
+
};
|
|
14
|
+
interface IchimokuType {
|
|
15
|
+
init: (data: NewStockType) => IchimokuResType;
|
|
16
|
+
next: (data: NewStockType, preList: IchimokuResType) => IchimokuResType;
|
|
17
|
+
}
|
|
18
|
+
export default class IchimokuCloud implements IchimokuType {
|
|
19
|
+
init(data: NewStockType): IchimokuResType;
|
|
20
|
+
/**
|
|
21
|
+
* 優化說明:
|
|
22
|
+
* 如果 preList.dataset 是一個可變陣列,我們直接 push 以達到最佳效能 O(1)。
|
|
23
|
+
* 如果你的框架 (如 React state) 強制要求 immutable,則需要改回 [...prev, data] 的寫法。
|
|
24
|
+
* 下面的寫法假設可以 Mutation (這在 Class 內部運算或 Backend 處理很常見)。
|
|
25
|
+
*/
|
|
26
|
+
next(data: NewStockType, preList: IchimokuResType): IchimokuResType;
|
|
27
|
+
private calcIchimoku;
|
|
28
|
+
/**
|
|
29
|
+
* 優化:
|
|
30
|
+
* 1. 移除 .slice(),避免產生 Garbage Collection。
|
|
31
|
+
* 2. 使用反向迴圈 (i--),通常在 JS 引擎中微幅快一點,且語意上是「從現在往回看」。
|
|
32
|
+
*/
|
|
33
|
+
private getMidPrice;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
var IchimokuCloud = /** @class */ (function () {
|
|
2
|
+
function IchimokuCloud() {
|
|
3
|
+
}
|
|
4
|
+
Object.defineProperty(IchimokuCloud.prototype, "init", {
|
|
5
|
+
enumerable: false,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true,
|
|
8
|
+
value: function (data) {
|
|
9
|
+
var dataset = [data];
|
|
10
|
+
return {
|
|
11
|
+
dataset: dataset,
|
|
12
|
+
ichimoku: this.calcIchimoku(dataset, dataset.length - 1),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* 優化說明:
|
|
18
|
+
* 如果 preList.dataset 是一個可變陣列,我們直接 push 以達到最佳效能 O(1)。
|
|
19
|
+
* 如果你的框架 (如 React state) 強制要求 immutable,則需要改回 [...prev, data] 的寫法。
|
|
20
|
+
* 下面的寫法假設可以 Mutation (這在 Class 內部運算或 Backend 處理很常見)。
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(IchimokuCloud.prototype, "next", {
|
|
23
|
+
enumerable: false,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: function (data, preList) {
|
|
27
|
+
// 強制轉型以進行 push (避免 typescript 報錯 readonly)
|
|
28
|
+
var mutableDataset = preList.dataset;
|
|
29
|
+
mutableDataset.push(data);
|
|
30
|
+
// 只需要計算最後一筆
|
|
31
|
+
var currentResult = this.calcIchimoku(mutableDataset, mutableDataset.length - 1);
|
|
32
|
+
return {
|
|
33
|
+
dataset: mutableDataset,
|
|
34
|
+
ichimoku: currentResult,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// 核心計算邏輯
|
|
39
|
+
Object.defineProperty(IchimokuCloud.prototype, "calcIchimoku", {
|
|
40
|
+
enumerable: false,
|
|
41
|
+
configurable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
value: function (dataList, i) {
|
|
44
|
+
var currentData = dataList[i];
|
|
45
|
+
// 優化:直接傳入 index 與 list,不產生新陣列
|
|
46
|
+
var tenkanVal = this.getMidPrice(dataList, i, 9);
|
|
47
|
+
var kijunVal = this.getMidPrice(dataList, i, 26);
|
|
48
|
+
var senkouBVal = this.getMidPrice(dataList, i, 52);
|
|
49
|
+
var senkouAVal = null;
|
|
50
|
+
if (tenkanVal !== null && kijunVal !== null) {
|
|
51
|
+
senkouAVal = (tenkanVal + kijunVal) / 2;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
tenkan: tenkanVal,
|
|
55
|
+
kijun: kijunVal,
|
|
56
|
+
senkouA: senkouAVal,
|
|
57
|
+
senkouB: senkouBVal,
|
|
58
|
+
chikou: currentData.c,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* 優化:
|
|
64
|
+
* 1. 移除 .slice(),避免產生 Garbage Collection。
|
|
65
|
+
* 2. 使用反向迴圈 (i--),通常在 JS 引擎中微幅快一點,且語意上是「從現在往回看」。
|
|
66
|
+
*/
|
|
67
|
+
Object.defineProperty(IchimokuCloud.prototype, "getMidPrice", {
|
|
68
|
+
enumerable: false,
|
|
69
|
+
configurable: true,
|
|
70
|
+
writable: true,
|
|
71
|
+
value: function (list, currentIndex, periods) {
|
|
72
|
+
if (currentIndex < periods - 1) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
// 計算視窗的起始 index
|
|
76
|
+
var start = currentIndex - (periods - 1);
|
|
77
|
+
// 初始化最大最小
|
|
78
|
+
// 小技巧:直接拿第一個值當初始值,避免 Infinity 的比較
|
|
79
|
+
var maxH = list[start].h;
|
|
80
|
+
var minL = list[start].l;
|
|
81
|
+
// 從 start + 1 開始遍歷到 currentIndex
|
|
82
|
+
for (var j = start + 1; j <= currentIndex; j++) {
|
|
83
|
+
var _a = list[j], h = _a.h, l = _a.l;
|
|
84
|
+
if (h > maxH)
|
|
85
|
+
maxH = h;
|
|
86
|
+
if (l < minL)
|
|
87
|
+
minL = l;
|
|
88
|
+
}
|
|
89
|
+
return (maxH + minL) / 2;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return IchimokuCloud;
|
|
93
|
+
}());
|
|
94
|
+
export default IchimokuCloud;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
13
|
+
import IchimokuCloud from "./ichimoku";
|
|
14
|
+
// 輔助函式:快速產生測試用的假資料
|
|
15
|
+
var createStock = function (idx, override) {
|
|
16
|
+
return __assign({ t: idx, o: 100, c: 100, h: 100, l: 100, v: 1000 }, override);
|
|
17
|
+
};
|
|
18
|
+
describe("IchimokuCloud Algo", function () {
|
|
19
|
+
var ichimoku;
|
|
20
|
+
beforeEach(function () {
|
|
21
|
+
ichimoku = new IchimokuCloud();
|
|
22
|
+
});
|
|
23
|
+
describe("init", function () {
|
|
24
|
+
it("should initialize with correct structure", function () {
|
|
25
|
+
var data = createStock(0, { c: 150 });
|
|
26
|
+
var res = ichimoku.init(data);
|
|
27
|
+
expect(res.dataset).toHaveLength(1);
|
|
28
|
+
expect(res.dataset[0]).toEqual(data);
|
|
29
|
+
// 第一筆資料,除了 chikou (收盤價) 外,其他應為 null
|
|
30
|
+
expect(res.ichimoku.chikou).toBe(150);
|
|
31
|
+
expect(res.ichimoku.tenkan).toBeNull();
|
|
32
|
+
expect(res.ichimoku.kijun).toBeNull();
|
|
33
|
+
expect(res.ichimoku.senkouA).toBeNull();
|
|
34
|
+
expect(res.ichimoku.senkouB).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe("next & Logic Calculation", function () {
|
|
38
|
+
it("should accumulate dataset correctly", function () {
|
|
39
|
+
var res = ichimoku.init(createStock(0));
|
|
40
|
+
res = ichimoku.next(createStock(1), res);
|
|
41
|
+
res = ichimoku.next(createStock(2), res);
|
|
42
|
+
expect(res.dataset).toHaveLength(3);
|
|
43
|
+
expect(res.dataset[2].t).toBe(2);
|
|
44
|
+
});
|
|
45
|
+
it("should calculate Tenkan-sen (9 periods) correctly", function () {
|
|
46
|
+
// 策略:建立 8 筆資料,確認 Tenkan 為 null
|
|
47
|
+
// 再加第 9 筆,確認 Tenkan 有值
|
|
48
|
+
// 設定數據:High 逐漸增加 (10~90),Low 保持 0
|
|
49
|
+
// 預期結果:(MaxHigh(90) + MinLow(0)) / 2 = 45
|
|
50
|
+
var res = ichimoku.init(createStock(0, { h: 10, l: 0 }));
|
|
51
|
+
// 加入第 2 到第 8 筆 (共 8 筆)
|
|
52
|
+
for (var i = 1; i < 8; i++) {
|
|
53
|
+
res = ichimoku.next(createStock(i, { h: (i + 1) * 10, l: 0 }), res);
|
|
54
|
+
expect(res.ichimoku.tenkan).toBeNull(); // 資料不足 9 筆
|
|
55
|
+
}
|
|
56
|
+
// 加入第 9 筆
|
|
57
|
+
res = ichimoku.next(createStock(8, { h: 90, l: 0 }), res);
|
|
58
|
+
// 驗證
|
|
59
|
+
expect(res.dataset).toHaveLength(9);
|
|
60
|
+
expect(res.ichimoku.tenkan).toBe(45); // (90 + 0) / 2
|
|
61
|
+
});
|
|
62
|
+
it("should calculate Kijun-sen (26 periods) correctly", function () {
|
|
63
|
+
// 策略:直接灌入 25 筆資料
|
|
64
|
+
// 第 1 筆 Low = 10,第 26 筆 High = 110,中間平穩
|
|
65
|
+
// Kijun = (110 + 10) / 2 = 60
|
|
66
|
+
var res = ichimoku.init(createStock(0, { h: 50, l: 10 })); // 最低點在 index 0
|
|
67
|
+
// 填補中間資料 (index 1 ~ 24)
|
|
68
|
+
for (var i = 1; i < 25; i++) {
|
|
69
|
+
res = ichimoku.next(createStock(i, { h: 50, l: 50 }), res);
|
|
70
|
+
expect(res.ichimoku.kijun).toBeNull(); // 資料不足 26 筆
|
|
71
|
+
}
|
|
72
|
+
// 加入第 26 筆 (index 25)
|
|
73
|
+
res = ichimoku.next(createStock(25, { h: 110, l: 50 }), res); // 最高點在 index 25
|
|
74
|
+
expect(res.dataset).toHaveLength(26);
|
|
75
|
+
expect(res.ichimoku.kijun).toBe(60); // (110 + 10) / 2
|
|
76
|
+
});
|
|
77
|
+
it("should calculate Senkou Span A correctly", function () {
|
|
78
|
+
// Senkou A = (Tenkan + Kijun) / 2
|
|
79
|
+
// 我們需要至少 26 筆資料讓 Kijun 有值 (此時 Tenkan 也有值)
|
|
80
|
+
var res = ichimoku.init(createStock(0));
|
|
81
|
+
// 我們設計一個場景:
|
|
82
|
+
// 過去 9 天 (Tenkan window): Max=20, Min=10 -> Tenkan = 15
|
|
83
|
+
// 過去 26 天 (Kijun window): Max=30, Min=0 -> Kijun = 15
|
|
84
|
+
// 預期 Senkou A = (15 + 15) / 2 = 15
|
|
85
|
+
// 先塞入前段資料
|
|
86
|
+
for (var i = 1; i < 26; i++) {
|
|
87
|
+
// 為了方便控制,我們讓最後一筆資料決定極值
|
|
88
|
+
// 這裡隨便塞,只要最後一筆能控制範圍即可,因為我們邏輯是找區間最大最小
|
|
89
|
+
// 但為了確保 Kijun 區間的 Min 是 0,我們在 index 0 設 l=0
|
|
90
|
+
// 為了確保 Kijun 區間的 Max 是 30,我們在 index 0 設 h=30 (如果它是最大)
|
|
91
|
+
// 簡單化:讓資料全部平躺,最後一瞬間拉高拉低
|
|
92
|
+
// 這裡不用迴圈邏輯太複雜,直接用輔助函式慢慢疊
|
|
93
|
+
res = ichimoku.next(createStock(i), res);
|
|
94
|
+
}
|
|
95
|
+
// 重新建立一個乾淨的測試流程,比較好控制數學
|
|
96
|
+
// Reset
|
|
97
|
+
res = ichimoku.init(createStock(0, { h: 100, l: 100 }));
|
|
98
|
+
// 填充 24 筆 (共 25 筆)
|
|
99
|
+
for (var i = 1; i < 25; i++) {
|
|
100
|
+
res = ichimoku.next(createStock(i, { h: 100, l: 100 }), res);
|
|
101
|
+
}
|
|
102
|
+
// 第 26 筆 (Index 25) 進來,決定生死
|
|
103
|
+
// Kijun 看過去 26 筆 (idx 0~25)
|
|
104
|
+
// Tenkan 看過去 9 筆 (idx 17~25)
|
|
105
|
+
// 設定目標:
|
|
106
|
+
// Tenkan: Max=110, Min=90 => Avg=100
|
|
107
|
+
// Kijun: Max=120, Min=80 => Avg=100
|
|
108
|
+
// SenkouA => 100
|
|
109
|
+
// 修改 dataset 裡的值 (模擬真實波動)
|
|
110
|
+
// 讓 index 0 (很久以前) 有個極低值 80 (影響 Kijun 不影響 Tenkan)
|
|
111
|
+
// 讓 index 0 有個極高值 120 (影響 Kijun 不影響 Tenkan)
|
|
112
|
+
// 注意:上面的 init 和 next 已經把資料寫死為 100 了,這在單元測試有點難搞
|
|
113
|
+
// 所以我們用更簡單的方法:在 next 過程中精準控制
|
|
114
|
+
// --- 重來:精準控制版 ---
|
|
115
|
+
var ichi = new IchimokuCloud();
|
|
116
|
+
var r = ichi.init(createStock(0, { h: 120, l: 80 })); // Kijun Range: H=120, L=80
|
|
117
|
+
// 填滿中間 16 筆 (Index 1~16),數值平穩不影響極值,且不讓 Tenkan 抓到
|
|
118
|
+
for (var i = 1; i <= 16; i++) {
|
|
119
|
+
r = ichi.next(createStock(i, { h: 100, l: 100 }), r);
|
|
120
|
+
}
|
|
121
|
+
// 接下來 8 筆 (Index 17~24),準備進入 Tenkan 範圍
|
|
122
|
+
// 我們讓 Tenkan 範圍 (包含下一筆 index 25) 為 H=110, L=90
|
|
123
|
+
for (var i = 17; i <= 24; i++) {
|
|
124
|
+
r = ichi.next(createStock(i, { h: 110, l: 90 }), r);
|
|
125
|
+
}
|
|
126
|
+
// 第 26 筆 (Index 25)
|
|
127
|
+
r = ichi.next(createStock(25, { h: 110, l: 90 }), r);
|
|
128
|
+
// 驗證 Tenkan (看 index 17~25)
|
|
129
|
+
// MaxH = 110, MinL = 90 => 100
|
|
130
|
+
expect(r.ichimoku.tenkan).toBe(100);
|
|
131
|
+
// 驗證 Kijun (看 index 0~25)
|
|
132
|
+
// MaxH = 120 (from index 0), MinL = 80 (from index 0) => 100
|
|
133
|
+
expect(r.ichimoku.kijun).toBe(100);
|
|
134
|
+
// 驗證 Senkou A
|
|
135
|
+
expect(r.ichimoku.senkouA).toBe(100);
|
|
136
|
+
});
|
|
137
|
+
it("should calculate Senkou Span B (52 periods) correctly", function () {
|
|
138
|
+
var res = ichimoku.init(createStock(0, { h: 200, l: 0 })); // 極值在最開始
|
|
139
|
+
// 填到 50 筆
|
|
140
|
+
for (var i = 1; i < 51; i++) {
|
|
141
|
+
res = ichimoku.next(createStock(i, { h: 100, l: 100 }), res);
|
|
142
|
+
expect(res.ichimoku.senkouB).toBeNull();
|
|
143
|
+
}
|
|
144
|
+
// 第 52 筆
|
|
145
|
+
res = ichimoku.next(createStock(51, { h: 100, l: 100 }), res);
|
|
146
|
+
// 52 期間 (0~51): Max=200, Min=0 => 100
|
|
147
|
+
expect(res.ichimoku.senkouB).toBe(100);
|
|
148
|
+
});
|
|
149
|
+
it("should handle Chikou Span (just close price)", function () {
|
|
150
|
+
var res = ichimoku.init(createStock(0, { c: 555 }));
|
|
151
|
+
expect(res.ichimoku.chikou).toBe(555);
|
|
152
|
+
res = ichimoku.next(createStock(1, { c: 888 }), res);
|
|
153
|
+
expect(res.ichimoku.chikou).toBe(888);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
package/dist/umd/index.js
CHANGED
|
@@ -1214,7 +1214,7 @@
|
|
|
1214
1214
|
};
|
|
1215
1215
|
return __assign$3.apply(this, arguments);
|
|
1216
1216
|
};
|
|
1217
|
-
var __spreadArray
|
|
1217
|
+
var __spreadArray = (undefined && undefined.__spreadArray) || function (to, from, pack) {
|
|
1218
1218
|
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
1219
1219
|
if (ar || !(i in from)) {
|
|
1220
1220
|
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
@@ -1254,7 +1254,7 @@
|
|
|
1254
1254
|
obv -= currentVolume;
|
|
1255
1255
|
}
|
|
1256
1256
|
return {
|
|
1257
|
-
dataset: __spreadArray
|
|
1257
|
+
dataset: __spreadArray(__spreadArray([], preList.dataset, true), [data], false),
|
|
1258
1258
|
obv: obv,
|
|
1259
1259
|
preClose: currentClose,
|
|
1260
1260
|
};
|
|
@@ -1869,59 +1869,98 @@
|
|
|
1869
1869
|
return Mfi;
|
|
1870
1870
|
}());
|
|
1871
1871
|
|
|
1872
|
-
var
|
|
1873
|
-
|
|
1874
|
-
if (ar || !(i in from)) {
|
|
1875
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
1876
|
-
ar[i] = from[i];
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
1880
|
-
};
|
|
1881
|
-
var Ad = /** @class */ (function () {
|
|
1882
|
-
function Ad() {
|
|
1872
|
+
var IchimokuCloud = /** @class */ (function () {
|
|
1873
|
+
function IchimokuCloud() {
|
|
1883
1874
|
}
|
|
1884
|
-
Object.defineProperty(
|
|
1875
|
+
Object.defineProperty(IchimokuCloud.prototype, "init", {
|
|
1885
1876
|
enumerable: false,
|
|
1886
1877
|
configurable: true,
|
|
1887
1878
|
writable: true,
|
|
1888
1879
|
value: function (data) {
|
|
1889
|
-
|
|
1890
|
-
var ad = this.calcAD(data);
|
|
1880
|
+
var dataset = [data];
|
|
1891
1881
|
return {
|
|
1892
|
-
dataset:
|
|
1893
|
-
|
|
1894
|
-
preClose: data.c,
|
|
1882
|
+
dataset: dataset,
|
|
1883
|
+
ichimoku: this.calcIchimoku(dataset, dataset.length - 1),
|
|
1895
1884
|
};
|
|
1896
1885
|
}
|
|
1897
1886
|
});
|
|
1898
|
-
|
|
1887
|
+
/**
|
|
1888
|
+
* 優化說明:
|
|
1889
|
+
* 如果 preList.dataset 是一個可變陣列,我們直接 push 以達到最佳效能 O(1)。
|
|
1890
|
+
* 如果你的框架 (如 React state) 強制要求 immutable,則需要改回 [...prev, data] 的寫法。
|
|
1891
|
+
* 下面的寫法假設可以 Mutation (這在 Class 內部運算或 Backend 處理很常見)。
|
|
1892
|
+
*/
|
|
1893
|
+
Object.defineProperty(IchimokuCloud.prototype, "next", {
|
|
1899
1894
|
enumerable: false,
|
|
1900
1895
|
configurable: true,
|
|
1901
1896
|
writable: true,
|
|
1902
1897
|
value: function (data, preList) {
|
|
1903
|
-
//
|
|
1904
|
-
var
|
|
1898
|
+
// 強制轉型以進行 push (避免 typescript 報錯 readonly)
|
|
1899
|
+
var mutableDataset = preList.dataset;
|
|
1900
|
+
mutableDataset.push(data);
|
|
1901
|
+
// 只需要計算最後一筆
|
|
1902
|
+
var currentResult = this.calcIchimoku(mutableDataset, mutableDataset.length - 1);
|
|
1905
1903
|
return {
|
|
1906
|
-
dataset:
|
|
1907
|
-
|
|
1908
|
-
preClose: data.c,
|
|
1904
|
+
dataset: mutableDataset,
|
|
1905
|
+
ichimoku: currentResult,
|
|
1909
1906
|
};
|
|
1910
1907
|
}
|
|
1911
1908
|
});
|
|
1912
|
-
//
|
|
1913
|
-
Object.defineProperty(
|
|
1909
|
+
// 核心計算邏輯
|
|
1910
|
+
Object.defineProperty(IchimokuCloud.prototype, "calcIchimoku", {
|
|
1914
1911
|
enumerable: false,
|
|
1915
1912
|
configurable: true,
|
|
1916
1913
|
writable: true,
|
|
1917
|
-
value: function (
|
|
1918
|
-
var
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1914
|
+
value: function (dataList, i) {
|
|
1915
|
+
var currentData = dataList[i];
|
|
1916
|
+
// 優化:直接傳入 index 與 list,不產生新陣列
|
|
1917
|
+
var tenkanVal = this.getMidPrice(dataList, i, 9);
|
|
1918
|
+
var kijunVal = this.getMidPrice(dataList, i, 26);
|
|
1919
|
+
var senkouBVal = this.getMidPrice(dataList, i, 52);
|
|
1920
|
+
var senkouAVal = null;
|
|
1921
|
+
if (tenkanVal !== null && kijunVal !== null) {
|
|
1922
|
+
senkouAVal = (tenkanVal + kijunVal) / 2;
|
|
1923
|
+
}
|
|
1924
|
+
return {
|
|
1925
|
+
tenkan: tenkanVal,
|
|
1926
|
+
kijun: kijunVal,
|
|
1927
|
+
senkouA: senkouAVal,
|
|
1928
|
+
senkouB: senkouBVal,
|
|
1929
|
+
chikou: currentData.c,
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
});
|
|
1933
|
+
/**
|
|
1934
|
+
* 優化:
|
|
1935
|
+
* 1. 移除 .slice(),避免產生 Garbage Collection。
|
|
1936
|
+
* 2. 使用反向迴圈 (i--),通常在 JS 引擎中微幅快一點,且語意上是「從現在往回看」。
|
|
1937
|
+
*/
|
|
1938
|
+
Object.defineProperty(IchimokuCloud.prototype, "getMidPrice", {
|
|
1939
|
+
enumerable: false,
|
|
1940
|
+
configurable: true,
|
|
1941
|
+
writable: true,
|
|
1942
|
+
value: function (list, currentIndex, periods) {
|
|
1943
|
+
if (currentIndex < periods - 1) {
|
|
1944
|
+
return null;
|
|
1945
|
+
}
|
|
1946
|
+
// 計算視窗的起始 index
|
|
1947
|
+
var start = currentIndex - (periods - 1);
|
|
1948
|
+
// 初始化最大最小
|
|
1949
|
+
// 小技巧:直接拿第一個值當初始值,避免 Infinity 的比較
|
|
1950
|
+
var maxH = list[start].h;
|
|
1951
|
+
var minL = list[start].l;
|
|
1952
|
+
// 從 start + 1 開始遍歷到 currentIndex
|
|
1953
|
+
for (var j = start + 1; j <= currentIndex; j++) {
|
|
1954
|
+
var _a = list[j], h = _a.h, l = _a.l;
|
|
1955
|
+
if (h > maxH)
|
|
1956
|
+
maxH = h;
|
|
1957
|
+
if (l < minL)
|
|
1958
|
+
minL = l;
|
|
1959
|
+
}
|
|
1960
|
+
return (maxH + minL) / 2;
|
|
1922
1961
|
}
|
|
1923
1962
|
});
|
|
1924
|
-
return
|
|
1963
|
+
return IchimokuCloud;
|
|
1925
1964
|
}());
|
|
1926
1965
|
|
|
1927
1966
|
function add(a, b) {
|
|
@@ -1960,11 +1999,11 @@
|
|
|
1960
1999
|
return devices;
|
|
1961
2000
|
}
|
|
1962
2001
|
|
|
1963
|
-
exports.Ad = Ad;
|
|
1964
2002
|
exports.Angle = TwoPointAngle;
|
|
1965
2003
|
exports.Boll = Boll;
|
|
1966
2004
|
exports.Ema = Ema;
|
|
1967
2005
|
exports.Gold = Gold;
|
|
2006
|
+
exports.Ichimoku = IchimokuCloud;
|
|
1968
2007
|
exports.Kd = Kd;
|
|
1969
2008
|
exports.Ma = Ma;
|
|
1970
2009
|
exports.Macd = MACD;
|
package/package.json
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { StockListType, StockType } from "./types";
|
|
2
|
-
type NewStockType = Required<Pick<StockType, "v">> & StockType;
|
|
3
|
-
export type AdResType = {
|
|
4
|
-
dataset: StockListType;
|
|
5
|
-
ad: number;
|
|
6
|
-
preClose: number;
|
|
7
|
-
};
|
|
8
|
-
interface AdType {
|
|
9
|
-
init: (data: NewStockType) => AdResType;
|
|
10
|
-
next: (data: NewStockType, preList: AdResType) => AdResType;
|
|
11
|
-
}
|
|
12
|
-
export default class Ad implements AdType {
|
|
13
|
-
init(data: NewStockType): AdResType;
|
|
14
|
-
next(data: NewStockType, preList: AdResType): AdResType;
|
|
15
|
-
private calcAD;
|
|
16
|
-
}
|
|
17
|
-
export {};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
3
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
4
|
-
if (ar || !(i in from)) {
|
|
5
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
6
|
-
ar[i] = from[i];
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
var Ad = /** @class */ (function () {
|
|
13
|
-
function Ad() {
|
|
14
|
-
}
|
|
15
|
-
Object.defineProperty(Ad.prototype, "init", {
|
|
16
|
-
enumerable: false,
|
|
17
|
-
configurable: true,
|
|
18
|
-
writable: true,
|
|
19
|
-
value: function (data) {
|
|
20
|
-
// AD 初始值
|
|
21
|
-
var ad = this.calcAD(data);
|
|
22
|
-
return {
|
|
23
|
-
dataset: [data],
|
|
24
|
-
ad: ad,
|
|
25
|
-
preClose: data.c,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
Object.defineProperty(Ad.prototype, "next", {
|
|
30
|
-
enumerable: false,
|
|
31
|
-
configurable: true,
|
|
32
|
-
writable: true,
|
|
33
|
-
value: function (data, preList) {
|
|
34
|
-
// AD 累加
|
|
35
|
-
var ad = preList.ad + this.calcAD(data);
|
|
36
|
-
return {
|
|
37
|
-
dataset: __spreadArray(__spreadArray([], preList.dataset, true), [data], false),
|
|
38
|
-
ad: ad,
|
|
39
|
-
preClose: data.c,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
// AD 計算公式
|
|
44
|
-
Object.defineProperty(Ad.prototype, "calcAD", {
|
|
45
|
-
enumerable: false,
|
|
46
|
-
configurable: true,
|
|
47
|
-
writable: true,
|
|
48
|
-
value: function (data) {
|
|
49
|
-
var h = data.h, l = data.l, c = data.c, v = data.v;
|
|
50
|
-
if (h === l)
|
|
51
|
-
return 0; // 避免除以零
|
|
52
|
-
return ((c - l - (h - c)) / (h - l)) * v;
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
return Ad;
|
|
56
|
-
}());
|
|
57
|
-
exports.default = Ad;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { StockListType, StockType } from "./types";
|
|
2
|
-
type NewStockType = Required<Pick<StockType, "v">> & StockType;
|
|
3
|
-
export type AdResType = {
|
|
4
|
-
dataset: StockListType;
|
|
5
|
-
ad: number;
|
|
6
|
-
preClose: number;
|
|
7
|
-
};
|
|
8
|
-
interface AdType {
|
|
9
|
-
init: (data: NewStockType) => AdResType;
|
|
10
|
-
next: (data: NewStockType, preList: AdResType) => AdResType;
|
|
11
|
-
}
|
|
12
|
-
export default class Ad implements AdType {
|
|
13
|
-
init(data: NewStockType): AdResType;
|
|
14
|
-
next(data: NewStockType, preList: AdResType): AdResType;
|
|
15
|
-
private calcAD;
|
|
16
|
-
}
|
|
17
|
-
export {};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
2
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
3
|
-
if (ar || !(i in from)) {
|
|
4
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
5
|
-
ar[i] = from[i];
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
9
|
-
};
|
|
10
|
-
var Ad = /** @class */ (function () {
|
|
11
|
-
function Ad() {
|
|
12
|
-
}
|
|
13
|
-
Object.defineProperty(Ad.prototype, "init", {
|
|
14
|
-
enumerable: false,
|
|
15
|
-
configurable: true,
|
|
16
|
-
writable: true,
|
|
17
|
-
value: function (data) {
|
|
18
|
-
// AD 初始值
|
|
19
|
-
var ad = this.calcAD(data);
|
|
20
|
-
return {
|
|
21
|
-
dataset: [data],
|
|
22
|
-
ad: ad,
|
|
23
|
-
preClose: data.c,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
Object.defineProperty(Ad.prototype, "next", {
|
|
28
|
-
enumerable: false,
|
|
29
|
-
configurable: true,
|
|
30
|
-
writable: true,
|
|
31
|
-
value: function (data, preList) {
|
|
32
|
-
// AD 累加
|
|
33
|
-
var ad = preList.ad + this.calcAD(data);
|
|
34
|
-
return {
|
|
35
|
-
dataset: __spreadArray(__spreadArray([], preList.dataset, true), [data], false),
|
|
36
|
-
ad: ad,
|
|
37
|
-
preClose: data.c,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
// AD 計算公式
|
|
42
|
-
Object.defineProperty(Ad.prototype, "calcAD", {
|
|
43
|
-
enumerable: false,
|
|
44
|
-
configurable: true,
|
|
45
|
-
writable: true,
|
|
46
|
-
value: function (data) {
|
|
47
|
-
var h = data.h, l = data.l, c = data.c, v = data.v;
|
|
48
|
-
if (h === l)
|
|
49
|
-
return 0; // 避免除以零
|
|
50
|
-
return ((c - l - (h - c)) / (h - l)) * v;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
return Ad;
|
|
54
|
-
}());
|
|
55
|
-
export default Ad;
|