Merge branch 'develop' into support_multiple_ticker

This commit is contained in:
Jean-Baptiste LE STANG
2018-01-20 19:25:47 +01:00
22 changed files with 767 additions and 253 deletions

View File

@@ -281,36 +281,36 @@ def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:
return dataframe
def get_signal(pair: str, signal: SignalType, interval: int) -> bool:
def get_signal(pair: str, interval: int) -> (bool, bool):
"""
Calculates current signal based several technical analysis indicators
:param pair: pair in format BTC_ANT or BTC-ANT
:return: True if pair is good for buying, False otherwise
:return: (True, False) if pair is good for buying and not for selling
"""
ticker_hist = get_ticker_history(pair, interval)
if not ticker_hist:
logger.warning('Empty ticker history for pair %s', pair)
return False
return (False, False)
try:
dataframe = analyze_ticker(ticker_hist)
except ValueError as ex:
logger.warning('Unable to analyze ticker for pair %s: %s', pair, str(ex))
return False
return (False, False)
except Exception as ex:
logger.exception('Unexpected error when analyzing ticker for pair %s: %s', pair, str(ex))
return False
return (False, False)
if dataframe.empty:
return False
return (False, False)
latest = dataframe.iloc[-1]
# Check if dataframe is out of date
signal_date = arrow.get(latest['date'])
if signal_date < arrow.now() - timedelta(minutes=10):
return False
return (False, False)
result = latest[signal.value] == 1
logger.debug('%s_trigger: %s (pair=%s, signal=%s)', signal.value, latest['date'], pair, result)
return result
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell))
return (buy, sell)

View File

