w-trade-backtest 1.0.8 → 1.1.2

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/docs/index.html CHANGED
@@ -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#buildKLinesTpSl">buildKLinesTpSl</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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>
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#buildKLinesTpSl">buildKLinesTpSl</a></li><li><a href="global.html#buildStrategyFastSession">buildStrategyFastSession</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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><li><a href="global.html#settleOrderPnl">settleOrderPnl</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -71,7 +71,7 @@
71
71
  <br class="clear">
72
72
 
73
73
  <footer>
74
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 08 2026 15:13:36 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
74
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 12:40:44 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
75
75
  </footer>
76
76
 
77
77
  <script>prettyPrint();</script>
@@ -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#buildKLinesTpSl">buildKLinesTpSl</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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>
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#buildKLinesTpSl">buildKLinesTpSl</a></li><li><a href="global.html#buildStrategyFastSession">buildStrategyFastSession</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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><li><a href="global.html#settleOrderPnl">settleOrderPnl</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -50,6 +50,7 @@ import fs from 'fs'
50
50
  import dig from 'wsemi/src/dig.mjs'
51
51
  import isestr from 'wsemi/src/isestr.mjs'
52
52
  import fsIsFile from 'wsemi/src/fsIsFile.mjs'
53
+ import settleOrderPnl from './settleOrderPnl.mjs'
53
54
 
54
55
 
