w-trade-backtest 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/w-trade-backtest.umd.js +2 -27
  2. package/dist/w-trade-backtest.umd.js.map +1 -1
  3. package/docs/buildKLinesTpSl.mjs.html +218 -0
  4. package/docs/calcOrders.mjs.html +2 -2
  5. package/docs/calcOrdersRatio.mjs.html +2 -2
  6. package/docs/calcOrdersSummary.mjs.html +2 -2
  7. package/docs/calcOrdersSummarySimple.mjs.html +2 -2
  8. package/docs/calcSummary.mjs.html +2 -2
  9. package/docs/closeAndSummaryOrders.mjs.html +7 -7
  10. package/docs/genKLinesTpSl.mjs.html +154 -0
  11. package/docs/genReport.mjs.html +34 -27
  12. package/docs/genReportCore.mjs.html +5 -5
  13. package/docs/global.html +1274 -165
  14. package/docs/index.html +2 -2
  15. package/docs/loadKLinesTpSl.mjs.html +189 -0
  16. package/docs/runStrategies.mjs.html +2 -2
  17. package/docs/runStrategiesAndBacktest.mjs.html +7 -7
  18. package/docs/runStrategy.mjs.html +2 -2
  19. package/package.json +2 -3
  20. package/src/WTradeBacktest.mjs +6 -0
  21. package/src/buildKLinesTpSl.mjs +146 -0
  22. package/src/closeAndSummaryOrders.mjs +5 -5
  23. package/src/genKLinesTpSl.mjs +82 -0
  24. package/src/genReport.mjs +32 -25
  25. package/src/genReportCore.mjs +3 -3
  26. package/src/loadKLinesTpSl.mjs +117 -0
  27. package/src/runStrategiesAndBacktest.mjs +5 -5
  28. package/test/unit-WTradeBacktest.test.mjs +23 -643
  29. package/test/unit-buildKLinesTpSl.test.mjs +98 -0
  30. package/test/unit-calcOrders.test.mjs +60 -0
  31. package/test/unit-calcOrdersRatio.test.mjs +39 -0
  32. package/test/unit-calcOrdersSummary.test.mjs +71 -0
  33. package/test/unit-calcOrdersSummarySimple.test.mjs +30 -0
  34. package/test/unit-calcSummary.test.mjs +30 -0
  35. package/test/unit-closeAndSummaryOrders.test.mjs +53 -0
  36. package/test/unit-genKLinesTpSl.test.mjs +46 -0
  37. package/test/unit-genReport.test.mjs +113 -0
  38. package/test/unit-genReportCore.test.mjs +39 -0
  39. package/test/unit-loadKLinesTpSl.test.mjs +158 -0
  40. package/test/unit-ott.test.mjs +20 -0
  41. package/test/unit-runStrategies.test.mjs +30 -0
  42. package/test/unit-runStrategiesAndBacktest.test.mjs +114 -0
  43. package/test/unit-runStrategy.test.mjs +53 -0
  44. package/test/unit-setup.mjs +120 -0
  45. package/src/readJson.mjs +0 -26
  46. package/src/writeJson.mjs +0 -31
  47. package/src/writeTxt.mjs +0 -19
package/src/genReport.mjs CHANGED
@@ -1,10 +1,17 @@
1
1
  import path from 'path'
2
- import _ from 'lodash-es'
3
- import w from 'wsemi'
2
+ import find from 'lodash-es/find.js'
3
+ import get from 'lodash-es/get.js'
4
+ import map from 'lodash-es/map.js'
5
+ import max from 'lodash-es/max.js'
6
+ import min from 'lodash-es/min.js'
7
+ import isearr from 'wsemi/src/isearr.mjs'
8
+ import isestr from 'wsemi/src/isestr.mjs'
9
+ import isnum from 'wsemi/src/isnum.mjs'
10
+ import fsIsFile from 'wsemi/src/fsIsFile.mjs'
11
+ import fsReadJson from 'wsemi/src/fsReadJson.mjs'
12
+ import fsWriteJson from 'wsemi/src/fsWriteJson.mjs'
4
13
  import calcSummary from './calcSummary.mjs'
5
14
  import genReportCore from './genReportCore.mjs'
6
- import readJson from './readJson.mjs'
7
- import writeJson from './writeJson.mjs'
8
15
 
9
16
 