@@ -14,7 +14,7 @@ from cachetools import cached, TTLCache
from freqtrade import (DependencyException, OperationalException, __version__,
exchange, persistence, rpc)
from freqtrade.analyze import SignalType, get_signal
from freqtrade.analyze import get_signal
from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.misc import (State, get_state, load_config, parse_args,
throttle, update_state)
@@ -129,9 +129,17 @@ def check_handle_timedout(timeoutvalue: int) -> None:
timeoutthreashold = arrow.utcnow().shift(minutes=-timeoutvalue).datetime
for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
order = exchange.get_order(trade.open_order_id)
try:
order = exchange.get_order(trade.open_order_id)
except requests.exceptions.RequestException:
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
continue
ordertime = arrow.get(order['opened'])
# Check if trade is still actually open
if int(order['remaining']) == 0:
continue
if order['type'] == "LIMIT_BUY" and ordertime < timeoutthreashold:
# Buy timeout - cancel order
exchange.cancel_order(trade.open_order_id)
@@ -140,6 +148,8 @@ def check_handle_timedout(timeoutvalue: int) -> None:
Trade.session.delete(trade)
Trade.session.flush()
logger.info('Buy order timeout for %s.', trade)
rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
else:
# if trade is partially complete, edit the stake details for the trade
# and close the order
@@ -147,6 +157,8 @@ def check_handle_timedout(timeoutvalue: int) -> None:
trade.stake_amount = trade.amount * trade.open_rate
trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade)
rpc.send_msg('*Timeout:* Remaining buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
elif order['type'] == "LIMIT_SELL" and ordertime < timeoutthreashold:
# Sell timeout - cancel order and update trade
if order['remaining'] == order['amount']:
@@ -157,6 +169,8 @@ def check_handle_timedout(timeoutvalue: int) -> None:
trade.close_date = None
trade.is_open = True
trade.open_order_id = None
rpc.send_msg('*Timeout:* Unfilled sell order for {} cancelled'.format(
trade.pair.replace('_', '/')))
logger.info('Sell order timeout for %s.', trade)
return True
else:
@@ -247,24 +261,27 @@ def handle_trade(trade: Trade, interval: int) -> bool:
logger.debug('Handling %s ...', trade)
current_rate = exchange.get_ticker(trade.pair)['bid']
# Check if minimal roi has been reached
if min_roi_reached(trade, current_rate, datetime.utcnow()):
(buy, sell) = (False, False)
if _CONF.get('experimental', {}).get('use_sell_signal'):
(buy, sell) = get_signal(trade.pair)
# Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee)
if not buy and min_roi_reached(trade, current_rate, datetime.utcnow()):
logger.debug('Executing sell due to ROI ...')
execute_sell(trade, current_rate)
return True
# Experimental: Check if sell signal has been enabled and triggered
if _CONF.get('experimental', {}).get('use_sell_signal'):
# Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
if _CONF.get('experimental', {}).get('sell_profit_only'):
logger.debug('Checking if trade is profitable ...')
if trade.calc_profit(rate=current_rate) <= 0:
return False
logger.debug('Checking sell_signal ...')
if get_signal(trade.pair, SignalType.SELL, interval):
logger.debug('Executing sell due to sell signal ...')
execute_sell(trade, current_rate)
return True
# Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
if _CONF.get('experimental', {}).get('sell_profit_only', False):
logger.debug('Checking if trade is profitable ...')
if not buy and trade.calc_profit(rate=current_rate) <= 0:
return False
if sell and not buy:
logger.debug('Executing sell due to sell signal ...')
execute_sell(trade, current_rate)
return True
return False
@@ -305,7 +322,8 @@ def create_trade(stake_amount: float, interval: int) -> bool:
# Pick pair based on StochRSI buy signals
for _pair in whitelist:
if get_signal(_pair, SignalType.BUY, interval):
(buy, sell) = get_signal(_pair)
if buy and not sell:
pair = _pair
break
else:

View File

@@ -4,6 +4,7 @@ import json
import logging
import time
import os
import re
from typing import Any, Callable, Dict, List
from jsonschema import Draft4Validator, validate
@@ -115,6 +116,14 @@ def common_args_parser(description: str):
type=str,
metavar='PATH',
)
parser.add_argument(
'--datadir',
help='path to backtest data (default freqdata/tests/testdata)',
dest='datadir',
default=os.path.join('freqtrade', 'tests', 'testdata'),
type=str,
metavar='PATH',
)
return parser
@@ -131,14 +140,6 @@ def parse_args(args: List[str], description: str):
action='store_true',
dest='dry_run_db',
)
parser.add_argument(
'-dd', '--datadir',
help='path to backtest data (default freqdata/tests/testdata',
dest='datadir',
default=os.path.join('freqtrade', 'tests', 'testdata'),
type=str,
metavar='PATH',
)
parser.add_argument(
'--dynamic-whitelist',
help='dynamically generate and update whitelist \
@@ -154,6 +155,113 @@ def parse_args(args: List[str], description: str):
return parser.parse_args(args)
def backtesting_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'-l', '--live',
action='store_true',
dest='live',
help='using live data',
)
parser.add_argument(
'-i', '--ticker-interval',
help='specify ticker interval in minutes (1, 5, 30, 60, 1440)',
dest='ticker_interval',
default=5,
type=int,
metavar='INT',
)
parser.add_argument(
'--realistic-simulation',
help='uses max_open_trades from config to simulate real world limitations',
action='store_true',
dest='realistic_simulation',
)
parser.add_argument(
'-r', '--refresh-pairs-cached',
help='refresh the pairs files in tests/testdata with the latest data from Bittrex. \
Use it if you want to run your backtesting with up-to-date data.',
action='store_true',
dest='refresh_pairs',
)
parser.add_argument(
'--export',
help='Export backtest results, argument are: trades\
Example --export=trades',
type=str,
default=None,
dest='export',
)
parser.add_argument(
'--timerange',
help='Specify what timerange of data to use.',
default=None,
type=str,
dest='timerange',
)
def hyperopt_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'-e', '--epochs',
help='specify number of epochs (default: 100)',
dest='epochs',
default=100,
type=int,
metavar='INT',
)
parser.add_argument(
'--use-mongodb',
help='parallelize evaluations with mongodb (requires mongod in PATH)',
dest='mongodb',
action='store_true',
)
parser.add_argument(
'-i', '--ticker-interval',
help='specify ticker interval in minutes (default: 5)',
dest='ticker_interval',
default=5,
type=int,
metavar='INT',
)
parser.add_argument(
'--timerange',
help='Specify what timerange of data to use.',
default=None,
type=str,
dest='timerange',
)
def parse_timerange(text):
if text is None:
return None
syntax = [('^-(\d{8})$', (None, 'date')),
('^(\d{8})-$', ('date', None)),
('^(\d{8})-(\d{8})$', ('date', 'date')),
('^(-\d+)$', (None, 'line')),
('^(\d+)-$', ('line', None)),
('^(\d+)-(\d+)$', ('index', 'index'))]
for rex, stype in syntax:
# Apply the regular expression to text
m = re.match(rex, text)
if m: # Regex has matched
rvals = m.groups()
n = 0
start = None
stop = None
if stype[0]:
start = rvals[n]
if stype[0] != 'date':
start = int(start)
n += 1
if stype[1]:
stop = rvals[n]
if stype[1] != 'date':
stop = int(stop)
return (stype, start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text)
def build_subcommands(parser: argparse.ArgumentParser) -> None:
""" Builds and attaches all subcommands """
from freqtrade.optimize import backtesting, hyperopt
@@ -163,59 +271,12 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None:
# Add backtesting subcommand
backtesting_cmd = subparsers.add_parser('backtesting', help='backtesting module')
backtesting_cmd.set_defaults(func=backtesting.start)
backtesting_cmd.add_argument(
'-l', '--live',
action='store_true',
dest='live',
help='using live data',
)
backtesting_cmd.add_argument(
'-i', '--ticker-interval',
help='specify ticker interval in minutes (1, 5, 30, 60, 1440)',
dest='ticker_interval',
default=5,
type=int,
metavar='INT',
)
backtesting_cmd.add_argument(
'--realistic-simulation',
help='uses max_open_trades from config to simulate real world limitations',
action='store_true',
dest='realistic_simulation',
)
backtesting_cmd.add_argument(
'-r', '--refresh-pairs-cached',
help='refresh the pairs files in tests/testdata with the latest data from Bittrex. \
Use it if you want to run your backtesting with up-to-date data.',
action='store_true',
dest='refresh_pairs',
)
backtesting_options(backtesting_cmd)
# Add hyperopt subcommand
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
hyperopt_cmd.set_defaults(func=hyperopt.start)
hyperopt_cmd.add_argument(
'-e', '--epochs',
help='specify number of epochs (default: 100)',
dest='epochs',
default=100,
type=int,
metavar='INT',
)
hyperopt_cmd.add_argument(
'--use-mongodb',
help='parallelize evaluations with mongodb (requires mongod in PATH)',
dest='mongodb',
action='store_true',
)
hyperopt_cmd.add_argument(
'-i', '--ticker-interval',
help='specify ticker interval in minutes (default: 5)',
dest='ticker_interval',
default=5,
type=int,
metavar='INT',
)
hyperopt_options(hyperopt_cmd)
# Required json-schema for user specified config

