Merge branch 'develop' into pr/rokups/4596

This commit is contained in:
Matthias
2021-04-03 17:00:37 +02:00
46 changed files with 160 additions and 107 deletions

View File

@@ -1,4 +1,5 @@
import logging
import secrets
from pathlib import Path
from typing import Any, Dict, List
@@ -138,6 +139,32 @@ def ask_user_config() -> Dict[str, Any]:
"message": "Insert Telegram chat id",
"when": lambda x: x['telegram']
},
{
"type": "confirm",
"name": "api_server",
"message": "Do you want to enable the Rest API (includes FreqUI)?",
"default": False,
},
{
"type": "text",
"name": "api_server_listen_addr",
"message": "Insert Api server Listen Address (best left untouched default!)",
"default": "127.0.0.1",
"when": lambda x: x['api_server']
},
{
"type": "text",
"name": "api_server_username",
"message": "Insert api-server username",
"default": "freqtrader",
"when": lambda x: x['api_server']
},
{
"type": "text",
"name": "api_server_password",
"message": "Insert api-server password",
"when": lambda x: x['api_server']
},
]
answers = prompt(questions)
@@ -145,6 +172,9 @@ def ask_user_config() -> Dict[str, Any]:
# Interrupted questionary sessions return an empty dict.
raise OperationalException("User interrupted interactive questions.")
# Force JWT token to be a random string
answers['api_server_jwt_key'] = secrets.token_hex()
return answers

View File

@@ -118,7 +118,7 @@ AVAILABLE_CLI_OPTIONS = {
# Optimize common
"timeframe": Arg(
'-i', '--timeframe', '--ticker-interval',
help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).',
help='Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).',
),
"timerange": Arg(
'--timerange',

View File

@@ -99,7 +99,7 @@ def start_list_hyperopts(args: Dict[str, Any]) -> None:
def start_list_timeframes(args: Dict[str, Any]) -> None:
"""
Print ticker intervals (timeframes) available on Exchange
Print timeframes available on Exchange
"""
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
# Do not use timeframe set in the config
@@ -177,7 +177,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
# human-readable formats.
print()
if len(pairs):
if pairs:
if args.get('print_list', False):
# print data as a list, with human-readable summary
print(f"{summary_str}: {', '.join(pairs.keys())}.")

View File

@@ -149,11 +149,6 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
if not conf.get('edge', {}).get('enabled'):
return
if conf.get('pairlist', {}).get('method') == 'VolumePairList':
raise OperationalException(
"Edge and VolumePairList are incompatible, "
"Edge will override whatever pairs VolumePairlist selects."
)
if not conf.get('ask_strategy', {}).get('use_sell_signal', True):
raise OperationalException(
"Edge requires `use_sell_signal` to be True, otherwise no sells will happen."

View File

@@ -72,6 +72,5 @@ def copy_sample_files(directory: Path, overwrite: bool = False) -> None:
if not overwrite:
logger.warning(f"File `{targetfile}` exists already, not deploying sample file.")
continue
else:
logger.warning(f"File `{targetfile}` exists already, overwriting.")
logger.warning(f"File `{targetfile}` exists already, overwriting.")
shutil.copy(str(sourcedir / source), str(targetfile))

View File

@@ -110,22 +110,35 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str)
df.reset_index(inplace=True)
len_before = len(dataframe)
len_after = len(df)
pct_missing = (len_after - len_before) / len_before if len_before > 0 else 0
if len_before != len_after:
logger.info(f"Missing data fillup for {pair}: before: {len_before} - after: {len_after}")
message = (f"Missing data fillup for {pair}: before: {len_before} - after: {len_after}"
f" - {round(pct_missing * 100, 2)} %")
if pct_missing > 0.01:
logger.info(message)
else:
# Don't be verbose if only a small amount is missing
logger.debug(message)
return df
def trim_dataframe(df: DataFrame, timerange, df_date_col: str = 'date') -> DataFrame:
def trim_dataframe(df: DataFrame, timerange, df_date_col: str = 'date',
startup_candles: int = 0) -> DataFrame:
"""
Trim dataframe based on given timerange
:param df: Dataframe to trim
:param timerange: timerange (use start and end date if available)
:param: df_date_col: Column in the dataframe to use as Date column
:param df_date_col: Column in the dataframe to use as Date column
:param startup_candles: When not 0, is used instead the timerange start date
:return: trimmed dataframe
"""
if timerange.starttype == 'date':
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
df = df.loc[df[df_date_col] >= start, :]
if startup_candles:
# Trim candles instead of timeframe in case of given startup_candle count
df = df.iloc[startup_candles:, :]
else:
if timerange.starttype == 'date':
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
df = df.loc[df[df_date_col] >= start, :]
if timerange.stoptype == 'date':
stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
df = df.loc[df[df_date_col] <= stop, :]

View File

@@ -84,9 +84,8 @@ class Edge:
self.fee = self.exchange.get_fee(symbol=expand_pairlist(
self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0])
def calculate(self) -> bool:
pairs = expand_pairlist(self.config['exchange']['pair_whitelist'],
list(self.exchange.markets))
def calculate(self, pairs: List[str]) -> bool:
heartbeat = self.edge_config.get('process_throttle_secs')
if (self._last_updated > 0) and (

View File

@@ -140,7 +140,7 @@ def retrier(_func=None, retries=API_RETRY_COUNT):
logger.warning('retrying %s() still for %s times', f.__name__, count)
count -= 1
kwargs.update({'count': count})
if isinstance(ex, DDosProtection) or isinstance(ex, RetryableOrderError):
if isinstance(ex, (DDosProtection, RetryableOrderError)):
# increasing backoff
backoff_delay = calculate_backoff(count + 1, retries)
logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}")

View File

@@ -806,7 +806,7 @@ class Exchange:
# Gather coroutines to run
for pair, timeframe in set(pair_list):
if (not ((pair, timeframe) in self._klines)
if (((pair, timeframe) not in self._klines)
or self._now_is_time_to_refresh(pair, timeframe)):
input_coroutines.append(self._async_get_candle_history(pair, timeframe,
since_ms=since_ms))
@@ -958,7 +958,7 @@ class Exchange:
while True:
t = await self._async_fetch_trades(pair,
params={self._trades_pagination_arg: from_id})
if len(t):
if t:
# Skip last id since its the key for the next call
trades.extend(t[:-1])
if from_id == t[-1][1] or t[-1][0] > until:
@@ -990,7 +990,7 @@ class Exchange:
# DEFAULT_TRADES_COLUMNS: 1 -> id
while True:
t = await self._async_fetch_trades(pair, since=since)
if len(t):
if t:
since = t[-1][0]
trades.extend(t)
# Reached the end of the defined-download period

View File

@@ -225,7 +225,7 @@ class FreqtradeBot(LoggingMixin):
# Calculating Edge positioning
if self.edge:
self.edge.calculate()
self.edge.calculate(_whitelist)
_whitelist = self.edge.adjust(_whitelist)
if trades:

View File

@@ -443,7 +443,8 @@ class Backtesting:
# Trim startup period from analyzed dataframe
for pair, df in preprocessed.items():
preprocessed[pair] = trim_dataframe(df, timerange)
preprocessed[pair] = trim_dataframe(df, timerange,
startup_candles=self.required_startup)
min_date, max_date = history.get_timerange(preprocessed)
logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '

View File

@@ -44,7 +44,7 @@ class EdgeCli:
'timerange') is None else str(self.config.get('timerange')))
def start(self) -> None:
result = self.edge.calculate()
result = self.edge.calculate(self.config['exchange']['pair_whitelist'])
if result:
print('') # blank line for readability
print(generate_edge_table(self.edge._cached_pairs))

