use more granular msg dict for buy/sell notifications
This commit is contained in:
parent
4cb1aa1d97
commit
0920fb6120
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
from .rpc import RPC, RPCMessageType, RPCException # noqa
|
||||||
|
from .rpc_manager import RPCManager # noqa
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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.
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user