Merge branch 'feat/objectify-ccxt' into cxxt_obj_sellfix
This commit is contained in:
commit
a140748b5a
@ -13,7 +13,7 @@ addons:
|
||||
install:
|
||||
- ./install_ta-lib.sh
|
||||
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
|
||||
- pip install --upgrade flake8 coveralls
|
||||
- pip install --upgrade flake8 coveralls pytest-random-order
|
||||
- pip install -r requirements.txt
|
||||
- pip install -e .
|
||||
jobs:
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""
|
||||
Functions to analyze ticker data with indicators and produce buy and sell signals
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Tuple
|
||||
@ -9,12 +10,14 @@ import arrow
|
||||
from pandas import DataFrame, to_datetime
|
||||
|
||||
from freqtrade.exchange import get_ticker_history
|
||||
from freqtrade.logger import Logger
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
from freqtrade.constants import Constants
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SignalType(Enum):
|
||||
"""
|
||||
Enum to distinguish between buy and sell signals
|
||||
@ -33,8 +36,6 @@ class Analyze(object):
|
||||
Init Analyze
|
||||
:param config: Bot configuration (use the one from Configuration())
|
||||
"""
|
||||
self.logger = Logger(name=__name__, level=config.get('loglevel')).get_logger()
|
||||
|
||||
self.config = config
|
||||
self.strategy = Strategy(self.config)
|
||||
|
||||
@ -110,20 +111,20 @@ class Analyze(object):
|
||||
"""
|
||||
ticker_hist = get_ticker_history(pair, interval)
|
||||
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
|
||||
|
||||
try:
|
||||
dataframe = self.analyze_ticker(ticker_hist)
|
||||
except ValueError as error:
|
||||
self.logger.warning(
|
||||
logger.warning(
|
||||
'Unable to analyze ticker for pair %s: %s',
|
||||
pair,
|
||||
str(error)
|
||||
)
|
||||
return False, False
|
||||
except Exception as error:
|
||||
self.logger.exception(
|
||||
logger.exception(
|
||||
'Unexpected error when analyzing ticker for pair %s: %s',
|
||||
pair,
|
||||
str(error)
|
||||
@ -131,7 +132,7 @@ class Analyze(object):
|
||||
return False, False
|
||||
|
||||
if dataframe.empty:
|
||||
self.logger.warning('Empty dataframe for pair %s', pair)
|
||||
logger.warning('Empty dataframe for pair %s', pair)
|
||||
return False, False
|
||||
|
||||
latest = dataframe.iloc[-1]
|
||||
@ -140,7 +141,7 @@ class Analyze(object):
|
||||
signal_date = arrow.get(latest['date'])
|
||||
interval_minutes = Constants.TICKER_INTERVAL_MINUTES[interval]
|
||||
if signal_date < arrow.utcnow() - timedelta(minutes=(interval_minutes + 5)):
|
||||
self.logger.warning(
|
||||
logger.warning(
|
||||
'Outdated history for pair %s. Last tick is %s minutes old',
|
||||
pair,
|
||||
(arrow.utcnow() - signal_date).seconds // 60
|
||||
@ -148,7 +149,7 @@ class Analyze(object):
|
||||
return False, False
|
||||
|
||||
(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',
|
||||
latest['date'],
|
||||
pair,
|
||||
@ -165,17 +166,17 @@ class Analyze(object):
|
||||
"""
|
||||
# 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):
|
||||
self.logger.debug('Required profit reached. Selling..')
|
||||
logger.debug('Required profit reached. Selling..')
|
||||
return True
|
||||
|
||||
# Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
|
||||
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:
|
||||
return 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 False
|
||||
@ -188,7 +189,7 @@ class Analyze(object):
|
||||
"""
|
||||
current_profit = trade.calc_profit_percent(current_rate)
|
||||
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
|
||||
|
||||
# Check if time matches and current rate is above threshold
|
||||
|
@ -3,6 +3,7 @@ This module contains the configuration class
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from argparse import Namespace
|
||||
from typing import Dict, Any
|
||||
from jsonschema import Draft4Validator, validate
|
||||
@ -11,7 +12,9 @@ import ccxt
|
||||
|
||||
from freqtrade import OperationalException
|
||||
from freqtrade.constants import Constants
|
||||
from freqtrade.logger import Logger
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Configuration(object):
|
||||
@ -21,8 +24,6 @@ class Configuration(object):
|
||||
"""
|
||||
def __init__(self, args: Namespace) -> None:
|
||||
self.args = args
|
||||
self.logging = Logger(name=__name__)
|
||||
self.logger = self.logging.get_logger()
|
||||
self.config = None
|
||||
|
||||
def load_config(self) -> Dict[str, Any]:
|
||||
@ -30,7 +31,7 @@ class Configuration(object):
|
||||
Extract information for sys.argv and load the bot configuration
|
||||
: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)
|
||||
|
||||
# Add the strategy file to use
|
||||
@ -57,7 +58,7 @@ class Configuration(object):
|
||||
with open(path) as file:
|
||||
conf = json.load(file)
|
||||
except FileNotFoundError:
|
||||
self.logger.critical(
|
||||
logger.critical(
|
||||
'Config file "%s" not found. Please create your config file',
|
||||
path
|
||||
)
|
||||
@ -65,7 +66,7 @@ class Configuration(object):
|
||||
|
||||
if 'internals' not in conf:
|
||||
conf['internals'] = {}
|
||||
self.logger.info('Validating configuration ...')
|
||||
logger.info('Validating configuration ...')
|
||||
|
||||
return self._validate_config(conf)
|
||||
|
||||
@ -78,13 +79,16 @@ class Configuration(object):
|
||||
# Log level
|
||||
if 'loglevel' in self.args and self.args.loglevel:
|
||||
config.update({'loglevel': self.args.loglevel})
|
||||
self.logging.set_level(self.args.loglevel)
|
||||
self.logger.info('Log level set at %s', config['loglevel'])
|
||||
logging.basicConfig(
|
||||
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
|
||||
if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist:
|
||||
config.update({'dynamic_whitelist': self.args.dynamic_whitelist})
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'Parameter --dynamic-whitelist detected. '
|
||||
'Using dynamically generated whitelist. '
|
||||
'(not applicable with Backtesting and Hyperopt)'
|
||||
@ -93,13 +97,13 @@ class Configuration(object):
|
||||
# Add dry_run_db if found and the bot in dry run
|
||||
if self.args.dry_run_db and config.get('dry_run', False):
|
||||
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', 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:
|
||||
self.logger.info('Dry run is disabled. (--dry_run_db ignored)')
|
||||
logger.info('Dry run is disabled. (--dry_run_db ignored)')
|
||||
|
||||
# Check if the exchange set by the user is supported
|
||||
self.check_exchange(config)
|
||||
@ -116,39 +120,39 @@ class Configuration(object):
|
||||
# (that will override the strategy configuration)
|
||||
if 'ticker_interval' in self.args and self.args.ticker_interval:
|
||||
config.update({'ticker_interval': self.args.ticker_interval})
|
||||
self.logger.info('Parameter -i/--ticker-interval detected ...')
|
||||
self.logger.info('Using ticker_interval: %s ...', config.get('ticker_interval'))
|
||||
logger.info('Parameter -i/--ticker-interval detected ...')
|
||||
logger.info('Using ticker_interval: %s ...', config.get('ticker_interval'))
|
||||
|
||||
# If -l/--live is used we add it to the configuration
|
||||
if 'live' in self.args and self.args.live:
|
||||
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' in self.args and self.args.realistic_simulation:
|
||||
config.update({'realistic_simulation': True})
|
||||
self.logger.info('Parameter --realistic-simulation detected ...')
|
||||
self.logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
|
||||
logger.info('Parameter --realistic-simulation detected ...')
|
||||
logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
|
||||
|
||||
# If --timerange is used we add it to the configuration
|
||||
if 'timerange' in self.args and 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' in self.args and 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 'refresh_pairs' in self.args and self.args.refresh_pairs:
|
||||
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' in self.args and 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
|
||||
|
||||
@ -160,18 +164,18 @@ class Configuration(object):
|
||||
# If --realistic-simulation is used we add it to the configuration
|
||||
if 'epochs' in self.args and self.args.epochs:
|
||||
config.update({'epochs': self.args.epochs})
|
||||
self.logger.info('Parameter --epochs detected ...')
|
||||
self.logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs'))
|
||||
logger.info('Parameter --epochs detected ...')
|
||||
logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs'))
|
||||
|
||||
# If --mongodb is used we add it to the configuration
|
||||
if 'mongodb' in self.args and 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' in self.args and 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
|
||||
|
||||
@ -185,7 +189,7 @@ class Configuration(object):
|
||||
validate(conf, Constants.CONF_SCHEMA)
|
||||
return conf
|
||||
except ValidationError as exception:
|
||||
self.logger.fatal(
|
||||
logger.fatal(
|
||||
'Invalid configuration. See config.json.example. Reason: %s',
|
||||
exception
|
||||
)
|
||||
@ -215,10 +219,10 @@ class Configuration(object):
|
||||
'The following exchanges are supported: {}'\
|
||||
.format(exchange, ', '.join(ccxt.exchanges))
|
||||
|
||||
self.logger.critical(exception_msg)
|
||||
logger.critical(exception_msg)
|
||||
raise OperationalException(
|
||||
exception_msg
|
||||
)
|
||||
|
||||
self.logger.debug('Exchange "%s" supported', exchange)
|
||||
logger.debug('Exchange "%s" supported', exchange)
|
||||
return True
|
||||
|
@ -74,6 +74,7 @@ def init(config: dict) -> None:
|
||||
'secret': exchange_config.get('secret'),
|
||||
'password': exchange_config.get('password'),
|
||||
'uid': exchange_config.get('uid'),
|
||||
'enableRateLimit': True,
|
||||
})
|
||||
except (KeyError, AttributeError):
|
||||
raise OperationalException('Exchange {} is not supported'.format(name))
|
||||
|
@ -4,6 +4,7 @@ Freqtrade is the main module of this bot. It contains the class Freqtrade()
|
||||
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
@ -13,16 +14,20 @@ import arrow
|
||||
import requests
|
||||
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.constants import Constants
|
||||
from freqtrade.fiat_convert import CryptoToFiatConverter
|
||||
from freqtrade.logger import Logger
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.rpc.rpc_manager import RPCManager
|
||||
from freqtrade.state import State
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FreqtradeBot(object):
|
||||
"""
|
||||
Freqtrade is the main class of the bot.
|
||||
@ -37,8 +42,10 @@ class FreqtradeBot(object):
|
||||
:param db_url: database connector string for sqlalchemy (Optional)
|
||||
"""
|
||||
|
||||
# Init the logger
|
||||
self.logger = Logger(name=__name__, level=config.get('loglevel')).get_logger()
|
||||
logger.info(
|
||||
'Starting freqtrade %s',
|
||||
__version__,
|
||||
)
|
||||
|
||||
# Init bot states
|
||||
self.state = State.STOPPED
|
||||
@ -81,7 +88,7 @@ class FreqtradeBot(object):
|
||||
:return: None
|
||||
"""
|
||||
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.rpc.cleanup()
|
||||
persistence.cleanup()
|
||||
@ -97,7 +104,7 @@ class FreqtradeBot(object):
|
||||
state = self.state
|
||||
if state != old_state:
|
||||
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:
|
||||
time.sleep(1)
|
||||
@ -126,7 +133,7 @@ class FreqtradeBot(object):
|
||||
result = func(*args, **kwargs)
|
||||
end = time.time()
|
||||
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)
|
||||
return result
|
||||
|
||||
@ -167,7 +174,7 @@ class FreqtradeBot(object):
|
||||
Trade.session.flush()
|
||||
|
||||
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)
|
||||
except OperationalException:
|
||||
self.rpc.send_msg(
|
||||
@ -177,7 +184,7 @@ class FreqtradeBot(object):
|
||||
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
|
||||
return state_changed
|
||||
|
||||
@ -228,7 +235,7 @@ class FreqtradeBot(object):
|
||||
# Market is not active
|
||||
if not market['active']:
|
||||
sanitized_whitelist.remove(pair)
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'Ignoring %s from whitelist. Market is not active.',
|
||||
pair
|
||||
)
|
||||
@ -260,7 +267,7 @@ class FreqtradeBot(object):
|
||||
stake_amount = self.config['stake_amount']
|
||||
interval = self.analyze.get_ticker_interval()
|
||||
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'Checking buy signals to create a new trade with stake_amount: %f ...',
|
||||
stake_amount
|
||||
)
|
||||
@ -275,7 +282,7 @@ class FreqtradeBot(object):
|
||||
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
|
||||
if trade.pair in whitelist:
|
||||
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:
|
||||
raise DependencyException('No currency pairs in whitelist')
|
||||
@ -343,10 +350,10 @@ class FreqtradeBot(object):
|
||||
if self.create_trade():
|
||||
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
|
||||
except DependencyException as exception:
|
||||
self.logger.warning('Unable to create trade: %s', exception)
|
||||
logger.warning('Unable to create trade: %s', exception)
|
||||
return False
|
||||
|
||||
def process_maybe_execute_sell(self, trade: Trade) -> bool:
|
||||
@ -357,20 +364,20 @@ class FreqtradeBot(object):
|
||||
# Get order details for actual price per unit
|
||||
if trade.open_order_id:
|
||||
# Update trade with order values
|
||||
self.logger.info('Found open order for %s', trade)
|
||||
logger.info('Found open order for %s', trade)
|
||||
order = exchange.get_order(trade.open_order_id, trade.pair)
|
||||
# TODO: correct place here ??
|
||||
# Try update amount (binance-fix)
|
||||
try:
|
||||
new_amount = self.get_real_amount(trade)
|
||||
if order['amount'] != new_amount:
|
||||
self.logger.info("Updating amount for Trade {} from {} to {}".format(
|
||||
logger.info("Updating amount for Trade {} from {} to {}".format(
|
||||
trade, order['amount'], new_amount))
|
||||
order['amount'] = new_amount
|
||||
trade.fee_open = 0
|
||||
|
||||
except OperationalException as exception:
|
||||
self.logger.warning("could not update trade amount: %s", exception)
|
||||
logger.warning("could not update trade amount: %s", exception)
|
||||
|
||||
trade.update(order)
|
||||
|
||||
@ -400,7 +407,7 @@ class FreqtradeBot(object):
|
||||
fee_abs += trade["fee"]["cost"]
|
||||
|
||||
if amount != order.amount:
|
||||
self.logger.warning("amount {} does not match amount {}".format(amount, order.amount))
|
||||
logger.warning("amount {} does not match amount {}".format(amount, order.amount))
|
||||
raise OperationalException("Half bought? Amounts don't match")
|
||||
real_amount = amount - fee_abs
|
||||
return real_amount
|
||||
@ -412,16 +419,16 @@ class FreqtradeBot(object):
|
||||
"""
|
||||
if trade.is_open and trade.open_order_id is None:
|
||||
# Trade is not open anymore
|
||||
self.logger.warning("could not open trade amount - Trade is not open anymore")
|
||||
logger.warning("could not open trade amount - Trade is not open anymore")
|
||||
return False
|
||||
try:
|
||||
new_amount = self.get_real_amount(trade)
|
||||
except OperationalException as exception:
|
||||
self.logger.warning("could not update trade amount: %s", exception)
|
||||
logger.warning("could not update trade amount: %s", exception)
|
||||
return False
|
||||
# updating amount
|
||||
if trade.amount != new_amount:
|
||||
self.logger.info("Updating amount for Trade {} from {} to {}".format(
|
||||
logger.info("Updating amount for Trade {} from {} to {}".format(
|
||||
trade, trade.amount, new_amount))
|
||||
trade.amount = new_amount
|
||||
trade.fee_open = 0 # Fee was applied - set to 0 for buy
|
||||
@ -436,7 +443,7 @@ class FreqtradeBot(object):
|
||||
if not trade.is_open:
|
||||
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']
|
||||
|
||||
(buy, sell) = (False, False)
|
||||
@ -462,7 +469,7 @@ class FreqtradeBot(object):
|
||||
try:
|
||||
order = exchange.get_order(trade.open_order_id, trade.pair)
|
||||
except requests.exceptions.RequestException:
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'Cannot query order for %s due to %s',
|
||||
trade,
|
||||
traceback.format_exc())
|
||||
@ -492,7 +499,7 @@ class FreqtradeBot(object):
|
||||
# FIX? do we really need to flush, caller of
|
||||
# check_handle_timedout will flush afterwards
|
||||
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(
|
||||
trade.pair.replace('_', '/')))
|
||||
return True
|
||||
@ -502,7 +509,7 @@ class FreqtradeBot(object):
|
||||
trade.amount = order['amount'] - order['remaining']
|
||||
trade.stake_amount = trade.amount * trade.open_rate
|
||||
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(
|
||||
trade.pair.replace('_', '/')))
|
||||
return False
|
||||
@ -523,7 +530,7 @@ class FreqtradeBot(object):
|
||||
trade.open_order_id = None
|
||||
self.rpc.send_msg('*Timeout:* Unfilled sell order for {} cancelled'.format(
|
||||
trade.pair.replace('_', '/')))
|
||||
self.logger.info('Sell order timeout for %s.', trade)
|
||||
logger.info('Sell order timeout for %s.', trade)
|
||||
return True
|
||||
|
||||
# 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
|
||||
from typing import List
|
||||
|
||||
from freqtrade import (__version__)
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.configuration import Configuration
|
||||
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:
|
||||
@ -34,19 +32,13 @@ def main(sysargv: List[str]) -> None:
|
||||
args.func(args)
|
||||
return 0
|
||||
|
||||
logger.info(
|
||||
'Starting freqtrade %s (loglevel=%s)',
|
||||
__version__,
|
||||
logging.getLevelName(args.loglevel)
|
||||
)
|
||||
|
||||
freqtrade = None
|
||||
try:
|
||||
# Load and validate configuration
|
||||
configuration = Configuration(args)
|
||||
config = Configuration(args).get_config()
|
||||
|
||||
# Init the bot
|
||||
freqtrade = FreqtradeBot(configuration.get_config())
|
||||
freqtrade = FreqtradeBot(config)
|
||||
|
||||
state = None
|
||||
while 1:
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
import gzip
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional, List, Dict, Tuple
|
||||
|
||||
from freqtrade import misc
|
||||
from freqtrade.exchange import get_ticker_history
|
||||
from freqtrade.logger import Logger
|
||||
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]:
|
||||
|
@ -3,6 +3,7 @@
|
||||
"""
|
||||
This module contains the backtesting logic
|
||||
"""
|
||||
import logging
|
||||
from argparse import Namespace
|
||||
from typing import Dict, Tuple, Any, List, Optional
|
||||
|
||||
@ -15,11 +16,13 @@ from freqtrade import exchange
|
||||
from freqtrade.analyze import Analyze
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.configuration import Configuration
|
||||
from freqtrade.logger import Logger
|
||||
from freqtrade.misc import file_dump_json
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Backtesting(object):
|
||||
"""
|
||||
Backtesting class, this class contains all the logic to run a backtest
|
||||
@ -29,10 +32,6 @@ class Backtesting(object):
|
||||
backtesting.start()
|
||||
"""
|
||||
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.analyze = None
|
||||
self.ticker_interval = None
|
||||
@ -208,7 +207,7 @@ class Backtesting(object):
|
||||
# For now export inside backtest(), maybe change so that backtest()
|
||||
# returns a tuple like: (dataframe, records, logs, etc)
|
||||
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)
|
||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
|
||||
return DataFrame.from_records(trades, columns=labels)
|
||||
@ -220,15 +219,15 @@ class Backtesting(object):
|
||||
"""
|
||||
data = {}
|
||||
pairs = self.config['exchange']['pair_whitelist']
|
||||
self.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_currency: %s ...', self.config['stake_currency'])
|
||||
logger.info('Using stake_amount: %s ...', self.config['stake_amount'])
|
||||
|
||||
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:
|
||||
data[pair] = exchange.get_ticker_history(pair, self.ticker_interval)
|
||||
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'))
|
||||
data = optimize.load_data(
|
||||
@ -243,14 +242,14 @@ class Backtesting(object):
|
||||
if self.config.get('realistic_simulation', False):
|
||||
max_open_trades = self.config['max_open_trades']
|
||||
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
|
||||
|
||||
preprocessed = self.tickerdata_to_dataframe(data)
|
||||
|
||||
# Print timeframe
|
||||
min_date, max_date = self.get_timeframe(preprocessed)
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'Measuring data from %s up to %s (%s days)..',
|
||||
min_date.isoformat(),
|
||||
max_date.isoformat(),
|
||||
@ -271,9 +270,7 @@ class Backtesting(object):
|
||||
'record': self.config.get('export')
|
||||
}
|
||||
)
|
||||
|
||||
self.logging.set_format('%(message)s')
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'\n==================================== '
|
||||
'BACKTESTING REPORT'
|
||||
' ====================================\n'
|
||||
@ -309,7 +306,6 @@ def start(args: Namespace) -> None:
|
||||
"""
|
||||
|
||||
# Initialize logger
|
||||
logger = Logger(name=__name__).get_logger()
|
||||
logger.info('Starting freqtrade in Backtesting mode')
|
||||
|
||||
# Initialize configuration
|
||||
|
@ -25,12 +25,14 @@ from pandas import DataFrame
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.configuration import Configuration
|
||||
from freqtrade.logger import Logger
|
||||
from freqtrade.optimize import load_data
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Hyperopt(Backtesting):
|
||||
"""
|
||||
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:
|
||||
|
||||
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
|
||||
# to the number of days
|
||||
self.target_trades = 600
|
||||
@ -194,14 +191,14 @@ class Hyperopt(Backtesting):
|
||||
"""
|
||||
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'))
|
||||
|
||||
def read_trials(self) -> Trials:
|
||||
"""
|
||||
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'))
|
||||
os.remove(self.trials_file)
|
||||
return trials
|
||||
@ -212,7 +209,7 @@ class Hyperopt(Backtesting):
|
||||
"""
|
||||
vals = json.dumps(self.trials.best_trial['misc']['vals'], indent=4)
|
||||
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:
|
||||
"""
|
||||
@ -220,13 +217,13 @@ class Hyperopt(Backtesting):
|
||||
"""
|
||||
if results['loss'] < self.current_best_loss:
|
||||
self.current_best_loss = results['loss']
|
||||
log_msg = '{:5d}/{}: {}. Loss {:.5f}'.format(
|
||||
log_msg = '\n{:5d}/{}: {}. Loss {:.5f}'.format(
|
||||
results['current_tries'],
|
||||
results['total_tries'],
|
||||
results['result'],
|
||||
results['loss']
|
||||
)
|
||||
self.logger.info(log_msg)
|
||||
print(log_msg)
|
||||
else:
|
||||
print('.', end='')
|
||||
sys.stdout.flush()
|
||||
@ -511,8 +508,8 @@ class Hyperopt(Backtesting):
|
||||
self.processed = self.tickerdata_to_dataframe(data)
|
||||
|
||||
if self.config.get('mongodb'):
|
||||
self.logger.info('Using mongodb ...')
|
||||
self.logger.info(
|
||||
logger.info('Using mongodb ...')
|
||||
logger.info(
|
||||
'Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!'
|
||||
)
|
||||
|
||||
@ -522,7 +519,7 @@ class Hyperopt(Backtesting):
|
||||
exp_key='exp1'
|
||||
)
|
||||
else:
|
||||
self.logger.info('Preparing Trials..')
|
||||
logger.info('Preparing Trials..')
|
||||
signal.signal(signal.SIGINT, self.signal_handler)
|
||||
# read trials file if we have one
|
||||
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.total_tries += self.current_tries
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'Continuing with trials. Current: %d, Total: %d',
|
||||
self.current_tries,
|
||||
self.total_tries
|
||||
)
|
||||
|
||||
try:
|
||||
# change the Logging format
|
||||
self.logging.set_format('\n%(message)s')
|
||||
|
||||
best_parameters = fmin(
|
||||
fn=self.generate_optimizer,
|
||||
space=self.hyperopt_space(),
|
||||
@ -563,11 +557,11 @@ class Hyperopt(Backtesting):
|
||||
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:
|
||||
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
|
||||
self.save_trials()
|
||||
@ -576,7 +570,7 @@ class Hyperopt(Backtesting):
|
||||
"""
|
||||
Hyperopt SIGINT handler
|
||||
"""
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'Hyperopt received %s',
|
||||
signal.Signals(sig).name
|
||||
)
|
||||
@ -597,8 +591,6 @@ def start(args: Namespace) -> None:
|
||||
logging.getLogger('hyperopt.mongoexp').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
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""
|
||||
This module contains class to define a RPC communications
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Tuple, Any
|
||||
@ -11,12 +11,14 @@ import sqlalchemy as sql
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade import exchange
|
||||
from freqtrade.logger import Logger
|
||||
from freqtrade.misc import shorten_date
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.state import State
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RPC(object):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
self.freqtrade = freqtrade
|
||||
self.logger = Logger(
|
||||
name=__name__,
|
||||
level=self.freqtrade.config.get('loglevel')
|
||||
).get_logger()
|
||||
|
||||
def rpc_trade_status(self) -> Tuple[bool, Any]:
|
||||
"""
|
||||
@ -350,7 +348,7 @@ class RPC(object):
|
||||
)
|
||||
).first()
|
||||
if not trade:
|
||||
self.logger.warning('forcesell: Invalid argument received')
|
||||
logger.warning('forcesell: Invalid argument received')
|
||||
return True, 'Invalid argument.'
|
||||
|
||||
_exec_forcesell(trade)
|
||||
|
@ -1,11 +1,14 @@
|
||||
"""
|
||||
This module contains class to manage RPC communications (Telegram, Slack, ...)
|
||||
"""
|
||||
import logging
|
||||
|
||||
from freqtrade.logger import Logger
|
||||
from freqtrade.rpc.telegram import Telegram
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RPCManager(object):
|
||||
"""
|
||||
Class to manage RPC objects (Telegram, Slack, ...)
|
||||
@ -18,12 +21,6 @@ class RPCManager(object):
|
||||
"""
|
||||
self.freqtrade = freqtrade
|
||||
|
||||
# Init the logger
|
||||
self.logger = Logger(
|
||||
name=__name__,
|
||||
level=self.freqtrade.config.get('loglevel')
|
||||
).get_logger()
|
||||
|
||||
self.registered_modules = []
|
||||
self.telegram = None
|
||||
self._init()
|
||||
@ -34,7 +31,7 @@ class RPCManager(object):
|
||||
:return:
|
||||
"""
|
||||
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.telegram = Telegram(self.freqtrade)
|
||||
|
||||
@ -44,7 +41,7 @@ class RPCManager(object):
|
||||
:return: None
|
||||
"""
|
||||
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.telegram.cleanup()
|
||||
|
||||
@ -54,6 +51,6 @@ class RPCManager(object):
|
||||
:param msg: message
|
||||
:return: None
|
||||
"""
|
||||
self.logger.info(msg)
|
||||
logger.info(msg)
|
||||
if 'telegram' in self.registered_modules:
|
||||
self.telegram.send_msg(msg)
|
||||
|
@ -3,7 +3,7 @@
|
||||
"""
|
||||
This module manage Telegram communication
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any, Callable
|
||||
|
||||
from tabulate import tabulate
|
||||
@ -15,6 +15,9 @@ from freqtrade.__init__ import __version__
|
||||
from freqtrade.rpc.rpc import RPC
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[..., Any]:
|
||||
"""
|
||||
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'])
|
||||
|
||||
if int(update.message.chat_id) != chat_id:
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'Rejected unauthorized message from: %s',
|
||||
update.message.chat_id
|
||||
)
|
||||
return wrapper
|
||||
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'Executing handler: %s for chat_id: %s',
|
||||
command_handler.__name__,
|
||||
chat_id
|
||||
@ -45,7 +48,7 @@ def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[
|
||||
try:
|
||||
return command_handler(self, *args, **kwargs)
|
||||
except BaseException:
|
||||
self.logger.exception('Exception occurred within Telegram module')
|
||||
logger.exception('Exception occurred within Telegram module')
|
||||
|
||||
return wrapper
|
||||
|
||||
@ -101,7 +104,7 @@ class Telegram(RPC):
|
||||
timeout=30,
|
||||
read_latency=60,
|
||||
)
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
'rpc.telegram is listening for following commands: %s',
|
||||
[h.command for h in handles]
|
||||
)
|
||||
@ -357,7 +360,7 @@ class Telegram(RPC):
|
||||
'max': [self._config['max_open_trades']]
|
||||
}, headers=['current', 'max'], tablefmt='simple')
|
||||
message = "<pre>{}</pre>".format(message)
|
||||
self.logger.debug(message)
|
||||
logger.debug(message)
|
||||
self.send_msg(message, parse_mode=ParseMode.HTML)
|
||||
|
||||
@authorized_only
|
||||
@ -428,7 +431,7 @@ class Telegram(RPC):
|
||||
except NetworkError as network_err:
|
||||
# Sometimes the telegram server resets the current connection,
|
||||
# if this is the case we send the message again.
|
||||
self.logger.warning(
|
||||
logger.warning(
|
||||
'Telegram NetworkError: %s! Trying one more time.',
|
||||
network_err.message
|
||||
)
|
||||
@ -439,7 +442,7 @@ class Telegram(RPC):
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
except TelegramError as telegram_err:
|
||||
self.logger.warning(
|
||||
logger.warning(
|
||||
'TelegramError: %s! Giving up on that message.',
|
||||
telegram_err.message
|
||||
)
|
||||
|
@ -4,6 +4,7 @@
|
||||
This module load custom strategies
|
||||
"""
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
@ -11,12 +12,14 @@ from collections import OrderedDict
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.constants import Constants
|
||||
from freqtrade.logger import Logger
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
|
||||
sys.path.insert(0, r'../../user_data/strategies')
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Strategy(object):
|
||||
"""
|
||||
This class contains all the logic to load custom strategy class
|
||||
@ -27,8 +30,6 @@ class Strategy(object):
|
||||
:param config:
|
||||
:return:
|
||||
"""
|
||||
self.logger = Logger(name=__name__).get_logger()
|
||||
|
||||
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
|
||||
if 'strategy' in config:
|
||||
strategy = config['strategy']
|
||||
@ -42,17 +43,17 @@ class Strategy(object):
|
||||
# Check if we need to override configuration
|
||||
if 'minimal_roi' in config:
|
||||
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:
|
||||
self.custom_strategy.stoploss = config['stoploss']
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
"Override strategy \'stoploss\' with value in config file: %s.", config['stoploss']
|
||||
)
|
||||
|
||||
if 'ticker_interval' in config:
|
||||
self.custom_strategy.ticker_interval = config['ticker_interval']
|
||||
self.logger.info(
|
||||
logger.info(
|
||||
"Override strategy \'ticker_interval\' with value in config file: %s.",
|
||||
config['ticker_interval']
|
||||
)
|
||||
@ -87,12 +88,12 @@ class Strategy(object):
|
||||
|
||||
# Fallback to the default strategy
|
||||
except (ImportError, TypeError) as error:
|
||||
self.logger.error(
|
||||
logger.error(
|
||||
"Impossible to load Strategy 'user_data/strategies/%s.py'. This file does not exist"
|
||||
" or contains Python code errors",
|
||||
strategy_name
|
||||
)
|
||||
self.logger.error(
|
||||
logger.error(
|
||||
"The error is:\n%s.",
|
||||
error
|
||||
)
|
||||
@ -106,7 +107,7 @@ class Strategy(object):
|
||||
module = importlib.import_module(filename, __package__)
|
||||
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()
|
||||
|
||||
@staticmethod
|
||||
|
@ -46,7 +46,7 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
|
||||
return FreqtradeBot(config, create_engine('sqlite://'))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@pytest.fixture(scope="function")
|
||||
def default_conf():
|
||||
""" Returns validated configuration suitable for most tests """
|
||||
configuration = {
|
||||
|
@ -18,9 +18,9 @@ from freqtrade.tests.conftest import log_has
|
||||
API_INIT = False
|
||||
|
||||
|
||||
def maybe_init_api(conf, mocker):
|
||||
def maybe_init_api(conf, mocker, force=False):
|
||||
global API_INIT
|
||||
if not API_INIT:
|
||||
if force or not API_INIT:
|
||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||
side_effect=lambda s: True)
|
||||
init(config=conf)
|
||||
@ -29,7 +29,7 @@ def maybe_init_api(conf, mocker):
|
||||
|
||||
def test_init(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
maybe_init_api(default_conf, mocker)
|
||||
maybe_init_api(default_conf, mocker, True)
|
||||
assert log_has('Instance is running with dry_run enabled', caplog.record_tuples)
|
||||
|
||||
|
||||
|
@ -450,10 +450,12 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
assert log_has(line, caplog.record_tuples)
|
||||
|
||||
|
||||
def test_backtest(init_backtesting, default_conf) -> None:
|
||||
def test_backtest(init_backtesting, default_conf, fee, mocker) -> None:
|
||||
"""
|
||||
Test Backtesting.backtest() method
|
||||
"""
|
||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||
|
||||
backtesting = _BACKTESTING
|
||||
|
||||
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
|
||||
@ -469,10 +471,12 @@ def test_backtest(init_backtesting, default_conf) -> None:
|
||||
assert not results.empty
|
||||
|
||||
|
||||
def test_backtest_1min_ticker_interval(init_backtesting, default_conf) -> None:
|
||||
def test_backtest_1min_ticker_interval(init_backtesting, default_conf, fee, mocker) -> None:
|
||||
"""
|
||||
Test Backtesting.backtest() method with 1 min ticker
|
||||
"""
|
||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||
|
||||
backtesting = _BACKTESTING
|
||||
|
||||
# Run a backtesting for an exiting 5min ticker_interval
|
||||
@ -513,10 +517,11 @@ def test_backtest_pricecontours(init_backtesting, default_conf, fee, mocker) ->
|
||||
|
||||
|
||||
# Test backtest using offline data (testdata directory)
|
||||
def test_backtest_ticks(init_backtesting, default_conf):
|
||||
def test_backtest_ticks(init_backtesting, default_conf, fee, mocker):
|
||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||
ticks = [1, 5]
|
||||
fun = _BACKTESTING.populate_buy_trend
|
||||
for tick in ticks:
|
||||
for _ in ticks:
|
||||
backtest_conf = _make_backtest_conf(conf=default_conf)
|
||||
results = _run_backtest_1(fun, backtest_conf)
|
||||
assert not results.empty
|
||||
|
@ -64,7 +64,6 @@ def test_start(mocker, default_conf, caplog) -> None:
|
||||
Test start() function
|
||||
"""
|
||||
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_optimize_conf',
|
||||
MagicMock(return_value=default_conf))
|
||||
@ -125,7 +124,7 @@ def test_loss_calculation_has_limited_profit(init_hyperopt) -> None:
|
||||
assert under > correct
|
||||
|
||||
|
||||
def test_log_results_if_loss_improves(init_hyperopt, caplog) -> None:
|
||||
def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt.current_best_loss = 2
|
||||
hyperopt.log_results(
|
||||
@ -136,7 +135,8 @@ def test_log_results_if_loss_improves(init_hyperopt, caplog) -> None:
|
||||
'result': 'foo'
|
||||
}
|
||||
)
|
||||
assert log_has(' 1/2: foo. Loss 1.00000', caplog.record_tuples)
|
||||
out, err = capsys.readouterr()
|
||||
assert ' 1/2: foo. Loss 1.00000'in out
|
||||
|
||||
|
||||
def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
|
||||
@ -184,7 +184,6 @@ def test_fmin_best_results(mocker, init_hyperopt, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
Strategy({'strategy': 'default_strategy'})
|
||||
@ -230,7 +229,6 @@ def test_fmin_throw_value_error(mocker, init_hyperopt, default_conf, caplog) ->
|
||||
conf.update({'timerange': None})
|
||||
conf.update({'spaces': 'all'})
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
Strategy({'strategy': 'default_strategy'})
|
||||
@ -274,7 +272,6 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, defa
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
|
||||
Strategy({'strategy': 'default_strategy'})
|
||||
|
@ -391,7 +391,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
|
||||
assert freqtradebot.state == State.STOPPED
|
||||
|
||||
|
||||
def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test rpc_forcesell() method
|
||||
"""
|
||||
@ -411,7 +411,8 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
'type': 'limit',
|
||||
'side': 'buy'
|
||||
}
|
||||
)
|
||||
),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -524,7 +525,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||
assert prec_satoshi(res[0]['profit'], 6.2)
|
||||
|
||||
|
||||
def test_rpc_count(mocker, default_conf, ticker) -> None:
|
||||
def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
|
||||
"""
|
||||
Test rpc_count() method
|
||||
"""
|
||||
@ -535,7 +536,8 @@ def test_rpc_count(mocker, default_conf, ticker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
@ -234,7 +234,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_status(default_conf, update, fee, mocker, ticker) -> None:
|
||||
def test_status(default_conf, update, mocker, fee, ticker) -> None:
|
||||
"""
|
||||
Test _status() method
|
||||
"""
|
||||
@ -250,7 +250,7 @@ def test_status(default_conf, update, fee, mocker, ticker) -> None:
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_pair_detail_url=MagicMock(),
|
||||
get_fee=fee
|
||||
get_fee=fee,
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
status_table = MagicMock()
|
||||
@ -279,7 +279,7 @@ def test_status(default_conf, update, fee, mocker, ticker) -> None:
|
||||
assert status_table.call_count == 1
|
||||
|
||||
|
||||
def test_status_handle(default_conf, update, ticker, mocker) -> None:
|
||||
def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test _status() method
|
||||
"""
|
||||
@ -288,7 +288,8 @@ def test_status_handle(default_conf, update, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
status_table = MagicMock()
|
||||
@ -324,7 +325,7 @@ def test_status_handle(default_conf, update, ticker, mocker) -> None:
|
||||
assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
|
||||
def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test _status_table() method
|
||||
"""
|
||||
@ -334,7 +335,8 @@ def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': 'mocked_order_id'})
|
||||
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
|
||||
get_fee=fee,
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@ -977,7 +979,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
|
||||
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_count_handle(default_conf, update, ticker, mocker) -> None:
|
||||
def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test _count() method
|
||||
"""
|
||||
@ -995,6 +997,7 @@ def test_count_handle(default_conf, update, ticker, mocker) -> None:
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': 'mocked_order_id'})
|
||||
)
|
||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
telegram = Telegram(freqtradebot)
|
||||
|
||||
|
@ -29,7 +29,6 @@ def test_strategy_structure():
|
||||
|
||||
def test_load_strategy(result):
|
||||
strategy = Strategy()
|
||||
strategy.logger = logging.getLogger(__name__)
|
||||
|
||||
assert not hasattr(Strategy, 'custom_strategy')
|
||||
strategy._load_strategy('test_strategy')
|
||||
@ -42,14 +41,13 @@ def test_load_strategy(result):
|
||||
|
||||
def test_load_not_found_strategy(caplog):
|
||||
strategy = Strategy()
|
||||
strategy.logger = logging.getLogger(__name__)
|
||||
|
||||
assert not hasattr(Strategy, 'custom_strategy')
|
||||
strategy._load_strategy('NotFoundStrategy')
|
||||
|
||||
error_msg = "Impossible to load Strategy 'user_data/strategies/{}.py'. This file does not " \
|
||||
"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):
|
||||
|
@ -126,8 +126,10 @@ def test_fiat_convert_get_price(mocker):
|
||||
|
||||
def test_fiat_convert_without_network():
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
|
||||
CryptoToFiatConverter._coinmarketcap = None
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
CryptoToFiatConverter._coinmarketcap = None
|
||||
|
||||
assert fiat_convert._coinmarketcap is None
|
||||
assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0
|
||||
|
@ -235,7 +235,7 @@ def test_refresh_whitelist() -> None:
|
||||
pass
|
||||
|
||||
|
||||
def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@ -246,7 +246,8 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
# Save state of current whitelist
|
||||
@ -270,7 +271,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
assert whitelist == default_conf['exchange']['pair_whitelist']
|
||||
|
||||
|
||||
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@ -282,7 +283,8 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, mock
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=buy_mock
|
||||
buy=buy_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@ -294,7 +296,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, mock
|
||||
assert rate * amount >= conf['stake_amount']
|
||||
|
||||
|
||||
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@ -306,7 +308,8 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, moc
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5)
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@ -314,7 +317,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, moc
|
||||
freqtrade.create_trade()
|
||||
|
||||
|
||||
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@ -325,7 +328,8 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, mocker) ->
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@ -340,7 +344,7 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, mocker) ->
|
||||
|
||||
|
||||
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
|
||||
limit_buy_order, mocker) -> None:
|
||||
limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@ -351,7 +355,8 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@ -365,7 +370,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
|
||||
freqtrade.create_trade()
|
||||
|
||||
|
||||
def test_create_trade_no_signal(default_conf, mocker) -> None:
|
||||
def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@ -379,7 +384,8 @@ def test_create_trade_no_signal(default_conf, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker_history=MagicMock(return_value=20),
|
||||
get_balance=MagicMock(return_value=20)
|
||||
get_balance=MagicMock(return_value=20),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@ -392,7 +398,7 @@ def test_create_trade_no_signal(default_conf, mocker) -> None:
|
||||
|
||||
|
||||
def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
markets, mocker, caplog) -> None:
|
||||
markets, fee, mocker, caplog) -> None:
|
||||
"""
|
||||
Test the trade creation in _process() method
|
||||
"""
|
||||
@ -405,7 +411,8 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
get_ticker=ticker,
|
||||
get_markets=markets,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_order=MagicMock(return_value=limit_buy_order)
|
||||
get_order=MagicMock(return_value=limit_buy_order),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@ -477,7 +484,8 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) ->
|
||||
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_process_trade_handling(default_conf, ticker, limit_buy_order, markets, mocker) -> None:
|
||||
def test_process_trade_handling(
|
||||
default_conf, ticker, limit_buy_order, markets, fee, mocker) -> None:
|
||||
"""
|
||||
Test _process()
|
||||
"""
|
||||
@ -490,7 +498,8 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, markets,
|
||||
get_ticker=ticker,
|
||||
get_markets=markets,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_order=MagicMock(return_value=limit_buy_order)
|
||||
get_order=MagicMock(return_value=limit_buy_order),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@ -621,7 +630,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock
|
||||
assert trade.close_date is not None
|
||||
|
||||
|
||||
def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@ -636,7 +645,8 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, mock
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
|
||||
@ -678,7 +688,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, mock
|
||||
assert freqtrade.handle_trade(trades[0]) is True
|
||||
|
||||
|
||||
def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog) -> None:
|
||||
def test_handle_trade_roi(default_conf, ticker, limit_buy_order, fee, mocker, caplog) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@ -693,7 +703,8 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog)
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
|
||||
@ -713,7 +724,8 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog)
|
||||
assert log_has('Required profit reached. Selling..', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker, caplog) -> None:
|
||||
def test_handle_trade_experimental(
|
||||
default_conf, ticker, limit_buy_order, fee, mocker, caplog) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@ -728,7 +740,8 @@ def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
|
||||
|
||||
@ -746,7 +759,7 @@ def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker
|
||||
assert log_has('Sell signal received. Selling..', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@ -757,7 +770,8 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@ -1153,7 +1167,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
|
||||
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -> None:
|
||||
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test sell_profit_only feature when enabled
|
||||
"""
|
||||
@ -1169,7 +1183,8 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -
|
||||
'ask': 0.00002173,
|
||||
'last': 0.00002172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['experimental'] = {
|
||||
@ -1185,7 +1200,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
|
||||
|
||||
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker) -> None:
|
||||
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test sell_profit_only feature when disabled
|
||||
"""
|
||||
@ -1201,7 +1216,8 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker)
|
||||
'ask': 0.00002173,
|
||||
'last': 0.00002172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['experimental'] = {
|
||||
@ -1217,7 +1233,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
|
||||
|
||||
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) -> None:
|
||||
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test sell_profit_only feature when enabled and we have a loss
|
||||
"""
|
||||
@ -1233,7 +1249,8 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) ->
|
||||
'ask': 0.00000173,
|
||||
'last': 0.00000172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['experimental'] = {
|
||||
@ -1249,7 +1266,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) ->
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
|
||||
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker) -> None:
|
||||
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test sell_profit_only feature when enabled and we have a loss
|
||||
"""
|
||||
@ -1265,7 +1282,8 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker) ->
|
||||
'ask': 0.00000173,
|
||||
'last': 0.00000172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
|
@ -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
|
@ -7,6 +7,11 @@ from sqlalchemy import create_engine
|
||||
from freqtrade.persistence import Trade, init, clean_dry_run_db
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_persistence(default_conf):
|
||||
init(default_conf)
|
||||
|
||||
|
||||
def test_init_create_session(default_conf, mocker):
|
||||
mocker.patch.dict('freqtrade.persistence._CONF', default_conf)
|
||||
|
||||
@ -89,6 +94,7 @@ def test_init_prod_db(default_conf, mocker):
|
||||
os.rename(prod_db_swp, prod_db)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee):
|
||||
"""
|
||||
On this test we will buy and sell a crypto currency.
|
||||
@ -144,6 +150,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee):
|
||||
assert trade.close_date is not None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@ -167,6 +174,7 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
assert trade.calc_profit_percent() == 0.06201057
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_close_trade_price_exception(limit_buy_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@ -181,6 +189,7 @@ def test_calc_close_trade_price_exception(limit_buy_order, fee):
|
||||
assert trade.calc_close_trade_price() == 0.0
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_open_order(limit_buy_order):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@ -204,6 +213,7 @@ def test_update_open_order(limit_buy_order):
|
||||
assert trade.close_date is None
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_invalid_order(limit_buy_order):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@ -217,6 +227,7 @@ def test_update_invalid_order(limit_buy_order):
|
||||
trade.update(limit_buy_order)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_open_trade_price(limit_buy_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@ -235,6 +246,7 @@ def test_calc_open_trade_price(limit_buy_order, fee):
|
||||
assert trade.calc_open_trade_price(fee=0.003) == 0.001003000
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@ -257,6 +269,7 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
assert trade.calc_close_trade_price(fee=0.005) == 0.0010619972
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_profit(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
@ -288,6 +301,7 @@ def test_calc_profit(limit_buy_order, limit_sell_order, fee):
|
||||
assert trade.calc_profit(fee=0.003) == 0.00006163
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_profit_percent(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
|
@ -10,7 +10,7 @@ Optional Cli parameters
|
||||
--timerange: specify what timerange of data to use.
|
||||
-l / --live: Live, to download the latest ticker for the pair
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from argparse import Namespace
|
||||
from os import path
|
||||
@ -22,13 +22,12 @@ import gzip
|
||||
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade import misc
|
||||
from freqtrade.logger import Logger
|
||||
from pandas import DataFrame
|
||||
from freqtrade.constants import Constants
|
||||
|
||||
import dateutil.parser
|
||||
|
||||
logger = Logger(name="freqtrade").get_logger()
|
||||
logger = logging.getLogger('freqtrade')
|
||||
|
||||
|
||||
def load_old_file(filename) -> (List[Dict], bool):
|
||||
|
@ -11,7 +11,7 @@ Optional Cli parameters
|
||||
--timerange: specify what timerange of data to use.
|
||||
-l / --live: Live, to download the latest ticker for the pair
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from argparse import Namespace
|
||||
|
||||
@ -24,11 +24,10 @@ import plotly.graph_objs as go
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.analyze import Analyze
|
||||
from freqtrade import exchange
|
||||
from freqtrade.logger import Logger
|
||||
import freqtrade.optimize as optimize
|
||||
|
||||
|
||||
logger = Logger(name="Graph dataframe").get_logger()
|
||||
logger = logging.getLogger('freqtrade')
|
||||
|
||||
|
||||
def plot_analyzed_dataframe(args: Namespace) -> None:
|
||||
|
@ -10,7 +10,7 @@ Optional Cli parameters
|
||||
-s / --strategy: strategy to use
|
||||
--timerange: specify what timerange of data to use.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import json
|
||||
from argparse import Namespace
|
||||
@ -24,14 +24,13 @@ import plotly.graph_objs as go
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.configuration import Configuration
|
||||
from freqtrade.analyze import Analyze
|
||||
from freqtrade.logger import Logger
|
||||
from freqtrade.constants import Constants
|
||||
|
||||
import freqtrade.optimize as optimize
|
||||
import freqtrade.misc as misc
|
||||
|
||||
|
||||
logger = Logger(name="Graph profits").get_logger()
|
||||
logger = logging.getLogger('freqtrade')
|
||||
|
||||
|
||||
# data:: [ pair, profit-%, enter, exit, time, duration]
|
||||
|
Loading…
Reference in New Issue
Block a user