【株×Python】「ローソク足形状特化スキャナー」自作してみた!デイトレ銘柄を一瞬で炙り出す開発日記

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

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

皆さんは毎日のデイトレードの銘柄選定、どうやっていますか? 移動平均線のゴールデンクロス? RSIの売られすぎ? それとも出来高急増ランキング?

世の中にはたくさんのスクリーニングツールがありますが、僕は常々、ある強い不満を抱えていました。

「自分の欲しい『ローソク足の形』を、ピンポイントで抽出できるツールがなかなかない……!」

「下ひげピンバー」や「大陽線」といったプライスアクション(値動き)こそ、デイトレの命。それなのに、既存のツールはインジケーター(指標)ばかりで、形状に特化したものがなかなか見つかりません。

――無いなら、作るしかない。

今回は、Pythonを使って自作した、僕専用の「ローソク足形状特化スキャナー」の開発舞台裏をお届けします。

1.ローソク足の形でスクリーニングができない!

僕がデイトレの銘柄選びで最も重要視しているのは、ローソク足の形状です。

人それぞれデイトレの戦略は違うと思いますが、僕の場合は「大陽線」「大陰線」や「下ひげピンバー」の形から翌日のデイトレ銘柄を選別していきます。

しかし、いざ銘柄を探そうとすると大きな壁にぶつかります。

ターゲットを日経225銘柄に絞ったとしても、何百ものチャートを1つずつ手動でチェックする時間は当然ありません。そこで証券会社のスクリーニング機能を使うわけですが、用意されているのは「移動平均線」や「RSI」といったテクニカル指標ばかり。「前日が下ひげピンバーの銘柄」といった、ピンポイントな形状指定はほぼ不可能です。

ローソク足の形状検索ができる専門サイトも一部ありますが、

  • データの更新時間を待つ必要がある
  • 検索条件の自由度が低く、自分好みの微調整ができない
  • UIが使いづらい

といった不満があり、

「既存のツールが使えないなら、自分の手で理想のシステムを組むしかない」

これが、今回Pythonを使ったチェッカー自作に踏み切った理由です。

2. 環境構築

開発環境は、僕もお気に入りの Visual Studio Code(VS Code) を使っています。

もしPCにPythonが入っていなければ、まずは公式サイトからインストールを済ませておいてください。

👉 Python公式サイト(ダウンロードページ)

⚠️ Windowsユーザーへの注意点 インストーラーを起動した際、画面下部にある 「Add python.exe to PATH」 というチェックボックスに必ずチェックを入れてからインストールを進めてください。これにチェックを入れ忘れると、この後の作業でエラーになってしまいます。

必要なライブラリのインストール手順

プログラムを動かすために必要な外部ライブラリは、実は2つだけです。「黒い画面(ターミナル)とか触ったことない…」というプログラミング初心者の方も、以下の手順通りにやれば一瞬で終わります!

  1. 画面を開く
    • Windowsの方: スタートメニュー(画面左下の虫眼鏡マーク)で「cmd」と検索し、コマンドプロンプトを開きます。
    • Macの方: アプリケーション一覧の「ユーティリティ」フォルダの中にある、またはSpotlight検索で「ターミナル」と検索してターミナルを開きます。
  2. コマンドをコピペして実行する 開いた黒い画面に、以下の文字をそのままコピー&ペーストして、キーボードのエンターキー(Enter)をポンッと押してください。

Bash

pip install yfinance pandas

画面に文字がダダダッと流れて、最後に Successfully installed... と出れば準備完了です!

今回導入したライブラリは、それぞれこんな魔法のような役割を持っています。

  • yfinance: Yahoo! Financeから過去の株価データを無料で一括ダウンロードできるライブラリ。
  • pandas: 大量の株価データをテーブル形式で超高速に計算・処理するライブラリ。

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

