在金融建模和量化分析中,我们经常需要处理离散的市场数据点,并构建连续函数关系。例如,从有限的市场报价构建完整收益率曲线,或为复杂衍生品开发近似定价公式。本文作为Python数值分析系列第二篇,将介绍函数逼近和数值展开的核心技术,并通过Python实现展示其在金融中的应用。
给定一组数据点 ,插值的目标是找到一个函数 ,使得 。这个函数能用于估计未知点的函数值。
金融应用场景:
拉格朗日插值法构造一个通过所有数据点的多项式。其核心公式为:
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])
优缺点:
样条插值通过分段多项式代替单一高阶多项式来避免龙格现象。三次样条在每个区间使用三次多项式,并确保在连接点处一阶和二阶导数连续。
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)
为什么金融分析师偏爱样条插值:
Nelson-Siegel模型是一种参数化方法,特别适合收益率曲线建模:
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模型的优势:
泰勒级数将函数在某点附近展开为幂级数:
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
金融应用:
在期权定价中,泰勒级数常用于推导希腊字母和近似定价公式:
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
实际应用:
交易员可以使用这种近似方法快速评估市场参数小幅变化对期权价格的影响,而无需重新运行完整的定价模型。
傅里叶级数将周期函数表示为正弦和余弦函数的无穷级数:
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
市场周期性分析应用:
傅里叶级数特别适合分析金融时间序列中的季节性和周期性模式。例如,我们可以用它来:
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模型来分析和预测收益率曲线。模型的六个参数能捕捉收益率曲线的各种形状,包括反常形态。
当交易员需要快速评估市场变动对投资组合的影响时,泰勒级数是最佳工具:
# 对不同参数变化下的期权价格进行敏感性分析
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较大时,线性近似可能显著偏离实际结果。
傅里叶分析可以揭示市场数据的隐藏周期模式:
# 生成模拟市场数据(带有季节性、趋势和噪声)
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模型 | 参数少,经济意义强 | 收益率曲线建模 | 需要优化求解 |
在掌握了函数逼近和数值展开技术后,您可以进一步探索:
下一篇文章将介绍数值微积分和求根方法,这些技术是衍生品定价和金融模型校准的基础。