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,506 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>calcOrdersSummary.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">calcOrdersSummary.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import each from 'lodash-es/each.js'
49
+ import filter from 'lodash-es/filter.js'
50
+ import get from 'lodash-es/get.js'
51
+ import isNumber from 'lodash-es/isNumber.js'
52
+ import map from 'lodash-es/map.js'
53
+ import size from 'lodash-es/size.js'
54
+ import sortBy from 'lodash-es/sortBy.js'
55
+ import dig from 'wsemi/src/dig.mjs'
56
+ import haskey from 'wsemi/src/haskey.mjs'
57
+ import isestr from 'wsemi/src/isestr.mjs'
58
+ import ispnum from 'wsemi/src/ispnum.mjs'
59
+
60
+
61
+ /**
62
+ * 統計全部訂單之回測摘要
63
+ *
64
+ * 統計交易次數、勝率、最大回撤、最大持倉、夏普值、最終權益與各盈虧比例等欄位
65
+ * 訂單需先經calcOrders結算(含uEquity)與calcOrdersRatio計算(含dayHold與rProfitOrLossDay,夏普值計算所需)
66
+ * uIni非正數或timeOhlcStart、timeOhlcEnd非有效字串時throw
67
+ *
68
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
69
+ * @function
70
+ * @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件(可用src/ott.mjs或自行以dayjs包裝)
71
+ * @param {Number} uIni 輸入初始資金正數
72
+ * @param {Array} ordersAll 輸入已結算訂單陣列
73
+ * @param {String} timeOhlcStart 輸入回測起始秒時間字串
74
+ * @param {String} timeOhlcEnd 輸入回測結束秒時間字串
75
+ * @returns {Object} 回傳回測摘要物件
76
+ * @example
77
+ *
78
+ * let arrOhlc = [
79
+ * { time: '2020-01-01T00:00:00', Open: 100, High: 101, Low: 99, Close: 100 },
80
+ * { time: '2020-01-01T04:00:00', Open: 100, High: 106, Low: 100, Close: 105 },
81
+ * { time: '2020-01-01T08:00:00', Open: 105, High: 107, Low: 102, Close: 103 },
82
+ * { time: '2020-01-01T12:00:00', Open: 103, High: 104, Low: 94, Close: 95 },
83
+ * { time: '2020-01-01T16:00:00', Open: 95, High: 98, Low: 92, Close: 93 },
84
+ * { time: '2020-01-01T20:00:00', Open: 93, High: 99, Low: 95, Close: 97 },
85
+ * ]
86
+ *
87
+ * //ordersSubmit, 4張下單: 前3張分別於後續K棒觸發止盈或止損, 第4張未觸發(未平倉)
88
+ * let kpDef = { uTrade: 100, timeEnd: '', priceEnd: '', modeResult: '', uFee: 0.05 }
89
+ * let ordersSubmit = [
90
+ * { mode: 'long', timeStart: '2020-01-01T00:00:00', priceStart: 100, priceTakeProfit: 105, priceStopLoss: 97, ...kpDef },
91
+ * { mode: 'long', timeStart: '2020-01-01T04:00:00', priceStart: 105, priceTakeProfit: 110.25, priceStopLoss: 101.85, ...kpDef },
92
+ * { mode: 'short', timeStart: '2020-01-01T08:00:00', priceStart: 103, priceTakeProfit: 97.85, priceStopLoss: 106.09, ...kpDef },
93
+ * { mode: 'long', timeStart: '2020-01-01T16:00:00', priceStart: 93, priceTakeProfit: 105, priceStopLoss: 85, ...kpDef },
94
+ * ]
95
+ *
96
+ * let orders = await calcOrders(arrOhlc, ordersSubmit, { uIni: 1000 })
97
+ * orders = calcOrdersRatio(ott, orders)
98
+ * let summary = calcOrdersSummary(ott, 1000, orders, '2020-01-01T00:00:00', '2020-01-01T20:00:00')
99
+ * console.log(summary)
100
+ * // => {
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 calcOrdersSummary = (ott, uIni, ordersAll, timeOhlcStart, timeOhlcEnd) => {
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
+ ordersAll = sortBy(ordersAll, 'timeStart')
137
+
138
+ let ordersFin = []
139
+ let ordersUnsettled = []
140
+ each(ordersAll, (o) => {
141
+ if (isestr(o.modeResult)) {
142
+ ordersFin.push(o)
143
+ }
144
+ else {
145
+ ordersUnsettled.push(o)
146
+ }
147
+ })
148
+
149
+ let numTrade = size(ordersAll) //總交易次數
150
+ let numTradeFin = size(ordersFin) //總已完成交易次數
151
+ let numTradeUnsettled = size(ordersUnsettled) //總未完成交易次數
152
+ let rWin = dig(size(filter(ordersAll, { modeResult: 'profit' })) / numTradeFin * 100, 2) + '%' //單位為%
153
+ let uDrawdownMax = 0 //最大回撤金額(USDT)
154
+ let rDrawdownMax = '' //最大回撤比例(%)
155
+ let rEquivalentDrawdownMax = '' //等效最大回撤比例(%)
156
+ let uEquityFinal = 0 //最終權益(USDT)
157
+ let uCumuProfitOrLossFinal = '' //最終盈虧(USDT)
158
+ let rCumuProfitOrLossFinal = '' //最終盈虧比例(%)
159
+ let rEquivalentCumuProfitOrLossFinal = '' //最終等效盈虧比例(%)
160
+ let uTradeAllMax = 0 //最大持倉金額(USDT)
161
+ let rTradeAllMax = '' //最大持倉比例(%)
162
+ let numTradeAllMax = 0 //最大持倉筆數(同時持有的部位數)
163
+ let rSharpe = 0 //夏普值
164
+ let uMax = uIni //最大盈虧(USDT)
165
+ let uMaxMax = uIni //最大回撤時之最大盈虧(USDT)
166
+
167
+ //kpTrade(持倉金額變化), kpCount(持倉筆數變化)
168
+ let kpTrade = {}
169
+ let kpCount = {}
170
+ each(ordersAll, (o) => {
171
+
172
+ //須已平倉單才能計算最大回撤
173
+ if (isestr(o.modeResult)) {
174
+
175
+ //uMax
176
+ uMax = Math.max(uMax, o.uEquity)
177
+
178
+ //uDrawdownMax, rDrawdownMax
179
+ let uDrawdown = uMax - o.uEquity
180
+ let rDrawdown = dig(uDrawdown / uMax * 100, 2) + '%' //單位為%
181
+ if (uDrawdown > uDrawdownMax) {
182
+ uDrawdownMax = uDrawdown
183
+ uMaxMax = uMax
184
+ rDrawdownMax = rDrawdown
185
+ }
186
+
187
+ }
188
+
189
+ //已或未平倉皆可計算最大持倉
190
+ if (true) {
191
+
192
+ //dir, 持倉方向
193
+ let dir = null
194
+ // dir = o.mode === 'long' ? 1 : -1 //long為正而short為負, 可抵銷
195
+ dir = 1 //long與short皆為1(皆會增加持倉)
196
+ // console.log('o.mode', o.mode, dir)
197
+
198
+ //下單就增加持倉
199
+ if (isestr(o.timeStart)) {
200
+ if (!haskey(kpTrade, o.timeStart)) {
201
+ kpTrade[o.timeStart] = 0
202
+ }
203
+ kpTrade[o.timeStart] += dir * o.uTrade
204
+ // console.log('o.timeStart', kpTrade[o.timeStart])
205
+ if (!haskey(kpCount, o.timeStart)) {
206
+ kpCount[o.timeStart] = 0
207
+ }
208
+ kpCount[o.timeStart] += 1 //開倉筆數+1
209
+ }
210
+
211
+ //結單就減少持倉
212
+ if (isestr(o.timeEnd)) {
213
+ if (!haskey(kpTrade, o.timeEnd)) {
214
+ kpTrade[o.timeEnd] = 0
215
+ }
216
+ kpTrade[o.timeEnd] -= dir * o.uTrade
217
+ // console.log('o.timeEnd', kpTrade[o.timeEnd])
218
+ if (!haskey(kpCount, o.timeEnd)) {
219
+ kpCount[o.timeEnd] = 0
220
+ }
221
+ kpCount[o.timeEnd] -= 1 //平倉筆數-1
222
+ }
223
+
224
+ }
225
+
226
+ })
227
+ // console.log('kpTrade(未累計)', kpTrade)
228
+
229
+ //計算累計持倉
230
+ if (true) {
231
+
232
+ //trades, 轉陣列與排序
233
+ let trades = []
234
+ each(kpTrade, (uTradeAll, time) => {
235
+ if (!isNumber(uTradeAll)) {
236
+ console.log('time', time)
237
+ throw new Error(`invalid uTradeAll[${uTradeAll}]`)
238
+ }
239
+ trades.push({ time, uTradeAll })
240
+ })
241
+ trades = sortBy(trades, 'time')
242
+ // console.log('trades', trades)
243
+
244
+ //紀錄累計值
245
+ let uAccm = 0
246
+ let _trades = map(trades, (item) => {
247
+ uAccm += item.uTradeAll
248
+ return {
249
+ ...item,
250
+ uAccm,
251
+ }
252
+ })
253
+ // console.log('_trades', _trades)
254
+
255
+ //_kpTrade
256
+ let _kpTrade = {}
257
+ each(_trades, (v) => {
258
+ _kpTrade[v.time] = v.uAccm
259
+ })
260
+
261
+ //update
262
+ kpTrade = _kpTrade
263
+
264
+ }
265
+ // console.log('kpTrade(累計)', kpTrade)
266
+
267
+ //計算累計持倉筆數(與kpTrade累計同款)
268
+ if (true) {
269
+
270
+ //counts, 轉陣列與排序
271
+ let counts = []
272
+ each(kpCount, (numTradeAll, time) => {
273
+ if (!isNumber(numTradeAll)) {
274
+ console.log('time', time)
275
+ throw new Error(`invalid numTradeAll[${numTradeAll}]`)
276
+ }
277
+ counts.push({ time, numTradeAll })
278
+ })
279
+ counts = sortBy(counts, 'time')
280
+
281
+ //紀錄累計值
282
+ let nAccm = 0
283
+ let _counts = map(counts, (item) => {
284
+ nAccm += item.numTradeAll
285
+ return {
286
+ ...item,
287
+ nAccm,
288
+ }
289
+ })
290
+
291
+ //_kpCount
292
+ let _kpCount = {}
293
+ each(_counts, (v) => {
294
+ _kpCount[v.time] = v.nAccm
295
+ })
296
+
297
+ //update
298
+ kpCount = _kpCount
299
+
300
+ }
301
+ // console.log('kpCount(累計)', kpCount)
302
+
303
+ //計算當前持倉金額與最大當前持倉金額
304
+ ordersAll = map(ordersAll, (o) => {
305
+
306
+ //當前持倉金額(USDT), 以下單時間計算
307
+ let uTradeAll = get(kpTrade, o.timeStart, null)
308
+ if (!isNumber(uTradeAll)) {
309
+ throw new Error(`invalid uTradeAll`)
310
+ }
311
+ uTradeAll = Math.abs(uTradeAll) //做多或空皆會持有倉位, 負倉位(當前持倉金額)代表多空可抵銷, 故此處一律取絕對值顯示
312
+ o.uTradeAll = uTradeAll
313
+
314
+ //當前持倉金額佔比(%), 以下單時間計算
315
+ let rTradeAll = dig(o.uTradeAll / uIni * 100, 2) + '%' //單位為%
316
+ o.rTradeAll = rTradeAll
317
+
318
+ //uTradeAllMax, rTradeAllMax
319
+ if (uTradeAllMax &lt; o.uTradeAll) {
320
+ uTradeAllMax = o.uTradeAll
321
+ rTradeAllMax = o.rTradeAll
322
+ }
323
+
324
+ //當前持倉筆數(同時持有的部位數), 以下單時間計算
325
+ let numTradeAll = get(kpCount, o.timeStart, null)
326
+ if (!isNumber(numTradeAll)) {
327
+ throw new Error(`invalid numTradeAll`)
328
+ }
329
+ o.numTradeAll = numTradeAll
330
+
331
+ //numTradeAllMax
332
+ if (numTradeAllMax &lt; o.numTradeAll) {
333
+ numTradeAllMax = o.numTradeAll
334
+ }
335
+
336
+ return o
337
+ })
338
+ // console.log('ordersAll', ordersAll[size(ordersAll) - 1], size(ordersAll))
339
+
340
+ //uEquityFinal, uCumuProfitOrLossFinal, rCumuProfitOrLossFinal
341
+ if (true) {
342
+ let ind = size(ordersFin) - 1
343
+ uEquityFinal = get(ordersFin, `${ind}.uEquity`, '') //最終權益(USDT)
344
+ uCumuProfitOrLossFinal = get(ordersFin, `${ind}.uCumuProfitOrLoss`, '') //最終盈虧(USDT)
345
+ rCumuProfitOrLossFinal = get(ordersFin, `${ind}.rCumuProfitOrLoss`, '') //最終盈虧比例(%)
346
+ }
347
+
348
+ //rEquivalentDrawdown, 使用最大回撤時之最大盈虧, 扣掉初始資金, 再基於全部交易後才能得知的最大持倉, 換算出等效最大回撤比例
349
+ if (true) {
350
+ let v
351
+ if ((uMaxMax - uIni + uTradeAllMax) > 0) {
352
+ v = uDrawdownMax / (uMaxMax - uIni + uTradeAllMax)
353
+ }
354
+ else {
355
+ v = 0
356
+ }
357
+ rEquivalentDrawdownMax = dig(v * 100, 2) + '%' //單位為%
358
+ // console.log('uDrawdownMax', uDrawdownMax)
359
+ // console.log('uMaxMax', uMaxMax)
360
+ // console.log('uMaxMax - uIni', uMaxMax - uIni)
361
+ // console.log('uMaxMax - uIni + uTradeAllMax', uMaxMax - uIni + uTradeAllMax)
362
+ }
363
+
364
+ //rEquivalentCumuProfitOrLossFinal
365
+ if (uTradeAllMax > 0 &amp;&amp; isNumber(uCumuProfitOrLossFinal)) {
366
+ rEquivalentCumuProfitOrLossFinal = uCumuProfitOrLossFinal / uTradeAllMax
367
+ }
368
+ else {
369
+ rEquivalentCumuProfitOrLossFinal = 0
370
+ }
371
+ rEquivalentCumuProfitOrLossFinal = dig((rEquivalentCumuProfitOrLossFinal) * 100, 2) + '%' //單位為%
372
+
373
+ //rSharpe
374
+ if (true) {
375
+
376
+ let rAvgProfitOrLossDay = 0
377
+ let rStdProfitOrLossDay = 0
378
+
379
+ //rAvgProfitOrLossDay, 加權平均日盈虧比例(加權日均報酬), 把所有交易攤平成連續的日報酬
380
+ let rrW = 0
381
+ let rrD = 0
382
+ each(ordersFin, (o) => {
383
+ let rW = o.rProfitOrLossDay * o.dayHold
384
+ rrW += rW
385
+ let rD = o.dayHold
386
+ rrD += rD
387
+ })
388
+ if (rrD > 0) {
389
+ rAvgProfitOrLossDay = rrW / rrD
390
+ }
391
+
392
+ //rStdProfitOrLossDay, 加權日盈虧比例標準差(加權日標準差)
393
+ let rtW = 0
394
+ each(ordersFin, (o) => {
395
+ let diff = o.rProfitOrLossDay - rAvgProfitOrLossDay
396
+ let rW = o.dayHold * (diff * diff)
397
+ rtW += rW
398
+ })
399
+ if (rrD > 1) {
400
+ rStdProfitOrLossDay = Math.sqrt(rtW / (rrD - 1))
401
+ }
402
+
403
+ //rSharpe, 夏普值
404
+ if (rStdProfitOrLossDay > 0) {
405
+ rSharpe = (rAvgProfitOrLossDay / rStdProfitOrLossDay) * Math.sqrt(252)
406
+ }
407
+ if (rSharpe &lt; -10) {
408
+ rSharpe = -10
409
+ }
410
+ if (rSharpe > 10) {
411
+ rSharpe = 10
412
+ }
413
+
414
+ }
415
+ // console.log('rSharpe', rSharpe)
416
+
417
+ let tStart = ott(timeOhlcStart)
418
+ let tEnd = ott(timeOhlcEnd)
419
+
420
+ //回測時長日
421
+ let btDays = tEnd.diff(tStart, 'day')
422
+
423
+ //回測時長年
424
+ let btYears = btDays / 365
425
+
426
+ //年化報酬率
427
+ let rCumuProfitOrLossFinalNormYear = ''
428
+ if (true) {
429
+ let v
430
+ if (uIni === 0 || btYears === 0) {
431
+ v = 0
432
+ }
433
+ else {
434
+ v = (uEquityFinal - uIni) / uIni / btYears
435
+ }
436
+ rCumuProfitOrLossFinalNormYear = `${dig(v * 100, 2)}%`
437
+ }
438
+ // let rCumuProfitOrLossFinalNormYear = `${dig((uEquityFinal - uIni) / uIni / btYears * 100, 2)}%`
439
+
440
+ //等效年化報酬率
441
+ let rEquivalentCumuProfitOrLossFinalNormYear = ''
442
+ if (true) {
443
+ let v
444
+ if (uTradeAllMax === 0 || btYears === 0) {
445
+ v = 0
446
+ }
447
+ else {
448
+ v = (uEquityFinal - uIni) / uTradeAllMax / btYears
449
+ }
450
+ rEquivalentCumuProfitOrLossFinalNormYear = `${dig(v * 100, 2)}%`
451
+ }
452
+ // let rEquivalentCumuProfitOrLossFinalNormYear = `${dig((uEquityFinal - uIni) / uTradeAllMax / btYears * 100, 2)}%`
453
+
454
+ //summary
455
+ let summary = {
456
+ btDays,
457
+ btYears: dig(btYears, 1),
458
+ numTrade,
459
+ numTradeFin,
460
+ numTradeUnsettled,
461
+ uTradeAllMax,
462
+ rTradeAllMax,
463
+ numTradeAllMax,
464
+ uDrawdownMax,
465
+ rDrawdownMax,
466
+ rEquivalentDrawdownMax,
467
+ rSharpe,
468
+ rWin,
469
+ uEquityFinal,
470
+ uCumuProfitOrLossFinal,
471
+ rCumuProfitOrLossFinal,
472
+ rCumuProfitOrLossFinalNormYear,
473
+ rEquivalentCumuProfitOrLossFinal,
474
+ rEquivalentCumuProfitOrLossFinalNormYear,
475
+ }
476
+
477
+ return summary
478
+ }
479
+
480
+
481
+ export default calcOrdersSummary
482
+ </code></pre>
483
+ </article>
484
+ </section>
485
+
486
+
487
+
488
+
489
+
490
+
491
+ </div>
492
+
493
+ <br class="clear">
494
+
495
+ <footer>
496
+ 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.
497
+ </footer>
498
+
499
+ <script>prettyPrint();</script>
500
+ <script src="scripts/polyfill.js"></script>
501
+ <script src="scripts/linenumber.js"></script>
502
+
503
+
504
+
505
+ </body>
506
+ </html>