
本ページにはプロモーションが含まれています。
PythonでFX自動売買を行うには、エントリー判定に使用するローソク足データを、常に最新の状態へ更新しておく必要があります。
しかし、初めてPythonとGMO FX APIを使ってローソク足を更新する場合は、
- 既存CSVの続きから最新データを取得するにはどうすればいいのか
- 15分足・1時間足・4時間足・日足をまとめて更新できるのか
- 取得済みのローソク足が重複して保存されないのか
- 毎回すべての過去データを取得し直す必要があるのか
といった疑問を持つ方も多いのではないでしょうか。
そこで本記事では、PythonとGMO FX APIを使って、作成済みのローソク足CSVへ不足している最新データを追加する方法を紹介します。
15分足・1時間足・4時間足・日足をまとめて更新できるPythonのサンプルコードも掲載しているので、ぜひ最後までご覧ください!
サンプルコード
↓ ↓ ↓ クリックすると開けます ↓ ↓ ↓
import csv
import json
import ssl
import time
from datetime import datetime, timedelta, timezone
from urllib.parse import urlencode
from urllib.request import Request, urlopen
import certifi
import backtest_config as strategy
# 更新するローソク足の時間足です。
# 1時間足以下は日単位、4時間足以上は年単位でGMO APIから取得します。
CANDLE_INTERVALS = ("15min", "1hour", "4hour", "1day")
# CSVに出力する列です。
CSV_COLUMNS = ["timestamp_jst", "open", "high", "low", "close"]
# GMO FXのPublic APIの接続先です。
PUBLIC_URL = "https://forex-api.coin.z.com/public"
# certifiが持っている証明書一覧を使って、HTTPS通信の証明書を検証します。
# Windows環境でCERTIFICATE_VERIFY_FAILEDになる場合の対策です。
SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
# GMO APIの時刻はUTCのミリ秒で返るため、CSVにはJSTへ変換して保存します。
JST = timezone(timedelta(hours=9), name="JST")
def public_get(path, params=None):
"""GMO FXのPublic APIへGETリクエストを送る。"""
# APIに渡すパラメータがある場合は、URL用のクエリ文字列へ変換します。
query = f"?{urlencode(params)}" if params else ""
# Public APIの接続先、APIごとのパス、クエリ文字列をつないでURLを作ります。
request = Request(PUBLIC_URL + path + query)
# GMO APIへアクセスして、返ってきたJSON文字列をPythonの辞書に変換します。
# 20秒以内に応答がない場合は、待ち続けずタイムアウトにします。
with urlopen(request, context=SSL_CONTEXT, timeout=20) as response:
payload = json.loads(response.read().decode("utf-8"))
# GMO APIのstatusが0以外なら、エラーとして処理を止めます。
if int(payload.get("status", -1)) != 0:
raise RuntimeError(f"GMO FX APIエラー: {payload.get('messages')}")
# payload["data"]に、APIから取得したデータが入っています。
return payload.get("data")
def fetch_klines(symbol, price_type, interval, date_key):
"""指定した日付のローソク足を取得し、CSV用の形式へ変換する。"""
# GMO APIへ連続でアクセスしすぎないように、1秒待ってから呼び出します。
time.sleep(1.0)
# 通貨ペア、価格種別、時間足、日付を指定してローソク足を取得します。
# date_keyは、1時間足以下では"20260102"、4時間足以上では"2026"の形式です。
rows = public_get(
"/v1/klines",
{
"symbol": symbol,
"priceType": price_type,
"interval": interval,
"date": date_key,
},
)
# APIのレスポンスから、CSVに保存するローソク足をこのリストへまとめます。
result = []
for row in rows or []:
# openTimeはUTCのミリ秒なので、日時へ変換してから日本時間へ直します。
timestamp = datetime.fromtimestamp(
int(row["openTime"]) / 1000, tz=timezone.utc
).astimezone(JST)
# CSVに必要な開始日時と四本値だけを取り出します。
result.append(
{
"timestamp_jst": timestamp.isoformat(timespec="seconds"),
"open": row["open"],
"high": row["high"],
"low": row["low"],
"close": row["close"],
}
)
# CSVへ追加できる形式に変換したローソク足を返します。
return result
def read_csv(path):
"""既存のローソク足CSVを読み込む。"""
# 初回実行などでCSVがまだ存在しない場合は、空のデータとして扱います。
if not path.exists():
return []
# utf-8-sigを指定し、BOM付き・BOMなしのどちらのCSVも読み込めるようにします。
with path.open("r", encoding="utf-8-sig", newline="") as file:
return list(csv.DictReader(file))
def update_candle_csvs(now):
"""作成済みCSVへ、最新のローソク足を重複なく追加する。"""
# GMOの1時間足以下の日付キーは、JST午前6時に切り替わります。
# 午前0時から6時までは前日をAPI上の基準日として扱います。
business_date = (now - timedelta(hours=6)).date()
print("ローソク足CSVの差分更新を開始します。")
# CANDLE_INTERVALSで指定した時間足を1つずつ更新します。
for interval in CANDLE_INTERVALS:
# 時間足に対応するCSVの保存先をbacktest_config.pyから取得します。
path = strategy.CANDLE_CSV_PATHS[interval]
# 保存済みのローソク足を読み込み、新しく取得した足を後から追加します。
rows = read_csv(path)
print(f"[{interval}] のデータ取得中...")
if interval in ("15min", "1hour"):
# 1時間足以下は日ごとに取得するため、CSVの最終日時を取得開始日にします。
# CSVが空の場合は、現在の基準日から取得します。
start_date = (
datetime.fromisoformat(rows[-1]["timestamp_jst"]).date()
if rows
else business_date
)
# 日付境界付近の足を取りこぼさないよう、前日から再取得します。
start_date = min(start_date, business_date) - timedelta(days=1)
# APIへ渡す日付を"20260102"のようなYYYYMMDD形式で1日ずつ作ります。
date_keys = [
(start_date + timedelta(days=offset)).strftime("%Y%m%d")
for offset in range((business_date - start_date).days + 1)
]
else:
# 4時間足以上は年ごとに取得するため、CSVの最終日時から開始年を決めます。
# CSVが空の場合は、現在の基準年だけを取得します。
start_year = (
datetime.fromisoformat(rows[-1]["timestamp_jst"]).year
if rows
else business_date.year
)
# APIへ渡す日付を"2026"のようなYYYY形式で1年ずつ作ります。
date_keys = [
str(year) for year in range(start_year, business_date.year + 1)
]
# 作成した日付または年を順番にAPIへ渡し、ローソク足を取得します。
for date_key in date_keys:
fetched_rows = fetch_klines(
strategy.SYMBOL,
strategy.PRICE_TYPE,
interval,
date_key,
)
# 既存のローソク足へ、今回取得したローソク足を追加します。
rows.extend(fetched_rows)
# 再取得した同じ開始時刻の足を1本にまとめます。
# 同じ時刻が複数ある場合は、後からAPIで取得した新しい内容を残します。
unique_rows = {row["timestamp_jst"]: row for row in rows}
# CSVが古い足から新しい足の順になるよう、開始日時で並べ替えます。
rows = sorted(unique_rows.values(), key=lambda row: row["timestamp_jst"])
# 保存先フォルダが存在しない場合は、親フォルダも含めて作成します。
path.parent.mkdir(parents=True, exist_ok=True)
# 更新後の全ローソク足でCSVを上書きします。
with path.open("w", encoding="utf-8", newline="") as file:
writer = csv.DictWriter(file, fieldnames=CSV_COLUMNS)
# 1行目へ列名を書き、その後へローソク足をまとめて書き込みます。
writer.writeheader()
writer.writerows(rows)
print("ローソク足CSVの差分更新が完了しました。")
def main():
# ローソク足の日付判定を日本時間で行うため、現在時刻をJSTで取得します。
now = datetime.now(JST)
# 設定されたすべての時間足について、CSVへ最新データを追加します。
update_candle_csvs(now)
print("15分足・1時間足・4時間足・日足のCSVを更新しました。")
# このファイルを直接実行したときだけmain()を動かします。
if __name__ == "__main__":
main()
GMO FX APIで最新ローソク足を取得する仕組み