ここからは実際のコードを公開します! 今回は、管理がしやすいようにプログラムを2つのファイルに分けて作成しました。
まずは、スキャン対象となる日経225の銘柄リストを管理するファイルから作っていきます。プログラムは2つのファイルに分けて管理します。同じフォルダに保存して実行してください。

① 銘柄リスト用ファイル:nikkei.py

yfinanceの仕様に合わせるため、4桁の証券コードの後ろに自動で .T (東京証券取引所の意味)を付与する形にしています。

(※ちなみに銘柄名を入れるのは面倒だったので、コードだけにしました💦 もし「4151」の後に「協和キリン」と名前を付けたい場合や、グロース・スタンダードの銘柄を増やしたい場合は、リストの中にどんどん追加していけば大丈夫です!)

以下のコードを丸ごとコピーして、nikkei.py という名前で保存してください。

# nikkei.py
# 日経225構成銘柄リスト

def get_nikkei225():

    stocks = [

        "4151",
"4502",
"4503",
"4506",
"4507",
"4519",
"4523",
"4568",
"4578",
"285A",
"4062",
"6479",
"6501",
"6503",
"6504",
"6506",
"6526",
"6645",
"6701",
"6702",
"6723",
"6724",
"6752",
"6753",
"6758",
"6762",
"6770",
"6841",
"6857",
"6861",
"6902",
"6920",
"6954",
"6963",
"6971",
"6976",
"6981",
"7735",
"7751",
"7752",
"8035",
"543A",
"7201",
"7202",
"7203",
"7211",
"7261",
"7267",
"7269",
"7270",
"7272",
"4543",
"4902",
"6146",
"7731",
"7733",
"7741",
"9432",
"9433",
"9434",
"9984",
"5831",
"7186",
"8304",
"8306",
"8308",
"8309",
"8316",
"8331",
"8354",
"8411",
"8253",
"8591",
"8697",
"8601",
"8604",
"8630",
"8725",
"8750",
"8766",
"8795",
"1332",
"2002",
"2269",
"2282",
"2501",
"2502",
"2503",
"2801",
"2802",
"2871",
"2914",
"3086",
"3092",
"3099",
"3382",
"7453",
"7532",
"8233",
"8252",
"8267",
"9843",
"9983",
"2413",
"2432",
"3659",
"3697",
"4307",
"4324",
"4385",
"4661",
"4689",
"4704",
"4751",
"4755",
"6098",
"6178",
"6532",
"7974",
"9602",
"9735",
"9766",
"1605",
"3401",
"3402",
"3861",
"3405",
"3407",
"4004",
"4005",
"4021",
"4042",
"4043",
"4061",
"4063",
"4183",
"4188",
"4208",
"4452",
"4901",
"4911",
"6988",
"5019",
"5020",
"5101",
"5108",
"5201",
"5214",
"5233",
"5301",
"5332",
"5333",
"5401",
"5406",
"5411",
"3436",
"5706",
"5711",
"5713",
"5714",
"5801",
"5802",
"5803",
"2768",
"8001",
"8002",
"8015",
"8031",
"8053",
"8058",
"1721",
"1801",
"1802",
"1803",
"1808",
"1812",
"1925",
"1928",
"1963",
"5631",
"6103",
"6113",
"6273",
"6301",
"6302",
"6305",
"6326",
"6361",
"6367",
"6471",
"6472",
"6473",
"7004",
"7011",
"7013",
"7012",
"7832",
"7911",
"7912",
"7951",
"3289",
"8801",
"8802",
"8804",
"8830",
"9001",
"9005",
"9007",
"9008",
"9009",
"9020",
"9021",
"9022",
"9064",
"9147",
"9101",
"9104",
"9107",
"9201",
"9202",
"9501",
"9502",
"9503",
"9531",
"9532",# ここに日経225コードを追加

    ]

    return [
        code + ".T"
        for code in stocks
    ]


