注文は出さず通知だけ!Pythonで「買いシグナル」をLINEに届けるシステム構築

自動化・運用

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

Pythonで株の自動売買に挑戦したいと考えたとき、多くの人が最初に思い浮かべるのは「完全自動で注文を出すシステム」です。しかし、いきなり自動発注に踏み込むのはリスクが高く、APIの利用条件や証券会社との契約、誤発注時の損失リスクなど、乗り越えるべきハードルが多数存在します。

そこで本記事が提案するのは、「買いシグナルをLINEに自動通知する」というアプローチです。テクニカル分析の結果をリアルタイムで自分のスマートフォンに通知し、最終的な売買判断は人間が行うという「半自動」の仕組みです。

この方法であれば誤発注のリスクはゼロであり、Pythonによる株価分析と自動化の基礎を安全に学ぶことができます。

なぜ「通知システム」から始めるべきなのか

自動売買の世界に一歩を踏み出す際、段階的なアプローチが成功の鍵となります。

完全自動売買のリスク

完全自動で注文を出すシステムには、以下のようなリスクが伴います。

  • 誤発注リスク: コードのバグにより、意図しない銘柄・数量・価格で注文が執行される
  • API停止リスク: 証券会社側のメンテナンスや仕様変更でシステムが停止する
  • 相場急変リスク: 想定外の暴落時にロジックが対応できず、大きな損失を被る
  • 口座凍結リスク: 非公式な方法で接続した場合、利用規約違反で口座が凍結される可能性がある

通知システムのメリット

一方、通知システムには以下のメリットがあります。

観点 完全自動売買 通知システム(本記事)
誤発注リスク 高い ゼロ
証券口座との接続 必要 不要
学習効果 高いが失敗コストも大きい 安全に分析ロジックを検証できる
実装難易度
運用コスト API維持・監視が必要 ほぼゼロ

まずは通知システムで分析ロジックの精度を検証し、十分な実績が確認できてから自動発注への移行を検討するのが、最も合理的なステップです。

本記事で構築するシステムの全体像

本記事で構築するシステムは、以下の3ステップで動作します。

  1. データ取得: yfinanceで日本株の株価データを取得する
  2. 分析判定: 移動平均線のゴールデンクロスを検出する
  3. LINE通知: 買いシグナルが発生した場合にLINEへ通知を送信する

LINE通知の仕組みと準備

PythonからLINEにメッセージを送信するために、LINE Notifyというサービスを利用します。

LINE Notifyとは

LINE Notifyは、LINE公式が提供する無料の通知サービスです。個人のLINEアカウントに対して、外部のプログラムからメッセージを送信できます。

主な特徴は以下のとおりです。

  • 完全無料で利用可能
  • 登録・設定が簡単(トークンを発行するだけ)
  • REST APIでHTTPリクエストを送信するだけで通知できる
  • 1時間あたり1,000回までのレート制限あり(個人利用には十分)

重要:LINE Notifyのサービス状況について

LINE Notifyは2025年3月末でサービス終了がアナウンスされましたが、後継としてLINE Messaging APIが無料枠で利用可能です。本記事ではまずLINE Notifyの仕組みで解説し、後半でMessaging APIへの移行方法にも言及します。代替手段として、同様にHTTPリクエストで通知を送れるSlack WebhookDiscord Webhookも利用できます。

LINE Messaging APIのアクセストークン取得手順

LINE Notifyの終了に伴い、現在はLINE Messaging APIを使用する方法が推奨されます。以下の手順でアクセストークンを取得してください。

  1. LINE Developersコンソールhttps://developers.line.biz/)にアクセスし、LINEアカウントでログインする
  2. 新規プロバイダーを作成する
  3. 「Messaging API」チャネルを作成する
  4. チャネル設定画面で「チャネルアクセストークン(長期)」を発行する
  5. 作成したチャネルのBot(公式アカウント)を自分のLINEで友だち追加する

トークンは絶対に他人に共有しないでください。トークンが漏洩すると、第三者があなたのBotを通じてメッセージを送信できてしまいます。

通知手段の比較と代替案