View File

@@ -8,11 +8,25 @@ from pandas import DataFrame
from freqtrade.exchange import get_ticker_history
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf
from freqtrade.analyze import populate_indicators, parse_ticker_dataframe
from freqtrade import misc
logger = logging.getLogger(__name__)
def load_tickerdata_file(datadir, pair, ticker_interval):
def trim_tickerlist(tickerlist, timerange):
(stype, start, stop) = timerange
if stype == (None, 'line'):
return tickerlist[stop:]
elif stype == ('line', None):
return tickerlist[0:start]
elif stype == ('index', 'index'):
return tickerlist[start:stop]
else:
return tickerlist
def load_tickerdata_file(datadir, pair, ticker_interval,
timerange=None):
"""
Load a pair from file,
:return dict OR empty if unsuccesful
@@ -30,11 +44,13 @@ def load_tickerdata_file(datadir, pair, ticker_interval):
# Read the file, load the json
with open(file) as tickerdata:
pairdata = json.load(tickerdata)
if timerange:
pairdata = trim_tickerlist(pairdata, timerange)
return pairdata
def load_data(datadir: str, ticker_interval: int, pairs: Optional[List[str]] = None,
refresh_pairs: Optional[bool] = False) -> Dict[str, List]:
refresh_pairs: Optional[bool] = False, timerange=None) -> Dict[str, List]:
"""
Loads ticker history data for the given parameters
:param ticker_interval: ticker interval in minutes
@@ -51,16 +67,21 @@ def load_data(datadir: str, ticker_interval: int, pairs: Optional[List[str]] = N
download_pairs(datadir, _pairs, ticker_interval)
for pair in _pairs:
pairdata = load_tickerdata_file(datadir, pair, ticker_interval)
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
if not pairdata:
# download the tickerdata from exchange
download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval)
# and retry reading the pair
pairdata = load_tickerdata_file(datadir, pair, ticker_interval)
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
result[pair] = pairdata
return result
def tickerdata_to_dataframe(data):
preprocessed = preprocess(data)
return preprocessed
def preprocess(tickerdata: Dict[str, List]) -> Dict[str, DataFrame]:
"""Creates a dataframe and populates indicators for given ticker data"""
return {pair: populate_indicators(parse_ticker_dataframe(pair_data))
@@ -126,7 +147,6 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
logger.debug("New End: {}".format(data[-1:][0]['T']))
data = sorted(data, key=lambda data: data['T'])
with open(filename, "wt") as fp:
json.dump(data, fp)
misc.file_dump_json(filename, data)
return True

View File

@@ -13,7 +13,6 @@ from freqtrade import exchange
from freqtrade.analyze import populate_buy_trend, populate_sell_trend
from freqtrade.exchange import Bittrex
from freqtrade.main import min_roi_reached
from freqtrade.optimize import preprocess
from freqtrade.persistence import Trade
logger = logging.getLogger(__name__)
@@ -67,17 +66,60 @@ def generate_text_table(
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt)
def backtest(stake_amount: float, processed: Dict[str, DataFrame],
max_open_trades: int = 0, realistic: bool = True, sell_profit_only: bool = False,
stoploss: int = -1.00, use_sell_signal: bool = False) -> DataFrame:
def get_trade_entry(pair, row, ticker, trade_count_lock, args):
stake_amount = args['stake_amount']
max_open_trades = args.get('max_open_trades', 0)
sell_profit_only = args.get('sell_profit_only', False)
stoploss = args.get('stoploss', -1)
use_sell_signal = args.get('use_sell_signal', False)
trade = Trade(open_rate=row.close,
open_date=row.date,
stake_amount=stake_amount,
amount=stake_amount / row.open,
fee=exchange.get_fee()
)
# calculate win/lose forwards from buy point
sell_subset = ticker[row.Index + 1:][['close', 'date', 'sell']]
for row2 in sell_subset.itertuples(index=True):
if max_open_trades > 0:
# Increase trade_count_lock for every iteration
trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1
current_profit_percent = trade.calc_profit_percent(rate=row2.close)
if (sell_profit_only and current_profit_percent < 0):
continue
if min_roi_reached(trade, row2.close, row2.date) or \
(row2.sell == 1 and use_sell_signal) or \
current_profit_percent <= stoploss:
current_profit_btc = trade.calc_profit(rate=row2.close)
return row2, (pair,
current_profit_percent,
current_profit_btc,
row2.Index - row.Index,
current_profit_btc > 0,
current_profit_btc < 0
)
def backtest(args) -> DataFrame:
"""
Implements backtesting functionality
:param stake_amount: btc amount to use for each trade
:param processed: a processed dictionary with format {pair, data}
:param max_open_trades: maximum number of concurrent trades (default: 0, disabled)
:param realistic: do we try to simulate realistic trades? (default: True)
:param args: a dict containing:
stake_amount: btc amount to use for each trade
processed: a processed dictionary with format {pair, data}
max_open_trades: maximum number of concurrent trades (default: 0, disabled)
realistic: do we try to simulate realistic trades? (default: True)
sell_profit_only: sell if profit only
use_sell_signal: act on sell-signal
stoploss: use stoploss
:return: DataFrame
"""
processed = args['processed']
max_open_trades = args.get('max_open_trades', 0)
realistic = args.get('realistic', True)
record = args.get('record', None)
records = []
trades = []
trade_count_lock: dict = {}
exchange._API = Bittrex({'key': '', 'secret': ''})
@@ -100,41 +142,25 @@ def backtest(stake_amount: float, processed: Dict[str, DataFrame],
# Increase lock
trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1
trade = Trade(
open_rate=row.close,
open_date=row.date,
stake_amount=stake_amount,
amount=stake_amount / row.open,
fee=exchange.get_fee()
)
# calculate win/lose forwards from buy point
sell_subset = ticker[row.Index + 1:][['close', 'date', 'sell']]
for row2 in sell_subset.itertuples(index=True):
if max_open_trades > 0:
# Increase trade_count_lock for every iteration
trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1
current_profit_percent = trade.calc_profit_percent(rate=row2.close)
if (sell_profit_only and current_profit_percent < 0):
continue
if min_roi_reached(trade, row2.close, row2.date) or \
(row2.sell == 1 and use_sell_signal) or \
current_profit_percent <= stoploss:
current_profit_btc = trade.calc_profit(rate=row2.close)
lock_pair_until = row2.Index
trades.append(
(
pair,
current_profit_percent,
current_profit_btc,
row2.Index - row.Index,
current_profit_btc > 0,
current_profit_btc < 0
)
)
break
ret = get_trade_entry(pair, row, ticker,
trade_count_lock, args)
if ret:
row2, trade_entry = ret
lock_pair_until = row2.Index
trades.append(trade_entry)
if record:
# Note, need to be json.dump friendly
# record a tuple of pair, current_profit_percent,
# entry-date, duration
records.append((pair, trade_entry[1],
row.date.strftime('%s'),
row2.date.strftime('%s'),
row.Index, trade_entry[3]))
# 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:
logger.info('Dumping backtest results')
misc.file_dump_json('backtest-result.json', records)
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration', 'profit', 'loss']
return DataFrame.from_records(trades, columns=labels)
@@ -167,6 +193,10 @@ def start(args):
logger.info('Using stake_currency: %s ...', config['stake_currency'])
logger.info('Using stake_amount: %s ...', config['stake_amount'])
timerange = misc.parse_timerange(args.timerange)
data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval,
refresh_pairs=args.refresh_pairs,
timerange=timerange)
max_open_trades = 0
if args.realistic_simulation:
logger.info('Using max_open_trades: %s ...', config['max_open_trades'])
@@ -176,21 +206,22 @@ def start(args):
from freqtrade import main
main._CONF = config
preprocessed = preprocess(data)
preprocessed = optimize.tickerdata_to_dataframe(data)
# Print timeframe
min_date, max_date = get_timeframe(preprocessed)
logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat())
# Execute backtest and print results
results = backtest(
stake_amount=config['stake_amount'],
processed=preprocessed,
max_open_trades=max_open_trades,
realistic=args.realistic_simulation,
sell_profit_only=config.get('experimental', {}).get('sell_profit_only', False),
stoploss=config.get('stoploss'),
use_sell_signal=config.get('experimental', {}).get('use_sell_signal', False)
)
sell_profit_only = config.get('experimental', {}).get('sell_profit_only', False)
use_sell_signal = config.get('experimental', {}).get('use_sell_signal', False)
results = backtest({'stake_amount': config['stake_amount'],
'processed': preprocessed,
'max_open_trades': max_open_trades,
'realistic': args.realistic_simulation,
'sell_profit_only': sell_profit_only,
'use_sell_signal': use_sell_signal,
'stoploss': config.get('stoploss'),
'record': args.export
})
logger.info(
'\n==================================== BACKTESTING REPORT ====================================\n%s', # noqa
generate_text_table(data, results, config['stake_currency'], args.ticker_interval)

View File

@@ -15,7 +15,7 @@ from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
from hyperopt.mongoexp import MongoTrials
from pandas import DataFrame
from freqtrade import main # noqa
from freqtrade import main, misc # noqa
from freqtrade import exchange, optimize
from freqtrade.exchange import Bittrex
from freqtrade.misc import load_config
@@ -164,7 +164,9 @@ def optimizer(params):
from freqtrade.optimize import backtesting
backtesting.populate_buy_trend = buy_strategy_generator(params)
results = backtest(OPTIMIZE_CONFIG['stake_amount'], PROCESSED, stoploss=params['stoploss'])
results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'],
'processed': PROCESSED,
'stoploss': params['stoploss']})
result_explanation = format_results(results)
total_profit = results.profit_percent.sum()
@@ -273,8 +275,11 @@ def start(args):
logger.info('Using config: %s ...', args.config)
config = load_config(args.config)
pairs = config['exchange']['pair_whitelist']
PROCESSED = optimize.preprocess(optimize.load_data(
args.datadir, pairs=pairs, ticker_interval=args.ticker_interval))
timerange = misc.parse_timerange(args.timerange)
data = optimize.load_data(args.datadir, pairs=pairs,
ticker_interval=args.ticker_interval,
timerange=timerange)
PROCESSED = optimize.tickerdata_to_dataframe(data)
if args.mongodb:
logger.info('Using mongodb ...')

