Merge branch 'feat/objectify-ccxt' into cxxt_obj_sellfix

This commit is contained in:
Matthias Voppichler 2018-04-21 22:39:22 +02:00
commit a140748b5a
28 changed files with 254 additions and 406 deletions

View File

@ -13,7 +13,7 @@ addons:
install: install:
- ./install_ta-lib.sh - ./install_ta-lib.sh
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
- pip install --upgrade flake8 coveralls - pip install --upgrade flake8 coveralls pytest-random-order
- pip install -r requirements.txt - pip install -r requirements.txt
- pip install -e . - pip install -e .
jobs: jobs:
@ -34,4 +34,4 @@ notifications:
cache: cache:
directories: directories:
- $HOME/.cache/pip - $HOME/.cache/pip
- ta-lib - ta-lib

View File

@ -1,6 +1,7 @@
""" """
Functions to analyze ticker data with indicators and produce buy and sell signals Functions to analyze ticker data with indicators and produce buy and sell signals
""" """
import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from enum import Enum from enum import Enum
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
@ -9,12 +10,14 @@ import arrow
from pandas import DataFrame, to_datetime from pandas import DataFrame, to_datetime
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import get_ticker_history
from freqtrade.logger import Logger
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.strategy.strategy import Strategy from freqtrade.strategy.strategy import Strategy
from freqtrade.constants import Constants from freqtrade.constants import Constants
logger = logging.getLogger(__name__)
class SignalType(Enum): class SignalType(Enum):
""" """
Enum to distinguish between buy and sell signals Enum to distinguish between buy and sell signals
@ -33,8 +36,6 @@ class Analyze(object):
Init Analyze Init Analyze
:param config: Bot configuration (use the one from Configuration()) :param config: Bot configuration (use the one from Configuration())
""" """
self.logger = Logger(name=__name__, level=config.get('loglevel')).get_logger()
self.config = config self.config = config
self.strategy = Strategy(self.config) self.strategy = Strategy(self.config)
@ -110,20 +111,20 @@ class Analyze(object):
""" """
ticker_hist = get_ticker_history(pair, interval) ticker_hist = get_ticker_history(pair, interval)
if not ticker_hist: if not ticker_hist:
self.logger.warning('Empty ticker history for pair %s', pair) logger.warning('Empty ticker history for pair %s', pair)
return False, False return False, False
try: try:
dataframe = self.analyze_ticker(ticker_hist) dataframe = self.analyze_ticker(ticker_hist)
except ValueError as error: except ValueError as error:
self.logger.warning( logger.warning(
'Unable to analyze ticker for pair %s: %s', 'Unable to analyze ticker for pair %s: %s',
pair, pair,
str(error) str(error)
) )
return False, False return False, False
except Exception as error: except Exception as error:
self.logger.exception( logger.exception(
'Unexpected error when analyzing ticker for pair %s: %s', 'Unexpected error when analyzing ticker for pair %s: %s',
pair, pair,
str(error) str(error)
@ -131,7 +132,7 @@ class Analyze(object):
return False, False return False, False
if dataframe.empty: if dataframe.empty:
self.logger.warning('Empty dataframe for pair %s', pair) logger.warning('Empty dataframe for pair %s', pair)
return False, False return False, False
latest = dataframe.iloc[-1] latest = dataframe.iloc[-1]
@ -140,7 +141,7 @@ class Analyze(object):
signal_date = arrow.get(latest['date']) signal_date = arrow.get(latest['date'])
interval_minutes = Constants.TICKER_INTERVAL_MINUTES[interval] interval_minutes = Constants.TICKER_INTERVAL_MINUTES[interval]
if signal_date < arrow.utcnow() - timedelta(minutes=(interval_minutes + 5)): if signal_date < arrow.utcnow() - timedelta(minutes=(interval_minutes + 5)):
self.logger.warning( logger.warning(
'Outdated history for pair %s. Last tick is %s minutes old', 'Outdated history for pair %s. Last tick is %s minutes old',
pair, pair,
(arrow.utcnow() - signal_date).seconds // 60 (arrow.utcnow() - signal_date).seconds // 60
@ -148,7 +149,7 @@ class Analyze(object):
return False, False return False, False
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1 (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
self.logger.debug( logger.debug(
'trigger: %s (pair=%s) buy=%s sell=%s', 'trigger: %s (pair=%s) buy=%s sell=%s',
latest['date'], latest['date'],
pair, pair,
@ -165,17 +166,17 @@ class Analyze(object):
""" """
# Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee)
if self.min_roi_reached(trade=trade, current_rate=rate, current_time=date): if self.min_roi_reached(trade=trade, current_rate=rate, current_time=date):
self.logger.debug('Required profit reached. Selling..') logger.debug('Required profit reached. Selling..')
return True return True
# Experimental: Check if the trade is profitable before selling it (avoid selling at loss) # Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
if self.config.get('experimental', {}).get('sell_profit_only', False): if self.config.get('experimental', {}).get('sell_profit_only', False):
self.logger.debug('Checking if trade is profitable..') logger.debug('Checking if trade is profitable..')
if trade.calc_profit(rate=rate) <= 0: if trade.calc_profit(rate=rate) <= 0:
return False return False
if sell and not buy and self.config.get('experimental', {}).get('use_sell_signal', False): if sell and not buy and self.config.get('experimental', {}).get('use_sell_signal', False):
self.logger.debug('Sell signal received. Selling..') logger.debug('Sell signal received. Selling..')
return True return True
return False return False
@ -188,7 +189,7 @@ class Analyze(object):
""" """
current_profit = trade.calc_profit_percent(current_rate) current_profit = trade.calc_profit_percent(current_rate)
if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss: if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss:
self.logger.debug('Stop loss hit.') logger.debug('Stop loss hit.')
return True return True
# Check if time matches and current rate is above threshold # Check if time matches and current rate is above threshold

View File

@ -3,6 +3,7 @@ This module contains the configuration class
""" """
import json import json
import logging
from argparse import Namespace from argparse import Namespace
from typing import Dict, Any from typing import Dict, Any
from jsonschema import Draft4Validator, validate from jsonschema import Draft4Validator, validate
@ -11,7 +12,9 @@ import ccxt
from freqtrade import OperationalException from freqtrade import OperationalException
from freqtrade.constants import Constants from freqtrade.constants import Constants
from freqtrade.logger import Logger
logger = logging.getLogger(__name__)
class Configuration(object): class Configuration(object):
@ -21,8 +24,6 @@ class Configuration(object):
""" """
def __init__(self, args: Namespace) -> None: def __init__(self, args: Namespace) -> None:
self.args = args self.args = args
self.logging = Logger(name=__name__)
self.logger = self.logging.get_logger()
self.config = None self.config = None
def load_config(self) -> Dict[str, Any]: def load_config(self) -> Dict[str, Any]:
@ -30,7 +31,7 @@ class Configuration(object):
Extract information for sys.argv and load the bot configuration Extract information for sys.argv and load the bot configuration
:return: Configuration dictionary :return: Configuration dictionary
""" """
self.logger.info('Using config: %s ...', self.args.config) logger.info('Using config: %s ...', self.args.config)
config = self._load_config_file(self.args.config) config = self._load_config_file(self.args.config)
# Add the strategy file to use # Add the strategy file to use
@ -57,7 +58,7 @@ class Configuration(object):
with open(path) as file: with open(path) as file:
conf = json.load(file) conf = json.load(file)
except FileNotFoundError: except FileNotFoundError:
self.logger.critical( logger.critical(
'Config file "%s" not found. Please create your config file', 'Config file "%s" not found. Please create your config file',
path path
) )
@ -65,7 +66,7 @@ class Configuration(object):
if 'internals' not in conf: if 'internals' not in conf:
conf['internals'] = {} conf['internals'] = {}
self.logger.info('Validating configuration ...') logger.info('Validating configuration ...')
return self._validate_config(conf) return self._validate_config(conf)
@ -78,13 +79,16 @@ class Configuration(object):
# Log level # Log level
if 'loglevel' in self.args and self.args.loglevel: if 'loglevel' in self.args and self.args.loglevel:
config.update({'loglevel': self.args.loglevel}) config.update({'loglevel': self.args.loglevel})
self.logging.set_level(self.args.loglevel) logging.basicConfig(
self.logger.info('Log level set at %s', config['loglevel']) level=config['loglevel'],
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
)
logger.info('Log level set to %s', logging.getLevelName(config['loglevel']))
# Add dynamic_whitelist if found # Add dynamic_whitelist if found
if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist: if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist:
config.update({'dynamic_whitelist': self.args.dynamic_whitelist}) config.update({'dynamic_whitelist': self.args.dynamic_whitelist})
self.logger.info( logger.info(
'Parameter --dynamic-whitelist detected. ' 'Parameter --dynamic-whitelist detected. '
'Using dynamically generated whitelist. ' 'Using dynamically generated whitelist. '
'(not applicable with Backtesting and Hyperopt)' '(not applicable with Backtesting and Hyperopt)'
@ -93,13 +97,13 @@ class Configuration(object):
# Add dry_run_db if found and the bot in dry run # Add dry_run_db if found and the bot in dry run
if self.args.dry_run_db and config.get('dry_run', False): if self.args.dry_run_db and config.get('dry_run', False):
config.update({'dry_run_db': True}) config.update({'dry_run_db': True})
self.logger.info('Parameter --dry-run-db detected ...') logger.info('Parameter --dry-run-db detected ...')
if config.get('dry_run_db', False): if config.get('dry_run_db', False):
if config.get('dry_run', False): if config.get('dry_run', False):
self.logger.info('Dry_run will use the DB file: "tradesv3.dry_run.sqlite"') logger.info('Dry_run will use the DB file: "tradesv3.dry_run.sqlite"')
else: else:
self.logger.info('Dry run is disabled. (--dry_run_db ignored)') logger.info('Dry run is disabled. (--dry_run_db ignored)')
# Check if the exchange set by the user is supported # Check if the exchange set by the user is supported
self.check_exchange(config) self.check_exchange(config)
@ -116,39 +120,39 @@ class Configuration(object):
# (that will override the strategy configuration) # (that will override the strategy configuration)
if 'ticker_interval' in self.args and self.args.ticker_interval: if 'ticker_interval' in self.args and self.args.ticker_interval:
config.update({'ticker_interval': self.args.ticker_interval}) config.update({'ticker_interval': self.args.ticker_interval})
self.logger.info('Parameter -i/--ticker-interval detected ...') logger.info('Parameter -i/--ticker-interval detected ...')
self.logger.info('Using ticker_interval: %s ...', config.get('ticker_interval')) logger.info('Using ticker_interval: %s ...', config.get('ticker_interval'))
# If -l/--live is used we add it to the configuration # If -l/--live is used we add it to the configuration
if 'live' in self.args and self.args.live: if 'live' in self.args and self.args.live:
config.update({'live': True}) config.update({'live': True})
self.logger.info('Parameter -l/--live detected ...') logger.info('Parameter -l/--live detected ...')
# If --realistic-simulation is used we add it to the configuration # If --realistic-simulation is used we add it to the configuration
if 'realistic_simulation' in self.args and self.args.realistic_simulation: if 'realistic_simulation' in self.args and self.args.realistic_simulation:
config.update({'realistic_simulation': True}) config.update({'realistic_simulation': True})
self.logger.info('Parameter --realistic-simulation detected ...') logger.info('Parameter --realistic-simulation detected ...')
self.logger.info('Using max_open_trades: %s ...', config.get('max_open_trades')) logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
# If --timerange is used we add it to the configuration # If --timerange is used we add it to the configuration
if 'timerange' in self.args and self.args.timerange: if 'timerange' in self.args and self.args.timerange:
config.update({'timerange': self.args.timerange}) config.update({'timerange': self.args.timerange})
self.logger.info('Parameter --timerange detected: %s ...', self.args.timerange) logger.info('Parameter --timerange detected: %s ...', self.args.timerange)
# If --datadir is used we add it to the configuration # If --datadir is used we add it to the configuration
if 'datadir' in self.args and self.args.datadir: if 'datadir' in self.args and self.args.datadir:
config.update({'datadir': self.args.datadir}) config.update({'datadir': self.args.datadir})
self.logger.info('Parameter --datadir detected: %s ...', self.args.datadir) logger.info('Parameter --datadir detected: %s ...', self.args.datadir)
# If -r/--refresh-pairs-cached is used we add it to the configuration # If -r/--refresh-pairs-cached is used we add it to the configuration
if 'refresh_pairs' in self.args and self.args.refresh_pairs: if 'refresh_pairs' in self.args and self.args.refresh_pairs:
config.update({'refresh_pairs': True}) config.update({'refresh_pairs': True})
self.logger.info('Parameter -r/--refresh-pairs-cached detected ...') logger.info('Parameter -r/--refresh-pairs-cached detected ...')
# If --export is used we add it to the configuration # If --export is used we add it to the configuration
if 'export' in self.args and self.args.export: if 'export' in self.args and self.args.export:
config.update({'export': self.args.export}) config.update({'export': self.args.export})
self.logger.info('Parameter --export detected: %s ...', self.args.export) logger.info('Parameter --export detected: %s ...', self.args.export)
return config return config
@ -160,18 +164,18 @@ class Configuration(object):
# If --realistic-simulation is used we add it to the configuration # If --realistic-simulation is used we add it to the configuration
if 'epochs' in self.args and self.args.epochs: if 'epochs' in self.args and self.args.epochs:
config.update({'epochs': self.args.epochs}) config.update({'epochs': self.args.epochs})
self.logger.info('Parameter --epochs detected ...') logger.info('Parameter --epochs detected ...')
self.logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs')) logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs'))
# If --mongodb is used we add it to the configuration # If --mongodb is used we add it to the configuration
if 'mongodb' in self.args and self.args.mongodb: if 'mongodb' in self.args and self.args.mongodb:
config.update({'mongodb': self.args.mongodb}) config.update({'mongodb': self.args.mongodb})
self.logger.info('Parameter --use-mongodb detected ...') logger.info('Parameter --use-mongodb detected ...')
# If --spaces is used we add it to the configuration # If --spaces is used we add it to the configuration
if 'spaces' in self.args and self.args.spaces: if 'spaces' in self.args and self.args.spaces:
config.update({'spaces': self.args.spaces}) config.update({'spaces': self.args.spaces})
self.logger.info('Parameter -s/--spaces detected: %s', config.get('spaces')) logger.info('Parameter -s/--spaces detected: %s', config.get('spaces'))
return config return config
@ -185,7 +189,7 @@ class Configuration(object):
validate(conf, Constants.CONF_SCHEMA) validate(conf, Constants.CONF_SCHEMA)
return conf return conf
except ValidationError as exception: except ValidationError as exception:
self.logger.fatal( logger.fatal(
'Invalid configuration. See config.json.example. Reason: %s', 'Invalid configuration. See config.json.example. Reason: %s',
exception exception
) )
@ -215,10 +219,10 @@ class Configuration(object):
'The following exchanges are supported: {}'\ 'The following exchanges are supported: {}'\
.format(exchange, ', '.join(ccxt.exchanges)) .format(exchange, ', '.join(ccxt.exchanges))
self.logger.critical(exception_msg) logger.critical(exception_msg)
raise OperationalException( raise OperationalException(
exception_msg exception_msg
) )
self.logger.debug('Exchange "%s" supported', exchange) logger.debug('Exchange "%s" supported', exchange)
return True return True

View File

@ -74,6 +74,7 @@ def init(config: dict) -> None:
'secret': exchange_config.get('secret'), 'secret': exchange_config.get('secret'),
'password': exchange_config.get('password'), 'password': exchange_config.get('password'),
'uid': exchange_config.get('uid'), 'uid': exchange_config.get('uid'),
'enableRateLimit': True,
}) })
except (KeyError, AttributeError): except (KeyError, AttributeError):
raise OperationalException('Exchange {} is not supported'.format(name)) raise OperationalException('Exchange {} is not supported'.format(name))

View File

@ -4,6 +4,7 @@ Freqtrade is the main module of this bot. It contains the class Freqtrade()
import copy import copy
import json import json
import logging
import time import time
import traceback import traceback
from datetime import datetime from datetime import datetime
@ -13,16 +14,20 @@ import arrow
import requests import requests
from cachetools import cached, TTLCache from cachetools import cached, TTLCache
from freqtrade import (DependencyException, OperationalException, exchange, persistence) from freqtrade import (
DependencyException, OperationalException, exchange, persistence, __version__
)
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade.constants import Constants from freqtrade.constants import Constants
from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.logger import Logger
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc.rpc_manager import RPCManager from freqtrade.rpc.rpc_manager import RPCManager
from freqtrade.state import State from freqtrade.state import State
logger = logging.getLogger(__name__)
class FreqtradeBot(object): class FreqtradeBot(object):
""" """
Freqtrade is the main class of the bot. Freqtrade is the main class of the bot.
@ -37,8 +42,10 @@ class FreqtradeBot(object):
:param db_url: database connector string for sqlalchemy (Optional) :param db_url: database connector string for sqlalchemy (Optional)
""" """
# Init the logger logger.info(
self.logger = Logger(name=__name__, level=config.get('loglevel')).get_logger() 'Starting freqtrade %s',
__version__,
)
# Init bot states # Init bot states
self.state = State.STOPPED self.state = State.STOPPED
@ -81,7 +88,7 @@ class FreqtradeBot(object):
:return: None :return: None
""" """
self.rpc.send_msg('*Status:* `Stopping trader...`') self.rpc.send_msg('*Status:* `Stopping trader...`')
self.logger.info('Stopping trader and cleaning up modules...') logger.info('Stopping trader and cleaning up modules...')
self.state = State.STOPPED self.state = State.STOPPED
self.rpc.cleanup() self.rpc.cleanup()
persistence.cleanup() persistence.cleanup()
@ -97,7 +104,7 @@ class FreqtradeBot(object):
state = self.state state = self.state
if state != old_state: if state != old_state:
self.rpc.send_msg('*Status:* `{}`'.format(state.name.lower())) self.rpc.send_msg('*Status:* `{}`'.format(state.name.lower()))
self.logger.info('Changing state to: %s', state.name) logger.info('Changing state to: %s', state.name)
if state == State.STOPPED: if state == State.STOPPED:
time.sleep(1) time.sleep(1)
@ -126,7 +133,7 @@ class FreqtradeBot(object):
result = func(*args, **kwargs) result = func(*args, **kwargs)
end = time.time() end = time.time()
duration = max(min_secs - (end - start), 0.0) duration = max(min_secs - (end - start), 0.0)
self.logger.debug('Throttling %s for %.2f seconds', func.__name__, duration) logger.debug('Throttling %s for %.2f seconds', func.__name__, duration)
time.sleep(duration) time.sleep(duration)
return result return result
@ -167,7 +174,7 @@ class FreqtradeBot(object):
Trade.session.flush() Trade.session.flush()
except (requests.exceptions.RequestException, json.JSONDecodeError) as error: except (requests.exceptions.RequestException, json.JSONDecodeError) as error:
self.logger.warning('%s, retrying in 30 seconds...', error) logger.warning('%s, retrying in 30 seconds...', error)
time.sleep(Constants.RETRY_TIMEOUT) time.sleep(Constants.RETRY_TIMEOUT)
except OperationalException: except OperationalException:
self.rpc.send_msg( self.rpc.send_msg(
@ -177,7 +184,7 @@ class FreqtradeBot(object):
hint='Issue `/start` if you think it is safe to restart.' hint='Issue `/start` if you think it is safe to restart.'
) )
) )
self.logger.exception('OperationalException. Stopping trader ...') logger.exception('OperationalException. Stopping trader ...')
self.state = State.STOPPED self.state = State.STOPPED
return state_changed return state_changed
@ -228,7 +235,7 @@ class FreqtradeBot(object):
# Market is not active # Market is not active
if not market['active']: if not market['active']:
sanitized_whitelist.remove(pair) sanitized_whitelist.remove(pair)
self.logger.info( logger.info(
'Ignoring %s from whitelist. Market is not active.', 'Ignoring %s from whitelist. Market is not active.',
pair pair
) )
@ -260,7 +267,7 @@ class FreqtradeBot(object):
stake_amount = self.config['stake_amount'] stake_amount = self.config['stake_amount']
interval = self.analyze.get_ticker_interval() interval = self.analyze.get_ticker_interval()
self.logger.info( logger.info(
'Checking buy signals to create a new trade with stake_amount: %f ...', 'Checking buy signals to create a new trade with stake_amount: %f ...',
stake_amount stake_amount
) )
@ -275,7 +282,7 @@ class FreqtradeBot(object):
for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
if trade.pair in whitelist: if trade.pair in whitelist:
whitelist.remove(trade.pair) whitelist.remove(trade.pair)
self.logger.debug('Ignoring %s in pair whitelist', trade.pair) logger.debug('Ignoring %s in pair whitelist', trade.pair)
if not whitelist: if not whitelist:
raise DependencyException('No currency pairs in whitelist') raise DependencyException('No currency pairs in whitelist')
@ -343,10 +350,10 @@ class FreqtradeBot(object):
if self.create_trade(): if self.create_trade():
return True return True
self.logger.info('Found no buy signals for whitelisted currencies. Trying again..') logger.info('Found no buy signals for whitelisted currencies. Trying again..')
return False return False
except DependencyException as exception: except DependencyException as exception:
self.logger.warning('Unable to create trade: %s', exception) logger.warning('Unable to create trade: %s', exception)
return False return False
def process_maybe_execute_sell(self, trade: Trade) -> bool: def process_maybe_execute_sell(self, trade: Trade) -> bool:
@ -357,20 +364,20 @@ class FreqtradeBot(object):
# Get order details for actual price per unit # Get order details for actual price per unit
if trade.open_order_id: if trade.open_order_id:
# Update trade with order values # Update trade with order values
self.logger.info('Found open order for %s', trade) logger.info('Found open order for %s', trade)
order = exchange.get_order(trade.open_order_id, trade.pair) order = exchange.get_order(trade.open_order_id, trade.pair)
# TODO: correct place here ?? # TODO: correct place here ??
# Try update amount (binance-fix) # Try update amount (binance-fix)
try: try:
new_amount = self.get_real_amount(trade) new_amount = self.get_real_amount(trade)
if order['amount'] != new_amount: if order['amount'] != new_amount:
self.logger.info("Updating amount for Trade {} from {} to {}".format( logger.info("Updating amount for Trade {} from {} to {}".format(
trade, order['amount'], new_amount)) trade, order['amount'], new_amount))
order['amount'] = new_amount order['amount'] = new_amount
trade.fee_open = 0 trade.fee_open = 0
except OperationalException as exception: except OperationalException as exception:
self.logger.warning("could not update trade amount: %s", exception) logger.warning("could not update trade amount: %s", exception)
trade.update(order) trade.update(order)
@ -400,7 +407,7 @@ class FreqtradeBot(object):
fee_abs += trade["fee"]["cost"] fee_abs += trade["fee"]["cost"]
if amount != order.amount: if amount != order.amount:
self.logger.warning("amount {} does not match amount {}".format(amount, order.amount)) logger.warning("amount {} does not match amount {}".format(amount, order.amount))
raise OperationalException("Half bought? Amounts don't match") raise OperationalException("Half bought? Amounts don't match")
real_amount = amount - fee_abs real_amount = amount - fee_abs
return real_amount return real_amount
@ -412,16 +419,16 @@ class FreqtradeBot(object):
""" """
if trade.is_open and trade.open_order_id is None: if trade.is_open and trade.open_order_id is None:
# Trade is not open anymore # Trade is not open anymore
self.logger.warning("could not open trade amount - Trade is not open anymore") logger.warning("could not open trade amount - Trade is not open anymore")
return False return False
try: try:
new_amount = self.get_real_amount(trade) new_amount = self.get_real_amount(trade)
except OperationalException as exception: except OperationalException as exception:
self.logger.warning("could not update trade amount: %s", exception) logger.warning("could not update trade amount: %s", exception)
return False return False
# updating amount # updating amount
if trade.amount != new_amount: if trade.amount != new_amount:
self.logger.info("Updating amount for Trade {} from {} to {}".format( logger.info("Updating amount for Trade {} from {} to {}".format(
trade, trade.amount, new_amount)) trade, trade.amount, new_amount))
trade.amount = new_amount trade.amount = new_amount
trade.fee_open = 0 # Fee was applied - set to 0 for buy trade.fee_open = 0 # Fee was applied - set to 0 for buy
@ -436,7 +443,7 @@ class FreqtradeBot(object):
if not trade.is_open: if not trade.is_open:
raise ValueError('attempt to handle closed trade: {}'.format(trade)) raise ValueError('attempt to handle closed trade: {}'.format(trade))
self.logger.debug('Handling %s ...', trade) logger.debug('Handling %s ...', trade)
current_rate = exchange.get_ticker(trade.pair)['bid'] current_rate = exchange.get_ticker(trade.pair)['bid']
(buy, sell) = (False, False) (buy, sell) = (False, False)
@ -462,7 +469,7 @@ class FreqtradeBot(object):
try: try:
order = exchange.get_order(trade.open_order_id, trade.pair) order = exchange.get_order(trade.open_order_id, trade.pair)
except requests.exceptions.RequestException: except requests.exceptions.RequestException:
self.logger.info( logger.info(
'Cannot query order for %s due to %s', 'Cannot query order for %s due to %s',
trade, trade,
traceback.format_exc()) traceback.format_exc())
@ -492,7 +499,7 @@ class FreqtradeBot(object):
# FIX? do we really need to flush, caller of # FIX? do we really need to flush, caller of
# check_handle_timedout will flush afterwards # check_handle_timedout will flush afterwards
Trade.session.flush() Trade.session.flush()
self.logger.info('Buy order timeout for %s.', trade) logger.info('Buy order timeout for %s.', trade)
self.rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format( self.rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format(
trade.pair.replace('_', '/'))) trade.pair.replace('_', '/')))
return True return True
@ -502,7 +509,7 @@ class FreqtradeBot(object):
trade.amount = order['amount'] - order['remaining'] trade.amount = order['amount'] - order['remaining']
trade.stake_amount = trade.amount * trade.open_rate trade.stake_amount = trade.amount * trade.open_rate
trade.open_order_id = None trade.open_order_id = None
self.logger.info('Partial buy order timeout for %s.', trade) logger.info('Partial buy order timeout for %s.', trade)
self.rpc.send_msg('*Timeout:* Remaining buy order for {} cancelled'.format( self.rpc.send_msg('*Timeout:* Remaining buy order for {} cancelled'.format(
trade.pair.replace('_', '/'))) trade.pair.replace('_', '/')))
return False return False
@ -523,7 +530,7 @@ class FreqtradeBot(object):
trade.open_order_id = None trade.open_order_id = None
self.rpc.send_msg('*Timeout:* Unfilled sell order for {} cancelled'.format( self.rpc.send_msg('*Timeout:* Unfilled sell order for {} cancelled'.format(
trade.pair.replace('_', '/'))) trade.pair.replace('_', '/')))
self.logger.info('Sell order timeout for %s.', trade) logger.info('Sell order timeout for %s.', trade)
return True return True
# TODO: figure out how to handle partially complete sell orders # TODO: figure out how to handle partially complete sell orders

