Java 实现A股前复权/后复权计算原理

A股除权除息(分红、送股、转增、配股)会导致股价出现"跳空",但公司基本面并未突变,所以需要复权还原真实涨跌幅。核心是先算出每个交易日的复权因子,再据此计算前复权/后复权价格。

核心公式

1. 除权参考价(除权除息日的理论价格):

 
除权价 = (前收盘价 - 每股派息 + 配股比例×配股价) / (1 + 送股比例 + 转增比例 + 配股比例)

2. 复权因子(从最早一天=1开始,只在除权日跳变,累乘):

 
factor[t] = factor[t-1] × (除权日前收盘价 / 除权参考价)

3. 两种复权价格:

类型公式含义
后复权 原始价 × factor[t] 最早一天价格不变,后续价格随因子放大,体现分红再投资的真实收益
前复权 原始价 × factor[t] / factor[最新一天] 最新一天价格等于原始价(贴近盘面),历史价格向下修正

前复权常用于看盘(价格接近当前实际报价),后复权常用于计算长期真实收益率/回测。

几点使用说明:

  • 数据准备:你需要每根K线的 preClose(前收盘价),以及每次除权除息的公告数据(派息、送股、转增、配股比例和配股价)——这些通常从交易所或数据商(如东方财富、Tushare、Wind)的除权除息表里获取,原始日线数据本身不包含这些信息。
  • 成交量:如果也要对成交量复权(保证成交额不变),一般用 原始成交量 / factor 反向调整,我这版没加,需要的话可以补。
  • 精度:实盘系统一般用 BigDecimal 避免浮点误差累积,这里为了代码清晰用了 double,量级大或者除权次数多时建议换成 BigDecimal
 
 
 
 
import java.time.LocalDate;
import java.util.*;

/**
 * A股前复权 / 后复权 价格计算
 *
 * 核心思路:
 * 1. 根据每次除权除息事件(派息、送股、转增、配股),计算"除权参考价"
 * 2. 用 (除权前收盘价 / 除权参考价) 累乘,得到每个交易日的"复权因子"
 * 3. 后复权价 = 原始价 × 复权因子(最早一天价格不变)
 *    前复权价 = 原始价 × 复权因子 / 最新一天的复权因子(最新一天价格不变)
 */
public class StockPriceAdjuster {

    /** 原始K线数据 */
    public static class KLine {
        LocalDate date;
        double open, high, low, close, preClose; // preClose = 前一交易日收盘价
        long volume;

        public KLine(LocalDate date, double open, double high, double low,
                     double close, double preClose, long volume) {
            this.date = date;
            this.open = open;
            this.high = high;
            this.low = low;
            this.close = close;
            this.preClose = preClose;
            this.volume = volume;
        }
    }

    /** 除权除息事件(来自公司公告) */
    public static class DividendEvent {
        LocalDate exDate;          // 除权除息日
        double cashDividend;       // 每股派息(税前,元),如每10股派1元则填0.1
        double bonusShareRatio;    // 每股送股比例,如10送2则填0.2
        double transferRatio;      // 每股转增比例,如10转3则填0.3
        double rightsRatio;        // 每股配股比例,如10配3则填0.3
        double rightsPrice;        // 配股价格(无配股则填0)

        public DividendEvent(LocalDate exDate, double cashDividend, double bonusShareRatio,
                              double transferRatio, double rightsRatio, double rightsPrice) {
            this.exDate = exDate;
            this.cashDividend = cashDividend;
            this.bonusShareRatio = bonusShareRatio;
            this.transferRatio = transferRatio;
            this.rightsRatio = rightsRatio;
            this.rightsPrice = rightsPrice;
        }
    }

    /**
     * 计算除权参考价:
     * (前收盘价 - 每股派息 + 配股比例×配股价) / (1 + 送股比例 + 转增比例 + 配股比例)
     */
    private static double calcExRightsPrice(double preClose, DividendEvent e) {
        double numerator = preClose - e.cashDividend + e.rightsRatio * e.rightsPrice;
        double denominator = 1 + e.bonusShareRatio + e.transferRatio + e.rightsRatio;
        return numerator / denominator;
    }

