【Python株自動売買】自作アルゴリズムのコード・メンテナンスとバックテスト改善ガイド

自動化・運用

※本記事のコードや情報は執筆時点の仕様に基づいています。投資は自己責任であり、必ずデモ環境や少額資金でテストした上で運用してください。

Pythonで株のアルゴリズム取引システムを自作し、バックテストで良い結果が出た。しかし、本当に難しいのはここからです。自作したアルゴリズムは「作って終わり」ではなく、定期的なメンテナンスと継続的な改善がなければ、遅かれ早かれ機能しなくなります。

相場の性質は常に変化し、利用しているライブラリやAPIの仕様も予告なくアップデートされます。一度動いたコードが半年後にエラーで停止していた——こうした事態は珍しくありません。

この記事では、Pythonで構築した株の自動売買アルゴリズムを長期的に安定稼働させるための、コード・メンテナンスの実践手法とバックテストを活用した継続的なロジック改善(PDCA)の回し方を体系的に解説します。

なぜアルゴリズムのメンテナンスが必要なのか

「一度作ったら放置でOK」と考える初心者は多いですが、これは最も危険な思い込みです。ここではメンテナンスが不可欠な理由を整理します。

相場環境は常に変化する

金融市場は静的なものではありません。以下のような変化が常に起こっています。

  • ボラティリティの変化: 低ボラティリティ相場から急にボラティリティが拡大する局面が発生する
  • トレンド性の変化: トレンド相場が続いた後にレンジ相場に移行する
  • 市場参加者の変化: 新たなアルゴリズムトレーダーの参入により、従来の優位性が失われる

過去2年間のバックテストで優秀な成績を出した戦略であっても、相場の性質が変われば通用しなくなる可能性は十分にあります。

ライブラリ・APIの仕様変更リスク

Pythonのエコシステムは進化が早く、依存しているライブラリが突然仕様を変更することがあります。

リスク要因具体例影響度
yfinanceのアップデートメソッド名やデータ形式の変更中〜高
pandasのバージョン変更非推奨メソッドの廃止
証券APIの仕様変更エンドポイントやパラメータの変更
Python本体のバージョンアップ文法や標準ライブラリの変更低〜中

アルゴリズム取引のシステムは「投資戦略」と「ソフトウェア」の両方を同時に保守する必要があります。どちらか一方でも怠れば、システム全体が機能不全に陥ります。

メンテナンスを怠った場合に起こること

放置されたアルゴリズムがたどる典型的な末路は以下のとおりです。

  1. ライブラリの更新により、ある日突然スクリプトがエラーで停止する
  2. エラーに気づかないまま、シグナルが生成されない期間が続く
  3. 相場環境の変化に適応できず、ドローダウンが拡大し続ける
  4. 気づいたときには大きな損失を抱えている

コード・メンテナンスの実践手法

ここからは、Pythonコードを長期的に安定稼働させるための具体的なメンテナンス手法を解説します。

依存ライブラリのバージョン管理

最も基本的かつ重要なのが、使用しているライブラリのバージョンを固定・管理することです。

`requirements.txt` というファイルを作成し、以下のようにバージョンを明記しておきます。

yfinance==0.2.36
pandas==2.2.0

このファイルがあれば、環境を再構築する際に `pip install -r requirements.txt` で同一環境を再現できます。

【コピペOK】依存ライブラリの自動バージョンチェック

以下のコードは、現在インストールされているライブラリのバージョンを確認し、想定バージョンとの差異を検出するものです。

import importlib
import sys

# ==============================
# 想定バージョン(自分の環境に合わせて更新)
# ==============================
EXPECTED_VERSIONS = {
    "yfinance": "0.2.36",
    "pandas": "2.2.0",
}

