Python量化:基于股票数据筛选和计算特征 -3

Python量化:基于股票数据筛选和计算特征 -3

由于最近正在搭建我的量化框架和基本项目,所以在此出一点小小的教程并附加代码

brief introduction

本期主要目的:

  1. 对原始数据进行预处理(排序、填充、去离群、标准化)
  2. 按股票分组,计算额外的滚动统计指标
  3. 拼接原始数据和新指标,得到增强后的训练集

代码定义的主要几个函数

  1. fillna_interpolate_by_stock: 按股票对数据进行插值填充缺失值
  2. outlier_mad_by_stock: 按股票去除离群点
  3. z_score_by_stock: 按股票进行标准化

代码的主要包含内容

  1. 读取训练数据train.csv
  2. 对数据按stock_id和time_id排序
  3. 调用fillna_interpolate_by_stock填充缺失值并插值
  4. 调用outlier_mad_by_stock按股票去除离群点
  5. 调用z_score_by_stock按股票标准化
  6. 对数据按stock_id和time_id重新排序
  7. 设置目标变量label
  8. 对数据进行拷贝,删除time_id和label
  9. 初始化空DataFrame存储结果
  10. 定义calculate_new_columns函数计算滚动统计量如5天均值、10天均值等
  11. 按股票分组,逐股票调用calculate_new_columns函数计算新的指标
  12. 拼接所有股票的统计量
  13. 填充NaN值
  14. 找到原始数据和新计算指标的公共列
  15. 从新数据中删除公共列
  16. 拼接原始数据和新数据
  17. 将结果保存到新CSV文件

主要功能详解

异常值缺失值处理

包含三个问题:为什么要进行处理、处理的好处在哪、如何进行处理

  • 为什么要进行预处理

    原始数据存在异常值缺失值,会极大影响后续的数据分析步骤,对数据分析和建模产生不良影响。

    案例:如果需要计算一百个人的平均收入,其中一个人的收入是100w,其他人都是1元,100w的缺失会极大影响平均值,收入1元个人在计算平均值的时候也是不可缺失的,因为缺少这个人,平均值会不可避免升高,但是实际衡量中我们需要考虑的是剩下来99个人的情况,因此,需要剔除异常值检查缺失值。

  • 预处理好处

    减少其对分析和建模结果的干扰,提高模型的质量和可靠性。

  • 如何进行预处理

    1. 检测异常值:通过统计方法、可视化探索或基于模型的方法来识别异常值。
    2. 确认异常值:验证异常值是否真的是数据中的异常,而不是错误或特殊情况。
    3. 处理异常值:根据数据集和分析目标,选择适当的处理方法,如删除异常值、替换为缺失值或进行数据转换。

标准化的作用

  1. 单位一致性:数据集中的特征可能以不同的单位(如米、千米、温度、货币等)进行度量,这些单位的差异可能会对算法的性能产生影响。通过标准化,我们可以消除单位对模型的影响,使得算法能够公平地对待所有的特征。
  2. 算法性能:许多机器学习算法(如支持向量机、逻辑回归、神经网络等)都假设所有的特征都是在同一尺度上。如果特征的尺度差异很大,不进行标准化可能会导致算法性能下降。
  3. 梯度下降:对于使用梯度下降方法优化的算法,如果特征的尺度差异很大,可能会导致优化过程变得困难,收敛速度变慢。
  4. 正则化:正则化是一种用于防止过拟合的技术,它通过向模型的损失函数添加一个惩罚项来限制模型的复杂度。但是,如果数据的尺度不一,正则化可能会对较大尺度的特征施加过多的惩罚,导致模型性能下降。
  5. 解释性:在某些情况下,标准化可以提高模型的解释性。例如,在线性回归模型中,标准化后的特征系数可以直接反映每个特征对预测结果的影响大小。

– 来自GPT4

滚动指标的计算

需要计算哪些滚动指标

包含两问题:有哪些指标可以计算、如何衡量指标的效用

  • 有哪些指标可以计算

    典型的:均线(5,10,30,60…), 动量,MACD…

  • 如何衡量指标的效用

​ 待续…

代码部分

数据案例(详细的案例数据在我的公众号:梦云星)

time_idstock_idfactor(1-300)lable
110.20.1

涉及到的包

import pandas as pd
import numpy as np
from tqdm import tqdm

缺失值填充

def fillna_interpolate_by_stock(data: DataFrame) -> DataFrame:
    """
    对每个股票的数据使用线性插值来填充 DataFrame 中的缺失值。

    参数:
        data (DataFrame): 输入的 DataFrame,包含一个 'stock_id' 列和可能的缺失值。

    返回:
        DataFrame: 输入的 DataFrame,其中的缺失值已经通过线性插值填充。
    """
    # 遍历每个与同一 'stock_id' 关联的行组
    for stock, group in tqdm(data.groupby('stock_id'), desc="按股票进行插值"):
        # 从该组中删除 'time_id' 列
        group_no_time_id = group.drop(columns=['time_id'])
        
        # 对该组(不包括 'time_id')进行线性插值,并更新 'data' 中对应的行
        data.loc[group.index, group_no_time_id.columns] = group_no_time_id.interpolate(method="linear")
    
    # 返回已填充缺失值的 DataFrame
    return data

