diff --git a/config_examples/config_freqai.okx.json b/config_examples/config_freqai.okx.json index 2953743..e27de21 100644 --- a/config_examples/config_freqai.okx.json +++ b/config_examples/config_freqai.okx.json @@ -67,7 +67,7 @@ "freqaimodel": "CatboostClassifier", "purge_old_models": 2, "train_period_days": 15, - "identifier": "test18", + "identifier": "test51", "train_period_days": 30, "backtest_period_days": 10, "live_retrain_hours": 0, diff --git a/docker-compose.yml b/docker-compose.yml index 0dfc5a0..38000eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,14 +40,25 @@ services: # --strategy FreqaiExampleStrategy # --strategy FreqaiExampleHybridStrategy # --strategy-path /freqtrade/templates + # command: > + # backtesting + # --logfile /freqtrade/user_data/logs/freqtrade.log + # --freqaimodel XGBoostRegressor + # --config /freqtrade/config_examples/config_freqai.okx.json + # --strategy-path /freqtrade/templates + # --strategy FreqaiExampleStrategy + # --timerange 20250310-20250410 + # --export trades command: > - backtesting + hyperopt --logfile /freqtrade/user_data/logs/freqtrade.log - --freqaimodel XGBoostRegressor + --freqaimodel XGBoostClassifier --config /freqtrade/config_examples/config_freqai.okx.json --strategy-path /freqtrade/templates --strategy FreqaiExampleStrategy - --timerange 20250320-20250420 - --export trades + --timerange 20250301-20250420 + --hyperopt-loss SharpeHyperOptLoss + --spaces buy sell roi stoploss trailing + -e 200 diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index 26210c2..c83ea1b 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -1,293 +1,160 @@ import logging from functools import reduce - +import numpy as np import talib.abstract as ta from pandas import DataFrame from technical import qtpylib - -from freqtrade.strategy import IStrategy - - +from freqtrade.strategy import IntParameter, IStrategy, DecimalParameter logger = logging.getLogger(__name__) - class FreqaiExampleStrategy(IStrategy): - """ - Example strategy showing how the user connects their own - IFreqaiModel to the strategy. - Warning! This is a showcase of functionality, - which means that it is designed to show various functions of FreqAI - and it runs on all computers. We use this showcase to help users - understand how to build a strategy, and we use it as a benchmark - to help debug possible problems. + minimal_roi = { + "0": DecimalParameter(low=0.01, high=0.05, default=0.02, space="roi", optimize=True, load=True).value, + "360": DecimalParameter(low=0.005, high=0.02, default=0.01, space="roi", optimize=True, load=True).value + } + stoploss = DecimalParameter(low=-0.1, high=-0.02, default=-0.07, space="stoploss", optimize=True, load=True).value + trailing_stop = True + trailing_stop_positive = 0.01 + trailing_stop_positive_offset = 0.02 + process_only_new_candles = True + use_exit_signal = True + startup_candle_count: int = 40 + can_short = False - This means this is *not* meant to be run live in production. - """ - - minimal_roi = {"0": 0.1, "240": -1} + buy_rsi = IntParameter(low=10, high=50, default=30, space="buy", optimize=True, load=True) + sell_rsi = IntParameter(low=50, high=90, default=70, space="sell", optimize=True, load=True) plot_config = { "main_plot": {}, "subplots": { - "&-s_close": {"&-s_close": {"color": "blue"}}, - "do_predict": { - "do_predict": {"color": "brown"}, - }, + "&-up_or_down": {"&-up_or_down": {"color": "blue"}}, + "do_predict": {"do_predict": {"color": "brown"}}, }, } - process_only_new_candles = True - stoploss = -0.05 - use_exit_signal = True - # this is the maximum period fed to talib (timeframe independent) - startup_candle_count: int = 40 - can_short = False - - def feature_engineering_expand_all( - self, dataframe: DataFrame, period: int, metadata: dict, **kwargs - ) -> DataFrame: - """ - *Only functional with FreqAI enabled strategies* - This function will automatically expand the defined features on the config defined - `indicator_periods_candles`, `include_timeframes`, `include_shifted_candles`, and - `include_corr_pairs`. In other words, a single feature defined in this function - will automatically expand to a total of - `indicator_periods_candles` * `include_timeframes` * `include_shifted_candles` * - `include_corr_pairs` numbers of features added to the model. - - All features must be prepended with `%` to be recognized by FreqAI internals. - - Access metadata such as the current pair/timeframe with: - - `metadata["pair"]` `metadata["tf"]` - - More details on how these config defined parameters accelerate feature engineering - in the documentation at: - - https://www.freqtrade.io/en/latest/freqai-parameter-table/#feature-parameters - - https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features - - :param dataframe: strategy dataframe which will receive the features - :param period: period of the indicator - usage example: - :param metadata: metadata of current pair - dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period) - """ + plot_config = { + "main_plot": {}, + "subplots": { + "&-up_or_down": {"&-up_or_down": {"color": "blue"}}, + "do_predict": {"do_predict": {"color": "brown"}}, + }, + } + def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame: dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) - #dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period) dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period) dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period) - - bollinger = qtpylib.bollinger_bands( - qtpylib.typical_price(dataframe), window=period, stds=2.2 - ) + dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period) + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2) dataframe["bb_lowerband-period"] = bollinger["lower"] dataframe["bb_middleband-period"] = bollinger["mid"] dataframe["bb_upperband-period"] = bollinger["upper"] - dataframe["%-bb_width-period"] = ( dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"] ) / dataframe["bb_middleband-period"] dataframe["%-close-bb_lower-period"] = dataframe["close"] / dataframe["bb_lowerband-period"] - dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period) - dataframe["%-relative_volume-period"] = ( dataframe["volume"] / dataframe["volume"].rolling(period).mean() ) - + dataframe.replace([np.inf, -np.inf], 0, inplace=True) + dataframe.fillna(method='ffill', inplace=True) + dataframe.fillna(0, inplace=True) return dataframe - def feature_engineering_expand_basic( - self, dataframe: DataFrame, metadata: dict, **kwargs - ) -> DataFrame: - """ - *Only functional with FreqAI enabled strategies* - This function will automatically expand the defined features on the config defined - `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`. - In other words, a single feature defined in this function - will automatically expand to a total of - `include_timeframes` * `include_shifted_candles` * `include_corr_pairs` - numbers of features added to the model. - - Features defined here will *not* be automatically duplicated on user defined - `indicator_periods_candles` - - All features must be prepended with `%` to be recognized by FreqAI internals. - - Access metadata such as the current pair/timeframe with: - - `metadata["pair"]` `metadata["tf"]` - - More details on how these config defined parameters accelerate feature engineering - in the documentation at: - - https://www.freqtrade.io/en/latest/freqai-parameter-table/#feature-parameters - - https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features - - :param dataframe: strategy dataframe which will receive the features - :param metadata: metadata of current pair - dataframe["%-pct-change"] = dataframe["close"].pct_change() - dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200) - """ + def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] dataframe["%-raw_price"] = dataframe["close"] + dataframe.replace([np.inf, -np.inf], 0, inplace=True) + dataframe.fillna(method='ffill', inplace=True) + dataframe.fillna(0, inplace=True) return dataframe - def feature_engineering_standard( - self, dataframe: DataFrame, metadata: dict, **kwargs - ) -> DataFrame: - """ - *Only functional with FreqAI enabled strategies* - This optional function will be called once with the dataframe of the base timeframe. - This is the final function to be called, which means that the dataframe entering this - function will contain all the features and columns created by all other - freqai_feature_engineering_* functions. - - This function is a good place to do custom exotic feature extractions (e.g. tsfresh). - This function is a good place for any feature that should not be auto-expanded upon - (e.g. day of the week). - - All features must be prepended with `%` to be recognized by FreqAI internals. - - Access metadata such as the current pair with: - - `metadata["pair"]` - - More details about feature engineering available: - - https://www.freqtrade.io/en/latest/freqai-feature-engineering - - :param dataframe: strategy dataframe which will receive the features - :param metadata: metadata of current pair - usage example: dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7 - """ + def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek dataframe["%-hour_of_day"] = dataframe["date"].dt.hour + dataframe.replace([np.inf, -np.inf], 0, inplace=True) + dataframe.fillna(method='ffill', inplace=True) + dataframe.fillna(0, inplace=True) return dataframe def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: - """ - *Only functional with FreqAI enabled strategies* - Required function to set the targets for the model. - All targets must be prepended with `&` to be recognized by the FreqAI internals. - - Access metadata such as the current pair with: - - `metadata["pair"]` - - More details about feature engineering available: - - https://www.freqtrade.io/en/latest/freqai-feature-engineering - - :param dataframe: strategy dataframe which will receive the targets - :param metadata: metadata of current pair - usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"] - """ - dataframe["&-s_close"] = ( - dataframe["close"] - .shift(-self.freqai_info["feature_parameters"]["label_period_candles"]) - .rolling(self.freqai_info["feature_parameters"]["label_period_candles"]) - .mean() - / dataframe["close"] - - 1 - ) - - # Classifiers are typically set up with strings as targets: - # df['&s-up_or_down'] = np.where( df["close"].shift(-100) > - # df["close"], 'up', 'down') - - # If user wishes to use multiple targets, they can add more by - # appending more columns with '&'. User should keep in mind that multi targets - # requires a multioutput prediction model such as - # freqai/prediction_models/CatboostRegressorMultiTarget.py, - # freqtrade trade --freqaimodel CatboostRegressorMultiTarget - - # df["&-s_range"] = ( - # df["close"] - # .shift(-self.freqai_info["feature_parameters"]["label_period_candles"]) - # .rolling(self.freqai_info["feature_parameters"]["label_period_candles"]) - # .max() - # - - # df["close"] - # .shift(-self.freqai_info["feature_parameters"]["label_period_candles"]) - # .rolling(self.freqai_info["feature_parameters"]["label_period_candles"]) - # .min() - # ) - + logger.info(f"Setting FreqAI targets for pair: {metadata['pair']}") + if "close" not in dataframe.columns: + logger.error("Required 'close' column missing in dataframe") + raise ValueError("Required 'close' column missing in dataframe") + try: + label_period = self.freqai_info["feature_parameters"]["label_period_candles"] + dataframe["&-up_or_down"] = np.where( + dataframe["close"].shift(-label_period) > dataframe["close"], + "up", + "down" + ) + except Exception as e: + logger.error(f"Failed to create &-up_or_down column: {str(e)}") + raise + logger.info(f"Target column head:\n{dataframe[['&-up_or_down']].head().to_string()}") return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - # All indicators must be populated by feature_engineering_*() functions - - # the model will return all labels created by user in `set_freqai_targets()` - # (& appended targets), an indication of whether or not the prediction should be accepted, - # the target mean/std values for each of the labels created by user in - # `set_freqai_targets()` for each training period. - + logger.info(f"Processing pair: {metadata['pair']}") dataframe = self.freqai.start(dataframe, metadata, self) - + dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) + 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["tema"] = ta.TEMA(dataframe, timeperiod=9) + dataframe.replace([np.inf, -np.inf], 0, inplace=True) + dataframe.fillna(method='ffill', inplace=True) + dataframe.fillna(0, inplace=True) + if "&-up_or_down" in dataframe.columns: + logger.info(f"&-up_or_down value counts:\n{dataframe['&-up_or_down'].value_counts().to_string()}") + logger.info(f"do_predict value counts:\n{dataframe['do_predict'].value_counts().to_string()}") return dataframe def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: enter_long_conditions = [ + qtpylib.crossed_above(df["rsi"], self.buy_rsi.value), + df["tema"] > df["tema"].shift(1), + df["volume"] > 0, df["do_predict"] == 1, - df["&-s_close"] > 0.01, + df["&-up_or_down"] == "up" + #df["%-bb_width-period_4h"] > 0.05 ] - if enter_long_conditions: df.loc[ - reduce(lambda x, y: x & y, enter_long_conditions), ["enter_long", "enter_tag"] + reduce(lambda x, y: x & y, enter_long_conditions), + ["enter_long", "enter_tag"] ] = (1, "long") - - enter_short_conditions = [ - df["do_predict"] == 1, - df["&-s_close"] < -0.01, - ] - - if enter_short_conditions: - df.loc[ - reduce(lambda x, y: x & y, enter_short_conditions), ["enter_short", "enter_tag"] - ] = (1, "short") - return df def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame: - exit_long_conditions = [df["do_predict"] == 1, df["&-s_close"] < 0] + exit_long_conditions = [ + qtpylib.crossed_above(df["rsi"], self.sell_rsi.value), + (df["close"] < df["close"].shift(1) * 0.97), + df["volume"] > 0, + df["do_predict"] == 1, + df["&-up_or_down"] == "down" + ] if exit_long_conditions: - df.loc[reduce(lambda x, y: x & y, exit_long_conditions), "exit_long"] = 1 - - exit_short_conditions = [df["do_predict"] == 1, df["&-s_close"] > 0] - if exit_short_conditions: - df.loc[reduce(lambda x, y: x & y, exit_short_conditions), "exit_short"] = 1 - + df.loc[ + reduce(lambda x, y: x & y, exit_long_conditions), + "exit_long" + ] = 1 return df def confirm_trade_entry( - self, - pair: str, - order_type: str, - amount: float, - rate: float, - time_in_force: str, - current_time, - entry_tag, - side: str, - **kwargs, + self, pair: str, order_type: str, amount: float, rate: float, + time_in_force: str, current_time, entry_tag, side: str, **kwargs ) -> bool: df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = df.iloc[-1].squeeze() - if side == "long": if rate > (last_candle["close"] * (1 + 0.0025)): return False - else: - if rate < (last_candle["close"] * (1 - 0.0025)): - return False - return True +