2018年6月17日日曜日

MT4 のヒストリカルデータを Python で読み込んでみた

MetaTrader のフォーラムに .hst ファイルフォーマットについて書かれていたので、Python で読み込むクラスを作ってみました。

参考

.hst file format . . . Old and New (Jan 2014) - Symbols - MQL4 and MetaTrader 4 - MQL4 programming forum

この手のバイト列を扱うクラスは struct を使うと比較的簡単に実装できます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# -*- coding: utf-8 -*-
import calendar
import collections
import mmap
import struct
import time
 
 
class History:
    """
    .hst ファイルを collection にマップするクラス
    各レコードは以下の値を持つ
 
    ctm: レコードの時間(1970/1/1 からの秒)
    open: 始値
    high: 高値
    low: 安値
    close: 終値
    volume: 出来高
 
    以下は version 401
    spread: スプレッド
    real_volume: 実出来高?
    """
    def __init__(self, file):
        self.mm = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
 
        # Read Header
        self.version, self.copyright, self.symbol, self.period, self.digits, self.timesign, self.last_sync =\
            struct.unpack("<i64s12s4i", self.mm[:96])
 
        # Check version and detect record structure
        self.header_size = 148
        if self.version == 400:
            self.record_size = 44
            self.record_struct = struct.Struct("<i5d")
            self.candle = collections.namedtuple(
                'Candle', ['ctm', 'open', 'low', 'high', 'close', 'volume'])
        elif self.version == 401:
            self.record_size = 60
            self.record_struct = struct.Struct("<q4dqiq")
            self.candle = collections.namedtuple(
                'Candle', ['ctm', 'open', 'high', 'low', 'close', 'volume', 'spread', 'real_volume'])
        else:
            raise NotImplementedError
 
        self.len = int((len(self.mm) - self.header_size) / self.record_size)
 
    def __len__(self):
        return self.len
 
    def __getitem__(self, i):
        if i < 0 or self.len <= i:
            raise IndexError
 
        pivot = self.header_size + self.record_size * i
        return self.candle(*self.record_struct.unpack(self.mm[pivot:pivot + self.record_size]))

以下のように、普通の配列同等に扱えます。

1
2
3
4
5
6
7
with open("USDJPY1.hst", "rb") as f:
    history = History(f)
 
    for record in history:
        print("{0} Open:{1} High:{2} Low:{3} Close:{4} Volume:{5} Spread:{6} RealVolume:{7}".format(
            time.strftime("%Y/%m/%d %H:%M",  time.gmtime(record.ctm)),
            record.open, record.high, record.low, record.close, record.volume, record.spread, record.real_volume))

次は日付でアクセス出来るようにするかな。