View File

@ -1,83 +0,0 @@
# pragma pylint: disable=too-few-public-methods
"""
This module contains the class for logger and logging messages
"""
import logging
class Logger(object):
"""
Logging class
"""
def __init__(self, name='', level=logging.INFO) -> None:
"""
Init the logger class
:param name: Name of the Logger scope
:param level: Logger level that should be used
:return: None
"""
self.name = name
self.logger = None
if level is None:
level = logging.INFO
self.level = level
self._init_logger()
def _init_logger(self) -> None:
"""
Setup the bot logger configuration
:return: None
"""
logging.basicConfig(
level=self.level,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
)
self.logger = self.get_logger()
self.set_level(self.level)
def get_logger(self) -> logging.RootLogger:
"""
Return the logger instance to use for sending message
:return: the logger instance
"""
return logging.getLogger(self.name)
def set_name(self, name: str) -> logging.RootLogger:
"""
Set the name of the logger
:param name: Name of the logger
:return: None
"""
self.name = name
self.logger = self.get_logger()
return self.logger
def set_level(self, level) -> None:
"""
Set the level of the logger
:param level:
:return: None
"""
self.level = level
self.logger.setLevel(self.level)
def set_format(self, log_format: str, propagate: bool = False) -> None:
"""
Set a new logging format
:return: None
"""
handler = logging.StreamHandler()
len_handlers = len(self.logger.handlers)
if len_handlers:
self.logger.removeHandler(handler)
handler.setFormatter(logging.Formatter(log_format))
self.logger.addHandler(handler)
self.logger.propagate = propagate

