Merge branch 'develop' into pr/rokups/4596

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

View File

@ -310,9 +310,18 @@ jobs:
needs: [ build_linux, build_macos, build_windows, docs_check ]
runs-on: ubuntu-20.04
steps:
- name: Check user permission
id: check
uses: scherermichael-oss/action-has-permission@1.0.6
with:
required-permission: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Slack Notification
uses: lazy-actions/slatify@v3.0.0
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
if: always() && steps.check.outputs.has-permission && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI*'

View File

@ -9,7 +9,7 @@ services:
# Build step - only needed when additional dependencies are needed
# build:
# context: .
# dockerfile: "./docker/Dockerfile.technical"
# dockerfile: "./docker/Dockerfile.custom"
restart: unless-stopped
container_name: freqtrade
volumes:

View File

@ -3,4 +3,5 @@ FROM freqtradeorg/freqtrade:develop
RUN apt-get update \
&& apt-get -y install git \
&& apt-get clean \
&& pip install git+https://github.com/freqtrade/technical
# The below dependency - pyti - serves as an example. Please use whatever you need!
&& pip install pyti

View File

@ -82,6 +82,8 @@ class MyAwesomeStrategy(IStrategy):
return [Real(-0.05, -0.01, name='stoploss')]
```
---
## Legacy Hyperopt
This Section explains the configuration of an explicit Hyperopt file (separate to the strategy).

View File

@ -23,8 +23,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}

View File

@ -156,8 +156,8 @@ Head over to the [Backtesting Documentation](backtesting.md) to learn more.
### Additional dependencies with docker-compose
If your strategy requires dependencies not included in the default image (like [technical](https://github.com/freqtrade/technical)) - it will be necessary to build the image on your host.
For this, please create a Dockerfile containing installation steps for the additional dependencies (have a look at [docker/Dockerfile.technical](https://github.com/freqtrade/freqtrade/blob/develop/docker/Dockerfile.technical) for an example).
If your strategy requires dependencies not included in the default image - it will be necessary to build the image on your host.
For this, please create a Dockerfile containing installation steps for the additional dependencies (have a look at [docker/Dockerfile.custom](https://github.com/freqtrade/freqtrade/blob/develop/docker/Dockerfile.custom) for an example).
You'll then also need to modify the `docker-compose.yml` file and uncomment the build step, as well as rename the image to avoid naming collisions.

View File

@ -3,7 +3,7 @@
The `Edge Positioning` module uses probability to calculate your win rate and risk reward ratio. It will use these statistics to control your strategy trade entry points, position size and, stoploss.
!!! Warning
`Edge positioning` is not compatible with dynamic (volume-based) whitelist.
WHen using `Edge positioning` with a dynamic whitelist (VolumePairList), make sure to also use `AgeFilter` and set it to at least `calculate_since_number_of_days` to avoid problems with missing data.
!!! Note
`Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
@ -221,8 +221,7 @@ usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--max-open-trades INT

View File

@ -55,8 +55,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}

View File

@ -66,8 +66,7 @@ optional arguments:
--timerange TIMERANGE
Specify what timerange of data to use.
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--no-trades Skip using trades from backtesting file and DB.
Common arguments:
@ -264,8 +263,7 @@ optional arguments:
Specify the source for trades (Can be DB or file
(backtest file)) Default: file
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).

View File

@ -1,3 +1,3 @@
mkdocs-material==7.0.6
mkdocs-material==7.0.7
mdx_truly_sane_lists==1.2
pymdown-extensions==8.1.1

View File

