【株×Python】「GC予備軍スキャナー」自作してみた!クロス直前銘柄&到達日数を一瞬で炙り出す開発日記

Pythonで自作した日経225ローソク足形状スキャナーを使ってデイトレ銘柄を選定する個人投資家 未分類

こんにちは、株道 経済研究所です。

前回の記事『【株×Python】「ローソク足形状特化スキャナー」自作してみた!デイトレ銘柄を一瞬で炙り出す開発日記』では、標準ツールでは絞り込めないローソク足の形状(下ヒゲピンバーや大陽線)を自動抽出する自作プログラムをご紹介しました。

今回は、Python自作ツール開発日記の第2弾をお届けします!

テーマは、テクニカル分析の王道中の王道……「ゴールデンクロス(GC)」です。

ですが、ただのゴールデンクロス検出ツールではありません。

皆さんも一度は思ったことがありませんか?

「ゴールデンクロスが発生した『後』に買っても、すでに高値掴みで遅すぎるんだよな……」

――そう、投資で本当に勝ちたいなら、クロスした瞬間に飛びつくのではなく、「まさにこれからクロスしようと猛スピードで接近している『GC予備軍』」を前もって捕捉しなければ意味がありません。

既存の証券会社ツールには「ゴールデンクロスした銘柄」のランキングはあっても、「あと何日でクロスしそうか」まで計算してくれるツールなんて存在しません。

――無いなら、作るしかない。(2回目)

今回は、移動平均線の「接近速度」を計算し、「あと何営業日でクロスするか」という予測日数まで一発で弾き出す『GC予備軍スキャナー』をPythonで自作しました!

1. なぜ「発生後のGC」は勝てないのか?

移動平均線(25日線と75日線など)が交差する「ゴールデンクロス」は、買いシグナルとして超有名です。

しかし、一般的なスクリーナーで「本日ゴールデンクロスした銘柄」を抽出して翌朝買ってみると、以下のような憂き目によく遭います。

  • すでに株価が上がりきっていて高値掴みになる
  • 買った途端に利確売り(材料出尽くし)に押されて下落する

つまり、「GCが発生してから動く」のでは遅すぎるのです。

本当に狙うべきは、「25日線が下から急角度で上昇し、75日線にあと数日でタッチしそうな『予備軍』の状態」

この「クロス直前のエネルギーが溜まっている状態」の銘柄をあらかじめリストアップしておき、ブレイクする瞬間を待ち伏せる(先回りする)ことこそが、トレードで優位性を生む極意です。

2. 今回作った「GC予備軍スキャナー」の凄さとこだわりロジック

今回自作したスキャナーは、単に「2本線の距離が近い銘柄」を出すだけではありません。僕が徹底的にこだわった4つの神ロジックを組み込んでいます。

① 距離(乖離率)の制限

25日線と75日線の距離が「8%以内」まで近づいている銘柄だけに絞り込みます(MAX_DIFF_RATE = 0.08)。

② 直前で「差が縮まっている」ことの証明

昨日より今日の方が、2つの線の距離が縮まっていなければ「近づいている」とは言えません。並走してズルズル下げているようなダマシ銘柄をここで叩き落とします(REQUIRE_DIFF_GETTING_SMALLER = True)。

③ 短期線(25日線)の上向き条件

短期線がしっかり上を向いて(上昇モメンタムがあって)接近していること(REQUIRE_SHORT_MA_UP = True)。

④ 【最重要】過去5日間の「相対速度」から到達営業日数を算出

ここが本ツールの真骨頂です!

直近1日だけの値動きだとノイズが大きいため、「過去5日間の25日線の移動スピード」と「75日線の移動スピード」の差(相対速度)を計算。

$$\text{相対速度} = \text{短期MAの5日平均変化量} – \text{長期MAの5日平均変化量}$$

現在の2線の距離をこの「相対速度」で割ることで、「今のペースを維持した場合、あと約○.○営業日後にクロスするか」という予測日数を弾き出します!

さらに「クロスまで15営業日以上かかるモメンタム不足の亀足銘柄」は自動で除外する仕様にしました。

3. 環境構築

今回のプログラムでは、スキャン結果を美しい格子状のテーブル(表)としてターミナルに出力するために、新たに tabulate というライブラリを使用します。

コマンドプロンプト(Windows)またはターミナル(Mac)を開き、以下のコマンドを実行してください。

Bash

pip install yfinance pandas tabulate

これだけで準備完了です!

4. 完成したコード(コピペOK)

管理を容易にするため、設定ファイル・銘柄読み込み・メイン処理の3つのファイルに分けて作成しています。同じフォルダ内に保存して実行してください。

① 設定ファイル:config.py

