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,128 @@
|
|
|
1
|
+
import each from 'lodash-es/each.js'
|
|
2
|
+
import get from 'lodash-es/get.js'
|
|
3
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
4
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
5
|
+
import dataProvide from 'w-data-tdprovide'
|
|
6
|
+
import estimKeys from './estimKeys.mjs'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 由fdParam內全部指標key分類後挑選keys,再率定各key之門檻與止盈止損,求解最佳策略
|
|
11
|
+
*
|
|
12
|
+
* 先由fdParam列舉全部指標key,並依名稱分為3類: 開頭為'index_'者為keysIndex(指數類)、含'_vbs_'者為keysVolumn(成交量類)、其餘為keysNorm(一般類)
|
|
13
|
+
* 分類後各類可經opt內對應之過濾函數再行篩選,接著呼叫funPickKeys由3類挑出待率定之keys,最後交由estimKeys求解
|
|
14
|
+
* fdOhlc、fdParam、timeStart、timeEnd、fdData無效或mode非'long'或'short'或funPickKeys非函數時throw
|
|
15
|
+
*
|
|
16
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-estimPicks.test.mjs Github}
|
|
17
|
+
* @function
|
|
18
|
+
* @param {Function} ott 輸入時區時間函數,傳入時間字串回傳dayjs時間物件,可用src/ott.mjs
|
|
19
|
+
* @param {Object} st 輸入設定物件,需可由opt.keySettings取得name、symbol與interval欄位
|
|
20
|
+
* @param {String} fdOhlc 輸入儲存K線(ohlc)序列資料夾字串
|
|
21
|
+
* @param {String} fdParam 輸入儲存指標參數序列資料夾字串
|
|
22
|
+
* @param {String} timeStart 輸入回測起始時間字串,格式'YYYY-MM-DDTHH:mm:ss'
|
|
23
|
+
* @param {String} timeEnd 輸入回測結束時間字串,格式'YYYY-MM-DDTHH:mm:ss'
|
|
24
|
+
* @param {String} mode 輸入交易方向字串,可選'long'或'short'
|
|
25
|
+
* @param {Function} funPickKeys 輸入挑選keys函數,傳入{keysNorm,keysIndex,keysVolumn}物件,回傳待率定之指標key字串陣列
|
|
26
|
+
* @param {String} fdData 輸入儲存策略資料夾字串,不存在時自動建立
|
|
27
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{},其餘欄位皆傳遞至estimKeys
|
|
28
|
+
* @param {Function} [opt.funFilterKeysNorm=null] 輸入過濾一般類key之async函數,傳入keysNorm陣列回傳過濾後陣列,預設null
|
|
29
|
+
* @param {Function} [opt.funFilterKeysIndex=null] 輸入過濾指數類key之async函數,傳入keysIndex陣列回傳過濾後陣列,預設null
|
|
30
|
+
* @param {Function} [opt.funFilterKeysVolumn=null] 輸入過濾成交量類key之async函數,傳入keysVolumn陣列回傳過濾後陣列,預設null
|
|
31
|
+
* @returns {Promise} 回傳Promise,resolve為undefined
|
|
32
|
+
* @example
|
|
33
|
+
*
|
|
34
|
+
* import ott from './src/ott.mjs'
|
|
35
|
+
*
|
|
36
|
+
* let st = {
|
|
37
|
+
* btc4hr: { name: 'btc', symbol: 'BTCUSDT', interval: '4hr' },
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* //funPickKeys, 由一般類挑第1個key為待率定key
|
|
41
|
+
* let funPickKeys = ({ keysNorm, keysIndex, keysVolumn }) => {
|
|
42
|
+
* return [keysNorm[0]]
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* await estimPicks(ott, st, './data/ohlc', './data/param', '2022-07-01T00:00:00', '2025-04-08T00:00:00', 'long', funPickKeys, './data/strategy', {
|
|
46
|
+
* keySettings: 'btc4hr',
|
|
47
|
+
* })
|
|
48
|
+
*
|
|
49
|
+
*/
|
|
50
|
+
let estimPicks = async (ott, st, fdOhlc, fdParam, timeStart, timeEnd, mode, funPickKeys, fdData, opt = {}) => {
|
|
51
|
+
|
|
52
|
+
if (!isestr(fdOhlc)) {
|
|
53
|
+
throw new Error(`invalid fdOhlc[${fdOhlc}]`)
|
|
54
|
+
}
|
|
55
|
+
if (!isestr(fdParam)) {
|
|
56
|
+
throw new Error(`invalid fdParam[${fdParam}]`)
|
|
57
|
+
}
|
|
58
|
+
if (!isestr(timeStart)) {
|
|
59
|
+
throw new Error(`invalid timeStart[${timeStart}]`)
|
|
60
|
+
}
|
|
61
|
+
if (!isestr(timeEnd)) {
|
|
62
|
+
throw new Error(`invalid timeEnd[${timeEnd}]`)
|
|
63
|
+
}
|
|
64
|
+
if (mode !== 'long' && mode !== 'short') {
|
|
65
|
+
throw new Error(`invalid mode[${mode}]`)
|
|
66
|
+
}
|
|
67
|
+
if (!isfun(funPickKeys)) {
|
|
68
|
+
throw new Error(`funPickKeys is not a function`)
|
|
69
|
+
}
|
|
70
|
+
if (!isestr(fdData)) {
|
|
71
|
+
throw new Error(`invalid fdData[${fdData}]`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let funFilterKeysNorm = get(opt, 'funFilterKeysNorm', null)
|
|
75
|
+
let funFilterKeysIndex = get(opt, 'funFilterKeysIndex', null)
|
|
76
|
+
let funFilterKeysVolumn = get(opt, 'funFilterKeysVolumn', null)
|
|
77
|
+
|
|
78
|
+
//provideData
|
|
79
|
+
let provideData = dataProvide(fdOhlc, fdParam)
|
|
80
|
+
|
|
81
|
+
//keysParam
|
|
82
|
+
let keysParam = provideData.getKeysParam()
|
|
83
|
+
// console.log('keysParam', keysParam)
|
|
84
|
+
|
|
85
|
+
//keysIndex, keysVolumn, keysNorm
|
|
86
|
+
let keysIndex = []
|
|
87
|
+
let keysVolumn = []
|
|
88
|
+
let keysNorm = []
|
|
89
|
+
each(keysParam, (v) => {
|
|
90
|
+
let b1 = v.indexOf('index_') === 0
|
|
91
|
+
let b2 = v.indexOf('_vbs_') >= 0
|
|
92
|
+
let b3 = !(b1 || b2)
|
|
93
|
+
if (b1) {
|
|
94
|
+
keysIndex.push(v)
|
|
95
|
+
}
|
|
96
|
+
else if (b2) {
|
|
97
|
+
keysVolumn.push(v)
|
|
98
|
+
}
|
|
99
|
+
else if (b3) {
|
|
100
|
+
keysNorm.push(v)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
// console.log('keysIndex', keysIndex)
|
|
104
|
+
// console.log('keysVolumn', keysVolumn)
|
|
105
|
+
// console.log('keysNorm', keysNorm)
|
|
106
|
+
// console.log(size(keysIndex) + size(keysVolumn) + size(keysNorm))
|
|
107
|
+
|
|
108
|
+
if (isfun(funFilterKeysNorm)) {
|
|
109
|
+
keysNorm = await funFilterKeysNorm(keysNorm)
|
|
110
|
+
}
|
|
111
|
+
if (isfun(funFilterKeysIndex)) {
|
|
112
|
+
keysIndex = await funFilterKeysIndex(keysIndex)
|
|
113
|
+
}
|
|
114
|
+
if (isfun(funFilterKeysVolumn)) {
|
|
115
|
+
keysVolumn = await funFilterKeysVolumn(keysVolumn)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
//keys
|
|
119
|
+
let keys = funPickKeys({ keysNorm, keysIndex, keysVolumn })
|
|
120
|
+
// console.log('keys', keys)
|
|
121
|
+
|
|
122
|
+
//estimKeys
|
|
123
|
+
await estimKeys(ott, st, fdOhlc, fdParam, timeStart, timeEnd, mode, keys, fdData, opt)
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
export default estimPicks
|
package/src/genSid.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import join from 'lodash-es/join.js'
|
|
2
|
+
import map from 'lodash-es/map.js'
|
|
3
|
+
import cont from './cont.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 產生策略識別碼sid
|
|
8
|
+
*
|
|
9
|
+
* 各cond先組為`${key}${sym}${th}`,再以cont.dlmSeps串接mode與各cond為識別字串,最後進行與genTid相同之縮寫替換
|
|
10
|
+
* 縮寫替換依序為: 剔除`${coin}_`、剔除`${interval}_`、'price_'轉'p_'、'index_'轉'i_'、'Ohlc_'轉'o_'、'Close_'轉'c_'、'Volumn_'轉'v_'
|
|
11
|
+
* 因替換係以`${coin}_`與`${interval}_`為樣式,故coin與interval須給予有效字串,否則將剔除全部底線
|
|
12
|
+
* sid已含各cond之門檻,故同組keys之不同門檻組合對應不同sid,可與僅由keys決定之tid區別
|
|
13
|
+
*
|
|
14
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-genSid.test.mjs Github}
|
|
15
|
+
* @function
|
|
16
|
+
* @param {String} coin 輸入幣種名稱字串,例如'btc'
|
|
17
|
+
* @param {String} interval 輸入K線週期字串,例如'4hr'
|
|
18
|
+
* @param {String} mode 輸入交易方向字串,可選'long'或'short'
|
|
19
|
+
* @param {Array} conds 輸入條件物件陣列,各元素為{key,sym,th},key為指標key字串,sym為'>'或'<',th為門檻數值
|
|
20
|
+
* @returns {String} 回傳策略識別碼sid字串
|
|
21
|
+
* @example
|
|
22
|
+
*
|
|
23
|
+
* let conds = [
|
|
24
|
+
* { key: 'btc_price_4hr_ma_1day', sym: '>', th: 0.05 },
|
|
25
|
+
* { key: 'btc_index_rsi_4hr', sym: '<', th: -0.2 },
|
|
26
|
+
* ]
|
|
27
|
+
*
|
|
28
|
+
* let sid = genSid('btc', '4hr', 'long', conds)
|
|
29
|
+
* console.log(sid)
|
|
30
|
+
* // => 'long ║ p_ma_1day>0.05 ║ i_rsi_4hr<-0.2'
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
let genSid = (coin, interval, mode, conds) => {
|
|
34
|
+
let ss = map(conds, (cond) => {
|
|
35
|
+
// key,
|
|
36
|
+
// sym,
|
|
37
|
+
// th,
|
|
38
|
+
return `${cond.key}${cond.sym}${cond.th}`
|
|
39
|
+
})
|
|
40
|
+
let sid = join([mode, ...ss], cont.dlmSeps)
|
|
41
|
+
sid = sid.replaceAll(`${coin}_`, '')
|
|
42
|
+
sid = sid.replaceAll(`${interval}_`, '')
|
|
43
|
+
sid = sid.replaceAll(`price_`, 'p_')
|
|
44
|
+
sid = sid.replaceAll(`index_`, 'i_')
|
|
45
|
+
sid = sid.replaceAll(`Ohlc_`, 'o_')
|
|
46
|
+
sid = sid.replaceAll(`Close_`, 'c_')
|
|
47
|
+
sid = sid.replaceAll(`Volumn_`, 'v_')
|
|
48
|
+
// console.log('sid', sid)
|
|
49
|
+
return sid
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export default genSid
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
2
|
+
import cont from './cont.mjs'
|
|
3
|
+
import genTid from './genTid.mjs'
|
|
4
|
+
import calcLevelNumTrade from './calcLevelNumTrade.mjs'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 產生策略儲存檔名
|
|
9
|
+
*
|
|
10
|
+
* 檔名為`${tid}${cont.dlmPkgs}${levelNumTrade}${cont.dlmPkgs}${rEquivalentCumuProfitOrLossFinalNormYear}.json`
|
|
11
|
+
* tid由genTid以coin、interval、mode與keys產生,levelNumTrade由calcLevelNumTrade以summary.numTrade產生
|
|
12
|
+
* 因tid與levelNumTrade皆入檔名,故可由檔名反解出同組keys與同交易次數級距之既有策略,供readStrategies列舉比較
|
|
13
|
+
* settings目前未參與檔名組成
|
|
14
|
+
*
|
|
15
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-genStrategyFileName.test.mjs Github}
|
|
16
|
+
* @function
|
|
17
|
+
* @param {String} coin 輸入幣種名稱字串,例如'btc'
|
|
18
|
+
* @param {String} interval 輸入K線週期字串,例如'4hr'
|
|
19
|
+
* @param {String} mode 輸入交易方向字串,可選'long'或'short'
|
|
20
|
+
* @param {Array} keys 輸入指標key字串陣列
|
|
21
|
+
* @param {Object} settings 輸入策略設定物件
|
|
22
|
+
* @param {Object} summary 輸入回測統計摘要物件,需含numTrade(交易次數)與rEquivalentCumuProfitOrLossFinalNormYear(最終等效年化盈虧百分比字串)欄位
|
|
23
|
+
* @returns {String} 回傳策略儲存檔名字串
|
|
24
|
+
* @example
|
|
25
|
+
*
|
|
26
|
+
* let keys = ['btc_price_4hr_ma_1day']
|
|
27
|
+
*
|
|
28
|
+
* let settings = { uIni: 1000, uTrade: 1, rTakeProfit: 0.05, rStopLoss: 0.03, rFee: 0.0005 }
|
|
29
|
+
*
|
|
30
|
+
* let summary = { numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '31.25%' }
|
|
31
|
+
*
|
|
32
|
+
* let fn = genStrategyFileName('btc', '4hr', 'long', keys, settings, summary)
|
|
33
|
+
* console.log(fn)
|
|
34
|
+
* // => 'long ║ p_ma_1day ⊙ 12 ⊙ 31.25%.json'
|
|
35
|
+
*
|
|
36
|
+
*/
|
|
37
|
+
let genStrategyFileName = (coin, interval, mode, keys, settings, summary) => {
|
|
38
|
+
|
|
39
|
+
//tid
|
|
40
|
+
let tid = genTid(coin, interval, mode, keys)
|
|
41
|
+
// console.log('tid', tid)
|
|
42
|
+
|
|
43
|
+
//numTrade
|
|
44
|
+
let numTrade = cint(summary.numTrade)
|
|
45
|
+
|
|
46
|
+
//levelNumTrade
|
|
47
|
+
let levelNumTrade = calcLevelNumTrade(numTrade)
|
|
48
|
+
|
|
49
|
+
//rEquivalentCumuProfitOrLossFinalNormYear
|
|
50
|
+
let rEquivalentCumuProfitOrLossFinalNormYear = summary.rEquivalentCumuProfitOrLossFinalNormYear
|
|
51
|
+
|
|
52
|
+
//fnStrategy
|
|
53
|
+
let fnStrategy = `${tid}${cont.dlmPkgs}${levelNumTrade}${cont.dlmPkgs}${rEquivalentCumuProfitOrLossFinalNormYear}.json`
|
|
54
|
+
|
|
55
|
+
return fnStrategy
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
export default genStrategyFileName
|
package/src/genTid.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import join from 'lodash-es/join.js'
|
|
2
|
+
import cont from './cont.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 產生策略種類識別碼tid
|
|
7
|
+
*
|
|
8
|
+
* 以cont.dlmSeps串接mode與各key為識別字串,再進行縮寫替換以縮短長度
|
|
9
|
+
* 縮寫替換依序為: 剔除`${coin}_`、剔除`${interval}_`、'price_'轉'p_'、'index_'轉'i_'、'Ohlc_'轉'o_'、'Close_'轉'c_'、'Volumn_'轉'v_'
|
|
10
|
+
* 因替換係以`${coin}_`與`${interval}_`為樣式,故coin與interval須給予有效字串,否則將剔除全部底線
|
|
11
|
+
* tid僅由mode與keys決定,未含各key之門檻條件,故同組keys之各門檻組合皆對應同一tid
|
|
12
|
+
*
|
|
13
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-genTid.test.mjs Github}
|
|
14
|
+
* @function
|
|
15
|
+
* @param {String} coin 輸入幣種名稱字串,例如'btc'
|
|
16
|
+
* @param {String} interval 輸入K線週期字串,例如'4hr'
|
|
17
|
+
* @param {String} mode 輸入交易方向字串,可選'long'或'short'
|
|
18
|
+
* @param {Array} keys 輸入指標key字串陣列
|
|
19
|
+
* @returns {String} 回傳策略種類識別碼tid字串
|
|
20
|
+
* @example
|
|
21
|
+
*
|
|
22
|
+
* let tid = genTid('btc', '4hr', 'long', ['btc_price_4hr_ma_1day', 'btc_index_rsi_4hr'])
|
|
23
|
+
* console.log(tid)
|
|
24
|
+
* // => 'long ║ p_ma_1day ║ i_rsi_4hr'
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
let genTid = (coin, interval, mode, keys) => {
|
|
28
|
+
let tid = join([mode, ...keys], cont.dlmSeps)
|
|
29
|
+
tid = tid.replaceAll(`${coin}_`, '')
|
|
30
|
+
tid = tid.replaceAll(`${interval}_`, '')
|
|
31
|
+
tid = tid.replaceAll(`price_`, 'p_')
|
|
32
|
+
tid = tid.replaceAll(`index_`, 'i_')
|
|
33
|
+
tid = tid.replaceAll(`Ohlc_`, 'o_')
|
|
34
|
+
tid = tid.replaceAll(`Close_`, 'c_')
|
|
35
|
+
tid = tid.replaceAll(`Volumn_`, 'v_')
|
|
36
|
+
// console.log('tid', tid)
|
|
37
|
+
return tid
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export default genTid
|
package/src/ott.mjs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import ot from 'dayjs'
|
|
2
|
+
import utc from 'dayjs/plugin/utc.js'
|
|
3
|
+
import timezone from 'dayjs/plugin/timezone.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
ot.extend(utc)
|
|
7
|
+
ot.extend(timezone)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
let TZ = 'Asia/Taipei'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 產生台北時區(Asia/Taipei)之dayjs時間物件
|
|
15
|
+
*
|
|
16
|
+
* 供estimKeys與w-trade-backtest之runStrategy等函數使用,使各處時間換算基準一致
|
|
17
|
+
* 未給予input時回傳當下時間,給予input時以台北時區解析該時間
|
|
18
|
+
*
|
|
19
|
+
* @function
|
|
20
|
+
* @param {String|Number|Date} [input=null] 輸入時間字串、時間數值或時間物件,未給予時代表當下時間,預設null
|
|
21
|
+
* @returns {Object} 回傳台北時區之dayjs時間物件
|
|
22
|
+
* @example
|
|
23
|
+
*
|
|
24
|
+
* console.log(ott('2020-01-01T00:00:00').format('YYYY-MM-DDTHH:mm:ssZ'))
|
|
25
|
+
* // => '2020-01-01T00:00:00+08:00'
|
|
26
|
+
*
|
|
27
|
+
*/
|
|
28
|
+
let ott = (input) => {
|
|
29
|
+
if (input === undefined || input === null) {
|
|
30
|
+
return ot().tz(TZ)
|
|
31
|
+
}
|
|
32
|
+
return ot.tz(input, TZ)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 取得當下台北時間字串,格式'YYYY-MM-DDTHH:mm:ssZ'
|
|
38
|
+
*
|
|
39
|
+
* @function
|
|
40
|
+
* @returns {String} 回傳當下台北時間字串
|
|
41
|
+
* @example
|
|
42
|
+
*
|
|
43
|
+
* console.log(nowTpeStr())
|
|
44
|
+
* // => '2026-08-04T22:08:31+08:00'
|
|
45
|
+
*
|
|
46
|
+
*/
|
|
47
|
+
let nowTpeStr = () => ott().format('YYYY-MM-DDTHH:mm:ssZ')
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 取得當下台北時間字串,格式'YYYYMMDDHHmmss'
|
|
52
|
+
*
|
|
53
|
+
* @function
|
|
54
|
+
* @returns {String} 回傳當下台北時間字串
|
|
55
|
+
* @example
|
|
56
|
+
*
|
|
57
|
+
* console.log(nowTpeStrp())
|
|
58
|
+
* // => '20260804220831'
|
|
59
|
+
*
|
|
60
|
+
*/
|
|
61
|
+
let nowTpeStrp = () => ott().format('YYYYMMDDHHmmss')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
export default ott
|
|
65
|
+
export { ott, nowTpeStr, nowTpeStrp }
|
package/src/p2r.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import cstr from 'wsemi/src/cstr.mjs'
|
|
2
|
+
import cdbl from 'wsemi/src/cdbl.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 轉換百分比字串為比例數值
|
|
7
|
+
*
|
|
8
|
+
* 輸入須為含'%'之字串(例如w-trade-backtest之summary內各比例欄位),剔除'%'後除以100為比例值
|
|
9
|
+
* 未含'%'時回傳0,故無任何交易之空字串亦回傳0
|
|
10
|
+
*
|
|
11
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-p2r.test.mjs Github}
|
|
12
|
+
* @function
|
|
13
|
+
* @param {String} p 輸入百分比字串,例如'31.25%'
|
|
14
|
+
* @returns {Number} 回傳比例數值
|
|
15
|
+
* @example
|
|
16
|
+
*
|
|
17
|
+
* console.log(p2r('31.25%'))
|
|
18
|
+
* // => 0.3125
|
|
19
|
+
*
|
|
20
|
+
* console.log(p2r('-5%'))
|
|
21
|
+
* // => -0.05
|
|
22
|
+
*
|
|
23
|
+
* console.log(p2r(''))
|
|
24
|
+
* // => 0
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
let p2r = (p) => {
|
|
28
|
+
p = cstr(p)
|
|
29
|
+
if (p.indexOf('%') < 0) {
|
|
30
|
+
return 0 //未有任何交易時會傳入空字串
|
|
31
|
+
}
|
|
32
|
+
p = p.replace('%', '')
|
|
33
|
+
p = cdbl(p)
|
|
34
|
+
p /= 100
|
|
35
|
+
return p
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export default p2r
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import drop from 'lodash-es/drop.js'
|
|
2
|
+
import each from 'lodash-es/each.js'
|
|
3
|
+
import filter from 'lodash-es/filter.js'
|
|
4
|
+
import get from 'lodash-es/get.js'
|
|
5
|
+
import size from 'lodash-es/size.js'
|
|
6
|
+
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
|
|
7
|
+
import fsReadJson from 'wsemi/src/fsReadJson.mjs'
|
|
8
|
+
import fsTreeFolder from 'wsemi/src/fsTreeFolder.mjs'
|
|
9
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
10
|
+
import replace from 'wsemi/src/replace.mjs'
|
|
11
|
+
import sep from 'wsemi/src/sep.mjs'
|
|
12
|
+
import cont from './cont.mjs'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 讀取儲存策略資料夾內各策略檔資訊
|
|
17
|
+
*
|
|
18
|
+
* 僅列舉fd第1層之檔案,資料夾不列入,fd非資料夾或無任何檔案時回傳空陣列
|
|
19
|
+
* 各檔以主檔名(剔除'.json')依cont.dlmPkgs切段,依序為tid、levelNumTrade與rEquivalentCumuProfitOrLossFinalNormYear,即genStrategyFileName所產生之檔名,切不出tid者予以略過
|
|
20
|
+
* tid再依cont.dlmSeps切段,首段為mode,其餘為各key所組成之ps
|
|
21
|
+
* tkid為`${tid}:${levelNumTrade}`,與estimKeys內用於判斷同組keys與同交易次數級距之組合方式一致
|
|
22
|
+
*
|
|
23
|
+
* Unit Test: {@link https://github.com/yuda-lyu/w-trade-solve/blob/master/test/unit-readStrategies.test.mjs Github}
|
|
24
|
+
* @function
|
|
25
|
+
* @param {String} fd 輸入儲存策略資料夾字串
|
|
26
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
27
|
+
* @param {Boolean} [opt.readContent=false] 輸入是否讀取策略檔內容布林值,為true時各元素額外提供data欄位,內容非合法json時data為null,預設false
|
|
28
|
+
* @returns {Array} 回傳策略資訊陣列,各元素為{path,name,tkid,tid,levelNumTrade,rEquivalentCumuProfitOrLossFinalNormYear,mode,ps},opt.readContent為true時額外提供data
|
|
29
|
+
* @example
|
|
30
|
+
*
|
|
31
|
+
* import fs from 'fs'
|
|
32
|
+
* import cont from './src/cont.mjs'
|
|
33
|
+
*
|
|
34
|
+
* let fd = './tmp/data-strategy'
|
|
35
|
+
* fs.mkdirSync(fd, { recursive: true })
|
|
36
|
+
*
|
|
37
|
+
* //以genStrategyFileName之檔名規則儲存策略檔
|
|
38
|
+
* let fn = `long${cont.dlmSeps}ma_1day${cont.dlmPkgs}12${cont.dlmPkgs}31.25%.json`
|
|
39
|
+
* fs.writeFileSync(`${fd}/${fn}`, JSON.stringify({ mode: 'long' }), 'utf8')
|
|
40
|
+
*
|
|
41
|
+
* console.log(readStrategies(fd))
|
|
42
|
+
* // => [
|
|
43
|
+
* // {
|
|
44
|
+
* // path: './tmp/data-strategy/long ║ ma_1day ⊙ 12 ⊙ 31.25%.json',
|
|
45
|
+
* // name: 'long ║ ma_1day ⊙ 12 ⊙ 31.25%.json',
|
|
46
|
+
* // tkid: 'long ║ ma_1day:12',
|
|
47
|
+
* // tid: 'long ║ ma_1day',
|
|
48
|
+
* // levelNumTrade: '12',
|
|
49
|
+
* // rEquivalentCumuProfitOrLossFinalNormYear: '31.25%',
|
|
50
|
+
* // mode: 'long',
|
|
51
|
+
* // ps: [ 'ma_1day' ]
|
|
52
|
+
* // }
|
|
53
|
+
* // ]
|
|
54
|
+
*
|
|
55
|
+
*/
|
|
56
|
+
let readStrategies = (fd, opt = {}) => {
|
|
57
|
+
|
|
58
|
+
//readContent
|
|
59
|
+
let readContent = get(opt, 'readContent', false)
|
|
60
|
+
|
|
61
|
+
//check
|
|
62
|
+
if (!fsIsFolder(fd)) {
|
|
63
|
+
console.log(`invalid fd[${fd}]`)
|
|
64
|
+
return []
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//vfps
|
|
68
|
+
let vfps = fsTreeFolder(fd)
|
|
69
|
+
vfps = filter(vfps, { isFolder: false })
|
|
70
|
+
// console.log('vfps', vfps)
|
|
71
|
+
|
|
72
|
+
//check
|
|
73
|
+
if (size(vfps) === 0) {
|
|
74
|
+
console.log(`fd[${fd}] no files`)
|
|
75
|
+
return []
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//ss
|
|
79
|
+
let ss = []
|
|
80
|
+
each(vfps, (v) => {
|
|
81
|
+
|
|
82
|
+
//qs
|
|
83
|
+
let name = replace(v.name, '.json', '')
|
|
84
|
+
let qs = sep(name, cont.dlmPkgs)
|
|
85
|
+
|
|
86
|
+
//tid
|
|
87
|
+
let tid = get(qs, 0, '')
|
|
88
|
+
|
|
89
|
+
//check
|
|
90
|
+
if (!isestr(tid)) {
|
|
91
|
+
return true //跳出換下一個
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//levelNumTrade, rEquivalentCumuProfitOrLossFinalNormYear, ps, mode
|
|
95
|
+
let levelNumTrade = get(qs, 1, '')
|
|
96
|
+
let rEquivalentCumuProfitOrLossFinalNormYear = get(qs, 2, '')
|
|
97
|
+
let ps = sep(tid, cont.dlmSeps)
|
|
98
|
+
let mode = get(ps, 0, '')
|
|
99
|
+
ps = drop(ps)
|
|
100
|
+
|
|
101
|
+
//tkid
|
|
102
|
+
let tkid = `${tid}:${levelNumTrade}`
|
|
103
|
+
|
|
104
|
+
//s
|
|
105
|
+
let s = {
|
|
106
|
+
path: v.path,
|
|
107
|
+
name: v.name,
|
|
108
|
+
tkid,
|
|
109
|
+
tid,
|
|
110
|
+
levelNumTrade,
|
|
111
|
+
rEquivalentCumuProfitOrLossFinalNormYear,
|
|
112
|
+
mode,
|
|
113
|
+
ps,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//readContent
|
|
117
|
+
if (readContent) {
|
|
118
|
+
let d = fsReadJson(v.path) //回傳{success}或{error}, 讀取或解析失敗時data為null
|
|
119
|
+
s.data = get(d, 'success', null)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//push
|
|
123
|
+
ss.push(s)
|
|
124
|
+
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
return ss
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
export default readStrategies
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import WTradeSolve from '../src/WTradeSolve.mjs'
|
|
3
|
+
import estimFinder from '../src/estimFinder.mjs'
|
|
4
|
+
import estimComps from '../src/estimComps.mjs'
|
|
5
|
+
import estimPicks from '../src/estimPicks.mjs'
|
|
6
|
+
import estimKeys from '../src/estimKeys.mjs'
|
|
7
|
+
import readStrategies from '../src/readStrategies.mjs'
|
|
8
|
+
import genTid from '../src/genTid.mjs'
|
|
9
|
+
import genSid from '../src/genSid.mjs'
|
|
10
|
+
import genStrategyFileName from '../src/genStrategyFileName.mjs'
|
|
11
|
+
import calcLevelNumTrade from '../src/calcLevelNumTrade.mjs'
|
|
12
|
+
import calcFitness from '../src/calcFitness.mjs'
|
|
13
|
+
import cont from '../src/cont.mjs'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
//規格來源: src/WTradeSolve.mjs
|
|
17
|
+
// 套件對外匯出之集合, 內部工具p2r與供測試使用之ott不列入
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
describe('WTradeSolve', function() {
|
|
21
|
+
|
|
22
|
+
it('匯出項目與順序符合定義', function() {
|
|
23
|
+
assert.deepStrictEqual(Object.keys(WTradeSolve), [
|
|
24
|
+
'estimFinder',
|
|
25
|
+
'estimComps',
|
|
26
|
+
'estimPicks',
|
|
27
|
+
'estimKeys',
|
|
28
|
+
'readStrategies',
|
|
29
|
+
'genTid',
|
|
30
|
+
'genSid',
|
|
31
|
+
'genStrategyFileName',
|
|
32
|
+
'calcLevelNumTrade',
|
|
33
|
+
'calcFitness',
|
|
34
|
+
'cont',
|
|
35
|
+
])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('各匯出項目即各模組之default', function() {
|
|
39
|
+
assert.strictEqual(WTradeSolve.estimFinder, estimFinder)
|
|
40
|
+
assert.strictEqual(WTradeSolve.estimComps, estimComps)
|
|
41
|
+
assert.strictEqual(WTradeSolve.estimPicks, estimPicks)
|
|
42
|
+
assert.strictEqual(WTradeSolve.estimKeys, estimKeys)
|
|
43
|
+
assert.strictEqual(WTradeSolve.readStrategies, readStrategies)
|
|
44
|
+
assert.strictEqual(WTradeSolve.genTid, genTid)
|
|
45
|
+
assert.strictEqual(WTradeSolve.genSid, genSid)
|
|
46
|
+
assert.strictEqual(WTradeSolve.genStrategyFileName, genStrategyFileName)
|
|
47
|
+
assert.strictEqual(WTradeSolve.calcLevelNumTrade, calcLevelNumTrade)
|
|
48
|
+
assert.strictEqual(WTradeSolve.calcFitness, calcFitness)
|
|
49
|
+
assert.strictEqual(WTradeSolve.cont, cont)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('estim系列為async函數, gen與calc系列為同步函數', function() {
|
|
53
|
+
assert.strictEqual(WTradeSolve.estimFinder.constructor.name, 'AsyncFunction')
|
|
54
|
+
assert.strictEqual(WTradeSolve.estimComps.constructor.name, 'AsyncFunction')
|
|
55
|
+
assert.strictEqual(WTradeSolve.estimPicks.constructor.name, 'AsyncFunction')
|
|
56
|
+
assert.strictEqual(WTradeSolve.estimKeys.constructor.name, 'AsyncFunction')
|
|
57
|
+
assert.strictEqual(WTradeSolve.genTid.constructor.name, 'Function')
|
|
58
|
+
assert.strictEqual(WTradeSolve.genSid.constructor.name, 'Function')
|
|
59
|
+
assert.strictEqual(WTradeSolve.genStrategyFileName.constructor.name, 'Function')
|
|
60
|
+
assert.strictEqual(WTradeSolve.calcLevelNumTrade.constructor.name, 'Function')
|
|
61
|
+
assert.strictEqual(WTradeSolve.calcFitness.constructor.name, 'Function')
|
|
62
|
+
assert.strictEqual(WTradeSolve.readStrategies.constructor.name, 'Function')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('cont提供tid與檔名所用之兩種分隔字串', function() {
|
|
66
|
+
assert.strictEqual(typeof WTradeSolve.cont.dlmSeps, 'string')
|
|
67
|
+
assert.strictEqual(typeof WTradeSolve.cont.dlmPkgs, 'string')
|
|
68
|
+
assert.notStrictEqual(WTradeSolve.cont.dlmSeps, WTradeSolve.cont.dlmPkgs)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('cont之分隔字串不含檔名不可用字元, 使策略檔名可寫出', function() {
|
|
72
|
+
//Windows與POSIX共同之檔名不可用字元
|
|
73
|
+
let bad = /[<>:"/\\|?*]/
|
|
74
|
+
assert.doesNotMatch(WTradeSolve.cont.dlmSeps, bad)
|
|
75
|
+
assert.doesNotMatch(WTradeSolve.cont.dlmPkgs, bad)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('內部工具p2r與供測試使用之ott不列入匯出', function() {
|
|
79
|
+
assert.strictEqual(WTradeSolve.p2r, undefined)
|
|
80
|
+
assert.strictEqual(WTradeSolve.ott, undefined)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
})
|