FREDとStooqで1990年以降の為替・金利・原油・金価格データを取得してSQLiteに保存する

FREDとStooqで1990年以降の為替・金利・原油・金価格データを取得してSQLiteに保存する

為替レート、経済指標、商品相場、原油、金価格の関係性を通貨ごとに整理していくには、まず過去を含めた時系列データが必要になります。

特にLTCM危機、日本の金融危機、アジア通貨危機、ITバブル、リーマンショック、コロナショックなどを比較するには、1990年代から現在までつながったデータが必要です。

今回は、FREDとStooqを使い、USDJPY、WTI、米国金利、FF金利、金価格をSQLiteへ保存するところまで試しました。

なお、この記事ではAPIキーは掲載しません。コマンド例やエラーURLにAPIキーが含まれる場合は、すべてダミー値に置き換えています。

今回の目的

今回の目的は、危機局面の比較や相関分析に使うため、1990年以降の主要時系列データをSQLiteへ保存することです。

最終的には、以下のような分析に使う想定です。

  • USDJPYと米金利の関係
  • WTIや金価格とドルの関係
  • 1998年のLTCM期と2008年のリーマン期の比較
  • 金利・為替・商品価格のローリング相関
  • Neo4jによる関係性の可視化

なぜ1990年以降のデータが必要か

短期の相関だけを見るなら、直近数年のデータでも足ります。

しかし、金融危機やレジーム変化を分析するなら、過去の危機局面を含める必要があります。

例えば、以下のようなイベントを同じ時間軸で比較したいです。

  • 1997年:アジア通貨危機
  • 1997年〜1999年:日本の金融危機
  • 1998年:LTCM危機
  • 2000年:ITバブル崩壊
  • 2008年:リーマンショック
  • 2020年:コロナショック
  • 2022年:インフレ・急速利上げ局面

このような比較には、長期で同じ形式のデータを集められるFREDが非常に使いやすいです。

最初に取得する系列

まずは、FREDから以下の系列を取得します。

系列ID 内容 用途
DEXJPUS USD/JPY 為替レート
DCOILWTICO WTI原油 商品価格・インフレ要因
DGS10 米10年国債利回り 長期金利
DGS2 米2年国債利回り 短期金利市場
FEDFUNDS FF金利(月次) 政策金利レジーム
DFF Daily Effective Federal Funds Rate 日次の短期金利・流動性分析

金価格については、FREDの金価格系列が取得できなかったため、Stooqを使う方針にしました。

SQLiteの保存テーブル

SQLiteには、まず最小構成として以下のテーブルを作ります。

CREATE TABLE IF NOT EXISTS series_observations (
    series_id TEXT NOT NULL,
    date TEXT NOT NULL,
    value REAL,
    PRIMARY KEY (series_id, date)
);

series_iddate を主キーにすることで、同じ系列・同じ日付のデータは重複しないようにします。

FRED APIキーの扱い

FRED APIを使うにはAPIキーが必要です。

APIキーはコードに直接書かず、環境変数から読み込みます。

export FRED_API_KEY="YOUR_FRED_API_KEY_HERE"

ここで注意点があります。

コピー&ペースト時に、半角のダブルクォートではなく全角の引用符が入ると、シェルが入力待ち状態になることがあります。

例えば、以下のような状態です。

dquote>

この場合は Ctrl + C で中断し、半角の " で入力し直します。

FREDからSQLiteへ保存するスクリプト

まず、FREDからデータを取得してSQLiteへ保存するスクリプトを作成します。

vim fetch_fred_to_sqlite.py

基本形は以下です。

import os
import sqlite3
import requests

FRED_API_KEY = os.environ["FRED_API_KEY"]
BASE = "https://api.stlouisfed.org/fred/series/observations"

SERIES = [
    "DEXJPUS",     # USD/JPY
    "DCOILWTICO",  # WTI
    "DGS10",       # US 10Y
    "DGS2",        # US 2Y
    "FEDFUNDS",    # Fed Funds Monthly
    "DFF",         # Daily Effective Federal Funds Rate
]

DB_PATH = "market.db"

