use more granular msg dict for buy/sell notifications

This commit is contained in:
gcarq 2018-07-03 20:26:48 +02:00
parent 4cb1aa1d97
commit 0920fb6120
10 changed files with 244 additions and 101 deletions

View File

@ -19,7 +19,8 @@ from freqtrade.analyze import Analyze
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc.rpc_manager import RPCManager from freqtrade.rpc import RPCMessageType
from freqtrade.rpc import RPCManager
from freqtrade.state import State from freqtrade.state import State
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -92,6 +93,7 @@ class FreqtradeBot(object):
state = self.state state = self.state
if state != old_state: if state != old_state:
self.rpc.send_msg({ self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'{state.name.lower()}' 'status': f'{state.name.lower()}'
}) })
logger.info('Changing state to: %s', state.name) logger.info('Changing state to: %s', state.name)
@ -170,6 +172,7 @@ class FreqtradeBot(object):
tb = traceback.format_exc() tb = traceback.format_exc()
hint = 'Issue `/start` if you think it is safe to restart.' hint = 'Issue `/start` if you think it is safe to restart.'
self.rpc.send_msg({ self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'OperationalException:\n```\n{tb}```{hint}' 'status': f'OperationalException:\n```\n{tb}```{hint}'
}) })
logger.exception('OperationalException. Stopping trader ...') logger.exception('OperationalException. Stopping trader ...')
@ -340,7 +343,6 @@ class FreqtradeBot(object):
pair_url = self.exchange.get_pair_detail_url(pair) pair_url = self.exchange.get_pair_detail_url(pair)
stake_currency = self.config['stake_currency'] stake_currency = self.config['stake_currency']
fiat_currency = self.config['fiat_display_currency'] fiat_currency = self.config['fiat_display_currency']
exc_name = self.exchange.name
# Calculate amount # Calculate amount
buy_limit = self.get_target_bid(self.exchange.get_ticker(pair)) buy_limit = self.get_target_bid(self.exchange.get_ticker(pair))
@ -363,12 +365,16 @@ class FreqtradeBot(object):
fiat_currency fiat_currency
) )
# Create trade entity and return
self.rpc.send_msg({ self.rpc.send_msg({
'status': 'type': RPCMessageType.BUY_NOTIFICATION,
f"""*{exc_name}:* Buying [{pair_s}]({pair_url}) \ 'exchange': self.exchange.name.capitalize(),
with limit `{buy_limit:.8f} ({stake_amount:.6f} \ 'pair': pair_s,
{stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`""" 'market_url': pair_url,
'limit': buy_limit,
'stake_amount': stake_amount,
'stake_amount_fiat': stake_amount_fiat,
'stake_currency': stake_currency,
'fiat_currency': fiat_currency
}) })
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
@ -555,6 +561,7 @@ class FreqtradeBot(object):
Trade.session.flush() Trade.session.flush()
logger.info('Buy order timeout for %s.', trade) logger.info('Buy order timeout for %s.', trade)
self.rpc.send_msg({ self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled buy order for {pair_s} cancelled due to timeout' 'status': f'Unfilled buy order for {pair_s} cancelled due to timeout'
}) })
return True return True
@ -566,6 +573,7 @@ class FreqtradeBot(object):
trade.open_order_id = None trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade) logger.info('Partial buy order timeout for %s.', trade)
self.rpc.send_msg({ self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Remaining buy order for {pair_s} cancelled due to timeout' 'status': f'Remaining buy order for {pair_s} cancelled due to timeout'
}) })
return False return False
@ -586,6 +594,7 @@ class FreqtradeBot(object):
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
self.rpc.send_msg({ self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled sell order for {pair_s} cancelled due to timeout' 'status': f'Unfilled sell order for {pair_s} cancelled due to timeout'
}) })
logger.info('Sell order timeout for %s.', trade) logger.info('Sell order timeout for %s.', trade)
@ -601,47 +610,47 @@ class FreqtradeBot(object):
:param limit: limit rate for the sell order :param limit: limit rate for the sell order
:return: None :return: None
""" """
exc = trade.exchange
pair = trade.pair
# Execute sell and update trade record # Execute sell and update trade record
order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id'] order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id']
trade.open_order_id = order_id trade.open_order_id = order_id
trade.close_rate_requested = limit trade.close_rate_requested = limit
fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2)
profit_trade = trade.calc_profit(rate=limit) profit_trade = trade.calc_profit(rate=limit)
current_rate = self.exchange.get_ticker(trade.pair)['bid'] current_rate = self.exchange.get_ticker(trade.pair)['bid']
profit = trade.calc_profit_percent(limit) profit_percent = trade.calc_profit_percent(limit)
pair_url = self.exchange.get_pair_detail_url(trade.pair) pair_url = self.exchange.get_pair_detail_url(trade.pair)
gain = "profit" if fmt_exp_profit > 0 else "loss" gain = "profit" if profit_percent > 0 else "loss"
message = f"*{exc}:* Selling\n" \ msg = {
f"*Current Pair:* [{pair}]({pair_url})\n" \ 'type': RPCMessageType.SELL_NOTIFICATION,
f"*Limit:* `{limit}`\n" \ 'exchange': trade.exchange.capitalize(),
f"*Amount:* `{round(trade.amount, 8)}`\n" \ 'pair': trade.pair,
f"*Open Rate:* `{trade.open_rate:.8f}`\n" \ 'gain': gain,
f"*Current Rate:* `{current_rate:.8f}`\n" \ 'market_url': pair_url,
f"*Profit:* `{round(profit * 100, 2):.2f}%`" \ 'limit': limit,
"" 'amount': trade.amount,
'open_rate': trade.open_rate,
'current_rate': current_rate,
'profit_amount': profit_trade,
'profit_percent': profit_percent,
}
# For regular case, when the configuration exists # For regular case, when the configuration exists
if 'stake_currency' in self.config and 'fiat_display_currency' in self.config: if 'stake_currency' in self.config and 'fiat_display_currency' in self.config:
stake = self.config['stake_currency'] stake_currency = self.config['stake_currency']
fiat = self.config['fiat_display_currency'] fiat_currency = self.config['fiat_display_currency']
fiat_converter = CryptoToFiatConverter() fiat_converter = CryptoToFiatConverter()
profit_fiat = fiat_converter.convert_amount( profit_fiat = fiat_converter.convert_amount(
profit_trade, profit_trade,
stake, stake_currency,
fiat fiat_currency,
) )
message += f'` ({gain}: {fmt_exp_profit:.2f}%, {profit_trade:.8f} {stake}`' \ msg.update({
f'` / {profit_fiat:.3f} {fiat})`'\ 'profit_fiat': profit_fiat,
'' 'stake_currency': stake_currency,
# Because telegram._forcesell does not have the configuration 'fiat_currency': fiat_currency,
# Ignore the FIAT value and does not show the stake_currency as well })
else:
gain = "profit" if fmt_exp_profit > 0 else "loss"
message += f'` ({gain}: {fmt_exp_profit:.2f}%, {profit_trade:.8f})`'
# Send the message # Send the message
self.rpc.send_msg({'status': message}) self.rpc.send_msg(msg)
Trade.session.flush() Trade.session.flush()

View File

@ -13,6 +13,7 @@ from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration from freqtrade.configuration import Configuration
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.state import State from freqtrade.state import State
from freqtrade.rpc import RPCMessageType
logger = logging.getLogger('freqtrade') logger = logging.getLogger('freqtrade')
@ -60,6 +61,7 @@ def main(sysargv: List[str]) -> None:
finally: finally:
if freqtrade: if freqtrade:
freqtrade.rpc.send_msg({ freqtrade.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': 'process died' 'status': 'process died'
}) })
freqtrade.cleanup() freqtrade.cleanup()
@ -76,6 +78,7 @@ def reconfigure(freqtrade: FreqtradeBot, args: Namespace) -> FreqtradeBot:
# Create new instance # Create new instance
freqtrade = FreqtradeBot(Configuration(args).get_config()) freqtrade = FreqtradeBot(Configuration(args).get_config())
freqtrade.rpc.send_msg({ freqtrade.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': 'config reloaded' 'status': 'config reloaded'
}) })
return freqtrade return freqtrade

