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,125 @@
1
+ import get from 'lodash-es/get.js'
2
+ import sample from 'lodash-es/sample.js'
3
+ import isestr from 'wsemi/src/isestr.mjs'
4
+ import estimComps from './estimComps.mjs'
5
+
6
+
7
+ /**
8
+ * 以隨機之交易方向與指標組成求解最佳策略
9
+ *
10
+ * 供批次探索用之進入點,未指定時mode由'long'與'short'隨機取一,comps由18種預設組成隨機取一,故重複呼叫即可隨機探索各種策略
11
+ * 未指定時timeStart預設為'2022-07-01T00:00:00',timeEnd則依mode給予不同值: 做多為'2025-04-08T00:00:00'(須涵蓋暴跌數據),做空為'2025-10-06T00:00:00'(須涵蓋暴漲數據),藉此提高率定難度
12
+ * fdOhlc、fdParam與fdData無效時throw
13
+ *
14
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-estimFinder.test.mjs Github}
15
+ * @function
16
+ * @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件,可用src/ott.mjs
17
+ * @param {Object} st 輸入設定物件,需可由opt.keySettings取得name、symbol與interval欄位
18
+ * @param {String} fdOhlc 輸入儲存K線(ohlc)序列資料夾字串
19
+ * @param {String} fdParam 輸入儲存指標參數序列資料夾字串
20
+ * @param {String} fdData 輸入儲存策略資料夾字串,不存在時自動建立
21
+ * @param {Object} [opt={}] 輸入設定物件,預設{},全部欄位皆傳遞至estimComps
22
+ * @param {String} [opt.mode=''] 輸入交易方向字串,可選'long'或'short',未給予時隨機取一,預設''
23
+ * @param {String} [opt.timeStart='2022-07-01T00:00:00'] 輸入回測起始時間字串,預設'2022-07-01T00:00:00'
24
+ * @param {String} [opt.timeEnd=''] 輸入回測結束時間字串,未給予時做多為'2025-04-08T00:00:00'、做空為'2025-10-06T00:00:00',預設''
25
+ * @param {String} [opt.comps=''] 輸入指標組成字串,例如'2,1,0',未給予時由18種預設組成隨機取一,預設''
26
+ * @returns {Promise} 回傳Promise,resolve為undefined
27
+ * @example
28
+ *
29
+ * import ott from './src/ott.mjs'
30
+ *
31
+ * let st = {
32
+ * btc4hr: { name: 'btc', symbol: 'BTCUSDT', interval: '4hr' },
33
+ * }
34
+ *
35
+ * //mode與comps皆隨機, 適合以迴圈重複呼叫批次探索
36
+ * await estimFinder(ott, st, './data/ohlc', './data/param', './data/strategy', {
37
+ * keySettings: 'btc4hr',
38
+ * })
39
+ *
40
+ */
41
+ let estimFinder = async (ott, st, fdOhlc, fdParam, fdData, opt = {}) => {
42
+
43
+ if (!isestr(fdOhlc)) {
44
+ throw new Error(`invalid fdOhlc[${fdOhlc}]`)
45
+ }
46
+ if (!isestr(fdParam)) {
47
+ throw new Error(`invalid fdParam[${fdParam}]`)
48
+ }
49
+ if (!isestr(fdData)) {
50
+ throw new Error(`invalid fdData[${fdData}]`)
51
+ }
52
+
53
+ let useMode = get(opt, 'mode', null)
54
+ let useTimeStart = get(opt, 'timeStart', null)
55
+ let useTimeEnd = get(opt, 'timeEnd', null)
56
+ let uesComps = get(opt, 'comps', null)
57
+
58
+ //mode
59
+ let mode = ''
60
+ if (isestr(useMode)) {
61
+ mode = useMode
62
+ }
63
+ else {
64
+ mode = Math.random() < 0.5 ? `long` : `short`
65
+ }
66
+
67
+ //timeStart
68
+ let timeStart = '2022-07-01T00:00:00'
69
+ if (isestr(useTimeStart)) {
70
+ timeStart = useTimeStart
71
+ }
72
+
73
+ //timeEnd
74
+ let timeEnd = ''
75
+ if (isestr(useTimeEnd)) {
76
+ timeEnd = useTimeEnd
77
+ }
78
+ else {
79
+ if (mode === 'long') {
80
+ timeEnd = '2025-04-08T00:00:00' //做多策略須使用至4/8暴跌數據, 提高難度 [tag:提高率定難度]
81
+ }
82
+ else {
83
+ timeEnd = '2025-10-06T00:00:00' //做空策略須使用至10/6暴漲數據, 增加難度 [tag:提高率定難度]
84
+ }
85
+ }
86
+
87
+ //comps
88
+ let compsAll = [
89
+
90
+ '2,0,0',
91
+ '3,0,0',
92
+ '4,0,0',
93
+ '2,1,0',
94
+ '3,1,0',
95
+ '4,1,0',
96
+ '2,2,0',
97
+ '3,2,0',
98
+ '4,2,0',
99
+
100
+ '2,0,1',
101
+ '3,0,1',
102
+ '4,0,1',
103
+ '2,1,1',
104
+ '3,1,1',
105
+ '4,1,1',
106
+ '2,2,1',
107
+ '3,2,1',
108
+ '4,2,1',
109
+
110
+ ]
111
+ let comps = ''
112
+ if (isestr(uesComps)) {
113
+ comps = uesComps
114
+ }
115
+ else {
116
+ comps = sample(compsAll) //例如comps='2,1,0', 此為挑2種norm, 1種index, 0種volumn
117
+ }
118
+
119
+ //estimComps
120
+ await estimComps(ott, st, fdOhlc, fdParam, timeStart, timeEnd, mode, comps, fdData, opt)
121
+
122
+ }
123
+
124
+
125
+ export default estimFinder
@@ -0,0 +1,499 @@
1
+ import path from 'path'
2
+ import each from 'lodash-es/each.js'
3
+ import get from 'lodash-es/get.js'
4
+ import map from 'lodash-es/map.js'
5
+ import round from 'lodash-es/round.js'
6
+ import size from 'lodash-es/size.js'
7
+ import cint from 'wsemi/src/cint.mjs'
8
+ import dig from 'wsemi/src/dig.mjs'
9
+ import fsCreateFolder from 'wsemi/src/fsCreateFolder.mjs'
10
+ import fsDeleteFile from 'wsemi/src/fsDeleteFile.mjs'
11
+ import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
12
+ import fsWriteJson from 'wsemi/src/fsWriteJson.mjs'
13
+ import haskey from 'wsemi/src/haskey.mjs'
14
+ import isearr from 'wsemi/src/isearr.mjs'
15
+ import isestr from 'wsemi/src/isestr.mjs'
16
+ import isnum from 'wsemi/src/isnum.mjs'
17
+ import rang from 'wsemi/src/rang.mjs'
18
+ import wround from 'wsemi/src/round.mjs'
19
+ import omlPSO from 'w-optimization/src/omlPSO.mjs'
20
+ import dataProvide from 'w-data-tdprovide'
21
+ import runStrategy from 'w-trade-backtest/src/runStrategy.mjs'
22
+ import readStrategies from './readStrategies.mjs'
23
+ import genTid from './genTid.mjs'
24
+ import genSid from './genSid.mjs'
25
+ import p2r from './p2r.mjs'
26
+ import calcLevelNumTrade from './calcLevelNumTrade.mjs'
27
+ import calcFitness from './calcFitness.mjs'
28
+ import genStrategyFileName from './genStrategyFileName.mjs'
29
+
30
+
31
+ /**
32
+ * 針對指定keys率定各key之門檻與止盈止損,求解最佳策略
33
+ *
34
+ * 設計變數為[止損比例, 止盈比例, ...各key之門檻],止損與止盈由opt.thsSl與opt.thsTp給予之百分比清單離散取值
35
+ * 各key門檻取值範圍為[-thLim,thLim]切為thLim*20段,並各自平移+vdir與-vdir(vdir=thLim*5)為兩組,使解值恆大於0代表'>'條件、恆小於0代表'<'條件,還原時再扣除平移值,故單一設計變數即同時涵蓋大於與小於兩種條件
36
+ * 以omlPSO粒子群最佳化求解,各次計算皆以w-trade-backtest之runStrategy回測並經calcFitness計算適應值
37
+ * 各次計算結果若同時滿足交易次數、勝率與最終等效年化盈虧三門檻,則以genStrategyFileName產生檔名存入fdData;同tid且同交易次數級距(tkid)已有策略時,僅於最終等效年化盈虧更佳時抽換,並刪除較差者
38
+ * fdOhlc、fdParam、timeStart、timeEnd、keys、fdData無效或mode非'long'或'short'時throw
39
+ *
40
+ * Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-estimKeys.test.mjs Github}
41
+ * @function
42
+ * @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件,可用src/ott.mjs
43
+ * @param {Object} st 輸入設定物件,需可由opt.keySettings取得name、symbol與interval欄位
44
+ * @param {String} fdOhlc 輸入儲存K線(ohlc)序列資料夾字串
45
+ * @param {String} fdParam 輸入儲存指標參數序列資料夾字串
46
+ * @param {String} timeStart 輸入回測起始時間字串,格式'YYYY-MM-DDTHH:mm:ss'
47
+ * @param {String} timeEnd 輸入回測結束時間字串,格式'YYYY-MM-DDTHH:mm:ss'
48
+ * @param {String} mode 輸入交易方向字串,可選'long'或'short'
49
+ * @param {Array} keys 輸入待率定門檻之指標key字串陣列
50
+ * @param {String} fdData 輸入儲存策略資料夾字串,不存在時自動建立
51
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
52
+ * @param {String} opt.keySettings 輸入取用st內設定之鍵字串,例如'btc4hr'代表取用st.btc4hr.name、st.btc4hr.symbol與st.btc4hr.interval
53
+ * @param {Array} [opt.thsTp=[1,2,3,4,5,6,7,8,9,10]] 輸入止盈門檻百分比數值陣列,預設[1,2,3,4,5,6,7,8,9,10]
54
+ * @param {Array} [opt.thsSl=[1,2,3,4,5,6,7,8,9,10]] 輸入止損門檻百分比數值陣列,預設[1,2,3,4,5,6,7,8,9,10]
55
+ * @param {Number} [opt.thNumTrade=1] 輸入策略須儲存之最少交易次數數值,預設1
56
+ * @param {Number} [opt.thRWin=0.5] 輸入策略須儲存之最低勝率比例數值,預設0.5
57
+ * @param {Number} [opt.thREquivalentCumuProfitOrLossFinalNormYear=0.15] 輸入策略須儲存之最低最終等效年化盈虧比例數值,預設0.15
58
+ * @param {Boolean} [opt.useShowLog=false] 輸入是否顯示求解過程log布林值,預設false
59
+ * @returns {Promise} 回傳Promise,resolve為omlPSO之求解結果物件,含bestSolution、stopMode、stopIteration與stopExecutions等欄位
60
+ * @example
61
+ *
62
+ * import ott from './src/ott.mjs'
63
+ *
64
+ * //st, 各設定以keySettings為鍵儲存
65
+ * let st = {
66
+ * btc4hr: {
67
+ * name: 'btc',
68
+ * symbol: 'BTCUSDT',
69
+ * interval: '4hr',
70
+ * },
71
+ * }
72
+ *
73
+ * //keys, 待率定門檻之指標key, 各key於fdParam內須有對應之`${key}.json`
74
+ * let keys = ['btc_price_4hr_ma_1day']
75
+ *
76
+ * let m = await estimKeys(ott, st, './data/ohlc', './data/param', '2022-07-01T00:00:00', '2025-04-08T00:00:00', 'long', keys, './data/strategy', {
77
+ * keySettings: 'btc4hr',
78
+ * thsTp: [1, 2, 3],
79
+ * thsSl: [1, 2, 3],
80
+ * useShowLog: true,
81
+ * })
82
+ * console.log(m.bestSolution.fitness)
83
+ * // => 1.0325
84
+ *
85
+ */
86
+ let estimKeys = async (ott, st, fdOhlc, fdParam, timeStart, timeEnd, mode, keys, fdData, opt = {}) => {
87
+
88
+ if (!isestr(fdOhlc)) {
89
+ throw new Error(`invalid fdOhlc[${fdOhlc}]`)
90
+ }
91
+ if (!isestr(fdParam)) {
92
+ throw new Error(`invalid fdParam[${fdParam}]`)
93
+ }
94
+ if (!isestr(timeStart)) {
95
+ throw new Error(`invalid timeStart[${timeStart}]`)
96
+ }
97
+ if (!isestr(timeEnd)) {
98
+ throw new Error(`invalid timeEnd[${timeEnd}]`)
99
+ }
100
+ if (mode !== 'long' && mode !== 'short') {
101
+ throw new Error(`invalid mode[${mode}]`)
102
+ }
103
+ if (!isearr(keys)) {
104
+ throw new Error(`invalid keys[${keys}]`)
105
+ }
106
+ if (!isestr(fdData)) {
107
+ throw new Error(`invalid fdData[${fdData}]`)
108
+ }
109
+
110
+ let keySettings = get(opt, 'keySettings', '')
111
+ if (!isestr(keySettings)) {
112
+ throw new Error(`invalid opt.keySettings[${keySettings}]`)
113
+ }
114
+ let thsTp = get(opt, 'thsTp', null)
115
+ if (!isearr(thsTp)) {
116
+ thsTp = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
117
+ }
118
+ let thsSl = get(opt, 'thsSl', null)
119
+ if (!isearr(thsSl)) {
120
+ thsSl = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
121
+ }
122
+ let thNumTrade = get(opt, 'thNumTrade', null)
123
+ if (!isnum(thNumTrade)) {
124
+ thNumTrade = 1
125
+ }
126
+ let thRWin = get(opt, 'thRWin', null)
127
+ if (!isnum(thRWin)) {
128
+ thRWin = 0.5
129
+ }
130
+ let thREquivalentCumuProfitOrLossFinalNormYear = get(opt, 'thREquivalentCumuProfitOrLossFinalNormYear', null)
131
+ if (!isnum(thREquivalentCumuProfitOrLossFinalNormYear)) {
132
+ thREquivalentCumuProfitOrLossFinalNormYear = 0.15
133
+ }
134
+ let useShowLog = get(opt, 'useShowLog', null)
135
+
136
+ //provideData
137
+ let provideData = dataProvide(fdOhlc, fdParam)
138
+
139
+ //st
140
+ let name = get(st, `${keySettings}.name`, '')
141
+ let symbol = get(st, `${keySettings}.symbol`, '')
142
+ let interval = get(st, `${keySettings}.interval`, '')
143
+
144
+ //fdData, 儲存策略資料夾, 由呼叫端指定, 不存在則建立
145
+ if (!fsIsFolder(fdData)) {
146
+ fsCreateFolder(fdData)
147
+ }
148
+
149
+ //funGetSeries
150
+ let funGetSeries = await provideData.buildGetTimeSeriesByTimeRange(timeStart, timeEnd)
151
+
152
+ //tid
153
+ let tid = genTid(name, interval, mode, keys)
154
+ // console.log('tid', tid)
155
+
156
+ //vdir, 添加平移值方便後續判識方向
157
+ let thLim = 2
158
+ let vdir = thLim * 5
159
+
160
+ //dps
161
+ let dps = []
162
+ let idx = 0 //額外(非conds)設計變數之個數
163
+ if (true) {
164
+
165
+ //dps, 止損門檻與增量
166
+ if (true) {
167
+ let values
168
+
169
+ //掛單止損門檻(%), 對應 xs[0] = rStopLoss
170
+ values = thsSl
171
+ values = map(values, (v) => {
172
+ return v / 100
173
+ })
174
+ // console.log('values', values)
175
+ dps.push({
176
+ values,
177
+ n: size(values),
178
+ })
179
+ idx++
180
+
181
+ //掛單止盈門檻(%), 對應 xs[1] = rTakeProfit
182
+ values = thsTp
183
+ values = map(values, (v) => {
184
+ return v / 100
185
+ })
186
+ // console.log('values', values)
187
+ dps.push({
188
+ values,
189
+ n: size(values),
190
+ })
191
+ idx++
192
+
193
+ // //每次下單金額(USDT), 因等效年化報酬都是會基於最大持倉去計算, 故下單金額與最大持倉成正比, 故不再須視為參數之一
194
+ // values = [1]
195
+ // // console.log('values', values)
196
+ // dps.push({
197
+ // values,
198
+ // n: size(values),
199
+ // })
200
+ // idx++
201
+
202
+ }
203
+
204
+ //dps, 基於keys
205
+ each(keys, () => {
206
+
207
+ //大於條件
208
+ // let valuesU_fine = rang(0.01, 0.10, 9) //細粒度小尺度區: [0.01, 0.10] step 0.01 (10 點, 給 absP99 < 0.1 的 115 個小尺度 keyOut)
209
+ // let valuesU_main = rang(0.15, 2.0, 37) //主區: [0.15, 2.0] step 0.05 (38 點)
210
+ // let valuesU = [...valuesU_fine, ...valuesU_main] //合計 48 點
211
+ let valuesU = rang(-thLim, thLim, thLim * 20)
212
+ valuesU = map(valuesU, (v) => {
213
+ return v + vdir //添加vdir使全部解皆>0, 方便後續判識轉換
214
+ })
215
+
216
+ //小於條件
217
+ // let valuesL_main = rang(-2.0, -0.15, 37) //主區: [-2.0, -0.15] step 0.05 (38 點)
218
+ // let valuesL_fine = rang(-0.10, -0.01, 9) //細粒度小尺度區: [-0.10, -0.01] step 0.01 (10 點, 給 absP99 < 0.1 的 115 個小尺度 keyOut)
219
+ // let valuesL = [...valuesL_main, ...valuesL_fine] //合計 48 點
220
+ let valuesL = rang(-thLim, thLim, thLim * 20)
221
+ valuesL = map(valuesL, (v) => {
222
+ return v - vdir //添加-vdir使全部解皆<0, 方便後續判識轉換
223
+ })
224
+
225
+ //values
226
+ let values = [
227
+ ...valuesU,
228
+ ...valuesL,
229
+ ]
230
+
231
+ //push
232
+ dps.push({
233
+ values,
234
+ n: size(values),
235
+ })
236
+
237
+ })
238
+
239
+ }
240
+
241
+ //ifun, fitnessMin
242
+ let ifun = 0
243
+ let fitnessMin = 1e20
244
+
245
+ //fun
246
+ let fun = async (xs) => {
247
+ //xs: 各元素為整數指標, 代表key所使用的conds
248
+
249
+ ifun++
250
+
251
+ //rStopLoss, rTakeProfit
252
+ let rStopLoss = xs[0] //止損
253
+ rStopLoss = round(rStopLoss, 3)
254
+ let rTakeProfit = xs[1] //止盈
255
+ rTakeProfit = round(rTakeProfit, 3)
256
+ let uTrade = 1 //原本使用xs[2]
257
+
258
+ //conds
259
+ let conds = map(keys, (key, i) => {
260
+
261
+ //ix
262
+ let ix = i + idx
263
+
264
+ //x
265
+ let x = get(xs, ix, null)
266
+
267
+ //check
268
+ if (x === null) {
269
+ console.log('xs', xs)
270
+ console.log('ix', ix)
271
+ console.log('key', key)
272
+ console.log('i', i)
273
+ throw new Error(`invalid x`)
274
+ }
275
+
276
+ //sym, th
277
+ let sym = ''
278
+ let th = null
279
+ if (x < 0) {
280
+ sym = '<'
281
+ th = x + vdir
282
+ th = wround(th, 3) //門檻小數位要夠多, 否則切細會被抹去
283
+ }
284
+ else {
285
+ sym = '>'
286
+ th = x - vdir
287
+ th = wround(th, 3) //門檻小數位要夠多, 否則切細會被抹去
288
+ }
289
+
290
+ return {
291
+ key,
292
+ sym,
293
+ th,
294
+ }
295
+ })
296
+ // console.log(ifun, 'conds', conds)
297
+
298
+ //sid
299
+ let sid = genSid(name, interval, mode, conds)
300
+ // console.log('sid', sid)
301
+
302
+ //strategy
303
+ let strategy = {
304
+
305
+ tid,
306
+ sid,
307
+
308
+ name,
309
+ symbol,
310
+ interval,
311
+
312
+ keyOhlc: `${name}_price_${interval}`, //OHLC數據
313
+
314
+ mode,
315
+
316
+ conds,
317
+
318
+ settings: {
319
+ uIni: 1000, //初始資金(USDT)
320
+ uTrade, //每次下單金額(USDT)
321
+ rTakeProfit, //止盈
322
+ rStopLoss, //止損
323
+ rFee: 0.0005, //手續費
324
+ },
325
+
326
+ }
327
+
328
+ //runStrategy
329
+ let r = await runStrategy(ott, strategy, funGetSeries)
330
+ // console.log(ifun, 'summary', r.summary)
331
+
332
+ //numTrade
333
+ let numTrade = cint(r.summary.numTrade)
334
+
335
+ //rWin
336
+ let rWin = p2r(r.summary.rWin)
337
+
338
+ // //rCumuProfitOrLossFinal
339
+ // let rCumuProfitOrLossFinal = p2r(r.summary.rCumuProfitOrLossFinal)
340
+
341
+ // //rTradeAllMax
342
+ // let rTradeAllMax = p2r(r.summary.rTradeAllMax)
343
+
344
+ // //rEquivalentCumuProfitOrLossFinal
345
+ // let rEquivalentCumuProfitOrLossFinal = p2r(r.summary.rEquivalentCumuProfitOrLossFinal)
346
+
347
+ //rEquivalentCumuProfitOrLossFinalNormYear
348
+ let rEquivalentCumuProfitOrLossFinalNormYear = p2r(r.summary.rEquivalentCumuProfitOrLossFinalNormYear)
349
+
350
+ //fitness
351
+ let fitness = calcFitness(strategy.settings, r.summary)
352
+
353
+ //res
354
+ let res = {
355
+
356
+ ...strategy,
357
+
358
+ summary: r.summary,
359
+
360
+ fitness,
361
+
362
+ }
363
+ // console.log(ifun, res)
364
+ let resBrief = {
365
+
366
+ '方向': res.mode,
367
+ '每次下單金額': res.settings.uTrade,
368
+ '止盈': dig(res.settings.rTakeProfit * 100, 1) + '%',
369
+ '止損': dig(res.settings.rStopLoss * 100, 1) + '%',
370
+ // '最大持倉金額': res.summary.uTradeAllMax,
371
+ '最大持倉比例': res.summary.rTradeAllMax,
372
+ '交易次數': res.summary.numTrade,
373
+ '最大回撤率': res.summary.rDrawdownMax,
374
+ '勝率': res.summary.rWin,
375
+ '夏普值': dig(res.summary.rSharpe, 4),
376
+ '最終盈虧': res.summary.rCumuProfitOrLossFinal,
377
+ '最終等效盈虧': res.summary.rEquivalentCumuProfitOrLossFinal,
378
+ '最終等效年化盈虧': res.summary.rEquivalentCumuProfitOrLossFinalNormYear,
379
+
380
+ 'fitness': res.fitness,
381
+
382
+ }
383
+ // console.log(ifun, resBrief)
384
+
385
+ //update fitnessMin
386
+ if (fitnessMin > fitness) {
387
+
388
+ if (useShowLog) {
389
+ console.log(ifun, '更新最佳解', resBrief)
390
+ }
391
+
392
+ //update
393
+ fitnessMin = fitness
394
+
395
+ }
396
+
397
+ //儲存可用參數組
398
+ if (
399
+ numTrade >= thNumTrade &&
400
+ rWin >= thRWin &&
401
+ // rCumuProfitOrLossFinal > 0 &&
402
+ // rTradeAllMax <= 0.8 &&
403
+ // rEquivalentCumuProfitOrLossFinal >= 0.3 &&
404
+ rEquivalentCumuProfitOrLossFinalNormYear >= thREquivalentCumuProfitOrLossFinalNormYear && //單策略等效年化報酬率之限制, 不能給太高
405
+ true
406
+ ) {
407
+
408
+ if (useShowLog) {
409
+ console.log(ifun, '可用參數組', resBrief)
410
+ }
411
+
412
+ //kpTid
413
+ let kpTkid = {}
414
+ if (true) {
415
+ let ss = readStrategies(fdData, { readContent: false })
416
+ each(ss, (s) => {
417
+ kpTkid[s.tkid] = s
418
+ })
419
+ }
420
+ // console.log('kpTkid', kpTkid)
421
+
422
+ //levelNumTrade
423
+ let levelNumTrade = calcLevelNumTrade(numTrade)
424
+
425
+ //tkid
426
+ let tkid = `${tid}:${levelNumTrade}`
427
+
428
+ //b
429
+ let b = false
430
+
431
+ //rEquivalentCumuProfitOrLossFinalNormYearNew
432
+ let rEquivalentCumuProfitOrLossFinalNormYearNew = rEquivalentCumuProfitOrLossFinalNormYear
433
+
434
+ //檢查是否已有tid策略
435
+ if (haskey(kpTkid, tkid)) {
436
+
437
+ //rEquivalentCumuProfitOrLossFinalNormYearOld
438
+ let rEquivalentCumuProfitOrLossFinalNormYearOld = p2r(kpTkid[tkid].rEquivalentCumuProfitOrLossFinalNormYear)
439
+
440
+ //check
441
+ if (rEquivalentCumuProfitOrLossFinalNormYearNew > rEquivalentCumuProfitOrLossFinalNormYearOld) {
442
+
443
+ //出現更好的等效盈虧比例, 須抽換
444
+ b = true
445
+
446
+ //先刪除較差tid策略
447
+ if (useShowLog) {
448
+ console.log(`刪除較差策略...`, `${rEquivalentCumuProfitOrLossFinalNormYearNew} > ${rEquivalentCumuProfitOrLossFinalNormYearOld}`, kpTkid[tkid].path)
449
+ }
450
+ fsDeleteFile(kpTkid[tkid].path)
451
+
452
+ }
453
+
454
+ }
455
+ else {
456
+
457
+ //新出現須儲存
458
+ b = true
459
+
460
+ }
461
+
462
+ //save
463
+ if (b) {
464
+
465
+ //fnStrategy
466
+ let fnStrategy = genStrategyFileName(name, interval, mode, keys, strategy.settings, r.summary)
467
+
468
+ //fpStrategy
469
+ let fpStrategy = path.resolve(fdData, fnStrategy)
470
+
471
+ //fsWriteJson
472
+ fsWriteJson(fpStrategy, res, { useFormat: true })
473
+
474
+ }
475
+
476
+ }
477
+
478
+ return fitness
479
+ }
480
+
481
+ //m
482
+ let m = await omlPSO(dps, fun, {
483
+ // funGenerationBefore,
484
+ // funGenerationAfter,
485
+ // funGetBetter,
486
+ })
487
+ if (useShowLog) {
488
+ // console.log('m', m)
489
+ console.log('bestSolution', m.bestSolution)
490
+ console.log('stopMode', m.stopMode)
491
+ console.log('stopIteration', m.stopIteration)
492
+ console.log('stopExecutions', m.stopExecutions)
493
+ }
494
+
495
+ return m
496
+ }
497
+
498
+
499
+ export default estimKeys