Files
Chan/strategies/ElliottWaveBTCStrategyV2.py
PorterandCursor 2c1232555e refactor: 缠论引擎迁入 chan/ 分层解耦,指标外置
将核心结构、指标与分析拆到 chan/{core,indicators,analysis,pipeline};
根目录保留兼容 shim;strategies 改为从 chan 包导入;买卖点经 bsp_macd 与 MACD 接合。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 14:47:13 +08:00

693 lines
24 KiB
Python

"""
Elliott Wave Strategy for BTC Perpetual Futures V2
基于真正的艾略特波浪理论 + 缠论分型确认
核心逻辑:
- 艾略特波浪识别: 自动识别1-5浪上涨和A-C浪下跌
- 多时间框架确认: 5m入场,1h确认趋势方向,1d确认大周期浪型
- 双向交易: 根据波浪位置决定做多或做空
- 动态风险管理: 根据波动率调整仓位和止损
改进点:
1. 实现真正的波浪计数器 (Wave Counter)
2. 斐波那契回撤/扩展用于止盈止损
3. 波浪完成度评估
4. 多周期共振确认
5. 市场情绪过滤
作者: AI Assistant (Optimized)
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from chan.pipeline.ChanLun import ChanLun
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import pandas as pd
import numpy as np
import talib.abstract as ta
import logging
from datetime import datetime
from typing import Optional, Tuple, List
from dataclasses import dataclass
from enum import Enum
logger = logging.getLogger(__name__)
class WaveType(Enum):
"""波浪类型"""
IMPULSE = "impulse" # 推动浪 (1,2,3,4,5)
CORRECTIVE = "corrective" # 调整浪 (A,B,C)
UNKNOWN = "unknown"
class WavePosition(Enum):
"""当前在波浪中的位置"""
WAVE_1 = 1
WAVE_2 = 2
WAVE_3 = 3
WAVE_4 = 4
WAVE_5 = 5
WAVE_A = 6
WAVE_B = 7
WAVE_C = 8
UNKNOWN = 0
@dataclass
class Wave:
"""波浪数据结构"""
start_idx: int
end_idx: int
start_price: float
end_price: float
wave_num: int # 1-5 or 6-8 (A-C)
wave_type: WaveType
is_complete: bool = False
class ElliottWaveBTCStrategyV2(IStrategy):
"""
艾略特波浪理论策略 V2
结合缠论分型进行波浪确认
"""
INTERFACE_VERSION = 3
can_short = True
# 基础止损止盈 (会根据波动率动态调整)
stoploss = -0.015
minimal_roi = {
"0": 0.08,
"60": 0.05,
"120": 0.03,
"240": 0.015
}
# 追踪止损
trailing_stop = True
trailing_stop_positive = 0.015
trailing_stop_positive_offset = 0.06
trailing_only_offset_is_reached = True
startup_candle_count = 1000
position_adjustment_enable = True
pair = 'BTC/USDT:USDT'
timeframe = '5m'
chan = ChanLun()
# ========== 策略参数 (可优化) ==========
# 波浪检测参数
wave_pivot_lookback = 5 # 波浪枢轴点回看周期
min_wave_bars = 8 # 最小波浪K线数
max_wave_bars = 200 # 最大波浪K线数
# 斐波那契参数
fib_entry_threshold = 0.618 # 入场回撤位
fib_target_1 = 1.272 # 第一目标位
fib_target_2 = 1.618 # 第二目标位
fib_stop_loss = 0.5 # 止损位 (低于/高于0.5)
# RSI参数
rsi_oversold = 35
rsi_overbought = 65
rsi_period = 14
# 波动率参数
atr_period = 14
atr_multiplier_entry = 1.5 # 入场ATR倍数
atr_multiplier_stop = 2.0 # 止损ATR倍数
# 趋势过滤参数
ema_trend_period = 200
trend_filter_strict = True # 严格趋势过滤
# 波浪完成度阈值
wave_completion_threshold = 0.8
def informative_pairs(self):
return [
(self.pair, '5m'),
(self.pair, '1h'),
(self.pair, '4h'),
(self.pair, '1d'),
]
def _add_indicators(self, df: DataFrame) -> DataFrame:
"""添加技术指标"""
# 基础EMA
df['ema20'] = ta.EMA(df, timeperiod=20)
df['ema50'] = ta.EMA(df, timeperiod=50)
df['ema200'] = ta.EMA(df, timeperiod=self.ema_trend_period)
# RSI
df['rsi'] = ta.RSI(df, timeperiod=self.rsi_period)
df['rsi_ma'] = df['rsi'].rolling(window=9).mean()
# ATR
df['atr'] = ta.ATR(df, timeperiod=self.atr_period)
df['atr_percent'] = df['atr'] / df['close'] * 100
# MACD
macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9)
df['macd'] = macd['macd']
df['macdsignal'] = macd['macdsignal']
df['macdhist'] = macd['macdhist']
# 布林带
bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
df['bb_upper'] = bb['upper']
df['bb_lower'] = bb['lower']
df['bb_middle'] = bb['middle']
df['bb_percent'] = (df['close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])
# 成交量
df['volume_sma'] = ta.SMA(df, timeperiod=20)
df['volume_ratio'] = df['volume'] / df['volume_sma']
# 波动率
df['volatility'] = df['close'].pct_change().rolling(20).std() * np.sqrt(365 * 24 * 12)
return df
def _detect_pivots(self, df: DataFrame, left_bars: int = 5) -> Tuple[List[int], List[int]]:
"""
检测价格枢轴点 (用于波浪识别) - 无未来数据版本
只使用左侧已确认的数据,避免lookahead bias
返回: (高点索引列表, 低点索引列表)
"""
highs = []
lows = []
# 只使用左侧数据确认枢轴点,不使用right_bars避免未来数据
for i in range(left_bars, len(df)):
# 检测高点: 当前点比之前left_bars个bar都高
is_high = True
for j in range(1, left_bars + 1):
if df['high'].iloc[i] <= df['high'].iloc[i - j]:
is_high = False
break
if is_high:
highs.append(i)
# 检测低点: 当前点比之前left_bars个bar都低
is_low = True
for j in range(1, left_bars + 1):
if df['low'].iloc[i] >= df['low'].iloc[i - j]:
is_low = False
break
if is_low:
lows.append(i)
return highs, lows
def _calculate_wave(self, pivots: List[int], df: DataFrame, is_up: bool) -> Optional[Wave]:
"""
计算单个波浪的属性
"""
if len(pivots) < 2:
return None
start_idx = pivots[0]
end_idx = pivots[-1]
start_price = df['low'].iloc[start_idx] if is_up else df['high'].iloc[start_idx]
end_price = df['high'].iloc[end_idx] if is_up else df['low'].iloc[end_idx]
wave_height = abs(end_price - start_price)
wave_bars = end_idx - start_idx
if wave_bars < self.min_wave_bars or wave_bars > self.max_wave_bars:
return None
return Wave(
start_idx=start_idx,
end_idx=end_idx,
start_price=start_price,
end_price=end_price,
wave_num=0, # 稍后分配
wave_type=WaveType.UNKNOWN
)
def _identify_elliott_waves(self, df: DataFrame) -> List[Wave]:
"""
识别艾略特波浪结构
简化版:基于枢轴点识别5浪上涨或3浪下跌
"""
highs, lows = self._detect_pivots(df, self.wave_pivot_lookback)
waves = []
all_pivots = sorted(highs + lows)
if len(all_pivots) < 4:
return waves
# 简化波浪识别:基于价格走势判断当前处于哪个浪
recent_pivots = all_pivots[-8:] # 取最近8个枢轴点
for i in range(0, len(recent_pivots) - 1, 2):
if i + 1 >= len(recent_pivots):
break
start_idx = recent_pivots[i]
end_idx = recent_pivots[i + 1]
# 确定是上涨还是下跌浪
price_change = df['close'].iloc[end_idx] - df['close'].iloc[start_idx]
is_up = price_change > 0
wave = Wave(
start_idx=start_idx,
end_idx=end_idx,
start_price=df['close'].iloc[start_idx],
end_price=df['close'].iloc[end_idx],
wave_num=(i // 2) + 1,
wave_type=WaveType.IMPULSE if is_up else WaveType.CORRECTIVE,
is_complete=True
)
waves.append(wave)
return waves
def _get_current_wave_position(self, df: DataFrame, waves: List[Wave]) -> WavePosition:
"""
判断当前处于波浪的哪个位置
"""
if not waves:
return WavePosition.UNKNOWN
last_wave = waves[-1]
current_price = df['close'].iloc[-1]
# 基于最后一浪的特征判断位置
if last_wave.wave_num == 1:
return WavePosition.WAVE_2 if current_price < last_wave.end_price else WavePosition.WAVE_1
elif last_wave.wave_num == 2:
return WavePosition.WAVE_3 if current_price > last_wave.end_price else WavePosition.WAVE_2
elif last_wave.wave_num == 3:
return WavePosition.WAVE_4 if current_price < last_wave.end_price else WavePosition.WAVE_3
elif last_wave.wave_num == 4:
return WavePosition.WAVE_5 if current_price > last_wave.end_price else WavePosition.WAVE_4
elif last_wave.wave_num >= 5:
return WavePosition.WAVE_A
return WavePosition.UNKNOWN
def _calculate_fibonacci_levels(self, wave: Wave) -> dict:
"""
计算斐波那契回撤和扩展位
"""
if wave is None:
return {}
price_range = abs(wave.end_price - wave.start_price)
is_up = wave.end_price > wave.start_price
if is_up:
levels = {
'0.0': wave.end_price,
'0.236': wave.end_price - price_range * 0.236,
'0.382': wave.end_price - price_range * 0.382,
'0.5': wave.end_price - price_range * 0.5,
'0.618': wave.end_price - price_range * 0.618,
'0.786': wave.end_price - price_range * 0.786,
'1.0': wave.start_price,
'1.272': wave.end_price + price_range * 0.272,
'1.618': wave.end_price + price_range * 0.618,
}
else:
levels = {
'0.0': wave.end_price,
'0.236': wave.end_price + price_range * 0.236,
'0.382': wave.end_price + price_range * 0.382,
'0.5': wave.end_price + price_range * 0.5,
'0.618': wave.end_price + price_range * 0.618,
'0.786': wave.end_price + price_range * 0.786,
'1.0': wave.start_price,
'1.272': wave.end_price - price_range * 0.272,
'1.618': wave.end_price - price_range * 0.618,
}
return levels
def _get_dataframe(self, timeframe: str) -> DataFrame:
return self.dp.get_pair_dataframe(pair=self.pair, timeframe=timeframe)
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""计算所有指标"""
dataframe = self._add_indicators(dataframe)
# 获取多时间框架数据
df_1h = self._get_dataframe('1h')
df_4h = self._get_dataframe('4h')
df_1d = self._get_dataframe('1d')
# 1小时指标
if len(df_1h) > 50:
df_1h = self._add_indicators(df_1h)
dataframe['1h_ema200'] = df_1h['ema200'].reindex(dataframe.index, method='ffill')
dataframe['1h_trend'] = np.where(dataframe['close'] > dataframe['1h_ema200'], 1, -1)
dataframe['1h_rsi'] = df_1h['rsi'].reindex(dataframe.index, method='ffill')
dataframe['1h_atr'] = df_1h['atr'].reindex(dataframe.index, method='ffill')
# 1h波浪识别
waves_1h = self._identify_elliott_waves(df_1h)
dataframe['1h_wave_position'] = self._get_current_wave_position(df_1h, waves_1h).value
else:
dataframe['1h_trend'] = 0
dataframe['1h_rsi'] = 50
dataframe['1h_wave_position'] = 0
# 4小时指标
if len(df_4h) > 50:
df_4h = self._add_indicators(df_4h)
dataframe['4h_ema200'] = df_4h['ema200'].reindex(dataframe.index, method='ffill')
dataframe['4h_trend'] = np.where(dataframe['close'] > dataframe['4h_ema200'], 1, -1)
else:
dataframe['4h_trend'] = 0
# 日线指标
if len(df_1d) > 50:
df_1d = self._add_indicators(df_1d)
dataframe['1d_ema200'] = df_1d['ema200'].reindex(dataframe.index, method='ffill')
dataframe['1d_trend'] = np.where(dataframe['close'] > dataframe['1d_ema200'], 1, -1)
dataframe['1d_rsi'] = df_1d['rsi'].reindex(dataframe.index, method='ffill')
# 日线波浪 (大趋势)
waves_1d = self._identify_elliott_waves(df_1d)
dataframe['1d_wave_position'] = self._get_current_wave_position(df_1d, waves_1d).value
else:
dataframe['1d_trend'] = 0
dataframe['1d_rsi'] = 50
dataframe['1d_wave_position'] = 0
# 当前时间框架波浪识别
waves = self._identify_elliott_waves(dataframe)
dataframe['wave_position'] = self._get_current_wave_position(dataframe, waves).value
# 缠论分型
dataframe['chan_state'] = self.chan.get_klu_state(dataframe)
# 生成交易信号
dataframe = self._generate_signals(dataframe, waves)
return dataframe
def _generate_signals(self, df: DataFrame, waves: List[Wave]) -> DataFrame:
"""
基于艾略特波浪理论生成交易信号
"""
n = len(df)
if n < 50:
return df
# 获取当前波浪位置
current_wave = self._get_current_wave_position(df, waves)
# 延迟分型 (避免未来数据)
df['_fx'] = df['chan_state'].shift(1).fillna('00')
# 趋势方向
trend_up = df['1h_trend'] > 0
trend_down = df['1h_trend'] < 0
trend_aligned_daily = df['1d_trend'] == df['1h_trend']
# RSI条件
rsi_oversold = df['rsi'] < self.rsi_oversold
rsi_overbought = df['rsi'] > self.rsi_overbought
rsi_divergence_long = (df['rsi'] > df['rsi'].shift(5)) & (df['close'] < df['close'].shift(5))
rsi_divergence_short = (df['rsi'] < df['rsi'].shift(5)) & (df['close'] > df['close'].shift(5))
# 波动率过滤
low_volatility = df['atr_percent'] < df['atr_percent'].rolling(50).mean() * 0.8
high_volatility = df['atr_percent'] > df['atr_percent'].rolling(50).mean() * 1.5
# ========== 多头信号 ==========
long_conditions = []
# 浪2回调做多 (最佳入场点)
# 条件: 浪2位置 + 底分型 + RSI超卖 + 趋势向上
long_cond_wave2 = (
(df['wave_position'] == WavePosition.WAVE_2.value) |
(df['1h_wave_position'] == WavePosition.WAVE_2.value)
) & (
(df['_fx'] == '-10') |
((df['close'] > df['ema20']) & (df['ema20'] > df['ema50']))
) & rsi_oversold & trend_up
long_conditions.append(('wave2', long_cond_wave2))
# 浪4回调做多 (谨慎入场)
long_cond_wave4 = (
(df['wave_position'] == WavePosition.WAVE_4.value) |
(df['1h_wave_position'] == WavePosition.WAVE_4.value)
) & (df['_fx'] == '-10') & rsi_oversold & trend_up & (
df['rsi_divergence_long'] if 'rsi_divergence_long' in df.columns else True
)
long_conditions.append(('wave4', long_cond_wave4))
# C浪结束做多 (趋势反转)
long_cond_wave_c = (
(df['wave_position'] == WavePosition.WAVE_C.value) |
(df['1h_wave_position'] == WavePosition.WAVE_C.value)
) & (df['_fx'] == '-10') & rsi_oversold & (
df['volume_ratio'] > 1.5 # 放量确认
)
long_conditions.append(('wave_c', long_cond_wave_c))
# 强势突破做多
long_cond_breakout = (
(df['close'] > df['bb_upper']) &
(df['volume_ratio'] > 2.0) &
trend_up &
(df['macdhist'] > 0) &
(df['1h_wave_position'].isin([WavePosition.WAVE_3.value, WavePosition.WAVE_5.value]))
)
long_conditions.append(('breakout', long_cond_breakout))
# 合并多头信号
df['elliott_long'] = False
for name, cond in long_conditions:
df[f'long_{name}'] = cond & ~low_volatility # 避免低波动时入场
df['elliott_long'] |= df[f'long_{name}']
# ========== 空头信号 ==========
short_conditions = []
# 浪2回调做空 (下跌趋势)
short_cond_wave2 = (
(df['wave_position'] == WavePosition.WAVE_2.value) |
(df['1h_wave_position'] == WavePosition.WAVE_2.value)
) & (
(df['_fx'] == '10') |
((df['close'] < df['ema20']) & (df['ema20'] < df['ema50']))
) & rsi_overbought & trend_down
short_conditions.append(('wave2', short_cond_wave2))
# 浪4回调做空 (谨慎)
short_cond_wave4 = (
(df['wave_position'] == WavePosition.WAVE_4.value) |
(df['1h_wave_position'] == WavePosition.WAVE_4.value)
) & (df['_fx'] == '10') & rsi_overbought & trend_down
short_conditions.append(('wave4', short_cond_wave4))
# 浪5结束做空 (趋势反转)
short_cond_wave5 = (
(df['wave_position'] == WavePosition.WAVE_5.value) |
(df['1h_wave_position'] == WavePosition.WAVE_5.value)
) & (df['_fx'] == '10') & rsi_overbought & (
df['volume_ratio'] > 1.5
)
short_conditions.append(('wave5', short_cond_wave5))
# B浪反弹做空 (继续下跌)
short_cond_wave_b = (
(df['wave_position'] == WavePosition.WAVE_B.value) |
(df['1h_wave_position'] == WavePosition.WAVE_B.value)
) & (df['_fx'] == '10') & rsi_overbought & trend_down
short_conditions.append(('wave_b', short_cond_wave_b))
# 强势跌破做空
short_cond_breakdown = (
(df['close'] < df['bb_lower']) &
(df['volume_ratio'] > 2.0) &
trend_down &
(df['macdhist'] < 0) &
(df['1h_wave_position'].isin([WavePosition.WAVE_3.value, WavePosition.WAVE_C.value]))
)
short_conditions.append(('breakdown', short_cond_breakdown))
# 合并空头信号
df['elliott_short'] = False
for name, cond in short_conditions:
df[f'short_{name}'] = cond & ~low_volatility
df['elliott_short'] |= df[f'short_{name}']
# 强趋势过滤
if self.trend_filter_strict:
df['elliott_long'] &= trend_up | (df['1d_trend'] > 0)
df['elliott_short'] &= trend_down | (df['1d_trend'] < 0)
# 避免高波动时期入场
df['elliott_long'] &= ~high_volatility
df['elliott_short'] &= ~high_volatility
return df
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""入场信号"""
dataframe['enter_long'] = 0
dataframe['enter_short'] = 0
dataframe['enter_tag'] = ''
if 'elliott_long' not in dataframe.columns:
return dataframe
# 多头入场
long_mask = dataframe['elliott_long'].fillna(False)
dataframe.loc[long_mask, 'enter_long'] = 1
# 标记入场类型
for col in dataframe.columns:
if col.startswith('long_') and col != 'elliott_long':
mask = dataframe[col].fillna(False) & (dataframe['enter_long'] == 1)
dataframe.loc[mask, 'enter_tag'] = col.replace('long_', 'elliott_')
# 空头入场
short_mask = dataframe['elliott_short'].fillna(False)
dataframe.loc[short_mask, 'enter_short'] = 1
for col in dataframe.columns:
if col.startswith('short_') and col != 'elliott_short':
mask = dataframe[col].fillna(False) & (dataframe['enter_short'] == 1)
dataframe.loc[mask, 'enter_tag'] = col.replace('short_', 'elliott_')
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""出场信号"""
dataframe['exit_long'] = 0
dataframe['exit_short'] = 0
if len(dataframe) < 2:
return dataframe
df = dataframe.copy()
df['_fx'] = df['chan_state'].shift(1).fillna('00')
# 多头出场条件
exit_long_cond = (
# 顶分型出场
(df['_fx'] == '10') |
# RSI超买
(df['rsi'] > 75) |
# 跌破EMA20
(df['close'] < df['ema20']) & (df['close'].shift(1) > df['ema20'].shift(1)) |
# MACD死叉
(df['macd'] < df['macdsignal']) & (df['macd'].shift(1) > df['macdsignal'].shift(1))
)
# 波浪位置出场
exit_long_wave = df['wave_position'].isin([
WavePosition.WAVE_5.value,
WavePosition.WAVE_C.value
])
dataframe['exit_long'] = (exit_long_cond | exit_long_wave).astype(int)
# 空头出场条件
exit_short_cond = (
# 底分型出场
(df['_fx'] == '-10') |
# RSI超卖
(df['rsi'] < 25) |
# 突破EMA20
(df['close'] > df['ema20']) & (df['close'].shift(1) < df['ema20'].shift(1)) |
# MACD金叉
(df['macd'] > df['macdsignal']) & (df['macd'].shift(1) < df['macdsignal'].shift(1))
)
# 波浪位置出场
exit_short_wave = df['wave_position'].isin([
WavePosition.WAVE_C.value,
WavePosition.WAVE_5.value
])
dataframe['exit_short'] = (exit_short_cond | exit_short_wave).astype(int)
return dataframe
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
动态止损:基于ATR和波浪位置调整
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 2:
return self.stoploss
current_candle = dataframe.iloc[-1]
atr = current_candle['atr_percent']
# 基于ATR的动态止损
dynamic_stop = -atr * self.atr_multiplier_stop / 100
# 根据盈利情况收紧止损
if current_profit > 0.03: # 盈利3%后收紧止损
return max(dynamic_stop, -0.01)
elif current_profit > 0.05: # 盈利5%后更紧
return max(dynamic_stop, -0.005)
return max(dynamic_stop, self.stoploss)
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:
"""
动态杠杆:根据波动率调整
"""
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
if len(dataframe) < 2:
return 2.0
current_candle = dataframe.iloc[-1]
volatility = current_candle['atr_percent']
# 低波动时提高杠杆,高波动时降低杠杆
if volatility < 0.5:
return 3.0
elif volatility < 1.0:
return 2.0
elif volatility < 2.0:
return 1.5
else:
return 1.0
def adjust_trade_position(self, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs) -> Optional[float]:
"""
仓位调整:金字塔加仓
"""
if current_profit < -0.01: # 亏损时不加仓
return None
if current_profit > 0.02 and current_profit < 0.03: # 盈利2-3%时加仓
return min_stake * 0.5 if min_stake else None
return None