Merge branch 'develop' into fix/sell_order_hanging

This commit is contained in:
Matthias 2019-08-15 06:46:12 +02:00
commit 9f26c4ebdc
34 changed files with 550 additions and 272 deletions

View File

@ -2,7 +2,7 @@
This page explains the different parameters of the bot and how to run it. This page explains the different parameters of the bot and how to run it.
!Note: !!! Note:
If you've used `setup.sh`, don't forget to activate your virtual environment (`source .env/bin/activate`) before running freqtrade commands. If you've used `setup.sh`, don't forget to activate your virtual environment (`source .env/bin/activate`) before running freqtrade commands.
@ -43,20 +43,23 @@ optional arguments:
--sd-notify Notify systemd service manager. --sd-notify Notify systemd service manager.
``` ```
### How to use a different configuration file? ### How to specify which configuration file be used?
The bot allows you to select which configuration file you want to use. Per The bot allows you to select which configuration file you want to use by means of
default, the bot will load the file `./config.json` the `-c/--config` command line option:
```bash ```bash
freqtrade -c path/far/far/away/config.json freqtrade -c path/far/far/away/config.json
``` ```
Per default, the bot loads the `config.json` configuration file from the current
working directory.
### How to use multiple configuration files? ### How to use multiple configuration files?
The bot allows you to use multiple configuration files by specifying multiple The bot allows you to use multiple configuration files by specifying multiple
`-c/--config` configuration options in the command line. Configuration parameters `-c/--config` options in the command line. Configuration parameters
defined in latter configuration files override parameters with the same name defined in the latter configuration files override parameters with the same name
defined in the previous configuration files specified in the command line earlier. defined in the previous configuration files specified in the command line earlier.
For example, you can make a separate configuration file with your key and secrete For example, you can make a separate configuration file with your key and secrete
@ -207,7 +210,7 @@ usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--customhyperopt NAME] [--hyperopt-path PATH] [--customhyperopt NAME] [--hyperopt-path PATH]
[--eps] [-e INT] [--eps] [-e INT]
[-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]] [-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
[--dmmp] [--print-all] [-j JOBS] [--dmmp] [--print-all] [--no-color] [-j JOBS]
[--random-state INT] [--min-trades INT] [--continue] [--random-state INT] [--min-trades INT] [--continue]
[--hyperopt-loss NAME] [--hyperopt-loss NAME]
@ -243,6 +246,8 @@ optional arguments:
(same as setting `max_open_trades` to a very high (same as setting `max_open_trades` to a very high
number). number).
--print-all Print all results, not only the best ones. --print-all Print all results, not only the best ones.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
-j JOBS, --job-workers JOBS -j JOBS, --job-workers JOBS
The number of concurrently running jobs for The number of concurrently running jobs for
hyperoptimization (hyperopt worker processes). If -1 hyperoptimization (hyperopt worker processes). If -1
@ -267,7 +272,7 @@ optional arguments:
## Edge commands ## Edge commands
To know your trade expectacny and winrate against historical data, you can use Edge. To know your trade expectancy and winrate against historical data, you can use Edge.
``` ```
usage: freqtrade edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] usage: freqtrade edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]

View File

@ -1,15 +1,34 @@
# Configure the bot # Configure the bot
This page explains how to configure your `config.json` file. This page explains how to configure the bot.
## Setup config.json ## The Freqtrade configuration file
We recommend to copy and use the `config.json.example` as a template The bot uses a set of configuration parameters during its operation that all together conform the bot configuration. It normally reads its configuration from a file (Freqtrade configuration file).
Per default, the bot loads configuration from the `config.json` file located in the current working directory.
You can change the name of the configuration file used by the bot with the `-c/--config` command line option.
In some advanced use cases, multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream.
If you used the [Quick start](installation.md/#quick-start) method for installing
the bot, the installation script should have already created the default configuration file (`config.json`) for you.
If default configuration file is not created we recommend you to copy and use the `config.json.example` as a template
for your bot configuration. for your bot configuration.
The table below will list all configuration parameters. The Freqtrade configuration file is to be written in the JSON format.
Mandatory Parameters are marked as **Required**. Additionally to the standard JSON syntax, you may use one-line `// ...` and multi-line `/* ... */` comments in your configuration files and trailing commas in the lists of parameters.
Do not worry if you are not familiar with JSON format -- simply open the configuration file with an editor of your choice, make some changes to the parameters you need, save your changes and, finally, restart the bot or, if it was previously stopped, run it again with the changes you made to the configuration. The bot validates syntax of the configuration file at startup and will warn you if you made any errors editing it.
## Configuration parameters
The table below will list all configuration parameters available.
Mandatory parameters are marked as **Required**.
| Command | Default | Description | | Command | Default | Description |
|----------|---------|-------------| |----------|---------|-------------|
@ -53,6 +72,7 @@ Mandatory Parameters are marked as **Required**.
| `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). | `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
| `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). | `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
| `experimental.ignore_roi_if_buy_signal` | false | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). | `experimental.ignore_roi_if_buy_signal` | false | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
| `experimental.block_bad_exchanges` | true | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
| `pairlist.method` | StaticPairList | Use static or dynamic volume-based pairlist. [More information below](#dynamic-pairlists). | `pairlist.method` | StaticPairList | Use static or dynamic volume-based pairlist. [More information below](#dynamic-pairlists).
| `pairlist.config` | None | Additional configuration for dynamic pairlists. [More information below](#dynamic-pairlists). | `pairlist.config` | None | Additional configuration for dynamic pairlists. [More information below](#dynamic-pairlists).
| `telegram.enabled` | true | **Required.** Enable or not the usage of Telegram. | `telegram.enabled` | true | **Required.** Enable or not the usage of Telegram.

View File

@ -352,6 +352,10 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
return dataframe return dataframe
``` ```
By default, hyperopt prints colorized results -- epochs with positive profit are printed in the green color. This highlighting helps you find epochs that can be interesting for later analysis. Epochs with zero total profit or with negative profits (losses) are printed in the normal color. If you do not need colorization of results (for instance, when you are redirecting hyperopt output to a file) you can switch colorization off by specifying the `--no-color` option in the command line.
You can use the `--print-all` command line option if you would like to see all results in the hyperopt output, not only the best ones. When `--print-all` is used, current best results are also colorized by default -- they are printed in bold (bright) style. This can also be switched off with the `--no-color` command line option.
### Understand Hyperopt ROI results ### Understand Hyperopt ROI results
If you are optimizing ROI (i.e. if optimization search-space contains 'all' or 'roi'), your result will look as follows and include a ROI table: If you are optimizing ROI (i.e. if optimization search-space contains 'all' or 'roi'), your result will look as follows and include a ROI table:

View File

@ -1,2 +1,3 @@
from freqtrade.configuration.arguments import Arguments, TimeRange # noqa: F401 from freqtrade.configuration.arguments import Arguments # noqa: F401
from freqtrade.configuration.timerange import TimeRange # noqa: F401
from freqtrade.configuration.configuration import Configuration # noqa: F401 from freqtrade.configuration.configuration import Configuration # noqa: F401

View File

@ -2,10 +2,8 @@
This module contains the argument manager class This module contains the argument manager class
""" """
import argparse import argparse
import re from typing import List, Optional
from typing import List, NamedTuple, Optional
import arrow
from freqtrade.configuration.cli_options import AVAILABLE_CLI_OPTIONS from freqtrade.configuration.cli_options import AVAILABLE_CLI_OPTIONS
from freqtrade import constants from freqtrade import constants
@ -23,7 +21,8 @@ ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_pos
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
"position_stacking", "epochs", "spaces", "position_stacking", "epochs", "spaces",
"use_max_market_positions", "print_all", "hyperopt_jobs", "use_max_market_positions", "print_all",
"print_colorized", "hyperopt_jobs",
"hyperopt_random_state", "hyperopt_min_trades", "hyperopt_random_state", "hyperopt_min_trades",
"hyperopt_continue", "hyperopt_loss"] "hyperopt_continue", "hyperopt_loss"]
@ -42,18 +41,6 @@ ARGS_PLOT_PROFIT = (ARGS_COMMON + ARGS_STRATEGY +
["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source"]) ["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source"])
class TimeRange(NamedTuple):
"""
NamedTuple defining timerange inputs.
[start/stop]type defines if [start/stop]ts shall be used.
if *type is None, don't use corresponding startvalue.
"""
starttype: Optional[str] = None
stoptype: Optional[str] = None
startts: int = 0
stopts: int = 0
class Arguments(object): class Arguments(object):
""" """
Arguments Class. Manage the arguments received by the cli Arguments Class. Manage the arguments received by the cli
@ -132,45 +119,3 @@ class Arguments(object):
) )
list_exchanges_cmd.set_defaults(func=start_list_exchanges) list_exchanges_cmd.set_defaults(func=start_list_exchanges)
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd) self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
@staticmethod
def parse_timerange(text: Optional[str]) -> TimeRange:
"""
Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange
:return: Start and End range period
"""
if text is None:
return TimeRange(None, None, 0, 0)
syntax = [(r'^-(\d{8})$', (None, 'date')),
(r'^(\d{8})-$', ('date', None)),
(r'^(\d{8})-(\d{8})$', ('date', 'date')),
(r'^-(\d{10})$', (None, 'date')),
(r'^(\d{10})-$', ('date', None)),
(r'^(\d{10})-(\d{10})$', ('date', 'date')),
(r'^(-\d+)$', (None, 'line')),
(r'^(\d+)-$', ('line', None)),
(r'^(\d+)-(\d+)$', ('index', 'index'))]
for rex, stype in syntax:
# Apply the regular expression to text
match = re.match(rex, text)
if match: # Regex has matched
rvals = match.groups()
index = 0
start: int = 0
stop: int = 0
if stype[0]:
starts = rvals[index]
if stype[0] == 'date' and len(starts) == 8:
start = arrow.get(starts, 'YYYYMMDD').timestamp
else:
start = int(starts)
index += 1
if stype[1]:
stops = rvals[index]
if stype[1] == 'date' and len(stops) == 8:
stop = arrow.get(stops, 'YYYYMMDD').timestamp
else:
stop = int(stops)
return TimeRange(stype[0], stype[1], start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text)