パラメーターを変更したい場合は、このファイルをいじるだけで一発で調整できるように設計しました

Python

"""
config.py
GC(ゴールデンクロス予備軍)スキャナー設定
"""

# ==========================
# 移動平均線設定
# ==========================

# 短期移動平均線
SHORT_MA = 25

# 長期移動平均線
LONG_MA = 75


# ==========================
# データ取得設定
# ==========================

# 移動平均計算に必要な期間
LOOKBACK_DAYS = 150


# ==========================
# GC接近判定設定
# ==========================

# 25日線と75日線の差が何%以内なら候補にするか(例:0.08 = 8%以内)
MAX_DIFF_RATE = 0.08

# 前日より平均線の差が縮小していること
REQUIRE_DIFF_GETTING_SMALLER = True

# 25日線が上向きであること
REQUIRE_SHORT_MA_UP = True

# 75日線が下向きでないこと(FalseにすることでV字回復銘柄も捉えます)
REQUIRE_LONG_MA_FLAT_OR_UP = False


# ==========================
# 出力設定
# ==========================

# 表示する候補数
TOP_N = 50

# CSV保存
SAVE_CSV = True

# 保存ファイル名
OUTPUT_FILE = "result/gc_result.csv"

② 銘柄リスト読み込み:nikkei.py

銘柄コードの管理用です。プログラムを実行する階層に data フォルダを作成し、その中に code 列を持つ nikkei225.csv を配置して読み込ませます

Python

"""
nikkei.py
日経225銘柄コード読み込み
"""

from pathlib import Path
import pandas as pd

# CSVファイル
CSV_FILE = Path("data") / "nikkei225.csv"


def get_nikkei225():
    """
    日経225銘柄コード一覧を取得
    """

    if not CSV_FILE.exists():
        raise FileNotFoundError(f"CSVファイルが見つかりません: {CSV_FILE}")

    df = pd.read_csv(CSV_FILE, dtype=str)

    if "code" not in df.columns:
        raise ValueError("CSVに 'code' 列がありません。")

    codes = df["code"].dropna().str.strip().tolist()

    # yfinance用に .T を付与
    codes = [f"{code}.T" for code in codes]

    # 重複削除
    codes = list(dict.fromkeys(codes))

    return codes


if __name__ == "__main__":
    codes = get_nikkei225()
    print(f"銘柄数 : {len(codes)}")

③ メインプログラム:gc_scanner.py

ここが全自動スキャンの本丸です

Python

"""
gc_scanner.py
GC(ゴールデンクロス予備軍)スキャナー(予測精度向上面・ノイズ除去版)
"""

from pathlib import Path
import config
from nikkei import get_nikkei225
import pandas as pd
from tabulate import tabulate
import yfinance as yf