View File

@@ -241,20 +241,27 @@ def _daily(bot: Bot, update: Update) -> None:
.order_by(Trade.close_date)\
.all()
curdayprofit = sum(trade.calc_profit() for trade in trades)
profit_days[profitday] = format(curdayprofit, '.8f')
profit_days[profitday] = {
'amount': format(curdayprofit, '.8f'),
'trades': len(trades)
}
stats = [
[
key,
'{value:.8f} {symbol}'.format(value=float(value), symbol=_CONF['stake_currency']),
'{value:.8f} {symbol}'.format(
value=float(value['amount']),
symbol=_CONF['stake_currency']
),
'{value:.3f} {symbol}'.format(
value=_FIAT_CONVERT.convert_amount(
value,
value['amount'],
_CONF['stake_currency'],
_CONF['fiat_display_currency']
),
symbol=_CONF['fiat_display_currency']
)
),
'{value} trade{s}'.format(value=value['trades'], s='' if value['trades'] < 2 else 's'),
]
for key, value in profit_days.items()
]
@@ -262,7 +269,8 @@ def _daily(bot: Bot, update: Update) -> None:
headers=[
'Day',
'Profit {}'.format(_CONF['stake_currency']),
'Profit {}'.format(_CONF['fiat_display_currency'])
'Profit {}'.format(_CONF['fiat_display_currency']),
'# Trades'
],
tablefmt='simple')

