Merge branch 'develop' into feat/plot_module

This commit is contained in:
Matthias 2019-06-21 19:28:38 +02:00
commit 5d6819bb28
18 changed files with 144 additions and 152 deletions

View File

@ -26,7 +26,8 @@ optional arguments:
--version show program's version number and exit --version show program's version number and exit
-c PATH, --config PATH -c PATH, --config PATH
Specify configuration file (default: None). Multiple Specify configuration file (default: None). Multiple
--config options may be used. --config options may be used. Can be set to '-' to
read config from stdin.
-d PATH, --datadir PATH -d PATH, --datadir PATH
Path to backtest data. Path to backtest data.
-s NAME, --strategy NAME -s NAME, --strategy NAME

View File

@ -43,6 +43,7 @@ Possible parameters are:
* `stake_amount` * `stake_amount`
* `stake_currency` * `stake_currency`
* `fiat_currency` * `fiat_currency`
* `order_type`
### Webhooksell ### Webhooksell
@ -61,6 +62,7 @@ Possible parameters are:
* `stake_currency` * `stake_currency`
* `fiat_currency` * `fiat_currency`
* `sell_reason` * `sell_reason`
* `order_type`
### Webhookstatus ### Webhookstatus

View File

@ -33,7 +33,8 @@ class Arguments(object):
self.parser = argparse.ArgumentParser(description=description) self.parser = argparse.ArgumentParser(description=description)
def _load_args(self) -> None: def _load_args(self) -> None:
self.common_args_parser() self.common_options()
self.main_options()
self._build_subcommands() self._build_subcommands()
def get_parsed_arg(self) -> argparse.Namespace: def get_parsed_arg(self) -> argparse.Namespace:
@ -60,62 +61,66 @@ class Arguments(object):
return parsed_arg return parsed_arg
def common_args_parser(self) -> None: def common_options(self) -> None:
""" """
Parses given common arguments and returns them as a parsed object. Parses arguments that are common for the main Freqtrade, all subcommands and scripts.
""" """
self.parser.add_argument( parser = self.parser
parser.add_argument(
'-v', '--verbose', '-v', '--verbose',
help='Verbose mode (-vv for more, -vvv to get all messages).', help='Verbose mode (-vv for more, -vvv to get all messages).',
action='count', action='count',
dest='loglevel', dest='loglevel',
default=0, default=0,
) )
self.parser.add_argument( parser.add_argument(
'--logfile', '--logfile',
help='Log to the file specified', help='Log to the file specified',
dest='logfile', dest='logfile',
type=str, metavar='FILE',
metavar='FILE'
) )
self.parser.add_argument( parser.add_argument(
'--version', '--version',
action='version', action='version',
version=f'%(prog)s {__version__}' version=f'%(prog)s {__version__}'
) )
self.parser.add_argument( parser.add_argument(
'-c', '--config', '-c', '--config',
help='Specify configuration file (default: %(default)s). ' help="Specify configuration file (default: %(default)s). "
'Multiple --config options may be used.', "Multiple --config options may be used. "
"Can be set to '-' to read config from stdin.",
dest='config', dest='config',
action='append', action='append',
type=str,
metavar='PATH', metavar='PATH',
) )
self.parser.add_argument( parser.add_argument(
'-d', '--datadir', '-d', '--datadir',
help='Path to backtest data.', help='Path to backtest data.',
dest='datadir', dest='datadir',
default=None,
type=str,
metavar='PATH', metavar='PATH',
) )
self.parser.add_argument(
def main_options(self) -> None:
"""
Parses arguments for the main Freqtrade.
"""
parser = self.parser
parser.add_argument(
'-s', '--strategy', '-s', '--strategy',
help='Specify strategy class name (default: %(default)s).', help='Specify strategy class name (default: %(default)s).',
dest='strategy', dest='strategy',
default='DefaultStrategy', default='DefaultStrategy',
type=str,
metavar='NAME', metavar='NAME',
) )
self.parser.add_argument( parser.add_argument(
'--strategy-path', '--strategy-path',
help='Specify additional strategy lookup path.', help='Specify additional strategy lookup path.',
dest='strategy_path', dest='strategy_path',
type=str,
metavar='PATH', metavar='PATH',
) )
self.parser.add_argument( parser.add_argument(
'--dynamic-whitelist', '--dynamic-whitelist',
help='Dynamically generate and update whitelist' help='Dynamically generate and update whitelist'
' based on 24h BaseVolume (default: %(const)s).' ' based on 24h BaseVolume (default: %(const)s).'
@ -126,52 +131,46 @@ class Arguments(object):
metavar='INT', metavar='INT',
nargs='?', nargs='?',
) )
self.parser.add_argument( parser.add_argument(
'--db-url', '--db-url',
help='Override trades database URL, this is useful if dry_run is enabled' help='Override trades database URL, this is useful if dry_run is enabled'
' or in custom deployments (default: %(default)s).', ' or in custom deployments (default: %(default)s).',
dest='db_url', dest='db_url',
type=str,
metavar='PATH', metavar='PATH',
) )
self.parser.add_argument( parser.add_argument(
'--sd-notify', '--sd-notify',
help='Notify systemd service manager.', help='Notify systemd service manager.',
action='store_true', action='store_true',
dest='sd_notify', dest='sd_notify',
) )
@staticmethod def common_optimize_options(self, subparser: argparse.ArgumentParser = None) -> None:
def optimizer_shared_options(parser: argparse.ArgumentParser) -> None:
""" """
Parses given common arguments for Backtesting, Edge and Hyperopt modules. Parses arguments common for Backtesting, Edge and Hyperopt modules.
:param parser: :param parser:
:return:
""" """
parser = subparser or self.parser
parser.add_argument( parser.add_argument(
'-i', '--ticker-interval', '-i', '--ticker-interval',
help='Specify ticker interval (1m, 5m, 30m, 1h, 1d).', help='Specify ticker interval (1m, 5m, 30m, 1h, 1d).',
dest='ticker_interval', dest='ticker_interval',
type=str,
) )
parser.add_argument( parser.add_argument(
'--timerange', '--timerange',
help='Specify what timerange of data to use.', help='Specify what timerange of data to use.',
default=None,
type=str,
dest='timerange', dest='timerange',
) )
parser.add_argument( parser.add_argument(
'--max_open_trades', '--max_open_trades',
help='Specify max_open_trades to use.', help='Specify max_open_trades to use.',
default=None,
type=int, type=int,
dest='max_open_trades', dest='max_open_trades',
) )
parser.add_argument( parser.add_argument(
'--stake_amount', '--stake_amount',
help='Specify stake_amount.', help='Specify stake_amount.',
default=None,
type=float, type=float,
dest='stake_amount', dest='stake_amount',
) )
@ -184,11 +183,12 @@ class Arguments(object):
dest='refresh_pairs', dest='refresh_pairs',
) )
@staticmethod def backtesting_options(self, subparser: argparse.ArgumentParser = None) -> None:
def backtesting_options(parser: argparse.ArgumentParser) -> None:
""" """
Parses given arguments for Backtesting module. Parses given arguments for Backtesting module.
""" """
parser = subparser or self.parser
parser.add_argument( parser.add_argument(
'--eps', '--enable-position-stacking', '--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking).', help='Allow buying the same pair multiple times (position stacking).',
@ -224,8 +224,6 @@ class Arguments(object):
'--export', '--export',
help='Export backtest results, argument are: trades. ' help='Export backtest results, argument are: trades. '
'Example --export=trades', 'Example --export=trades',
type=str,
default=None,
dest='export', dest='export',
) )
parser.add_argument( parser.add_argument(
@ -234,37 +232,36 @@ class Arguments(object):
requires --export to be set as well\ requires --export to be set as well\
Example --export-filename=user_data/backtest_data/backtest_today.json\ Example --export-filename=user_data/backtest_data/backtest_today.json\
(default: %(default)s)', (default: %(default)s)',
type=str,
default=os.path.join('user_data', 'backtest_data', 'backtest-result.json'), default=os.path.join('user_data', 'backtest_data', 'backtest-result.json'),
dest='exportfilename', dest='exportfilename',
metavar='PATH', metavar='PATH',
) )
@staticmethod def edge_options(self, subparser: argparse.ArgumentParser = None) -> None:
def edge_options(parser: argparse.ArgumentParser) -> None:
""" """
Parses given arguments for Edge module. Parses given arguments for Edge module.
""" """
parser = subparser or self.parser
parser.add_argument( parser.add_argument(
'--stoplosses', '--stoplosses',
help='Defines a range of stoploss against which edge will assess the strategy ' help='Defines a range of stoploss against which edge will assess the strategy '
'the format is "min,max,step" (without any space).' 'the format is "min,max,step" (without any space).'
'example: --stoplosses=-0.01,-0.1,-0.001', 'example: --stoplosses=-0.01,-0.1,-0.001',
type=str,
dest='stoploss_range', dest='stoploss_range',
) )
@staticmethod def hyperopt_options(self, subparser: argparse.ArgumentParser = None) -> None:
def hyperopt_options(parser: argparse.ArgumentParser) -> None:
""" """
Parses given arguments for Hyperopt module. Parses given arguments for Hyperopt module.
""" """
parser = subparser or self.parser
parser.add_argument( parser.add_argument(
'--customhyperopt', '--customhyperopt',
help='Specify hyperopt class name (default: %(default)s).', help='Specify hyperopt class name (default: %(default)s).',
dest='hyperopt', dest='hyperopt',
default=constants.DEFAULT_HYPEROPT, default=constants.DEFAULT_HYPEROPT,
type=str,
metavar='NAME', metavar='NAME',
) )
parser.add_argument( parser.add_argument(
@ -321,7 +318,6 @@ class Arguments(object):
'--random-state', '--random-state',
help='Set random state to some positive integer for reproducible hyperopt results.', help='Set random state to some positive integer for reproducible hyperopt results.',
dest='hyperopt_random_state', dest='hyperopt_random_state',
default=None,
type=Arguments.check_int_positive, type=Arguments.check_int_positive,
metavar='INT', metavar='INT',
) )
@ -335,11 +331,12 @@ class Arguments(object):
metavar='INT', metavar='INT',
) )
@staticmethod def list_exchanges_options(self, subparser: argparse.ArgumentParser = None) -> None:
def list_exchanges_options(parser: argparse.ArgumentParser) -> None:
""" """
Parses given arguments for the list-exchanges command. Parses given arguments for the list-exchanges command.
""" """
parser = subparser or self.parser
parser.add_argument( parser.add_argument(
'-1', '--one-column', '-1', '--one-column',
help='Print exchanges in one column', help='Print exchanges in one column',
@ -349,7 +346,7 @@ class Arguments(object):
def _build_subcommands(self) -> None: def _build_subcommands(self) -> None:
""" """
Builds and attaches all subcommands Builds and attaches all subcommands.
:return: None :return: None
""" """
from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge
@ -360,19 +357,19 @@ class Arguments(object):
# Add backtesting subcommand # Add backtesting subcommand
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.') backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.')
backtesting_cmd.set_defaults(func=start_backtesting) backtesting_cmd.set_defaults(func=start_backtesting)
self.optimizer_shared_options(backtesting_cmd) self.common_optimize_options(backtesting_cmd)
self.backtesting_options(backtesting_cmd) self.backtesting_options(backtesting_cmd)
# Add edge subcommand # Add edge subcommand
edge_cmd = subparsers.add_parser('edge', help='Edge module.') edge_cmd = subparsers.add_parser('edge', help='Edge module.')
edge_cmd.set_defaults(func=start_edge) edge_cmd.set_defaults(func=start_edge)
self.optimizer_shared_options(edge_cmd) self.common_optimize_options(edge_cmd)
self.edge_options(edge_cmd) self.edge_options(edge_cmd)
# Add hyperopt subcommand # Add hyperopt subcommand
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.') hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.')
hyperopt_cmd.set_defaults(func=start_hyperopt) hyperopt_cmd.set_defaults(func=start_hyperopt)
self.optimizer_shared_options(hyperopt_cmd) self.common_optimize_options(hyperopt_cmd)
self.hyperopt_options(hyperopt_cmd) self.hyperopt_options(hyperopt_cmd)
# Add list-exchanges subcommand # Add list-exchanges subcommand
@ -437,69 +434,43 @@ class Arguments(object):
) )
return uint return uint
def scripts_options(self) -> None: def common_scripts_options(self, subparser: argparse.ArgumentParser = None) -> None:
""" """
Parses given arguments for scripts. Parses arguments common for scripts.
""" """
self.parser.add_argument( parser = subparser or self.parser
parser.add_argument(
'-p', '--pairs', '-p', '--pairs',
help='Show profits for only this pairs. Pairs are comma-separated.', help='Show profits for only this pairs. Pairs are comma-separated.',
dest='pairs', dest='pairs',
default=None
) )
def download_data_options(self) -> None: def download_data_options(self) -> None:
""" """
Parses given arguments for testdata download Parses given arguments for testdata download script
""" """
self.parser.add_argument( parser = self.parser
'-v', '--verbose',
help='Verbose mode (-vv for more, -vvv to get all messages).', parser.add_argument(
action='count',
dest='loglevel',
default=0,
)
self.parser.add_argument(
'--logfile',
help='Log to the file specified',
dest='logfile',
type=str,
metavar='FILE',
)
self.parser.add_argument(
'-c', '--config',
help='Specify configuration file (default: %(default)s). '
'Multiple --config options may be used.',
dest='config',
action='append',
type=str,
metavar='PATH',
)
self.parser.add_argument(
'-d', '--datadir',
help='Path to backtest data.',
dest='datadir',
metavar='PATH',
)
self.parser.add_argument(
'--pairs-file', '--pairs-file',
help='File containing a list of pairs to download.', help='File containing a list of pairs to download.',
dest='pairs_file', dest='pairs_file',
metavar='FILE', metavar='FILE',
) )
self.parser.add_argument( parser.add_argument(
'--days', '--days',
help='Download data for given number of days.', help='Download data for given number of days.',
dest='days', dest='days',
type=Arguments.check_int_positive, type=Arguments.check_int_positive,
metavar='INT', metavar='INT',
) )
self.parser.add_argument( parser.add_argument(
'--exchange', '--exchange',
help='Exchange name (default: %(default)s). Only valid if no config is provided.', help='Exchange name (default: %(default)s). Only valid if no config is provided.',
dest='exchange', dest='exchange',
) )
self.parser.add_argument( parser.add_argument(
'-t', '--timeframes', '-t', '--timeframes',
help='Specify which tickers to download. Space separated list. \ help='Specify which tickers to download. Space separated list. \
Default: %(default)s.', Default: %(default)s.',
@ -508,7 +479,7 @@ class Arguments(object):
nargs='+', nargs='+',
dest='timeframes', dest='timeframes',
) )
self.parser.add_argument( parser.add_argument(
'--erase', '--erase',
help='Clean all existing data for the selected exchange/pairs/timeframes.', help='Clean all existing data for the selected exchange/pairs/timeframes.',
dest='erase', dest='erase',
@ -517,33 +488,26 @@ class Arguments(object):
def plot_dataframe_options(self) -> None: def plot_dataframe_options(self) -> None:
""" """
Parses given arguments for plot_dataframe Parses given arguments for plot dataframe script
""" """
self.parser.add_argument( parser = self.parser
'-p', '--pairs',
help='Show profits for only this pairs. Pairs are comma-separated.', parser.add_argument(
dest='pairs',
required=True,
default=None
)
self.parser.add_argument(
'--indicators1', '--indicators1',
help='Set indicators from your strategy you want in the first row of the graph. ' help='Set indicators from your strategy you want in the first row of the graph. '
'Separate them with a comma. E.g: ema3,ema5 (default: %(default)s)', 'Separate them with a coma. E.g: ema3,ema5 (default: %(default)s)',
type=str,
default='sma,ema3,ema5', default='sma,ema3,ema5',
dest='indicators1', dest='indicators1',
) )
self.parser.add_argument( parser.add_argument(
'--indicators2', '--indicators2',
help='Set indicators from your strategy you want in the third row of the graph. ' help='Set indicators from your strategy you want in the third row of the graph. '
'Separate them with a comma. E.g: macd,fastd,fastk (default: %(default)s)', 'Separate them with a coma. E.g: fastd,fastk (default: %(default)s)',
type=str,
default='macd,macdsignal', default='macd,macdsignal',
dest='indicators2', dest='indicators2',
) )
self.parser.add_argument( parser.add_argument(
'--plot-limit', '--plot-limit',
help='Specify tick limit for plotting - too high values cause huge files - ' help='Specify tick limit for plotting - too high values cause huge files - '
'Default: %(default)s', 'Default: %(default)s',

View File

@ -34,13 +34,17 @@ def set_loggers(log_level: int = 0) -> None:
logging.getLogger('telegram').setLevel(logging.INFO) logging.getLogger('telegram').setLevel(logging.INFO)
def _extend_with_default(validator_class): def _extend_validator(validator_class):
validate_properties = validator_class.VALIDATORS["properties"] """
Extended validator for the Freqtrade configuration JSON Schema.
Currently it only handles defaults for subschemas.
"""
validate_properties = validator_class.VALIDATORS['properties']
def set_defaults(validator, properties, instance, schema): def set_defaults(validator, properties, instance, schema):
for prop, subschema in properties.items(): for prop, subschema in properties.items():
if "default" in subschema: if 'default' in subschema:
instance.setdefault(prop, subschema["default"]) instance.setdefault(prop, subschema['default'])
for error in validate_properties( for error in validate_properties(
validator, properties, instance, schema, validator, properties, instance, schema,
@ -48,11 +52,11 @@ def _extend_with_default(validator_class):
yield error yield error
return validators.extend( return validators.extend(
validator_class, {"properties": set_defaults}, validator_class, {'properties': set_defaults}
) )
ValidatorWithDefaults = _extend_with_default(Draft4Validator) FreqtradeValidator = _extend_validator(Draft4Validator)
class Configuration(object): class Configuration(object):
@ -75,6 +79,7 @@ class Configuration(object):
# Now expecting a list of config filenames here, not a string # Now expecting a list of config filenames here, not a string
for path in self.args.config: for path in self.args.config:
logger.info('Using config: %s ...', path) logger.info('Using config: %s ...', path)
# Merge config options, overwriting old values # Merge config options, overwriting old values
config = deep_merge_dicts(self._load_config_file(path), config) config = deep_merge_dicts(self._load_config_file(path), config)
@ -117,7 +122,8 @@ class Configuration(object):
:return: configuration as dictionary :return: configuration as dictionary
""" """
try: try:
with open(path) as file: # Read config from stdin if requested in the options
with open(path) if path != '-' else sys.stdin as file:
conf = json.load(file) conf = json.load(file)
except FileNotFoundError: except FileNotFoundError:
raise OperationalException( raise OperationalException(
@ -362,7 +368,7 @@ class Configuration(object):
:return: Returns the config if valid, otherwise throw an exception :return: Returns the config if valid, otherwise throw an exception
""" """
try: try:
ValidatorWithDefaults(constants.CONF_SCHEMA).validate(conf) FreqtradeValidator(constants.CONF_SCHEMA).validate(conf)
return conf return conf
except ValidationError as exception: except ValidationError as exception:
logger.critical( logger.critical(

View File

@ -205,19 +205,19 @@ class FreqtradeBot(object):
else: else:
stake_amount = self.config['stake_amount'] stake_amount = self.config['stake_amount']
avaliable_amount = self.wallets.get_free(self.config['stake_currency']) available_amount = self.wallets.get_free(self.config['stake_currency'])
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT: if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
open_trades = len(Trade.get_open_trades()) open_trades = len(Trade.get_open_trades())
if open_trades >= self.config['max_open_trades']: if open_trades >= self.config['max_open_trades']:
logger.warning('Can\'t open a new trade: max number of trades is reached') logger.warning('Can\'t open a new trade: max number of trades is reached')
return None return None
return avaliable_amount / (self.config['max_open_trades'] - open_trades) return available_amount / (self.config['max_open_trades'] - open_trades)
# Check if stake_amount is fulfilled # Check if stake_amount is fulfilled
if avaliable_amount < stake_amount: if available_amount < stake_amount:
raise DependencyException( raise DependencyException(
f"Available balance({avaliable_amount} {self.config['stake_currency']}) is " f"Available balance({available_amount} {self.config['stake_currency']}) is "
f"lower than stake amount({stake_amount} {self.config['stake_currency']})" f"lower than stake amount({stake_amount} {self.config['stake_currency']})"
) )
@ -345,8 +345,8 @@ class FreqtradeBot(object):
return False return False
amount = stake_amount / buy_limit_requested amount = stake_amount / buy_limit_requested
order_type = self.strategy.order_types['buy']
order = self.exchange.buy(pair=pair, ordertype=self.strategy.order_types['buy'], order = self.exchange.buy(pair=pair, ordertype=order_type,
amount=amount, rate=buy_limit_requested, amount=amount, rate=buy_limit_requested,
time_in_force=time_in_force) time_in_force=time_in_force)
order_id = order['id'] order_id = order['id']
@ -356,7 +356,6 @@ class FreqtradeBot(object):
buy_limit_filled_price = buy_limit_requested buy_limit_filled_price = buy_limit_requested
if order_status == 'expired' or order_status == 'rejected': if order_status == 'expired' or order_status == 'rejected':
order_type = self.strategy.order_types['buy']
order_tif = self.strategy.order_time_in_force['buy'] order_tif = self.strategy.order_time_in_force['buy']
# return false if the order is not filled # return false if the order is not filled
@ -390,6 +389,7 @@ class FreqtradeBot(object):
'exchange': self.exchange.name.capitalize(), 'exchange': self.exchange.name.capitalize(),
'pair': pair_s, 'pair': pair_s,
'limit': buy_limit_filled_price, 'limit': buy_limit_filled_price,
'order_type': order_type,
'stake_amount': stake_amount, 'stake_amount': stake_amount,
'stake_currency': stake_currency, 'stake_currency': stake_currency,
'fiat_currency': fiat_currency 'fiat_currency': fiat_currency
@ -875,6 +875,7 @@ class FreqtradeBot(object):
'pair': trade.pair, 'pair': trade.pair,
'gain': gain, 'gain': gain,
'limit': trade.close_rate_requested, 'limit': trade.close_rate_requested,
'order_type': self.strategy.order_types['sell'],
'amount': trade.amount, 'amount': trade.amount,
'open_rate': trade.open_rate, 'open_rate': trade.open_rate,
'current_rate': current_rate, 'current_rate': current_rate,

View File

@ -5,8 +5,9 @@ from typing import Any, Dict
from filelock import FileLock, Timeout from filelock import FileLock, Timeout
from freqtrade import DependencyException, constants from freqtrade import DependencyException, constants
from freqtrade.configuration import Configuration
from freqtrade.state import RunMode from freqtrade.state import RunMode
from freqtrade.utils import setup_utils_configuration
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -17,12 +18,7 @@ def setup_configuration(args: Namespace, method: RunMode) -> Dict[str, Any]:
:param args: Cli args from Arguments() :param args: Cli args from Arguments()
:return: Configuration :return: Configuration
""" """
configuration = Configuration(args, method) config = setup_utils_configuration(args, method)
config = configuration.load_config()
# Ensure we do not use Exchange credentials
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
if method == RunMode.BACKTEST: if method == RunMode.BACKTEST:
if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT: if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT:

View File

@ -132,7 +132,7 @@ class Telegram(RPC):
msg['stake_amount_fiat'] = 0 msg['stake_amount_fiat'] = 0
message = ("*{exchange}:* Buying {pair}\n" message = ("*{exchange}:* Buying {pair}\n"
"with limit `{limit:.8f}\n" "at rate `{limit:.8f}\n"
"({stake_amount:.6f} {stake_currency}").format(**msg) "({stake_amount:.6f} {stake_currency}").format(**msg)
if msg.get('fiat_currency', None): if msg.get('fiat_currency', None):
@ -144,7 +144,7 @@ class Telegram(RPC):
msg['profit_percent'] = round(msg['profit_percent'] * 100, 2) msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)
message = ("*{exchange}:* Selling {pair}\n" message = ("*{exchange}:* Selling {pair}\n"
"*Limit:* `{limit:.8f}`\n" "*Rate:* `{limit:.8f}`\n"
"*Amount:* `{amount:.8f}`\n" "*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{open_rate:.8f}`\n" "*Open Rate:* `{open_rate:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n" "*Current Rate:* `{current_rate:.8f}`\n"

View File

@ -756,6 +756,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
'gain': 'profit', 'gain': 'profit',
'limit': 1.172e-05, 'limit': 1.172e-05,
'amount': 90.99181073703367, 'amount': 90.99181073703367,
'order_type': 'limit',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'current_rate': 1.172e-05, 'current_rate': 1.172e-05,
'profit_amount': 6.126e-05, 'profit_amount': 6.126e-05,
@ -810,6 +811,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
'gain': 'loss', 'gain': 'loss',
'limit': 1.044e-05, 'limit': 1.044e-05,
'amount': 90.99181073703367, 'amount': 90.99181073703367,
'order_type': 'limit',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'current_rate': 1.044e-05, 'current_rate': 1.044e-05,
'profit_amount': -5.492e-05, 'profit_amount': -5.492e-05,
@ -855,6 +857,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
'gain': 'loss', 'gain': 'loss',
'limit': 1.098e-05, 'limit': 1.098e-05,
'amount': 90.99181073703367, 'amount': 90.99181073703367,
'order_type': 'limit',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'current_rate': 1.098e-05, 'current_rate': 1.098e-05,
'profit_amount': -5.91e-06, 'profit_amount': -5.91e-06,
@ -1188,6 +1191,7 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
'exchange': 'Bittrex', 'exchange': 'Bittrex',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'limit': 1.099e-05, 'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.001, 'stake_amount': 0.001,
'stake_amount_fiat': 0.0, 'stake_amount_fiat': 0.0,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
@ -1195,7 +1199,7 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
}) })
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== '*Bittrex:* Buying ETH/BTC\n' \ == '*Bittrex:* Buying ETH/BTC\n' \
'with limit `0.00001099\n' \ 'at rate `0.00001099\n' \
'(0.001000 BTC,0.000 USD)`' '(0.001000 BTC,0.000 USD)`'
@ -1217,6 +1221,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'gain': 'loss', 'gain': 'loss',
'limit': 3.201e-05, 'limit': 3.201e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'order_type': 'market',
'open_rate': 7.5e-05, 'open_rate': 7.5e-05,
'current_rate': 3.201e-05, 'current_rate': 3.201e-05,
'profit_amount': -0.05746268, 'profit_amount': -0.05746268,
@ -1227,7 +1232,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
}) })
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== ('*Binance:* Selling KEY/ETH\n' == ('*Binance:* Selling KEY/ETH\n'
'*Limit:* `0.00003201`\n' '*Rate:* `0.00003201`\n'
'*Amount:* `1333.33333333`\n' '*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n' '*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n' '*Current Rate:* `0.00003201`\n'
@ -1242,6 +1247,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'gain': 'loss', 'gain': 'loss',
'limit': 3.201e-05, 'limit': 3.201e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'order_type': 'market',
'open_rate': 7.5e-05, 'open_rate': 7.5e-05,
'current_rate': 3.201e-05, 'current_rate': 3.201e-05,
'profit_amount': -0.05746268, 'profit_amount': -0.05746268,
@ -1251,7 +1257,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
}) })
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== ('*Binance:* Selling KEY/ETH\n' == ('*Binance:* Selling KEY/ETH\n'
'*Limit:* `0.00003201`\n' '*Rate:* `0.00003201`\n'
'*Amount:* `1333.33333333`\n' '*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n' '*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n' '*Current Rate:* `0.00003201`\n'
@ -1339,6 +1345,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
'exchange': 'Bittrex', 'exchange': 'Bittrex',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'limit': 1.099e-05, 'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.001, 'stake_amount': 0.001,
'stake_amount_fiat': 0.0, 'stake_amount_fiat': 0.0,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
@ -1346,7 +1353,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
}) })
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== '*Bittrex:* Buying ETH/BTC\n' \ == '*Bittrex:* Buying ETH/BTC\n' \
'with limit `0.00001099\n' \ 'at rate `0.00001099\n' \
'(0.001000 BTC)`' '(0.001000 BTC)`'
@ -1367,6 +1374,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'gain': 'loss', 'gain': 'loss',
'limit': 3.201e-05, 'limit': 3.201e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'order_type': 'limit',
'open_rate': 7.5e-05, 'open_rate': 7.5e-05,
'current_rate': 3.201e-05, 'current_rate': 3.201e-05,
'profit_amount': -0.05746268, 'profit_amount': -0.05746268,
@ -1377,7 +1385,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
}) })
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] \
== '*Binance:* Selling KEY/ETH\n' \ == '*Binance:* Selling KEY/ETH\n' \
'*Limit:* `0.00003201`\n' \ '*Rate:* `0.00003201`\n' \
'*Amount:* `1333.33333333`\n' \ '*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00007500`\n' \ '*Open Rate:* `0.00007500`\n' \
'*Current Rate:* `0.00003201`\n' \ '*Current Rate:* `0.00003201`\n' \

