Merge branch 'develop' into feat/short
This commit is contained in:
commit
9b58c58609
@ -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)_
|
||||||
|
@ -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/)
|
||||||
|
@ -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",
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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']
|
||||||
|
@ -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'},
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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":
|
||||||
|
@ -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}, "
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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])
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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] = []
|
||||||
|
@ -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())
|
||||||
|
@ -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.")
|
||||||
|
@ -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/"
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user