w-trade-backtest 1.0.3 → 1.0.4
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/dist/w-trade-backtest.umd.js +8 -8
- package/dist/w-trade-backtest.umd.js.map +1 -1
- package/docs/calcOrders.mjs.html +2 -2
- package/docs/calcOrdersRatio.mjs.html +2 -2
- package/docs/calcOrdersSummary.mjs.html +2 -2
- package/docs/calcOrdersSummarySimple.mjs.html +2 -2
- package/docs/calcSummary.mjs.html +2 -2
- package/docs/closeAndSummaryOrders.mjs.html +2 -2
- package/docs/genReport.mjs.html +2 -2
- package/docs/genReportCore.mjs.html +2 -2
- package/docs/global.html +591 -2
- package/docs/index.html +2 -2
- package/docs/runStrategies.mjs.html +2 -2
- package/docs/runStrategiesAndBacktest.mjs.html +224 -0
- package/docs/runStrategy.mjs.html +2 -2
- package/package.json +1 -1
- package/src/WTradeBacktest.mjs +2 -0
- package/src/runStrategiesAndBacktest.mjs +152 -0
- package/test/unit-WTradeBacktest.test.mjs +114 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>runStrategiesAndBacktest.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#runStrategiesAndBacktest">runStrategiesAndBacktest</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">runStrategiesAndBacktest.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 isearr from 'wsemi/src/isearr.mjs'
|
|
52
|
+
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
|
|
53
|
+
import WDataTdprovide from 'w-data-tdprovide/src/WDataTdprovide.mjs'
|
|
54
|
+
import runStrategies from './runStrategies.mjs'
|
|
55
|
+
import genReportCore from './genReportCore.mjs'
|
|
56
|
+
import writeJson from './writeJson.mjs'
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 執行多策略回測並輸出訂單、摘要與報告檔案
|
|
61
|
+
*
|
|
62
|
+
* 以w-data-tdprovide讀取fdOhlc與fdParam資料夾數據,建立timeStart至timeEnd範圍之序列查詢函數後執行runStrategies
|
|
63
|
+
* 合併結果命名為'全部策略',並輸出orders.json、summary.json、report.html至fdTest資料夾
|
|
64
|
+
* fdOhlc或fdParam非資料夾、strategies非有效陣列時throw
|
|
65
|
+
*
|
|
66
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
|
|
67
|
+
* @function
|
|
68
|
+
* @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件(可用src/ott.mjs或自行以dayjs包裝)
|
|
69
|
+
* @param {String} fdOhlc 輸入儲存K線(ohlc)序列資料夾字串,各序列以`${key}.json`儲存
|
|
70
|
+
* @param {String} fdParam 輸入儲存指標參數序列資料夾字串
|
|
71
|
+
* @param {Array} strategies 輸入策略陣列,各元素需含sid與runStrategy之strategy欄位
|
|
72
|
+
* @param {String} timeStart 輸入回測起始秒時間字串
|
|
73
|
+
* @param {String} timeEnd 輸入回測結束秒時間字串
|
|
74
|
+
* @param {String} fdTest 輸入輸出結果資料夾字串
|
|
75
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
76
|
+
* @param {Boolean} [opt.writeOrders=true] 輸入是否輸出orders.json布林值,預設true
|
|
77
|
+
* @param {Boolean} [opt.writeSummary=true] 輸入是否輸出summary.json布林值,預設true
|
|
78
|
+
* @param {Boolean} [opt.writeReport=true] 輸入是否輸出report.html布林值,預設true
|
|
79
|
+
* @returns {Promise} 回傳Promise,resolve為{name,orders,summary}物件,另於fdTest輸出結果檔案
|
|
80
|
+
* @example
|
|
81
|
+
*
|
|
82
|
+
* import fs from 'fs'
|
|
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
|
+
* //建立數據資料夾, fdOhlc放K線序列, fdParam放指標參數序列
|
|
103
|
+
* fs.mkdirSync('./data-ohlc', { recursive: true })
|
|
104
|
+
* fs.mkdirSync('./data-param', { recursive: true })
|
|
105
|
+
* fs.writeFileSync('./data-ohlc/btc.json', JSON.stringify(arrOhlc), 'utf8')
|
|
106
|
+
* fs.writeFileSync('./data-param/sig.json', JSON.stringify(arrSig), 'utf8')
|
|
107
|
+
*
|
|
108
|
+
* let settings = { uIni: 1000, uTrade: 100, rTakeProfit: 0.05, rStopLoss: 0.03, rFee: 0.0005 }
|
|
109
|
+
* let strategies = [
|
|
110
|
+
* { sid: 's1', mode: 'long', keyOhlc: 'btc', conds: [{ key: 'sig', sym: '>', th: 0.5, opr: 'and' }], settings },
|
|
111
|
+
* { sid: 's2', mode: 'short', keyOhlc: 'btc', conds: [{ key: 'sig', sym: '<', th: 0.5, opr: 'and' }], settings },
|
|
112
|
+
* ]
|
|
113
|
+
*
|
|
114
|
+
* let r = await runStrategiesAndBacktest(ott, './data-ohlc', './data-param', strategies, '2020-01-01T00:00:00', '2020-01-01T20:00:00', './result')
|
|
115
|
+
* console.log(r.name, r.summary.uIni, r.summary.numTrade, r.summary.rWin)
|
|
116
|
+
* // => 全部策略 2000 6 40.00%
|
|
117
|
+
* console.log(fs.readdirSync('./result'))
|
|
118
|
+
* // => [ 'orders.json', 'report.html', 'summary.json' ]
|
|
119
|
+
*
|
|
120
|
+
*/
|
|
121
|
+
let runStrategiesAndBacktest = async (ott, fdOhlc, fdParam, strategies, timeStart, timeEnd, fdTest, opt = {}) => {
|
|
122
|
+
|
|
123
|
+
if (!fsIsFolder(fdOhlc)) {
|
|
124
|
+
throw new Error(`fdOhlc[${fdOhlc}] is not a forder`)
|
|
125
|
+
}
|
|
126
|
+
if (!fsIsFolder(fdParam)) {
|
|
127
|
+
throw new Error(`fdParam[${fdParam}] is not a forder`)
|
|
128
|
+
}
|
|
129
|
+
//check
|
|
130
|
+
if (!isearr(strategies)) {
|
|
131
|
+
throw new Error(`invalid strategies`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let writeOrders = get(opt, 'writeOrders', true)
|
|
135
|
+
let writeSummary = get(opt, 'writeSummary', true)
|
|
136
|
+
let writeReport = get(opt, 'writeReport', true)
|
|
137
|
+
|
|
138
|
+
//wdp
|
|
139
|
+
let wdp = WDataTdprovide(fdOhlc, fdParam)
|
|
140
|
+
|
|
141
|
+
//funGetSeries
|
|
142
|
+
let funGetSeries = await wdp.buildGetTimeSeriesByTimeRange(timeStart, timeEnd)
|
|
143
|
+
|
|
144
|
+
//runStrategies
|
|
145
|
+
let r = await runStrategies(ott, strategies, funGetSeries, {})
|
|
146
|
+
r.name = `全部策略`
|
|
147
|
+
|
|
148
|
+
if (writeOrders) {
|
|
149
|
+
|
|
150
|
+
//fnOrders
|
|
151
|
+
let fnOrders = `orders.json`
|
|
152
|
+
|
|
153
|
+
//fpOrders
|
|
154
|
+
let fpOrders = path.resolve(fdTest, fnOrders)
|
|
155
|
+
|
|
156
|
+
//writeJson
|
|
157
|
+
writeJson(fpOrders, r.orders, { structured: true })
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (writeSummary) {
|
|
162
|
+
|
|
163
|
+
//fnSummary
|
|
164
|
+
let fnSummary = `summary.json`
|
|
165
|
+
|
|
166
|
+
//fpSummary
|
|
167
|
+
let fpSummary = path.resolve(fdTest, fnSummary)
|
|
168
|
+
|
|
169
|
+
//stsm
|
|
170
|
+
let stsm = {
|
|
171
|
+
name: r.name,
|
|
172
|
+
summary: r.summary,
|
|
173
|
+
}
|
|
174
|
+
// console.log('stsm', stsm)
|
|
175
|
+
|
|
176
|
+
//writeJson
|
|
177
|
+
writeJson(fpSummary, stsm, { structured: true })
|
|
178
|
+
|
|
179
|
+
console.log('summary', r.summary, size(r.orders))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (writeReport) {
|
|
183
|
+
|
|
184
|
+
//fnHtml
|
|
185
|
+
let fnHtml = `report.html`
|
|
186
|
+
|
|
187
|
+
//fpHtml
|
|
188
|
+
let fpHtml = path.resolve(fdTest, fnHtml)
|
|
189
|
+
|
|
190
|
+
//genReport
|
|
191
|
+
genReportCore(r, fpHtml)
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return r
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
export default runStrategiesAndBacktest
|
|
200
|
+
</code></pre>
|
|
201
|
+
</article>
|
|
202
|
+
</section>
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<br class="clear">
|
|
212
|
+
|
|
213
|
+
<footer>
|
|
214
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Aug 03 2026 10:26:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
215
|
+
</footer>
|
|
216
|
+
|
|
217
|
+
<script>prettyPrint();</script>
|
|
218
|
+
<script src="scripts/polyfill.js"></script>
|
|
219
|
+
<script src="scripts/linenumber.js"></script>
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
</body>
|
|
224
|
+
</html>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
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>
|
|
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#runStrategiesAndBacktest">runStrategiesAndBacktest</a></li><li><a href="global.html#runStrategy">runStrategy</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -442,7 +442,7 @@ export default runStrategy
|
|
|
442
442
|
<br class="clear">
|
|
443
443
|
|
|
444
444
|
<footer>
|
|
445
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
445
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Mon Aug 03 2026 10:26:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
446
446
|
</footer>
|
|
447
447
|
|
|
448
448
|
<script>prettyPrint();</script>
|
package/package.json
CHANGED
package/src/WTradeBacktest.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import runStrategy from './runStrategy.mjs'
|
|
2
2
|
import runStrategies from './runStrategies.mjs'
|
|
3
|
+
import runStrategiesAndBacktest from './runStrategiesAndBacktest.mjs'
|
|
3
4
|
import calcOrders from './calcOrders.mjs'
|
|
4
5
|
import calcOrdersRatio from './calcOrdersRatio.mjs'
|
|
5
6
|
import calcOrdersSummary from './calcOrdersSummary.mjs'
|
|
@@ -13,6 +14,7 @@ import genReportCore from './genReportCore.mjs'
|
|
|
13
14
|
let r = {
|
|
14
15
|
runStrategy,
|
|
15
16
|
runStrategies,
|
|
17
|
+
runStrategiesAndBacktest,
|
|
16
18
|
calcOrders,
|
|
17
19
|
calcOrdersRatio,
|
|
18
20
|
calcOrdersSummary,
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import get from 'lodash-es/get.js'
|
|
3
|
+
import size from 'lodash-es/size.js'
|
|
4
|
+
import isearr from 'wsemi/src/isearr.mjs'
|
|
5
|
+
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
|
|
6
|
+
import WDataTdprovide from 'w-data-tdprovide/src/WDataTdprovide.mjs'
|
|
7
|
+
import runStrategies from './runStrategies.mjs'
|
|
8
|
+
import genReportCore from './genReportCore.mjs'
|
|
9
|
+
import writeJson from './writeJson.mjs'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 執行多策略回測並輸出訂單、摘要與報告檔案
|
|
14
|
+
*
|
|
15
|
+
* 以w-data-tdprovide讀取fdOhlc與fdParam資料夾數據,建立timeStart至timeEnd範圍之序列查詢函數後執行runStrategies
|
|
16
|
+
* 合併結果命名為'全部策略',並輸出orders.json、summary.json、report.html至fdTest資料夾
|
|
17
|
+
* fdOhlc或fdParam非資料夾、strategies非有效陣列時throw
|
|
18
|
+
*
|
|
19
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
|
|
20
|
+
* @function
|
|
21
|
+
* @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件(可用src/ott.mjs或自行以dayjs包裝)
|
|
22
|
+
* @param {String} fdOhlc 輸入儲存K線(ohlc)序列資料夾字串,各序列以`${key}.json`儲存
|
|
23
|
+
* @param {String} fdParam 輸入儲存指標參數序列資料夾字串
|
|
24
|
+
* @param {Array} strategies 輸入策略陣列,各元素需含sid與runStrategy之strategy欄位
|
|
25
|
+
* @param {String} timeStart 輸入回測起始秒時間字串
|
|
26
|
+
* @param {String} timeEnd 輸入回測結束秒時間字串
|
|
27
|
+
* @param {String} fdTest 輸入輸出結果資料夾字串
|
|
28
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
29
|
+
* @param {Boolean} [opt.writeOrders=true] 輸入是否輸出orders.json布林值,預設true
|
|
30
|
+
* @param {Boolean} [opt.writeSummary=true] 輸入是否輸出summary.json布林值,預設true
|
|
31
|
+
* @param {Boolean} [opt.writeReport=true] 輸入是否輸出report.html布林值,預設true
|
|
32
|
+
* @returns {Promise} 回傳Promise,resolve為{name,orders,summary}物件,另於fdTest輸出結果檔案
|
|
33
|
+
* @example
|
|
34
|
+
*
|
|
35
|
+
* import fs from 'fs'
|
|
36
|
+
*
|
|
37
|
+
* let arrOhlc = [
|
|
38
|
+
* { time: '2020-01-01T00:00:00', Open: 100, High: 101, Low: 99, Close: 100 },
|
|
39
|
+
* { time: '2020-01-01T04:00:00', Open: 100, High: 106, Low: 100, Close: 105 },
|
|
40
|
+
* { time: '2020-01-01T08:00:00', Open: 105, High: 107, Low: 102, Close: 103 },
|
|
41
|
+
* { time: '2020-01-01T12:00:00', Open: 103, High: 104, Low: 94, Close: 95 },
|
|
42
|
+
* { time: '2020-01-01T16:00:00', Open: 95, High: 98, Low: 92, Close: 93 },
|
|
43
|
+
* { time: '2020-01-01T20:00:00', Open: 93, High: 99, Low: 95, Close: 97 },
|
|
44
|
+
* ]
|
|
45
|
+
*
|
|
46
|
+
* let arrSig = [
|
|
47
|
+
* { time: '2020-01-01T00:00:00', param: 1 },
|
|
48
|
+
* { time: '2020-01-01T04:00:00', param: 0 },
|
|
49
|
+
* { time: '2020-01-01T08:00:00', param: 1 },
|
|
50
|
+
* { time: '2020-01-01T12:00:00', param: 0 },
|
|
51
|
+
* { time: '2020-01-01T16:00:00', param: 0 },
|
|
52
|
+
* { time: '2020-01-01T20:00:00', param: 0 },
|
|
53
|
+
* ]
|
|
54
|
+
*
|
|
55
|
+
* //建立數據資料夾, fdOhlc放K線序列, fdParam放指標參數序列
|
|
56
|
+
* fs.mkdirSync('./data-ohlc', { recursive: true })
|
|
57
|
+
* fs.mkdirSync('./data-param', { recursive: true })
|
|
58
|
+
* fs.writeFileSync('./data-ohlc/btc.json', JSON.stringify(arrOhlc), 'utf8')
|
|
59
|
+
* fs.writeFileSync('./data-param/sig.json', JSON.stringify(arrSig), 'utf8')
|
|
60
|
+
*
|
|
61
|
+
* let settings = { uIni: 1000, uTrade: 100, rTakeProfit: 0.05, rStopLoss: 0.03, rFee: 0.0005 }
|
|
62
|
+
* let strategies = [
|
|
63
|
+
* { sid: 's1', mode: 'long', keyOhlc: 'btc', conds: [{ key: 'sig', sym: '>', th: 0.5, opr: 'and' }], settings },
|
|
64
|
+
* { sid: 's2', mode: 'short', keyOhlc: 'btc', conds: [{ key: 'sig', sym: '<', th: 0.5, opr: 'and' }], settings },
|
|
65
|
+
* ]
|
|
66
|
+
*
|
|
67
|
+
* let r = await runStrategiesAndBacktest(ott, './data-ohlc', './data-param', strategies, '2020-01-01T00:00:00', '2020-01-01T20:00:00', './result')
|
|
68
|
+
* console.log(r.name, r.summary.uIni, r.summary.numTrade, r.summary.rWin)
|
|
69
|
+
* // => 全部策略 2000 6 40.00%
|
|
70
|
+
* console.log(fs.readdirSync('./result'))
|
|
71
|
+
* // => [ 'orders.json', 'report.html', 'summary.json' ]
|
|
72
|
+
*
|
|
73
|
+
*/
|
|
74
|
+
let runStrategiesAndBacktest = async (ott, fdOhlc, fdParam, strategies, timeStart, timeEnd, fdTest, opt = {}) => {
|
|
75
|
+
|
|
76
|
+
if (!fsIsFolder(fdOhlc)) {
|
|
77
|
+
throw new Error(`fdOhlc[${fdOhlc}] is not a forder`)
|
|
78
|
+
}
|
|
79
|
+
if (!fsIsFolder(fdParam)) {
|
|
80
|
+
throw new Error(`fdParam[${fdParam}] is not a forder`)
|
|
81
|
+
}
|
|
82
|
+
//check
|
|
83
|
+
if (!isearr(strategies)) {
|
|
84
|
+
throw new Error(`invalid strategies`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let writeOrders = get(opt, 'writeOrders', true)
|
|
88
|
+
let writeSummary = get(opt, 'writeSummary', true)
|
|
89
|
+
let writeReport = get(opt, 'writeReport', true)
|
|
90
|
+
|
|
91
|
+
//wdp
|
|
92
|
+
let wdp = WDataTdprovide(fdOhlc, fdParam)
|
|
93
|
+
|
|
94
|
+
//funGetSeries
|
|
95
|
+
let funGetSeries = await wdp.buildGetTimeSeriesByTimeRange(timeStart, timeEnd)
|
|
96
|
+
|
|
97
|
+
//runStrategies
|
|
98
|
+
let r = await runStrategies(ott, strategies, funGetSeries, {})
|
|
99
|
+
r.name = `全部策略`
|
|
100
|
+
|
|
101
|
+
if (writeOrders) {
|
|
102
|
+
|
|
103
|
+
//fnOrders
|
|
104
|
+
let fnOrders = `orders.json`
|
|
105
|
+
|
|
106
|
+
//fpOrders
|
|
107
|
+
let fpOrders = path.resolve(fdTest, fnOrders)
|
|
108
|
+
|
|
109
|
+
//writeJson
|
|
110
|
+
writeJson(fpOrders, r.orders, { structured: true })
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (writeSummary) {
|
|
115
|
+
|
|
116
|
+
//fnSummary
|
|
117
|
+
let fnSummary = `summary.json`
|
|
118
|
+
|
|
119
|
+
//fpSummary
|
|
120
|
+
let fpSummary = path.resolve(fdTest, fnSummary)
|
|
121
|
+
|
|
122
|
+
//stsm
|
|
123
|
+
let stsm = {
|
|
124
|
+
name: r.name,
|
|
125
|
+
summary: r.summary,
|
|
126
|
+
}
|
|
127
|
+
// console.log('stsm', stsm)
|
|
128
|
+
|
|
129
|
+
//writeJson
|
|
130
|
+
writeJson(fpSummary, stsm, { structured: true })
|
|
131
|
+
|
|
132
|
+
console.log('summary', r.summary, size(r.orders))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (writeReport) {
|
|
136
|
+
|
|
137
|
+
//fnHtml
|
|
138
|
+
let fnHtml = `report.html`
|
|
139
|
+
|
|
140
|
+
//fpHtml
|
|
141
|
+
let fpHtml = path.resolve(fdTest, fnHtml)
|
|
142
|
+
|
|
143
|
+
//genReport
|
|
144
|
+
genReportCore(r, fpHtml)
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return r
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
export default runStrategiesAndBacktest
|
|
@@ -11,6 +11,7 @@ import genReport from '../src/genReport.mjs'
|
|
|
11
11
|
import genReportCore from '../src/genReportCore.mjs'
|
|
12
12
|
import ott, { nowTpeStr } from '../src/ott.mjs'
|
|
13
13
|
import runStrategies from '../src/runStrategies.mjs'
|
|
14
|
+
import runStrategiesAndBacktest from '../src/runStrategiesAndBacktest.mjs'
|
|
14
15
|
import runStrategy from '../src/runStrategy.mjs'
|
|
15
16
|
import writeJson from '../src/writeJson.mjs'
|
|
16
17
|
import writeTxt from '../src/writeTxt.mjs'
|
|
@@ -34,6 +35,9 @@ import writeTxt from '../src/writeTxt.mjs'
|
|
|
34
35
|
// opt.timeOhlcStart/timeOhlcEnd預設由訂單起訖推得, opt.withWriteSummary:true另輸出summary.json
|
|
35
36
|
// closeAndSummaryOrders(ott, fdOhlc, fdParam, uIni, timeOhlcStart, timeOhlcEnd, keyOhlc, ordersSubmit, fdTest, opt):
|
|
36
37
|
// 以w-data-tdprovide讀取fdOhlc/fdParam數據, 結算ordersSubmit並輸出orders.json/summary.json/report.html至fdTest
|
|
38
|
+
// runStrategiesAndBacktest(ott, fdOhlc, fdParam, strategies, timeStart, timeEnd, fdTest, opt):
|
|
39
|
+
// 以w-data-tdprovide讀取fdOhlc/fdParam數據建立funGetSeries後跑runStrategies, 結果name為'全部策略',
|
|
40
|
+
// 並依opt.writeOrders/writeSummary/writeReport(皆預設true)輸出orders.json/summary.json/report.html至fdTest
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
//approx, 浮點容差比較
|
|
@@ -538,4 +542,114 @@ describe('WTradeBacktest', function() {
|
|
|
538
542
|
|
|
539
543
|
})
|
|
540
544
|
|
|
545
|
+
describe('runStrategiesAndBacktest', function() {
|
|
546
|
+
|
|
547
|
+
//sig訊號: t00與t08為1(>th觸發), 其餘為0, 存於fdParam之sig.json
|
|
548
|
+
let arrSig = [
|
|
549
|
+
{ time: t00, param: 1 },
|
|
550
|
+
{ time: t04, param: 0 },
|
|
551
|
+
{ time: t08, param: 1 },
|
|
552
|
+
{ time: t12, param: 0 },
|
|
553
|
+
{ time: t16, param: 0 },
|
|
554
|
+
{ time: t20, param: 0 },
|
|
555
|
+
]
|
|
556
|
+
let settings = {
|
|
557
|
+
uIni: 1000,
|
|
558
|
+
uTrade: 100,
|
|
559
|
+
rTakeProfit: 0.05,
|
|
560
|
+
rStopLoss: 0.03,
|
|
561
|
+
rFee: 0.0005,
|
|
562
|
+
}
|
|
563
|
+
let strategies = [
|
|
564
|
+
{ sid: 's1', mode: 'long', keyOhlc: 'btc', conds: [{ key: 'sig', sym: '>', th: 0.5, opr: 'and' }], settings },
|
|
565
|
+
{ sid: 's2', mode: 'short', keyOhlc: 'btc', conds: [{ key: 'sig', sym: '<', th: 0.5, opr: 'and' }], settings },
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
//buildFdData: 於`${fdTmp}/${fd}`建立data-ohlc(btc.json)與data-param(sig.json), 回傳兩資料夾路徑
|
|
569
|
+
let buildFdData = (fd) => {
|
|
570
|
+
let fdOhlc = path.resolve(fdTmp, fd, 'data-ohlc')
|
|
571
|
+
let fdParam = path.resolve(fdTmp, fd, 'data-param')
|
|
572
|
+
fs.mkdirSync(fdOhlc, { recursive: true })
|
|
573
|
+
fs.mkdirSync(fdParam, { recursive: true })
|
|
574
|
+
fs.writeFileSync(path.resolve(fdOhlc, 'btc.json'), JSON.stringify(buildArrOhlc()), 'utf8')
|
|
575
|
+
fs.writeFileSync(path.resolve(fdParam, 'sig.json'), JSON.stringify(arrSig), 'utf8')
|
|
576
|
+
return { fdOhlc, fdParam }
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
it('讀取數據執行多策略, 回傳name為全部策略之合併結果並輸出orders.json/summary.json/report.html', async function() {
|
|
580
|
+
let { fdOhlc, fdParam } = buildFdData('rsab-all')
|
|
581
|
+
let fdTest = path.resolve(fdTmp, 'rsab-all', 'result')
|
|
582
|
+
|
|
583
|
+
let r = await runStrategiesAndBacktest(ott, fdOhlc, fdParam, strategies, t00, t20, fdTest)
|
|
584
|
+
|
|
585
|
+
//r, 同runStrategies之合併結果(s1兩單, s2四單, 依timeStart排序)另附name
|
|
586
|
+
assert.strictEqual(r.name, '全部策略')
|
|
587
|
+
assert.strictEqual(r.orders.length, 6)
|
|
588
|
+
assert.deepStrictEqual(r.orders.map((o) => o.sid), ['s1', 's2', 's1', 's2', 's2', 's2'])
|
|
589
|
+
assert.strictEqual(r.summary.uIni, 2000)
|
|
590
|
+
assert.strictEqual(r.summary.timeOhlcStart, t00)
|
|
591
|
+
assert.strictEqual(r.summary.timeOhlcEnd, t20)
|
|
592
|
+
assert.strictEqual(r.summary.numTrade, 6)
|
|
593
|
+
assert.strictEqual(r.summary.numTradeUnsettled, 1)
|
|
594
|
+
assert.strictEqual(r.summary.rWin, '40.00%')
|
|
595
|
+
|
|
596
|
+
//orders.json
|
|
597
|
+
let orders = JSON.parse(fs.readFileSync(path.resolve(fdTest, 'orders.json'), 'utf8'))
|
|
598
|
+
assert.strictEqual(orders.length, 6)
|
|
599
|
+
assert.strictEqual(orders[0].sid, 's1')
|
|
600
|
+
assert.strictEqual(orders[0].timeStart, t00)
|
|
601
|
+
|
|
602
|
+
//summary.json
|
|
603
|
+
let stsm = JSON.parse(fs.readFileSync(path.resolve(fdTest, 'summary.json'), 'utf8'))
|
|
604
|
+
assert.strictEqual(stsm.name, '全部策略')
|
|
605
|
+
assert.strictEqual(stsm.summary.numTrade, 6)
|
|
606
|
+
assert.strictEqual(stsm.summary.rWin, '40.00%')
|
|
607
|
+
|
|
608
|
+
//report.html, 已置換name與orders
|
|
609
|
+
let fpHtml = path.resolve(fdTest, 'report.html')
|
|
610
|
+
assert.ok(fs.existsSync(fpHtml))
|
|
611
|
+
let h = fs.readFileSync(fpHtml, 'utf8')
|
|
612
|
+
assert.ok(h.includes('全部策略'))
|
|
613
|
+
assert.ok(!h.includes('{orders}'))
|
|
614
|
+
assert.ok(!h.includes('{summary}'))
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
it('writeOrders/writeSummary/writeReport皆false時不輸出檔案, 仍回傳結果', async function() {
|
|
618
|
+
let { fdOhlc, fdParam } = buildFdData('rsab-nowrite')
|
|
619
|
+
let fdTest = path.resolve(fdTmp, 'rsab-nowrite', 'result')
|
|
620
|
+
|
|
621
|
+
let r = await runStrategiesAndBacktest(ott, fdOhlc, fdParam, strategies, t00, t20, fdTest, {
|
|
622
|
+
writeOrders: false,
|
|
623
|
+
writeSummary: false,
|
|
624
|
+
writeReport: false,
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
assert.strictEqual(r.orders.length, 6)
|
|
628
|
+
assert.ok(!fs.existsSync(path.resolve(fdTest, 'orders.json')))
|
|
629
|
+
assert.ok(!fs.existsSync(path.resolve(fdTest, 'summary.json')))
|
|
630
|
+
assert.ok(!fs.existsSync(path.resolve(fdTest, 'report.html')))
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
it('fdOhlc非資料夾時reject', async function() {
|
|
634
|
+
let { fdParam } = buildFdData('rsab-invalid')
|
|
635
|
+
let fdOhlc = path.resolve(fdTmp, 'rsab-invalid', 'none')
|
|
636
|
+
let fdTest = path.resolve(fdTmp, 'rsab-invalid', 'result')
|
|
637
|
+
await assert.rejects(runStrategiesAndBacktest(ott, fdOhlc, fdParam, strategies, t00, t20, fdTest), { message: `fdOhlc[${fdOhlc}] is not a forder` })
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it('fdParam非資料夾時reject', async function() {
|
|
641
|
+
let { fdOhlc } = buildFdData('rsab-invalid2')
|
|
642
|
+
let fdParam = path.resolve(fdTmp, 'rsab-invalid2', 'none')
|
|
643
|
+
let fdTest = path.resolve(fdTmp, 'rsab-invalid2', 'result')
|
|
644
|
+
await assert.rejects(runStrategiesAndBacktest(ott, fdOhlc, fdParam, strategies, t00, t20, fdTest), { message: `fdParam[${fdParam}] is not a forder` })
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
it('strategies非有效陣列時reject', async function() {
|
|
648
|
+
let { fdOhlc, fdParam } = buildFdData('rsab-nostrategies')
|
|
649
|
+
let fdTest = path.resolve(fdTmp, 'rsab-nostrategies', 'result')
|
|
650
|
+
await assert.rejects(runStrategiesAndBacktest(ott, fdOhlc, fdParam, [], t00, t20, fdTest), { message: `invalid strategies` })
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
})
|
|
654
|
+
|
|
541
655
|
})
|