Merge branch 'develop' into pr/thopd88/3611
This commit is contained in:
@@ -156,7 +156,9 @@ CONF_SCHEMA = {
|
||||
'emergencysell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'stoploss_on_exchange': {'type': 'boolean'},
|
||||
'stoploss_on_exchange_interval': {'type': 'number'}
|
||||
'stoploss_on_exchange_interval': {'type': 'number'},
|
||||
'stoploss_on_exchange_limit_ratio': {'type': 'number', 'minimum': 0.0,
|
||||
'maximum': 1.0}
|
||||
},
|
||||
'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
|
||||
},
|
||||
|
@@ -81,7 +81,7 @@ class Binance(Exchange):
|
||||
return order
|
||||
except ccxt.InsufficientFunds as e:
|
||||
raise ExchangeError(
|
||||
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
|
||||
f'Insufficient funds to create {ordertype} sell order on market {pair}. '
|
||||
f'Tried to sell amount {amount} at rate {rate}. '
|
||||
f'Message: {e}') from e
|
||||
except ccxt.InvalidOrder as e:
|
||||
|
@@ -258,8 +258,8 @@ class Exchange:
|
||||
api.urls['api'] = api.urls['test']
|
||||
logger.info("Enabled Sandbox API on %s", name)
|
||||
else:
|
||||
logger.warning(name, "No Sandbox URL in CCXT, exiting. "
|
||||
"Please check your config.json")
|
||||
logger.warning(
|
||||
f"No Sandbox URL in CCXT for {name}, exiting. Please check your config.json")
|
||||
raise OperationalException(f'Exchange {name} does not provide a sandbox api')
|
||||
|
||||
def _load_async_markets(self, reload: bool = False) -> None:
|
||||
@@ -525,13 +525,13 @@ class Exchange:
|
||||
|
||||
except ccxt.InsufficientFunds as e:
|
||||
raise ExchangeError(
|
||||
f'Insufficient funds to create {ordertype} {side} order on market {pair}.'
|
||||
f'Insufficient funds to create {ordertype} {side} order on market {pair}. '
|
||||
f'Tried to {side} amount {amount} at rate {rate}.'
|
||||
f'Message: {e}') from e
|
||||
except ccxt.InvalidOrder as e:
|
||||
raise ExchangeError(
|
||||
f'Could not create {ordertype} {side} order on market {pair}.'
|
||||
f'Tried to {side} amount {amount} at rate {rate}.'
|
||||
f'Could not create {ordertype} {side} order on market {pair}. '
|
||||
f'Tried to {side} amount {amount} at rate {rate}. '
|
||||
f'Message: {e}') from e
|
||||
except ccxt.DDoSProtection as e:
|
||||
raise DDosProtection(e) from e
|
||||
|
@@ -89,7 +89,7 @@ class Kraken(Exchange):
|
||||
return order
|
||||
except ccxt.InsufficientFunds as e:
|
||||
raise ExchangeError(
|
||||
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
|
||||
f'Insufficient funds to create {ordertype} sell order on market {pair}. '
|
||||
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
||||
f'Message: {e}') from e
|
||||
except ccxt.InvalidOrder as e:
|
||||
|
@@ -598,6 +598,7 @@ class FreqtradeBot:
|
||||
Sends rpc notification when a buy occured.
|
||||
"""
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': self.exchange.name.capitalize(),
|
||||
'pair': trade.pair,
|
||||
@@ -621,6 +622,7 @@ class FreqtradeBot:
|
||||
current_rate = self.get_buy_rate(trade.pair, False)
|
||||
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||
'exchange': self.exchange.name.capitalize(),
|
||||
'pair': trade.pair,
|
||||
@@ -825,10 +827,8 @@ class FreqtradeBot:
|
||||
return False
|
||||
|
||||
# If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange
|
||||
if (not stoploss_order):
|
||||
|
||||
if not stoploss_order:
|
||||
stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss
|
||||
|
||||
stop_price = trade.open_rate * (1 + stoploss)
|
||||
|
||||
if self.create_stoploss_order(trade=trade, stop_price=stop_price, rate=stop_price):
|
||||
@@ -1151,6 +1151,7 @@ class FreqtradeBot:
|
||||
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'trade_id': trade.id,
|
||||
'exchange': trade.exchange.capitalize(),
|
||||
'pair': trade.pair,
|
||||
'gain': gain,
|
||||
@@ -1193,6 +1194,7 @@ class FreqtradeBot:
|
||||
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'trade_id': trade.id,
|
||||
'exchange': trade.exchange.capitalize(),
|
||||
'pair': trade.pair,
|
||||
'gain': gain,
|
||||
|
@@ -56,7 +56,7 @@ class PriceFilter(IPairList):
|
||||
:param ticker: ticker dict as returned from ccxt.load_markets()
|
||||
:return: True if the pair can stay, false if it should be removed
|
||||
"""
|
||||
if ticker['last'] is None:
|
||||
if ticker['last'] is None or ticker['last'] == 0:
|
||||
self.log_on_refresh(logger.info,
|
||||
f"Removed {ticker['symbol']} from whitelist, because "
|
||||
"ticker['last'] is empty (Usually no trade in the last 24h).")
|
||||
|
@@ -10,11 +10,13 @@ from freqtrade.data.btanalysis import (calculate_max_drawdown,
|
||||
create_cum_profit,
|
||||
extract_trades_of_period, load_trades)
|
||||
from freqtrade.data.converter import trim_dataframe
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import timeframe_to_prev_date
|
||||
from freqtrade.misc import pair_to_filename
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||
from freqtrade.strategy import IStrategy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -467,6 +469,8 @@ def load_and_plot_trades(config: Dict[str, Any]):
|
||||
"""
|
||||
strategy = StrategyResolver.load_strategy(config)
|
||||
|
||||
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
|
||||
IStrategy.dp = DataProvider(config, exchange)
|
||||
plot_elements = init_plotscript(config)
|
||||
trades = plot_elements['trades']
|
||||
pair_counter = 0
|
||||
|
@@ -42,14 +42,14 @@ class HyperOptResolver(IResolver):
|
||||
extra_dir=config.get('hyperopt_path'))
|
||||
|
||||
if not hasattr(hyperopt, 'populate_indicators'):
|
||||
logger.warning("Hyperopt class does not provide populate_indicators() method. "
|
||||
"Using populate_indicators from the strategy.")
|
||||
logger.info("Hyperopt class does not provide populate_indicators() method. "
|
||||
"Using populate_indicators from the strategy.")
|
||||
if not hasattr(hyperopt, 'populate_buy_trend'):
|
||||
logger.warning("Hyperopt class does not provide populate_buy_trend() method. "
|
||||
"Using populate_buy_trend from the strategy.")
|
||||
logger.info("Hyperopt class does not provide populate_buy_trend() method. "
|
||||
"Using populate_buy_trend from the strategy.")
|
||||
if not hasattr(hyperopt, 'populate_sell_trend'):
|
||||
logger.warning("Hyperopt class does not provide populate_sell_trend() method. "
|
||||
"Using populate_sell_trend from the strategy.")
|
||||
logger.info("Hyperopt class does not provide populate_sell_trend() method. "
|
||||
"Using populate_sell_trend from the strategy.")
|
||||
return hyperopt
|
||||
|
||||
|
||||
|
@@ -252,9 +252,10 @@ class RPC:
|
||||
def _rpc_trade_history(self, limit: int) -> Dict:
|
||||
""" Returns the X last trades """
|
||||
if limit > 0:
|
||||
trades = Trade.get_trades().order_by(Trade.id.desc()).limit(limit)
|
||||
trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by(
|
||||
Trade.id.desc()).limit(limit)
|
||||
else:
|
||||
trades = Trade.get_trades().order_by(Trade.id.desc()).all()
|
||||
trades = Trade.get_trades([Trade.is_open.is_(False)]).order_by(Trade.id.desc()).all()
|
||||
|
||||
output = [trade.to_json() for trade in trades]
|
||||
|
||||
@@ -523,7 +524,7 @@ class RPC:
|
||||
# check if valid pair
|
||||
|
||||
# check if pair already has an open pair
|
||||
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair.is_(pair)]).first()
|
||||
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
||||
if trade:
|
||||
raise RPCException(f'position for {pair} already open - id: {trade.id}')
|
||||
|
||||
@@ -532,7 +533,7 @@ class RPC:
|
||||
|
||||
# execute buy
|
||||
if self._freqtrade.execute_buy(pair, stakeamount, price):
|
||||
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair.is_(pair)]).first()
|
||||
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
||||
return trade
|
||||
else:
|
||||
return None
|
||||
|
@@ -5,6 +5,7 @@ This module manage Telegram communication
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import arrow
|
||||
from typing import Any, Callable, Dict
|
||||
|
||||
from tabulate import tabulate
|
||||
@@ -92,6 +93,7 @@ class Telegram(RPC):
|
||||
CommandHandler('stop', self._stop),
|
||||
CommandHandler('forcesell', self._forcesell),
|
||||
CommandHandler('forcebuy', self._forcebuy),
|
||||
CommandHandler('trades', self._trades),
|
||||
CommandHandler('delete', self._delete),
|
||||
CommandHandler('performance', self._performance),
|
||||
CommandHandler('daily', self._daily),
|
||||
@@ -497,6 +499,41 @@ class Telegram(RPC):
|
||||
except RPCException as e:
|
||||
self._send_msg(str(e))
|
||||
|
||||
@authorized_only
|
||||
def _trades(self, update: Update, context: CallbackContext) -> None:
|
||||
"""
|
||||
Handler for /trades <n>
|
||||
Returns last n recent trades.
|
||||
:param bot: telegram bot
|
||||
:param update: message update
|
||||
:return: None
|
||||
"""
|
||||
stake_cur = self._config['stake_currency']
|
||||
try:
|
||||
nrecent = int(context.args[0])
|
||||
except (TypeError, ValueError, IndexError):
|
||||
nrecent = 10
|
||||
try:
|
||||
trades = self._rpc_trade_history(
|
||||
nrecent
|
||||
)
|
||||
trades_tab = tabulate(
|
||||
[[arrow.get(trade['open_date']).humanize(),
|
||||
trade['pair'],
|
||||
f"{(100 * trade['close_profit']):.2f}% ({trade['close_profit_abs']})"]
|
||||
for trade in trades['trades']],
|
||||
headers=[
|
||||
'Open Date',
|
||||
'Pair',
|
||||
f'Profit ({stake_cur})',
|
||||
],
|
||||
tablefmt='simple')
|
||||
message = (f"<b>{min(trades['trades_count'], nrecent)} recent trades</b>:\n"
|
||||
+ (f"<pre>{trades_tab}</pre>" if trades['trades_count'] > 0 else ''))
|
||||
self._send_msg(message, parse_mode=ParseMode.HTML)
|
||||
except RPCException as e:
|
||||
self._send_msg(str(e))
|
||||
|
||||
@authorized_only
|
||||
def _delete(self, update: Update, context: CallbackContext) -> None:
|
||||
"""
|
||||
@@ -628,6 +665,7 @@ class Telegram(RPC):
|
||||
" *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"
|
||||
"*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\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"
|
||||
|
Reference in New Issue
Block a user