“用 MiniQMT(迅投 QMT)的 Tick 逐笔数据,统计个股每日资金流向。
20260507143758
一、为什么要计算资金流向?
资金流向是判断市场情绪和主力意图的重要指标。简单来说:
但市面上很多资金流向数据都是"黑盒",我们不知道它是怎么算的。今天这篇文章,花姐带你从零开始,用 Python 和 MiniQMT 的 Tick 数据,自己动手算一遍。
二、核心原理:四步计算法
整个计算流程分为四个步骤:
获取 Tick 数据 → 计算单笔方向 → 汇总每日统计 → 按单量分类
20260507144537第 1 步:获取 Tick 逐笔数据
MiniQMT 提供了 xtdata 模块,可以获取股票的逐笔成交数据(Tick 数据)。每一条 Tick 记录包含:
| |
|---|
time | |
lastPrice | |
amount | |
volume | |
bidPrice | |
askPrice | |
transactionNum | |
from xtquant import xtdata# 下载并获取 tick 数据xtdata.download_history_data( stock_code='601609.SH', period='tick', start_time='20260407')data = xtdata.get_market_data_ex( stock_list=['601609.SH'], period='tick', start_time='20260407')df = data['601609.SH']
注意:Tick 数据中的 amount 和 volume 是累计值,所以需要用 diff() 计算单笔的成交额和成交量。
第 2 步:判断每笔成交的方向(主动买入 vs 主动卖出)
这是资金流向计算的核心。关键规则:
“主动买入:成交价 ≥ 卖一价(askPrice[0])主动卖出:成交价 ≤ 买一价(bidPrice[0])
原理:如果成交价触及或超过了卖一价,说明买方愿意以更高的价格买入,是主动买入;反之则是主动卖出。
# 提取买卖一价df['bid1'] = df['bidPrice'].apply(lambda x: float(x[0]) if isinstance(x, (list, tuple)) and len(x) > 0else np.nan)df['ask1'] = df['askPrice'].apply(lambda x: float(x[0]) if isinstance(x, (list, tuple)) and len(x) > 0else np.nan)# 判断方向conditions = [ df['lastPrice'] >= df['ask1'], # 主动买入 df['lastPrice'] <= df['bid1'] # 主动卖出]choices = ['buy', 'sell']df['direction'] = np.select(conditions, choices, default='neutral')
单笔成交额计算:
# amount 是累计值,用 diff 得到单笔成交额df['tick_amount'] = df['amount'].diff().fillna(df['amount'])
第 3 步:汇总每日资金流向
将每笔的主动买入、主动卖出汇总到每日:
daily = df.groupby('date').agg({'buy_amount': 'sum','sell_amount': 'sum','net_inflow': 'sum',}).reset_index()
其中:
- 主动买入金额 = 所有 direction='buy' 的 tick_amount 之和
- 主动卖出金额 = 所有 direction='sell' 的 tick_amount 之和
第 4 步:按单量大小分类(大单/中单/小单)
除了总体的资金流向,我们还想知道大资金和散户的行为差异。分类方法:
“平均单笔成交量 = 单笔成交量 / 成交笔数
- 中单:中位数 < 平均单笔成交量 ≤ 2 × 中位数
# 计算平均单笔成交量df['avg_trade_size'] = df['tick_volume'] / df['transactionNum']# 用中位数作为分界点avg_size = df['avg_trade_size'].median()# 分类df['order_size'] = pd.cut( df['avg_trade_size'], bins=[0, avg_size, 2 * avg_size, np.inf], labels=['小单', '中单', '大单'])
然后分别计算大单、中单、小单的净流入:
big = df[df['order_size'] == '大单'].groupby('date')['net_inflow'].sum()mid = df[df['order_size'] == '中单'].groupby('date')['net_inflow'].sum()small = df[df['order_size'] == '小单'].groupby('date')['net_inflow'].sum()
三、数据过滤:只统计交易时段
A 股的交易时间是 9:30-15:00,但 Tick 数据可能包含盘前集合竞价的数据。我们需要过滤:
df['time_only'] = df['time'].dt.timemask = (df['time_only'] >= pd.to_datetime('09:30:00').time()) & \ (df['time_only'] <= pd.to_datetime('15:00:00').time())df = df.loc[mask]
四、可视化:四图联动
计算完成后,我们用 matplotlib 绘制四张子图:
fig, axes = plt.subplots(4, 1, figsize=(14, 14), gridspec_kw={'height_ratios': [2, 1, 1, 1]})
关键设计:
- 收盘价和资金流向分开绘制,避免量纲不同导致曲线变形
五、完整代码结构
get_tick_data() # 获取 Tick 数据 ↓calculate_money_flow() # 计算单笔方向 + 累计值 ↓aggregate_daily_money_flow() # 按天聚合统计 ↓plot_daily_money_flow() # 绘图展示
函数职责分离:
calculate_money_flow():只做 Tick 级计算aggregate_daily_money_flow():只做日级聚合plot_daily_money_flow():只做可视化
六、使用示例
# 分析某股票最近 5 天的资金流向result = analyze_stock_money_flow( stock_code='6*****.SH', start_time='20260401', end_time='20260407', save_path='money_flow.png')# result 是每日聚合的 DataFrameprint(result)
输出示例:
date buy_amount sell_amount net_inflow big_net mid_net small_net0 2026-04-07 4.359756e+07 4.959483e+07 -5.997272e+06 -5.586435e+06 -3.161270e+05 -9.471000e+041 2026-04-08 1.637802e+08 1.152624e+08 4.851781e+07 4.786713e+07 1.598940e+05 4.907790e+052 2026-04-09 -2.126938e+08 9.301448e+07 -3.057082e+08 -2.428960e+07 -9.147700e+05 -6.960310e+053 2026-04-10 8.310812e+07 -7.883693e+07 1.619451e+08 -1.173246e+06 -3.758390e+05 -1.149020e+054 2026-04-13 8.592027e+07 -7.356728e+07 1.594876e+08 -8.348566e+06 -4.594390e+05 -1.775310e+055 2026-04-14 9.031151e+07 -8.363566e+07 1.739472e+08 -1.166546e+07 -4.113270e+05 -1.627900e+04...
七、注意事项
- MiniQMT 需提前启动:运行前确保 QMT 客户端已登录并连接行情
- 数据下载需要时间:首次获取某股票的 Tick 数据会自动下载,可能需要几秒到几分钟
- 时间转中国时区:Tick 数据的时间戳是 UTC,需要转换为
Asia/Shanghai - 中文字体:matplotlib 需设置中文字体,否则中文显示为方框
plt.rcParams['font.sans-serif'] = ['SimHei']plt.rcParams['axes.unicode_minus'] = False
源码已经放到知识星球了,需要的自取。