1. イントロダクション

Raspberry Piで 24時間稼働するFX USD/JPYのシグナルBot を個人開発しました。
もともとはEthereumのシグナルボットを作っていたのですが、FX(特にドル円)の方がデータ取得が安定しており、テクニカル分析との相性も良かったため方針を転換。最終的に、以下の 3つのコンポーネント で構成されるシステムが完成しました。
- シグナル判定エンジン (
fx_signal.py) — 5つのテクニカル指標で売買を判定 - サイバーパンクダッシュボード (
fx_dashboard.py) — Flask製リアルタイムWebUI - AIチャットBot「ラテちゃん」 (
latte_chat.py) — Telegramで会話するFXアシスタント
本記事では、システムの全体設計からUI実装、AIキャラクターの作り方まで、開発の全記録を公開します。
2. システムアーキテクチャ全体像

データの流れはシンプルです。
- Yahoo Finance API から USD/JPY の価格データを定期取得
- Raspberry Pi 上の
fx_signal.pyが5つのテクニカル指標で分析 - シグナル発火時に Telegram Bot で即座に通知
- 全データを SQLite に蓄積し、Flask Webダッシュボード でリアルタイム可視化
技術スタック
| カテゴリ | 技術 |
|---|---|
| 言語 | Python 3 (Flask, sqlite3, urllib, requests) |
| データベース | SQLite (WALモード) |
| フロントエンド | Chart.js (カスタムプラグイン) |
| 価格API | Yahoo Finance API + 2つのフォールバックAPI |
| 通知 | Telegram Bot API |
| AI | Z.ai API (GLM-4.5-Air) |
| 常駐化 | systemd / cron |
なぜラズパイか
24時間常時稼働、省電力、自宅サーバーとしてのコンパクトさ。当初はメインPCとPiの分散構成で開発していましたが、処理負荷が軽かったため最終的に Pi一台に集約 しました。電気代を気にせず放置できるのが最大のメリットです。
3. シグナル判定エンジン(技術的核心)
シグナル判定には、単一指標の誤検知を防ぐため 「5指標の合議制(コンセンサスモデル)」 を採用しています。
5指標の判定ロジック
| # | 指標 | 買いシグナル条件 | 売りシグナル条件 |
|---|---|---|---|
| 1 | RSI (14期間) | RSI ≤ 40 | RSI ≥ 60 |
| 2 | MACDヒストグラム | ヒストグラム > 0 | ヒストグラム < 0 |
| 3 | 5期間トレンド | -0.15%以下の下落 | +0.15%以上の上昇 |
| 4 | ボリンジャーバンド | 価格 ≤ 下限バンド | 価格 ≥ 上限バンド |
| 5 | MA クロスオーバー | ゴールデンクロス | デッドクロス |
各指標が1票を持ち、閾値2/5で発火。つまり5つの指標のうち2つ以上が同じ方向を示した場合にシグナルを出します。
RSI(Wilderの平滑化)
def calculate_rsi(prices, period=14):
deltas = [prices[i] - prices[i-1] for i in range(1, len(prices))]
gains = [d if d > 0 else 0 for d in deltas]
losses = [-d if d < 0 else 0 for d in deltas]
avg_gain = sum(gains[:period]) / period
avg_loss = sum(losses[:period]) / period
for i in range(period, len(gains)):
avg_gain = (avg_gain * (period - 1) + gains[i]) / period
avg_loss = (avg_loss * (period - 1) + losses[i]) / period
if avg_loss == 0:
return 100
rs = avg_gain / avg_loss
return 100 - (100 / (1 + rs))
EMA(指数移動平均)
def calculate_ema(prices, period):
multiplier = 2 / (period + 1)
ema = [sum(prices[:period]) / period]
for price in prices[period:]:
ema.append((price - ema[-1]) * multiplier + ema[-1])
return ema
運用上の工夫
- クールダウン機能: 同一方向のシグナルは 30分間スパム防止。短期間に何度も同じ通知が飛ぶのを抑制します。
- 週末スキップ: FX市場の休場(土日)を考慮し、無駄な判定を回避。
- 全パラメータ外出し:
config.jsonで閾値やRSI期間を自由に調整可能。
4. サイバーパンクダッシュボード
デザインコンセプト
ターミナル風 × サイバーパンクの世界観を目指しました。
| 要素 | カラーコード | 用途 |
|---|---|---|
| 背景 | #050a14 |
ダークネイビー |
| アクセント | #00ffcc |
シアン/ティール |
| 買いシグナル | #00ffcc |
シアン |
| 売りシグナル | #ff3366 |
レッド/ピンク |
| テキスト | #e0e8f0 / #a0b0c0 |
メイン/サブ |
フォントは JetBrains Mono(コード・数値) + Inter(UIテキスト)を組み合わせ、テクニカルな雰囲気を演出しています。
UIコンポーネント
ダッシュボードは6つのカードで構成されています。

- USD/JPY 価格カード — リアルタイム価格 + 変動率バッジ
- RSIゲージ — 半円ドーナツ + カスタム針プラグイン(Chart.js)

- MACDチャート — ライン + バーのコンボチャート
- ラテちゃんの判定カード — 5指標のドット表示 + 総合スコア

- 価格チャート — グラデーション塗りつぶし + 時間範囲切替(6H/24H/72H/168H)
- シグナル履歴テーブル — タイムスタンプ、売買タイプ、価格、RSI、レーティングを一覧表示

