""" ChanLun Wave Strategy for BTC Perpetual Futures 基于缠论波浪策略 V8 核心逻辑: - 使用Chan库计算KLC-based缠论分型 - 只做空头(在下跌趋势中做空反弹) - 顶分型确认 + RSI > 55 + 趋势确认 → 做空 - 空头出场:底分型 + RSI < 40 策略设计: - 短周期(5m)为主,长周期(1h/1d)确认趋势 - 使用Chan库KLC分型确认入场 - RSI > 55 做空条件,RSI < 40 出场条件 - 不做多头(下跌趋势中做多风险太大) 作者: AI Assistant """ 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 logger = logging.getLogger(__name__) class ElliottWaveBTCStrategy(IStrategy): INTERFACE_VERSION = 3 can_short = True stoploss = -0.02 minimal_roi = { "0": 0.06, "120": 0.03, "360": 0.01 } trailing_stop = True trailing_stop_positive = 0.02 trailing_stop_positive_offset = 0.08 trailing_only_offset_is_reached = True startup_candle_count = 500 position_adjustment_enable = False pair = 'BTC/USDT:USDT' timeframe = '5m' chan = ChanLun() def informative_pairs(self): return [ (self.pair, '5m'), (self.pair, '1h'), (self.pair, '1d'), ] def _add_indicators(self, df: DataFrame) -> DataFrame: df['ema20'] = ta.EMA(df, timeperiod=20) df['ema50'] = ta.EMA(df, timeperiod=50) df['ema200'] = ta.EMA(df, timeperiod=200) df['rsi'] = ta.RSI(df, timeperiod=14) df['atr'] = ta.ATR(df, timeperiod=14) macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9) df['macd'] = macd['macd'] df['macdsignal'] = macd['macdsignal'] df['macdhist'] = macd['macdhist'] # Chan库指标 df['ema52'] = ta.EMA(df, timeperiod=52) df['ema104'] = ta.EMA(df, timeperiod=104) df['ema24'] = ta.EMA(df, timeperiod=24) df['ema26'] = ta.EMA(df, timeperiod=26) df['volume_sma'] = ta.SMA(df, timeperiod=20) df['volume_ratio'] = df['volume'] / df['volume_sma'] bb = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0) df['bb2633upper'] = bb['upperband'] df['bb2633lower'] = bb['lowerband'] df['bb2633middle'] = bb['middleband'] return df 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_1d = self._get_dataframe('1d') if len(df_1h) > 0: df_1h = self._add_indicators(df_1h) df_1h['chan_state'] = self.chan.get_klu_state(df_1h) dataframe['1h_ema200'] = df_1h['ema200'].reindex(dataframe.index, method='ffill') dataframe['1h_trend_up'] = (df_1h['close'] > df_1h['ema200']).reindex(dataframe.index, method='ffill') dataframe['1h_trend_down'] = (df_1h['close'] < df_1h['ema200']).reindex(dataframe.index, method='ffill') dataframe['1h_chan_state'] = df_1h['chan_state'].reindex(dataframe.index, method='ffill') else: dataframe['1h_ema200'] = dataframe['ema200'] dataframe['1h_trend_up'] = True dataframe['1h_trend_down'] = True dataframe['1h_chan_state'] = '00' if len(df_1d) > 0: df_1d = self._add_indicators(df_1d) df_1d['chan_state'] = self.chan.get_klu_state(df_1d) dataframe['1d_ema200'] = df_1d['ema200'].reindex(dataframe.index, method='ffill') dataframe['1d_trend_up'] = (df_1d['close'] > df_1d['ema200']).reindex(dataframe.index, method='ffill') dataframe['1d_trend_down'] = (df_1d['close'] < df_1d['ema200']).reindex(dataframe.index, method='ffill') dataframe['1d_rsi'] = df_1d['rsi'].reindex(dataframe.index, method='ffill') dataframe['1d_chan_state'] = df_1d['chan_state'].reindex(dataframe.index, method='ffill') else: dataframe['1d_ema200'] = dataframe['ema200'] dataframe['1d_trend_up'] = True dataframe['1d_trend_down'] = True dataframe['1d_rsi'] = 50 dataframe['1d_chan_state'] = '00' # 缠论分型(使用Chan库) dataframe['chan_state'] = self.chan.get_klu_state(dataframe) dataframe = self._generate_signals(dataframe) return dataframe def _generate_signals(self, df: DataFrame) -> DataFrame: """缠论分型 + 趋势确认 - 做空为主""" n = len(df) if n < 10: return df # 延迟分型状态(避免未来数据) df['_fx'] = df['chan_state'].shift(1).fillna('00') # 1h趋势 hourly_down = df['1h_trend_down'].fillna(False) hourly_up = df['1h_trend_up'].fillna(False) # MACD方向 macd_cross_down = (df['macd'] < df['macdsignal']) & (df['macd'].shift(1) >= df['macdsignal'].shift(1)) # === 空头信号(下跌趋势中做空)=== # 条件1: 顶分型 + RSI > 55 + 1h下跌趋势 short_cond1 = ( (df['_fx'] == '10') & (df['rsi'] > 55) & hourly_down ) # 条件2: 1h共振顶分型 + RSI > 55 short_cond2 = ( (df['_fx'] == '10') & (df['1h_chan_state'].fillna('00') == '10') & (df['rsi'] > 55) ) # 条件3: 顶分型 + MACD死叉 + RSI > 60 short_cond3 = ( (df['_fx'] == '10') & macd_cross_down & (df['rsi'] > 60) ) df['chan_short'] = (short_cond1 | short_cond2 | short_cond3).astype(bool) # === 多头信号(仅在1h上涨趋势中做多,且很少)=== # 只在1d和1h同时上涨时才做多,且需要强确认 daily_up = df['1d_trend_up'].fillna(False) long_cond = ( (df['_fx'] == '-10') & (df['rsi'] < 30) & # 极低RSI才做多 hourly_up & daily_up ) # 1h和1d共振底分型 long_cond2 = ( (df['_fx'] == '-10') & (df['rsi'] < 30) & (df['1h_chan_state'].fillna('00') == '-10') & (df['1d_chan_state'].fillna('00') == '-10') ) df['chan_long'] = (long_cond | long_cond2).astype(bool) return df def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['enter_long'] = 0 dataframe['enter_short'] = 0 dataframe['enter_tag'] = '' if 'chan_long' not in dataframe.columns: return dataframe dataframe.loc[dataframe['chan_long'], 'enter_long'] = 1 dataframe.loc[dataframe['chan_long'], 'enter_tag'] = 'chan_long' dataframe.loc[dataframe['chan_short'], 'enter_short'] = 1 dataframe.loc[dataframe['chan_short'], 'enter_tag'] = 'chan_short' 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 if 'chan_state' not in dataframe.columns: return dataframe df = dataframe.copy() df['_fx'] = df['chan_state'].shift(1).fillna('00') # 空头出场:底分型 + RSI < 40(仅在明显反弹时出场) dataframe['exit_short'] = ((df['_fx'] == '-10') & (df['rsi'] < 40)).astype(int) # 多头出场:顶分型 + RSI > 60 dataframe['exit_long'] = ((df['_fx'] == '10') & (df['rsi'] > 60)).astype(int) return dataframe 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: return 2.0