シストレどうですか

Algorithmic Trading for Dummies

OANDA API解説編 第15回 最新のレートをストリーミングで取得

今回は最新レートの取得をストリーミングを使って取得してみました。
初めてのブログで最新のレートを取得する方法をやりましたが、今回はストリーミングで連続して取得する方法です。

jantzen.hatenablog.com


最新のレートを取得する (ストリーミング)

GET /v3/accounts/{accountID}/pricing/stream
保有口座内で指定された銘柄の価格情報をストリーミングで取得する

OANDAの開発ガイド(英語版)はこちら

f:id:jantzen:20210111084602p:plain:w405:h300

いきなり長い説明がついていますが、これを翻訳すると以下のような感じなります。

"このリクエストが実行された時点からのレートを連続で取得。この配信は発生したすべてのレートを含むわけではありませんが、各銘柄毎に1秒間に最大4回(250ミリ秒に1回)のレートを配信します。 もし250ミリ秒以内に一つ以上のレートが配信された場合はその間の最後に発生したレートが配信されるので、レートの変動が急激な間はすべてのレートを取得することが出来ないということを意味しています。 また、違う接続で実行されたリクエストとは配信時間の開始タイミングが違っていますので、レートの変動が急激な間は、配信のタイミングによって取得できるレートが人によって違う事を意味しています。 (例:すべての配信のタイミングが毎時0秒を基準に始まっているわけではありません。)
注意:streaming用のURLを使用してください。"

この事から、すべてのレートが配信されないかもしれないという前提になっています。

URL

上の解説の注記にもありました通りストレーミングの場合は接続するURLが変わってきますので以下のURLを使います。

引数

1回ずつレートを取得する場合と引数の内容はあまり変わらないようです。

名前 場所 必須 説明 内容
Authorization header string Y 署名なしトークンID  
Accept-Datetime-Format header AcceptDatetimeFormat 時刻フォーマット指定 初期値:RFC3339 (yyyy-mm-ddThh:mm:ss.nnnnnnnnnZ形式)
その他:UNIX (12345678.000000123形式)
accountID path AccountID Y 口座番号
instruments query List of
InstrumentName(csv)
Y 出力したい通貨ペア 例:USD_JPY 複数指定の場合はカンマ区切りUSD_JPY,EUR_USD
snapshot query boolean 接続の最初時にそれまでの直前のレートを表示するか 初期値:True、False
includeHomeConversion query boolean 口座保有通貨への換算レートを含めるかどうか 初期値:False

"snapshot"ですが一番初めに返されるレートが要求する直前のレートになります。
意味がよくわからない場合は、週末にレートを取得してみてください。マーケットが閉まっているので新しいレートは取得できませんが、"snapshot"=Trueの時は土曜日の朝の時間帯のレートが戻ってくると思います。

サンプル

というわけで、早速いろいろ試してみました。

とりあえずやってみる

ストリーミングで取得する場合は、iter()_lines()という場所にデータが格納されるようですので、 まずはどんなデータを受け取るのか最小限でためしてみました。

#外部モジュール
import requests
import json
  
#口座情報 (自分の番号に置き換える)
API_Token = '9999999999999999999999999999999999999999999999999999999999999999'
API_AccountID = '999-999-99999999-999'
  
#URLの設定 (デモ口座用ストリーミング)
API_URL =  "https://stream-fxpractice.oanda.com"
  
#銘柄指定
INSTRUMENT = "USD_JPY"
  
#エンドポイント用変数の設定
url = API_URL + "/v3/accounts/%s/pricing/stream?instruments=%s" %(str(API_AccountID), INSTRUMENT)
    
headers = {
        'Authorization' : 'Bearer ' + API_Token
    }
  
try:
    
    #データの要求
    response_body = requests.get(url, headers=headers, stream=True)
    response_body.raise_for_status()
  
    #ストリーミングデータの読み込み
    for line in response_body.iter_lines():
    
        if line:
            price_json = json.loads(line)
            print(json.dumps(price_json, indent=2))
            
except Exception as e:
    
    print("Error(e) : %s" %e)
    print("中止")

iter_lines()ってなんのこっちゃ?アイター⁇という感じですが、調べてみるとIterator(イテレーター)で辞書だと反復という意味らしいです。 機能についてネットで調べるとなるほどね!?という感じです。(こんなレベルですみません・・・😓)
はさておき、実行するととりあえずは以下のようなデータを受け取ります。

{
  "type": "PRICE",
  "time": "2021-01-08T01:07:52.019956574Z",
  "bids": [
    {
      "price": "103.876",
      "liquidity": 10000000
    }
  ],
  "asks": [
    {
      "price": "103.888",
      "liquidity": 10000000
    }
  ],
  "closeoutBid": "103.876",
  "closeoutAsk": "103.888",
  "status": "tradeable",
  "tradeable": true,
  "instrument": "USD_JPY"
}
{
  "type": "HEARTBEAT",
  "time": "2021-01-08T01:07:52.734772415Z"
}

