Merge branch 'develop' into timeframe
This commit is contained in:
@@ -172,8 +172,8 @@ class ApiServer(RPC):
|
||||
self.app.add_url_rule(f'{BASE_URI}/stop', 'stop', view_func=self._stop, methods=['POST'])
|
||||
self.app.add_url_rule(f'{BASE_URI}/stopbuy', 'stopbuy',
|
||||
view_func=self._stopbuy, methods=['POST'])
|
||||
self.app.add_url_rule(f'{BASE_URI}/reload_conf', 'reload_conf',
|
||||
view_func=self._reload_conf, methods=['POST'])
|
||||
self.app.add_url_rule(f'{BASE_URI}/reload_config', 'reload_config',
|
||||
view_func=self._reload_config, methods=['POST'])
|
||||
# Info commands
|
||||
self.app.add_url_rule(f'{BASE_URI}/balance', 'balance',
|
||||
view_func=self._balance, methods=['GET'])
|
||||
@@ -304,12 +304,12 @@ class ApiServer(RPC):
|
||||
|
||||
@require_login
|
||||
@rpc_catch_errors
|
||||
def _reload_conf(self):
|
||||
def _reload_config(self):
|
||||
"""
|
||||
Handler for /reload_conf.
|
||||
Handler for /reload_config.
|
||||
Triggers a config file reload
|
||||
"""
|
||||
msg = self._rpc_reload_conf()
|
||||
msg = self._rpc_reload_config()
|
||||
return self.rest_dump(msg)
|
||||
|
||||
@require_login
|
||||
|
@@ -106,6 +106,8 @@ class RPC:
|
||||
'exchange': config['exchange']['name'],
|
||||
'strategy': config['strategy'],
|
||||
'forcebuy_enabled': config.get('forcebuy_enable', False),
|
||||
'ask_strategy': config.get('ask_strategy', {}),
|
||||
'bid_strategy': config.get('bid_strategy', {}),
|
||||
'state': str(self._freqtrade.state)
|
||||
}
|
||||
return val
|
||||
@@ -131,6 +133,14 @@ class RPC:
|
||||
except DependencyException:
|
||||
current_rate = NAN
|
||||
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}%'
|
||||
if trade.close_profit is not None else None)
|
||||
trade_dict = trade.to_json()
|
||||
@@ -141,6 +151,11 @@ class RPC:
|
||||
current_rate=current_rate,
|
||||
current_profit=current_profit,
|
||||
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(
|
||||
order['type'], order['side'], order['remaining']
|
||||
) if order else None,
|
||||
@@ -284,8 +299,9 @@ class RPC:
|
||||
|
||||
# Prepare data to display
|
||||
profit_closed_coin_sum = round(sum(profit_closed_coin), 8)
|
||||
profit_closed_percent = (round(mean(profit_closed_ratio) * 100, 2) if profit_closed_ratio
|
||||
else 0.0)
|
||||
profit_closed_ratio_mean = mean(profit_closed_ratio) if profit_closed_ratio 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_coin_sum,
|
||||
stake_currency,
|
||||
@@ -293,7 +309,8 @@ class RPC:
|
||||
) if self._fiat_converter else 0
|
||||
|
||||
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_coin_sum,
|
||||
stake_currency,
|
||||
@@ -305,10 +322,18 @@ class RPC:
|
||||
num = float(len(durations) or 1)
|
||||
return {
|
||||
'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_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,
|
||||
'trade_count': len(trades),
|
||||
'closed_trade_count': len([t for t in trades if not t.is_open]),
|
||||
@@ -394,9 +419,9 @@ class RPC:
|
||||
|
||||
return {'status': 'already stopped'}
|
||||
|
||||
def _rpc_reload_conf(self) -> Dict[str, str]:
|
||||
""" Handler for reload_conf. """
|
||||
self._freqtrade.state = State.RELOAD_CONF
|
||||
def _rpc_reload_config(self) -> Dict[str, str]:
|
||||
""" Handler for reload_config. """
|
||||
self._freqtrade.state = State.RELOAD_CONFIG
|
||||
return {'status': 'reloading config ...'}
|
||||
|
||||
def _rpc_stopbuy(self) -> Dict[str, str]:
|
||||
@@ -407,7 +432,7 @@ class RPC:
|
||||
# Set 'max_open_trades' to 0
|
||||
self._freqtrade.config['max_open_trades'] = 0
|
||||
|
||||
return {'status': 'No more buy will occur from now. Run /reload_conf to reset.'}
|
||||
return {'status': 'No more buy will occur from now. Run /reload_config to reset.'}
|
||||
|
||||
def _rpc_forcesell(self, trade_id: str) -> Dict[str, str]:
|
||||
"""
|
||||
|
@@ -3,6 +3,7 @@
|
||||
"""
|
||||
This module manage Telegram communication
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Callable, Dict
|
||||
|
||||
@@ -19,7 +20,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
logger.debug('Included module rpc.telegram ...')
|
||||
|
||||
|
||||
MAX_TELEGRAM_MESSAGE_LENGTH = 4096
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
|
||||
:param command_handler: Telegram CommandHandler
|
||||
:return: decorated function
|
||||
"""
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
""" Decorator logic """
|
||||
update = kwargs.get('update') or args[0]
|
||||
@@ -94,8 +95,8 @@ class Telegram(RPC):
|
||||
CommandHandler('performance', self._performance),
|
||||
CommandHandler('daily', self._daily),
|
||||
CommandHandler('count', self._count),
|
||||
CommandHandler('reload_conf', self._reload_conf),
|
||||
CommandHandler('show_config', self._show_config),
|
||||
CommandHandler(['reload_config', 'reload_conf'], self._reload_config),
|
||||
CommandHandler(['show_config', 'show_conf'], self._show_config),
|
||||
CommandHandler('stopbuy', self._stopbuy),
|
||||
CommandHandler('whitelist', self._whitelist),
|
||||
CommandHandler('blacklist', self._blacklist),
|
||||
@@ -133,7 +134,7 @@ class Telegram(RPC):
|
||||
else:
|
||||
msg['stake_amount_fiat'] = 0
|
||||
|
||||
message = ("*{exchange}:* Buying {pair}\n"
|
||||
message = ("\N{LARGE BLUE CIRCLE} *{exchange}:* Buying {pair}\n"
|
||||
"*Amount:* `{amount:.8f}`\n"
|
||||
"*Open Rate:* `{limit:.8f}`\n"
|
||||
"*Current Rate:* `{current_rate:.8f}`\n"
|
||||
@@ -144,7 +145,8 @@ class Telegram(RPC):
|
||||
message += ")`"
|
||||
|
||||
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:
|
||||
msg['amount'] = round(msg['amount'], 8)
|
||||
@@ -153,7 +155,9 @@ class Telegram(RPC):
|
||||
microsecond=0) - msg['open_date'].replace(microsecond=0)
|
||||
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"
|
||||
"*Open Rate:* `{open_rate:.8f}`\n"
|
||||
"*Current Rate:* `{current_rate:.8f}`\n"
|
||||
@@ -165,21 +169,21 @@ class Telegram(RPC):
|
||||
# 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', 'fiat_currency', 'stake_currency'])
|
||||
and self._fiat_converter):
|
||||
and self._fiat_converter):
|
||||
msg['profit_fiat'] = self._fiat_converter.convert_amount(
|
||||
msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
|
||||
message += (' `({gain}: {profit_amount:.8f} {stake_currency}'
|
||||
' / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
|
||||
|
||||
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)
|
||||
|
||||
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
|
||||
message = '*Status:* `{status}`'.format(**msg)
|
||||
|
||||
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:
|
||||
message = '{status}'.format(**msg)
|
||||
@@ -189,6 +193,20 @@ class Telegram(RPC):
|
||||
|
||||
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
|
||||
def _status(self, update: Update, context: CallbackContext) -> None:
|
||||
"""
|
||||
@@ -222,8 +240,8 @@ class Telegram(RPC):
|
||||
# Adding initial stoploss only if it is different from stoploss
|
||||
"*Initial Stoploss:* `{initial_stop_loss:.8f}` " +
|
||||
("`({initial_stop_loss_pct:.2f}%)`") if (
|
||||
r['stop_loss'] != r['initial_stop_loss']
|
||||
and r['initial_stop_loss_pct'] is not None) else "",
|
||||
r['stop_loss'] != r['initial_stop_loss']
|
||||
and r['initial_stop_loss_pct'] is not None) else "",
|
||||
|
||||
# Adding stoploss and stoploss percentage only if it is not None
|
||||
"*Stoploss:* `{stop_loss:.8f}` " +
|
||||
@@ -315,10 +333,12 @@ class Telegram(RPC):
|
||||
stake_cur,
|
||||
fiat_disp_cur)
|
||||
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_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']
|
||||
trade_count = stats['trade_count']
|
||||
first_trade_date = stats['first_trade_date']
|
||||
@@ -333,13 +353,16 @@ class Telegram(RPC):
|
||||
if stats['closed_trade_count'] > 0:
|
||||
markdown_msg = ("*ROI:* Closed trades\n"
|
||||
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")
|
||||
else:
|
||||
markdown_msg = "`No closed trade` \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"*Total Trade Count:* `{trade_count}`\n"
|
||||
f"*First Trade opened:* `{first_trade_date}`\n"
|
||||
@@ -363,14 +386,14 @@ class Telegram(RPC):
|
||||
"This mode is still experimental!\n"
|
||||
"Starting capital: "
|
||||
f"`{self._config['dry_run_wallet']}` {self._config['stake_currency']}.\n"
|
||||
)
|
||||
)
|
||||
for currency in result['currencies']:
|
||||
if currency['est_stake'] > 0.0001:
|
||||
curr_output = "*{currency}:*\n" \
|
||||
"\t`Available: {free: .8f}`\n" \
|
||||
"\t`Balance: {balance: .8f}`\n" \
|
||||
"\t`Pending: {used: .8f}`\n" \
|
||||
"\t`Est. {stake}: {est_stake: .8f}`\n".format(**currency)
|
||||
curr_output = ("*{currency}:*\n"
|
||||
"\t`Available: {free: .8f}`\n"
|
||||
"\t`Balance: {balance: .8f}`\n"
|
||||
"\t`Pending: {used: .8f}`\n"
|
||||
"\t`Est. {stake}: {est_stake: .8f}`\n").format(**currency)
|
||||
else:
|
||||
curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)
|
||||
|
||||
@@ -381,9 +404,9 @@ class Telegram(RPC):
|
||||
else:
|
||||
output += curr_output
|
||||
|
||||
output += "\n*Estimated Value*:\n" \
|
||||
"\t`{stake}: {total: .8f}`\n" \
|
||||
"\t`{symbol}: {value: .2f}`\n".format(**result)
|
||||
output += ("\n*Estimated Value*:\n"
|
||||
"\t`{stake}: {total: .8f}`\n"
|
||||
"\t`{symbol}: {value: .2f}`\n").format(**result)
|
||||
self._send_msg(output)
|
||||
except RPCException as e:
|
||||
self._send_msg(str(e))
|
||||
@@ -413,15 +436,15 @@ class Telegram(RPC):
|
||||
self._send_msg('Status: `{status}`'.format(**msg))
|
||||
|
||||
@authorized_only
|
||||
def _reload_conf(self, update: Update, context: CallbackContext) -> None:
|
||||
def _reload_config(self, update: Update, context: CallbackContext) -> None:
|
||||
"""
|
||||
Handler for /reload_conf.
|
||||
Handler for /reload_config.
|
||||
Triggers a config file reload
|
||||
:param bot: telegram bot
|
||||
:param update: message update
|
||||
:return: None
|
||||
"""
|
||||
msg = self._rpc_reload_conf()
|
||||
msg = self._rpc_reload_config()
|
||||
self._send_msg('Status: `{status}`'.format(**msg))
|
||||
|
||||
@authorized_only
|
||||
@@ -576,32 +599,32 @@ class Telegram(RPC):
|
||||
:param update: message update
|
||||
:return: None
|
||||
"""
|
||||
forcebuy_text = "*/forcebuy <pair> [<rate>]:* `Instantly buys the given pair. " \
|
||||
"Optionally takes a rate at which to buy.` \n"
|
||||
message = "*/start:* `Starts the trader`\n" \
|
||||
"*/stop:* `Stops the trader`\n" \
|
||||
"*/status [table]:* `Lists all open trades`\n" \
|
||||
" *table :* `will display trades in a table`\n" \
|
||||
" `pending buy orders are marked with an asterisk (*)`\n" \
|
||||
" `pending sell orders are marked with a double asterisk (**)`\n" \
|
||||
"*/profit:* `Lists cumulative profit from all finished trades`\n" \
|
||||
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " \
|
||||
"regardless of profit`\n" \
|
||||
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else '' }" \
|
||||
"*/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" \
|
||||
"*/count:* `Show number of trades running compared to allowed number of trades`" \
|
||||
"\n" \
|
||||
"*/balance:* `Show account balance per currency`\n" \
|
||||
"*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \
|
||||
"*/reload_conf:* `Reload configuration file` \n" \
|
||||
"*/show_config:* `Show running configuration` \n" \
|
||||
"*/whitelist:* `Show current whitelist` \n" \
|
||||
"*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " \
|
||||
"to the blacklist.` \n" \
|
||||
"*/edge:* `Shows validated pairs by Edge if it is enabled` \n" \
|
||||
"*/help:* `This help message`\n" \
|
||||
"*/version:* `Show version`"
|
||||
forcebuy_text = ("*/forcebuy <pair> [<rate>]:* `Instantly buys the given pair. "
|
||||
"Optionally takes a rate at which to buy.` \n")
|
||||
message = ("*/start:* `Starts the trader`\n"
|
||||
"*/stop:* `Stops the trader`\n"
|
||||
"*/status [table]:* `Lists all open trades`\n"
|
||||
" *table :* `will display trades in a table`\n"
|
||||
" `pending buy orders are marked with an asterisk (*)`\n"
|
||||
" `pending sell orders are marked with a double asterisk (**)`\n"
|
||||
"*/profit:* `Lists cumulative profit from all finished trades`\n"
|
||||
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, "
|
||||
"regardless of profit`\n"
|
||||
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}"
|
||||
"*/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"
|
||||
"*/count:* `Show number of trades running compared to allowed number of trades`"
|
||||
"\n"
|
||||
"*/balance:* `Show account balance per currency`\n"
|
||||
"*/stopbuy:* `Stops buying, but handles open trades gracefully` \n"
|
||||
"*/reload_config:* `Reload configuration file` \n"
|
||||
"*/show_config:* `Show running configuration` \n"
|
||||
"*/whitelist:* `Show current whitelist` \n"
|
||||
"*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs "
|
||||
"to the blacklist.` \n"
|
||||
"*/edge:* `Shows validated pairs by Edge if it is enabled` \n"
|
||||
"*/help:* `This help message`\n"
|
||||
"*/version:* `Show version`")
|
||||
|
||||
self._send_msg(message)
|
||||
|
||||
@@ -643,6 +666,8 @@ class Telegram(RPC):
|
||||
f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n"
|
||||
f"*Max open Trades:* `{val['max_open_trades']}`\n"
|
||||
f"*Minimum ROI:* `{val['minimal_roi']}`\n"
|
||||
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
|
||||
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
|
||||
f"{sl_info}"
|
||||
f"*Timeframe:* `{val['timeframe']}`\n"
|
||||
f"*Strategy:* `{val['strategy']}`\n"
|
||||
|
Reference in New Issue
Block a user