w-trade-backtest 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +9 -0
- package/.eslintignore +3 -0
- package/.eslintrc.js +55 -0
- package/.jsdoc +25 -0
- package/README.md +167 -0
- package/SECURITY.md +5 -0
- package/babel.config.js +16 -0
- package/dist/renderOrdersTable.js +217 -0
- package/dist/renderOrdersTimeline.js +159 -0
- package/dist/renderProfitPlot.js +198 -0
- package/dist/renderSummary.js +194 -0
- package/dist/tmp.html +232 -0
- package/dist/w-trade-backtest.umd.js +32 -0
- package/dist/w-trade-backtest.umd.js.map +1 -0
- package/docs/calcOrders.mjs.html +278 -0
- package/docs/calcOrdersRatio.mjs.html +146 -0
- package/docs/calcOrdersSummary.mjs.html +506 -0
- package/docs/calcOrdersSummarySimple.mjs.html +207 -0
- package/docs/calcSummary.mjs.html +201 -0
- package/docs/closeAndSummaryOrders.mjs.html +265 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/genReport.mjs.html +196 -0
- package/docs/genReportCore.mjs.html +128 -0
- package/docs/global.html +4231 -0
- package/docs/index.html +84 -0
- package/docs/runStrategies.mjs.html +244 -0
- package/docs/runStrategy.mjs.html +455 -0
- package/docs/scripts/collapse.js +39 -0
- package/docs/scripts/commonNav.js +28 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/nav.js +12 -0
- package/docs/scripts/polyfill.js +4 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/scripts/search.js +99 -0
- package/docs/styles/jsdoc.css +776 -0
- package/docs/styles/prettify.css +80 -0
- package/g.mjs +146 -0
- package/package.json +30 -0
- package/script.txt +19 -0
- package/src/WTradeBacktest.mjs +27 -0
- package/src/calcOrders.mjs +206 -0
- package/src/calcOrdersRatio.mjs +74 -0
- package/src/calcOrdersSummary.mjs +434 -0
- package/src/calcOrdersSummarySimple.mjs +135 -0
- package/src/calcSummary.mjs +129 -0
- package/src/closeAndSummaryOrders.mjs +193 -0
- package/src/genReport.mjs +124 -0
- package/src/genReportCore.mjs +56 -0
- package/src/ott.mjs +28 -0
- package/src/readJson.mjs +26 -0
- package/src/renderOrdersTable.js +217 -0
- package/src/renderOrdersTimeline.js +159 -0
- package/src/renderProfitPlot.js +198 -0
- package/src/renderSummary.js +194 -0
- package/src/runStrategies.mjs +172 -0
- package/src/runStrategy.mjs +383 -0
- package/src/tmp.html +232 -0
- package/src/writeJson.mjs +31 -0
- package/src/writeTxt.mjs +19 -0
- package/test/unit-WTradeBacktest.test.mjs +541 -0
- package/toolg/addVersion.mjs +4 -0
- package/toolg/cleanFolder.mjs +5 -0
- package/toolg/gDistRollup.mjs +51 -0
- package/toolg/gDocsExams.mjs +48 -0
- package/toolg/modifyReadme.mjs +4 -0
|
@@ -0,0 +1,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' && 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' && 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 < 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 && 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();
|