2種類のデータをあるようで、”type"が”PRICE"か"HEARTBEAT"となります。
Heartbeat(心拍)って名前がその機能にピッタリで英語って時々こういうなるほどね的な表現があるので感心します。 (止まったらそのセッションは死んでいるという事ですから。)
ストリーミングの監視用として将来使う事があるかもしれませんが、とりあえず必要なのは”PRICE"の方なのでこの内容についてみてみます。

使えそうな値としては”bids"と"asks"の部分ですね。ただ下位の要素が配列で囲まれているので要注意ですがとりあえずは0番目を指定すればよさそうです。
同じような値をもつフィールドとしては "closeoutBid"と"closeoutAsk"がありますが、開発ガイド(ClientPrice部分)によれば、

#
#The closeout bid Price. This Price is used when a bid is required to
# closeout a Position (margin closeout or manual) yet there is no bid
# liquidity. The closeout bid is never used to open a new position.
#
closeoutBid : (PriceValue),

"クローズ用のbidプライス。bidサイズに関係なくポジションのクローズを要求されている時に使われるプライスである。 このプライスは新規のポジションオープンには決して使われない。"

とあり、正直わかりずらいのですが、厳密に言えば新規にポジションをオープンする時は"asks",”bids"で、クローズする時は"closeoutBid","closeoutAsk"を見てねという事になりますでしょうか。
ただ上の例では"bids"、"asks"の"price"と"closeoutBid","closeoutAsk"のそれぞれのプライスは同じ値ですね。 他のサイトも参考にして今回は"bids","asks"内の配列の0番目の値を利用する事とします。

また、ろうそく足の取得と違い"mid"のプライスも入っていませんので自分で計算する事とします。
"tradeable"は取引可能かどうかを表すのでこれも含める事にします。

以上の事を考慮して、値を取得する部分を追加した例を作成してみました。(tryの部分のみ表示)

try:
        
    response_body = requests.get(url, headers=headers, stream=True)
    response_body.raise_for_status()
  
    for line in response_body.iter_lines():
      
        if line:
            price_json = json.loads(line)
  
            #プライスデータ
            if "PRICE" in price_json["type"]:
                instrument = price_json["instrument"]
                ask = float(price_json["asks"][0]["price"])
                bid = float(price_json["bids"][0]["price"])
                mid = round((ask + bid) / 2, 3)
                tick_time = price_json["time"]
                tradeable = price_json["tradeable"]
                print("%s, %s, %s, %s, %s, %s" %(instrument, bid, ask, mid, tick_time, tradeable))
  
            #ハートビート
            elif "HEARTBEAT" in price_json["type"]:
                print("HB, %s" %price_json["time"])
  
            #その他
            else:
                print(json.dumps(price_json, indent=2))
                break
            

実行すると以下のようになります。

USD_JPY, 103.989, 104.003, 103.996, 2021-01-10T23:17:39.895955871Z, True
USD_JPY, 103.992, 104.006, 103.999, 2021-01-10T23:18:03.142870750Z, True
HB, 2021-01-10T23:18:04.043037277Z
USD_JPY, 103.992, 104.006, 103.999, 2021-01-10T23:18:07.403240878Z, True
USD_JPY, 103.991, 104.005, 103.998, 2021-01-10T23:18:08.167937781Z, True
USD_JPY, 103.992, 104.006, 103.999, 2021-01-10T23:18:08.508847654Z, True
HB, 2021-01-10T23:18:09.043054054Z
HB, 2021-01-10T23:18:14.043161481Z
USD_JPY, 103.993, 104.008, 104.0, 2021-01-10T23:18:17.867772922Z, True```json

加えてスプレッドの計算もしておけば、スプレッドが何pips以上の時はエントリーしないみたいな事にも使えます。

その他

エンドポイントにレートの要求をする引数を設定する際に違うやり方があったので参考までに書いておきます。

上の例では銘柄("instruments")を指定する際に?の後に取得したい通貨組み合わせが続きますが、?の代わりに"params"という引数を使っても可能です。

url = .../v3/accounts/{AccountID}/pricing/stream?instruments=USD_JPY,EUR_USD  👈
headers = {...}
res = requests.Request('GET', url, headers=headers, stream=True)
の代わりに
url = .../v3/accounts/{AccountID}/pricing/stream
headers = {...}
params = {'instruments' : 'USD_JPY,EUR_USD'}  👈
res = requests.Request('GET', url, headers=headers, params=params, stream=True)

ちなみにストリーミングでないレートの取得用のエンドポイントでも同じ事を試してみましたが、エラーが返ってきたので使えないようです…。


まとめ

ストリーミングを使ってのレートの取得方法について調べてみました。
ストリーミングを使いこなせいようになれば、本格的なbot作成も思いのまま?になりそうです。

注意点としては、今回はあくまでもデモ口座の中の動きだけしか確認していませんので、”type"が”PRICE"と"HEARTBEAT"以外もあるかも知れませんし、"price"で[0]以外の配列があるかどうかもよく調べる必要がありそうです。(マニュアル内の例やoandapyv20の事例でもサイズ("liquidity")別のプライスが表示されています。)
それと文中の翻訳部分ですがあくまでも意訳であり正確を期するものではありませんので、正式な解釈は原文に委ねます。