4. 布林带多周期突破信号

任务目标

本案例实现一个基于日、周、月三个周期布林带(Bollinger Bands)的突破信号生成器。当收盘价同时突破日线与周线的布林带上轨时产生“强突破买入”信号;当收盘价同时跌破日线与周线的下轨时产生“强突破卖出”信号。月线布林带作为趋势过滤条件——仅当价格位于月线中轨上方时允许买入信号,位于月线中轨下方时允许卖出信号,从而过滤逆大势的假突破。

实现方案

使用Python与pandas实现。布林带参数采用20日/20周/20月周期,标准差倍数取2。数据源使用yfinance获取股票日线行情,通过resample将日线聚合为周线与月线。核心逻辑为:计算三个周期的布林带上下轨及中轨,将周/月布林带值通过reindex与ffill对齐到日线,最后按上述规则生成信号。

完整代码

import pandas as pd
import numpy as np
import yfinance as yf

def add_bollinger(df, window=20, num_std=2):
    """计算布林带,返回带中轨/上轨/下轨的DataFrame"""
    df = df.copy()
    df['mid'] = df['Close'].rolling(window).mean()
    std = df['Close'].rolling(window).std()
    df['upper'] = df['mid'] + num_std * std
    df['lower'] = df['mid'] - num_std * std
    return df

def resample_weekly_monthly(df):
    """将日线聚合为周线与月线(取每周/每月最后一个交易日)"""
    df.index = pd.to_datetime(df.index)
    weekly = df['Close'].resample('W').last().dropna()
    monthly = df['Close'].resample('ME').last().dropna()
    return weekly, monthly

def generate_signals(ticker, start='2023-01-01', end='2024-12-31'):
    # 1. 获取日线数据
    raw = yf.download(ticker, start=start, end=end, progress=False)
    if raw.empty:
        raise ValueError(f"未获取到 {ticker} 的数据,请检查代码或日期范围")
    daily = raw[['Close']].copy()
    daily.columns = ['Close']

    # 2. 计算日线布林带
    daily_boll = add_bollinger(daily)

    # 3. 聚合周线与月线,并计算各自布林带
    weekly_series, monthly_series = resample_weekly_monthly(daily)
    weekly_df = add_bollinger(weekly_series.to_frame('Close'))
    monthly_df = add_bollinger(monthly_series.to_frame('Close'))

    # 4. 将周/月布林带对齐到日线(向前填充)
    weekly_boll = weekly_df[['mid', 'upper', 'lower']].reindex(
        daily.index, method='ffill')
    monthly_boll = monthly_df[['mid', 'upper', 'lower']].reindex(
        daily.index, method='ffill')

    # 5. 合并所有布林带值
    result = pd.DataFrame(index=daily.index)
    result['Close'] = daily['Close']
    result['D_mid'] = daily_boll['mid']
    result['D_upper'] = daily_boll['upper']
    result['D_lower'] = daily_boll['lower']
    result['W_upper'] = weekly_boll['upper']
    result['W_lower'] = weekly_boll['lower']
    result['M_mid'] = monthly_boll['mid']

    # 6. 生成信号
    # 买入:收盘价突破日上轨 AND 突破周上轨 AND 收盘价 > 月中轨
    buy_cond = (result['Close'] > result['D_upper']) & \
               (result['Close'] > result['W_upper']) & \
               (result['Close'] > result['M_mid'])
    # 卖出:收盘价跌破日下轨 AND 跌破周下轨 AND 收盘价 < 月中轨
    sell_cond = (result['Close'] < result['D_lower']) & \
                (result['Close'] < result['W_lower']) & \
                (result['Close'] < result['M_mid'])

    result['Signal'] = 0
    result.loc[buy_cond, 'Signal'] = 1
    result.loc[sell_cond, 'Signal'] = -1

    # 7. 仅保留有信号的日期
    signals = result[result['Signal'] != 0]
    return result, signals

if __name__ == '__main__':
    # 示例:以苹果股票为例
    full_data, signal_days = generate_signals('AAPL')
    print(f"总交易日数: {len(full_data)}")
    print(f"产生信号天数: {len(signal_days)}")
    if not signal_days.empty:
        print("\n信号明细(前10条):")
        print(signal_days[['Close', 'D_upper', 'W_upper', 'M_mid', 'Signal']].head(10))
    else:
        print("所选时间范围内无突破信号")

运行方式

  1. 安装依赖:pip install pandas numpy yfinance
  2. 将上述代码保存为 bollinger_signal.py
  3. 运行:python bollinger_signal.py

验证方法

程序运行后应输出总交易日数(通常为数百天)和信号天数。若信号天数大于0,则打印前10条信号明细。判定通过的准则:

  • 程序无异常退出,且输出的信号明细中每条记录的Close均大于对应D_upper和W_upper(买入信号)或小于D_lower和W_lower(卖出信号);
  • 买入信号的Close均大于M_mid,卖出信号的Close均小于M_mid;
  • 若信号数为0,说明该股票在所选区间内未出现同时满足多周期条件的极端突破,可更换股票代码(如'MSFT'、'600519.SS')或扩大日期范围重新验证。

常见问题

  • 周线/月线数据不足:若起始日期距今不足20周或20个月,对应布林带会出现NaN,导致无法产生信号。建议将start参数至少提前6个月。
  • 数据源限制:yfinance对部分A股代码支持有限,若下载失败可改用美股代码或使用本地CSV数据替换下载部分。
  • 信号延迟:周/月布林带使用ffill填充,意味着当周/月布林带尚未收盘更新时,使用的是上一周期的值,这是避免未来函数的必要处理。