APIの使い方

Pythonを使い「REST API」で過去の取引履歴を取得して損益を計算する方法


PythonでREST API(以下、API)を利用して過去の取引履歴を取得し、損益を計算する方法について解説します。

平均リターンや損失、勝率やリスクリワード比率などを計算します。

通貨ペア別や日別、時間帯別や曜日別など、さまざまな粒度でパフォーマンスを確認することが可能です。

なお、ここで利用しているのはデモ用アカウントであるため、取引データが少ないかつ保有期間も短く、損益もとても小さい数値になっています。

実際の取引データで活用すれば、より具体的な結果が得られるはずです。

APIで取引履歴のデータを取得する

まずはAPIを使って取引履歴のデータを取得します。

取引履歴データを取得するためには、oandapyV20.endpoints.transactionsを利用します。

次の手順で任意期間の取引履歴データを取得することができます。

  • 1.TransactionListで任意期間における対象データのIDの範囲を取得する
  • 2.TransactionIDRangeで各IDの詳細情報を取得する

取引履歴の一覧データを取得するために必要なステップは2つあります。

まずは、TransactionListで任意の期間における対象データのIDの範囲を取得します。

この情報を使い、TransactionIDRangeで各IDの詳細情報を取得します。

コードの詳細については「Pythonを使い「REST API」でトランザクションデータを取得する方法」について解説で解説しています。

1. TransactionListで任意期間における対象データのIDの範囲を取得する

次のコードでは、サンプルとして2022年9月1日から2022年10月31日までの取引履歴データが含まれるIDの範囲を取得しています。

約定データのみを取得するため、typeをORDER_FILLに指定します。



from oandapyV20.endpoints import transactions
from oandapyV20 import API

account_id = "アカウントIDを設定してください"
access_token = "APIトークンを設定してください"

# API用のクラスを定義
client = API(access_token=access_token, environment="practice")

params = {
    "from": "2022-09-01",
    "to": "2022-10-31",
    "type": "ORDER_FILL",
}

# データ取得
r = transactions.TransactionList(accountID=account_id, params=params)
res = client.request(r)

print(res)


2022年9月1日から2022年10月31日までの取引履歴データが含まれるIDの範囲を取得

結果をみると、pagesに格納されているURLからID情報を確認することができます。

データ数が多い場合、このURLは複数に分かれます。

ここではURLは1つで、from=90&to=201となっているので、IDの範囲が90〜201に該当データが含まれていることがわかります。

データ数(count)は40個です。

2. TransactionIDRangeで各IDの詳細情報を取得する

次に対象IDの詳細情報を取得します。

次のコードでは、pagesのURLから該当するIDの範囲を抽出し、TransactionIDRangeを使って、各IDの詳細情報を取得しています。



from oandapyV20.endpoints import transactions
from oandapyV20 import API
import re 

account_id = "アカウントIDを設定してください"
access_token = "APIトークンを設定してください"

# API用のクラスを定義
client = API(access_token=access_token, environment="practice")

transaction_data = []

for i in res["pages"]:
    # 正規表現を使ってid情報を抽出
    m = re.search(pattern=r"from=(?P[0-9]+)&to=(?P[0-9]+)", string=i)
    
    # パラメータを定義
    data = {
        "type": "ORDER_FILL",
        "from": int(m.group("from")),
        "to": int(m.group("to")),
    }

    # データを取得
    r = transactions.TransactionIDRange(accountID=account_id, params=data)
    res_idrange = client.request(r)
    
    # transactionsでデータを格納
    transaction_data.extend(res_idrange.get("transactions"))


これでデータの取得が完了しました。

TransactionIDRangeで取得したデータには、transactionsとlastTransactionIDがあります。

ここでは、transactionsに格納されているリスト形式のデータのみを利用するので、transasction_dataというリストを用意して、ここに追加しました。

また、上記のコードではpagesの中身を順番に確認していくので、URLが複数ある場合にも対応できます。

参考記事:Pythonを使い「REST API」でトランザクションデータを取得する方法について解説

