23
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

OANDAから為替などのヒストリカルデータを取得する

前置き

oanda は練習用口座を登録をすれば無料でAPIを試すことができます。
その中にヒストリカルデータの取得やtickのストリーミング配信もあり面白いです。
今回は機械学習でよく使うのでヒストリカルデータの取得だけを目的にします。
2002年5月分から最新のデータまで根こそぎもらいます。
※注意、2002年あたりのデータは取引が少なく結構まばらでした。

OANDA REST API

古いv1と新しいv20がありますが日本の口座ではv1しか対応していません。
またinstruments(銘柄?)も日本の口座だと少ないです。
というわけで海外の練習用口座からv20でヒストリカルデータをもらいます。
私が登録したのは5年以上前ですが登録では特に書類などは必要ありませんでした。
巷のフリーサービスと同じように登録できるはずです。
APIkeyだけ発行すればOKです。

APIまんまだと以下の様な感じ

curl -X GET "https://api-fxtrade.oanda.com/v1/instruments?accountId=12345&instruments=AUD_CAD%2CAUD_CHF"

wrapper位作ってくれればいいのにと思うのですが、なんかサードパーティーまかせです。

今まで使っていたもの

いままでは以前に作ったnodejsのラッパーを使っていたのですが、機械学習ではpythonばっかりやっているのでpythonで作ります。
nodejsのものもヒストリカルデータの取得だけであれば以下の様に取得できるはずです。

var oanda = require('./oanda/')({
    key: '99999999999999999999999999999999-99999999999999999999999999999999'
    type: 'practice' // 'real' or 'sandbox'
});

oanda.api.accounts(function(err,accounts){
    var account = accounts[0];

    account.candles({
        instrument : "USD_JPY",
        count : 5000,// APIの仕様で5000マックスです。
    },function(err,candles){
        console.log(err, candles);
    });
});

またnodejsのは日本口座用にv1で作ってあります。

環境

python 3.5.2
jupyter notebook
oanda rest api v20
windows 10

もし入ってない場合は以下を。

pip3 install requests pandas

実装

作りが中途半場ですので、データだけほしい人は中は見なくていいと思います。
ヒストリカルデータの取得とそれに必要なアカウント情報、銘柄情報だけ取得できるよう実装しました。

import requests, re,os
import pandas as pd
class TypeBase():
    def __init__(self,dictonary):
        for key in dictonary :
            target = self._converter(dictonary[key])
            setattr(self, key, target)
    def _converter(self,target):
        if type(target) is str:
            if re.match("^\d+?\.\d+?$", target):
                target = float(target)
            elif re.match("^\d+$", target):
                target = int(target)
        return target
class Instrument(TypeBase):
    def __init__(self,dictonary):
        super().__init__(dictonary)
class Host():
    def __init__(self,rest,stream):
        self.rest = rest 
        self.stream = stream 
        for name in ['rest','stream']:
            setattr(self, name, getattr(self, name) + '/v3/')
class Request():
    def __init__(self,key,type):
        if type is 'sandbox':
            self.host = Host('http://api-sandbox.oanda.com','http://stream-sandbox.oanda.com' )
        elif type is 'practice':
            self.host = Host('https://api-fxpractice.oanda.com','https://stream-fxpractice.oanda.com' )
        elif type is 'real':
            self.host = Host('https://api-fxtrade.oanda.com','https://stream-fxtrade.oanda.com' )
        else :
            raise Exception('unknown type')
        self.headers = {
            "Authorization" : 'Bearer ' + key,
            "X-Accept-Datetime-Format" : 'UNIX'
        }
    def get(self,url,params={}):
        response = requests.get(self.host.rest+url,headers=self.headers,params=params)
        json = response.json()
        err = None
        if 'errorMessage' in json:
            err = json['errorMessage']
            return err,None
        return None,json

class API():
    def __init__(self,key,type,id=None):
        self.request = Request(key,type)
        if id:
            self.account_id = id
            return;
        err,accounts = self.accounts()
        if err:
            raise Exception(err)
        using = None
        print('============== ACCOUNTS =================')
        for account in accounts:
            if using : 
                print(account['id'])
            else:
                using = account['id']
                print(account['id'] + ' << using this account.')
        self.account_id = using
    def _get(self,url,name,clas=None,params={}):
        err, data = self.request.get(url,params)
        if data and name in data:
            data = data[name]
            if clas :
                instanced = []
                for single in data:
                    instanced.append(clas(single))
                data = instanced
        return err , data
    def accounts(self):
        return self._get('accounts','accounts')
    def instruments(self,account_id=None):
        if account_id is None:
            account_id = self.account_id
        return self._get(
            'accounts/' + account_id + '/instruments',
            'instruments',
            Instrument)
    def candles(self,params):
        instrument = params['instrument']
        params.pop('instrument',None)
        if 'count' not in params:
            params['count'] = 5000
        err, candles = self._get(
            'instruments/'+instrument+'/candles',
            'candles',
            params=params)
        if candles is None:
            return err,candles
        if len(candles) == 0:
            return 'no more data',None
        temp = []
        for candle in candles:
            if 'mid' in candle:
                for key, value in {'o':'open','h':'high','l':'low','c':'close'}.items():
                    candle[value] = candle['mid'][key]
                candle.pop('mid',None)
                candle.pop('complete',None)
                temp.append(candle)
        candles = pd.DataFrame(temp)
        candles.time = pd.to_datetime(candles.time)
        candles.time = candles.time.dt.tz_localize('UTC')
        candles = candles.set_index('time')
        candles = candles.reindex_axis(['open','high','low','close','volume'], axis=1)
        for name in ['open','high','low','close']:
            candles[name] = candles[name].astype('float')
        return err, candles
