用Pandas shift()搞定股票数据分析:从日涨跌幅到移动平均线

金融数据分析的核心在于从时间序列中提取有价值的信息。Pandas作为Python生态中最强大的数据分析工具,其shift()函数配合diff()、rolling()等方法,能够高效完成从基础涨跌幅计算到复杂技术指标生成的全套流程。本文将手把手带您实现股票数据分析的完整特征工程路径。

1. 构建虚拟股票数据集

任何分析都需要从数据开始。我们先模拟一个包含日期和收盘价的股票数据集:

import pandas as pd
import numpy as np

# 设置随机种子保证可复现
np.random.seed(42)  

# 生成180个交易日的虚拟数据
dates = pd.date_range("2023-01-01", periods=180, freq='B')  # B表示工作日
prices = np.cumprod(1 + np.random.normal(0.001, 0.02, 180)) * 100  # 几何布朗运动模拟股价

df = pd.DataFrame({
    'Date': dates,
    'Close': np.round(prices, 2)
}).set_index('Date')

print(df.head())

输出示例:

               Close
Date                
2023-01-02   100.00
2023-01-03    98.45
2023-01-04    96.33
2023-01-05    94.72
2023-01-06    93.18

提示:实际应用中可直接用yfinance等库获取真实股票数据,但虚拟数据更适合教学演示。

2. 基础特征工程:涨跌幅计算

2.1 日收益率计算

日收益率是金融分析中最基础的特征,有两种常见计算方式:

  • 简单收益率:(今日收盘价 - 昨日收盘价) / 昨日收盘价
  • 对数收益率:ln(今日收盘价 / 昨日收盘价)
# 方法一:使用diff()
df['Simple_Return'] = df['Close'].diff() / df['Close'].shift(1)

# 方法二:使用pct_change()
df['Simple_Return_Alt'] = df['Close'].pct_change()

# 对数收益率
df['Log_Return'] = np.log(df['Close'] / df['Close'].shift(1))

print(df[['Close', 'Simple_Return', 'Log_Return']].head(3))

关键点说明:

  • shift(1)将数据向下移动1行,使昨日收盘价与今日对齐
  • 首日数据因无前值会产生NaN,这是正常现象

2.2 多周期收益率比较

计算不同时间跨度的收益率可观察不同投资周期的表现:

# 计算5日收益率
df['5d_Return'] = df['Close'].pct_change(5)

# 计算20日收益率(约1个月)
df['20d_Return'] = df['Close'].pct_change(20)

# 可视化对比
df[['Simple_Return', '5d_Return', '20d_Return']].plot(figsize=(12,6), alpha=0.7)
plt.title('不同周期收益率对比')
plt.ylabel('收益率');

3. 技术指标生成:移动平均线

3.1 简单移动平均线(SMA)

移动平均线是平滑价格波动的经典指标,计算窗口内的平均值:

# 计算5日和20日移动平均
df['SMA_5'] = df['Close'].rolling(window=5).mean()
df['SMA_20'] = df['Close'].rolling(window=20).mean()

# 可视化
df[['Close', 'SMA_5', 'SMA_20']].plot(figsize=(12,6))
plt.title('价格与移动平均线');

3.2 指数移动平均线(EMA)

EMA给予近期价格更高权重,对价格变化更敏感:

df['EMA_12'] = df['Close'].ewm(span=12, adjust=False).mean()
df['EMA_26'] = df['Close'].ewm(span=26, adjust=False).mean()