View File

@ -0,0 +1,2 @@
from .rpc import RPC, RPCMessageType, RPCException # noqa
from .rpc_manager import RPCManager # noqa

View File

@ -5,6 +5,7 @@ import logging
from abc import abstractmethod from abc import abstractmethod
from datetime import timedelta, datetime, date from datetime import timedelta, datetime, date
from decimal import Decimal from decimal import Decimal
from enum import Enum
from typing import Dict, Any, List from typing import Dict, Any, List
import arrow import arrow
@ -19,6 +20,15 @@ from freqtrade.state import State
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RPCMessageType(Enum):
STATUS_NOTIFICATION = 'status'
BUY_NOTIFICATION = 'buy'
SELL_NOTIFICATION = 'sell'
def __repr__(self):
return self.value
class RPCException(Exception): class RPCException(Exception):
""" """
Should be raised with a rpc-formatted message in an _rpc_* method Should be raised with a rpc-formatted message in an _rpc_* method
@ -33,11 +43,6 @@ class RPCException(Exception):
def __str__(self): def __str__(self):
return self.message return self.message
def __json__(self):
return {
'msg': self.message
}
class RPC(object): class RPC(object):
""" """

View File

@ -2,9 +2,9 @@
This module contains class to manage RPC communications (Telegram, Slack, ...) This module contains class to manage RPC communications (Telegram, Slack, ...)
""" """
import logging import logging
from typing import List, Dict from typing import List, Dict, Any
from freqtrade.rpc.rpc import RPC from freqtrade.rpc import RPC
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -32,7 +32,7 @@ class RPCManager(object):
mod.cleanup() mod.cleanup()
del mod del mod
def send_msg(self, msg: Dict[str, str]) -> None: def send_msg(self, msg: Dict[str, Any]) -> None:
""" """
Send given message to all registered rpc modules. Send given message to all registered rpc modules.
A message consists of one or more key value pairs of strings. A message consists of one or more key value pairs of strings.