if __name__ == "__main__":

    stocks = get_nikkei225()

    print("日経225銘柄数:", len(stocks))

    for stock in stocks:
        print(stock)

もし銘柄名も一緒に管理したい場合の書き方

Python

def get_nikkei225_with_names():
    # 「コード: 銘柄名」のペアで管理する
    stocks = {
        "4151": "協和キリン",
        "4502": "武田薬品工業",
        "4503": "アステラス製薬",
        # ...こんな風に並べていく
    }

    # yfinance用に ".T" を付けたコードと、名前をセットにして返す
    return {code + ".T": name for code, name in stocks.items()}

② メインプログラム:scanner.py

準備が整ったら、いよいよローソク足の形状を自動判定する本丸のプログラムを作ります。

このプログラムでは、僕の欲しいローソク足の形状(大陽線、大陰線、長い下ヒゲ)がスクリーニングできるようにしています。

このプログラムの最大の特徴は、「過去60日間のローソク足の平均サイズ」をベースに計算している点です。銘柄によってボラティリティ(値動きの大きさ)は全く違いますよね。一律で「〇〇円以上の値幅」と決めるのではなく、「その銘柄の普段のサイズの2倍以上」を自動で計算して「大陽線」と判定する、ロジックを組みました。

以下のコードを丸ごとコピーして、先ほどと同じフォルダに scanner.py というファイル名で保存してください。

Python

# scanner.py
# 日経225 ローソク足スキャナー

import pandas as pd
import yfinance as yf
from nikkei import get_nikkei225

# ==========================
# 設定(3ヶ月ベースに最適化)
# ==========================
BODY_AVG_DAYS = 60  # 実体平均(日数):3ヶ月(約60営業日)
BIG_BODY_RATE = 2.0  # 大陽線・大陰線(平均の2倍以上)
MIN_BODY_RATE = 0.3  # 小さすぎる実体を除外(大陽線・大陰線用)
BOUZU_RATE = 0.05  # 坊主判定(ヒゲ5%以下)


# ==========================
# ローソク足判定
# ==========================
def judge_candle(df):
    result = []

    # データ不足
    if df.empty or len(df) < BODY_AVG_DAYS:
        return result

    # MultiIndex対策
    if isinstance(df.columns, pd.MultiIndex):
        df.columns = df.columns.get_level_values(0)

    # コピーを作成
    df = df.copy()

    # 実体サイズ
    df["Body"] = (df["Close"] - df["Open"]).abs()

    # 過去60日平均(3ヶ月)
    df["AvgBody"] = df["Body"].rolling(BODY_AVG_DAYS).mean()

    # 最新行
    row = df.iloc[-1]

    avg_body = float(row["AvgBody"])

    # 平均が計算できていない
    if pd.isna(avg_body):
        return result

    open_price = float(row["Open"])
    high = float(row["High"])
    low = float(row["Low"])
    close = float(row["Close"])
    body = float(row["Body"])

    upper_wick = high - max(open_price, close)
    lower_wick = min(open_price, close) - low
    total_range = high - low

    # ゼロ除算対策と、値動きが全くない日は除外
    if total_range <= 0:
        return result

    # ==========================
    # 強い陽線(高値引け)
    # ==========================
    if (
        close > open_price
        and body >= avg_body * BIG_BODY_RATE
        and upper_wick <= total_range * BOUZU_RATE
    ):
        result.append("大陽線(高値引け)")

    # ==========================
    # 大陰線(安値引け)
    # ==========================
    if (
        close < open_price
        and body >= avg_body * BIG_BODY_RATE
        and lower_wick <= total_range * BOUZU_RATE
    ):
        result.append("大陰線(安値引け)")

    # ==========================
    # 長い下ヒゲ(高精度ピンバー)
    # ==========================
    # 1. 下ヒゲがローソク足全体の 60% 以上を占める
    # 2. 上ヒゲは実体以下(上値が重くない)
    # 3. 実体は全体の 5%超 〜 30%以下(十字線は完全に弾く)
    if (
        lower_wick >= total_range * 0.60
        and upper_wick <= body
        and total_range * 0.05 < body <= total_range * 0.30
    ):
        if close > open_price:
            result.append("長い下ヒゲ(陽線)")
        else:
            result.append("長い下ヒゲ(陰線)")

    return result