技術的ポイント
- Chart.jsカスタムプラグイン: RSIゲージの針は、Chart.jsのプラグインAPIを使って独自描画。半円ドーナツの上に
afterDrawフックで針を重ねています。 - Visibility API: ブラウザタブが非表示の時は自動更新を停止し、CPUリソースを節約。
- 30秒自動更新:
setIntervalによるポーリングでリアルタイム感を実現。 - backdrop-filter + CSS変数: グラスモーフィズムのカードデザインとテーマ管理。
Flask APIエンドポイント
| メソッド | パス | 内容 |
|---|---|---|
| GET | /api/current |
現在価格 + 前回比 |
| GET | /api/indicators |
RSI/MACD/BB/スコア |
| GET | /api/prices |
時系列価格データ |
| GET | /api/signals |
シグナル履歴 |
| POST | /api/signals/push |
Pi → ダッシュボード同期 |
5. AIチャットBot「ラテちゃん」

Geminiがプロンプト、Niji Journeyで生成。
キャラクター設計
| 項目 | 内容 |
|---|---|
| 名前 | ラテ |
| 性格 | 穏やかで落ち着いた、親しみやすいFXアシスタント |
| 口調 | 「〜だね」「〜かな」「〜だよ」 |
| 一人称 | 「わたし」 |
| 絵文字 | 控えめに ✨ 程度 |
これらもすべてAIが決めてくれました。FXの用語のRate(レート)をローマ字読みにしてラテ。
技術実装
- Z.ai API (GLM-4.5-Air) によるLLM連携
- リアルタイムFXデータの コンテキスト注入
- 会話履歴の保持(直近5往復)
- システムプロンプトでキャラクター制御
ラテちゃんの最大の特徴は、回答時に 最新の相場データがコンテキストとして注入される こと。以下のようなデータが毎回システムプロンプトに含まれます。
現在のドル円データ (2026-03-04 15:30 JST)
USD/JPY: 157.25円
RSI(14): 38.5 (売られすぎゾーン)
MACD: 0.1234 / Signal: 0.0987 / Hist: 0.0247
総合判定: 買いシグナル (買いスコア:3 売りスコア:0)
これにより、「今ドル円どう?」と聞くだけで、ラテちゃんがリアルタイムの相場状況を踏まえて回答してくれます。
Telegramでの通知実例
シグナル発火時には、根拠となった指標も含めて通知が届きます。
✨ 買いシグナル検出!
ドル円 157.47円
RSI: 26.07
根拠:
- RSI=26.07(売られすぎ)
- MACDマイナス圏(-0.0071)
- BB下限突破(157.47≤157.48)
そろそろ買い時かも✨
6. インフラ・運用
systemdサービス設定
ラテちゃんとダッシュボードは systemd で常駐化し、障害時の自動復旧を設定しています。
[Unit]
Description=Latte Chat Bot Service
After=network-online.target
[Service]
ExecStart=/usr/bin/python3 /home/yuikei/latte_chat.py
WorkingDirectory=/home/yuikei
MemoryMax=80M
Restart=on-failure
User=yuikei
[Install]
WantedBy=multi-user.target
ダッシュボード側も同様に MemoryMax=100M で制限をかけ、ラズパイの限られたリソースを守っています。
SQLite WALモードの選定理由
fx_signal.py(書き込み)と fx_dashboard.py(読み取り)が同時にDBへアクセスするため、WALモード(Write-Ahead Logging)を採用。ロックの競合を最小限に抑えつつ、ラズパイ向きの軽量さを維持しています。
価格取得の3段フォールバック戦略
API障害に備え、3つのデータソースを 優先順位付きでカスケード しています。
- Yahoo Finance API(メイン)
- FreeForexAPI(第1フォールバック)
- Open Exchange Rates(第2フォールバック)
上位のAPIが失敗した場合、自動的に次のAPIを試行。グレースフルデグラデーションにより、ユーザーが障害に気づくことなくサービスが継続します。
その他の運用設定
- 静的IP設定:
nmcliで192.168.68.67に固定。LAN内からいつでもダッシュボードにアクセス可能。 - SDカード容量: 28GB中5.4GB使用。DBは約19KB/日の増加なので、実質的に容量を気にする必要なし。
- cronによる定期実行:
fx_signal.pyは5分おきにcronで実行。
7. まとめ・今後の展望
開発を通じて学んだこと
- テクニカル指標(RSI、MACD、ボリンジャーバンド等)の数学的な仕組みと実装
- Raspberry Piでの Python常時稼働サービス構築(systemd、cron、メモリ管理)
- Chart.jsカスタムプラグイン開発(Canvas APIによる独自描画)
- LLMのキャラクターデザインとコンテキスト注入テクニック
今後の改善案
- WebSocket化: ポーリング → リアルタイムプッシュ通知へ
- マルチ通貨ペア対応: EUR/USD、GBP/JPY等の追加
- バックテスト機能: 過去データでシグナル精度を検証
- ダッシュボード認証: Basic Auth or トークンベースの保護
- モバイル対応強化: レスポンシブの更なる改善
プロジェクト規模
約2,500行、12ファイル。個人開発としてはそれなりの規模ですが、Python + SQLite + Flask というシンプルなスタックのおかげで、見通しの良いコードベースを維持できました。
ラズパイ1台でFXの「目」と「脳」と「口」を動かす。小さなコンピュータの可能性は、まだまだ広がりそうです。