View File

@@ -11,6 +11,13 @@ from freqtrade.optimize.backtesting import backtest, generate_text_table, get_ti
import freqtrade.optimize.backtesting as backtesting
def trim_dictlist(dl, num):
new = {}
for pair, pair_data in dl.items():
new[pair] = pair_data[num:]
return new
def test_generate_text_table():
results = pd.DataFrame(
{
@@ -43,8 +50,11 @@ def test_backtest(default_conf, mocker):
exchange._API = Bittrex({'key': '', 'secret': ''})
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
results = backtest(default_conf['stake_amount'],
optimize.preprocess(data), 10, True)
data = trim_dictlist(data, -200)
results = backtest({'stake_amount': default_conf['stake_amount'],
'processed': optimize.preprocess(data),
'max_open_trades': 10,
'realistic': True})
assert not results.empty
@@ -54,21 +64,17 @@ def test_backtest_1min_ticker_interval(default_conf, mocker):
# Run a backtesting for an exiting 5min ticker_interval
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'])
results = backtest(default_conf['stake_amount'],
optimize.preprocess(data), 1, True)
data = trim_dictlist(data, -200)
results = backtest({'stake_amount': default_conf['stake_amount'],
'processed': optimize.preprocess(data),
'max_open_trades': 1,
'realistic': True})
assert not results.empty
def trim_dictlist(dl, num):
new = {}
for pair, pair_data in dl.items():
new[pair] = pair_data[num:]
return new
def load_data_test(what):
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'])
data = trim_dictlist(data, -100)
timerange = ((None, 'line'), None, -100)
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'], timerange=timerange)
pair = data['BTC_UNITEST']
datalen = len(pair)
# Depending on the what parameter we now adjust the
@@ -113,7 +119,10 @@ def simple_backtest(config, contour, num_results):
data = load_data_test(contour)
processed = optimize.preprocess(data)
assert isinstance(processed, dict)
results = backtest(config['stake_amount'], processed, 1, True)
results = backtest({'stake_amount': config['stake_amount'],
'processed': processed,
'max_open_trades': 1,
'realistic': True})
# results :: <class 'pandas.core.frame.DataFrame'>
assert len(results) == num_results
@@ -125,8 +134,11 @@ def simple_backtest(config, contour, num_results):
def test_backtest2(default_conf, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
results = backtest(default_conf['stake_amount'],
optimize.preprocess(data), 10, True)
data = trim_dictlist(data, -200)
results = backtest({'stake_amount': default_conf['stake_amount'],
'processed': optimize.preprocess(data),
'max_open_trades': 10,
'realistic': True})
assert not results.empty
@@ -149,10 +161,10 @@ def test_backtest_pricecontours(default_conf, mocker):
simple_backtest(default_conf, contour, numres)
def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False):
tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1)
def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False, timerange=None):
tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1, timerange=timerange)
pairdata = {'BTC_UNITEST': tickerdata}
return trim_dictlist(pairdata, -100)
return pairdata
def test_backtest_start(default_conf, mocker, caplog):
@@ -166,6 +178,8 @@ def test_backtest_start(default_conf, mocker, caplog):
args.level = 10
args.live = False
args.datadir = None
args.export = None
args.timerange = '-100' # needed due to MagicMock malleability
backtesting.start(args)
# check the logs, that will contain the backtest result
exists = ['Using max_open_trades: 1 ...',

View File

@@ -54,6 +54,7 @@ def create_trials(mocker):
def test_start_calls_fmin(mocker):
trials = create_trials(mocker)
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
mocker.patch('freqtrade.optimize.hyperopt.TRIALS', return_value=trials)
mocker.patch('freqtrade.optimize.hyperopt.sorted',
return_value=trials.results)
@@ -61,7 +62,8 @@ def test_start_calls_fmin(mocker):
mocker.patch('freqtrade.optimize.load_data')
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=False)
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=False,
timerange=None)
start(args)
mock_fmin.assert_called_once()
@@ -70,11 +72,12 @@ def test_start_calls_fmin(mocker):
def test_start_uses_mongotrials(mocker):
mock_mongotrials = mocker.patch('freqtrade.optimize.hyperopt.MongoTrials',
return_value=create_trials(mocker))
mocker.patch('freqtrade.optimize.preprocess')
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
mocker.patch('freqtrade.optimize.load_data')
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=True)
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=True,
timerange=None)
start(args)
mock_mongotrials.assert_called_once()
@@ -125,11 +128,12 @@ def test_fmin_best_results(mocker, caplog):
}
mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker))
mocker.patch('freqtrade.optimize.preprocess')
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
mocker.patch('freqtrade.optimize.load_data')
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
args = mocker.Mock(epochs=1, config='config.json.example')
args = mocker.Mock(epochs=1, config='config.json.example',
timerange=None)
start(args)
exists = [
@@ -147,11 +151,12 @@ def test_fmin_best_results(mocker, caplog):
def test_fmin_throw_value_error(mocker, caplog):
mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker))
mocker.patch('freqtrade.optimize.preprocess')
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
mocker.patch('freqtrade.optimize.load_data')
mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError())
args = mocker.Mock(epochs=1, config='config.json.example')
args = mocker.Mock(epochs=1, config='config.json.example',
timerange=None)
start(args)
exists = [
@@ -185,7 +190,8 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker):
return_value={})
args = mocker.Mock(epochs=1,
config='config.json.example',
mongodb=False)
mongodb=False,
timerange=None)
start(args)

