# --- Do not remove these libs --- from freqtrade.strategy import IStrategy from typing import Dict, List from functools import reduce from pandas import DataFrame, pandas import freqtrade.vendor.qtpylib.indicators as qtpylib # -------------------------------- from technical.util import resample_to_interval, resampled_merge import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib from datetime import datetime, timedelta, timezone from freqtrade.persistence import Trade, Order from typing import Optional import logging logger = logging.getLogger(__name__) ### Now you can use logger.info('asfd') to log # freqtrade trade -c ./user_data/Chan/config/ChanLun_SOL.json --strategy ChanLun_SOL_2 --strategy-path ./user_data/Chan/strategies # freqtrade backtesting -c ./user_data/Chan/config/ChanLun_SOL.json --strategy ChanLun_SOL_2 --strategy-path ./user_data/Chan/strategies --timerange=20250309- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_SOL.json -t 1m --pairs SOL/USDT:USDT --timerange=20250501- # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy ChanLun_SOL_2 --strategy-path ./user_data/strategies -c ./user_data/ChanLun_SOL.json -e 200 --timerange=20250101-20250215 # sudo docker compose run --rm chan_btc backtesting -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies --timerange=20250101- # sudo docker compose run --rm chan_btc download-data -c ./user_data/Chan.json --pairs SOL/USDT:USDT -t 1m --timerange 20240101- # sudo docker compose run --rm chan_btc trade -c ./user_data/Chan.json --strategy Chan_SOL_2 --strategy-path ./user_data/strategies class ChanLun_SOL_2(IStrategy): INTERFACE_VERSION: int = 3 # 优化的ROI设置 - 更快速获利 minimal_roi = { "0": 0.012, # 立即获利1.2% "5": 0.01, # 5分钟后获利1% "15": 0.007, # 15分钟后获利0.7% "30": 0.005 # 30分钟后获利0.5% } can_short = True stoploss = -0.007 # 降低止损为0.7% # 追踪止损设置 - 更积极的追踪止损 trailing_stop = True trailing_stop_positive = 0.003 # 0.3% trailing_stop_positive_offset = 0.005 # 0.5% trailing_only_offset_is_reached = True # 时间周期 timeframe = '5m' informative_timeframe = '1h' startup_candle_count = 200 # 只做空头策略 only_short = True def informative_pairs(self): pairs = self.dp.current_whitelist() informative_pairs = [(pair, self.informative_timeframe) for pair in pairs] return informative_pairs def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # 获取更高时间周期的数据 informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.informative_timeframe) # === 高时间周期指标 === # 三均线系统 informative['ema50'] = ta.EMA(informative, timeperiod=50) informative['ema100'] = ta.EMA(informative, timeperiod=100) informative['ema200'] = ta.SMA(informative, timeperiod=200) # 使用SMA作为长期趋势 # 趋势方向 informative['uptrend'] = ( (informative['ema50'] > informative['ema100']) & (informative['ema100'] > informative['ema200']) & (informative['close'] > informative['ema50']) ).astype(int) informative['downtrend'] = ( (informative['ema50'] < informative['ema100']) & (informative['ema100'] < informative['ema200']) & (informative['close'] < informative['ema50']) ).astype(int) # 强下降趋势 informative['strong_downtrend'] = ( (informative['ema50'] < informative['ema100']) & (informative['ema100'] < informative['ema200']) & (informative['close'] < informative['ema50']) & (informative['ema50'].shift(3) < informative['ema50']) # 确认EMA50下降 ).astype(int) # 添加高时间周期的ADX指标 informative['adx'] = ta.ADX(informative, timeperiod=14) # 添加高时间周期的波动率 informative['atr'] = ta.ATR(informative, timeperiod=14) informative['atr_percent'] = (informative['atr'] / informative['close']) * 100 # 高时间周期RSI informative['rsi'] = ta.RSI(informative, timeperiod=14) # 将informative数据帧中的列重命名,以便在合并后区分 for col in informative.columns: if col not in ['date', 'open', 'high', 'low', 'close', 'volume']: informative[f"{col}_{self.informative_timeframe}"] = informative[col] # 删除原始列,只保留重命名后的列和必要的日期、OHLCV列 for col in list(informative.columns): if col not in ['date', 'open', 'high', 'low', 'close', 'volume'] and not col.endswith(f"_{self.informative_timeframe}"): del informative[col] # 打印列名以便调试 logger.info(f"Informative columns after renaming: {informative.columns.tolist()}") # 合并数据 - 使用正确的参数 dataframe = resampled_merge(dataframe, informative, self.informative_timeframe) # 打印合并后的列名以便调试 logger.info(f"Dataframe columns after merge: {dataframe.columns.tolist()}") # === 主时间周期指标 === # 布林带 bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] dataframe['bb_width'] = ((bollinger['upper'] - bollinger['lower']) / bollinger['mid']) # 动量指标 dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) dataframe['mfi'] = ta.MFI(dataframe, timeperiod=14) # MACD macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] dataframe['macdhist'] = macd['macdhist'] # 均线 dataframe['ema9'] = ta.EMA(dataframe, timeperiod=9) dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21) dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) dataframe['sma200'] = ta.SMA(dataframe, timeperiod=200) # 成交量 dataframe['volume_mean'] = dataframe['volume'].rolling(window=20).mean() dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_mean'] # 波动率 dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) # ADX - 趋势强度指标 dataframe['adx'] = ta.ADX(dataframe, timeperiod=14) # 价格突破 dataframe['upper_break'] = ( (dataframe['close'] > dataframe['bb_upperband']) & (dataframe['close'].shift() <= dataframe['bb_upperband'].shift()) ).astype(int) dataframe['lower_break'] = ( (dataframe['close'] < dataframe['bb_lowerband']) & (dataframe['close'].shift() >= dataframe['bb_lowerband'].shift()) ).astype(int) # 均线交叉 dataframe['ema_cross_up'] = ( (dataframe['ema9'] > dataframe['ema21']) & (dataframe['ema9'].shift() <= dataframe['ema21'].shift()) ).astype(int) dataframe['ema_cross_down'] = ( (dataframe['ema9'] < dataframe['ema21']) & (dataframe['ema9'].shift() >= dataframe['ema21'].shift()) ).astype(int) # 超买超卖区域 dataframe['rsi_oversold'] = (dataframe['rsi'] < 30).astype(int) dataframe['rsi_overbought'] = (dataframe['rsi'] > 70).astype(int) # 价格与均线的关系 dataframe['price_above_ema50'] = (dataframe['close'] > dataframe['ema50']).astype(int) dataframe['price_below_ema50'] = (dataframe['close'] < dataframe['ema50']).astype(int) # 趋势强度 dataframe['strong_trend'] = (dataframe['adx'] > 25).astype(int) # 添加蜡烛图形态识别 dataframe['doji'] = ta.CDLDOJI(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close']) dataframe['engulfing'] = ta.CDLENGULFING(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close']) dataframe['hammer'] = ta.CDLHAMMER(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close']) dataframe['shooting_star'] = ta.CDLSHOOTINGSTAR(dataframe['open'], dataframe['high'], dataframe['low'], dataframe['close']) # 价格动量 dataframe['momentum'] = dataframe['close'] - dataframe['close'].shift(5) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # 检查列名是否存在 downtrend_col = 'resample_60_downtrend_1h' strong_downtrend_col = 'resample_60_strong_downtrend_1h' adx_col = 'resample_60_adx_1h' rsi_col = 'resample_60_rsi_1h' # 如果列名不存在,使用替代方案 for col, default_value in [ (downtrend_col, 0), (strong_downtrend_col, 0), (adx_col, 25), (rsi_col, 50) ]: if col not in dataframe.columns: logger.warning(f"Column {col} not found in dataframe. Creating with default value {default_value}.") dataframe[col] = default_value # 禁用多头入场 dataframe['enter_long'] = 0 # 空头入场条件 - 专注于空头策略 short_conditions = ( # 高时间周期处于下降趋势 (dataframe[downtrend_col] > 0) & # 趋势强度确认 (dataframe[adx_col] > 25) & # 条件1: 价格突破上轨后回落 + 成交量确认 ( (dataframe['upper_break'].rolling(window=5).sum() > 0) & # 最近5根K线内有突破上轨 (dataframe['close'] < dataframe['close'].shift(2)) & # 价格开始下跌 (dataframe['close'] < dataframe['ema9']) & # 价格在短期均线下方 (dataframe['volume_ratio'] > 1.3) & # 成交量放大 (dataframe['rsi'] < 70) & # RSI不在极度超买区 (dataframe['rsi'] > 40) & # RSI不在超卖区 (dataframe[rsi_col] < 60) # 高时间周期RSI不过高 ) | # 条件2: 均线死叉 + RSI超买回落 + 趋势确认 ( (dataframe['ema_cross_down'] > 0) & # 均线死叉 (dataframe['rsi'] > 55) & # RSI相对较高 (dataframe['rsi'] < dataframe['rsi'].shift(3)) & # RSI下降 (dataframe['volume_ratio'] > 1.2) & # 成交量放大 (dataframe['adx'] > 20) & # ADX显示有一定趋势强度 ((dataframe['shooting_star'] > 0) | (dataframe['engulfing'] < 0)) # 流星线或看跌吞没形态 ) | # 条件3: 价格在高点回落 + 强趋势 ( (dataframe['close'] < dataframe['high'].shift()) & (dataframe['high'].shift() > dataframe['high'].shift(2)) & (dataframe['close'] < dataframe['ema21']) & (dataframe['adx'] > 30) & (dataframe['rsi'] < dataframe['rsi'].shift()) & (dataframe['rsi'].shift() > 65) & (dataframe['volume_ratio'] > 1.0) ) | # 条件4: 强下降趋势确认 ( (dataframe[strong_downtrend_col] > 0) & (dataframe['close'] < dataframe['ema21']) & (dataframe['close'] < dataframe['close'].shift(3)) & (dataframe['momentum'] < 0) & (dataframe['volume_ratio'] > 1.1) & (dataframe['adx'] > 25) ) ) dataframe.loc[short_conditions, 'enter_short'] = 1 dataframe.loc[short_conditions, 'enter_tag'] = 'chan_sol_short' return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # 禁用多头出场 dataframe['exit_long'] = 0 # 空头出场条件 - 更精确的出场 short_exit_conditions = ( # 条件1: 趋势反转信号 ( (dataframe['ema_cross_up'] > 0) & # 均线金叉 (dataframe['volume_ratio'] > 1.0) # 成交量确认 ) | # 条件2: 价格突破中期均线 ( (dataframe['close'] > dataframe['ema21']) & (dataframe['close'].shift() < dataframe['ema21'].shift()) & # 确认是刚刚突破 (dataframe['volume_ratio'] > 1.2) # 成交量确认 ) | # 条件3: 超卖信号 ( (dataframe['rsi'] < 30) & # RSI超卖 (dataframe['close'] < dataframe['bb_lowerband']) # 价格突破下轨 ) | # 条件4: 动量减弱 ( (dataframe['rsi'] < 35) & (dataframe['rsi'] > dataframe['rsi'].shift()) & (dataframe['rsi'].shift() > dataframe['rsi'].shift(2)) & # RSI连续两根K线上升 (dataframe['momentum'] > 0) # 价格动量转为正 ) | # 条件5: 锤子线形态 (潜在反转信号) ( (dataframe['hammer'] > 0) & (dataframe['volume_ratio'] > 1.3) ) ) dataframe.loc[short_exit_conditions, 'exit_short'] = 1 dataframe.loc[short_exit_conditions, 'exit_tag'] = 'chan_sol_short_exit' return dataframe def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, time_in_force: str, current_time: datetime, entry_tag: Optional[str], side: str, **kwargs) -> bool: """ 在进入交易前进行额外的确认 """ # 只做空头交易 if side == "sell" and entry_tag == "chan_sol_short": return True return False 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 1.0 def get_ticker_indicator(self): return int(self.timeframe[:-1])