def init_db(conn: sqlite3.Connection) -> None:
    conn.execute("""
    CREATE TABLE IF NOT EXISTS series_observations (
        series_id TEXT NOT NULL,
        date TEXT NOT NULL,
        value REAL,
        PRIMARY KEY (series_id, date)
    );
    """)
    conn.commit()

def fetch_fred_series(series_id: str, observation_start: str = "1990-01-01"):
    params = {
        "series_id": series_id,
        "api_key": FRED_API_KEY,
        "file_type": "json",
        "observation_start": observation_start,
    }

    r = requests.get(BASE, params=params, timeout=30)
    r.raise_for_status()

    data = r.json()
    return data["observations"]

def upsert_observations(conn: sqlite3.Connection, series_id: str, observations) -> int:
    rows = []

    for o in observations:
        d = o["date"]
        v = o["value"]

        if v == ".":
            val = None
        else:
            try:
                val = float(v)
            except ValueError:
                val = None

        rows.append((series_id, d, val))

    cur = conn.executemany("""
        INSERT OR REPLACE INTO series_observations(series_id, date, value)
        VALUES (?, ?, ?);
    """, rows)

    conn.commit()
    return cur.rowcount

def main():
    with sqlite3.connect(DB_PATH) as conn:
        init_db(conn)

        for sid in SERIES:
            obs = fetch_fred_series(sid, observation_start="1990-01-01")
            n = upsert_observations(conn, sid, obs)
            print(f"[{sid}] upserted: {n} rows")

if __name__ == "__main__":
    main()

FREDの金価格系列でエラー

最初は、金価格として以下のFRED系列を使おうとしました。

GOLDAMGBD228NLBM

しかし、実行すると以下のようなエラーになりました。

requests.exceptions.HTTPError: 400 Client Error: Bad Request

APIキーや通信が原因ではなく、FRED側で指定したseries_idが存在しない状態でした。

実際にseries APIで確認すると、以下のような応答になりました。

{"error_code":400,"error_message":"Bad Request. The series does not exist."}

つまり、GOLDAMGBD228NLBM はFRED上で取得できない系列になっていました。

金価格はStooqから取得する方針に変更

FREDで金価格系列を取得できなかったため、金価格はStooqから取得する方針にしました。

Stooqでは、日次データをCSV形式で取得できます。


https://stooq.com/q/d/l/?s=xauusd&i=d

ただし、現時点ではStooqのCSV取得でAPIキーが必要になる場合があります。

そのため、この記事のコードは「Stooqから取得したCSVをSQLiteへ保存する考え方」として残しますが、APIキーの取得方法、URLへの付与方法、環境変数での管理方法は別記事で扱う予定です。

APIキーを使う場合は、記事やGitHubに直接書かず、.env や環境変数で管理します。

Stooq APIキーの扱い

StooqのCSV取得でAPIキーが必要な場合は、以下のように環境変数へ設定して使います。

export STOOQ_API_KEY="YOUR_STOOQ_API_KEY_HERE"

APIキーはコードやブログ記事に直接書かず、環境変数や .env で管理します。

この記事では、APIキーの取得手順そのものは扱いません。後日、StooqのAPIキー取得とCSV取得の手順を別記事としてまとめる予定です。

Stooqから金価格を取得する関数

以下は、Stooqから日次CSVを取得し、終値を取り出す基本形です。

現時点では、StooqのCSV取得にAPIキーが必要になる場合があります。その場合は、URLに apikey を付ける必要があります。APIキー取得と設定方法は別記事でまとめます。

import csv
import io
import os
import requests

def fetch_stooq_daily(symbol: str):
    # symbol例: "xauusd"
    # 現在はapikeyが必要になる場合があるため、環境変数から読む形にしておく
    apikey = os.environ.get("STOOQ_API_KEY")

    url = f"https://stooq.com/q/d/l/?s={symbol}&i=d"

    if apikey:
        url += f"&apikey={apikey}"

    r = requests.get(
        url,
        headers={"User-Agent": "Mozilla/5.0"},
        timeout=30
    )
    r.raise_for_status()

    # CSV: Date,Open,High,Low,Close,Volume
    f = io.StringIO(r.text)
    reader = csv.DictReader(f)

    out = []

    for row in reader:
        d = row["Date"]
        close = row.get("Close")

        if close in (None, "", "null"):
            val = None
        else:
            try:
                val = float(close)
            except ValueError:
                val = None

        out.append((d, val))

    return out