GMO FX APIでは、認証不要のPublic APIを利用して、外国為替FXのローソク足データを取得可能です。
取得時には、通貨ペア、BID・ASKの価格種別、15分足や1時間足などの時間足、対象の日付を指定します。
APIからは、ローソク足の開始時刻と始値・高値・安値・終値が返されます。
Pythonから定期的にAPIへアクセスし、取得済みのデータと組み合わせることで、最新のローソク足を継続的に保存が可能です。
今回作成するPythonスクリプトの処理内容

今回のスクリプト(candle_updater.py)では、作成済みのローソク足CSVを読み込み、最後に保存されている日時を確認します。
その日時を基準に、GMO FX APIから不足している期間のローソク足データを取得し、既存CSVへ追加します。
15分足・1時間足は日付単位、4時間足・日足は年単位で取得対象を判定可能です。
また、日付の切り替わり付近でデータを取りこぼさないよう、15分足と1時間足では最終日より前の日付から再取得します。
取得したローソク足は既存データと結合し、開始時刻が同じデータを1本にまとめます。
同じ時刻のデータが複数ある場合は、あとからGMO FX APIで取得した内容を残す仕組みです。
最後に、すべてのローソク足を日時順に並べ直し、15分足・1時間足・4時間足・日足のCSVを上書き保存します。
これにより、既存データを残したまま、不足している最新のローソク足を追加が可能です。
最新ローソク足を既存CSVへ追加する3ステップ

