📊

FX取引と五十日アノマリー:その発見と新たな可能性の探索

2023/05/14に公開

FX(外国為替取引)の世界では、ドル円の「五十日仲値アノマリー」という現象が注目を集めています。この現象は、一部のトレーダーによって検証され、自動売買ツールに組み込まれ、実際のトレーディングに活用されているという報告があがっています。

ところで、この五十日アノマリーは誰によって初めて発見されたのでしょうか?そして、このような情報はどのようにして生まれ、広まっていくのでしょうか?こういった疑問を抱くのは自然なことです。

また、アノマリーとは、通常のパターンから外れた現象を指す言葉です。では、この五十日アノマリー以外にも、時間帯による値動きを調べることで新たなアノマリーが発見できる可能性はあるのでしょうか?もしかしたら、我々は「第二の五十日アノマリー」を見つけ出すかもしれません。

そこで、まずはPythonというプログラミング言語を使って五十日アノマリーを探し出すことができるかどうか検証してみます。その後、特定の時間帯に取引の優位性が存在するかどうかをPythonを使って調査していきたいと考えています。

以上が、現在のFX市場で話題となっている五十日アノマリーと、それに続く新たな可能性を探求するためのプロジェクトの概要です。これからこの探索の過程と結果を共有していきますので、ぜひ引き続きご覧ください。

Pythonを用いたFXアノマリー探索:その方法と期待

まず、我々が行うべき第一歩は、五十日アノマリーが実際に存在するのか、そしてそれがPythonで検出可能であるかを確認することです。具体的には、ドル円の過去1.5年分のデータを集め、そのパターンを解析します。その過程でPythonの強力なデータ解析ライブラリ、pandasを活用してデータを取り扱い、matplotlibを使って結果を視覚化することができます。

このアノマリーが存在することが確認できれば、次に取り組むのは、他の通貨ペアや時間帯で同様のアノマリーが見つかるかどうかを調査することです。この段階では、ドル円と時間帯の組み合わせを試し、結果を比較します。

為替レートの時刻帯別変動とリターンの最適化

このプロジェクトでは、MetaTrader 5 (MT5) を使用して為替レートのデータを取得し、特定の時刻帯での為替レートの変動を分析します。その後、異常値の発生を検出し、特定の時間帯でのリターンが最大となる時間帯を探します。

データの取得

まず、MT5からUSDJPYのレートデータを取得します。時間足は5分足を用い、最新から過去に向かって100,000件のデータを取得します。

為替のヒストリカルデータはExnessのMT5のデータ(GMT+0)を使うので日本時間に合わせるため時刻に9を足しています。

import pandas as pd
import MetaTrader5 as mt5
from datetime import timedelta

# MT5への接続
mt5.initialize()

# 読み込む通貨ペアと時間枠、データの件数を指定
symbol = 'USDJPYm'
timeframe = mt5.TIMEFRAME_M5
count = 100000

# データの取得
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)

# DataFrameに変換
df = pd.DataFrame(rates)

# 日時をPandasの日時形式に変換し、GMT+9に変換
df['time'] = pd.to_datetime(df['time'], unit='s') + timedelta(hours=9)

print(df)

データの整形と可視化

次に、特定の時間帯でのレートの変動を計算します。ここでは、例として0時から10時までの時間帯を対象にします。この数値を変化させるとリターンをすぐに計算できるのでいじってみてください。

# 開始時間と終了時間を指定
start_hour = 0
end_hour = 10

# 指定した時間帯のデータのみを選択
df_period = df.set_index('time').between_time(f'{start_hour}:00', f'{end_hour}:00').reset_index()

# 各日の始まりと終わりのレコードを抽出
df_start = df_period.groupby(df_period['time'].dt.date).first()
df_end = df_period.groupby(df_period['time'].dt.date).last()

# 各日の開始価格から終了価格までの変化率を計算
df_start['rate_change'] = (df_end['close'] - df_start['open']) / df_start['open']

# 累積リターンを計算
df_start['cumulative_return'] = (1 + df_start['rate_change']).cumprod() - 1

# データの可視化
plt.figure(figsize=(15,7))
plt.plot(df_start.index, df_start['cumulative_return'])
plt.title('Cumulative Returns Over Time')
plt.xlabel('Time')
plt.ylabel('Cumulative Returns')
plt.show()

次に曜日と日付のヒートマップを作成します。
ヒートマップをみると特定日のリターンが可視化されます。

# データフレームのインデックスを日付に変換
df_start.index = pd.to_datetime(df_start.index)

# 各日の曜日を取得
df_start['day_of_week'] = df_start.index.dayofweek

# 各日と曜日ごとの平均リターンを計算
df_return = df_start.groupby([df_start.index, 'day_of_week'])['rate_change'].mean().unstack()

# データフレームのインデックスを日付に変換
df_return.index = pd.to_datetime(df_return.index)

# 日付から日(1〜31)を取得
df_return['day_of_month'] = df_return.index.day

# 日と曜日ごとの平均リターンを計算
df_return = df_return.groupby('day_of_month').mean()

print(df_return)

import seaborn as sns

