w-trade-solve 1.0.5 → 2.0.2
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 +3 -2
- 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 +2 -2
- package/docs/estimComps.mjs.html +1 -1
- package/docs/estimFinder.mjs.html +1 -1
- package/docs/estimKeys.mjs.html +145 -77
- package/docs/estimPicks.mjs.html +1 -1
- package/docs/genSid.mjs.html +1 -1
- package/docs/genStrategyFileName.mjs.html +14 -10
- package/docs/genTid.mjs.html +1 -1
- package/docs/global.html +192 -22
- 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 +127 -43
- package/g.mjs +3 -2
- package/package.json +2 -2
- package/src/cont.mjs +1 -1
- package/src/estimKeys.mjs +144 -76
- package/src/genStrategyFileName.mjs +12 -8
- package/src/readStrategies.mjs +126 -42
- package/test/unit-estimKeys.test.mjs +222 -5
- package/test/unit-genStrategyFileName.test.mjs +56 -17
- package/test/unit-readStrategies.test.mjs +189 -53
|
@@ -4,7 +4,9 @@ import path from 'path'
|
|
|
4
4
|
import estimKeys from '../src/estimKeys.mjs'
|
|
5
5
|
import readStrategies from '../src/readStrategies.mjs'
|
|
6
6
|
import genTid from '../src/genTid.mjs'
|
|
7
|
-
import
|
|
7
|
+
import genStrategyFileName from '../src/genStrategyFileName.mjs'
|
|
8
|
+
import calcLevelNumTrade from '../src/calcLevelNumTrade.mjs'
|
|
9
|
+
import calcFitness from '../src/calcFitness.mjs'
|
|
8
10
|
import ott from '../src/ott.mjs'
|
|
9
11
|
import { buildFdTmp, buildFdData, name, symbol, interval } from './unit-setup.mjs'
|
|
10
12
|
|
|
@@ -15,7 +17,10 @@ import { buildFdTmp, buildFdData, name, symbol, interval } from './unit-setup.mj
|
|
|
15
17
|
// 各key門檻由rang(-2,2,40)之41點各自平移+10與-10為82點, 解值>0代表'>'條件、<0代表'<'條件, 還原時扣除平移值
|
|
16
18
|
// 以opt.methodOml指定之演算法求解(預設PSO), 各次以runStrategy回測並經calcFitness計算適應值
|
|
17
19
|
// 滿足thNumTrade、thRWin與thREquivalentCumuProfitOrLossFinalNormYear三門檻者, 以genStrategyFileName存入fdData
|
|
18
|
-
//
|
|
20
|
+
// 檔名為tkid之純函數(雜湊格式), 同tkid已有策略時僅於等效年化盈虧更佳時原地覆寫, 故同tkid恆僅一檔
|
|
21
|
+
// 策略檔內容除策略本體與summary、fitness外, 另落地tkid、numTrade、levelNumTrade、keys與solve區塊使欄位自足
|
|
22
|
+
// opt.funRunStrategyCustom可注入自訂評估核心(回傳{summary}), 求解全程改用之
|
|
23
|
+
// opt.useRunStrategyBeforeSave為true時存檔前重跑回測, 檔內numTrade/levelNumTrade/tkid/fitness皆由重跑之summary重新衍生(genRes同源)
|
|
19
24
|
// fdData不存在時自動建立
|
|
20
25
|
|
|
21
26
|
|
|
@@ -116,6 +121,34 @@ describe('estimKeys', function() {
|
|
|
116
121
|
})
|
|
117
122
|
})
|
|
118
123
|
|
|
124
|
+
it('策略檔內容欄位自足: 含tkid、numTrade、levelNumTrade與keys, 識別不依賴檔名', function() {
|
|
125
|
+
let ss = readStrategies(d.fdData, { readContent: true })
|
|
126
|
+
ss.forEach((s) => {
|
|
127
|
+
let c = s.data
|
|
128
|
+
assert.strictEqual(c.numTrade, Number(c.summary.numTrade))
|
|
129
|
+
assert.strictEqual(c.levelNumTrade, calcLevelNumTrade(Number(c.summary.numTrade)))
|
|
130
|
+
assert.strictEqual(c.tkid, `${c.tid}:${c.levelNumTrade}`)
|
|
131
|
+
assert.deepStrictEqual(c.keys, keys)
|
|
132
|
+
assert.strictEqual(c.fitness, calcFitness(c.settings, c.summary)) //fitness與summary同源
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('策略檔內容含solve區塊, 記錄求解時間窗、演算法與三門檻等求解條件', function() {
|
|
137
|
+
let ss = readStrategies(d.fdData, { readContent: true })
|
|
138
|
+
ss.forEach((s) => {
|
|
139
|
+
let c = s.data
|
|
140
|
+
assert.strictEqual(c.solve.timeStart, d.timeStart)
|
|
141
|
+
assert.strictEqual(c.solve.timeEnd, d.timeEnd)
|
|
142
|
+
assert.strictEqual(c.solve.methodOml, 'PSO') //未給methodOml時採PSO, 記錄實際採用者
|
|
143
|
+
assert.strictEqual(c.solve.thNumTrade, optBase.thNumTrade)
|
|
144
|
+
assert.strictEqual(c.solve.thRWin, optBase.thRWin)
|
|
145
|
+
assert.strictEqual(c.solve.thREquivalentCumuProfitOrLossFinalNormYear, optBase.thREquivalentCumuProfitOrLossFinalNormYear)
|
|
146
|
+
assert.deepStrictEqual(c.solve.thsTp, optBase.thsTp)
|
|
147
|
+
assert.deepStrictEqual(c.solve.thsSl, optBase.thsSl)
|
|
148
|
+
assert.match(c.solve.timeSolved, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
119
152
|
it('策略檔內之conds各元素為{key,sym,th}, key取自keys, sym為>或<, th落於[-2,2]', function() {
|
|
120
153
|
let ss = readStrategies(d.fdData, { readContent: true })
|
|
121
154
|
ss.forEach((s) => {
|
|
@@ -173,6 +206,191 @@ describe('estimKeys', function() {
|
|
|
173
206
|
|
|
174
207
|
})
|
|
175
208
|
|
|
209
|
+
describe('自訂評估核心funRunStrategyCustom', function() {
|
|
210
|
+
|
|
211
|
+
let keys = ['btc_4hr_ma_1day']
|
|
212
|
+
let d = null
|
|
213
|
+
|
|
214
|
+
before(function() {
|
|
215
|
+
d = buildFdData(fdTmp, 'custom', keys, { n: 20 })
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
//optQuick: 縮小求解量之共用設定
|
|
219
|
+
let optQuick = {
|
|
220
|
+
...optBase,
|
|
221
|
+
Np: 8,
|
|
222
|
+
NContiguous: 5,
|
|
223
|
+
UseRepeat: false,
|
|
224
|
+
UseImmigration: false,
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
//smStub: 過三門檻之固定精簡summary(含契約要求之numTrade/rWin/uEquityFinal/eqYr四欄)
|
|
228
|
+
let smStub = {
|
|
229
|
+
numTrade: 10,
|
|
230
|
+
rWin: '60.00%',
|
|
231
|
+
uEquityFinal: 1005,
|
|
232
|
+
rEquivalentCumuProfitOrLossFinalNormYear: '20.00%',
|
|
233
|
+
rTradeAllMax: '1.00%',
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
//call: 以獨立策略資料夾執行求解並讀回策略檔
|
|
237
|
+
let call = async (tag, extra = {}) => {
|
|
238
|
+
let fdData = path.resolve(fdTmp, 'custom', `s-${tag}`)
|
|
239
|
+
let m = await estimKeys(ott, name, symbol, interval, d.fdOhlc, d.fdParam, d.timeStart, d.timeEnd, 'long', keys, fdData, {
|
|
240
|
+
...optQuick,
|
|
241
|
+
...extra,
|
|
242
|
+
})
|
|
243
|
+
let ss = readStrategies(fdData, { readContent: true })
|
|
244
|
+
return { m, ss }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
it('注入後求解全程改用自訂函數, 並收到{ott,strategy,funGetSeries}', async function() {
|
|
248
|
+
let nCall = 0
|
|
249
|
+
let gotShape = false
|
|
250
|
+
let { m, ss } = await call('inject', {
|
|
251
|
+
funRunStrategyCustom: async ({ ott, strategy, funGetSeries }) => {
|
|
252
|
+
nCall++
|
|
253
|
+
if (!gotShape) {
|
|
254
|
+
gotShape = typeof ott === 'function' && typeof funGetSeries === 'function' && Array.isArray(strategy.conds) && strategy.mode === 'long'
|
|
255
|
+
}
|
|
256
|
+
return { summary: smStub }
|
|
257
|
+
},
|
|
258
|
+
})
|
|
259
|
+
assert.ok(nCall > 0, '自訂函數未被呼叫')
|
|
260
|
+
assert.strictEqual(nCall, m.stopExecutions, '自訂函數呼叫次數須等於核心分析次數')
|
|
261
|
+
assert.ok(gotShape, '自訂函數收到之引數形式非預期')
|
|
262
|
+
assert.strictEqual(ss.length, 1, 'stub固定summary故同tkid恆僅一檔')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('預設不重跑, 存檔即為自訂函數之summary, 衍生欄位與之同源', async function() {
|
|
266
|
+
let { ss } = await call('nosave-rerun', {
|
|
267
|
+
funRunStrategyCustom: async () => {
|
|
268
|
+
return { summary: smStub }
|
|
269
|
+
},
|
|
270
|
+
})
|
|
271
|
+
let c = ss[0].data
|
|
272
|
+
assert.deepStrictEqual(c.summary, smStub)
|
|
273
|
+
assert.strictEqual(c.numTrade, 10)
|
|
274
|
+
assert.strictEqual(c.levelNumTrade, calcLevelNumTrade(10))
|
|
275
|
+
assert.strictEqual(c.tkid, `${c.tid}:${calcLevelNumTrade(10)}`)
|
|
276
|
+
assert.strictEqual(c.fitness, calcFitness(c.settings, smStub))
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('useRunStrategyBeforeSave為true時存檔前重跑runStrategy, 落地完整官方summary且全部衍生欄位同源', async function() {
|
|
280
|
+
//stub僅首次評估放行門檻: 官方重跑之numTrade級距與stub可能不同, 若後續評估再查同檔會觸發tkid不一致throw(該防護屬預期行為), 故僅存檔一次
|
|
281
|
+
let nCall = 0
|
|
282
|
+
let { ss } = await call('save-rerun', {
|
|
283
|
+
funRunStrategyCustom: async () => {
|
|
284
|
+
nCall++
|
|
285
|
+
if (nCall === 1) {
|
|
286
|
+
return { summary: smStub }
|
|
287
|
+
}
|
|
288
|
+
return { summary: { ...smStub, numTrade: 0 } } //不過thNumTrade門檻
|
|
289
|
+
},
|
|
290
|
+
useRunStrategyBeforeSave: true,
|
|
291
|
+
})
|
|
292
|
+
assert.ok(ss.length >= 1)
|
|
293
|
+
ss.forEach((s) => {
|
|
294
|
+
let c = s.data
|
|
295
|
+
//官方summary具stub沒有的完整欄位
|
|
296
|
+
assert.ok('rSharpe' in c.summary, '落地summary須為官方完整欄位')
|
|
297
|
+
assert.ok('rDrawdownMax' in c.summary)
|
|
298
|
+
//檔內衍生欄位由重跑之summary重新衍生(genRes同源)
|
|
299
|
+
assert.strictEqual(c.numTrade, Number(c.summary.numTrade))
|
|
300
|
+
assert.strictEqual(c.levelNumTrade, calcLevelNumTrade(Number(c.summary.numTrade)))
|
|
301
|
+
assert.strictEqual(c.tkid, `${c.tid}:${c.levelNumTrade}`)
|
|
302
|
+
assert.strictEqual(c.fitness, calcFitness(c.settings, c.summary))
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('funRunStrategyCustomBeforeSave給定時, 存檔前改用該函數', async function() {
|
|
307
|
+
let smSave = { ...smStub, rEquivalentCumuProfitOrLossFinalNormYear: '33.00%', numTrade: 12 }
|
|
308
|
+
let { ss } = await call('save-custom', {
|
|
309
|
+
funRunStrategyCustom: async () => {
|
|
310
|
+
return { summary: smStub }
|
|
311
|
+
},
|
|
312
|
+
useRunStrategyBeforeSave: true,
|
|
313
|
+
funRunStrategyCustomBeforeSave: async () => {
|
|
314
|
+
return { summary: smSave }
|
|
315
|
+
},
|
|
316
|
+
})
|
|
317
|
+
let c = ss[0].data
|
|
318
|
+
assert.deepStrictEqual(c.summary, smSave)
|
|
319
|
+
assert.strictEqual(c.numTrade, 12) //由重跑summary重新衍生
|
|
320
|
+
assert.strictEqual(c.fitness, calcFitness(c.settings, smSave))
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('重跑結果跨級距時, 檔名以重跑summary重算, 檔名與檔內同源', async function() {
|
|
324
|
+
//stub級距11(numTrade 10), 重跑回傳級距12(numTrade 30) → 檔案應落在級距12檔名
|
|
325
|
+
let smSave = { ...smStub, numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '33.00%' }
|
|
326
|
+
let nCall = 0
|
|
327
|
+
let { ss } = await call('save-relevel', {
|
|
328
|
+
funRunStrategyCustom: async () => {
|
|
329
|
+
nCall++
|
|
330
|
+
return { summary: nCall === 1 ? smStub : { ...smStub, numTrade: 0 } } //僅首次放行, 避免後續評估以級距11舊檔名再查
|
|
331
|
+
},
|
|
332
|
+
useRunStrategyBeforeSave: true,
|
|
333
|
+
funRunStrategyCustomBeforeSave: async () => {
|
|
334
|
+
return { summary: smSave }
|
|
335
|
+
},
|
|
336
|
+
})
|
|
337
|
+
assert.strictEqual(ss.length, 1)
|
|
338
|
+
assert.match(ss[0].name, /^long_12_[0-9a-f]{16}\.json$/, `檔名 ${ss[0].name} 須為重跑之級距12`)
|
|
339
|
+
let c = ss[0].data
|
|
340
|
+
assert.strictEqual(c.numTrade, 30)
|
|
341
|
+
assert.strictEqual(c.levelNumTrade, 12)
|
|
342
|
+
assert.strictEqual(c.tkid, `${c.tid}:12`)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('跨級距且新檔名已有更佳者時, 放棄本次寫入', async function() {
|
|
346
|
+
//預置級距12檔案(eqYr 50%), 重跑回傳級距12但eqYr 33%較差 → 不得覆寫
|
|
347
|
+
let fdData = path.resolve(fdTmp, 'custom', 's-relevel-keep')
|
|
348
|
+
fs.mkdirSync(fdData, { recursive: true })
|
|
349
|
+
let tid = genTid(name, '4hr', 'long', keys)
|
|
350
|
+
let smBetter = { numTrade: 30, rWin: '70.00%', uEquityFinal: 1010, rEquivalentCumuProfitOrLossFinalNormYear: '50.00%' }
|
|
351
|
+
let fnBetter = genStrategyFileName(name, '4hr', 'long', keys, {}, smBetter)
|
|
352
|
+
fs.writeFileSync(path.resolve(fdData, fnBetter), JSON.stringify({
|
|
353
|
+
tid,
|
|
354
|
+
tkid: `${tid}:12`,
|
|
355
|
+
name,
|
|
356
|
+
interval: '4hr',
|
|
357
|
+
summary: smBetter,
|
|
358
|
+
}), 'utf8')
|
|
359
|
+
|
|
360
|
+
let smSave = { ...smStub, numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '33.00%' }
|
|
361
|
+
let nCall = 0
|
|
362
|
+
await estimKeys(ott, name, symbol, interval, d.fdOhlc, d.fdParam, d.timeStart, d.timeEnd, 'long', keys, fdData, {
|
|
363
|
+
...optQuick,
|
|
364
|
+
funRunStrategyCustom: async () => {
|
|
365
|
+
nCall++
|
|
366
|
+
return { summary: nCall === 1 ? smStub : { ...smStub, numTrade: 0 } }
|
|
367
|
+
},
|
|
368
|
+
useRunStrategyBeforeSave: true,
|
|
369
|
+
funRunStrategyCustomBeforeSave: async () => {
|
|
370
|
+
return { summary: smSave }
|
|
371
|
+
},
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
let ss = readStrategies(fdData, { readContent: true })
|
|
375
|
+
assert.strictEqual(ss.length, 1, '不得產生新檔')
|
|
376
|
+
assert.strictEqual(ss[0].data.summary.rEquivalentCumuProfitOrLossFinalNormYear, '50.00%', '既有更佳者不得被覆寫')
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('未開useRunStrategyBeforeSave時, funRunStrategyCustomBeforeSave不生效', async function() {
|
|
380
|
+
let smSave = { ...smStub, rEquivalentCumuProfitOrLossFinalNormYear: '33.00%' }
|
|
381
|
+
let { ss } = await call('save-custom-off', {
|
|
382
|
+
funRunStrategyCustom: async () => {
|
|
383
|
+
return { summary: smStub }
|
|
384
|
+
},
|
|
385
|
+
funRunStrategyCustomBeforeSave: async () => {
|
|
386
|
+
return { summary: smSave }
|
|
387
|
+
},
|
|
388
|
+
})
|
|
389
|
+
assert.deepStrictEqual(ss[0].data.summary, smStub)
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
})
|
|
393
|
+
|
|
176
394
|
describe('omlPSO設定傳遞', function() {
|
|
177
395
|
|
|
178
396
|
//opt除estimKeys自身欄位外皆原樣傳遞至omlPSO, 兩者欄位名稱無重疊
|
|
@@ -488,14 +706,13 @@ describe('estimKeys', function() {
|
|
|
488
706
|
|
|
489
707
|
describe('策略檔名規則', function() {
|
|
490
708
|
|
|
491
|
-
it('
|
|
709
|
+
it('檔名為雜湊格式`${mode}_${levelNumTrade}_${hash16}.json`, 長度固定不受keys個數影響', function() {
|
|
492
710
|
//由求解階段所產生之檔案驗證
|
|
493
711
|
let fdData = path.resolve(fdTmp, 'solve', 'data-strategy')
|
|
494
712
|
let fns = fs.readdirSync(fdData)
|
|
495
713
|
assert.ok(fns.length > 0)
|
|
496
714
|
fns.forEach((fn) => {
|
|
497
|
-
assert.
|
|
498
|
-
assert.ok(fn.endsWith('.json'), `檔名 ${fn}`)
|
|
715
|
+
assert.match(fn, /^long_\d+_[0-9a-f]{16}\.json$/, `檔名 ${fn}`)
|
|
499
716
|
})
|
|
500
717
|
})
|
|
501
718
|
|
|
@@ -1,39 +1,61 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
|
+
import crypto from 'crypto'
|
|
2
3
|
import genStrategyFileName from '../src/genStrategyFileName.mjs'
|
|
3
4
|
import genTid from '../src/genTid.mjs'
|
|
4
5
|
import calcLevelNumTrade from '../src/calcLevelNumTrade.mjs'
|
|
5
|
-
import cont from '../src/cont.mjs'
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
//規格來源: src/genStrategyFileName.mjs
|
|
9
9
|
// genStrategyFileName(coin, interval, mode, keys, settings, summary):
|
|
10
|
-
// 檔名為`${
|
|
10
|
+
// 檔名為`${mode}_${levelNumTrade}_${hash}.json`
|
|
11
|
+
// hash為md5(`${coin}:${interval}:${tid}:${levelNumTrade}`)取前16碼(64bits)
|
|
11
12
|
// tid由genTid(coin, interval, mode, keys)產生
|
|
12
13
|
// levelNumTrade由calcLevelNumTrade(cint(summary.numTrade))產生
|
|
13
|
-
//
|
|
14
|
+
// 檔名為(coin,interval,tid,levelNumTrade)之純函數, 與求解結果(等效年化盈虧)無關, 故同tkid恆對應同一檔名
|
|
15
|
+
// hash輸入含coin與interval, 使genTid剔除幣種與週期後相同之tid不致跨幣種/跨週期碰撞
|
|
14
16
|
// settings未參與檔名組成
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
let p =
|
|
19
|
+
let p = '_'
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
let settings = { uIni: 1000, uTrade: 1, rTakeProfit: 0.05, rStopLoss: 0.03, rFee: 0.0005 }
|
|
21
23
|
|
|
22
24
|
|
|
25
|
+
//buildFn: 依規格自行組出預期檔名
|
|
26
|
+
let buildFn = (coin, interval, mode, keys, numTrade) => {
|
|
27
|
+
let tid = genTid(coin, interval, mode, keys)
|
|
28
|
+
let levelNumTrade = calcLevelNumTrade(numTrade)
|
|
29
|
+
let hash = crypto.createHash('md5').update(`${coin}:${interval}:${tid}:${levelNumTrade}`, 'utf8').digest('hex').slice(0, 16)
|
|
30
|
+
return `${mode}${p}${levelNumTrade}${p}${hash}.json`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
23
34
|
describe('genStrategyFileName', function() {
|
|
24
35
|
|
|
25
|
-
it('
|
|
36
|
+
it('檔名為mode、級距與雜湊16碼以底線串接並附.json', function() {
|
|
26
37
|
let keys = ['btc_price_4hr_ma_1day']
|
|
27
38
|
let summary = { numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '31.25%' }
|
|
28
39
|
let fn = genStrategyFileName('btc', '4hr', 'long', keys, settings, summary)
|
|
29
|
-
|
|
40
|
+
//tid為'long ║ p_ma_1day', 級距12, hash=md5('btc:4hr:long ║ p_ma_1day:12')前16碼
|
|
41
|
+
assert.strictEqual(fn, 'long_12_9fce6961e8f56493.json')
|
|
30
42
|
})
|
|
31
43
|
|
|
32
|
-
it('
|
|
44
|
+
it('檔名符合`${mode}_${levelNumTrade}_${hash16}.json`樣式', function() {
|
|
33
45
|
let keys = ['btc_index_rsi_4hr', 'btc_price_4hr_ma_1day']
|
|
34
46
|
let summary = { numTrade: 8, rEquivalentCumuProfitOrLossFinalNormYear: '5%' }
|
|
35
47
|
let fn = genStrategyFileName('btc', '4hr', 'short', keys, settings, summary)
|
|
36
|
-
assert.
|
|
48
|
+
assert.match(fn, /^short_2_[0-9a-f]{16}\.json$/)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('雜湊段依規格為md5(`${coin}:${interval}:${tid}:${levelNumTrade}`)前16碼, 可由genTid與calcLevelNumTrade重組', function() {
|
|
52
|
+
let keys = ['btc_index_rsi_4hr', 'btc_price_4hr_ma_1day']
|
|
53
|
+
let nums = [0, 3, 30, 120, 3500, 9000]
|
|
54
|
+
nums.forEach((numTrade) => {
|
|
55
|
+
let summary = { numTrade, rEquivalentCumuProfitOrLossFinalNormYear: '1%' }
|
|
56
|
+
let fn = genStrategyFileName('btc', '4hr', 'long', keys, settings, summary)
|
|
57
|
+
assert.strictEqual(fn, buildFn('btc', '4hr', 'long', keys, numTrade), `numTrade[${numTrade}]`)
|
|
58
|
+
})
|
|
37
59
|
})
|
|
38
60
|
|
|
39
61
|
it('級距段與calcLevelNumTrade之輸出一致', function() {
|
|
@@ -46,11 +68,12 @@ describe('genStrategyFileName', function() {
|
|
|
46
68
|
})
|
|
47
69
|
})
|
|
48
70
|
|
|
49
|
-
it('
|
|
71
|
+
it('等效年化盈虧不影響檔名, 同tkid恆對應同一檔名', function() {
|
|
72
|
+
//此為O(1)直查與原地覆寫之基礎: 同tkid之新舊策略檔名相同
|
|
50
73
|
let keys = ['aa']
|
|
51
|
-
let
|
|
52
|
-
let
|
|
53
|
-
assert.strictEqual(
|
|
74
|
+
let a = genStrategyFileName('btc', '4hr', 'long', keys, settings, { numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '10%' })
|
|
75
|
+
let b = genStrategyFileName('btc', '4hr', 'long', keys, settings, { numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '-71.17%' })
|
|
76
|
+
assert.strictEqual(a, b)
|
|
54
77
|
})
|
|
55
78
|
|
|
56
79
|
it('settings不影響檔名', function() {
|
|
@@ -66,14 +89,30 @@ describe('genStrategyFileName', function() {
|
|
|
66
89
|
let a = genStrategyFileName('btc', '4hr', 'long', keys, settings, { numTrade: 20, rEquivalentCumuProfitOrLossFinalNormYear: '10%' })
|
|
67
90
|
let b = genStrategyFileName('btc', '4hr', 'long', keys, settings, { numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '10%' })
|
|
68
91
|
assert.notStrictEqual(a, b)
|
|
69
|
-
assert.strictEqual(a.split(p)[0], b.split(p)[0]) //tid相同
|
|
70
92
|
})
|
|
71
93
|
|
|
72
|
-
it('
|
|
73
|
-
|
|
74
|
-
|
|
94
|
+
it('不同幣種或週期之同組keys檔名不同, 不因genTid剔除幣種與週期而碰撞', function() {
|
|
95
|
+
//genTid('eth','4hr',...,['eth_price_4hr_ma_1day'])與genTid('btc','4hr',...,['btc_price_4hr_ma_1day'])同為'long ║ p_ma_1day',
|
|
96
|
+
// hash輸入含coin與interval, 故檔名仍不同
|
|
97
|
+
let summary = { numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '10%' }
|
|
98
|
+
let a = genStrategyFileName('eth', '4hr', 'long', ['eth_price_4hr_ma_1day'], settings, summary)
|
|
99
|
+
let b = genStrategyFileName('btc', '4hr', 'long', ['btc_price_4hr_ma_1day'], settings, summary)
|
|
100
|
+
let c = genStrategyFileName('btc', '1hr', 'long', ['btc_price_1hr_ma_1day'], settings, summary)
|
|
101
|
+
assert.strictEqual(genTid('eth', '4hr', 'long', ['eth_price_4hr_ma_1day']), genTid('btc', '4hr', 'long', ['btc_price_4hr_ma_1day'])) //tid確實相同
|
|
102
|
+
assert.notStrictEqual(a, b)
|
|
103
|
+
assert.notStrictEqual(b, c)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('keys個數多且名稱長時檔名長度仍固定, 不受NTFS單檔名段255字元上限影響', function() {
|
|
107
|
+
//舊式檔名隨keys個數線性成長, 7因子已達約240字元; 雜湊檔名恆為`${mode}_${級距}_${16碼}.json`
|
|
108
|
+
let keys = []
|
|
109
|
+
for (let i = 0; i < 20; i++) {
|
|
110
|
+
keys.push(`btc_price_4hr_someVeryLongIndicatorName${i}_15day`)
|
|
111
|
+
}
|
|
112
|
+
let summary = { numTrade: 30, rEquivalentCumuProfitOrLossFinalNormYear: '10%' }
|
|
75
113
|
let fn = genStrategyFileName('btc', '4hr', 'long', keys, settings, summary)
|
|
76
|
-
assert.
|
|
114
|
+
assert.match(fn, /^long_12_[0-9a-f]{16}\.json$/)
|
|
115
|
+
assert.ok(fn.length <= 40, `檔名長度 ${fn.length} 須固定短`)
|
|
77
116
|
})
|
|
78
117
|
|
|
79
118
|
it('summary.numTrade非數值時先經cint轉為0, 級距為0', function() {
|