ドル円を手動でやってた頃、「夜中の動きを全部見逃した!」って後悔するのが週に3回くらいあった。子供が生まれてからは、もはや深夜にチャートを見るなんて夢物語。IT会社に転職したのをきっかけに「APIで自動売買できるんじゃないか」と思って調べたら、OANDAが無料でREST APIを公開していることを知った。「え、これタダで使えるの?」ってちょっと疑ったくらい。
OANDAを選んだ理由
FX自動売買のAPIとして有名なのは、OANDA・GMOクリック証券・IG証券あたり。僕がOANDAを選んだ理由は3つ。
① REST APIが無料で使える(他社はAPIに月額料金がかかることも多い)。② デモ口座でリアルタイムの実装テストができる。③ Python用ライブラリ(oandapyV20)がある。プログラミング初心者の僕には、ライブラリの存在は本当に大きい。「1からHTTPリクエストを書く」は最初の壁が高すぎる。
準備:デモ口座とAPIキーの取得
まずOANDAのデモ口座を開設する。本番口座ではなくデモで動作確認できるので安心。
① OANDA Japanのサイトでデモ口座を開設 → ② ログイン後「マイアカウント」→「APIアクセスの管理」→「APIアクセストークンの生成」でAPIキーを取得 → ③ アカウントIDはダッシュボードのURLや口座情報ページで確認できる(数字8桁くらいのやつ)。
環境構築
pip install oandapyV20 pandas
APIキーとアカウントIDは環境変数で管理するのが安全。コードにベタ書きは絶対NG(経験談あり。。。)。
# .envファイル(Gitにコミットしないこと!)
OANDA_API_KEY=あなたのAPIキー
OANDA_ACCOUNT_ID=あなたのアカウントID
import os
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("OANDA_API_KEY")
ACCOUNT_ID = os.getenv("OANDA_ACCOUNT_ID")
ENVIRONMENT = "practice" # デモ口座は "practice"、本番は "live"
現在レートの取得
まず「ドル円の今のレートを取れるか」から確認。
import oandapyV20
import oandapyV20.endpoints.pricing as pricing
client = oandapyV20.API(access_token=API_KEY, environment=ENVIRONMENT)
# ドル円の現在レートを取得
params = {"instruments": "USD_JPY"}
r = pricing.PricingInfo(accountID=ACCOUNT_ID, params=params)
client.request(r)
price_data = r.response["prices"][0]
bid = float(price_data["bids"][0]["price"])
ask = float(price_data["asks"][0]["price"])
spread = ask - bid
print(f"ドル円 Bid: {bid:.3f}")
print(f"ドル円 Ask: {ask:.3f}")
print(f"スプレッド: {spread*100:.1f}銭")
過去データ(ローソク足)の取得
テクニカル指標の計算に過去データが必要。OANDAのAPIで直接取れる。
import oandapyV20.endpoints.instruments as instruments
import pandas as pd
def get_candles(instrument="USD_JPY", granularity="H1", count=200):
"""
ローソク足データを取得する
granularity: M1(1分), M5(5分), H1(1時間), D(日足)など
"""
params = {
"granularity": granularity,
"count": count,
"price": "M" # Mid price
}
r = instruments.InstrumentsCandles(instrument=instrument, params=params)
client.request(r)
candles = []
for c in r.response["candles"]:
if c["complete"]:
candles.append({
"time": c["time"][:19],
"open": float(c["mid"]["o"]),
"high": float(c["mid"]["h"]),
"low": float(c["mid"]["l"]),
"close": float(c["mid"]["c"]),
"volume": c["volume"]
})
df = pd.DataFrame(candles)
df["time"] = pd.to_datetime(df["time"])
df.set_index("time", inplace=True)
return df
# 1時間足を200本取得
df = get_candles("USD_JPY", "H1", 200)
print(df.tail())
print(f"\n取得件数: {len(df)}本")
移動平均クロスで自動売買ロジックを作る
取得したデータに移動平均を計算して、クロスシグナルが出たら注文を出す。
import oandapyV20.endpoints.orders as orders
import json
def place_market_order(instrument, units, stop_loss_pips=20):
"""
成行注文を発注する
units: 正数=買い、負数=売り(1ユニット=1通貨)
"""
# ストップロス価格の計算
current_price = get_current_price(instrument)
pip_value = 0.01 if "JPY" in instrument else 0.0001
if units > 0: # 買い
sl_price = round(current_price["bid"] - stop_loss_pips * pip_value, 3)
else: # 売り
sl_price = round(current_price["ask"] + stop_loss_pips * pip_value, 3)
data = {
"order": {
"type": "MARKET",
"instrument": instrument,
"units": str(units),
"stopLossOnFill": {
"price": str(sl_price)
}
}
}
r = orders.OrderCreate(accountID=ACCOUNT_ID, data=data)
client.request(r)
return r.response
def get_current_price(instrument):
"""現在価格を取得"""
params = {"instruments": instrument}
r = pricing.PricingInfo(accountID=ACCOUNT_ID, params=params)
client.request(r)
p = r.response["prices"][0]
return {
"bid": float(p["bids"][0]["price"]),
"ask": float(p["asks"][0]["price"])
}
def check_and_trade():
"""移動平均クロス戦略の実行"""
df = get_candles("USD_JPY", "H1", 100)
# 移動平均の計算
df["ma_short"] = df["close"].rolling(10).mean()
df["ma_long"] = df["close"].rolling(25).mean()
# 直近2本でクロス判定
prev_short = df["ma_short"].iloc[-2]
prev_long = df["ma_long"].iloc[-2]
curr_short = df["ma_short"].iloc[-1]
curr_long = df["ma_long"].iloc[-1]
if prev_short <= prev_long and curr_short > curr_long:
print("ゴールデンクロス! → 買いシグナル")
# デモでは1000通貨(最小単位)で試す
response = place_market_order("USD_JPY", units=1000)
print(f"注文完了: {response['orderFillTransaction']['id']}")
elif prev_short >= prev_long and curr_short < curr_long:
print("デッドクロス! → 売りシグナル")
response = place_market_order("USD_JPY", units=-1000)
print(f"注文完了: {response['orderFillTransaction']['id']}")
else:
print(f"シグナルなし(MA短期:{curr_short:.3f} / 長期:{curr_long:.3f})")
# 1時間ごとに実行する場合(schedule使用)
import schedule
import time
schedule.every().hour.at(":05").do(check_and_trade)
print("自動売買ボット起動中...")
while True:
schedule.run_pending()
time.sleep(60)
オープンポジションの確認とクローズ
import oandapyV20.endpoints.positions as positions
import oandapyV20.endpoints.trades as trades_ep
def get_open_positions():
"""オープンポジションを確認"""
r = positions.OpenPositions(accountID=ACCOUNT_ID)
client.request(r)
return r.response["positions"]
def close_all_positions():
"""全ポジションを決済"""
open_pos = get_open_positions()
for pos in open_pos:
instrument = pos["instrument"]
long_units = int(pos["long"]["units"])
short_units = int(pos["short"]["units"])
if long_units > 0:
r = positions.PositionClose(
accountID=ACCOUNT_ID,
instrument=instrument,
data={"longUnits": "ALL"}
)
client.request(r)
print(f"{instrument}: 買いポジション決済")
if short_units < 0:
r = positions.PositionClose(
accountID=ACCOUNT_ID,
instrument=instrument,
data={"shortUnits": "ALL"}
)
client.request(r)
print(f"{instrument}: 売りポジション決済")
デモで試してみた感想
最初にデモ口座でこのコードを動かしたとき、「本当に注文が入った!」って感動した。OANDAのダッシュボードを開いたら確かにドル円の買いポジションが入っていて、ちょっと「自分はすごいものを作ってしまった」感があった(笑)。
ただ実際に動かすには課題もある。接続が切れたときの再接続処理、同じシグナルで二重注文しないための状態管理、エラーハンドリングなど。特にAPI制限(1秒あたりのリクエスト数)には最初に引っかかった。
まとめ
OANDA REST APIとPythonを組み合わせれば、育児中でも寝ている間にドル円を自動売買できる環境が作れる。デモ口座で十分に動作を確認してから本番に移行するのが鉄則。
個人的には、今はまだデモで稼働させながらロジックを改善中。スプレッドのコストを考慮したリアルな損益計算も追加したいし、次は「ドル円が重要指標の前後どう動くか」をPythonで分析してみたいと思ってる。