View File

@ -12,7 +12,7 @@ from telegram.error import NetworkError, TelegramError
from telegram.ext import CommandHandler, Updater from telegram.ext import CommandHandler, Updater
from freqtrade.__init__ import __version__ from freqtrade.__init__ import __version__
from freqtrade.rpc.rpc import RPC, RPCException from freqtrade.rpc import RPC, RPCException, RPCMessageType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -110,9 +110,42 @@ class Telegram(RPC):
""" """
self._updater.stop() self._updater.stop()
def send_msg(self, msg: Dict[str, str]) -> None: def send_msg(self, msg: Dict[str, Any]) -> None:
""" Send a message to telegram channel """ """ Send a message to telegram channel """
self._send_msg('*Status:* `{status}`'.format(**msg))
if msg['type'] == RPCMessageType.BUY_NOTIFICATION:
message = "*{exchange}:* Buying [{pair}]({market_url})\n" \
"with limit `{limit:.8f}\n" \
"({stake_amount:.6f} {stake_currency}," \
"{stake_amount_fiat:.3f} {fiat_currency})`" \
.format(**msg)
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
msg['amount'] = round(msg['amount'], 8)
msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)
message = "*{exchange}:* Selling [{pair}]({market_url})\n" \
"*Limit:* `{limit:.8f}`\n" \
"*Amount:* `{amount:.8f}`\n" \
"*Open Rate:* `{open_rate:.8f}`\n" \
"*Current Rate:* `{current_rate:.8f}`\n" \
"*Profit:* `{profit_percent:.2f}%`".format(**msg)
# Check if all sell properties are available.
# This might not be the case if the message origin is triggered by /forcesell
if all(prop in msg for prop in ['gain', 'profit_fiat',
'fiat_currency', 'stake_currency']):
message += '` ({gain}: {profit_amount:.8f} {stake_currency}`' \
'` / {profit_fiat:.3f} {fiat_currency})`'.format(**msg)
self._send_msg(message)
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
message = '*Status:* `{status}`'.format(**msg)
else:
raise NotImplementedError('Unknown message type: {}'.format(msg['type']))
self._send_msg(message)
@authorized_only @authorized_only
def _status(self, bot: Bot, update: Update) -> None: def _status(self, bot: Bot, update: Update) -> None:

View File

@ -11,7 +11,7 @@ import pytest
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc.rpc import RPC, RPCException from freqtrade.rpc import RPC, RPCException
from freqtrade.state import State from freqtrade.state import State
from freqtrade.tests.test_freqtradebot import (patch_coinmarketcap, from freqtrade.tests.test_freqtradebot import (patch_coinmarketcap,
patch_get_signal) patch_get_signal)

View File

@ -6,8 +6,8 @@ import logging
from copy import deepcopy from copy import deepcopy
from unittest.mock import MagicMock from unittest.mock import MagicMock
from freqtrade.rpc.rpc_manager import RPCManager from freqtrade.rpc import RPCMessageType, RPCManager
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has from freqtrade.tests.conftest import log_has, get_patched_freqtradebot
def test_rpc_manager_object() -> None: def test_rpc_manager_object() -> None:
@ -102,9 +102,12 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
freqtradebot = get_patched_freqtradebot(mocker, conf) freqtradebot = get_patched_freqtradebot(mocker, conf)
rpc_manager = RPCManager(freqtradebot) rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({'status': 'test'}) rpc_manager.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': 'test'
})
assert log_has("Sending rpc message: {'status': 'test'}", caplog.record_tuples) assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog.record_tuples)
assert telegram_mock.call_count == 0 assert telegram_mock.call_count == 0
@ -117,7 +120,10 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot) rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({'status': 'test'}) rpc_manager.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': 'test'
})
assert log_has("Sending rpc message: {'status': 'test'}", caplog.record_tuples) assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog.record_tuples)
assert telegram_mock.call_count == 1 assert telegram_mock.call_count == 1

