DuckDB と Narwhal API を使うと、祝日、発表日、経済データ、コモディティ、為替、予測市場の確率を SQL で照会できます。小さな Python 関数がAPIキーを使って JSON を取得し、レスポンスをファイルとして保存し、DuckDB がそれを読み込んでテーブルにします。
セットアップする内容
DuckDB は read_json でローカルの JSON を読み込みます。この構成では、データ領域ごとにテーブルを持つ narwhal.duckdb データベースができます。API リクエストとレスポンスファイルの書き出しは Python が担当し、SQL がそのファイルを読みます。完全版の例には、祝日と発表日の件数、CPI の観測値、Brent の価格、為替レート、連邦準備制度の政策確率を調べるクエリが含まれます。Narwhal は 1 つのアカウントと 1 つの APIキーでこれらの API を提供し、REST と MCP が同じキーを共有します。
APIキーを設定
- 無料キーを作成する narwhalapi.com/signup で無料のキーを作成します。例に出てくるすべてのデータ領域で、同じ Narwhal のAPIキーを使います。リクエスト用のヘルパーは、そのキーを
AuthorizationヘッダーにBearer YOUR_KEYとして付けて送ります。 - キーを環境変数に保存する Python ファイル用に、
NARWHAL_API_KEYという環境変数を設定します。Python のセットアップはos.environでこれを読み、fetchが使うヘッダーに入れるため、キーが SQL の文面に現れることはありません。 - Python パッケージをインストールする Python ファイル用に
duckdbとrequestsをインストールします。例ではどちらのパッケージもインポートし、requestsで各レスポンスを取得し、duckdb.connectでデータベースを開きます。pip install duckdb requests
コードでデータを取得する
Python の例をファイルに保存し、環境で NARWHAL_API_KEY が使える状態で実行します。fetch を呼ぶたびに名前付きの JSON ファイルが書き出され、続く SQL がそのファイルを対応するテーブルに読み込みます。ヘルパーは https://api.narwhalapi.com に GET リクエストを送り、レスポンスを書き込む前に raise_for_status を呼びます。最後のクエリは、同じ DuckDB 接続で作成済みのテーブルを読み取ります。特定の領域だけ取得したいときは、このヘルパーと接続を使う、より短い領域別サンプルを使ってください。
import os
import duckdb
import requests
API = "https://api.narwhalapi.com"
HEADERS = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}
def fetch(name, path, **params):
r = requests.get(API + path, params=params, headers=HEADERS, timeout=10)
r.raise_for_status()
with open(f"{name}.json", "w") as file:
file.write(r.text)
con = duckdb.connect("narwhal.duckdb")
fetch("holidays", "/v1/calendars/holidays/USA/2026")
con.sql(
"CREATE OR REPLACE TABLE holidays AS "
"SELECT unnest(holidays, recursive := true) "
"FROM read_json('holidays.json')"
)
fetch(
"releases",
"/v1/economics/calendar",
countries="USA",
start_date="2026-11-01",
end_date="2026-11-30",
limit=100,
)
con.sql(
"CREATE OR REPLACE TABLE releases AS "
"SELECT unnest(releases, recursive := true) "
"FROM read_json('releases.json')"
)
fetch("cpi", "/v1/economics/USA/cpi/observations", start="2025-09")
con.sql(
"CREATE OR REPLACE TABLE cpi AS "
"SELECT unnest(observations, recursive := true) "
"FROM read_json('cpi.json')"
)
fetch("brent", "/v1/commodities/spot/BRENT/USD")
con.sql(
"CREATE OR REPLACE TABLE brent AS SELECT price, unit FROM read_json('brent.json')"
)
fetch("fx", "/v1/fx/rates/USD", currencies="EUR,IDR")
con.sql(
"CREATE OR REPLACE TABLE fx AS "
"SELECT unnest(rates, recursive := true) "
"FROM read_json('fx.json')"
)
fetch("fed", "/v1/predictions/policy-paths/federal-reserve")
con.sql(
"CREATE OR REPLACE TABLE fed AS "
"SELECT unnest(meetings, recursive := true) "
"FROM read_json('fed.json')"
)
print(
con.sql("SELECT count(*) FROM holidays").fetchall(),
con.sql("SELECT count(*) FROM releases").fetchall(),
)
print(con.sql("SELECT period, value FROM cpi ORDER BY period DESC LIMIT 2").fetchall())
print(
con.sql("SELECT * FROM brent").fetchall(),
con.sql("SELECT currency, rate FROM fx").fetchall(),
)
print(
con.sql(
"SELECT p_hold, expected_change_bps FROM fed ORDER BY resolves_at LIMIT 1"
).fetchall()
)スケジュールで実行する
取得を繰り返すには、Python ファイルを cron から実行します。リクエストヘッダーは環境変数からキーを読むため、スケジュール実行される Python ファイルで NARWHAL_API_KEY が使えるようにしてください。各呼び出しはレスポンスを取得し直し、DuckDB が読む前に JSON ファイルを書き出します。CREATE OR REPLACE TABLE により、実行を繰り返しても結果は同じになります。同じ名前のテーブルが、それらのファイルから読んだデータで置き換わるためです。スケジュール実行する場合は、fetch の呼び出しとテーブル作成文を同じ Python ファイルにまとめておいてください。
DuckDB で祝日を調べる
2026年のアメリカの祝日カレンダーを取得し、その holidays フィールドを holidays テーブルに読み込みます。サンプルは date と name を選ぶので、SQL の結果には祝日の日付と名前が並びます。2026年10月24日時点では、このレスポンスに祝日が 11 件含まれていました。祝日の一覧ではなく件数が欲しい場合は、完全版の Python の例にこのテーブルの件数を数えるクエリもあります。
fetch("holidays", "/v1/calendars/holidays/USA/2026")
con.sql(
"CREATE OR REPLACE TABLE holidays AS "
"SELECT unnest(holidays, recursive := true) "
"FROM read_json('holidays.json')"
)
print(con.sql("SELECT date, name FROM holidays").fetchall())DuckDB で発表日を調べる
開始日、終了日、件数の上限を指定して、2026年11月のアメリカの経済カレンダーを取得します。レスポンスの releases フィールドには at、period、topic、status が入っており、このサンプルはテーブルから期間、トピック、ステータスの列を読み取ります。2026年10月24日時点では、レスポンスに発表が 31 件含まれていました。ここで扱うのは経済指標の発表日です。期間ごとの CPI の値は、次のサンプルの観測値リクエストで取得します。
fetch(
"releases",
"/v1/economics/calendar",
countries="USA",
start_date="2026-11-01",
end_date="2026-11-30",
limit=100,
)
con.sql(
"CREATE OR REPLACE TABLE releases AS "
"SELECT unnest(releases, recursive := true) "
"FROM read_json('releases.json')"
)
print(con.sql("SELECT period, topic, status FROM releases").fetchall())DuckDB で経済データを調べる
start を 2025-09 にして、アメリカの CPI 観測値を取得します。JSON のフィールド名は observations で、できあがる cpi テーブルには period と value の列があり、クエリがそれを選びます。2026年10月24日時点では、2026-09 の CPI の値は 336.940 でした。サンプルは観測値を期間の降順に並べて件数を絞り、完全版の Python の例と同じクエリを使います。
fetch("cpi", "/v1/economics/USA/cpi/observations", start="2025-09")
con.sql(
"CREATE OR REPLACE TABLE cpi AS "
"SELECT unnest(observations, recursive := true) "
"FROM read_json('cpi.json')"
)
print(con.sql("SELECT period, value FROM cpi ORDER BY period DESC LIMIT 2").fetchall())DuckDB でコモディティを調べる
USD 建ての Brent スポット価格を取得し、保存した JSON から price と unit を直接読み取ります。このテーブル作成文は、リストを展開せずにそれらのフィールドを選びます。2026年10月24日時点では、Brent は 1 バレル 116.8809 ドルでした。サンプルは SELECT * で brent テーブルを読むため、Python に返る結果には価格と単位の両方が含まれます。
fetch("brent", "/v1/commodities/spot/BRENT/USD")
con.sql(
"CREATE OR REPLACE TABLE brent AS SELECT price, unit FROM read_json('brent.json')"
)
print(con.sql("SELECT * FROM brent").fetchall())DuckDB で為替を調べる
基準通貨に USD を使い、currencies パラメーターに EUR と IDR を指定します。レスポンスの rates フィールドを fx テーブルに読み込み、currency と rate をあわせて選びます。2026年10月24日時点では、USD/EUR は 0.89271、USD/IDR は 18107.8 でした。これらのレートは文字列として保存されます。クエリは、行を Python に読み戻すときに、各通貨とそのレートを対にして保ちます。
fetch("fx", "/v1/fx/rates/USD", currencies="EUR,IDR")
con.sql(
"CREATE OR REPLACE TABLE fx AS "
"SELECT unnest(rates, recursive := true) "
"FROM read_json('fx.json')"
)
print(con.sql("SELECT currency, rate FROM fx").fetchall())DuckDB で予測市場を調べる
レスポンスの meetings フィールドから連邦準備制度の政策パスを読み取り、fed テーブルに入れます。2026年10月24日時点で、2026年10月28日の会合の据え置き確率は 0.9188、予想される変動幅は 1.7 ベーシスポイントでした。クエリは resolves_at で並べ、その会合の p_hold と expected_change_bps を読みます。返される数値列は Python に渡されます。
fetch("fed", "/v1/predictions/policy-paths/federal-reserve")
con.sql(
"CREATE OR REPLACE TABLE fed AS "
"SELECT unnest(meetings, recursive := true) "
"FROM read_json('fed.json')"
)
print(
con.sql(
"SELECT p_hold, expected_change_bps FROM fed ORDER BY resolves_at LIMIT 1"
).fetchall()
)リスト型のフィールドに unnest を使うのはなぜですか?
リストは 1 つの JSON フィールドの中に入っています。unnest(list_column, recursive := true) を使うと項目ごとに 1 行になるため、祝日、発表、観測値、レート、会合のリストが、それぞれのテーブルの行になります。unnest の中にはレスポンスのフィールド名を使います。たとえば CPI のレスポンスでは、保存したファイルとテーブルの名前が cpi でも、フィールド名は observations を使います。
Python でタイムスタンプを選ぶにはどうすればよいですか?
Python で at や resolves_at のタイムスタンプを選ぶ前に、pytz をインストールしてください。必要に応じて、SQL 側でタイムスタンプをキャストすることもできます。予測市場のサンプルは SQL 内で resolves_at で並べ、数値列を Python に返します。発表日のサンプルは period、topic、status を選びます。
公開。