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.
Files changed (84) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +55 -0
  4. package/.jsdoc +25 -0
  5. package/README.md +167 -0
  6. package/SECURITY.md +5 -0
  7. package/babel.config.js +16 -0
  8. package/dist/renderOrdersTable.js +217 -0
  9. package/dist/renderOrdersTimeline.js +159 -0
  10. package/dist/renderProfitPlot.js +198 -0
  11. package/dist/renderSummary.js +194 -0
  12. package/dist/tmp.html +232 -0
  13. package/dist/w-trade-backtest.umd.js +32 -0
  14. package/dist/w-trade-backtest.umd.js.map +1 -0
  15. package/docs/calcOrders.mjs.html +278 -0
  16. package/docs/calcOrdersRatio.mjs.html +146 -0
  17. package/docs/calcOrdersSummary.mjs.html +506 -0
  18. package/docs/calcOrdersSummarySimple.mjs.html +207 -0
  19. package/docs/calcSummary.mjs.html +201 -0
  20. package/docs/closeAndSummaryOrders.mjs.html +265 -0
  21. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  23. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  24. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  25. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  26. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  27. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  28. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  34. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  35. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  36. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  37. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  38. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  39. package/docs/genReport.mjs.html +196 -0
  40. package/docs/genReportCore.mjs.html +128 -0
  41. package/docs/global.html +4231 -0
  42. package/docs/index.html +84 -0
  43. package/docs/runStrategies.mjs.html +244 -0
  44. package/docs/runStrategy.mjs.html +455 -0
  45. package/docs/scripts/collapse.js +39 -0
  46. package/docs/scripts/commonNav.js +28 -0
  47. package/docs/scripts/linenumber.js +25 -0
  48. package/docs/scripts/nav.js +12 -0
  49. package/docs/scripts/polyfill.js +4 -0
  50. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  51. package/docs/scripts/prettify/lang-css.js +2 -0
  52. package/docs/scripts/prettify/prettify.js +28 -0
  53. package/docs/scripts/search.js +99 -0
  54. package/docs/styles/jsdoc.css +776 -0
  55. package/docs/styles/prettify.css +80 -0
  56. package/g.mjs +146 -0
  57. package/package.json +30 -0
  58. package/script.txt +19 -0
  59. package/src/WTradeBacktest.mjs +27 -0
  60. package/src/calcOrders.mjs +206 -0
  61. package/src/calcOrdersRatio.mjs +74 -0
  62. package/src/calcOrdersSummary.mjs +434 -0
  63. package/src/calcOrdersSummarySimple.mjs +135 -0
  64. package/src/calcSummary.mjs +129 -0
  65. package/src/closeAndSummaryOrders.mjs +193 -0
  66. package/src/genReport.mjs +124 -0
  67. package/src/genReportCore.mjs +56 -0
  68. package/src/ott.mjs +28 -0
  69. package/src/readJson.mjs +26 -0
  70. package/src/renderOrdersTable.js +217 -0
  71. package/src/renderOrdersTimeline.js +159 -0
  72. package/src/renderProfitPlot.js +198 -0
  73. package/src/renderSummary.js +194 -0
  74. package/src/runStrategies.mjs +172 -0
  75. package/src/runStrategy.mjs +383 -0
  76. package/src/tmp.html +232 -0
  77. package/src/writeJson.mjs +31 -0
  78. package/src/writeTxt.mjs +19 -0
  79. package/test/unit-WTradeBacktest.test.mjs +541 -0
  80. package/toolg/addVersion.mjs +4 -0
  81. package/toolg/cleanFolder.mjs +5 -0
  82. package/toolg/gDistRollup.mjs +51 -0
  83. package/toolg/gDocsExams.mjs +48 -0
  84. 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 &amp;&amp; opt.skipSort)) {
122
+ ordersAll = ordersAll.slice().sort((a, b) => a.timeStart &lt; 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>