/ / /
第2讲 函数逼近与数值展开技术
🔴
入学要求
💯
能力测试
🛣️
课程安排
🕹️
研究资源

第2讲 函数逼近与数值展开技术

在金融建模和量化分析中,我们经常需要处理离散的市场数据点,并构建连续函数关系。例如,从有限的市场报价构建完整收益率曲线,或为复杂衍生品开发近似定价公式。本文作为Python数值分析系列第二篇,将介绍函数逼近和数值展开的核心技术,并通过Python实现展示其在金融中的应用。

1. 插值方法基础

1.1 什么是插值问题?

给定一组数据点 (xi,yi)(x_i, y_i),插值的目标是找到一个函数 f(x)f(x),使得 f(xi)=yif(x_i) = y_i。这个函数能用于估计未知点的函数值。

金融应用场景

1.2 拉格朗日插值法:最基础的多项式插值

拉格朗日插值法构造一个通过所有数据点的多项式。其核心公式为:

L(x)=i=0nyijixxjxixjL(x) = \sum_{i=0}^{n} y_i \prod_{j \neq i} \frac{x - x_j}{x_i - x_j}
import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate

def lagrange_interpolation(x_data, y_data, x):
    """使用拉格朗日插值法计算给定x处的函数值"""
    n = len(x_data)
    y = 0

    for i in range(n):
        # 计算拉格朗日基函数
        basis = 1
        for j in range(n):
            if i != j:
                basis *= (x - x_data[j]) / (x_data[i] - x_data[j])

        # 累加
        y += y_data[i] * basis

    return y

# 示例:短期国债收益率插值
maturities = np.array([0.25, 0.5, 1, 2, 3, 5, 7, 10])  # 期限(年)
yields = np.array([4.5, 4.6, 4.7, 4.9, 5.0, 5.1, 5.15, 5.2])  # 收益率(%)

# 创建插值函数
x_dense = np.linspace(0.25, 10, 100)
y_lagrange = np.array([lagrange_interpolation(maturities, yields, xi) for xi in x_dense])

优缺点

1.3 样条插值:平滑曲线的最佳选择

样条插值通过分段多项式代替单一高阶多项式来避免龙格现象。三次样条在每个区间使用三次多项式,并确保在连接点处一阶和二阶导数连续。

def natural_cubic_spline(x_data, y_data, x):
    """使用自然三次样条插值计算给定x处的函数值"""
    # 使用SciPy的样条插值
    spline = interpolate.CubicSpline(x_data, y_data, bc_type='natural')
    return spline(x)

# 计算三次样条插值结果
y_spline = natural_cubic_spline(maturities, yields, x_dense)

为什么金融分析师偏爱样条插值

1.4 Nelson-Siegel模型:参数化收益率曲线

Nelson-Siegel模型是一种参数化方法,特别适合收益率曲线建模:

r(t)=β0+β11eλtλt+β2(1eλtλteλt)r(t) = \beta_0 + \beta_1 \frac{1 - e^{-\lambda t}}{\lambda t} + \beta_2 \left(\frac{1 - e^{-\lambda t}}{\lambda t} - e^{-\lambda t}\right)
from scipy.optimize import minimize

def nelson_siegel(t, beta0, beta1, beta2, tau):
    """Nelson-Siegel收益率曲线模型"""
    # 处理t=0的特殊情况
    if isinstance(t, (int, float)) and t == 0:
        term2 = 1
        term3 = 0
    else:
        exp_term = np.exp(-t/tau)
        term2 = (1 - exp_term) / (t/tau)
        term3 = term2 - exp_term

    return beta0 + beta1 * term2 + beta2 * term3

def nelson_siegel_objective(params, maturities, observed_yields):
    """Nelson-Siegel模型的目标函数(残差平方和)"""
    beta0, beta1, beta2, tau = params
    predicted = np.array([nelson_siegel(t, beta0, beta1, beta2, tau) for t in maturities])
    return np.sum((predicted - observed_yields)**2)

# 估计Nelson-Siegel模型参数
initial_params = [5.0, -1.0, 1.0, 1.5]  # 初始猜测: beta0, beta1, beta2, tau
bounds = [(0, 10), (-10, 10), (-10, 10), (0.1, 10)]  # 参数范围约束

result = minimize(
    nelson_siegel_objective,
    initial_params,
    args=(maturities, yields),
    bounds=bounds,
    method='L-BFGS-B'
)

optimal_params = result.x

Nelson-Siegel模型的优势

2. 级数展开与收敛性分析

2.1 泰勒级数:函数的局部近似

泰勒级数将函数在某点附近展开为幂级数:

f(x)=f(a)+f(a)(xa)+f(a)2!(xa)2+f(a)3!(xa)3+f(x) = f(a) + f'(a)(x-a) + \frac{f''(a)}{2!}(x-a)^2 + \frac{f'''(a)}{3!}(x-a)^3 + \cdots
import sympy as sp

