1. 理解筹码分布与获利比率的核心逻辑我第一次接触winner函数时也被这个看似简单却内涵丰富的指标吸引住了。简单来说winner函数计算的是在当前收盘价下市场上有多少比例的持仓是盈利的。这个指标之所以有价值是因为它反映了市场参与者的整体盈亏状态这对判断市场情绪和潜在买卖压力非常有帮助。要理解winner函数首先得搞清楚筹码分布这个概念。筹码分布描述的是市场上持仓成本的价格分布情况。想象一下市场上每个投资者买入股票的价格都不一样有的在10元买的有的在12元买的。筹码分布就是统计这些不同买入价对应的持仓量。由于我们无法获取每个投资者的真实持仓数据只能通过交易数据来估算。这里的关键假设是每日的换手率会影响筹码的重新分布。比如某只股票第一天全部筹码都在10元第二天有20%的换手率平均交易价格是11元那么第二天的筹码分布就会变成80%在10元20%在11元。2. 筹码分布的迭代计算模型理解了基本概念后我们来看具体的计算模型。筹码分布的计算是一个迭代过程需要逐日更新。让我们用一个更详细的例子来说明假设某只股票总流通股为1000万股第1天平均交易价格10元换手率0%第2天换手率20%平均交易价格11元第3天换手率30%平均交易价格12元计算过程如下第1天结束时所有1000万股的成本都是10元第2天保留的筹码1000万 × (1-20%) 800万股仍为10元成本新交易的筹码1000万 × 20% 200万股11元成本第3天从10元保留的筹码800万 × (1-30%) 560万股从11元保留的筹码200万 × (1-30%) 140万股新交易的筹码1000万 × 30% 300万股12元成本这个模型的核心在于每日的换手部分会以当天的平均价格形成新的筹码而未换手部分则保留原有成本。通过这种迭代方式我们可以构建出整个市场的持仓成本分布。3. Python实现winner函数的关键步骤现在我们来用Python实现这个计算过程。我将代码分成几个关键部分讲解3.1 数据准备首先需要获取必要的历史数据def winner_core(ContextInfo, close): close_price close[-1] # 获取最新收盘价 # 获取过去250天的成交量和成交金额数据 df ContextInfo.get_market_data([volume, amount], stock_codeContextInfo.get_universe(), skip_pausedTrue, period1d, end_timeclose.index[-1], count250) df df.loc[df[volume] ! 0] # 过滤掉停牌日 df[mean] df[amount] / df[volume] / 100 # 计算日均价这里我们获取了成交量(volume)和成交金额(amount)并计算出每日的平均交易价格(mean)。3.2 换手率处理换手率数据是关键输入需要特别注意处理# 获取换手率数据 turnover_rate ContextInfo.get_turnover_rate(ContextInfo.get_universe(), df.index[0], df.index[-1]) df[turnover] turnover_rate[000001.SZ].values df[turnover][0] 0 # 首日换手率设为0 # 计算保留比例 df[1_turnover] 1 - df[turnover] df[2_turnover] df[1_turnover][::-1].values df[3_turnover] df[2_turnover].shift(periods1) df[3_turnover][0] 1这段代码做了几件事获取历史换手率数据计算每日的保留比例(1 - 换手率)对保留比例进行时间序列处理为后续的累积计算做准备3.3 筹码分布计算这是最核心的部分实现了我们前面讨论的迭代模型# 计算累积保留比例 df[4_turnover] df[3_turnover].cumprod()[::-1].values df[turnover][0] 1 # 计算各价格区间的筹码分布 df[chouma] df[turnover] * df[4_turnover] # 计算获利比率 return df.loc[df[mean] close_price][chouma].sum()4_turnover计算的是从当日到计算日的累积保留比例而chouma则是各价格区间的筹码分布。最后我们将所有低于当前收盘价的筹码相加就得到了获利比率。4. 完整函数实现与封装为了让代码更易用我们可以将上述逻辑封装成完整的函数def winner(ContextInfo, close): result [] n len(close) for i in range(n): res winner_core(ContextInfo, close[:i1]) result.append(res) return pd.Series(result, indexclose.index)这个封装函数可以处理整个时间序列为每个交易日计算对应的获利比率。在实际使用中你可以这样调用# 假设context是回测上下文close是收盘价序列 profit_ratio winner(context, close)5. 验证与调优实现完算法后验证其准确性很重要。我们可以通过与同花顺等专业软件的数据对比来验证在我的测试中对于平安银行(000001)这样的流动性较好的股票我们的计算结果与同花顺的数据非常接近误差通常在1%以内。但对于交易不活跃的小盘股差异可能会大一些这主要是因为我们的模型假设每日换手均匀但实际上大单交易可能集中在特定价格平均价格的计算方法不同停牌和复牌日的处理方式可能有差异如果要提高准确性可以考虑以下优化方向使用Tick级数据如果能获取更精细的交易数据可以更准确地计算筹码分布考虑大单交易对大单交易赋予不同权重调整换手率计算窗口根据股票特性优化换手率的计算周期6. 实际应用场景winner函数计算出的获利比率在实际分析中非常有用以下是几个典型应用场景支撑压力分析当获利比率接近100%时可能面临获利回吐压力当接近0%时可能形成支撑市场情绪判断高获利比率可能伴随乐观情绪但也可能意味着回调风险策略信号生成可以结合其他指标构建交易信号如获利比率突破某阈值时触发交易例如你可以创建一个简单的策略def initialize(context): # 策略初始化 pass def handle_data(context, data): # 计算获利比率 profit_ratio winner(context, data.history(close, 250, 1d)) current_ratio profit_ratio[-1] # 简单策略当获利比例低于30%时买入高于70%时卖出 if current_ratio 0.3: order_target_percent(context.stock, 1.0) elif current_ratio 0.7: order_target_percent(context.stock, 0.0)7. 常见问题与解决方案在实际使用中你可能会遇到以下问题数据不足问题对于新股历史数据不足会导致计算结果不准确。解决方案是设置最小计算窗口如至少需要60个交易日数据。停牌处理股票停牌期间没有交易数据。我们的代码已经通过skip_pausedTrue参数跳过了停牌日但要注意复牌首日的换手率可能会异常高。极端行情下的失真在连续涨停或跌停时由于交易不充分平均价格可能无法反映真实成本。这时可以考虑使用限价订单簿数据来改进。计算效率优化对于全市场计算原始实现可能较慢。可以考虑以下优化# 使用numpy向量化计算 def vectorized_winner(df, close_prices): # 向量化实现代码 pass参数调优默认使用250天历史数据但对于不同特性的股票可以调整这个参数。高波动性股票可能需要更短的窗口。8. 进阶思考与扩展掌握了基础实现后你可以进一步探索以下方向多时间框架分析同时计算日线、周线、月线级别的获利比率获取更全面的市场视角。结合成交量分析给不同成交量水平下的筹码赋予不同权重比如放量突破时的筹码可能更重要。动态换手率调整根据市场波动性动态调整换手率计算窗口在波动大时使用更短窗口。板块与市场整体分析计算整个板块或市场的获利比率用于判断整体市场情绪。机器学习增强使用机器学习模型来学习专业软件中的winner函数进一步提高准确性。实现这些扩展需要更复杂的代码但核心思想仍然是基于筹码分布的基本原理。比如动态换手率调整可以这样实现def dynamic_window_winner(context, close, volatility_lookback30): # 计算历史波动率 returns np.log(close/close.shift(1)) volatility returns.rolling(volatility_lookback).std() # 根据波动率动态调整窗口 windows np.where(volatility 0.02, 60, 250) # 高波动用短窗口 result [] for i in range(len(close)): window windows[i] if np.isnan(window) or i window: result.append(np.nan) continue res winner_core(context, close[i-window:i1]) result.append(res) return pd.Series(result, indexclose.index)这个实现根据股票的历史波动率动态调整计算窗口在波动大时使用60天窗口平时使用250天窗口。
从筹码分布到获利比率:Python实战模拟通达信winner函数
1. 理解筹码分布与获利比率的核心逻辑我第一次接触winner函数时也被这个看似简单却内涵丰富的指标吸引住了。简单来说winner函数计算的是在当前收盘价下市场上有多少比例的持仓是盈利的。这个指标之所以有价值是因为它反映了市场参与者的整体盈亏状态这对判断市场情绪和潜在买卖压力非常有帮助。要理解winner函数首先得搞清楚筹码分布这个概念。筹码分布描述的是市场上持仓成本的价格分布情况。想象一下市场上每个投资者买入股票的价格都不一样有的在10元买的有的在12元买的。筹码分布就是统计这些不同买入价对应的持仓量。由于我们无法获取每个投资者的真实持仓数据只能通过交易数据来估算。这里的关键假设是每日的换手率会影响筹码的重新分布。比如某只股票第一天全部筹码都在10元第二天有20%的换手率平均交易价格是11元那么第二天的筹码分布就会变成80%在10元20%在11元。2. 筹码分布的迭代计算模型理解了基本概念后我们来看具体的计算模型。筹码分布的计算是一个迭代过程需要逐日更新。让我们用一个更详细的例子来说明假设某只股票总流通股为1000万股第1天平均交易价格10元换手率0%第2天换手率20%平均交易价格11元第3天换手率30%平均交易价格12元计算过程如下第1天结束时所有1000万股的成本都是10元第2天保留的筹码1000万 × (1-20%) 800万股仍为10元成本新交易的筹码1000万 × 20% 200万股11元成本第3天从10元保留的筹码800万 × (1-30%) 560万股从11元保留的筹码200万 × (1-30%) 140万股新交易的筹码1000万 × 30% 300万股12元成本这个模型的核心在于每日的换手部分会以当天的平均价格形成新的筹码而未换手部分则保留原有成本。通过这种迭代方式我们可以构建出整个市场的持仓成本分布。3. Python实现winner函数的关键步骤现在我们来用Python实现这个计算过程。我将代码分成几个关键部分讲解3.1 数据准备首先需要获取必要的历史数据def winner_core(ContextInfo, close): close_price close[-1] # 获取最新收盘价 # 获取过去250天的成交量和成交金额数据 df ContextInfo.get_market_data([volume, amount], stock_codeContextInfo.get_universe(), skip_pausedTrue, period1d, end_timeclose.index[-1], count250) df df.loc[df[volume] ! 0] # 过滤掉停牌日 df[mean] df[amount] / df[volume] / 100 # 计算日均价这里我们获取了成交量(volume)和成交金额(amount)并计算出每日的平均交易价格(mean)。3.2 换手率处理换手率数据是关键输入需要特别注意处理# 获取换手率数据 turnover_rate ContextInfo.get_turnover_rate(ContextInfo.get_universe(), df.index[0], df.index[-1]) df[turnover] turnover_rate[000001.SZ].values df[turnover][0] 0 # 首日换手率设为0 # 计算保留比例 df[1_turnover] 1 - df[turnover] df[2_turnover] df[1_turnover][::-1].values df[3_turnover] df[2_turnover].shift(periods1) df[3_turnover][0] 1这段代码做了几件事获取历史换手率数据计算每日的保留比例(1 - 换手率)对保留比例进行时间序列处理为后续的累积计算做准备3.3 筹码分布计算这是最核心的部分实现了我们前面讨论的迭代模型# 计算累积保留比例 df[4_turnover] df[3_turnover].cumprod()[::-1].values df[turnover][0] 1 # 计算各价格区间的筹码分布 df[chouma] df[turnover] * df[4_turnover] # 计算获利比率 return df.loc[df[mean] close_price][chouma].sum()4_turnover计算的是从当日到计算日的累积保留比例而chouma则是各价格区间的筹码分布。最后我们将所有低于当前收盘价的筹码相加就得到了获利比率。4. 完整函数实现与封装为了让代码更易用我们可以将上述逻辑封装成完整的函数def winner(ContextInfo, close): result [] n len(close) for i in range(n): res winner_core(ContextInfo, close[:i1]) result.append(res) return pd.Series(result, indexclose.index)这个封装函数可以处理整个时间序列为每个交易日计算对应的获利比率。在实际使用中你可以这样调用# 假设context是回测上下文close是收盘价序列 profit_ratio winner(context, close)5. 验证与调优实现完算法后验证其准确性很重要。我们可以通过与同花顺等专业软件的数据对比来验证在我的测试中对于平安银行(000001)这样的流动性较好的股票我们的计算结果与同花顺的数据非常接近误差通常在1%以内。但对于交易不活跃的小盘股差异可能会大一些这主要是因为我们的模型假设每日换手均匀但实际上大单交易可能集中在特定价格平均价格的计算方法不同停牌和复牌日的处理方式可能有差异如果要提高准确性可以考虑以下优化方向使用Tick级数据如果能获取更精细的交易数据可以更准确地计算筹码分布考虑大单交易对大单交易赋予不同权重调整换手率计算窗口根据股票特性优化换手率的计算周期6. 实际应用场景winner函数计算出的获利比率在实际分析中非常有用以下是几个典型应用场景支撑压力分析当获利比率接近100%时可能面临获利回吐压力当接近0%时可能形成支撑市场情绪判断高获利比率可能伴随乐观情绪但也可能意味着回调风险策略信号生成可以结合其他指标构建交易信号如获利比率突破某阈值时触发交易例如你可以创建一个简单的策略def initialize(context): # 策略初始化 pass def handle_data(context, data): # 计算获利比率 profit_ratio winner(context, data.history(close, 250, 1d)) current_ratio profit_ratio[-1] # 简单策略当获利比例低于30%时买入高于70%时卖出 if current_ratio 0.3: order_target_percent(context.stock, 1.0) elif current_ratio 0.7: order_target_percent(context.stock, 0.0)7. 常见问题与解决方案在实际使用中你可能会遇到以下问题数据不足问题对于新股历史数据不足会导致计算结果不准确。解决方案是设置最小计算窗口如至少需要60个交易日数据。停牌处理股票停牌期间没有交易数据。我们的代码已经通过skip_pausedTrue参数跳过了停牌日但要注意复牌首日的换手率可能会异常高。极端行情下的失真在连续涨停或跌停时由于交易不充分平均价格可能无法反映真实成本。这时可以考虑使用限价订单簿数据来改进。计算效率优化对于全市场计算原始实现可能较慢。可以考虑以下优化# 使用numpy向量化计算 def vectorized_winner(df, close_prices): # 向量化实现代码 pass参数调优默认使用250天历史数据但对于不同特性的股票可以调整这个参数。高波动性股票可能需要更短的窗口。8. 进阶思考与扩展掌握了基础实现后你可以进一步探索以下方向多时间框架分析同时计算日线、周线、月线级别的获利比率获取更全面的市场视角。结合成交量分析给不同成交量水平下的筹码赋予不同权重比如放量突破时的筹码可能更重要。动态换手率调整根据市场波动性动态调整换手率计算窗口在波动大时使用更短窗口。板块与市场整体分析计算整个板块或市场的获利比率用于判断整体市场情绪。机器学习增强使用机器学习模型来学习专业软件中的winner函数进一步提高准确性。实现这些扩展需要更复杂的代码但核心思想仍然是基于筹码分布的基本原理。比如动态换手率调整可以这样实现def dynamic_window_winner(context, close, volatility_lookback30): # 计算历史波动率 returns np.log(close/close.shift(1)) volatility returns.rolling(volatility_lookback).std() # 根据波动率动态调整窗口 windows np.where(volatility 0.02, 60, 250) # 高波动用短窗口 result [] for i in range(len(close)): window windows[i] if np.isnan(window) or i window: result.append(np.nan) continue res winner_core(context, close[i-window:i1]) result.append(res) return pd.Series(result, indexclose.index)这个实现根据股票的历史波动率动态调整计算窗口在波动大时使用60天窗口平时使用250天窗口。