use native python logger
This commit is contained in:
parent
3f8d7dae39
commit
fa7f74b4bc
@ -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__, 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__)
|
|
||||||
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__, 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
|
||||||
|
@ -8,11 +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 = logging.getLogger('freqtrade')
|
||||||
|
|
||||||
|
|
||||||
def main(sysargv: List[str]) -> None:
|
def main(sysargv: List[str]) -> None:
|
||||||
@ -26,28 +26,20 @@ def main(sysargv: List[str]) -> None:
|
|||||||
)
|
)
|
||||||
args = arguments.get_parsed_arg()
|
args = arguments.get_parsed_arg()
|
||||||
|
|
||||||
logger = Logger(__name__, level=args.loglevel).get_logger()
|
|
||||||
|
|
||||||
# A subcommand has been issued.
|
# A subcommand has been issued.
|
||||||
# Means if Backtesting or Hyperopt have been called we exit the bot
|
# Means if Backtesting or Hyperopt have been called we exit the bot
|
||||||
if hasattr(args, 'func'):
|
if hasattr(args, 'func'):
|
||||||
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__).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__, 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'
|
||||||
@ -301,7 +298,6 @@ def start(args: Namespace) -> None:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Initialize logger
|
# Initialize logger
|
||||||
logger = Logger(__name__).get_logger()
|
|
||||||
logger.info('Starting freqtrade in Backtesting mode')
|
logger.info('Starting freqtrade in Backtesting mode')
|
||||||
|
|
||||||
# Initialize configuration
|
# Initialize configuration
|
||||||
|
@ -31,6 +31,9 @@ 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 +45,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__, 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 +192,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 +210,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 +224,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 +509,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 +520,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 +528,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 +558,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 +571,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
|
||||||
)
|
)
|
||||||
|
@ -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__,
|
|
||||||
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__,
|
|
||||||
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__).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
|
||||||
|
@ -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):
|
||||||
|
@ -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__).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__).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