def taylor_approximation(func_expr, x0, n, x_var='x'):
    """计算函数在x0处的n阶泰勒多项式"""
    # 创建符号变量
    x = sp.Symbol(x_var)

    # 解析函数表达式
    func = sp.sympify(func_expr)

    # 计算泰勒级数
    taylor_poly = 0
    for i in range(n+1):
        # 计算i阶导数
        if i == 0:
            derivative = func
        else:
            derivative = sp.diff(func, x, i)

        # 在x0处求值
        derivative_at_x0 = derivative.subs(x, x0)

        # 添加到泰勒多项式
        taylor_poly += derivative_at_x0 * (x - x0)**i / sp.factorial(i)

    # 创建数值计算函数
    numerical_func = sp.lambdify(x, taylor_poly, 'numpy')

    return taylor_poly, numerical_func

金融应用

2.2 泰勒级数在期权定价中的应用

在期权定价中,泰勒级数常用于推导希腊字母和近似定价公式:

def black_scholes_call(S, K, T, r, sigma):
    """计算欧式看涨期权的Black-Scholes价格"""
    from scipy.stats import norm

    d1 = (np.log(S/K) + (r + sigma**2/2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)

    call_price = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
    return call_price

def bs_greeks(S, K, T, r, sigma):
    """计算Black-Scholes模型下的主要希腊字母"""
    from scipy.stats import norm

    d1 = (np.log(S/K) + (r + sigma**2/2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)

    # 计算希腊字母
    delta = norm.cdf(d1)
    gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T))
    theta = -S * norm.pdf(d1) * sigma / (2 * np.sqrt(T)) - r * K * np.exp(-r * T) * norm.cdf(d2)
    vega = S * np.sqrt(T) * norm.pdf(d1)
    rho = K * T * np.exp(-r * T) * norm.cdf(d2)

    return delta, gamma, theta, vega, rho

def approx_bs_call_with_greeks(S, K, T, r, sigma, dS=0, dT=0, dSigma=0):
    """使用泰勒展开和希腊字母计算期权近似价格"""
    # 基准价格
    base_price = black_scholes_call(S, K, T, r, sigma)

    # 计算希腊字母
    delta, gamma, theta, vega, rho = bs_greeks(S, K, T, r, sigma)

    # 使用泰勒展开计算价格变化
    price_change = delta * dS + 0.5 * gamma * dS**2 + theta * dT + vega * dSigma

    return base_price + price_change

实际应用

交易员可以使用这种近似方法快速评估市场参数小幅变化对期权价格的影响,而无需重新运行完整的定价模型。

2.3 傅里叶级数:周期函数的分解

傅里叶级数将周期函数表示为正弦和余弦函数的无穷级数:

f(x)=a02+n=1[ancos(nx)+bnsin(nx)]f(x) = \frac{a_0}{2} + \sum_{n=1}^{\infty} \left[ a_n \cos(nx) + b_n \sin(nx) \right]
def compute_fourier_coefficients(func, period=2*np.pi, n_terms=10):
    """计算函数的傅里叶系数"""
    L = period / 2
    x = np.linspace(-L, L, 1000)
    dx = x[1] - x[0]

    # 计算函数值
    f_values = func(x)

    # 计算a0
    a0 = np.sum(f_values) * dx / L

    # 计算系数
    a = np.zeros(n_terms + 1)
    b = np.zeros(n_terms + 1)

    a[0] = a0

    for n in range(1, n_terms + 1):
        a[n] = np.sum(f_values * np.cos(n * np.pi * x / L)) * dx / L
        b[n] = np.sum(f_values * np.sin(n * np.pi * x / L)) * dx / L

    return a, b

def fourier_series(x, a, b, period=2*np.pi):
    """使用傅里叶系数计算傅里叶级数"""
    L = period / 2
    n_terms = len(a) - 1

    # 计算傅里叶级数
    y = a[0] / 2
    for n in range(1, n_terms + 1):
        y += a[n] * np.cos(n * np.pi * x / L) + b[n] * np.sin(n * np.pi * x / L)

    return y

市场周期性分析应用

傅里叶级数特别适合分析金融时间序列中的季节性和周期性模式。例如,我们可以用它来:

3. 高级应用实例

3.1 Nelson-Siegel-Svensson模型

Nelson-Siegel-Svensson模型是Nelson-Siegel的扩展版本,增加了两个参数以提高灵活性:

def nelson_siegel_svensson(t, beta0, beta1, beta2, beta3, tau1, tau2):
    """Nelson-Siegel-Svensson收益率曲线模型"""
    # 处理t=0的特殊情况
    if isinstance(t, (int, float)) and t == 0:
        term1 = 1
        term2 = 0
        term3 = 0
    else:
        exp_term1 = np.exp(-t/tau1)
        exp_term2 = np.exp(-t/tau2)
        term1 = (1 - exp_term1) / (t/tau1)
        term2 = term1 - exp_term1
        term3 = (1 - exp_term2) / (t/tau2) - exp_term2

    return beta0 + beta1 * term1 + beta2 * term2 + beta3 * term3

