50 分钟真实数据快照已执行的网页阅读版

按明确报价单位计算汇率收益

先将 ECB 外国单位/欧元报价取倒数,再计算欧元计价价格收益和历史关联。

经济与金融 · 第 04 课🔋 离线核心分析⏱ 50 分钟内嵌真实数据📦 numpy · pandas · matplotlib · IPython

目标与运行方法

本课适合具备 Python 基础的本科生。先阅读每一步的方法与图注,再修改参数。AI 练习为可选部分,不需要 AI 账号即可完成核心课程。数据写在 notebook metadata 中,不需要另下载数据文件。请保持 notebook 已保存到当前工作文件夹。

学习任务是理解本课的研究问题、执行透明的计算、校验结果,并说明结论的边界。

环境与参数

In [1] · Python
from pathlib import Path
import base64, gzip, hashlib, json, platform
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import display, HTML
from datetime import datetime
from IPython.display import display as _display
def display(value):
    if isinstance(value,pd.DataFrame):
        table=value.to_html(max_rows=12,escape=True)
        style="<style>.vibeit-readable-table{max-width:100%;overflow-x:auto}.vibeit-readable-table table{width:max-content;max-width:none;border-collapse:collapse}.vibeit-readable-table td,.vibeit-readable-table th{white-space:nowrap!important;word-break:normal!important;overflow-wrap:normal!important}</style>"
        return _display(HTML(style+'<div class="vibeit-readable-table">'+table+'</div>'))
    return _display(value)
RECIPE_ID='ec04-fx-returns'
OUTPUT_DIR=Path.cwd()/"results"/RECIPE_ID
plt.rcParams.update({"figure.dpi":120,"font.size":10,"axes.spines.top":False,"axes.spines.right":False,
                      "axes.prop_cycle":plt.cycler(color=["#18785f","#416aa6","#bc6541","#9974a5"])})
print("Python:",platform.python_version(),"; NumPy:",np.__version__,"; Pandas:",pd.__version__)
print("Working folder:",Path.cwd())
print("Core data mode: embedded snapshot; no network requests")
Python: 3.13.13 ; NumPy: 2.5.3 ; Pandas: 3.0.6
Working folder: /Users/zhiluo/Developer/vibeit-cookbook/cookbook/downloads
Core data mode: embedded snapshot; no network requests

读取内嵌快照

In [2] · Python
def load_snapshot(recipe_id, expected_sha256, snapshot_key):
    """Read embedded data from this notebook, including a renamed copy."""
    for path in sorted(Path.cwd().glob("*.ipynb")):
        try:
            notebook = json.loads(path.read_text(encoding="utf-8"))
            metadata = notebook.get("metadata", {}).get("vibeit_cookbook", {})
            blob = metadata.get("snapshots", {}).get(snapshot_key)
            if metadata.get("recipe_id") != recipe_id or not blob:
                continue
            if blob.get("sha256") != expected_sha256:
                continue
            raw = gzip.decompress(base64.b64decode(blob["gzip_base64"], validate=True))
            if hashlib.sha256(raw).hexdigest() != expected_sha256:
                raise ValueError("Embedded snapshot checksum mismatch: " + snapshot_key)
            return json.loads(raw)
        except (OSError, json.JSONDecodeError):
            continue
    raise FileNotFoundError(
        "Save/open the complete .ipynb in the active working folder. "
        "The embedded snapshot could not be located for " + recipe_id)

SNAPSHOT_HASHES={'ecb': '7afd344000313ec2b4f6fb2548894e2a4817dc3687c14f8de47937ebf7bba0ad'}
def snapshot(key):
    return load_snapshot(RECIPE_ID,SNAPSHOT_HASHES[key],key)
print("Embedded snapshots:",list(SNAPSHOT_HASHES))
Embedded snapshots: ['ecb']

方法函数

In [3] · Python
def iso_datetimes(values):
    """Parse source ISO dates with the standard library; keep tables as ISO strings."""
    return [datetime.fromisoformat(str(value)) for value in values]

print("Method ready: iso_datetimes")
Method ready: iso_datetimes

数据与模型边界

历史价格型货币例子假设每日再平衡,不含利息/套息、费用、成交成本和税,不确定未来表现或构成投资建议。来源:ECB,原始参考汇率可在 ECB 免费取得。倒数、收益和风险统计为课程派生计算,不是 ECB 统计。

分步分析

1. 先声明报价方向

报价为外国单位/欧元,一外国单位的欧元价值为其倒数。显示时将计算价格重定为 100。