View File

@ -74,6 +74,7 @@ def test_send_msg(default_conf, mocker):
'gain': "profit", 'gain': "profit",
'limit': 0.005, 'limit': 0.005,
'amount': 0.8, 'amount': 0.8,
'order_type': 'limit',
'open_rate': 0.004, 'open_rate': 0.004,
'current_rate': 0.005, 'current_rate': 0.005,
'profit_amount': 0.001, 'profit_amount': 0.001,
@ -126,6 +127,7 @@ def test_exception_send_msg(default_conf, mocker, caplog):
'exchange': 'Bittrex', 'exchange': 'Bittrex',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'limit': 0.005, 'limit': 0.005,
'order_type': 'limit',
'stake_amount': 0.8, 'stake_amount': 0.8,
'stake_amount_fiat': 500, 'stake_amount_fiat': 500,
'stake_currency': 'BTC', 'stake_currency': 'BTC',

View File

@ -47,9 +47,9 @@ def test_parse_args_verbose() -> None:
assert args.loglevel == 1 assert args.loglevel == 1
def test_scripts_options() -> None: def test_common_scripts_options() -> None:
arguments = Arguments(['-p', 'ETH/BTC'], '') arguments = Arguments(['-p', 'ETH/BTC'], '')
arguments.scripts_options() arguments.common_scripts_options()
args = arguments.get_parsed_arg() args = arguments.get_parsed_arg()
assert args.pairs == 'ETH/BTC' assert args.pairs == 'ETH/BTC'
@ -178,6 +178,7 @@ def test_download_data_options() -> None:
'--exchange', 'binance' '--exchange', 'binance'
] ]
arguments = Arguments(args, '') arguments = Arguments(args, '')
arguments.common_options()
arguments.download_data_options() arguments.download_data_options()
args = arguments.parse_args() args = arguments.parse_args()
assert args.pairs_file == 'file_with_pairs' assert args.pairs_file == 'file_with_pairs'