55
56
  /**
@@ -142,9 +143,7 @@ let loadKLinesTpSl = (fpJson) => {
142
143
  }
143
144
  o.timeEnd = r.timeEnd
144
145
  o.priceEnd = r.win === 1 ? o.priceTakeProfit : o.priceStopLoss
145
- o.uProfitOrLoss = o.mode === 'long'
146
- ? o.uTrade * (o.priceEnd / o.priceStart) - o.uTrade - 2 * o.uFee
147
- : (o.priceStart - o.priceEnd) * (o.uTrade / o.priceStart) - 2 * o.uFee
146
+ o.uProfitOrLoss = settleOrderPnl(o.mode, o.uTrade, o.priceStart, o.priceEnd, o.uFee)
148
147
  o.rProfitOrLoss = o.uProfitOrLoss / o.uTrade
149
148
  uCumu += o.uProfitOrLoss
150
149
  o.uCumuProfitOrLoss = uCumu
@@ -176,7 +175,7 @@ export default loadKLinesTpSl
176
175
  <br class="clear">
177
176
 
178
177
  <footer>
179
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 08 2026 15:13:36 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
178
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 12:40:44 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
180
179
  </footer>
181
180
 
182
181
  <script>prettyPrint();</script>
@@ -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#buildKLinesTpSl">buildKLinesTpSl</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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>
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#buildKLinesTpSl">buildKLinesTpSl</a></li><li><a href="global.html#buildStrategyFastSession">buildStrategyFastSession</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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><li><a href="global.html#settleOrderPnl">settleOrderPnl</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -231,7 +231,7 @@ export default runStrategies
231
231
  <br class="clear">
232
232
 
233
233
  <footer>
234
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 08 2026 15:13:36 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
234
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 12:40:44 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
235
235
  </footer>
236
236
 
237
237
  <script>prettyPrint();</script>
@@ -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#buildKLinesTpSl">buildKLinesTpSl</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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>
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#buildKLinesTpSl">buildKLinesTpSl</a></li><li><a href="global.html#buildStrategyFastSession">buildStrategyFastSession</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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><li><a href="global.html#settleOrderPnl">settleOrderPnl</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -211,7 +211,7 @@ export default runStrategiesAndBacktest
211
211
  <br class="clear">
212
212
 
213
213
  <footer>
214
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 08 2026 15:13:36 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
214
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 12:40:44 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
215
215
  </footer>
216
216
 
217
217
  <script>prettyPrint();</script>
@@ -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#buildKLinesTpSl">buildKLinesTpSl</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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>
32
+ <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#buildKLinesTpSl">buildKLinesTpSl</a></li><li><a href="global.html#buildStrategyFastSession">buildStrategyFastSession</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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><li><a href="global.html#settleOrderPnl">settleOrderPnl</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 Sat Aug 08 2026 15:13:36 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
445
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 12:40:44 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
446
446
  </footer>
447
447
 
448
448
  <script>prettyPrint();</script>
@@ -0,0 +1,100 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>settleOrderPnl.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#buildKLinesTpSl">buildKLinesTpSl</a></li><li><a href="global.html#buildStrategyFastSession">buildStrategyFastSession</a></li><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#genKLinesTpSl">genKLinesTpSl</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#loadKLinesTpSl">loadKLinesTpSl</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><li><a href="global.html#settleOrderPnl">settleOrderPnl</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">settleOrderPnl.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>/**
49
+ * 計算單筆訂單之淨盈虧金額(USDT)
50
+ *
51
+ * 結算公式與calcOrders逐字對齊(bit-exact): long為uTrade*(priceEnd/priceStart)-uTrade-2*uFee,short為(priceStart-priceEnd)*(uTrade/priceStart)-2*uFee
52
+ * 供loadKLinesTpSl之ordersFromBars與buildStrategyFastSession共用,避免套件內出現第三份手抄公式
53
+ *
54
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
55
+ * @function
56
+ * @param {String} mode 輸入交易方向字串,'long'或'short'
57
+ * @param {Number} uTrade 輸入下單金額(USDT)
58
+ * @param {Number} priceStart 輸入進場價格
59
+ * @param {Number} priceEnd 輸入出場價格
60
+ * @param {Number} uFee 輸入單邊手續費(USDT),結算扣2*uFee
61
+ * @returns {Number} 回傳淨盈虧金額(USDT),含手續費
62
+ * @example
63
+ *
64
+ * console.log(settleOrderPnl('long', 100, 100, 105, 0.05))
65
+ * // => 4.9
66
+ *
67
+ */
68
+ let settleOrderPnl = (mode, uTrade, priceStart, priceEnd, uFee) => {
69
+ return mode === 'long'
70
+ ? uTrade * (priceEnd / priceStart) - uTrade - 2 * uFee
71
+ : (priceStart - priceEnd) * (uTrade / priceStart) - 2 * uFee
72
+ }
73
+
74
+
75
+ export default settleOrderPnl
76
+ </code></pre>
77
+ </article>
78
+ </section>
79
+
80
+
81
+
82
+
83
+
84
+
85
+ </div>
86
+
87
+ <br class="clear">
88
+
89
+ <footer>
90
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 12:40:44 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
91
+ </footer>
92
+
93
+ <script>prettyPrint();</script>
94
+ <script src="scripts/polyfill.js"></script>
95
+ <script src="scripts/linenumber.js"></script>
96
+
97
+
98
+
99
+ </body>
100
+ </html>
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "w-trade-backtest",
3
- "version": "1.0.8",
3
+ "version": "1.1.2",
4
4
  "main": "dist/w-trade-backtest.umd.js",
5
5
  "dependencies": {
6
- "w-data-tdprovide": "^1.0.6",
7
- "wsemi": "^1.8.70"
6
+ "w-data-tdprovide": "^1.0.7",
7
+ "wsemi": "^1.8.73"
8
8
  },
