<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'])
```