View File

@ -1994,6 +1994,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc
'gain': 'profit', 'gain': 'profit',
'limit': 1.172e-05, 'limit': 1.172e-05,
'amount': 90.99181073703367, 'amount': 90.99181073703367,
'order_type': 'limit',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'current_rate': 1.172e-05, 'current_rate': 1.172e-05,
'profit_amount': 6.126e-05, 'profit_amount': 6.126e-05,
@ -2040,6 +2041,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets,
'gain': 'loss', 'gain': 'loss',
'limit': 1.044e-05, 'limit': 1.044e-05,
'amount': 90.99181073703367, 'amount': 90.99181073703367,
'order_type': 'limit',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'current_rate': 1.044e-05, 'current_rate': 1.044e-05,
'profit_amount': -5.492e-05, 'profit_amount': -5.492e-05,
@ -2094,6 +2096,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
'gain': 'loss', 'gain': 'loss',
'limit': 1.08801e-05, 'limit': 1.08801e-05,
'amount': 90.99181073703367, 'amount': 90.99181073703367,
'order_type': 'limit',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'current_rate': 1.044e-05, 'current_rate': 1.044e-05,
'profit_amount': -1.498e-05, 'profit_amount': -1.498e-05,
@ -2265,6 +2268,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
'gain': 'profit', 'gain': 'profit',
'limit': 1.172e-05, 'limit': 1.172e-05,
'amount': 90.99181073703367, 'amount': 90.99181073703367,
'order_type': 'limit',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'current_rate': 1.172e-05, 'current_rate': 1.172e-05,
'profit_amount': 6.126e-05, 'profit_amount': 6.126e-05,
@ -2312,6 +2316,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
'gain': 'loss', 'gain': 'loss',
'limit': 1.044e-05, 'limit': 1.044e-05,
'amount': 90.99181073703367, 'amount': 90.99181073703367,
'order_type': 'limit',
'open_rate': 1.099e-05, 'open_rate': 1.099e-05,
'current_rate': 1.044e-05, 'current_rate': 1.044e-05,
'profit_amount': -5.492e-05, 'profit_amount': -5.492e-05,