View File

@ -8,13 +8,11 @@ import logging
import sys import sys
from typing import List from typing import List
from freqtrade import (__version__)
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration from freqtrade.configuration import Configuration
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.logger import Logger
logger = Logger(name='freqtrade').get_logger() logger = logging.getLogger('freqtrade')
def main(sysargv: List[str]) -> None: def main(sysargv: List[str]) -> None:
@ -34,19 +32,13 @@ def main(sysargv: List[str]) -> None:
args.func(args) args.func(args)
return 0 return 0
logger.info(
'Starting freqtrade %s (loglevel=%s)',
__version__,
logging.getLevelName(args.loglevel)
)
freqtrade = None freqtrade = None
try: try:
# Load and validate configuration # Load and validate configuration
configuration = Configuration(args) config = Configuration(args).get_config()
# Init the bot # Init the bot
freqtrade = FreqtradeBot(configuration.get_config()) freqtrade = FreqtradeBot(config)
state = None state = None
while 1: while 1:

View File

@ -2,15 +2,15 @@
import gzip import gzip
import json import json
import logging
import os import os
from typing import Optional, List, Dict, Tuple from typing import Optional, List, Dict, Tuple
from freqtrade import misc from freqtrade import misc
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import get_ticker_history
from freqtrade.logger import Logger
from user_data.hyperopt_conf import hyperopt_optimize_conf from user_data.hyperopt_conf import hyperopt_optimize_conf
logger = Logger(name=__name__).get_logger() logger = logging.getLogger(__name__)
def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) -> List[Dict]: def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) -> List[Dict]:

