見出し画像

FXの自動取引をするために調べたこと

FXが面白い。チャートを見ているだけであっという間に時間が過ぎていく。市場は土日を除く平日24時間開いており、大なり小なりレンジ(一定の変動幅の中で価格が上がったり、下がったりを何度も繰り返す状態)がある。本業もあり、1日中チャートに張り付いている訳にもいかないので、プログラムで売買すれば儲けられると考えた。

自動取引の障壁は、意外と高い

為替情報の取得

とりあえずデータを集めて分析するか、と考えていたがオープンな(無料)データが全くない。過去の資料で現在は取得できないものが多くあった。私の調べた範囲では、MetaTrader5 (MT5) を使うのが良いと思う。MT5は、FX取引ツールで、Pythonコードからの操作がが可能です。

証券会社はOANDA証券

MT5を扱える証券会社は限られていて日本国内では数社しかない。(2023/06現在)その中からOANDA証券を選ぶ。OANDAではデモ口座を作成してMT5のデモ体験が可能。当たり前だけど口座の開設には時間がかかる。1、2週間ぐらいだったはず。

MacだとMT5は動かない

MT5はWindows環境でないと動かない。ConohaのWindowsServerを契約してMT5を動かした。macOSからWineを使ってMT5を動かすこともできたが、Pythonから操作する際にライブラリが足りなくてエラーになってしまった。

MT5取引プログラム

PythonからMT5を操作するプログラムコードを載せておきます。OANDAのデモ口座が30日間しかアクセスできないため、取引をするところまで行っていません。Pythonだとバックテストも自分で実装する必要がありますが、それもできません。

ログイン

MT5を起動、ログインします。LOGINパラメータは、ご自身の環境に合わせて変更する必要があります。

import MetaTrader5 as mt5

LOGIN_ID = 123456789
LOGIN_SERVER = "DemoAccount"
LOGIN_PASS = "DemoPassword"

def login():
    # MT5と接続
    if not mt5.initialize(login=LOGIN_ID, server=LOGIN_SERVER, password=LOGIN_PASS):
        print(f"initialize() failed, error code = {mt5.last_error()}")
        return False

    # 接続ができたらMT5のバージョンを表示する
    print(f"MetaTrader5 package version {mt5.__version__}")
    
    # 接続状態とパラメータをリクエストする
    print(f"terminal_info={mt5.terminal_info()}")
    
    return True

if login():
    print(f"Login OK")

データ取得

OANDA証券では、過去数年の1分足データは残っていない。10分足とかなら取れたはず。取得したデータによってTIMEFRAMEを切り替える。
取得するデータのタイムゾーンはニューヨークが基準となっている。日本時間で取得したい場合は変換が必要。サマータイムの実装は適当なので注意。

from datetime import datetime, timezone
import pandas as pd
import pytz

# set time zone to UTC
utc = pytz.timezone("Etc/UTC")
tokyo = pytz.timezone("Asia/Tokyo")
est = pytz.timezone('US/Eastern') # ニューヨーク EST=東部標準時 

def convert_jst(time):
    # タイムゾーンの再設定のため、tzinfo属性を削除
    time = time.replace(tzinfo=None)
    return tokyo.localize(time)

def convert_est(time):
    # タイムゾーンの再設定のため、tzinfo属性を削除
    time = time.replace(tzinfo=None)
    return est.localize(time)

def convert_mt5_utc(time):
    convert_time = time
    # 夏時間と冬時間の切替りについて未検証、概算
    if convert_time.month > 3 and convert_time.month < 11:
        convert_time = convert_time - timedelta(hours=3)
    else:
        convert_time = convert_time - timedelta(hours=2)
    # タイムゾーンの再設定のため、tzinfo属性を削除
    convert_time = convert_time.replace(tzinfo=None)
    return utc.localize(convert_time)

def convert_utc_mt5(time):
    convert_time = time
    # 夏時間と冬時間の切替りについて未検証、概算
    if convert_time.month > 3 and convert_time.month < 11:
        convert_time = convert_time + timedelta(hours=3)
    else:
        convert_time = convert_time + timedelta(hours=2)
    # タイムゾーンの再設定のため、tzinfo属性を削除
    convert_time = convert_time.replace(tzinfo=None)
    return utc.localize(convert_time)

# ローカル時間帯オフセットの実装を避けるために「datetime」オブジェクトをUTC時間帯で作成する
dt_from = datetime.now(utc)
dt_from = convert_utc_mt5(dt_from)

symbol = "USDJPY"
tf = mt5.TIMEFRAME_M1 # M1, M2, M3, M4, M5, M6, M10, M12, M15, M20, M30, H1, H2, H3, H4, H6, H8, H12, D1, W1, MN
rates = mt5.copy_rates_from(symbol, tf, dt_from, 1000)

if rates is None:
    print(f"copy_rates_range() failed, error code = {mt5.last_error()}")
else:
    df = pd.DataFrame(rates)
    df['time'] = pd.to_datetime(df['time'], unit='s') # UNIX時間を変換
    df['utc_time'] = df['time'].apply(convert_mt5_utc)
    df['jst_time'] = df['utc_time'].apply(convert_jst)
    df['est_time'] = df['utc_time'].apply(convert_est)

df.tail()

簡単なチャート

折れ線グラフ表示です。

import matplotlib.pyplot as plt
from pandas.plotting import register_matplotlib_converters
register_matplotlib_converters()

