mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-14 04:03:55 +00:00
Merge branch 'develop' into ask_strategy_verbosity
This commit is contained in:
commit
ed1268cf39
|
@ -132,6 +132,14 @@ class RPC:
|
||||||
except DependencyException:
|
except DependencyException:
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
current_profit = trade.calc_profit_ratio(current_rate)
|
current_profit = trade.calc_profit_ratio(current_rate)
|
||||||
|
current_profit_abs = trade.calc_profit(current_rate)
|
||||||
|
# Calculate guaranteed profit (in case of trailing stop)
|
||||||
|
stoploss_entry_dist = trade.calc_profit(trade.stop_loss)
|
||||||
|
stoploss_entry_dist_ratio = trade.calc_profit_ratio(trade.stop_loss)
|
||||||
|
# calculate distance to stoploss
|
||||||
|
stoploss_current_dist = trade.stop_loss - current_rate
|
||||||
|
stoploss_current_dist_ratio = stoploss_current_dist / current_rate
|
||||||
|
|
||||||
fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
|
fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
|
||||||
if trade.close_profit is not None else None)
|
if trade.close_profit is not None else None)
|
||||||
trade_dict = trade.to_json()
|
trade_dict = trade.to_json()
|
||||||
|
@ -142,6 +150,11 @@ class RPC:
|
||||||
current_rate=current_rate,
|
current_rate=current_rate,
|
||||||
current_profit=current_profit,
|
current_profit=current_profit,
|
||||||
current_profit_pct=round(current_profit * 100, 2),
|
current_profit_pct=round(current_profit * 100, 2),
|
||||||
|
current_profit_abs=current_profit_abs,
|
||||||
|
stoploss_current_dist=stoploss_current_dist,
|
||||||
|
stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8),
|
||||||
|
stoploss_entry_dist=stoploss_entry_dist,
|
||||||
|
stoploss_entry_dist_ratio=round(stoploss_entry_dist_ratio, 8),
|
||||||
open_order='({} {} rem={:.8f})'.format(
|
open_order='({} {} rem={:.8f})'.format(
|
||||||
order['type'], order['side'], order['remaining']
|
order['type'], order['side'], order['remaining']
|
||||||
) if order else None,
|
) if order else None,
|
||||||
|
@ -285,8 +298,9 @@ class RPC:
|
||||||
|
|
||||||
# Prepare data to display
|
# Prepare data to display
|
||||||
profit_closed_coin_sum = round(sum(profit_closed_coin), 8)
|
profit_closed_coin_sum = round(sum(profit_closed_coin), 8)
|
||||||
profit_closed_percent = (round(mean(profit_closed_ratio) * 100, 2) if profit_closed_ratio
|
profit_closed_ratio_mean = mean(profit_closed_ratio) if profit_closed_ratio else 0.0
|
||||||
else 0.0)
|
profit_closed_ratio_sum = sum(profit_closed_ratio) if profit_closed_ratio else 0.0
|
||||||
|
|
||||||
profit_closed_fiat = self._fiat_converter.convert_amount(
|
profit_closed_fiat = self._fiat_converter.convert_amount(
|
||||||
profit_closed_coin_sum,
|
profit_closed_coin_sum,
|
||||||
stake_currency,
|
stake_currency,
|
||||||
|
@ -294,7 +308,8 @@ class RPC:
|
||||||
) if self._fiat_converter else 0
|
) if self._fiat_converter else 0
|
||||||
|
|
||||||
profit_all_coin_sum = round(sum(profit_all_coin), 8)
|
profit_all_coin_sum = round(sum(profit_all_coin), 8)
|
||||||
profit_all_percent = round(mean(profit_all_ratio) * 100, 2) if profit_all_ratio else 0.0
|
profit_all_ratio_mean = mean(profit_all_ratio) if profit_all_ratio else 0.0
|
||||||
|
profit_all_ratio_sum = sum(profit_all_ratio) if profit_all_ratio else 0.0
|
||||||
profit_all_fiat = self._fiat_converter.convert_amount(
|
profit_all_fiat = self._fiat_converter.convert_amount(
|
||||||
profit_all_coin_sum,
|
profit_all_coin_sum,
|
||||||
stake_currency,
|
stake_currency,
|
||||||
|
@ -306,10 +321,18 @@ class RPC:
|
||||||
num = float(len(durations) or 1)
|
num = float(len(durations) or 1)
|
||||||
return {
|
return {
|
||||||
'profit_closed_coin': profit_closed_coin_sum,
|
'profit_closed_coin': profit_closed_coin_sum,
|
||||||
'profit_closed_percent': profit_closed_percent,
|
'profit_closed_percent': round(profit_closed_ratio_mean * 100, 2), # DEPRECATED
|
||||||
|
'profit_closed_percent_mean': round(profit_closed_ratio_mean * 100, 2),
|
||||||
|
'profit_closed_ratio_mean': profit_closed_ratio_mean,
|
||||||
|
'profit_closed_percent_sum': round(profit_closed_ratio_sum * 100, 2),
|
||||||
|
'profit_closed_ratio_sum': profit_closed_ratio_sum,
|
||||||
'profit_closed_fiat': profit_closed_fiat,
|
'profit_closed_fiat': profit_closed_fiat,
|
||||||
'profit_all_coin': profit_all_coin_sum,
|
'profit_all_coin': profit_all_coin_sum,
|
||||||
'profit_all_percent': profit_all_percent,
|
'profit_all_percent': round(profit_all_ratio_mean * 100, 2), # DEPRECATED
|
||||||
|
'profit_all_percent_mean': round(profit_all_ratio_mean * 100, 2),
|
||||||
|
'profit_all_ratio_mean': profit_all_ratio_mean,
|
||||||
|
'profit_all_percent_sum': round(profit_all_ratio_sum * 100, 2),
|
||||||
|
'profit_all_ratio_sum': profit_all_ratio_sum,
|
||||||
'profit_all_fiat': profit_all_fiat,
|
'profit_all_fiat': profit_all_fiat,
|
||||||
'trade_count': len(trades),
|
'trade_count': len(trades),
|
||||||
'closed_trade_count': len([t for t in trades if not t.is_open]),
|
'closed_trade_count': len([t for t in trades if not t.is_open]),
|
||||||
|
|
|
@ -19,7 +19,6 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
logger.debug('Included module rpc.telegram ...')
|
logger.debug('Included module rpc.telegram ...')
|
||||||
|
|
||||||
|
|
||||||
MAX_TELEGRAM_MESSAGE_LENGTH = 4096
|
MAX_TELEGRAM_MESSAGE_LENGTH = 4096
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +28,7 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
|
||||||
:param command_handler: Telegram CommandHandler
|
:param command_handler: Telegram CommandHandler
|
||||||
:return: decorated function
|
:return: decorated function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self, *args, **kwargs):
|
||||||
""" Decorator logic """
|
""" Decorator logic """
|
||||||
update = kwargs.get('update') or args[0]
|
update = kwargs.get('update') or args[0]
|
||||||
|
@ -133,7 +133,7 @@ class Telegram(RPC):
|
||||||
else:
|
else:
|
||||||
msg['stake_amount_fiat'] = 0
|
msg['stake_amount_fiat'] = 0
|
||||||
|
|
||||||
message = ("*{exchange}:* Buying {pair}\n"
|
message = ("\N{LARGE BLUE CIRCLE} *{exchange}:* Buying {pair}\n"
|
||||||
"*Amount:* `{amount:.8f}`\n"
|
"*Amount:* `{amount:.8f}`\n"
|
||||||
"*Open Rate:* `{limit:.8f}`\n"
|
"*Open Rate:* `{limit:.8f}`\n"
|
||||||
"*Current Rate:* `{current_rate:.8f}`\n"
|
"*Current Rate:* `{current_rate:.8f}`\n"
|
||||||
|
@ -144,7 +144,8 @@ class Telegram(RPC):
|
||||||
message += ")`"
|
message += ")`"
|
||||||
|
|
||||||
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
|
||||||
message = "*{exchange}:* Cancelling Open Buy Order for {pair}".format(**msg)
|
message = "\N{WARNING SIGN} *{exchange}:* " \
|
||||||
|
"Cancelling Open Buy Order for {pair}".format(**msg)
|
||||||
|
|
||||||
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
|
||||||
msg['amount'] = round(msg['amount'], 8)
|
msg['amount'] = round(msg['amount'], 8)
|
||||||
|
@ -153,7 +154,9 @@ class Telegram(RPC):
|
||||||
microsecond=0) - msg['open_date'].replace(microsecond=0)
|
microsecond=0) - msg['open_date'].replace(microsecond=0)
|
||||||
msg['duration_min'] = msg['duration'].total_seconds() / 60
|
msg['duration_min'] = msg['duration'].total_seconds() / 60
|
||||||
|
|
||||||
message = ("*{exchange}:* Selling {pair}\n"
|
msg['emoji'] = self._get_sell_emoji(msg)
|
||||||
|
|
||||||
|
message = ("{emoji} *{exchange}:* Selling {pair}\n"
|
||||||
"*Amount:* `{amount:.8f}`\n"
|
"*Amount:* `{amount:.8f}`\n"
|
||||||
"*Open Rate:* `{open_rate:.8f}`\n"
|
"*Open Rate:* `{open_rate:.8f}`\n"
|
||||||
"*Current Rate:* `{current_rate:.8f}`\n"
|
"*Current Rate:* `{current_rate:.8f}`\n"
|
||||||
|
@ -165,21 +168,21 @@ class Telegram(RPC):
|
||||||
# Check if all sell properties are available.
|
# Check if all sell properties are available.
|
||||||
# This might not be the case if the message origin is triggered by /forcesell
|
# This might not be the case if the message origin is triggered by /forcesell
|
||||||
if (all(prop in msg for prop in ['gain', 'fiat_currency', 'stake_currency'])
|
if (all(prop in msg for prop in ['gain', 'fiat_currency', 'stake_currency'])
|
||||||
and self._fiat_converter):
|
and self._fiat_converter):
|
||||||
msg['profit_fiat'] = self._fiat_converter.convert_amount(
|
msg['profit_fiat'] = self._fiat_converter.convert_amount(
|
||||||
msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
|
msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
|
||||||
message += (' `({gain}: {profit_amount:.8f} {stake_currency}'
|
message += (' `({gain}: {profit_amount:.8f} {stake_currency}'
|
||||||
' / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
|
' / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
|
||||||
|
|
||||||
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
|
||||||
message = ("*{exchange}:* Cancelling Open Sell Order "
|
message = ("\N{WARNING SIGN} *{exchange}:* Cancelling Open Sell Order "
|
||||||
"for {pair}. Reason: {reason}").format(**msg)
|
"for {pair}. Reason: {reason}").format(**msg)
|
||||||
|
|
||||||
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
|
||||||
message = '*Status:* `{status}`'.format(**msg)
|
message = '*Status:* `{status}`'.format(**msg)
|
||||||
|
|
||||||
elif msg['type'] == RPCMessageType.WARNING_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.WARNING_NOTIFICATION:
|
||||||
message = '*Warning:* `{status}`'.format(**msg)
|
message = '\N{WARNING SIGN} *Warning:* `{status}`'.format(**msg)
|
||||||
|
|
||||||
elif msg['type'] == RPCMessageType.CUSTOM_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.CUSTOM_NOTIFICATION:
|
||||||
message = '{status}'.format(**msg)
|
message = '{status}'.format(**msg)
|
||||||
|
@ -189,6 +192,20 @@ class Telegram(RPC):
|
||||||
|
|
||||||
self._send_msg(message)
|
self._send_msg(message)
|
||||||
|
|
||||||
|
def _get_sell_emoji(self, msg):
|
||||||
|
"""
|
||||||
|
Get emoji for sell-side
|
||||||
|
"""
|
||||||
|
|
||||||
|
if float(msg['profit_percent']) >= 5.0:
|
||||||
|
return "\N{ROCKET}"
|
||||||
|
elif float(msg['profit_percent']) >= 0.0:
|
||||||
|
return "\N{EIGHT SPOKED ASTERISK}"
|
||||||
|
elif msg['sell_reason'] == "stop_loss":
|
||||||
|
return"\N{WARNING SIGN}"
|
||||||
|
else:
|
||||||
|
return "\N{CROSS MARK}"
|
||||||
|
|
||||||
@authorized_only
|
@authorized_only
|
||||||
def _status(self, update: Update, context: CallbackContext) -> None:
|
def _status(self, update: Update, context: CallbackContext) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -222,8 +239,8 @@ class Telegram(RPC):
|
||||||
# Adding initial stoploss only if it is different from stoploss
|
# Adding initial stoploss only if it is different from stoploss
|
||||||
"*Initial Stoploss:* `{initial_stop_loss:.8f}` " +
|
"*Initial Stoploss:* `{initial_stop_loss:.8f}` " +
|
||||||
("`({initial_stop_loss_pct:.2f}%)`") if (
|
("`({initial_stop_loss_pct:.2f}%)`") if (
|
||||||
r['stop_loss'] != r['initial_stop_loss']
|
r['stop_loss'] != r['initial_stop_loss']
|
||||||
and r['initial_stop_loss_pct'] is not None) else "",
|
and r['initial_stop_loss_pct'] is not None) else "",
|
||||||
|
|
||||||
# Adding stoploss and stoploss percentage only if it is not None
|
# Adding stoploss and stoploss percentage only if it is not None
|
||||||
"*Stoploss:* `{stop_loss:.8f}` " +
|
"*Stoploss:* `{stop_loss:.8f}` " +
|
||||||
|
@ -315,10 +332,12 @@ class Telegram(RPC):
|
||||||
stake_cur,
|
stake_cur,
|
||||||
fiat_disp_cur)
|
fiat_disp_cur)
|
||||||
profit_closed_coin = stats['profit_closed_coin']
|
profit_closed_coin = stats['profit_closed_coin']
|
||||||
profit_closed_percent = stats['profit_closed_percent']
|
profit_closed_percent_mean = stats['profit_closed_percent_mean']
|
||||||
|
profit_closed_percent_sum = stats['profit_closed_percent_sum']
|
||||||
profit_closed_fiat = stats['profit_closed_fiat']
|
profit_closed_fiat = stats['profit_closed_fiat']
|
||||||
profit_all_coin = stats['profit_all_coin']
|
profit_all_coin = stats['profit_all_coin']
|
||||||
profit_all_percent = stats['profit_all_percent']
|
profit_all_percent_mean = stats['profit_all_percent_mean']
|
||||||
|
profit_all_percent_sum = stats['profit_all_percent_sum']
|
||||||
profit_all_fiat = stats['profit_all_fiat']
|
profit_all_fiat = stats['profit_all_fiat']
|
||||||
trade_count = stats['trade_count']
|
trade_count = stats['trade_count']
|
||||||
first_trade_date = stats['first_trade_date']
|
first_trade_date = stats['first_trade_date']
|
||||||
|
@ -333,13 +352,16 @@ class Telegram(RPC):
|
||||||
if stats['closed_trade_count'] > 0:
|
if stats['closed_trade_count'] > 0:
|
||||||
markdown_msg = ("*ROI:* Closed trades\n"
|
markdown_msg = ("*ROI:* Closed trades\n"
|
||||||
f"∙ `{profit_closed_coin:.8f} {stake_cur} "
|
f"∙ `{profit_closed_coin:.8f} {stake_cur} "
|
||||||
f"({profit_closed_percent:.2f}%)`\n"
|
f"({profit_closed_percent_mean:.2f}%) "
|
||||||
|
f"({profit_closed_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
||||||
f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n")
|
f"∙ `{profit_closed_fiat:.3f} {fiat_disp_cur}`\n")
|
||||||
else:
|
else:
|
||||||
markdown_msg = "`No closed trade` \n"
|
markdown_msg = "`No closed trade` \n"
|
||||||
|
|
||||||
markdown_msg += (f"*ROI:* All trades\n"
|
markdown_msg += (f"*ROI:* All trades\n"
|
||||||
f"∙ `{profit_all_coin:.8f} {stake_cur} ({profit_all_percent:.2f}%)`\n"
|
f"∙ `{profit_all_coin:.8f} {stake_cur} "
|
||||||
|
f"({profit_all_percent_mean:.2f}%) "
|
||||||
|
f"({profit_all_percent_sum} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
|
||||||
f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n"
|
f"∙ `{profit_all_fiat:.3f} {fiat_disp_cur}`\n"
|
||||||
f"*Total Trade Count:* `{trade_count}`\n"
|
f"*Total Trade Count:* `{trade_count}`\n"
|
||||||
f"*First Trade opened:* `{first_trade_date}`\n"
|
f"*First Trade opened:* `{first_trade_date}`\n"
|
||||||
|
@ -363,14 +385,14 @@ class Telegram(RPC):
|
||||||
"This mode is still experimental!\n"
|
"This mode is still experimental!\n"
|
||||||
"Starting capital: "
|
"Starting capital: "
|
||||||
f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n"
|
f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n"
|
||||||
)
|
)
|
||||||
for currency in result['currencies']:
|
for currency in result['currencies']:
|
||||||
if currency['est_stake'] > 0.0001:
|
if currency['est_stake'] > 0.0001:
|
||||||
curr_output = "*{currency}:*\n" \
|
curr_output = "*{currency}:*\n" \
|
||||||
"\t`Available: {free: .8f}`\n" \
|
"\t`Available: {free: .8f}`\n" \
|
||||||
"\t`Balance: {balance: .8f}`\n" \
|
"\t`Balance: {balance: .8f}`\n" \
|
||||||
"\t`Pending: {used: .8f}`\n" \
|
"\t`Pending: {used: .8f}`\n" \
|
||||||
"\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency)
|
"\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency)
|
||||||
else:
|
else:
|
||||||
curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)
|
curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)
|
||||||
|
|
||||||
|
@ -587,7 +609,7 @@ class Telegram(RPC):
|
||||||
"*/profit:* `Lists cumulative profit from all finished trades`\n" \
|
"*/profit:* `Lists cumulative profit from all finished trades`\n" \
|
||||||
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " \
|
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " \
|
||||||
"regardless of profit`\n" \
|
"regardless of profit`\n" \
|
||||||
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else '' }" \
|
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}" \
|
||||||
"*/performance:* `Show performance of each finished trade grouped by pair`\n" \
|
"*/performance:* `Show performance of each finished trade grouped by pair`\n" \
|
||||||
"*/daily <n>:* `Shows profit or loss per day, over the last n days`\n" \
|
"*/daily <n>:* `Shows profit or loss per day, over the last n days`\n" \
|
||||||
"*/count:* `Show number of trades running compared to allowed number of trades`" \
|
"*/count:* `Show number of trades running compared to allowed number of trades`" \
|
||||||
|
|
|
@ -42,8 +42,12 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||||
rpc._rpc_trade_status()
|
rpc._rpc_trade_status()
|
||||||
|
|
||||||
freqtradebot.enter_positions()
|
freqtradebot.enter_positions()
|
||||||
|
trades = Trade.get_open_trades()
|
||||||
|
trades[0].open_order_id = None
|
||||||
|
freqtradebot.exit_positions(trades)
|
||||||
|
|
||||||
results = rpc._rpc_trade_status()
|
results = rpc._rpc_trade_status()
|
||||||
assert {
|
assert results[0] == {
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'base_currency': 'BTC',
|
'base_currency': 'BTC',
|
||||||
|
@ -54,11 +58,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||||
'fee_open': ANY,
|
'fee_open': ANY,
|
||||||
'fee_open_cost': ANY,
|
'fee_open_cost': ANY,
|
||||||
'fee_open_currency': ANY,
|
'fee_open_currency': ANY,
|
||||||
'fee_close': ANY,
|
'fee_close': fee.return_value,
|
||||||
'fee_close_cost': ANY,
|
'fee_close_cost': ANY,
|
||||||
'fee_close_currency': ANY,
|
'fee_close_currency': ANY,
|
||||||
'open_rate_requested': ANY,
|
'open_rate_requested': ANY,
|
||||||
'open_trade_price': ANY,
|
'open_trade_price': 0.0010025,
|
||||||
'close_rate_requested': ANY,
|
'close_rate_requested': ANY,
|
||||||
'sell_reason': ANY,
|
'sell_reason': ANY,
|
||||||
'sell_order_status': ANY,
|
'sell_order_status': ANY,
|
||||||
|
@ -80,28 +84,32 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||||
'close_profit_abs': None,
|
'close_profit_abs': None,
|
||||||
'current_profit': -0.00408133,
|
'current_profit': -0.00408133,
|
||||||
'current_profit_pct': -0.41,
|
'current_profit_pct': -0.41,
|
||||||
'stop_loss': 0.0,
|
'current_profit_abs': -4.09e-06,
|
||||||
'stop_loss_abs': 0.0,
|
'stop_loss': 9.882e-06,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_abs': 9.882e-06,
|
||||||
'stop_loss_ratio': None,
|
'stop_loss_pct': -10.0,
|
||||||
|
'stop_loss_ratio': -0.1,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': ANY,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': ANY,
|
||||||
'initial_stop_loss': 0.0,
|
'initial_stop_loss': 9.882e-06,
|
||||||
'initial_stop_loss_abs': 0.0,
|
'initial_stop_loss_abs': 9.882e-06,
|
||||||
'initial_stop_loss_pct': None,
|
'initial_stop_loss_pct': -10.0,
|
||||||
'initial_stop_loss_ratio': None,
|
'initial_stop_loss_ratio': -0.1,
|
||||||
'open_order': '(limit buy rem=0.00000000)',
|
'stoploss_current_dist': -1.1080000000000002e-06,
|
||||||
|
'stoploss_current_dist_ratio': -0.10081893,
|
||||||
|
'stoploss_entry_dist': -0.00010475,
|
||||||
|
'stoploss_entry_dist_ratio': -0.10448878,
|
||||||
|
'open_order': None,
|
||||||
'exchange': 'bittrex',
|
'exchange': 'bittrex',
|
||||||
|
}
|
||||||
} == results[0]
|
|
||||||
|
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||||
MagicMock(side_effect=DependencyException("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=DependencyException("Pair 'ETH/BTC' not available")))
|
||||||
results = rpc._rpc_trade_status()
|
results = rpc._rpc_trade_status()
|
||||||
assert isnan(results[0]['current_profit'])
|
assert isnan(results[0]['current_profit'])
|
||||||
assert isnan(results[0]['current_rate'])
|
assert isnan(results[0]['current_rate'])
|
||||||
assert {
|
assert results[0] == {
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'base_currency': 'BTC',
|
'base_currency': 'BTC',
|
||||||
|
@ -112,7 +120,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||||
'fee_open': ANY,
|
'fee_open': ANY,
|
||||||
'fee_open_cost': ANY,
|
'fee_open_cost': ANY,
|
||||||
'fee_open_currency': ANY,
|
'fee_open_currency': ANY,
|
||||||
'fee_close': ANY,
|
'fee_close': fee.return_value,
|
||||||
'fee_close_cost': ANY,
|
'fee_close_cost': ANY,
|
||||||
'fee_close_currency': ANY,
|
'fee_close_currency': ANY,
|
||||||
'open_rate_requested': ANY,
|
'open_rate_requested': ANY,
|
||||||
|
@ -138,20 +146,25 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||||
'close_profit_abs': None,
|
'close_profit_abs': None,
|
||||||
'current_profit': ANY,
|
'current_profit': ANY,
|
||||||
'current_profit_pct': ANY,
|
'current_profit_pct': ANY,
|
||||||
'stop_loss': 0.0,
|
'current_profit_abs': ANY,
|
||||||
'stop_loss_abs': 0.0,
|
'stop_loss': 9.882e-06,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_abs': 9.882e-06,
|
||||||
'stop_loss_ratio': None,
|
'stop_loss_pct': -10.0,
|
||||||
|
'stop_loss_ratio': -0.1,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': ANY,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': ANY,
|
||||||
'initial_stop_loss': 0.0,
|
'initial_stop_loss': 9.882e-06,
|
||||||
'initial_stop_loss_abs': 0.0,
|
'initial_stop_loss_abs': 9.882e-06,
|
||||||
'initial_stop_loss_pct': None,
|
'initial_stop_loss_pct': -10.0,
|
||||||
'initial_stop_loss_ratio': None,
|
'initial_stop_loss_ratio': -0.1,
|
||||||
'open_order': '(limit buy rem=0.00000000)',
|
'stoploss_current_dist': ANY,
|
||||||
|
'stoploss_current_dist_ratio': ANY,
|
||||||
|
'stoploss_entry_dist': -0.00010475,
|
||||||
|
'stoploss_entry_dist_ratio': -0.10448878,
|
||||||
|
'open_order': None,
|
||||||
'exchange': 'bittrex',
|
'exchange': 'bittrex',
|
||||||
} == results[0]
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||||
|
|
|
@ -430,9 +430,17 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li
|
||||||
'profit_all_coin': 6.217e-05,
|
'profit_all_coin': 6.217e-05,
|
||||||
'profit_all_fiat': 0,
|
'profit_all_fiat': 0,
|
||||||
'profit_all_percent': 6.2,
|
'profit_all_percent': 6.2,
|
||||||
|
'profit_all_percent_mean': 6.2,
|
||||||
|
'profit_all_ratio_mean': 0.06201058,
|
||||||
|
'profit_all_percent_sum': 6.2,
|
||||||
|
'profit_all_ratio_sum': 0.06201058,
|
||||||
'profit_closed_coin': 6.217e-05,
|
'profit_closed_coin': 6.217e-05,
|
||||||
'profit_closed_fiat': 0,
|
'profit_closed_fiat': 0,
|
||||||
'profit_closed_percent': 6.2,
|
'profit_closed_percent': 6.2,
|
||||||
|
'profit_closed_ratio_mean': 0.06201058,
|
||||||
|
'profit_closed_percent_mean': 6.2,
|
||||||
|
'profit_closed_ratio_sum': 0.06201058,
|
||||||
|
'profit_closed_percent_sum': 6.2,
|
||||||
'trade_count': 1,
|
'trade_count': 1,
|
||||||
'closed_trade_count': 1,
|
'closed_trade_count': 1,
|
||||||
}
|
}
|
||||||
|
@ -497,6 +505,10 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||||
assert rc.json == []
|
assert rc.json == []
|
||||||
|
|
||||||
ftbot.enter_positions()
|
ftbot.enter_positions()
|
||||||
|
trades = Trade.get_open_trades()
|
||||||
|
trades[0].open_order_id = None
|
||||||
|
ftbot.exit_positions(trades)
|
||||||
|
|
||||||
rc = client_get(client, f"{BASE_URI}/status")
|
rc = client_get(client, f"{BASE_URI}/status")
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert len(rc.json) == 1
|
assert len(rc.json) == 1
|
||||||
|
@ -511,25 +523,30 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||||
'close_rate': None,
|
'close_rate': None,
|
||||||
'current_profit': -0.00408133,
|
'current_profit': -0.00408133,
|
||||||
'current_profit_pct': -0.41,
|
'current_profit_pct': -0.41,
|
||||||
|
'current_profit_abs': -4.09e-06,
|
||||||
'current_rate': 1.099e-05,
|
'current_rate': 1.099e-05,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'open_date_hum': 'just now',
|
'open_date_hum': 'just now',
|
||||||
'open_timestamp': ANY,
|
'open_timestamp': ANY,
|
||||||
'open_order': '(limit buy rem=0.00000000)',
|
'open_order': None,
|
||||||
'open_rate': 1.098e-05,
|
'open_rate': 1.098e-05,
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
'stop_loss': 0.0,
|
'stop_loss': 9.882e-06,
|
||||||
'stop_loss_abs': 0.0,
|
'stop_loss_abs': 9.882e-06,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': -10.0,
|
||||||
'stop_loss_ratio': None,
|
'stop_loss_ratio': -0.1,
|
||||||
'stoploss_order_id': None,
|
'stoploss_order_id': None,
|
||||||
'stoploss_last_update': None,
|
'stoploss_last_update': ANY,
|
||||||
'stoploss_last_update_timestamp': None,
|
'stoploss_last_update_timestamp': ANY,
|
||||||
'initial_stop_loss': 0.0,
|
'initial_stop_loss': 9.882e-06,
|
||||||
'initial_stop_loss_abs': 0.0,
|
'initial_stop_loss_abs': 9.882e-06,
|
||||||
'initial_stop_loss_pct': None,
|
'initial_stop_loss_pct': -10.0,
|
||||||
'initial_stop_loss_ratio': None,
|
'initial_stop_loss_ratio': -0.1,
|
||||||
|
'stoploss_current_dist': -1.1080000000000002e-06,
|
||||||
|
'stoploss_current_dist_ratio': -0.10081893,
|
||||||
|
'stoploss_entry_dist': -0.00010475,
|
||||||
|
'stoploss_entry_dist_ratio': -0.10448878,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'close_rate_requested': None,
|
'close_rate_requested': None,
|
||||||
'current_rate': 1.099e-05,
|
'current_rate': 1.099e-05,
|
||||||
|
@ -541,9 +558,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||||
'fee_open_currency': None,
|
'fee_open_currency': None,
|
||||||
'open_date': ANY,
|
'open_date': ANY,
|
||||||
'is_open': True,
|
'is_open': True,
|
||||||
'max_rate': 0.0,
|
'max_rate': 1.099e-05,
|
||||||
'min_rate': None,
|
'min_rate': 1.098e-05,
|
||||||
'open_order_id': ANY,
|
'open_order_id': None,
|
||||||
'open_rate_requested': 1.098e-05,
|
'open_rate_requested': 1.098e-05,
|
||||||
'open_trade_price': 0.0010025,
|
'open_trade_price': 0.0010025,
|
||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
|
|
|
@ -434,7 +434,8 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'No closed trade' in msg_mock.call_args_list[-1][0][0]
|
assert 'No closed trade' in msg_mock.call_args_list[-1][0][0]
|
||||||
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
|
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
|
||||||
assert '∙ `-0.00000500 BTC (-0.50%)`' in msg_mock.call_args_list[-1][0][0]
|
assert ('∙ `-0.00000500 BTC (-0.50%) (-0.5 \N{GREEK CAPITAL LETTER SIGMA}%)`'
|
||||||
|
in msg_mock.call_args_list[-1][0][0])
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
|
|
||||||
# Update the ticker with a market going up
|
# Update the ticker with a market going up
|
||||||
|
@ -447,10 +448,12 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||||
telegram._profit(update=update, context=MagicMock())
|
telegram._profit(update=update, context=MagicMock())
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0]
|
assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0]
|
||||||
assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0]
|
assert ('∙ `0.00006217 BTC (6.20%) (6.2 \N{GREEK CAPITAL LETTER SIGMA}%)`'
|
||||||
|
in msg_mock.call_args_list[-1][0][0])
|
||||||
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
|
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
|
||||||
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
|
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
|
||||||
assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0]
|
assert ('∙ `0.00006217 BTC (6.20%) (6.2 \N{GREEK CAPITAL LETTER SIGMA}%)`'
|
||||||
|
in msg_mock.call_args_list[-1][0][0])
|
||||||
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
|
assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0]
|
assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0]
|
||||||
|
@ -1222,7 +1225,7 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
|
||||||
'open_date': arrow.utcnow().shift(hours=-1)
|
'open_date': arrow.utcnow().shift(hours=-1)
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== '*Bittrex:* Buying ETH/BTC\n' \
|
== '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \
|
||||||
'*Amount:* `1333.33333333`\n' \
|
'*Amount:* `1333.33333333`\n' \
|
||||||
'*Open Rate:* `0.00001099`\n' \
|
'*Open Rate:* `0.00001099`\n' \
|
||||||
'*Current Rate:* `0.00001099`\n' \
|
'*Current Rate:* `0.00001099`\n' \
|
||||||
|
@ -1244,7 +1247,7 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== ('*Bittrex:* Cancelling Open Buy Order for ETH/BTC')
|
== ('\N{WARNING SIGN} *Bittrex:* Cancelling Open Buy Order for ETH/BTC')
|
||||||
|
|
||||||
|
|
||||||
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||||
|
@ -1277,7 +1280,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||||
'close_date': arrow.utcnow(),
|
'close_date': arrow.utcnow(),
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== ('*Binance:* Selling KEY/ETH\n'
|
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Current Rate:* `0.00003201`\n'
|
'*Current Rate:* `0.00003201`\n'
|
||||||
|
@ -1305,7 +1308,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||||
'close_date': arrow.utcnow(),
|
'close_date': arrow.utcnow(),
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== ('*Binance:* Selling KEY/ETH\n'
|
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Current Rate:* `0.00003201`\n'
|
'*Current Rate:* `0.00003201`\n'
|
||||||
|
@ -1335,7 +1338,8 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
||||||
'reason': 'Cancelled on exchange'
|
'reason': 'Cancelled on exchange'
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== ('*Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: Cancelled on exchange')
|
== ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. '
|
||||||
|
'Reason: Cancelled on exchange')
|
||||||
|
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
|
@ -1345,7 +1349,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
||||||
'reason': 'timeout'
|
'reason': 'timeout'
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== ('*Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout')
|
== ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout')
|
||||||
# Reset singleton function to avoid random breaks
|
# Reset singleton function to avoid random breaks
|
||||||
telegram._fiat_converter.convert_amount = old_convamount
|
telegram._fiat_converter.convert_amount = old_convamount
|
||||||
|
|
||||||
|
@ -1379,7 +1383,7 @@ def test_warning_notification(default_conf, mocker) -> None:
|
||||||
'type': RPCMessageType.WARNING_NOTIFICATION,
|
'type': RPCMessageType.WARNING_NOTIFICATION,
|
||||||
'status': 'message'
|
'status': 'message'
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] == '*Warning:* `message`'
|
assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`'
|
||||||
|
|
||||||
|
|
||||||
def test_custom_notification(default_conf, mocker) -> None:
|
def test_custom_notification(default_conf, mocker) -> None:
|
||||||
|
@ -1438,7 +1442,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
||||||
'open_date': arrow.utcnow().shift(hours=-1)
|
'open_date': arrow.utcnow().shift(hours=-1)
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== '*Bittrex:* Buying ETH/BTC\n' \
|
== '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \
|
||||||
'*Amount:* `1333.33333333`\n' \
|
'*Amount:* `1333.33333333`\n' \
|
||||||
'*Open Rate:* `0.00001099`\n' \
|
'*Open Rate:* `0.00001099`\n' \
|
||||||
'*Current Rate:* `0.00001099`\n' \
|
'*Current Rate:* `0.00001099`\n' \
|
||||||
|
@ -1474,7 +1478,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||||
'close_date': arrow.utcnow(),
|
'close_date': arrow.utcnow(),
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== '*Binance:* Selling KEY/ETH\n' \
|
== '\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n' \
|
||||||
'*Amount:* `1333.33333333`\n' \
|
'*Amount:* `1333.33333333`\n' \
|
||||||
'*Open Rate:* `0.00007500`\n' \
|
'*Open Rate:* `0.00007500`\n' \
|
||||||
'*Current Rate:* `0.00003201`\n' \
|
'*Current Rate:* `0.00003201`\n' \
|
||||||
|
@ -1484,6 +1488,29 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||||
'*Profit:* `-57.41%`'
|
'*Profit:* `-57.41%`'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('msg,expected', [
|
||||||
|
({'profit_percent': 20.1, 'sell_reason': 'roi'}, "\N{ROCKET}"),
|
||||||
|
({'profit_percent': 5.1, 'sell_reason': 'roi'}, "\N{ROCKET}"),
|
||||||
|
({'profit_percent': 2.56, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||||
|
({'profit_percent': 1.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||||
|
({'profit_percent': 0.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
|
||||||
|
({'profit_percent': -5.0, 'sell_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
|
||||||
|
({'profit_percent': -2.0, 'sell_reason': 'sell_signal'}, "\N{CROSS MARK}"),
|
||||||
|
])
|
||||||
|
def test__sell_emoji(default_conf, mocker, msg, expected):
|
||||||
|
del default_conf['fiat_display_currency']
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
|
_init=MagicMock(),
|
||||||
|
_send_msg=msg_mock
|
||||||
|
)
|
||||||
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
telegram = Telegram(freqtradebot)
|
||||||
|
|
||||||
|
assert telegram._get_sell_emoji(msg) == expected
|
||||||
|
|
||||||
|
|
||||||
def test__send_msg(default_conf, mocker) -> None:
|
def test__send_msg(default_conf, mocker) -> None:
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
bot = MagicMock()
|
bot = MagicMock()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user