View File

@ -3,6 +3,7 @@
""" """
This module contains the backtesting logic This module contains the backtesting logic
""" """
import logging
from argparse import Namespace from argparse import Namespace
from typing import Dict, Tuple, Any, List, Optional from typing import Dict, Tuple, Any, List, Optional
@ -15,11 +16,13 @@ from freqtrade import exchange
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration from freqtrade.configuration import Configuration
from freqtrade.logger import Logger
from freqtrade.misc import file_dump_json from freqtrade.misc import file_dump_json
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
logger = logging.getLogger(__name__)
class Backtesting(object): class Backtesting(object):
""" """
Backtesting class, this class contains all the logic to run a backtest Backtesting class, this class contains all the logic to run a backtest
@ -29,10 +32,6 @@ class Backtesting(object):
backtesting.start() backtesting.start()
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Dict[str, Any]) -> None:
# Init the logger
self.logging = Logger(name=__name__, level=config['loglevel'])
self.logger = self.logging.get_logger()
self.config = config self.config = config
self.analyze = None self.analyze = None
self.ticker_interval = None self.ticker_interval = None
@ -208,7 +207,7 @@ class Backtesting(object):
# For now export inside backtest(), maybe change so that backtest() # For now export inside backtest(), maybe change so that backtest()
# returns a tuple like: (dataframe, records, logs, etc) # returns a tuple like: (dataframe, records, logs, etc)
if record and record.find('trades') >= 0: if record and record.find('trades') >= 0:
self.logger.info('Dumping backtest results') logger.info('Dumping backtest results')
file_dump_json('backtest-result.json', records) file_dump_json('backtest-result.json', records)
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration'] labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
return DataFrame.from_records(trades, columns=labels) return DataFrame.from_records(trades, columns=labels)
@ -220,15 +219,15 @@ class Backtesting(object):
""" """
data = {} data = {}
pairs = self.config['exchange']['pair_whitelist'] pairs = self.config['exchange']['pair_whitelist']
self.logger.info('Using stake_currency: %s ...', self.config['stake_currency']) logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
self.logger.info('Using stake_amount: %s ...', self.config['stake_amount']) logger.info('Using stake_amount: %s ...', self.config['stake_amount'])
if self.config.get('live'): if self.config.get('live'):
self.logger.info('Downloading data for all pairs in whitelist ...') logger.info('Downloading data for all pairs in whitelist ...')
for pair in pairs: for pair in pairs:
data[pair] = exchange.get_ticker_history(pair, self.ticker_interval) data[pair] = exchange.get_ticker_history(pair, self.ticker_interval)
else: else:
self.logger.info('Using local backtesting data (using whitelist in given config) ...') logger.info('Using local backtesting data (using whitelist in given config) ...')
timerange = Arguments.parse_timerange(self.config.get('timerange')) timerange = Arguments.parse_timerange(self.config.get('timerange'))
data = optimize.load_data( data = optimize.load_data(
@ -243,14 +242,14 @@ class Backtesting(object):
if self.config.get('realistic_simulation', False): if self.config.get('realistic_simulation', False):
max_open_trades = self.config['max_open_trades'] max_open_trades = self.config['max_open_trades']
else: else:
self.logger.info('Ignoring max_open_trades (realistic_simulation not set) ...') logger.info('Ignoring max_open_trades (realistic_simulation not set) ...')
max_open_trades = 0 max_open_trades = 0
preprocessed = self.tickerdata_to_dataframe(data) preprocessed = self.tickerdata_to_dataframe(data)
# Print timeframe # Print timeframe
min_date, max_date = self.get_timeframe(preprocessed) min_date, max_date = self.get_timeframe(preprocessed)
self.logger.info( logger.info(
'Measuring data from %s up to %s (%s days)..', 'Measuring data from %s up to %s (%s days)..',
min_date.isoformat(), min_date.isoformat(),
max_date.isoformat(), max_date.isoformat(),
@ -271,9 +270,7 @@ class Backtesting(object):
'record': self.config.get('export') 'record': self.config.get('export')
} }
) )
logger.info(
self.logging.set_format('%(message)s')
self.logger.info(
'\n==================================== ' '\n==================================== '
'BACKTESTING REPORT' 'BACKTESTING REPORT'
' ====================================\n' ' ====================================\n'
@ -309,7 +306,6 @@ def start(args: Namespace) -> None:
""" """
# Initialize logger # Initialize logger
logger = Logger(name=__name__).get_logger()
logger.info('Starting freqtrade in Backtesting mode') logger.info('Starting freqtrade in Backtesting mode')
# Initialize configuration # Initialize configuration

View File

@ -25,12 +25,14 @@ from pandas import DataFrame
import freqtrade.vendor.qtpylib.indicators as qtpylib import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration from freqtrade.configuration import Configuration
from freqtrade.logger import Logger
from freqtrade.optimize import load_data from freqtrade.optimize import load_data
from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.backtesting import Backtesting
from user_data.hyperopt_conf import hyperopt_optimize_conf from user_data.hyperopt_conf import hyperopt_optimize_conf
logger = logging.getLogger(__name__)
class Hyperopt(Backtesting): class Hyperopt(Backtesting):
""" """
Hyperopt class, this class contains all the logic to run a hyperopt simulation Hyperopt class, this class contains all the logic to run a hyperopt simulation
@ -42,11 +44,6 @@ class Hyperopt(Backtesting):
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Dict[str, Any]) -> None:
super().__init__(config) super().__init__(config)
# Rename the logging to display Hyperopt file instead of Backtesting
self.logging = Logger(name=__name__, level=config['loglevel'])
self.logger = self.logging.get_logger()
# set TARGET_TRADES to suit your number concurrent trades so its realistic # set TARGET_TRADES to suit your number concurrent trades so its realistic
# to the number of days # to the number of days
self.target_trades = 600 self.target_trades = 600
@ -194,14 +191,14 @@ class Hyperopt(Backtesting):
""" """
Save hyperopt trials to file Save hyperopt trials to file
""" """
self.logger.info('Saving Trials to \'%s\'', self.trials_file) logger.info('Saving Trials to \'%s\'', self.trials_file)
pickle.dump(self.trials, open(self.trials_file, 'wb')) pickle.dump(self.trials, open(self.trials_file, 'wb'))
def read_trials(self) -> Trials: def read_trials(self) -> Trials:
""" """
Read hyperopt trials file Read hyperopt trials file
""" """
self.logger.info('Reading Trials from \'%s\'', self.trials_file) logger.info('Reading Trials from \'%s\'', self.trials_file)
trials = pickle.load(open(self.trials_file, 'rb')) trials = pickle.load(open(self.trials_file, 'rb'))
os.remove(self.trials_file) os.remove(self.trials_file)
return trials return trials
@ -212,7 +209,7 @@ class Hyperopt(Backtesting):
""" """
vals = json.dumps(self.trials.best_trial['misc']['vals'], indent=4) vals = json.dumps(self.trials.best_trial['misc']['vals'], indent=4)
results = self.trials.best_trial['result']['result'] results = self.trials.best_trial['result']['result']
self.logger.info('Best result:\n%s\nwith values:\n%s', results, vals) logger.info('Best result:\n%s\nwith values:\n%s', results, vals)
def log_results(self, results) -> None: def log_results(self, results) -> None:
""" """
@ -220,13 +217,13 @@ class Hyperopt(Backtesting):
""" """
if results['loss'] < self.current_best_loss: if results['loss'] < self.current_best_loss:
self.current_best_loss = results['loss'] self.current_best_loss = results['loss']
log_msg = '{:5d}/{}: {}. Loss {:.5f}'.format( log_msg = '\n{:5d}/{}: {}. Loss {:.5f}'.format(
results['current_tries'], results['current_tries'],
results['total_tries'], results['total_tries'],
results['result'], results['result'],
results['loss'] results['loss']
) )
self.logger.info(log_msg) print(log_msg)
else: else:
print('.', end='') print('.', end='')
sys.stdout.flush() sys.stdout.flush()
@ -511,8 +508,8 @@ class Hyperopt(Backtesting):
self.processed = self.tickerdata_to_dataframe(data) self.processed = self.tickerdata_to_dataframe(data)
if self.config.get('mongodb'): if self.config.get('mongodb'):
self.logger.info('Using mongodb ...') logger.info('Using mongodb ...')
self.logger.info( logger.info(
'Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!' 'Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!'
) )
@ -522,7 +519,7 @@ class Hyperopt(Backtesting):
exp_key='exp1' exp_key='exp1'
) )
else: else:
self.logger.info('Preparing Trials..') logger.info('Preparing Trials..')
signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGINT, self.signal_handler)
# read trials file if we have one # read trials file if we have one
if os.path.exists(self.trials_file) and os.path.getsize(self.trials_file) > 0: if os.path.exists(self.trials_file) and os.path.getsize(self.trials_file) > 0:
@ -530,16 +527,13 @@ class Hyperopt(Backtesting):
self.current_tries = len(self.trials.results) self.current_tries = len(self.trials.results)
self.total_tries += self.current_tries self.total_tries += self.current_tries
self.logger.info( logger.info(
'Continuing with trials. Current: %d, Total: %d', 'Continuing with trials. Current: %d, Total: %d',
self.current_tries, self.current_tries,
self.total_tries self.total_tries
) )
try: try:
# change the Logging format
self.logging.set_format('\n%(message)s')
best_parameters = fmin( best_parameters = fmin(
fn=self.generate_optimizer, fn=self.generate_optimizer,
space=self.hyperopt_space(), space=self.hyperopt_space(),
@ -563,11 +557,11 @@ class Hyperopt(Backtesting):
best_parameters best_parameters
) )
self.logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4)) logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
if 'roi_t1' in best_parameters: if 'roi_t1' in best_parameters:
self.logger.info('ROI table:\n%s', self.generate_roi_table(best_parameters)) logger.info('ROI table:\n%s', self.generate_roi_table(best_parameters))
self.logger.info('Best Result:\n%s', best_result) logger.info('Best Result:\n%s', best_result)
# Store trials result to file to resume next time # Store trials result to file to resume next time
self.save_trials() self.save_trials()
@ -576,7 +570,7 @@ class Hyperopt(Backtesting):
""" """
Hyperopt SIGINT handler Hyperopt SIGINT handler
""" """
self.logger.info( logger.info(
'Hyperopt received %s', 'Hyperopt received %s',
signal.Signals(sig).name signal.Signals(sig).name
) )
@ -597,8 +591,6 @@ def start(args: Namespace) -> None:
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING) logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING) logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
# Initialize logger
logger = Logger(name=__name__).get_logger()
logger.info('Starting freqtrade in Hyperopt mode') logger.info('Starting freqtrade in Hyperopt mode')
# Initialize configuration # Initialize configuration

