moomoo APIと自然言語処理でFX自動売買を構築する方法【LLM連携ガイド2026】
最終更新: 2026年06月
moomoo証券のOpenD APIにLLMを接続し、「ドル円が節目を超えたらロング」という自然言語の指示をそのまま自動売買ロジックへ変換する——このアイデアを技術的に実現する方法を体系的に整理した。ただし、moomooが直接FX取引に対応しているかどうかは執筆時点で不確定情報が多い。本記事では「自然言語×API」というアーキテクチャコンセプトを中心に据え、確認できた範囲での実装手法と、CFD・ETFへの応用可能性について論じる。コードの再現性と論理的整合性を最優先にしており、「とりあえず動くコード」ではなく「設計として正しいコード」を提示することを意識した。
Photo by Maxim Hopman on Unsplash
moomooとそのAPIの概要
moomoo証券は、ナスダック上場企業Futu Holdings(FUTU)が運営するフィンテック系証券会社だ。主に米国株・香港株・ETFの取引プラットフォームとして知られており、個人投資家向けのアプリUIの洗練度が特徴的だが、筆者が注目したのはその裏側にあるOpenDというゲートウェイAPIである。
OpenDは、ローカルマシン上で起動するデーモンプロセスとして動作する。クライアントアプリケーション(PythonスクリプトやC++プログラム等)は、TCP接続またはWebSocketを介してOpenDと通信し、OpenDがFutuのサーバーとの認証・通信を代行する。この二層構造は、証券会社APIとしては珍しい設計だ。通常のREST APIと違い、「ローカルで常駐するプロキシ」がある形になる。
2026年4月、moomooは「Moomoo API Skills」を正式にリリースした。これは自然言語の指示だけでシステムトレードを構築・運用できる機能で、AIエージェントが「コード生成→バックテスト→発注」を会話ベースで実行できるとされている。対応市場には日本も含まれており、日本株との連携が確認されている。Safety Firstアーキテクチャとして、取引認証情報はOpenD経由でローカル環境に保持され、第三者AIサーバーには渡らない設計になっている点は評価できる。
公式Pythonライブラリは futu-api(PyPI)で提供されており、以下のように取得できる。
pip install futu-api
対応商品として確認できているのは、米国株・香港株・シンガポール株・日本株・ETFである。FX(外国為替証拠金取引)への直接対応については、公式ドキュメントで明確な記述が見当たらないため、利用前に必ずmoomoo公式サイトで最新情報を確認してほしい。
→ Python × FXバックテスト環境の構築については「Pythonでバックテストを高速化する方法」もあわせてご参照ください。
自然言語インターフェースとは——LLMが「指示を解釈する」仕組み
「ドル円が1時間足でボリンジャーバンド+2σを上抜けしたらUSDJPYを0.1ロット買う」——この文章を人間が書き、LLMがコードに翻訳する。これが自然言語インターフェースの本質だ。
実装上の課題は二つある。一つは意図の曖昧性解消、もう一つは構造化データへの変換だ。
自然言語の取引指示には曖昧さが潜む。「上抜け」は何本の足で確定するのか。「0.1ロット」は固定量か資産比率か。LLMはこれらを文脈から推定するが、金融取引では推定ミスが直接損失につながる。そこで筆者が採用したアーキテクチャは「解釈結果を必ず人間がレビューするワンクッション」を設けることだ。自然言語→JSON変換→レビュー→実行、というパイプラインにすることで、LLMの確率的な解釈ミスを運用レイヤーで吸収できる。
構造化変換には、ClaudeやGPT-4oのStructured Output機能(または関数呼び出し)を使う。モデルが任意のJSONを生成するのではなく、あらかじめ定義したスキーマに従って出力させることで、後段のパーサーが安定する。
LLMのAPIレイテンシは一般に800ms〜3,000msほどかかる。スキャルピングのように数秒単位のレスポンスが求められる戦略には不向きだが、スイングトレードやデイトレードのシグナル解析であれば実用範囲に収まる。
Photo by Igor Omilaev on Unsplash
moomoo Open APIの主要機能
OpenDを経由して利用できる主要機能を整理する。
リアルタイムデータ取得
subscribe()でティッカーをサブスクライブすると、コールバック経由でリアルタイム約定データ・板情報・ローソク足が取得できる。取得レートには口座プランによる制限がある場合があるため、API利用規約の確認が必要だ。
注文管理
成行・指値・逆指値注文を place_order() で発注できる。注文変更・キャンセルもAPIで操作可能。注文ステータスはコールバックで非同期通知される。
ポジション管理
get_position_list() で保有ポジションの一覧と損益情報が取得できる。
履歴データ(バックテスト用)
get_history_kline() で過去のローソク足データを取得できる。分足から月足まで対応しており、サードパーティバックテストフレームワーク(backtrader等)へのデータ供給源として使える。
対応言語はPython・Java・C#・C++・JavaScriptが公式にサポートされており、いずれのスタックからでも同じOpenDゲートウェイを通じてAPIにアクセスできる。
Pythonで自然言語→取引指示変換パイプラインを作る
アーキテクチャ全体像を先に示す。
[ユーザーの自然言語入力]
↓
[LLM解析レイヤー(Claude / GPT-4o)]
↓
[構造化JSON(TradeInstruction)]
↓
[バリデーション & レビューゲート]
↓
[moomoo APIコール層]
↓
[注文確認・ポジション管理]
自然言語解析レイヤー(LLM)
Anthropic SDKを使ってClaudeに自然言語の取引指示を解析させる例を示す。ポイントはシステムプロンプトで出力スキーマを厳密に定義することだ。
import anthropic
import json
client = anthropic.Anthropic()
SYSTEM_PROMPT = """
あなたは金融取引指示を解析するアシスタントです。
ユーザーの自然言語による取引指示を、以下のJSONスキーマに変換してください。
出力スキーマ:
{
"symbol": "銘柄コード(例: US.AAPL, HK.00700)",
"action": "buy または sell",
"order_type": "market(成行)または limit(指値)または stop(逆指値)",
"quantity": 数量(整数),
"price": 指値価格(成行の場合はnull),
"stop_price": 逆指値価格(不要な場合はnull),
"condition": "発動条件の説明文(任意)",
"confidence": 解釈の確信度(0.0〜1.0),
"ambiguities": ["曖昧な点のリスト"]
}
重要: 曖昧な指示には必ずambiguitiesに課題を列挙し、confidenceを下げること。
投資助言は行わない。指示の解釈のみ行う。
"""
def parse_trade_instruction(natural_language: str) -> dict:
"""
自然言語の取引指示をJSON構造に変換する。
LLMの解釈結果を返すが、実行前に必ずレビューを挟むこと。
"""
message = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[
{"role": "user", "content": natural_language}
]
)
response_text = message.content[0].text
# JSONブロックを抽出
try:
if "```json" in response_text:
json_str = response_text.split("```json")[1].split("```")[0].strip()
elif "```" in response_text:
json_str = response_text.split("```")[1].split("```")[0].strip()
else:
json_str = response_text.strip()
return json.loads(json_str)
except json.JSONDecodeError as e:
raise ValueError(f"LLMの出力をJSONに変換できませんでした: {e}\n出力: {response_text}")
# 使用例
if __name__ == "__main__":
instruction = "アップルが200日移動平均を上抜けしたら100株成行で買う"
result = parse_trade_instruction(instruction)
print(json.dumps(result, ensure_ascii=False, indent=2))
confidenceが0.8未満の場合は自動的に人間のレビューを要求するロジックを後段で入れることを筆者は推奨する。
取引指示への変換ロジック
LLMの出力をmoomoo APIが受け付けるデータ型に変換するレイヤーだ。
from futu import TrdSide, OrderType, TrdMarket
from dataclasses import dataclass
from typing import Optional
@dataclass
class MoomooOrderParams:
"""moomoo APIの place_order に渡すパラメータ"""
code: str
price: float
qty: float
trd_side: TrdSide
order_type: OrderType
trd_env: str # TrdEnv.SIMULATE or TrdEnv.REAL
def convert_to_moomoo_order(instruction: dict) -> MoomooOrderParams:
"""
LLMが生成したJSON指示をmoomoo APIパラメータに変換する。
変換に失敗した場合は例外を発生させ、注文を止める。
"""
# バリデーション
required_fields = ["symbol", "action", "order_type", "quantity"]
for field in required_fields:
if field not in instruction or instruction[field] is None:
raise ValueError(f"必須フィールドが不足しています: {field}")
# confidenceチェック——0.8未満は自動実行しない
confidence = instruction.get("confidence", 0.0)
if confidence < 0.8:
ambiguities = instruction.get("ambiguities", [])
raise ValueError(
f"解釈の確信度が不足しています({confidence:.2f})。"
f"曖昧な点: {ambiguities}"
)
# アクション変換
action_map = {"buy": TrdSide.BUY, "sell": TrdSide.SELL}
trd_side = action_map.get(instruction["action"].lower())
if trd_side is None:
raise ValueError(f"不明なアクション: {instruction['action']}")
# 注文タイプ変換
order_type_map = {
"market": OrderType.MARKET,
"limit": OrderType.NORMAL,
"stop": OrderType.STOP
}
order_type = order_type_map.get(instruction["order_type"].lower())
if order_type is None:
raise ValueError(f"不明な注文タイプ: {instruction['order_type']}")
price = instruction.get("price")
if order_type == OrderType.NORMAL and (price is None or price <= 0):
raise ValueError("指値注文には有効な価格が必要です")
if order_type == OrderType.MARKET:
price = 0.0
return MoomooOrderParams(
code=instruction["symbol"],
price=float(price),
qty=float(instruction["quantity"]),
trd_side=trd_side,
order_type=order_type,
trd_env="SIMULATE" # 本番では TrdEnv.REAL に変更
)
APIコール層
OpenDへの接続と実際の発注処理だ。本番環境では必ずシミュレーション口座で動作確認してから移行すること。
from futu import OpenSecTradeContext, TrdEnv, RET_OK
import logging
logger = logging.getLogger(__name__)
class MoomooTrader:
"""
moomoo OpenD APIへの接続・注文管理クラス。
OpenDがローカルで起動している必要がある。
"""
def __init__(self, host: str = '127.0.0.1', port: int = 11111):
self.host = host
self.port = port
self.trade_ctx = None
def __enter__(self):
self.trade_ctx = OpenSecTradeContext(
filter_trdmarket=None,
host=self.host,
port=self.port
)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.trade_ctx:
self.trade_ctx.close()
def place_order(self, params: MoomooOrderParams) -> dict:
"""
注文を発注する。シミュレーション環境でのみ動作するよう初期設定。
本番注文は trd_env=TrdEnv.REAL に明示変更が必要。
"""
if self.trade_ctx is None:
raise RuntimeError("取引コンテキストが初期化されていません")
logger.info(
f"注文発注: {params.code} {params.trd_side} "
f"{params.qty}株 @ {params.price}"
)
ret, data = self.trade_ctx.place_order(
price=params.price,
qty=params.qty,
code=params.code,
trd_side=params.trd_side,
order_type=params.order_type,
trd_env=TrdEnv.SIMULATE # 注意: 本番時は変更必要
)
if ret != RET_OK:
raise RuntimeError(f"注文失敗: {data}")
logger.info(f"注文成功: {data}")
return data.to_dict()
def get_positions(self) -> list:
"""保有ポジション一覧を取得"""
ret, data = self.trade_ctx.position_list_query(
trd_env=TrdEnv.SIMULATE
)
if ret != RET_OK:
raise RuntimeError(f"ポジション取得失敗: {data}")
return data.to_dict('records')
FX・CFD取引への応用と制約
ここが最も注意が必要な節だ。
moomooのOpenD APIがFX(外国為替証拠金取引)に対応しているかどうかは、執筆時点の公式ドキュメントでは確認できなかった。利用前にmoomoo証券の公式サポートまたはドキュメントで最新の対応状況を確認することを強く推奨する。
一方で、CFD(差金決済取引)やETFへの応用という観点では話が変わる。たとえばドル円の動きに連動するような通貨関連ETFが対応銘柄に含まれている場合、本記事で示したパイプラインはそのまま転用できる。自然言語でETFの売買指示を出し、LLMが解析してAPIに渡す構造は共通だ。
また、アーキテクチャ上の概念として「自然言語→構造化指示→API実行」という考え方は、対応ブローカーやプラットフォームを変えれば汎用的に使える。moomooに限らず、Interactive BrokersのTWS APIやAlpacaのREST APIに接続するラッパーを差し替えるだけで同じ自然言語インターフェースが動く設計にしている。
制約として明確にしておく点:
- moomooのFX直接取引への対応は要確認
- OpenDのローカル稼働が前提のため、サーバーレス環境では動作しない
- APIレート制限・データ取得制限は口座プランによって異なる
- LLM経由の注文はレイテンシが高く、スキャルピング系戦略には不向き
→ FX自動売買のリスク管理全般については「FX自動売買のメリット・デメリットと始め方」もあわせてご参照ください。
リスク管理ロジックの実装
自動売買でもっとも軽視されがちで、実際にもっとも重要な部分がリスク管理だ。LLMがどれだけ正確に指示を解釈しても、ポジションサイズやストップロスの設計が甘ければシステム全体が破綻する。
from dataclasses import dataclass
@dataclass
class RiskParameters:
"""リスク管理パラメータ"""
max_position_pct: float = 0.02 # 1ポジションの最大損失を資産の2%に限定
max_total_exposure_pct: float = 0.10 # 全ポジション合計の最大エクスポージャー
max_daily_loss_pct: float = 0.05 # 1日の最大損失額(資産比)
stop_loss_atr_multiplier: float = 2.0 # ATRの2倍をストップロス幅に使う
class RiskManager:
"""
注文前のリスクチェックを担当するクラス。
バリデーションに失敗した場合は例外を発生させ、注文を阻止する。
"""
def __init__(self, params: RiskParameters, account_equity: float):
self.params = params
self.account_equity = account_equity
self.daily_loss = 0.0
def calculate_position_size(
self,
entry_price: float,
stop_loss_price: float,
risk_pct: float = None
) -> float:
"""
許容リスク額からポジションサイズを逆算する(固定比率法)。
"""
risk_pct = risk_pct or self.params.max_position_pct
max_loss_amount = self.account_equity * risk_pct
price_diff = abs(entry_price - stop_loss_price)
if price_diff == 0:
raise ValueError("エントリー価格とストップロス価格が同一です")
position_size = max_loss_amount / price_diff
return int(position_size) # 整数株に切り捨て
def validate_order(
self,
order_params: MoomooOrderParams,
current_positions: list,
entry_price: float
) -> None:
"""
注文を実行前にリスク観点でバリデートする。
問題があれば例外を発生させる。
"""
# 1日の損失上限チェック
max_daily_loss = self.account_equity * self.params.max_daily_loss_pct
if self.daily_loss >= max_daily_loss:
raise ValueError(
f"日次最大損失上限に達しています: "
f"{self.daily_loss:.0f}円 / 上限{max_daily_loss:.0f}円"
)
# 既存ポジションのエクスポージャー計算
total_exposure = sum(
pos.get('market_val', 0) for pos in current_positions
)
new_exposure = order_params.qty * entry_price
max_exposure = self.account_equity * self.params.max_total_exposure_pct
if total_exposure + new_exposure > max_exposure:
raise ValueError(
f"総エクスポージャーが上限を超えます: "
f"現在{total_exposure:.0f} + 新規{new_exposure:.0f} "
f"> 上限{max_exposure:.0f}"
)
def calculate_stop_loss(
self, entry_price: float, atr: float, side: str
) -> float:
"""ATRベースのストップロス価格を計算する"""
stop_distance = atr * self.params.stop_loss_atr_multiplier
if side == "buy":
return entry_price - stop_distance
else:
return entry_price + stop_distance
ここで示した calculate_position_size() は、いわゆる固定比率法(Fixed Fractional)の実装だ。資産の2%を超えるリスクを1トレードに晒さないというルールは、古典的ではあるが実証的に有効なドローダウン抑制策として知られている。ATRベースのストップロスを組み合わせることで、銘柄のボラティリティに適応したリスク管理が可能になる。
バックテストと検証プロセス
本番稼働前にバックテストで戦略の有効性を検証するのは必須だ。moomooのAPIから過去データを取得し、backtraderなどのフレームワークに流し込む方法を示す。
from futu import OpenQuoteContext, KLType, RET_OK
import pandas as pd
def fetch_historical_data(
symbol: str,
kl_type: KLType = KLType.K_DAY,
start: str = '2024-01-01',
end: str = '2025-12-31'
) -> pd.DataFrame:
"""
moomoo APIから過去ローソク足データを取得する。
ページネーション処理込みの実装。
"""
quote_ctx = OpenQuoteContext(host='127.0.0.1', port=11111)
ret, data, page_req_key = quote_ctx.request_history_kline(
symbol,
start=start,
end=end,
ktype=kl_type,
max_count=1000
)
if ret != RET_OK:
quote_ctx.close()
raise RuntimeError(f"データ取得失敗: {data}")
all_data = [data]
while page_req_key is not None:
ret, data, page_req_key = quote_ctx.request_history_kline(
symbol,
ktype=kl_type,
max_count=1000,
page_req_key=page_req_key
)
if ret != RET_OK:
break
all_data.append(data)
quote_ctx.close()
df = pd.concat(all_data, ignore_index=True)
df['time_key'] = pd.to_datetime(df['time_key'])
df = df.sort_values('time_key').reset_index(drop=True)
return df[['time_key', 'open', 'high', 'low', 'close', 'volume']]
バックテスト結果を解釈する際の注意点として、サンプル数が少ない場合(N=30未満)は統計的有意性が低いことを常に意識する必要がある。シャープレシオが高く見えても、相場レジームが単一期間のバックテストは過学習(overfitting)のリスクが高い。ウォークフォワード検証や、アウトオブサンプル期間での検証を別途実施することを推奨する。
本番運用前のチェックリスト
筆者が自動売買システムを本番稼働させる前に毎回確認する項目を列挙する。
APIと環境の確認
- [ ] OpenDが最新バージョンにアップデートされているか
- [ ] moomoo証券でAPIアクセスが有効化されているか
- [ ] 対象銘柄がAPIで取引可能か(FX等は要確認)
- [ ] シミュレーション口座でのテストが完了しているか
リスク管理の確認
- [ ] ポジションサイズ計算が意図どおり動作しているか(手動で計算と照合)
- [ ] ストップロスが必ず設定される実装になっているか
- [ ] 日次損失上限が正しく機能しているか(意図的に上限を超えてテスト)
- [ ] 証拠金維持率のアラートが設定されているか
LLM解析レイヤーの確認
- [ ] ambiguousな指示でconfidenceが下がるか(境界ケースでテスト)
- [ ] APIキーが環境変数から読み込まれているか(ハードコード禁止)
- [ ] LLMのレート制限エラー時にリトライロジックが動作するか
本番切り替え
- [ ]
TrdEnv.SIMULATE→TrdEnv.REALの変更箇所を二重確認 - [ ] ログが適切に出力・保存されているか
- [ ] 異常時の緊急停止機能(kill switch)が実装されているか
Photo by Marga Santoso on Unsplash
よくある質問(FAQ)
Q: moomoo APIはFX取引に直接対応していますか? A: 執筆時点では公式ドキュメントにFX直接対応の明確な記述が確認できませんでした。利用前にmoomoo証券の公式サポートまたは最新ドキュメントで確認することを強く推奨します。通貨関連ETFやCFDへの応用可能性は別途確認が必要です。
Q: OpenDはどのように動作しますか? A: OpenDはローカルマシン上で常駐するプロキシプロセスです。PythonスクリプトはTCP(デフォルト11111番ポート)でOpenDと通信し、OpenDがFutuのサーバーへの認証・データ転送を代行します。クラウドサーバーで運用する場合はOpenDもそのサーバーにインストールして起動する必要があります。
Q: 自然言語で取引指示を出すのは安全ですか? A: LLMは確率的なモデルです。同じ文章から異なる解釈が生じる可能性があります。本記事で示したようにconfidenceスコアでフィルタリングし、人間のレビューゲートを挟むことでリスクを低減できますが、完全に排除はできません。高頻度・大ロットの注文に自然言語インターフェースを使う場合は特に慎重な設計が必要です。
Q: バックテストはAPIで可能ですか?
A: request_history_kline() で過去のローソク足データを取得できます。ただし取得可能な期間・時間足・銘柄数には制限があるため、大規模バックテストには別途データプロバイダーの利用も検討してください。
Q: 個人開発者でも利用できますか? A: moomoo証券に口座を開設し、APIアクセスを申請することで個人でも利用できます。ただし、ツールとしての利用規約・APIの利用条件は定期的に変更される可能性があります。最新の利用規約を必ず確認してください。
免責事項
本記事は情報提供を目的としており、投資助言・推奨・勧誘を意図するものではありません。
FX取引および金融商品への投資は元本を大幅に超える損失が生じる可能性があります。自動売買システムは想定外の動作をする可能性があり、システムの欠陥・ネットワーク障害・予期しない相場変動等により損失が発生するリスクがあります。
本記事で紹介したコードはあくまで概念実装例です。実際の運用に使用する場合は、十分なテストとリスク評価を自己責任で実施してください。
moomoo証券のAPI仕様・対応商品・利用規約は変更される可能性があります。本記事の内容と最新の公式情報が異なる場合は、必ず公式情報を優先してください。
本記事の情報に基づく投資判断による損失について、筆者は一切の責任を負いません。