# MACD指标计算
df['MACD'] = df['EMA_12'] - df['EMA_26']
df['Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()

3.3 布林带(Bollinger Bands)

布林带结合移动平均和标准差,反映价格波动范围:

# 计算中轨、上轨和下轨
df['Middle_Band'] = df['Close'].rolling(20).mean()
df['Upper_Band'] = df['Middle_Band'] + 2*df['Close'].rolling(20).std() 
df['Lower_Band'] = df['Middle_Band'] - 2*df['Close'].rolling(20).std()

# 可视化
df[['Close', 'Middle_Band', 'Upper_Band', 'Lower_Band']].plot(figsize=(12,6))
plt.fill_between(df.index, df['Lower_Band'], df['Upper_Band'], alpha=0.1)
plt.title('布林带指标');

4. 高级特征工程

4.1 波动率计算

波动率是风险评估的重要指标:

# 历史波动率(20日年化)
df['Volatility_20d'] = df['Log_Return'].rolling(20).std() * np.sqrt(252)

# 实现波动率(过去5日)
df['Realized_Vol_5d'] = df['Log_Return'].rolling(5).std() * np.sqrt(252)

# 可视化
df[['Volatility_20d', 'Realized_Vol_5d']].plot(figsize=(12,4))
plt.title('波动率指标对比');

4.2 相对强弱指数(RSI)

RSI反映超买超卖状态,计算14日RSI:

delta = df['Close'].diff()
gain = delta.where(delta > 0, 0)
loss = -delta.where(delta < 0, 0)

avg_gain = gain.rolling(14).mean()
avg_loss = loss.rolling(14).mean()

rs = avg_gain / avg_loss
df['RSI'] = 100 - (100 / (1 + rs))

# 可视化
df['RSI'].plot(figsize=(12,4), ylim=[0,100])
plt.axhline(70, color='r', linestyle='--')
plt.axhline(30, color='g', linestyle='--')
plt.title('相对强弱指数(RSI)');

4.3 交易信号生成

结合多个指标生成交易信号:

# 金叉/死叉信号
df['SMA_Signal'] = np.where(df['SMA_5'] > df['SMA_20'], 1, -1)

# MACD信号
df['MACD_Signal'] = np.where(df['MACD'] > df['Signal'], 1, -1)

# 布林带信号
df['BB_Signal'] = np.where(df['Close'] < df['Lower_Band'], 1, 
                          np.where(df['Close'] > df['Upper_Band'], -1, 0))

# 综合信号
df['Composite_Signal'] = (df['SMA_Signal'] + df['MACD_Signal'] + df['BB_Signal']) / 3

5. 可视化与分析

5.1 多指标综合视图

fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(15,10), gridspec_kw={'height_ratios': [3,1,1]})

# 价格和均线
df[['Close', 'SMA_5', 'SMA_20']].plot(ax=ax1)
ax1.set_title('价格与技术指标')

# MACD
df[['MACD', 'Signal']].plot(ax=ax2)
ax2.axhline(0, color='gray', linestyle='--')

# RSI
df['RSI'].plot(ax=ax3, ylim=[0,100])
ax3.axhline(70, color='r', linestyle='--')
ax3.axhline(30, color='g', linestyle='--')

plt.tight_layout()

5.2 回测简单策略

# 假设当综合信号>0时买入,<0时卖出
df['Strategy_Return'] = df['Simple_Return'] * df['Composite_Signal'].shift(1)

# 计算累计收益
df['Cum_Market'] = (1 + df['Simple_Return']).cumprod()
df['Cum_Strategy'] = (1 + df['Strategy_Return']).cumprod()

# 可视化
df[['Cum_Market', 'Cum_Strategy']].plot(figsize=(12,6))
plt.title('策略回测结果')
plt.ylabel('累计收益率');

6. 实战技巧与优化建议

  1. 处理缺失值:移动窗口计算会产生前N天的NaN值,可用dropna()或指定min_periods参数:

    df['SMA_20'] = df['Close'].rolling(20, min_periods=10).mean()
    
  2. 性能优化:大数据量时避免重复计算:

    # 不好的做法:重复计算rolling
    df['A'] = df['Close'].rolling(20).mean()
    df['B'] = df['Close'].rolling(20).std()
    
    # 好的做法:先创建rolling对象
    roller = df['Close'].rolling(20)
    df['A'] = roller.mean()
    df['B'] = roller.std()
    
  3. 多股票分析:使用groupby处理多只股票:

    # 假设df包含'Ticker'列
    df.groupby('Ticker')['Close'].rolling(20).mean()
    
  4. 实时更新:增量计算最新指标:

    last_20 = df['Close'].iloc[-20:]
    current_sma = last_20.mean()
    
  5. 参数优化:用网格搜索寻找最佳参数组合:

    from sklearn.model_selection import ParameterGrid
    
    param_grid = {
        'sma_short': range(5, 20, 5),
        'sma_long': range(20, 60, 10)
    }
    
    for params in ParameterGrid(param_grid):
        df[f"SMA_{params['sma_short']}"] = df['Close'].rolling(params['sma_short']).mean()
        df[f"SMA_{params['sma_long']}"] = df['Close'].rolling(params['sma_long']).mean()
        # 评估策略表现...
    

通过Pandas这一系列时间序列操作,我们实现了从基础数据预处理到复杂技术指标生成的完整流程。实际应用中,这些技术可扩展到期权定价、风险价值计算等更复杂的金融工程领域。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