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,72 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import genTid from '../src/genTid.mjs'
|
|
3
|
+
import cont from '../src/cont.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
//規格來源: src/genTid.mjs
|
|
7
|
+
// genTid(coin, interval, mode, keys): 以cont.dlmSeps串接mode與各key, 再進行縮寫替換
|
|
8
|
+
// 縮寫替換依序為: 剔除`${coin}_`、剔除`${interval}_`、'price_'轉'p_'、'index_'轉'i_'、
|
|
9
|
+
// 'Ohlc_'轉'o_'、'Close_'轉'c_'、'Volumn_'轉'v_'
|
|
10
|
+
// tid僅由mode與keys決定, 未含門檻
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
let d = cont.dlmSeps
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
describe('genTid', function() {
|
|
17
|
+
|
|
18
|
+
it('以dlmSeps串接mode與各key', function() {
|
|
19
|
+
//無任何可替換樣式時, 僅單純串接
|
|
20
|
+
let tid = genTid('btc', '4hr', 'long', ['aa', 'bb'])
|
|
21
|
+
assert.strictEqual(tid, `long${d}aa${d}bb`)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('剔除key內之coin前綴與interval前綴(各帶底線)', function() {
|
|
25
|
+
let tid = genTid('btc', '4hr', 'long', ['btc_ma', '4hr_rsi'])
|
|
26
|
+
assert.strictEqual(tid, `long${d}ma${d}rsi`)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('price_轉p_, index_轉i_', function() {
|
|
30
|
+
let tid = genTid('btc', '4hr', 'long', ['btc_price_4hr_ma_1day', 'btc_index_rsi_4hr'])
|
|
31
|
+
assert.strictEqual(tid, `long${d}p_ma_1day${d}i_rsi_4hr`)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('Ohlc_轉o_, Close_轉c_, Volumn_轉v_', function() {
|
|
35
|
+
let tid = genTid('btc', '4hr', 'long', ['Ohlc_a', 'Close_b', 'Volumn_c'])
|
|
36
|
+
assert.strictEqual(tid, `long${d}o_a${d}c_b${d}v_c`)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('mode為short時開頭為short', function() {
|
|
40
|
+
let tid = genTid('btc', '4hr', 'short', ['btc_index_rsi_4hr'])
|
|
41
|
+
assert.strictEqual(tid, `short${d}i_rsi_4hr`)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('keys為空陣列時僅回傳mode', function() {
|
|
45
|
+
let tid = genTid('btc', '4hr', 'long', [])
|
|
46
|
+
assert.strictEqual(tid, 'long')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('同組keys不論順序以外之任何門檻皆對應同一tid, 故tid可代表策略種類', function() {
|
|
50
|
+
//genTid未取用門檻, 故僅keys相同即同tid
|
|
51
|
+
let keys = ['btc_price_4hr_ma_1day']
|
|
52
|
+
assert.strictEqual(genTid('btc', '4hr', 'long', keys), genTid('btc', '4hr', 'long', keys))
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('keys順序不同時tid不同', function() {
|
|
56
|
+
let a = genTid('btc', '4hr', 'long', ['btc_ma', 'btc_rsi'])
|
|
57
|
+
let b = genTid('btc', '4hr', 'long', ['btc_rsi', 'btc_ma'])
|
|
58
|
+
assert.notStrictEqual(a, b)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('coin為空字串時替換樣式退化為底線, 將剔除全部底線', function() {
|
|
62
|
+
//替換樣式為`${coin}_`, coin為''時樣式即'_'
|
|
63
|
+
let tid = genTid('', '4hr', 'long', ['btc_ma_1day'])
|
|
64
|
+
assert.strictEqual(tid, `long${d}btcma1day`)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('key未含coin前綴時不受剔除影響', function() {
|
|
68
|
+
let tid = genTid('eth', '1hr', 'long', ['btc_ma'])
|
|
69
|
+
assert.strictEqual(tid, `long${d}btc_ma`)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import p2r from '../src/p2r.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//規格來源: src/p2r.mjs
|
|
6
|
+
// p2r(p): 轉換百分比字串為比例數值
|
|
7
|
+
// 先經wsemi cstr轉字串, 未含'%'時回傳0(涵蓋無任何交易之空字串)
|
|
8
|
+
// 含'%'時剔除第1個'%'後經wsemi cdbl轉數值, 再除以100
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
describe('p2r', function() {
|
|
12
|
+
|
|
13
|
+
it('百分比字串轉為比例數值', function() {
|
|
14
|
+
assert.strictEqual(p2r('31.25%'), 0.3125)
|
|
15
|
+
assert.strictEqual(p2r('100%'), 1)
|
|
16
|
+
assert.strictEqual(p2r('0%'), 0)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('負百分比字串轉為負比例數值', function() {
|
|
20
|
+
assert.strictEqual(p2r('-5%'), -0.05)
|
|
21
|
+
assert.strictEqual(p2r('-71.17%'), -0.7117)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('未含%之字串回傳0', function() {
|
|
25
|
+
//w-trade-backtest於無任何交易時各比例欄位為空字串
|
|
26
|
+
assert.strictEqual(p2r(''), 0)
|
|
27
|
+
assert.strictEqual(p2r('50'), 0)
|
|
28
|
+
assert.strictEqual(p2r('abc'), 0)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('非字串輸入先經cstr轉字串後判斷, 無%故回傳0', function() {
|
|
32
|
+
assert.strictEqual(p2r(50), 0)
|
|
33
|
+
assert.strictEqual(p2r(null), 0)
|
|
34
|
+
assert.strictEqual(p2r(undefined), 0)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('超過100%之字串可轉為大於1之比例', function() {
|
|
38
|
+
assert.strictEqual(p2r('250%'), 2.5)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('%前非數值時經cdbl轉為0', function() {
|
|
42
|
+
assert.strictEqual(p2r('abc%'), 0)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('與calcFitness之用法一致: 勝率百分比字串轉為[0,1]比例', function() {
|
|
46
|
+
//calcFitness以p2r轉rWin後限縮至[0,1]
|
|
47
|
+
assert.strictEqual(p2r('60%'), 0.6)
|
|
48
|
+
assert.strictEqual(p2r('12.5%'), 0.125)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
})
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import readStrategies from '../src/readStrategies.mjs'
|
|
5
|
+
import genStrategyFileName from '../src/genStrategyFileName.mjs'
|
|
6
|
+
import genTid from '../src/genTid.mjs'
|
|
7
|
+
import cont from '../src/cont.mjs'
|
|
8
|
+
import { buildFdTmp } from './unit-setup.mjs'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
//規格來源: src/readStrategies.mjs
|
|
12
|
+
// readStrategies(fd, opt): 列舉fd第1層之檔案並解析策略資訊
|
|
13
|
+
// fd非資料夾時回傳[], 資料夾內無檔案時回傳[]
|
|
14
|
+
// 僅列檔案, 資料夾不列入(fsTreeFolder預設levelLimit=1, 故子資料夾內檔案亦不列入)
|
|
15
|
+
// 主檔名以wsemi replace剔除全部'.json'後, 依cont.dlmPkgs切段, 依序為tid、levelNumTrade與
|
|
16
|
+
// rEquivalentCumuProfitOrLossFinalNormYear, 三者皆為字串, 缺段者為空字串
|
|
17
|
+
// 切不出tid(非wsemi isestr判定之有效字串)者略過
|
|
18
|
+
// tid再依cont.dlmSeps切段, 首段為mode, 其餘為ps
|
|
19
|
+
// tkid為`${tid}:${levelNumTrade}`
|
|
20
|
+
// opt.readContent為true時以data提供檔案內容, 內容非合法json時data為null
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
let fdTmp = buildFdTmp('readStrategies')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
let settings = { uIni: 1000 }
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
//buildFd: 建立空資料夾並回傳路徑
|
|
30
|
+
let buildFd = (fd) => {
|
|
31
|
+
let p = path.resolve(fdTmp, fd)
|
|
32
|
+
fs.mkdirSync(p, { recursive: true })
|
|
33
|
+
return p
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
//writeStrategy: 以genStrategyFileName之規則寫出策略檔
|
|
38
|
+
let writeStrategy = (fdData, keys, numTrade, rEq, content = {}) => {
|
|
39
|
+
let fn = genStrategyFileName('btc', '4hr', 'long', keys, settings, {
|
|
40
|
+
numTrade,
|
|
41
|
+
rEquivalentCumuProfitOrLossFinalNormYear: rEq,
|
|
42
|
+
})
|
|
43
|
+
fs.writeFileSync(path.resolve(fdData, fn), JSON.stringify(content), 'utf8')
|
|
44
|
+
return fn
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
describe('readStrategies', function() {
|
|
49
|
+
|
|
50
|
+
after(function() {
|
|
51
|
+
fs.rmSync(fdTmp, { recursive: true, force: true })
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('檔名解析', function() {
|
|
55
|
+
|
|
56
|
+
it('讀出genStrategyFileName所寫之策略檔, 各欄位可由檔名反解', function() {
|
|
57
|
+
let fd = buildFd('basic')
|
|
58
|
+
let keys = ['btc_price_4hr_ma_1day']
|
|
59
|
+
let fn = writeStrategy(fd, keys, 30, '31.25%')
|
|
60
|
+
|
|
61
|
+
let ss = readStrategies(fd)
|
|
62
|
+
assert.strictEqual(ss.length, 1)
|
|
63
|
+
|
|
64
|
+
let tid = genTid('btc', '4hr', 'long', keys)
|
|
65
|
+
assert.strictEqual(ss[0].name, fn)
|
|
66
|
+
assert.strictEqual(ss[0].tid, tid)
|
|
67
|
+
assert.strictEqual(ss[0].levelNumTrade, '12')
|
|
68
|
+
assert.strictEqual(ss[0].tkid, `${tid}:12`)
|
|
69
|
+
assert.strictEqual(ss[0].rEquivalentCumuProfitOrLossFinalNormYear, '31.25%')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('levelNumTrade與等效年化盈虧皆為字串, 未轉型', function() {
|
|
73
|
+
let fd = buildFd('str')
|
|
74
|
+
writeStrategy(fd, ['aa'], 30, '31.25%')
|
|
75
|
+
|
|
76
|
+
let ss = readStrategies(fd)
|
|
77
|
+
assert.strictEqual(typeof ss[0].levelNumTrade, 'string')
|
|
78
|
+
assert.strictEqual(typeof ss[0].rEquivalentCumuProfitOrLossFinalNormYear, 'string')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('tid再依dlmSeps拆為mode與ps', function() {
|
|
82
|
+
let fd = buildFd('modeps')
|
|
83
|
+
//keys經genTid縮寫後為p_ma_1day與i_rsi_4hr
|
|
84
|
+
writeStrategy(fd, ['btc_price_4hr_ma_1day', 'btc_index_rsi_4hr'], 30, '10%')
|
|
85
|
+
|
|
86
|
+
let ss = readStrategies(fd)
|
|
87
|
+
assert.strictEqual(ss[0].mode, 'long')
|
|
88
|
+
assert.deepStrictEqual(ss[0].ps, ['p_ma_1day', 'i_rsi_4hr'])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('單一key時ps僅一元素', function() {
|
|
92
|
+
let fd = buildFd('ps1')
|
|
93
|
+
writeStrategy(fd, ['btc_price_4hr_ma_1day'], 30, '10%')
|
|
94
|
+
|
|
95
|
+
let ss = readStrategies(fd)
|
|
96
|
+
assert.deepStrictEqual(ss[0].ps, ['p_ma_1day'])
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('等效年化盈虧為負值時亦可正確反解', function() {
|
|
100
|
+
let fd = buildFd('neg')
|
|
101
|
+
writeStrategy(fd, ['aa'], 30, '-71.17%')
|
|
102
|
+
|
|
103
|
+
let ss = readStrategies(fd)
|
|
104
|
+
assert.strictEqual(ss[0].rEquivalentCumuProfitOrLossFinalNormYear, '-71.17%')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('path為絕對路徑, 即使fd以相對路徑給予', function() {
|
|
108
|
+
let fd = buildFd('abs')
|
|
109
|
+
let fn = writeStrategy(fd, ['aa'], 30, '10%')
|
|
110
|
+
|
|
111
|
+
let fdRel = path.relative(process.cwd(), fd)
|
|
112
|
+
let ss = readStrategies(fdRel)
|
|
113
|
+
assert.strictEqual(ss[0].path, path.resolve(fd, fn))
|
|
114
|
+
assert.ok(path.isAbsolute(ss[0].path))
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('同tid不同級距之策略檔各自為一筆, tkid不同', function() {
|
|
118
|
+
let fd = buildFd('levels')
|
|
119
|
+
let keys = ['aa']
|
|
120
|
+
writeStrategy(fd, keys, 20, '10%') //級距11
|
|
121
|
+
writeStrategy(fd, keys, 30, '20%') //級距12
|
|
122
|
+
|
|
123
|
+
let ss = readStrategies(fd)
|
|
124
|
+
assert.strictEqual(ss.length, 2)
|
|
125
|
+
|
|
126
|
+
let tid = genTid('btc', '4hr', 'long', keys)
|
|
127
|
+
let tkids = ss.map((v) => {
|
|
128
|
+
return v.tkid
|
|
129
|
+
}).sort()
|
|
130
|
+
assert.deepStrictEqual(tkids, [`${tid}:11`, `${tid}:12`].sort())
|
|
131
|
+
assert.strictEqual(ss[0].tid, ss[1].tid)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('與genStrategyFileName可往返: 由讀回之欄位可重組原檔名', function() {
|
|
135
|
+
let fd = buildFd('roundtrip')
|
|
136
|
+
let keys = ['btc_price_4hr_ma_1day', 'btc_index_rsi_4hr']
|
|
137
|
+
let fn = writeStrategy(fd, keys, 300, '8.5%')
|
|
138
|
+
|
|
139
|
+
let ss = readStrategies(fd)
|
|
140
|
+
let s = ss[0]
|
|
141
|
+
let fnRebuild = `${s.tid}${cont.dlmPkgs}${s.levelNumTrade}${cont.dlmPkgs}${s.rEquivalentCumuProfitOrLossFinalNormYear}.json`
|
|
142
|
+
assert.strictEqual(fnRebuild, fn)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
describe('非策略檔名之處理', function() {
|
|
148
|
+
|
|
149
|
+
it('不符檔名規則者仍列入, 缺少之段為空字串', function() {
|
|
150
|
+
//僅要求切得出tid, 故單段檔名亦成立
|
|
151
|
+
let fd = buildFd('loose')
|
|
152
|
+
fs.writeFileSync(path.resolve(fd, 'plain.json'), '{}', 'utf8')
|
|
153
|
+
|
|
154
|
+
let ss = readStrategies(fd)
|
|
155
|
+
assert.strictEqual(ss.length, 1)
|
|
156
|
+
assert.strictEqual(ss[0].tid, 'plain')
|
|
157
|
+
assert.strictEqual(ss[0].levelNumTrade, '')
|
|
158
|
+
assert.strictEqual(ss[0].rEquivalentCumuProfitOrLossFinalNormYear, '')
|
|
159
|
+
assert.strictEqual(ss[0].tkid, 'plain:')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('非json副檔名之檔案亦列入, 主檔名未剔除副檔名', function() {
|
|
163
|
+
//僅剔除'.json', 故'.txt'留在tid內
|
|
164
|
+
let fd = buildFd('ext')
|
|
165
|
+
fs.writeFileSync(path.resolve(fd, 'foo.txt'), 'x', 'utf8')
|
|
166
|
+
|
|
167
|
+
let ss = readStrategies(fd)
|
|
168
|
+
assert.strictEqual(ss.length, 1)
|
|
169
|
+
assert.strictEqual(ss[0].tid, 'foo.txt')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('檔名內多處.json皆被剔除', function() {
|
|
173
|
+
let fd = buildFd('multi')
|
|
174
|
+
fs.writeFileSync(path.resolve(fd, 'a.jsonb.json'), '{}', 'utf8')
|
|
175
|
+
|
|
176
|
+
let ss = readStrategies(fd)
|
|
177
|
+
assert.strictEqual(ss[0].tid, 'ab')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('主檔名剔除.json後為空字串者被略過', function() {
|
|
181
|
+
let fd = buildFd('emptyname')
|
|
182
|
+
writeStrategy(fd, ['aa'], 30, '10%')
|
|
183
|
+
fs.writeFileSync(path.resolve(fd, '.json'), '{}', 'utf8')
|
|
184
|
+
|
|
185
|
+
let ss = readStrategies(fd)
|
|
186
|
+
assert.strictEqual(ss.length, 1)
|
|
187
|
+
assert.notStrictEqual(ss[0].tid, '')
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('列舉範圍', function() {
|
|
193
|
+
|
|
194
|
+
it('僅列第1層, 子資料夾與其內檔案皆不列入', function() {
|
|
195
|
+
let fd = buildFd('nested')
|
|
196
|
+
writeStrategy(fd, ['aa'], 30, '10%')
|
|
197
|
+
let fdSub = path.resolve(fd, 'sub')
|
|
198
|
+
fs.mkdirSync(fdSub, { recursive: true })
|
|
199
|
+
writeStrategy(fdSub, ['bb'], 30, '10%')
|
|
200
|
+
|
|
201
|
+
let ss = readStrategies(fd)
|
|
202
|
+
assert.strictEqual(ss.length, 1)
|
|
203
|
+
assert.deepStrictEqual(ss[0].ps, ['aa'])
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('空資料夾回傳空陣列', function() {
|
|
207
|
+
let fd = buildFd('empty')
|
|
208
|
+
assert.deepStrictEqual(readStrategies(fd), [])
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('僅含子資料夾而無檔案時回傳空陣列', function() {
|
|
212
|
+
let fd = buildFd('onlysub')
|
|
213
|
+
fs.mkdirSync(path.resolve(fd, 'sub'), { recursive: true })
|
|
214
|
+
assert.deepStrictEqual(readStrategies(fd), [])
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('資料夾不存在時回傳空陣列', function() {
|
|
218
|
+
assert.deepStrictEqual(readStrategies(path.resolve(fdTmp, 'not-exist-folder')), [])
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('fd非有效字串時回傳空陣列', function() {
|
|
222
|
+
assert.deepStrictEqual(readStrategies(''), [])
|
|
223
|
+
assert.deepStrictEqual(readStrategies(null), [])
|
|
224
|
+
assert.deepStrictEqual(readStrategies(123), [])
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
describe('讀取內容', function() {
|
|
230
|
+
|
|
231
|
+
it('預設不讀取內容, 無data欄位', function() {
|
|
232
|
+
let fd = buildFd('nodata')
|
|
233
|
+
writeStrategy(fd, ['aa'], 30, '10%', { tid: 'x', fitness: 1.5 })
|
|
234
|
+
|
|
235
|
+
let ss = readStrategies(fd)
|
|
236
|
+
assert.strictEqual(ss[0].data, undefined)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('opt.readContent為true時以data提供檔案內容', function() {
|
|
240
|
+
let fd = buildFd('data')
|
|
241
|
+
let content = { tid: 'x', fitness: 1.5, summary: { numTrade: 30 } }
|
|
242
|
+
writeStrategy(fd, ['aa'], 30, '10%', content)
|
|
243
|
+
|
|
244
|
+
let ss = readStrategies(fd, { readContent: true })
|
|
245
|
+
assert.deepStrictEqual(ss[0].data, content)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('內容非合法json時data為null', function() {
|
|
249
|
+
let fd = buildFd('badjson')
|
|
250
|
+
fs.writeFileSync(path.resolve(fd, 'bad.json'), 'not-a-json', 'utf8')
|
|
251
|
+
|
|
252
|
+
let ss = readStrategies(fd, { readContent: true })
|
|
253
|
+
assert.strictEqual(ss[0].data, null)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
})
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//共用fixtures與工具, 各unit-*.test.mjs引用
|
|
6
|
+
// 本檔不帶.test.中綴, 不會被mocha當測試檔抓取
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
//buildFdTmp: 暫存根資料夾, 各測試檔獨立使用./test/tmp-unit-{函數名}, 結束時整夾刪除,
|
|
10
|
+
// 各檔不共用父層, 故平行測試(mocha --parallel)時互不干擾
|
|
11
|
+
let buildFdTmp = (name) => path.resolve(`./test/tmp-unit-${name}`)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
//st: estim系列所需之設定物件, 以keySettings為鍵取用
|
|
15
|
+
let keySettings = 'btc4hr'
|
|
16
|
+
let buildSt = () => {
|
|
17
|
+
return {
|
|
18
|
+
[keySettings]: {
|
|
19
|
+
name: 'btc',
|
|
20
|
+
symbol: 'BTCUSDT',
|
|
21
|
+
interval: '4hr',
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
//keyOhlc: 依estimKeys內`${name}_price_${interval}`規則組出之K線序列key
|
|
28
|
+
let keyOhlc = 'btc_price_4hr'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
//tBase, tStep: 基準起始時間2020-01-01T00:00:00, 每根K線間隔4小時
|
|
32
|
+
let tBase = Date.UTC(2020, 0, 1)
|
|
33
|
+
let tStep = 4 * 3600 * 1000
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
//genTimes: 產生n根K線之時間字串序列, 格式'2020-01-01T00:00:00'
|
|
37
|
+
let genTimes = (n, opt = {}) => {
|
|
38
|
+
let base = opt.base !== undefined ? opt.base : tBase
|
|
39
|
+
let ts = []
|
|
40
|
+
for (let i = 0; i < n; i++) {
|
|
41
|
+
ts.push(new Date(base + i * tStep).toISOString().slice(0, 19))
|
|
42
|
+
}
|
|
43
|
+
return ts
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
//genArrOhlc: 以sin/cos合成之確定性K線(不用Math.random以確保可重現)
|
|
48
|
+
// Open為前一根Close, High/Low各外擴1, 故止盈止損皆有機會觸發
|
|
49
|
+
let genArrOhlc = (n, opt = {}) => {
|
|
50
|
+
let ts = genTimes(n, opt)
|
|
51
|
+
let c = opt.start !== undefined ? opt.start : 100
|
|
52
|
+
let arr = []
|
|
53
|
+
for (let i = 0; i < n; i++) {
|
|
54
|
+
let o = c
|
|
55
|
+
c = Math.max(1, c + Math.sin(i * 0.31) * 2 + Math.cos(i * 0.11) * 1.5)
|
|
56
|
+
arr.push({
|
|
57
|
+
time: ts[i],
|
|
58
|
+
Open: o,
|
|
59
|
+
High: Math.max(o, c) + 1,
|
|
60
|
+
Low: Math.min(o, c) - 1,
|
|
61
|
+
Close: c,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return arr
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
//genArrParam: 以sin合成之確定性指標序列, 值域[-1,1]落於estimKeys門檻範圍[-2,2]內
|
|
69
|
+
let genArrParam = (n, opt = {}) => {
|
|
70
|
+
let ts = genTimes(n, opt)
|
|
71
|
+
let phase = opt.phase !== undefined ? opt.phase : 0
|
|
72
|
+
let arr = []
|
|
73
|
+
for (let i = 0; i < n; i++) {
|
|
74
|
+
arr.push({
|
|
75
|
+
time: ts[i],
|
|
76
|
+
param: Math.sin(i * 0.17 + phase),
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
return arr
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
//buildFdData: 於fdTmp/fd內建立data-ohlc(keyOhlc.json)與data-param(各keysParam一檔)與data-strategy
|
|
84
|
+
// keysParam為[{key, phase}]或[key]
|
|
85
|
+
let buildFdData = (fdTmp, fd, keysParam, opt = {}) => {
|
|
86
|
+
let n = opt.n !== undefined ? opt.n : 30
|
|
87
|
+
|
|
88
|
+
let fdOhlc = path.resolve(fdTmp, fd, 'data-ohlc')
|
|
89
|
+
let fdParam = path.resolve(fdTmp, fd, 'data-param')
|
|
90
|
+
let fdData = path.resolve(fdTmp, fd, 'data-strategy')
|
|
91
|
+
fs.mkdirSync(fdOhlc, { recursive: true })
|
|
92
|
+
fs.mkdirSync(fdParam, { recursive: true })
|
|
93
|
+
|
|
94
|
+
let arrOhlc = genArrOhlc(n, opt)
|
|
95
|
+
fs.writeFileSync(path.resolve(fdOhlc, `${keyOhlc}.json`), JSON.stringify(arrOhlc), 'utf8')
|
|
96
|
+
|
|
97
|
+
keysParam.forEach((v, i) => {
|
|
98
|
+
let key = typeof v === 'string' ? v : v.key
|
|
99
|
+
let arrParam = genArrParam(n, { ...opt, phase: i * 0.5 })
|
|
100
|
+
fs.writeFileSync(path.resolve(fdParam, `${key}.json`), JSON.stringify(arrParam), 'utf8')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
let timeStart = arrOhlc[0].time
|
|
104
|
+
let timeEnd = arrOhlc[n - 1].time
|
|
105
|
+
|
|
106
|
+
return { fdOhlc, fdParam, fdData, timeStart, timeEnd, arrOhlc }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
buildFdTmp,
|
|
112
|
+
keySettings,
|
|
113
|
+
buildSt,
|
|
114
|
+
keyOhlc,
|
|
115
|
+
genTimes,
|
|
116
|
+
genArrOhlc,
|
|
117
|
+
genArrParam,
|
|
118
|
+
buildFdData
|
|
119
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// import path from 'path'
|
|
2
|
+
import rollupFiles from 'w-package-tools/src/rollupFiles.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
let fdSrc = './src'
|
|
6
|
+
let fdTar = './dist'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async function rp() {
|
|
10
|
+
|
|
11
|
+
await rollupFiles({ //rollupFiles預設會clean folder
|
|
12
|
+
fns: 'WTradeSolve.mjs',
|
|
13
|
+
fdSrc,
|
|
14
|
+
fdTar,
|
|
15
|
+
nameDistType: 'kebabCase',
|
|
16
|
+
runin: 'nodejs', //本套件以fs讀寫策略檔與掃描資料夾, 僅能於nodejs執行
|
|
17
|
+
// bNodePolyfill: true,
|
|
18
|
+
// bMinify: false,
|
|
19
|
+
globals: {
|
|
20
|
+
path: 'path',
|
|
21
|
+
fs: 'fs',
|
|
22
|
+
},
|
|
23
|
+
external: [
|
|
24
|
+
'path',
|
|
25
|
+
'fs',
|
|
26
|
+
],
|
|
27
|
+
})
|
|
28
|
+
.catch((err) => {
|
|
29
|
+
console.log(err)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
rp()
|
|
34
|
+
.catch((err) => {
|
|
35
|
+
console.log(err)
|
|
36
|
+
})
|
|
37
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import _ from 'lodash-es'
|
|
3
|
+
import getFiles from 'w-package-tools/src/getFiles.mjs'
|
|
4
|
+
import getPks from 'w-package-tools/src/getPks.mjs'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
let fdSrc = './examples/'
|
|
8
|
+
let fdTar = './docs/examples/'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
function main() {
|
|
12
|
+
//把example裡面cdn更換, 再複製到docs的example內, 作為日後發佈為靜態網站
|
|
13
|
+
|
|
14
|
+
//pks
|
|
15
|
+
let pks = getPks()
|
|
16
|
+
|
|
17
|
+
//url
|
|
18
|
+
let url = `https://cdn.jsdelivr.net/npm/w-trade-solve@${pks.version}/dist/w-trade-solve.umd.js`
|
|
19
|
+
|
|
20
|
+
//mkdirSync
|
|
21
|
+
if (!fs.existsSync(fdTar)) {
|
|
22
|
+
fs.mkdirSync(fdTar)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//getFiles
|
|
26
|
+
let ltfs = getFiles(fdSrc)
|
|
27
|
+
|
|
28
|
+
_.each(ltfs, function(v) {
|
|
29
|
+
|
|
30
|
+
//fn
|
|
31
|
+
let fn = fdSrc + v
|
|
32
|
+
|
|
33
|
+
//c
|
|
34
|
+
let c = fs.readFileSync(fn, 'utf8')
|
|
35
|
+
|
|
36
|
+
//replace
|
|
37
|
+
let r
|
|
38
|
+
r = `../dist/w-trade-solve.umd.js`
|
|
39
|
+
c = c.replace(r, url)
|
|
40
|
+
|
|
41
|
+
//write
|
|
42
|
+
//console.log(c)
|
|
43
|
+
fs.writeFileSync(fdTar + v, c, 'utf8')
|
|
44
|
+
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
// main()
|