def test_conditional_value_at_risk(self, test_cutoff): # empyrical can't tolerate NaNs here res_a = empyrical.conditional_value_at_risk(ret['a'].iloc[1:], cutoff=test_cutoff) res_b = empyrical.conditional_value_at_risk(ret['b'].iloc[1:], cutoff=test_cutoff) res_c = empyrical.conditional_value_at_risk(ret['c'].iloc[1:], cutoff=test_cutoff) assert isclose(ret['a'].vbt.returns.conditional_value_at_risk(cutoff=test_cutoff), res_a) pd.testing.assert_series_equal( ret.vbt.returns.conditional_value_at_risk(cutoff=test_cutoff), pd.Series([res_a, res_b, res_c], index=ret.columns).rename('conditional_value_at_risk') )
def calculate_statistics(self, df: DataFrame = None, output=True): """""" self.output("开始计算策略统计指标") # Check DataFrame input exterior if df is None: df = self.daily_df # Check for init DataFrame if df is None: # Set all statistics to 0 if no trade. start_date = "" end_date = "" total_days = 0 profit_days = 0 loss_days = 0 end_balance = 0 max_drawdown = 0 max_ddpercent = 0 max_drawdown_duration = 0 max_drawdown_end = 0 total_net_pnl = 0 daily_net_pnl = 0 total_commission = 0 daily_commission = 0 total_slippage = 0 daily_slippage = 0 total_turnover = 0 daily_turnover = 0 total_trade_count = 0 daily_trade_count = 0 total_return = 0 annual_return = 0 daily_return = 0 return_std = 0 sharpe_ratio = 0 sortino_info = 0 win_ratio = 0 return_drawdown_ratio = 0 tail_ratio_info = 0 stability_return = 0 win_loss_pnl_ratio = 0 pnl_medio = 0 duration_medio = 0 calmar_ratio = 0 else: # Calculate balance related time series data df["balance"] = df["net_pnl"].cumsum() + self.capital df["return"] = np.log(df["balance"] / df["balance"].shift(1)).fillna(0) df["highlevel"] = (df["balance"].rolling(min_periods=1, window=len(df), center=False).max()) df["drawdown"] = df["balance"] - df["highlevel"] df["ddpercent"] = df["drawdown"] / df["highlevel"] * 100 # Calculate statistics value start_date = df.index[0] end_date = df.index[-1] total_days = len(df) profit_days = len(df[df["net_pnl"] > 0]) loss_days = len(df[df["net_pnl"] < 0]) end_balance = df["balance"].iloc[-1] max_drawdown = df["drawdown"].min() max_ddpercent = df["ddpercent"].min() max_drawdown_end = df["drawdown"].idxmin() if isinstance(max_drawdown_end, date): max_drawdown_start = df["balance"][:max_drawdown_end].idxmax() max_drawdown_duration = (max_drawdown_end - max_drawdown_start).days else: max_drawdown_duration = 0 total_net_pnl = df["net_pnl"].sum() daily_net_pnl = total_net_pnl / total_days win = df[df["net_pnl"] > 0] win_amount = win["net_pnl"].sum() win_pnl_medio = win["net_pnl"].mean() # win_duration_medio = win["duration"].mean().total_seconds()/3600 win_count = win["trade_count"].sum() pnl_medio = df["net_pnl"].mean() # duration_medio = df["duration"].mean().total_seconds()/3600 loss = df[df["net_pnl"] < 0] loss_amount = loss["net_pnl"].sum() loss_pnl_medio = loss["net_pnl"].mean() # loss_duration_medio = loss["duration"].mean().total_seconds()/3600 total_commission = df["commission"].sum() daily_commission = total_commission / total_days total_slippage = df["slippage"].sum() daily_slippage = total_slippage / total_days total_turnover = df["turnover"].sum() daily_turnover = total_turnover / total_days total_trade_count = df["trade_count"].sum() win_ratio = (win_count / total_trade_count) * 100 win_loss_pnl_ratio = -win_pnl_medio / loss_pnl_medio daily_trade_count = total_trade_count / total_days total_return = (end_balance / self.capital - 1) * 100 annual_return = total_return / total_days * 240 daily_return = df["return"].mean() * 100 return_std = df["return"].std() * 100 if return_std: sharpe_ratio = daily_return / return_std * np.sqrt(240) else: sharpe_ratio = 0 return_drawdown_ratio = -total_return / max_ddpercent #calmar_ratio:年化收益率与历史最大回撤率之间的比率 calmar_ratio = annual_return / abs(max_ddpercent) #sortino_info sortino_info = sortino_ratio(df['return']) omega_info = omega_ratio(df['return']) #年化波动率 annual_volatility_info = annual_volatility(df['return']) #年化复合增长率 cagr_info = cagr(df['return']) #年化下行风险率 annual_downside_risk = downside_risk(df['return']) """CVaR即条件风险价值,其含义为在投资组合的损失超过某个给定VaR值的条件下,该投资组合的平均损失值。""" c_var = conditional_value_at_risk(df['return']) """风险价值(VaR)是对投资损失风险的一种度量。它估计在正常的市场条件下,在设定的时间段(例如一天)中, 一组投资可能(以给定的概率)损失多少。金融业中的公司和监管机构通常使用VaR来衡量弥补可能损失所需的资产数量""" var_info = value_at_risk(df['return']) #收益稳定率 stability_return = stability_of_timeseries(df['return']) #尾部比率0.25 == 1/4,收益1,风险4 tail_ratio_info = tail_ratio(df['return']) # Output if output: self.output("-" * 30) self.output(f"首个交易日:\t{start_date}") self.output(f"最后交易日:\t{end_date}") self.output(f"总交易日:\t{total_days}") self.output(f"盈利交易日:\t{profit_days}") self.output(f"亏损交易日:\t{loss_days}") self.output(f"起始资金:\t{self.capital:,.2f}") self.output(f"结束资金:\t{end_balance:,.2f}") self.output(f"总收益率:\t{total_return:,.2f}%") self.output(f"年化收益:\t{annual_return:,.2f}%") self.output(f"最大回撤: \t{max_drawdown:,.2f}") self.output(f"百分比最大回撤: {max_ddpercent:,.2f}%") self.output(f"最长回撤天数: \t{max_drawdown_duration}") self.output(f"总盈亏:\t{total_net_pnl:,.2f}") self.output(f"总手续费:\t{total_commission:,.2f}") self.output(f"总滑点:\t{total_slippage:,.2f}") self.output(f"总成交金额:\t{total_turnover:,.2f}") self.output(f"总成交笔数:\t{total_trade_count}") self.output(f"日均盈亏:\t{daily_net_pnl:,.2f}") self.output(f"日均手续费:\t{daily_commission:,.2f}") self.output(f"日均滑点:\t{daily_slippage:,.2f}") self.output(f"日均成交金额:\t{daily_turnover:,.2f}") self.output(f"日均成交笔数:\t{daily_trade_count}") self.output(f"日均收益率:\t{daily_return:,.2f}%") self.output(f"收益标准差:\t{return_std:,.2f}%") self.output(f"胜率:\t{win_ratio:,.2f}") self.output(f"盈亏比:\t\t{win_loss_pnl_ratio:,.2f}") self.output(f"平均每笔盈亏:\t{pnl_medio:,.2f}") self.output(f"calmar_ratio:\t{calmar_ratio:,.3f}") # self.output(f"平均持仓小时:\t{duration_medio:,.2f}") self.output(f"Sharpe Ratio:\t{sharpe_ratio:,.2f}") self.output(f"sortino Ratio:\t{sortino_info:,.3f}") self.output(f"收益回撤比:\t{return_drawdown_ratio:,.2f}") statistics = { "start_date": start_date, "end_date": end_date, "total_days": total_days, "profit_days": profit_days, "loss_days": loss_days, "capital": self.capital, "end_balance": end_balance, "max_drawdown": max_drawdown, "max_ddpercent": max_ddpercent, "max_drawdown_end": max_drawdown_end, "max_drawdown_duration": max_drawdown_duration, "total_net_pnl": total_net_pnl, "daily_net_pnl": daily_net_pnl, "total_commission": total_commission, "daily_commission": daily_commission, "total_slippage": total_slippage, "daily_slippage": daily_slippage, "total_turnover": total_turnover, "daily_turnover": daily_turnover, "total_trade_count": total_trade_count, "daily_trade_count": daily_trade_count, "total_return": total_return, "annual_return": annual_return, "daily_return": daily_return, "return_std": return_std, "sharpe_ratio": sharpe_ratio, 'sortino_info': sortino_info, "win_ratio": win_ratio, "return_drawdown_ratio": return_drawdown_ratio, "tail_ratio_info": tail_ratio_info, "stability_return": stability_return, "win_loss_pnl_ratio": win_loss_pnl_ratio, "pnl_medio": pnl_medio, "calmar_ratio": calmar_ratio } # Filter potential error infinite value for key, value in statistics.items(): if value in (np.inf, -np.inf): value = 0 statistics[key] = np.nan_to_num(value) self.output("策略统计指标计算完成") return statistics
def expected_shortfall(portfolio_daily_returns, probability=0.05): return ep.conditional_value_at_risk(portfolio_daily_returns, probability)