w-trade-solve 1.0.1

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 (88) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +55 -0
  4. package/.jsdoc +25 -0
  5. package/README.md +132 -0
  6. package/SECURITY.md +5 -0
  7. package/babel.config.js +16 -0
  8. package/dist/w-trade-solve.umd.js +19 -0
  9. package/dist/w-trade-solve.umd.js.map +1 -0
  10. package/docs/calcFitness.mjs.html +205 -0
  11. package/docs/calcLevelNumTrade.mjs.html +164 -0
  12. package/docs/cont.mjs.html +94 -0
  13. package/docs/estimComps.mjs.html +190 -0
  14. package/docs/estimFinder.mjs.html +197 -0
  15. package/docs/estimKeys.mjs.html +571 -0
  16. package/docs/estimPicks.mjs.html +200 -0
  17. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  18. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  19. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  20. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  21. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  23. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  24. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  25. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  26. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  27. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  28. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  34. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  35. package/docs/genSid.mjs.html +125 -0
  36. package/docs/genStrategyFileName.mjs.html +131 -0
  37. package/docs/genTid.mjs.html +113 -0
  38. package/docs/global.html +4932 -0
  39. package/docs/index.html +84 -0
  40. package/docs/ott.mjs.html +137 -0
  41. package/docs/p2r.mjs.html +111 -0
  42. package/docs/readStrategies.mjs.html +203 -0
  43. package/docs/scripts/collapse.js +39 -0
  44. package/docs/scripts/commonNav.js +28 -0
  45. package/docs/scripts/linenumber.js +25 -0
  46. package/docs/scripts/nav.js +12 -0
  47. package/docs/scripts/polyfill.js +4 -0
  48. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  49. package/docs/scripts/prettify/lang-css.js +2 -0
  50. package/docs/scripts/prettify/prettify.js +28 -0
  51. package/docs/scripts/search.js +99 -0
  52. package/docs/styles/jsdoc.css +776 -0
  53. package/docs/styles/prettify.css +80 -0
  54. package/g.mjs +110 -0
  55. package/package.json +31 -0
  56. package/script.txt +19 -0
  57. package/src/WTradeSolve.mjs +29 -0
  58. package/src/calcFitness.mjs +133 -0
  59. package/src/calcLevelNumTrade.mjs +92 -0
  60. package/src/cont.mjs +22 -0
  61. package/src/estimComps.mjs +118 -0
  62. package/src/estimFinder.mjs +125 -0
  63. package/src/estimKeys.mjs +499 -0
  64. package/src/estimPicks.mjs +128 -0
  65. package/src/genSid.mjs +53 -0
  66. package/src/genStrategyFileName.mjs +59 -0
  67. package/src/genTid.mjs +41 -0
  68. package/src/ott.mjs +65 -0
  69. package/src/p2r.mjs +39 -0
  70. package/src/readStrategies.mjs +131 -0
  71. package/test/unit-WTradeSolve.test.mjs +83 -0
  72. package/test/unit-calcFitness.test.mjs +121 -0
  73. package/test/unit-calcLevelNumTrade.test.mjs +80 -0
  74. package/test/unit-estimComps.test.mjs +193 -0
  75. package/test/unit-estimFinder.test.mjs +184 -0
  76. package/test/unit-estimKeys.test.mjs +260 -0
  77. package/test/unit-estimPicks.test.mjs +241 -0
  78. package/test/unit-genSid.test.mjs +75 -0
  79. package/test/unit-genStrategyFileName.test.mjs +85 -0
  80. package/test/unit-genTid.test.mjs +72 -0
  81. package/test/unit-p2r.test.mjs +51 -0
  82. package/test/unit-readStrategies.test.mjs +258 -0
  83. package/test/unit-setup.mjs +119 -0
  84. package/toolg/addVersion.mjs +4 -0
  85. package/toolg/cleanFolder.mjs +5 -0
  86. package/toolg/gDistRollup.mjs +37 -0
  87. package/toolg/gDocsExams.mjs +48 -0
  88. package/toolg/modifyReadme.mjs +4 -0
