0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「動きがあったら教えて」をAIで。特定トピックの変化を検知し、言語化して届ける次世代アラートシステム

Last updated at Posted at 2025-12-24

きっかけ

以前OpenAI APIに課金したクレジットを久しぶりに確認したら有効期限が年末に迫っていた.「クレジットって期限あったの?使わなきゃただのお布施になってしまう」と思ったが,かつてチャット起動用のChrome拡張を自作して以来,これといった使い道が思いつかないまま放置していた.そんな折,ド文系の友人からノーコードツールを触ってみたいと言われたので,自分も初めてノーコードツールというものに手を出してみることにした.

成果物

株価の変化状況を検索し,検索結果をAIが推論する.その推論結果に応じ,通知を行うべきか判断し,行う場合はメールを送信するシステム.

スクリーンショット 2025-12-24 2.47.10(2).png

前提

  • OpenAI APIに課金していて,使えるクレジットがあること及びAPIキーがあること
  • Difyへの登録が済んでいること
  • Googleのアカウントを持っていて,GmailやGASとの連携ができること
  • 今回の実装ではSerpAPIへの登録も必要

今回やったこと

  • 実現したいことを考えて,ブロックを並べる
    • 監視した結果をAIの推論材料にする
    • 推論結果を踏まえたメール本文の作成をAIが行う
  • メールをGASで送信するためのコードをGeminiに書いてもらう

実現したいことを考えて,ブロックを並べる

とりあえず2時間に1回ぐらい起動して株価が暴騰・暴落していたら勝手にメールで教えてくれるシステムが簡単だろうと思った.しかし実際に作ってみると、気づけば10時間ほど溶けていた。ノーコードツールとはいえ、最低限のプログラミング的な思考やデータ構造の理解がないと、それなりに厳しいと感じた。

監視した結果をAIの推論材料にする

画像でいうと1個目から4個目までのブロックの部分.まず,一番最初にやることはDifyにログインしてワークフローを作れるようにすること.
ブロック1:開始ノードにはスケジュールトリガーを選択.個人的にビジュアル設定よりも柔軟に変更しやすいので,Cron式を使用する.内容は15 9-15/2 * * 1-5(意味:毎時15分に実行,9時 - 15時 かつ 2時間に1回,平日).

ブロック2:ブロック1の+ボタンをクリックして,ツールからGoogleSearchを選択.Query stringには通常のGoogle検索と同じ感じで入力する.今回は例として4755 株価 チャート 指標とした.また,language_codeをjp,Country codeを81に設定した.ここで,このブロックを使うにはSerpAPIのAPIキーが必要といったエラーが出るので,仕方なくSerpAPIに登録してAPIキーを取得し,認証する.このブロックの出力のうち,検索結果はJSONの形で与えられるがLLMに文字列として入力したいので,次のブロックで変換する.

ブロック3:同様に+ボタンをクリックして,コード実行ブロックを選択.入力変数名をjson_data,変数値にブロック2の{x}json,出力変数名はコードに合わせて適当にresult_textにした.以下のコードはGeminiに書いてもらったので実質ノーコードではあるが,仮に動かなかったときにプログラムを読み解く力が無ければ,ノーコードでやり続けるのは根気が必要そう.

def main(json_data: list):
    full_text = ""
    # JSONの配列の中から必要な情報を抜き出す
    for entry in json_data:
        # organic_results(検索結果)をループ
        results = entry.get("organic_results", [])
        for r in results:
            title = r.get("title", "")
            snippet = r.get("snippet", "")
            full_text += f"タイトル: {title}\n概要: {snippet}\n\n"
            
        # search_results(詳細結果)があればそれも追加
        s_results = entry.get("search_results", [])
        for sr in s_results:
            content = sr.get("content", "")
            full_text += f"内容: {content}\n\n"

    # 最終的に1つの文字列として返す
    return {
        "result_text": full_text
    }

ブロック4:+ボタンからLLMを選択.AIモデルにはクレジットを大きく消費したいという理由から無駄にGPT-5.2を選択.コンテキストにはブロック3の出力{x}result_textを指定.ここに入力するためにString型に変えた.
システムプロンプトとユーザープロンプトはGeminiに書いてもらった.ここも考えがいがあるとは思うのだけれど,動かすことを最優先に.
メールで送って欲しいのは急激に株価が変化した時だけなので,それをY/Nの2値で出力するように設定したシステムプロンプトが以下.

