SWエンジニアの雑記ブログ

約10年SWエンジニアとして働いている私が投資やら副業やら学んだことを記録していくブログです。

ヒストリカルデータをCSVファイルに変換する

pythonヒストリカルデータ(HST形式のファイル)をCSV形式に変換するツールを作成します。なぜ、pythonかというと最近仕事でpythonに触れる機会がありまして、pythonの書きやすさや可読性の高さに惚れたからです。私はC++を基本的に読み書きすることが多く、C++と比較して「なんてpythonは書きやすく、読みやすいんだ」と感動しました。もうpythonが書きたくて仕方ない!!そんな感じに今なっています。

私のpythonへの愛情表現はこの辺にしておき、早速本題に入ります。 ヒストリカルデータはほかの記事で説明したとおり、HSTという拡張子のファイルでファイルの中には決まったデータ形式(フォーマット)でデータが記録されています。今回作成するツールは、HSTファイルを読み込み、CSV形式でデータを出力するツール(スクリプト)です。

ヒストリカルデータのフォーマット

ヒストリカルデータファイルには、以下の形式でデータが格納されています。(バージョンによって違うみたいですが、私の使用したHSTファイルは以下のようになってました。) 著作権等の管理情報が入ったヘッダーデータと、始値/終値/高値/低値が入ったデータ部で構成されています。

 

CSVファイルに変換するPythonスクリプトを作る

HSTファイルのフォーマットがわかったので、あとはフォーマットに沿って読み込み、必要なデータのみをCSV形式で書き出すPythonスクリプトを作るのみです!! こんな感じになりました。

実行環境

OS: Windows 7 x64 Python for Windowsインストール環境 Python  3.6.2

ソースコード

import argparse
import io

from ctypes import *
import struct
import datetime

# HSTファイルのヘッダー構成
class HSTHeader(Structure):
    _fields_ = [
        ( 'version',c_int ),
        ( 'copyright',c_char * 64 ),
        ( 'symbol',c_char * 12 ),
        ( 'period',c_int ),
        ( 'digits',c_int ),
        ( 'timesign',c_int ),
        ( 'last_sync',c_int ),
        ( 'unused',c_int * 13 ),
    ]

# HSTファイルのデータ構成
# アライメントが入ってしまうので現在時刻についてはfields含めない
class HSTData(Structure):
    _fields_ = [
        ( 'open' , c_double ),
        ( 'low' , c_double ),
        ( 'high', c_double ),
        ( 'close', c_double ),
        ( 'vol',c_double ),
    ]

    def __init__(self):
        # 現在時刻についてはfieldsに入れてしまうと
        # 自動アライメントが入ってしまうので別枠で用意しておく。
        self.currentTime = 0

# HSTHeaderの解析関数
def analyzeHstHeader(fileObj):

    # Header部のサイズ
    headerSize = sizeof(HSTHeader)

    # Header部のバイナリデータを取得
    headerBinaryData = fileObj.read(headerSize)

    # HSTHeaderにバイナリデータをキャスト
    headerData = HSTHeader()
    buffer = io.BytesIO(headerBinaryData)
    buffer.readinto(headerData)

    return headerData

# HSTDataの解析関数
# 読み込むデータがない場合はNoneを返却する。
def analyzeHstData(fileObj):

    data = HSTData()
    
    # 時刻データの取得
    dateBinaryData = fileObj.read(4)

    # fileが末尾だったらNULLを返却
    if dateBinaryData == b'':
        return None

    data.currentTime, = struct.unpack('i',dateBinaryData)

    # Data部のサイズ
    dataSize = sizeof(HSTData)

    # Data部のバイナリデータを取得
    dataBinaryData = fileObj.read(dataSize)

    # HSTDataにバイナリデータをキャスト

    buffer = io.BytesIO(dataBinaryData)
    buffer.readinto(data)

    return data

# HSTHeader情報を標準出力に出力する
def outputHstHeader(headerData):
    print('version:',headerData.version)
    print('copyright:',headerData.copyright.decode('utf-8'))
    print('symbol:',headerData.symbol.decode('utf-8'))
    print('period:',headerData.period)
    print('digits:',headerData.digits )
    print('timesign:',headerData.timesign )
    print('last_sync:',headerData.last_sync )
    print('-------------------------------')

# CSVにヘッダーを出力
def outputCSVHeader():
    print('currentTime,open,low,high,close,vol')

# HSTファイルのデータ部出力
def outputHstData(data):
    print( datetime.datetime.fromtimestamp(data.currentTime), ',' , data.open, ',' ,data.low, ',' ,data.high, ',' ,data.close, ',' ,data.vol)

if __name__ == '__main__':
    argParser = argparse.ArgumentParser()
    argParser.add_argument("HSTFilePath")
    args = argParser.parse_args()

    # バイナリモードでHSTファイルを読み込む
    fileObj = open(args.HSTFilePath,"rb")

    headerData = analyzeHstHeader(fileObj)

    outputHstHeader(headerData)

    outputCSVHeader()
    data = analyzeHstData(fileObj)
    while data is not None:
        # dataについて出力
        outputHstData(data)
        # 次のデータを取得。ファイルの末尾ならNoneになる。
        data = analyzeHstData(fileObj)
    
    fileObj.close()

(念のため) このソースの使用については自由ですが、いかなる事象や理由であろうと責任は一切負いません

実行例

> python hst_to_csv.py USDJPY.hst
version: 400
copyright: (C)opyright 2003, MetaQuotes Software Corp.
symbol: USDJPY
period: 1
digits: 1
timesign: 1403020128
last_sync: 0
-------------------------------
currentTime,open,low,high,close,vol
2005-01-10 11:31:00 , 104.79 , 104.79 , 104.79 , 104.79 , 5.0
2005-01-10 11:32:00 , 104.79 , 104.78 , 104.79 , 104.78 , 6.0
2005-01-10 11:33:00 , 104.78 , 104.77 , 104.78 , 104.77 , 5.0
2005-01-10 11:34:00 , 104.77 , 104.77 , 104.79 , 104.79 , 5.0
2005-01-10 11:35:00 , 104.79 , 104.78 , 104.79 , 104.78 , 4.0
2005-01-10 11:36:00 , 104.78 , 104.78 , 104.78 , 104.78 , 2.0
2005-01-10 11:37:00 , 104.78 , 104.78 , 104.78 , 104.78 , 2.0
2005-01-10 11:38:00 , 104.78 , 104.78 , 104.78 , 104.78 , 2.0
2005-01-10 11:39:00 , 104.78 , 104.78 , 104.79 , 104.79 , 4.0
2005-01-10 11:40:00 , 104.8 , 104.77 , 104.8 , 104.77 , 5.0
2005-01-10 11:41:00 , 104.77 , 104.75 , 104.78 , 104.75 , 9.0
2005-01-10 11:42:00 , 104.75 , 104.75 , 104.76 , 104.76 , 3.0
2005-01-10 11:43:00 , 104.76 , 104.74 , 104.76 , 104.74 , 5.0
2005-01-10 11:44:00 , 104.74 , 104.74 , 104.74 , 104.74 , 3.0
2005-01-10 11:45:00 , 104.74 , 104.74 , 104.74 , 104.74 , 2.0

 

さ、これでMT4を使用しなくてもPythonを使用して、バックテスト(過去のチャートでトレード検証)ができそうです。 (MT4もいつかは覚えなくてはならないような気もする....)