@ -264,7 +264,7 @@ All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpr
## List Timeframes
Use the `list-timeframes` subcommand to see the list of timeframes (ticker intervals) available for the exchange.
Use the `list-timeframes` subcommand to see the list of timeframes available for the exchange.
```
usage: freqtrade list-timeframes [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [-1]

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.")
shutil.copy(str(sourcedir / source), str(targetfile))

View File

@ -110,19 +110,32 @@ 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 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, :]

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,
if any(x == 2 for x in [strategy._populate_fun_len,
strategy._buy_fun_len,
strategy._sell_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.

View File

@ -2,7 +2,7 @@
-r requirements.txt
# Required for hyperopt
scipy==1.6.1
scipy==1.6.2
scikit-learn==0.24.1
scikit-optimize==0.8.1
filelock==3.0.12

View File

@ -1,11 +1,11 @@
numpy==1.20.1
numpy==1.20.2
pandas==1.2.3
ccxt==1.43.89
ccxt==1.45.44
# Pin cryptography for now due to rust build errors with piwheels
cryptography==3.4.6
cryptography==3.4.7
aiohttp==3.7.4.post0
SQLAlchemy==1.4.2
SQLAlchemy==1.4.3
python-telegram-bot==13.4.1
arrow==1.0.3
cachetools==4.2.1
@ -14,6 +14,7 @@ urllib3==1.26.4
wrapt==1.12.1
jsonschema==3.2.0
TA-Lib==0.4.19
technical==1.2.2
tabulate==0.8.9
pycoingecko==1.4.0
jinja2==2.11.3
@ -39,4 +40,4 @@ aiofiles==0.6.0
colorama==0.4.4
# Building config files interactively
questionary==1.9.0
prompt-toolkit==3.0.17
prompt-toolkit==3.0.18

View File

@ -77,6 +77,7 @@ setup(name='freqtrade',
'wrapt',
'jsonschema',
'TA-Lib',
'technical',
'tabulate',
'pycoingecko',
'py_find_1st',

View File

@ -50,6 +50,10 @@ def test_start_new_config(mocker, caplog, exchange):
'telegram': False,
'telegram_token': 'asdf1244',
'telegram_chat_id': '1144444',
'api_server': False,
'api_server_listen_addr': '127.0.0.1',
'api_server_username': 'freqtrader',
'api_server_password': 'MoneyMachine',
}
mocker.patch('freqtrade.commands.build_config_commands.ask_user_config',
return_value=sample_selections)

View File

@ -10,7 +10,7 @@ from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_forma
trades_to_ohlcv, trim_dataframe)
from freqtrade.data.history import (get_timerange, load_data, load_pair_history,
validate_backtest_data)
from tests.conftest import log_has
from tests.conftest import log_has, log_has_re
from tests.data.test_history import _backup_file, _clean_test_file
@ -62,8 +62,8 @@ def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
# Column names should not change
assert (data.columns == data2.columns).all()
assert log_has(f"Missing data fillup for UNITTEST/BTC: before: "
f"{len(data)} - after: {len(data2)}", caplog)
assert log_has_re(f"Missing data fillup for UNITTEST/BTC: before: "
f"{len(data)} - after: {len(data2)}.*", caplog)
# Test fillup actually fixes invalid backtest data
min_date, max_date = get_timerange({'UNITTEST/BTC': data})
@ -125,8 +125,8 @@ def test_ohlcv_fill_up_missing_data2(caplog):
# Column names should not change
assert (data.columns == data2.columns).all()
assert log_has(f"Missing data fillup for UNITTEST/BTC: before: "
f"{len(data)} - after: {len(data2)}", caplog)
assert log_has_re(f"Missing data fillup for UNITTEST/BTC: before: "
f"{len(data)} - after: {len(data2)}.*", caplog)
def test_ohlcv_drop_incomplete(caplog):
@ -197,6 +197,16 @@ def test_trim_dataframe(testdatadir) -> None:
assert all(data_modify.iloc[-1] == data.iloc[-1])
assert all(data_modify.iloc[0] == data.iloc[30])
data_modify = data.copy()
tr = TimeRange('date', None, min_date + 1800, 0)
# Remove first 20 candles - ignores min date
data_modify = trim_dataframe(data_modify, tr, startup_candles=20)
assert not data_modify.equals(data)
assert len(data_modify) < len(data)
assert len(data_modify) == len(data) - 20
assert all(data_modify.iloc[-1] == data.iloc[-1])
assert all(data_modify.iloc[0] == data.iloc[20])
data_modify = data.copy()
# Remove last 30 minutes (1800 s)
tr = TimeRange(None, 'date', 0, max_date - 1800)

View File

@ -266,7 +266,7 @@ def test_edge_heartbeat_calculate(mocker, edge_conf):
# should not recalculate if heartbeat not reached
edge._last_updated = arrow.utcnow().int_timestamp - heartbeat + 1
assert edge.calculate() is False
assert edge.calculate(edge_conf['exchange']['pair_whitelist']) is False
def mocked_load_data(datadir, pairs=[], timeframe='0m',
@ -310,7 +310,7 @@ def test_edge_process_downloaded_data(mocker, edge_conf):
mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert edge.calculate()
assert edge.calculate(edge_conf['exchange']['pair_whitelist'])
assert len(edge._cached_pairs) == 2
assert edge._last_updated <= arrow.utcnow().int_timestamp + 2
@ -322,7 +322,7 @@ def test_edge_process_no_data(mocker, edge_conf, caplog):
mocker.patch('freqtrade.edge.edge_positioning.load_data', MagicMock(return_value={}))
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert not edge.calculate()
assert not edge.calculate(edge_conf['exchange']['pair_whitelist'])
assert len(edge._cached_pairs) == 0
assert log_has("No data found. Edge is stopped ...", caplog)
assert edge._last_updated == 0
@ -337,7 +337,7 @@ def test_edge_process_no_trades(mocker, edge_conf, caplog):
mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', MagicMock(return_value=[]))
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert not edge.calculate()
assert not edge.calculate(edge_conf['exchange']['pair_whitelist'])
assert len(edge._cached_pairs) == 0
assert log_has("No trades found.", caplog)

View File

@ -268,7 +268,7 @@ tc16 = BTContainer(data=[
# Test 17: Buy, hold for 120 mins, then forcesell using roi=-1
# Causes negative profit even though sell-reason is ROI.
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration)
# Uses open as sell-rate (special case) - since the roi-time is a multiple of the ticker interval.
# Uses open as sell-rate (special case) - since the roi-time is a multiple of the timeframe.
tc17 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],

View File

@ -92,6 +92,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'profit_ratio': -0.00408133,
'profit_pct': -0.41,
'profit_abs': -4.09e-06,
'profit_fiat': ANY,
'stop_loss_abs': 9.882e-06,
'stop_loss_pct': -10.0,
'stop_loss_ratio': -0.1,
@ -159,6 +160,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'profit_ratio': ANY,
'profit_pct': ANY,
'profit_abs': ANY,
'profit_fiat': ANY,
'stop_loss_abs': 9.882e-06,
'stop_loss_pct': -10.0,
'stop_loss_ratio': -0.1,

View File

@ -786,6 +786,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'profit_ratio': -0.00408133,
'profit_pct': -0.41,
'profit_abs': -4.09e-06,
'profit_fiat': ANY,
'current_rate': 1.099e-05,
'open_date': ANY,
'open_date_hum': 'just now',
@ -965,6 +966,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'profit_ratio': None,
'profit_pct': None,
'profit_abs': None,
'profit_fiat': None,
'fee_close': 0.0025,
'fee_close_cost': None,
'fee_close_currency': None,

View File

@ -28,7 +28,7 @@ class DefaultStrategy(IStrategy):
# Optimal stoploss designed for the strategy
stoploss = -0.10
# Optimal ticker interval for the strategy
# Optimal timeframe for the strategy
timeframe = '5m'
# Optional order type mapping

View File

@ -31,7 +31,7 @@ class TestStrategyLegacy(IStrategy):
# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.10
# Optimal ticker interval for the strategy
# Optimal timeframe for the strategy
# Keep the legacy value here to test compatibility
ticker_interval = '5m'

View File

@ -860,22 +860,6 @@ def test_validate_tsl(default_conf):
validate_config_consistency(default_conf)
def test_validate_edge(edge_conf):
edge_conf.update({"pairlist": {
"method": "VolumePairList",
}})
with pytest.raises(OperationalException,
match="Edge and VolumePairList are incompatible, "
"Edge will override whatever pairs VolumePairlist selects."):
validate_config_consistency(edge_conf)
edge_conf.update({"pairlist": {
"method": "StaticPairList",
}})
validate_config_consistency(edge_conf)
def test_validate_edge2(edge_conf):
edge_conf.update({"ask_strategy": {
"use_sell_signal": True,