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,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
+ })