LINE以外にも、Pythonから簡単に通知を送れるサービスがあります。

サービス 費用 設定の手軽さ 特徴
LINE Messaging API 無料枠あり(月200通) やや複雑 スマホ通知で見逃しにくい
Slack Webhook 無料 簡単 PC作業中の通知に最適
Discord Webhook 無料 非常に簡単 設定が最も手軽
メール(Gmail SMTP) 無料 やや複雑 汎用性が高い

本記事ではLINE Messaging APIDiscord Webhookの両方のコードを提示します。環境に合わせて選択してください。

テクニカル分析:ゴールデンクロスの検出ロジック

通知を送るためには、まず「いつ通知を送るか」の判定ロジックが必要です。本記事では、最も基本的なテクニカル指標である移動平均線のゴールデンクロスを採用します。

ゴールデンクロスとは

ゴールデンクロスとは、短期移動平均線が長期移動平均線を下から上に突き抜ける現象のことです。一般的に「買いシグナル」として知られています。

  • 短期移動平均線: 直近の株価動向を反映(本記事では5日移動平均を使用)
  • 長期移動平均線: 中長期のトレンドを反映(本記事では25日移動平均を使用)

判定ロジックの設計

ゴールデンクロスの発生は、以下の条件で判定します。

  • 前日: 短期移動平均 ≦ 長期移動平均(短期が下にある状態)
  • 当日: 短期移動平均 > 長期移動平均(短期が上に抜けた状態)

この「前日は下、当日は上」という逆転が起きたタイミングがゴールデンクロスです。

分析精度に関する注意点

ゴールデンクロスは万能な指標ではありません。以下の点を必ず理解した上で使用してください。

  • レンジ相場(横ばい)ではダマシ(偽シグナル)が頻発する
  • シグナル発生時点では既にある程度の値動きが終わっている(遅行指標)
  • 単独で売買判断の根拠とするには不十分

本記事のシステムはあくまで学習・検証用です。実際の売買判断は、出来高やファンダメンタルズ、複数のテクニカル指標を組み合わせて行ってください。

【コピペOK】買いシグナルLINE通知システムのコード

ここからは、実際に動作するコードを提示します。yfinanceで株価データを取得し、ゴールデンクロスを検出した場合にLINEまたはDiscordに通知を送信します。

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

コマンドプロンプトで以下を実行してください。


pip install yfinance pandas requests

【コピペOK】メインスクリプト(LINE Messaging API版)

以下のコードを signal_notify.py として保存してください。


import yfinance as yf
import pandas as pd
import requests
from datetime import datetime

# ==============================
# 設定エリア(ここを編集)
# ==============================

# 監視銘柄リスト(東証の場合は末尾に .T を付ける)
WATCH_LIST = [
    {"symbol": "7203.T", "name": "トヨタ自動車"},
    {"symbol": "6758.T", "name": "ソニーグループ"},
    {"symbol": "9984.T", "name": "ソフトバンクグループ"},
]

# 移動平均の期間設定
SHORT_WINDOW = 5    # 短期移動平均(日)
LONG_WINDOW = 25    # 長期移動平均(日)

# LINE Messaging API設定
LINE_CHANNEL_ACCESS_TOKEN = "ここにチャネルアクセストークンを貼り付け"
LINE_USER_ID = "ここにあなたのユーザーIDを貼り付け"

# Discord Webhook設定(LINEの代わりにDiscordを使う場合)
DISCORD_WEBHOOK_URL = ""  # 空欄ならDiscord通知はスキップ

# ==============================
# 通知関数
# ==============================
def send_line_message(message):
    """LINE Messaging APIでプッシュメッセージを送信する"""
    url = "https://api.line.me/v2/bot/message/push"
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {LINE_CHANNEL_ACCESS_TOKEN}",
    }
    payload = {
        "to": LINE_USER_ID,
        "messages": [
            {"type": "text", "text": message}
        ],
    }
    try:
        response = requests.post(url, headers=headers, json=payload)
        if response.status_code == 200:
            print("[LINE] 通知送信成功")
        else:
            print(f"[LINE] 送信失敗: {response.status_code} {response.text}")
    except Exception as e:
        print(f"[LINE] エラー: {e}")