取得したデータを整形する

次に取得したデータを整形していきます。

データの中身を確認する

まずはデータの内容を確認します。

先ほど取得したtransasction_dataの1つ目を取り出して中身を確認してみます。



print(transaction_data[0])


データの内容を確認

注目するべき点は、tradeOpendというデータがあることです。

transasction_dataの中身を順番にみていくと、tradeOpendとtradeClosedの2種類があることが確認できます。

こちらはtradeClosedが含まれているデータです。



print(transaction_data[1])


tradeClosedが含まれているデータ

tradeOpendはポジションにエントリーした際の約定データ、tradeClosedはポジションをクローズした際の約定データです。

tradeClosedにはtradeIDが含まれており、これを利用すればエントリー時のデータと紐づけることができます。

取引ごとにデータをまとめる

データの中身が確認できたので、取引ごとにデータをまとめていきます。

ここではまずtransasction_dataをtradeOpendとtradeClosedで分けます。

そしてtradeClosedのデータに基づいて、各データをtradeOpendのデータと紐付けて、それぞれから必要なデータを抽出してDataFrameを作成します。

次のコードでは、transaction_dataからDataFrameを作成しています。



import datetime 
import pandas as pd

# transaction_dataをtradeOpendとtradeClosedで分ける
# IDで検索できるように辞書型で定義する
open_trades = {}
close_trades = {}

for transaction in transaction_data:
    # tradeOpend, tradeClosedで分別
    if transaction.get("tradeOpened"):
        open_trades[transaction.get("id")] = transaction
        
    if transaction.get("tradesClosed"):
        close_trades[transaction.get("id")] = transaction


# 抽出データを格納するためのリストを用意
trades = []

# 各tradeClosedデータに対して、tradeOpendデータと紐付けを行い、必要データをtradesに格納する
# ここではエントリー/クローズ時の日時、損益、価格、取引量などをまとめた。
# 日時データはdatetimeに変換、各数値データはintやfloatに適宜変換
for trade in close_trades.values():
    for close in trade["tradesClosed"]:
        data = {}

        # close trade info
        data["instrument"] = trade.get("instrument")
        data["realizedPL"] = float(close["realizedPL"])
        data["close_time"] = datetime.datetime.strptime(trade.get("time")[:-4], "%Y-%m-%dT%H:%M:%S.%f")
        data["close_price"] = float(close["price"])

        # open trade info
        data["open_time"] = datetime.datetime.strptime(open_trades[close["tradeID"]].get("time")[:-4], "%Y-%m-%dT%H:%M:%S.%f")
        data["open_price"] = float(open_trades[close["tradeID"]].get("price"))
        data["units"] = int(open_trades[close["tradeID"]].get("units"))
        
        trades.append(data)
# DataFrameに変換
df = pd.DataFrame(trades)
df.head()


transasction_dataをtradeOpendとtradeClosedで分けます

ここでは、通貨ペア(instrument),損益( realizedPL),取引量(units: マイナスならショートを意味する)、価格(open_priceとclose_price)、取引日時(open_timeとclose_time)の情報を取り出して、 DataFrameにまとめました。

次にエントリー時とクローズ時の価格を使って損益率を計算します。

unitsがプラスならロング、マイナスならショートを意味するため、これを使って計算方法を切り替える必要があります。



import numpy as np

df["pl_rate"] = np.where(
    df["units"]>0, 
    (df["close_price"] - df["open_price"])/ df["open_price"]*100, 
    (df["open_price"] - df["close_price"])/df["open_price"]*100
)
df.head(10)


エントリー時とクローズ時の価格を使って損益率を計算

pl_rateというカラムに損益率が追加されました。

ショート時もきちんと計算できていることが確認できます。

これでデータの準備が整いました。

いろいろな粒度で損益を確認する

それでは用意したデータを使って、損益を確認していきます。

全体的な損益はもちろん、日時別や曜日別、通貨ペア別やロング/ショート別など、データを加工すればさまざまな粒度で見ることができます。

