戦略・検証

移動平均クロス戦略とは?Pythonで実装してバックテストする基本


※本記事にはアフィリエイト広告(プロモーション)が含まれています。

自動売買ボットを作り始めるとき、最初に検討する戦略の一つが「移動平均クロス」です。仕組みがシンプルで実装しやすく、テクニカル分析の入門としてもよく紹介されます。この記事では、移動平均クロス戦略の考え方と、Pythonでシグナルを計算してバックテストする基本的な手順を解説します。

移動平均クロス戦略とは何か

移動平均クロス戦略は、期間の異なる2本の移動平均線の交差をエントリー・エグジットの合図にする戦略です。

  • ゴールデンクロス: 短期移動平均線が長期移動平均線を下から上に抜ける → 買いシグナル
  • デッドクロス: 短期移動平均線が長期移動平均線を上から下に抜ける → 売りシグナル

考え方としては、短期線が長期線を上抜けたということは「直近の値動きが以前より強くなってきた」ことを示唆し、トレンドの転換や継続を捉えようとするものです。移動平均そのものの計算方法はテクニカル指標をPythonで計算する方法で解説しているので、まだの方はそちらを先に読んでおくと理解しやすくなります。

必要なデータを取得する

まずはccxtでOHLCVデータを取得します。ccxtの基本的な使い方はPython+ccxt入門を参照してください。

import ccxt
import pandas as pd

exchange = ccxt.mexc()
ohlcv = exchange.fetch_ohlcv('BTC/USDT', timeframe='1h', limit=1000)

df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)

OHLCVデータをDataFrameとして扱う際の細かい注意点はpandasでOHLCV処理にまとめてあります。

pandasでシグナルを計算する

短期・長期の移動平均を計算し、両者の大小関係が入れ替わったタイミングをシグナルとして取り出します。

short_window = 20
long_window = 75

df['sma_short'] = df['close'].rolling(short_window).mean()
df['sma_long'] = df['close'].rolling(long_window).mean()

# 短期線が長期線より上にあるかどうかを1/0で表す
df['position'] = (df['sma_short'] > df['sma_long']).astype(int)

# 状態が切り替わった足だけがシグナル(1=ゴールデンクロス, -1=デッドクロス)
df['signal'] = df['position'].diff()

signal が 1 になった足がゴールデンクロス(買い転換)、-1 になった足がデッドクロス(売り転換、あるいは決済)です。ローソク足の確定を待たずにシグナルを使うと未来の値を先取りする「ルックアヘッド」バイアスが混入するので、必ず確定済みの足でシグナルを判定するようにしてください。

ベクトル化してシンプルにバックテストする

for文でループを回すよりも、pandasの演算をそのまま使う「ベクトル化」の方が高速で、コードも見通しが良くなります。

# 1本先の値動きに対して、その足でのポジション(0 or 1)を掛け合わせる
df['returns'] = df['close'].pct_change()
df['strategy_returns'] = df['position'].shift(1) * df['returns']

# 累積リターンを確認する
df['cumulative'] = (1 + df['strategy_returns']).cumprod()

position.shift(1) を使っているのは、シグナルが確定した「次の足」からエントリーする形にするためです。ここをずらし忘れると、シグナル確定と同時に約定した前提になり、実際には有り得ない好条件で計算してしまいます。

手数料を忘れずに組み込む

移動平均クロス戦略はエントリー・エグジットの頻度がそこそこ高くなりやすく、手数料の影響を無視すると実態とかけ離れた結果になります。

fee_rate = 0.0006  # 例: テイカー手数料 0.06%(取引所により異なるため要確認)
trade_count = df['signal'].abs().sum()  # クロスが発生した回数
df['strategy_returns'] -= df['signal'].abs().shift(1).fillna(0) * fee_rate

※ 手数料率は取引所やメイカー/テイカー、キャンペーンの有無によって変わるため、必ず利用する取引所の最新情報を確認してください。手数料体系の基本はメイカー/テイカー手数料ガイドで解説しています。

検証結果の見方で注意すべきこと

バックテストで良さそうな数字が出ても、それをそのまま信じるのは危険です。

  • パラメータを細かく調整しすぎない: 短期・長期の期間を何十通りも試して一番良かった組み合わせを採用すると、過去データに過剰適合しているだけの可能性が高くなります
  • 検証期間を分けて確認する: ある期間だけで良い成績でも、別の期間では崩れることがあります。ウォークフォワード検証のように学習期間と検証期間を分けて確認する方法が有効です
  • レンジ相場は苦手: 移動平均クロスはトレンドが出ている相場向きの戦略で、方向感のないレンジ相場では「だまし」のクロスが多発し、手数料負けしやすい傾向があります

バックテストの数字の解釈で陥りがちな罠についてはバックテストの罠で詳しく取り上げているので、あわせて確認しておくことをおすすめします。成績を評価する際の指標の見方は成績指標の読み方も参考になります。

PR

この記事のようにAPI経由でOHLCVデータを取得してボットを検証する場合、実際に使う取引所のアカウントが必要になります。MEXCはAPI仕様が比較的シンプルで、ボット開発の検証環境としてもよく利用されています。

MEXCの口座を無料で開設する(紹介リンク)

まとめ

  • 移動平均クロス戦略は、短期線と長期線の交差(ゴールデンクロス/デッドクロス)をシグナルにするシンプルな戦略
  • rolling().mean() と大小比較で、pandasだけで簡単にシグナルを計算できる
  • バックテストは shift(1) で「シグナル確定後にエントリー」の形にし、ルックアヘッドを避ける
  • 手数料を組み込まないと、実態とかけ離れた良い成績に見えてしまう
  • パラメータの過剰調整や単一期間だけの検証は禁物。複数期間・ウォークフォワードで確認する

移動平均クロスはあくまで戦略の「型」の一つに過ぎません。そのまま使うより、他の指標と組み合わせたり、レンジ相場を避けるフィルターを加えたりと、自分のボットに合わせて発展させていくのが実践的な使い方です。