
为什么需要从日数据抽季度数据
做股票或期货策略时,高开低收的日数据太细碎,季度数据却能帮你看见大势。比如你研究一家公司连续20个季度的营收与股价关系,或者测试一个季度调仓的期货趋势策略,都需要把每天的价格浓缩成每季一根K线。手工整理不现实,用SQL或Python自动抽取才是交易员的日常。
季度数据的定义与常见规则
季度划分有两种主流方式:自然季度(1-3月、4-6月、7-9月、10-12月)和按财报季(部分期货品种按合约季)。股票通常用自然季度,每季最后一天的收盘价作为该季收盘价,第一天的开盘价作为季度开盘价,期间最高价与最低价分别取最大值和最小值。成交量则是整个季度累加。

有些策略会使用季度末最后一个交易日的价格,有些用季度内均价。规则要先定清楚,否则抽取出来的数据无法回测。
SQL方案 利用窗口函数与分组
假设有一张日线表 daily_price,字段为 code(股票代码)、trade_date(日期)、open、high、low、close、volume。要抽取每只股票每季度的OHLCV。
第一步,给每行打上季度标签。不同数据库语法略有差异,核心是用年份和季度组合。MySQL可以用 CONCAT(YEAR(trade_date), 'Q', QUARTER(trade_date)),PostgreSQL用 EXTRACT(YEAR FROM trade_date) || 'Q' || EXTRACT(QUARTER FROM trade_date)。
第二步,用窗口函数找出每季第一天和最后一天。以MySQL 8.0为例:
WITH qtr_label AS (
SELECT *,
CONCAT(YEAR(trade_date), 'Q', QUARTER(trade_date)) AS qtr,
ROW_NUMBER() OVER (PARTITION BY code, CONCAT(YEAR(trade_date), 'Q', QUARTER(trade_date)) ORDER BY trade_date) AS rn_first,
ROW_NUMBER() OVER (PARTITION BY code, CONCAT(YEAR(trade_date), 'Q', QUARTER(trade_date)) ORDER BY trade_date DESC) AS rn_last
FROM daily_price
)
SELECT
code,
qtr,
MAX(CASE WHEN rn_first = 1 THEN open END) AS q_open,
MAX(high) AS q_high,
MIN(low) AS q_low,
MAX(CASE WHEN rn_last = 1 THEN close END) AS q_close,
SUM(volume) AS q_volume
FROM qtr_label
GROUP BY code, qtr
ORDER BY code, qtr;
这里用 MAX(CASE WHEN ...) 提取首日开盘和末日收盘,是因为分组后每季只有一行满足条件。若数据库支持 FIRST_VALUE 和 LAST_VALUE 窗口函数,也可直接取值,但要注意 LAST_VALUE 默认窗口帧问题,需指定 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。
对于期货,如果主力合约换月,需要先处理连续合约再抽取季度数据,否则季度开盘价可能来自不同合约。
Python方案 用pandas重采样更灵活
拿到日线DataFrame后,把日期设为索引,用 resample('Q') 聚合。pandas的Q代表季度末,Q-DEC为自然季。代码示例如下:
import pandas as pd
df = pd.read_csv('daily_price.csv', parse_dates=['trade_date'])
df = df.set_index('trade_date')
df = df.sort_index()
quarterly = df.resample('Q').agg({
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'
})
quarterly.index = quarterly.index.to_period('Q')
如果有多只股票,需要先按code分组再重采样:
def to_quarterly(group):
return group.resample('Q').agg({'open':'first','high':'max','low':'min','close':'last','volume':'sum'})
quarterly = df.groupby('code').apply(to_quarterly).reset_index()
pandas也能处理非自然季度,比如按期货合约季(3月、6月、9月、12月),只需自定义偏移量或先标记季度再分组。
关键细节与坑
停牌或缺失数据会破坏首日开盘的准确性。若某季第一天没交易,pandas的first会取该季第一个有数据的交易日,SQL的ROW_NUMBER也取第一条记录。多数场景下这符合逻辑,但严格回测时需要标记出真实缺失。
交易日期格式要统一,字符串日期会导致排序错误,必须先转成日期类型。SQL中用STR_TO_DATE或TO_DATE,Python用pd.to_datetime。
期货的夜盘归属问题:国内期货夜盘属于下一交易日,抽取季度数据时要确保日期已经按交易日历调整,否则季度最后一天的收盘价可能落在夜盘时段,造成偏差。
季度数据的频率低,样本量小,回测时容易过拟合。用季度数据做策略,最好结合基本面因子,不要单纯依赖技术指标。
实战中的选择建议
SQL适合在数据库里直接跑,数据量大时效率高,结果可以存入新表供后续查询。Python适合探索性分析,能快速画图、计算收益率、合并财务数据。两者也可以结合:用SQL抽取季度数据存入宽表,再用Python读取做回测。
无论哪种工具,核心逻辑就一条:按季度分组,每组的首开、高、低、末收、量总和。把这五个值抓准,季度数据就抽好了。