View File

@ -1,7 +1,7 @@
""" """
This module contains class to define a RPC communications This module contains class to define a RPC communications
""" """
import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from decimal import Decimal from decimal import Decimal
from typing import Tuple, Any from typing import Tuple, Any
@ -11,12 +11,14 @@ import sqlalchemy as sql
from pandas import DataFrame from pandas import DataFrame
from freqtrade import exchange from freqtrade import exchange
from freqtrade.logger import Logger
from freqtrade.misc import shorten_date from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.state import State from freqtrade.state import State
logger = logging.getLogger(__name__)
class RPC(object): class RPC(object):
""" """
RPC class can be used to have extra feature, like bot data, and access to DB data RPC class can be used to have extra feature, like bot data, and access to DB data
@ -28,10 +30,6 @@ class RPC(object):
:return: None :return: None
""" """
self.freqtrade = freqtrade self.freqtrade = freqtrade
self.logger = Logger(
name=__name__,
level=self.freqtrade.config.get('loglevel')
).get_logger()
def rpc_trade_status(self) -> Tuple[bool, Any]: def rpc_trade_status(self) -> Tuple[bool, Any]:
""" """
@ -350,7 +348,7 @@ class RPC(object):
) )
).first() ).first()
if not trade: if not trade:
self.logger.warning('forcesell: Invalid argument received') logger.warning('forcesell: Invalid argument received')
return True, 'Invalid argument.' return True, 'Invalid argument.'
_exec_forcesell(trade) _exec_forcesell(trade)

View File

@ -1,11 +1,14 @@
""" """
This module contains class to manage RPC communications (Telegram, Slack, ...) This module contains class to manage RPC communications (Telegram, Slack, ...)
""" """
import logging
from freqtrade.logger import Logger
from freqtrade.rpc.telegram import Telegram from freqtrade.rpc.telegram import Telegram
logger = logging.getLogger(__name__)
class RPCManager(object): class RPCManager(object):
""" """
Class to manage RPC objects (Telegram, Slack, ...) Class to manage RPC objects (Telegram, Slack, ...)
@ -18,12 +21,6 @@ class RPCManager(object):
""" """
self.freqtrade = freqtrade self.freqtrade = freqtrade
# Init the logger
self.logger = Logger(
name=__name__,
level=self.freqtrade.config.get('loglevel')
).get_logger()
self.registered_modules = [] self.registered_modules = []
self.telegram = None self.telegram = None
self._init() self._init()
@ -34,7 +31,7 @@ class RPCManager(object):
:return: :return:
""" """
if self.freqtrade.config['telegram'].get('enabled', False): if self.freqtrade.config['telegram'].get('enabled', False):
self.logger.info('Enabling rpc.telegram ...') logger.info('Enabling rpc.telegram ...')
self.registered_modules.append('telegram') self.registered_modules.append('telegram')
self.telegram = Telegram(self.freqtrade) self.telegram = Telegram(self.freqtrade)
@ -44,7 +41,7 @@ class RPCManager(object):
:return: None :return: None
""" """
if 'telegram' in self.registered_modules: if 'telegram' in self.registered_modules:
self.logger.info('Cleaning up rpc.telegram ...') logger.info('Cleaning up rpc.telegram ...')
self.registered_modules.remove('telegram') self.registered_modules.remove('telegram')
self.telegram.cleanup() self.telegram.cleanup()
@ -54,6 +51,6 @@ class RPCManager(object):
:param msg: message :param msg: message
:return: None :return: None
""" """
self.logger.info(msg) logger.info(msg)
if 'telegram' in self.registered_modules: if 'telegram' in self.registered_modules:
self.telegram.send_msg(msg) self.telegram.send_msg(msg)

View File

@ -3,7 +3,7 @@
""" """
This module manage Telegram communication This module manage Telegram communication
""" """
import logging
from typing import Any, Callable from typing import Any, Callable
from tabulate import tabulate from tabulate import tabulate
@ -15,6 +15,9 @@ from freqtrade.__init__ import __version__
from freqtrade.rpc.rpc import RPC from freqtrade.rpc.rpc import RPC
logger = logging.getLogger(__name__)
def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[..., Any]: def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[..., Any]:
""" """
Decorator to check if the message comes from the correct chat_id Decorator to check if the message comes from the correct chat_id
@ -31,13 +34,13 @@ def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[
chat_id = int(self._config['telegram']['chat_id']) chat_id = int(self._config['telegram']['chat_id'])
if int(update.message.chat_id) != chat_id: if int(update.message.chat_id) != chat_id:
self.logger.info( logger.info(
'Rejected unauthorized message from: %s', 'Rejected unauthorized message from: %s',
update.message.chat_id update.message.chat_id
) )
return wrapper return wrapper
self.logger.info( logger.info(
'Executing handler: %s for chat_id: %s', 'Executing handler: %s for chat_id: %s',
command_handler.__name__, command_handler.__name__,
chat_id chat_id
@ -45,7 +48,7 @@ def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[
try: try:
return command_handler(self, *args, **kwargs) return command_handler(self, *args, **kwargs)
except BaseException: except BaseException:
self.logger.exception('Exception occurred within Telegram module') logger.exception('Exception occurred within Telegram module')
return wrapper return wrapper
@ -101,7 +104,7 @@ class Telegram(RPC):
timeout=30, timeout=30,
read_latency=60, read_latency=60,
) )
self.logger.info( logger.info(
'rpc.telegram is listening for following commands: %s', 'rpc.telegram is listening for following commands: %s',
[h.command for h in handles] [h.command for h in handles]
) )
@ -357,7 +360,7 @@ class Telegram(RPC):
'max': [self._config['max_open_trades']] 'max': [self._config['max_open_trades']]
}, headers=['current', 'max'], tablefmt='simple') }, headers=['current', 'max'], tablefmt='simple')
message = "<pre>{}</pre>".format(message) message = "<pre>{}</pre>".format(message)
self.logger.debug(message) logger.debug(message)
self.send_msg(message, parse_mode=ParseMode.HTML) self.send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only @authorized_only
@ -428,7 +431,7 @@ class Telegram(RPC):
except NetworkError as network_err: except NetworkError as network_err:
# Sometimes the telegram server resets the current connection, # Sometimes the telegram server resets the current connection,
# if this is the case we send the message again. # if this is the case we send the message again.
self.logger.warning( logger.warning(
'Telegram NetworkError: %s! Trying one more time.', 'Telegram NetworkError: %s! Trying one more time.',
network_err.message network_err.message
) )
@ -439,7 +442,7 @@ class Telegram(RPC):
reply_markup=reply_markup reply_markup=reply_markup
) )
except TelegramError as telegram_err: except TelegramError as telegram_err:
self.logger.warning( logger.warning(
'TelegramError: %s! Giving up on that message.', 'TelegramError: %s! Giving up on that message.',
telegram_err.message telegram_err.message
) )