@@ -0,0 +1,80 @@
1
+ .pln {
2
+ color: #ddd;
3
+ }
4
+
5
+ /* string content */
6
+ .str {
7
+ color: #61ce3c;
8
+ }
9
+
10
+ /* a keyword */
11
+ .kwd {
12
+ color: #fbde2d;
13
+ }
14
+
15
+ /* a comment */
16
+ .com {
17
+ color: #aeaeae;
18
+ }
19
+
20
+ /* a type name */
21
+ .typ {
22
+ color: #8da6ce;
23
+ }
24
+
25
+ /* a literal value */
26
+ .lit {
27
+ color: #fbde2d;
28
+ }
29
+
30
+ /* punctuation */
31
+ .pun {
32
+ color: #ddd;
33
+ }
34
+
35
+ /* lisp open bracket */
36
+ .opn {
37
+ color: #000000;
38
+ }
39
+
40
+ /* lisp close bracket */
41
+ .clo {
42
+ color: #000000;
43
+ }
44
+
45
+ /* a markup tag name */
46
+ .tag {
47
+ color: #8da6ce;
48
+ }
49
+
50
+ /* a markup attribute name */
51
+ .atn {
52
+ color: #fbde2d;
53
+ }
54
+
55
+ /* a markup attribute value */
56
+ .atv {
57
+ color: #ddd;
58
+ }
59
+
60
+ /* a declaration */
61
+ .dec {
62
+ color: #EF5050;
63
+ }
64
+
65
+ /* a variable name */
66
+ .var {
67
+ color: #c82829;
68
+ }
69
+
70
+ /* a function name */
71
+ .fun {
72
+ color: #4271ae;
73
+ }
74
+
75
+ /* Specify class=linenums on a pre to get line numbering */
76
+ ol.linenums {
77
+ margin-top: 0;
78
+ margin-bottom: 0;
79
+ padding-bottom: 2px;
80
+ }
package/g.mjs ADDED
@@ -0,0 +1,110 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import ott from './src/ott.mjs'
4
+ import wts from './src/WTradeSolve.mjs'
5
+
6
+
7
+ async function test() {
8
+
9
+ //示範用資料夾, fdOhlc 放 K 線序列, fdParam 放指標參數序列, 各以 `${key}.json` 儲存
10
+ // fdData 為求得策略之儲存資料夾, 不存在時由 estimKeys 自動建立
11
+ let fdOhlc = './tmp/data-ohlc'
12
+ let fdParam = './tmp/data-param'
13
+ let fdData = './tmp/data-strategy'
14
+ fs.rmSync(fdData, { recursive: true, force: true })
15
+ fs.mkdirSync(fdOhlc, { recursive: true })
16
+ fs.mkdirSync(fdParam, { recursive: true })
17
+
18
+ //st, 設定物件, 由 opt.keySettings 指定取用之鍵
19
+ // name 與 interval 用於組出 K 線序列 key(`${name}_price_${interval}`)與縮寫 tid
20
+ let st = {
21
+ btc4hr: {
22
+ name: 'btc',
23
+ symbol: 'BTCUSDT',
24
+ interval: '4hr',
25
+ },
26
+ }
27
+
28
+ //arrOhlc 與 arrParam, 20 根 4hr K 線與對應之指標序列(以 sin/cos 合成之示範資料)
29
+ let arrOhlc = []
30
+ let arrParam = []
31
+ let c = 100
32
+ for (let i = 0; i < 20; i++) {
33
+ let time = new Date(Date.UTC(2020, 0, 1) + i * 4 * 3600 * 1000).toISOString().slice(0, 19)
34
+ let o = c
35
+ c = c + Math.sin(i * 0.31) * 2 + Math.cos(i * 0.11) * 1.5
36
+ arrOhlc.push({ time, Open: o, High: Math.max(o, c) + 1, Low: Math.min(o, c) - 1, Close: c })
37
+ arrParam.push({ time, param: Math.sin(i * 0.17) })
38
+ }
39
+ fs.writeFileSync(path.resolve(fdOhlc, 'btc_price_4hr.json'), JSON.stringify(arrOhlc), 'utf8')
40
+ fs.writeFileSync(path.resolve(fdParam, 'btc_4hr_ma_1day.json'), JSON.stringify(arrParam), 'utf8')
41
+
42
+ //keys, 待率定門檻之指標 key, 各 key 於 fdParam 內須有對應之 `${key}.json`
43
+ let keys = ['btc_4hr_ma_1day']
44
+
45
+ //estimKeys, 以 omlPSO 率定[止損, 止盈, 各 key 門檻], 逐次以 runStrategy 回測並取 fitness 最小者
46
+ // 滿足 thNumTrade / thRWin / thREquivalentCumuProfitOrLossFinalNormYear 三門檻之參數組會存入 fdData
47
+ let m = await wts.estimKeys(ott, st, fdOhlc, fdParam, arrOhlc[0].time, arrOhlc[19].time, 'long', keys, fdData, {
48
+ keySettings: 'btc4hr',
49
+ thsTp: [3], //止盈候選(%), 僅給 1 點使示範較快收斂
50
+ thsSl: [2], //止損候選(%)
51
+ thNumTrade: 5, //至少 5 筆交易才存檔
52
+ thRWin: 0.5, //勝率至少 50% 才存檔
53
+ thREquivalentCumuProfitOrLossFinalNormYear: 0, //等效年化盈虧至少 0 才存檔
54
+ })
55
+
56
+ //設計變數為[止損, 止盈, 各 key 門檻], 故個數為 2 + keys 個數
57
+ // 各 key 門檻之解值帶有平移量 10, 大於 0 代表'>'條件、小於 0 代表'<'條件, 還原時扣除平移量
58
+ // estimKeys 為隨機求解, 故以下各數值每次執行皆不同, 僅結構固定
59
+ console.log(m.bestSolution.ps.length, m.bestSolution.ps)
60
+ // => 3 [
61
+ // { ind: 0, value: 0.02 },
62
+ // { ind: 0, value: 0.03 },
63
+ // { ind: 63, value: -9.799999999999999 }
64
+ // ]
65
+
66
+ //readStrategies, 讀回 fdData 內之策略, tkid 為`${tid}:${交易次數級距}`, 同 tkid 僅保留等效年化盈虧最佳者
67
+ let ss = wts.readStrategies(fdData, { readContent: true })
68
+ console.log(ss.map((s) => {
69
+ return `${s.tkid} → ${s.rEquivalentCumuProfitOrLossFinalNormYear}`
70
+ }))
71
+ // => [
72
+ // 'long ║ ma_1day:1 → 194.67%',
73
+ // 'long ║ ma_1day:11 → 839.50%',
74
+ // 'long ║ ma_1day:2 → 644.83%'
75
+ // ]
76
+
77
+ //策略內容含還原後之 conds(門檻已扣除平移量)與回測所用之 settings
78
+ console.log(ss[0].data.conds, ss[0].data.settings)
79
+ // => [ { key: 'btc_4hr_ma_1day', sym: '<', th: 0.3 } ] {
80
+ // uIni: 1000,
81
+ // uTrade: 1,
82
+ // rTakeProfit: 0.03,
83
+ // rStopLoss: 0.02,
84
+ // rFee: 0.0005
85
+ // }
86
+
87
+ //genTid, 策略種類識別碼, 僅由 mode 與 keys 決定, 並將 `${name}_`、`${interval}_` 等樣式縮寫
88
+ console.log(wts.genTid('btc', '4hr', 'long', keys))
89
+ // => 'long ║ ma_1day'
90
+
91
+ //calcLevelNumTrade, 交易次數之級距代碼, 使不同交易次數規模之策略各自保留最佳者
92
+ console.log(wts.calcLevelNumTrade(3), wts.calcLevelNumTrade(30), wts.calcLevelNumTrade(300))
93
+ // => 1 12 102
94
+
95
+ //genStrategyFileName, 策略儲存檔名, 為`${tid} ⊙ ${級距} ⊙ ${等效年化盈虧}.json`
96
+ console.log(wts.genStrategyFileName('btc', '4hr', 'long', keys, {}, { numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '31.25%' }))
97
+ // => 'long ║ ma_1day ⊙ 12 ⊙ 31.25%.json'
98
+
99
+ //calcFitness, 求解所用之目標函數值, 越小越佳, 由勝率(權重 2)與等效年化盈虧(權重 1)組成
100
+ console.log(wts.calcFitness({ uIni: 1000 }, { numTrade: 30, uEquityFinal: 1200, rWin: '60%', rEquivalentCumuProfitOrLossFinalNormYear: '25%' }))
101
+ // => 1.3
102
+
103
+ }
104
+ test()
105
+ .catch((err) => {
106
+ console.log('catch', err)
107
+ })
108
+
109
+
110
+ //node g.mjs
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "w-trade-solve",
3
+ "version": "1.0.1",
4
+ "main": "dist/w-trade-solve.umd.js",
5
+ "dependencies": {
6
+ "lodash-es": "^4.18.1",
7
+ "w-data-tdprovide": "^1.0.4",
8
+ "w-optimization": "^1.0.22",
9
+ "w-trade-backtest": "^1.0.6",
10
+ "wsemi": "^1.8.67"
11
+ },
12
+ "devDependencies": {
13
+ "w-package-tools": "^1.1.12"
14
+ },
15
+ "scripts": {
16
+ "test": "mocha \"test/unit-*.test.mjs\" --parallel --timeout 60000",
17
+ "deploy": "gh-pages -d docs"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/yuda-lyu/w-trade-solve.git"
22
+ },
23
+ "keywords": [
24
+ "package",
25
+ "tool",
26
+ "csv",
27
+ "nodejs"
28
+ ],
29
+ "author": "yuda-lyu(semisphere)",
30
+ "license": "MIT"
31
+ }
package/script.txt ADDED
@@ -0,0 +1,19 @@
1
+ #node toolg/addVersion.mjs
2
+ node toolg/modifyReadme.mjs
3
+
4
+ node toolg/cleanFolder.mjs
5
+ ./node_modules/.bin/jsdoc -c .jsdoc
6
+
7
+ node toolg/gDocsExams.mjs
8
+
9
+ node toolg/gDistRollup.mjs
10
+
11
+ git add . -A
12
+ git commit -m 'modify: '
13
+ git push origin master:master
14
+
15
+ npm run deploy
16
+
17
+ #npm test
18
+
19
+ #npm publish
@@ -0,0 +1,29 @@
1
+ import estimFinder from './estimFinder.mjs'
2
+ import estimComps from './estimComps.mjs'
3
+ import estimPicks from './estimPicks.mjs'
4
+ import estimKeys from './estimKeys.mjs'
5
+ import readStrategies from './readStrategies.mjs'
6
+ import genTid from './genTid.mjs'
7
+ import genSid from './genSid.mjs'
8
+ import genStrategyFileName from './genStrategyFileName.mjs'
9
+ import calcLevelNumTrade from './calcLevelNumTrade.mjs'
10
+ import calcFitness from './calcFitness.mjs'
11
+ import cont from './cont.mjs'
12
+
13
+
14
+ let r = {
15
+ estimFinder,
16
+ estimComps,
17
+ estimPicks,
18
+ estimKeys,
19
+ readStrategies,
20
+ genTid,
21
+ genSid,
22
+ genStrategyFileName,
23
+ calcLevelNumTrade,
24
+ calcFitness,
25
+ cont,
26
+ }
27
+
28
+
29
+ export default r
@@ -0,0 +1,133 @@
1
+ import cint from 'wsemi/src/cint.mjs'
2
+ import p2r from './p2r.mjs'
3
+
4
+
5
+ /**
6
+ * 計算策略適應值fitness
7
+ *
8
+ * 供最佳化求解使用之目標函數值,數值越小代表策略越佳
9
+ * 由勝率與最終等效年化盈虧兩者組成: 勝率rWin限縮至[0,1],等效年化盈虧rEquivalentCumuProfitOrLossFinalNormYear限縮至[0,0.5]再正規化至[0,1],兩者皆轉為越小越好後,以權重2與1加權求和,故無懲罰時fitness落於[0,3]
10
+ * 交易次數為0時加上懲罰值1000000,最終累計盈虧金額(summary.uEquityFinal-settings.uIni)小於等於0時再加上懲罰值1000000,故兩者皆觸發時可達2000000以上
11
+ *
12
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-calcFitness.test.mjs Github}
13
+ * @function
14
+ * @param {Object} settings 輸入策略設定物件,需含uIni(初始資金)欄位
15
+ * @param {Object} summary 輸入回測統計摘要物件,需含numTrade(交易次數)、uEquityFinal(最終權益金額)、rWin(勝率百分比字串)、rEquivalentCumuProfitOrLossFinalNormYear(最終等效年化盈虧百分比字串)欄位
16
+ * @returns {Number} 回傳適應值數值,越小代表策略越佳
17
+ * @example
18
+ *
19
+ * let settings = { uIni: 1000 }
20
+ *
21
+ * //勝率60%, 等效年化盈虧25%(正規化後0.5), fitness = 2*(1-0.6) + 1*(1-0.5) = 1.3
22
+ * let summary = {
23
+ * numTrade: 30,
24
+ * uEquityFinal: 1200,
25
+ * rWin: '60%',
26
+ * rEquivalentCumuProfitOrLossFinalNormYear: '25%',
27
+ * }
28
+ * console.log(calcFitness(settings, summary))
29
+ * // => 1.3
30
+ *
31
+ * //無交易且無獲利時, 兩懲罰值皆觸發
32
+ * let summaryNone = {
33
+ * numTrade: 0,
34
+ * uEquityFinal: 1000,
35
+ * rWin: '',
36
+ * rEquivalentCumuProfitOrLossFinalNormYear: '',
37
+ * }
38
+ * console.log(calcFitness(settings, summaryNone))
39
+ * // => 2000003
40
+ *
41
+ */
42
+ let calcFitness = (settings, summary) => {
43
+
44
+ //numTrade
45
+ let numTrade = cint(summary.numTrade)
46
+
47
+ //uIni
48
+ let uIni = settings.uIni
49
+
50
+ // //uTradeAllMax
51
+ // let uTradeAllMax = summary.uTradeAllMax
52
+ // // console.log('uTradeAllMax', uTradeAllMax)
53
+
54
+ // //uDrawdownMax
55
+ // let uDrawdownMax = summary.uDrawdownMax
56
+
57
+ //uEquityFinal
58
+ let uEquityFinal = summary.uEquityFinal
59
+
60
+ //uCumuProfitOrLossFinal
61
+ let uCumuProfitOrLossFinal = uEquityFinal - uIni
62
+ // console.log('uCumuProfitOrLossFinal', uCumuProfitOrLossFinal)
63
+
64
+ //rEquivalentCumuProfitOrLossFinalNormYear, 等效年化報酬率
65
+ let rEquivalentCumuProfitOrLossFinalNormYear = p2r(summary.rEquivalentCumuProfitOrLossFinalNormYear)
66
+ rEquivalentCumuProfitOrLossFinalNormYear = Math.min(Math.max(rEquivalentCumuProfitOrLossFinalNormYear, 0), 0.5) //最大限制為年化50% (依實際年化分布調整)
67
+ rEquivalentCumuProfitOrLossFinalNormYear /= 0.5 //正規化最大值1
68
+
69
+ //_rEquivalentCumuProfitOrLossFinalNormYear, 等效年化報酬率越高越好, 須轉換至越低越好
70
+ let _rEquivalentCumuProfitOrLossFinalNormYear = 1 - rEquivalentCumuProfitOrLossFinalNormYear
71
+ // console.log('_rEquivalentCumuProfitOrLossFinalNormYear', _rEquivalentCumuProfitOrLossFinalNormYear)
72
+
73
+ // //rCumuProfitOrLossFinal
74
+ // let rCumuProfitOrLossFinal = p2r(summary.rCumuProfitOrLossFinal)
75
+
76
+ // //_rCumuProfitOrLossFinal, 盈虧越高須轉越低越好
77
+ // let _rCumuProfitOrLossFinal = 3000 - Math.max(rCumuProfitOrLossFinal, 0)
78
+ // if (_rCumuProfitOrLossFinal < 0) {
79
+ // throw new Error(`最終盈虧比例[${_rCumuProfitOrLossFinal}]非預期過高`)
80
+ // }
81
+ // if (rCumuProfitOrLossFinal <= 0) {
82
+ // _rCumuProfitOrLossFinal += 300000 - rCumuProfitOrLossFinal //懲罰值
83
+ // }
84
+
85
+ // //rDrawdownMax
86
+ // let rDrawdownMax = p2r(summary.rDrawdownMax)
87
+ // rDrawdownMax = Math.min(Math.max(rDrawdownMax, 0), 1)
88
+
89
+ // //_rDrawdownMax, 最大回撤率不用轉換, 越低越好
90
+ // let _rDrawdownMax = rDrawdownMax
91
+
92
+ // //rSharpe
93
+ // let rSharpe = w.cdbl(summary.rSharpe)
94
+
95
+ // //_rSharpe, 夏普值越高須轉越低越好
96
+ // let _rSharpe = 5 - Math.min(Math.max(rSharpe, 0), 5) //夏普值理論上可無限大, 故須限制範圍
97
+ // if (_rSharpe < 0) {
98
+ // throw new Error(`夏普值[${rSharpe}]非預期過高`)
99
+ // }
100
+ // if (rSharpe <= 0) {
101
+ // _rSharpe += 100000 - rSharpe //懲罰值
102
+ // }
103
+
104
+ //ratioWin
105
+ let ratioWin = p2r(summary.rWin)
106
+ ratioWin = Math.min(Math.max(ratioWin, 0), 1)
107
+
108
+ //_ratioWin, 勝率越高越好, 須轉換至越低越好
109
+ let _ratioWin = 1 - ratioWin
110
+
111
+ // //ratioKeep
112
+ // let ratioKeep = p2r(summary.rTradeAllMax)
113
+
114
+ //fitness
115
+ let fitness = 0
116
+ // fitness += 3 * _rCumuProfitOrLossFinal
117
+ // fitness += 2 * _rDrawdownMax
118
+ // fitness += 2 * _rSharpe
119
+ fitness += 2 * _ratioWin
120
+ fitness += 1 * _rEquivalentCumuProfitOrLossFinalNormYear
121
+ if (numTrade === 0) {
122
+ fitness += 1000000 //懲罰值
123
+ }
124
+ if (uCumuProfitOrLossFinal <= 0) {
125
+ fitness += 1000000 //懲罰值
126
+ }
127
+ // console.log('fitness', fitness)
128
+
129
+ return fitness
130
+ }
131
+
132
+
133
+ export default calcFitness
@@ -0,0 +1,92 @@
1
+ import isnum from 'wsemi/src/isnum.mjs'
2
+ import cint from 'wsemi/src/cint.mjs'
3
+
4
+
5
+ /**
6
+ * 計算交易次數所屬級距代碼levelNumTrade
7
+ *
8
+ * 交易次數過少之策略統計意義不足,過多則屬高頻,故以級距分群,使不同交易次數規模之策略各自保留最佳者
9
+ * 級距對應為: <=0為0,1-5為1,6-9為2,10-24為11,25-49為12,50-74為13,75-99為14,100-249為101,250-499為102,500-749為103,750-999為104,1000-1999為1001,2000-2999為1002,3000-3999為1003,4000-4999為1004,>=5000為100
10
+ * numTrade非數值時throw
11
+ *
12
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-calcLevelNumTrade.test.mjs Github}
13
+ * @function
14
+ * @param {Number} numTrade 輸入交易次數數值
15
+ * @returns {Number} 回傳級距代碼數值
16
+ * @example
17
+ *
18
+ * console.log(calcLevelNumTrade(0))
19
+ * // => 0
20
+ *
21
+ * console.log(calcLevelNumTrade(3))
22
+ * // => 1
23
+ *
24
+ * console.log(calcLevelNumTrade(30))
25
+ * // => 12
26
+ *
27
+ * console.log(calcLevelNumTrade(8000))
28
+ * // => 100
29
+ *
30
+ */
31
+ let calcLevelNumTrade = (numTrade) => {
32
+
33
+ if (!isnum(numTrade)) {
34
+ throw new Error(`numTrade[${numTrade}] is not a number`)
35
+ }
36
+ numTrade = cint(numTrade)
37
+
38
+ let levelNumTrade = 0
39
+ if (numTrade <= 0) {
40
+ levelNumTrade = 0
41
+ }
42
+ else if (numTrade <= 5) {
43
+ levelNumTrade = 1
44
+ }
45
+ else if (numTrade <= 10 - 1) {
46
+ levelNumTrade = 2
47
+ }
48
+ else if (numTrade <= 25 - 1) {
49
+ levelNumTrade = 11
50
+ }
51
+ else if (numTrade <= 50 - 1) {
52
+ levelNumTrade = 12
53
+ }
54
+ else if (numTrade <= 75 - 1) {
55
+ levelNumTrade = 13
56
+ }
57
+ else if (numTrade <= 100 - 1) {
58
+ levelNumTrade = 14
59
+ }
60
+ else if (numTrade <= 250 - 1) {
61
+ levelNumTrade = 101
62
+ }
63
+ else if (numTrade <= 500 - 1) {
64
+ levelNumTrade = 102
65
+ }
66
+ else if (numTrade <= 750 - 1) {
67
+ levelNumTrade = 103
68
+ }
69
+ else if (numTrade <= 1000 - 1) {
70
+ levelNumTrade = 104
71
+ }
72
+ else if (numTrade <= 2000 - 1) {
73
+ levelNumTrade = 1001
74
+ }
75
+ else if (numTrade <= 3000 - 1) {
76
+ levelNumTrade = 1002
77
+ }
78
+ else if (numTrade <= 4000 - 1) {
79
+ levelNumTrade = 1003
80
+ }
81
+ else if (numTrade <= 5000 - 1) {
82
+ levelNumTrade = 1004
83
+ }
84
+ else {
85
+ levelNumTrade = 100
86
+ }
87
+
88
+ return levelNumTrade
89
+ }
90
+
91
+
92
+ export default calcLevelNumTrade
package/src/cont.mjs ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 共用常數
3
+ *
4
+ * dlmSeps為tid與sid內各段(mode與各key或各cond)之分隔字串
5
+ * dlmPkgs為策略檔名內各段(tid、levelNumTrade、rEquivalentCumuProfitOrLossFinalNormYear)之分隔字串
6
+ *
7
+ * @name cont
8
+ * @const
9
+ * @type {Object}
10
+ * @example
11
+ *
12
+ * console.log(cont)
13
+ * // => { dlmSeps: ' ║ ', dlmPkgs: ' ⊙ ' }
14
+ *
15
+ */
16
+ let r = {
17
+ dlmSeps: ' ║ ',
18
+ dlmPkgs: ' ⊙ ',
19
+ }
20
+
21
+
22
+ export default r
@@ -0,0 +1,118 @@
1
+ import get from 'lodash-es/get.js'
2
+ import map from 'lodash-es/map.js'
3
+ import size from 'lodash-es/size.js'
4
+ import cint from 'wsemi/src/cint.mjs'
5
+ import isestr from 'wsemi/src/isestr.mjs'
6
+ import randomIntsNdpRange from 'wsemi/src/randomIntsNdpRange.mjs'
7
+ import sep from 'wsemi/src/sep.mjs'
8
+ import estimPicks from './estimPicks.mjs'
9
+
10
+
11
+ /**
12
+ * 依指標組成comps隨機挑選keys,再率定各key之門檻與止盈止損,求解最佳策略
13
+ *
14
+ * comps為以逗號分隔之3個數字字串,依序代表由一般類(keysNorm)、指數類(keysIndex)與成交量類(keysVolumn)各隨機挑選之key個數,例如'2,1,0'代表挑2個一般類、1個指數類與0個成交量類
15
+ * 各類皆以randomIntsNdpRange不重複隨機取樣,故同一comps每次執行所挑之keys不同
16
+ * comps非3段時throw,其餘檢核與分類邏輯同estimPicks
17
+ *
18
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-estimComps.test.mjs Github}
19
+ * @function
20
+ * @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件,可用src/ott.mjs
21
+ * @param {Object} st 輸入設定物件,需可由opt.keySettings取得name、symbol與interval欄位
22
+ * @param {String} fdOhlc 輸入儲存K線(ohlc)序列資料夾字串
23
+ * @param {String} fdParam 輸入儲存指標參數序列資料夾字串
24
+ * @param {String} timeStart 輸入回測起始時間字串,格式'YYYY-MM-DDTHH:mm:ss'
25
+ * @param {String} timeEnd 輸入回測結束時間字串,格式'YYYY-MM-DDTHH:mm:ss'
26
+ * @param {String} mode 輸入交易方向字串,可選'long'或'short'
27
+ * @param {String} comps 輸入指標組成字串,為逗號分隔之3個數字,例如'2,1,0'
28
+ * @param {String} fdData 輸入儲存策略資料夾字串,不存在時自動建立
29
+ * @param {Object} [opt={}] 輸入設定物件,預設{},全部欄位皆傳遞至estimPicks
30
+ * @returns {Promise} 回傳Promise,resolve為undefined
31
+ * @example
32
+ *
33
+ * import ott from './src/ott.mjs'
34
+ *
35
+ * let st = {
36
+ * btc4hr: { name: 'btc', symbol: 'BTCUSDT', interval: '4hr' },
37
+ * }
38
+ *
39
+ * //comps='2,1,0', 挑2種一般類、1種指數類與0種成交量類
40
+ * await estimComps(ott, st, './data/ohlc', './data/param', '2022-07-01T00:00:00', '2025-04-08T00:00:00', 'long', '2,1,0', './data/strategy', {
41
+ * keySettings: 'btc4hr',
42
+ * })
43
+ *
44
+ */
45
+ let estimComps = async (ott, st, fdOhlc, fdParam, timeStart, timeEnd, mode, comps, fdData, opt = {}) => {
46
+
47
+ if (!isestr(fdOhlc)) {
48
+ throw new Error(`invalid fdOhlc[${fdOhlc}]`)
49
+ }
50
+ if (!isestr(fdParam)) {
51
+ throw new Error(`invalid fdParam[${fdParam}]`)
52
+ }
53
+ if (!isestr(timeStart)) {
54
+ throw new Error(`invalid timeStart[${timeStart}]`)
55
+ }
56
+ if (!isestr(timeEnd)) {
57
+ throw new Error(`invalid timeEnd[${timeEnd}]`)
58
+ }
59
+ if (mode !== 'long' && mode !== 'short') {
60
+ throw new Error(`invalid mode[${mode}]`)
61
+ }
62
+ if (!isestr(comps)) {
63
+ throw new Error(`invalid comps[${comps}]`)
64
+ }
65
+ if (!isestr(fdData)) {
66
+ throw new Error(`invalid fdData[${fdData}]`)
67
+ }
68
+
69
+ //cs, 例如comps='2,1,0', 此為挑2種norm, 1種index, 0種volumn
70
+ let cs = sep(comps, ',')
71
+ if (size(cs) !== 3) {
72
+ throw new Error(`comps[${comps}]非預期`)
73
+ }
74
+ cs = map(cs, cint)
75
+ // console.log('cs', cs)
76
+
77
+ //pkn
78
+ let pkn = (arr, n) => {
79
+ let up = size(arr) - 1
80
+ let ks = randomIntsNdpRange(0, up, n)
81
+ let rs = map(ks, (k) => {
82
+ return get(arr, k)
83
+ })
84
+ return rs
85
+ }
86
+
87
+ //funPickKeys
88
+ let funPickKeys = ({ keysNorm, keysIndex, keysVolumn }) => {
89
+ let keys = []
90
+ if (cs[0] >= 1) {
91
+ keys = [
92
+ ...keys,
93
+ ...pkn(keysNorm, cs[0]),
94
+ ]
95
+ }
96
+ if (cs[1] >= 1) {
97
+ keys = [
98
+ ...keys,
99
+ ...pkn(keysIndex, cs[1]),
100
+ ]
101
+ }
102
+ if (cs[2] >= 1) {
103
+ keys = [
104
+ ...keys,
105
+ ...pkn(keysVolumn, cs[2]),
106
+ ]
107
+ }
108
+ // console.log('keys', keys)
109
+ return keys
110
+ }
111
+
112
+ //estimPicks
113
+ await estimPicks(ott, st, fdOhlc, fdParam, timeStart, timeEnd, mode, funPickKeys, fdData, opt)
114
+
115
+ }
116
+
117
+
118
+ export default estimComps