📈

FX自動売買ツール作り方:為替のヒートマップからアノマリーEAを作成する

2023/05/14に公開
4

はじめに

FX(外国為替)の自動売買ツールは、トレーディングの自動化を可能にし、効率的な取引を実現するためのツールです。本記事では、為替のヒートマップを活用してアノマリー(異常値)を検出し、それを基にした自動売買ツール(Expert Advisor, EA)の作成方法について説明します。

為替のヒートマップとは、通貨ペアの価格変動を視覚化したもので、時間軸と価格レベルを組み合わせて表示します。ヒートマップは、価格の変動や傾向を一目で把握するのに便利であり、アノマリーの検出にも役立ちます。

データの収集と前処理

最初に、過去の価格データを収集しましょう。信頼性のあるデータプロバイダーや取引所からデータを入手することが重要です。取得したデータは、必要に応じて前処理を行い、適切な形式に整えます。

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)

import matplotlib.pyplot as plt

# 開始時間と終了時間を指定
start_time = '00:00:00'
end_time = '23:55:00'

# 指定した時間帯のデータのみを選択
df_period = df.set_index('time').between_time(start_time, end_time).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()

アノマリーの検出手法

ヒートマップで可視化してねらい目を目視で確認します。

曜日ヒートマップの作成

Pythonのデータ解析ライブラリであるpandasや可視化ライブラリであるmatplotlibを使用して、為替のヒートマップを作成します。時間軸を横軸に、価格レベルを縦軸に配置し、各セルの色を価格変動の強さやパターンに対応させます。

# データフレームのインデックスを日付に変換
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()

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()

月曜日と金曜が円安ですね。

日付時間帯のヒートマップの作成

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()

色が濃いのが
2,3,4,5,6,7,8,9,10,11,14,15,16,17,18,19,20,24,25の0時から10時です。もしくは
5,6,9,19,20,21,24,25,27,30,31の17時から0時です。

色薄いのが9,20,23,24,25,29,30,31の10時から15時ですね。

曜日のヒートマップから月曜日と金曜日はBUYのみ。火水木はSELLのみとします。

自動売買ロジックの実装

アノマリーを検出した場合に自動売買を行うロジックを実装します。
TradingViewのPineScriptでやってみます。

// 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")
//ロング
is_buy_day_asa= false
buy_day_asa = array.from(2,3,4,5,6,7,8,9,10,11,14,15,16,17,18,19,20,24,25)
for i = 0 to array.size(buy_day_asa) - 1
    if current_day == array.get(buy_day_asa, i)
        is_buy_day_asa := true

is_buy_day_yoru= false
buy_day_yoru = array.from(5,6,9,19,20,21,24,25,27,30,31)
for i = 0 to array.size(buy_day_yoru) - 1
    if current_day == array.get(buy_day_yoru, i)
        is_buy_day_yoru := true


buy_time =(time("1", "0400-0900:26","Asia/Tokyo") and is_buy_day_asa) or (time("1", "1600-2300:26","Asia/Tokyo") and is_buy_day_yoru)
bgcolor(buy_time ? color.new(color.green, 90) : na)
if strategy.position_size > 0
    if not buy_time
        strategy.close_all() 
if strategy.position_size == 0
    if buy_time
        strategy.entry("buy",strategy.long)

//ショート

is_sell_day= false
sell_days = array.from(9,20,23,24,25,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-1400:345","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)


MT4でバックテスト

実装した自動売買ロジックを過去のデータに対してバックテストします。これにより、過去の相場状況においてどれだけの利益を上げることができたのかを評価することができます。

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2018, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+

