diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 0b52af17b..8ae0c1d16 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -114,7 +114,7 @@ class AwesomeStrategy(IStrategy): See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks. -## Buy Signal Name +## Buy Tag When your strategy has multiple buy signal, you can name it. Then you can access you buy signal on `custom_sell` @@ -126,7 +126,7 @@ def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: (dataframe['rsi'] < 35) & (dataframe['volume'] > 0) ), - ['buy', 'buy_signal_name']] = 1, 'buy_signal_rsi' + ['buy', 'buy_tag']] = 1, 'buy_signal_rsi' return dataframe @@ -134,7 +134,7 @@ def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_r current_profit: float, **kwargs): dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) last_candle = dataframe.iloc[-1].squeeze() - if trade.buy_signal_name == 'buy_signal_rsi' and last_candle['rsi'] > 80: + if trade.buy_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80: return 'sell_signal_rsi' return None diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index f6122c5aa..d62712cbb 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date', 'fee_open', 'fee_close', 'trade_duration', 'profit_ratio', 'profit_abs', 'sell_reason', 'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs', - 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_signal_name'] + 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag'] def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index 71e9d7d9e..d803baf31 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -3,5 +3,5 @@ from freqtrade.enums.backteststate import BacktestState from freqtrade.enums.rpcmessagetype import RPCMessageType from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode from freqtrade.enums.selltype import SellType -from freqtrade.enums.signaltype import SignalNameType, SignalType +from freqtrade.enums.signaltype import SignalTagType, SignalType from freqtrade.enums.state import State diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index 8529c6b79..d2995d57a 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -9,8 +9,8 @@ class SignalType(Enum): SELL = "sell" -class SignalNameType(Enum): +class SignalTagType(Enum): """ Enum for signal columns """ - BUY_SIGNAL_NAME = "buy_signal_name" + BUY_TAG = "buy_tag" diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 95c769730..cb9157d33 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -420,7 +420,7 @@ class FreqtradeBot(LoggingMixin): return False # running get_signal on historical data fetched - (buy, sell, buy_signal_name) = self.strategy.get_signal( + (buy, sell, buy_tag) = self.strategy.get_signal( pair, self.strategy.timeframe, analyzed_df @@ -431,13 +431,13 @@ class FreqtradeBot(LoggingMixin): bid_check_dom = self.config.get('bid_strategy', {}).get('check_depth_of_market', {}) if ((bid_check_dom.get('enabled', False)) and - (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): + (bid_check_dom.get('bids_to_ask_delta', 0) > 0)): if self._check_depth_of_market_buy(pair, bid_check_dom): - return self.execute_buy(pair, stake_amount, buy_signal_name=buy_signal_name) + return self.execute_buy(pair, stake_amount, buy_tag=buy_tag) else: return False - return self.execute_buy(pair, stake_amount, buy_signal_name=buy_signal_name) + return self.execute_buy(pair, stake_amount, buy_tag=buy_tag) else: return False @@ -466,7 +466,7 @@ class FreqtradeBot(LoggingMixin): return False def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None, - forcebuy: bool = False, buy_signal_name: str = '') -> bool: + forcebuy: bool = False, buy_tag: str = '') -> bool: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY @@ -569,7 +569,7 @@ class FreqtradeBot(LoggingMixin): exchange=self.exchange.id, open_order_id=order_id, strategy=self.strategy.get_strategy_name(), - buy_signal_name=buy_signal_name, + buy_tag=buy_tag, timeframe=timeframe_to_minutes(self.config['timeframe']) ) trade.orders.append(order_obj) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7fdc70e70..5099c07ef 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -43,7 +43,7 @@ CLOSE_IDX = 3 SELL_IDX = 4 LOW_IDX = 5 HIGH_IDX = 6 -BUY_SIGNAL_NAME_IDX = 7 +buy_tag_IDX = 7 class Backtesting: @@ -210,7 +210,7 @@ class Backtesting: """ # Every change to this headers list must evaluate further usages of the resulting tuple # and eventually change the constants for indexes at the top - headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_signal_name'] + headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag'] data: Dict = {} self.progress.init_step(BacktestState.CONVERT, len(processed)) @@ -221,7 +221,7 @@ class Backtesting: if not pair_data.empty: pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist - pair_data.loc[:, 'buy_signal_name'] = '' # cleanup if buy_signal_name is exist + pair_data.loc[:, 'buy_tag'] = '' # cleanup if buy_tag is exist df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() @@ -230,7 +230,7 @@ class Backtesting: # from the previous candle df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1) - df_analyzed.loc[:, 'buy_signal_name'] = df_analyzed.loc[:, 'buy_signal_name'].shift(1) + df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) df_analyzed.drop(df_analyzed.head(1).index, inplace=True) @@ -265,7 +265,7 @@ class Backtesting: # Worst case: price reaches stop_positive_offset and dives down. stop_rate = (sell_row[OPEN_IDX] * (1 + abs(self.strategy.trailing_stop_positive_offset) - - abs(self.strategy.trailing_stop_positive))) + abs(self.strategy.trailing_stop_positive))) else: # Worst case: price ticks tiny bit above open and dives down. stop_rate = sell_row[OPEN_IDX] * (1 - abs(trade.stop_loss_pct)) @@ -370,7 +370,7 @@ class Backtesting: fee_open=self.fee, fee_close=self.fee, is_open=True, - buy_signal_name=row[BUY_SIGNAL_NAME_IDX], + buy_tag=row[buy_tag_IDX], exchange='backtesting', ) return trade diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index f6a345ed1..035452437 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -47,7 +47,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col min_rate = get_column_def(cols, 'min_rate', 'null') sell_reason = get_column_def(cols, 'sell_reason', 'null') strategy = get_column_def(cols, 'strategy', 'null') - buy_signal_name = get_column_def(cols, 'buy_signal_name', 'null') + buy_tag = get_column_def(cols, 'buy_tag', 'null') # If ticker-interval existed use that, else null. if has_column(cols, 'ticker_interval'): timeframe = get_column_def(cols, 'timeframe', 'ticker_interval') @@ -81,7 +81,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col stake_amount, amount, amount_requested, open_date, close_date, open_order_id, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stoploss_order_id, stoploss_last_update, - max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_signal_name, + max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag, timeframe, open_trade_value, close_profit_abs ) select id, lower(exchange), @@ -104,7 +104,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {sell_order_status} sell_order_status, - {strategy} strategy, {buy_signal_name} buy_signal_name, {timeframe} timeframe, + {strategy} strategy, {buy_tag} buy_tag, {timeframe} timeframe, {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs from {table_back_name} """)) @@ -168,7 +168,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None: inspector = inspect(engine) cols = inspector.get_columns('trades') - if not has_column(cols, 'buy_signal_name'): + if not has_column(cols, 'buy_tag'): logger.info(f'Running database migration for trades - backup: {table_back_name}') migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) # Reread columns - the above recreated the table! diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 058e892ec..f42166762 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -257,7 +257,7 @@ class LocalTrade(): sell_reason: str = '' sell_order_status: str = '' strategy: str = '' - buy_signal_name: str = '' + buy_tag: str = '' timeframe: Optional[int] = None def __init__(self, **kwargs): @@ -289,7 +289,7 @@ class LocalTrade(): 'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None, 'stake_amount': round(self.stake_amount, 8), 'strategy': self.strategy, - 'buy_signal_name': self.buy_signal_name, + 'buy_tag': self.buy_tag, 'timeframe': self.timeframe, 'fee_open': self.fee_open, @@ -638,7 +638,7 @@ class LocalTrade(): # skip case if trailing-stop changed the stoploss already. if (trade.stop_loss == trade.initial_stop_loss - and trade.initial_stop_loss_pct != desired_stoploss): + and trade.initial_stop_loss_pct != desired_stoploss): # Stoploss value got changed logger.info(f"Stoploss for {trade} needs adjustment...") @@ -705,7 +705,7 @@ class Trade(_DECL_BASE, LocalTrade): sell_reason = Column(String(100), nullable=True) sell_order_status = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True) - buy_signal_name = Column(String(100), nullable=True) + buy_tag = Column(String(100), nullable=True) timeframe = Column(Integer, nullable=True) def __init__(self, **kwargs): diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 8ee0cea59..a5517403d 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -13,7 +13,7 @@ from pandas import DataFrame from freqtrade.constants import ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import SellType, SignalNameType, SignalType +from freqtrade.enums import SellType, SignalTagType, SignalType from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange.exchange import timeframe_to_next_date @@ -422,7 +422,7 @@ class IStrategy(ABC, HyperStrategyMixin): logger.debug("Skipping TA Analysis for already analyzed candle") dataframe['buy'] = 0 dataframe['sell'] = 0 - dataframe['buy_signal_name'] = '' + dataframe['buy_tag'] = '' # Other Defs in strategy that want to be called every loop here # twitter_sell = self.watch_twitter_feed(dataframe, metadata) @@ -525,9 +525,9 @@ class IStrategy(ABC, HyperStrategyMixin): ) return False, False, '' - (buy, sell, buy_signal_name) = latest[SignalType.BUY.value] == 1,\ + (buy, sell, buy_tag) = latest[SignalType.BUY.value] == 1,\ latest[SignalType.SELL.value] == 1,\ - latest.get(SignalNameType.BUY_SIGNAL_NAME.value, '') + latest.get(SignalTagType.BUY_TAG.value, '') logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) timeframe_seconds = timeframe_to_seconds(timeframe) @@ -535,8 +535,8 @@ class IStrategy(ABC, HyperStrategyMixin): current_time=datetime.now(timezone.utc), timeframe_seconds=timeframe_seconds, buy=buy): - return False, sell, buy_signal_name - return buy, sell, buy_signal_name + return False, sell, buy_tag + return buy, sell, buy_tag def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, timeframe_seconds: int, buy: bool): diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index f77e6f70f..240f3f871 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -18,7 +18,7 @@ class BTrade(NamedTuple): sell_reason: SellType open_tick: int close_tick: int - buy_signal_name: Optional[str] = '' + buy_tag: Optional[str] = '' class BTContainer(NamedTuple): @@ -44,7 +44,7 @@ def _get_frame_time_from_offset(offset): def _build_backtest_dataframe(data): - columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell', 'buy_signal_name'] + columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell', 'buy_tag'] frame = DataFrame.from_records(data, columns=columns) frame['date'] = frame['date'].apply(_get_frame_time_from_offset) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index d1bf28d58..f7077ef56 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -532,7 +532,7 @@ tc33 = BTContainer(data=[ sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1, - buy_signal_name='buy_signal_01' + buy_tag='buy_signal_01' )] ) @@ -619,6 +619,6 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: for c, trade in enumerate(data.trades): res = results.iloc[c] assert res.sell_reason == trade.sell_reason.value - assert res.buy_signal_name == trade.buy_signal_name + assert res.buy_tag == trade.buy_tag assert res.open_date == _get_frame_time_from_offset(trade.open_tick) assert res.close_date == _get_frame_time_from_offset(trade.close_tick) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 64795d064..873b27b4d 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -584,7 +584,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'min_rate': [0.1038, 0.10302485], 'max_rate': [0.10501, 0.1038888], 'is_open': [False, False], - 'buy_signal_name': ['', ''], + 'buy_tag': ['', ''], }) pd.testing.assert_frame_equal(results, expected) data_pair = processed[pair] @@ -859,7 +859,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): 'locks': [], 'rejected_signals': 20, 'final_balance': 1000, - }) + }) mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index df8679470..889aa05cf 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -69,7 +69,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, - 'buy_signal_name': ANY, + 'buy_tag': ANY, 'timeframe': 5, 'open_order_id': ANY, 'close_date': None, @@ -136,7 +136,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'min_rate': ANY, 'max_rate': ANY, 'strategy': ANY, - 'buy_signal_name': ANY, + 'buy_tag': ANY, 'timeframe': ANY, 'open_order_id': ANY, 'close_date': None, diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 3678cee4a..c812f3007 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -49,7 +49,7 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history): assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, '') mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 1 - mocked_history.loc[1, 'buy_signal_name'] = 'buy_signal_01' + mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01' assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01') diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 8e486d145..af4979919 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -861,7 +861,7 @@ def test_to_json(default_conf, fee): open_date=arrow.utcnow().shift(hours=-2).datetime, open_rate=0.123, exchange='binance', - buy_signal_name='', + buy_tag='', open_order_id='dry_run_buy_12345' ) result = trade.to_json() @@ -911,7 +911,7 @@ def test_to_json(default_conf, fee): 'min_rate': None, 'max_rate': None, 'strategy': None, - 'buy_signal_name': '', + 'buy_tag': '', 'timeframe': None, 'exchange': 'binance', } @@ -928,7 +928,7 @@ def test_to_json(default_conf, fee): close_date=arrow.utcnow().shift(hours=-1).datetime, open_rate=0.123, close_rate=0.125, - buy_signal_name='buys_signal_001', + buy_tag='buys_signal_001', exchange='binance', ) result = trade.to_json() @@ -978,7 +978,7 @@ def test_to_json(default_conf, fee): 'sell_reason': None, 'sell_order_status': None, 'strategy': None, - 'buy_signal_name': 'buys_signal_001', + 'buy_tag': 'buys_signal_001', 'timeframe': None, 'exchange': 'binance', } @@ -1323,7 +1323,7 @@ def test_Trade_object_idem(): 'get_open_trades_without_assigned_fees', 'get_open_order_trades', 'get_trades', - ) + ) # Parent (LocalTrade) should have the same attributes for item in trade: