# --- Do not remove these libs --- from statistics import median from freqtrade.strategy import IStrategy import sys import os # 添加父目录到系统路径 sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from ChanLun import ChanLun from ChanLun_Classifier import ChanLunClassifier from 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 from datetime import datetime, timedelta from freqtrade.persistence import Trade from typing import Optional, List, Dict import logging logger = logging.getLogger(__name__) from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal ### Now you can use logger.info('asfd') to log # freqtrade plot-dataframe --strategy ChanLun_BTC_15 --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_15.json --timerange=20250309- # freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies # freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --export none --strategy-path ./user_data/Chan/strategies --timerange=20250525- # freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_15.json -t 1m --pairs SOL/USDT:USDT --timerange=20250405- # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces stoploss --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_15.json -e 200 --timerange=20250201-20250501 # freqtrade live-backtest -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- # sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- # sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_BTC_15.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101- # sudo docker compose run --rm chanlun_btc trade -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies class ChanLun_BTC_15(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, "240": 0.1, "480": 0.02, "960": 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_2 = { "0": 0.10, "1200": 0.05, "2400": 0.025, "3600": 0 } can_short = True lev = 10 stoploss = -0.8 trailing_stop = False trailing_stop_positive = 0.025 trailing_stop_positive_offset = 0.045 trailing_only_offset_is_reached = False startup_candle_count = 600 time5 = 5 time15 = 15 time30 = 30 time60 = 60 time4h = 240 time5 = 15 last_time = datetime.now() chan = ChanLun() classifier = ChanLunClassifier(None) def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: # resample our dataframes dataframe_5 = resample_to_interval(dataframe, self.get_ticker_indicator() * 5) dataframe_15 = resample_to_interval(dataframe, self.get_ticker_indicator() * 15) dataframe_30 = resample_to_interval(dataframe, self.get_ticker_indicator() * 30) dataframe_60 = resample_to_interval(dataframe, self.get_ticker_indicator() * 60) dataframe_4h = resample_to_interval(dataframe, self.get_ticker_indicator() * 240) #dataframe_1d = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='1d') #dataframe_1w = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 10080) #dataframe_1m = resample_to_interval(dataframe_1d, self.get_ticker_indicator() * 43200) dataframe_1d = resample_to_interval(dataframe, self.get_ticker_indicator() * 1440) #dataframe_1w = resample_to_interval(dataframe, self.get_ticker_indicator() * 10080) #dataframe_1m = resample_to_interval(dataframe, self.get_ticker_indicator() * 43200) dataframe = self.add_indicators(dataframe) dataframe_5 = self.add_indicators(dataframe_5) dataframe_30 = self.add_indicators(dataframe_30) dataframe_60 = self.add_indicators(dataframe_60) dataframe_4h = self.add_indicators(dataframe_4h) dataframe_1d = self.add_indicators(dataframe_1d) #self.chan.plot_dual(dataframe_5, dataframe_30) dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) state_list, fx_list = self.chan.get_klc_strength_list(dataframe_15) dataframe_15['state'] = state_list dataframe_15['fx'] = fx_list klc_list = self.chan.get_klc_list(dataframe_15) bi_list = self.chan.cal_bi_list(klc_list) if self.last_time + timedelta(minutes=1) < datetime.now(): print(state_list[-1], state_list[-2], state_list[-3], state_list[-4], state_list[-5]) print(fx_list[-1], fx_list[-2], fx_list[-3], fx_list[-4], fx_list[-5]) print(klc_list[-1].klc_fx_type, klc_list[-2].klc_fx_type, klc_list[-3].klc_fx_type, klc_list[-4].klc_fx_type, klc_list[-5].klc_fx_type) print("-------------------------------------------------------------------------------") self.last_time = datetime.now() #dataframe = resampled_merge(dataframe, dataframe_5) dataframe = resampled_merge(dataframe, dataframe_15) #dataframe = resampled_merge(dataframe, dataframe_30) #dataframe = resampled_merge(dataframe, dataframe_60) #dataframe = resampled_merge(dataframe, dataframe_4h) return dataframe def add_indicators(self, df): fast = 8 slow = 16 period = 6 macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period) df['macd'] = macd['macd'] df['macdsignal'] = macd['macdsignal'] df['macdhist'] = macd['macdhist'] df['ma5'] = ta.MA(df, timeperiod=5) df['ma10'] = ta.MA(df, timeperiod=10) df['ma30'] = ta.EMA(df, timeperiod=30) df['ma250'] = ta.MA(df, timeperiod=250) df['rsi'] = ta.RSI(df, timeperiod=14) df['volume_ratio'] = self.cal_volume_ratio(df) return df def cal_volume_ratio(self, dataframe, window=10): df = dataframe.copy() # 计算过去N根K线的平均成交量 df['avg_volume'] = df['volume'].rolling(window=window).mean() # 计算量比 df['volume_ratio'] = df['volume'] / df['avg_volume'] # 填充缺失值(前N根K线) df['volume_ratio'] = df['volume_ratio'].fillna(1.0) return df['volume_ratio'] 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 populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time5) fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time5) dataframe.loc[ ( #(dataframe['state'] == "-30") (dataframe[state_str].shift(self.time5*2) > 0) & (dataframe[fx_str].shift(self.time5*2) == -1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) ), ['enter_long', 'enter_tag']] = (1, 'long_signal_chan') dataframe.loc[ ( #(dataframe['state'] == "-30") (dataframe[state_str].shift(self.time5*2) > 0) & (dataframe[fx_str].shift(self.time5*2) == 1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") #(qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal'])) ), ['enter_short', 'enter_tag']] = (1, 'short_signal_chan') return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time5) fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time5) dataframe.loc[ ( #(dataframe['state']== "30") (dataframe[state_str].shift(self.time5*2) > 0) & (dataframe[fx_str].shift(self.time5*2) == 1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") ), ['exit_long', 'exit_tag']] = (1, 'long_close_signal_chan') dataframe.loc[ ( #(dataframe['state']== "30") (dataframe[state_str].shift(self.time5*2) > 0) & (dataframe[fx_str].shift(self.time5*2) == -1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") ), ['exit_short', 'exit_tag']] = (1, 'short_close_signal_chan') 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 def get_ticker_indicator(self): return int(self.timeframe[:-1])