wide notifications fixes

This commit is contained in:
Yazeed Al Oyoun
2020-02-08 21:02:52 +01:00
parent fff8ced3b0
commit f3b1161640
12 changed files with 288 additions and 85 deletions

View File

@@ -78,7 +78,7 @@ CONF_SCHEMA = {
'amend_last_stake_amount': {'type': 'boolean', 'default': False},
'last_stake_amount_min_ratio': {
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5
},
},
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
'dry_run': {'type': 'boolean'},
'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET},
@@ -191,7 +191,9 @@ CONF_SCHEMA = {
'properties': {
'enabled': {'type': 'boolean'},
'webhookbuy': {'type': 'object'},
'webhookbuycancel': {'type': 'object'},
'webhooksell': {'type': 'object'},
'webhooksellcancel': {'type': 'object'},
'webhookstatus': {'type': 'object'},
},
},

View File

@@ -234,7 +234,7 @@ class FreqtradeBot:
return trades_created
def get_buy_rate(self, pair: str, tick: Dict = None) -> float:
def get_buy_rate(self, pair: str, refresh: bool = False, tick: Dict = None) -> float:
"""
Calculates bid target between current ask price and last price
:return: float: Price
@@ -253,7 +253,7 @@ class FreqtradeBot:
else:
if not tick:
logger.info('Using Last Ask / Last Price')
ticker = self.exchange.fetch_ticker(pair)
ticker = self.exchange.fetch_ticker(pair, refresh)
else:
ticker = tick
if ticker['ask'] < ticker['last']:
@@ -404,7 +404,7 @@ class FreqtradeBot:
stake_amount = self.get_trade_stake_amount(pair)
if not stake_amount:
logger.debug("Stake amount is 0, ignoring possible trade for {pair}.")
logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
return False
logger.info(f"Buy signal found: about create a new trade with stake_amount: "
@@ -414,10 +414,12 @@ class FreqtradeBot:
if ((bid_check_dom.get('enabled', False)) and
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
if self._check_depth_of_market_buy(pair, bid_check_dom):
logger.info(f'Executed Buy for {pair}.')
return self.execute_buy(pair, stake_amount)
else:
return False
logger.info(f'Executed Buy for {pair}')
return self.execute_buy(pair, stake_amount)
else:
return False
@@ -450,7 +452,7 @@ class FreqtradeBot:
"""
Executes a limit buy for the given pair
:param pair: pair for which we want to create a LIMIT_BUY
:return: None
:return: bool
"""
time_in_force = self.strategy.order_time_in_force['buy']
@@ -458,7 +460,7 @@ class FreqtradeBot:
buy_limit_requested = price
else:
# Calculate price
buy_limit_requested = self.get_buy_rate(pair)
buy_limit_requested = self.get_buy_rate(pair, True)
min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested)
if min_stake_amount is not None and min_stake_amount > stake_amount:
@@ -547,11 +549,37 @@ class FreqtradeBot:
'type': RPCMessageType.BUY_NOTIFICATION,
'exchange': self.exchange.name.capitalize(),
'pair': trade.pair,
'limit': trade.open_rate,
'limit': trade.open_rate_requested,
'order_type': order_type,
'stake_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
'amount': trade.amount,
'open_date': trade.open_date or datetime.utcnow(),
'current_rate': trade.open_rate_requested,
}
# Send the message
self.rpc.send_msg(msg)
def _notify_buy_cancel(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a buy cancel occured.
"""
current_rate = self.get_buy_rate(trade.pair, False)
msg = {
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
'exchange': self.exchange.name.capitalize(),
'pair': trade.pair,
'limit': trade.open_rate_requested,
'order_type': order_type,
'stake_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
'amount': trade.amount,
'open_date': trade.open_date,
'current_rate': current_rate,
}
# Send the message
@@ -587,7 +615,7 @@ class FreqtradeBot:
return trades_closed
def get_sell_rate(self, pair: str, refresh: bool) -> float:
def get_sell_rate(self, pair: str, refresh: bool = False) -> float:
"""
Get sell rate - either using get-ticker bid or first bid based on orderbook
The orderbook portion is only used for rpc messaging, which would otherwise fail
@@ -751,7 +779,7 @@ class FreqtradeBot:
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat:
# cancelling the current stoploss on exchange first
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})'
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s}) '
'in order to add another one ...', order['id'])
try:
self.exchange.cancel_order(order['id'], trade.pair)
@@ -777,7 +805,7 @@ class FreqtradeBot:
if should_sell.sell_flag:
self.execute_sell(trade, sell_rate, should_sell.sell_type)
logger.info('executed sell, reason: %s', should_sell.sell_type)
logger.info(f'Executed Sell for {trade.pair}. Reason: {should_sell.sell_type}')
return True
return False
@@ -820,41 +848,41 @@ class FreqtradeBot:
if ((order['side'] == 'buy' and order['status'] == 'canceled')
or (self._check_timed_out('buy', order))):
self.handle_timedout_limit_buy(trade, order)
self.wallets.update()
order_type = self.strategy.order_types['buy']
self._notify_buy_cancel(trade, order_type)
elif ((order['side'] == 'sell' and order['status'] == 'canceled')
or (self._check_timed_out('sell', order))):
self.handle_timedout_limit_sell(trade, order)
self.wallets.update()
order_type = self.strategy.order_types['sell']
self._notify_sell_cancel(trade, order_type)
def handle_buy_order_full_cancel(self, trade: Trade, reason: str) -> None:
"""Close trade in database and send message"""
def delete_trade(self, trade: Trade) -> None:
"""Delete trade in database"""
Trade.session.delete(trade)
Trade.session.flush()
logger.info('Buy order %s for %s.', reason, trade)
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled buy order for {trade.pair} {reason}'
})
def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
"""
Buy timeout - cancel order
:return: True if order was fully cancelled
"""
reason = "cancelled due to timeout"
if order['status'] != 'canceled':
reason = "cancelled due to timeout"
corder = self.exchange.cancel_order(trade.open_order_id, trade.pair)
logger.info('Buy order %s for %s.', reason, trade)
else:
# Order was cancelled already, so we can reuse the existing dict
corder = order
reason = "canceled on Exchange"
reason = "cancelled on exchange"
logger.info('Buy order %s for %s.', reason, trade)
if corder.get('remaining', order['remaining']) == order['amount']:
# if trade is not partially completed, just delete the trade
self.handle_buy_order_full_cancel(trade, reason)
self.delete_trade(trade)
return True
# if trade is partially complete, edit the stake details for the trade
@@ -878,10 +906,6 @@ class FreqtradeBot:
trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade)
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Remaining buy order for {trade.pair} cancelled due to timeout'
})
return False
def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> bool:
@@ -889,24 +913,22 @@ class FreqtradeBot:
Sell timeout - cancel order and update trade
:return: True if order was fully cancelled
"""
# if trade is not partially completed, just cancel the trade
if order['remaining'] == order['amount']:
# if trade is not partially completed, just cancel the trade
if order["status"] != "canceled":
reason = "due to timeout"
reason = "cancelled due to timeout"
# if trade is not partially completed, just delete the trade
self.exchange.cancel_order(trade.open_order_id, trade.pair)
logger.info('Sell order timeout for %s.', trade)
logger.info('Sell order %s for %s.', reason, trade)
else:
reason = "on exchange"
logger.info('Sell order canceled on exchange for %s.', trade)
reason = "cancelled on exchange"
logger.info('Sell order %s for %s.', reason, trade)
trade.close_rate = None
trade.close_profit = None
trade.close_date = None
trade.is_open = True
trade.open_order_id = None
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled sell order for {trade.pair} cancelled {reason}'
})
return True
@@ -938,13 +960,13 @@ class FreqtradeBot:
raise DependencyException(
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> None:
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> bool:
"""
Executes a limit sell for the given trade and limit
:param trade: Trade instance
:param limit: limit rate for the sell order
:param sellreason: Reason the sell was triggered
:return: None
:return: bool
"""
sell_type = 'sell'
if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
@@ -965,7 +987,7 @@ class FreqtradeBot:
order_type = self.strategy.order_types[sell_type]
if sell_reason == SellType.EMERGENCY_SELL:
# Emergencysells (default to market!)
# Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergencysell", "market")
amount = self._safe_sell_amount(trade.pair, trade.amount)
@@ -990,6 +1012,8 @@ class FreqtradeBot:
self._notify_sell(trade, order_type)
return True
def _notify_sell(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a sell occured.
@@ -1006,7 +1030,7 @@ class FreqtradeBot:
'exchange': trade.exchange.capitalize(),
'pair': trade.pair,
'gain': gain,
'limit': trade.close_rate_requested,
'limit': profit_rate,
'order_type': order_type,
'amount': trade.amount,
'open_rate': trade.open_rate,
@@ -1017,6 +1041,45 @@ class FreqtradeBot:
'open_date': trade.open_date,
'close_date': trade.close_date or datetime.utcnow(),
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
}
if 'fiat_display_currency' in self.config:
msg.update({
'fiat_currency': self.config['fiat_display_currency'],
})
# Send the message
self.rpc.send_msg(msg)
def _notify_sell_cancel(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a sell cancel occured.
"""
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
profit_trade = trade.calc_profit(rate=profit_rate)
# Use cached ticker here - it was updated seconds ago.
current_rate = self.get_sell_rate(trade.pair, False)
profit_percent = trade.calc_profit_ratio(profit_rate)
gain = "profit" if profit_percent > 0 else "loss"
msg = {
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
'exchange': trade.exchange.capitalize(),
'pair': trade.pair,
'gain': gain,
'limit': profit_rate,
'order_type': order_type,
'amount': trade.amount,
'open_rate': trade.open_rate,
'current_rate': current_rate,
'profit_amount': profit_trade,
'profit_percent': profit_percent,
'sell_reason': trade.sell_reason,
'open_date': trade.open_date,
'close_date': trade.close_date,
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
}
if 'fiat_display_currency' in self.config:

View File

@@ -26,7 +26,9 @@ class RPCMessageType(Enum):
WARNING_NOTIFICATION = 'warning'
CUSTOM_NOTIFICATION = 'custom'
BUY_NOTIFICATION = 'buy'
BUY_CANCEL_NOTIFICATION = 'buy_cancel'
SELL_NOTIFICATION = 'sell'
SELL_CANCEL_NOTIFICATION = 'sell_cancel'
def __repr__(self):
return self.value
@@ -39,6 +41,7 @@ class RPCException(Exception):
raise RPCException('*Status:* `no active trade`')
"""
def __init__(self, message: str) -> None:
super().__init__(self)
self.message = message
@@ -157,15 +160,16 @@ class RPC:
profit_str = f'{trade_perc:.2f}%'
if self._fiat_converter:
fiat_profit = self._fiat_converter.convert_amount(
trade_profit,
stake_currency,
fiat_display_currency
)
trade_profit,
stake_currency,
fiat_display_currency
)
if fiat_profit and not isnan(fiat_profit):
profit_str += f" ({fiat_profit:.2f})"
trades_list.append([
trade.id,
trade.pair,
trade.pair + ['', '*'][trade.open_order_id is not None
and trade.close_rate_requested is None],
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
profit_str
])

View File

@@ -134,13 +134,18 @@ class Telegram(RPC):
msg['stake_amount_fiat'] = 0
message = ("*{exchange}:* Buying {pair}\n"
"at rate `{limit:.8f}\n"
"({stake_amount:.6f} {stake_currency}").format(**msg)
"*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{limit:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Total:* `({stake_amount:.6f} {stake_currency}").format(**msg)
if msg.get('fiat_currency', None):
message += ",{stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
message += ", {stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
message += ")`"
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
message = "*{exchange}:* Cancelling Buy {pair}".format(**msg)
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
msg['amount'] = round(msg['amount'], 8)
msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)
@@ -149,10 +154,10 @@ class Telegram(RPC):
msg['duration_min'] = msg['duration'].total_seconds() / 60
message = ("*{exchange}:* Selling {pair}\n"
"*Rate:* `{limit:.8f}`\n"
"*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Close Rate:* `{limit:.8f}`\n"
"*Sell Reason:* `{sell_reason}`\n"
"*Duration:* `{duration} ({duration_min:.1f} min)`\n"
"*Profit:* `{profit_percent:.2f}%`").format(**msg)
@@ -163,8 +168,11 @@ class Telegram(RPC):
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)
message += (' `({gain}: {profit_amount:.8f} {stake_currency}'
' / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
message = "*{exchange}:* Cancelling Sell {pair}".format(**msg)
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
message = '*Status:* `{status}`'.format(**msg)

View File

@@ -41,8 +41,12 @@ class Webhook(RPC):
if msg['type'] == RPCMessageType.BUY_NOTIFICATION:
valuedict = self._config['webhook'].get('webhookbuy', None)
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
valuedict = self._config['webhook'].get('webhookbuycancel', None)
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
valuedict = self._config['webhook'].get('webhooksell', None)
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
valuedict = self._config['webhook'].get('webhooksellcancel', None)
elif msg['type'] in(RPCMessageType.STATUS_NOTIFICATION,
RPCMessageType.CUSTOM_NOTIFICATION,
RPCMessageType.WARNING_NOTIFICATION):