マルチモーダルLLM(チャート画像+テキスト統合)による売買判断戦略
戦略概要
本戦略は、マルチモーダルLLM(Large Language Model with Vision)を活用し、チャート画像とテキスト情報を単一モデルで統合分析して売買判断を行うアプローチです。従来は画像分析とテキスト分析を別々のモデルで処理し、結果を後から統合する必要がありましたが、GPT-4V、Claude Vision、Gemini Proなどの登場により、一体的な判断が可能になりました。
LLMでなければ実現できなかった理由
| 処理 | 従来手法 | マルチモーダルLLM |
|---|---|---|
| チャート画像のパターン認識 | CNN + 手動ラベリング | 視覚的に「見て」パターンを理解 |
| テキストとの統合 | 別モデルで処理後に統合 | 単一モデルで一体的に判断 |
| ニュース文脈との照合 | ルールベース | 視覚情報と文脈を同時に理解 |
| 新しいチャートパターン | 再学習が必要 | ゼロショットで対応可能 |
| 説明可能性 | ブラックボックス | 判断理由を自然言語で出力 |
主要マルチモーダルLLMの比較(2026年3月時点)
| モデル | 金融ベンチマーク | 入力コスト(/1M) | 出力コスト(/1M) | 特徴 |
|---|---|---|---|---|
| OpenAI GPT-5.2 | 92/100 | $0.01 | $0.03 | 高精度、画像+テキスト統合が最も安定 |
| Claude Sonnet 4.6 | 89/100 | $0.02 | $0.04 | 視覚推論強化、複雑なチャート分析向き |
| Gemini 3.1 Pro | 88/100 | $0.015 | $0.035 | 多言語対応、リアルタイム統合 |
| Meta LLaMA 4 | 85/100 | $0.005 | $0.01 | オープンソース、低コスト |
| FinVision (Molmoベース) | 87/100 | $0.01 | $0.02 | 金融特化オープンソース |
出典: FinanceAgent v1.1 Benchmark (2026年3月)
チャート画像のキャプチャと前処理
CHART-IMG APIの活用
TradingViewチャートのリアルタイムスクリーンショットを生成するREST APIを使用します。
import requests
import base64
from dataclasses import dataclass
@dataclass
class ChartCapture:
"""チャートキャプチャ設定"""
symbol: str
interval: str # "1D", "4H", "1H", etc.
studies: list[str] = None # ["RSI", "MACD", "BB"]
def capture_chart_image(config: ChartCapture) -> str:
"""CHART-IMG APIでチャート画像を取得"""
base_url = "https://api.chart-img.com/v2/tradingview/advanced-chart"
params = {
"symbol": config.symbol,
"interval": config.interval,
"theme": "dark",
"width": 800,
"height": 600,
}
if config.studies:
params["studies"] = ",".join(config.studies)
headers = {
"Authorization": f"Bearer {CHART_IMG_API_KEY}"
}
response = requests.get(base_url, params=params, headers=headers)
# Base64エンコードで返却
return base64.b64encode(response.content).decode("utf-8")
画像前処理のベストプラクティス
| 処理 | 目的 | レイテンシー |
|---|---|---|
| リサイズ (800x600推奨) | APIコスト削減、精度維持 | 0.05秒 |
| コントラスト調整 | パターン認識向上 | 0.1秒 |
| Base64エンコード | API送信形式 | 0.02秒 |
| 合計 | 約0.2秒 |
Claude Vision / GPT-4V でのチャート分析プロンプト設計
Claude Vision向けプロンプト(XML構造化)
import anthropic
import json
from dataclasses import dataclass
@dataclass
class ChartAnalysis:
"""チャート分析結果"""
trend: str # "bullish", "bearish", "neutral"
patterns: list[str] # 検出されたパターン
support_levels: list[float]
resistance_levels: list[float]
technical_score: float # -1.0 ~ 1.0
key_observations: list[str]
recommended_action: str
def analyze_chart_with_claude(
chart_base64: str,
symbol: str,
client: anthropic.Anthropic
) -> ChartAnalysis:
"""Claude Visionでチャート分析"""
prompt = f"""<chart_analysis>
<task>
このチャート画像を分析し、テクニカル指標に基づく売買判断材料を抽出してください。
銘柄: {symbol}
</task>
<analysis_requirements>
1. トレンド判定: bullish/bearish/neutral
2. チャートパターン: ヘッドアンドショルダー、ダブルトップ/ボトム、三角保ち合い等
3. サポート/レジスタンスライン: 主要な価格帯を3つまで
4. テクニカルスコア: -1.0(強い売り)〜 +1.0(強い買い)
5. 主要な観察点: 最大3つ
6. 推奨アクション: buy/sell/hold
</analysis_requirements>
<output_format>
JSON形式で出力してください。
</output_format>
</chart_analysis>"""
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=2000,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": chart_base64,
},
},
{
"type": "text",
"text": prompt,
},
],
}
],
)
result = json.loads(response.content[0].text)
return ChartAnalysis(
trend=result["trend"],
patterns=result["patterns"],
support_levels=result["support_levels"],
resistance_levels=result["resistance_levels"],
technical_score=result["technical_score"],
key_observations=result["key_observations"],
recommended_action=result["recommended_action"]
)
GPT-4V向けプロンプト
from openai import OpenAI
def analyze_chart_with_gpt4v(
chart_base64: str,
symbol: str,
client: OpenAI
) -> ChartAnalysis:
"""GPT-4Vでチャート分析"""
prompt = f"""このチャート画像から{symbol}の株価パターンを分析してください。
分析要件:
- トレンド方向(bullish/bearish/neutral)
- 検出されたチャートパターン
- サポート/レジスタンスレベル
- RSI/MACDがあれば、その状態
- テクニカルスコア(-1.0〜+1.0)
- 推奨アクション
JSON形式で回答してください。"""
response = client.chat.completions.create(
model="gpt-4-vision-preview",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{chart_base64}"
}
}
]
}
],
max_tokens=2000
)
result = json.loads(response.choices[0].message.content)
return ChartAnalysis(**result)
テキスト情報との統合判断ロジック
チャート分析結果とニュース/決算情報を統合し、最終的な売買シグナルを生成します。
from dataclasses import dataclass
from enum import Enum
class SignalStrength(Enum):
STRONG_BUY = 2
BUY = 1
HOLD = 0
SELL = -1
STRONG_SELL = -2
@dataclass
class IntegratedSignal:
"""統合売買シグナル"""
symbol: str
signal: SignalStrength
chart_score: float # テクニカルスコア
text_score: float # テキストセンチメント
integrated_score: float # 統合スコア
confidence: float
reasoning: str
sources: list[str]
def integrate_chart_and_text(
chart_analysis: ChartAnalysis,
text_sentiment: float, # -1.0 ~ 1.0
news_headlines: list[str],
client: anthropic.Anthropic
) -> IntegratedSignal:
"""チャート分析とテキスト情報を統合"""
# スコアの重み付け統合
# テクニカル: 60%, センチメント: 40%
integrated_score = (
chart_analysis.technical_score * 0.6 +
text_sentiment * 0.4
)
# 矛盾検出と解決
contradiction = (
(chart_analysis.technical_score > 0.3 and text_sentiment < -0.3) or
(chart_analysis.technical_score < -0.3 and text_sentiment > 0.3)
)
if contradiction:
# 矛盾がある場合はLLMで判断
resolution_prompt = f"""
チャート分析とニュースセンチメントに矛盾があります。
## チャート分析
- トレンド: {chart_analysis.trend}
- テクニカルスコア: {chart_analysis.technical_score}
- パターン: {', '.join(chart_analysis.patterns)}
- 観察点: {', '.join(chart_analysis.key_observations)}
## ニュース情報
- センチメントスコア: {text_sentiment}
- 主要ヘッドライン:
{chr(10).join(f'- {h}' for h in news_headlines[:5])}
どちらの情報を優先すべきか、理由とともに判断してください。
最終的な売買推奨(strong_buy/buy/hold/sell/strong_sell)を提示してください。
"""
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1000,
messages=[{"role": "user", "content": resolution_prompt}]
)
# LLMの判断を解析(簡略化)
llm_judgment = response.content[0].text
confidence = 0.6 # 矛盾時は信頼度を下げる
else:
confidence = 0.8
llm_judgment = ""
# シグナル決定
if integrated_score >= 0.5:
signal = SignalStrength.STRONG_BUY
elif integrated_score >= 0.2:
signal = SignalStrength.BUY
elif integrated_score <= -0.5:
signal = SignalStrength.STRONG_SELL
elif integrated_score <= -0.2:
signal = SignalStrength.SELL
else:
signal = SignalStrength.HOLD
return IntegratedSignal(
symbol=chart_analysis.patterns[0] if chart_analysis.patterns else "",
signal=signal,
chart_score=chart_analysis.technical_score,
text_score=text_sentiment,
integrated_score=integrated_score,
confidence=confidence,
reasoning=f"チャート: {chart_analysis.trend}, テキスト: {'ポジティブ' if text_sentiment > 0 else 'ネガティブ' if text_sentiment < 0 else '中立'}",
sources=news_headlines[:3]
)
フルパイプライン実装
import asyncio
from datetime import datetime
class MultimodalTradingAgent:
"""マルチモーダル売買判断エージェント"""
def __init__(
self,
anthropic_client: anthropic.Anthropic,
chart_api_key: str
):
self.llm_client = anthropic_client
self.chart_api_key = chart_api_key
async def analyze_symbol(
self,
symbol: str,
news_text: str
) -> IntegratedSignal:
"""銘柄の統合分析"""
# 1. チャート画像キャプチャ
chart_config = ChartCapture(
symbol=symbol,
interval="1D",
studies=["RSI", "MACD", "BB"]
)
chart_image = capture_chart_image(chart_config)
# 2. 並行処理でチャート分析とテキスト分析
chart_task = asyncio.create_task(
self._analyze_chart_async(chart_image, symbol)
)
text_task = asyncio.create_task(
self._analyze_text_async(news_text, symbol)
)
chart_analysis, text_sentiment = await asyncio.gather(
chart_task, text_task
)
# 3. 統合判断
signal = integrate_chart_and_text(
chart_analysis,
text_sentiment,
news_text.split("\n")[:10], # 先頭10行をヘッドラインとして使用
self.llm_client
)
return signal
async def _analyze_chart_async(
self,
chart_base64: str,
symbol: str
) -> ChartAnalysis:
"""非同期チャート分析"""
return analyze_chart_with_claude(
chart_base64, symbol, self.llm_client
)
async def _analyze_text_async(
self,
text: str,
symbol: str
) -> float:
"""非同期テキストセンチメント分析"""
prompt = f"""以下のニュース/決算情報から{symbol}に関するセンチメントを分析してください。
{text}
センチメントスコアを-1.0(非常にネガティブ)〜+1.0(非常にポジティブ)で回答してください。
数値のみを回答してください。"""
response = self.llm_client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=100,
messages=[{"role": "user", "content": prompt}]
)
return float(response.content[0].text.strip())
バックテスト方法と精度評価
バックテスト設計
from dataclasses import dataclass
import pandas as pd
@dataclass
class BacktestResult:
"""バックテスト結果"""
total_return: float
sharpe_ratio: float
max_drawdown: float
win_rate: float
total_trades: int
avg_holding_days: float
def backtest_multimodal_strategy(
historical_data: pd.DataFrame,
signals: list[IntegratedSignal],
initial_capital: float = 1_000_000
) -> BacktestResult:
"""マルチモーダル戦略のバックテスト"""
capital = initial_capital
positions = {}
trades = []
for signal in signals:
date = signal.timestamp
price = historical_data.loc[date, "close"]
if signal.signal in [SignalStrength.BUY, SignalStrength.STRONG_BUY]:
# ポジション構築
position_size = capital * 0.1 * signal.confidence
shares = position_size // price
positions[signal.symbol] = {
"shares": shares,
"entry_price": price,
"entry_date": date
}
capital -= shares * price
elif signal.signal in [SignalStrength.SELL, SignalStrength.STRONG_SELL]:
# ポジション解消
if signal.symbol in positions:
pos = positions[signal.symbol]
pnl = (price - pos["entry_price"]) * pos["shares"]
capital += pos["shares"] * price
trades.append({
"symbol": signal.symbol,
"pnl": pnl,
"holding_days": (date - pos["entry_date"]).days
})
del positions[signal.symbol]
# 結果計算
total_pnl = sum(t["pnl"] for t in trades)
wins = sum(1 for t in trades if t["pnl"] > 0)
return BacktestResult(
total_return=(capital - initial_capital) / initial_capital,
sharpe_ratio=calculate_sharpe(trades),
max_drawdown=calculate_max_drawdown(trades),
win_rate=wins / len(trades) if trades else 0,
total_trades=len(trades),
avg_holding_days=sum(t["holding_days"] for t in trades) / len(trades) if trades else 0
)
バックテスト結果の目安
2023-2025年のS&P500構成銘柄でのバックテスト結果:
| 戦略 | 予測精度 | Sharpe比率 | 年率リターン | 最大DD |
|---|---|---|---|---|
| LSTM+Transformer統合 | 95% | 1.5-2.0 | 15-25% | -12% |
| Transformer単体 | 93% | 1.2-1.5 | 12-18% | -15% |
| Bidirectional LSTM | 91% | 1.0-1.3 | 10-15% | -18% |
| マルチモーダルLLM (本戦略) | 88-92% | 1.2-1.8 | 12-20% | -15% |
出典: Trading-R1モデルバックテスト結果, arxiv.org (2026年2月)
コストとレイテンシー
API利用コスト試算
| 項目 | 1リクエスト | 100銘柄/日 | 月間 (20営業日) |
|---|---|---|---|
| チャート画像取得 (CHART-IMG) | $0.002 | $0.20 | $4.00 |
| Claude Vision分析 | $0.03 | $3.00 | $60.00 |
| テキスト分析 | $0.01 | $1.00 | $20.00 |
| 合計 | $0.042 | $4.20 | $84.00 |
レイテンシー内訳
| 処理 | 平均時間 | 最大時間 |
|---|---|---|
| チャートキャプチャ | 0.5秒 | 1.0秒 |
| 画像前処理 | 0.2秒 | 0.3秒 |
| LLM推論(チャート) | 1.5秒 | 3.0秒 |
| LLM推論(テキスト) | 0.8秒 | 1.5秒 |
| 統合判断 | 0.3秒 | 0.5秒 |
| 合計(並列処理時) | 2.0秒 | 3.5秒 |
注意: 高頻度取引(HFT)には不向き。スイングトレード〜ポジショントレード向け。
リスク要因
| リスク | 影響 | 対策 |
|---|---|---|
| データバイアス | 過去データへの過剰適合、地政学リスクで誤判断 | 多様なデータソース、人間の監督 |
| レイテンシー | リアルタイム機会喪失(HFTには致命的) | スイングトレード以上の時間軸で運用 |
| APIコスト | 大規模運用で月間$1000超 | バッチ処理、重要銘柄に絞る |
| ハルシネーション | 存在しないパターンを「検出」 | 複数モデル検証、閾値設定 |
| モデルドリフト | LLM更新で分析傾向変化 | 定期的な精度検証、A/Bテスト |
日本市場への適用
2026年現在、日本のAIインフラ市場は5.5億ドルを超え、株式取引の自動化が進んでいます。本戦略の日本株適用における考慮点:
- CHART-IMG: 東証銘柄コード(例:
TYO:7203)に対応 - 決算情報: TDnet/適時開示情報のPDF解析が必要
- 時間帯: 東証の取引時間(9:00-15:30 JST)に合わせたスケジューリング
- 言語: Claude/GPT-4Vは日本語テキストにも高精度で対応
まとめ
マルチモーダルLLMによるチャート画像+テキスト統合分析は、従来は別々のシステムで処理していた視覚情報とテキスト情報を単一モデルで一体的に判断できる革新的なアプローチです。
推奨される導入ステップ:
- 検証フェーズ (1-2週間): 10-20銘柄で精度検証、人間の判断との一致率測定
- ペーパートレード (1ヶ月): 実際のシグナルに基づく仮想売買で執行タイミング検証
- 小ロット実運用 (3ヶ月): 総資産の5-10%で実運用開始
- スケールアップ: 精度・収益性を確認後、段階的に拡大
重要: レイテンシーの制約からデイトレードや高頻度取引には不向きです。スイングトレード(数日〜数週間)以上の時間軸での運用を推奨します。
免責事項: 本記事は情報提供を目的としており、特定の金融商品の売買を推奨するものではありません。投資判断は自己責任で行ってください。バックテスト結果は過去のデータに基づくものであり、将来の収益を保証するものではありません。LLMの出力にはハルシネーション(事実と異なる情報の生成)のリスクがあり、必ず人間による検証を行ってください。