Merge branch 'develop' into feat/short

This commit is contained in:
Sam Germain 2021-08-06 18:12:53 -06:00
commit 9b58c58609
33 changed files with 106 additions and 106 deletions

View File

@ -26,8 +26,8 @@ hesitate to read the source code and understand the mechanism of this bot.
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange. Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist))
- [X] [Bittrex](https://bittrex.com/) - [X] [Bittrex](https://bittrex.com/)
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#blacklists))
- [X] [Kraken](https://kraken.com/) - [X] [Kraken](https://kraken.com/)
- [X] [FTX](https://ftx.com) - [X] [FTX](https://ftx.com)
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ - [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_

View File

@ -36,7 +36,7 @@ Freqtrade is a crypto-currency algorithmic trading software developed in python
Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange. Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange.
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](exchanges.md#blacklists)) - [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist))
- [X] [Bittrex](https://bittrex.com/) - [X] [Bittrex](https://bittrex.com/)
- [X] [FTX](https://ftx.com) - [X] [FTX](https://ftx.com)
- [X] [Kraken](https://kraken.com/) - [X] [Kraken](https://kraken.com/)

View File

@ -193,7 +193,7 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
selections['exchange'] = render_template( selections['exchange'] = render_template(
templatefile=f"subtemplates/exchange_{exchange_template}.j2", templatefile=f"subtemplates/exchange_{exchange_template}.j2",
arguments=selections arguments=selections
) )
except TemplateNotFound: except TemplateNotFound:
selections['exchange'] = render_template( selections['exchange'] = render_template(
templatefile="subtemplates/exchange_generic.j2", templatefile="subtemplates/exchange_generic.j2",

View File

@ -38,15 +38,15 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
indicators = render_template_with_fallback( indicators = render_template_with_fallback(
templatefile=f"subtemplates/indicators_{subtemplate}.j2", templatefile=f"subtemplates/indicators_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/indicators_{fallback}.j2", templatefallbackfile=f"subtemplates/indicators_{fallback}.j2",
) )
buy_trend = render_template_with_fallback( buy_trend = render_template_with_fallback(
templatefile=f"subtemplates/buy_trend_{subtemplate}.j2", templatefile=f"subtemplates/buy_trend_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/buy_trend_{fallback}.j2", templatefallbackfile=f"subtemplates/buy_trend_{fallback}.j2",
) )
sell_trend = render_template_with_fallback( sell_trend = render_template_with_fallback(
templatefile=f"subtemplates/sell_trend_{subtemplate}.j2", templatefile=f"subtemplates/sell_trend_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/sell_trend_{fallback}.j2", templatefallbackfile=f"subtemplates/sell_trend_{fallback}.j2",
) )
plot_config = render_template_with_fallback( plot_config = render_template_with_fallback(
templatefile=f"subtemplates/plot_config_{subtemplate}.j2", templatefile=f"subtemplates/plot_config_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/plot_config_{fallback}.j2", templatefallbackfile=f"subtemplates/plot_config_{fallback}.j2",
@ -97,19 +97,19 @@ def deploy_new_hyperopt(hyperopt_name: str, hyperopt_path: Path, subtemplate: st
buy_guards = render_template_with_fallback( buy_guards = render_template_with_fallback(
templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2", templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/hyperopt_buy_guards_{fallback}.j2", templatefallbackfile=f"subtemplates/hyperopt_buy_guards_{fallback}.j2",
) )
sell_guards = render_template_with_fallback( sell_guards = render_template_with_fallback(
templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2", templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/hyperopt_sell_guards_{fallback}.j2", templatefallbackfile=f"subtemplates/hyperopt_sell_guards_{fallback}.j2",
) )
buy_space = render_template_with_fallback( buy_space = render_template_with_fallback(
templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2", templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/hyperopt_buy_space_{fallback}.j2", templatefallbackfile=f"subtemplates/hyperopt_buy_space_{fallback}.j2",
) )
sell_space = render_template_with_fallback( sell_space = render_template_with_fallback(
templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2", templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/hyperopt_sell_space_{fallback}.j2", templatefallbackfile=f"subtemplates/hyperopt_sell_space_{fallback}.j2",
) )
strategy_text = render_template(templatefile='base_hyperopt.py.j2', strategy_text = render_template(templatefile='base_hyperopt.py.j2',
arguments={"hyperopt": hyperopt_name, arguments={"hyperopt": hyperopt_name,

View File

@ -187,7 +187,7 @@ def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> Li
x for x in epochs x for x in epochs
if x['results_metrics'].get( if x['results_metrics'].get(
'trade_count', x['results_metrics'].get('total_trades') 'trade_count', x['results_metrics'].get('total_trades')
) < filteroptions['filter_max_trades'] ) < filteroptions['filter_max_trades']
] ]
return epochs return epochs
@ -239,7 +239,7 @@ def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List:
x for x in epochs x for x in epochs
if x['results_metrics'].get( if x['results_metrics'].get(
'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100 'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100
) < filteroptions['filter_max_avg_profit'] ) < filteroptions['filter_max_avg_profit']
] ]
if filteroptions['filter_min_total_profit'] is not None: if filteroptions['filter_min_total_profit'] is not None:
epochs = _hyperopt_filter_epochs_trade(epochs, 0) epochs = _hyperopt_filter_epochs_trade(epochs, 0)
@ -247,7 +247,7 @@ def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List:
x for x in epochs x for x in epochs
if x['results_metrics'].get( if x['results_metrics'].get(
'profit', x['results_metrics'].get('profit_total_abs', 0) 'profit', x['results_metrics'].get('profit_total_abs', 0)
) > filteroptions['filter_min_total_profit'] ) > filteroptions['filter_min_total_profit']
] ]
if filteroptions['filter_max_total_profit'] is not None: if filteroptions['filter_max_total_profit'] is not None:
epochs = _hyperopt_filter_epochs_trade(epochs, 0) epochs = _hyperopt_filter_epochs_trade(epochs, 0)
@ -255,7 +255,7 @@ def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List:
x for x in epochs x for x in epochs
if x['results_metrics'].get( if x['results_metrics'].get(
'profit', x['results_metrics'].get('profit_total_abs', 0) 'profit', x['results_metrics'].get('profit_total_abs', 0)
) < filteroptions['filter_max_total_profit'] ) < filteroptions['filter_max_total_profit']
] ]
return epochs return epochs

View File

@ -51,10 +51,10 @@ def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
if not is_exchange_known_ccxt(exchange): if not is_exchange_known_ccxt(exchange):
raise OperationalException( raise OperationalException(
f'Exchange "{exchange}" is not known to the ccxt library ' f'Exchange "{exchange}" is not known to the ccxt library '
f'and therefore not available for the bot.\n' f'and therefore not available for the bot.\n'
f'The following exchanges are available for Freqtrade: ' f'The following exchanges are available for Freqtrade: '
f'{", ".join(available_exchanges())}' f'{", ".join(available_exchanges())}'
) )
valid, reason = validate_exchange(exchange) valid, reason = validate_exchange(exchange)

View File

@ -115,7 +115,7 @@ def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
if conf.get('stoploss') == 0.0: if conf.get('stoploss') == 0.0:
raise OperationalException( raise OperationalException(
'The config stoploss needs to be different from 0 to avoid problems with sell orders.' 'The config stoploss needs to be different from 0 to avoid problems with sell orders.'
) )
# Skip if trailing stoploss is not activated # Skip if trailing stoploss is not activated
if not conf.get('trailing_stop', False): if not conf.get('trailing_stop', False):
return return
@ -180,7 +180,7 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
raise OperationalException( raise OperationalException(
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n" "Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
f"Please fix the protection {prot.get('method')}" f"Please fix the protection {prot.get('method')}"
) )
if ('lookback_period' in prot and 'lookback_period_candles' in prot): if ('lookback_period' in prot and 'lookback_period_candles' in prot):
raise OperationalException( raise OperationalException(

View File

@ -108,5 +108,5 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
raise OperationalException( raise OperationalException(
"Both 'timeframe' and 'ticker_interval' detected." "Both 'timeframe' and 'ticker_interval' detected."
"Please remove 'ticker_interval' from your configuration to continue operating." "Please remove 'ticker_interval' from your configuration to continue operating."
) )
config['timeframe'] = config['ticker_interval'] config['timeframe'] = config['ticker_interval']

View File

@ -280,7 +280,7 @@ CONF_SCHEMA = {
'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'off' 'default': 'off'
}, },
} }
}, },
'reload': {'type': 'boolean'}, 'reload': {'type': 'boolean'},

View File

@ -231,12 +231,12 @@ class Edge:
'Minimum expectancy and minimum winrate are met only for %s,' 'Minimum expectancy and minimum winrate are met only for %s,'
' so other pairs are filtered out.', ' so other pairs are filtered out.',
self._final_pairs self._final_pairs
) )
else: else:
logger.info( logger.info(
'Edge removed all pairs as no pair with minimum expectancy ' 'Edge removed all pairs as no pair with minimum expectancy '
'and minimum winrate was found !' 'and minimum winrate was found !'
) )
return self._final_pairs return self._final_pairs
@ -247,7 +247,7 @@ class Edge:
final = [] final = []
for pair, info in self._cached_pairs.items(): for pair, info in self._cached_pairs.items():
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)): info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)):
final.append({ final.append({
'Pair': pair, 'Pair': pair,
'Winrate': info.winrate, 'Winrate': info.winrate,

View File

@ -44,7 +44,7 @@ def main(sysargv: List[str] = None) -> None:
"as `freqtrade trade [options...]`.\n" "as `freqtrade trade [options...]`.\n"
"To see the full list of options available, please use " "To see the full list of options available, please use "
"`freqtrade --help` or `freqtrade <command> --help`." "`freqtrade --help` or `freqtrade <command> --help`."
) )
except SystemExit as e: except SystemExit as e:
return_code = e return_code = e

View File

@ -444,9 +444,9 @@ class Hyperopt:
' [', progressbar.ETA(), ', ', progressbar.Timer(), ']', ' [', progressbar.ETA(), ', ', progressbar.Timer(), ']',
] ]
with progressbar.ProgressBar( with progressbar.ProgressBar(
max_value=self.total_epochs, redirect_stdout=False, redirect_stderr=False, max_value=self.total_epochs, redirect_stdout=False, redirect_stderr=False,
widgets=widgets widgets=widgets
) as pbar: ) as pbar:
EVALS = ceil(self.total_epochs / jobs) EVALS = ceil(self.total_epochs / jobs)
for i in range(EVALS): for i in range(EVALS):
# Correct the number of epochs to be processed for the last # Correct the number of epochs to be processed for the last