10
17
  /**
@@ -61,57 +68,57 @@ import writeJson from './writeJson.mjs'
61
68
  let genReport = async (ott, fpOrders, opt = {}) => {
62
69
 
63
70
  //check
64
- if (!w.isestr(fpOrders)) {
71
+ if (!isestr(fpOrders)) {
65
72
  throw new Error(`invalid fpOrders`)
66
73
  }
67
- if (!w.fsIsFile(fpOrders)) {
74
+ if (!fsIsFile(fpOrders)) {
68
75
  throw new Error(`fpOrders[${fpOrders}] is not a file`)
69
76
  }
70
77
 
71
- //orders
72
- let orders = readJson(fpOrders)
73
- if (!w.isearr(orders)) {
78
+ //orders, fsReadJson回傳{success}或{error}, 讀取或解析失敗時取不到success
79
+ let orders = get(fsReadJson(fpOrders), 'success', null)
80
+ if (!isearr(orders)) {
74
81
  throw new Error(`orders in fpOrders[${fpOrders}] is not an effective array`)
75
82
  }
76
83
 
77
84
  //fpOut
78
- let fpOut = _.get(opt, 'fpOut', '')
79
- if (!w.isestr(fpOut)) {
85
+ let fpOut = get(opt, 'fpOut', '')
86
+ if (!isestr(fpOut)) {
80
87
  fpOut = path.resolve(path.dirname(fpOrders), 'report.html')
81
88
  }
82
89
 
83
90
  //name
84
- let name = _.get(opt, 'name', '')
85
- if (!w.isestr(name)) {
91
+ let name = get(opt, 'name', '')
92
+ if (!isestr(name)) {
86
93
  name = path.basename(path.dirname(path.resolve(fpOrders)))
87
94
  }
88
95
 
89
96
  //uIni, 未給則由首筆已結算單推回(calcOrders 定義 uEquity = uIni + uCumuProfitOrLoss)
90
- let uIni = _.get(opt, 'uIni', null)
91
- if (!w.isnum(uIni)) {
92
- let o = _.find(orders, (o) => w.isnum(o.uEquity) && w.isnum(o.uCumuProfitOrLoss))
97
+ let uIni = get(opt, 'uIni', null)
98
+ if (!isnum(uIni)) {
99
+ let o = find(orders, (o) => isnum(o.uEquity) && isnum(o.uCumuProfitOrLoss))
93
100
  uIni = o ? o.uEquity - o.uCumuProfitOrLoss : 1000
94
101
  }
95
102
 
96
103
  //timeOhlcStart, timeOhlcEnd, 未給則由訂單起訖推得
97
- let timeOhlcStart = _.get(opt, 'timeOhlcStart', '')
98
- if (!w.isestr(timeOhlcStart)) {
99
- timeOhlcStart = _.min(_.map(orders, 'timeStart'))
104
+ let timeOhlcStart = get(opt, 'timeOhlcStart', '')
105
+ if (!isestr(timeOhlcStart)) {
106
+ timeOhlcStart = min(map(orders, 'timeStart'))
100
107
  }
101
- let timeOhlcEnd = _.get(opt, 'timeOhlcEnd', '')
102
- if (!w.isestr(timeOhlcEnd)) {
103
- timeOhlcEnd = _.max(_.map(orders, (o) => w.isestr(o.timeEnd) ? o.timeEnd : o.timeStart))
108
+ let timeOhlcEnd = get(opt, 'timeOhlcEnd', '')
109
+ if (!isestr(timeOhlcEnd)) {
110
+ timeOhlcEnd = max(map(orders, (o) => isestr(o.timeEnd) ? o.timeEnd : o.timeStart))
104
111
  }
105
112
 
106
113
  //withWriteSummary
107
- let withWriteSummary = _.get(opt, 'withWriteSummary', false)
114
+ let withWriteSummary = get(opt, 'withWriteSummary', false)
108
115
 
109
116
  //calcSummary
110
117
  let summary = await calcSummary(ott, uIni, orders, timeOhlcStart, timeOhlcEnd)
111
118
 
112
- //writeJson
119
+ //fsWriteJson
113
120
  if (withWriteSummary) {
114
- writeJson(path.resolve(path.dirname(fpOut), 'summary.json'), { name, summary }, { structured: true })
121
+ fsWriteJson(path.resolve(path.dirname(fpOut), 'summary.json'), { name, summary }, { useFormat: true })
115
122
  }
116
123
 
117
124
  //genReportCore
@@ -1,7 +1,7 @@
1
1
  import path from 'path'
2
2
  import fs from 'fs'
3
3
  import { fileURLToPath } from 'url'
4
- import writeTxt from './writeTxt.mjs'
4
+ import fsWriteText from 'wsemi/src/fsWriteText.mjs'
5
5
 
6
6
 
7
7
  //模板資產(tmp.html, render*.js)與本模組同資料夾, 以模組位置解析, 不依賴 cwd
@@ -47,8 +47,8 @@ let genReportCore = (r, fpOut) => {
47
47
  h = h.replace(`{orders}`, JSON.stringify(r.orders))
48
48
  h = h.replace(`{summary}`, JSON.stringify(r.summary))
49
49
 
50
- //writeTxt
51
- writeTxt(fpOut, h)
50
+ //fsWriteText
51
+ fsWriteText(fpOut, h)
52
52
 
53
53
  }
54
54
 
@@ -0,0 +1,117 @@
1
+ import path from 'path'
2
+ import fs from 'fs'
3
+ import dig from 'wsemi/src/dig.mjs'
4
+ import isestr from 'wsemi/src/isestr.mjs'
5
+ import fsIsFile from 'wsemi/src/fsIsFile.mjs'
6
+
7
+
8
+ /**
9
+ * 載入止盈止損先觸結果快取檔並提供訂單重建
10
+ *
11
+ * 讀取buildKLinesTpSl產出之快取json,提供ordersFromBars依指定mode與tpsl組合與進場時間陣列重建訂單陣列
12
+ * 重建訂單之盈虧與累計欄位公式逐欄對齊calcOrders,可直接餵入calcOrdersSummary或calcOrdersSummarySimple
13
+ * fpJson非有效字串或非檔案時throw
14
+ *
15
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-backtest/blob/master/test/unit-WTradeBacktest.test.mjs Github}
16
+ * @function
17
+ * @param {String} fpJson 輸入buildKLinesTpSl產出之快取json檔案路徑字串
18
+ * @returns {Object} 回傳物件,內含meta(快取設定物件)、bars(以進場時間為key之結果物件)與ordersFromBars(mode, tpsl, entryTimes, opt)訂單重建函數,函數之opt可給uTrade(每單金額,預設1)、uIni(初始資金,預設1000)、rFee(手續費率,預設0.0005)、maxExitTime(出場時間大於等於此值之單視為未平倉,預設null)與skipSort(呼叫端保證entryTimes已升序時可省排序,預設false),回傳訂單陣列,快取內查無之進場時間會被濾除,未結單各盈虧欄位留空
19
+ * @example
20
+ *
21
+ * //fpJson為buildKLinesTpSl產出之快取檔
22
+ * let r = loadKLinesTpSl('./cache/kltpsl.json')
23
+ * console.log(r.meta.nBars)
24
+ * // => 6
25
+ *
26
+ * let orders = r.ordersFromBars('long', 5, ['2020-01-01T00:00:00', '2020-01-01T12:00:00'], { uTrade: 100, uIni: 1000 })
27
+ * console.log(orders.map((o) => `${o.timeStart} ${o.timeEnd || 'unsettled'} ${o.modeResult || '-'}`))
28
+ * // => [
29
+ * // '2020-01-01T00:00:00 2020-01-01T04:00:00 profit',
30
+ * // '2020-01-01T12:00:00 unsettled -'
31
+ * // ]
32
+ * console.log(orders[0].priceStart, orders[0].priceEnd, orders[0].uProfitOrLoss, orders[0].uEquity)
33
+ * // => 100 105 4.9 1004.9
34
+ *
35
+ */
36
+ let loadKLinesTpSl = (fpJson) => {
37
+
38
+ //check
39
+ if (!isestr(fpJson)) {
40
+ throw new Error(`invalid fpJson`)
41
+ }
42
+ if (!fsIsFile(fpJson)) {
43
+ throw new Error(`fpJson[${fpJson}] is not a file`)
44
+ }
45
+
46
+ let obj = JSON.parse(fs.readFileSync(path.resolve(fpJson), 'utf8'))
47
+ let meta = obj.meta
48
+ let bars = obj.bars
49
+
50
+ let ordersFromBars = (mode, tpsl, entryTimes, opt = {}) => {
51
+ let { uTrade = 1, uIni = 1000, rFee = 0.0005, maxExitTime = null, skipSort = false } = opt
52
+ let k = `${mode}_${tpsl}`
53
+ let tpr = tpsl / 100
54
+ let uFee = rFee * uTrade
55
+
56
+ //先建全部 order 物件(未結者欄位留空, 對齊 calcOrders 對未平倉單的處理)。
57
+ // skipSort: 呼叫端保證 entryTimes 已升序時省 [...].sort()(熱路徑省數千筆複製+排序); 預設 false 維持原行為。
58
+ let ets = skipSort ? entryTimes : [...entryTimes].sort()
59
+ let orders = ets.filter((t) => bars[t]).map((t) => {
60
+ let c0 = bars[t].close
61
+ return {
62
+ mode,
63
+ timeStart: t,
64
+ priceStart: c0,
65
+ uTrade,
66
+ rTakeProfit: tpr,
67
+ priceTakeProfit: mode === 'long' ? (1 + tpr) * c0 : (1 - tpr) * c0,
68
+ rStopLoss: tpr,
69
+ priceStopLoss: mode === 'long' ? (1 - tpr) * c0 : (1 + tpr) * c0,
70
+ timeEnd: '',
71
+ priceEnd: '',
72
+ modeResult: '',
73
+ rFee,
74
+ uFee,
75
+ uProfitOrLoss: '',
76
+ rProfitOrLoss: '',
77
+ uCumuProfitOrLoss: '',
78
+ rCumuProfitOrLoss: '',
79
+ uEquity: '',
80
+ uTradeAll: '',
81
+ rTradeAll: '',
82
+ }
83
+ })
84
+
85
+ //填已結單(公式對齊 d04 calcOrders)
86
+ let uEquity = uIni
87
+ let uCumu = 0
88
+ for (let o of orders) {
89
+ let r = bars[o.timeStart][k]
90
+ if (!r || r.win < 0 || !r.timeEnd) {
91
+ continue //未結 → 留空(計入 numTrade, 不計入已結績效)
92
+ }
93
+ if (maxExitTime && r.timeEnd >= maxExitTime) {
94
+ continue //區段內尚未觸 → 留未平倉
95
+ }
96
+ o.timeEnd = r.timeEnd
97
+ 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
101
+ o.rProfitOrLoss = o.uProfitOrLoss / o.uTrade
102
+ uCumu += o.uProfitOrLoss
103
+ o.uCumuProfitOrLoss = uCumu
104
+ o.rCumuProfitOrLoss = dig((o.uCumuProfitOrLoss / uIni) * 100, 2) + '%'
105
+ uEquity += o.uProfitOrLoss
106
+ o.uEquity = uEquity
107
+ o.modeResult = o.uProfitOrLoss < 0 ? 'loss' : 'profit'
108
+ }
109
+
110
+ return orders
111
+ }
112
+
113
+ return { meta, bars, ordersFromBars }
114
+ }
115
+
116
+
117
+ export default loadKLinesTpSl
@@ -3,10 +3,10 @@ import get from 'lodash-es/get.js'
3
3
  import size from 'lodash-es/size.js'
