# --- Do not remove these libs --- from statistics import median from freqtrade.strategy import IStrategy, stoploss_from_absolute import sys import os # 添加父目录到系统路径 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from chanlun import ChanLun from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLC_FX, Chan_BI_DIR, Chan_KLC_FX # -------------------------------- from technical.util import resample_to_interval, resampled_merge import talib.abstract as ta from pandas import DataFrame import pandas as pd from datetime import datetime, timedelta from freqtrade.persistence import Trade, Order from typing import Optional import logging logger = logging.getLogger(__name__) """ 大周期:1h 小周期:15m,30m 大周期EMA156以下找做空机会 找到最近的中枢,中枢下跌以后穿过EMA156,EMA52均线,形成死叉,macd黄白线穿越0轴 EMA24,EMA52,EMA104,EMA156成下跌趋势依次排列(EMA156 > EMA104 > EMA52 > EMA24) 做空 1. 做空开始点位条件: 确定下跌周期,价格在大于大周期的时间周期找到MACD归零轴+EMA52阻力线,按照K线动能理论,小周期确认是否背驰,背驰则开仓并且MACD穿零轴 止损放到最近的顶分型高点或者价格突破EMA156 2. 开始点位止盈策略 计算盈亏比方式:至少1:2,到达1:2后平仓一半,移动止损到开仓价,1:3再平仓剩下的一半仓位,依次类推 如果大周期遇到底背离可以平完所有仓位 3. 加仓点位 小周期顶分型+价格接近或突破大周期EMA24但是不突破EMA52后下跌可以加仓到最大仓位+大周期黄白线归零轴/小周期顶分型+小周期EMA52归零轴 大周期顶分型+大周期macd归零轴可以加仓到最大仓位 大周期顶分型或顶分型后,macd穿零轴后价格和macd红绿柱背驰可以加仓到最大仓位 小周期顶分型+大周期macd归零轴 """ ### Now you can use logger.info('asfd') to log # freqtrade plot-dataframe --strategy ChanLun_BTC --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_30.json --timerange=20250309- # freqtrade trade -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies # freqtrade backtesting -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange=20260101- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA52.json -t 1m 1m 1h 1d 1w 1M --pairs BTC/USDT:USDT --timerange=20240101- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_EMA52.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101- # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_EMA52.json -e 200 --timerange=20250201-20250901 # freqtrade edge -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901 # freqtrade plot-dataframe -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901 # sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies --timerange=20250721- # sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_EMA52.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101- # sudo docker compose run --rm chanlun_btc trade -c ./user_data/Chan/config/ChanLun_EMA52.json --strategy ChanLun_EMA52 --strategy-path ./user_data/Chan/strategies class ChanLun_EMA52(IStrategy): INTERFACE_VERSION: int = 3 # Minimal ROI designed for the strategy. # This attribute will be overridden if the config file contains "minimal_roi" # 30m and 1h minimal_roi = { "0": 0.15, "360": 0.2, "640": 0.1, "1200": 0 } # 5m and 15m minimal_roi_1 = { "0": 0.1, "60": 0.05, "120": 0.02, "240": 0 } # 15m and 30m minimal_roi_1 = { "0": 0.1, "240": 0.05, "480": 0.03, "600": 0 } minimal_roi_1 = { "0": 1.50, "120": 0.05, "240": 0.025, "360": 0 } can_short = True lev = 1.0 stoploss = -0.3 # 设置为很大的负值,让custom_stoploss来控制 use_custom_stoploss = False # 启用自定义止损 trailing_stop = False trailing_stop_positive = 0.03 trailing_stop_positive_offset = 0.06 trailing_only_offset_is_reached = False # 关闭分批止盈/仓位调整 position_adjustment_enable = False # startup_candle_count = 1600 big_tf = '1h' small_tf = '15m' last_time = None chan = ChanLun() last_order = None last_trade = None pair = 'BTC/USDT:USDT' long_tf = '1h' short_tf = '15m' long_time = 60 short_time = 15 def informative_pairs(self): return [(self.pair, "1h"), (self.pair, "1d"), #(self.pair, "1M"), (self.pair, "15m"), #(self.pair, "1w"), ] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe = self.add_indicators(dataframe) long_df = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h') long_df = self.add_indicators(long_df) dataframe['rsi'] = ta.RSI(long_df, timeperiod=14) if self.last_time is None or self.last_time + timedelta(seconds=10) < datetime.now(): self.last_time = datetime.now() logger.info("init_dataframes----------------------------") last_price = dataframe.iloc[-1]['close'] date = dataframe.iloc[-1]['date'] tf_ema52_list = self.chan.check_price_ema52(last_price) self.init_dataframes(dataframe) logger.info("Date: " + date.strftime('%Y-%m-%d %H:%M:%S') + " Price: " + str(last_price) + " EMA52_list: " + str(tf_ema52_list)) #print(long_df.iloc[-1]) dataframe = resampled_merge(dataframe, long_df) return dataframe def ema_dir(self, dataframe): """ 趋势方向综合判断,分为三个维度: 1. ema_dir: 主趋势方向 (基于MACD零轴 + 价格与EMA52/EMA156关系) - 3: 强多(价格在EMA156上方 + MACD在零轴上方 + 价格在EMA24上方) - 2: 中多(价格在EMA156上方 + MACD在零轴上方) - 1: 弱多(价格在EMA52上方 或 MACD在零轴上方,满足其一) - -1: 弱空(价格在EMA52下方 或 MACD在零轴下方,满足其一) - -2: 中空(价格在EMA156下方 + MACD在零轴下方) - -3: 强空(价格在EMA156下方 + MACD在零轴下方 + 价格在EMA24下方) - 0: 盘整(无明确方向) 2. ema_align: EMA排列状态(辅助确认趋势强度) - 1: 多头排列 (EMA24 > EMA52 > EMA104 > EMA156) - -1: 空头排列 (EMA24 < EMA52 < EMA104 < EMA156) - 0: 交叉/纠缠 3. ema_slope: EMA52斜率方向(趋势加速/减速判断) - 正值: EMA52向上倾斜,趋势加速 - 负值: EMA52向下倾斜,趋势减速 """ close = dataframe['close'] ema24 = dataframe['ema24'] ema52 = dataframe['ema52'] ema104 = dataframe['ema104'] ema156 = dataframe['ema156'] macd_signal = dataframe['macdsignal'] # 黄线(慢线),用于判断零轴 # === 1. 主趋势方向 === # 核心条件:价格与EMA156的关系(大趋势)+ MACD黄线与零轴的关系 above_ema156 = close > ema156 below_ema156 = close < ema156 above_ema52 = close > ema52 below_ema52 = close < ema52 above_ema24 = close > ema24 below_ema24 = close < ema24 macd_above_zero = macd_signal > 0 macd_below_zero = macd_signal < 0 dataframe['ema_dir'] = 0 # 强多:价格在EMA156上方 + MACD零轴上方 + 价格在EMA24上方(超强势结构) dataframe.loc[above_ema156 & macd_above_zero & above_ema24, 'ema_dir'] = 3 # 中多:价格在EMA156上方 + MACD零轴上方 dataframe.loc[above_ema156 & macd_above_zero & ~above_ema24, 'ema_dir'] = 2 # 弱多:满足其一(价格在EMA52上方 或 MACD零轴上方) dataframe.loc[(above_ema52 & ~macd_above_zero) | (macd_above_zero & ~above_ema156), 'ema_dir'] = 1 # 弱空:满足其一(价格在EMA52下方 或 MACD零轴下方) dataframe.loc[(below_ema52 & ~macd_below_zero) | (macd_below_zero & ~below_ema156), 'ema_dir'] = -1 # 中空:价格在EMA156下方 + MACD零轴下方 dataframe.loc[below_ema156 & macd_below_zero & ~below_ema24, 'ema_dir'] = -2 # 强空:价格在EMA156下方 + MACD零轴下方 + 价格在EMA24下方(超强空势结构) dataframe.loc[below_ema156 & macd_below_zero & below_ema24, 'ema_dir'] = -3 # === 2. EMA排列状态(辅助参考)=== bull_align = (ema24 > ema52) & (ema52 > ema104) & (ema104 > ema156) bear_align = (ema24 < ema52) & (ema52 < ema104) & (ema104 < ema156) dataframe['ema_align'] = 0 dataframe.loc[bull_align, 'ema_align'] = 1 dataframe.loc[bear_align, 'ema_align'] = -1 # === 3. EMA52斜率(趋势加速/减速)=== # 用EMA52的变化率判断趋势是否在加速 dataframe['ema_slope'] = (ema52 - ema52.shift(3)) / ema52.shift(3) * 100 return dataframe def add_indicators(self, dataframe): dataframe['ema24'] = ta.EMA(dataframe, timeperiod=24) dataframe['dir24'] = dataframe['close'] - dataframe['ema24'] dataframe['ema52'] = ta.EMA(dataframe, timeperiod=52) dataframe['dir52'] = dataframe['close'] - dataframe['ema52'] dataframe['ema104'] = ta.EMA(dataframe, timeperiod=104) dataframe['dir104'] = dataframe['close'] - dataframe['ema104'] dataframe['ema156'] = ta.EMA(dataframe, timeperiod=156) dataframe['dir156'] = dataframe['close'] - dataframe['ema156'] dataframe['dir52_156'] = dataframe['dir52'] - dataframe['dir156'] dataframe_macd = ta.MACD(dataframe, fast=12, slow=26, signal=9) dataframe['macdsignal'] = dataframe_macd['macdsignal'] dataframe['macd'] = dataframe_macd['macd'] dataframe['macdhist'] = dataframe_macd['macdhist'] return dataframe def init_dataframes(self, dataframe_1m): dataframe_15m = self.dp.get_pair_dataframe(pair=self.pair, timeframe='15m') dataframe_1h = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h') dataframe_1d = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1d') #dataframe_1w = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1w') #dataframe_1M = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1M') self.chan.init_dataframes(dataframe_1m, dataframe_15m,dataframe_1h, dataframe_1d) def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float, entry_tag: str | None, side: str, **kwargs) -> float: new_entryprice = proposed_rate if trade: if trade.is_short: new_entryprice = proposed_rate - 50 else: new_entryprice = proposed_rate + 50 return new_entryprice def custom_exit_price(self, pair: str, trade: Trade, current_time: datetime, proposed_rate: float, current_profit: float, exit_tag: str | None, **kwargs) -> float: new_exitprice = proposed_rate if trade: if trade.is_short: new_exitprice = proposed_rate + 50 else: new_exitprice = proposed_rate - 50 return new_exitprice 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]: # 关闭分批止盈,始终不调整仓位 return None def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs): # 不做分批止盈/最终止盈处理,退出由策略信号/ROI/止损决定 return None def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ (dataframe['dir156'] > 0) & (dataframe['dir52_156'] > 0) & (dataframe['macdhist'] > 0), 'enter_long'] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ (dataframe['dir156'] < 0) & (dataframe['dir52_156'] < 0) & (dataframe['macdhist'] < 0), 'exit_long'] = 1 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 self.lev