View File

@ -1,17 +1,18 @@
from freqtrade.utils import setup_configuration, start_list_exchanges from freqtrade.utils import setup_utils_configuration, start_list_exchanges
from freqtrade.tests.conftest import get_args from freqtrade.tests.conftest import get_args
from freqtrade.state import RunMode from freqtrade.state import RunMode
import re import re
def test_setup_configuration(): def test_setup_utils_configuration():
args = [ args = [
'--config', 'config.json.example', '--config', 'config.json.example',
] ]
config = setup_configuration(get_args(args), RunMode.OTHER) config = setup_utils_configuration(get_args(args), RunMode.OTHER)
assert "exchange" in config assert "exchange" in config
assert config['exchange']['dry_run'] is True
assert config['exchange']['key'] == '' assert config['exchange']['key'] == ''
assert config['exchange']['secret'] == '' assert config['exchange']['secret'] == ''

View File

@ -10,15 +10,16 @@ from freqtrade.state import RunMode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def setup_configuration(args: Namespace, method: RunMode) -> Dict[str, Any]: def setup_utils_configuration(args: Namespace, method: RunMode) -> Dict[str, Any]:
""" """
Prepare the configuration for the Hyperopt module Prepare the configuration for utils subcommands
:param args: Cli args from Arguments() :param args: Cli args from Arguments()
:return: Configuration :return: Configuration
""" """
configuration = Configuration(args, method) configuration = Configuration(args, method)
config = configuration.load_config() config = configuration.load_config()
config['exchange']['dry_run'] = True
# Ensure we do not use Exchange credentials # Ensure we do not use Exchange credentials
config['exchange']['key'] = '' config['exchange']['key'] = ''
config['exchange']['secret'] = '' config['exchange']['secret'] = ''