View File

@ -203,7 +203,7 @@ class HyperoptTools():
elif space == "roi": elif space == "roi":
result = result[:-1] + f'{appendix}\n' result = result[:-1] + f'{appendix}\n'
minimal_roi_result = rapidjson.dumps({ minimal_roi_result = rapidjson.dumps({
str(k): v for k, v in (space_params or no_params).items() str(k): v for k, v in (space_params or no_params).items()
}, default=str, indent=4, number_mode=rapidjson.NM_NATIVE) }, default=str, indent=4, number_mode=rapidjson.NM_NATIVE)
result += f"minimal_roi = {minimal_roi_result}" result += f"minimal_roi = {minimal_roi_result}"
elif space == "trailing": elif space == "trailing":

View File

@ -31,7 +31,7 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N
filename = Path.joinpath( filename = Path.joinpath(
recordfilename.parent, recordfilename.parent,
f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}' f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}'
).with_suffix(recordfilename.suffix) ).with_suffix(recordfilename.suffix)
file_dump_json(filename, stats) file_dump_json(filename, stats)
latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN) latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN)
@ -173,7 +173,7 @@ def generate_strategy_comparison(all_results: Dict) -> List[Dict]:
for strategy, results in all_results.items(): for strategy, results in all_results.items():
tabular_data.append(_generate_result_line( tabular_data.append(_generate_result_line(
results['results'], results['config']['dry_run_wallet'], strategy) results['results'], results['config']['dry_run_wallet'], strategy)
) )
try: try:
max_drawdown_per, _, _, _, _ = calculate_max_drawdown(results['results'], max_drawdown_per, _, _, _, _ = calculate_max_drawdown(results['results'],
value_col='profit_ratio') value_col='profit_ratio')
@ -604,7 +604,7 @@ def text_table_add_metrics(strat_results: Dict) -> str:
strat_results['stake_currency']) strat_results['stake_currency'])
stake_amount = round_coin_value( stake_amount = round_coin_value(
strat_results['stake_amount'], strat_results['stake_currency'] strat_results['stake_amount'], strat_results['stake_currency']
) if strat_results['stake_amount'] != UNLIMITED_STAKE_AMOUNT else 'unlimited' ) if strat_results['stake_amount'] != UNLIMITED_STAKE_AMOUNT else 'unlimited'
message = ("No trades made. " message = ("No trades made. "
f"Your starting balance was {start_balance}, " f"Your starting balance was {start_balance}, "

View File

@ -334,8 +334,8 @@ def add_areas(fig, row: int, data: pd.DataFrame, indicators) -> make_subplots:
) )
elif indicator_b not in data: elif indicator_b not in data:
logger.info( logger.info(
'fill_to: "%s" ignored. Reason: This indicator is not ' 'fill_to: "%s" ignored. Reason: This indicator is not '
'in your strategy.', indicator_b 'in your strategy.', indicator_b
) )
return fig return fig