class OANDA():
    def __init__(self, key= None,type='practice',id=None):
        if key is None:
            raise Exception('need api key')
        self.api = API(key,type,id)

使い方

インスタンス作成

oanda = OANDA(
  # required 自分で取得したものを入れてください。
    key='99999999999999999999999999999999-99999999999999999999999999999999', 
    # default 'practice' あとは'real','sandbox'
    type='practice', 
    # default None 指定がない場合取得できた最初のアカウントを使用します。
    id='999-999-999999-999') 

アカウント確認

err, accounts = oanda.api.accounts()
accounts #[{'id': '999-999-99999-999', 'tags': []}]

銘柄確認

err, instruments = oanda.api.instruments()
for instrument in instruments:
    print(instrument.name + '[' + instrument.type + ']')
XAU_JPY[METAL]
TWIX_USD[CFD]
XAU_CAD[METAL]
CAD_CHF[CURRENCY]
NZD_CHF[CURRENCY]
EUR_GBP[CURRENCY]
EUR_JPY[CURRENCY]
SG30_SGD[CFD]
USD_CZK[CURRENCY]
GBP_NZD[CURRENCY]
NZD_HKD[CURRENCY]
SGD_JPY[CURRENCY]
USD_NOK[CURRENCY]
EUR_DKK[CURRENCY]
EUR_PLN[CURRENCY]
XAG_CHF[METAL]
FR40_EUR[CFD]
EUR_AUD[CURRENCY]
TRY_JPY[CURRENCY]
XAU_XAG[METAL]
AUD_HKD[CURRENCY]
US30_USD[CFD]
AUD_NZD[CURRENCY]
EUR_HKD[CURRENCY]
USD_HKD[CURRENCY]
USD_DKK[CURRENCY]
XCU_USD[CFD]
GBP_PLN[CURRENCY]
EUR_NZD[CURRENCY]
XPT_USD[METAL]
NZD_JPY[CURRENCY]
EUR_CAD[CURRENCY]
XAG_NZD[METAL]
AUD_USD[CURRENCY]
SOYBN_USD[CFD]
XAU_CHF[METAL]
AUD_CAD[CURRENCY]
USB02Y_USD[CFD]
US2000_USD[CFD]
GBP_SGD[CURRENCY]
USD_SEK[CURRENCY]
CAD_SGD[CURRENCY]
USD_CHF[CURRENCY]
XAG_EUR[METAL]
XAG_SGD[METAL]
EUR_HUF[CURRENCY]
USD_CAD[CURRENCY]
HKD_JPY[CURRENCY]
XAU_GBP[METAL]
WTICO_USD[CFD]
XAG_USD[METAL]
XAG_JPY[METAL]
XAU_NZD[METAL]
XAG_AUD[METAL]
NATGAS_USD[CFD]
NZD_USD[CURRENCY]
USD_JPY[CURRENCY]
EUR_TRY[CURRENCY]
XAU_USD[METAL]
NAS100_USD[CFD]
CHF_ZAR[CURRENCY]
GBP_HKD[CURRENCY]
ZAR_JPY[CURRENCY]
CHF_JPY[CURRENCY]
EUR_SEK[CURRENCY]
USD_SGD[CURRENCY]
UK100_GBP[CFD]
USD_THB[CURRENCY]
GBP_CHF[CURRENCY]
AU200_AUD[CFD]
SPX500_USD[CFD]
EUR_SGD[CURRENCY]
USD_INR[CURRENCY]
UK10YB_GBP[CFD]
DE10YB_EUR[CFD]
AUD_CHF[CURRENCY]
NZD_CAD[CURRENCY]
CHF_HKD[CURRENCY]
SGD_HKD[CURRENCY]
HK33_HKD[CFD]
XAG_CAD[METAL]
XPD_USD[METAL]
IN50_USD[CFD]
XAG_GBP[METAL]
CAD_JPY[CURRENCY]
NZD_SGD[CURRENCY]
CAD_HKD[CURRENCY]
XAU_AUD[METAL]
EUR_NOK[CURRENCY]
USD_SAR[CURRENCY]
GBP_CAD[CURRENCY]
USD_HUF[CURRENCY]
GBP_AUD[CURRENCY]
USD_PLN[CURRENCY]
GBP_USD[CURRENCY]
USD_MXN[CURRENCY]
XAU_HKD[METAL]
XAG_HKD[METAL]
AUD_JPY[CURRENCY]
DE30_EUR[CFD]
USB05Y_USD[CFD]
EUR_CHF[CURRENCY]
AUD_SGD[CURRENCY]
BCO_USD[CFD]
XAU_EUR[METAL]
XAU_SGD[METAL]
SUGAR_USD[CFD]
EUR_CZK[CURRENCY]
GBP_ZAR[CURRENCY]
WHEAT_USD[CFD]
EUR_USD[CURRENCY]
EUR_ZAR[CURRENCY]
CORN_USD[CFD]
USD_ZAR[CURRENCY]
CN50_USD[CFD]
SGD_CHF[CURRENCY]
EU50_EUR[CFD]
USD_CNH[CURRENCY]
GBP_JPY[CURRENCY]
USD_TRY[CURRENCY]
USB30Y_USD[CFD]
JP225_USD[CFD]
NL25_EUR[CFD]
USB10Y_USD[CFD]

