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)
結果をみると、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])
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()
ここでは、通貨ペア(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
これで計算は完了です。
次からはこのデータをチャートで可視化していきます。
合計損益
まずは合計損益から見てみます。
次のコードではリターンと損失の合計を棒グラフで示しています。
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 | 経歴 |
---|---|
2016年大学院卒業後、外資系IT企業に入社。 その後は金融情報→不動産テック→アドテク企業で、Pythonを用いたプロセスオートメーション、ダッシュボード開発、データ分析、AI開発などの業務に従事。 プログラミングや株式投資に関する情報を発信する「気ままなブログ」を運営中。 |
「REST API」をさらに学びたい方へオススメのコンテンツ
OANDA証券では、「REST API」に関する記事を豊富に提供しています。
「REST API」を利用するための手順、プログラミング言語の一つPythonを使い「REST API」を使用するまでの手順など、初心者の方向けコンテンツも豊富にあるので、APIを使った取引を始めてみたい方はぜひ参考にしてください。
ただしAPIを利用した取引は、OANDA証券の口座開設+いくつかの条件があります。
事前に確認を行い、ぜひOANDA証券の口座を開設し「REST API」を使った取引をご検討ください。
本ホームページに掲載されている事項は、投資判断の参考となる情報の提供を目的としたものであり、投資の勧誘を目的としたものではありません。投資方針、投資タイミング等は、ご自身の責任において判断してください。本サービスの情報に基づいて行った取引のいかなる損失についても、当社は一切の責を負いかねますのでご了承ください。また、当社は、当該情報の正確性および完全性を保証または約束するものでなく、今後、予告なしに内容を変更または廃止する場合があります。なお、当該情報の欠落・誤謬等につきましてもその責を負いかねますのでご了承ください。