見出し画像

FX戦略検討~ヒストリカルデータ

前置き

たけパパです。
キース・フィッチェンさんの「トレードシステムの法則」を読んでいます。
株式、商品、FXなどのマーケットの中で、トレンドフォロー、カウンタートレンドの戦略を調べてみると、マーケットによって適した戦略が異なるといった検証がなされていて、自分でもやってみたいと思いました。
とにかくたくさんのデータを使って検証することが大切、ということが述べられていて、できるだけ多くのヒストリカルデータを使ってこういった分析をするにはどうするのが簡単で早いか、TradingView, MT4, Pythonでやる方法について考えてみました。

記事の記載内容には十分注意しておりますが、誤記や認識間違い等あるかもしれませんのでご容赦ください。また、投資助言や勧誘にあたるものではありません。本記事の内容に基づいた投資結果については、一切の責任は負わないものとします。投資は自己の判断のもと、自己責任でお願い致します。

ヒストリカルデータの検討

TradingView

TradingViewは、銘柄と時間足、契約プランによりますがかなり古いデータでも利用可能。例えばPro+で日足だと、主要通貨ペアでは1970~1986年くらいのデータから利用可能です。日中足になると、バーの数で制約が出てきて、1時間足だとPro+でも1年くらいしか遡れません。
なので日足で良ければTradingViewのPineスクリプトを組んで検証したくなるのですが、どうやらPineスクリプトでは、ストラテジーの中でチャートに表示したものと別の銘柄に注文を出すことができなさそう。strategy.entryというコマンドを使うのですが、銘柄を指定する引数がないんですよね。

strategy.entry(id, direction, qty, limit, stop, oca_name, oca_type, comment, alert_message, disable_alert)

ということで、チャートに表示した単一銘柄を見るのは良いのですが、複数銘柄を比較して良いものをエントリーする、といったロジックは組めなさそう。
また、単にたくさんの銘柄についてそれぞれバックテストをしてみる場合でも、バッチファイルが組めなさそうなので手動で延々とやらなければなりません。FXの20通貨ペアくらいならまだしも、作ったシステムでS&P500の500銘柄の過去のパフォーマンスを検証しよう、とか思ったら地獄です。

MT4

ではMT4の方はどうでしょうか?
MT4のデフォルトのヒストリカルデータは正確性に欠けることで有名のようで、できるだけ正確なヒストリカルデータをインポートする方法が色々と紹介されています。

MT4に正確なヒストリカルデータをインポートする方法|FX-EAラボ (fx-ea-labo.com)

こちらのサイトを参考に、デューカスコピージャパンからヒストリカルデータを入手してみたところ、主要通貨ペアで2003年~といったところでした。
MT4だとTick Data Suiteという有料サービスもあり、一番正確なヒストリカルデータを入手可能です。ただ、Tick Data Suiteも基本的にはデューカスコピーが元ネタのようで、入手可能な期間は古いものでも2003年~となっています。リーマンショックは含んでいますが、ドットコムバブルの時期も含めて見てみたいところです。
また、証券会社によるかと思いますが、MT4で扱える銘柄はFX, CFDがメインで、個別株は多くありません。

Yahoo Finance

Yahoo FinanceではAPIを使って過去のチャートデータを入手可能です。APIを使うのでPythonの方法の一部という感じです。TradingViewに出会う前にYahoo FinanceのAPIを使って色々やっていたのを今になって思い出しました。ちゃんと確認していませんが、遡れるデータは2007年くらいまでのようです。やはり、日足ではTradingViewが一番古いデータまで扱えそうです。

Python

Pythonだともちろんトレード専用の解析システムはありませんが、TradingViewからエクスポートしたチャートデータを読み込んでpandasのデータフレームにでもすれば、かなり柔軟に処理できそうです。
一度データを読み込んでしまえば、複数銘柄を取り扱うのも簡単です。

FXのみで、日中足のデータで解析をするならMT4+デューカスコピー(またはTick Data Suite)が良さそうです。日足では、TradingViewのデータをエクスポートしてMT4で読み込める.hstファイルに変換して読み込ませるという方法もありそうですが、そこまでやらなくてもPythonで読んで解析した方が楽そう。ということで、Pythonでやってみることにしました。

データのエクスポート

TradingViewからデータをエクスポートします。
チャートのレイアウト名が表示されているところの右側の三角印から、「チャートデータのエクスポート」を選んでチャートデータをcsvで出力します。

画像

出力はUNIXタイムかISO(UTC+9)を選べます。UNIXタイムの方が汎用性が高そうですが、パッと見て日付時刻が分かりにくいのでISO(UTC+9)で出力しました。自分で読み込むので形式が分かっていれば良いでしょう。
こんなデータが出力されます。

画像
1971年~のドル円データ。357円かぁ・・・

こうして、主要28通貨ペアの日足チャートデータをエクスポートしました。

画像
検討対象の通貨ペア

Pythonでの読み込みと解析

恐ろしく拙いので自分の裸を見られるような恥ずかしさがありますが、書いたコードを載せておきます。
TradingViewでエクスポートしたチャートデータを読み込んで、月の初めで購入し、次の月の初めで売る、というだけのシンプルなものです。

import math
import glob
import pandas as pd
from pathlib import Path