View File

@ -1,6 +1,6 @@
# 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.667 ccxt==1.18.725
SQLAlchemy==1.3.4 SQLAlchemy==1.3.4
python-telegram-bot==11.1.0 python-telegram-bot==11.1.0
arrow==0.14.2 arrow==0.14.2

View File

@ -5,9 +5,9 @@
flake8==3.7.7 flake8==3.7.7
flake8-type-annotations==0.1.0 flake8-type-annotations==0.1.0
flake8-tidy-imports==2.0.0 flake8-tidy-imports==2.0.0
pytest==4.6.2 pytest==4.6.3
pytest-mock==1.10.4 pytest-mock==1.10.4
pytest-asyncio==0.10.0 pytest-asyncio==0.10.0
pytest-cov==2.7.1 pytest-cov==2.7.1
coveralls==1.8.0 coveralls==1.8.1
mypy==0.701 mypy==0.701

View File

@ -20,7 +20,8 @@ logger = logging.getLogger('download_backtest_data')
DEFAULT_DL_PATH = 'user_data/data' DEFAULT_DL_PATH = 'user_data/data'
arguments = Arguments(sys.argv[1:], 'download utility') arguments = Arguments(sys.argv[1:], 'Download backtest data')
arguments.common_options()
arguments.download_data_options() arguments.download_data_options()
# Do not read the default config if config is not specified # Do not read the default config if config is not specified

View File

@ -147,10 +147,12 @@ def plot_parse_args(args: List[str]) -> Dict[str, Any]:
:return: args: Array with all arguments :return: args: Array with all arguments
""" """
arguments = Arguments(args, 'Graph dataframe') arguments = Arguments(args, 'Graph dataframe')
arguments.common_options()
arguments.main_options()
arguments.common_optimize_options()
arguments.backtesting_options()
arguments.common_scripts_options()
arguments.plot_dataframe_options() arguments.plot_dataframe_options()
arguments.common_args_parser()
arguments.optimizer_shared_options(arguments.parser)
arguments.backtesting_options(arguments.parser)
parsed_args = arguments.parse_args() parsed_args = arguments.parse_args()
# Load the configuration # Load the configuration

View File

@ -206,10 +206,11 @@ def plot_parse_args(args: List[str]) -> Namespace:
:return: args: Array with all arguments :return: args: Array with all arguments
""" """
arguments = Arguments(args, 'Graph profits') arguments = Arguments(args, 'Graph profits')
arguments.scripts_options() arguments.common_options()
arguments.common_args_parser() arguments.main_options()
arguments.optimizer_shared_options(arguments.parser) arguments.common_optimize_options()
arguments.backtesting_options(arguments.parser) arguments.backtesting_options()
arguments.common_scripts_options()
return arguments.parse_args() return arguments.parse_args()