package com.smppw.core.reta.calc;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import com.smppw.common.pojo.dto.DateValue;
import com.smppw.core.reta.Rate;
import com.smppw.utils.BigDecimalUtils;
import com.smppw.utils.Statistics;
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.LUDecomposition;
import org.apache.commons.math3.linear.RealMatrix;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author wangzaijun
 * @date 2023/6/27 8:57
 * @description 业绩可持续性指标
 */
public class Hurst extends Rate {
    public static void main(String[] args) {
        List<Double> navList = ListUtil.list(true);
        for (int i = 1; i <= 199; i++) {
            navList.add((double) i);
        }

        Double hurst = new Hurst().getHurst(navList);
        System.out.println(hurst);
    }

    @Override
    public Double calc(List<DateValue> dateValues) {
        if (CollUtil.isEmpty(dateValues) || dateValues.size() <= 2) {
            return null;
        }
        dateValues.sort(Comparator.comparing(DateValue::getDate));
        List<Double> navList = dateValues.stream().map(DateValue::getValue)
                .filter(Objects::nonNull).collect(Collectors.toList());
        return this.getHurst(navList);
    }

    /**
     * 把净值序列转收益序列，并且去掉第一个点
     *
     * @param navList 净值序列
     * @return /
     */
    private List<Double> navToRet(List<Double> navList) {
        List<Double> rets = ListUtil.list(true);
        Double prevNav = navList.get(0);
        for (int i = 1; i <= navList.size() - 1; i++) {
            double ret = 0d;
            Double nav = navList.get(i);
            if (prevNav != null && nav != null) {
                // 不用double计算，有精度丢失问题
                ret = BigDecimalUtils.toBigDecimal(nav).divide(BigDecimalUtils.toBigDecimal(prevNav), 20, RoundingMode.HALF_UP)
                        .subtract(BigDecimal.ONE).doubleValue();
            }
            rets.add(ret);
            prevNav = navList.get(i);
        }
        return rets;
    }

    private double getRS(List<Double> navList) {
        // 计算极差
        Double maxNav = Collections.max(navList);
        Double minNav = Collections.min(navList);
        double R = maxNav / minNav - 1;
        // 计算标准差
        List<Double> rets = this.navToRet(navList);
//        DescriptiveStatistics statistics = new DescriptiveStatistics(rets);
//        double S = statistics.getStandardDeviation();
        double S = Statistics.stdDevUnbiased(rets);
        return R / S;
    }

    private Double getHurst(List<Double> navList) {
        int size = navList.size();
        // R/S计算的最大窗口
        int maxWindow = size - 1;
        // R/S计算的最小窗口
        int minWindow = 10;
        if (maxWindow <= minWindow) {
            return null;
        }
        List<Integer> windowSizes = ListUtil.list(true);
        for (double i = Math.log10(minWindow); i < Math.log10(maxWindow); i += 0.25) {
            windowSizes.add((int) Math.pow(10, i));
        }
        windowSizes.add(size);
        List<Double> rsList = ListUtil.list(true);
        for (int w : windowSizes) {
            List<Double> rs = ListUtil.list(true);
            for (int start = 0; start <= size - 1; start += w) {
                if (start + w > size) {
                    break;
                }
                List<Double> calcRetList = navList.stream().skip(start).limit(w).collect(Collectors.toList());
                double rsValue = this.getRS(calcRetList);
                if (rsValue != 0) {
                    rs.add(rsValue);
                }
            }
            rsList.add(Statistics.mean(rs));
        }

        int n = windowSizes.size();
        double[][] x = new double[n][2];
        for (int i = 0; i < n; i++) {
            x[i][0] = Math.log(windowSizes.get(i));
            x[i][1] = 1;
        }
        RealMatrix X = new Array2DRowRealMatrix(x);

        double[] rsArray = new double[n];
        for (int i = 0; i < n; i++) {
            rsArray[i] = Math.log(rsList.get(i));
        }
        RealMatrix Y = new Array2DRowRealMatrix(rsArray);
        RealMatrix res = new LUDecomposition(X.transpose().multiply(X)).getSolver().getInverse()
                .multiply(X.transpose()).multiply(Y);
        return res.getRow(0)[0];
    }
}