View File

@ -9,7 +9,7 @@ import re
from copy import deepcopy from copy import deepcopy
from datetime import datetime from datetime import datetime
from random import randint from random import randint
from unittest.mock import MagicMock from unittest.mock import MagicMock, ANY
from telegram import Chat, Message, Update from telegram import Chat, Message, Update
from telegram.error import NetworkError from telegram.error import NetworkError
@ -17,6 +17,7 @@ from telegram.error import NetworkError
from freqtrade import __version__ from freqtrade import __version__
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc import RPCMessageType
from freqtrade.rpc.telegram import Telegram, authorized_only from freqtrade.rpc.telegram import Telegram, authorized_only
from freqtrade.state import State from freqtrade.state import State
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has, from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has,
@ -757,13 +758,23 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
telegram._forcesell(bot=MagicMock(), update=update) telegram._forcesell(bot=MagicMock(), update=update)
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
last_call = rpc_mock.call_args_list[-1][0][0]['status'] last_msg = rpc_mock.call_args_list[-1][0][0]
assert 'Selling' in last_call assert {
assert '[ETH/BTC]' in last_call 'type': RPCMessageType.SELL_NOTIFICATION,
assert 'Amount' in last_call 'exchange': 'Bittrex',
assert '0.00001172' in last_call 'pair': 'ETH/BTC',
assert 'profit: 6.11%, 0.00006126' in last_call 'gain': 'profit',
assert '0.919 USD' in last_call 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'limit': 1.172e-05,
'amount': 90.99181073703367,
'open_rate': 1.099e-05,
'current_rate': 1.172e-05,
'profit_amount': 6.126e-05,
'profit_percent': 0.06110514,
'profit_fiat': 0.9189,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
} == last_msg
def test_forcesell_down_handle(default_conf, update, ticker, fee, def test_forcesell_down_handle(default_conf, update, ticker, fee,
@ -803,14 +814,25 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
update.message.text = '/forcesell 1' update.message.text = '/forcesell 1'
telegram._forcesell(bot=MagicMock(), update=update) telegram._forcesell(bot=MagicMock(), update=update)
last_call = rpc_mock.call_args_list[-1][0][0]['status']
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling' in last_call
assert '[ETH/BTC]' in last_call last_msg = rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in last_call assert {
assert '0.00001044' in last_call 'type': RPCMessageType.SELL_NOTIFICATION,
assert 'loss: -5.48%, -0.00005492' in last_call 'exchange': 'Bittrex',
assert '-0.824 USD' in last_call 'pair': 'ETH/BTC',
'gain': 'loss',
'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'limit': 1.044e-05,
'amount': 90.99181073703367,
'open_rate': 1.099e-05,
'current_rate': 1.044e-05,
'profit_amount': -5.492e-05,
'profit_percent': -0.05478343,
'profit_fiat': -0.8238000000000001,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
} == last_msg
def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None: def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
@ -843,10 +865,23 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
telegram._forcesell(bot=MagicMock(), update=update) telegram._forcesell(bot=MagicMock(), update=update)
assert rpc_mock.call_count == 4 assert rpc_mock.call_count == 4
for args in rpc_mock.call_args_list: msg = rpc_mock.call_args_list[0][0][0]
assert '0.00001098' in args[0][0]['status'] assert {
assert 'loss: -0.59%, -0.00000591 BTC' in args[0][0]['status'] 'type': RPCMessageType.SELL_NOTIFICATION,
assert '-0.089 USD' in args[0][0]['status'] 'exchange': 'Bittrex',
'pair': 'ETH/BTC',
'gain': 'loss',
'market_url': ANY,
'limit': 1.098e-05,
'amount': 90.99181073703367,
'open_rate': 1.099e-05,
'current_rate': 1.098e-05,
'profit_amount': -5.91e-06,
'profit_percent': -0.00589292,
'profit_fiat': -0.08865,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
} == msg
def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
@ -1040,7 +1075,22 @@ def test_version_handle(default_conf, update, mocker) -> None:
assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0] assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0]
def test_send_msg(default_conf, mocker) -> None: def test_send_msg_buy_notification() -> None:
# TODO: implement me
pass
def test_send_msg_sell_notification() -> None:
# TODO: implement me
pass
def test_send_msg_status_notification() -> None:
# TODO: implement me
pass
def test__send_msg(default_conf, mocker) -> None:
""" """
Test send_msg() method Test send_msg() method
""" """
@ -1056,7 +1106,7 @@ def test_send_msg(default_conf, mocker) -> None:
assert len(bot.method_calls) == 1 assert len(bot.method_calls) == 1
def test_send_msg_network_error(default_conf, mocker, caplog) -> None: def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
""" """
Test send_msg() method Test send_msg() method
""" """

