pd.expanding()时间序列中min_periods到底怎么理解

本文详细解析了pandas库中DataFrame的expanding和rolling函数的使用方法,特别是min_periods参数的意义,通过实例展示了如何设定窗口大小进行数据计算。
DataFrame.expanding(min_periods=1, center=False, axis=0)
return 的是a Window sub-classed for the particular operation

参数min_periods : int, default 1

意思是:Minimum number of observations in window required to have a value (otherwise result is NA).

在很多时间序列函数中都有这么一个参数。具体该怎么理解呢?

直译过来是窗口中需要有值的最小观测数量。

df = pd.DataFrame({'B': [0, 1, 2, 3, 4, 5, 6, 7, 8, np.NaN]})
print(df['B'].expanding(3).sum())

1552538093529

如果是看到结果再理解,这里在逻辑上有点迷糊。

反过来,先理解最小数量,指的是df中需要至少3个数据才开始计算,center=False指当前数据行向前找3个。因此第1行和第2行向前找三个都不够数量,因此不予计算,所以结果是NaN。

对于rolling()函数也是一样理解。

先考虑df,后才有return的结果NaN,这样就清晰多了。

.
.
.
2019-03-14 12:41:11写于杭州

<think>我们需要修改绿柱和红柱平均值的计算方式:要求是过去50根K线内,所有绿柱的总和除以绿柱的数量(即只计算绿柱出现的时候,非绿柱不参与计算)。同理,红柱也是只计算红柱出现的时候,非红柱不参与计算。 当前代码中,我们是将非绿柱的值设为0,然后对整个窗口求平均(包括0值)。这样计算的是整个窗口内所有K线的平均值(包括非绿柱的K线),而我们需要的是仅绿柱出现时的平均值。 修改思路: 1. 对于绿柱:创建一个只包含绿柱的序列,非绿柱设为NaN,然后使用rolling窗口的mean()函数(默认会跳过NaN)。 2. 同样,对于红柱:创建一个只包含红柱绝对值(或者负值,但计算平均值时我们关心大小,所以取绝对值)的序列,非红柱设为NaN,然后计算滚动平均值。 具体步骤: 步骤1:创建绿柱序列(只保留正值,负值设为NaN) 步骤2:使用rolling窗口计算,设置min_periods=1(表示至少需要1个非NaN值) 步骤3:同样处理红柱(只保留负值,然后取绝对值,或者将非红柱设为NaN,然后取绝对值计算平均值) 注意:因为红柱是负值,我们计算平均值时应该用绝对值(这样平均值才是正数,便于比较),但也可以直接用负值计算平均值然后取绝对值,两种方法都可以。这里我们采用:将红柱保留为负值,非红柱设为NaN,然后计算平均值,再取绝对值(或者直接计算负值的平均值,然后取绝对值)。 实际上,我们可以这样: 绿柱平均值 = 绿柱序列(正值,非绿柱为NaN)的滚动平均值 红柱平均值 = 红柱序列(负值,非红柱为NaN)的滚动平均值,然后取绝对值(因为平均值是负数,取正数便于比较) 但是,我们也可以这样:红柱序列用绝对值(这样计算平均值就是正数),这样就不需要最后取绝对值了。 这里我们选择第二种方法,即红柱序列用绝对值,这样计算的平均值就是正数。 具体修改代码: 绿柱: green_hist = hist.where(hist > 0) # 只保留正值,其他设为NaN green_avg = green_hist.rolling(window=config['macd_avg_window'], min_periods=1).mean() 红柱: red_hist = hist.where(hist < 0).abs() # 只保留负值,然后取绝对值,这样序列中红柱是正数,非红柱为NaN red_avg = red_hist.rolling(window=config['macd_avg_window'], min_periods=1).mean() 这样,green_avg就是过去50根K线内绿柱(只考虑绿柱出现时)的平均值,red_avg就是过去50根K线内红柱(只考虑红柱出现时)的平均值(绝对值)。 然后,阈值计算方式不变: green_threshold = green_avg * config['macd_avg_multiplier'] red_threshold = red_avg * config['macd_avg_multiplier'] 注意:由于我们使用了NaN,rolling窗口计算平均值时会跳过NaN,所以平均值只计算非NaN的值(即只计算绿柱或红柱)。 另外,我们也可以使用sum和count分别计算总和和数量,然后相除,但是使用mean()更加简洁。 下面我们修改generate_signals函数中的相关代码。</think># 修改MACD柱平均值计算方式 根据您的要求,我将修改MACD柱平均值的计算方式,使其变为"绿柱总和/绿柱数量"和"红柱总和/红柱数量"的形式。以下是完整修改后的代码: ```python def generate_signals(df, config): """生成交易信号""" # 初始化信号列 df['Signal'] = config['no_position'] # 1. 计算振幅:(最高价-最低价)/最低价 * 100% amplitude = (df['high'] - df['low']) / df['low'] * 100 # 2. 计算动态平均振幅 window_size = config.get('amplitude_window', 5) avg_amplitude = amplitude.rolling(window=window_size).mean().shift(1) # 处理前N行缺失值 if len(df) > window_size: avg_amplitude.iloc[:window_size] = amplitude.iloc[:window_size].mean() else: avg_amplitude = amplitude.expanding().mean().shift(1) # 3. 振幅条件:当前振幅大于动态平均振幅 amplitude_condition = amplitude > avg_amplitude # 4. 添加振幅指标到DataFrame df['Amplitude'] = amplitude df['Avg_Amplitude'] = avg_amplitude # 布林带突破条件 upper_break = (df['close'] > df['BB_Upper']) & (df['close'].shift(1) <= df['BB_Upper'].shift(1)) lower_break = (df['close'] < df['BB_Lower']) & (df['close'].shift(1) >= df['BB_Lower'].shift(1)) # 成交量放大条件 volume_condition = df['volume'] > (df['Volume_MA'] * config['volume_multiple']) # MACD位置条件 macd_below_zero = (df['MACD'] < 0) & (df['MACD_Signal'] < 0) macd_above_zero = (df['MACD'] > 0) & (df['MACD_Signal'] > 0) # MACD柱连续缩减条件 hist = df['MACD_Histogram'] hist_abs = hist.abs() K = config['K'] # ============== 修改后的MACD柱平均值计算 ============== # 1. 创建绿柱和红柱的掩码 green_mask = (hist > 0) # 绿柱掩码 red_mask = (hist < 0) # 红柱掩码 # 2. 计算绿柱平均值:绿柱总和 / 绿柱数量 green_hist = hist.copy() green_hist[~green_mask] = 0 # 非绿柱设为0 # 绿柱总和 green_sum = green_hist.rolling( window=config['macd_avg_window'], min_periods=1 ).sum() # 绿柱数量 green_count = green_mask.rolling( window=config['macd_avg_window'], min_periods=1 ).sum() # 避免除以0 green_avg = green_sum / green_count.replace(0, 1) # 3. 计算红柱平均值:红柱绝对值总和 / 红柱数量 red_hist = hist.abs().copy() red_hist[~red_mask] = 0 # 非红柱设为0 # 红柱绝对值总和 red_sum = red_hist.rolling( window=config['macd_avg_window'], min_periods=1 ).sum() # 红柱数量 red_count = red_mask.rolling( window=config['macd_avg_window'], min_periods=1 ).sum() # 避免除以0 red_avg = red_sum / red_count.replace(0, 1) # 4. 计算绿柱阈值(平均值乘以系数) green_threshold = green_avg * config['macd_avg_multiplier'] # 5. 计算红柱阈值(平均值乘以系数) red_threshold = red_avg * config['macd_avg_multiplier'] # 6. 创建MACD柱突破条件 green_break_condition = green_mask & (hist > green_threshold) red_break_condition = red_mask & (hist.abs() > red_threshold) # 7. 将指标添加到DataFrame(可选,用于可视化) df['Green_Avg'] = green_avg df['Red_Avg'] = red_avg df['Green_Threshold'] = green_threshold df['Red_Threshold'] = red_threshold # ============== 修改结束 ============== # 做空条件:连续三根绿柱缩减 + 当前绿柱突破绿柱阈值 macd_short_condition = ( green_mask & (hist_abs < hist_abs.shift(1) * (1 - K)) & (hist.shift(1) > 0) & (hist_abs.shift(1) > hist_abs.shift(2) * (1 - K)) & (hist.shift(2) > 0) & green_break_condition ) # 做多条件:连续三根红柱缩减 + 当前红柱突破红柱阈值 macd_long_condition = ( red_mask & (hist_abs < hist_abs.shift(1) * (1 - K)) & (hist.shift(1) < 0) & (hist_abs.shift(1) > hist_abs.shift(2) * (1 - K)) & (hist.shift(2) < 0) & red_break_condition ) # 买入信号 buy_condition = macd_long_condition & amplitude_condition # 卖出信号 sell_condition = macd_short_condition & amplitude_condition # 设置信号 df.loc[buy_condition, 'Signal'] = config['long'] df.loc[sell_condition, 'Signal'] = config['short'] df['B'] = buy_condition.astype(int) df['S'] = -1 * sell_condition.astype(int) df['signal_return_to_market_tracking'] = df['B'] + df['S'] return df ``` ## 修改说明 ### 1. 绿柱平均值计算 原始代码: ```python green_hist = hist.copy() green_hist[green_hist < 0] = 0 # 将负值设为0 green_avg = green_hist.rolling(window=config['macd_avg_window'], min_periods=1).mean() ``` 修改后代码: ```python # 创建绿柱掩码 green_mask = (hist > 0) # 计算绿柱总和 green_hist = hist.copy() green_hist[~green_mask] = 0 # 非绿柱设为0 green_sum = green_hist.rolling(window=config['macd_avg_window'], min_periods=1).sum() # 计算绿柱数量 green_count = green_mask.rolling(window=config['macd_avg_window'], min_periods=1).sum() # 计算平均值:绿柱总和 / 绿柱数量 green_avg = green_sum / green_count.replace(0, 1) # 避免除以0 ``` ### 2. 红柱平均值计算 原始代码: ```python red_hist = hist.copy() red_hist[red_hist > 0] = 0 # 将正值设为0 red_avg = red_hist.abs().rolling(window=config['macd_avg_window'], min_periods=1).mean() ``` 修改后代码: ```python # 创建红柱掩码 red_mask = (hist < 0) # 计算红柱绝对值总和 red_hist = hist.abs().copy() red_hist[~red_mask] = 0 # 非红柱设为0 red_sum = red_hist.rolling(window=config['macd_avg_window'], min_periods=1).sum() # 计算红柱数量 red_count = red_mask.rolling(window=config['macd_avg_window'], min_periods=1).sum() # 计算平均值:红柱绝对值总和 / 红柱数量 red_avg = red_sum / red_count.replace(0, 1) # 避免除以0 ``` ### 3. 避免除以零错误 ```python green_avg = green_sum / green_count.replace(0, 1) red_avg = red_sum / red_count.replace(0, 1) ``` 使用`replace(0, 1)`确保当绿柱或红柱数量为零时不会发生除以零错误。当没有绿柱/红柱时,平均值设为0。 ## 修改后的优势 1. **更精确的计算**: - 只考虑实际存在的绿柱/红柱 - 排除非绿柱/红柱对平均值的影响 2. **更符合业务逻辑**: - 真实反映绿柱/红柱的平均强度 - 避免在绿柱/红柱稀少时产生偏差 3. **更稳定的阈值**: - 平均值计算更准确,阈值更可靠 - 在绿柱/红柱出现频率变化时更稳定 4. **避免极端值影响**: - 当窗口内只有少量绿柱/红柱时,平均值不会过度放大 - 减少异常值对阈值的影响 ## 完整策略配置 ```python STRATEGY_CONFIG = { 'symbol': 'BTC/USDT', 'bb_window': 20, # 布林带窗口 'bb_dev': 1.5, # 布林带标准差倍数 'volume_ma_window': 5, # 成交量均线窗口 'volume_multiple': 3.3, # 成交量放大倍数 'macd_fast': 12, # MACD快线 'macd_slow': 26, # MACD慢线 'macd_signal': 9, # MACD信号线 'holding_period': 5, # 持仓周期(K线数) 'long': 1, # 买入信号 'short': -1, # 卖出信号 'period_close': 3, # 持仓周期平仓信号 'profit_close': 4, # 止盈平仓信号 'stop_close': 5, # 止损平仓信号 'no_position': 0, # 无信号 'take_profit': 0.01, # 止盈点位0.5% 'stop_loss': 0.01, # 止损点位1% 'amplitude_window': 5, # 振幅计算窗口 'K': 0.1, # macd趋势反转参数,数值越大,反转趋势性越强 'macd_avg_window': 50, # MACD柱平均值计算窗口 'macd_avg_multiplier': 1.2, # MACD柱平均值乘数 } ``` ## 可视化增强 在`generate_html_chart`函数中添加以下代码,可视化MACD柱数量和平均值: ```python # 在MACD子图中添加绿柱数量 fig.add_trace(go.Scatter( x=df['date'], y=df['Green_Count'], # 需要先添加绿柱数量到DataFrame name='绿柱数量', line=dict(color='rgba(39, 174, 96, 0.4)'), yaxis='y2' ), row=3, col=1) # 在MACD子图中添加红柱数量 fig.add_trace(go.Scatter( x=df['date'], y=df['Red_Count'], # 需要先添加红柱数量到DataFrame name='红柱数量', line=dict(color='rgba(231, 76, 60, 0.4)'), yaxis='y2' ), row=3, col=1) # 更新布局添加右侧Y轴 fig.update_layout( yaxis2=dict( title='柱数量', overlaying='y', side='right', showgrid=False ) ) ``` ## 性能优化建议 对于大型数据集,可以使用更高效的计算方法: ```python # 使用groupby和transform优化计算 def calculate_avg(series, mask, window): """高效计算特定窗口内的平均值""" # 创建临时DataFrame temp_df = pd.DataFrame({ 'value': series, 'mask': mask }) # 计算分组滚动平均值 temp_df['group'] = (~mask).cumsum() # 计算每个组的滚动和和计数 temp_df['sum'] = temp_df.groupby('group')['value'].transform( lambda x: x.rolling(window, min_periods=1).sum() ) temp_df['count'] = temp_df.groupby('group')['mask'].transform( lambda x: x.rolling(window, min_periods=1).sum() ) # 计算平均值 return temp_df['sum'] / temp_df['count'].replace(0, 1) # 在generate_signals中使用 green_avg = calculate_avg(hist, green_mask, config['macd_avg_window']) red_avg = calculate_avg(hist.abs(), red_mask, config['macd_avg_window']) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值