この記事でわかること
- DiscordからX(Twitter)への通知移行の手順
- tweepy v4 + Twitter API v2(OAuth 1.0a)の正しい設定方法
- Gemini APIのクォータ問題とモデル選定の注意点
- APSchedulerで1〜2時間おきに自動実行する方法
- 実際に踏んだエラーと解決策まとめ
📘 外部参考:Tweepy 公式ドキュメント / X API(旧Twitter)公式
📘 外部参考:Google Gemini API(公式)
なぜDiscordからXに切り替えたか
もともと、AI自動売買ボットのシグナル通知はDiscord Webhook URLで実装していた。コード1行で送れるお手軽さが魅力だった。
📘 外部参考:Discord Webhook ガイド(公式・日本語)
しかし、運用してみると以下の課題が見えてきた。
- 通知を確認するにはDiscordアプリを開く必要がある
- 外部への情報発信にならない(クローズドな環境)
- X(Twitter)と連携すれば、フォロワーにリアルタイムでシグナルを届けられる
というわけで、通知先をX(Twitter)に全面移行することにした。
X(Twitter) APIの取得手順
1. X Developer Portalでアプリを作成
developer.twitter.com にアクセスし、アプリを作成する。
重要なポイント:
アプリの権限設定を 「Read and Write」 にすること。デフォルトは「Read only」になっており、ツイートの投稿ができない。権限変更後は必ずAccess TokenとAccess Token Secretを再生成すること(古いトークンは無効になる)。
2. 必要なAPIキー(4つ)
| キー名 | 取得場所 |
|---|---|
| API Key | App Settings > Keys and Tokens |
| API Secret | 同上 |
| Access Token | 同上(Generate後) |
| Access Token Secret | 同上 |
3. .envファイルへの設定
X_API_KEY=xxxxxxxxxxxxxxxxxxxx
X_API_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X_ACCESS_TOKEN=xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X_ACCESS_TOKEN_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X通知モジュールの実装(x_notifier.py)
import tweepy
import os
from datetime import datetime
from dotenv import load_dotenv
load_dotenv()
class XNotifier:
def __init__(self):
api_key = os.getenv("X_API_KEY")
api_secret = os.getenv("X_API_SECRET")
access_token = os.getenv("X_ACCESS_TOKEN")
access_token_secret = os.getenv("X_ACCESS_TOKEN_SECRET")
if not all([api_key, api_secret, access_token, access_token_secret]):
self.client = None
return
self.client = tweepy.Client(
consumer_key=api_key,
consumer_secret=api_secret,
access_token=access_token,
access_token_secret=access_token_secret
)
def post(self, text: str) -> bool:
if not self.client:
print("[X] APIキー未設定 - スキップ")
return False
try:
if len(text) > 140:
text = text[:137] + "..."
self.client.create_tweet(text=text)
print(f"[X] 投稿成功: {text[:50]}...")
return True
except Exception as e:
print(f"[X] 投稿エラー : {e}")
return False
def notify_signal(self, symbol: str, decision: str, score: float, reasoning: str):
emoji = {"BUY": "📈", "SELL": "📉", "HOLD": "⏸"}.get(decision, "❓")
reason_short = reasoning[:40] + "…" if len(reasoning) > 40 else reasoning
now = datetime.now().strftime("%m/%d %H:%M")
text = f"{emoji}【{symbol}】{decision} {now}\nスコア:{score:+.2f} {reason_short}\n#AI自動売買 #アルゴトレード"
return self.post(text)
def notify_weekly_report(self, report_text: str):
now = datetime.now().strftime("%m/%d %H:%M")
text = f"📊【週次レポート】{now}\n" + report_text[:100] + "\n#AI自動売買"
return self.post(text)
ポイント:
- 140字制限は post() 内で自動トリミング
- datetimeタイムスタンプを毎回入れることで「重複ツイート」エラーを回避
- tweepy.Client はTwitter API v2対応(v4系ライブラリ)
APSchedulerで定期実行
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.triggers.cron import CronTrigger
scheduler = BlockingScheduler(timezone="Asia/Tokyo")
60分ごとにシグナル確認・X投稿
scheduler.add_job(run_cycle, IntervalTrigger(minutes=60))
毎週月曜9:00 JSTに週次レポートをX投稿
scheduler.add_job(send_weekly_report, CronTrigger(
day_of_week="mon", hour=9, minute=0, timezone="Asia/Tokyo"
))
scheduler.start()
起動コマンド:
# バックグラウンド実行(Windows・ターミナルを閉じても継続)
start /B pythonw main_trader.py --schedule 60
ログを確認しながら実行
python main_trader.py --schedule 60
Gemini APIのモデル選定と注意点
AI判断エンジンにはGoogle Gemini APIを使っている。モデル選びで何度かつまずいたのでまとめておく。
モデル変遷
| モデル名 | 状況 |
|---|---|
| gemini-2.5-flash(無料枠) | 20リクエスト/日 → すぐクォータ超過 |
| gemini-1.5-flash | 404エラー(廃止済み) |
| gemini-2.0-flash | 無料枠:1500リクエスト/日 ✅ |
| gemini-2.5-flash(課金後) | 利用可能 ✅ |
2シンボル(SPY・USDJPY)× 60分サイクルで1日24リクエスト。gemini-2.0-flash の1500/日で十分に余裕がある。
クォータ超過時の安全なフォールバック実装
from google.api_core.exceptions import ResourceExhausted
try:
response = model.generate_content(prompt)
except ResourceExhausted:
return {"decision": "HOLD", "score": 0.0, "reasoning": "APIクォータ超過のためスキップ"}
except Exception as e:
return {"decision": "HOLD", "score": 0.0, "reasoning": f"エラー: {str(e)[:50]}"}
クォータ超過でもHOLDとして処理を継続し、ボットが止まらない設計にしている。
実際に踏んだエラーと解決策
エラー① 401 Unauthorized
原因: アプリ権限が「Read only」のままだった。
解決: Developer Portalで「Read and Write」に変更 → Access Tokenを再生成。
エラー② 403 Forbidden(duplicate content)
原因: SPYとUSDJPYが同じ HOLD score:0.00 で全く同じツイート文になっていた。
解決: ツイート文に日時(%m/%d %H:%M)を追加して毎回内容を変える。
エラー③ 404 Not Found(Gemini)
原因: gemini-1.5-flash は廃止済み。課金前に gemini-2.5-flash を使おうとした。
解決: 無料枠は gemini-2.0-flash、課金後は gemini-2.5-flash を使用。
エラー④ Windows ターミナルでの文字化け
原因: ⏸(U+23F8)などの絵文字がWindows CP932コンソールで表示できない。
解決: main_trader.py の先頭に以下を追加。
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
完成した自動投稿ツイートのイメージ
HOLDの場合:
⏸【SPY】HOLD 04/27 17:23
スコア:+0.00 市場の方向性が不明確なため様子見…
#AI自動売買 #アルゴトレード
BUYシグナルの場合:
📈【SPY】BUY 04/28 09:00
スコア:+0.75 移動平均線のゴールデンクロスを確認…
#AI自動売買 #アルゴトレード
まとめ
Discordと違いXへの投稿は外部発信になるため、フォロワーへのリアルタイム情報提供ができる。実装上のポイントは以下の3点だ。
- OAuth 1.0a(4キー)を使うこと(OAuth 2.0ではツイート投稿不可)
- 毎回ツイート内容を変える(タイムスタンプ追加で重複エラーを回避)
- Geminiモデルは課金状況に合わせて選ぶ(無料:gemini-2.0-flash、課金後:gemini-2.5-flash)
自動売買×自動投稿の組み合わせで、売買シグナルを透明性高く発信できる環境が整った。
免責事項:本記事で紹介しているシステムはペーパートレード(模擬取引)環境での動作を前提としています。実際の投資は自己責任でお願いします。