あなたは優秀な株式トレーダーです。入力された検索結果から、現在の株価トレンドを分析してください。
以下の条件のいずれかに当てはまる場合は「Y」、それ以外(横ばい、微増減、判断不能)は「N」のみを
出力してください。
【判定基準(Yとする条件)】
直近で数%以上の急騰または急落がある。
RSIや移動平均線などの言及があり、明らかに過熱または売られすぎのサインがある。
出来高が急増しており、トレンドの転換点に見える。
出力は「Y」または「N」の1文字のみとしてください。解説は不要です。

ユーザープロンプトは以下.

以下の検索結果を分析し、重要性とその理由を回答してください。
【検索結果】{{#context#}}

コンテキストをユーザープロンプトに与える時は { と入力したら候補に出てくるが,上記のように{{#context#}}と入力してもよい.

推論結果を踏まえたメール本文の作成をAIが行う

画像でいうと5個目から7個目までのブロックの部分.
ブロック5:IF/ELSEブロックを選択.株価の変化が小さい時はメールを送らないで欲しいので,ブロック4の判定結果がY,つまり出力のうち"text":"Y"となっているかを条件にする.

スクリーンショット 2025-12-24 2.19.38.png

"Y"にしないこと.Falseと判定されてしまう.

ブロック6:LLMブロックを選択.メール本文作成に高度な処理が必要ないと考え,AIモデルにはGPT-5-nanoを選択.コンテキストにはブロック4と同様,ブロック3の出力{x}result_textを指定.
システムプロンプトとユーザープロンプトはおなじみGeminiに書いてもらう.

あなたは株価アラートの編集者です。楽天(4755)の株価に大きな動きがありました。
以下の形式で、通知用のメッセージを作成してください。
【出力形式】
件名:[ここに15文字以内の件名]
本文:[ここに200文字以内の本文] 
※他の挨拶やJSON形式は一切不要です。上記形式のみ出力してください。

ユーザープロンプトは以下.

以下の情報を元に、メールの通知文を作成してください。
【最新の株価・ニュースデータ】{{#context#}}

ブロック7:+ボタンを押して,ツールからwebhook_senderを選択.今回メール送信はツールにあるGmailを使うのではなく,Webhook経由でGASにPOSTする。理由はGASがマイブームだから.
Text Inputはブロック6の出力のうち{x}text,メソッドはPOSTとした.Webhook URLにはGASでデプロイしたWebアプリのURLを入力(末尾に/execがついている方).そのWebアプリは次のセクションで解説.

メールをGASで送信するためのコードをGeminiに書いてもらう

ここまで出来たらテストをするのがおすすめ.適当な時刻に設定して,ワークフローが最後まで動くことを確認する.このときの最後のブロックの出力次第でGASにどう書くかが決まるし,実際に入力データの整形でかなり詰まった.

参考までに実際に使っているコード.gsを以下に載せる.

function doPost(e) {
  // 自分のメアドに書き換える
  var myEmail = "";

  try {
    // Webhookから届いた生JSON
    var outerData = JSON.parse(e.postData.contents);

    var textInput = outerData.text || "";

    textInput = textInput.trim();

    var subject = "【株価アラート】";
    var body = textInput;

    // 件名・本文が含まれている場合は分離
    if (textInput.includes("件名:") && textInput.includes("本文:")) {
      var parts = textInput.split("本文:");
      subject = parts[0].replace("件名:", "").trim();
      body = parts[1].trim();
    }

    var formattedBody =
      subject + "\n\n" +
      body + "\n\n" +
      "――――――――――\n" +
      "※本メールは自動配信です\n" +
      "※投資判断は自己責任でお願いします";

    GmailApp.sendEmail(myEmail, subject, formattedBody);

    return ContentService.createTextOutput("Success");

  } catch (err) {
    GmailApp.sendEmail(
      myEmail,
      "【株価アラート】(GASエラー)",
      "エラー内容:\n" + err + "\n\n受信データ:\n" + e.postData.contents
    );
    return ContentService.createTextOutput("Error");
  }
}

TODO

  • 幅広く検索するのではなく,検索するサイトをURLで指定したい.特に20分遅れのサイトではなく,リアルタイム性が高いものを選択.掲示板の盛り上がりとかも使えなくはなさそう.
  • 出来高やMACDといったテクニカル指標に加えて,決算やTOBなどのリアルタイム性が高いニュースも総合して判断するシステムに作り変えたい.けれども,もうクレジットの期限が……

参考文献

この記事は以下の情報を参考にして執筆しました.

  • Geminiとの対話のみ
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?