ここからは、GMO FX APIから最新のローソク足を取得し、作成済みのCSVへ追加する手順を解説します。
今回のスクリプトを実行するには、事前に以下の2つが必要です。
- 15分足・1時間足・4時間足・日足のローソク足CSV
- 通貨ペア・価格種別・CSV保存先を設定した
backtest_config.py
まだ準備できていない場合は、以下の記事を参考に用意してください。
1. ローソク足CSVの作成方法
2. backtest_config.pyの作成方法
なお、今回はバックテストを実行する必要はありません。
バックテストの記事で作成したbacktest_config.pyを用意し、通貨ペア・価格種別・各時間足のCSV保存先が正しいことを確認すれば準備完了です。
準備ができたら、次の3ステップで進めます。
candle_updater.pyを用意する- Pythonスクリプトを実行する
- 更新されたCSVを確認する
それでは、1つずつ詳しく見ていきましょう。
ステップ1:candle_updater.pyを用意する
まずは、GMO FX APIからローソク足を取得し、既存CSVへ追加するcandle_updater.pyを用意します。
backtest_config.pyと同じフォルダに、candle_updater.pyという名前で新しいファイルを作成し、今回紹介するサンプルコードを貼り付けて保存してください。
サンプルコード
↓ ↓ ↓ クリックすると開けます ↓ ↓ ↓
import csv
import json
import ssl
import time
from datetime import datetime, timedelta, timezone
from urllib.parse import urlencode
from urllib.request import Request, urlopen
import certifi
import backtest_config as strategy
# 更新するローソク足の時間足です。
# 1時間足以下は日単位、4時間足以上は年単位でGMO APIから取得します。
CANDLE_INTERVALS = ("15min", "1hour", "4hour", "1day")
# CSVに出力する列です。
CSV_COLUMNS = ["timestamp_jst", "open", "high", "low", "close"]
# GMO FXのPublic APIの接続先です。
PUBLIC_URL = "https://forex-api.coin.z.com/public"
# certifiが持っている証明書一覧を使って、HTTPS通信の証明書を検証します。
# Windows環境でCERTIFICATE_VERIFY_FAILEDになる場合の対策です。
SSL_CONTEXT = ssl.create_default_context(cafile=certifi.where())
# GMO APIの時刻はUTCのミリ秒で返るため、CSVにはJSTへ変換して保存します。
JST = timezone(timedelta(hours=9), name="JST")
def public_get(path, params=None):
"""GMO FXのPublic APIへGETリクエストを送る。"""
# APIに渡すパラメータがある場合は、URL用のクエリ文字列へ変換します。
query = f"?{urlencode(params)}" if params else ""
# Public APIの接続先、APIごとのパス、クエリ文字列をつないでURLを作ります。
request = Request(PUBLIC_URL + path + query)
# GMO APIへアクセスして、返ってきたJSON文字列をPythonの辞書に変換します。
# 20秒以内に応答がない場合は、待ち続けずタイムアウトにします。
with urlopen(request, context=SSL_CONTEXT, timeout=20) as response:
payload = json.loads(response.read().decode("utf-8"))
# GMO APIのstatusが0以外なら、エラーとして処理を止めます。
if int(payload.get("status", -1)) != 0:
raise RuntimeError(f"GMO FX APIエラー: {payload.get('messages')}")
# payload["data"]に、APIから取得したデータが入っています。
return payload.get("data")
def fetch_klines(symbol, price_type, interval, date_key):
"""指定した日付のローソク足を取得し、CSV用の形式へ変換する。"""
# GMO APIへ連続でアクセスしすぎないように、1秒待ってから呼び出します。
time.sleep(1.0)
# 通貨ペア、価格種別、時間足、日付を指定してローソク足を取得します。
# date_keyは、1時間足以下では"20260102"、4時間足以上では"2026"の形式です。
rows = public_get(
"/v1/klines",
{
"symbol": symbol,
"priceType": price_type,
"interval": interval,
"date": date_key,
},
)
# APIのレスポンスから、CSVに保存するローソク足をこのリストへまとめます。
result = []
for row in rows or []:
# openTimeはUTCのミリ秒なので、日時へ変換してから日本時間へ直します。
timestamp = datetime.fromtimestamp(
int(row["openTime"]) / 1000, tz=timezone.utc
).astimezone(JST)
# CSVに必要な開始日時と四本値だけを取り出します。
result.append(
{
"timestamp_jst": timestamp.isoformat(timespec="seconds"),
"open": row["open"],
"high": row["high"],
"low": row["low"],
"close": row["close"],
}
)
# CSVへ追加できる形式に変換したローソク足を返します。
return result
def read_csv(path):
"""既存のローソク足CSVを読み込む。"""
# 初回実行などでCSVがまだ存在しない場合は、空のデータとして扱います。
if not path.exists():
return []
# utf-8-sigを指定し、BOM付き・BOMなしのどちらのCSVも読み込めるようにします。
with path.open("r", encoding="utf-8-sig", newline="") as file:
return list(csv.DictReader(file))
def update_candle_csvs(now):
"""作成済みCSVへ、最新のローソク足を重複なく追加する。"""
# GMOの1時間足以下の日付キーは、JST午前6時に切り替わります。
# 午前0時から6時までは前日をAPI上の基準日として扱います。
business_date = (now - timedelta(hours=6)).date()
print("ローソク足CSVの差分更新を開始します。")
# CANDLE_INTERVALSで指定した時間足を1つずつ更新します。
for interval in CANDLE_INTERVALS:
# 時間足に対応するCSVの保存先をbacktest_config.pyから取得します。
path = strategy.CANDLE_CSV_PATHS[interval]
# 保存済みのローソク足を読み込み、新しく取得した足を後から追加します。
rows = read_csv(path)
print(f"[{interval}] のデータ取得中...")
if interval in ("15min", "1hour"):
# 1時間足以下は日ごとに取得するため、CSVの最終日時を取得開始日にします。
# CSVが空の場合は、現在の基準日から取得します。
start_date = (
datetime.fromisoformat(rows[-1]["timestamp_jst"]).date()
if rows
else business_date
)
# 日付境界付近の足を取りこぼさないよう、前日から再取得します。
start_date = min(start_date, business_date) - timedelta(days=1)
# APIへ渡す日付を"20260102"のようなYYYYMMDD形式で1日ずつ作ります。
date_keys = [
(start_date + timedelta(days=offset)).strftime("%Y%m%d")
for offset in range((business_date - start_date).days + 1)
]
else:
# 4時間足以上は年ごとに取得するため、CSVの最終日時から開始年を決めます。
# CSVが空の場合は、現在の基準年だけを取得します。
start_year = (
datetime.fromisoformat(rows[-1]["timestamp_jst"]).year
if rows
else business_date.year
)
# APIへ渡す日付を"2026"のようなYYYY形式で1年ずつ作ります。
date_keys = [
str(year) for year in range(start_year, business_date.year + 1)
]
# 作成した日付または年を順番にAPIへ渡し、ローソク足を取得します。
for date_key in date_keys:
fetched_rows = fetch_klines(
strategy.SYMBOL,
strategy.PRICE_TYPE,
interval,
date_key,
)
# 既存のローソク足へ、今回取得したローソク足を追加します。
rows.extend(fetched_rows)
# 再取得した同じ開始時刻の足を1本にまとめます。
# 同じ時刻が複数ある場合は、後からAPIで取得した新しい内容を残します。
unique_rows = {row["timestamp_jst"]: row for row in rows}
# CSVが古い足から新しい足の順になるよう、開始日時で並べ替えます。
rows = sorted(unique_rows.values(), key=lambda row: row["timestamp_jst"])
# 保存先フォルダが存在しない場合は、親フォルダも含めて作成します。
path.parent.mkdir(parents=True, exist_ok=True)
# 更新後の全ローソク足でCSVを上書きします。
with path.open("w", encoding="utf-8", newline="") as file:
writer = csv.DictWriter(file, fieldnames=CSV_COLUMNS)
# 1行目へ列名を書き、その後へローソク足をまとめて書き込みます。
writer.writeheader()
writer.writerows(rows)
print("ローソク足CSVの差分更新が完了しました。")
def main():
# ローソク足の日付判定を日本時間で行うため、現在時刻をJSTで取得します。
now = datetime.now(JST)
# 設定されたすべての時間足について、CSVへ最新データを追加します。
update_candle_csvs(now)
print("15分足・1時間足・4時間足・日足のCSVを更新しました。")
# このファイルを直接実行したときだけmain()を動かします。
if __name__ == "__main__":
main()
主なファイル構成は、以下のとおりです。
プロジェクトフォルダ
├─ candle_updater.py
├─ backtest_config.py
└─ data
└─ candles
└─ USD_JPY
├─ 15min_ASK.csv
├─ 1hour_ASK.csv
├─ 4hour_ASK.csv
└─ 1day_ASK.csv
candle_updater.pyでは、次の時間足を更新します。
CANDLE_INTERVALS = ("15min", "1hour", "4hour", "1day")
更新する時間足を変更したい場合は、CANDLE_INTERVALSの内容を変更してください。
ファイルを保存したら、candle_updater.pyとbacktest_config.pyが同じフォルダにあることを確認しましょう。
ステップ2:Pythonスクリプトを実行する
必要なファイルを用意したら、PowerShellやコマンドプロンプトを開き、candle_updater.pyがあるフォルダへ移動します。
cd プロジェクトフォルダのパス
続いて、以下のコマンドを実行してください。
python candle_updater.py
スクリプトを実行すると、15分足・1時間足・4時間足・日足の順番で、既存CSVへ不足しているローソク足が追加されます。
正常に実行された場合は、以下のような内容が表示されます。