def main():
    print("=== GC予備軍スキャナー開始 ===")

    # 1. 銘柄コード取得
    codes = get_nikkei225()
    print(f"対象銘柄数: {len(codes)}件")

    results = []

    # 2. 一括ダウンロード
    print("株価データを取得・解析中...")
    data = yf.download(
        codes,
        period=f"{config.LOOKBACK_DAYS}d",
        progress=True,
        auto_adjust=True,
    )

    for code in codes:
        try:
            df = pd.DataFrame(
                {
                    "Close": data["Close"][code],
                }
            ).dropna()

            # 過去5日間の平均速度を計算するため +7 日分確保
            min_required = max(config.SHORT_MA, config.LONG_MA) + 7
            if len(df) < min_required:
                continue

            df["SHORT_MA"] = df["Close"].rolling(window=config.SHORT_MA).mean()
            df["LONG_MA"] = df["Close"].rolling(window=config.LONG_MA).mean()

            latest = df.iloc[-1]
            prev = df.iloc[-2]
            prev_5d = df.iloc[-6]  # 5営業日前のデータ

            short_ma, long_ma = latest["SHORT_MA"], latest["LONG_MA"]
            prev_short, prev_long = prev["SHORT_MA"], prev["LONG_MA"]

            if (
                pd.isna(short_ma)
                or pd.isna(long_ma)
                or pd.isna(prev_short)
                or pd.isna(prev_long)
            ):
                continue

            # 条件1: 短期 <= 長期(未クロスであること)
            if short_ma > long_ma:
                continue

            # 条件2: 距離(乖離率)
            diff_rate = (long_ma - short_ma) / long_ma
            if diff_rate > config.MAX_DIFF_RATE:
                continue

            # 条件3: 差の縮小(直前1日で接近していること)
            prev_diff = (prev_long - prev_short) / prev_long
            if config.REQUIRE_DIFF_GETTING_SMALLER and diff_rate >= prev_diff:
                continue

            # 条件4: 短期線が上向き
            if config.REQUIRE_SHORT_MA_UP and short_ma <= prev_short:
                continue

            # 条件5: 長期線が下向きでないこと
            if config.REQUIRE_LONG_MA_FLAT_OR_UP and long_ma < prev_long:
                continue

            # --- 予測日数の算出ロジック(5日間平均 + 15日上限フィルター) ---
            short_speed = (short_ma - prev_5d["SHORT_MA"]) / 5.0
            long_speed = (long_ma - prev_5d["LONG_MA"]) / 5.0
            relative_speed = short_speed - long_speed
            distance = long_ma - short_ma

            # 相対スピードがプラス(追いつく勢いがある)場合のみ計算
            if relative_speed > 0:
                est_days = distance / relative_speed

                # 15営業日以上かかる場合は「モメンタム不足」としてスキップ
                if est_days > 15:
                    continue

                est_days_str = f"約{round(est_days, 1)}営業日後"
            else:
                continue

            results.append(
                {
                    "コード": code,
                    "株価": round(latest["Close"], 1),
                    f"短期MA({config.SHORT_MA})": round(short_ma, 1),
                    f"長期MA({config.LONG_MA})": round(long_ma, 1),
                    "接近率(%)": round(diff_rate * 100, 2),
                    "予測GC": est_days_str,
                }
            )

        except Exception:
            continue

    # 3. 結果の整形と出力
    result_df = pd.DataFrame(results)

    if not result_df.empty:
        result_df = result_df.sort_values(by="接近率(%)", ascending=True)
        result_df = result_df.head(config.TOP_N)

        print(f"\n▼ 抽出結果(上位 {len(result_df)} 件)")

        # tabulate で綺麗に出力
        table = tabulate(
            result_df, headers="keys", tablefmt="grid", showindex=False
        )
        print(table)

        # CSV保存
        if config.SAVE_CSV:
            output_path = Path(config.OUTPUT_FILE)
            output_path.parent.mkdir(parents=True, exist_ok=True)
            result_df.to_csv(output_path, index=False, encoding="utf-8-sig")
            print(f"\nファイルを保存しました: {output_path.resolve()}")
    else:
        print("\n条件を満たす銘柄は見つかりませんでした。")


if __name__ == "__main__":
    main()

5. 実際の実行画面と「予測日数」の精度

このプログラムを実行すると、黒い画面に一瞬で以下のような美しいテーブルが出力されます!

Plaintext

=== GC予備軍スキャナー開始 ===
対象銘柄数: 225件
株価データを取得・解析中...

▼ 抽出結果(上位 5 件)
+------------+--------+---------------+---------------+-------------+----------------+
| コード     |   株価 |   短期MA(25)  |   長期MA(75)  |   接近率(%) | 予測GC         |
+============+========+===============+===============+=============+================+
| 6758.T     | 2850.5 |        2780.0 |        2792.5 |        0.45 | 約1.2営業日後  |
| 7203.T     | 3420.0 |        3310.0 |        3345.0 |        1.05 | 約2.8営業日後  |
| 6501.T     | 4150.0 |        4010.0 |        4080.0 |        1.72 | 約4.1営業日後  |
+------------+--------+---------------+---------------+-------------+----------------+

ファイルを保存しました: /path/to/result/gc_result.csv

見てください、この圧倒的な可視性!

単に離れている距離(接近率%)だけでなく、「約1.2営業日後」のように、いつゴールデンクロスを迎えるかのカウントダウンが明確に数値化されます。

「接近率が1%を切っていて、あと1〜2営業日でクロスする」という銘柄のチャートを開いてみると、まさに25日線が下からアッパーカットのように75日線へ突き刺さりに行っている「最高の形状」がズラリと炙り出されます。

6. エピローグ:クロス前を待ち伏せる圧倒的快感

市販のツールを眺めて「GCが出た銘柄」の後追い追い買いをしていた頃とは、世界観が180度変わりました。

自分でロジックを考え、Pythonでプログラムを組み、「これから跳ねる直前の銘柄」を市場の誰よりも早く10秒でリストアップして待ち伏せる――。

これぞ自作スキャナー開発の醍醐味であり、個人投資家がテクノロジーで市場に勝つための最強の武器だと確信しています。

前回のローソク足スキャナーと組み合わせることで、「下ヒゲピンバーを形成しつつ、あと2日でゴールデンクロスする銘柄」といった、究極のピンポイントスクリーニングも可能になります。

コードは自由にコピペ・改変していただいて構いません。ぜひ皆さんのトレード戦略に合わせてカスタマイズしてみてください!

以上、株道 経済研究所の開発日記でした。次回もお楽しみに!

スポンサーリンク

コメント