View File

@@ -129,22 +129,25 @@ def test_download_pairs(default_conf, ticker_history, mocker):
_backup_file(file1_5)
_backup_file(file2_1)
_backup_file(file2_5)
assert os.path.isfile(file1_1) is False
assert os.path.isfile(file2_1) is False
assert download_pairs(None, pairs=['BTC-MEME', 'BTC-CFI'], ticker_interval=1) is True
assert os.path.isfile(file1_1) is True
assert os.path.isfile(file1_5) is False
assert os.path.isfile(file2_1) is True
assert os.path.isfile(file2_5) is False
# clean files freshly downloaded
_clean_test_file(file1_1)
_clean_test_file(file2_1)
assert os.path.isfile(file1_5) is False
assert os.path.isfile(file2_5) is False
assert download_pairs(None, pairs=['BTC-MEME', 'BTC-CFI'], ticker_interval=5) is True
assert os.path.isfile(file1_1) is False
assert os.path.isfile(file1_5) is True
assert os.path.isfile(file2_1) is False
assert os.path.isfile(file2_5) is True
# clean files freshly downloaded
@@ -199,3 +202,11 @@ def test_load_tickerdata_file():
assert not load_tickerdata_file(None, 'BTC_UNITEST', 7)
tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 1)
assert _btc_unittest_length == len(tickerdata)
def test_tickerdata_to_dataframe():
timerange = ((None, 'line'), None, -100)
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
tickerlist = {'BTC_UNITEST': tick}
data = optimize.tickerdata_to_dataframe(tickerlist)
assert 100 == len(data['BTC_UNITEST'])

View File