View File

@@ -384,7 +384,8 @@ class Hyperopt:
# Trim startup period from analyzed dataframe
for pair, df in preprocessed.items():
preprocessed[pair] = trim_dataframe(df, timerange)
preprocessed[pair] = trim_dataframe(df, timerange,
startup_candles=self.backtesting.required_startup)
min_date, max_date = get_timerange(preprocessed)
logger.info(f'Hyperopting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '

View File

@@ -31,7 +31,7 @@ class IHyperOpt(ABC):
Defines the mandatory structure must follow any custom hyperopt
Class attributes you can use:
ticker_interval -> int: value of the ticker interval to use for the strategy
timeframe -> int: value of the timeframe to use for the strategy
"""
ticker_interval: str # DEPRECATED
timeframe: str
@@ -91,7 +91,7 @@ class IHyperOpt(ABC):
This method implements adaptive roi hyperspace with varied
ranges for parameters which automatically adapts to the
ticker interval used.
timeframe used.
It's used by Freqtrade by default, if no custom roi_space method is defined.
"""
@@ -113,7 +113,7 @@ class IHyperOpt(ABC):
# * 'roi_p' (limits for the ROI value steps) components are scaled logarithmically.
#
# The scaling is designed so that it maps exactly to the legacy Freqtrade roi_space()
# method for the 5m ticker interval.
# method for the 5m timeframe.
roi_t_scale = timeframe_min / 5
roi_p_scale = math.log1p(timeframe_min) / math.log1p(5)
roi_limits = {

View File

@@ -611,7 +611,7 @@ class LocalTrade():
else:
# Not used during backtesting, but might be used by a strategy
sel_trades = [trade for trade in LocalTrade.trades + LocalTrade.trades_open]
sel_trades = list(LocalTrade.trades + LocalTrade.trades_open)
if pair:
sel_trades = [trade for trade in sel_trades if trade.pair == pair]

View File

@@ -2,7 +2,7 @@
Performance pair list filter
"""
import logging
from typing import Any, Dict, List
from typing import Dict, List
import pandas as pd
@@ -15,11 +15,6 @@ logger = logging.getLogger(__name__)
class PerformanceFilter(IPairList):
def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
@property
def needstickers(self) -> bool:
"""

View File

@@ -1,7 +1,6 @@
import logging
from datetime import datetime, timedelta
from typing import Any, Dict
from freqtrade.persistence import Trade
from freqtrade.plugins.protections import IProtection, ProtectionReturn
@@ -15,9 +14,6 @@ class CooldownPeriod(IProtection):
has_global_stop: bool = False
has_local_stop: bool = True
def __init__(self, config: Dict[str, Any], protection_config: Dict[str, Any]) -> None:
super().__init__(config, protection_config)
def _reason(self) -> str:
"""
LockReason to use

View File

@@ -196,9 +196,9 @@ class StrategyResolver(IResolver):
strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
if any([x == 2 for x in [strategy._populate_fun_len,
strategy._buy_fun_len,
strategy._sell_fun_len]]):
if any(x == 2 for x in [strategy._populate_fun_len,
strategy._buy_fun_len,
strategy._sell_fun_len]):
strategy.INTERFACE_VERSION = 1
return strategy

View File

@@ -168,6 +168,7 @@ class TradeSchema(BaseModel):
profit_ratio: Optional[float]
profit_pct: Optional[float]
profit_abs: Optional[float]
profit_fiat: Optional[float]
sell_reason: Optional[str]
sell_order_status: Optional[str]
stop_loss_abs: Optional[float]

View File

@@ -173,6 +173,15 @@ class RPC:
current_rate = NAN
current_profit = trade.calc_profit_ratio(current_rate)
current_profit_abs = trade.calc_profit(current_rate)
# Calculate fiat profit
if self._fiat_converter:
current_profit_fiat = self._fiat_converter.convert_amount(
current_profit_abs,
self._freqtrade.config['stake_currency'],
self._freqtrade.config['fiat_display_currency']
)
# Calculate guaranteed profit (in case of trailing stop)
stoploss_entry_dist = trade.calc_profit(trade.stop_loss)
stoploss_entry_dist_ratio = trade.calc_profit_ratio(trade.stop_loss)
@@ -191,6 +200,7 @@ class RPC:
profit_ratio=current_profit,
profit_pct=round(current_profit * 100, 2),
profit_abs=current_profit_abs,
profit_fiat=current_profit_fiat,
stoploss_current_dist=stoploss_current_dist,
stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8),

View File

@@ -54,15 +54,15 @@
"chat_id": "{{ telegram_chat_id }}"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"enabled": {{ api_server | lower }},
"listen_ip_address": "{{ api_server_listen_addr | default("127.0.0.1", true) }}",
"listen_port": 8080,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "somethingrandom",
"jwt_secret_key": "{{ api_server_jwt_key }}",
"CORS_origins": [],
"username": "",
"password": ""
"username": "{{ api_server_username }}",
"password": "{{ api_server_password }}"
},
"bot_name": "freqtrade",
"initial_state": "running",

View File

@@ -28,8 +28,9 @@ class {{ strategy }}(IStrategy):
You must keep:
- the lib in the section "Do not remove these libs"
- the prototype for the methods: minimal_roi, stoploss, populate_indicators, populate_buy_trend,
populate_sell_trend, hyperopt_space, buy_strategy_generator
- the methods: populate_indicators, populate_buy_trend, populate_sell_trend
You should keep:
- timeframe, minimal_roi, stoploss, trailing_*
"""
# Strategy interface version - allow new iterations of the strategy interface.
# Check the documentation or the Sample strategy to get the latest version.

View File

@@ -29,8 +29,9 @@ class SampleStrategy(IStrategy):
You must keep:
- the lib in the section "Do not remove these libs"
- the prototype for the methods: minimal_roi, stoploss, populate_indicators, populate_buy_trend,
populate_sell_trend, hyperopt_space, buy_strategy_generator
- the methods: populate_indicators, populate_buy_trend, populate_sell_trend
You should keep:
- timeframe, minimal_roi, stoploss, trailing_*
"""
# Strategy interface version - allow new iterations of the strategy interface.
# Check the documentation or the Sample strategy to get the latest version.