def send_discord_message(message):
    """Discord Webhookでメッセージを送信する"""
    if not DISCORD_WEBHOOK_URL:
        return
    payload = {"content": message}
    try:
        response = requests.post(DISCORD_WEBHOOK_URL, json=payload)
        if response.status_code == 204:
            print("[Discord] 通知送信成功")
        else:
            print(f"[Discord] 送信失敗: {response.status_code}")
    except Exception as e:
        print(f"[Discord] エラー: {e}")


def notify(message):
    """利用可能な通知手段すべてに送信する"""
    print(f"n{message}n")
    if LINE_CHANNEL_ACCESS_TOKEN and LINE_USER_ID:
        send_line_message(message)
    send_discord_message(message)


# ==============================
# 株価取得・分析関数
# ==============================
def fetch_and_analyze(symbol, name):
    """指定銘柄の株価を取得し、ゴールデンクロスを判定する"""
    print(f"--- {name}({symbol})を分析中 ---")

    ticker = yf.Ticker(symbol)
    df = ticker.history(period="3mo")

    if df.empty or len(df) < LONG_WINDOW + 1:
        print(f"  データ不足のためスキップ(取得行数: {len(df)})")
        return None

    # 移動平均線の計算
    df["SMA_short"] = df["Close"].rolling(window=SHORT_WINDOW).mean()
    df["SMA_long"] = df["Close"].rolling(window=LONG_WINDOW).mean()

    # 欠損値を除去
    df = df.dropna()

    if len(df) < 2:
        print("  分析に必要なデータが不足しています")
        return None

    # ゴールデンクロスの判定
    today = df.iloc[-1]
    yesterday = df.iloc[-2]

    cross_occurred = (
        yesterday["SMA_short"] <= yesterday["SMA_long"]
        and today["SMA_short"] > today["SMA_long"]
    )

    result = {
        "symbol": symbol,
        "name": name,
        "close": today["Close"],
        "sma_short": today["SMA_short"],
        "sma_long": today["SMA_long"],
        "golden_cross": cross_occurred,
        "date": df.index[-1].strftime("%Y-%m-%d"),
    }

    status = "ゴールデンクロス発生!" if cross_occurred else "シグナルなし"
    print(f"  終値: {today['Close']:.1f} | "
          f"SMA{SHORT_WINDOW}: {today['SMA_short']:.1f} | "
          f"SMA{LONG_WINDOW}: {today['SMA_long']:.1f} | "
          f"{status}")

    return result


# ==============================
# メイン処理
# ==============================
def main():
    print("=" * 50)
    print(f"買いシグナル検出システム 実行日時: {datetime.now()}")
    print("=" * 50)

    signals = []

    for stock in WATCH_LIST:
        result = fetch_and_analyze(stock["symbol"], stock["name"])
        if result and result["golden_cross"]:
            signals.append(result)

    # シグナルが検出された場合に通知
    if signals:
        message_lines = [
            "🔔 買いシグナル検出!",
            f"検出日: {signals[0]['date']}",
            "",
        ]
        for s in signals:
            message_lines.append(f"📈 {s['name']}({s['symbol']})")
            message_lines.append(f"  終値: {s['close']:.1f}円")
            message_lines.append(
                f"  SMA{SHORT_WINDOW}: {s['sma_short']:.1f} > "
                f"SMA{LONG_WINDOW}: {s['sma_long']:.1f}"
            )
            message_lines.append("")

        message_lines.append("※ゴールデンクロスは参考指標です。")
        message_lines.append("  売買判断は自己責任でお願いします。")

        notify("n".join(message_lines))
    else:
        print("n本日の買いシグナルはありませんでした。")

    print("n" + "=" * 50)
    print("処理完了")
    print("=" * 50)


if __name__ == "__main__":
    main()

設定項目の解説

コード上部の設定エリアについて補足します。

