w-trade-backtest 1.0.3
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/.editorconfig +9 -0
- package/.eslintignore +3 -0
- package/.eslintrc.js +55 -0
- package/.jsdoc +25 -0
- package/README.md +167 -0
- package/SECURITY.md +5 -0
- package/babel.config.js +16 -0
- package/dist/renderOrdersTable.js +217 -0
- package/dist/renderOrdersTimeline.js +159 -0
- package/dist/renderProfitPlot.js +198 -0
- package/dist/renderSummary.js +194 -0
- package/dist/tmp.html +232 -0
- package/dist/w-trade-backtest.umd.js +32 -0
- package/dist/w-trade-backtest.umd.js.map +1 -0
- package/docs/calcOrders.mjs.html +278 -0
- package/docs/calcOrdersRatio.mjs.html +146 -0
- package/docs/calcOrdersSummary.mjs.html +506 -0
- package/docs/calcOrdersSummarySimple.mjs.html +207 -0
- package/docs/calcSummary.mjs.html +201 -0
- package/docs/closeAndSummaryOrders.mjs.html +265 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/genReport.mjs.html +196 -0
- package/docs/genReportCore.mjs.html +128 -0
- package/docs/global.html +4231 -0
- package/docs/index.html +84 -0
- package/docs/runStrategies.mjs.html +244 -0
- package/docs/runStrategy.mjs.html +455 -0
- package/docs/scripts/collapse.js +39 -0
- package/docs/scripts/commonNav.js +28 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/nav.js +12 -0
- package/docs/scripts/polyfill.js +4 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/scripts/search.js +99 -0
- package/docs/styles/jsdoc.css +776 -0
- package/docs/styles/prettify.css +80 -0
- package/g.mjs +146 -0
- package/package.json +30 -0
- package/script.txt +19 -0
- package/src/WTradeBacktest.mjs +27 -0
- package/src/calcOrders.mjs +206 -0
- package/src/calcOrdersRatio.mjs +74 -0
- package/src/calcOrdersSummary.mjs +434 -0
- package/src/calcOrdersSummarySimple.mjs +135 -0
- package/src/calcSummary.mjs +129 -0
- package/src/closeAndSummaryOrders.mjs +193 -0
- package/src/genReport.mjs +124 -0
- package/src/genReportCore.mjs +56 -0
- package/src/ott.mjs +28 -0
- package/src/readJson.mjs +26 -0
- package/src/renderOrdersTable.js +217 -0
- package/src/renderOrdersTimeline.js +159 -0
- package/src/renderProfitPlot.js +198 -0
- package/src/renderSummary.js +194 -0
- package/src/runStrategies.mjs +172 -0
- package/src/runStrategy.mjs +383 -0
- package/src/tmp.html +232 -0
- package/src/writeJson.mjs +31 -0
- package/src/writeTxt.mjs +19 -0
- package/test/unit-WTradeBacktest.test.mjs +541 -0
- package/toolg/addVersion.mjs +4 -0
- package/toolg/cleanFolder.mjs +5 -0
- package/toolg/gDistRollup.mjs +51 -0
- package/toolg/gDocsExams.mjs +48 -0
- package/toolg/modifyReadme.mjs +4 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>calcOrdersSummarySimple.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#calcOrders">calcOrders</a></li><li><a href="global.html#calcOrdersRatio">calcOrdersRatio</a></li><li><a href="global.html#calcOrdersSummary">calcOrdersSummary</a></li><li><a href="global.html#calcOrdersSummarySimple">calcOrdersSummarySimple</a></li><li><a href="global.html#calcSummary">calcSummary</a></li><li><a href="global.html#closeAndSummaryOrders">closeAndSummaryOrders</a></li><li><a href="global.html#genReport">genReport</a></li><li><a href="global.html#genReportCore">genReportCore</a></li><li><a href="global.html#runStrategies">runStrategies</a></li><li><a href="global.html#runStrategy">runStrategy</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">calcOrdersSummarySimple.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>//精簡版 calcOrdersSummary — 只算「選股熱路徑」需要的欄位, 純迴圈無 lodash、不需先跑 calcOrdersRatio。
|
|
49
|
+
//
|
|
50
|
+
//用途: walk-forward 選股每組合每日各算一次摘要(數百萬次), 原 calcOrdersRatio+calcOrdersSummary 走 lodash 多趟、配大量物件, 太慢。
|
|
51
|
+
// 本版直接從 orders 一趟算出排序所需的 {numTrade, rWin, 等效年化}(+ 幾個便宜欄), 不算 drawdown/Sharpe/累計曲線。
|
|
52
|
+
//
|
|
53
|
+
//輸入 ordersAll: 已含 timeStart/timeEnd/uTrade/uProfitOrLoss/uEquity/modeResult 的訂單(由 calcOrders/calcOrdersFast/ordersFromBars 設定)。
|
|
54
|
+
// 與原 calcOrdersSummary 的對應欄位 bit-exact:
|
|
55
|
+
// numTrade / numTradeFin / rWin / uTradeAllMax / rTradeAllMax / uEquityFinal / btDays / btYears /
|
|
56
|
+
// rEquivalentCumuProfitOrLossFinalNormYear
|
|
57
|
+
// (演算法與原版一致: 最大同時持倉 = 各單 [timeStart,timeEnd] 佔 uTrade 的最大同時總和, 以「各訂單 timeStart 時刻累計持倉」取 max;
|
|
58
|
+
// 等效年化 = (uEquityFinal - uIni) / uTradeAllMax / 年數。)
|
|
59
|
+
import dig from 'wsemi/src/dig.mjs'
|
|
60
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 統計全部訂單之精簡版回測摘要
|
|
64
|
+
*
|
|
65
|
+
* 為calcOrdersSummary之精簡版,只算選股熱路徑所需欄位,純迴圈實作且不需先跑calcOrdersRatio
|
|
66
|
+
* 對應欄位與完整版bit-exact: numTrade、numTradeFin、rWin、uTradeAllMax、rTradeAllMax、uEquityFinal、btDays、btYears、rEquivalentCumuProfitOrLossFinalNormYear
|
|
67
|
+
* 不算drawdown、夏普值與累計曲線
|
|
68
|
+
*
|
|
69
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
|
|
70
|
+
* @function
|
|
71
|
+
* @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件(可用src/ott.mjs或自行以dayjs包裝)
|
|
72
|
+
* @param {Number} uIni 輸入初始資金正數
|
|
73
|
+
* @param {Array} ordersAll 輸入已結算訂單陣列(經calcOrders設定timeStart、timeEnd、uTrade、uEquity、modeResult)
|
|
74
|
+
* @param {String} timeOhlcStart 輸入回測起始秒時間字串
|
|
75
|
+
* @param {String} timeOhlcEnd 輸入回測結束秒時間字串
|
|
76
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
77
|
+
* @param {Boolean} [opt.skipSort=false] 輸入呼叫端保證ordersAll已依timeStart升序時可跳過排序布林值,預設false
|
|
78
|
+
* @returns {Object} 回傳精簡版回測摘要物件
|
|
79
|
+
* @example
|
|
80
|
+
*
|
|
81
|
+
* let arrOhlc = [
|
|
82
|
+
* { time: '2020-01-01T00:00:00', Open: 100, High: 101, Low: 99, Close: 100 },
|
|
83
|
+
* { time: '2020-01-01T04:00:00', Open: 100, High: 106, Low: 100, Close: 105 },
|
|
84
|
+
* { time: '2020-01-01T08:00:00', Open: 105, High: 107, Low: 102, Close: 103 },
|
|
85
|
+
* { time: '2020-01-01T12:00:00', Open: 103, High: 104, Low: 94, Close: 95 },
|
|
86
|
+
* { time: '2020-01-01T16:00:00', Open: 95, High: 98, Low: 92, Close: 93 },
|
|
87
|
+
* { time: '2020-01-01T20:00:00', Open: 93, High: 99, Low: 95, Close: 97 },
|
|
88
|
+
* ]
|
|
89
|
+
*
|
|
90
|
+
* //ordersSubmit, 4張下單: 前3張分別於後續K棒觸發止盈或止損, 第4張未觸發(未平倉)
|
|
91
|
+
* let kpDef = { uTrade: 100, timeEnd: '', priceEnd: '', modeResult: '', uFee: 0.05 }
|
|
92
|
+
* let ordersSubmit = [
|
|
93
|
+
* { mode: 'long', timeStart: '2020-01-01T00:00:00', priceStart: 100, priceTakeProfit: 105, priceStopLoss: 97, ...kpDef },
|
|
94
|
+
* { mode: 'long', timeStart: '2020-01-01T04:00:00', priceStart: 105, priceTakeProfit: 110.25, priceStopLoss: 101.85, ...kpDef },
|
|
95
|
+
* { mode: 'short', timeStart: '2020-01-01T08:00:00', priceStart: 103, priceTakeProfit: 97.85, priceStopLoss: 106.09, ...kpDef },
|
|
96
|
+
* { mode: 'long', timeStart: '2020-01-01T16:00:00', priceStart: 93, priceTakeProfit: 105, priceStopLoss: 85, ...kpDef },
|
|
97
|
+
* ]
|
|
98
|
+
*
|
|
99
|
+
* let orders = await calcOrders(arrOhlc, ordersSubmit, { uIni: 1000 })
|
|
100
|
+
* let summary = calcOrdersSummarySimple(ott, 1000, orders, '2020-01-01T00:00:00', '2020-01-01T20:00:00')
|
|
101
|
+
* console.log(summary)
|
|
102
|
+
* // => {
|
|
103
|
+
* // numTrade: 4,
|
|
104
|
+
* // numTradeFin: 3,
|
|
105
|
+
* // rWin: '66.67%',
|
|
106
|
+
* // uTradeAllMax: 200,
|
|
107
|
+
* // rTradeAllMax: '20.00%',
|
|
108
|
+
* // uEquityFinal: 1006.6999999999999,
|
|
109
|
+
* // btDays: 0,
|
|
110
|
+
* // btYears: '0.0',
|
|
111
|
+
* // rEquivalentCumuProfitOrLossFinalNormYear: '0.00%',
|
|
112
|
+
* // timeOhlcStart: '2020-01-01T00:00:00',
|
|
113
|
+
* // timeOhlcEnd: '2020-01-01T20:00:00'
|
|
114
|
+
* // }
|
|
115
|
+
*
|
|
116
|
+
*/
|
|
117
|
+
let calcOrdersSummarySimple = (ott, uIni, ordersAll, timeOhlcStart, timeOhlcEnd, opt = {}) => {
|
|
118
|
+
|
|
119
|
+
//依 timeStart 排序(與原版一致; uEquity 是 calcOrdersFast 依此序的累計)。
|
|
120
|
+
// opt.skipSort: 呼叫端保證 ordersAll 已依 timeStart 升序時可跳過 slice+sort(熱路徑省數千筆排序); 預設 false 維持原行為。
|
|
121
|
+
if (!(opt && opt.skipSort)) {
|
|
122
|
+
ordersAll = ordersAll.slice().sort((a, b) => a.timeStart < b.timeStart ? -1 : (a.timeStart > b.timeStart ? 1 : 0))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let numTrade = ordersAll.length
|
|
126
|
+
let numTradeFin = 0
|
|
127
|
+
let nWin = 0
|
|
128
|
+
let uEquityFinal = '' //無已平倉時 '' (對齊原版 _.get 預設)
|
|
129
|
+
|
|
130
|
+
//持倉變化事件: 下單 +uTrade, 平倉 -uTrade (long/short 皆 +1, 都佔用倉位)
|
|
131
|
+
let delta = new Map()
|
|
132
|
+
for (let o of ordersAll) {
|
|
133
|
+
if (isestr(o.modeResult)) {
|
|
134
|
+
numTradeFin++
|
|
135
|
+
if (o.modeResult === 'profit') nWin++
|
|
136
|
+
uEquityFinal = o.uEquity //依 timeStart 序, 最後一筆已平倉者勝出 = 原版 ordersFin[last].uEquity
|
|
137
|
+
}
|
|
138
|
+
delta.set(o.timeStart, (delta.get(o.timeStart) || 0) + o.uTrade)
|
|
139
|
+
if (isestr(o.timeEnd)) delta.set(o.timeEnd, (delta.get(o.timeEnd) || 0) - o.uTrade)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//累計持倉(各時刻) → 各訂單 timeStart 時刻取 max abs = 最大同時持倉
|
|
143
|
+
let times = [...delta.keys()].sort()
|
|
144
|
+
let cum = 0
|
|
145
|
+
let cumAt = new Map()
|
|
146
|
+
for (let t of times) {
|
|
147
|
+
cum += delta.get(t); cumAt.set(t, cum)
|
|
148
|
+
}
|
|
149
|
+
let uTradeAllMax = 0
|
|
150
|
+
for (let o of ordersAll) {
|
|
151
|
+
let v = Math.abs(cumAt.get(o.timeStart))
|
|
152
|
+
if (v > uTradeAllMax) uTradeAllMax = v
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let rWin = dig(nWin / numTradeFin * 100, 2) + '%'
|
|
156
|
+
let rTradeAllMax = dig(uTradeAllMax / uIni * 100, 2) + '%'
|
|
157
|
+
|
|
158
|
+
let btDays = ott(timeOhlcEnd).diff(ott(timeOhlcStart), 'day')
|
|
159
|
+
let btYears = btDays / 365
|
|
160
|
+
|
|
161
|
+
let rEquivalentCumuProfitOrLossFinalNormYear
|
|
162
|
+
{
|
|
163
|
+
let v = (uTradeAllMax === 0 || btYears === 0) ? 0 : (uEquityFinal - uIni) / uTradeAllMax / btYears
|
|
164
|
+
rEquivalentCumuProfitOrLossFinalNormYear = `${dig(v * 100, 2)}%`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
numTrade,
|
|
169
|
+
numTradeFin,
|
|
170
|
+
rWin,
|
|
171
|
+
uTradeAllMax,
|
|
172
|
+
rTradeAllMax,
|
|
173
|
+
uEquityFinal,
|
|
174
|
+
btDays,
|
|
175
|
+
btYears: dig(btYears, 1),
|
|
176
|
+
rEquivalentCumuProfitOrLossFinalNormYear,
|
|
177
|
+
timeOhlcStart, //回傳區間起訖(echo 傳入參數, 對齊完整版 calcSummary:53-54; deterministic, 不破壞 bit-exact)
|
|
178
|
+
timeOhlcEnd,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export default calcOrdersSummarySimple
|
|
183
|
+
</code></pre>
|
|
184
|
+
</article>
|
|
185
|
+
</section>
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<br class="clear">
|
|
195
|
+
|
|
196
|
+
<footer>
|
|
197
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 02 2026 20:09:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
198
|
+
</footer>
|
|
199
|
+
|
|
200
|
+
<script>prettyPrint();</script>
|
|
201
|
+
<script src="scripts/polyfill.js"></script>
|
|
202
|
+
<script src="scripts/linenumber.js"></script>
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
</body>
|
|
207
|
+
</html>
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>calcSummary.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#calcOrders">calcOrders</a></li><li><a href="global.html#calcOrdersRatio">calcOrdersRatio</a></li><li><a href="global.html#calcOrdersSummary">calcOrdersSummary</a></li><li><a href="global.html#calcOrdersSummarySimple">calcOrdersSummarySimple</a></li><li><a href="global.html#calcSummary">calcSummary</a></li><li><a href="global.html#closeAndSummaryOrders">closeAndSummaryOrders</a></li><li><a href="global.html#genReport">genReport</a></li><li><a href="global.html#genReportCore">genReportCore</a></li><li><a href="global.html#runStrategies">runStrategies</a></li><li><a href="global.html#runStrategy">runStrategy</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">calcSummary.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import map from 'lodash-es/map.js'
|
|
49
|
+
import sortBy from 'lodash-es/sortBy.js'
|
|
50
|
+
import dig from 'wsemi/src/dig.mjs'
|
|
51
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
52
|
+
import ispnum from 'wsemi/src/ispnum.mjs'
|
|
53
|
+
import calcOrdersRatio from './calcOrdersRatio.mjs'
|
|
54
|
+
import calcOrdersSummary from './calcOrdersSummary.mjs'
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 基於全部交易單重算累積收益並統計回測摘要
|
|
59
|
+
*
|
|
60
|
+
* 依timeStart排序後重算各已平倉單之uEquity、uCumuProfitOrLoss與rCumuProfitOrLoss(多策略各自結算後合併重算用)
|
|
61
|
+
* 再執行calcOrdersRatio與calcOrdersSummary,並附加timeTest、timeOhlcStart、timeOhlcEnd、uIni欄位
|
|
62
|
+
* uIni非正數或timeOhlcStart、timeOhlcEnd非有效字串時reject
|
|
63
|
+
*
|
|
64
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
|
|
65
|
+
* @function
|
|
66
|
+
* @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件(可用src/ott.mjs或自行以dayjs包裝)
|
|
67
|
+
* @param {Number} uIni 輸入初始資金正數
|
|
68
|
+
* @param {Array} orders 輸入已結算訂單陣列(經calcOrders)
|
|
69
|
+
* @param {String} timeOhlcStart 輸入回測起始秒時間字串
|
|
70
|
+
* @param {String} timeOhlcEnd 輸入回測結束秒時間字串
|
|
71
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
72
|
+
* @returns {Promise} 回傳Promise,resolve為回測摘要物件
|
|
73
|
+
* @example
|
|
74
|
+
*
|
|
75
|
+
* let arrOhlc = [
|
|
76
|
+
* { time: '2020-01-01T00:00:00', Open: 100, High: 101, Low: 99, Close: 100 },
|
|
77
|
+
* { time: '2020-01-01T04:00:00', Open: 100, High: 106, Low: 100, Close: 105 },
|
|
78
|
+
* { time: '2020-01-01T08:00:00', Open: 105, High: 107, Low: 102, Close: 103 },
|
|
79
|
+
* { time: '2020-01-01T12:00:00', Open: 103, High: 104, Low: 94, Close: 95 },
|
|
80
|
+
* { time: '2020-01-01T16:00:00', Open: 95, High: 98, Low: 92, Close: 93 },
|
|
81
|
+
* { time: '2020-01-01T20:00:00', Open: 93, High: 99, Low: 95, Close: 97 },
|
|
82
|
+
* ]
|
|
83
|
+
*
|
|
84
|
+
* //ordersSubmit, 4張下單: 前3張分別於後續K棒觸發止盈或止損, 第4張未觸發(未平倉)
|
|
85
|
+
* let kpDef = { uTrade: 100, timeEnd: '', priceEnd: '', modeResult: '', uFee: 0.05 }
|
|
86
|
+
* let ordersSubmit = [
|
|
87
|
+
* { mode: 'long', timeStart: '2020-01-01T00:00:00', priceStart: 100, priceTakeProfit: 105, priceStopLoss: 97, ...kpDef },
|
|
88
|
+
* { mode: 'long', timeStart: '2020-01-01T04:00:00', priceStart: 105, priceTakeProfit: 110.25, priceStopLoss: 101.85, ...kpDef },
|
|
89
|
+
* { mode: 'short', timeStart: '2020-01-01T08:00:00', priceStart: 103, priceTakeProfit: 97.85, priceStopLoss: 106.09, ...kpDef },
|
|
90
|
+
* { mode: 'long', timeStart: '2020-01-01T16:00:00', priceStart: 93, priceTakeProfit: 105, priceStopLoss: 85, ...kpDef },
|
|
91
|
+
* ]
|
|
92
|
+
*
|
|
93
|
+
* let orders = await calcOrders(arrOhlc, ordersSubmit, { uIni: 1000 })
|
|
94
|
+
* let summary = await calcSummary(ott, 1000, orders, '2020-01-01T00:00:00', '2020-01-01T20:00:00')
|
|
95
|
+
* console.log(summary)
|
|
96
|
+
* // => {
|
|
97
|
+
* // timeTest: '2026-08-01T22:27:10+08:00', //執行當下時間
|
|
98
|
+
* // timeOhlcStart: '2020-01-01T00:00:00',
|
|
99
|
+
* // timeOhlcEnd: '2020-01-01T20:00:00',
|
|
100
|
+
* // uIni: 1000,
|
|
101
|
+
* // btDays: 0,
|
|
102
|
+
* // btYears: '0.0',
|
|
103
|
+
* // numTrade: 4,
|
|
104
|
+
* // numTradeFin: 3,
|
|
105
|
+
* // numTradeUnsettled: 1,
|
|
106
|
+
* // uTradeAllMax: 200,
|
|
107
|
+
* // rTradeAllMax: '20.00%',
|
|
108
|
+
* // numTradeAllMax: 2,
|
|
109
|
+
* // uDrawdownMax: 3.1000000000000227,
|
|
110
|
+
* // rDrawdownMax: '0.31%',
|
|
111
|
+
* // rEquivalentDrawdownMax: '1.51%',
|
|
112
|
+
* // rSharpe: 7.675814289051032,
|
|
113
|
+
* // rWin: '66.67%',
|
|
114
|
+
* // uEquityFinal: 1006.6999999999999,
|
|
115
|
+
* // uCumuProfitOrLossFinal: 6.700000000000006,
|
|
116
|
+
* // rCumuProfitOrLossFinal: '0.67%',
|
|
117
|
+
* // rCumuProfitOrLossFinalNormYear: '0.00%',
|
|
118
|
+
* // rEquivalentCumuProfitOrLossFinal: '3.35%',
|
|
119
|
+
* // rEquivalentCumuProfitOrLossFinalNormYear: '0.00%'
|
|
120
|
+
* // }
|
|
121
|
+
*
|
|
122
|
+
*/
|
|
123
|
+
let calcSummary = async(ott, uIni, orders, timeOhlcStart, timeOhlcEnd, opt = {}) => {
|
|
124
|
+
|
|
125
|
+
if (!ispnum(uIni)) {
|
|
126
|
+
throw new Error(`uIni[${uIni}] is not a positive number`)
|
|
127
|
+
}
|
|
128
|
+
if (!isestr(timeOhlcStart)) {
|
|
129
|
+
throw new Error(`invalid timeOhlcStart[${timeOhlcStart}]`)
|
|
130
|
+
}
|
|
131
|
+
if (!isestr(timeOhlcEnd)) {
|
|
132
|
+
throw new Error(`invalid timeOhlcEnd[${timeOhlcEnd}]`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
//sortBy, 已或未平倉訂單一定有timeStart欄位
|
|
136
|
+
orders = sortBy(orders, 'timeStart')
|
|
137
|
+
|
|
138
|
+
//因各策略執行已自行計算, 須基於全策略全交易單重算累積收益
|
|
139
|
+
let uEquity = uIni
|
|
140
|
+
let uCumuProfitOrLoss = 0
|
|
141
|
+
orders = map(orders, (o) => {
|
|
142
|
+
|
|
143
|
+
//未平倉訂單不計算
|
|
144
|
+
if (!isestr(o.modeResult)) {
|
|
145
|
+
return o
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
uEquity += o.uProfitOrLoss
|
|
149
|
+
o.uEquity = uEquity
|
|
150
|
+
uCumuProfitOrLoss += o.uProfitOrLoss
|
|
151
|
+
o.uCumuProfitOrLoss = uCumuProfitOrLoss
|
|
152
|
+
o.rCumuProfitOrLoss = dig((o.uCumuProfitOrLoss / uIni) * 100, 2) + '%' //單位為%
|
|
153
|
+
|
|
154
|
+
return o
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
//計算各單持倉天數, 等效日盈虧比例
|
|
158
|
+
orders = calcOrdersRatio(ott, orders)
|
|
159
|
+
|
|
160
|
+
//評分計算
|
|
161
|
+
let _summary = calcOrdersSummary(ott, uIni, orders, timeOhlcStart, timeOhlcEnd)
|
|
162
|
+
|
|
163
|
+
//summary
|
|
164
|
+
let summary = {
|
|
165
|
+
timeTest: ott().format('YYYY-MM-DDTHH:mm:ssZ'),
|
|
166
|
+
timeOhlcStart,
|
|
167
|
+
timeOhlcEnd,
|
|
168
|
+
uIni,
|
|
169
|
+
..._summary,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return summary
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
export default calcSummary
|
|
177
|
+
</code></pre>
|
|
178
|
+
</article>
|
|
179
|
+
</section>
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<br class="clear">
|
|
189
|
+
|
|
190
|
+
<footer>
|
|
191
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 02 2026 20:09:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
192
|
+
</footer>
|
|
193
|
+
|
|
194
|
+
<script>prettyPrint();</script>
|
|
195
|
+
<script src="scripts/polyfill.js"></script>
|
|
196
|
+
<script src="scripts/linenumber.js"></script>
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
</body>
|
|
201
|
+
</html>
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>closeAndSummaryOrders.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#calcOrders">calcOrders</a></li><li><a href="global.html#calcOrdersRatio">calcOrdersRatio</a></li><li><a href="global.html#calcOrdersSummary">calcOrdersSummary</a></li><li><a href="global.html#calcOrdersSummarySimple">calcOrdersSummarySimple</a></li><li><a href="global.html#calcSummary">calcSummary</a></li><li><a href="global.html#closeAndSummaryOrders">closeAndSummaryOrders</a></li><li><a href="global.html#genReport">genReport</a></li><li><a href="global.html#genReportCore">genReportCore</a></li><li><a href="global.html#runStrategies">runStrategies</a></li><li><a href="global.html#runStrategy">runStrategy</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">closeAndSummaryOrders.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import path from 'path'
|
|
49
|
+
import get from 'lodash-es/get.js'
|
|
50
|
+
import size from 'lodash-es/size.js'
|
|
51
|
+
import ispnum from 'wsemi/src/ispnum.mjs'
|
|
52
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
53
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
54
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
55
|
+
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
|
|
56
|
+
import WDataTdprovide from 'w-data-tdprovide/src/WDataTdprovide.mjs'
|
|
57
|
+
import writeJson from './writeJson.mjs'
|
|
58
|
+
import calcOrders from './calcOrders.mjs'
|
|
59
|
+
import calcSummary from './calcSummary.mjs'
|
|
60
|
+
import genReportCore from './genReportCore.mjs'
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 結算下單清單並輸出訂單、摘要與報告檔案
|
|
65
|
+
*
|
|
66
|
+
* 以w-data-tdprovide讀取fdOhlc與fdParam資料夾數據,依timeOhlcStart至timeOhlcEnd範圍取keyOhlc之K線序列
|
|
67
|
+
* 經calcOrders結算ordersSubmit與calcSummary統計後,輸出orders.json、summary.json、report.html至fdTest資料夾
|
|
68
|
+
* fdOhlc或fdParam非資料夾、uIni非正數、timeOhlcStart或timeOhlcEnd非有效字串或非4小時整數倍區間時reject
|
|
69
|
+
*
|
|
70
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
|
|
71
|
+
* @function
|
|
72
|
+
* @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件(可用src/ott.mjs或自行以dayjs包裝)
|
|
73
|
+
* @param {String} fdOhlc 輸入儲存K線(ohlc)序列資料夾字串,各序列以`${key}.json`儲存
|
|
74
|
+
* @param {String} fdParam 輸入儲存指標參數序列資料夾字串
|
|
75
|
+
* @param {Number} uIni 輸入初始資金正數
|
|
76
|
+
* @param {String} timeOhlcStart 輸入回測起始秒時間字串
|
|
77
|
+
* @param {String} timeOhlcEnd 輸入回測結束秒時間字串
|
|
78
|
+
* @param {String} keyOhlc 輸入K線序列key字串
|
|
79
|
+
* @param {Array} ordersSubmit 輸入下單陣列(格式同calcOrders之orders)
|
|
80
|
+
* @param {String} fdTest 輸入輸出結果資料夾字串
|
|
81
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
82
|
+
* @param {Boolean} [opt.writeOrders=true] 輸入是否輸出orders.json布林值,預設true
|
|
83
|
+
* @param {Boolean} [opt.writeSummary=true] 輸入是否輸出summary.json布林值,預設true
|
|
84
|
+
* @param {Boolean} [opt.writeReport=true] 輸入是否輸出report.html布林值,預設true
|
|
85
|
+
* @returns {Promise} 回傳Promise,無resolve值,於fdTest輸出結果檔案
|
|
86
|
+
* @example
|
|
87
|
+
*
|
|
88
|
+
* import fs from 'fs'
|
|
89
|
+
*
|
|
90
|
+
* let arrOhlc = [
|
|
91
|
+
* { time: '2020-01-01T00:00:00', Open: 100, High: 101, Low: 99, Close: 100 },
|
|
92
|
+
* { time: '2020-01-01T04:00:00', Open: 100, High: 106, Low: 100, Close: 105 },
|
|
93
|
+
* { time: '2020-01-01T08:00:00', Open: 105, High: 107, Low: 102, Close: 103 },
|
|
94
|
+
* { time: '2020-01-01T12:00:00', Open: 103, High: 104, Low: 94, Close: 95 },
|
|
95
|
+
* { time: '2020-01-01T16:00:00', Open: 95, High: 98, Low: 92, Close: 93 },
|
|
96
|
+
* { time: '2020-01-01T20:00:00', Open: 93, High: 99, Low: 95, Close: 97 },
|
|
97
|
+
* ]
|
|
98
|
+
*
|
|
99
|
+
* //建立數據資料夾, fdOhlc放K線序列, fdParam放指標參數序列
|
|
100
|
+
* fs.mkdirSync('./data-ohlc', { recursive: true })
|
|
101
|
+
* fs.mkdirSync('./data-param', { recursive: true })
|
|
102
|
+
* fs.writeFileSync('./data-ohlc/btc.json', JSON.stringify(arrOhlc), 'utf8')
|
|
103
|
+
*
|
|
104
|
+
* let ordersSubmit = [{
|
|
105
|
+
* mode: 'long',
|
|
106
|
+
* timeStart: '2020-01-01T00:00:00',
|
|
107
|
+
* priceStart: 100,
|
|
108
|
+
* uTrade: 100,
|
|
109
|
+
* priceTakeProfit: 105,
|
|
110
|
+
* priceStopLoss: 97,
|
|
111
|
+
* timeEnd: '',
|
|
112
|
+
* priceEnd: '',
|
|
113
|
+
* modeResult: '',
|
|
114
|
+
* uFee: 0.05,
|
|
115
|
+
* }]
|
|
116
|
+
*
|
|
117
|
+
* await closeAndSummaryOrders(ott, './data-ohlc', './data-param', 1000, '2020-01-01T00:00:00', '2020-01-01T20:00:00', 'btc', ordersSubmit, './result')
|
|
118
|
+
* console.log(fs.readdirSync('./result'))
|
|
119
|
+
* // => [ 'orders.json', 'report.html', 'summary.json' ]
|
|
120
|
+
*
|
|
121
|
+
*/
|
|
122
|
+
let closeAndSummaryOrders = async (ott, fdOhlc, fdParam, uIni, timeOhlcStart, timeOhlcEnd, keyOhlc, ordersSubmit, fdTest, opt = {}) => {
|
|
123
|
+
|
|
124
|
+
if (!fsIsFolder(fdOhlc)) {
|
|
125
|
+
throw new Error(`fdOhlc[${fdOhlc}] is not a forder`)
|
|
126
|
+
}
|
|
127
|
+
if (!fsIsFolder(fdParam)) {
|
|
128
|
+
throw new Error(`fdParam[${fdParam}] is not a forder`)
|
|
129
|
+
}
|
|
130
|
+
if (!ispnum(uIni)) {
|
|
131
|
+
throw new Error(`uIni[${uIni}] is not a positive number`)
|
|
132
|
+
}
|
|
133
|
+
if (!isestr(timeOhlcStart)) {
|
|
134
|
+
throw new Error(`invalid timeOhlcStart[${timeOhlcStart}]`)
|
|
135
|
+
}
|
|
136
|
+
if (!isestr(timeOhlcEnd)) {
|
|
137
|
+
throw new Error(`invalid timeOhlcEnd[${timeOhlcEnd}]`)
|
|
138
|
+
}
|
|
139
|
+
if (!isestr(keyOhlc)) {
|
|
140
|
+
throw new Error(`invalid keyOhlc[${keyOhlc}]`)
|
|
141
|
+
}
|
|
142
|
+
if (!isarr(ordersSubmit)) {
|
|
143
|
+
throw new Error(`invalid ordersSubmit[${ordersSubmit}]`)
|
|
144
|
+
}
|
|
145
|
+
if (!isestr(fdTest)) {
|
|
146
|
+
throw new Error(`invalid fdTest[${fdTest}]`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let writeOrders = get(opt, 'writeOrders', true)
|
|
150
|
+
let writeSummary = get(opt, 'writeSummary', true)
|
|
151
|
+
let writeReport = get(opt, 'writeReport', true)
|
|
152
|
+
|
|
153
|
+
let wdp = WDataTdprovide(fdOhlc, fdParam)
|
|
154
|
+
|
|
155
|
+
//td
|
|
156
|
+
let ts = ott(timeOhlcStart)
|
|
157
|
+
let te = ott(timeOhlcEnd)
|
|
158
|
+
let td = te.diff(ts, 'hour')
|
|
159
|
+
// console.log('td', td)
|
|
160
|
+
|
|
161
|
+
//m
|
|
162
|
+
let m = td / 4
|
|
163
|
+
if (m !== cint(m)) {
|
|
164
|
+
throw new Error(`timeOhlcStart[${timeOhlcStart}] to timeOhlcEnd[${timeOhlcEnd}] are not hour intervals`)
|
|
165
|
+
}
|
|
166
|
+
// console.log('m', m)
|
|
167
|
+
|
|
168
|
+
//funGetSeries
|
|
169
|
+
let funGetSeries = await wdp.buildGetTimeSeriesByTimeRange(timeOhlcStart, timeOhlcEnd)
|
|
170
|
+
|
|
171
|
+
//arrOhlc
|
|
172
|
+
let arrOhlc = await funGetSeries(keyOhlc)
|
|
173
|
+
// console.log('arrOhlc', arrOhlc, last(arrOhlc))
|
|
174
|
+
|
|
175
|
+
//ordersClose
|
|
176
|
+
let ordersClose = await calcOrders(arrOhlc, ordersSubmit, { uIni })
|
|
177
|
+
// console.log('ordersClose', ordersClose)
|
|
178
|
+
|
|
179
|
+
//summary
|
|
180
|
+
let summary = await calcSummary(ott, uIni, ordersClose, timeOhlcStart, timeOhlcEnd)
|
|
181
|
+
console.log('summary', summary, size(ordersClose))
|
|
182
|
+
|
|
183
|
+
//r
|
|
184
|
+
let r = {
|
|
185
|
+
name: `全部策略`,
|
|
186
|
+
orders: ordersClose,
|
|
187
|
+
summary,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (writeOrders) {
|
|
191
|
+
|
|
192
|
+
//fnOrders
|
|
193
|
+
let fnOrders = `orders.json`
|
|
194
|
+
|
|
195
|
+
//fpOrders
|
|
196
|
+
let fpOrders = path.resolve(fdTest, fnOrders)
|
|
197
|
+
|
|
198
|
+
//writeJson
|
|
199
|
+
writeJson(fpOrders, r.orders, { structured: true })
|
|
200
|
+
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (writeSummary) {
|
|
204
|
+
|
|
205
|
+
//fnSummary
|
|
206
|
+
let fnSummary = `summary.json`
|
|
207
|
+
|
|
208
|
+
//fpSummary
|
|
209
|
+
let fpSummary = path.resolve(fdTest, fnSummary)
|
|
210
|
+
|
|
211
|
+
//stsm
|
|
212
|
+
let stsm = {
|
|
213
|
+
name: r.name,
|
|
214
|
+
summary: r.summary,
|
|
215
|
+
}
|
|
216
|
+
// console.log('stsm', stsm)
|
|
217
|
+
|
|
218
|
+
//writeJson
|
|
219
|
+
writeJson(fpSummary, stsm, { structured: true })
|
|
220
|
+
|
|
221
|
+
console.log('summary', r.summary, size(r.orders))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (writeReport) {
|
|
225
|
+
|
|
226
|
+
//fnHtml
|
|
227
|
+
let fnHtml = `report.html`
|
|
228
|
+
|
|
229
|
+
//fpHtml
|
|
230
|
+
let fpHtml = path.resolve(fdTest, fnHtml)
|
|
231
|
+
|
|
232
|
+
//genReportCore
|
|
233
|
+
genReportCore(r, fpHtml)
|
|
234
|
+
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
export default closeAndSummaryOrders
|
|
241
|
+
</code></pre>
|
|
242
|
+
</article>
|
|
243
|
+
</section>
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<br class="clear">
|
|
253
|
+
|
|
254
|
+
<footer>
|
|
255
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 02 2026 20:09:43 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
256
|
+
</footer>
|
|
257
|
+
|
|
258
|
+
<script>prettyPrint();</script>
|
|
259
|
+
<script src="scripts/polyfill.js"></script>
|
|
260
|
+
<script src="scripts/linenumber.js"></script>
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
</body>
|
|
265
|
+
</html>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|