2020-05-03

【プログラム更新】約定データから秒足をつくる #2

昨日アップロードしたbitFlyerの約定データで5秒足を生成するプログラムを更新した。

Article Image

2020/05/07:コードバグ修正

コードの日付の計算の文法が誤っておりありえない日付が出力されていたのを修正。

//旧
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)
}


この記事のタグ

この記事をシェア


謎の技術研究部 (謎技研)