)
Python量化投资实战用夏普比率评估你的股票组合表现附完整代码在量化投资领域评估投资组合的表现远不止看收益率这么简单。真正专业的投资者会问我的超额收益是用多大风险换来的这就是夏普比率Sharpe Ratio要回答的核心问题。本文将带你用Python从零构建一个完整的股票组合评估系统不仅会教你计算夏普比率的核心公式更重要的是展示如何在实际投资决策中应用这一指标。1. 夏普比率的核心逻辑与Python实现基础夏普比率由诺贝尔经济学奖得主威廉·夏普William Sharpe于1966年提出它的核心思想是每承担一单位风险你能获得多少超额收益。这个看似简单的概念在实际应用中却有许多需要注意的细节。1.1 理解公式背后的投资逻辑标准夏普比率公式为Sharpe Ratio (E(Rp) - Rf) / σp其中E(Rp)投资组合的预期收益率Rf无风险利率通常用国债收益率σp投资组合收益率的标准差风险度量在Python中实现这个公式前我们需要明确几个关键点收益率计算周期日收益率、周收益率还是年收益率无风险利率的选择美国10年期国债中国国债如何匹配投资期限年化处理不同时间周期的收益率如何统一比较提示在实际操作中大多数专业机构使用年化夏普比率进行比较这需要根据数据频率进行适当转换。1.2 Python基础实现让我们先用最基础的Python代码实现夏普比率计算import numpy as np def simple_sharpe_ratio(returns, risk_free_rate0.03, periods252): 基础版夏普比率计算 :param returns: 日收益率序列 :param risk_free_rate: 年化无风险利率 :param periods: 年化周期日252周52月12 :return: 年化夏普比率 excess_returns returns - risk_free_rate/periods return np.sqrt(periods) * np.mean(excess_returns) / np.std(excess_returns)这个简单版本已经可以工作但真实的量化投资环境要复杂得多。接下来我们会逐步完善它。2. 专业级夏普比率计算框架在实际量化投资中我们需要考虑更多因素才能得到可靠的夏普比率评估。以下是专业投资者关注的五个关键点收益率计算方式对数收益率还是简单收益率无风险利率选择如何获取实时无风险利率数据空头头寸处理策略是否允许做空数据频率影响高频数据与低频数据的差异极端事件调整黑天鹅事件对结果的影响2.1 改进的Python实现下面是一个更专业的实现版本import numpy as np import pandas as pd from scipy import stats def professional_sharpe_ratio(returns, risk_free_rate0.03, periods252, log_returnsFalse, adjustment_factor1.0): 专业版夏普比率计算 :param returns: 收益率序列 :param risk_free_rate: 年化无风险利率 :param periods: 年化周期 :param log_returns: 是否为对数收益率 :param adjustment_factor: 极端事件调整因子 :return: 调整后的年化夏普比率 if log_returns: excess_returns returns - np.log(1 risk_free_rate)/periods else: excess_returns returns - risk_free_rate/periods sharpe np.sqrt(periods) * np.mean(excess_returns) / np.std(excess_returns) # 极端值调整 kurtosis stats.kurtosis(returns, fisherFalse) if kurtosis 3.5: # 高峰度分布调整 adjustment 1 - 0.2*(kurtosis-3.5) sharpe * adjustment * adjustment_factor return sharpe2.2 关键参数对比下表展示了不同参数设置对夏普比率计算结果的影响参数常见取值影响程度适用场景无风险利率2%-5%中等根据投资货币和期限选择年化周期252(日),52(周),12(月)高必须与数据频率匹配收益率类型简单/对数低高频交易建议用对数调整因子0.8-1.2视情况市场波动剧烈时重要3. 实战评估真实股票组合表现现在让我们把这些理论应用到真实的股票组合评估中。我们将使用yfinance库获取真实市场数据构建一个包含5只科技股的组合并评估其历史表现。3.1 数据获取与预处理首先安装并导入必要库!pip install yfinance pandas numpy matplotlib import yfinance as yf import pandas as pd import numpy as np import matplotlib.pyplot as plt获取苹果(AAPL)、微软(MSFT)、亚马逊(AMZN)、谷歌(GOOGL)和特斯拉(TSLA)的历史数据tickers [AAPL, MSFT, AMZN, GOOGL, TSLA] start_date 2020-01-01 end_date 2023-12-31 # 下载收盘价数据 data yf.download(tickers, startstart_date, endend_date)[Adj Close]3.2 组合构建与收益率计算假设我们采用等权重组合# 计算日收益率 returns data.pct_change().dropna() # 等权重组合 weights np.array([0.2, 0.2, 0.2, 0.2, 0.2]) portfolio_returns (returns * weights).sum(axis1)可视化组合收益率分布plt.figure(figsize(12, 6)) plt.hist(portfolio_returns, bins50, alpha0.75) plt.title(Portfolio Daily Returns Distribution) plt.xlabel(Daily Return) plt.ylabel(Frequency) plt.show()3.3 完整评估流程现在我们可以综合评估这个组合的表现def full_portfolio_evaluation(returns, risk_free0.03, periods252): # 计算夏普比率 sharpe professional_sharpe_ratio(returns, risk_free, periods) # 计算最大回撤 cumulative (1 returns).cumprod() peak cumulative.expanding(min_periods1).max() drawdown (cumulative/peak - 1).min() # 计算年化收益率 annual_return (1 returns.mean())**periods - 1 return { Sharpe Ratio: sharpe, Max Drawdown: drawdown, Annual Return: annual_return, Volatility: returns.std() * np.sqrt(periods) } results full_portfolio_evaluation(portfolio_returns) print(pd.DataFrame.from_dict(results, orientindex, columns[Value]))典型输出结果可能类似于Value Sharpe Ratio 1.25 Max Drawdown -0.32 Annual Return 0.18 Volatility 0.244. 高级话题夏普比率的局限与改进虽然夏普比率是业界最常用的风险调整后收益指标但它并非完美。了解这些局限性对专业投资者至关重要。4.1 主要局限性正态分布假设夏普比率假设收益率服从正态分布但实际市场常有肥尾仅考虑波动率把上涨和下跌风险同等看待时间依赖性不同时间周期计算的结果可能差异很大4.2 改进方案针对这些局限业界发展出了一些改进指标Sortino比率只考虑下行风险def sortino_ratio(returns, risk_free0.03, periods252): excess returns - risk_free/periods downside excess[excess 0].std() return np.sqrt(periods) * excess.mean() / downsideCalmar比率使用最大回撤作为风险度量def calmar_ratio(returns, periods252): cumulative (1 returns).cumprod() peak cumulative.expanding().max() drawdown (cumulative/peak - 1).min() annual_return (1 returns.mean())**periods - 1 return annual_return / abs(drawdown)Omega比率考虑整个收益分布def omega_ratio(returns, threshold0): excess returns - threshold up excess[excess 0].sum() down -excess[excess 0].sum() return up / down if down ! 0 else np.nan4.3 多指标综合评估框架专业投资者通常会同时考察多个指标def comprehensive_evaluation(returns, risk_free0.03, periods252): metrics { Sharpe: professional_sharpe_ratio(returns, risk_free, periods), Sortino: sortino_ratio(returns, risk_free, periods), Calmar: calmar_ratio(returns, periods), Omega: omega_ratio(returns, risk_free/periods), Win Rate: (returns 0).mean() } return pd.DataFrame.from_dict(metrics, orientindex, columns[Value])5. 完整代码实现与实战技巧现在让我们把所有内容整合成一个完整的、可直接复用的Python类。5.1 PortfolioAnalyzer类实现import numpy as np import pandas as pd from scipy import stats import matplotlib.pyplot as plt class PortfolioAnalyzer: def __init__(self, prices, weightsNone, risk_free0.03): :param prices: DataFrame of asset prices :param weights: Portfolio weights (None for equal weight) :param risk_free: Annual risk-free rate self.prices prices self.weights np.ones(len(prices.columns))/len(prices.columns) if weights is None else weights self.risk_free risk_free self.returns self._calculate_returns() def _calculate_returns(self): return self.prices.pct_change().dropna() property def portfolio_returns(self): return (self.returns * self.weights).sum(axis1) def sharpe_ratio(self, periods252, adjustedTrue): excess self.portfolio_returns - self.risk_free/periods sharpe np.sqrt(periods) * excess.mean() / excess.std() if adjusted: kurtosis stats.kurtosis(self.portfolio_returns, fisherFalse) if kurtosis 3.5: sharpe * 1 - 0.2*(kurtosis-3.5) return sharpe def plot_performance(self): cumulative (1 self.portfolio_returns).cumprod() peak cumulative.expanding().max() drawdown (cumulative/peak - 1) fig, (ax1, ax2) plt.subplots(2, 1, figsize(12, 8)) cumulative.plot(axax1, titleCumulative Returns) ax1.set_ylabel(Growth of $1) drawdown.plot(axax2, titleDrawdown, colorred) ax2.set_ylabel(Drawdown) ax2.axhline(0, colorblack, linestyle--) plt.tight_layout() return fig def full_report(self): report { Annual Return: (1 self.portfolio_returns.mean())**252 - 1, Annual Volatility: self.portfolio_returns.std() * np.sqrt(252), Sharpe Ratio: self.sharpe_ratio(), Max Drawdown: (1 self.portfolio_returns).cumprod().div( (1 self.portfolio_returns).cumprod().expanding().max()).sub(1).min(), Win Rate: (self.portfolio_returns 0).mean(), Best Month: self.portfolio_returns.resample(M).apply( lambda x: (1 x).prod() - 1).max(), Worst Month: self.portfolio_returns.resample(M).apply( lambda x: (1 x).prod() - 1).min() } return pd.DataFrame.from_dict(report, orientindex, columns[Value])5.2 使用示例# 获取数据 tickers [AAPL, MSFT, AMZN, GOOGL, TSLA] data yf.download(tickers, start2020-01-01, end2023-12-31)[Adj Close] # 自定义权重 custom_weights np.array([0.3, 0.25, 0.2, 0.15, 0.1]) # 重仓苹果和微软 # 分析组合 analyzer PortfolioAnalyzer(data, weightscustom_weights) print(analyzer.full_report()) # 可视化表现 fig analyzer.plot_performance() plt.show()5.3 实战技巧与注意事项权重优化可以使用夏普比率作为目标函数优化组合权重from scipy.optimize import minimize def negative_sharpe(weights, returns, risk_free, periods): port_returns (returns * weights).sum(axis1) excess port_returns - risk_free/periods return -np.sqrt(periods) * excess.mean() / excess.std() # 约束条件权重和为1 constraints ({type: eq, fun: lambda w: np.sum(w) - 1}) bounds [(0, 1) for _ in range(len(tickers))] # 不允许做空 # 初始猜测等权重 init_guess np.ones(len(tickers))/len(tickers) # 优化 opt minimize(negative_sharpe, init_guess, args(returns, 0.03, 252), methodSLSQP, boundsbounds, constraintsconstraints) optimal_weights opt.x回测周期选择不同市场周期牛市、熊市、震荡市的结果差异很大建议测试多个周期交易成本考虑实际夏普比率会比理论值低因为没考虑交易费用数据频率陷阱高频数据计算的夏普比率通常被高估因为没考虑流动性限制参数敏感性测试改变无风险利率或年化周期观察结果稳定性