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,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 < 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 < 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 && 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 < -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>
|