4
4
  import isearr from 'wsemi/src/isearr.mjs'
5
5
  import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
6
+ import fsWriteJson from 'wsemi/src/fsWriteJson.mjs'
6
7
  import WDataTdprovide from 'w-data-tdprovide/src/WDataTdprovide.mjs'
7
8
  import runStrategies from './runStrategies.mjs'
8
9
  import genReportCore from './genReportCore.mjs'
9
- import writeJson from './writeJson.mjs'
10
10
 
11
11
 
12
12
  /**
@@ -106,8 +106,8 @@ let runStrategiesAndBacktest = async (ott, fdOhlc, fdParam, strategies, timeStar
106
106
  //fpOrders
107
107
  let fpOrders = path.resolve(fdTest, fnOrders)
108
108
 
109
- //writeJson
110
- writeJson(fpOrders, r.orders, { structured: true })
109
+ //fsWriteJson
110
+ fsWriteJson(fpOrders, r.orders, { useFormat: true })
111
111
 
112
112
  }
113
113
 
@@ -126,8 +126,8 @@ let runStrategiesAndBacktest = async (ott, fdOhlc, fdParam, strategies, timeStar
126
126
  }
127
127
  // console.log('stsm', stsm)
128
128
 
129
- //writeJson
130
- writeJson(fpSummary, stsm, { structured: true })
129
+ //fsWriteJson
130
+ fsWriteJson(fpSummary, stsm, { useFormat: true })
131
131
 
132
132
  console.log('summary', r.summary, size(r.orders))
133
133
  }