# チャートにティックを表示する
plt.plot(df['time'], df['open'], label='open')
plt.plot(df['time'], df['close'], label='close')
 
# 凡例を表示する
plt.legend(loc='upper left')
 
# ヘッダを追加する
plt.title('time rates')
 
# チャートを表示する
plt.show()

ローソク足チャート

ローソク足のグラフ表示です。
mplfinanceをインストールする必要があります。

import mplfinance as mpf
import matplotlib.pyplot as plt

plot_df = df.set_index(['jst_time'])
plot_df = plot_df.head(100)
mpf.plot(plot_df,type='candle', figratio=(2,1), style="binance")

移動平均

加重移動平均と指数移動平均を計算します。

import numpy as np

def wma(value):
    # 加重移動平均
    weight = np.arange(len(value)) + 1
    wma = np.sum(weight * value) / weight.sum()
    
    return wma

def ema(values, period):
    # 指数移動平均
    ema = np.zeros(len(values))
    ema[:] = np.nan # NaN で一旦初期化
    ema[period-1] = values[:period].mean() # 最初だけ単純移動平均で算出
    
    for day in range(period, len(values)):
        ema[day] = ema[day-1] + (values[day] - ema[day-1]) / (period + 1) * 2
    
    return ema

df["SMA3"]=df["close"].rolling(3).mean().round(3)
df["SMA5"]=df["close"].rolling(5).mean().round(3)
df["SMA7"]=df["close"].rolling(7).mean().round(3)
df["WMA3"]=df["close"].rolling(3).apply(wma, raw=True).round(3)
df["WMA5"]=df["close"].rolling(5).apply(wma, raw=True).round(3)
df["WMA7"]=df["close"].rolling(7).apply(wma, raw=True).round(3)
df["EMA3"] = df["close"].ewm(span=3, adjust=False).mean().round(3)
df["EMA5"] = df["close"].ewm(span=5, adjust=False).mean().round(3)
df["EMA7"] = df["close"].ewm(span=7, adjust=False).mean().round(3)
display(df.tail(5))

ジグザグ

チャートの上限、下限を計算します。

import numpy as np
import pandas as pd


PEAK, VALLEY = 1, -1

def _identify_initial_pivot(X, up_thresh, down_thresh):
    """Quickly identify the X[0] as a peak or valley."""
    x_0 = X[0]
    max_x = x_0
    max_t = 0
    min_x = x_0
    min_t = 0
    up_thresh += 1
    down_thresh += 1

    for t in range(1, len(X)):
        x_t = X[t]

        if x_t / min_x >= up_thresh:
            return VALLEY if min_t == 0 else PEAK

        if x_t / max_x <= down_thresh:
            return PEAK if max_t == 0 else VALLEY

        if x_t > max_x:
            max_x = x_t
            max_t = t

        if x_t < min_x:
            min_x = x_t
            min_t = t

    t_n = len(X)-1
    return VALLEY if x_0 < X[t_n] else PEAK

def peak_valley_pivots_candlestick(close, high, low, up_thresh, down_thresh):
    if down_thresh > 0:
        raise ValueError('The down_thresh must be negative.')

    initial_pivot = _identify_initial_pivot(close, up_thresh, down_thresh)

    t_n = len(close)
    pivots = np.zeros(t_n, dtype='i1')
    pivots[0] = initial_pivot

    # Adding one to the relative change thresholds saves operations. Instead
    # of computing relative change at each point as x_j / x_i - 1, it is
    # computed as x_j / x_1. Then, this value is compared to the threshold + 1.
    # This saves (t_n - 1) subtractions.
    up_thresh += 1
    down_thresh += 1

    trend = -initial_pivot
    last_pivot_t = 0
    last_pivot_x = close[0]
    for t in range(1, len(close)):

        if trend == -1:
            x = low[t]
            r = x / last_pivot_x
            if r >= up_thresh:
                pivots[last_pivot_t] = trend#
                trend = 1
                #last_pivot_x = x
                last_pivot_x = high[t]
                last_pivot_t = t
            elif x < last_pivot_x:
                last_pivot_x = x
                last_pivot_t = t
        else:
            x = high[t]
            r = x / last_pivot_x
            if r <= down_thresh:
                pivots[last_pivot_t] = trend
                trend = -1
                #last_pivot_x = x
                last_pivot_x = low[t]
                last_pivot_t = t
            elif x > last_pivot_x:
                last_pivot_x = x
                last_pivot_t = t


    if last_pivot_t == t_n-1:
        pivots[last_pivot_t] = trend
    elif pivots[t_n-1] == 0:
        pivots[t_n-1] = trend

    return pivots


pivots = peak_valley_pivots_candlestick(df.close, df.high, df.low , 0.0001, -0.0001)
df['Pivots'] = pivots
df['Pivot Price'] = np.nan  # This line clears old pivot prices
df.loc[df['Pivots'] == 1, 'Pivot Price'] = df.high
df.loc[df['Pivots'] == -1, 'Pivot Price'] = df.low

pivot_df = df[df['Pivots'] != 0]
display(pivot_df)

最後に

株や為替の自動取引はとても需要があると思うが、取引環境が整備されていない。今回は自動取引どころか為替データへアクセスするまでで終了。また気が向いたらチャレンジする。

この記事が気に入ったらサポートをしてみませんか?