9
9
  "devDependencies": {
10
10
  "w-package-tools": "^1.1.12"
@@ -12,6 +12,7 @@ import genReportCore from './genReportCore.mjs'
12
12
  import genKLinesTpSl from './genKLinesTpSl.mjs'
13
13
  import buildKLinesTpSl from './buildKLinesTpSl.mjs'
14
14
  import loadKLinesTpSl from './loadKLinesTpSl.mjs'
15
+ import buildStrategyFastSession from './buildStrategyFastSession.mjs'
15
16
 
16
17
 
17
18
  let r = {
@@ -29,6 +30,7 @@ let r = {
29
30
  genKLinesTpSl,
30
31
  buildKLinesTpSl,
31
32
  loadKLinesTpSl,
33
+ buildStrategyFastSession,
32
34
  }
33
35
 
34
36
 
@@ -0,0 +1,293 @@
1
+ import get from 'lodash-es/get.js'
2
+ import dig from 'wsemi/src/dig.mjs'
3
+ import isearr from 'wsemi/src/isearr.mjs'
4
+ import iseobj from 'wsemi/src/iseobj.mjs'
5
+ import genKLinesTpSl from './genKLinesTpSl.mjs'
6
+ import calcOrdersSummarySimple from './calcOrdersSummarySimple.mjs'
7
+ import settleOrderPnl from './settleOrderPnl.mjs'
8
+
9
+
10
+ /**
11
+ * 建立策略快速評估session,供求解熱路徑(如RGA/PSO目標函數)重複評估同一K線窗之不同條件組合
12
+ *
13
+ * 建置期一次攤提runStrategy每次呼叫都要重建的不變量: 各因子{time,param}序列轉為K棒對位Float64Array(缺值填NaN)、各(tp,sl)組合之first-touch預算表(genKLinesTpSl,惰性建置+Map快取)
14
+ * 評估期單迴圈逐棒判斷conds觸發並沿calcOrders同式結算(settleOrderPnl),摘要直接以calcOrdersSummarySimple計算,與官方全鏈(runStrategy→calcOrders→calcOrdersSummary)對應欄位嚴格相等
15
+ * 觸發語義與runStrategy等價: 任一cond序列於該棒缺值(NaN)即整棒跳過(等同僅於全部cond序列皆有值之共同時間點判斷)、'and'組須全真且'or'組至少一真、sym為'>'比大於否則比小於
16
+ * 序列param須為有限數值,缺值以該時間點不存在表示; opt.mode非'long'或'short'、arrOhlc或serieses無效時throw; evaluate之conds非有效陣列(含空陣列, 同runStrategy)或cond.key不在serieses時throw
17
+ *
18
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
19
+ * @function
20
+ * @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件(可用src/ott.mjs或自行以dayjs包裝)
21
+ * @param {Object} opt 輸入設定物件
22
+ * @param {Array} opt.arrOhlc 輸入K線陣列,各元素需含time(秒時間字串)、Close、High、Low欄位,且依時間由小至大排序,與runStrategy之funGetSeries(keyOhlc)同一份
23
+ * @param {String} opt.mode 輸入交易方向字串,'long'或'short'
24
+ * @param {Object} opt.serieses 輸入因子序列物件,key為因子名稱,value為{time,param}陣列,param須為有限數值
25
+ * @param {Array} [opt.tpsls=[]] 輸入預建first-touch表之{tp,sl}陣列,未列組合於evaluate時惰性建置,預設[]
26
+ * @returns {Object} 回傳session物件,內含evaluate({conds,tp,sl,settings},opt)評估函數,conds各元素為{key,sym,th,opr}(與runStrategy同構),settings可給uIni(預設1000)、uTrade(預設1)、rFee(預設0.0005),evaluate預設回傳calcOrdersSummarySimple同構摘要物件,evaluate之opt.withOrders為true時回傳{orders,summary}(orders與loadKLinesTpSl之ordersFromBars同構,供除錯對照之慢速路徑)
27
+ * @example
28
+ *
29
+ * let arrOhlc = [
30
+ * { time: '2020-01-01T00:00:00', Open: 100, High: 101, Low: 99, Close: 100 },
31
+ * { time: '2020-01-01T04:00:00', Open: 100, High: 106, Low: 100, Close: 105 },
32
+ * { time: '2020-01-01T08:00:00', Open: 105, High: 107, Low: 102, Close: 103 },
33
+ * { time: '2020-01-01T12:00:00', Open: 103, High: 104, Low: 94, Close: 95 },
34
+ * { time: '2020-01-01T16:00:00', Open: 95, High: 98, Low: 92, Close: 93 },
35
+ * { time: '2020-01-01T20:00:00', Open: 93, High: 99, Low: 95, Close: 97 },
36
+ * ]
37
+ *
38
+ * let arrSig = [
39
+ * { time: '2020-01-01T00:00:00', param: 1 },
40
+ * { time: '2020-01-01T04:00:00', param: 0 },
41
+ * { time: '2020-01-01T08:00:00', param: 1 },
42
+ * { time: '2020-01-01T12:00:00', param: 0 },
43
+ * { time: '2020-01-01T16:00:00', param: 0 },
44
+ * { time: '2020-01-01T20:00:00', param: 0 },
45
+ * ]
46
+ *
47
+ * let session = buildStrategyFastSession(ott, {
48
+ * arrOhlc,
49
+ * mode: 'long',
50
+ * serieses: { sig: arrSig },
51
+ * })
52
+ *
53
+ * let sm = session.evaluate({
54
+ * conds: [{ key: 'sig', sym: '>', th: 0.5, opr: 'and' }],
55
+ * tp: 0.05,
56
+ * sl: 0.03,
57
+ * settings: { uIni: 1000, uTrade: 100, rFee: 0.0005 },
58
+ * })
59
+ * console.log(sm.numTrade, sm.rWin, sm.uEquityFinal)
60
+ * // => 2 50.00% 1001.8
61
+ *
62
+ */
63
+ let buildStrategyFastSession = (ott, opt = {}) => {
64
+
65
+ //mode
66
+ let mode = get(opt, 'mode', '')
67
+ if (mode !== 'long' && mode !== 'short') {
68
+ throw new Error(`invalid opt.mode[${mode}] not 'long' or 'short'`)
69
+ }
70
+
71
+ //arrOhlc
72
+ let arrOhlc = get(opt, 'arrOhlc', null)
73
+ if (!isearr(arrOhlc)) {
74
+ throw new Error(`invalid opt.arrOhlc`)
75
+ }
76
+
77
+ //serieses
78
+ let serieses = get(opt, 'serieses', null)
79
+ if (!iseobj(serieses)) {
80
+ throw new Error(`invalid opt.serieses`)
81
+ }
82
+
83
+ //n, time, C
84
+ let n = arrOhlc.length
85
+ let time = new Array(n)
86
+ let C = new Float64Array(n)
87
+ let kpTimeInd = new Map() //K棒時間→index
88
+ for (let i = 0; i < n; i++) {
89
+ let r = arrOhlc[i]
90
+ time[i] = r.time
91
+ C[i] = r.Close
92
+ kpTimeInd.set(r.time, i)
93
+ }
94
+
95
+ //timeOhlcStart, timeOhlcEnd
96
+ let timeOhlcStart = n > 0 ? time[0] : ''
97
+ let timeOhlcEnd = n > 0 ? time[n - 1] : ''
98
+
99
+ //kpAligned, 各因子序列轉K棒對位Float64Array(缺值填NaN), 序列中不屬K棒之時間點不影響觸發(runStrategy亦僅於K棒時間判斷)
100
+ let kpAligned = {}
101
+ for (let key of Object.keys(serieses)) {
102
+ let arr = new Float64Array(n).fill(NaN)
103
+ for (let v of serieses[key]) {
104
+ let ind = kpTimeInd.get(v.time)
105
+ if (ind !== undefined) {
106
+ arr[ind] = v.param
107
+ }
108
+ }
109
+ kpAligned[key] = arr
110
+ }
111
+
112
+ //kpFirstTouch, 各(tp,sl)組合之first-touch表快取(惰性建置)
113
+ let kpFirstTouch = new Map()
114
+ let getFirstTouch = (tp, sl) => {
115
+ let k = `${tp}_${sl}`
116
+ let ft = kpFirstTouch.get(k)
117
+ if (!ft) {
118
+ ft = genKLinesTpSl(arrOhlc, { tp, sl, mode }) //tp/sl無效時由genKLinesTpSl throw
119
+ kpFirstTouch.set(k, ft)
120
+ }
121
+ return ft
122
+ }
123
+
124
+ //預建
125
+ let tpsls = get(opt, 'tpsls', [])
126
+ if (isearr(tpsls)) {
127
+ for (let v of tpsls) {
128
+ getFirstTouch(v.tp, v.sl)
129
+ }
130
+ }
131
+
132
+ //evaluate
133
+ let evaluate = (input = {}, optEv = {}) => {
134
+
135
+ //conds, 空陣列同runStrategy一律throw(isearr對空陣列回false)
136
+ let conds = get(input, 'conds', [])
137
+ if (!isearr(conds)) {
138
+ throw new Error(`invalid conds`)
139
+ }
140
+
141
+ //tp, sl
142
+ let tp = get(input, 'tp', null)
143
+ let sl = get(input, 'sl', null)
144
+
145
+ //settings
146
+ let st = get(input, 'settings', {})
147
+ let uIni = get(st, 'uIni', 1000)
148
+ let uTrade = get(st, 'uTrade', 1)
149
+ let rFee = get(st, 'rFee', 0.0005)
150
+ let uFee = rFee * uTrade
151
+
152
+ //withOrders
153
+ let withOrders = get(optEv, 'withOrders', false)
154
+
155
+ //ft
156
+ let ft = getFirstTouch(tp, sl)
157
+ let win = ft.win
158
+ let hold = ft.hold
159
+
160
+ //cs, 條件預解析(sym預設'>'、th預設0、opr非'and'/'or'一律'and', 與runStrategy同)
161
+ let cs = conds.map((cond) => {
162
+ let key = get(cond, 'key', '')
163
+ let arr = kpAligned[key]
164
+ if (!arr) {
165
+ throw new Error(`invalid cond.key[${key}] not in opt.serieses`)
166
+ }
167
+ let sym = get(cond, 'sym', '>')
168
+ let th = get(cond, 'th', 0)
169
+ let opr = get(cond, 'opr', null)
170
+ if (opr !== 'and' && opr !== 'or') {
171
+ opr = 'and'
172
+ }
173
+ return { arr, gt: sym === '>', th, isOr: opr === 'or' }
174
+ })
175
+ //orders, 逐棒判斷觸發與結算(依timeStart升序產生, 累計順序與calcOrders一致)
176
+ let orders = []
177
+ let uEquity = uIni
178
+ let uCumu = 0
179
+ for (let i = 0; i < n; i++) {
180
+
181
+ //bTrigger, 任一cond於該棒缺值(NaN)即整棒跳過(等同runStrategy僅於全部cond序列皆有值之共同時間點判斷)
182
+ // 'and'組須全真(空組無約束), 'or'組至少一真(無or組不約束), 與runStrategy合成邏輯同構
183
+ let bSkip = false
184
+ let okAnd = true
185
+ let hasOr = false
186
+ let okOr = false
187
+ for (let c of cs) {
188
+ let y = c.arr[i]
189
+ if (Number.isNaN(y)) {
190
+ bSkip = true
191
+ break
192
+ }
193
+ let b = c.gt ? y > c.th : y < c.th
194
+ if (c.isOr) {
195
+ hasOr = true
196
+ okOr = okOr || b
197
+ }
198
+ else {
199
+ okAnd = okAnd && b
200
+ }
201
+ }
202
+ if (bSkip || !okAnd || (hasOr && !okOr)) {
203
+ continue
204
+ }
205
+
206
+ //進場, 該棒Close
207
+ let c0 = C[i]
208
+ let w = win[i]
209
+
210
+ //o, 訂單紀錄(預設精簡欄位供calcOrdersSummarySimple使用; withOrders時與ordersFromBars同構)
211
+ let o
212
+ if (withOrders) {
213
+ o = {
214
+ mode,
215
+ timeStart: time[i],
216
+ priceStart: c0,
217
+ uTrade,
218
+ rTakeProfit: tp,
219
+ priceTakeProfit: mode === 'long' ? (1 + tp) * c0 : (1 - tp) * c0,
220
+ rStopLoss: sl,
221
+ priceStopLoss: mode === 'long' ? (1 - sl) * c0 : (1 + sl) * c0,
222
+ timeEnd: '',
223
+ priceEnd: '',
224
+ modeResult: '',
225
+ rFee,
226
+ uFee,
227
+ uProfitOrLoss: '',
228
+ rProfitOrLoss: '',
229
+ uCumuProfitOrLoss: '',
230
+ rCumuProfitOrLoss: '',
231
+ uEquity: '',
232
+ uTradeAll: '',
233
+ rTradeAll: '',
234
+ }
235
+ }
236
+ else {
237
+ o = {
238
+ timeStart: time[i],
239
+ timeEnd: '',
240
+ uTrade,
241
+ uProfitOrLoss: '',
242
+ uEquity: '',
243
+ modeResult: '',
244
+ }
245
+ }
246
+
247
+ //結算(win>=0為已觸發, 未觸發者留空=未平倉, 計入numTrade不計入已結績效)
248
+ if (w >= 0) {
249
+ let priceEnd
250
+ if (w === 1) {
251
+ priceEnd = mode === 'long' ? (1 + tp) * c0 : (1 - tp) * c0 //止盈線
252
+ }
253
+ else {
254
+ priceEnd = mode === 'long' ? (1 - sl) * c0 : (1 + sl) * c0 //止損線
255
+ }
256
+ let u = settleOrderPnl(mode, uTrade, c0, priceEnd, uFee)
257
+ uCumu += u
258
+ uEquity += u
259
+ o.timeEnd = time[i + hold[i]]
260
+ o.uProfitOrLoss = u
261
+ o.uEquity = uEquity
262
+ o.modeResult = u < 0 ? 'loss' : 'profit'
263
+ if (withOrders) {
264
+ o.priceEnd = priceEnd
265
+ o.rProfitOrLoss = u / uTrade
266
+ o.uCumuProfitOrLoss = uCumu
267
+ o.rCumuProfitOrLoss = dig((uCumu / uIni) * 100, 2) + '%'
268
+ }
269
+ }
270
+
271
+ //push
272
+ orders.push(o)
273
+
274
+ }
275
+
276
+ //summary, 直接調用官方calcOrdersSummarySimple(orders已依timeStart升序故skipSort)
277
+ let summary = calcOrdersSummarySimple(ott, uIni, orders, timeOhlcStart, timeOhlcEnd, { skipSort: true })
278
+
279
+ //withOrders時回傳訂單陣列供除錯對照
280
+ if (withOrders) {
281
+ return { orders, summary }
282
+ }
283
+
284
+ return summary
285
+ }
286
+
287
+ return {
288
+ evaluate,
289
+ }
290
+ }
291
+
292
+
293
+ export default buildStrategyFastSession
@@ -100,13 +100,16 @@ let calcOrdersSummarySimple = (ott, uIni, ordersAll, timeOhlcStart, timeOhlcEnd,
100
100
  cum += delta.get(t); cumAt.set(t, cum)
101
101
  }
102
102
  let uTradeAllMax = 0
103
+ let rTradeAllMax = '' //無訂單(峰值從未更新)時維持'' (對齊原版初始值語義, 原版僅於uTradeAllMax被更新時賦值)
103
104
  for (let o of ordersAll) {
104
105
  let v = Math.abs(cumAt.get(o.timeStart))
105
- if (v > uTradeAllMax) uTradeAllMax = v
106
+ if (v > uTradeAllMax) {
107
+ uTradeAllMax = v
108
+ rTradeAllMax = dig(uTradeAllMax / uIni * 100, 2) + '%'
109
+ }
106
110
  }
107
111
 
108
112
  let rWin = dig(nWin / numTradeFin * 100, 2) + '%'
109
- let rTradeAllMax = dig(uTradeAllMax / uIni * 100, 2) + '%'
110
113
 
111
114
  let btDays = ott(timeOhlcEnd).diff(ott(timeOhlcStart), 'day')
112
115
  let btYears = btDays / 365
@@ -3,6 +3,7 @@ import fs from 'fs'
3
3
  import dig from 'wsemi/src/dig.mjs'
4
4
  import isestr from 'wsemi/src/isestr.mjs'
5
5
  import fsIsFile from 'wsemi/src/fsIsFile.mjs'
6
+ import settleOrderPnl from './settleOrderPnl.mjs'
6
7
 
7
8
 
8
9
  /**
@@ -95,9 +96,7 @@ let loadKLinesTpSl = (fpJson) => {
95
96
  }
96
97
  o.timeEnd = r.timeEnd
97
98
  o.priceEnd = r.win === 1 ? o.priceTakeProfit : o.priceStopLoss
98
- o.uProfitOrLoss = o.mode === 'long'
99
- ? o.uTrade * (o.priceEnd / o.priceStart) - o.uTrade - 2 * o.uFee
100
- : (o.priceStart - o.priceEnd) * (o.uTrade / o.priceStart) - 2 * o.uFee
99
+ o.uProfitOrLoss = settleOrderPnl(o.mode, o.uTrade, o.priceStart, o.priceEnd, o.uFee)
101
100
  o.rProfitOrLoss = o.uProfitOrLoss / o.uTrade
102
101
  uCumu += o.uProfitOrLoss
103
102
  o.uCumuProfitOrLoss = uCumu
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 計算單筆訂單之淨盈虧金額(USDT)
3
+ *
4
+ * 結算公式與calcOrders逐字對齊(bit-exact): long為uTrade*(priceEnd/priceStart)-uTrade-2*uFee,short為(priceStart-priceEnd)*(uTrade/priceStart)-2*uFee
5
+ * 供loadKLinesTpSl之ordersFromBars與buildStrategyFastSession共用,避免套件內出現第三份手抄公式
6
+ *
7
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
8
+ * @function
9
+ * @param {String} mode 輸入交易方向字串,'long'或'short'
10
+ * @param {Number} uTrade 輸入下單金額(USDT)
11
+ * @param {Number} priceStart 輸入進場價格
12
+ * @param {Number} priceEnd 輸入出場價格
13
+ * @param {Number} uFee 輸入單邊手續費(USDT),結算扣2*uFee
14
+ * @returns {Number} 回傳淨盈虧金額(USDT),含手續費
15
+ * @example
16
+ *
17
+ * console.log(settleOrderPnl('long', 100, 100, 105, 0.05))
18
+ * // => 4.9
19
+ *
20
+ */
21
+ let settleOrderPnl = (mode, uTrade, priceStart, priceEnd, uFee) => {
22
+ return mode === 'long'
23
+ ? uTrade * (priceEnd / priceStart) - uTrade - 2 * uFee
24
+ : (priceStart - priceEnd) * (uTrade / priceStart) - 2 * uFee
25
+ }
26
+
27
+
28
+ export default settleOrderPnl
@@ -25,6 +25,7 @@ describe('WTradeBacktest', function() {
25
25
  'genKLinesTpSl',
26
26
  'buildKLinesTpSl',
27
27
  'loadKLinesTpSl',
28
+ 'buildStrategyFastSession',
28
29
  ]
29
30
  assert.deepStrictEqual(Object.keys(WTradeBacktest), keys)
30
31
  for (let k of keys) {