惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
L
LangChain Blog
A
About on SuperTechFans
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
博客园 - 三生石上(FineUI控件)
N
Netflix TechBlog - Medium
D
DataBreaches.Net
P
Proofpoint News Feed
小众软件
小众软件
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
雷峰网
雷峰网
G
Google Developers Blog

博客园 - 锐洋智能

A 股盘前行情 & 除权下发时间(工程实测 + 交易所文档) A股怎么算的量比? CIDR 从最大网段 `0.0.0.0/0` → 最小单个 IP `/32`完整梳理 CIDR 无类别域间路由写法 windows 下 降级迁移 Redis 8.8.0(源) → Redis 8.6.3(目标) Eclipse IDE for Enterprise Find/Replace 窗口可以"停驻" 安装 SDelete 方式 SDelete 的核心作用,不是 “删文件”,而是 “把你已经删掉的文件,彻底从磁盘上抹干净”,同时帮你把虚拟机里的 “空闲空间” 变成连续的、可被回收的状态。 windows 10 启动就运行了一个批处理文件 在什么地方修改?启动项中? 让 Spring Framework7.0.7 支持 velocity Java 9+ 开启了模块化安全限制,不允许 Ignite 直接访问底层内存地址,导致 Ignite 启动失败 Spring 5.x + 老项目的 JWT 拦截器 + 自动续期 接口鉴权:Session/Cookie 与 JWT 的核心区别 下是针对 RedisSessionManager 的 Tomcat context.xml 配置示例,覆盖基础单机 Redis、带密码 / 指定库、Redis 哨兵集群、自定义序列化 / 持久化策略 等常见场景 Redis-8.6.3-Windows-x64-cygwin 与 Redis-8.6.3-Windows-x64-msys2 有什么不一样? commons-fileupload2 M4 升级 M5 报错解决方案 阿里云的网络安全策略 ip 地址详细说明 Paralithic、 QLExpress、AviatorScript、exp4j 性能对比一下 java 序列化影响(重要!) paralithic 与 Aviator 表达式那一个更快 券商接受委托的完整时间线 jQuery 4.0 移除了许多已废弃的方法和特性 xheditor插件无限递归错误解决方法 jedis-7.1.0.jar 升级至 jedis-7.2.0.jar 就提示:The type JedisPoolConfig is deprecated The type JedisPooled is deprecated Spring 从 5.x 到 6.x 和 7.x 区别 js 原生 剪切板 复制功能 安装 Microsoft Visual C++ 运行时 Java与Python进程通信优化方案 Autocomplete | jQuery UI 同一面页定义不同的 .ui-autocomplete Quartz的misfire处理策略设置不当导致Job在应用启动时立即执行。
Java 实现A股前复权/后复权计算原理
锐洋智能 · 2026-08-25 · via 博客园 - 锐洋智能

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]);
        }
    }
}