View File

@ -18,9 +18,9 @@ from freqtrade import (DependencyException, OperationalException,
TemporaryError, constants) TemporaryError, constants)
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc import RPCMessageType
from freqtrade.state import State from freqtrade.state import State
from freqtrade.tests.conftest import (log_has, patch_coinmarketcap, from freqtrade.tests.conftest import log_has, patch_coinmarketcap, patch_exchange
patch_exchange)
# Functions for recurrent object patching # Functions for recurrent object patching
@ -1375,14 +1375,23 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid']) freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
last_call = rpc_mock.call_args_list[-1][0][0]['status'] last_msg = rpc_mock.call_args_list[-1][0][0]
assert 'Selling' in last_call assert {
assert '[ETH/BTC]' in last_call 'type': RPCMessageType.SELL_NOTIFICATION,
assert 'Amount' in last_call 'exchange': 'Bittrex',
assert 'Profit' in last_call 'pair': 'ETH/BTC',
assert '0.00001172' in last_call 'gain': 'profit',
assert 'profit: 6.11%, 0.00006126' in last_call 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
assert '0.919 USD' in last_call 'limit': 1.172e-05,
'amount': 90.99181073703367,
'open_rate': 1.099e-05,
'current_rate': 1.172e-05,
'profit_amount': 6.126e-05,
'profit_percent': 0.06110514,
'profit_fiat': 0.9189,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
} == last_msg
def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, mocker) -> None: def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, mocker) -> None:
@ -1418,13 +1427,23 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets,
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid']) freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
last_call = rpc_mock.call_args_list[-1][0][0]['status'] last_msg = rpc_mock.call_args_list[-1][0][0]
assert 'Selling' in last_call assert {
assert '[ETH/BTC]' in last_call 'type': RPCMessageType.SELL_NOTIFICATION,
assert 'Amount' in last_call 'exchange': 'Bittrex',
assert '0.00001044' in last_call 'pair': 'ETH/BTC',
assert 'loss: -5.48%, -0.00005492' in last_call 'gain': 'loss',
assert '-0.824 USD' in last_call 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'limit': 1.044e-05,
'amount': 90.99181073703367,
'open_rate': 1.099e-05,
'current_rate': 1.044e-05,
'profit_amount': -5.492e-05,
'profit_percent': -0.05478343,
'profit_fiat': -0.8238000000000001,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
} == last_msg
def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee, def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
@ -1461,13 +1480,20 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid']) freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
last_call = rpc_mock.call_args_list[-1][0][0]['status'] last_msg = rpc_mock.call_args_list[-1][0][0]
assert 'Selling' in last_call assert {
assert '[ETH/BTC]' in last_call 'type': RPCMessageType.SELL_NOTIFICATION,
assert 'Amount' in last_call 'exchange': 'Bittrex',
assert '0.00001172' in last_call 'pair': 'ETH/BTC',
assert '(profit: 6.11%, 0.00006126)' in last_call 'gain': 'profit',
assert 'USD' not in last_call 'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'limit': 1.172e-05,
'amount': 90.99181073703367,
'open_rate': 1.099e-05,
'current_rate': 1.172e-05,
'profit_amount': 6.126e-05,
'profit_percent': 0.06110514,
} == last_msg
def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee, def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
@ -1504,11 +1530,20 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid']) freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
last_call = rpc_mock.call_args_list[-1][0][0]['status'] last_msg = rpc_mock.call_args_list[-1][0][0]
assert 'Selling' in last_call assert {
assert '[ETH/BTC]' in last_call 'type': RPCMessageType.SELL_NOTIFICATION,
assert '0.00001044' in last_call 'exchange': 'Bittrex',
assert 'loss: -5.48%, -0.00005492' in last_call 'pair': 'ETH/BTC',
'gain': 'loss',
'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'limit': 1.044e-05,
'amount': 90.99181073703367,
'open_rate': 1.099e-05,
'current_rate': 1.044e-05,
'profit_amount': -5.492e-05,
'profit_percent': -0.05478343,
} == last_msg
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,