View File

@ -4,6 +4,7 @@
This module load custom strategies This module load custom strategies
""" """
import importlib import importlib
import logging
import os import os
import sys import sys
from collections import OrderedDict from collections import OrderedDict
@ -11,12 +12,14 @@ from collections import OrderedDict
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Constants from freqtrade.constants import Constants
from freqtrade.logger import Logger
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
sys.path.insert(0, r'../../user_data/strategies') sys.path.insert(0, r'../../user_data/strategies')
logger = logging.getLogger(__name__)
class Strategy(object): class Strategy(object):
""" """
This class contains all the logic to load custom strategy class This class contains all the logic to load custom strategy class
@ -27,8 +30,6 @@ class Strategy(object):
:param config: :param config:
:return: :return:
""" """
self.logger = Logger(name=__name__).get_logger()
# Verify the strategy is in the configuration, otherwise fallback to the default strategy # Verify the strategy is in the configuration, otherwise fallback to the default strategy
if 'strategy' in config: if 'strategy' in config:
strategy = config['strategy'] strategy = config['strategy']
@ -42,17 +43,17 @@ class Strategy(object):
# Check if we need to override configuration # Check if we need to override configuration
if 'minimal_roi' in config: if 'minimal_roi' in config:
self.custom_strategy.minimal_roi = config['minimal_roi'] self.custom_strategy.minimal_roi = config['minimal_roi']
self.logger.info("Override strategy \'minimal_roi\' with value in config file.") logger.info("Override strategy \'minimal_roi\' with value in config file.")
if 'stoploss' in config: if 'stoploss' in config:
self.custom_strategy.stoploss = config['stoploss'] self.custom_strategy.stoploss = config['stoploss']
self.logger.info( logger.info(
"Override strategy \'stoploss\' with value in config file: %s.", config['stoploss'] "Override strategy \'stoploss\' with value in config file: %s.", config['stoploss']
) )
if 'ticker_interval' in config: if 'ticker_interval' in config:
self.custom_strategy.ticker_interval = config['ticker_interval'] self.custom_strategy.ticker_interval = config['ticker_interval']
self.logger.info( logger.info(
"Override strategy \'ticker_interval\' with value in config file: %s.", "Override strategy \'ticker_interval\' with value in config file: %s.",
config['ticker_interval'] config['ticker_interval']
) )
@ -87,12 +88,12 @@ class Strategy(object):
# Fallback to the default strategy # Fallback to the default strategy
except (ImportError, TypeError) as error: except (ImportError, TypeError) as error:
self.logger.error( logger.error(
"Impossible to load Strategy 'user_data/strategies/%s.py'. This file does not exist" "Impossible to load Strategy 'user_data/strategies/%s.py'. This file does not exist"
" or contains Python code errors", " or contains Python code errors",
strategy_name strategy_name
) )
self.logger.error( logger.error(
"The error is:\n%s.", "The error is:\n%s.",
error error
) )
@ -106,7 +107,7 @@ class Strategy(object):
module = importlib.import_module(filename, __package__) module = importlib.import_module(filename, __package__)
custom_strategy = getattr(module, module.class_name) custom_strategy = getattr(module, module.class_name)
self.logger.info("Load strategy class: %s (%s.py)", module.class_name, filename) logger.info("Load strategy class: %s (%s.py)", module.class_name, filename)
return custom_strategy() return custom_strategy()
@staticmethod @staticmethod

View File

@ -46,7 +46,7 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
return FreqtradeBot(config, create_engine('sqlite://')) return FreqtradeBot(config, create_engine('sqlite://'))
@pytest.fixture(scope="module") @pytest.fixture(scope="function")
def default_conf(): def default_conf():
""" Returns validated configuration suitable for most tests """ """ Returns validated configuration suitable for most tests """
configuration = { configuration = {

View File

@ -18,9 +18,9 @@ from freqtrade.tests.conftest import log_has
API_INIT = False API_INIT = False
def maybe_init_api(conf, mocker): def maybe_init_api(conf, mocker, force=False):
global API_INIT global API_INIT
if not API_INIT: if force or not API_INIT:
mocker.patch('freqtrade.exchange.validate_pairs', mocker.patch('freqtrade.exchange.validate_pairs',
side_effect=lambda s: True) side_effect=lambda s: True)
init(config=conf) init(config=conf)
@ -29,7 +29,7 @@ def maybe_init_api(conf, mocker):
def test_init(default_conf, mocker, caplog): def test_init(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
maybe_init_api(default_conf, mocker) maybe_init_api(default_conf, mocker, True)
assert log_has('Instance is running with dry_run enabled', caplog.record_tuples) assert log_has('Instance is running with dry_run enabled', caplog.record_tuples)

View File

@ -450,10 +450,12 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
assert log_has(line, caplog.record_tuples) assert log_has(line, caplog.record_tuples)
def test_backtest(init_backtesting, default_conf) -> None: def test_backtest(init_backtesting, default_conf, fee, mocker) -> None:
""" """
Test Backtesting.backtest() method Test Backtesting.backtest() method
""" """
mocker.patch('freqtrade.exchange.get_fee', fee)
backtesting = _BACKTESTING backtesting = _BACKTESTING
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC']) data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
@ -469,10 +471,12 @@ def test_backtest(init_backtesting, default_conf) -> None:
assert not results.empty assert not results.empty
def test_backtest_1min_ticker_interval(init_backtesting, default_conf) -> None: def test_backtest_1min_ticker_interval(init_backtesting, default_conf, fee, mocker) -> None:
""" """
Test Backtesting.backtest() method with 1 min ticker Test Backtesting.backtest() method with 1 min ticker
""" """
mocker.patch('freqtrade.exchange.get_fee', fee)
backtesting = _BACKTESTING backtesting = _BACKTESTING
# Run a backtesting for an exiting 5min ticker_interval # Run a backtesting for an exiting 5min ticker_interval
@ -513,10 +517,11 @@ def test_backtest_pricecontours(init_backtesting, default_conf, fee, mocker) ->
# Test backtest using offline data (testdata directory) # Test backtest using offline data (testdata directory)
def test_backtest_ticks(init_backtesting, default_conf): def test_backtest_ticks(init_backtesting, default_conf, fee, mocker):
mocker.patch('freqtrade.exchange.get_fee', fee)
ticks = [1, 5] ticks = [1, 5]
fun = _BACKTESTING.populate_buy_trend fun = _BACKTESTING.populate_buy_trend
for tick in ticks: for _ in ticks:
backtest_conf = _make_backtest_conf(conf=default_conf) backtest_conf = _make_backtest_conf(conf=default_conf)
results = _run_backtest_1(fun, backtest_conf) results = _run_backtest_1(fun, backtest_conf)
assert not results.empty assert not results.empty

View File

@ -64,7 +64,6 @@ def test_start(mocker, default_conf, caplog) -> None:
Test start() function Test start() function
""" """
start_mock = MagicMock() start_mock = MagicMock()
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock) mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf',
MagicMock(return_value=default_conf)) MagicMock(return_value=default_conf))
@ -125,7 +124,7 @@ def test_loss_calculation_has_limited_profit(init_hyperopt) -> None:
assert under > correct assert under > correct
def test_log_results_if_loss_improves(init_hyperopt, caplog) -> None: def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
hyperopt = _HYPEROPT hyperopt = _HYPEROPT
hyperopt.current_best_loss = 2 hyperopt.current_best_loss = 2
hyperopt.log_results( hyperopt.log_results(
@ -136,7 +135,8 @@ def test_log_results_if_loss_improves(init_hyperopt, caplog) -> None:
'result': 'foo' 'result': 'foo'
} }
) )
assert log_has(' 1/2: foo. Loss 1.00000', caplog.record_tuples) out, err = capsys.readouterr()
assert ' 1/2: foo. Loss 1.00000'in out
def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None: def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
@ -184,7 +184,6 @@ def test_fmin_best_results(mocker, init_hyperopt, default_conf, caplog) -> None:
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
Strategy({'strategy': 'default_strategy'}) Strategy({'strategy': 'default_strategy'})
@ -230,7 +229,6 @@ def test_fmin_throw_value_error(mocker, init_hyperopt, default_conf, caplog) ->
conf.update({'timerange': None}) conf.update({'timerange': None})
conf.update({'spaces': 'all'}) conf.update({'spaces': 'all'})
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
Strategy({'strategy': 'default_strategy'}) Strategy({'strategy': 'default_strategy'})
@ -274,7 +272,6 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, defa
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
Strategy({'strategy': 'default_strategy'}) Strategy({'strategy': 'default_strategy'})

View File

@ -391,7 +391,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
assert freqtradebot.state == State.STOPPED assert freqtradebot.state == State.STOPPED
def test_rpc_forcesell(default_conf, ticker, mocker) -> None: def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
""" """
Test rpc_forcesell() method Test rpc_forcesell() method
""" """
@ -411,7 +411,8 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
'type': 'limit', 'type': 'limit',
'side': 'buy' 'side': 'buy'
} }
) ),
get_fee=fee,
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
@ -524,7 +525,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert prec_satoshi(res[0]['profit'], 6.2) assert prec_satoshi(res[0]['profit'], 6.2)
def test_rpc_count(mocker, default_conf, ticker) -> None: def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
""" """
Test rpc_count() method Test rpc_count() method
""" """
@ -535,7 +536,8 @@ def test_rpc_count(mocker, default_conf, ticker) -> None:
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
get_ticker=ticker get_ticker=ticker,
get_fee=fee,
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))