米国債とかもちゃんとありますね。

for property in vars(instruments[0]):
    print(property, ":", getattr(instruments[0],property))

中身はこんな感じ

tradeUnitsPrecision : 0
maximumPositionSize : 0
displayName : Gold/JPY
pipLocation : 1
marginRate : 0.05
minimumTrailingStopDistance : 50
maximumOrderUnits : 50000
type : METAL
name : XAU_JPY
maximumTrailingStopDistance : 100000
minimumTradeSize : 1
displayPrecision : 0

ヒストリカルデータの取得

err,candles = oanda.api.candles({
            'instrument' : 'USD_JPY',
            'granularity' : 'M10'
        })

priceは'M'(midpoint candles)のみ対応,countのデフォルトは5000にしました。
他はOANDAの仕様にのっとってます。

補足[足の種類]

value description
S5 5 second candlesticks, minute alignment
S10 10 second candlesticks, minute alignment
S15 15 second candlesticks, minute alignment
S30 30 second candlesticks, minute alignment
M1 1 minute candlesticks, minute alignment
M2 2 minute candlesticks, hour alignment
M4 4 minute candlesticks, hour alignment
M5 5 minute candlesticks, hour alignment
M10 10 minute candlesticks, hour alignment
M15 15 minute candlesticks, hour alignment
M30 30 minute candlesticks, hour alignment
H1 1 hour candlesticks, hour alignment
H2 2 hour candlesticks, day alignment
H3 3 hour candlesticks, day alignment
H4 4 hour candlesticks, day alignment
H6 6 hour candlesticks, day alignment
H8 8 hour candlesticks, day alignment
H12 12 hour candlesticks, day alignment
D 1 day candlesticks, day alignment
W 1 week candlesticks, aligned to start of week
M 1 month candlesticks, aligned to first day of the month

いっぱい取得する

とりあえずUSD_JPY,GBP_USDでD,H1,M30,M15,M10,M5をもらいます。
一回の取得で5000足分しかとれないのでくっつけていきます。
ファイルに書かないで一度全部メモリにもってしまっているので気を付けてください。
M5で50MB位あります。(古いほうからとればよかった、、。)
ファイルは"./data/"+instrument+"/"+ granularity + ".csv"に保存していっています。

def getHistorical(instrument,granularity):
    before = None;
    count = 1
    while(True):
        params = {
            'instrument' : instrument,
            'granularity' : granularity
        };
        if before is not None:
            params['to'] = before.iloc[0].name.timestamp()
        err,candles = oanda.api.candles(params)
        if err is not None:
            print(err)
            break;
        count += 1
        print("getting " + str(count), end="\r")
        if before is None:
            before = candles
        else:
            before = candles.append(before,verify_integrity=True)
    directory = "./data/"+instrument+"/"
    if not os.path.exists(directory):
        os.makedirs(directory)
    before.to_csv(directory + granularity + ".csv", date_format='%Y-%m-%d %H:%M:%S')
import time
instruments = ['USD_JPY','GBP_USD']
granularities = ['D','H1','M30','M15','M10','M5']
for instrument in instruments:
    for granularity in granularities:
        print('start',instrument,granularity)
        start = time.time()
        getHistorical(instrument,granularity)
        print('end',instrument,granularity,time.time() - start)
start USD_JPY D
no more data
end USD_JPY D 4.406407117843628
start USD_JPY H1
no more data
end USD_JPY H1 50.374043464660645
start USD_JPY M30
no more data
end USD_JPY M30 103.10180807113647
start USD_JPY M15
・
・
・
・

あとはDBにいれるなりなんなりいじってください。
これで強化学習シタイ!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
23
Help us understand the problem. What are the problem?