def upsert_simple_series(conn: sqlite3.Connection, series_id: str, date_value_list) -> int:
    rows = [(series_id, d, v) for d, v in date_value_list]

    cur = conn.executemany("""
        INSERT OR REPLACE INTO series_observations(series_id, date, value)
        VALUES (?, ?, ?);
    """, rows)

    conn.commit()
    return cur.rowcount

main() のFRED取得後に、以下を追加します。

gold = fetch_stooq_daily("xauusd")
n = upsert_simple_series(conn, "XAUUSD_STOOQ", gold)
print(f"[XAUUSD_STOOQ] upserted: {n} rows (Gold from Stooq)")

これで、FREDから取る系列とStooqから取る金価格を、同じテーブルに保存できます。

なお、StooqのAPIキーが必要な環境では、事前に STOOQ_API_KEY を設定しておきます。

取得結果

FREDから取得できる系列をSQLiteへ保存し、金価格についてはStooqから取得したCSVを同じテーブルへ保存する構成にしました。

python fetch_fred_to_sqlite.py

実行結果は以下です。

[DEXJPUS] upserted: 9420 rows
[DCOILWTICO] upserted: 9421 rows
[DGS10] upserted: 9423 rows
[DGS2] upserted: 9423 rows
[FEDFUNDS] upserted: 433 rows
[DFF] upserted: 13191 rows
[XAUUSD_STOOQ] upserted: 15181 rows (Gold from Stooq)

ただし、Stooqからの取得は現時点ではAPIキーが必要になる場合があります。そのため、StooqのAPIキー取得・設定方法については別記事で詳しくまとめます。

SQLiteで保存件数を確認する

SQLiteで系列ごとの件数を確認します。

sqlite3 market.db 'select series_id, count(*) from series_observations group by series_id order by series_id;'

結果は以下です。

DCOILWTICO|9421
DEXJPUS|9420
DFF|13191
DGS10|9423
DGS2|9423
FEDFUNDS|433
XAUUSD_STOOQ|15181

XAUUSD_STOOQ が入っているので、金価格も保存できています。

ただし、StooqのAPIキーが必要な場合、APIキー未設定のままだと取得に失敗する可能性があります。

FEDFUNDSの件数が少ない理由

FEDFUNDS は433行しかありません。

最初は少ないように見えますが、これは月次データなので正常です。

期間を確認します。

sqlite3 market.db "
SELECT MIN(date), MAX(date), COUNT(*)
FROM series_observations
WHERE series_id='FEDFUNDS';"

結果は以下です。

1990-01-01|2026-01-01|433

1990年から2026年まで、月1レコードで入っているため、件数としては自然です。

DFFを追加した理由

相関分析や危機時の流動性を見るには、月次の FEDFUNDS だけでは粗すぎます。

そこで、日次のFF金利である DFF を追加しました。

DFFの期間を確認します。

sqlite3 market.db "
SELECT MIN(date), MAX(date), COUNT(*)
FROM series_observations
WHERE series_id='DFF';"

結果は以下です。

1990-01-01|2026-02-11|13191

1990年以降の日次データとして、ほぼ全期間取得できています。

FEDFUNDSとDFFの使い分け

系列 頻度 用途
FEDFUNDS 月次 政策金利レジームの確認
DFF 日次 短期金利・流動性・危機局面分析
DGS2 日次 短期金利市場
DGS10 日次 長期金利市場

危機比較や相関分析では、主に DFFDGS2DGS10 を使い、FEDFUNDS は政策レジーム確認用として扱うのがよさそうです。

日付整合はまだDBに書き込まない

系列ごとに休日やデータ頻度が違うため、相関計算前に日付を揃える必要があります。

ただし、現時点では元データを加工してDB上で無理に揃える必要はありません。

理由は、価格データ、金利データ、月次指標で扱いが違うからです。

  • 価格データは基本的に補完しない
  • 金利データは必要に応じて前方補完してもよい
  • 月次データはレジーム判定やイベント系列として扱う

