mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 18:23:55 +00:00
Merge pull request #584 from gcarq/feature/fix-loglevel
Drop Logger class and ensure parent logger detection
This commit is contained in:
commit
586f49cafd
|
@ -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,11 +10,13 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -32,8 +35,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)
|
||||||
|
|
||||||
|
@ -107,20 +108,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)
|
||||||
|
@ -128,7 +129,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]
|
||||||
|
@ -136,7 +137,7 @@ class Analyze(object):
|
||||||
# Check if dataframe is out of date
|
# Check if dataframe is out of date
|
||||||
signal_date = arrow.get(latest['date'])
|
signal_date = arrow.get(latest['date'])
|
||||||
if signal_date < arrow.utcnow() - timedelta(minutes=(interval + 5)):
|
if signal_date < arrow.utcnow() - timedelta(minutes=(interval + 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
|
||||||
|
@ -144,7 +145,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,
|
||||||
|
@ -161,17 +162,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
|
||||||
|
@ -184,7 +185,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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -10,7 +11,9 @@ from jsonschema import Draft4Validator, validate
|
||||||
from jsonschema.exceptions import ValidationError, best_match
|
from jsonschema.exceptions import ValidationError, best_match
|
||||||
|
|
||||||
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):
|
||||||
|
@ -20,8 +23,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]:
|
||||||
|
@ -29,7 +30,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
|
||||||
|
@ -56,7 +57,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
|
||||||
)
|
)
|
||||||
|
@ -64,7 +65,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)
|
||||||
|
|
||||||
|
@ -77,13 +78,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)'
|
||||||
|
@ -92,13 +96,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)')
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -112,39 +116,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: %d ...', config.get('ticker_interval'))
|
logger.info('Using ticker_interval: %d ...', 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
|
||||||
|
|
||||||
|
@ -156,18 +160,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
|
||||||
|
|
||||||
|
@ -181,7 +185,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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -129,7 +136,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
|
||||||
|
|
||||||
|
@ -170,7 +177,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(
|
||||||
|
@ -180,7 +187,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
|
||||||
|
|
||||||
|
@ -222,7 +229,7 @@ class FreqtradeBot(object):
|
||||||
# Market is not active
|
# Market is not active
|
||||||
if not status['IsActive']:
|
if not status['IsActive']:
|
||||||
sanitized_whitelist.remove(pair)
|
sanitized_whitelist.remove(pair)
|
||||||
self.logger.info(
|
logger.info(
|
||||||
'Ignoring %s from whitelist (reason: %s).',
|
'Ignoring %s from whitelist (reason: %s).',
|
||||||
pair, status.get('Notice') or 'wallet is not active'
|
pair, status.get('Notice') or 'wallet is not active'
|
||||||
)
|
)
|
||||||
|
@ -253,7 +260,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
|
||||||
)
|
)
|
||||||
|
@ -268,7 +275,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')
|
||||||
|
@ -333,10 +340,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:
|
||||||
|
@ -347,7 +354,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.update(exchange.get_order(trade.open_order_id))
|
||||||
|
|
||||||
if trade.is_open and trade.open_order_id is None:
|
if trade.is_open and trade.open_order_id is None:
|
||||||
|
@ -363,7 +370,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)
|
||||||
|
@ -389,7 +396,7 @@ class FreqtradeBot(object):
|
||||||
try:
|
try:
|
||||||
order = exchange.get_order(trade.open_order_id)
|
order = exchange.get_order(trade.open_order_id)
|
||||||
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())
|
||||||
|
@ -419,7 +426,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
|
||||||
|
@ -429,7 +436,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
|
||||||
|
@ -450,7 +457,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
|
||||||
|
|
|
@ -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
|
|
|
@ -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,20 +32,14 @@ def main(sysargv: List[str]) -> None:
|
||||||
args.func(args)
|
args.func(args)
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(
|
|
||||||
'Starting freqtrade %s (loglevel=%s)',
|
|
||||||
__version__,
|
|
||||||
logging.getLevelName(args.loglevel)
|
|
||||||
)
|
|
||||||
|
|
||||||
freqtrade = None
|
freqtrade = None
|
||||||
return_code = 1
|
return_code = 1
|
||||||
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:
|
||||||
|
|
|
@ -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]:
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
@ -16,11 +17,13 @@ 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.exchange import Bittrex
|
from freqtrade.exchange import Bittrex
|
||||||
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
|
||||||
|
@ -30,10 +33,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
|
||||||
|
@ -200,7 +199,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)
|
||||||
|
@ -212,15 +211,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(
|
||||||
|
@ -235,14 +234,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(),
|
||||||
|
@ -263,9 +262,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'
|
||||||
|
@ -299,13 +296,9 @@ def start(args: Namespace) -> None:
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Initialize logger
|
|
||||||
logger = Logger(name=__name__).get_logger()
|
|
||||||
logger.info('Starting freqtrade in Backtesting mode')
|
|
||||||
|
|
||||||
# Initialize configuration
|
# Initialize configuration
|
||||||
config = setup_configuration(args)
|
config = setup_configuration(args)
|
||||||
|
logger.info('Starting freqtrade in Backtesting mode')
|
||||||
|
|
||||||
# Initialize backtesting object
|
# Initialize backtesting object
|
||||||
backtesting = Backtesting(config)
|
backtesting = Backtesting(config)
|
||||||
|
|
|
@ -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,13 +591,11 @@ 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')
|
|
||||||
|
|
||||||
# Initialize configuration
|
# Initialize configuration
|
||||||
# Monkey patch the configuration with hyperopt_conf.py
|
# Monkey patch the configuration with hyperopt_conf.py
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
|
logger.info('Starting freqtrade in Hyperopt mode')
|
||||||
|
|
||||||
optimize_config = hyperopt_optimize_conf()
|
optimize_config = hyperopt_optimize_conf()
|
||||||
config = configuration._load_common_config(optimize_config)
|
config = configuration._load_common_config(optimize_config)
|
||||||
config = configuration._load_backtesting_config(config)
|
config = configuration._load_backtesting_config(config)
|
||||||
|
|
|
@ -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]:
|
||||||
"""
|
"""
|
||||||
|
@ -346,7 +344,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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -50,7 +50,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.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(default_conf)
|
read_data=json.dumps(default_conf)
|
||||||
|
@ -110,7 +109,7 @@ def test_loss_calculation_has_limited_profit() -> None:
|
||||||
assert under > correct
|
assert under > correct
|
||||||
|
|
||||||
|
|
||||||
def test_log_results_if_loss_improves(caplog) -> None:
|
def test_log_results_if_loss_improves(capsys) -> None:
|
||||||
hyperopt = _HYPEROPT
|
hyperopt = _HYPEROPT
|
||||||
hyperopt.current_best_loss = 2
|
hyperopt.current_best_loss = 2
|
||||||
hyperopt.log_results(
|
hyperopt.log_results(
|
||||||
|
@ -121,7 +120,8 @@ def test_log_results_if_loss_improves(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(caplog) -> None:
|
def test_no_log_if_loss_does_not_improve(caplog) -> None:
|
||||||
|
@ -169,7 +169,6 @@ def test_fmin_best_results(mocker, 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())
|
|
||||||
|
|
||||||
Strategy({'strategy': 'default_strategy'})
|
Strategy({'strategy': 'default_strategy'})
|
||||||
hyperopt = Hyperopt(conf)
|
hyperopt = Hyperopt(conf)
|
||||||
|
@ -214,7 +213,6 @@ def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None:
|
||||||
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())
|
|
||||||
Strategy({'strategy': 'default_strategy'})
|
Strategy({'strategy': 'default_strategy'})
|
||||||
hyperopt = Hyperopt(conf)
|
hyperopt = Hyperopt(conf)
|
||||||
hyperopt.trials = create_trials(mocker)
|
hyperopt.trials = create_trials(mocker)
|
||||||
|
@ -256,7 +254,6 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> No
|
||||||
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())
|
|
||||||
|
|
||||||
Strategy({'strategy': 'default_strategy'})
|
Strategy({'strategy': 'default_strategy'})
|
||||||
hyperopt = Hyperopt(conf)
|
hyperopt = Hyperopt(conf)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
|
|
@ -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(__name__)
|
||||||
|
|
||||||
|
|
||||||
def plot_analyzed_dataframe(args: Namespace) -> None:
|
def plot_analyzed_dataframe(args: Namespace) -> None:
|
||||||
|
|
|
@ -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,13 +24,12 @@ 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
|
|
||||||
|
|
||||||
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(__name__)
|
||||||
|
|
||||||
|
|
||||||
# data:: [ pair, profit-%, enter, exit, time, duration]
|
# data:: [ pair, profit-%, enter, exit, time, duration]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user