异常值处理

from typing import DataFrame
from tqdm import tqdm
import numpy as np

def outlier_mad_by_stock(data: DataFrame, k=1.483) -> DataFrame:
    """
    使用中位绝对偏差(MAD)方法识别并处理每一股票的数据中的离群值。
参数:
    data (DataFrame): 输入的 DataFrame,包含一个 'stock_id' 列。
    k (float): 调整因子,默认为 1.483。

返回:
    DataFrame: 输入的 DataFrame,其中的离群值已经被处理。
"""
# 遍历每个与同一 'stock_id' 关联的行组
for stock, group in tqdm(data.groupby('stock_id'), desc="按股票移除离群值"):
    # 从该组中删去 'time_id' 列
    group_no_time_id = group.drop(columns=['time_id'])
    
    # 计算中位数和 MAD
    median = group_no_time_id.median()
    mad = (group_no_time_id - median).abs().median()
    
    # 计算上下限
    upper_limit = median + 3 * k * mad
    lower_limit = median - 3 * k * mad
    upper_compression_limit = median + (3.5 * k * mad)
    lower_compression_limit = median - (3.5 * k * mad)

    # 压缩超过上限的值
    above_upper = group_no_time_id > upper_limit
    for col in group_no_time_id.columns:
        compressed = np.interp(group_no_time_id.loc[above_upper[col], col],
                               (group_no_time_id[col].min(), group_no_time_id[col].max()),
                               (upper_limit[col], upper_compression_limit[col]))
        group_no_time_id.loc[above_upper[col], col] = compressed

    # 压缩低于下限的值
    below_lower = group_no_time_id < lower_limit
    for col in group_no_time_id.columns:
        compressed = np.interp(group_no_time_id.loc[below_lower[col], col],
                               (group_no_time_id[col].min(), group_no_time_id[col].max()),
                               (lower_compression_limit[col], lower_limit[col]))
        group_no_time_id.loc[below_lower[col], col] = compressed

    # 更新原始数据
    data.loc[group.index, group_no_time_id.columns] = group_no_time_id

# 返回已处理离群值的 DataFrame
return data

标准化

def z_score_by_stock(data: DataFrame, columns: Optional[List[str]] = None) -> DataFrame:
    """
    按股票标准化特定的列。

    参数:
        data (DataFrame): 输入的 DataFrame,包含一个 'stock_id' 列。
        columns (List[str], optional): 需要被标准化的列的列表。默认为 None,此时将标准化除 'time_id' 之外的所有列。

    返回:
        DataFrame: 输入的 DataFrame,其中指定的列已经被标准化。
    """
    # 遍历每个与同一 'stock_id' 关联的行组
    for stock, group in tqdm(data.groupby('stock_id'), desc="按股票标准化因子"):
        # 如果没有指定要标准化的列,则默认标准化除 'time_id' 之外的所有列
        if columns is None:
            cols = group.columns.drop(['time_id'])
        else:
            cols = columns
        # 按照 Z-Score 公式进行标准化:(值 - 平均值) / 标准差
        data.loc[group.index, cols] = (group[cols] - group[cols].mean()) / group[cols].std()
    
    # 返回已标准化的 DataFrame
    return data

计算滚动平均值

import numpy as np
import pandas as pd
from typing import DataFrame

def calculate_new_columns(group: DataFrame, col: str) -> DataFrame:
    """
    为给定的分组和列计算新的特征,包括滚动平均值、加权移动平均值和动量。
参数:
    group (DataFrame): 输入的 DataFrame。
    col (str): 需要计算新特征的列的名称。

返回:
    DataFrame: 包含新特征的 DataFrame。
"""
# 初始化新的 DataFrame 以存储新的特征
new_columns = pd.DataFrame(index=group.index)

# 计算 5, 10, 30, 35, 60 天的滚动平均值
for window in [5, 10, 30, 35, 60]:
    new_columns[f'{col}_{window}_day_mean'] = group[col].rolling(window=window).mean()

# 计算加权移动平均值 (X),权重为 1 到 30
weights = np.arange(1, 31)  # for a 30-day moving window
new_columns[f'{col}_X'] = group[col].rolling(window=30).apply(lambda x: np.sum(weights * x) / np.sum(weights),
                                                              raw=True)

# 计算动量 (MTM),即当前值减去前一天的值
new_columns[f'{col}_MTM'] = group[col] - group[col].shift(1)

# 返回包含新特征的 DataFrame
return new_columns

这期教程到此为止,欢迎关注同系列教程

完整的代码数据案例在我的公众号:梦云星


参考:

https://zhuanlan.zhihu.com/p/644489331

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值