したがって、元データはそのまま保存し、相関計算時にPandasやSQLで日付を揃える方針にします。

相関計算前にreturnsを作る

価格や金利の相関を計算する場合、価格水準そのものではなく、リターンや変化量を使う方が安全です。

価格そのものの相関を見ると、トレンドによる偽相関が出やすくなります。

そのため、次にやるべきことは returns テーブルの生成です。

流れとしては以下です。

  1. 各系列ごとに日付順で値を並べる
  2. 前日との差分または対数リターンを計算する
  3. series_returns のようなテーブルへ保存する
  4. 相関計算時に系列同士を日付でinner joinする

今回の到達点

今回の作業で、以下まで完了しました。

  • FREDからUSDJPY、WTI、米10年金利、米2年金利、FEDFUNDSを取得した
  • FREDの金価格系列が取得できないことを確認した
  • 金価格はStooqのXAUUSD日次CSVから取得する方針にした
  • StooqのCSV取得はAPIキーが必要になる場合があるため、APIキー対応できる形のコードにした
  • 日次FF金利として DFF を追加した
  • すべて market.dbseries_observations に保存した
  • FEDFUNDSが月次、DFFが日次であることを確認した
  • 次はreturns生成に進む方針を決めた

ハマりどころ

FREDのseries_idは存在確認が必要

FREDで以前使われていたseries_idが、現在も使えるとは限りません。

存在しないseries_idを指定すると、以下のようなエラーになります。

Bad Request. The series does not exist.

series APIで存在確認するか、候補を複数持っておくと安全です。

APIキーをログに残さない

FRED APIのエラーURLには、APIキーが含まれる場合があります。

ブログやGitHubにログを貼るときは、必ず以下のようにダミー化します。

api_key=YOUR_FRED_API_KEY_HERE

StooqのAPIキーについても同様です。

STOOQ_API_KEY=YOUR_STOOQ_API_KEY_HERE

金価格はFREDだけに依存しない

今回、FREDの金価格系列は取得できませんでした。

そのため、金価格はStooqなど別ソースも使えるようにしておく方がよさそうです。

ただし、StooqのCSV取得はAPIキーが必要になる場合があるため、APIキーの取得・設定・安全な管理方法は別記事に分けて整理します。

FEDFUNDSは月次データ

FEDFUNDS は月次データです。

日次の短期金利分析には、DFF を使う方が適しています。

次にやること

次は、SQLiteに保存した時系列からreturnsを生成します。

  • series_returns テーブルを作成する
  • 価格系は対数リターンを計算する
  • 金利系は差分を計算する
  • 1998年のLTCM期を切り出して相関を見る
  • その後、2008年リーマン期と比較する

まずは元データを壊さず、returnsや相関は別テーブルとして作成していきます。

StooqのAPIキー取得については別記事にする

この記事では、Stooqから金価格CSVを取得してSQLiteに保存する考え方とコードだけを残しました。

現時点では、StooqのCSV取得でAPIキーが必要になる場合があります。

そのため、以下は別記事で扱う予定です。

  • StooqのAPIキー取得方法
  • CSVダウンロードURLへの apikey 付与方法
  • STOOQ_API_KEY を環境変数で管理する方法
  • .env から読み込む方法
  • APIキーをログやGitHubに残さない運用

コード自体は、APIキー対応すれば今後も役立つ可能性があるため、この記事にも基本形として残しておきます。

まとめ

今回は、FREDとStooqを使って1990年以降のUSDJPY、WTI、米国金利、FF金利、金価格をSQLiteへ保存しました。

FREDの金価格系列は取得できなかったため、金価格はStooqのXAUUSD日次CSVから取得する構成にしました。

ただし、現時点ではStooqのCSV取得にAPIキーが必要になる場合があります。この記事では取得コードの考え方を残し、APIキーの取得・設定・安全な管理方法は別記事で扱う予定です。

また、月次の FEDFUNDS だけでなく、日次の DFF も追加することで、短期金利や流動性の変化をより細かく見られるようになりました。

次は、これらの時系列からreturnsを生成し、LTCM期やリーマン期の相関構造を比較できるようにしていきます。

コメント

タイトルとURLをコピーしました