4. 固定时间窗口内逐笔成交聚合特征
任务拆解:从逐笔成交到固定窗口特征矩阵
本例的目标是把一张逐笔成交明细表,按固定时间窗口(例如 1 分钟)切分成若干组,每组计算 4 个聚合量:成交笔数、总成交量、总成交额、买卖不平衡量,最终拼成一个以窗口为行、特征为列的时间序列矩阵。关键约束是:窗口边界必须固定对齐(如 09:30:00、09:31:00……),不能按“每 N 笔”或“从首笔起算”分组,否则不同交易日的窗口不可比。
下面用一段可运行的 Python 代码完成整个流程。示例数据为构造的教学数据,方向字段 side 取 +1 表示主动买入、-1 表示主动卖出,价格与数量均为假设值。
import pandas as pd
import numpy as np
# 1) 构造教学用逐笔成交数据(示例,非真实行情)
raw = pd.DataFrame({
"trade_time": pd.to_datetime([
"2024-06-03 09:30:05", "2024-06-03 09:30:41", "2024-06-03 09:30:58",
"2024-06-03 09:31:02", "2024-06-03 09:31:27", "2024-06-03 09:31:55",
"2024-06-03 09:32:10", "2024-06-03 09:32:33", "2024-06-03 09:32:49",
"2024-06-03 09:33:04",
]),
"price": [10.00, 10.02, 10.01, 10.03, 10.05, 10.04, 10.02, 10.00, 9.99, 10.01],
"volume": [300, 500, 200, 800, 400, 600, 700, 300, 500, 200],
"side": [1, 1, -1, 1, -1, 1, -1, -1, 1, -1],
})
# 2) 单笔成交额与带符号成交量(方向符号化)
raw["amount"] = raw["price"] * raw["volume"]
raw["signed_volume"] = raw["side"] * raw["volume"]
# 3) 固定时间窗口分组:floor 到 1 分钟
raw["window"] = raw["trade_time"].dt.floor("1min")
# 4) 窗口内聚合
feat = raw.groupby("window").agg(
trade_count=("volume", "size"), # 成交笔数
total_volume=("volume", "sum"), # 总成交量
total_amount=("amount", "sum"), # 总成交额
buy_volume=("volume", lambda s: s[raw.loc[s.index, "side"] == 1].sum()),
sell_volume=("volume", lambda s: s[raw.loc[s.index, "side"] == -1].sum()),
).reset_index()
# 5) 买卖不平衡量 = 主动买入量 - 主动卖出量
feat["volume_imbalance"] = feat["buy_volume"] - feat["sell_volume"]
# 6) 输出特征矩阵(窗口为索引)
matrix = feat.set_index("window")[
["trade_count", "total_volume", "total_amount", "volume_imbalance"]
]
print(matrix)
按上述示例数据,09:30 窗口含 3 笔(300+500+200=1000 股,成交额 10.00×300+10.02×500+10.01×200=10012 元,买入 800 股、卖出 200 股,不平衡量 +600);09:31 窗口含 3 笔(1800 股,买入 1200、卖出 600,不平衡量 +600);09:32 窗口含 3 笔(1500 股,买入 500、卖出 1200,不平衡量 −700);09:33 窗口仅 1 笔(200 股,不平衡量 −200)。这些数值可由给定输入直接推出,属于示例计算结果。
窗口边界用
dt.floor("1min") 实现,它把任意时间戳向下取整到整分钟,保证跨交易日窗口严格对齐。若改用 resample,需注意它会补齐空窗口(成交笔数为 0),这对后续建模通常更有利,但会改变行数,需在特征矩阵中明确是否保留空窗口。实现要点与验证
- 方向符号约定:
side=+1为主动买入、-1为主动卖出,不平衡量 = 买入量 − 卖出量,正值表示买方主导。若数据源用B/S字符,需先映射为数值再聚合。 - 窗口对齐:用
dt.floor("1min")而非resample时,空窗口不会出现;若需要连续时间轴,改用resample("1min").agg(...)并fillna(0),但要注意trade_count补 0 与volume_imbalance补 0 的语义一致。 - 验证方法:对每个窗口检查
buy_volume + sell_volume == total_volume,以及total_amount == Σ(price×volume);再核对窗口内笔数之和等于原始行数,确保没有成交被漏分或重复计入。 - 常见坑:
groupby后直接用size()统计笔数时,若数据含重复时间戳不影响;但若先按trade_time去重再聚合,会改变笔数含义,需明确“笔”指原始成交记录条数。
示例中的价格、数量与聚合结果均为教学构造数据,用于演示计算路径,不代表任何真实标的的行情或回测表现。实际使用时请以交易所逐笔成交字段定义为准,并确认
side 字段的买卖方向口径(主动买/主动卖 vs 外盘/内盘)。