# ==========================
# メイン処理
# ==========================
def main():
    stocks = get_nikkei225()
    results = {}

    print("\n日経225スキャン開始(3ヶ月平均ベース)\n")

    # 一括取得(6ヶ月分取得)
    df_all = yf.download(
        stocks,
        period="6mo",
        interval="1d",
        auto_adjust=False,
        progress=False,
        group_by="ticker",
        threads=True,
    )

    # MultiIndexかどうか確認
    multi = isinstance(df_all.columns, pd.MultiIndex)

    for ticker in stocks:
        code = ticker.replace(".T", "")

        try:
            if multi:
                if ticker not in df_all.columns.get_level_values(0):
                    continue
                df = df_all[ticker]
            else:
                df = df_all

            # 欠損値除外
            df = df.dropna(subset=["Close"])

            patterns = judge_candle(df)

            for p in patterns:
                results.setdefault(p, []).append(code)

            # 進捗を少しずつ出す
            print(".", end="", flush=True)

        except Exception as e:
            print(f"\n{code} エラー: {e}")

    # ==========================
    # 結果表示
    # ==========================
    print("\n" + "=" * 50)
    print(" スキャン結果")
    print("=" * 50)

    targets = [
        "大陽線(高値引け)",
        "大陰線(安値引け)",
        "長い下ヒゲ(陽線)",
        "長い下ヒゲ(陰線)",
    ]

    for target in targets:
        print(f"\n【{target}】")
        if target in results:
            for code in sorted(results[target]):
                print(code)
        else:
            print("なし")


if __name__ == "__main__":
    main()

こだわりのロジック解説(ここがスキャナーの命!)

ただコードを貼るだけでは面白くないので、僕がこだわった「高精度ピンバー(長い下ひげ)」の条件を解説します。

  1. lower_wick >= total_range * 0.60 下ひげが、ローソク足全体の長さ(高値から安値まで)の60%以上を占めていること。これで「しっかり押し目買いが入った形」を確定させます。
  2. upper_wick <= body 上ひげは実体以下であること。いくら下ひげが長くても、上ひげも長かったら「上値が重くて売られた証拠」になってしまうので、それを排除しています。
  3. total_range * 0.05 < body <= total_range * 0.30 ここが一番のポイント! 実体が小さすぎる「十字線」や「同時線」は、デイトレの順張り・逆張りとしては迷いが生じやすいので完全に弾き、しっかり方向性が出たピンバーだけを炙り出します。

4. エピローグ:10秒で終わる銘柄選定の快適さ

このプログラムを実行すると、約10秒で僕の欲しいローソク足の形状がズラリとスクリーニングできるようになりました!

自作スクリーニングの検索結果画像

実際に運用してみて分かったのですが、明確なローソク足の形だけを狙おうと判定を厳しくしすぎると、相場環境によっては「該当銘柄なし」になってしまうことも多かったです💦 このあたりは、日々の相場に合わせて数値の微調整が必要だなと感じています。

ただ、このプログラムの本当に素晴らしいところは、コードのトップに置いてある定数(BIG_BODY_RATE など)の数値を書き換えるだけで、「自分好みのローソク足の形」にいくらでもカスタマイズできる点です。

今回は僕好みの「大陽線」や「下ひげピンバー」に特化して作りましたが、「はらみ線」や「包み線(抱き線)」が欲しいという方は、ぜひロジックを追加して自分専用に改良してみてください!

以上、株道経済研究所の開発日記でした。

スポンサーリンク

コメント