if __name__ == '__main__':
    # ***************************************************
    # パラメータ設定
    # ***************************************************
    short_periods = 20
    Lot_unit = 10000

    # パスを指定
    path_charts_list = r'C:/'
    files_fullpath = glob.glob(path_charts_list + 'FX_*.csv')
    output_path = r'C:/'
    outfile = r'a.csv'
 
    # 日足ファイルを辞書型(DataFrame)に読み込み
    data_dic = {}  # 空の辞書型を作成

    # 日足ファイル中のデータを読み込み
    for fullpath in files_fullpath:
        fname = Path(fullpath).stem # 日足ファイル名を作成(keyのため)
        df = pd.read_csv(fullpath, encoding="shift-jis")
        df.drop(columns=['Volume MA'], inplace=True) # Volume MAを削除
        df['datetime'] = pd.to_datetime(df.time)  # 日付の列を追加
        df.drop(columns=['time'], inplace=True) # time列を削除

        df["ave_short"] = df["close"].rolling(short_periods, min_periods=short_periods).mean()
        df["stddev"] = df["close"].rolling(short_periods, min_periods=short_periods).std(ddof=0)

        # 辞書型へ追加
        data_dic[fname] = df
 
    # ***************************************************
    # トレード計算
    # ***************************************************
    order_dic = {}  # 空の辞書型を作成
    colums = ["order_type", "open_date", "close_date", "open_price", "close_price", "lots", "profit_loss"]

    for stock in data_dic.keys():
        order_dic[stock] = pd.DataFrame()
        df = data_dic[stock]  # 個別銘柄の価格情報 'open','high','low','close','Volume', 'datetime','ave_short', 'stddev'
        pos = 0
        open_price = 0.0
        close_price = 0.0
        buysell = 1
        profit_loss = 0
        lots = 0
        n = 0
        month = 0

        for row in df.itertuples():
            # 月が変わったことを調べる
            if row.datetime.month != month:
                month = row.datetime.month
                # もし注文があれば、最初にクローズする。orderclose
                if pos != 0:
                    close_price = row.open
                    close_date = row.datetime
                    profit_loss = buysell * math.log10(close_price/open_price)  # 損益を計算, 対数変化率
                    pos = pos - lots

                    s = pd.DataFrame([[buysell, open_date, close_date, open_price, close_price, lots, profit_loss]], columns=colums, index=[n])
                    order_dic[stock] = pd.concat([order_dic[stock], s])
                    n = n + 1
 
                # 注文がなければ、始値で購入する ordersend
                if pos == 0:
                    open_price = row.open #建玉価格
                    open_date = row.datetime #購入時刻
                    lots = Lot_unit #ロット
                    buysell = 1 # 1:buy, -1:sell
                    pos = pos + lots

    # ***************************************************
    # 後処理
    # ***************************************************
    # 毎年の平均利益を求める
    BaH = "BaH(対数変化率)"
    BaH_ratio = "BaH(変化率%)"
    num_trade = "num_trade"
    columns = [BaH, num_trade]
    yearly_profit_loss = pd.DataFrame(index=range(1970,2024), columns=columns)
    yearly_profit_loss.fillna(0.0, inplace=True)

    for stock in order_dic.keys():#銘柄ループ
        for row in order_dic[stock].itertuples():#行ごと(トレード毎)に見ていく
            year = int(row.close_date.year)
            yearly_profit_loss.at[year, BaH] = yearly_profit_loss.at[year, BaH] + row.profit_loss
            yearly_profit_loss.at[year, num_trade] = yearly_profit_loss.at[year, num_trade] + 1

    yearly_profit_loss[BaH] = yearly_profit_loss[BaH]/yearly_profit_loss[num_trade]
    yearly_profit_loss.fillna(0,inplace=True) #NAを0埋め
    yearly_profit_loss[BaH_ratio] = (10**yearly_profit_loss[BaH] - 1.0)*100

    # ***************************************************
    # 計算結果をcsvにして保存
    # ***************************************************
    yearly_profit_loss.to_csv(output_path+outfile)

結果

28通貨ペアの1971年~2023年までのバイアンドホールド(BaH)のトレード結果です。トレード数が336となっている1987年からは全28通貨ペアのデータが揃っていて、それ以前はいくつかの通貨ペアのデータがありません。通貨ペアごとに同じ取引数量(ロット)でも損益額が異なるので、対数変化率で計算するようにしました。一番右は普通の変化率に直した結果で、1か月、1トレードあたり平均で-0.07%の損失になることが分かりました。1トレード10万ドルと考えると、単純計算で70ドルの損失です。「トレードシステムの法則」では2000年~2011年、21通貨ペアの平均で-87ドルとなっていたので、おおむね同じ傾向が得られたと思います。

画像
28通貨ペアのバイアンドホールド(BaH)の取引結果

まとめ

複数銘柄を含む横断的な戦略分析をどうやって行こうか考えた末、日足データではTradingViewのデータをエクスポートしてPythonで分析してみることにしました。日中足で2003年~であれば、MT4でデューカスコピーないしTick Data Suiteのデータを使うのが良さそうです。
まずは一番基本となる、バイアンドホールドの戦略をPythonで実装して検証しました。1トレードあたり平均で-0.07%の損失と、FXではバイアンドホールド戦略が機能しないことが確かめられました。
次回はトレンドフォロー、カウンタートレンドの戦略を試してみたいと思います。

今回の記事は以上です。最後までお読みいただきありがとうございました。

いいなと思ったら応援しよう!

コメント

ログイン または 会員登録 するとコメントできます。
note会員1000万人突破記念 1000万ポイントみんなで山分け祭 エントリー7/8(火)まで
FX戦略検討~ヒストリカルデータ|たけパパの自由研究
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1