View File

@ -144,7 +144,7 @@ class IPairList(LoggingMixin, ABC):
markets = self._exchange.markets markets = self._exchange.markets
if not markets: if not markets:
raise OperationalException( raise OperationalException(
'Markets not loaded. Make sure that exchange is initialized correctly.') 'Markets not loaded. Make sure that exchange is initialized correctly.')
sanitized_whitelist: List[str] = [] sanitized_whitelist: List[str] = []
for pair in pairlist: for pair in pairlist:

View File

@ -120,9 +120,9 @@ class VolumePairList(IPairList):
# Use fresh pairlist # Use fresh pairlist
# Check if pair quote currency equals to the stake currency. # Check if pair quote currency equals to the stake currency.
filtered_tickers = [ filtered_tickers = [
v for k, v in tickers.items() v for k, v in tickers.items()
if (self._exchange.get_pair_quote_currency(k) == self._stake_currency if (self._exchange.get_pair_quote_currency(k) == self._stake_currency
and v[self._sort_key] is not None)] and v[self._sort_key] is not None)]
pairlist = [s['symbol'] for s in filtered_tickers] pairlist = [s['symbol'] for s in filtered_tickers]
pairlist = self.filter_pairlist(pairlist, tickers) pairlist = self.filter_pairlist(pairlist, tickers)
@ -197,7 +197,7 @@ class VolumePairList(IPairList):
if self._min_value > 0: if self._min_value > 0:
filtered_tickers = [ filtered_tickers = [
v for v in filtered_tickers if v[self._sort_key] > self._min_value] v for v in filtered_tickers if v[self._sort_key] > self._min_value]
sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[self._sort_key]) sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[self._sort_key])