ローソク足CSVの差分更新を開始します。
[15min] のデータ取得中...
[1hour] のデータ取得中...
[4hour] のデータ取得中...
[1day] のデータ取得中...
ローソク足CSVの差分更新が完了しました。
15分足・1時間足・4時間足・日足のCSVを更新しました。
GMO FX APIへ連続でアクセスしすぎないよう、リクエストごとに1秒待機します。
そのため、取得する日付や年が多い場合は、処理の完了まで少し時間がかかることがあります。
エラーが表示された場合は、backtest_config.pyの通貨ペア・価格種別・CSV保存先と、実際に用意したCSVが一致しているか確認してください。
ステップ3:更新されたCSVを確認する
スクリプトの実行が完了したら、backtest_config.pyで指定した保存先のCSVを開きます。
CSVの最後の行を確認し、実行前より新しい日時のローソク足が追加されていれば更新完了です。
timestamp_jst,open,high,low,close
2026-08-06T18:30:00+09:00,147.120,147.180,147.090,147.160
2026-08-06T18:45:00+09:00,147.160,147.210,147.130,147.190
今回のスクリプトでは、既存データと新しく取得したデータを結合したあと、timestamp_jstを基準に重複を削除します。
そのため、既存CSVと重なる期間のローソク足を再取得しても、同じ開始時刻のデータが複数保存されることはありません。
15分足・1時間足・4時間足・日足のすべてについて、最新日時まで正常に更新されているか確認しましょう。
最新ローソク足をCSVへ追加する際の注意点

