ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python实现筹码分布图与Winner函数:从原理到实战

2026/9/21 1:02:03 拓冰建站 浏览量
Python实现筹码分布图与Winner函数:从原理到实战 1. 从一根K线到一张筹码地图为什么Winner函数值得你花时间大多数人看盘的习惯是盯着K线图上的红绿柱再配合MACD、KDJ这些指标做判断。但K线只告诉你价格在某个时间段内走过的轨迹它不会告诉你当前这个价位上到底有多少筹码是获利的多少是套牢的。而这恰恰是判断支撑和压力最核心的信息。举个很常见的场景一只股票从10块涨到15块K线看起来一路向上很漂亮。但如果你知道在14到15块这个区间堆积了大量筹码而这些筹码大部分是最近几天才买入的那你就该警惕了——一旦价格回落这批人很容易因为恐慌而集中抛售形成踩踏。反过来如果一只股票在12块附近横盘了很久筹码大量集中在12块那12块就是一个非常扎实的支撑位因为大部分持有者的成本都在这里他们不愿意亏钱卖。这就是筹码分布图的价值。它把谁在什么价位持有多少筹码这件事可视化出来让你一眼看清市场的成本结构。而Winner函数就是实现这个可视化的核心工具。它本质上是一个计算获利盘比例的函数——给定一个价格它能告诉你当前有多少比例的筹码处于盈利状态。这篇文章我会从零开始把筹码分布图的原理、Winner函数的计算逻辑、Python实现步骤、以及实际使用中的坑和技巧全部讲清楚。不管你是刚学Python的新手还是已经用过pandas做数据分析的老手都能跟着复现出来。代码不依赖任何付费数据源用公开的行情数据就能跑。提示本文所有代码基于Python 3.9核心依赖只有pandas、numpy和matplotlib不需要安装TA-Lib等重型库。2. 筹码分布图的底层逻辑它到底在算什么2.1 筹码分布的本质是成本-持仓的映射要理解筹码分布先要理解一个基本事实每一股股票在任意时刻都有一个买入成本。有人10块买的有人12块买的有人15块买的。如果把这些成本按照持仓量画成一个直方图横轴是价格纵轴是持仓量你就得到了一张筹码分布图。但问题在于你不可能知道每个人的真实成本。交易所不会公布这种数据券商也不会。所以筹码分布图本质上是一个估算模型它基于两个核心假设假设一每天的成交量代表了当天发生换手的筹码量。成交量越大说明当天有越多筹码换了主人。假设二换手后的筹码成本等于当天的成交均价通常用(最高价最低价收盘价)/3来近似。基于这两个假设我们可以用历史成交量数据逐步推算出当前各个价位上的筹码分布。这个过程叫做筹码衰减或筹码迁移。2.2 衰减系数为什么老筹码会消失如果只是简单累加每天的成交量那筹码分布图会越来越胖因为历史成交量一直在叠加。但实际上老筹码会因为换手而逐渐被新筹码替代。所以需要一个衰减机制。常见的做法是引入一个衰减系数通常用换手率来驱动。换手率越高说明筹码交换越充分老筹码被替代得越快。具体公式可以这样理解当日某价位的剩余筹码 昨日该价位筹码 × (1 - 衰减率) 当日新增筹码衰减率的计算方式有多种我实测下来比较稳定的一种是衰减率 换手率 × 衰减因子其中衰减因子是一个经验参数通常在0.5到1.5之间。衰减因子越大老筹码消失得越快筹码分布图越年轻衰减因子越小老筹码保留得越多分布图越厚重。注意衰减因子没有绝对正确的值。不同的市场、不同的标的、不同的时间周期最优参数都不一样。我一般会先用1.0跑一遍然后根据图形形态微调。2.3 Winner函数的数学定义Winner函数的核心逻辑其实很简单。给定一个价格P遍历筹码分布中所有成本低于P的价位把这些价位上的筹码量加起来再除以总筹码量就得到获利盘比例。用数学语言表达Winner(P) Σ(筹码量[i]) / Σ(筹码量[所有]) 其中 成本[i] P这个值在0到1之间。0表示当前价格低于所有筹码的成本全员套牢1表示当前价格高于所有筹码的成本全员获利。在实际应用中我们通常不只看一个价格点的Winner值而是计算一整条曲线——把价格从最低到最高遍历一遍画出Winner值随价格变化的曲线。这条曲线和筹码分布图叠加在一起就能非常直观地看出支撑和压力的位置。3. 用Python实现筹码分布计算从数据到图形3.1 数据准备你需要哪些字段要计算筹码分布最少需要以下日线数据字段说明用途date交易日期时间序列索引open开盘价辅助计算均价high最高价计算成交均价low最低价计算成交均价close收盘价计算成交均价volume成交量计算换手率turnover_rate换手率驱动筹码衰减如果你用的是akshare或者tushare这类公开数据接口这些字段基本都能直接拿到。如果没有换手率字段可以用成交量除以流通股本估算。import pandas as pd import numpy as np import matplotlib.pyplot as plt # 假设df是你的行情数据已经按日期升序排列 # 必要字段high, low, close, volume, turnover_rate df df.sort_values(date).reset_index(dropTrue)3.2 核心算法逐日迭代计算筹码分布下面这段代码是筹码分布计算的核心。我把它拆成几个关键步骤每一步都加了注释说明意图。def calc_chip_distribution(df, decay_factor1.0, price_bins200): 计算筹码分布 df: 包含 high, low, close, volume, turnover_rate 的DataFrame decay_factor: 衰减因子默认1.0 price_bins: 价格分箱数量决定分辨率 # 确定价格范围 price_min df[low].min() price_max df[high].max() prices np.linspace(price_min, price_max, price_bins) # 初始化筹码分布数组 chips np.zeros(price_bins) for i in range(len(df)): row df.iloc[i] avg_price (row[high] row[low] row[close]) / 3 turnover row[turnover_rate] / 100.0 # 转成小数 # 衰减老筹码按换手率比例消失 decay min(turnover * decay_factor, 1.0) chips chips * (1 - decay) # 新增当天筹码按三角分布分配到均价附近 # 用三角分布是因为当天成交价不会完全集中在均价一个点上 spread (row[high] - row[low]) / 2 if spread 0: spread avg_price * 0.001 # 防止除零 # 找到均价对应的bin索引 center_idx np.searchsorted(prices, avg_price) center_idx min(center_idx, price_bins - 1) # 在均价附近生成三角分布权重 for j in range(price_bins): dist abs(prices[j] - avg_price) if dist spread: weight (1 - dist / spread) * turnover chips[j] weight # 归一化 if chips.sum() 0: chips chips / chips.sum() return prices, chips这段代码有几个关键设计点需要解释为什么用三角分布而不是均匀分布因为当天的成交价往往集中在均价附近极端高价和极端低价成交的量相对较少。三角分布能更好地模拟这种中间多、两边少的形态。如果你追求更高精度可以用正态分布但计算量会大一些。为什么衰减要在新增之前做因为当天的换手是发生在当天交易过程中的老筹码先被换掉然后新筹码才进来。顺序反过来会导致新筹码也被衰减逻辑上不对。price_bins取多少合适我试过100到500的范围。200是一个比较平衡的值分辨率够用计算也不会太慢。如果你做的是分钟级的高频数据可以适当加大到500。3.3 Winner函数的实现有了筹码分布Winner函数的实现就非常直接了def winner_function(prices, chips, current_price): 计算当前价格的获利盘比例 mask prices current_price if chips[mask].sum() 0: return 0.0 return chips[mask].sum() / chips.sum() def winner_curve(prices, chips): 计算完整的价格-Winner曲线 winners np.zeros(len(prices)) cumulative 0.0 total chips.sum() for i in range(len(prices)): cumulative chips[i] winners[i] cumulative / total return winners注意这里winner_curve用的是累加的方式比每次重新计算mask要快很多。当price_bins到500以上时这个优化能省不少时间。3.4 可视化把筹码分布和Winner曲线画在一起画图这部分我习惯用双轴图左边是筹码分布直方图右边是Winner曲线。这样一眼就能看出当前价格在筹码结构中的位置。def plot_chip_distribution(prices, chips, winners, current_price): fig, ax1 plt.subplots(figsize(14, 7)) # 筹码分布水平柱状图 ax1.barh(prices, chips, height(prices[1]-prices[0])*0.8, colorsteelblue, alpha0.6, label筹码分布) ax1.axhline(ycurrent_price, colorred, linestyle--, linewidth1.5, labelf当前价 {current_price:.2f}) ax1.set_xlabel(筹码占比) ax1.set_ylabel(价格) ax1.legend(locupper left) # Winner曲线双轴 ax2 ax1.twiny() ax2.plot(winners, prices, colororange, linewidth2, labelWinner曲线) ax2.set_xlabel(获利盘比例) ax2.legend(locupper right) plt.title(筹码分布与Winner曲线) plt.tight_layout() plt.show()跑出来的图横着看是筹码分布橙色曲线是Winner函数。当前价对应的Winner值就是获利盘比例。如果这个值在0.7以上说明大部分筹码都在获利抛压可能较大如果在0.3以下说明大部分筹码套牢反弹时压力重重。4. 实战中的参数调优与常见坑4.1 衰减因子到底怎么选这是被问得最多的问题。我的经验是不要迷信固定值要根据标的的换手特征来调。对于换手率常年在1%以下的低换手标的比如一些大盘蓝筹衰减因子可以设小一点0.5到0.8让老筹码保留更多分布图更能反映长期成本结构。对于换手率经常在5%以上的高换手标的比如小盘题材股衰减因子可以设大一点1.2到1.5让筹码分布更快反映近期变化。我一般会同时跑三个衰减因子0.5、1.0、1.5对比看哪个的图形更符合价格实际走势。如果某个参数下筹码密集区正好对应了历史上的重要支撑压力位那这个参数就是合适的。4.2 数据频率的选择日线还是分钟线日线数据适合做中长线的筹码分析计算量小噪音少。分钟线数据适合做短线但计算量大很多而且需要处理更多的异常值。我实测下来对于大多数个人投资者日线数据已经足够。如果你做的是日内交易可以用30分钟或60分钟线但要注意衰减因子需要相应调小因为分钟级的换手率数值和日线不是一个量级。4.3 除权除息的处理这是一个很容易被忽略的坑。如果标的发生了除权除息价格会出现跳空但你的筹码分布还在用除权前的价格计算结果就会完全错乱。处理方法有两种一是用前复权数据把所有历史价格调整到当前价格体系下二是用后复权数据但这样当前价格就不是真实交易价格了。我推荐用前复权因为这样当前价格是真实的Winner函数的输出也更直观。# 如果用的是akshare可以这样获取前复权数据 # df ak.stock_zh_a_hist(symbol000001, perioddaily, adjustqfq)4.4 停牌和涨跌停的处理停牌期间没有成交量换手率为0筹码分布不变这没问题。但涨跌停时换手率可能很低但价格波动很大这时候筹码分布可能会出现不连续的跳跃。我的处理方式是对于涨跌停日如果换手率低于0.5%就把当天的筹码新增量设为一个很小的值避免在极端价格上堆积不合理的筹码。这个阈值可以根据标的的历史波动特征调整。4.5 计算性能优化如果你的数据有几千条price_bins设了500那双重循环会跑得比较慢。我试过几种优化方式用numpy的向量化操作替代内层循环把三角分布的计算预先做成查找表用numba做JIT加速对于日线数据几千条记录优化后基本能在1秒内跑完。如果你只是偶尔跑一次不优化也能接受大概几秒钟。5. 怎么用筹码分布图做支撑压力判断5.1 筹码密集区就是天然的支撑压力位这是最核心的用法。筹码分布图上某个价格区间的筹码量特别大说明大量持有者的成本在这个区间。当价格跌到这个区间时这些持有者不愿意亏钱卖就会形成支撑当价格涨到这个区间时之前套牢的人可能急于解套就会形成压力。具体判断标准强支撑筹码密集区在 current_price 下方且 Winner(current_price) 在0.5到0.8之间。说明大部分筹码获利但还有一部分套牢盘在上方抛压可控。强压力筹码密集区在 current_price 上方且 Winner(current_price) 低于0.3。说明大部分筹码套牢反弹时解套抛压很重。突破信号价格放量突破筹码密集区且 Winner 曲线快速上升。说明套牢盘被消化上方空间打开。5.2 Winner曲线的斜率比数值更重要很多人只看Winner的绝对值比如获利盘80%了该跑了。但更重要的其实是曲线的斜率。如果Winner曲线在某段价格区间非常陡峭说明这个区间筹码很少价格很容易快速穿过。如果曲线很平缓说明这个区间筹码密集价格要突破需要很大的成交量。我习惯把Winner曲线和筹码分布图叠加看曲线陡峭的地方对应筹码分布的山谷曲线平缓的地方对应山峰。山峰就是支撑压力位山谷就是快速通道。5.3 结合成交量验证筹码分布图本身是静态的它告诉你筹码在哪里但不告诉你筹码会不会动。要判断支撑压力是否有效必须结合成交量。如果价格跌到筹码密集区成交量萎缩说明卖压不大支撑有效。如果成交量放大说明有人在集中抛售支撑可能被击穿。反过来价格涨到筹码密集区如果放量突破说明买盘强劲压力被消化如果缩量滞涨说明抛压沉重压力有效。5.4 一个完整的判断流程我自己的操作流程是这样的先看筹码分布图找出主要的筹码密集区标记为潜在的支撑压力位。看当前价格在哪个位置计算Winner值判断整体获利盘情况。看Winner曲线在关键位置的斜率判断突破难度。结合近期成交量判断筹码密集区的有效性。如果价格在筹码密集区附近出现明显的缩量或放量信号就是入场或离场的参考点。这套流程不是万能的但比单纯看K线要靠谱得多。至少你知道自己在跟什么样的筹码结构打交道。6. 把筹码分布图接入你的日常复盘流程6.1 批量计算多只标的的筹码分布如果你每天要复盘几十只标的一个个手动跑太慢了。可以写一个批量处理的脚本def batch_analysis(symbols, start_date, end_date): results {} for symbol in symbols: df get_stock_data(symbol, start_date, end_date) if df is None or len(df) 60: continue prices, chips calc_chip_distribution(df) winners winner_curve(prices, chips) current_price df[close].iloc[-1] winner_val winner_function(prices, chips, current_price) results[symbol] { current_price: current_price, winner: winner_val, prices: prices, chips: chips, winners: winners } return results跑完之后你可以按Winner值排序快速找出获利盘过高或过低的标的作为进一步分析的候选。6.2 保存和对比历史筹码分布筹码分布是动态变化的今天的分布和一个月前的分布可能完全不同。我习惯每周保存一次筹码分布数据然后对比看筹码的迁移方向。如果筹码在逐步向上迁移说明有人在不断买入推高成本这是健康的上涨结构。如果筹码在高位快速堆积说明追高的人很多风险在积累。如果筹码在低位密集说明有人在悄悄吸筹可能有机会。# 保存筹码分布 np.savez(fchip_{symbol}_{date}.npz, pricesprices, chipschips) # 加载对比 old np.load(fchip_{symbol}_{old_date}.npz) old_chips old[chips] # 对比 old_chips 和 chips 的差异6.3 结合其他指标做交叉验证筹码分布不是孤立的指标它最好和均线、成交量、MACD等配合使用。我自己的习惯是筹码密集区 均线支撑 强支撑筹码密集区 放量突破 有效突破Winner值 MACD背离 反转信号但要注意任何指标都有失效的时候。筹码分布的最大价值是提供一个成本结构的视角让你知道市场参与者的平均持仓成本在哪里。它不能预测未来但能帮你理解现在。6.4 常见问题排查在实际使用中你可能会遇到这些问题问题可能原因解决方案筹码分布图全是尖峰price_bins太小增大到200以上Winner值一直是1或0价格范围设置不对检查price_min和price_max图形和价格走势对不上没有做前复权用前复权数据重新计算计算速度太慢数据量太大或bins太多减少bins或优化循环筹码分布不更新换手率字段为0检查数据源用成交量估算提示如果你用的是免费数据源换手率字段有时会缺失或为0。这时候可以用成交量除以流通股本手动计算流通股本可以从公开信息中获取。7. 一些个人体会和后续扩展方向这套筹码分布加Winner函数的方案我从去年开始在自己的复盘流程里用最大的感受是它强迫你从价格思维切换到成本思维。以前看K线看到的是价格在涨在跌现在看筹码分布看到的是人在买在卖成本在迁移。有几个细节我觉得值得反复琢磨。一是衰减因子的选择它本质上是在回答你认为多久以前的筹码还有参考价值这个问题。对于短线交易者可能一周前的筹码就不重要了对于长线投资者半年前的筹码可能还在影响当前走势。二是Winner曲线的形态它比数值本身更有信息量。一条平滑上升的Winner曲线和一条阶梯状上升的曲线背后的筹码结构完全不同。后续我打算在这几个方向继续折腾一是把筹码分布做成动态图按时间轴播放看筹码是怎么一步步迁移的二是把Winner函数和机器学习结合用历史数据训练一个支撑压力位的预测模型三是把整套逻辑封装成一个简单的桌面工具不用每次跑代码。如果你也在用类似的方法或者有更好的思路欢迎交流。这个领域没有标准答案每个人的参数和用法都不一样多试多调才是正道。