plt.figure(figsize=(10, 6))
sns.heatmap(df_return, cmap='coolwarm', center=0)
plt.title('Return by Day of Month and Day of Week')
plt.xlabel('Day of Week')
plt.ylabel('Day of Month')
plt.xticks(ticks=range(7), labels=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'])
plt.show()

# 開始時間と終了時間を指定
start_hour = 0
end_hour = 10

次は円高傾向になるという10時から15時までをヒートマップで見てみます。

# 開始時間と終了時間を指定
start_hour = 10
end_hour = 15

ここまでやってみた感想は別に五十日でなくても時間帯による傾向が表れた結果になった。

リターン最大化の時間帯探索

次に、特定の時間帯でのリターンが最大となる時間帯を探します。全ての可能な開始時間と終了時間の組み合わせに対してループを行い、それぞれの時間帯における累積リターンを計算します。そして、リターンがこれまでの最大値を超えていたら、そのリターンとその時間帯を記録します。

max_return = -float('inf')
max_start_hour = None
max_end_hour = None

# 全ての可能な開始時間と終了時間の組み合わせに対してループ
for start_hour in range(24):
    for end_hour in range(start_hour+1, 24):  # 終了時間は開始時間より後でなければならない
        # 指定した時間帯のデータのみを選択
        df_period = df.set_index('time').between_time(f'{start_hour}:00', f'{end_hour}:00').reset_index()

        # 各日の始まりと終わりのレコードを抽出
        df_start = df_period.groupby(df_period['time'].dt.date).first()
        df_end = df_period.groupby(df_period['time'].dt.date).last()

        # 各日の開始価格から終了価格までの変化率を計算
        df_start['rate_change'] = (df_end['close'] - df_start['open']) / df_start['open']

        # 累積リターンを計算
        df_start['cumulative_return'] = (1 + df_start['rate_change']).cumprod() - 1

        # DataFrameが空でない場合のみ、リターンをチェック
        if not df_start['cumulative_return'].empty:
            final_return = df_start['cumulative_return'].iloc[-1]
            # リターンがこれまでの最大値を超えていたら、最大値とその時間帯を更新
            if final_return > max_return:
                max_return = final_return
                max_start_hour = start_hour
                max_end_hour = end_hour

print(f'Maximum return is {max_return} which is achieved between {max_start_hour}:00 and {max_end_hour}:00.')

これによると5時でロングして21時でショートする最大のリターンになる組み合わせです。

# 開始時間と終了時間を指定
start_hour = 5
end_hour = 21

見た感じはそこまで傾向は無さそうな気がする。月曜日と金曜日が円安傾向ですね。

次に時間の詳細を見てみます。

1時間後の価格変化をヒートマップで可視化する

import pandas as pd
import MetaTrader5 as mt5
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
from datetime import timedelta

# MT5への接続
mt5.initialize()

# 読み込む通貨ペアと時間枠、データの件数を指定
symbol = 'USDJPYm'
timeframe = mt5.TIMEFRAME_M5
count = 100000

# データの取得
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)

# DataFrameに変換
df = pd.DataFrame(rates)

# 日時をPandasの日時形式に変換し、GMT+9に変換
df['time'] = pd.to_datetime(df['time'], unit='s') + timedelta(hours=9)

# 1時間前の終値を取得
df['prev_close'] = df['close'].shift(12)

# 時間ごとの変化率を計算
df['hourly_change_rate'] = (df['close'] - df['prev_close']) / df['prev_close'] * 100

# 欠損値を削除
df.dropna(inplace=True)

# ヒートマップの作成と可視化
heatmap_data = df.pivot_table(index=df['time'].dt.hour, columns=df['time'].dt.day, values='hourly_change_rate')
plt.figure(figsize=(10, 6))
sns.heatmap(heatmap_data, cmap='RdYlGn', cbar=False, annot=False)
plt.title('Hourly Price Change Rate Heatmap')
plt.xlabel('Day')
plt.ylabel('Hour')
plt.show()

ぱっと見はよくわからないけど、29日~31日の10時から15時少し円高傾向っぽいですね。

TradingViewで確かめてみます。

// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © hosono_p

//@version=5
strategy("tansaku", overlay=true)

current_day= dayofmonth(time("1"), "Asia/Tokyo")
saturday = dayofmonth(time("1")+ 1000*60*60*24 , "Asia/Tokyo")
sunday = dayofmonth(time("1")+ 1000*60*60*48, "Asia/Tokyo")
is_sell_day= false

sell_days = array.from(29,30,31)

for i = 0 to array.size(sell_days) - 1
    if current_day == array.get(sell_days, i)
        is_sell_day := true

sell_time = time("1", "0900-1500:23456","Asia/Tokyo") and is_sell_day
bgcolor(sell_time ? color.new(color.red, 90) : na)


if strategy.position_size < 0
    if not sell_time
        strategy.close_all() 
if strategy.position_size == 0
    if sell_time
        strategy.entry("sell",strategy.short)

直近はこの傾向が強いみたいですね。

考察

データを可視化すると29日,30日,31日の傾向が浮き彫りにできました。10時から15時はショートしてみるといいかも知れません。あと最適化した結果のヒートマップを見ると29日,30日,31日の15時から21時までは円安傾向なのでそれもチャンスがありそうです。

29日,30日,31日 15時ロング21時に決済

結果:五十日アノマリーのPythonによる探索

データの可視化を通じて、五十日アノマリーの傾向を探ろうとしましたが、特定の傾向は見つかりませんでした。そのため、五十日アノマリーの仮説は、五十日に関連する商習慣が存在するという点から成り立つと考えられます。

月末の日付(29日、30日、31日)において、10時から15時の時間帯にアノマリーが発生していることが見つかりました。

さらに、データの最適化を行った結果だけでいうとロング(買い)の取引は朝の5時に行うと良さそうです。決済(売り)の取引は夜の21時に行うと良さそうです。

この結果からは、五十日アノマリーの存在が確認できませんでしたが、時間帯による値動きの特徴が見つかりました。これらの情報を活用することで、トレーディング戦略の改善やリスク管理の向上が期待できます。

今後の展望としては、さらに多くのデータや異なる通貨ペア、時間帯に対して分析を行い、他のアノマリーや優位性のある取引条件を探求することが考えられます。

Discussion