View File

@ -234,7 +234,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
) )
def test_status(default_conf, update, fee, mocker, ticker) -> None: def test_status(default_conf, update, mocker, fee, ticker) -> None:
""" """
Test _status() method Test _status() method
""" """
@ -250,7 +250,7 @@ def test_status(default_conf, update, fee, mocker, ticker) -> None:
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_pair_detail_url=MagicMock(), get_pair_detail_url=MagicMock(),
get_fee=fee get_fee=fee,
) )
msg_mock = MagicMock() msg_mock = MagicMock()
status_table = MagicMock() status_table = MagicMock()
@ -279,7 +279,7 @@ def test_status(default_conf, update, fee, mocker, ticker) -> None:
assert status_table.call_count == 1 assert status_table.call_count == 1
def test_status_handle(default_conf, update, ticker, mocker) -> None: def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
""" """
Test _status() method Test _status() method
""" """
@ -288,7 +288,8 @@ def test_status_handle(default_conf, update, ticker, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker get_ticker=ticker,
get_fee=fee,
) )
msg_mock = MagicMock() msg_mock = MagicMock()
status_table = MagicMock() status_table = MagicMock()
@ -324,7 +325,7 @@ def test_status_handle(default_conf, update, ticker, mocker) -> None:
assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0] assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0]
def test_status_table_handle(default_conf, update, ticker, mocker) -> None: def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
""" """
Test _status_table() method Test _status_table() method
""" """
@ -334,7 +335,8 @@ def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}) buy=MagicMock(return_value={'id': 'mocked_order_id'}),
get_fee=fee,
) )
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -977,7 +979,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
assert 'not running' in msg_mock.call_args_list[0][0][0] assert 'not running' in msg_mock.call_args_list[0][0][0]
def test_count_handle(default_conf, update, ticker, mocker) -> None: def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
""" """
Test _count() method Test _count() method
""" """
@ -995,6 +997,7 @@ def test_count_handle(default_conf, update, ticker, mocker) -> None:
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}) buy=MagicMock(return_value={'id': 'mocked_order_id'})
) )
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)

View File

@ -29,7 +29,6 @@ def test_strategy_structure():
def test_load_strategy(result): def test_load_strategy(result):
strategy = Strategy() strategy = Strategy()
strategy.logger = logging.getLogger(__name__)
assert not hasattr(Strategy, 'custom_strategy') assert not hasattr(Strategy, 'custom_strategy')
strategy._load_strategy('test_strategy') strategy._load_strategy('test_strategy')
@ -42,14 +41,13 @@ def test_load_strategy(result):
def test_load_not_found_strategy(caplog): def test_load_not_found_strategy(caplog):
strategy = Strategy() strategy = Strategy()
strategy.logger = logging.getLogger(__name__)
assert not hasattr(Strategy, 'custom_strategy') assert not hasattr(Strategy, 'custom_strategy')
strategy._load_strategy('NotFoundStrategy') strategy._load_strategy('NotFoundStrategy')
error_msg = "Impossible to load Strategy 'user_data/strategies/{}.py'. This file does not " \ error_msg = "Impossible to load Strategy 'user_data/strategies/{}.py'. This file does not " \
"exist or contains Python code errors".format('NotFoundStrategy') "exist or contains Python code errors".format('NotFoundStrategy')
assert ('test_strategy', logging.ERROR, error_msg) in caplog.record_tuples assert ('freqtrade.strategy.strategy', logging.ERROR, error_msg) in caplog.record_tuples
def test_strategy(result): def test_strategy(result):

View File

@ -126,8 +126,10 @@ def test_fiat_convert_get_price(mocker):
def test_fiat_convert_without_network(): def test_fiat_convert_without_network():
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
CryptoToFiatConverter._coinmarketcap = None
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
CryptoToFiatConverter._coinmarketcap = None
assert fiat_convert._coinmarketcap is None assert fiat_convert._coinmarketcap is None
assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0 assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0

View File

@ -235,7 +235,7 @@ def test_refresh_whitelist() -> None:
pass pass
def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None: def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
""" """
Test create_trade() method Test create_trade() method
""" """
@ -246,7 +246,8 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
# Save state of current whitelist # Save state of current whitelist
@ -270,7 +271,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
assert whitelist == default_conf['exchange']['pair_whitelist'] assert whitelist == default_conf['exchange']['pair_whitelist']
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, mocker) -> None: def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
""" """
Test create_trade() method Test create_trade() method
""" """
@ -282,7 +283,8 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, mock
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=buy_mock buy=buy_mock,
get_fee=fee,
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
@ -294,7 +296,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, mock
assert rate * amount >= conf['stake_amount'] assert rate * amount >= conf['stake_amount']
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, mocker) -> None: def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
""" """
Test create_trade() method Test create_trade() method
""" """
@ -306,7 +308,8 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, moc
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5) get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5),
get_fee=fee,
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
@ -314,7 +317,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, moc
freqtrade.create_trade() freqtrade.create_trade()
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, mocker) -> None: def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
""" """
Test create_trade() method Test create_trade() method
""" """
@ -325,7 +328,8 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, mocker) ->
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
@ -340,7 +344,7 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, mocker) ->
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
limit_buy_order, mocker) -> None: limit_buy_order, fee, mocker) -> None:
""" """
Test create_trade() method Test create_trade() method
""" """
@ -351,7 +355,8 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
@ -365,7 +370,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
freqtrade.create_trade() freqtrade.create_trade()
def test_create_trade_no_signal(default_conf, mocker) -> None: def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
""" """
Test create_trade() method Test create_trade() method
""" """
@ -379,7 +384,8 @@ def test_create_trade_no_signal(default_conf, mocker) -> None:
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker_history=MagicMock(return_value=20), get_ticker_history=MagicMock(return_value=20),
get_balance=MagicMock(return_value=20) get_balance=MagicMock(return_value=20),
get_fee=fee,
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
@ -392,7 +398,7 @@ def test_create_trade_no_signal(default_conf, mocker) -> None:
def test_process_trade_creation(default_conf, ticker, limit_buy_order, def test_process_trade_creation(default_conf, ticker, limit_buy_order,
markets, mocker, caplog) -> None: markets, fee, mocker, caplog) -> None:
""" """
Test the trade creation in _process() method Test the trade creation in _process() method
""" """
@ -405,7 +411,8 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
get_ticker=ticker, get_ticker=ticker,
get_markets=markets, get_markets=markets,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_order=MagicMock(return_value=limit_buy_order) get_order=MagicMock(return_value=limit_buy_order),
get_fee=fee,
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
@ -477,7 +484,8 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) ->
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0] assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]
def test_process_trade_handling(default_conf, ticker, limit_buy_order, markets, mocker) -> None: def test_process_trade_handling(
default_conf, ticker, limit_buy_order, markets, fee, mocker) -> None:
""" """
Test _process() Test _process()
""" """
@ -490,7 +498,8 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, markets,
get_ticker=ticker, get_ticker=ticker,
get_markets=markets, get_markets=markets,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_order=MagicMock(return_value=limit_buy_order) get_order=MagicMock(return_value=limit_buy_order),
get_fee=fee,
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
@ -621,7 +630,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock
assert trade.close_date is not None assert trade.close_date is not None
def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, mocker) -> None: def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
""" """
Test check_handle() method Test check_handle() method
""" """
@ -636,7 +645,8 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, mock
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
@ -678,7 +688,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, mock
assert freqtrade.handle_trade(trades[0]) is True assert freqtrade.handle_trade(trades[0]) is True
def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog) -> None: def test_handle_trade_roi(default_conf, ticker, limit_buy_order, fee, mocker, caplog) -> None:
""" """
Test check_handle() method Test check_handle() method
""" """
@ -693,7 +703,8 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog)
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
@ -713,7 +724,8 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog)
assert log_has('Required profit reached. Selling..', caplog.record_tuples) assert log_has('Required profit reached. Selling..', caplog.record_tuples)
def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker, caplog) -> None: def test_handle_trade_experimental(
default_conf, ticker, limit_buy_order, fee, mocker, caplog) -> None:
""" """
Test check_handle() method Test check_handle() method
""" """
@ -728,7 +740,8 @@ def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
@ -746,7 +759,7 @@ def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker
assert log_has('Sell signal received. Selling..', caplog.record_tuples) assert log_has('Sell signal received. Selling..', caplog.record_tuples)
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker) -> None: def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, fee, mocker) -> None:
""" """
Test check_handle() method Test check_handle() method
""" """
@ -757,7 +770,8 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
'freqtrade.freqtradebot.exchange', 'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
@ -1153,7 +1167,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -> None: def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, fee, mocker) -> None:
""" """
Test sell_profit_only feature when enabled Test sell_profit_only feature when enabled
""" """
@ -1169,7 +1183,8 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -
'ask': 0.00002173, 'ask': 0.00002173,
'last': 0.00002172 'last': 0.00002172
}), }),
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['experimental'] = { conf['experimental'] = {
@ -1185,7 +1200,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker) -> None: def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, fee, mocker) -> None:
""" """
Test sell_profit_only feature when disabled Test sell_profit_only feature when disabled
""" """
@ -1201,7 +1216,8 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker)
'ask': 0.00002173, 'ask': 0.00002173,
'last': 0.00002172 'last': 0.00002172
}), }),
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['experimental'] = { conf['experimental'] = {
@ -1217,7 +1233,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker)
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) -> None: def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker) -> None:
""" """
Test sell_profit_only feature when enabled and we have a loss Test sell_profit_only feature when enabled and we have a loss
""" """
@ -1233,7 +1249,8 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) ->
'ask': 0.00000173, 'ask': 0.00000173,
'last': 0.00000172 'last': 0.00000172
}), }),
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['experimental'] = { conf['experimental'] = {
@ -1249,7 +1266,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) ->
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker) -> None: def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocker) -> None:
""" """
Test sell_profit_only feature when enabled and we have a loss Test sell_profit_only feature when enabled and we have a loss
""" """
@ -1265,7 +1282,8 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker) ->
'ask': 0.00000173, 'ask': 0.00000173,
'last': 0.00000172 'last': 0.00000172
}), }),
buy=MagicMock(return_value={'id': limit_buy_order['id']}) buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)