ここでは次の3つの粒度で損益を見ていきます。

  • 1.期間全体
  • 2.通貨ペア別
  • 3.曜日別

可視化を行う際のライブラリはPlotlyを使います。

期間全体の損益を確認する

まずは期間全体の損益を確認します。

合計損益、勝率、合計リターン、合計損失

次のコードでは、期間全体の合計損益、勝率、合計リターン、合計損失を計算しています。



# リターンと損失を集計
total_profit = df[df["realizedPL"]>0]["realizedPL"].sum()
total_loss = df[df["realizedPL"]<0]["realizedPL"].sum()

print("合計損益", round(total_profit + total_loss, 2))
print("勝率", round(len(df[df["realizedPL"]>0]) / len(df) * 100, 2))
print("合計リターン", round(total_profit, 2))
print("合計損失", round(total_loss, 2))


期間全体の合計損益、勝率、合計リターン、合計損失を計算

realizedPLを使って、勝敗を判断して集計しています。

合計損益はわずかにマイナスとなりました。

平均リターン、平均損失、リスクリワード比率

次に平均リターンと平均損失を計算して、そこからリスクリワード比率を計算します。



# リターンと損失を集計
avg_profit = df[df["pl_rate"]>0]["pl_rate"].mean()
avg_loss = df[df["pl_rate"]<0]["pl_rate"].mean()

print("平均リターン", round(avg_profit, 3))
print("平均損失", round(avg_loss, 3))
print("リスクリワード比率", round(avg_profit / abs(avg_loss), 2))


平均リターンと平均損失を計算して、そこからリスクリワード比率を計算

リスクリワード比率は平均リターンと平均損失の比で計算します。

リスクリワード比率が1を超えると、平均リターンが平均損失を上回っていることを意味するため、トータルで利益を上げられる可能性が高くなります。

通貨ペア別の損益を確認する

次に通貨ペア別の損益を見てみます。

Win/Lossというカラムを追加して、通貨ペア別のリターンと損失をそれぞれ計算します。

そしてagg()を使って、realizedPLでは合計値、pl_rateでは平均値を計算します。



df["Win/Loss"] = np.where(df["realizedPL"]>0, "Win", "Loss")
df_agg = df.groupby(["instrument", "Win/Loss"]).agg({"realizedPL": "sum", "pl_rate": "mean"}).sort_index(ascending=False).reset_index()
df_agg


Win/Lossというカラムを追加して、通貨ペア別のリターンと損失をそれぞれ計算

これで計算は完了です。

次からはこのデータをチャートで可視化していきます。

合計損益

まずは合計損益から見てみます。

次のコードではリターンと損失の合計を棒グラフで示しています。



import plotly.express as px

df_agg = df.groupby(["instrument", "Win/Loss"]).agg({"realizedPL": "sum", "pl_rate": "mean"}).sort_index(ascending=False).reset_index()

fig = px.bar(data_frame=df_agg, x="Win/Loss", y="realizedPL", color="instrument")

fig.show()


リターンと損失の合計を棒グラフで表示

通貨ペア別に積み上げ棒グラフにしました。

各通貨におけるリターンと損失の内訳を簡単に確認できます。

平均リターン率・平均損失率

次に平均リターン率および損失率を可視化してみます。

ここでは通貨ペア別にチャートを分けて表示してみました。



df_agg = df.groupby(["instrument", "Win/Loss"]).agg({"realizedPL": "sum", "pl_rate": "mean"}).sort_index(ascending=False).reset_index()

fig = px.bar(data_frame=df_agg, x="Win/Loss", y="pl_rate", color="Win/Loss", facet_col="instrument")

fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
fig.show()


平均リターン率および損失率を可視化

各通貨ペアの損益状況が簡単に確認できます。

結果を見ると、USD_JPYではリターンが損失を上回っており、EUR_USDの損失がリターンを上回っていることがわかります。

曜日別の損益を確認する

最後に曜日別の損益を見てみます。