# ==============================
# バージョンチェック処理
# ==============================
def check_versions():
    print(f"■ Python バージョン: {sys.version}\n")
    print(f"{'ライブラリ':<15} {'現在':<15} {'想定':<15} {'状態'}")
    print("-" * 60)

    for lib_name, expected_ver in EXPECTED_VERSIONS.items():
        try:
            lib = importlib.import_module(lib_name)
            current_ver = getattr(lib, "__version__", "不明")

            if current_ver == expected_ver:
                status = "✅ 一致"
            else:
                status = "⚠️ 差異あり"

            print(f"{lib_name:<15} {current_ver:<15} {expected_ver:<15} {status}")

        except ImportError:
            print(f"{lib_name:<15} {'未インストール':<15} {expected_ver:<15} ❌ エラー")

    print("\n--- チェック完了 ---")

if __name__ == "__main__":
    check_versions()

エラーハンドリングとログ出力の実装

本番環境で稼働するアルゴリズムには、適切なエラーハンドリングとログ出力が不可欠です。エラーが発生しても「黙って停止する」のではなく、何がいつ起きたかを記録に残す仕組みを組み込みます。

【コピペOK】ログ出力付きデータ取得テンプレート

以下のコードは、ログファイルを自動生成しながらデータ取得を行うテンプレートです。

import yfinance as yf
import logging
from datetime import datetime

