涨跌家数指标:情绪线的起点

各位同学,今天我们来聊聊量化交易里最基础、也最直观的情绪指标——涨跌家数比。

说白了,这个指标就是数一数今天有多少股票涨了,多少跌了。然后算个比值。听起来简单吧?但就是这么一个简单的数字,背后藏着市场情绪的密码。

我个人习惯把这个指标叫做「市场呼吸灯」。为什么?因为它能直观反映市场是「喘得上气」还是「憋得慌」。牛市里涨的家数多,熊市里跌的家数多,这个道理谁都懂。但怎么把这个感觉变成可量化的信号,才是我们今天要解决的问题。

一、涨跌家数比的定义

先给个公式,大家一看就明白:

涨跌家数比 = 当日上涨家数 / 当日下跌家数

举个例子:今天沪深两市有3000只股票上涨,1500只下跌,那么涨跌比就是2.0。这意味着每2只股票上涨,对应1只股票下跌。市场情绪偏乐观。

如果反过来,1000只涨,3500只跌,比值就是0.286。嗯,这时候你该警惕了。

我在项目中遇到过一个问题:有些同学直接用「上涨家数 - 下跌家数」来做差值。我个人不建议这么做。为什么?因为差值受股票总数影响太大。十年前A股只有2000只股票,现在快5000只了。同样的差值,在不同时期代表的意义完全不同。用比值,天然做了归一化处理,更稳定。

二、数据获取与计算

好,理论讲完了,咱们直接上代码。我习惯用Tushare或者AKShare来获取数据。这里以AKShare为例,因为它免费且数据全。

import akshare as ak
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 获取全市场涨跌家数数据
def get_market_sentiment(date):
    """
    获取指定日期的涨跌家数
    """
    df = ak.stock_zh_a_spot_em()
    
    # 计算涨跌家数
    up_count = len(df[df['涨跌幅'] > 0])
    down_count = len(df[df['涨跌幅'] < 0])
    flat_count = len(df[df['涨跌幅'] == 0])
    
    # 计算比值
    if down_count > 0:
        ratio = up_count / down_count
    else:
        ratio = np.inf  # 极端情况,没有下跌的股票
    
    return {
        'date': date,
        'up': up_count,
        'down': down_count,
        'flat': flat_count,
        'ratio': ratio
    }

# 测试一下
result = get_market_sentiment('2024-01-15')
print(f"上涨: {result['up']}, 下跌: {result['down']}, 涨跌比: {result['ratio']:.2f}")

这里有个细节要注意:平盘的家数怎么处理?我一般直接忽略,因为平盘的股票数量很少,对整体比值影响不大。但如果你做高频分析,可以考虑把平盘家数按0.5:0.5的比例分摊到涨跌两边。

⚠️ 避坑指南: 我曾经在数据清洗时踩过一个坑——有些股票当天停牌,涨跌幅为0。这些股票不应该算在「平盘」里,因为它们根本没有交易。一定要先过滤掉停牌股票,再计算涨跌家数。

三、构建基础情绪线

单日的涨跌比波动太大,直接看容易眼花。我建议做两个处理:

  1. 移动平均平滑:用5日或10日均线把毛刺去掉
  2. 归一化到0-100区间:方便和其他指标对比

来看代码实现:

def build_sentiment_line(start_date, end_date, window=5):
    """
    构建基础情绪线
    """
    # 获取日期序列
    dates = pd.date_range(start_date, end_date, freq='B')
    
    sentiment_data = []
    for date in dates:
        try:
            result = get_market_sentiment(date.strftime('%Y-%m-%d'))
            sentiment_data.append(result)
        except:
            continue
    
    df = pd.DataFrame(sentiment_data)
    
    # 计算移动平均
    df['ma_ratio'] = df['ratio'].rolling(window=window).mean()
    
    # 归一化处理(将比值映射到0-100)
    min_ratio = df['ma_ratio'].min()
    max_ratio = df['ma_ratio'].max()
    df['sentiment_score'] = (df['ma_ratio'] - min_ratio) / (max_ratio - min_ratio) * 100
    
    return df

# 生成情绪线
sentiment_df = build_sentiment_line('2023-01-01', '2024-01-01')
print(sentiment_df.tail())

你想想看,这个情绪线有什么意义?当sentiment_score低于20时,说明市场极度悲观,可能接近冰点。高于80时,说明市场极度亢奋,可能接近沸点。这就是我们后面要讲的「冰点-沸点」交易策略的基础。

四、核心逻辑框架

为了让大家更直观地理解整个体系,我画了一张流程图:

涨跌家数指标 → 基础情绪线 核心流程 Step 1: 数据获取 AKShare 全市场行情 Step 2: 计算涨跌比 上涨家数 / 下跌家数 Step 3: 平滑处理 5日/10日移动平均 Step 4: 归一化 映射到 0-100 区间 Step 5: 情绪线 sentiment_score 📊 基础情绪线构建完成 冰点区域 (0-20) 沸点区域 (80-100)

五、实战中的注意事项

讲完了理论和方法,我来分享几个实战中容易踩的坑:

💡 核心要点:

  • 数据源一致性:不同数据源对「全市场」的定义不同。有的包含北交所,有的不包含。我建议固定使用一个数据源,不要混用。
  • 新股处理:新股上市首日涨跌幅不受限制,会严重干扰比值。我一般会剔除上市不满20个交易日的股票。
  • 极端值处理:当下跌家数为0时,比值会变成无穷大。这种情况虽然罕见,但代码里一定要做保护。

📌 我的个人习惯:

我会把涨跌比和成交量结合起来看。如果涨跌比很高但成交量萎缩,说明市场只是局部热点,不是全面上涨。这种时候要小心追高。

六、情绪线的可视化

最后,我们来看看怎么把情绪线画出来。好的可视化能让你一眼看出市场的情绪状态。

def plot_sentiment_line(df):
    """
    绘制情绪线
    """
    fig, ax = plt.subplots(figsize=(12, 6))
    
    # 绘制情绪线
    ax.plot(df['date'], df['sentiment_score'], 
            color='#3498db', linewidth=2, label='情绪指数')
    
    # 绘制冰点/沸点区域
    ax.axhspan(0, 20, alpha=0.2, color='red', label='冰点区域')
    ax.axhspan(80, 100, alpha=0.2, color='green', label='沸点区域')
    
    # 添加参考线
    ax.axhline(y=50, color='gray', linestyle='--', alpha=0.5)
    
    ax.set_title('市场情绪线(涨跌家数比)', fontsize=16)
    ax.set_ylabel('情绪指数')
    ax.legend()
    plt.show()

# 调用函数
plot_sentiment_line(sentiment_df)

嗯,到这里,基础情绪线的构建就讲完了。你可能会问:这个指标单独用够不够?说实话,不够。它只是一个基础信号,需要和其他指标配合使用。但作为起点,它已经足够优秀了。

记住一句话:涨跌家数比是市场的体温计,不是诊断书。它能告诉你市场「发烧了」,但具体是什么病,还需要进一步检查。

下一节,我们会在这个基础上,加入成交量、涨跌停家数等维度,构建更立体的情绪指标体系。但今天,先把基础打牢。

📊 今日作业:

  1. 用AKShare获取最近30个交易日的涨跌家数数据
  2. 计算每日涨跌比,并用5日均线平滑
  3. 找出最近一个月内,情绪指数低于20和高于80的日期
  4. 对照这些日期的市场走势,看看冰点和沸点是否准确

公众号:蓝海数据掘金营,微信deep3321