コードの日付の計算の文法が誤っておりありえない日付が出力されていたのを修正。
//旧
date: start_time + dayjs().second(arrayNum * sec_time_span), //日付ラベル
//新
date: dayjs(start_time).add(arrayNum * sec_time_span, 'second'), //日付ラベル
昨日BLOGに掲載したプログラムを更新した。
bitFlyerの約定データから5秒足を生成して表示するだけのプログラムだ。
ファイルの出力等は実装されていない。
OHLCからOHLCVにアップグレード
VはBUYとSELLで分けて表示する
足の秒数変更に対応
前のバージョンから秒数を変更することはできたがエラーをハンドリングしていなかった。
割り切れない数字や小数点が入るとエラーとして停止する。
ログに各足の時刻ラベルを表示
OHLCV生成処理のリファクタリング
ごく僅かに処理性能と可読性アップ
バリデーションの項目を追加
その他バグを修正
2020/05/07:サンプルの年月が異常だが現在掲載のコードで修正されている。
2020/05/04:サンプルを付け忘れていたので追記とした。
----- Start Creating 5seconds OHLCV Data -----
2070/09/04 10:55:35 [O: 970938][H: 970964][L: 970922][C: 970953][BUY: 0.05][SELL: 0.41]
2070/09/04 10:55:40 [O: 970922][H: 971006][L: 970767][C: 970907][BUY: 3.77][SELL: 1.26]
2070/09/04 10:55:45 [O: 970878][H: 971053][L: 970839][C: 971006][BUY: 1.55][SELL: 0.59]
2070/09/04 10:55:50 [O: 971053][H: 971165][L: 970889][C: 971026][BUY: 4.53][SELL: 1.16]
2070/09/04 10:55:55 [O: 971006][H: 971006][L: 970732][C: 970800][BUY: 1.35][SELL: 6.74]
2070/09/04 10:56:00 [O: 970782][H: 971200][L: 970522][C: 971200][BUY: 15.93][SELL: 4.97]
2070/09/04 10:56:05 [O: 971200][H: 971428][L: 971044][C: 971126][BUY: 7.51][SELL: 6.24]
2070/09/04 10:56:10 [O: 971095][H: 971207][L: 970958][C: 971178][BUY: 2.06][SELL: 4.19]
2070/09/04 10:56:15 [O: 971235][H: 971271][L: 971174][C: 971271][BUY: 0.08][SELL: 0.21]
2070/09/04 10:56:20 [O: 971307][H: 971445][L: 971187][C: 971276][BUY: 0.97][SELL: 2.16]
2070/09/04 10:56:25 [O: 971266][H: 971304][L: 971188][C: 971263][BUY: 0.29][SELL: 2.71]
2070/09/04 10:56:30 [O: 971263][H: 971448][L: 971178][C: 971178][BUY: 2.06][SELL: 4.22]
--- Report ---
Execution time: 0s 69.2402ms
Volumeは桁数がバラバラなのでスペース埋めするコードも実装した。
厳密にはBTCの価格も桁数が変化するが稀なため実装していない。
足を生成しただけでバックテストのバの字すらイメージできていない。
この先どうしたら良いのか何もわからない。
システムトレードの才能の無さを感じる。
import Config from '../utils/config.mjs'
import DB from './classes.mjs'
import dayjs from 'dayjs'
//------------------------------------------
// 5秒足作成
// v0.1.1
// 2020/05/07 日付ラベルの計算式を修正
//------------------------------------------
//------------------------------------------
// 設定
//------------------------------------------
const sec_time_span = 5 //秒足の秒数
//------------------------------------------
const config = new Config('../config/config.yaml') //DB用設定の読み込み
const use_validation = true //生成された足に不整合がないか検証する
// DBサーバ
const remoteDB = new DB({
host: config.db_host_remote,
username: config.db_username,
password: config.db_password,
}).influx
if (sec_time_span - Math.floor(sec_time_span)!=0){
console.log(`Error: sec_time_spanは整数である必要があります。`)
process.exit(1);
} else if(60 % sec_time_span) {
console.log(`Error: 1分(60秒)はsec_time_span(${sec_time_span}秒)では割り切れません。`)
process.exit(1);
} else {
console.log(`----- Start Creating ${sec_time_span}seconds OHLCV Data -----`)
}
let errors = [] //エラー報告用
const process_time_start = process.hrtime() //計測用
remoteDB.query(`SELECT * FROM "bitFlyer_db"."autogen"."lightning_executions_FX_BTC_JPY" WHERE time > now() - 1m`)
.then((res) => {
if (res.length === 0) { console.log("No Data Selected."); process.exit(1); }
chandleMain(res)
}).catch((err) => {
console.log(err)
})
const chandleMain = (data) => {
const start_time = caliculateStartTime(data)
let candle = [] //足が格納される箱
for (const d of data) {
const arrayNum = caliculateArrayNum(start_time, d.exec_date)
//要素を新規
if (!candle[arrayNum]) {
candle[arrayNum] = {
date: start_time + dayjs().second(arrayNum * sec_time_span), //日付ラベル
O_date: dayjs(d.exec_date),
O: d.price,
H: d.price,
L: d.price,
C: d.price,
C_date: dayjs(d.exec_date),
V: { BUY: 0, SELL: 0 }
}
} else {
//O より早いレコードが入ってきた場合oを更新
if (candle[arrayNum].O_date > dayjs(d.exec_date)) {
candle[arrayNum].O_date = dayjs(d.exec_date)
candle[arrayNum].O = d.price
}
//Hを生成
if (candle[arrayNum].H < d.price) {
candle[arrayNum].H = d.price
}
//Lを生成
if (candle[arrayNum].L > d.price) {
candle[arrayNum].L = d.price
}
//C より遅いレコードが入ってきた場合Cを更新
if (candle[arrayNum].C_date < dayjs(d.exec_date)) {
candle[arrayNum].C_date = dayjs(d.exec_date)
candle[arrayNum].C = d.price
}
}
// Vを生成(BUY:SELL別Volume)
// 注意:candle[arrayNum].V[d.side]としてはいけない
// v.sideは板寄せ時の約定で空白が入っている可能性がある
if (d.side === 'BUY') {
candle[arrayNum].V.BUY += d.size
} else if (d.side === 'SELL') {
candle[arrayNum].V.SELL += d.size
}
}
//データを表示
candle.map((c, index, array) => {
//不整合がないか検証
if (use_validation) { validation({ c, index, array }) }
//出力
const BUY = (' ' + (Math.round(c.V.BUY * 100) / 100)).slice(-6) //文字のパディング
const SELL = (' ' + (Math.round(c.V.SELL * 100) / 100)).slice(-6) //文字のパディング
console.log(`${dayjs(c.date).format('YYYY/MM/DD HH:mm:ss')} [O: ${c.O}][H: ${c.H}][L: ${c.L}][C: ${c.C}][BUY: ${BUY}][SELL: ${SELL}]`)
})
//レポート
console.log("--- Report ---")
for (const e of errors) {
console.log(e)
}
//処理時間計測
const performance_time_end = process.hrtime(process_time_start)
console.log(`Execution time: ${performance_time_end[0]}s ${performance_time_end[1] / 1000000}ms`)
}
// --------------------------------------------------------------------------------------------------------------------------------
// funcs
// --------------------------------------------------------------------------------------------------------------------------------
// 生成した足にエラーがないか検証する
const validation = ({ c, index, array }) => {
try {
if (c.L > c.H) errors.push(`Error(index${index}): L > H`) //LがHより大きい
if (c.O_date > c.C_date) errors.push(`Error(${index}): O_date > C_date`) //Oの日付よりCの日付のほうが早い
if (index > 0) {
if (array[index - 1]) {
if (array[index - 1].C_date > c.O_date) { errors.push(`Error(${index}): C_date[-1] > O_date`) } //前レコードのCの日付より今のレコードのOの日付のほうが早い
const prevPlus5 = dayjs(array[index - 1].date).unix() + sec_time_span
const current = dayjs(array[index].date).unix()
if (prevPlus5 != current) { errors.push(`Error(${index}): Date Differs ${prevPlus5}(prev + 5) : ${current}(current)`) } //日付ラベルが不正
} else {
errors.push(`info: index${index - 1} dosen't exists.`)
}
}
} catch (e) {
//不明なエラー
//アルゴリズム上発生しない想定だが、何らかのバグでキーが存在しなかった場合はあり得る
errors.push(`Unknown Error - index: ${index}\n${e}`)
}
}
//開始時間の計算
const caliculateStartTime = (data) => {
const start1 = dayjs(data[0].exec_date).second() //最初の秒数
//console.log(`秒数 ${start1}`)
const start2 = Math.floor(start1 / sec_time_span) //要素番号
//console.log(`要素番号: ${start2}`)
const start3 = start2 * sec_time_span
//console.log(`スタート秒数: ${start3}`)
const start4 = dayjs(data[0].exec_date).startOf('minute').second(start3)
//console.log(`スタート時間: ${start4}`)
return start4
}
//要素番号の計算
const caliculateArrayNum = (start_time, exec_date) => {
const time = dayjs(exec_date).unix() - start_time.unix()
return Math.floor(time / 5)
}