Merge branch 'develop' into feat/short

This commit is contained in:
Matthias
2022-02-11 17:02:04 +01:00
63 changed files with 1158 additions and 349 deletions

View File

@@ -117,7 +117,7 @@ class Telegram(RPCHandler):
r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$',
r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$',
r'/forcebuy$', r'/forcelong$', r'/forceshort$',
r'/edge$', r'/help$', r'/version$']
r'/edge$', r'/health$', r'/help$', r'/version$']
# Create keys for generation
valid_keys_print = [k.replace('$', '') for k in valid_keys]
@@ -180,6 +180,7 @@ class Telegram(RPCHandler):
CommandHandler(['blacklist_delete', 'bl_delete'], self._blacklist_delete),
CommandHandler('logs', self._logs),
CommandHandler('edge', self._edge),
CommandHandler('health', self._health),
CommandHandler('help', self._help),
CommandHandler('version', self._version),
]
@@ -390,6 +391,48 @@ class Telegram(RPCHandler):
else:
return "\N{CROSS MARK}"
def _prepare_entry_details(self, filled_orders, base_currency, is_open):
"""
Prepare details of trade with entry adjustment enabled
"""
lines = []
for x, order in enumerate(filled_orders):
cur_entry_datetime = arrow.get(order["order_filled_date"])
cur_entry_amount = order["amount"]
cur_entry_average = order["average"]
lines.append(" ")
if x == 0:
lines.append("*Entry #{}:*".format(x+1))
lines.append("*Entry Amount:* {} ({:.8f} {})"
.format(cur_entry_amount, order["cost"], base_currency))
lines.append("*Average Entry Price:* {}".format(cur_entry_average))
else:
sumA = 0
sumB = 0
for y in range(x):
sumA += (filled_orders[y]["amount"] * filled_orders[y]["average"])
sumB += filled_orders[y]["amount"]
prev_avg_price = sumA/sumB
price_to_1st_entry = ((cur_entry_average - filled_orders[0]["average"])
/ filled_orders[0]["average"])
minus_on_entry = (cur_entry_average - prev_avg_price)/prev_avg_price
dur_entry = cur_entry_datetime - arrow.get(filled_orders[x-1]["order_filled_date"])
days = dur_entry.days
hours, remainder = divmod(dur_entry.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
lines.append("*Entry #{}:* at {:.2%} avg profit".format(x+1, minus_on_entry))
if is_open:
lines.append("({})".format(cur_entry_datetime
.humanize(granularity=["day", "hour", "minute"])))
lines.append("*Entry Amount:* {} ({:.8f} {})"
.format(cur_entry_amount, order["cost"], base_currency))
lines.append("*Average Entry Price:* {} ({:.2%} from 1st entry rate)"
.format(cur_entry_average, price_to_1st_entry))
lines.append("*Order filled at:* {}".format(order["order_filled_date"]))
lines.append("({}d {}h {}m {}s from previous entry)"
.format(days, hours, minutes, seconds))
return lines
@authorized_only
def _status(self, update: Update, context: CallbackContext) -> None:
"""
@@ -413,39 +456,59 @@ class Telegram(RPCHandler):
trade_ids = [int(i) for i in context.args if i.isnumeric()]
results = self._rpc._rpc_trade_status(trade_ids=trade_ids)
position_adjust = self._config.get('position_adjustment_enable', False)
max_entries = self._config.get('max_entry_position_adjustment', -1)
messages = []
for r in results:
r['open_date_hum'] = arrow.get(r['open_date']).humanize()
r['num_entries'] = len(r['filled_entry_orders'])
r['sell_reason'] = r.get('sell_reason', "")
lines = [
"*Trade ID:* `{trade_id}` `(since {open_date_hum})`",
"*Trade ID:* `{trade_id}`" +
("` (since {open_date_hum})`" if r['is_open'] else ""),
"*Current Pair:* {pair}",
"*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"),
"*Leverage:* `{leverage}`" if r.get('leverage') else "",
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
"*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "",
"*Exit Reason:* `{sell_reason}`" if r['sell_reason'] else "",
]
if position_adjust:
max_buy_str = (f"/{max_entries + 1}" if (max_entries > 0) else "")
lines.append("*Number of Entries:* `{num_entries}`" + max_buy_str)
lines.extend([
"*Open Rate:* `{open_rate:.8f}`",
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
"*Current Rate:* `{current_rate:.8f}`",
"*Close Rate:* `{close_rate:.8f}`" if r['close_rate'] else "",
"*Open Date:* `{open_date}`",
"*Close Date:* `{close_date}`" if r['close_date'] else "",
"*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "",
("*Current Profit:* " if r['is_open'] else "*Close Profit: *")
+ "`{profit_ratio:.2%}`",
]
if (r['stop_loss_abs'] != r['initial_stop_loss_abs']
and r['initial_stop_loss_ratio'] is not None):
# Adding initial stoploss only if it is different from stoploss
lines.append("*Initial Stoploss:* `{initial_stop_loss_abs:.8f}` "
"`({initial_stop_loss_ratio:.2%})`")
])
# Adding stoploss and stoploss percentage only if it is not None
lines.append("*Stoploss:* `{stop_loss_abs:.8f}` " +
("`({stop_loss_ratio:.2%})`" if r['stop_loss_ratio'] else ""))
lines.append("*Stoploss distance:* `{stoploss_current_dist:.8f}` "
"`({stoploss_current_dist_ratio:.2%})`")
if r['open_order']:
if r['sell_order_status']:
lines.append("*Open Order:* `{open_order}` - `{sell_order_status}`")
else:
lines.append("*Open Order:* `{open_order}`")
if r['is_open']:
if (r['stop_loss_abs'] != r['initial_stop_loss_abs']
and r['initial_stop_loss_ratio'] is not None):
# Adding initial stoploss only if it is different from stoploss
lines.append("*Initial Stoploss:* `{initial_stop_loss_abs:.8f}` "
"`({initial_stop_loss_ratio:.2%})`")
# Adding stoploss and stoploss percentage only if it is not None
lines.append("*Stoploss:* `{stop_loss_abs:.8f}` " +
("`({stop_loss_ratio:.2%})`" if r['stop_loss_ratio'] else ""))
lines.append("*Stoploss distance:* `{stoploss_current_dist:.8f}` "
"`({stoploss_current_dist_ratio:.2%})`")
if r['open_order']:
if r['sell_order_status']:
lines.append("*Open Order:* `{open_order}` - `{sell_order_status}`")
else:
lines.append("*Open Order:* `{open_order}`")
lines_detail = self._prepare_entry_details(
r['filled_entry_orders'], r['base_currency'], r['is_open'])
lines.extend((lines_detail if (len(r['filled_entry_orders']) > 1) else ""))
# Filter empty lines using list-comprehension
messages.append("\n".join([line for line in lines if line]).format(**r))
@@ -726,9 +789,9 @@ class Telegram(RPCHandler):
duration_msg = tabulate(
[
['Wins', str(timedelta(seconds=durations['wins']))
if durations['wins'] != 'N/A' else 'N/A'],
if durations['wins'] is not None else 'N/A'],
['Losses', str(timedelta(seconds=durations['losses']))
if durations['losses'] != 'N/A' else 'N/A']
if durations['losses'] is not None else 'N/A']
],
headers=['', 'Avg. Duration']
)
@@ -1318,6 +1381,7 @@ class Telegram(RPCHandler):
"*/logs [limit]:* `Show latest logs - defaults to 10` \n"
"*/count:* `Show number of active trades compared to allowed number of trades`\n"
"*/edge:* `Shows validated pairs by Edge if it is enabled` \n"
"*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n"
"_Statistics_\n"
"------------\n"
@@ -1345,6 +1409,19 @@ class Telegram(RPCHandler):
self._send_msg(message, parse_mode=ParseMode.MARKDOWN)
@authorized_only
def _health(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /health
Shows the last process timestamp
"""
try:
health = self._rpc._health()
message = f"Last process: `{health['last_process_loc']}`"
self._send_msg(message)
except RPCException as e:
self._send_msg(str(e))
@authorized_only
def _version(self, update: Update, context: CallbackContext) -> None:
"""