动态规划计算最大回撤

1,965 阅读3分钟

动态规划计算最大回撤

定义

回撤用来衡量基金的抗风险能力。

最大回撤定义

是指在某一段时期内产品净值从最高点开始回落到最低点的幅度。

最大回撤率,不一定是(最高点净值-最低点净值)/最高点时的净值,也许它会出现在其中某一段的回落。

公式可以这样表达:

D为某一天的净值,i为某一天,j为i后的某一天,Di为第i天的产品净值,Dj则是Di后面某一天的净值

drawdown就是最大回撤率

drawdown=max((Di-Dj)/Di)= max(1- Dj/Di),其实就是对每一个净值进行回撤率求值,然后找出最大的。 这里的净值一般是复权净值,因为基金分红后净值会下跌,单纯的用单位净值计算会出现不准的情况。

计算

定义数据结构,包含回撤区间和最大回撤值

    public static class MaxWithdrawal {
        /**
         * 最大回撤的数据列表
         */
        private List<String> list;
        /**
         * 最大回撤的值
         */
        private String maxValue;
​
        public List<String> getList() {
            return list;
        }
​
        public String getMaxValue() {
            return maxValue;
        }
    }

动态规划方式,时间复杂度O(N),空间复杂度 O(3N),3个辅助数组

    /**
     * 动态规划方式计算最大回撤,依照定义,最大回撤值是一个负数
     * D为某一天的净值,i为某一天,j为i后的某一天,Di为第i天的产品净值,Dj则是Di后面某一天的净值
     * drawdown就是最大回撤率
     * drawdown=max((Di-Dj)/Di),其实就是对每一个净值进行回撤率求值,然后找出最大的。
     * @param list
     * @return
     */
    public static MaxWithdrawal maxWithdrawalDp(List<String> list) {
        if (Preconditions.isNullOrEmpty(list))
            return null;
​
        int size = list.size();
        double[] dpMax = new double[size]; //定义dpMax[i]为0到i的最大值
        dpMax[0] =  NumberHelper.getDouble(list.get(0), 0);
        double[] dpMin = new double[size]; //定义dpMin[i]为0到i的最小值
        dpMin[0] =  NumberHelper.getDouble(list.get(0), 0);
​
        double[] dp = new double[size]; //定义dp[i]为0到i的最大回撤
        dp[0] = 0;
​
        int maxIndex = size -1;
        int minIndex = 0;
​
        int i = 0; //i指针跟踪最大值
        int j = 0; //j指针跟踪最小值
        for (int k = 1; k< size; k++) {
            if (NumberHelper.getDouble(list.get(k), 0) > dpMax[k-1]) {
                dpMax[k] = NumberHelper.getDouble(list.get(k), 0);
                i++;
                j++;
                maxIndex = k;
                L.i("动规dpMax["+k+"]:"+dpMax[k]);
            } else {
                dpMax[k] = dpMax[k-1];
            }
​
            if (NumberHelper.getDouble(list.get(k), 0) < dpMin[k-1]) {
                dpMin[k] = NumberHelper.getDouble(list.get(k), 0);
                j++;
                minIndex = k;
                L.i("动规dpMin["+k+"]:"+dpMin[k]);
            } else {
                dpMin[k] = dpMin[k-1];
            }
​
​
            if (j > i) { //最小值在最大值之后回撤才有意义
                //drawdown=(Di - Dj)/Di = 1 - Dj/Di
                dp[k] = Math.max(1- dpMin[k] / dpMax[k], dp[k-1]);
                L.i("动规dp["+k+"]:"+dp[k]);
            } else {
                dp[k] = dp[k-1];
            }
        }
​
​
        L.i("动规dp["+(size-1)+"]:"+dp[size-1] + ",maxIndex:"+maxIndex+",minIndex:"+minIndex);
        if (dp[size-1] == 0 || j == i || maxIndex >= minIndex) {
            //找不到最大回撤
            return null;
        }
        MaxWithdrawal data = new MaxWithdrawal();
        data.maxValue = NumberHelper.parsePercent(String.valueOf(dp[size-1]), "0.00%", 2, true, false);
        data.list = list.subList(maxIndex,minIndex+1);
        return data;
    }

压缩数组节省空间

​
    /**
     * 压缩数组节省空间
     *
     * @param list
     * @return
     */
    public static MaxWithdrawal maxWithdrawalDp2(List<String> list) {
        if (Preconditions.isNullOrEmpty(list)) {
            return null;
        }
        int size = list.size();
        int i = 0; //i指针跟踪最大值
        int j = 0; //j指针跟踪最小值
​
        int maxIndex = size - 1;
        int minIndex = 0;
​
        double dpMax = NumberHelper.getDouble(list.get(0), 0); //当前遍历最大值
        double dpMin = NumberHelper.getDouble(list.get(0), 0); //当前遍历最小值
        double dp = 0; //当前计算得出回撤值
        for (int k = 1; k < size; k++) {
            if (NumberHelper.getDouble(list.get(k), 0) > dpMax) {
                dpMax = NumberHelper.getDouble(list.get(k), 0);
                i++;
                j++;
                maxIndex = k;
            }
            if (NumberHelper.getDouble(list.get(k).getOriginRange(), 0) < dpMin) {
                dpMin = NumberHelper.getDouble(list.get(k), 0);
                j++;
                minIndex = k;
            }
​
            if (j > i) {
                dp = Math.max(1 - dpMin / dpMax, dp); //dp = (dpMax - dpMin) / dpMax
            }
        }
​
        if (j == i || minIndex <= maxIndex || dp == 0) {
            return null;
        }
​
        MaxWithdrawal data = new MaxWithdrawal();
        data.maxValue = NumberHelper.parsePercent(String.valueOf(dp), "0.00%", 2, true, false);
        data.list = list.subList(maxIndex, minIndex + 1);
​
        return data;
    }
​

如果按照定义计算时间复杂度O(N^2),一般是不会采用这种方式的。