    /**
     * 计算每个交易日的累计复权因子(最早一天=1,遇到除权日跳变一次,其余日期沿用上一值)
     */
    public static Map<LocalDate, Double> calcAdjustFactors(List<KLine> klines, List<DividendEvent> events) {
        Map<LocalDate, DividendEvent> eventMap = new HashMap<>();
        for (DividendEvent e : events) {
            eventMap.put(e.exDate, e);
        }

        klines.sort(Comparator.comparing(k -> k.date)); // 必须按时间升序

        Map<LocalDate, Double> factorMap = new LinkedHashMap<>();
        double factor = 1.0;

        for (KLine k : klines) {
            DividendEvent e = eventMap.get(k.date);
            if (e != null) {
                double exRightsPrice = calcExRightsPrice(k.preClose, e);
                factor = factor * (k.preClose / exRightsPrice);
            }
            factorMap.put(k.date, factor);
        }
        return factorMap;
    }

    /** 后复权:最早一天价格不变,后续按因子放大,反映分红再投资后的真实收益 */
    public static double backwardAdjust(double rawPrice, double factor) {
        return rawPrice * factor;
    }

    /** 前复权:最新一天价格等于原始价,历史价格按比例缩小,贴近当前盘面 */
    public static double forwardAdjust(double rawPrice, double factor, double latestFactor) {
        return rawPrice * factor / latestFactor;
    }

    /** 对整批K线做前复权或后复权,返回 {open, high, low, close} 数组列表 */
    public static List<double[]> adjustAll(List<KLine> klines, List<DividendEvent> events, boolean forward) {
        Map<LocalDate, Double> factors = calcAdjustFactors(klines, events);
        double latestFactor = factors.get(klines.get(klines.size() - 1).date);

        List<double[]> result = new ArrayList<>();
        for (KLine k : klines) {
            double f = factors.get(k.date);
            double open, high, low, close;
            if (forward) {
                open = forwardAdjust(k.open, f, latestFactor);
                high = forwardAdjust(k.high, f, latestFactor);
                low = forwardAdjust(k.low, f, latestFactor);
                close = forwardAdjust(k.close, f, latestFactor);
            } else {
                open = backwardAdjust(k.open, f);
                high = backwardAdjust(k.high, f);
                low = backwardAdjust(k.low, f);
                close = backwardAdjust(k.close, f);
            }
            result.add(new double[]{open, high, low, close});
        }
        return result;
    }

    public static void main(String[] args) {
        List<KLine> klines = new ArrayList<>();
        klines.add(new KLine(LocalDate.of(2024, 1, 2), 10.00, 10.20, 9.90, 10.10, 10.05, 1_000_000));
        klines.add(new KLine(LocalDate.of(2024, 1, 3), 10.10, 10.30, 10.00, 10.20, 10.10, 1_100_000));
        // 假设 2024-01-04 除权除息:每10股派1元,10送2股
        klines.add(new KLine(LocalDate.of(2024, 1, 4), 8.60, 8.80, 8.50, 8.70, 10.20, 1_500_000));
        klines.add(new KLine(LocalDate.of(2024, 1, 5), 8.75, 8.90, 8.60, 8.80, 8.70, 900_000));

        List<DividendEvent> events = new ArrayList<>();
        events.add(new DividendEvent(LocalDate.of(2024, 1, 4), 0.10, 0.20, 0.0, 0.0, 0.0));

        System.out.println("原始收盘价:");
        for (KLine k : klines) {
            System.out.println(k.date + " " + k.close);
        }

        List<double[]> forwardResult = adjustAll(klines, events, true);
        System.out.println("\n前复权收盘价:");
        for (int i = 0; i < klines.size(); i++) {
            System.out.printf("%s %.4f%n", klines.get(i).date, forwardResult.get(i)[3]);
        }

        List<double[]> backwardResult = adjustAll(klines, events, false);
        System.out.println("\n后复权收盘价:");
        for (int i = 0; i < klines.size(); i++) {
            System.out.printf("%s %.4f%n", klines.get(i).date, backwardResult.get(i)[3]);
        }
    }
}

 

posted @ 2026-08-25 19:52  锐洋智能  阅读(21)  评论(0)    收藏  举报