In [4] · Python
ecb=snapshot("ecb");rates=pd.DataFrame(ecb["rows"])
rates["date"]=rates["Date"].astype(str);rates=rates.set_index("date").sort_index()
currencies=ecb["currency_order"];original_dates=len(rates);rates=rates[currencies].dropna()
assert rates.index.is_unique and rates.index.is_monotonic_increasing and (rates>0).all().all()
prices=1/rates;returns=prices.pct_change(fill_method=None).iloc[1:]
print(ecb["source_notice"])
print("Quote:",ecb["quote"],"; common complete dates:",len(rates),"of",original_dates)
display(rates.head(6))
indices=100*prices/prices.iloc[0]
fig,ax=plt.subplots(figsize=(9,5))
for code in currencies:ax.plot(iso_datetimes(indices.index),indices[code],label=code)
ax.legend()
ax.set(xlabel="ECB reference date",ylabel="EUR-valued foreign-unit index (first date=100)",title="Explicit inverted quotation direction");plt.tight_layout();plt.show()
Source: European Central Bank. The original reference rates are available free from the ECB. Course returns, inversions and risk estimates are derived calculations, not ECB statistics.
Quote: Foreign currency units per 1 EUR ; common complete dates: 1538 of 1538
USD GBP JPY CNY
date
2019-01-02 1.1397 0.90165 124.28 7.8165
2019-01-03 1.1348 0.90312 122.21 7.8019
2019-01-04 1.1403 0.89988 123.20 7.8280
2019-01-07 1.1445 0.89720 123.90 7.8421
2019-01-08 1.1440 0.89743 124.46 7.8405
2019-01-09 1.1455 0.89913 124.70 7.8226
Computed figure from this code cell

2. 检查参考日收益分布

在共同观测日期用百分比表示简单变化,不将缺失日插成零收益。这是价格变化,不含利息/套息、费用及可成交报价。

In [5] · Python
fig,ax=plt.subplots(figsize=(8,5))
for code in currencies:ax.hist(100*returns[code],bins=60,alpha=.35,label=code)
ax.set(xlabel="Reference-date price return (%)",ylabel="Intervals",title="Frozen historical return distributions");ax.legend();plt.tight_layout();plt.show()
Computed figure from this code cell

3. 解读历史对数收益关联

相关描述共同日期的冻结时段,无量纲,不确定稳定的未来对冲关系。

In [6] · Python
log_returns=np.log(prices/prices.shift(1)).iloc[1:];correlation=log_returns.corr()
fig,ax=plt.subplots(figsize=(6,5));im=ax.imshow(correlation,cmap="coolwarm",vmin=-1,vmax=1)
ax.set_xticks(range(4),currencies);ax.set_yticks(range(4),currencies)
for i in range(4):
 for j in range(4):ax.text(j,i,f"{correlation.iloc[i,j]:.2f}",ha="center",va="center")
fig.colorbar(im,ax=ax,label="Historical log-return correlation");plt.tight_layout();plt.show()
Computed figure from this code cell

4. 核对倒数并导出计价

倒数价格对数变化为原报价对数变化的反号。保留原报价与派生价格,使单位约定可复现。

In [7] · Python
assert np.allclose(log_returns,-np.log(rates/rates.shift(1)).iloc[1:])
assert np.allclose(returns,np.expm1(log_returns)) and np.allclose(correlation,correlation.T)
OUTPUT_DIR.mkdir(parents=True,exist_ok=True);rates.to_csv(OUTPUT_DIR/"foreign-units-per-EUR.csv")
prices.to_csv(OUTPUT_DIR/"EUR-per-foreign-unit.csv");returns.to_csv(OUTPUT_DIR/"EUR-price-returns.csv")
print("Quotation identity and common dates verified; exports:",OUTPUT_DIR)
Quotation identity and common dates verified; exports: /Users/zhiluo/Developer/vibeit-cookbook/cookbook/downloads/results/ec04-fx-returns

自主练习与参考答案

  1. 为何取报价倒数?
  2. 缺失自然日意味着什么?

参考解释。 倒数为欧元/外国单位;共同观测区间定义收益,不插自然日零值。

先保留当前 notebook 副本,再修改参数。把新图与原图一起比较,并记录改变了哪些输入、哪些计算和哪些结论。

可选的 AI 编程练习

提示词 1

仅用 NumPy、Pandas、Matplotlib 比较原/倒数对数变化并标反号。

提示词 2

同包导出有日期的价格累计指数,不声称含利息。

人工验收。 恒等式通过,原报价、计价及免费 ECB 署名明确。

检查修改后的源代码,再手动运行受影响单元及后续校验。要求助手解释修改的目的、使用的包和数据来源,并用实际输出核对解释。

可选联网扩展

In [8] · Python
RUN_ONLINE_UPDATE=False
if RUN_ONLINE_UPDATE:
    import requests
    try:
        response=requests.get('https://data-api.ecb.europa.eu/service/data/EXR/D.USD.EUR.SP00.A?startPeriod=2024-01-01&endPeriod=2024-12-31&format=csvdata',timeout=20)
        response.raise_for_status()
        print("Source:",response.url)
        print(response.text[:700])
    except requests.RequestException as error:
        print("Online extension unavailable:",type(error).__name__,str(error)[:160])
else:
    print("Online extension skipped; embedded core data unchanged")
Online extension skipped; embedded core data unchanged

来源、快照与下一步

  • ECB SDMX euro reference exchange rates — ECB copyright / free reuse with accurate source attribution; data obtained free from ECB; derived returns explicitly labelled; retrieved 2026-10-09. SHA-256 5f39fc39f60606abafae26c00492d05388c7bdf29cce1922f173f99bd0273ce8. Retain 2019-01-01 through 2024-12-31 and USD, GBP, JPY, CNY; preserve missing values and quotation direction.

下一步。 在保留本课的数据检查、参数记录和结果校验之后,将相同方法用于自己的公开或已获授权数据。记录研究设计与方法限制,不把示例结果当成新实验的验证。完整数据许可与生成说明见 cookbook 网页。