@@ -77,7 +77,7 @@ def test_authorized_only_exception(default_conf, mocker):
def test_status_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
@@ -112,7 +112,7 @@ def test_status_handle(default_conf, update, ticker, mocker):
def test_status_table_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple(
@@ -154,7 +154,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker):
def test_profit_handle(
default_conf, update, ticker, ticker_sell_up, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
@@ -210,7 +210,7 @@ def test_profit_handle(
def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf,
@@ -247,7 +247,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf,
@@ -308,7 +308,7 @@ def test_exec_forcesell_open_orders(default_conf, ticker, mocker):
def test_forcesell_all_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf,
@@ -339,7 +339,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker):
def test_forcesell_handle_invalid(default_conf, update, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True))
msg_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf,
@@ -376,7 +376,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker):
def test_performance_handle(
default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
@@ -410,7 +410,7 @@ def test_performance_handle(
def test_daily_handle(
default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
@@ -448,6 +448,28 @@ def test_daily_handle(
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 1 trade') in msg_mock.call_args_list[0][0][0]
assert str(' 0 trade') in msg_mock.call_args_list[0][0][0]
# Reset msg_mock
msg_mock.reset_mock()
# Add two other trades
create_trade(0.001, int(default_conf['ticker_interval']))
create_trade(0.001, int(default_conf['ticker_interval']))
trades = Trade.query.all()
for trade in trades:
trade.update(limit_buy_order)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
update.message.text = '/daily 1'
_daily(bot=MagicMock(), update=update)
assert str(' 0.00018651 BTC') in msg_mock.call_args_list[0][0][0]
assert str(' 2.798 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 3 trades') in msg_mock.call_args_list[0][0][0]
# Try invalid data
msg_mock.reset_mock()
@@ -460,7 +482,7 @@ def test_daily_handle(
def test_count_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram',
@@ -492,7 +514,7 @@ def test_count_handle(default_conf, update, ticker, mocker):
def test_performance_handle_invalid(default_conf, update, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True))
msg_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf,

View File

@@ -6,7 +6,7 @@ import arrow
import pytest
from pandas import DataFrame
from freqtrade.analyze import (SignalType, get_signal, parse_ticker_dataframe,
from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
populate_buy_trend, populate_indicators,
populate_sell_trend)
@@ -40,30 +40,30 @@ def test_returns_latest_buy_signal(mocker):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
mocker.patch(
'freqtrade.analyze.analyze_ticker',
return_value=DataFrame([{'buy': 1, 'date': arrow.utcnow()}])
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
)
assert get_signal('BTC-ETH', SignalType.BUY, 5)
assert get_signal('BTC-ETH', 5) == (True, False)
mocker.patch(
'freqtrade.analyze.analyze_ticker',
return_value=DataFrame([{'buy': 0, 'date': arrow.utcnow()}])
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
)
assert not get_signal('BTC-ETH', SignalType.BUY, 5)
assert get_signal('BTC-ETH',5) == (False, True)
def test_returns_latest_sell_signal(mocker):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
mocker.patch(
'freqtrade.analyze.analyze_ticker',
return_value=DataFrame([{'sell': 1, 'date': arrow.utcnow()}])
return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}])
)
assert get_signal('BTC-ETH', SignalType.SELL, 5)
assert get_signal('BTC-ETH', 5) == (False, True)
mocker.patch(
'freqtrade.analyze.analyze_ticker',
return_value=DataFrame([{'sell': 0, 'date': arrow.utcnow()}])
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
)
assert not get_signal('BTC-ETH', SignalType.SELL, 5)
assert get_signal('BTC-ETH', 5) == (True, False)
def test_get_signal_handles_exceptions(mocker):
@@ -71,4 +71,4 @@ def test_get_signal_handles_exceptions(mocker):
mocker.patch('freqtrade.analyze.analyze_ticker',
side_effect=Exception('invalid ticker history '))
assert not get_signal('BTC-ETH', SignalType.BUY, 5)
assert get_signal('BTC-ETH', 5) == (False, False)

View File

@@ -10,7 +10,6 @@ from sqlalchemy import create_engine
import freqtrade.main as main
from freqtrade import DependencyException, OperationalException
from freqtrade.analyze import SignalType
from freqtrade.exchange import Exchanges
from freqtrade.main import (_process, check_handle_timedout, create_trade,
execute_sell, get_target_bid, handle_trade, init)
@@ -52,7 +51,7 @@ def test_main_start_hyperopt(mocker):
def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
@@ -82,7 +81,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, m
def test_process_exchange_failures(default_conf, ticker, health, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -99,7 +98,7 @@ def test_process_operational_exception(default_conf, ticker, health, mocker):
msg_mock = MagicMock()
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=msg_mock)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
@@ -117,8 +116,7 @@ def test_process_operational_exception(default_conf, ticker, health, mocker):
def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.main.get_signal',
side_effect=lambda *args: False if args[1] == SignalType.SELL else True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
@@ -140,7 +138,7 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, m
def test_create_trade(default_conf, ticker, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -171,7 +169,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker):
def test_create_trade_minimal_amount(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
buy_mock = mocker.patch(
'freqtrade.main.exchange.buy', MagicMock(return_value='mocked_limit_buy')
)
@@ -187,7 +185,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, mocker):
def test_create_trade_no_stake_amount(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -200,7 +198,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, mocker):
def test_create_trade_no_pairs(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -216,7 +214,7 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker):
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -233,7 +231,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -256,6 +254,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
trade.update(limit_buy_order)
assert trade.is_open is True
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade, int(default_conf['ticker_interval'])) is True
assert trade.open_order_id == 'mocked_limit_sell'
@@ -268,11 +267,57 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
assert trade.close_date is not None
def test_handle_overlpapping_signals(default_conf, ticker, mocker, caplog):
default_conf.update({'experimental': {'use_sell_signal': True}})
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_limit_buy'))
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
init(default_conf, create_engine('sqlite://'))
create_trade(0.001, int(default_conf['ticker_interval']))
# Buy and Sell triggering, so doing nothing ...
trades = Trade.query.all()
assert len(trades) == 0
# Buy is triggering, so buying ...
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
create_trade(0.001, int(default_conf['ticker_interval']))
trades = Trade.query.all()
assert len(trades) == 1
assert trades[0].is_open is True
# Buy and Sell are not triggering, so doing nothing ...
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, False))
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False
trades = Trade.query.all()
assert len(trades) == 1
assert trades[0].is_open is True
# Buy and Sell are triggering, so doing nothing ...
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True))
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False
trades = Trade.query.all()
assert len(trades) == 1
assert trades[0].is_open is True
# Sell is triggering, guess what : we are Selling!
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
trades = Trade.query.all()
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is True
def test_handle_trade_roi(default_conf, ticker, mocker, caplog):
default_conf.update({'experimental': {'use_sell_signal': True}})
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -291,13 +336,12 @@ def test_handle_trade_roi(default_conf, ticker, mocker, caplog):
# we might just want to check if we are in a sell condition without
# executing
# if ROI is reached we must sell
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade, interval=int(default_conf['ticker_interval']))
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
# if ROI is reached we must sell even if sell-signal is not signalled
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade, interval=int(default_conf['ticker_interval']))
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
@@ -305,7 +349,7 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog):
default_conf.update({'experimental': {'use_sell_signal': True}})
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -319,11 +363,10 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog):
trade = Trade.query.first()
trade.is_open = True
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, False))
value_returned = handle_trade(trade, int(default_conf['ticker_interval']))
assert ('freqtrade', logging.DEBUG, 'Checking sell_signal ...') in caplog.record_tuples
assert value_returned is False
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade, int(default_conf['ticker_interval']))
s = 'Executing sell due to sell signal ...'
assert ('freqtrade', logging.DEBUG, s) in caplog.record_tuples
@@ -331,7 +374,7 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog):
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -356,7 +399,8 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
cancel_order_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
@@ -381,6 +425,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo
# check it does cancel buy orders over the time limit
check_handle_timedout(600)
assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
assert len(trades) == 0
@@ -388,7 +433,8 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo
def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
cancel_order_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
@@ -414,6 +460,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
# check it does cancel sell orders over the time limit
check_handle_timedout(600)
assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1
assert trade_sell.is_open is True
@@ -421,7 +468,8 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
cancel_order_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
@@ -447,6 +495,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
# note this is for a partially-complete buy order
check_handle_timedout(600)
assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
assert len(trades) == 1
assert trades[0].amount == 23.0
@@ -470,7 +519,7 @@ def test_balance_bigger_last_ask(mocker):
def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
@@ -503,7 +552,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker):
def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
@@ -540,7 +589,7 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker):
def test_execute_sell_without_conf_sell_down(default_conf, ticker, ticker_sell_down, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s,: (True, False))
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
@@ -572,7 +621,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, ticker_sell_d
def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
@@ -609,7 +658,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -625,6 +674,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker):
trade = Trade.query.first()
trade.update(limit_buy_order)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade, int(default_conf['ticker_interval'])) is True
@@ -636,7 +686,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -652,6 +702,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
trade = Trade.query.first()
trade.update(limit_buy_order)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade, int(default_conf['ticker_interval'])) is True
@@ -663,7 +714,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -679,6 +730,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
trade = Trade.query.first()
trade.update(limit_buy_order)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade, int(default_conf['ticker_interval'])) is False
@@ -690,7 +742,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t, i: True)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
@@ -706,4 +758,5 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker):
trade = Trade.query.first()
trade.update(limit_buy_order)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade, int(default_conf['ticker_interval'])) is True

View File

@@ -5,10 +5,11 @@ import time
from copy import deepcopy
import pytest
from unittest.mock import MagicMock
from jsonschema import ValidationError
from freqtrade.misc import (common_args_parser, load_config, parse_args,
throttle)
throttle, file_dump_json, parse_timerange)
def test_throttle():
@@ -133,6 +134,21 @@ def test_parse_args_hyperopt_custom(mocker):
assert call_args.func is not None
def test_file_dump_json(default_conf, mocker):
file_open = mocker.patch('freqtrade.misc.open', MagicMock())
json_dump = mocker.patch('json.dump', MagicMock())
file_dump_json('somefile', [1, 2, 3])
assert file_open.call_count == 1
assert json_dump.call_count == 1
def test_parse_timerange_incorrect():
assert ((None, 'line'), None, -200) == parse_timerange('-200')
assert (('line', None), 200, None) == parse_timerange('200-')
with pytest.raises(Exception, match=r'Incorrect syntax.*'):
parse_timerange('-')
def test_load_config(default_conf, mocker):
file_mock = mocker.patch('freqtrade.misc.open', mocker.mock_open(
read_data=json.dumps(default_conf)