View File

@ -2,9 +2,9 @@ import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade import OperationalException from freqtrade import OperationalException
from freqtrade.exchange import (is_exchange_bad, is_exchange_available, from freqtrade.exchange import (available_exchanges, get_exchange_bad_reason,
is_exchange_officially_supported, available_exchanges) is_exchange_available, is_exchange_bad,
is_exchange_officially_supported)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -31,9 +31,8 @@ def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
) )
if check_for_bad and is_exchange_bad(exchange): if check_for_bad and is_exchange_bad(exchange):
logger.warning(f'Exchange "{exchange}" is known to not work with the bot yet. ' raise OperationalException(f'Exchange "{exchange}" is known to not work with the bot yet. '
f'Use it only for development and testing purposes.') f'Reason: {get_exchange_bad_reason(exchange)}')
return False
if is_exchange_officially_supported(exchange): if is_exchange_officially_supported(exchange):
logger.info(f'Exchange "{exchange}" is officially supported ' logger.info(f'Exchange "{exchange}" is officially supported '

View File

@ -191,6 +191,13 @@ AVAILABLE_CLI_OPTIONS = {
action='store_true', action='store_true',
default=False, default=False,
), ),
"print_colorized": Arg(
'--no-color',
help='Disable colorization of hyperopt results. May be useful if you are '
'redirecting output to a file.',
action='store_false',
default=True,
),
"hyperopt_jobs": Arg( "hyperopt_jobs": Arg(
'-j', '--job-workers', '-j', '--job-workers',
help='The number of concurrently running jobs for hyperoptimization ' help='The number of concurrently running jobs for hyperoptimization '

View File

@ -148,7 +148,7 @@ class Configuration(object):
config['internals'].update({'sd_notify': True}) config['internals'].update({'sd_notify': True})
# Check if the exchange set by the user is supported # Check if the exchange set by the user is supported
check_exchange(config) check_exchange(config, config.get('experimental', {}).get('block_bad_exchanges', True))
def _process_datadir_options(self, config: Dict[str, Any]) -> None: def _process_datadir_options(self, config: Dict[str, Any]) -> None:
""" """
@ -236,6 +236,12 @@ class Configuration(object):
self._args_to_config(config, argname='print_all', self._args_to_config(config, argname='print_all',
logstring='Parameter --print-all detected ...') logstring='Parameter --print-all detected ...')
if 'print_colorized' in self.args and not self.args.print_colorized:
logger.info('Parameter --no-color detected ...')
config.update({'print_colorized': False})
else:
config.update({'print_colorized': True})
self._args_to_config(config, argname='hyperopt_jobs', self._args_to_config(config, argname='hyperopt_jobs',
logstring='Parameter -j/--job-workers detected: {}') logstring='Parameter -j/--job-workers detected: {}')

View File

@ -1,7 +1,7 @@
""" """
This module contain functions to load the configuration file This module contain functions to load the configuration file
""" """
import json import rapidjson
import logging import logging
import sys import sys
from typing import Any, Dict from typing import Any, Dict
@ -12,6 +12,9 @@ from freqtrade import OperationalException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
CONFIG_PARSE_MODE = rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS
def load_config_file(path: str) -> Dict[str, Any]: def load_config_file(path: str) -> Dict[str, Any]:
""" """
Loads a config file from the given path Loads a config file from the given path
@ -21,7 +24,7 @@ def load_config_file(path: str) -> Dict[str, Any]:
try: try:
# Read config from stdin if requested in the options # Read config from stdin if requested in the options
with open(path) if path != '-' else sys.stdin as file: with open(path) if path != '-' else sys.stdin as file:
config = json.load(file) config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
except FileNotFoundError: except FileNotFoundError:
raise OperationalException( raise OperationalException(
f'Config file "{path}" not found!' f'Config file "{path}" not found!'

View File

@ -0,0 +1,70 @@
"""
This module contains the argument manager class
"""
import re
from typing import Optional
import arrow
class TimeRange():
"""
object defining timerange inputs.
[start/stop]type defines if [start/stop]ts shall be used.
if *type is None, don't use corresponding startvalue.
"""
def __init__(self, starttype: Optional[str] = None, stoptype: Optional[str] = None,
startts: int = 0, stopts: int = 0):
self.starttype: Optional[str] = starttype
self.stoptype: Optional[str] = stoptype
self.startts: int = startts
self.stopts: int = stopts
def __eq__(self, other):
"""Override the default Equals behavior"""
return (self.starttype == other.starttype and self.stoptype == other.stoptype
and self.startts == other.startts and self.stopts == other.stopts)
@staticmethod
def parse_timerange(text: Optional[str]):
"""
Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange
:return: Start and End range period
"""
if text is None:
return TimeRange(None, None, 0, 0)
syntax = [(r'^-(\d{8})$', (None, 'date')),
(r'^(\d{8})-$', ('date', None)),
(r'^(\d{8})-(\d{8})$', ('date', 'date')),
(r'^-(\d{10})$', (None, 'date')),
(r'^(\d{10})-$', ('date', None)),
(r'^(\d{10})-(\d{10})$', ('date', 'date')),
(r'^(-\d+)$', (None, 'line')),
(r'^(\d+)-$', ('line', None)),
(r'^(\d+)-(\d+)$', ('index', 'index'))]
for rex, stype in syntax:
# Apply the regular expression to text
match = re.match(rex, text)
if match: # Regex has matched
rvals = match.groups()
index = 0
start: int = 0
stop: int = 0
if stype[0]:
starts = rvals[index]
if stype[0] == 'date' and len(starts) == 8:
start = arrow.get(starts, 'YYYYMMDD').timestamp
else:
start = int(starts)
index += 1
if stype[1]:
stops = rvals[index]
if stype[1] == 'date' and len(stops) == 8:
stop = arrow.get(stops, 'YYYYMMDD').timestamp
else:
stop = int(stops)
return TimeRange(stype[0], stype[1], start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text)

View File

@ -10,7 +10,7 @@ import utils_find_1st as utf1st
from pandas import DataFrame from pandas import DataFrame
from freqtrade import constants, OperationalException from freqtrade import constants, OperationalException
from freqtrade.configuration import Arguments, TimeRange from freqtrade.configuration import TimeRange
from freqtrade.data import history from freqtrade.data import history
from freqtrade.strategy.interface import SellType from freqtrade.strategy.interface import SellType
@ -75,7 +75,7 @@ class Edge():
self._stoploss_range_step self._stoploss_range_step
) )
self._timerange: TimeRange = Arguments.parse_timerange("%s-" % arrow.now().shift( self._timerange: TimeRange = TimeRange.parse_timerange("%s-" % arrow.now().shift(
days=-1 * self._since_number_of_days).format('YYYYMMDD')) days=-1 * self._since_number_of_days).format('YYYYMMDD'))
self.fee = self.exchange.get_fee() self.fee = self.exchange.get_fee()

View File

@ -1,5 +1,6 @@
from freqtrade.exchange.exchange import Exchange # noqa: F401 from freqtrade.exchange.exchange import Exchange # noqa: F401
from freqtrade.exchange.exchange import (is_exchange_bad, # noqa: F401 from freqtrade.exchange.exchange import (get_exchange_bad_reason, # noqa: F401
is_exchange_bad,
is_exchange_available, is_exchange_available,
is_exchange_officially_supported, is_exchange_officially_supported,
available_exchanges) available_exchanges)

View File

@ -25,6 +25,11 @@ logger = logging.getLogger(__name__)
API_RETRY_COUNT = 4 API_RETRY_COUNT = 4
BAD_EXCHANGES = {
"bitmex": "Various reasons",
"bitstamp": "Does not provide history. "
"Details in https://github.com/freqtrade/freqtrade/issues/1983",
}
def retrier_async(f): def retrier_async(f):
@ -755,7 +760,11 @@ class Exchange(object):
def is_exchange_bad(exchange: str) -> bool: def is_exchange_bad(exchange: str) -> bool:
return exchange in ['bitmex', 'bitstamp'] return exchange in BAD_EXCHANGES
def get_exchange_bad_reason(exchange: str) -> str:
return BAD_EXCHANGES.get(exchange, "")
def is_exchange_available(exchange: str, ccxt_module=None) -> bool: def is_exchange_available(exchange: str, ccxt_module=None) -> bool:

View File

@ -105,13 +105,12 @@ class FreqtradeBot(object):
# Adjust stoploss if it was changed # Adjust stoploss if it was changed
Trade.stoploss_reinitialization(self.strategy.stoploss) Trade.stoploss_reinitialization(self.strategy.stoploss)
def process(self) -> bool: def process(self) -> None:
""" """
Queries the persistence layer for open trades and handles them, Queries the persistence layer for open trades and handles them,
otherwise a new trade is created. otherwise a new trade is created.
:return: True if one or more trades has been created or closed, False otherwise :return: True if one or more trades has been created or closed, False otherwise
""" """
state_changed = False
# Check whether markets have to be reloaded # Check whether markets have to be reloaded
self.exchange._reload_markets() self.exchange._reload_markets()
@ -138,19 +137,17 @@ class FreqtradeBot(object):
# First process current opened trades # First process current opened trades
for trade in trades: for trade in trades:
state_changed |= self.process_maybe_execute_sell(trade) self.process_maybe_execute_sell(trade)
# Then looking for buy opportunities # Then looking for buy opportunities
if len(trades) < self.config['max_open_trades']: if len(trades) < self.config['max_open_trades']:
state_changed = self.process_maybe_execute_buy() self.process_maybe_execute_buy()
if 'unfilledtimeout' in self.config: if 'unfilledtimeout' in self.config:
# Check and handle any timed out open orders # Check and handle any timed out open orders
self.check_handle_timedout() self.check_handle_timedout()
Trade.session.flush() Trade.session.flush()
return state_changed
def _extend_whitelist_with_trades(self, whitelist: List[str], trades: List[Any]): def _extend_whitelist_with_trades(self, whitelist: List[str], trades: List[Any]):
""" """
Extend whitelist with pairs from open trades Extend whitelist with pairs from open trades
@ -259,11 +256,12 @@ class FreqtradeBot(object):
amount_reserve_percent = max(amount_reserve_percent, 0.5) amount_reserve_percent = max(amount_reserve_percent, 0.5)
return min(min_stake_amounts) / amount_reserve_percent return min(min_stake_amounts) / amount_reserve_percent
def create_trade(self) -> bool: def create_trades(self) -> bool:
""" """
Checks the implemented trading indicator(s) for a randomly picked pair, Checks the implemented trading strategy for buy-signals, using the active pair whitelist.
if one pair triggers the buy_signal a new trade record gets created If a pair triggers the buy_signal a new trade record gets created.
:return: True if a trade object has been created and persisted, False otherwise Checks pairs as long as the open trade count is below `max_open_trades`.
:return: True if at least one trade has been created.
""" """
interval = self.strategy.ticker_interval interval = self.strategy.ticker_interval
whitelist = copy.deepcopy(self.active_pair_whitelist) whitelist = copy.deepcopy(self.active_pair_whitelist)
@ -282,15 +280,16 @@ class FreqtradeBot(object):
logger.info("No currency pair in whitelist, but checking to sell open trades.") logger.info("No currency pair in whitelist, but checking to sell open trades.")
return False return False
buycount = 0
# running get_signal on historical data fetched # running get_signal on historical data fetched
for _pair in whitelist: for _pair in whitelist:
(buy, sell) = self.strategy.get_signal( (buy, sell) = self.strategy.get_signal(
_pair, interval, self.dataprovider.ohlcv(_pair, self.strategy.ticker_interval)) _pair, interval, self.dataprovider.ohlcv(_pair, self.strategy.ticker_interval))
if buy and not sell: if buy and not sell and len(Trade.get_open_trades()) < self.config['max_open_trades']:
stake_amount = self._get_trade_stake_amount(_pair) stake_amount = self._get_trade_stake_amount(_pair)
if not stake_amount: if not stake_amount:
return False continue
logger.info(f"Buy signal found: about create a new trade with stake_amount: " logger.info(f"Buy signal found: about create a new trade with stake_amount: "
f"{stake_amount} ...") f"{stake_amount} ...")
@ -300,12 +299,13 @@ class FreqtradeBot(object):
if (bidstrat_check_depth_of_market.get('enabled', False)) and\ if (bidstrat_check_depth_of_market.get('enabled', False)) and\
(bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0): (bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0):
if self._check_depth_of_market_buy(_pair, bidstrat_check_depth_of_market): if self._check_depth_of_market_buy(_pair, bidstrat_check_depth_of_market):
return self.execute_buy(_pair, stake_amount) buycount += self.execute_buy(_pair, stake_amount)
else: else:
return False continue
return self.execute_buy(_pair, stake_amount)
return False buycount += self.execute_buy(_pair, stake_amount)
return buycount > 0
def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool:
""" """
@ -429,21 +429,17 @@ class FreqtradeBot(object):
return True return True
def process_maybe_execute_buy(self) -> bool: def process_maybe_execute_buy(self) -> None:
""" """
Tries to execute a buy trade in a safe way Tries to execute a buy trade in a safe way
:return: True if executed :return: True if executed
""" """
try: try:
# Create entity and execute trade # Create entity and execute trade
if self.create_trade(): if not self.create_trades():
return True logger.info('Found no buy signals for whitelisted currencies. Trying again...')
logger.info('Found no buy signals for whitelisted currencies. Trying again..')
return False
except DependencyException as exception: except DependencyException as exception:
logger.warning('Unable to create trade: %s', exception) logger.warning('Unable to create trade: %s', exception)
return False
def process_maybe_execute_sell(self, trade: Trade) -> bool: def process_maybe_execute_sell(self, trade: Trade) -> bool:
""" """

View File

@ -12,7 +12,7 @@ from typing import Any, Dict, List, NamedTuple, Optional
from pandas import DataFrame from pandas import DataFrame
from freqtrade import OperationalException from freqtrade import OperationalException
from freqtrade.configuration import Arguments from freqtrade.configuration import TimeRange
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
@ -404,7 +404,7 @@ class Backtesting(object):
logger.info('Using stake_currency: %s ...', self.config['stake_currency']) logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
logger.info('Using stake_amount: %s ...', self.config['stake_amount']) logger.info('Using stake_amount: %s ...', self.config['stake_amount'])
timerange = Arguments.parse_timerange(None if self.config.get( timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange'))) 'timerange') is None else str(self.config.get('timerange')))
data = history.load_data( data = history.load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None, datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,

View File

@ -9,7 +9,7 @@ from tabulate import tabulate
from freqtrade import constants from freqtrade import constants
from freqtrade.edge import Edge from freqtrade.edge import Edge
from freqtrade.configuration import Arguments from freqtrade.configuration import TimeRange
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
@ -41,7 +41,7 @@ class EdgeCli(object):
self.edge = Edge(config, self.exchange, self.strategy) self.edge = Edge(config, self.exchange, self.strategy)
self.edge._refresh_pairs = self.config.get('refresh_pairs', False) self.edge._refresh_pairs = self.config.get('refresh_pairs', False)
self.timerange = Arguments.parse_timerange(None if self.config.get( self.timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange'))) 'timerange') is None else str(self.config.get('timerange')))
self.edge._timerange = self.timerange self.edge._timerange = self.timerange

View File

@ -13,12 +13,14 @@ from pathlib import Path
from pprint import pprint from pprint import pprint
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
from colorama import init as colorama_init
from colorama import Fore, Style
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects, cpu_count from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects, cpu_count
from pandas import DataFrame from pandas import DataFrame
from skopt import Optimizer from skopt import Optimizer
from skopt.space import Dimension from skopt.space import Dimension
from freqtrade.configuration import Arguments from freqtrade.configuration import TimeRange
from freqtrade.data.history import load_data, get_timeframe from freqtrade.data.history import load_data, get_timeframe
from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.backtesting import Backtesting
# Import IHyperOptLoss to allow users import from this file # Import IHyperOptLoss to allow users import from this file
@ -153,8 +155,17 @@ class Hyperopt(Backtesting):
Log results if it is better than any previous evaluation Log results if it is better than any previous evaluation
""" """
print_all = self.config.get('print_all', False) print_all = self.config.get('print_all', False)
if print_all or results['loss'] < self.current_best_loss: is_best_loss = results['loss'] < self.current_best_loss
if print_all or is_best_loss:
if is_best_loss:
self.current_best_loss = results['loss']
log_str = self.format_results_logstring(results) log_str = self.format_results_logstring(results)
# Colorize output
if self.config.get('print_colorized', False):
if results['total_profit'] > 0:
log_str = Fore.GREEN + log_str
if print_all and is_best_loss:
log_str = Style.BRIGHT + log_str
if print_all: if print_all:
print(log_str) print(log_str)
else: else:
@ -169,7 +180,6 @@ class Hyperopt(Backtesting):
total = self.total_epochs total = self.total_epochs
res = results['results_explanation'] res = results['results_explanation']
loss = results['loss'] loss = results['loss']
self.current_best_loss = results['loss']
log_str = f'{current:5d}/{total}: {res} Objective: {loss:.5f}' log_str = f'{current:5d}/{total}: {res} Objective: {loss:.5f}'
log_str = f'*{log_str}' if results['is_initial_point'] else f' {log_str}' log_str = f'*{log_str}' if results['is_initial_point'] else f' {log_str}'
return log_str return log_str
@ -237,6 +247,7 @@ class Hyperopt(Backtesting):
results_explanation = self.format_results(results) results_explanation = self.format_results(results)
trade_count = len(results.index) trade_count = len(results.index)
total_profit = results.profit_abs.sum()
# If this evaluation contains too short amount of trades to be # If this evaluation contains too short amount of trades to be
# interesting -- consider it as 'bad' (assigned max. loss value) # interesting -- consider it as 'bad' (assigned max. loss value)
@ -247,6 +258,7 @@ class Hyperopt(Backtesting):
'loss': MAX_LOSS, 'loss': MAX_LOSS,
'params': params, 'params': params,
'results_explanation': results_explanation, 'results_explanation': results_explanation,
'total_profit': total_profit,
} }
loss = self.calculate_loss(results=results, trade_count=trade_count, loss = self.calculate_loss(results=results, trade_count=trade_count,
@ -256,6 +268,7 @@ class Hyperopt(Backtesting):
'loss': loss, 'loss': loss,
'params': params, 'params': params,
'results_explanation': results_explanation, 'results_explanation': results_explanation,
'total_profit': total_profit,
} }
def format_results(self, results: DataFrame) -> str: def format_results(self, results: DataFrame) -> str:
@ -297,7 +310,7 @@ class Hyperopt(Backtesting):
) )
def start(self) -> None: def start(self) -> None:
timerange = Arguments.parse_timerange(None if self.config.get( timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange'))) 'timerange') is None else str(self.config.get('timerange')))
data = load_data( data = load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None, datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
@ -339,6 +352,10 @@ class Hyperopt(Backtesting):
logger.info(f'Number of parallel jobs set as: {config_jobs}') logger.info(f'Number of parallel jobs set as: {config_jobs}')
opt = self.get_optimizer(config_jobs) opt = self.get_optimizer(config_jobs)
if self.config.get('print_colorized', False):
colorama_init(autoreset=True)
try: try:
with Parallel(n_jobs=config_jobs) as parallel: with Parallel(n_jobs=config_jobs) as parallel:
jobs = parallel._effective_n_jobs() jobs = parallel._effective_n_jobs()

View File

@ -4,7 +4,7 @@ from typing import Dict, List, Optional
import pandas as pd import pandas as pd
from freqtrade.configuration import Arguments from freqtrade.configuration import TimeRange
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.btanalysis import (combine_tickers_with_mean, from freqtrade.data.btanalysis import (combine_tickers_with_mean,
create_cum_profit, load_trades) create_cum_profit, load_trades)
@ -42,7 +42,7 @@ def init_plotscript(config):
pairs = config["exchange"]["pair_whitelist"] pairs = config["exchange"]["pair_whitelist"]
# Set timerange to use # Set timerange to use
timerange = Arguments.parse_timerange(config.get("timerange")) timerange = TimeRange.parse_timerange(config.get("timerange"))
tickers = history.load_data( tickers = history.load_data(
datadir=Path(str(config.get("datadir"))), datadir=Path(str(config.get("datadir"))),

View File

@ -0,0 +1,133 @@
{
/* Single-line C-style comment */
"max_open_trades": 3,
/*
* Multi-line C-style comment
*/
"stake_currency": "BTC",
"stake_amount": 0.05,
"fiat_display_currency": "USD", // C++-style comment
"amount_reserve_percent" : 0.05, // And more, tabs before this comment
"dry_run": false,
"ticker_interval": "5m",
"trailing_stop": false,
"trailing_stop_positive": 0.005,
"trailing_stop_positive_offset": 0.0051,
"trailing_only_offset_is_reached": false,
"minimal_roi": {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
},
"stoploss": -0.10,
"unfilledtimeout": {
"buy": 10,
"sell": 30, // Trailing comma should also be accepted now
},
"bid_strategy": {
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9
},
"order_types": {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
},
"order_time_in_force": {
"buy": "gtc",
"sell": "gtc"
},
"pairlist": {
"method": "VolumePairList",
"config": {
"number_assets": 20,
"sort_key": "quoteVolume",
"precision_filter": false
}
},
"exchange": {
"name": "bittrex",
"sandbox": false,
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"password": "",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false,
"rateLimit": 500,
"aiohttp_trust_env": false
},
"pair_whitelist": [
"ETH/BTC",
"LTC/BTC",
"ETC/BTC",
"DASH/BTC",
"ZEC/BTC",
"XLM/BTC",
"NXT/BTC",
"POWR/BTC",
"ADA/BTC",
"XMR/BTC"
],
"pair_blacklist": [
"DOGE/BTC"
],
"outdated_offset": 5,
"markets_refresh_interval": 60
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"capital_available_percentage": 0.5,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"experimental": {
"use_sell_signal": false,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
},
"telegram": {
// We can now comment out some settings
// "enabled": true,
"enabled": false,
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"username": "freqtrader",
"password": "SuperSecurePassword"
},
"db_url": "sqlite:///tradesv3.sqlite",
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
},
"strategy": "DefaultStrategy",
"strategy_path": "user_data/strategies/"
}

View File

@ -4,7 +4,7 @@ import pytest
from arrow import Arrow from arrow import Arrow
from pandas import DataFrame, to_datetime from pandas import DataFrame, to_datetime
from freqtrade.configuration import Arguments, TimeRange from freqtrade.configuration import TimeRange
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, from freqtrade.data.btanalysis import (BT_DATA_COLUMNS,
combine_tickers_with_mean, combine_tickers_with_mean,
create_cum_profit, create_cum_profit,
@ -121,7 +121,7 @@ def test_combine_tickers_with_mean():
def test_create_cum_profit(): def test_create_cum_profit():
filename = make_testdata_path(None) / "backtest-result_test.json" filename = make_testdata_path(None) / "backtest-result_test.json"
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
timerange = Arguments.parse_timerange("20180110-20180112") timerange = TimeRange.parse_timerange("20180110-20180112")
df = load_pair_history(pair="POWR/BTC", ticker_interval='5m', df = load_pair_history(pair="POWR/BTC", ticker_interval='5m',
datadir=None, timerange=timerange) datadir=None, timerange=timerange)

View File

@ -578,7 +578,8 @@ def test_generate_optimizer(mocker, default_conf) -> None:
'loss': 1.9840569076926293, 'loss': 1.9840569076926293,
'results_explanation': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC ' 'results_explanation': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC '
'( 2.31Σ%). Avg duration 100.0 mins.', '( 2.31Σ%). Avg duration 100.0 mins.',
'params': optimizer_param 'params': optimizer_param,
'total_profit': 0.00023300
} }
hyperopt = Hyperopt(default_conf) hyperopt = Hyperopt(default_conf)

View File

@ -44,7 +44,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
with pytest.raises(RPCException, match=r'.*no active trade*'): with pytest.raises(RPCException, match=r'.*no active trade*'):
rpc._rpc_trade_status() rpc._rpc_trade_status()
freqtradebot.create_trade() freqtradebot.create_trades()
results = rpc._rpc_trade_status() results = rpc._rpc_trade_status()
assert { assert {
'trade_id': 1, 'trade_id': 1,
@ -116,7 +116,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
with pytest.raises(RPCException, match=r'.*no active order*'): with pytest.raises(RPCException, match=r'.*no active order*'):
rpc._rpc_status_table() rpc._rpc_status_table()
freqtradebot.create_trade() freqtradebot.create_trades()
result = rpc._rpc_status_table() result = rpc._rpc_status_table()
assert 'instantly' in result['Since'].all() assert 'instantly' in result['Since'].all()
assert 'ETH/BTC' in result['Pair'].all() assert 'ETH/BTC' in result['Pair'].all()
@ -151,7 +151,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter()
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -208,7 +208,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -222,7 +222,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
trade.close_date = datetime.utcnow() trade.close_date = datetime.utcnow()
trade.is_open = False trade.is_open = False
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -292,7 +292,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -536,7 +536,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
msg = rpc._rpc_forcesell('all') msg = rpc._rpc_forcesell('all')
assert msg == {'result': 'Created sell orders for all open trades.'} assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.create_trade() freqtradebot.create_trades()
msg = rpc._rpc_forcesell('all') msg = rpc._rpc_forcesell('all')
assert msg == {'result': 'Created sell orders for all open trades.'} assert msg == {'result': 'Created sell orders for all open trades.'}
@ -570,7 +570,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert trade.amount == filled_amount assert trade.amount == filled_amount
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.filter(Trade.id == '2').first() trade = Trade.query.filter(Trade.id == '2').first()
amount = trade.amount amount = trade.amount
# make an limit-buy open trade, if there is no 'filled', don't sell it # make an limit-buy open trade, if there is no 'filled', don't sell it
@ -589,7 +589,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
assert cancel_order_mock.call_count == 2 assert cancel_order_mock.call_count == 2
assert trade.amount == amount assert trade.amount == amount
freqtradebot.create_trade() freqtradebot.create_trades()
# make an limit-sell open trade # make an limit-sell open trade
mocker.patch( mocker.patch(
'freqtrade.exchange.Exchange.get_order', 'freqtrade.exchange.Exchange.get_order',
@ -622,7 +622,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -660,7 +660,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
assert counts["current"] == 0 assert counts["current"] == 0
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
counts = rpc._rpc_count() counts = rpc._rpc_count()
assert counts["current"] == 1 assert counts["current"] == 1

View File

@ -275,7 +275,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets):
assert rc.json["max"] == 1.0 assert rc.json["max"] == 1.0
# Create some test data # Create some test data
ftbot.create_trade() ftbot.create_trades()
rc = client_get(client, f"{BASE_URI}/count") rc = client_get(client, f"{BASE_URI}/count")
assert_response(rc) assert_response(rc)
assert rc.json["current"] == 1.0 assert rc.json["current"] == 1.0
@ -329,7 +329,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li
assert len(rc.json) == 1 assert len(rc.json) == 1
assert rc.json == {"error": "Error querying _profit: no closed trade"} assert rc.json == {"error": "Error querying _profit: no closed trade"}
ftbot.create_trade() ftbot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
@ -418,7 +418,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
assert_response(rc, 502) assert_response(rc, 502)
assert rc.json == {'error': 'Error querying _status: no active trade'} assert rc.json == {'error': 'Error querying _status: no active trade'}
ftbot.create_trade() ftbot.create_trades()
rc = client_get(client, f"{BASE_URI}/status") rc = client_get(client, f"{BASE_URI}/status")
assert_response(rc) assert_response(rc)
assert len(rc.json) == 1 assert len(rc.json) == 1
@ -548,7 +548,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets):
assert_response(rc, 502) assert_response(rc, 502)
assert rc.json == {"error": "Error querying _forcesell: invalid argument"} assert rc.json == {"error": "Error querying _forcesell: invalid argument"}
ftbot.create_trade() ftbot.create_trades()
rc = client_post(client, f"{BASE_URI}/forcesell", rc = client_post(client, f"{BASE_URI}/forcesell",
data='{"tradeid": "1"}') data='{"tradeid": "1"}')

View File

@ -192,7 +192,7 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
# Create some test data # Create some test data
for _ in range(3): for _ in range(3):
freqtradebot.create_trade() freqtradebot.create_trades()
telegram._status(bot=MagicMock(), update=update) telegram._status(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
@ -240,7 +240,7 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
msg_mock.reset_mock() msg_mock.reset_mock()
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
# Trigger status while we have a fulfilled order for the open trade # Trigger status while we have a fulfilled order for the open trade
telegram._status(bot=MagicMock(), update=update) telegram._status(bot=MagicMock(), update=update)
@ -292,7 +292,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
msg_mock.reset_mock() msg_mock.reset_mock()
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
telegram._status_table(bot=MagicMock(), update=update) telegram._status_table(bot=MagicMock(), update=update)
@ -308,6 +308,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
limit_sell_order, markets, mocker) -> None: limit_sell_order, markets, mocker) -> None:
patch_exchange(mocker) patch_exchange(mocker)
default_conf['max_open_trades'] = 1
mocker.patch( mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
return_value=15000.0 return_value=15000.0
@ -331,7 +332,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -357,9 +358,9 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
# Reset msg_mock # Reset msg_mock
msg_mock.reset_mock() msg_mock.reset_mock()
freqtradebot.config['max_open_trades'] = 2
# Add two other trades # Add two other trades
freqtradebot.create_trade() freqtradebot.create_trades()
freqtradebot.create_trade()
trades = Trade.query.all() trades = Trade.query.all()
for trade in trades: for trade in trades:
@ -438,7 +439,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
msg_mock.reset_mock() msg_mock.reset_mock()
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
@ -733,7 +734,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -784,7 +785,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
# Decrease the price and sell it # Decrease the price and sell it
mocker.patch.multiple( mocker.patch.multiple(
@ -832,14 +833,13 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
markets=PropertyMock(return_value=markets), markets=PropertyMock(return_value=markets),
validate_pairs=MagicMock(return_value={}) validate_pairs=MagicMock(return_value={})
) )
default_conf['max_open_trades'] = 4
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False)) patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
for _ in range(4): freqtradebot.create_trades()
freqtradebot.create_trade()
rpc_mock.reset_mock() rpc_mock.reset_mock()
update.message.text = '/forcesell all' update.message.text = '/forcesell all'
@ -983,7 +983,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -1028,7 +1028,7 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
# Create some test data # Create some test data
freqtradebot.create_trade() freqtradebot.create_trades()
msg_mock.reset_mock() msg_mock.reset_mock()
telegram._count(bot=MagicMock(), update=update) telegram._count(bot=MagicMock(), update=update)

View File

@ -3,7 +3,7 @@ import argparse
import pytest import pytest
from freqtrade.configuration import Arguments, TimeRange from freqtrade.configuration import Arguments
from freqtrade.configuration.arguments import ARGS_DOWNLOADER, ARGS_PLOT_DATAFRAME from freqtrade.configuration.arguments import ARGS_DOWNLOADER, ARGS_PLOT_DATAFRAME
from freqtrade.configuration.cli_options import check_int_positive from freqtrade.configuration.cli_options import check_int_positive
@ -86,30 +86,6 @@ def test_parse_args_strategy_path_invalid() -> None:
Arguments(['--strategy-path'], '').get_parsed_arg() Arguments(['--strategy-path'], '').get_parsed_arg()
def test_parse_timerange_incorrect() -> None:
assert TimeRange(None, 'line', 0, -200) == Arguments.parse_timerange('-200')
assert TimeRange('line', None, 200, 0) == Arguments.parse_timerange('200-')
assert TimeRange('index', 'index', 200, 500) == Arguments.parse_timerange('200-500')
assert TimeRange('date', None, 1274486400, 0) == Arguments.parse_timerange('20100522-')
assert TimeRange(None, 'date', 0, 1274486400) == Arguments.parse_timerange('-20100522')
timerange = Arguments.parse_timerange('20100522-20150730')
assert timerange == TimeRange('date', 'date', 1274486400, 1438214400)
# Added test for unix timestamp - BTC genesis date
assert TimeRange('date', None, 1231006505, 0) == Arguments.parse_timerange('1231006505-')
assert TimeRange(None, 'date', 0, 1233360000) == Arguments.parse_timerange('-1233360000')
timerange = Arguments.parse_timerange('1231006505-1233360000')
assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
# TODO: Find solution for the following case (passing timestamp in ms)
timerange = Arguments.parse_timerange('1231006505000-1233360000000')
assert TimeRange('date', 'date', 1231006505, 1233360000) != timerange
with pytest.raises(Exception, match=r'Incorrect syntax.*'):
Arguments.parse_timerange('-')
def test_parse_args_backtesting_invalid() -> None: def test_parse_args_backtesting_invalid() -> None:
with pytest.raises(SystemExit, match=r'2'): with pytest.raises(SystemExit, match=r'2'):
Arguments(['backtesting --ticker-interval'], '').get_parsed_arg() Arguments(['backtesting --ticker-interval'], '').get_parsed_arg()

View File

@ -499,9 +499,9 @@ def test_check_exchange(default_conf, caplog) -> None:
# Test a 'bad' exchange, which known to have serious problems # Test a 'bad' exchange, which known to have serious problems
default_conf.get('exchange').update({'name': 'bitmex'}) default_conf.get('exchange').update({'name': 'bitmex'})
assert not check_exchange(default_conf) with pytest.raises(OperationalException,
assert log_has_re(r"Exchange .* is known to not work with the bot yet\. " match=r"Exchange .* is known to not work with the bot yet.*"):
r"Use it only for development and testing purposes\.", caplog) check_exchange(default_conf)
caplog.clear() caplog.clear()
# Test a 'bad' exchange with check_for_bad=False # Test a 'bad' exchange with check_for_bad=False
@ -642,6 +642,17 @@ def test_validate_tsl(default_conf):
configuration._validate_config_consistency(default_conf) configuration._validate_config_consistency(default_conf)
def test_load_config_test_comments() -> None:
"""
Load config with comments
"""
config_file = Path(__file__).parents[0] / "config_test_comments.json"
print(config_file)
conf = load_config_file(str(config_file))
assert conf
def test_load_config_default_exchange(all_conf) -> None: def test_load_config_default_exchange(all_conf) -> None:
""" """
config['exchange'] subtree has required options in it config['exchange'] subtree has required options in it

View File

@ -253,13 +253,13 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf,
assert result == default_conf['stake_amount'] / conf['max_open_trades'] assert result == default_conf['stake_amount'] / conf['max_open_trades']
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)' # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
freqtrade.create_trade() freqtrade.execute_buy('ETH/BTC', result)
result = freqtrade._get_trade_stake_amount('LTC/BTC') result = freqtrade._get_trade_stake_amount('LTC/BTC')
assert result == default_conf['stake_amount'] / (conf['max_open_trades'] - 1) assert result == default_conf['stake_amount'] / (conf['max_open_trades'] - 1)
# create 2 trades, order amount should be None # create 2 trades, order amount should be None
freqtrade.create_trade() freqtrade.execute_buy('LTC/BTC', result)
result = freqtrade._get_trade_stake_amount('XRP/BTC') result = freqtrade._get_trade_stake_amount('XRP/BTC')
assert result is None assert result is None
@ -301,6 +301,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
patch_edge(mocker) patch_edge(mocker)
edge_conf['max_open_trades'] = float('inf')
# Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2 # Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
# Thus, if price falls 21%, stoploss should be triggered # Thus, if price falls 21%, stoploss should be triggered
@ -325,7 +326,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker,
freqtrade.active_pair_whitelist = ['NEO/BTC'] freqtrade.active_pair_whitelist = ['NEO/BTC']
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
############################################# #############################################
@ -341,6 +342,7 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, markets,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
patch_edge(mocker) patch_edge(mocker)
edge_conf['max_open_trades'] = float('inf')
# Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2 # Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
# Thus, if price falls 15%, stoploss should not be triggered # Thus, if price falls 15%, stoploss should not be triggered
@ -365,7 +367,7 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, markets,
freqtrade.active_pair_whitelist = ['NEO/BTC'] freqtrade.active_pair_whitelist = ['NEO/BTC']
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
############################################# #############################################
@ -379,6 +381,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
default_conf['stake_amount'] = 0.0000098751 default_conf['stake_amount'] = 0.0000098751
default_conf['max_open_trades'] = 2
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_ticker=ticker, get_ticker=ticker,
@ -388,7 +391,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker,
) )
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade is not None assert trade is not None
@ -396,7 +399,7 @@ def test_total_open_trades_stakes(mocker, default_conf, ticker,
assert trade.is_open assert trade.is_open
assert trade.open_date is not None assert trade.open_date is not None
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.order_by(Trade.id.desc()).first() trade = Trade.query.order_by(Trade.id.desc()).first()
assert trade is not None assert trade is not None
@ -519,7 +522,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
assert result == min(8, 2 * 2) / 0.9 assert result == min(8, 2 * 2) / 0.9
def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None: def test_create_trades(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
@ -534,7 +537,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocke
whitelist = deepcopy(default_conf['exchange']['pair_whitelist']) whitelist = deepcopy(default_conf['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade is not None assert trade is not None
@ -552,7 +555,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocke
assert whitelist == default_conf['exchange']['pair_whitelist'] assert whitelist == default_conf['exchange']['pair_whitelist']
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, def test_create_trades_no_stake_amount(default_conf, ticker, limit_buy_order,
fee, markets, mocker) -> None: fee, markets, mocker) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
@ -568,10 +571,10 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
with pytest.raises(DependencyException, match=r'.*stake amount.*'): with pytest.raises(DependencyException, match=r'.*stake amount.*'):
freqtrade.create_trade() freqtrade.create_trades()
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, def test_create_trades_minimal_amount(default_conf, ticker, limit_buy_order,
fee, markets, mocker) -> None: fee, markets, mocker) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
@ -587,12 +590,12 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order,
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount'] rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount']
assert rate * amount >= default_conf['stake_amount'] assert rate * amount >= default_conf['stake_amount']
def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order, def test_create_trades_too_small_stake_amount(default_conf, ticker, limit_buy_order,
fee, markets, mocker) -> None: fee, markets, mocker) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
@ -609,10 +612,10 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
assert not freqtrade.create_trade() assert not freqtrade.create_trades()
def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order, def test_create_trades_limit_reached(default_conf, ticker, limit_buy_order,
fee, markets, mocker) -> None: fee, markets, mocker) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
@ -630,11 +633,11 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
assert not freqtrade.create_trade() assert not freqtrade.create_trades()
assert freqtrade._get_trade_stake_amount('ETH/BTC') is None assert freqtrade._get_trade_stake_amount('ETH/BTC') is None
def test_create_trade_no_pairs_let(default_conf, ticker, limit_buy_order, fee, def test_create_trades_no_pairs_let(default_conf, ticker, limit_buy_order, fee,
markets, mocker, caplog) -> None: markets, mocker, caplog) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
@ -650,12 +653,12 @@ def test_create_trade_no_pairs_let(default_conf, ticker, limit_buy_order, fee,
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
assert freqtrade.create_trade() assert freqtrade.create_trades()
assert not freqtrade.create_trade() assert not freqtrade.create_trades()
assert log_has("No currency pair in whitelist, but checking to sell open trades.", caplog) assert log_has("No currency pair in whitelist, but checking to sell open trades.", caplog)
def test_create_trade_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee, def test_create_trades_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee,
markets, mocker, caplog) -> None: markets, mocker, caplog) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
@ -670,11 +673,11 @@ def test_create_trade_no_pairs_in_whitelist(default_conf, ticker, limit_buy_orde
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
assert not freqtrade.create_trade() assert not freqtrade.create_trades()
assert log_has("Whitelist is empty.", caplog) assert log_has("Whitelist is empty.", caplog)
def test_create_trade_no_signal(default_conf, fee, mocker) -> None: def test_create_trades_no_signal(default_conf, fee, mocker) -> None:
default_conf['dry_run'] = True default_conf['dry_run'] = True
patch_RPCManager(mocker) patch_RPCManager(mocker)
@ -690,7 +693,56 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
Trade.query = MagicMock() Trade.query = MagicMock()
Trade.query.filter = MagicMock() Trade.query.filter = MagicMock()
assert not freqtrade.create_trade() assert not freqtrade.create_trades()
@pytest.mark.parametrize("max_open", range(0, 5))
def test_create_trades_multiple_trades(default_conf, ticker,
fee, markets, mocker, max_open) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
default_conf['max_open_trades'] = max_open
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
buy=MagicMock(return_value={'id': "12355555"}),
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trades()
trades = Trade.get_open_trades()
assert len(trades) == max_open
def test_create_trades_preopen(default_conf, ticker, fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
default_conf['max_open_trades'] = 4
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
buy=MagicMock(return_value={'id': "12355555"}),
get_fee=fee,
markets=PropertyMock(return_value=markets)
)
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
# Create 2 existing trades
freqtrade.execute_buy('ETH/BTC', default_conf['stake_amount'])
freqtrade.execute_buy('NEO/BTC', default_conf['stake_amount'])
assert len(Trade.get_open_trades()) == 2
# Create 2 new trades using create_trades
assert freqtrade.create_trades()
trades = Trade.get_open_trades()
assert len(trades) == 4
def test_process_trade_creation(default_conf, ticker, limit_buy_order, def test_process_trade_creation(default_conf, ticker, limit_buy_order,
@ -711,8 +763,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert not trades assert not trades
result = freqtrade.process() freqtrade.process()
assert result is True
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert len(trades) == 1 assert len(trades) == 1
@ -744,8 +795,7 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non
worker = Worker(args=None, config=default_conf) worker = Worker(args=None, config=default_conf)
patch_get_signal(worker.freqtrade) patch_get_signal(worker.freqtrade)
result = worker._process() worker._process()
assert result is False
assert sleep_mock.has_calls() assert sleep_mock.has_calls()
@ -763,8 +813,7 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) ->
assert worker.state == State.RUNNING assert worker.state == State.RUNNING
result = worker._process() worker._process()
assert result is False
assert worker.state == State.STOPPED assert worker.state == State.STOPPED
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]['status'] assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]['status']
@ -786,13 +835,14 @@ def test_process_trade_handling(
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert not trades assert not trades
result = freqtrade.process() freqtrade.process()
assert result is True
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert len(trades) == 1 assert len(trades) == 1
result = freqtrade.process() # Nothing happened ...
assert result is False freqtrade.process()
assert len(trades) == 1
def test_process_trade_no_whitelist_pair( def test_process_trade_no_whitelist_pair(
@ -834,11 +884,10 @@ def test_process_trade_no_whitelist_pair(
)) ))
assert pair not in freqtrade.active_pair_whitelist assert pair not in freqtrade.active_pair_whitelist
result = freqtrade.process() freqtrade.process()
assert pair in freqtrade.active_pair_whitelist assert pair in freqtrade.active_pair_whitelist
# Make sure each pair is only in the list once # Make sure each pair is only in the list once
assert len(freqtrade.active_pair_whitelist) == len(set(freqtrade.active_pair_whitelist)) assert len(freqtrade.active_pair_whitelist) == len(set(freqtrade.active_pair_whitelist))
assert result is True
def test_process_informative_pairs_added(default_conf, ticker, markets, mocker) -> None: def test_process_informative_pairs_added(default_conf, ticker, markets, mocker) -> None:
@ -1078,7 +1127,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
# Fourth case: when stoploss is set and it is hit # Fourth case: when stoploss is set and it is hit
# should unset stoploss_order_id and return true # should unset stoploss_order_id and return true
# as a trade actually happened # as a trade actually happened
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
@ -1153,7 +1202,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
@ -1243,7 +1292,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
# setting stoploss_on_exchange_interval to 60 seconds # setting stoploss_on_exchange_interval to 60 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60 freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 60
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
@ -1286,7 +1335,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
patch_edge(mocker) patch_edge(mocker)
edge_conf['max_open_trades'] = float('inf')
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
@ -1324,7 +1373,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist) freqtrade.active_pair_whitelist = freqtrade.edge.adjust(freqtrade.active_pair_whitelist)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
@ -1388,21 +1437,19 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
stop_price=0.00002344 * 0.99) stop_price=0.00002344 * 0.99)
def test_process_maybe_execute_buy(mocker, default_conf) -> None: def test_process_maybe_execute_buy(mocker, default_conf, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade', MagicMock(return_value=True)) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trades', MagicMock(return_value=False))
assert freqtrade.process_maybe_execute_buy() freqtrade.process_maybe_execute_buy()
assert log_has('Found no buy signals for whitelisted currencies. Trying again...', caplog)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade', MagicMock(return_value=False))
assert not freqtrade.process_maybe_execute_buy()
def test_process_maybe_execute_buy_exception(mocker, default_conf, caplog) -> None: def test_process_maybe_execute_buy_exception(mocker, default_conf, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.FreqtradeBot.create_trade', 'freqtrade.freqtradebot.FreqtradeBot.create_trades',
MagicMock(side_effect=DependencyException) MagicMock(side_effect=DependencyException)
) )
freqtrade.process_maybe_execute_buy() freqtrade.process_maybe_execute_buy()
@ -1589,7 +1636,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -1629,7 +1676,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order,
patch_get_signal(freqtrade, value=(True, True)) patch_get_signal(freqtrade, value=(True, True))
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
# Buy and Sell triggering, so doing nothing ... # Buy and Sell triggering, so doing nothing ...
trades = Trade.query.all() trades = Trade.query.all()
@ -1638,7 +1685,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order,
# Buy is triggering, so buying ... # Buy is triggering, so buying ...
patch_get_signal(freqtrade, value=(True, False)) patch_get_signal(freqtrade, value=(True, False))
freqtrade.create_trade() freqtrade.create_trades()
trades = Trade.query.all() trades = Trade.query.all()
nb_trades = len(trades) nb_trades = len(trades)
assert nb_trades == 1 assert nb_trades == 1
@ -1685,7 +1732,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order,
patch_get_signal(freqtrade, value=(True, False)) patch_get_signal(freqtrade, value=(True, False))
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.is_open = True trade.is_open = True
@ -1717,7 +1764,7 @@ def test_handle_trade_experimental(
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.is_open = True trade.is_open = True
@ -1745,7 +1792,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
# Create trade and sell it # Create trade and sell it
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -2085,7 +2132,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -2131,7 +2178,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -2180,7 +2227,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -2237,7 +2284,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee,
freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.strategy.order_types['stoploss_on_exchange'] = True
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
Trade.session = MagicMock() Trade.session = MagicMock()
@ -2284,7 +2331,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -2336,7 +2383,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
freqtrade.process_maybe_execute_sell(trade) freqtrade.process_maybe_execute_sell(trade)
assert trade assert trade
@ -2388,7 +2435,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -2449,7 +2496,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -2480,7 +2527,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order,
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -2511,7 +2558,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple( freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
sell_flag=False, sell_type=SellType.NONE)) sell_flag=False, sell_type=SellType.NONE))
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -2542,7 +2589,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -2572,7 +2619,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, m
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -2604,7 +2651,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, markets, caplog,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -2657,7 +2704,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -2715,7 +2762,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -2778,7 +2825,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee,
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -2837,7 +2884,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order,
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
@ -3094,7 +3141,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee,
whitelist = deepcopy(default_conf['exchange']['pair_whitelist']) whitelist = deepcopy(default_conf['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade is not None assert trade is not None
@ -3128,7 +3175,7 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o
# Save state of current whitelist # Save state of current whitelist
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade is None assert trade is None
@ -3234,7 +3281,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
freqtrade.create_trade() freqtrade.create_trades()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade

View File

@ -5,7 +5,7 @@ from unittest.mock import MagicMock
import plotly.graph_objects as go import plotly.graph_objects as go
from plotly.subplots import make_subplots from plotly.subplots import make_subplots
from freqtrade.configuration import Arguments, TimeRange from freqtrade.configuration import TimeRange
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data
from freqtrade.plot.plotting import (add_indicators, add_profit, from freqtrade.plot.plotting import (add_indicators, add_profit,
@ -222,7 +222,7 @@ def test_generate_plot_file(mocker, caplog):
def test_add_profit(): def test_add_profit():
filename = history.make_testdata_path(None) / "backtest-result_test.json" filename = history.make_testdata_path(None) / "backtest-result_test.json"
bt_data = load_backtest_data(filename) bt_data = load_backtest_data(filename)
timerange = Arguments.parse_timerange("20180110-20180112") timerange = TimeRange.parse_timerange("20180110-20180112")
df = history.load_pair_history(pair="POWR/BTC", ticker_interval='5m', df = history.load_pair_history(pair="POWR/BTC", ticker_interval='5m',
datadir=None, timerange=timerange) datadir=None, timerange=timerange)
@ -242,7 +242,7 @@ def test_add_profit():
def test_generate_profit_graph(): def test_generate_profit_graph():
filename = history.make_testdata_path(None) / "backtest-result_test.json" filename = history.make_testdata_path(None) / "backtest-result_test.json"
trades = load_backtest_data(filename) trades = load_backtest_data(filename)
timerange = Arguments.parse_timerange("20180110-20180112") timerange = TimeRange.parse_timerange("20180110-20180112")
pairs = ["POWR/BTC", "XLM/BTC"] pairs = ["POWR/BTC", "XLM/BTC"]
tickers = history.load_data(datadir=None, tickers = history.load_data(datadir=None,

View File

@ -0,0 +1,28 @@
# pragma pylint: disable=missing-docstring, C0103
import pytest
from freqtrade.configuration import TimeRange
def test_parse_timerange_incorrect() -> None:
assert TimeRange(None, 'line', 0, -200) == TimeRange.parse_timerange('-200')
assert TimeRange('line', None, 200, 0) == TimeRange.parse_timerange('200-')
assert TimeRange('index', 'index', 200, 500) == TimeRange.parse_timerange('200-500')
assert TimeRange('date', None, 1274486400, 0) == TimeRange.parse_timerange('20100522-')
assert TimeRange(None, 'date', 0, 1274486400) == TimeRange.parse_timerange('-20100522')
timerange = TimeRange.parse_timerange('20100522-20150730')
assert timerange == TimeRange('date', 'date', 1274486400, 1438214400)
# Added test for unix timestamp - BTC genesis date
assert TimeRange('date', None, 1231006505, 0) == TimeRange.parse_timerange('1231006505-')
assert TimeRange(None, 'date', 0, 1233360000) == TimeRange.parse_timerange('-1233360000')
timerange = TimeRange.parse_timerange('1231006505-1233360000')
assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
# TODO: Find solution for the following case (passing timestamp in ms)
timerange = TimeRange.parse_timerange('1231006505000-1233360000000')
assert TimeRange('date', 'date', 1231006505, 1233360000) != timerange
with pytest.raises(Exception, match=r'Incorrect syntax.*'):
TimeRange.parse_timerange('-')

View File

@ -127,11 +127,10 @@ class Worker(object):
time.sleep(duration) time.sleep(duration)
return result return result
def _process(self) -> bool: def _process(self) -> None:
logger.debug("========================================") logger.debug("========================================")
state_changed = False
try: try:
state_changed = self.freqtrade.process() self.freqtrade.process()
except TemporaryError as error: except TemporaryError as error:
logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...") logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...")
time.sleep(constants.RETRY_TIMEOUT) time.sleep(constants.RETRY_TIMEOUT)
@ -144,10 +143,6 @@ class Worker(object):
}) })
logger.exception('OperationalException. Stopping trader ...') logger.exception('OperationalException. Stopping trader ...')
self.freqtrade.state = State.STOPPED self.freqtrade.state = State.STOPPED
# TODO: The return value of _process() is not used apart tests
# and should (could) be eliminated later. See PR #1689.
# state_changed = True
return state_changed
def _reconfigure(self) -> None: def _reconfigure(self) -> None:
""" """

View File

@ -1,9 +1,9 @@
# requirements without requirements installable via conda # requirements without requirements installable via conda
# mainly used for Raspberry pi installs # mainly used for Raspberry pi installs
ccxt==1.18.1021 ccxt==1.18.1043
SQLAlchemy==1.3.6 SQLAlchemy==1.3.6
python-telegram-bot==11.1.0 python-telegram-bot==11.1.0
arrow==0.14.4 arrow==0.14.5
cachetools==3.1.1 cachetools==3.1.1
requests==2.22.0 requests==2.22.0
urllib3==1.25.3 urllib3==1.25.3
@ -23,10 +23,13 @@ filelock==3.0.12
py_find_1st==1.1.4 py_find_1st==1.1.4
#Load ticker files 30% faster #Load ticker files 30% faster
python-rapidjson==0.7.2 python-rapidjson==0.8.0
# Notify systemd # Notify systemd
sdnotify==0.3.2 sdnotify==0.3.2
# Api server # Api server
flask==1.1.1 flask==1.1.1
# Support for colorized terminal output
colorama==0.4.1

View File

@ -3,4 +3,4 @@
numpy==1.17.0 numpy==1.17.0
pandas==0.25.0 pandas==0.25.0
scipy==1.3.0 scipy==1.3.1

View File

@ -105,7 +105,7 @@ if not pairs or args.pairs_file:
timerange = TimeRange() timerange = TimeRange()
if args.days: if args.days:
time_since = arrow.utcnow().shift(days=-args.days).strftime("%Y%m%d") time_since = arrow.utcnow().shift(days=-args.days).strftime("%Y%m%d")
timerange = arguments.parse_timerange(f'{time_since}-') timerange = TimeRange.parse_timerange(f'{time_since}-')
logger.info(f'About to download pairs: {pairs}, intervals: {timeframes} to {dl_path}') logger.info(f'About to download pairs: {pairs}, intervals: {timeframes} to {dl_path}')

View File

@ -64,6 +64,7 @@ setup(name='freqtrade',
'py_find_1st', 'py_find_1st',
'python-rapidjson', 'python-rapidjson',
'sdnotify', 'sdnotify',
'colorama',
# from requirements.txt # from requirements.txt
'numpy', 'numpy',
'pandas', 'pandas',