use native python logger

This commit is contained in:
gcarq 2018-03-25 21:37:14 +02:00
parent 001d7443da
commit 403f59ef45
18 changed files with 145 additions and 343 deletions

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

@ -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')
@ -340,10 +347,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:
@ -354,7 +361,7 @@ 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)
trade.update(exchange.get_order(trade.open_order_id, trade.pair)) trade.update(exchange.get_order(trade.open_order_id, trade.pair))
if trade.is_open and trade.open_order_id is None: if trade.is_open and trade.open_order_id is None:
@ -370,7 +377,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)
@ -396,7 +403,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())
@ -426,7 +433,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
@ -436,7 +443,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
@ -457,7 +464,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
@ -206,7 +205,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)
@ -218,15 +217,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(
@ -241,14 +240,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(),
@ -269,9 +268,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'
@ -307,7 +304,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:
""" """
@ -226,7 +223,7 @@ class Hyperopt(Backtesting):
results['result'], results['result'],
results['loss'] results['loss']
) )
self.logger.info(log_msg) logger.info(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

@ -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))
@ -184,7 +183,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 +228,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 +271,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

@ -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

@ -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

@ -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]