View File

@ -28,13 +28,13 @@ class PairListManager():
self._tickers_needed = False self._tickers_needed = False
for pairlist_handler_config in self._config.get('pairlists', None): for pairlist_handler_config in self._config.get('pairlists', None):
pairlist_handler = PairListResolver.load_pairlist( pairlist_handler = PairListResolver.load_pairlist(
pairlist_handler_config['method'], pairlist_handler_config['method'],
exchange=exchange, exchange=exchange,
pairlistmanager=self, pairlistmanager=self,
config=config, config=config,
pairlistconfig=pairlist_handler_config, pairlistconfig=pairlist_handler_config,
pairlist_pos=len(self._pairlist_handlers) pairlist_pos=len(self._pairlist_handlers)
) )
self._tickers_needed |= pairlist_handler.needstickers self._tickers_needed |= pairlist_handler.needstickers
self._pairlist_handlers.append(pairlist_handler) self._pairlist_handlers.append(pairlist_handler)

View File

@ -54,9 +54,9 @@ class StoplossGuard(IProtection):
trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until) trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until)
trades = [trade for trade in trades1 if (str(trade.sell_reason) in ( trades = [trade for trade in trades1 if (str(trade.sell_reason) in (
SellType.TRAILING_STOP_LOSS.value, SellType.STOP_LOSS.value, SellType.TRAILING_STOP_LOSS.value, SellType.STOP_LOSS.value,
SellType.STOPLOSS_ON_EXCHANGE.value) SellType.STOPLOSS_ON_EXCHANGE.value)
and trade.close_profit and trade.close_profit < 0)] and trade.close_profit and trade.close_profit < 0)]
if len(trades) < self._trade_limit: if len(trades) < self._trade_limit:
return False, None, None return False, None, None

