交易中的数学:夏普(Sharpe)和索蒂诺(Sortino)比率
投资回报率是投资者和萌新交易员用来分析交易绩效的最明显指标。 专业交易者会采用更可靠的工具来分析策略,比如夏普(Sharpe)比率和索蒂诺(Sortino)比率等。 在这篇文章中,我们研究简单的例子来理解这些比率是如何计算的。 交易策略的评估细节在前文中曾进行了讨论“交易中的数学: 如何评估交易结果"。 建议您阅读这篇文章,从而刷新认知、或学习新知识。赫兹股票期量化软件
编辑搜图
请点击输入图片描述(最多18字)
夏普比率
经验丰富的投资者和交易者经常运用多种策略进行交易,投资不同的资产,以此获得持久的结果。 这是智能投资的概念之一,意味着创建投资组合。 每个证券/策略组合都有自己的风险和回报参数,能以某种方式进行比较。赫兹股票期量化软件
进行这种比较的最具参考价值的工具之一就是夏普比率,它是由诺贝尔奖获得者威廉·F·夏普于 1966 年开发的。 该比率的计算采用基本绩效指标,包括平均回报率、回报标准差和无风险回报。赫兹股票期量化软件
夏普比率的缺点是,用于分析的源数据必须呈正态分布。 换言之,收益分布图应该是对称的,不应该有尖峰或陡坑。
夏普比率使用以下公式计算:
Sharpe Ratio = (Return - RiskFree)/Std
其中:
Return — 某一时段的平均回报率。 例如,月度、季度、年度、等等。
RiskFree — 同期无风险回报率。 传统上,这些资产包括银行存款、债券和其它 100% 可靠的最低风险资产。
Std — 同期投资组合回报的标准偏差。 收益偏离预期值越大,交易员账户或投资组合资产的风险和波动性就越高。赫兹股票期量化软件
回报
回报率是依据一定时段内资产价值的变化来计算的。 返回值用于计算夏普比率的同一时间段。 一般来讲,会考虑年度夏普比率,但计算也可以依据季度、月度、甚至每日的数值。 回报率由以下公式计算:
Return[i] = (Close[i]-Close[i-1])/Close[i-1]
其中:
Return[i] — 间隔 i 的回报;
Close[i] — 第 i 个区间结束时的资产价值;
Close[i-1] — 上一个间隔结束时的资产价值。
换言之,回报可以写为所选期间资产价值的相对变化:
Return[i] = Delta[i]/Previous
其中:
Delta[i] = (Close[i]-Close[i-1]) — 所选期间资产价值的绝对变化;
Previous = Close[i-1] — 上一个间隔结束时的资产价值。赫兹股票期量化软件
若要依据日线数值计算年度的夏普比率,我们应该采用年内每天的回报值,并取回报累积除以天数计算平均日回报。
Return = Sum(Return[i])/N
其中 N 是天数。
无风险回报
无风险回报的概念是有条件的,因为风险总会存在。 由于夏普比率用于比较相同时间间隔内的不同策略/投资组合,因此可以在公式中选取零无风险回报。 就是,
RiskFree = 0
标准偏差或回报率
标准偏差表示随机变量如何偏离平均值。 首先,计算平均回报值,然后累计距均值的回报偏差。 结果之和除以回报数字以便获得离散度。 离散度的平方根是标准偏差。
D = Sum((Return - Return[i])^2 )/N
STD = SQRT(D)
前面提及的文章中提供了计算标准偏差的示例。
计算任何时间段的夏普比率,并将其转换为年度值
自 1966 年以来,夏普比率的计算方法一直不曾改变。 这种计算方法被广泛认可后,该变量改为更现代的名称。 在那时,资金和投资组合绩效评估是基于若干年来赚取的回报。 此后,依据月度数据进行计算,而由此产赫兹股票期量化软件生的夏普比率会被映射到年度值。 这种方法可以比较两种资金、投资组合或策略。
夏普比率能够轻易地从不同时期和时间帧扩展到年度值。 这是依据将结果值乘以年度间隔与当前间隔之比的平方根来实现的。 我们来研究下面的例子。
假设我们采用每日回报值计算夏普比率 — SharpeDaily。 结果应转换为年度值 SharpeAnnual。 年度比率与周期比率的平方根成正比,即一年当中相应的每日间隔数量。 鉴于一年当中有 252 个交易日,基于每日回报的夏普比率应乘以 252 的平方根。 这将得到年度夏普比率:赫兹股票期量化软件
SharpeAnnual = SQRT(252)*SharpeDaily // 252 working days in a year
如果该值是基于 H1 时间帧计算的,我们要采用相同的原则 — 首先将 SharpeHourly 转换为 SharpeDaily,然后计算年度 Sharpe 比率。 一根 D1 柱线包括 24 根 H1 柱线,因此公式如下:
SharpeDaily = SQRT(24)*SharpeHourly // 24 hours fit into D1
并非所有金融产品都是 24 小时交易的。 但在测试人员对同一金融产品的交易策略进行评估时,这一点并不重要,因为比较是针对相同的测试间隔和时间帧进行的。
依据夏普比率评估策略
依据策略/投资组合的绩效,夏普比率可得到不分数值,甚至是负值。 将夏普比率转换为年度值可由经典方式进行解释:分值
含义 说明 夏普比率 < 0糟糕这样的策略无利可图 0 < 夏普比率 < 1.0
未定义
风险没有得到足够回报。 在没有其它选择的情况下,可以考虑这种策略
夏普比率 ≥ 1.0
良好
如果夏普比率大于 1,可能意味着风险得到了足够回报,投资组合/策略可以显示出正面的结果
夏普比率 ≥ 3.0优秀高分值表示在每笔特定成交中遭遇亏损的概率非常低
不要忘记夏普系数是一个常规的统计变量。 它反映了回报和风险之间的比率。 因此,在分析不同的投资组合和策略时,重要的是将夏普比率与建议值相关联,或与相关值进行比较。赫兹股票期量化软件
针对 EURUSD,2020 年度的夏普比率计算
夏普比率最初用来评估通常由许多股票组成的投资组合。 股票的价值每天都在变化,投资组合的价值也随之变化。 价值和回报的变化可以在任何时间帧内进行衡量。 我们来观察 EURUSD 计算结果。
计算是在两个时间帧 H1 和 D1 上进行的。 然后,我们将结果转换为年度值,并进行比较,看看是否存在差异。 我们将选用 2020 年的柱线收盘价进行计算。
MQL5 的代码
//+------------------------------------------------------------------+//| Script program start function |//+------------------------------------------------------------------+void OnStart()
{//--- double H1_close[],D1_close[];
double h1_returns[],d1_returns[];
datetime from = D'01.01.2020';
datetime to = D'01.01.2021';
int bars = CopyClose("EURUSD",PERIOD_H1,from,to,H1_close);
if(bars == -1)
Print("CopyClose(\"EURUSD\",PERIOD_H1,01.01.2020,01.01.2021 failed. Error ",GetLastError());
else {
Print("\nCalculate the mean and standard deviation of returns on H1 bars");
Print("H1 bars=",ArraySize(H1_close));
GetReturns(H1_close,h1_returns);
double average = ArrayMean(h1_returns);
PrintFormat("H1 average=%G",average);
double std = ArrayStd(h1_returns);
PrintFormat("H1 std=%G",std);
double sharpe_H1 = average / std;
PrintFormat("H1 Sharpe=%G",sharpe_H1);
double sharpe_annual_H1 = sharpe_H1 * MathSqrt(ArraySize(h1_returns));
Print("Sharpe_annual(H1)=", sharpe_annual_H1);
}
bars = CopyClose("EURUSD",PERIOD_D1,from,to,D1_close);
if(bars == -1)
Print("CopyClose(\"EURUSD\",PERIOD_D1,01.01.2020,01.01.2021 failed. Error ",GetLastError());
else {
Print("\nCalculate the mean and standard deviation of returns on D1 bars");
Print("D1 bars=",ArraySize(D1_close));
GetReturns(D1_close,d1_returns);
double average = ArrayMean(d1_returns);
PrintFormat("D1 average=%G",average);
double std = ArrayStd(d1_returns);
PrintFormat("D1 std=%G",std);
double sharpe_D1 = average / std;
double sharpe_annual_D1 = sharpe_D1 * MathSqrt(ArraySize(d1_returns));
Print("Sharpe_annual(H1)=", sharpe_annual_D1);
}
}//+------------------------------------------------------------------+//| Fills the returns[] array of returns |//+------------------------------------------------------------------+void GetReturns(const double & values[], double & returns[])
{
int size = ArraySize(values);//--- if less than 2 values, return an empty array of returns if(size < 2)
{
ArrayResize(returns,0);
PrintFormat("%s: Error. ArraySize(values)=%d",size);
return;
}
else {
//--- fill returns in a loop ArrayResize(returns, size - 1);
double delta;
for(int i = 1; i < size; i++)
{
returns[i - 1] = 0;
if(values[i - 1] != 0)
{
delta = values[i] - values[i - 1];
returns[i - 1] = delta / values[i - 1];
}
}
}//--- }//+------------------------------------------------------------------+//| Calculates the average number of array elements |//+------------------------------------------------------------------+double ArrayMean(const double & array[])
{
int size = ArraySize(array);
if(size < 1)
{
PrintFormat("%s: Error, array is empty",__FUNCTION__);
return(0);
}
double mean = 0;
for(int i = 0; i < size; i++)
mean += array[i];
mean /= size;
return(mean);
}//+------------------------------------------------------------------+//| Calculates the standard deviation of array elements |//+------------------------------------------------------------------+double ArrayStd(const double & array[])
{
int size = ArraySize(array);
if(size < 1)
{
PrintFormat("%s: Error, array is empty",__FUNCTION__);
return(0);
}
double mean = ArrayMean(array);
double std = 0;
for(int i = 0; i < size; i++)
std += (array[i] - mean) * (array[i] - mean);
std /= size;
std = MathSqrt(std);
return(std);
} //+------------------------------------------------------------------+/*
Result
Calculate the mean and standard deviation of returns on H1 bars
H1 bars:6226
H1 average=1.44468E-05
H1 std=0.00101979
H1 Sharpe=0.0141664Sharpe_annual(H1)=1.117708053392263Calculate the mean and standard deviation of returns on D1 bars
D1 bars:260
D1 average=0.000355823
D1 std=0.00470188Sharpe_annual(H1)=1.2179005039019222*/
使用 MetaTrader 5 函数库 计算的 Python 代码
import math
from datetime import datetimeimport MetaTrader5 as mt5# display data on the MetaTrader 5 packageprint("MetaTrader5 package author: ", mt5.__author__)
print("MetaTrader5 package version: ", mt5.__version__)# import the 'pandas' module for displaying data obtained in the tabular formimport pandas as pd
pd.set_option('display.max_columns', 50) # how many columns to show
pd.set_option('display.width', 1500) # max width of the table to show# import pytz module for working with the time zoneimport pytz# establish connection to the MetaTrader 5 terminalif not mt5.initialize():
print("initialize() failed")
mt5.shutdown()# set time zone to UTCtimezone = pytz.timezone("Etc/UTC")# create datetime objects in the UTC timezone to avoid the local time zone offsetutc_from = datetime(2020, 1, 1, tzinfo=timezone)
utc_to = datetime(2020, 12, 31, hour=23, minute=59, second=59, tzinfo=timezone)# get EURUSD H1 bars in the interval 2020.01.01 00:00 - 2020.31.12 13:00 in the UTC timezonerates_H1 = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, utc_from, utc_to)# also get D1 bars in the interval 2020.01.01 00:00 - 2020.31.12 13:00 in the UTC timezonerates_D1 = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_D1, utc_from, utc_to)# shut down connection to the MetaTrader 5 terminal and continue processing obtained barsmt5.shutdown()# create DataFrame out of the obtained datarates_frame = pd.DataFrame(rates_H1)# add the "Return" columnrates_frame['return'] = 0.0# now calculate the returns as return[i] = (close[i] - close[i-1])/close[i-1]prev_close = 0.0for i, row in rates_frame.iterrows():
close = row['close']
rates_frame.at[i, 'return'] = close / prev_close - 1 if prev_close != 0.0 else 0.0 prev_close = close
print("\nCalculate the mean and standard deviation of returns on H1 bars")
print('H1 rates:', rates_frame.shape[0])
ret_average = rates_frame[1:]['return'].mean() # skip the first row with zero returnprint('H1 return average=', ret_average)
ret_std = rates_frame[1:]['return'].std(ddof=0) # skip the first row with zero returnprint('H1 return std =', ret_std)
sharpe_H1 = ret_average / ret_std
print('H1 Sharpe = Average/STD = ', sharpe_H1)
sharpe_annual_H1 = sharpe_H1 * math.sqrt(rates_H1.shape[0]-1)
print('Sharpe_annual(H1) =', sharpe_annual_H1)# now calculate the Sharpe ratio on the D1 timeframerates_daily = pd.DataFrame(rates_D1)# add the "Return" columnrates_daily['return'] = 0.0# calculate returnsprev_return = 0.0for i, row in rates_daily.iterrows():
close = row['close']
rates_daily.at[i, 'return'] = close / prev_return - 1 if prev_return != 0.0 else 0.0 prev_return = close
print("\nCalculate the mean and standard deviation of returns on D1 bars")
print('D1 rates:', rates_daily.shape[0])
daily_average = rates_daily[1:]['return'].mean()
print('D1 return average=', daily_average)
daily_std = rates_daily[1:]['return'].std(ddof=0)
print('D1 return std =', daily_std)
sharpe_daily = daily_average / daily_std
print('D1 Sharpe =', sharpe_daily)
sharpe_annual_D1 = sharpe_daily * math.sqrt(rates_daily.shape[0]-1)
print('Sharpe_annual(D1) =', sharpe_annual_D1)
Result
Calculate the mean and standard deviation of returns on H1 bars
H1 rates: 6226
H1 return average= 1.4446773215242986e-05
H1 return std = 0.0010197932969323495
H1 Sharpe = Average/STD = 0.014166373968823358Sharpe_annual(H1) = 1.117708053392236Calculate the mean and standard deviation of returns on D1 bars
D1 rates: 260
D1 return average= 0.0003558228355051694
D1 return std = 0.004701883757646081
D1 Sharpe = 0.07567665511222807Sharpe_annual(D1) = 1.2179005039019217
如您所见,MQL5 和 Python 的计算结果是相同的。 源代码附在下面(CalculateSharpe_2TF)。赫兹股票期量化软件
依据 H1 和 D1 柱线计算的年度夏普比率的差别:分别对应 1.117708 和 1.217900。 我们来尝试找出原因。