ホームホーム

OANDA Streaming API を使って為替レートを取得する

はじめに

OANDA(FX 業者)が提供しているストリーミング API を使って,リアルタイムの為替レートを取得してみます。 データは HTTP の Transfer-Encoding: chunked を利用して送られてきます。

試した環境

stunnel の設定

サーバとの通信は SSL/TLS で暗号化されているので,クライアント側にも暗号化と復号の処理が必要です。 OpenSSL を使えばよさそうですが,今回は簡単に済ませるために stunnel を使いました。

この場合の通信経路は,

クライアント ⇔ stunnel ⇔ インターネット ⇔ サーバ

となって,暗号化と復号の処理は stunnel が担ってくれます。

設定ファイルは以下のようになりました。

[oanda-stream]
client = yes
accept = 127.0.0.1:8081
connect = stream-fxtrade.oanda.com:443

ただし,デモ口座を使う場合は,

connect = stream-fxpractice.oanda.com:443

とします。

クライアントのサンプル

以下のプログラムを作成しました。 処理の流れがわかりやすいようにエラー処理は省略しています。

#include "stdafx.h"

#include <Winsock2.h>
#include <ws2tcpip.h>

#include <string>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

const std::string host = "127.0.0.1";
const int port = 8081;
const std::string accountId = "???????";  // アカウント ID
const std::string bearer = "???????";     // アクセストークン
const std::string instruments = "USD_JPY%2CEUR_JPY";  // レートを取得したい通貨ペアのリスト

SOCKET sock;  // ソケット

// 1行送信
void SendLine(const std::string& line)
{
    std::string s = line + "\r\n";
    send(sock, s.c_str(), s.length(), 0);
}

// 1行受信
void RecvLine(std::string& line)
{
    line.clear();
    char c;
    do
    {
        recv(sock, &c, 1, 0);
        line += c;
    } while (c != '\n');
}

int main()
{
    // winsock の初期化
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // ソケットの作成
    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // 接続
    sockaddr_in addr = { 0 };
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET, host.c_str(), &addr.sin_addr.s_addr);

    connect(sock, (SOCKADDR *)& addr, sizeof(addr));

    // リクエストの送信
    SendLine("GET /v1/prices?accountId=" + accountId + "&instruments=" + instruments + " HTTP/1.1");
    SendLine("Host: " + host);
    SendLine("Authorization: Bearer " + bearer);
    SendLine("");

    // ステータスラインとヘッダの受信
    while (1)
    {
        std::string line;
        RecvLine(line);
        if (line == "\r\n")  // 空行を読んだらヘッダの終わり
            break;    
    }

    // ボディの受信
    while (1)
    {
        // チャンクサイズの受信
        std::string line;
        RecvLine(line);

        long cb = strtol(line.c_str(), nullptr, 16);  // チャンクサイズは 16 進数

        // チャンク本体の受信
        char c;
        for (int i = 0; i < cb; i++)
        {
            recv(sock, &c, 1, 0);
            if (c != '\r')
            {
                std::cout.put(c);
            }
        }

        //  CRLF を読み飛ばす
        recv(sock, &c, 1, 0);   
        recv(sock, &c, 1, 0);

        //
        std::cout.flush();
    }

    // 到達しないけど一応...
    closesocket(sock);
    WSACleanup();
    return 0;
}

受信データ例

以下のように,JSON 形式のデータが取得できました。

{"tick":{"instrument":"USD_JPY","time":"2018-05-23T09:28:23.993870Z","bid":109.681,"ask":109.689}}
{"tick":{"instrument":"EUR_JPY","time":"2018-05-23T09:28:23.189355Z","bid":128.669,"ask":128.682}}