# ==============================
# ログ設定
# ==============================
LOG_FILE = f"algo_log_{datetime.now().strftime('%Y%m%d')}.log"
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[
        logging.FileHandler(LOG_FILE, encoding="utf-8"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# ==============================
# データ取得処理(エラーハンドリング付き)
# ==============================
def safe_fetch(symbol, period="1y"):
    logger.info(f"データ取得開始: {symbol}")

    try:
        ticker = yf.Ticker(symbol)
        df = ticker.history(period=period)

        if df.empty:
            logger.warning(f"データが空です: {symbol}")
            return None

        logger.info(f"取得成功: {symbol} ({len(df)}行)")
        return df

    except Exception as e:
        logger.error(f"取得失敗: {symbol} - {e}")
        return None

# ==============================
# メイン処理
# ==============================
def main():
    symbols = ["7203.T", "6758.T", "9984.T"]

    for symbol in symbols:
        df = safe_fetch(symbol)
        if df is not None:
            filename = f"{symbol}_data.csv"
            df.to_csv(filename)
            logger.info(f"CSV保存完了: {filename}")

    logger.info("=== 全処理完了 ===")

if __name__ == "__main__":
    main()

実行すると、コンソールへの出力と同時にログファイル(例:`algo_log_20250615.log`)が自動生成されます。障害発生時の原因調査に不可欠な仕組みです。

バックテストを活用した継続的なロジック改善(PDCA)

コードの安定性を確保したら、次は戦略そのものを定期的に見直す仕組みを構築します。

アルゴリズムPDCAサイクルの全体像

アルゴリズム取引の改善は、以下の4ステップを繰り返すことで進めます。

ステップ内容頻度の目安
Plan(計画)仮説を立てる(例:RSIフィルターを追加すれば勝率が上がるのではないか)随時
Do(実行)コードを修正し、バックテストを実行する仮説ごと
Check(検証)バックテスト結果を複数の指標で評価するテスト実行後
Act(改善)有効であれば採用、無効であれば棄却して次の仮説へ検証後

重要なのは「1回のバックテストで判断しない」ことです。 複数の期間・複数の銘柄でテストし、安定して有効性が確認できた改善のみを採用してください。

【コピペOK】パラメータ最適化の自動スキャン

「移動平均の期間を5日と25日にしているが、もっと良い組み合わせがあるのではないか」——こうした仮説を手動で一つずつ検証するのは非効率です。以下のコードは、短期・長期移動平均のパラメータを自動的にスキャンし、最も成績の良い組み合わせを発見するものです。

import yfinance as yf
import pandas as pd

# ==============================
# 設定エリア
# ==============================
SYMBOL = "7203.T"
PERIOD = "3y"
SHORT_RANGE = range(3, 15)     # 短期MAの候補(3〜14日)
LONG_RANGE = range(15, 60, 5)  # 長期MAの候補(15〜55日、5日刻み)

# ==============================
# 単一パラメータのバックテスト
# ==============================
def backtest(df, short_w, long_w):
    tmp = df.copy()
    tmp["sma_s"] = tmp["Close"].rolling(window=short_w).mean()
    tmp["sma_l"] = tmp["Close"].rolling(window=long_w).mean()
    tmp.dropna(inplace=True)

    tmp["signal"] = 0
    tmp.loc[tmp["sma_s"] > tmp["sma_l"], "signal"] = 1
    tmp.loc[tmp["sma_s"] <= tmp["sma_l"], "signal"] = -1
    tmp["position"] = tmp["signal"].shift(1)

    tmp["ret"] = tmp["Close"].pct_change()
    tmp["strat_ret"] = tmp["ret"] * tmp["position"]
    tmp.dropna(inplace=True)

    if tmp.empty:
        return None

    cum = (1 + tmp["strat_ret"]).cumprod()
    total_return = round((cum.iloc[-1] - 1) * 100, 2)

    cum_max = cum.cummax()
    drawdown = ((cum - cum_max) / cum_max).min()
    max_dd = round(drawdown * 100, 2)

    trades = int(tmp["position"].diff().abs().sum())

    return {
        "短期MA": short_w,
        "長期MA": long_w,
        "総リターン(%)": total_return,
        "最大DD(%)": max_dd,
        "取引回数": trades,
    }

# ==============================
# パラメータスキャン実行
# ==============================
def param_scan():
    print(f"=== {SYMBOL} パラメータスキャン開始 ===\n")

    ticker = yf.Ticker(SYMBOL)
    df = ticker.history(period=PERIOD)

    if df.empty:
        print("エラー: データが取得できませんでした。")
        return

    results = []
    for s in SHORT_RANGE:
        for l in LONG_RANGE:
            if s >= l:
                continue
            result = backtest(df, s, l)
            if result:
                results.append(result)

    if not results:
        print("有効な結果がありませんでした。")
        return

    result_df = pd.DataFrame(results)
    result_df.sort_values("総リターン(%)", ascending=False, inplace=True)

    print("■ リターン上位10パターン:")
    print(result_df.head(10).to_string(index=False))

    # 安定性重視(DD小さく、リターンも正)
    stable = result_df[
        (result_df["総リターン(%)"] > 0) &
        (result_df["最大DD(%)"] > -20)
    ].head(5)

    print("\n■ 安定性重視の推奨パターン(リターン正 & DD -20%以内):")
    if stable.empty:
        print("  該当なし")
    else:
        print(stable.to_string(index=False))

    result_df.to_csv(f"{SYMBOL}_param_scan.csv", index=False, encoding="utf-8-sig")
    print(f"\n--- 全結果保存: {SYMBOL}_param_scan.csv ---")

if __name__ == "__main__":
    param_scan()

パラメータスキャン結果の正しい読み方

スキャン結果を見る際に注意すべき点があります。

  • リターンが最も高いパラメータが最良とは限らない。 最大ドローダウンが-40%を超えている場合、実運用では精神的に耐えられない可能性が高いです
  • 取引回数が極端に少ないパターンは統計的な信頼性が低く、偶然の結果である可能性があります
  • 複数の隣接パラメータで安定した成績が出ている領域を選ぶのが、過剰最適化を避ける有効な方法です

「リターン上位10」を見るだけでなく、必ず「安定性重視の推奨パターン」も確認してください。実運用では最大ドローダウンの小ささが、戦略を継続できるかどうかを決定づけます。

定期メンテナンスのスケジュール設計

メンテナンスを「思いついたときにやる」のではなく、あらかじめスケジュールに組み込むことで、放置リスクを大幅に低減できます。

推奨メンテナンススケジュール

頻度作業内容目的
毎日ログファイルの確認(エラーの有無)異常の早期発見
毎週直近1週間の取引結果と損益の確認パフォーマンス監視
毎月バックテストの再実行(直近データを含む)戦略の有効性検証
四半期パラメータスキャンの再実行環境変化への適応
半年ライブラリのバージョン更新と互換性テストソフトウェアの安定性維持

メンテナンスチェックリスト

月次メンテナンス時に確認すべき項目をリストにまとめます。

  • [ ] yfinanceで正常にデータが取得できるか
  • [ ] バックテストの結果が前月と大きく乖離していないか
  • [ ] 最大ドローダウンが許容範囲(例:-20%)を超えていないか
  • [ ] 取引回数が異常に増減していないか
  • [ ] ログファイルにエラーや警告が記録されていないか
  • [ ] 使用ライブラリに非推奨警告(DeprecationWarning)が出ていないか

戦略の「引退基準」を事前に定める

すべての戦略にはいつか寿命が来ます。以下のいずれかに該当した場合は、戦略の停止または大幅な見直しを検討する必要があります。

  • 直近3ヶ月のリターンがバックテスト時の想定を大幅に下回っている
  • 最大ドローダウンが事前に設定した上限を超えた
  • バックテストを再実行しても、もはや有効性が確認できない

「この戦略はもうダメかもしれない」と感じたときではなく、事前に定めた数値基準に達したときに機械的に判断するのが正しいアプローチです。感情で判断すると、損失の拡大を招きます。

よくあるエラーと対処法

yfinanceのアップデート後にコードが動かなくなった

yfinanceはアップデート頻度が高いライブラリです。メソッド名やデータの返却形式が変わることがあります。対処法は以下のとおりです。

  • `pip install yfinance==0.2.36` のように、動作確認済みのバージョンを固定する
  • アップデートする場合は、必ず既存コードのテストを実行してから本番環境に反映する
  • yfinanceのGitHubリポジトリのリリースノートで変更内容を確認する

pandasの「FutureWarning」が大量に表示される

pandasのバージョンアップに伴い、将来廃止予定のメソッドを使用していると `FutureWarning` が表示されます。すぐにエラーにはなりませんが、次回のメジャーアップデートで動作しなくなる可能性があります。

対処法は、警告メッセージに記載されている代替メソッドへの置き換えです。放置せず、月次メンテナンスの際に対応することを推奨します。

バックテスト結果が月ごとに大きくブレる

これは戦略そのものの不安定性を示している可能性があります。以下の点を確認してください。

  • パラメータが過剰最適化されていないか
  • 特定の相場環境(例:急騰・急落局面)に依存した結果になっていないか
  • 取引回数が少なすぎて統計的に有意でない可能性がないか

対策としては、検証期間を3年以上に延長する、異なる銘柄でもテストする、といった方法が有効です。

まとめ

Pythonで構築した株のアルゴリズム取引システムを長期的に運用するためのポイントを整理します。

  • アルゴリズムは「作って終わり」ではなく、定期的なメンテナンスと改善が不可欠です
  • ライブラリのバージョン管理とログ出力の仕組みを最初に構築しておくことで、障害対応が格段に楽になります
  • 戦略の改善はPDCAサイクルで回し、パラメータスキャンの結果はリターンだけでなく最大ドローダウンも重視してください
  • 毎日・毎週・毎月・四半期ごとのメンテナンススケジュールを事前に設計し、放置を防ぐ仕組みを作ることが重要です
  • 戦略の引退基準を感情ではなく数値で事前に定めておくことが、長期的な資産保全につながります

今回紹介したログ出力テンプレートとパラメータスキャンコードを自分のシステムに組み込めば、アルゴリズム取引の「運用品質」は大きく向上します。次のステップとして、Slackやメールへのアラート通知機能を追加し、異常検知を自動化する仕組みの構築に進むのが効果的です。

タイトルとURLをコピーしました