使用python计算最大回撤
1. 单期简单收益率
R t = P t − P t − 1 P t − 1 R_{t}=\dfrac {P _{t}-P_{t-1}}{P_{t-1}} Rt=Pt−1Pt−Pt−1
说明:
- R t R_t Rt 为单期简单收益率
- P t P_t Pt 为t期的价格
- P t − 1 P_{t-1} Pt−1 为t-1期的价格
import datetime
import pandas as pd
pd.core.common.is_list_like = pd.api.types.is_list_like
price = pd.Series([3.42,3.51,3.68,3.43,3.56,3.67], index=[datetime.date(2015,7,x) for x in range(3,9)])
price
2015-07-03 3.42
2015-07-04 3.51
2015-07-05 3.68
2015-07-06 3.43
2015-07-07 3.56
2015-07-08 3.67
dtype: float64
利用ffn库计算单期简单收益
import ffn
r = ffn.to_returns(price)
r
2015-07-03 NaN
2015-07-04 0.026316
2015-07-05 0.048433
2015-07-06 -0.067935
2015-07-07 0.037901
2015-07-08 0.030899
dtype: float64
2. 最大回撤
最大回撤(Maximum Drawdown, MDD) 用来衡量投资(特别是基金)的表现,
2.1 回撤:某资产在时刻T的回撤是指资产在(0,T)的最高峰值与现在价值 P T P_T PT之间的回落值,用数学公式表达为:
D ( T ) = max { 0 , max t ∈ ( 0 , T ) P t − P T } D\left( T\right) =\max \left\{ 0,\max _{t\in (0,T)}P_t-P_{T}\right\} D(T)=max{ 0,maxt∈(0,T)Pt−PT}
2.2 对应的回撤率为:
d ( T ) = D ( T ) max t ∈ ( 0 , T ) p t d\left(T\right) = \dfrac {D\left( T\right) }{\max _{t\in \left( 0,T\right) }p_{t}} d(T)=maxt∈(0,T)ptD(T)
知道回撤的含义之后,最大回撤就比较容易理解了,资产在T时刻的最大回撤MDD(T),就是资产在时段(0,T)内回撤的最大值,对应的数学公式为:
M D D ( T ) = max τ ∈ ( 0 , T ) D ( τ ) = max τ ∈ ( 0 , T ) [ max t ∈ ( 0 , τ ) P t − P τ ] MDD\left(T\right) = {\max _{\tau \in \left(0,T\right)}} D\left(\tau\right) = {\max _{\tau \in \left(0,T\right)}} \begin{aligned} \left[ \max _{t\in \left( 0,\tau \right) }P_{t}-P_{\tau}\right] \end{aligned} MDD(T)=maxτ∈(0,T)D(τ)=maxτ∈(0,T)[t∈(0,τ)maxPt−Pτ]
相应的最大回撤率为:
m d d ( T ) = max τ ∈ ( 0 , T ) d ( τ ) M D D ( T ) max t ∈ ( 0 , T ) P t mdd\left(T\right) = {\max _{\tau \in \left(0,T\right)}} d\left(\tau \right) \dfrac {MDD\left(T\right)}{\max _{t \in \left(0,T\right)}P_t} mdd(T)=maxτ∈(0,T)d(τ)maxt∈(0,T)PtMDD(T)
直观的讲,MDD(T)对应的是在(0,T)时段内资产价值从最高峰回落到最低谷的幅度。最大回撤常用来描述投资者在持有资产是可能面临的最大亏损。
2.3 利用收益率计算最大回撤
如果某资产的收益率序列为R1,R2,…,RT,在初始时刻0时,我们投资1元在该资产上并一直持有到T时刻,则初始值为1元的资产价值就会随时间变化为:(1+R1),(1+R1)(1+R2),(1+R1)(1+R2)(1+R3),…, ∏ k = 1 T ( 1 + R k ) \prod _{k=1}^{T}\left( 1+R_{k}\right) ∏k=1T(1+Rk)
时刻T对应的回撤值为:
D ( T ) = max { 0 , max t ∈ ( 0 , T ) ∏ k = 1 t ( 1 + R k ) − ∏ k = 1 T ( 1 + R k ) } D(T) = \max \left\{ 0, \max _{t \in (0,T)} \prod _{k=1}^{t}\left( 1+R_{k}\right) - \prod _{k=1}^{T}\left( 1+R_{k}\right) \right\} D(T)=max{
0,maxt∈(0,T)∏k=1t(1+Rk)−∏k=1T(1+Rk)}
相应的回撤率为:
d ( T ) = D ( T ) max t ∈ ( 0 , T ) ∏ k = 1 t ( 1 + R k ) d(T) = \dfrac {D\left( T\right) }{\max _{t\in \left( 0,T\right) }\prod _{k=1}^{t}\left( 1+R_{k}\right) } d(T)=maxt∈(0,T)∏k=1t(1+Rk)D(T)
最大回撤为:
M D D ( T ) = max τ ∈ ( 0 , T ) D ( τ ) = max τ ∈ ( 0 , T ) [ max t ∈ ( 0 , τ ) ∏ k = 1 t ( 1 + R k ) − ∏ k = 1 τ ( 1 + R k ) ] MDD(T) = \max _{\tau \in \left( 0,T\right) }D\left( \tau \right) = \max _{\tau \in \left( 0,T\right) } \begin{aligned} \left[\max _{t\in \left( 0,\tau \right) }\prod _{k=1}^{t}\left( 1+R_{k}\right) - \prod _{k=1}^{\tau}\left( 1+R_{k}\right) \right]\end{aligned} MDD(T)=maxτ∈(0,T)D(τ)=maxτ∈(0,T)[t∈(0,τ)maxk=1∏t(1+Rk)−k=1∏τ(1+Rk)]
相应的最大回撤率为:
m d d ( T ) = max τ ∈ ( 0 , T ) d ( τ ) = M D D ( T ) max t ∈ ( 0 , T ) ∏ k = 1 t ( 1 + R k ) mdd(T) = \max _{\tau \in \left( 0,T\right) }d(\tau) = \dfrac {MDD(T)}{\max _{t \in \left( 0,T\right) }\prod _{k=1}^{t}\left( 1+R_{k}\right)} mdd(T)=maxτ∈(0,T)d(τ)=maxt∈(0,T)∏k=1t(1+Rk)MDD(T)
value = (1 + r).cumprod()
value
2015-07-03 NaN
2015-07-04 1.026316
2015-07-05 1.076023
2015-07-06 1.002924
2015-07-07 1.040936
2015-07-08 1.073099
dtype: float64
D = value.cummax() - value
D
2015-07-03 NaN
2015-07-04 0.000000
2015-07-05 0.000000
2015-07-06 0.073099
2015-07-07 0.035088
2015-07-08 0.002924
dtype: float64
d = D / (D + value)
d
2015-07-03 NaN
2015-07-04 0.000000
2015-07-05 0.000000
2015-07-06 0.067935
2015-07-07 0.032609
2015-07-08 0.002717
dtype: float64
MDD = D.max()
MDD
0.07309941520467844
mdd =d.max()
mdd
# 对应的最大回撤率值为
0.06793478260869572
# 采用ffn库计算收益率累积最大回撤
ffn.calc_max_drawdown(value)
-0.06793478260869568
from empyrical import max_drawdown
# 使用 empyrical 计算收益率序列最大回撤
max_drawdown(r)
-0.06793478260869572
3. Java版本的最大回测
import java.util.Arrays;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MaxDrawdownService {
/**
* 计算最大回撤:某段时间内连续收益率之和最小的值为最大回撤
*
* @Param rates:收益率
*/
@SuppressWarnings("all")
public static double caculateMaxDrawdown(List<Double> rates) {
double s = 0;
double e = 0;
double max = 0;
double temp = 0;
double ts = 0;
// 收益率不为空
if (!rates.isEmpty()) {
for (int i = 0; i < rates.size(); i++) {
// 获得收益率
double r = rates.get(i);
temp = temp + r;
if (temp > 0) {
ts = i + 1;
e = i + 1;
temp = 0;
} else {
if (temp < max) {
s = ts;
e = i;
max = temp;
}
}
}
}
log.info("最大回撤计算结果:maxsum={},start={},end={}", max, s, e);
return max;
}
/**
* 按照资金计算最大回撤
* @param equityValues
* @return
*/
public static double calMaxDrawdown(List<Double> equityValues) {
if (equityValues == null || equityValues.size() < 2)
return 0;
double maxDrawdown = 0; // 最大回撤
double maxEquityValue = equityValues.get(0); // 当日之前的最大资产净值
for (int i = 1; i < equityValues.size(); i++) {
double currentEquityValue = equityValues.get(i); // 当日资产净值
double drawDown = (1 - currentEquityValue / maxEquityValue);
maxDrawdown = Math.max(maxDrawdown, drawDown);
maxEquityValue = Math.max(currentEquityValue, maxEquityValue);
}
log.info("calMaxDrawdown最大回撤计算结果:{}", maxDrawdown);
return maxDrawdown;
}
public static void main(String[] args) {
Double[] c = {
0.026316,0.048433,-0.067935,0.037901,0.030899};
caculateMaxDrawdown(Arrays.asList(c));
Double[] c2 = {
3.42,3.51,3.68,3.43,3.56,3.67};
calMaxDrawdown(Arrays.asList(c2));
}
}