#property strict
int MagicNumber = 0001000;
datetime get_japan_time() {
   return TimeCurrent() + 9 * 60 * 60;
}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool is_buy() {
   int i = 0;
   int current_day = TimeDay(get_japan_time());
   int hour=TimeHour(get_japan_time());
   int youbi = TimeDayOfWeek(get_japan_time());

//ロング
   bool is_buy_day_asa = false;
   int buy_day_asa[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18, 19, 20, 24, 25};

   for (i = 0; i < ArraySize(buy_day_asa); i++) {
      if (current_day == buy_day_asa[i])
         is_buy_day_asa = true;
   }

   bool is_buy_day_yoru = false;
   int buy_day_yoru[] = {5, 6, 9, 19, 20, 21, 24, 25, 27, 30, 31};

   for (i = 0; i < ArraySize(buy_day_yoru); i++) {
      if (current_day == buy_day_yoru[i])
         is_buy_day_yoru = true;
   }
   bool is_youbi = youbi==1 || youbi==5 ;
   bool buy_time = (hour >= 5 && hour <= 9 && is_buy_day_asa) || (hour >= 17 && hour <= 24 && is_buy_day_yoru);
   return buy_time && is_youbi;

}

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool is_sell() {
//ショート
   int i = 0;
   int current_day = TimeDay(get_japan_time());
   int hour=TimeHour(get_japan_time());
   int youbi = TimeDayOfWeek(get_japan_time());
   bool is_sell_day = false;
   int sell_days[] = {9, 20, 23, 24, 25, 29, 30, 31};

   for (i = 0; i < ArraySize(sell_days); i++) {
      if (current_day == sell_days[i])
         is_sell_day = true;
   }

   bool is_youbi = youbi==2 || youbi==3 || youbi==4 ;

   bool sell_time = (hour >= 10 && hour <= 14 && is_sell_day && is_youbi);

   return sell_time;

}

void OnTick() {

//エントリー
   if(position_count(OP_BUY)==0 && position_count(OP_SELL)==0 && is_buy()) {
      position_entry(OP_BUY);
   }
   if(position_count(OP_BUY)==0 && position_count(OP_SELL)==0 && is_sell()) {
      position_entry(OP_SELL);
   }

   //エントリー
   if(position_count(OP_BUY)>0 && !is_buy()) {
      position_close(OP_BUY);
   }

   if(position_count(OP_SELL)>0 && !is_sell()) {
      position_close(OP_SELL);
   }


}


//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int position_count(int side) {

   int count = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--) {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
         if(OrderType() == side) {
            if(OrderSymbol()==Symbol()) {
               if(OrderMagicNumber()==MagicNumber) {
                  count++;
               }
            }
         }
      }
   }
   return count;
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void position_entry(int side) {

   double qty = 0.1;

   if(side==0) {
      bool res= OrderSend(NULL,side,qty,Ask,0,0,0,NULL,MagicNumber,0,clrGreen);
   }
   if(side==1) {
      bool res= OrderSend(NULL,side,qty,Bid,0,0,0,NULL,MagicNumber,0,clrRed);
   }

}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void position_close(int side) {
   for(int i = OrdersTotal() - 1; i >= 0; i--) {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
         if(OrderType() == side) {
            if(OrderSymbol()==Symbol()) {
               if(OrderMagicNumber()==MagicNumber) {
                  bool res=  OrderClose(OrderTicket(), OrderLots(), OrderClosePrice(), 0, clrBlue);
               }
            }
         }
      }
   }
}
//+------------------------------------------------------------------+


結論

FX自動売買ツールの作成は、データの収集・前処理、ヒートマップの作成、アノマリーの検出、自動売買ロジックの実装、バックテストと最適化、リアルタイムトレードの実装、モニタリングと改善という一連の手順を経て行われます。適切なデータ分析とロジックの実装に加えて、リスク管理や効果的なパラメータの設定にも注意を払う必要があります。

Discussion

あたるえんぴつあたるえんぴつ

こんにちは。
取得する通貨のデータ、EAのコードに関して夏時間、冬時間を考慮しなくていいのでしょうか。

ホソノPホソノP

夏時間冬時間考慮した方がいいですよ。場合によっては通貨国の夏時間計算したりもしますよ。記事はエクスネスなのでGMT0で固定されてるため夏時間の計算は不要で大丈夫です。