实际应用:中央银行和大型金融机构常使用NSS模型来分析和预测收益率曲线。模型的六个参数能捕捉收益率曲线的各种形状,包括反常形态。

3.2 利用泰勒级数进行敏感性分析

当交易员需要快速评估市场变动对投资组合的影响时,泰勒级数是最佳工具:

# 对不同参数变化下的期权价格进行敏感性分析
S = 100  # 当前股价
K = 100  # 行权价
T = 1.0  # 到期时间(年)
r = 0.05  # 无风险利率
sigma = 0.2  # 波动率

# 计算期权希腊字母
delta, gamma, theta, vega, rho = bs_greeks(S, K, T, r, sigma)

# 分析股价变动影响
delta_S_values = np.linspace(-5, 5, 11)  # 股价变动范围
exact_prices = [black_scholes_call(S + dS, K, T, r, sigma) for dS in delta_S_values]
approx_prices = [black_scholes_call(S, K, T, r, sigma) + delta * dS + 0.5 * gamma * dS**2
                for dS in delta_S_values]

print("股价变动对期权价格的影响:")
for i, dS in enumerate(delta_S_values):
    print(f"股价变动 {dS:+.2f}:精确价格 = {exact_prices[i]:.4f}, 近似价格 = {approx_prices[i]:.4f}, "
          f"误差 = {approx_prices[i] - exact_prices[i]:.4f}")

这种分析帮助交易员理解市场变动的非线性影响,特别是当gamma较大时,线性近似可能显著偏离实际结果。

3.3 市场周期性分析

傅里叶分析可以揭示市场数据的隐藏周期模式:

# 生成模拟市场数据(带有季节性、趋势和噪声)
def seasonal_market_data(t, amplitude=1.0, trend=0.1, noise_level=0.2):
    seasonal = amplitude * np.sin(2 * np.pi * t)  # 年度季节性
    trend_component = trend * t                   # 长期趋势
    noise = noise_level * np.random.randn(len(t)) # 随机噪声
    return seasonal + trend_component + noise

# 生成5年的月度数据
t = np.linspace(0, 5, 60)
market_data = seasonal_market_data(t)

# 去除趋势
trend = np.polyfit(t, market_data, 1)
detrended_data = market_data - (trend[0] * t + trend[1])

# 分析频率成分
def data_func(x):
    idx = (x + 5) / 10 * 59
    idx = np.clip(idx, 0, 59).astype(int)
    return detrended_data[idx]

a_coefs, b_coefs = compute_fourier_coefficients(data_func, period=10, n_terms=5)

# 计算各频率的振幅
frequencies = np.arange(len(a_coefs))
amplitude = np.sqrt(a_coefs**2 + b_coefs**2)

print("主要频率成分:")
for i in range(1, len(frequencies)):
    period = 1 / frequencies[i] if frequencies[i] > 0 else float('inf')
    print(f"频率 {frequencies[i]}: 振幅 = {amplitude[i]:.4f}, 周期 = {period:.2f} 年")

这种分析可以揭示季节性效应、商业周期,甚至像"一月效应"这样的市场异常。

实用建议

插值方法选择指南

方法优势适用场景注意事项
拉格朗日插值简单直观少量数据点高次多项式不稳定
三次样条插值平滑、稳定收益率曲线构建外推性能有限
NS/NSS模型参数少,经济意义强收益率曲线建模需要优化求解

实践要点

  1. 数据质量是关键
    • 检查异常值和缺失数据
    • 考虑是否需要先去除趋势
    • 对于模型拟合,初始参数选择很重要
  1. 合理使用外推
    • 插值内部通常可靠,外推区域需谨慎
    • 参数化模型(如NS)比纯插值方法有更好的外推性能
    • 始终检查外推结果的合理性
  1. 利用导数信息
    • 收益率曲线的一阶导数代表远期利率
    • 二阶导数反映曲率,关系到凸性风险
    • 样条插值保持导数连续性,有助于这些分析
  1. 泰勒级数应用技巧
    • 展开点选择靠近评估区域
    • 对非线性强的函数,考虑更高阶近似
    • 总是评估近似误差的大小

下一步学习方向

在掌握了函数逼近和数值展开技术后,您可以进一步探索:

下一篇文章将介绍数值微积分和求根方法,这些技术是衍生品定价和金融模型校准的基础。

参考资料

  1. 《Python Programming And Numerical Methods: A Guide For Engineers And Scientists》
  1. 《Numerical Recipes: The Art of Scientific Computing》by Press et al.
  1. 《Options, Futures, and Other Derivatives》by John C. Hull
  1. 《Interest Rate Models - Theory and Practice》by Brigo and Mercurio
  1. Scipy文档: https://docs.scipy.org/doc/scipy/reference/tutorial/interpolate.html