View File

@ -8,6 +8,3 @@ from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from freqtrade.resolvers.pairlist_resolver import PairListResolver from freqtrade.resolvers.pairlist_resolver import PairListResolver
from freqtrade.resolvers.protection_resolver import ProtectionResolver from freqtrade.resolvers.protection_resolver import ProtectionResolver
from freqtrade.resolvers.strategy_resolver import StrategyResolver from freqtrade.resolvers.strategy_resolver import StrategyResolver

View File

@ -50,7 +50,7 @@ class StrategyResolver(IResolver):
if 'timeframe' not in config: if 'timeframe' not in config:
logger.warning( logger.warning(
"DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'." "DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'."
) )
strategy.timeframe = strategy.ticker_interval strategy.timeframe = strategy.ticker_interval
if strategy._ft_params_from_file: if strategy._ft_params_from_file:

View File

@ -199,8 +199,8 @@ def pair_history(pair: str, timeframe: str, timerange: str, strategy: str,
config=Depends(get_config)): config=Depends(get_config)):
config = deepcopy(config) config = deepcopy(config)
config.update({ config.update({
'strategy': strategy, 'strategy': strategy,
}) })
return RPC._rpc_analysed_history_full(config, pair, timeframe, timerange) return RPC._rpc_analysed_history_full(config, pair, timeframe, timerange)

View File

@ -62,7 +62,7 @@ class CryptoToFiatConverter:
# If the request is not a 429 error we want to raise the normal error # If the request is not a 429 error we want to raise the normal error
logger.error( logger.error(
"Could not load FIAT Cryptocurrency map for the following problem: {}".format( "Could not load FIAT Cryptocurrency map for the following problem: {}".format(
request_exception request_exception
) )
) )
except (Exception) as exception: except (Exception) as exception:

View File

@ -15,6 +15,7 @@ class RPCManager:
""" """
Class to manage RPC objects (Telegram, API, ...) Class to manage RPC objects (Telegram, API, ...)
""" """
def __init__(self, freqtrade) -> None: def __init__(self, freqtrade) -> None:
""" Initializes all enabled rpc modules """ """ Initializes all enabled rpc modules """
self.registered_modules: List[RPCHandler] = [] self.registered_modules: List[RPCHandler] = []

View File