設定項目 説明 変更方法
WATCH_LIST 監視する銘柄のリスト 銘柄コードと名称を追加・削除
SHORT_WINDOW 短期移動平均の日数 デフォルト5日。デイトレなら3日も可
LONG_WINDOW 長期移動平均の日数 デフォルト25日。中期なら75日も可
LINE_CHANNEL_ACCESS_TOKEN LINE Messaging APIのトークン LINE Developersで発行した値を貼り付け
LINE_USER_ID 通知先のユーザーID LINE Developersコンソールで確認可能
DISCORD_WEBHOOK_URL Discord Webhookの送信先URL Discord側で発行したURLを貼り付け

LINE_CHANNEL_ACCESS_TOKENLINE_USER_ID をコード内に直接記述するのはテスト段階に限定してください。本番運用では環境変数や .env ファイルで管理することを推奨します。

Discord Webhookの設定方法(代替通知手段)

LINEの設定が面倒な場合、Discord Webhookを使えばより手軽に通知を実現できます。

Discord Webhookの取得手順

  1. Discordでサーバーを作成する(既存サーバーでも可)
  2. 通知を受け取りたいチャンネルの設定(歯車アイコン)を開く
  3. 「連携サービス」→「ウェブフック」を選択する
  4. 「新しいウェブフック」を作成し、「ウェブフックURLをコピー」をクリックする
  5. コピーしたURLをスクリプトの DISCORD_WEBHOOK_URL に貼り付ける

Discordを使う場合、LINEのトークン設定は空欄のままで問題ありません。

定期実行の自動化(Windowsタスクスケジューラ)

このスクリプトを毎日自動で実行するには、Windowsのタスクスケジューラを使用します。

タスクスケジューラの設定手順

  1. Windowsキーを押し「タスクスケジューラ」と入力して起動する
  2. 「基本タスクの作成」をクリックする
  3. タスク名を入力する(例:「株価シグナル通知」)
  4. トリガーを「毎日」に設定し、実行時刻を「18:00」に設定する(市場終了後)
  5. 操作で「プログラムの開始」を選択する
  6. 以下の情報を入力する
    • プログラム: python(またはPythonのフルパス)
    • 引数の追加: C:Users<ユーザー名>python-projectssignal_notify.py
    • 開始(オプション): C:Users<ユーザー名>python-projects

実行時刻は東証の取引終了後(15:30以降)に設定してください。取引時間中はyfinanceのデータが確定していないため、正確な判定ができません。

よくあるエラーと対処法

LINE通知が届かない

以下の点を順番に確認してください。

  1. チャネルアクセストークンが正しくコピーされているか(前後に余計なスペースがないか)
  2. ユーザーIDが正しいか(LINE Developersコンソールの「チャネル基本設定」で確認)
  3. BotのLINE公式アカウントを友だち追加しているか
  4. インターネット接続が正常か

yfinanceでデータが取得できない

「データ不足のためスキップ」と表示される場合、以下の原因が考えられます。

  • 銘柄コードの末尾に .T が付いていない(東証銘柄の場合は必須)
  • 上場廃止された銘柄コードを指定している
  • yfinanceのバージョンが古い(pip install --upgrade yfinance で更新)

ゴールデンクロスが一度も検出されない

テスト段階でシグナルが発生しないのは正常です。ゴールデンクロスは頻繁に発生する現象ではありません。

動作確認をしたい場合は、SHORT_WINDOW = 3LONG_WINDOW = 5 のように期間を短くすると、シグナルが発生しやすくなります。ただし、これはあくまでテスト目的の設定です。

まとめ

本記事では、Pythonで株の自動売買に踏み出す第一歩として、買いシグナルをLINEやDiscordに自動通知するシステムを構築しました。

  • 完全自動売買はリスクが高いため、まずは通知システムから始めるのが安全な選択肢
  • yfinanceで株価データを取得し、移動平均線のゴールデンクロスを検出する
  • LINE Messaging APIまたはDiscord Webhookで分析結果をスマートフォンに通知する
  • Windowsタスクスケジューラと組み合わせれば、毎日自動で分析・通知が実行される

このシステムで分析ロジックの精度を検証し、十分な成果が確認できた段階で、次のステップとしてバックテスト(過去データでの検証)や、証券会社APIを利用した自動発注への移行を検討してください。

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