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