まずはポジションをクローズした日を基準として曜日を求めます。



df["day_of_week"] = df["close_time"].apply(lambda x: x.strftime("%A"))
df.head()


ポジションをクローズした日を基準として曜日を求めます

day_of_weekというカラムが追加されました。

こちらを使って、曜日別の損益を計算します。

合計損益

まずは合計損益です。

曜日別でリターンと損失をそれぞれ集計します。

結果を棒グラフで可視化します。



df_agg = df.groupby(["day_of_week", "Win/Loss"]).agg({"realizedPL": "sum", "pl_rate": "mean"}).reset_index()

fig = px.bar(data_frame=df_agg, x="day_of_week", y="realizedPL", color="Win/Loss")

fig.update_layout(
    xaxis = dict(
        categoryarray=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
    )
)

fig.show()


曜日別でリターンと損失をそれぞれ集計

結果を見ると、月曜日と火曜日は勝ち越していますが、木曜日と金曜日は負け越していることがわかります。

平均リターン率・平均損失率

最後に曜日別の平均のリターン率と損失率をみてみます。

通貨ペアごとにチャートを分けて表示します。



df_agg = df.groupby(["instrument", "day_of_week", "Win/Loss"]).agg({"realizedPL": "sum", "pl_rate": "mean"}).reset_index()

fig = px.bar(data_frame=df_agg, x="day_of_week", y="realizedPL", color="Win/Loss", facet_col="instrument", facet_col_wrap=1)

fig.update_layout(
    xaxis = dict(
        categoryarray=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
    ),
    height=800
)
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))


fig.show()


通貨ペアごとにチャートを分けて表示

各通貨ペアの曜日別の損益情報が可視化されました。

これを見ると通貨ペアごとの特徴を簡単に把握できます。

こういったデータに基づいて、売買戦略の改善や考案に活用することができます。

このように、APIを使って過去の取引データを集計すると、さまざまな粒度で損益を確認できます。

期間全体のパフォーマンスはもちろん、通貨ペア別や曜日別、本記事では取り上げていませんが、時間帯別や保有期間別、日別など、いろいろな確認方法があります。

こういったデータを活用すれば、自身の売買戦略の特徴や弱点や強みなどを把握できます。

把握したデータに基づいて売買戦略を改善していけば、さらにパフォーマンスを向上させることができるでしょう。

本記事の執筆者:TAT

               
本記事の執筆者:TAT 経歴
TAT2016年大学院卒業後、外資系IT企業に入社。
その後は金融情報→不動産テック→アドテク企業で、Pythonを用いたプロセスオートメーション、ダッシュボード開発、データ分析、AI開発などの業務に従事。
プログラミングや株式投資に関する情報を発信する「気ままなブログ」を運営中。

「REST API」をさらに学びたい方へオススメのコンテンツ

API

OANDA証券では、「REST API」に関する記事を豊富に提供しています。
「REST API」を利用するための手順、プログラミング言語の一つPythonを使い「REST API」を使用するまでの手順など、初心者の方向けコンテンツも豊富にあるので、APIを使った取引を始めてみたい方はぜひ参考にしてください。
ただしAPIを利用した取引は、OANDA証券の口座開設+いくつかの条件があります。
事前に確認を行い、ぜひOANDA証券の口座を開設し「REST API」を使った取引をご検討ください。


本ホームページに掲載されている事項は、投資判断の参考となる情報の提供を目的としたものであり、投資の勧誘を目的としたものではありません。投資方針、投資タイミング等は、ご自身の責任において判断してください。本サービスの情報に基づいて行った取引のいかなる損失についても、当社は一切の責を負いかねますのでご了承ください。また、当社は、当該情報の正確性および完全性を保証または約束するものでなく、今後、予告なしに内容を変更または廃止する場合があります。なお、当該情報の欠落・誤謬等につきましてもその責を負いかねますのでご了承ください。

この記事をシェアする

ホーム » APIの使い方 » Pythonを使い「REST API」で過去の取引履歴を取得して損益を計算する方法