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,455 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>runStrategy.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">runStrategy.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 first from 'lodash-es/first.js'
50
+ import get from 'lodash-es/get.js'
51
+ import last from 'lodash-es/last.js'
52
+ import size from 'lodash-es/size.js'
53
+ import arrSort from 'wsemi/src/arrSort.mjs'
54
+ import haskey from 'wsemi/src/haskey.mjs'
55
+ import isearr from 'wsemi/src/isearr.mjs'
56
+ import iseobj from 'wsemi/src/iseobj.mjs'
57
+ import isestr from 'wsemi/src/isestr.mjs'
58
+ import isfun from 'wsemi/src/isfun.mjs'
59
+ import pmSeries from 'wsemi/src/pmSeries.mjs'
60
+ import calcOrders from './calcOrders.mjs'
61
+ import calcOrdersRatio from './calcOrdersRatio.mjs'
62
+ import calcOrdersSummary from './calcOrdersSummary.mjs'
63
+
64
+
65
+ /**
66
+ * 執行單一策略回測
67
+ *
68
+ * 於各時間點以conds判斷觸發下單(各條件以sym與th比較該key序列之param值,opr為'and'須全真、'or'至少一真)
69
+ * 觸發時以該K棒Close下單,並依settings計算止盈止損價格,再經calcOrders結算與calcOrdersRatio、calcOrdersSummary統計
70
+ * 僅於全部conds序列皆有值之共同時間點判斷觸發
71
+ * strategy.mode非'long'或'short'、keyOhlc或conds無效、funGetSeries非函數時reject
72
+ *
73
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
74
+ * @function
75
+ * @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件(可用src/ott.mjs或自行以dayjs包裝)
76
+ * @param {Object} strategy 輸入策略物件,需含mode('long'或'short')、keyOhlc(K線序列key字串)、conds(條件陣列,各元素為{key,sym,th,opr})、settings({uIni,uTrade,rTakeProfit,rStopLoss,rFee})欄位
77
+ * @param {Function} funGetSeries 輸入序列查詢async函數,依key回傳時間序列陣列
78
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
79
+ * @param {Boolean} [opt.withCalcOrderProfitOrLoss=true] 輸入是否經calcOrders結算訂單布林值,預設true
80
+ * @param {Boolean} [opt.withSummary=true] 輸入是否計算統計摘要布林值,預設true
81
+ * @returns {Promise} 回傳Promise,resolve為{orders,summary}物件
82
+ * @example
83
+ *
84
+ * let arrOhlc = [
85
+ * { time: '2020-01-01T00:00:00', Open: 100, High: 101, Low: 99, Close: 100 },
86
+ * { time: '2020-01-01T04:00:00', Open: 100, High: 106, Low: 100, Close: 105 },
87
+ * { time: '2020-01-01T08:00:00', Open: 105, High: 107, Low: 102, Close: 103 },
88
+ * { time: '2020-01-01T12:00:00', Open: 103, High: 104, Low: 94, Close: 95 },
89
+ * { time: '2020-01-01T16:00:00', Open: 95, High: 98, Low: 92, Close: 93 },
90
+ * { time: '2020-01-01T20:00:00', Open: 93, High: 99, Low: 95, Close: 97 },
91
+ * ]
92
+ *
93
+ * let arrSig = [
94
+ * { time: '2020-01-01T00:00:00', param: 1 },
95
+ * { time: '2020-01-01T04:00:00', param: 0 },
96
+ * { time: '2020-01-01T08:00:00', param: 1 },
97
+ * { time: '2020-01-01T12:00:00', param: 0 },
98
+ * { time: '2020-01-01T16:00:00', param: 0 },
99
+ * { time: '2020-01-01T20:00:00', param: 0 },
100
+ * ]
101
+ *
102
+ * let funGetSeries = async (key) => {
103
+ * if (key === 'btc') {
104
+ * return arrOhlc
105
+ * }
106
+ * if (key === 'sig') {
107
+ * return arrSig
108
+ * }
109
+ * throw new Error(`invalid key[${key}]`)
110
+ * }
111
+ *
112
+ * let strategy = {
113
+ * mode: 'long',
114
+ * keyOhlc: 'btc',
115
+ * conds: [{ key: 'sig', sym: '>', th: 0.5, opr: 'and' }],
116
+ * settings: { uIni: 1000, uTrade: 100, rTakeProfit: 0.05, rStopLoss: 0.03, rFee: 0.0005 },
117
+ * }
118
+ *
119
+ * let r = await runStrategy(ott, strategy, funGetSeries)
120
+ * console.log(r.orders.map((o) => `${o.timeStart} ${o.mode} ${o.priceStart}->${o.priceEnd} ${o.modeResult}`))
121
+ * // => [
122
+ * // '2020-01-01T00:00:00 long 100->105 profit',
123
+ * // '2020-01-01T08:00:00 long 103->99.91 loss'
124
+ * // ]
125
+ * console.log(r.summary.numTrade, r.summary.rWin, r.summary.uEquityFinal)
126
+ * // => 2 50.00% 1001.8
127
+ *
128
+ */
129
+ let runStrategy = async(ott, strategy, funGetSeries, opt = {}) => {
130
+
131
+ //mode
132
+ let mode = get(strategy, 'mode', '')
133
+ if (mode !== 'long' &amp;&amp; mode !== 'short') {
134
+ throw new Error(`invalid strategy.mode[${mode}] not 'long' or 'short'`)
135
+ }
136
+
137
+ //keyOhlc
138
+ let keyOhlc = get(strategy, 'keyOhlc', '')
139
+ if (!isestr(keyOhlc)) {
140
+ throw new Error(`invalid strategy.keyOhlc`)
141
+ }
142
+
143
+ //conds
144
+ let conds = get(strategy, 'conds', [])
145
+ if (!isearr(conds)) {
146
+ throw new Error(`invalid strategy.conds`)
147
+ }
148
+
149
+ //nps
150
+ let nps = size(conds)
151
+ // console.log('nps', nps)
152
+
153
+ //st
154
+ let st = get(strategy, 'settings', {})
155
+ if (!iseobj(st)) {
156
+ st = {
157
+ uIni: 1000, //初始資金(USDT)
158
+ uTrade: 1, //每次下單金額(USDT)
159
+ rTakeProfit: 0.05, //止盈
160
+ rStopLoss: 0.03, //止損
161
+ rFee: 0.0005, //手續費
162
+ }
163
+ }
164
+ // console.log('st', st)
165
+
166
+ //check
167
+ if (!isfun(funGetSeries)) {
168
+ throw new Error(`funGetSeries is not a function`)
169
+ }
170
+
171
+ //withCalcOrderProfitOrLoss
172
+ let withCalcOrderProfitOrLoss = get(opt, 'withCalcOrderProfitOrLoss', true)
173
+
174
+ //withSummary
175
+ let withSummary = get(opt, 'withSummary', true)
176
+
177
+ //arrOhlc
178
+ let arrOhlc = await funGetSeries(keyOhlc)
179
+ // console.log('arrOhlc', arrOhlc, last(arrOhlc))
180
+
181
+ //因conds需要各時間點都有值才能做交集判斷, 所以須提取各conds所需數據且皆為同起訖時間之長度
182
+ let kpTimeNum = {} //儲存各數據之全部數據時間, 若出現相同數據時間就+1, 若該時間內累計數字等同於數據種數, 就代表該時間為全部數據皆有之時間
183
+ let kpArrParams = {} //儲存各數據
184
+ await pmSeries(conds, async (p) => {
185
+
186
+ //arr
187
+ let arr = await funGetSeries(p.key)
188
+ // console.log('arr', arr)
189
+
190
+ //default
191
+ if (!haskey(kpArrParams, p.key)) {
192
+ kpArrParams[p.key] = {}
193
+ }
194
+
195
+ //save
196
+ each(arr, (v) => {
197
+ //v.time
198
+ //v.param
199
+
200
+ //save param
201
+ kpArrParams[p.key][v.time] = v.param
202
+
203
+ //default
204
+ if (!haskey(kpTimeNum, v.time)) {
205
+ kpTimeNum[v.time] = 0
206
+ }
207
+
208
+ //add 1
209
+ kpTimeNum[v.time] += 1
210
+
211
+ })
212
+ // console.log('kpArrParams', kpArrParams)
213
+ // console.log('kpTimeNum', kpTimeNum)
214
+
215
+ })
216
+
217
+ //timeEff, 各數據皆有共同之數據時間, 為有效時間陣列
218
+ let timeEff = []
219
+ if (true) {
220
+ each(kpTimeNum, (n, time) => {
221
+ if (n === nps) {
222
+ timeEff.push(time)
223
+ }
224
+ })
225
+ timeEff = arrSort(timeEff)
226
+ // console.log('timeEff', timeEff)
227
+ }
228
+
229
+ //kpTimeEff, 轉為字典物件用於後續判斷
230
+ let kpTimeEff = {}
231
+ if (true) {
232
+ each(timeEff, (time) => {
233
+ kpTimeEff[time] = true
234
+ })
235
+ }
236
+
237
+ //kpParams, 儲存各有效時間之各數據
238
+ let kpParams = {}
239
+ if (true) {
240
+ each(timeEff, (time) => {
241
+ kpParams[time] = {}
242
+ each(conds, (p) => {
243
+ kpParams[time][p.key] = kpArrParams[p.key][time]
244
+ })
245
+ })
246
+ // console.log('kpParams', kpParams)
247
+ }
248
+
249
+ //timeOhlcStart, timeOhlcEnd
250
+ let timeOhlcStart = get(first(arrOhlc), 'time', '')
251
+ let timeOhlcEnd = get(last(arrOhlc), 'time', '')
252
+ // console.log('timeOhlcStart', timeOhlcStart)
253
+ // console.log('timeOhlcEnd', timeOhlcEnd)
254
+
255
+ //orders
256
+ let orders = []
257
+ await pmSeries(arrOhlc, async(v) => {
258
+ // console.log('v', v)
259
+
260
+ //time
261
+ let time = get(v, 'time')
262
+ // console.log('time', time)
263
+
264
+ //check
265
+ if (!haskey(kpTimeEff, time)) {
266
+ return true //跳出換下一個
267
+ }
268
+ // console.log('time', time)
269
+
270
+ //bTrigger
271
+ let bTrigger = true
272
+ if (true) {
273
+
274
+ //bs
275
+ let bs = []
276
+ each(conds, (cond, kcond) => {
277
+
278
+ //y, 儲存值
279
+ let y = kpParams[time][cond.key]
280
+ // console.log(time, cond.key, 'y', y)
281
+
282
+ //sym, th, opr
283
+ let sym = get(cond, 'sym', '>')
284
+ let th = get(cond, 'th', 0)
285
+ let opr = get(cond, 'opr', null)
286
+ if (opr !== 'and' &amp;&amp; opr !== 'or') {
287
+ opr = 'and'
288
+ }
289
+ // console.log(time, cond.key, 'sym', sym)
290
+ // console.log(time, cond.key, 'th', th)
291
+ // console.log(time, cond.key, 'opr', opr)
292
+
293
+ //b
294
+ let b = false
295
+ if (sym === '>') {
296
+ b = y > th
297
+ }
298
+ else {
299
+ b = y &lt; th
300
+ }
301
+
302
+ //push
303
+ bs.push({
304
+ opr,
305
+ b,
306
+ })
307
+
308
+ })
309
+
310
+ //依各 cond 的 opr 分組合成 bTrigger:
311
+ // 'and' 組 → 全部須為 true(必要條件); 'or' 組 → 至少一個 true(選擇條件, 無 or 組則不約束)
312
+ // bTrigger = (所有 and 條件皆真) AND (任一 or 條件為真 或 根本沒有 or 條件)
313
+ let okAnd = bs.filter((v) => v.opr === 'and').every((v) => v.b) //空 and 組 → every 回 true(無約束)
314
+ let bsOr = bs.filter((v) => v.opr === 'or')
315
+ let okOr = bsOr.length === 0 || bsOr.some((v) => v.b) //沒有 or 條件 → 不約束; 有則至少一個須真
316
+ bTrigger = okAnd &amp;&amp; okOr
317
+
318
+ }
319
+
320
+ let bLong = false
321
+ let bShort = false
322
+ if (bTrigger) {
323
+ if (mode === 'long') {
324
+ bLong = true
325
+ }
326
+ else { //'short'
327
+ bShort = true
328
+ }
329
+ }
330
+
331
+ if (bLong) { //做多
332
+ // console.log('long...', time, v.Close)
333
+ let o = {
334
+ mode: 'long',
335
+ timeStart: time, //下單買入時所屬k棒時間
336
+ priceStart: v.Close, //單買入時Close價格
337
+ uTrade: st.uTrade, //下單金額(USDT)
338
+ rTakeProfit: st.rTakeProfit, //下單設定止盈比例
339
+ priceTakeProfit: (1 + st.rTakeProfit) * v.Close, //下單設定止盈價格(USDT)
340
+ rStopLoss: st.rStopLoss, //下單設定止損比例
341
+ priceStopLoss: (1 - st.rStopLoss) * v.Close, //下單設定止損價格(USDT)
342
+ timeEnd: '', //賣出時所屬k棒時間, 待最後結算更新
343
+ priceEnd: '', //賣出時Close價格, 待最後結算更新
344
+ modeResult: '', //交易結果, 止盈(takeProfit)或止損(stopLoss), 待最後結算更新
345
+ rFee: st.rFee, //手續費比例
346
+ uFee: st.rFee * st.uTrade, //交易手續費(USDT)
347
+ uProfitOrLoss: '', //盈虧金額(USDT), 含手續費, 待最後結算更新
348
+ rProfitOrLoss: '', //盈虧比例, 待最後結算更新
349
+ uCumuProfitOrLoss: '', //累計盈虧金額(USDT), 含手續費, 待最後結算更新
350
+ rCumuProfitOrLoss: '', //累計盈虧比例(%), 待最後結算更新
351
+ uEquity: '', //當前權益金額(USDT), 待最後結算更新
352
+ uTradeAll: '', //當前持倉金額(USDT), 待最後結算更新
353
+ rTradeAll: '', //當前持倉金額佔比(%), 待最後結算更新
354
+ }
355
+ orders.push(o)
356
+ }
357
+ else if (bShort) { //做空
358
+ // console.log('short...', time, v.Close)
359
+ let o = {
360
+ mode: 'short',
361
+ timeStart: time, //賣出時所屬k棒時間
362
+ priceStart: v.Close, //賣出時Close價格
363
+ uTrade: st.uTrade, //下單金額(USDT)
364
+ rTakeProfit: st.rTakeProfit, //止盈
365
+ priceTakeProfit: (1 - st.rTakeProfit) * v.Close, //止盈價格(USDT)
366
+ rStopLoss: st.rStopLoss, //止損
367
+ priceStopLoss: (1 + st.rStopLoss) * v.Close, //止損價格(USDT)
368
+ timeEnd: '', //買入時所屬k棒時間, 待最後結算更新
369
+ priceEnd: '', //買入時Close價格, 待最後結算更新
370
+ modeResult: '', //交易結果, 止盈(takeProfit)或止損(stopLoss), 待最後結算更新
371
+ rFee: st.rFee, //手續費比例
372
+ uFee: st.rFee * st.uTrade, //交易手續費(USDT)
373
+ uProfitOrLoss: '', //盈虧金額(USDT), 含手續費, 待最後結算更新
374
+ rProfitOrLoss: '', //盈虧比例, 待最後結算更新
375
+ uCumuProfitOrLoss: '', //累計盈虧金額(USDT), 含手續費, 待最後結算更新
376
+ rCumuProfitOrLoss: '', //累計盈虧比例(%), 待最後結算更新
377
+ uEquity: '', //當前權益金額(USDT), 待最後結算更新
378
+ uTradeAll: '', //當前持倉金額(USDT), 待最後結算更新
379
+ rTradeAll: '', //當前持倉金額佔比(%), 待最後結算更新
380
+ }
381
+ orders.push(o)
382
+ }
383
+
384
+ })
385
+ // console.log('orders', orders)
386
+ // console.log('size(orders)', size(orders))
387
+
388
+ //withCalcOrderProfitOrLoss
389
+ if (withCalcOrderProfitOrLoss) {
390
+ orders = await calcOrders(arrOhlc, orders, { uIni: st.uIni })
391
+ // console.log('orders', orders)
392
+ }
393
+
394
+ //summary
395
+ let summary = {
396
+ timeTest: ott().format('YYYY-MM-DDTHH:mm:ssZ'),
397
+ timeOhlcStart,
398
+ timeOhlcEnd,
399
+ uIni: get(st, 'uIni', ''), //額外提取uIni儲存至summary, 其他參數須依照單策略或多策略自行再添加
400
+ }
401
+
402
+ //withSummary
403
+ if (withSummary) {
404
+
405
+ //計算各單持倉天數, 等效日盈虧比例
406
+ orders = calcOrdersRatio(ott, orders)
407
+
408
+ //評分計算
409
+ let _summary = calcOrdersSummary(ott, st.uIni, orders, timeOhlcStart, timeOhlcEnd)
410
+
411
+ //merge
412
+ summary = {
413
+ ...summary,
414
+ ..._summary,
415
+ }
416
+
417
+ }
418
+
419
+ //r
420
+ let r = {
421
+ orders,
422
+ summary,
423
+ }
424
+ // console.log('r', r)
425
+
426
+ return r
427
+ }
428
+
429
+
430
+ export default runStrategy
431
+ </code></pre>
432
+ </article>
433
+ </section>
434
+
435
+
436
+
437
+
438
+
439
+
440
+ </div>
441
+
442
+ <br class="clear">
443
+
444
+ <footer>
445
+ 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.
446
+ </footer>
447
+
448
+ <script>prettyPrint();</script>
449
+ <script src="scripts/polyfill.js"></script>
450
+ <script src="scripts/linenumber.js"></script>
451
+
452
+
453
+
454
+ </body>
455
+ </html>
@@ -0,0 +1,39 @@
1
+ function hideAllButCurrent(){
2
+ //by default all submenut items are hidden
3
+ //but we need to rehide them for search
4
+ document.querySelectorAll("nav > ul").forEach(function(parent) {
5
+ if (parent.className.indexOf("collapse_top") !== -1) {
6
+ parent.style.display = "none";
7
+ }
8
+ });
9
+ document.querySelectorAll("nav > ul > li > ul li").forEach(function(parent) {
10
+ parent.style.display = "none";
11
+ });
12
+ document.querySelectorAll("nav > h3").forEach(function(section) {
13
+ if (section.className.indexOf("collapsed_header") !== -1) {
14
+ section.addEventListener("click", function(){
15
+ if (section.nextSibling.style.display === "none") {
16
+ section.nextSibling.style.display = "block";
17
+ } else {
18
+ section.nextSibling.style.display = "none";
19
+ }
20
+ });
21
+ }
22
+ });
23
+
24
+ //only current page (if it exists) should be opened
25
+ var file = window.location.pathname.split("/").pop().replace(/\.html/, '');
26
+ document.querySelectorAll("nav > ul > li > a").forEach(function(parent) {
27
+ var href = parent.attributes.href.value.replace(/\.html/, '');
28
+ if (file === href) {
29
+ if (parent.parentNode.parentNode.className.indexOf("collapse_top") !== -1) {
30
+ parent.parentNode.parentNode.style.display = "block";
31
+ }
32
+ parent.parentNode.querySelectorAll("ul li").forEach(function(elem) {
33
+ elem.style.display = "block";
34
+ });
35
+ }
36
+ });
37
+ }
38
+
39
+ hideAllButCurrent();
@@ -0,0 +1,28 @@
1
+ if (typeof fetch === 'function') {
2
+ const init = () => {
3
+ if (typeof scrollToNavItem !== 'function') return false
4
+ scrollToNavItem()
5
+ // hideAllButCurrent not always loaded
6
+ if (typeof hideAllButCurrent === 'function') hideAllButCurrent()
7
+ return true
8
+ }
9
+ fetch('./nav.inc.html')
10
+ .then(response => response.ok ? response.text() : `${response.url} => ${response.status} ${response.statusText}`)
11
+ .then(body => {
12
+ document.querySelector('nav').innerHTML += body
13
+ // nav.js should be quicker to load than nav.inc.html, a fallback just in case
14
+ return init()
15
+ })
16
+ .then(done => {
17
+ if (done) return
18
+ let i = 0
19
+ ;(function waitUntilNavJs () {
20
+ if (init()) return
21
+ if (i++ < 100) return setTimeout(waitUntilNavJs, 300)
22
+ console.error(Error('nav.js not loaded after 30s waiting for it'))
23
+ })()
24
+ })
25
+ .catch(error => console.error(error))
26
+ } else {
27
+ console.error(Error('Browser too old to display commonNav (remove commonNav docdash option)'))
28
+ }
@@ -0,0 +1,25 @@
1
+ /*global document */
2
+ (function() {
3
+ var source = document.getElementsByClassName('prettyprint source linenums');
4
+ var i = 0;
5
+ var lineNumber = 0;
6
+ var lineId;
7
+ var lines;
8
+ var totalLines;
9
+ var anchorHash;
10
+
11
+ if (source && source[0]) {
12
+ anchorHash = document.location.hash.substring(1);
13
+ lines = source[0].getElementsByTagName('li');
14
+ totalLines = lines.length;
15
+
16
+ for (; i < totalLines; i++) {
17
+ lineNumber++;
18
+ lineId = 'line' + lineNumber;
19
+ lines[i].id = lineId;
20
+ if (lineId === anchorHash) {
21
+ lines[i].className += ' selected';
22
+ }
23
+ }
24
+ }
25
+ })();
@@ -0,0 +1,12 @@
1
+ function scrollToNavItem() {
2
+ var path = window.location.href.split('/').pop().replace(/\.html/, '');
3
+ document.querySelectorAll('nav a').forEach(function(link) {
4
+ var href = link.attributes.href.value.replace(/\.html/, '');
5
+ if (path === href) {
6
+ link.scrollIntoView({block: 'center'});
7
+ return;
8
+ }
9
+ })
10
+ }
11
+
12
+ scrollToNavItem();
@@ -0,0 +1,4 @@
1
+ //IE Fix, src: https://www.reddit.com/r/programminghorror/comments/6abmcr/nodelist_lacks_foreach_in_internet_explorer/
2
+ if (typeof(NodeList.prototype.forEach)!==typeof(alert)){
3
+ NodeList.prototype.forEach=Array.prototype.forEach;
4
+ }