今回のスクリプトは、更新後のデータで既存CSVを上書きします。
万が一に備えて、初回実行前には元のCSVをコピーしておくと安心です。
また、既存CSVにはtimestamp_jst、open、high、low、closeの列が必要です。
列名やデータ形式が異なる場合は、読み込みや日時の判定でエラーになる可能性があります。
実行前には、backtest_config.pyの通貨ペア・価格種別・CSV保存先が、用意したCSVと一致しているか確認してください。
なお、今回のサンプルには、通信エラーが発生した場合の再試行処理や、取得したローソク足が確定済みかを判定する処理は含まれていません。
まとめ

今回は、PythonとGMO FX APIを使って、作成済みのローソク足CSVへ不足している最新データを追加する方法を解説しました。
今回作成したスクリプトでは、既存CSVの最終日時を確認し、GMO FX APIから不足期間のローソク足を取得可能です。
取得したデータは既存データと結合し、重複を削除したうえで日時順に並べ直し、15分足・1時間足・4時間足・日足のCSVを更新します。
これにより、毎回すべてのローソク足を取得し直さなくても、作成済みのCSVを最新の状態に保てるようになるのです。
次のステップでは、更新したローソク足CSVとバックテストで作成したコンフィグを使い、最新の足でエントリー条件を判定する処理を実装します。

