From 3b97b3d5c8158905e81fddb2ab36306bccf07e70 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Thu, 15 Sep 2022 00:56:51 +0200 Subject: [PATCH] fix mypy error for strategy --- .../RL/BaseReinforcementLearningModel.py | 9 +- freqtrade/freqai/freqai_interface.py | 2 + freqtrade/strategy/interface.py | 1 - tests/strategy/strats/freqai_rl_test_strat.py | 139 ++++++++++++++++++ 4 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 tests/strategy/strats/freqai_rl_test_strat.py diff --git a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py index f822208f8..a583fc9cd 100644 --- a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py +++ b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py @@ -155,12 +155,13 @@ class BaseReinforcementLearningModel(IFreqaiModel): trade_duration = 0 for trade in open_trades: if trade.pair == pair: - # FIXME: mypy typing doesnt like that strategy may be "None" (it never will be) # FIXME: get_rate and trade_udration shouldn't work with backtesting, # we need to use candle dates and prices to compute that. - pytest.set_trace() - current_value = self.strategy.dp._exchange.get_rate( - pair, refresh=False, side="exit", is_short=trade.is_short) + if self.strategy.dp._exchange is None: + logger.error('No exchange available.') + else: + current_value = self.strategy.dp._exchange.get_rate( + pair, refresh=False, side="exit", is_short=trade.is_short) openrate = trade.open_rate now = datetime.now(timezone.utc).timestamp() trade_duration = int((now - trade.open_date.timestamp()) / self.base_tf_seconds) diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index 7b35cd918..7550f1884 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -92,6 +92,7 @@ class IFreqaiModel(ABC): self._threads: List[threading.Thread] = [] self._stop_event = threading.Event() + self.strategy: IStrategy = None def __getstate__(self): """ @@ -119,6 +120,7 @@ class IFreqaiModel(ABC): self.live = strategy.dp.runmode in (RunMode.DRY_RUN, RunMode.LIVE) self.dd.set_pair_dict_info(metadata) + self.strategy = strategy if self.live: self.inference_timer('start') diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 03ca4af70..9401ebebe 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -160,7 +160,6 @@ class IStrategy(ABC, HyperStrategyMixin): "already on disk." ) download_all_data_for_training(self.dp, self.config) - self.freqai.strategy = self else: # Gracious failures if freqAI is disabled but "start" is called. class DummyClass(): diff --git a/tests/strategy/strats/freqai_rl_test_strat.py b/tests/strategy/strats/freqai_rl_test_strat.py new file mode 100644 index 000000000..7b36dc6be --- /dev/null +++ b/tests/strategy/strats/freqai_rl_test_strat.py @@ -0,0 +1,139 @@ +import logging +from functools import reduce + +import pandas as pd +import talib.abstract as ta +from pandas import DataFrame + +from freqtrade.strategy import IStrategy, merge_informative_pair + + +logger = logging.getLogger(__name__) + + +class freqai_rl_test_strat(IStrategy): + """ + Test strategy - used for testing freqAI functionalities. + DO not use in production. + """ + + minimal_roi = {"0": 0.1, "240": -1} + + plot_config = { + "main_plot": {}, + "subplots": { + "prediction": {"prediction": {"color": "blue"}}, + "target_roi": { + "target_roi": {"color": "brown"}, + }, + "do_predict": { + "do_predict": {"color": "brown"}, + }, + }, + } + + process_only_new_candles = True + stoploss = -0.05 + use_exit_signal = True + startup_candle_count: int = 30 + can_short = False + + def informative_pairs(self): + whitelist_pairs = self.dp.current_whitelist() + corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"] + informative_pairs = [] + for tf in self.config["freqai"]["feature_parameters"]["include_timeframes"]: + for pair in whitelist_pairs: + informative_pairs.append((pair, tf)) + for pair in corr_pairs: + if pair in whitelist_pairs: + continue # avoid duplication + informative_pairs.append((pair, tf)) + return informative_pairs + + def populate_any_indicators( + self, pair, df, tf, informative=None, set_generalized_indicators=False + ): + + coin = pair.split('/')[0] + + if informative is None: + informative = self.dp.get_pair_dataframe(pair, tf) + + # first loop is automatically duplicating indicators for time periods + for t in self.freqai_info["feature_parameters"]["indicator_periods_candles"]: + + t = int(t) + informative[f"%-{coin}rsi-period_{t}"] = ta.RSI(informative, timeperiod=t) + informative[f"%-{coin}mfi-period_{t}"] = ta.MFI(informative, timeperiod=t) + informative[f"%-{coin}adx-period_{t}"] = ta.ADX(informative, window=t) + + # FIXME: add these outside the user strategy? + # The following columns are necessary for RL models. + informative[f"%-{coin}raw_close"] = informative["close"] + informative[f"%-{coin}raw_open"] = informative["open"] + informative[f"%-{coin}raw_high"] = informative["high"] + informative[f"%-{coin}raw_low"] = informative["low"] + + indicators = [col for col in informative if col.startswith("%")] + # This loop duplicates and shifts all indicators to add a sense of recency to data + for n in range(self.freqai_info["feature_parameters"]["include_shifted_candles"] + 1): + if n == 0: + continue + informative_shift = informative[indicators].shift(n) + informative_shift = informative_shift.add_suffix("_shift-" + str(n)) + informative = pd.concat((informative, informative_shift), axis=1) + + df = merge_informative_pair(df, informative, self.config["timeframe"], tf, ffill=True) + skip_columns = [ + (s + "_" + tf) for s in ["date", "open", "high", "low", "close", "volume"] + ] + df = df.drop(columns=skip_columns) + + # Add generalized indicators here (because in live, it will call this + # function to populate indicators during training). Notice how we ensure not to + # add them multiple times + if set_generalized_indicators: + df["%-day_of_week"] = (df["date"].dt.dayofweek + 1) / 7 + df["%-hour_of_day"] = (df["date"].dt.hour + 1) / 25 + + # For RL, there are no direct targets to set. This is filler (neutral) + # until the agent sends an action. + df["&-action"] = 0 + + return df + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + + dataframe = self.freqai.start(dataframe, metadata, self) + + return dataframe + + def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: + + enter_long_conditions = [df["do_predict"] == 1, df["&-action"] == 1] + + if enter_long_conditions: + df.loc[ + reduce(lambda x, y: x & y, enter_long_conditions), ["enter_long", "enter_tag"] + ] = (1, "long") + + enter_short_conditions = [df["do_predict"] == 1, df["&-action"] == 3] + + 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["&-action"] == 2] + 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["&-action"] == 4] + if exit_short_conditions: + df.loc[reduce(lambda x, y: x & y, exit_short_conditions), "exit_short"] = 1 + + return df