











A股除权除息(分红、送股、转增、配股)会导致股价出现"跳空",但公司基本面并未突变,所以需要复权还原真实涨跌幅。核心是先算出每个交易日的复权因子,再据此计算前复权/后复权价格。
1. 除权参考价(除权除息日的理论价格):
除权价 = (前收盘价 - 每股派息 + 配股比例×配股价) / (1 + 送股比例 + 转增比例 + 配股比例)
2. 复权因子(从最早一天=1开始,只在除权日跳变,累乘):
factor[t] = factor[t-1] × (除权日前收盘价 / 除权参考价)
3. 两种复权价格:
| 类型 | 公式 | 含义 |
|---|---|---|
| 后复权 | 原始价 × factor[t] |
最早一天价格不变,后续价格随因子放大,体现分红再投资的真实收益 |
| 前复权 | 原始价 × factor[t] / factor[最新一天] |
最新一天价格等于原始价(贴近盘面),历史价格向下修正 |
前复权常用于看盘(价格接近当前实际报价),后复权常用于计算长期真实收益率/回测。
几点使用说明:
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]);
}
}
}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。