w-trade-solve 1.0.2 → 1.0.3
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/README.md +15 -11
- package/dist/w-trade-solve.umd.js +4 -4
- package/dist/w-trade-solve.umd.js.map +1 -1
- package/docs/calcFitness.mjs.html +1 -1
- package/docs/calcLevelNumTrade.mjs.html +1 -1
- package/docs/cont.mjs.html +1 -1
- package/docs/estimComps.mjs.html +16 -11
- package/docs/estimFinder.mjs.html +17 -12
- package/docs/estimKeys.mjs.html +53 -34
- package/docs/estimPicks.mjs.html +17 -12
- package/docs/genSid.mjs.html +1 -1
- package/docs/genStrategyFileName.mjs.html +1 -1
- package/docs/genTid.mjs.html +1 -1
- package/docs/global.html +332 -67
- package/docs/index.html +1 -1
- package/docs/ott.mjs.html +1 -1
- package/docs/p2r.mjs.html +1 -1
- package/docs/readStrategies.mjs.html +1 -1
- package/g.mjs +14 -11
- package/package.json +1 -1
- package/src/estimComps.mjs +15 -10
- package/src/estimFinder.mjs +16 -11
- package/src/estimKeys.mjs +52 -33
- package/src/estimPicks.mjs +16 -11
- package/test/unit-estimComps.test.mjs +34 -18
- package/test/unit-estimFinder.test.mjs +33 -20
- package/test/unit-estimKeys.test.mjs +115 -17
- package/test/unit-estimPicks.test.mjs +27 -17
- package/test/unit-setup.mjs +8 -14
|
@@ -2,11 +2,11 @@ import assert from 'assert'
|
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import estimFinder from '../src/estimFinder.mjs'
|
|
4
4
|
import ott from '../src/ott.mjs'
|
|
5
|
-
import { buildFdTmp, buildFdData,
|
|
5
|
+
import { buildFdTmp, buildFdData, name, symbol, interval } from './unit-setup.mjs'
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
//規格來源: src/estimFinder.mjs
|
|
9
|
-
// estimFinder(ott,
|
|
9
|
+
// estimFinder(ott, name, symbol, interval, fdOhlc, fdParam, fdData, opt):
|
|
10
10
|
// mode未給時由'long'與'short'隨機取一
|
|
11
11
|
// timeStart未給時為'2022-07-01T00:00:00'
|
|
12
12
|
// timeEnd未給時依mode給予: long為'2025-04-08T00:00:00', short為'2025-10-06T00:00:00'
|
|
@@ -19,9 +19,6 @@ import { buildFdTmp, buildFdData, buildSt, keySettings } from './unit-setup.mjs'
|
|
|
19
19
|
let fdTmp = buildFdTmp('estimFinder')
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
let st = buildSt()
|
|
23
|
-
|
|
24
|
-
|
|
25
22
|
//keysParam: norm 4個、index 2個、volumn 1個, 使18種預設comps皆可取樣
|
|
26
23
|
let keysNorm = ['btc_4hr_ma_1day', 'btc_4hr_ma_2day', 'btc_4hr_rsi', 'btc_4hr_kdj']
|
|
27
24
|
let keysIndex = ['index_fear_greed', 'index_dxy']
|
|
@@ -39,13 +36,16 @@ let d = null
|
|
|
39
36
|
//call: 以合法引數為底, 依ov.opt覆寫opt後呼叫
|
|
40
37
|
let call = (ov = {}) => {
|
|
41
38
|
let a = {
|
|
39
|
+
name,
|
|
40
|
+
symbol,
|
|
41
|
+
interval,
|
|
42
42
|
fdOhlc: d.fdOhlc,
|
|
43
43
|
fdParam: d.fdParam,
|
|
44
44
|
fdData: d.fdData,
|
|
45
|
-
opt: {
|
|
45
|
+
opt: { comps: '1,0,0' },
|
|
46
46
|
...ov,
|
|
47
47
|
}
|
|
48
|
-
return estimFinder(ott,
|
|
48
|
+
return estimFinder(ott, a.name, a.symbol, a.interval, a.fdOhlc, a.fdParam, a.fdData, a.opt)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
|
|
@@ -75,19 +75,19 @@ describe('estimFinder', function() {
|
|
|
75
75
|
|
|
76
76
|
it('未給opt.timeStart時使用2022-07-01T00:00:00', async function() {
|
|
77
77
|
//測試資料恰自2022-07-01起算, 故預設timeStart通過而停在timeEnd檢核
|
|
78
|
-
let msg = await catchMsg(call({ opt: {
|
|
78
|
+
let msg = await catchMsg(call({ opt: { comps: '1,0,0', mode: 'long' } }))
|
|
79
79
|
assert.match(msg, /timeEnd/, `實得: ${msg}`)
|
|
80
80
|
assert.doesNotMatch(msg, /timeStartData/, `實得: ${msg}`)
|
|
81
81
|
})
|
|
82
82
|
|
|
83
83
|
it('資料起始晚於預設timeStart時, 錯誤訊息指出預設之timeStart', async function() {
|
|
84
84
|
let dd = buildFdData(fdTmp, 'ts-late', keysNorm, { n: 10, base: Date.UTC(2023, 0, 1) })
|
|
85
|
-
let msg = await catchMsg(estimFinder(ott,
|
|
85
|
+
let msg = await catchMsg(estimFinder(ott, name, symbol, interval, dd.fdOhlc, dd.fdParam, dd.fdData, { comps: '1,0,0', mode: 'long' }))
|
|
86
86
|
assert.match(msg, /timeStart\[2022-07-01T00:00:00\]/, `實得: ${msg}`)
|
|
87
87
|
})
|
|
88
88
|
|
|
89
89
|
it('給予opt.timeStart時以其覆寫預設值', async function() {
|
|
90
|
-
let msg = await catchMsg(call({ opt: {
|
|
90
|
+
let msg = await catchMsg(call({ opt: { comps: '1,0,0', mode: 'long', timeStart: '1990-01-01T00:00:00' } }))
|
|
91
91
|
assert.match(msg, /timeStart\[1990-01-01T00:00:00\]/, `實得: ${msg}`)
|
|
92
92
|
})
|
|
93
93
|
|
|
@@ -96,18 +96,18 @@ describe('estimFinder', function() {
|
|
|
96
96
|
describe('timeEnd預設與覆寫', function() {
|
|
97
97
|
|
|
98
98
|
it('mode為long時預設timeEnd為2025-04-08T00:00:00', async function() {
|
|
99
|
-
let msg = await catchMsg(call({ opt: {
|
|
99
|
+
let msg = await catchMsg(call({ opt: { comps: '1,0,0', mode: 'long' } }))
|
|
100
100
|
assert.match(msg, /timeEnd\[2025-04-08T00:00:00\]/, `實得: ${msg}`)
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
it('mode為short時預設timeEnd為2025-10-06T00:00:00', async function() {
|
|
104
|
-
let msg = await catchMsg(call({ opt: {
|
|
104
|
+
let msg = await catchMsg(call({ opt: { comps: '1,0,0', mode: 'short' } }))
|
|
105
105
|
assert.match(msg, /timeEnd\[2025-10-06T00:00:00\]/, `實得: ${msg}`)
|
|
106
106
|
})
|
|
107
107
|
|
|
108
108
|
it('給予opt.timeEnd時以其覆寫預設值, 不再依mode區分', async function() {
|
|
109
|
-
let msgLong = await catchMsg(call({ opt: {
|
|
110
|
-
let msgShort = await catchMsg(call({ opt: {
|
|
109
|
+
let msgLong = await catchMsg(call({ opt: { comps: '1,0,0', mode: 'long', timeEnd: '2099-01-01T00:00:00' } }))
|
|
110
|
+
let msgShort = await catchMsg(call({ opt: { comps: '1,0,0', mode: 'short', timeEnd: '2099-01-01T00:00:00' } }))
|
|
111
111
|
assert.match(msgLong, /timeEnd\[2099-01-01T00:00:00\]/, `實得: ${msgLong}`)
|
|
112
112
|
assert.match(msgShort, /timeEnd\[2099-01-01T00:00:00\]/, `實得: ${msgShort}`)
|
|
113
113
|
})
|
|
@@ -122,7 +122,7 @@ describe('estimFinder', function() {
|
|
|
122
122
|
//以預設timeEnd反推所用之mode: long對應2025-04-08, short對應2025-10-06
|
|
123
123
|
let got = new Set()
|
|
124
124
|
for (let i = 0; i < 40; i++) {
|
|
125
|
-
let msg = await catchMsg(call({ opt: {
|
|
125
|
+
let msg = await catchMsg(call({ opt: { comps: '1,0,0' } }))
|
|
126
126
|
if (msg.indexOf('timeEnd[2025-04-08T00:00:00]') >= 0) {
|
|
127
127
|
got.add('long')
|
|
128
128
|
}
|
|
@@ -138,7 +138,7 @@ describe('estimFinder', function() {
|
|
|
138
138
|
|
|
139
139
|
it('給予opt.mode時不再隨機', async function() {
|
|
140
140
|
for (let i = 0; i < 10; i++) {
|
|
141
|
-
let msg = await catchMsg(call({ opt: {
|
|
141
|
+
let msg = await catchMsg(call({ opt: { comps: '1,0,0', mode: 'short' } }))
|
|
142
142
|
assert.match(msg, /timeEnd\[2025-10-06T00:00:00\]/, `實得: ${msg}`)
|
|
143
143
|
}
|
|
144
144
|
})
|
|
@@ -148,24 +148,37 @@ describe('estimFinder', function() {
|
|
|
148
148
|
describe('comps預設與覆寫', function() {
|
|
149
149
|
|
|
150
150
|
it('未給opt.comps時由預設組成隨機取一, 挑出之keys非空', async function() {
|
|
151
|
-
//
|
|
151
|
+
//keys非空時通過keys檢核, 續於預設timeEnd超出資料範圍處reject
|
|
152
152
|
for (let i = 0; i < 20; i++) {
|
|
153
|
-
await assert.rejects(call({ opt: {} }), /
|
|
153
|
+
await assert.rejects(call({ opt: {} }), /timeEnd\[/)
|
|
154
154
|
}
|
|
155
155
|
})
|
|
156
156
|
|
|
157
157
|
it('給予opt.comps時以其覆寫, 三段皆為0則未挑任何key', async function() {
|
|
158
|
-
await assert.rejects(call({ opt: {
|
|
158
|
+
await assert.rejects(call({ opt: { comps: '0,0,0' } }), /invalid keys/)
|
|
159
159
|
})
|
|
160
160
|
|
|
161
161
|
it('給予非3段之opt.comps時throw', async function() {
|
|
162
|
-
await assert.rejects(call({ opt: {
|
|
162
|
+
await assert.rejects(call({ opt: { comps: '1,2' } }), /非預期/)
|
|
163
163
|
})
|
|
164
164
|
|
|
165
165
|
})
|
|
166
166
|
|
|
167
167
|
describe('輸入檢核', function() {
|
|
168
168
|
|
|
169
|
+
it('name非有效字串時reject', async function() {
|
|
170
|
+
await assert.rejects(call({ name: '' }), /invalid name/)
|
|
171
|
+
await assert.rejects(call({ name: null }), /invalid name/)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('symbol非有效字串時reject', async function() {
|
|
175
|
+
await assert.rejects(call({ symbol: '' }), /invalid symbol/)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('interval非有效字串時reject', async function() {
|
|
179
|
+
await assert.rejects(call({ interval: '' }), /invalid interval/)
|
|
180
|
+
})
|
|
181
|
+
|
|
169
182
|
it('fdOhlc非有效字串時reject', async function() {
|
|
170
183
|
await assert.rejects(call({ fdOhlc: '' }), /invalid fdOhlc/)
|
|
171
184
|
await assert.rejects(call({ fdOhlc: null }), /invalid fdOhlc/)
|
|
@@ -6,14 +6,14 @@ import readStrategies from '../src/readStrategies.mjs'
|
|
|
6
6
|
import genTid from '../src/genTid.mjs'
|
|
7
7
|
import cont from '../src/cont.mjs'
|
|
8
8
|
import ott from '../src/ott.mjs'
|
|
9
|
-
import { buildFdTmp, buildFdData,
|
|
9
|
+
import { buildFdTmp, buildFdData, name, symbol, interval } from './unit-setup.mjs'
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
//規格來源: src/estimKeys.mjs
|
|
13
|
-
// estimKeys(ott,
|
|
13
|
+
// estimKeys(ott, name, symbol, interval, fdOhlc, fdParam, timeStart, timeEnd, mode, keys, fdData, opt):
|
|
14
14
|
// 設計變數為[止損, 止盈, ...各key門檻], 止損止盈由opt.thsSl與opt.thsTp之百分比清單除以100離散取值
|
|
15
15
|
// 各key門檻由rang(-2,2,40)之41點各自平移+10與-10為82點, 解值>0代表'>'條件、<0代表'<'條件, 還原時扣除平移值
|
|
16
|
-
// 以
|
|
16
|
+
// 以opt.methodOml指定之演算法求解(預設PSO), 各次以runStrategy回測並經calcFitness計算適應值
|
|
17
17
|
// 滿足thNumTrade、thRWin與thREquivalentCumuProfitOrLossFinalNormYear三門檻者, 以genStrategyFileName存入fdData
|
|
18
18
|
// 同tkid(tid+級距)已有策略時, 僅於等效年化盈虧更佳時抽換並刪除較差者, 故同tkid恆僅一檔
|
|
19
19
|
// fdData不存在時自動建立
|
|
@@ -22,12 +22,8 @@ import { buildFdTmp, buildFdData, buildSt, keySettings } from './unit-setup.mjs'
|
|
|
22
22
|
let fdTmp = buildFdTmp('estimKeys')
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
let st = buildSt()
|
|
26
|
-
|
|
27
|
-
|
|
28
25
|
//optBase: 三門檻皆放寬, 使求解過程之可用參數組必然寫出策略檔
|
|
29
26
|
let optBase = {
|
|
30
|
-
keySettings,
|
|
31
27
|
thsTp: [3],
|
|
32
28
|
thsSl: [2],
|
|
33
29
|
thNumTrade: 1,
|
|
@@ -51,7 +47,7 @@ describe('estimKeys', function() {
|
|
|
51
47
|
before(async function() {
|
|
52
48
|
this.timeout(120000)
|
|
53
49
|
d = buildFdData(fdTmp, 'solve', keys, { n: 20 })
|
|
54
|
-
m = await estimKeys(ott,
|
|
50
|
+
m = await estimKeys(ott, name, symbol, interval, d.fdOhlc, d.fdParam, d.timeStart, d.timeEnd, 'long', keys, d.fdData, optBase)
|
|
55
51
|
})
|
|
56
52
|
|
|
57
53
|
it('回傳omlPSO之求解結果, 含bestSolution與停止資訊', function() {
|
|
@@ -167,7 +163,7 @@ describe('estimKeys', function() {
|
|
|
167
163
|
let keys = ['btc_4hr_ma_1day']
|
|
168
164
|
let d = buildFdData(fdTmp, 'nosave', keys, { n: 20 })
|
|
169
165
|
|
|
170
|
-
await estimKeys(ott,
|
|
166
|
+
await estimKeys(ott, name, symbol, interval, d.fdOhlc, d.fdParam, d.timeStart, d.timeEnd, 'long', keys, d.fdData, {
|
|
171
167
|
...optBase,
|
|
172
168
|
thNumTrade: 1e9, //資料僅20根K線, 無從達成
|
|
173
169
|
})
|
|
@@ -200,7 +196,7 @@ describe('estimKeys', function() {
|
|
|
200
196
|
//call: 以獨立之策略資料夾執行求解
|
|
201
197
|
let call = (tag, extra = {}) => {
|
|
202
198
|
let fdData = path.resolve(fdTmp, 'pso', `s-${tag}`)
|
|
203
|
-
return estimKeys(ott,
|
|
199
|
+
return estimKeys(ott, name, symbol, interval, d.fdOhlc, d.fdParam, d.timeStart, d.timeEnd, 'long', keys, fdData, {
|
|
204
200
|
...optBase,
|
|
205
201
|
...optPso,
|
|
206
202
|
...extra,
|
|
@@ -295,7 +291,7 @@ describe('estimKeys', function() {
|
|
|
295
291
|
|
|
296
292
|
it('PSO設定與estimKeys自身設定可同時給予且互不干擾', async function() {
|
|
297
293
|
let fdData = path.resolve(fdTmp, 'pso', 's-mix')
|
|
298
|
-
let m = await estimKeys(ott,
|
|
294
|
+
let m = await estimKeys(ott, name, symbol, interval, d.fdOhlc, d.fdParam, d.timeStart, d.timeEnd, 'long', keys, fdData, {
|
|
299
295
|
...optBase,
|
|
300
296
|
...optPso,
|
|
301
297
|
NContiguous: 5,
|
|
@@ -319,6 +315,97 @@ describe('estimKeys', function() {
|
|
|
319
315
|
|
|
320
316
|
})
|
|
321
317
|
|
|
318
|
+
describe('methodOml演算法選擇', function() {
|
|
319
|
+
|
|
320
|
+
//各演算法之超參數集合不同, 以funGenerationBefore所收到之params鍵集合判別實際採用者
|
|
321
|
+
// 迴圈數之欄位名亦不同: RGA與DE為Ng、HS為Ni、PSO與ACO為Nl, 故一次給齊各欄位後,
|
|
322
|
+
// 由stopMode所標明之欄位名即可看出實際採用之演算法
|
|
323
|
+
|
|
324
|
+
let keys = ['btc_4hr_ma_1day']
|
|
325
|
+
let d = null
|
|
326
|
+
|
|
327
|
+
before(function() {
|
|
328
|
+
d = buildFdData(fdTmp, 'method', keys, { n: 20 })
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
//kpParams: 各演算法於funGenerationBefore所提供之超參數鍵(已排序)
|
|
332
|
+
let kpParams = {
|
|
333
|
+
RGA: ['LocalSearchMethod', 'ModeOutLimit', 'rgaCrossover', 'rgaElitism', 'rgaMutation', 'rgaMutationRate', 'rgaSelection'],
|
|
334
|
+
DE: ['LocalSearchMethod', 'ModeOutLimit', 'deCrossoverFactor', 'deF', 'deLanda', 'deMutation'],
|
|
335
|
+
HS: ['LocalSearchMethod', 'ModeOutLimit', 'hsHMC', 'hsHMCR', 'hsPA', 'hsPAR'],
|
|
336
|
+
PSO: ['LocalSearchMethod', 'ModeOutLimit', 'psoBeta', 'psoC1', 'psoC2', 'psoGamma', 'psoInertiaMin'],
|
|
337
|
+
ACO: ['LocalSearchMethod', 'ModeOutLimit', 'acoAlpha', 'acoExploitationRate', 'acoRo'],
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
//kpKeyIteration: 各演算法之迴圈數欄位名
|
|
341
|
+
let kpKeyIteration = {
|
|
342
|
+
RGA: 'Ng',
|
|
343
|
+
DE: 'Ng',
|
|
344
|
+
HS: 'Ni',
|
|
345
|
+
PSO: 'Nl',
|
|
346
|
+
ACO: 'Nl',
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
//optStop: 一次給齊各演算法之迴圈數與族群數欄位, 各演算法僅取用自身認得者
|
|
350
|
+
let optStop = {
|
|
351
|
+
Ng: 2,
|
|
352
|
+
Ni: 2,
|
|
353
|
+
Nl: 2,
|
|
354
|
+
Np: 6,
|
|
355
|
+
Ns: 6,
|
|
356
|
+
Na: 6,
|
|
357
|
+
UseRepeat: false,
|
|
358
|
+
UseImmigration: false,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
//run: 執行求解並回傳{m, params}
|
|
362
|
+
let run = async (tag, extra = {}) => {
|
|
363
|
+
let fdData = path.resolve(fdTmp, 'method', `s-${tag}`)
|
|
364
|
+
let params = null
|
|
365
|
+
let m = await estimKeys(ott, name, symbol, interval, d.fdOhlc, d.fdParam, d.timeStart, d.timeEnd, 'long', keys, fdData, {
|
|
366
|
+
...optBase,
|
|
367
|
+
...optStop,
|
|
368
|
+
...extra,
|
|
369
|
+
funGenerationBefore: (o) => {
|
|
370
|
+
if (params === null) {
|
|
371
|
+
params = o.params
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
})
|
|
375
|
+
return { m, params }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
Object.keys(kpParams).forEach((methodOml) => {
|
|
379
|
+
it(`methodOml為${methodOml}時採用該演算法`, async function() {
|
|
380
|
+
let { m, params } = await run(methodOml, { methodOml })
|
|
381
|
+
assert.deepStrictEqual(Object.keys(params).sort(), kpParams[methodOml], `${methodOml}之超參數集合非預期`)
|
|
382
|
+
assert.match(m.stopMode, new RegExp(`^stop by ${kpKeyIteration[methodOml]}\\[2\\]$`), `${methodOml}實得: ${m.stopMode}`)
|
|
383
|
+
assert.strictEqual(m.stopIteration, 2)
|
|
384
|
+
})
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('未給methodOml時採PSO', async function() {
|
|
388
|
+
let { m, params } = await run('default')
|
|
389
|
+
assert.deepStrictEqual(Object.keys(params).sort(), kpParams.PSO)
|
|
390
|
+
assert.match(m.stopMode, /^stop by Nl\[2\]$/, `實得: ${m.stopMode}`)
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
it('methodOml非五者之一時亦採PSO', async function() {
|
|
394
|
+
let { m, params } = await run('unknown', { methodOml: 'XXX' })
|
|
395
|
+
assert.deepStrictEqual(Object.keys(params).sort(), kpParams.PSO)
|
|
396
|
+
assert.match(m.stopMode, /^stop by Nl\[2\]$/, `實得: ${m.stopMode}`)
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('各演算法之求解結果皆含bestSolution與停止資訊', async function() {
|
|
400
|
+
let { m } = await run('shape', { methodOml: 'DE' })
|
|
401
|
+
assert.ok(Number.isFinite(m.bestSolution.fitness))
|
|
402
|
+
assert.strictEqual(m.bestSolution.ps.length, 2 + keys.length)
|
|
403
|
+
assert.ok(typeof m.stopMode === 'string' && m.stopMode.length > 0)
|
|
404
|
+
assert.ok(m.stopExecutions > 0)
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
})
|
|
408
|
+
|
|
322
409
|
describe('輸入檢核', function() {
|
|
323
410
|
|
|
324
411
|
let keys = ['btc_4hr_ma_1day']
|
|
@@ -331,6 +418,9 @@ describe('estimKeys', function() {
|
|
|
331
418
|
//call: 以合法引數為底, 依opt覆寫指定引數後呼叫
|
|
332
419
|
let call = (ov = {}) => {
|
|
333
420
|
let a = {
|
|
421
|
+
name,
|
|
422
|
+
symbol,
|
|
423
|
+
interval,
|
|
334
424
|
fdOhlc: d.fdOhlc,
|
|
335
425
|
fdParam: d.fdParam,
|
|
336
426
|
timeStart: d.timeStart,
|
|
@@ -341,9 +431,22 @@ describe('estimKeys', function() {
|
|
|
341
431
|
opt: optBase,
|
|
342
432
|
...ov,
|
|
343
433
|
}
|
|
344
|
-
return estimKeys(ott,
|
|
434
|
+
return estimKeys(ott, a.name, a.symbol, a.interval, a.fdOhlc, a.fdParam, a.timeStart, a.timeEnd, a.mode, a.keys, a.fdData, a.opt)
|
|
345
435
|
}
|
|
346
436
|
|
|
437
|
+
it('name非有效字串時reject', async function() {
|
|
438
|
+
await assert.rejects(call({ name: '' }), /invalid name/)
|
|
439
|
+
await assert.rejects(call({ name: null }), /invalid name/)
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
it('symbol非有效字串時reject', async function() {
|
|
443
|
+
await assert.rejects(call({ symbol: '' }), /invalid symbol/)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('interval非有效字串時reject', async function() {
|
|
447
|
+
await assert.rejects(call({ interval: '' }), /invalid interval/)
|
|
448
|
+
})
|
|
449
|
+
|
|
347
450
|
it('fdOhlc非有效字串時reject', async function() {
|
|
348
451
|
await assert.rejects(call({ fdOhlc: '' }), /invalid fdOhlc/)
|
|
349
452
|
await assert.rejects(call({ fdOhlc: null }), /invalid fdOhlc/)
|
|
@@ -375,11 +478,6 @@ describe('estimKeys', function() {
|
|
|
375
478
|
await assert.rejects(call({ fdData: '' }), /invalid fdData/)
|
|
376
479
|
})
|
|
377
480
|
|
|
378
|
-
it('opt.keySettings非有效字串時reject', async function() {
|
|
379
|
-
await assert.rejects(call({ opt: {} }), /invalid opt.keySettings/)
|
|
380
|
-
await assert.rejects(call({ opt: { keySettings: 123 } }), /invalid opt.keySettings/)
|
|
381
|
-
})
|
|
382
|
-
|
|
383
481
|
it('回測時間範圍超出資料範圍時reject', async function() {
|
|
384
482
|
//w-data-tdprovide之getTimeSeriesFullByTimeRange檢核起訖時間
|
|
385
483
|
await assert.rejects(call({ timeStart: '1990-01-01T00:00:00' }), /timeStartData/)
|
|
@@ -2,11 +2,11 @@ import assert from 'assert'
|
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import estimPicks from '../src/estimPicks.mjs'
|
|
4
4
|
import ott from '../src/ott.mjs'
|
|
5
|
-
import { buildFdTmp, buildFdData,
|
|
5
|
+
import { buildFdTmp, buildFdData, name, symbol, interval } from './unit-setup.mjs'
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
//規格來源: src/estimPicks.mjs
|
|
9
|
-
// estimPicks(ott,
|
|
9
|
+
// estimPicks(ott, name, symbol, interval, fdOhlc, fdParam, timeStart, timeEnd, mode, funPickKeys, fdData, opt):
|
|
10
10
|
// 由fdParam列舉全部指標key後分3類: 開頭為'index_'者為keysIndex, 含'_vbs_'者為keysVolumn, 其餘為keysNorm
|
|
11
11
|
// 分類後各類依序經opt.funFilterKeysNorm、opt.funFilterKeysIndex、opt.funFilterKeysVolumn過濾(皆await)
|
|
12
12
|
// 再呼叫funPickKeys({keysNorm,keysIndex,keysVolumn})取得keys, 最後交由estimKeys求解
|
|
@@ -16,9 +16,6 @@ import { buildFdTmp, buildFdData, buildSt, keySettings } from './unit-setup.mjs'
|
|
|
16
16
|
let fdTmp = buildFdTmp('estimPicks')
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
let st = buildSt()
|
|
20
|
-
|
|
21
|
-
|
|
22
19
|
//keysParam: 涵蓋3類, index_開頭為指數類, 含_vbs_為成交量類, 其餘為一般類
|
|
23
20
|
let keyNormA = 'btc_4hr_ma_1day'
|
|
24
21
|
let keyNormB = 'btc_4hr_rsi'
|
|
@@ -33,6 +30,9 @@ let d = null
|
|
|
33
30
|
//call: 以合法引數為底, 依ov覆寫指定引數後呼叫
|
|
34
31
|
let call = (ov = {}) => {
|
|
35
32
|
let a = {
|
|
33
|
+
name,
|
|
34
|
+
symbol,
|
|
35
|
+
interval,
|
|
36
36
|
fdOhlc: d.fdOhlc,
|
|
37
37
|
fdParam: d.fdParam,
|
|
38
38
|
timeStart: d.timeStart,
|
|
@@ -42,10 +42,10 @@ let call = (ov = {}) => {
|
|
|
42
42
|
return []
|
|
43
43
|
},
|
|
44
44
|
fdData: d.fdData,
|
|
45
|
-
opt: {
|
|
45
|
+
opt: {},
|
|
46
46
|
...ov,
|
|
47
47
|
}
|
|
48
|
-
return estimPicks(ott,
|
|
48
|
+
return estimPicks(ott, a.name, a.symbol, a.interval, a.fdOhlc, a.fdParam, a.timeStart, a.timeEnd, a.mode, a.funPickKeys, a.fdData, a.opt)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
|
|
@@ -92,10 +92,10 @@ describe('estimPicks', function() {
|
|
|
92
92
|
it('僅開頭為index_者歸為指數類, 名稱中段含index_者仍為一般類', async function() {
|
|
93
93
|
let dd = buildFdData(fdTmp, 'cls-index', ['btc_index_rsi', 'index_rsi'], { n: 10 })
|
|
94
94
|
let got = null
|
|
95
|
-
await assert.rejects(estimPicks(ott,
|
|
95
|
+
await assert.rejects(estimPicks(ott, name, symbol, interval, dd.fdOhlc, dd.fdParam, dd.timeStart, dd.timeEnd, 'long', (o) => {
|
|
96
96
|
got = o
|
|
97
97
|
return []
|
|
98
|
-
}, dd.fdData, {
|
|
98
|
+
}, dd.fdData, {}), /invalid keys/)
|
|
99
99
|
|
|
100
100
|
assert.deepStrictEqual(got.keysIndex, ['index_rsi'])
|
|
101
101
|
assert.deepStrictEqual(got.keysNorm, ['btc_index_rsi'])
|
|
@@ -105,10 +105,10 @@ describe('estimPicks', function() {
|
|
|
105
105
|
//判斷順序為index_開頭 → 含_vbs_ → 其餘
|
|
106
106
|
let dd = buildFdData(fdTmp, 'cls-vbs', ['index_a_vbs_b', 'btc_vbs_c'], { n: 10 })
|
|
107
107
|
let got = null
|
|
108
|
-
await assert.rejects(estimPicks(ott,
|
|
108
|
+
await assert.rejects(estimPicks(ott, name, symbol, interval, dd.fdOhlc, dd.fdParam, dd.timeStart, dd.timeEnd, 'long', (o) => {
|
|
109
109
|
got = o
|
|
110
110
|
return []
|
|
111
|
-
}, dd.fdData, {
|
|
111
|
+
}, dd.fdData, {}), /invalid keys/)
|
|
112
112
|
|
|
113
113
|
assert.deepStrictEqual(got.keysIndex, ['index_a_vbs_b'])
|
|
114
114
|
assert.deepStrictEqual(got.keysVolumn, ['btc_vbs_c'])
|
|
@@ -123,7 +123,6 @@ describe('estimPicks', function() {
|
|
|
123
123
|
let got = null
|
|
124
124
|
await assert.rejects(call({
|
|
125
125
|
opt: {
|
|
126
|
-
keySettings,
|
|
127
126
|
funFilterKeysNorm: (ks) => {
|
|
128
127
|
return ks.filter((v) => {
|
|
129
128
|
return v === keyNormA
|
|
@@ -143,7 +142,6 @@ describe('estimPicks', function() {
|
|
|
143
142
|
let got = null
|
|
144
143
|
await assert.rejects(call({
|
|
145
144
|
opt: {
|
|
146
|
-
keySettings,
|
|
147
145
|
funFilterKeysIndex: () => {
|
|
148
146
|
return []
|
|
149
147
|
},
|
|
@@ -166,7 +164,6 @@ describe('estimPicks', function() {
|
|
|
166
164
|
let got = null
|
|
167
165
|
await assert.rejects(call({
|
|
168
166
|
opt: {
|
|
169
|
-
keySettings,
|
|
170
167
|
funFilterKeysNorm: async (ks) => {
|
|
171
168
|
return ks.slice(0, 1)
|
|
172
169
|
},
|
|
@@ -194,19 +191,32 @@ describe('estimPicks', function() {
|
|
|
194
191
|
})
|
|
195
192
|
|
|
196
193
|
it('funPickKeys回傳非空陣列時通過keys檢核, 續往後續檢核', async function() {
|
|
197
|
-
|
|
194
|
+
//以超出資料範圍之timeEnd呼叫, 停在w-data-tdprovide之時間範圍檢核, 代表keys已通過
|
|
198
195
|
await assert.rejects(call({
|
|
199
196
|
funPickKeys: () => {
|
|
200
197
|
return [keyNormA]
|
|
201
198
|
},
|
|
202
|
-
|
|
203
|
-
}), /
|
|
199
|
+
timeEnd: '2099-01-01T00:00:00',
|
|
200
|
+
}), /timeEndData/)
|
|
204
201
|
})
|
|
205
202
|
|
|
206
203
|
})
|
|
207
204
|
|
|
208
205
|
describe('輸入檢核', function() {
|
|
209
206
|
|
|
207
|
+
it('name非有效字串時reject', async function() {
|
|
208
|
+
await assert.rejects(call({ name: '' }), /invalid name/)
|
|
209
|
+
await assert.rejects(call({ name: null }), /invalid name/)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('symbol非有效字串時reject', async function() {
|
|
213
|
+
await assert.rejects(call({ symbol: '' }), /invalid symbol/)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('interval非有效字串時reject', async function() {
|
|
217
|
+
await assert.rejects(call({ interval: '' }), /invalid interval/)
|
|
218
|
+
})
|
|
219
|
+
|
|
210
220
|
it('fdOhlc非有效字串時reject', async function() {
|
|
211
221
|
await assert.rejects(call({ fdOhlc: '' }), /invalid fdOhlc/)
|
|
212
222
|
})
|
package/test/unit-setup.mjs
CHANGED
|
@@ -11,21 +11,14 @@ import path from 'path'
|
|
|
11
11
|
let buildFdTmp = (name) => path.resolve(`./test/tmp-unit-${name}`)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
//
|
|
15
|
-
let
|
|
16
|
-
let
|
|
17
|
-
|
|
18
|
-
[keySettings]: {
|
|
19
|
-
name: 'btc',
|
|
20
|
-
symbol: 'BTCUSDT',
|
|
21
|
-
interval: '4hr',
|
|
22
|
-
},
|
|
23
|
-
}
|
|
24
|
-
}
|
|
14
|
+
//name, symbol, interval: estim系列前置之幣種設定
|
|
15
|
+
let name = 'btc'
|
|
16
|
+
let symbol = 'BTCUSDT'
|
|
17
|
+
let interval = '4hr'
|
|
25
18
|
|
|
26
19
|
|
|
27
20
|
//keyOhlc: 依estimKeys內`${name}_price_${interval}`規則組出之K線序列key
|
|
28
|
-
let keyOhlc =
|
|
21
|
+
let keyOhlc = `${name}_price_${interval}`
|
|
29
22
|
|
|
30
23
|
|
|
31
24
|
//tBase, tStep: 基準起始時間2020-01-01T00:00:00, 每根K線間隔4小時
|
|
@@ -109,8 +102,9 @@ let buildFdData = (fdTmp, fd, keysParam, opt = {}) => {
|
|
|
109
102
|
|
|
110
103
|
export {
|
|
111
104
|
buildFdTmp,
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
name,
|
|
106
|
+
symbol,
|
|
107
|
+
interval,
|
|
114
108
|
keyOhlc,
|
|
115
109
|
genTimes,
|
|
116
110
|
genArrOhlc,
|