Administrator
7 days ago 084bb8457b44f765b32365cb09f613ea871634dc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
 * 波动率指标计算类
 * <p>
 * 波动率是衡量金融市场价格波动程度的指标,通常用价格的标准差与平均值的比率表示。
 * 本类实现了基于滚动窗口的波动率计算,通过标准差与平均值的比值计算出百分比形式的波动率。
 */
package com.xcong.excoin.modules.okxNewPrice.indicator.macdAndMatrategy;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.LinkedList;
import java.util.List;
 
/**
 * 波动率指标实现类
 * <p>波动率计算原理:使用标准差与平均值的比值,以百分比形式表示价格的波动程度。</p>
 * <p>计算公式:波动率 = (标准差 / 平均值) * 100%</p>
 *
 * <p>使用示例:</p>
 * <pre>
 * // 初始化20日波动率计算器
 * Volatility vol = new Volatility(20);
 *
 * // 动态添加每日价格
 * priceFeed.subscribe(price -> {
 * vol.addPrice(price);
 * vol.calculate();
 * });
 *
 * // 判断是否满足低波动条件(<1%)
 * if (vol.getValue().compareTo(new BigDecimal("1.00")) < 0) {
 * System.out.println("低波动市场,暂停交易");
 * }
 * </pre>
 */
public class Volatility {
    /** 波动率计算的周期(如20日波动率) */
    private final int period;
 
    /** 当前计算出的波动率值(百分比形式) */
    private BigDecimal volatility = BigDecimal.ZERO;
 
    /** 使用LinkedList存储滚动窗口内的价格数据,便于添加和删除操作 */
    private LinkedList<BigDecimal> priceWindow = new LinkedList<>();
 
    /** 窗口内价格的总和,用于快速计算平均值 */
    private BigDecimal sum = BigDecimal.ZERO;
 
    /** 窗口内价格平方的总和,用于快速计算方差 */
    private BigDecimal sumSquares = BigDecimal.ZERO;
 
    /**
     * 构造函数,创建指定周期的波动率计算器
     *
     * @param period 波动率计算周期,如20表示计算20日波动率
     */
    public Volatility(int period) {
        this.period = period;
    }
 
    /**
     * 添加新价格到计算窗口,并维护窗口内的价格数据
     * 采用滑动窗口方式,当价格数量超过周期时,自动移除最早的价格
     *
     * @param price 新的价格数据,使用BigDecimal确保计算精度
     * @throws IllegalArgumentException 当价格为null时抛出异常
     */
    public void addPrice(BigDecimal price) {
        if (price == null) {
            throw new IllegalArgumentException("Price cannot be null");
        }
 
        // 当窗口大小达到周期时,移除最早的价格,并从总和中减去
        if (priceWindow.size() == period) {
            BigDecimal removed = priceWindow.removeFirst();
            sum = sum.subtract(removed);
            sumSquares = sumSquares.subtract(removed.pow(2));
        }
 
        // 添加新价格到窗口,并更新总和
        priceWindow.add(price);
        sum = sum.add(price);
        sumSquares = sumSquares.add(price.pow(2));
    }
 
    /**
     * 计算当前窗口内价格的波动率
     * 使用标准差与平均值的比值计算波动率百分比
     */
    public void calculate() {
        // 数据点不足,无法计算波动率
        if (priceWindow.size() < period) {
            return;
        }
 
        // 计算平均值:sum / period
        BigDecimal avg = sum.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP);
 
        // 防止除以零的情况
        if (avg.compareTo(BigDecimal.ZERO) == 0) {
            volatility = BigDecimal.ZERO;
            return;
        }
 
        // 计算方差:(sumSquares / period) - avg^2
        BigDecimal variance = sumSquares.divide(new BigDecimal(period), 8, RoundingMode.HALF_UP)
                .subtract(avg.pow(2));
 
        // 确保方差非负(防止浮点数计算误差导致负数方差)
        variance = variance.max(BigDecimal.ZERO);
 
        // 计算标准差:sqrt(variance)
        BigDecimal stdDev = sqrt(variance, 8);
 
        // 计算波动率:(标准差 / 平均值) * 100%
        volatility = stdDev.divide(avg, 8, RoundingMode.HALF_UP)
                .multiply(new BigDecimal(100))
                .setScale(2, RoundingMode.HALF_UP);
    }
 
    /**
     * 计算BigDecimal的平方根(使用牛顿迭代法)
     *
     * @param value 要计算平方根的数值
     * @param scale 结果的精度(小数位数)
     * @return 平方根结果
     */
    private BigDecimal sqrt(BigDecimal value, int scale) {
        // 负数没有实数平方根,返回0
        if (value.compareTo(BigDecimal.ZERO) < 0) {
            return BigDecimal.ZERO;
        }
 
        // 使用牛顿迭代法计算平方根
        BigDecimal x = value.divide(new BigDecimal(2), scale, RoundingMode.HALF_UP); // 初始猜测值
        BigDecimal prev;
 
        do {
            prev = x;
            // 牛顿迭代公式:x(n+1) = (x(n) + value/x(n))/2
            // 添加零检查,防止除以零异常
            if (x.compareTo(BigDecimal.ZERO) == 0) {
                x = new BigDecimal(1); // 设置一个合理的初始值
            }
            x = x.add(value.divide(x, scale, RoundingMode.HALF_UP)).divide(new BigDecimal(2), scale, RoundingMode.HALF_UP);
        } while (x.subtract(prev).abs().compareTo(BigDecimal.ONE.movePointLeft(scale)) > 0); // 直到满足精度要求
 
        return x;
    }
 
    /**
     * 获取最新的波动率计算结果
     *
     * @return 波动率值,以百分比形式表示(例如:2.5表示2.5%)
     */
    public BigDecimal getValue() {
        return volatility;
    }
}