アルゴリズムトレード・自動売買を始めて数年が経ちます。この記事では私が実際に犯した失敗と、そこから得た教訓を包み隠さずお伝えします。「勝てるバックテスト」を信じて本番投入し、何度も痛い目を見た経験から学んだことです。
失敗1:先読みバイアスに気づかなかった
何が起きたか
バックテストで年率40%というとんでもない成績が出た戦略を本番投入しました。結果はもちろん大惨敗。後で調べると、終値が確定した後にシグナルを出し、同日の終値で取引する前提になっていました。
# ❌ 私がやってしまった先読みバイアスの例
df["Signal"] = (df["MA25"] > df["MA75"]).astype(int)
df["Return"] = df["Close"].pct_change() * df["Signal"] # 当日終値で判定して当日終値で取引
# ✅ 正しい実装
df["Signal"] = (df["MA25"] > df["MA75"]).astype(int).shift(1) # 翌日に実行
df["Return"] = df["Close"].pct_change() * df["Signal"]
教訓
バックテストの結果が「良すぎる」と感じたら、必ず先読みバイアスを疑ってください。shift(1)が抜けているケースが最も多い初歩的なミスです。
失敗2:過最適化(カーブフィッティング)
何が起きたか
「最適なパラメーターを探せば勝てるはず」と思い、移動平均の日数・RSIの閾値・損切りラインを何百通りも試しました。バックテストでシャープレシオ2.5、最大ドローダウン8%という夢のような戦略が完成。本番では3ヶ月で-15%。
問題のコード(過最適化)
# ❌ 過最適化の例:1000以上のパラメーターの組み合わせを総当たり
best_return = -999
for short in range(1, 100):
for long_ in range(1, 300):
for rsi_buy in range(20, 50):
for rsi_sell in range(50, 80):
# ...バックテスト...
# 最高成績を"最適パラメーター"として採用
pass
# この時点でパラメーターは過去データに完全に過学習している
教訓
パラメーターが多いほど過学習のリスクが高まります。シンプルな戦略ほど将来にも通用しやすいです。必ずウォークフォワード分析で過学習を検証しましょう。
失敗3:コスト(手数料・スプレッド)を無視した
何が起きたか
デイトレード系の戦略でバックテスト年率20%。しかし手数料を入れると年率-5%という悲惨な結果に。
def calculate_net_return(trades_df, commission=0.001, slippage=0.001):
"""手数料・スリッページを考慮した純リターン"""
# 往復コスト = 片道コスト × 2
round_trip_cost = (commission + slippage) * 2
n_trades = len(trades_df)
gross_return = trades_df["return"].sum()
total_cost = round_trip_cost * n_trades
net_return = gross_return - total_cost
print(f"取引回数: {n_trades}")
print(f"粗リターン: {gross_return:.1%}")
print(f"総コスト: {total_cost:.1%}")
print(f"純リターン: {net_return:.1%}")
return net_return
教訓
取引頻度が高い戦略ほどコストの影響が大きいです。手数料・スリッページを必ず組み込み、純リターンで評価してください。
失敗4:本番と同じシステムをテストに使わなかった
バックテストはPythonのローカル環境、本番注文は証券会社の自動注文システム。注文のタイミング・価格の取得方法が微妙に違い、バックテストと本番が乖離しました。
教訓
必ずペーパートレード(仮想取引)で本番に近い環境での検証を挟みましょう。最低1ヶ月はペーパートレードで動作確認してから本番投入することをオススメします。
失敗5:ブラックスワン(想定外の急落)への備えがなかった
コロナショック時に全ポジションを保有したまま。翌日のストップ安で損切りできず、想定の3倍の損失を被りました。
# ✅ 最大ドローダウンを監視して自動停止する仕組み
class RiskManager:
def __init__(self, max_drawdown_limit=0.15):
self.max_drawdown_limit = max_drawdown_limit
self.peak_value = None
self.is_stopped = False
def update(self, current_value):
if self.peak_value is None:
self.peak_value = current_value
self.peak_value = max(self.peak_value, current_value)
drawdown = (self.peak_value - current_value) / self.peak_value
if drawdown >= self.max_drawdown_limit:
self.is_stopped = True
print(f"緊急停止: ドローダウン {drawdown:.1%} が上限を超えました")
return not self.is_stopped
教訓
最大ドローダウンの上限を設定し、超えたら自動停止する仕組みを必ず実装してください。
最終的に気づいたこと
自動売買で継続的に利益を上げるのは簡単ではありません。しかし適切なリスク管理と現実的な期待値を持って取り組むことで、手動売買よりも感情的にはるかに楽に投資できます。失敗から学び、少しずつ改善を続けることが大切です。
まとめ:避けるべき5つの失敗
- 先読みバイアス(shift(1)の忘れ)
- 過最適化(パラメーターの乱用)
- 手数料・スリッページの無視
- ペーパートレードのスキップ
- ブラックスワン対策の欠如

