]:* `Lists cumulative profit from all finished trades, "
"over the last n days`\n"
diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py
index 0bbfc8906..d11097ed2 100644
--- a/freqtrade/strategy/interface.py
+++ b/freqtrade/strategy/interface.py
@@ -509,6 +509,7 @@ class IStrategy(ABC, HyperStrategyMixin):
dataframe['buy'] = 0
dataframe['sell'] = 0
dataframe['buy_tag'] = None
+ dataframe['exit_tag'] = None
# Other Defs in strategy that want to be called every loop here
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
@@ -586,7 +587,7 @@ class IStrategy(ABC, HyperStrategyMixin):
pair: str,
timeframe: str,
dataframe: DataFrame
- ) -> Tuple[bool, bool, Optional[str]]:
+ ) -> Tuple[bool, bool, Optional[str], Optional[str]]:
"""
Calculates current signal based based on the buy / sell columns of the dataframe.
Used by Bot to get the signal to buy or sell
@@ -597,7 +598,7 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
if not isinstance(dataframe, DataFrame) or dataframe.empty:
logger.warning(f'Empty candle (OHLCV) data for pair {pair}')
- return False, False, None
+ return False, False, None, None
latest_date = dataframe['date'].max()
latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1]
@@ -612,7 +613,7 @@ class IStrategy(ABC, HyperStrategyMixin):
'Outdated history for pair %s. Last tick is %s minutes old',
pair, int((arrow.utcnow() - latest_date).total_seconds() // 60)
)
- return False, False, None
+ return False, False, None, None
buy = latest[SignalType.BUY.value] == 1
@@ -621,6 +622,7 @@ class IStrategy(ABC, HyperStrategyMixin):
sell = latest[SignalType.SELL.value] == 1
buy_tag = latest.get(SignalTagType.BUY_TAG.value, None)
+ exit_tag = latest.get(SignalTagType.EXIT_TAG.value, None)
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s',
latest['date'], pair, str(buy), str(sell))
@@ -629,8 +631,8 @@ class IStrategy(ABC, HyperStrategyMixin):
current_time=datetime.now(timezone.utc),
timeframe_seconds=timeframe_seconds,
buy=buy):
- return False, sell, buy_tag
- return buy, sell, buy_tag
+ return False, sell, buy_tag, exit_tag
+ return buy, sell, buy_tag, exit_tag
def ignore_expired_candle(self, latest_date: datetime, current_time: datetime,
timeframe_seconds: int, buy: bool):
diff --git a/tests/conftest.py b/tests/conftest.py
index b35a220df..698c464ed 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -186,7 +186,7 @@ def get_patched_worker(mocker, config) -> Worker:
return Worker(args=None, config=config)
-def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, None)) -> None:
+def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, None, None)) -> None:
"""
:param mocker: mocker to patch IStrategy class
:param value: which value IStrategy.get_signal() must return
diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py
index 024803be0..4496df37d 100644
--- a/tests/conftest_trades.py
+++ b/tests/conftest_trades.py
@@ -89,6 +89,7 @@ def mock_trade_2(fee):
open_order_id='dry_run_sell_12345',
strategy='StrategyTestV2',
timeframe=5,
+ buy_tag='TEST1',
sell_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
@@ -241,6 +242,7 @@ def mock_trade_5(fee):
open_rate=0.123,
exchange='binance',
strategy='SampleStrategy',
+ buy_tag='TEST1',
stoploss_order_id='prod_stoploss_3455',
timeframe=5,
)
@@ -295,6 +297,7 @@ def mock_trade_6(fee):
open_rate=0.15,
exchange='binance',
strategy='SampleStrategy',
+ buy_tag='TEST2',
open_order_id="prod_sell_6",
timeframe=5,
)
diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py
index 8a2be39a1..68088d2d5 100644
--- a/tests/optimize/__init__.py
+++ b/tests/optimize/__init__.py
@@ -54,6 +54,8 @@ def _build_backtest_dataframe(data):
frame[column] = frame[column].astype('float64')
if 'buy_tag' not in columns:
frame['buy_tag'] = None
+ if 'exit_tag' not in columns:
+ frame['exit_tag'] = None
# Ensure all candles make kindof sense
assert all(frame['low'] <= frame['close'])
diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py
index b5fa44d01..ab7aa74a1 100644
--- a/tests/optimize/test_backtesting.py
+++ b/tests/optimize/test_backtesting.py
@@ -567,6 +567,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
195, # Low
201.5, # High
'', # Buy Signal Name
+ '', # Exit Signal Name
]
trade = backtesting._enter_trade(pair, row=row)
@@ -581,26 +582,27 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
195, # Low
210.5, # High
'', # Buy Signal Name
+ '', # Exit Signal Name
]
row_detail = pd.DataFrame(
[
[
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
- 1, 200, 199, 0, 197, 200.1, '',
+ 1, 200, 199, 0, 197, 200.1, '', '',
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=1, tzinfo=timezone.utc),
- 0, 199, 199.5, 0, 199, 199.7, '',
+ 0, 199, 199.5, 0, 199, 199.7, '', '',
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=2, tzinfo=timezone.utc),
- 0, 199.5, 200.5, 0, 199, 200.8, '',
+ 0, 199.5, 200.5, 0, 199, 200.8, '', '',
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=3, tzinfo=timezone.utc),
- 0, 200.5, 210.5, 0, 193, 210.5, '', # ROI sell (?)
+ 0, 200.5, 210.5, 0, 193, 210.5, '', '', # ROI sell (?)
], [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=4, tzinfo=timezone.utc),
- 0, 200, 199, 0, 193, 200.1, '',
+ 0, 200, 199, 0, 193, 200.1, '', '',
],
- ], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"]
+ ], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"]
)
# No data available.
@@ -614,7 +616,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
assert isinstance(trade, LocalTrade)
# Assign empty ... no result.
backtesting.detail_data[pair] = pd.DataFrame(
- [], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"])
+ [], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"])
res = backtesting._get_sell_trade_entry(trade, row)
assert res is None
@@ -678,7 +680,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'min_rate': [0.10370188, 0.10300000000000001],
'max_rate': [0.10501, 0.1038888],
'is_open': [False, False],
- 'buy_tag': [None, None],
+ 'buy_tag': [None, None]
})
pd.testing.assert_frame_equal(results, expected)
data_pair = processed[pair]
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index f8c923958..945217b8a 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -825,8 +825,226 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert len(res) == 1
assert res[0]['pair'] == 'ETH/BTC'
assert res[0]['count'] == 1
+ assert prec_satoshi(res[0]['profit_pct'], 6.2)
+
+
+def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
+ limit_sell_order, mocker) -> None:
+ mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ get_balances=MagicMock(return_value=ticker),
+ fetch_ticker=ticker,
+ get_fee=fee,
+ )
+
+ freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+ patch_get_signal(freqtradebot)
+ rpc = RPC(freqtradebot)
+
+ # Create some test data
+ freqtradebot.enter_positions()
+ trade = Trade.query.first()
+ assert trade
+
+ # Simulate fulfilled LIMIT_BUY order for trade
+ trade.update(limit_buy_order)
+
+ # Simulate fulfilled LIMIT_SELL order for trade
+ trade.update(limit_sell_order)
+
+ trade.close_date = datetime.utcnow()
+ trade.is_open = False
+ res = rpc._rpc_buy_tag_performance(None)
+
+ assert len(res) == 1
+ assert res[0]['buy_tag'] == 'Other'
+ assert res[0]['count'] == 1
+ assert prec_satoshi(res[0]['profit_pct'], 6.2)
+
+ trade.buy_tag = "TEST_TAG"
+ res = rpc._rpc_buy_tag_performance(None)
+
+ assert len(res) == 1
+ assert res[0]['buy_tag'] == 'TEST_TAG'
+ assert res[0]['count'] == 1
+ assert prec_satoshi(res[0]['profit_pct'], 6.2)
+
+
+def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee):
+ mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ markets=PropertyMock(return_value=markets)
+ )
+
+ freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+ create_mock_trades(fee)
+ rpc = RPC(freqtradebot)
+
+ res = rpc._rpc_buy_tag_performance(None)
+
+ assert len(res) == 2
+ assert res[0]['buy_tag'] == 'TEST1'
+ assert res[0]['count'] == 1
+ assert prec_satoshi(res[0]['profit_pct'], 0.5)
+ assert res[1]['buy_tag'] == 'Other'
+ assert res[1]['count'] == 1
+ assert prec_satoshi(res[1]['profit_pct'], 1.0)
+
+ # Test for a specific pair
+ res = rpc._rpc_buy_tag_performance('ETC/BTC')
+ assert len(res) == 1
+ assert res[0]['count'] == 1
+ assert res[0]['buy_tag'] == 'TEST1'
+ assert prec_satoshi(res[0]['profit_pct'], 0.5)
+
+
+def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, fee,
+ limit_sell_order, mocker) -> None:
+ mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ get_balances=MagicMock(return_value=ticker),
+ fetch_ticker=ticker,
+ get_fee=fee,
+ )
+
+ freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+ patch_get_signal(freqtradebot)
+ rpc = RPC(freqtradebot)
+
+ # Create some test data
+ freqtradebot.enter_positions()
+ trade = Trade.query.first()
+ assert trade
+
+ # Simulate fulfilled LIMIT_BUY order for trade
+ trade.update(limit_buy_order)
+
+ # Simulate fulfilled LIMIT_SELL order for trade
+ trade.update(limit_sell_order)
+
+ trade.close_date = datetime.utcnow()
+ trade.is_open = False
+ res = rpc._rpc_sell_reason_performance(None)
+
+ assert len(res) == 1
+ assert res[0]['sell_reason'] == 'Other'
+ assert res[0]['count'] == 1
+ assert prec_satoshi(res[0]['profit_pct'], 6.2)
+
+ trade.sell_reason = "TEST1"
+ res = rpc._rpc_sell_reason_performance(None)
+
+ assert len(res) == 1
+ assert res[0]['sell_reason'] == 'TEST1'
+ assert res[0]['count'] == 1
+ assert prec_satoshi(res[0]['profit_pct'], 6.2)
+
+
+def test_sell_reason_performance_handle_2(mocker, default_conf, markets, fee):
+ mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ markets=PropertyMock(return_value=markets)
+ )
+
+ freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+ create_mock_trades(fee)
+ rpc = RPC(freqtradebot)
+
+ res = rpc._rpc_sell_reason_performance(None)
+
+ assert len(res) == 2
+ assert res[0]['sell_reason'] == 'sell_signal'
+ assert res[0]['count'] == 1
+ assert prec_satoshi(res[0]['profit_pct'], 0.5)
+ assert res[1]['sell_reason'] == 'roi'
+ assert res[1]['count'] == 1
+ assert prec_satoshi(res[1]['profit_pct'], 1.0)
+
+ # Test for a specific pair
+ res = rpc._rpc_sell_reason_performance('ETC/BTC')
+ assert len(res) == 1
+ assert res[0]['count'] == 1
+ assert res[0]['sell_reason'] == 'sell_signal'
+ assert prec_satoshi(res[0]['profit_pct'], 0.5)
+
+
+def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
+ limit_sell_order, mocker) -> None:
+ mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ get_balances=MagicMock(return_value=ticker),
+ fetch_ticker=ticker,
+ get_fee=fee,
+ )
+
+ freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+ patch_get_signal(freqtradebot)
+ rpc = RPC(freqtradebot)
+
+ # Create some test data
+ freqtradebot.enter_positions()
+ trade = Trade.query.first()
+ assert trade
+
+ # Simulate fulfilled LIMIT_BUY order for trade
+ trade.update(limit_buy_order)
+
+ # Simulate fulfilled LIMIT_SELL order for trade
+ trade.update(limit_sell_order)
+
+ trade.close_date = datetime.utcnow()
+ trade.is_open = False
+ res = rpc._rpc_mix_tag_performance(None)
+
+ assert len(res) == 1
+ assert res[0]['mix_tag'] == 'Other Other'
+ assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit'], 6.2)
+ trade.buy_tag = "TESTBUY"
+ trade.sell_reason = "TESTSELL"
+ res = rpc._rpc_mix_tag_performance(None)
+
+ assert len(res) == 1
+ assert res[0]['mix_tag'] == 'TESTBUY TESTSELL'
+ assert res[0]['count'] == 1
+ assert prec_satoshi(res[0]['profit'], 6.2)
+
+
+def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee):
+ mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ markets=PropertyMock(return_value=markets)
+ )
+
+ freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+ create_mock_trades(fee)
+ rpc = RPC(freqtradebot)
+
+ res = rpc._rpc_mix_tag_performance(None)
+
+ assert len(res) == 2
+ assert res[0]['mix_tag'] == 'TEST1 sell_signal'
+ assert res[0]['count'] == 1
+ assert prec_satoshi(res[0]['profit'], 0.5)
+ assert res[1]['mix_tag'] == 'Other roi'
+ assert res[1]['count'] == 1
+ assert prec_satoshi(res[1]['profit'], 1.0)
+
+ # Test for a specific pair
+ res = rpc._rpc_mix_tag_performance('ETC/BTC')
+
+ assert len(res) == 1
+ assert res[0]['count'] == 1
+ assert res[0]['mix_tag'] == 'TEST1 sell_signal'
+ assert prec_satoshi(res[0]['profit'], 0.5)
+
def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index 02ed26459..e0bbee861 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -812,8 +812,10 @@ def test_api_performance(botclient, fee):
rc = client_get(client, f"{BASE_URI}/performance")
assert_response(rc)
assert len(rc.json()) == 2
- assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_abs': 0.01872279},
- {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_abs': -0.1150375}]
+ assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_pct': 7.61,
+ 'profit_ratio': 0.07609203, 'profit_abs': 0.01872279},
+ {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_pct': -5.57,
+ 'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}]
def test_api_status(botclient, mocker, ticker, fee, markets):
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 7dde7b803..5f49c8bf7 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -33,6 +33,7 @@ class DummyCls(Telegram):
"""
Dummy class for testing the Telegram @authorized_only decorator
"""
+
def __init__(self, rpc: RPC, config) -> None:
super().__init__(rpc, config)
self.state = {'called': False}
@@ -92,7 +93,8 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
- "['delete'], ['performance'], ['stats'], ['daily'], ['count'], ['locks'], "
+ "['delete'], ['performance'], ['buys'], ['sells'], ['mix_tags'], "
+ "['stats'], ['daily'], ['count'], ['locks'], "
"['unlock', 'delete_locks'], ['reload_config', 'reload_conf'], "
"['show_config', 'show_conf'], ['stopbuy'], "
"['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version']"
@@ -713,6 +715,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'profit_ratio': 0.0629778,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
+ 'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
@@ -776,6 +779,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'profit_ratio': -0.05482878,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
+ 'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
@@ -829,6 +833,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'profit_ratio': -0.00408133,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
+ 'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
@@ -974,6 +979,102 @@ def test_performance_handle(default_conf, update, ticker, fee,
assert 'ETH/BTC\t0.00006217 BTC (6.20%) (1)
' in msg_mock.call_args_list[0][0][0]
+def test_buy_tag_performance_handle(default_conf, update, ticker, fee,
+ limit_buy_order, limit_sell_order, mocker) -> None:
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ fetch_ticker=ticker,
+ get_fee=fee,
+ )
+ telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
+ patch_get_signal(freqtradebot)
+
+ # Create some test data
+ freqtradebot.enter_positions()
+ trade = Trade.query.first()
+ assert trade
+
+ # Simulate fulfilled LIMIT_BUY order for trade
+ trade.update(limit_buy_order)
+
+ trade.buy_tag = "TESTBUY"
+ # Simulate fulfilled LIMIT_SELL order for trade
+ trade.update(limit_sell_order)
+
+ trade.close_date = datetime.utcnow()
+ trade.is_open = False
+
+ telegram._buy_tag_performance(update=update, context=MagicMock())
+ assert msg_mock.call_count == 1
+ assert 'Buy Tag Performance' in msg_mock.call_args_list[0][0][0]
+ assert 'TESTBUY\t0.00006217 BTC (6.20%) (1)
' in msg_mock.call_args_list[0][0][0]
+
+
+def test_sell_reason_performance_handle(default_conf, update, ticker, fee,
+ limit_buy_order, limit_sell_order, mocker) -> None:
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ fetch_ticker=ticker,
+ get_fee=fee,
+ )
+ telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
+ patch_get_signal(freqtradebot)
+
+ # Create some test data
+ freqtradebot.enter_positions()
+ trade = Trade.query.first()
+ assert trade
+
+ # Simulate fulfilled LIMIT_BUY order for trade
+ trade.update(limit_buy_order)
+
+ trade.sell_reason = 'TESTSELL'
+ # Simulate fulfilled LIMIT_SELL order for trade
+ trade.update(limit_sell_order)
+
+ trade.close_date = datetime.utcnow()
+ trade.is_open = False
+
+ telegram._sell_reason_performance(update=update, context=MagicMock())
+ assert msg_mock.call_count == 1
+ assert 'Sell Reason Performance' in msg_mock.call_args_list[0][0][0]
+ assert 'TESTSELL\t0.00006217 BTC (6.20%) (1)
' in msg_mock.call_args_list[0][0][0]
+
+
+def test_mix_tag_performance_handle(default_conf, update, ticker, fee,
+ limit_buy_order, limit_sell_order, mocker) -> None:
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ fetch_ticker=ticker,
+ get_fee=fee,
+ )
+ telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
+ patch_get_signal(freqtradebot)
+
+ # Create some test data
+ freqtradebot.enter_positions()
+ trade = Trade.query.first()
+ assert trade
+
+ # Simulate fulfilled LIMIT_BUY order for trade
+ trade.update(limit_buy_order)
+
+ trade.buy_tag = "TESTBUY"
+ trade.sell_reason = "TESTSELL"
+
+ # Simulate fulfilled LIMIT_SELL order for trade
+ trade.update(limit_sell_order)
+
+ trade.close_date = datetime.utcnow()
+ trade.is_open = False
+
+ telegram._mix_tag_performance(update=update, context=MagicMock())
+ assert msg_mock.call_count == 1
+ assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0]
+ assert ('TESTBUY TESTSELL\t0.00006217 BTC (6.20%) (1)
'
+ in msg_mock.call_args_list[0][0][0])
+
+
def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -997,9 +1098,9 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
msg = (' current max total stake\n--------- ----- -------------\n'
' 1 {} {}
').format(
- default_conf['max_open_trades'],
- default_conf['stake_amount']
- )
+ default_conf['max_open_trades'],
+ default_conf['stake_amount']
+ )
assert msg in msg_mock.call_args_list[0][0][0]
@@ -1382,6 +1483,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD',
+ 'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-1),
'close_date': arrow.utcnow(),
@@ -1389,6 +1491,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
+ '*Buy Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `1:00:00 (60.0 min)`\n'
'*Amount:* `1333.33333333`\n'
@@ -1412,6 +1515,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
+ 'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(),
@@ -1419,6 +1523,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Profit:* `-57.41%`\n'
+ '*Buy Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
'*Amount:* `1333.33333333`\n'
@@ -1483,6 +1588,7 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD',
+ 'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-1),
'close_date': arrow.utcnow(),
@@ -1574,12 +1680,14 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD',
+ 'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Profit:* `-57.41%`\n'
+ '*Buy Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `2:35:03 (155.1 min)`\n'
'*Amount:* `1333.33333333`\n'
diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py
index ebd950fd6..f57a9f34e 100644
--- a/tests/strategy/test_interface.py
+++ b/tests/strategy/test_interface.py
@@ -30,7 +30,7 @@ _STRATEGY = StrategyTestV2(config={})
_STRATEGY.dp = DataProvider({}, None, None)
-def test_returns_latest_signal(mocker, default_conf, ohlcv_history):
+def test_returns_latest_signal(ohlcv_history):
ohlcv_history.loc[1, 'date'] = arrow.utcnow()
# Take a copy to correctly modify the call
mocked_history = ohlcv_history.copy()
@@ -38,20 +38,39 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history):
mocked_history['buy'] = 0
mocked_history.loc[1, 'sell'] = 1
- assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None)
+ assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None, None)
mocked_history.loc[1, 'sell'] = 0
mocked_history.loc[1, 'buy'] = 1
- assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None)
+ assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None, None)
mocked_history.loc[1, 'sell'] = 0
mocked_history.loc[1, 'buy'] = 0
- assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
+ assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None, None)
mocked_history.loc[1, 'sell'] = 0
mocked_history.loc[1, 'buy'] = 1
mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01'
- assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01')
+ assert _STRATEGY.get_signal(
+ 'ETH/BTC',
+ '5m',
+ mocked_history) == (
+ True,
+ False,
+ 'buy_signal_01',
+ None)
+
+ mocked_history.loc[1, 'buy_tag'] = None
+ mocked_history.loc[1, 'exit_tag'] = 'sell_signal_01'
+
+ assert _STRATEGY.get_signal(
+ 'ETH/BTC',
+ '5m',
+ mocked_history) == (
+ True,
+ False,
+ None,
+ 'sell_signal_01')
def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
@@ -68,17 +87,24 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
def test_get_signal_empty(default_conf, mocker, caplog):
- assert (False, False, None) == _STRATEGY.get_signal(
+ assert (False, False, None, None) == _STRATEGY.get_signal(
'foo', default_conf['timeframe'], DataFrame()
)
assert log_has('Empty candle (OHLCV) data for pair foo', caplog)
caplog.clear()
- assert (False, False, None) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None)
+ assert (
+ False,
+ False,
+ None,
+ None) == _STRATEGY.get_signal(
+ 'bar',
+ default_conf['timeframe'],
+ None)
assert log_has('Empty candle (OHLCV) data for pair bar', caplog)
caplog.clear()
- assert (False, False, None) == _STRATEGY.get_signal(
+ assert (False, False, None, None) == _STRATEGY.get_signal(
'baz',
default_conf['timeframe'],
DataFrame([])
@@ -118,7 +144,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history):
caplog.set_level(logging.INFO)
mocker.patch.object(_STRATEGY, 'assert_df')
- assert (False, False, None) == _STRATEGY.get_signal(
+ assert (False, False, None, None) == _STRATEGY.get_signal(
'xyz',
default_conf['timeframe'],
mocked_history
@@ -140,7 +166,7 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
caplog.set_level(logging.INFO)
mocker.patch.object(_STRATEGY, 'assert_df')
- assert (True, False, None) == _STRATEGY.get_signal(
+ assert (True, False, None, None) == _STRATEGY.get_signal(
'xyz',
default_conf['timeframe'],
mocked_history
@@ -653,7 +679,7 @@ def test_strategy_safe_wrapper(value):
ret = strategy_safe_wrapper(working_method, message='DeadBeef')(value)
- assert type(ret) == type(value)
+ assert isinstance(ret, type(value))
assert ret == value
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index 838a158e0..0435dc3a2 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -236,7 +236,7 @@ def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker,
# stoploss shoud be hit
assert freqtrade.handle_trade(trade) is not ignore_strat_sl
if not ignore_strat_sl:
- assert log_has('Executing Sell for NEO/BTC. Reason: stop_loss', caplog)
+ assert log_has_re(r'Executing Sell for NEO/BTC. Reason: stop_loss.*', caplog)
assert trade.sell_reason == SellType.STOP_LOSS.value
@@ -450,7 +450,7 @@ def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None:
)
default_conf_usdt['stake_amount'] = 10
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade, value=(False, False, None))
+ patch_get_signal(freqtrade, value=(False, False, None, None))
Trade.query = MagicMock()
Trade.query.filter = MagicMock()
@@ -677,7 +677,7 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker)
inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")])
mocker.patch(
'freqtrade.strategy.interface.IStrategy.get_signal',
- return_value=(False, False, '')
+ return_value=(False, False, '', '')
)
mocker.patch('time.sleep', return_value=None)
@@ -1808,7 +1808,7 @@ def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_
assert trade.is_open is True
freqtrade.wallets.update()
- patch_get_signal(freqtrade, value=(False, True, None))
+ patch_get_signal(freqtrade, value=(False, True, None, 'sell_signal1'))
assert freqtrade.handle_trade(trade) is True
assert trade.open_order_id == limit_sell_order_usdt['id']
@@ -1819,6 +1819,7 @@ def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_
assert trade.close_profit == 0.09451372
assert trade.calc_profit() == 5.685
assert trade.close_date is not None
+ assert trade.sell_reason == 'sell_signal1'
def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,
@@ -1836,7 +1837,7 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or
)
freqtrade = FreqtradeBot(default_conf_usdt)
- patch_get_signal(freqtrade, value=(True, True, None))
+ patch_get_signal(freqtrade, value=(True, True, None, None))
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.enter_positions()
@@ -1855,7 +1856,7 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or
assert trades[0].is_open is True
# Buy and Sell are not triggering, so doing nothing ...
- patch_get_signal(freqtrade, value=(False, False, None))
+ patch_get_signal(freqtrade, value=(False, False, None, None))
assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all()
nb_trades = len(trades)
@@ -1863,7 +1864,7 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or
assert trades[0].is_open is True
# Buy and Sell are triggering, so doing nothing ...
- patch_get_signal(freqtrade, value=(True, True, None))
+ patch_get_signal(freqtrade, value=(True, True, None, None))
assert freqtrade.handle_trade(trades[0]) is False
trades = Trade.query.all()
nb_trades = len(trades)
@@ -1871,7 +1872,7 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or
assert trades[0].is_open is True
# Sell is triggering, guess what : we are Selling!
- patch_get_signal(freqtrade, value=(False, True, None))
+ patch_get_signal(freqtrade, value=(False, True, None, None))
trades = Trade.query.all()
assert freqtrade.handle_trade(trades[0]) is True
@@ -1905,7 +1906,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_o
# we might just want to check if we are in a sell condition without
# executing
# if ROI is reached we must sell
- patch_get_signal(freqtrade, value=(False, True, None))
+ patch_get_signal(freqtrade, value=(False, True, None, None))
assert freqtrade.handle_trade(trade)
assert log_has("ETH/USDT - Required profit reached. sell_type=SellType.ROI",
caplog)
@@ -1934,10 +1935,10 @@ def test_handle_trade_use_sell_signal(default_conf_usdt, ticker_usdt, limit_buy_
trade = Trade.query.first()
trade.is_open = True
- patch_get_signal(freqtrade, value=(False, False, None))
+ patch_get_signal(freqtrade, value=(False, False, None, None))
assert not freqtrade.handle_trade(trade)
- patch_get_signal(freqtrade, value=(False, True, None))
+ patch_get_signal(freqtrade, value=(False, True, None, None))
assert freqtrade.handle_trade(trade)
assert log_has("ETH/USDT - Sell signal received. sell_type=SellType.SELL_SIGNAL",
caplog)
@@ -2579,6 +2580,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'limit': 2.2,
'amount': 30.0,
'order_type': 'limit',
+ 'buy_tag': None,
'open_rate': 2.0,
'current_rate': 2.3,
'profit_amount': 5.685,
@@ -2632,6 +2634,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'limit': 2.01,
'amount': 30.0,
'order_type': 'limit',
+ 'buy_tag': None,
'open_rate': 2.0,
'current_rate': 2.0,
'profit_amount': -0.00075,
@@ -2699,6 +2702,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
'limit': 2.25,
'amount': 30.0,
'order_type': 'limit',
+ 'buy_tag': None,
'open_rate': 2.0,
'current_rate': 2.3,
'profit_amount': 7.18125,
@@ -2758,6 +2762,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'limit': 1.98,
'amount': 30.0,
'order_type': 'limit',
+ 'buy_tag': None,
'open_rate': 2.0,
'current_rate': 2.0,
'profit_amount': -0.8985,
@@ -2975,6 +2980,7 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
'limit': 2.2,
'amount': 30.0,
'order_type': 'market',
+ 'buy_tag': None,
'open_rate': 2.0,
'current_rate': 2.3,
'profit_amount': 5.685,
@@ -3068,7 +3074,7 @@ def test_sell_profit_only(
trade = Trade.query.first()
trade.update(limit_buy_order_usdt)
freqtrade.wallets.update()
- patch_get_signal(freqtrade, value=(False, True, None))
+ patch_get_signal(freqtrade, value=(False, True, None, None))
assert freqtrade.handle_trade(trade) is handle_first
if handle_second:
@@ -3103,7 +3109,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_
trade = Trade.query.first()
amnt = trade.amount
trade.update(limit_buy_order_usdt)
- patch_get_signal(freqtrade, value=(False, True, None))
+ patch_get_signal(freqtrade, value=(False, True, None, None))
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985))
assert freqtrade.handle_trade(trade) is True
@@ -3212,11 +3218,11 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt,
trade = Trade.query.first()
trade.update(limit_buy_order_usdt)
freqtrade.wallets.update()
- patch_get_signal(freqtrade, value=(True, True, None))
+ patch_get_signal(freqtrade, value=(True, True, None, None))
assert freqtrade.handle_trade(trade) is False
# Test if buy-signal is absent (should sell due to roi = true)
- patch_get_signal(freqtrade, value=(False, True, None))
+ patch_get_signal(freqtrade, value=(False, True, None, None))
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.ROI.value
@@ -3402,11 +3408,11 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usd
trade = Trade.query.first()
trade.update(limit_buy_order_usdt)
# Sell due to min_roi_reached
- patch_get_signal(freqtrade, value=(True, True, None))
+ patch_get_signal(freqtrade, value=(True, True, None, None))
assert freqtrade.handle_trade(trade) is True
# Test if buy-signal is absent
- patch_get_signal(freqtrade, value=(False, True, None))
+ patch_get_signal(freqtrade, value=(False, True, None, None))
assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == SellType.ROI.value
@@ -3848,7 +3854,7 @@ def test_order_book_ask_strategy(
freqtrade.wallets.update()
assert trade.is_open is True
- patch_get_signal(freqtrade, value=(False, True, None))
+ patch_get_signal(freqtrade, value=(False, True, None, None))
assert freqtrade.handle_trade(trade) is True
assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0]
diff --git a/tests/test_persistence.py b/tests/test_persistence.py
index d036b045e..719dc8263 100644
--- a/tests/test_persistence.py
+++ b/tests/test_persistence.py
@@ -1317,6 +1317,10 @@ def test_Trade_object_idem():
'get_open_trades_without_assigned_fees',
'get_open_order_trades',
'get_trades',
+ 'get_sell_reason_performance',
+ 'get_buy_tag_performance',
+ 'get_mix_tag_performance',
+
)
# Parent (LocalTrade) should have the same attributes