View File

@ -1,97 +0,0 @@
"""
Unit test file for logger.py
"""
import logging
from freqtrade.logger import Logger
def test_logger_object() -> None:
"""
Test the Constants object has the mandatory Constants
:return: None
"""
logger = Logger()
assert logger.name == ''
assert logger.level == 20
assert logger.level is logging.INFO
assert hasattr(logger, 'get_logger')
logger = Logger(name='Foo', level=logging.WARNING)
assert logger.name == 'Foo'
assert logger.name != ''
assert logger.level == 30
assert logger.level is logging.WARNING
def test_get_logger() -> None:
"""
Test Logger.get_logger() and Logger._init_logger()
:return: None
"""
logger = Logger(name='get_logger', level=logging.WARNING)
get_logger = logger.get_logger()
assert logger.logger is get_logger
assert get_logger is not None
assert hasattr(get_logger, 'debug')
assert hasattr(get_logger, 'info')
assert hasattr(get_logger, 'warning')
assert hasattr(get_logger, 'critical')
assert hasattr(get_logger, 'exception')
def test_set_name() -> None:
"""
Test Logger.set_name()
:return: None
"""
logger = Logger(name='set_name')
assert logger.name == 'set_name'
logger.set_name('set_name_new')
assert logger.name == 'set_name_new'
def test_set_level() -> None:
"""
Test Logger.set_name()
:return: None
"""
logger = Logger(name='Foo', level=logging.WARNING)
assert logger.level == logging.WARNING
assert logger.get_logger().level == logging.WARNING
logger.set_level(logging.INFO)
assert logger.level == logging.INFO
assert logger.get_logger().level == logging.INFO
def test_sending_msg(caplog) -> None:
"""
Test send a logging message
:return: None
"""
logger = Logger(name='sending_msg', level=logging.WARNING).get_logger()
logger.info('I am an INFO message')
assert('sending_msg', logging.INFO, 'I am an INFO message') not in caplog.record_tuples
logger.warning('I am an WARNING message')
assert ('sending_msg', logging.WARNING, 'I am an WARNING message') in caplog.record_tuples
def test_set_format(caplog) -> None:
"""
Test Logger.set_format()
:return: None
"""
log = Logger(name='set_format')
logger = log.get_logger()
logger.info('I am the first message')
assert ('set_format', logging.INFO, 'I am the first message') in caplog.record_tuples
log.set_format(log_format='%(message)s', propagate=True)
logger.info('I am the second message')
assert ('set_format', logging.INFO, 'I am the second message') in caplog.record_tuples

View File

@ -7,6 +7,11 @@ from sqlalchemy import create_engine
from freqtrade.persistence import Trade, init, clean_dry_run_db from freqtrade.persistence import Trade, init, clean_dry_run_db
@pytest.fixture(scope='function')
def init_persistence(default_conf):
init(default_conf)
def test_init_create_session(default_conf, mocker): def test_init_create_session(default_conf, mocker):
mocker.patch.dict('freqtrade.persistence._CONF', default_conf) mocker.patch.dict('freqtrade.persistence._CONF', default_conf)
@ -89,6 +94,7 @@ def test_init_prod_db(default_conf, mocker):
os.rename(prod_db_swp, prod_db) os.rename(prod_db_swp, prod_db)
@pytest.mark.usefixtures("init_persistence")
def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee): def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee):
""" """
On this test we will buy and sell a crypto currency. On this test we will buy and sell a crypto currency.
@ -144,6 +150,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee):
assert trade.close_date is not None assert trade.close_date is not None
@pytest.mark.usefixtures("init_persistence")
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee): def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -167,6 +174,7 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
assert trade.calc_profit_percent() == 0.06201057 assert trade.calc_profit_percent() == 0.06201057
@pytest.mark.usefixtures("init_persistence")
def test_calc_close_trade_price_exception(limit_buy_order, fee): def test_calc_close_trade_price_exception(limit_buy_order, fee):
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -181,6 +189,7 @@ def test_calc_close_trade_price_exception(limit_buy_order, fee):
assert trade.calc_close_trade_price() == 0.0 assert trade.calc_close_trade_price() == 0.0
@pytest.mark.usefixtures("init_persistence")
def test_update_open_order(limit_buy_order): def test_update_open_order(limit_buy_order):
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -204,6 +213,7 @@ def test_update_open_order(limit_buy_order):
assert trade.close_date is None assert trade.close_date is None
@pytest.mark.usefixtures("init_persistence")
def test_update_invalid_order(limit_buy_order): def test_update_invalid_order(limit_buy_order):
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -217,6 +227,7 @@ def test_update_invalid_order(limit_buy_order):
trade.update(limit_buy_order) trade.update(limit_buy_order)
@pytest.mark.usefixtures("init_persistence")
def test_calc_open_trade_price(limit_buy_order, fee): def test_calc_open_trade_price(limit_buy_order, fee):
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -235,6 +246,7 @@ def test_calc_open_trade_price(limit_buy_order, fee):
assert trade.calc_open_trade_price(fee=0.003) == 0.001003000 assert trade.calc_open_trade_price(fee=0.003) == 0.001003000
@pytest.mark.usefixtures("init_persistence")
def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee): def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -257,6 +269,7 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
assert trade.calc_close_trade_price(fee=0.005) == 0.0010619972 assert trade.calc_close_trade_price(fee=0.005) == 0.0010619972
@pytest.mark.usefixtures("init_persistence")
def test_calc_profit(limit_buy_order, limit_sell_order, fee): def test_calc_profit(limit_buy_order, limit_sell_order, fee):
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -288,6 +301,7 @@ def test_calc_profit(limit_buy_order, limit_sell_order, fee):
assert trade.calc_profit(fee=0.003) == 0.00006163 assert trade.calc_profit(fee=0.003) == 0.00006163
@pytest.mark.usefixtures("init_persistence")
def test_calc_profit_percent(limit_buy_order, limit_sell_order, fee): def test_calc_profit_percent(limit_buy_order, limit_sell_order, fee):
trade = Trade( trade = Trade(
pair='ETH/BTC', pair='ETH/BTC',

View File

@ -10,7 +10,7 @@ Optional Cli parameters
--timerange: specify what timerange of data to use. --timerange: specify what timerange of data to use.
-l / --live: Live, to download the latest ticker for the pair -l / --live: Live, to download the latest ticker for the pair
""" """
import logging
import sys import sys
from argparse import Namespace from argparse import Namespace
from os import path from os import path
@ -22,13 +22,12 @@ import gzip
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
from freqtrade import misc from freqtrade import misc
from freqtrade.logger import Logger
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Constants from freqtrade.constants import Constants
import dateutil.parser import dateutil.parser
logger = Logger(name="freqtrade").get_logger() logger = logging.getLogger('freqtrade')
def load_old_file(filename) -> (List[Dict], bool): def load_old_file(filename) -> (List[Dict], bool):

View File

@ -11,7 +11,7 @@ Optional Cli parameters
--timerange: specify what timerange of data to use. --timerange: specify what timerange of data to use.
-l / --live: Live, to download the latest ticker for the pair -l / --live: Live, to download the latest ticker for the pair
""" """
import logging
import sys import sys
from argparse import Namespace from argparse import Namespace
@ -24,11 +24,10 @@ import plotly.graph_objs as go
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade import exchange from freqtrade import exchange
from freqtrade.logger import Logger
import freqtrade.optimize as optimize import freqtrade.optimize as optimize
logger = Logger(name="Graph dataframe").get_logger() logger = logging.getLogger('freqtrade')
def plot_analyzed_dataframe(args: Namespace) -> None: def plot_analyzed_dataframe(args: Namespace) -> None:

View File

@ -10,7 +10,7 @@ Optional Cli parameters
-s / --strategy: strategy to use -s / --strategy: strategy to use
--timerange: specify what timerange of data to use. --timerange: specify what timerange of data to use.
""" """
import logging
import sys import sys
import json import json
from argparse import Namespace from argparse import Namespace
@ -24,14 +24,13 @@ import plotly.graph_objs as go
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration from freqtrade.configuration import Configuration
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade.logger import Logger
from freqtrade.constants import Constants from freqtrade.constants import Constants
import freqtrade.optimize as optimize import freqtrade.optimize as optimize
import freqtrade.misc as misc import freqtrade.misc as misc
logger = Logger(name="Graph profits").get_logger() logger = logging.getLogger('freqtrade')
# data:: [ pair, profit-%, enter, exit, time, duration] # data:: [ pair, profit-%, enter, exit, time, duration]