@ -77,7 +77,6 @@ class Telegram(RPCHandler):
""" This class handles all telegram communication """ """ This class handles all telegram communication """
def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None: def __init__(self, rpc: RPC, config: Dict[str, Any]) -> None:
""" """
Init the Telegram call, and init the super class RPCHandler Init the Telegram call, and init the super class RPCHandler
:param rpc: instance of RPC Helper class :param rpc: instance of RPC Helper class
@ -270,7 +269,7 @@ class Telegram(RPCHandler):
noti = '' noti = ''
if msg_type == RPCMessageType.SELL: if msg_type == RPCMessageType.SELL:
sell_noti = self._config['telegram'] \ sell_noti = self._config['telegram'] \
.get('notification_settings', {}).get(str(msg_type), {}) .get('notification_settings', {}).get(str(msg_type), {})
# For backward compatibility sell still can be string # For backward compatibility sell still can be string
if isinstance(sell_noti, str): if isinstance(sell_noti, str):
noti = sell_noti noti = sell_noti
@ -278,7 +277,7 @@ class Telegram(RPCHandler):
noti = sell_noti.get(str(msg['sell_reason']), default_noti) noti = sell_noti.get(str(msg['sell_reason']), default_noti)
else: else:
noti = self._config['telegram'] \ noti = self._config['telegram'] \
.get('notification_settings', {}).get(str(msg_type), default_noti) .get('notification_settings', {}).get(str(msg_type), default_noti)
if noti == 'off': if noti == 'off':
logger.info(f"Notification '{msg_type}' not sent.") logger.info(f"Notification '{msg_type}' not sent.")
@ -541,7 +540,7 @@ class Telegram(RPCHandler):
f"`{first_trade_date}`\n" f"`{first_trade_date}`\n"
f"*Latest Trade opened:* `{latest_trade_date}\n`" f"*Latest Trade opened:* `{latest_trade_date}\n`"
f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`" f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`"
) )
if stats['closed_trade_count'] > 0: if stats['closed_trade_count'] > 0:
markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n" markdown_msg += (f"\n*Avg. Duration:* `{avg_duration}`\n"
f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`") f"*Best Performing:* `{best_pair}: {best_rate:.2f}%`")
@ -576,13 +575,14 @@ class Telegram(RPCHandler):
sell_reasons_msg = tabulate( sell_reasons_msg = tabulate(
sell_reasons_tabulate, sell_reasons_tabulate,
headers=['Sell Reason', 'Sells', 'Wins', 'Losses'] headers=['Sell Reason', 'Sells', 'Wins', 'Losses']
) )
durations = stats['durations'] durations = stats['durations']
duration_msg = tabulate([ duration_msg = tabulate(
['Wins', str(timedelta(seconds=durations['wins'])) [
if durations['wins'] != 'N/A' else 'N/A'], ['Wins', str(timedelta(seconds=durations['wins']))
['Losses', str(timedelta(seconds=durations['losses'])) if durations['wins'] != 'N/A' else 'N/A'],
if durations['losses'] != 'N/A' else 'N/A'] ['Losses', str(timedelta(seconds=durations['losses']))
if durations['losses'] != 'N/A' else 'N/A']
], ],
headers=['', 'Avg. Duration'] headers=['', 'Avg. Duration']
) )
@ -1100,7 +1100,7 @@ class Telegram(RPCHandler):
if reload_able: if reload_able:
reply_markup = InlineKeyboardMarkup([ reply_markup = InlineKeyboardMarkup([
[InlineKeyboardButton("Refresh", callback_data=callback_path)], [InlineKeyboardButton("Refresh", callback_data=callback_path)],
]) ])
else: else:
reply_markup = InlineKeyboardMarkup([[]]) reply_markup = InlineKeyboardMarkup([[]])
msg += "\nUpdated: {}".format(datetime.now().ctime()) msg += "\nUpdated: {}".format(datetime.now().ctime())

View File

@ -38,7 +38,7 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
# Detailed explanation in https://github.com/freqtrade/freqtrade/issues/4073 # Detailed explanation in https://github.com/freqtrade/freqtrade/issues/4073
informative['date_merge'] = ( informative['date_merge'] = (
informative["date"] + pd.to_timedelta(minutes_inf, 'm') - pd.to_timedelta(minutes, 'm') informative["date"] + pd.to_timedelta(minutes_inf, 'm') - pd.to_timedelta(minutes, 'm')
) )
else: else:
raise ValueError("Tried to merge a faster timeframe to a slower timeframe." raise ValueError("Tried to merge a faster timeframe to a slower timeframe."
"This would create new rows, and can throw off your regular indicators.") "This would create new rows, and can throw off your regular indicators.")

View File

@ -6,8 +6,8 @@
*/ */
"stake_currency": "BTC", "stake_currency": "BTC",
"stake_amount": 0.05, "stake_amount": 0.05,
"fiat_display_currency": "USD", // C++-style comment "fiat_display_currency": "USD", // C++-style comment
"amount_reserve_percent" : 0.05, // And more, tabs before this comment "amount_reserve_percent": 0.05, // And more, tabs before this comment
"dry_run": false, "dry_run": false,
"timeframe": "5m", "timeframe": "5m",
"trailing_stop": false, "trailing_stop": false,
@ -15,15 +15,15 @@
"trailing_stop_positive_offset": 0.0051, "trailing_stop_positive_offset": 0.0051,
"trailing_only_offset_is_reached": false, "trailing_only_offset_is_reached": false,
"minimal_roi": { "minimal_roi": {
"40": 0.0, "40": 0.0,
"30": 0.01, "30": 0.01,
"20": 0.02, "20": 0.02,
"0": 0.04 "0": 0.04
}, },
"stoploss": -0.10, "stoploss": -0.10,
"unfilledtimeout": { "unfilledtimeout": {
"buy": 10, "buy": 10,
"sell": 30, // Trailing comma should also be accepted now "sell": 30, // Trailing comma should also be accepted now
}, },
"bid_strategy": { "bid_strategy": {
"use_order_book": false, "use_order_book": false,
@ -34,7 +34,7 @@
"bids_to_ask_delta": 1 "bids_to_ask_delta": 1
} }
}, },
"ask_strategy":{ "ask_strategy": {
"use_order_book": false, "use_order_book": false,
"order_book_min": 1, "order_book_min": 1,
"order_book_max": 9 "order_book_max": 9
@ -64,7 +64,9 @@
"key": "your_exchange_key", "key": "your_exchange_key",
"secret": "your_exchange_secret", "secret": "your_exchange_secret",
"password": "", "password": "",
"ccxt_config": {"enableRateLimit": true}, "ccxt_config": {
"enableRateLimit": true
},
"ccxt_async_config": { "ccxt_async_config": {
"enableRateLimit": false, "enableRateLimit": false,
"rateLimit": 500, "rateLimit": 500,
@ -103,8 +105,8 @@
"remove_pumps": false "remove_pumps": false
}, },
"telegram": { "telegram": {
// We can now comment out some settings // We can now comment out some settings
// "enabled": true, // "enabled": true,
"enabled": false, "enabled": false,
"token": "your_telegram_token", "token": "your_telegram_token",
"chat_id": "your_telegram_chat_id" "chat_id": "your_telegram_chat_id"
@ -124,4 +126,4 @@
}, },
"strategy": "DefaultStrategy", "strategy": "DefaultStrategy",
"strategy_path": "user_data/strategies/" "strategy_path": "user_data/strategies/"
} }

View File

@ -399,7 +399,7 @@ def test_hyperopt_format_results(hyperopt):
'rejected_signals': 2, 'rejected_signals': 2,
'backtest_start_time': 1619718665, 'backtest_start_time': 1619718665,
'backtest_end_time': 1619718665, 'backtest_end_time': 1619718665,
} }
results_metrics = generate_strategy_stats({'XRP/BTC': None}, '', bt_result, results_metrics = generate_strategy_stats({'XRP/BTC': None}, '', bt_result,
Arrow(2017, 11, 14, 19, 32, 00), Arrow(2017, 11, 14, 19, 32, 00),
Arrow(2017, 12, 14, 19, 32, 00), market_change=0) Arrow(2017, 12, 14, 19, 32, 00), market_change=0)

View File

@ -93,7 +93,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, min_ago_open=200, min_ago_close=30,
)) ))
assert not freqtrade.protections.global_stop() assert not freqtrade.protections.global_stop()
assert not log_has_re(message, caplog) assert not log_has_re(message, caplog)
@ -150,7 +150,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, profit_rate=0.9, min_ago_open=200, min_ago_close=30, profit_rate=0.9,
)) ))
assert not freqtrade.protections.stop_per_pair(pair) assert not freqtrade.protections.stop_per_pair(pair)
assert not freqtrade.protections.global_stop() assert not freqtrade.protections.global_stop()

View File

@ -139,9 +139,9 @@ def test_fiat_too_many_requests_response(mocker, caplog):
assert length_cryptomap == 0 assert length_cryptomap == 0
assert fiat_convert._backoff > datetime.datetime.now().timestamp() assert fiat_convert._backoff > datetime.datetime.now().timestamp()
assert log_has( assert log_has(
'Too many requests for Coingecko API, backing off and trying again later.', 'Too many requests for Coingecko API, backing off and trying again later.',
caplog caplog
) )
def test_fiat_invalid_response(mocker, caplog): def test_fiat_invalid_response(mocker, caplog):

View File

@ -942,7 +942,7 @@ def test_api_whitelist(botclient):
"whitelist": ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'], "whitelist": ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'],
"length": 4, "length": 4,
"method": ["StaticPairList"] "method": ["StaticPairList"]
} }
def test_api_forcebuy(botclient, mocker, fee): def test_api_forcebuy(botclient, mocker, fee):
@ -1033,7 +1033,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'buy_tag': None, 'buy_tag': None,
'timeframe': 5, 'timeframe': 5,
'exchange': 'binance', 'exchange': 'binance',
} }
def test_api_forcesell(botclient, mocker, ticker, fee, markets): def test_api_forcesell(botclient, mocker, ticker, fee, markets):
@ -1215,7 +1215,7 @@ def test_api_strategies(botclient):
'DefaultStrategy', 'DefaultStrategy',
'HyperoptableStrategy', 'HyperoptableStrategy',
'TestStrategyLegacy' 'TestStrategyLegacy'
]} ]}
def test_api_strategy(botclient): def test_api_strategy(botclient):

View File

@ -125,7 +125,7 @@ def test_parse_args_backtesting_custom() -> None:
'--strategy-list', '--strategy-list',
'DefaultStrategy', 'DefaultStrategy',
'SampleStrategy' 'SampleStrategy'
] ]
call_args = Arguments(args).get_parsed_arg() call_args = Arguments(args).get_parsed_arg()
assert call_args['config'] == ['test_conf.json'] assert call_args['config'] == ['test_conf.json']
assert call_args['verbosity'] == 0 assert call_args['verbosity'] == 0

View File

@ -1130,17 +1130,17 @@ def test_pairlist_resolving_fallback(mocker):
@pytest.mark.parametrize("setting", [ @pytest.mark.parametrize("setting", [
("ask_strategy", "use_sell_signal", True, ("ask_strategy", "use_sell_signal", True,
None, "use_sell_signal", False), None, "use_sell_signal", False),
("ask_strategy", "sell_profit_only", True, ("ask_strategy", "sell_profit_only", True,
None, "sell_profit_only", False), None, "sell_profit_only", False),
("ask_strategy", "sell_profit_offset", 0.1, ("ask_strategy", "sell_profit_offset", 0.1,
None, "sell_profit_offset", 0.01), None, "sell_profit_offset", 0.01),
("ask_strategy", "ignore_roi_if_buy_signal", True, ("ask_strategy", "ignore_roi_if_buy_signal", True,
None, "ignore_roi_if_buy_signal", False), None, "ignore_roi_if_buy_signal", False),
("ask_strategy", "ignore_buying_expired_candle_after", 5, ("ask_strategy", "ignore_buying_expired_candle_after", 5,
None, "ignore_buying_expired_candle_after", 6), None, "ignore_buying_expired_candle_after", 6),
]) ])
def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog): def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog):
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)
@ -1180,10 +1180,10 @@ def test_process_temporary_deprecated_settings(mocker, default_conf, setting, ca
@pytest.mark.parametrize("setting", [ @pytest.mark.parametrize("setting", [
("experimental", "use_sell_signal", False), ("experimental", "use_sell_signal", False),
("experimental", "sell_profit_only", True), ("experimental", "sell_profit_only", True),
("experimental", "ignore_roi_if_buy_signal", True), ("experimental", "ignore_roi_if_buy_signal", True),
]) ])
def test_process_removed_settings(mocker, default_conf, setting): def test_process_removed_settings(mocker, default_conf, setting):
patched_configuration_load_config_file(mocker, default_conf) patched_configuration_load_config_file(mocker, default_conf)