Merge branch 'develop' into rpc-refactor

This commit is contained in:
kryofly 2018-01-28 15:38:18 +01:00
commit aa22db9fd9
28 changed files with 448 additions and 352 deletions

View File

@ -5,18 +5,44 @@ This page explains how to plot prices, indicator, profits.
- [Plot price and indicators](#plot-price-and-indicators) - [Plot price and indicators](#plot-price-and-indicators)
- [Plot profit](#plot-profit) - [Plot profit](#plot-profit)
## Installation
Plotting scripts use Plotly library. Install/upgrade it with:
```
pip install --upgrade plotly
```
At least version 2.3.0 is required.
## Plot price and indicators ## Plot price and indicators
Usage for the price plotter: Usage for the price plotter:
script/plot_dataframe.py [-h] [-p pair]
```
script/plot_dataframe.py [-h] [-p pair] [--live]
```
Example Example
``` ```
python script/plot_dataframe.py -p BTC_ETH,BTC_LTC python script/plot_dataframe.py -p BTC_ETH
``` ```
The -p pair argument, can be used to specify what The `-p` pair argument, can be used to specify what
pair you would like to plot. pair you would like to plot.
**Advanced use**
To plot the current live price use the `--live` flag:
```
python scripts/plot_dataframe.py -p BTC_ETH --live
```
To plot a timerange (to zoom in):
```
python scripts/plot_dataframe.py -p BTC_ETH --timerange=100-200
```
Timerange doesn't work with live data.
## Plot profit ## Plot profit
@ -38,9 +64,12 @@ The third graph can be useful to spot outliers, events in pairs
that makes profit spikes. that makes profit spikes.
Usage for the profit plotter: Usage for the profit plotter:
script/plot_profit.py [-h] [-p pair] [--datadir directory] [--ticker_interval num]
The -p pair argument, can be used to plot a single pair ```
script/plot_profit.py [-h] [-p pair] [--datadir directory] [--ticker_interval num]
```
The `-p` pair argument, can be used to plot a single pair
Example Example
``` ```

View File

@ -1,5 +1,4 @@
import logging import logging
import requests
from typing import Dict, List, Optional from typing import Dict, List, Optional
from bittrex.bittrex import Bittrex as _Bittrex from bittrex.bittrex import Bittrex as _Bittrex
@ -15,20 +14,6 @@ _API: _Bittrex = None
_API_V2: _Bittrex = None _API_V2: _Bittrex = None
_EXCHANGE_CONF: dict = {} _EXCHANGE_CONF: dict = {}
# API socket timeout
API_TIMEOUT = 60
def custom_requests(request_url, apisign):
"""
Set timeout for requests
"""
return requests.get(
request_url,
headers={"apisign": apisign},
timeout=API_TIMEOUT
).json()
class Bittrex(Exchange): class Bittrex(Exchange):
""" """
@ -47,14 +32,12 @@ class Bittrex(Exchange):
api_secret=_EXCHANGE_CONF['secret'], api_secret=_EXCHANGE_CONF['secret'],
calls_per_second=1, calls_per_second=1,
api_version=API_V1_1, api_version=API_V1_1,
dispatch=custom_requests
) )
_API_V2 = _Bittrex( _API_V2 = _Bittrex(
api_key=_EXCHANGE_CONF['key'], api_key=_EXCHANGE_CONF['key'],
api_secret=_EXCHANGE_CONF['secret'], api_secret=_EXCHANGE_CONF['secret'],
calls_per_second=1, calls_per_second=1,
api_version=API_V2_0, api_version=API_V2_0,
dispatch=custom_requests
) )
self.cached_ticker = {} self.cached_ticker = {}

View File

@ -1,12 +1,19 @@
"""
Module that define classes to convert Crypto-currency to FIAT
e.g BTC to USD
"""
import logging import logging
import time import time
from pymarketcap import Pymarketcap from pymarketcap import Pymarketcap
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class CryptoFiat(): class CryptoFiat():
"""
Object to describe what is the price of Crypto-currency in a FIAT
"""
# Constants # Constants
CACHE_DURATION = 6 * 60 * 60 # 6 hours CACHE_DURATION = 6 * 60 * 60 # 6 hours
@ -49,6 +56,11 @@ class CryptoFiat():
class CryptoToFiatConverter(object): class CryptoToFiatConverter(object):
"""
Main class to initiate Crypto to FIAT.
This object contains a list of pair Crypto, FIAT
This object is also a Singleton
"""
__instance = None __instance = None
_coinmarketcap = None _coinmarketcap = None

View File

@ -6,7 +6,7 @@ import sys
import time import time
import traceback import traceback
from datetime import datetime from datetime import datetime
from typing import Dict, List, Optional from typing import Dict, List, Optional, Any
import arrow import arrow
import requests import requests
@ -23,7 +23,7 @@ from freqtrade.strategy.strategy import Strategy
logger = logging.getLogger('freqtrade') logger = logging.getLogger('freqtrade')
_CONF = {} _CONF: Dict[str, Any] = {}
def refresh_whitelist(whitelist: List[str]) -> List[str]: def refresh_whitelist(whitelist: List[str]) -> List[str]:
@ -55,7 +55,7 @@ def refresh_whitelist(whitelist: List[str]) -> List[str]:
return final_list return final_list
def process_maybe_execute_buy(conf, interval): def process_maybe_execute_buy(interval):
""" """
Tries to execute a buy trade in a safe way Tries to execute a buy trade in a safe way
:return: True if executed :return: True if executed
@ -64,7 +64,7 @@ def process_maybe_execute_buy(conf, interval):
# Create entity and execute trade # Create entity and execute trade
if create_trade(float(_CONF['stake_amount']), interval): if create_trade(float(_CONF['stake_amount']), interval):
return True return True
else:
logger.info( logger.info(
'Checked all whitelisted currencies. ' 'Checked all whitelisted currencies. '
'Found no suitable entry positions for buying. Will keep looking ...' 'Found no suitable entry positions for buying. Will keep looking ...'
@ -115,7 +115,7 @@ def _process(interval: int, nb_assets: Optional[int] = 0) -> bool:
# Query trades from persistence layer # Query trades from persistence layer
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.query.filter(Trade.is_open.is_(True)).all()
if len(trades) < _CONF['max_open_trades']: if len(trades) < _CONF['max_open_trades']:
state_changed = process_maybe_execute_buy(_CONF, interval) state_changed = process_maybe_execute_buy(interval)
for trade in trades: for trade in trades:
state_changed |= process_maybe_execute_sell(trade, interval) state_changed |= process_maybe_execute_sell(trade, interval)
@ -159,7 +159,7 @@ def handle_timedout_limit_buy(trade: Trade, order: Dict) -> bool:
rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format( rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format(
trade.pair.replace('_', '/'))) trade.pair.replace('_', '/')))
return True return True
else:
# if trade is partially complete, edit the stake details for the trade # if trade is partially complete, edit the stake details for the trade
# and close the order # and close the order
trade.amount = order['amount'] - order['remaining'] trade.amount = order['amount'] - order['remaining']
@ -189,7 +189,7 @@ def handle_timedout_limit_sell(trade: Trade, order: Dict) -> bool:
trade.pair.replace('_', '/'))) trade.pair.replace('_', '/')))
logger.info('Sell order timeout for %s.', trade) logger.info('Sell order timeout for %s.', trade)
return True return True
else:
# TODO: figure out how to handle partially complete sell orders # TODO: figure out how to handle partially complete sell orders
return False return False

View File

@ -5,8 +5,10 @@ import logging
import time import time
import os import os
import re import re
from datetime import datetime
from typing import Any, Callable, Dict, List from typing import Any, Callable, Dict, List
import numpy as np
from jsonschema import Draft4Validator, validate from jsonschema import Draft4Validator, validate
from jsonschema.exceptions import ValidationError, best_match from jsonschema.exceptions import ValidationError, best_match
from wrapt import synchronized from wrapt import synchronized
@ -16,11 +18,6 @@ from freqtrade import __version__
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def file_dump_json(filename, data):
with open(filename, 'w') as fp:
json.dump(data, fp)
class State(enum.Enum): class State(enum.Enum):
RUNNING = 0 RUNNING = 0
STOPPED = 1 STOPPED = 1
@ -30,6 +27,44 @@ class State(enum.Enum):
_STATE = State.STOPPED _STATE = State.STOPPED
############################################
# Used by scripts #
# Matplotlib doesn't support ::datetime64, #
# so we need to convert it into ::datetime #
############################################
def datesarray_to_datetimearray(dates):
"""
Convert an pandas-array of timestamps into
An numpy-array of datetimes
:return: numpy-array of datetime
"""
times = []
dates = dates.astype(datetime)
for i in range(0, dates.size):
date = dates[i].to_pydatetime()
times.append(date)
return np.array(times)
def common_datearray(dfs):
alldates = {}
for pair, pair_data in dfs.items():
dates = datesarray_to_datetimearray(pair_data['date'])
for date in dates:
alldates[date] = 1
lst = []
for date, _ in alldates.items():
lst.append(date)
arr = np.array(lst)
return np.sort(arr, axis=0)
def file_dump_json(filename, data):
with open(filename, 'w') as fp:
json.dump(data, fp)
@synchronized @synchronized
def update_state(state: State) -> None: def update_state(state: State) -> None:
""" """
@ -163,6 +198,15 @@ def parse_args(args: List[str], description: str):
return parser.parse_args(args) return parser.parse_args(args)
def scripts_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
'-p', '--pair',
help='Show profits for only this pairs. Pairs are comma-separated.',
dest='pair',
default=None
)
def backtesting_options(parser: argparse.ArgumentParser) -> None: def backtesting_options(parser: argparse.ArgumentParser) -> None:
parser.add_argument( parser.add_argument(
'-l', '--live', '-l', '--live',

View File

@ -22,7 +22,7 @@ def trim_tickerlist(tickerlist, timerange):
return tickerlist[0:start] return tickerlist[0:start]
elif stype == ('index', 'index'): elif stype == ('index', 'index'):
return tickerlist[start:stop] return tickerlist[start:stop]
else:
return tickerlist return tickerlist

View File

@ -26,7 +26,7 @@ def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]
:return: tuple containing min_date, max_date :return: tuple containing min_date, max_date
""" """
all_dates = Series([]) all_dates = Series([])
for pair, pair_data in data.items(): for pair_data in data.values():
all_dates = all_dates.append(pair_data['date']) all_dates = all_dates.append(pair_data['date'])
all_dates.sort_values(inplace=True) all_dates.sort_values(inplace=True)
return arrow.get(all_dates.iloc[0]), arrow.get(all_dates.iloc[-1]) return arrow.get(all_dates.iloc[0]), arrow.get(all_dates.iloc[-1])
@ -212,7 +212,10 @@ def start(args):
preprocessed = optimize.tickerdata_to_dataframe(data) preprocessed = optimize.tickerdata_to_dataframe(data)
# Print timeframe # Print timeframe
min_date, max_date = get_timeframe(preprocessed) min_date, max_date = get_timeframe(preprocessed)
logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat()) logger.info('Measuring data from %s up to %s (%s days)..',
min_date.isoformat(),
max_date.isoformat(),
(max_date-min_date).days)
# Execute backtest and print results # Execute backtest and print results
sell_profit_only = config.get('experimental', {}).get('sell_profit_only', False) sell_profit_only = config.get('experimental', {}).get('sell_profit_only', False)
use_sell_signal = config.get('experimental', {}).get('use_sell_signal', False) use_sell_signal = config.get('experimental', {}).get('use_sell_signal', False)

View File

@ -10,7 +10,7 @@ import sys
from functools import reduce from functools import reduce
from math import exp from math import exp
from operator import itemgetter from operator import itemgetter
from typing import Dict, List from typing import Dict, Any, Callable
import numpy import numpy
import talib.abstract as ta import talib.abstract as ta
@ -35,7 +35,7 @@ logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# set TARGET_TRADES to suit your number concurrent trades so its realistic to 20days of data # set TARGET_TRADES to suit your number concurrent trades so its realistic to the number of days
TARGET_TRADES = 600 TARGET_TRADES = 600
TOTAL_TRIES = 0 TOTAL_TRIES = 0
_CURRENT_TRIES = 0 _CURRENT_TRIES = 0
@ -221,11 +221,11 @@ def calculate_loss(total_profit: float, trade_count: int, trade_duration: float)
""" objective function, returns smaller number for more optimal results """ """ objective function, returns smaller number for more optimal results """
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8) trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT) profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
duration_loss = 0.7 + 0.3 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1) duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
return trade_loss + profit_loss + duration_loss return trade_loss + profit_loss + duration_loss
def generate_roi_table(params): def generate_roi_table(params) -> Dict[str, float]:
roi_table = {} roi_table = {}
roi_table["0"] = params['roi_p1'] + params['roi_p2'] + params['roi_p3'] roi_table["0"] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
roi_table[str(params['roi_t3'])] = params['roi_p1'] + params['roi_p2'] roi_table[str(params['roi_t3'])] = params['roi_p1'] + params['roi_p2']
@ -235,24 +235,24 @@ def generate_roi_table(params):
return roi_table return roi_table
def roi_space() -> List[Dict]: def roi_space() -> Dict[str, Any]:
return { return {
'roi_t1': hp.quniform('roi_t1', 10, 220, 10), 'roi_t1': hp.quniform('roi_t1', 10, 120, 20),
'roi_t2': hp.quniform('roi_t2', 10, 120, 10), 'roi_t2': hp.quniform('roi_t2', 10, 60, 15),
'roi_t3': hp.quniform('roi_t3', 10, 120, 10), 'roi_t3': hp.quniform('roi_t3', 10, 40, 10),
'roi_p1': hp.quniform('roi_p1', 0.01, 0.05, 0.01), 'roi_p1': hp.quniform('roi_p1', 0.01, 0.04, 0.01),
'roi_p2': hp.quniform('roi_p2', 0.01, 0.10, 0.01), 'roi_p2': hp.quniform('roi_p2', 0.01, 0.07, 0.01),
'roi_p3': hp.quniform('roi_p3', 0.01, 0.30, 0.01), 'roi_p3': hp.quniform('roi_p3', 0.01, 0.20, 0.01),
} }
def stoploss_space() -> Dict: def stoploss_space() -> Dict[str, Any]:
return { return {
'stoploss': hp.uniform('stoploss', -0.5, -0.02), 'stoploss': hp.quniform('stoploss', -0.5, -0.02, 0.02),
} }
def indicator_space() -> List[Dict]: def indicator_space() -> Dict[str, Any]:
""" """
Define your Hyperopt space for searching strategy parameters Define your Hyperopt space for searching strategy parameters
""" """
@ -263,19 +263,19 @@ def indicator_space() -> List[Dict]:
]), ]),
'mfi': hp.choice('mfi', [ 'mfi': hp.choice('mfi', [
{'enabled': False}, {'enabled': False},
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)} {'enabled': True, 'value': hp.quniform('mfi-value', 10, 25, 5)}
]), ]),
'fastd': hp.choice('fastd', [ 'fastd': hp.choice('fastd', [
{'enabled': False}, {'enabled': False},
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)} {'enabled': True, 'value': hp.quniform('fastd-value', 15, 45, 5)}
]), ]),
'adx': hp.choice('adx', [ 'adx': hp.choice('adx', [
{'enabled': False}, {'enabled': False},
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)} {'enabled': True, 'value': hp.quniform('adx-value', 20, 50, 5)}
]), ]),
'rsi': hp.choice('rsi', [ 'rsi': hp.choice('rsi', [
{'enabled': False}, {'enabled': False},
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)} {'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 5)}
]), ]),
'uptrend_long_ema': hp.choice('uptrend_long_ema', [ 'uptrend_long_ema': hp.choice('uptrend_long_ema', [
{'enabled': False}, {'enabled': False},
@ -312,11 +312,11 @@ def indicator_space() -> List[Dict]:
} }
def hyperopt_space() -> List[Dict]: def hyperopt_space() -> Dict[str, Any]:
return {**indicator_space(), **roi_space(), **stoploss_space()} return {**indicator_space(), **roi_space(), **stoploss_space()}
def buy_strategy_generator(params) -> None: def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
""" """
Define the buy strategy parameters to be used by hyperopt Define the buy strategy parameters to be used by hyperopt
""" """

View File

@ -1,7 +1,9 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
import talib.abstract as ta import talib.abstract as ta
from pandas import DataFrame
import freqtrade.vendor.qtpylib.indicators as qtpylib import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
class_name = 'DefaultStrategy' class_name = 'DefaultStrategy'

View File

@ -1,8 +1,22 @@
"""
IStrategy interface
This module defines the interface to apply for strategies
"""
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from pandas import DataFrame from pandas import DataFrame
class IStrategy(ABC): class IStrategy(ABC):
"""
Interface for freqtrade strategies
Defines the mandatory structure must follow any custom strategies
Attributes you can use:
minimal_roi -> Dict: Minimal ROI designed for the strategy
stoploss -> float: optimal stoploss designed for the strategy
ticker_interval -> int: value of the ticker interval to use for the strategy
"""
@property @property
def name(self) -> str: def name(self) -> str:
""" """
@ -11,13 +25,6 @@ class IStrategy(ABC):
""" """
return self.__class__.__name__ return self.__class__.__name__
"""
Attributes you can use:
minimal_roi -> Dict: Minimal ROI designed for the strategy
stoploss -> float: optimal stoploss designed for the strategy
ticker_interval -> int: value of the ticker interval to use for the strategy
"""
@abstractmethod @abstractmethod
def populate_indicators(self, dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
""" """

View File

@ -1,10 +1,12 @@
"""
This module load custom strategies
"""
import os import os
import sys import sys
import logging import logging
import importlib import importlib
from pandas import DataFrame from pandas import DataFrame
from typing import Dict
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
@ -12,16 +14,36 @@ sys.path.insert(0, r'../../user_data/strategies')
class Strategy(object): class Strategy(object):
"""
This class contains all the logic to load custom strategy class
"""
__instance = None __instance = None
DEFAULT_STRATEGY = 'default_strategy' DEFAULT_STRATEGY = 'default_strategy'
def __new__(cls): def __new__(cls):
"""
Used to create the Singleton
:return: Strategy object
"""
if Strategy.__instance is None: if Strategy.__instance is None:
Strategy.__instance = object.__new__(cls) Strategy.__instance = object.__new__(cls)
return Strategy.__instance return Strategy.__instance
def __init__(self):
if Strategy.__instance is None:
self.logger = None
self.minimal_roi = None
self.stoploss = None
self.ticker_interval = None
self.custom_strategy = None
def init(self, config): def init(self, config):
"""
Load the custom class from config parameter
:param config:
:return:
"""
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
# Verify the strategy is in the configuration, otherwise fallback to the default strategy # Verify the strategy is in the configuration, otherwise fallback to the default strategy
@ -42,21 +64,22 @@ class Strategy(object):
if 'stoploss' in config: if 'stoploss' in config:
self.custom_strategy.stoploss = config['stoploss'] self.custom_strategy.stoploss = config['stoploss']
self.logger.info( self.logger.info(
"Override strategy \'stoploss\' with value in config file: {}.".format( "Override strategy \'stoploss\' with value in config file: %s.", config['stoploss']
config['stoploss']
)
) )
if 'ticker_interval' in config: if 'ticker_interval' in config:
self.custom_strategy.ticker_interval = config['ticker_interval'] self.custom_strategy.ticker_interval = config['ticker_interval']
self.logger.info( self.logger.info(
"Override strategy \'ticker_interval\' with value in config file: {}.".format( "Override strategy \'ticker_interval\' with value in config file: %s.",
config['ticker_interval'] config['ticker_interval']
) )
)
# Minimal ROI designed for the strategy
self.minimal_roi = self.custom_strategy.minimal_roi self.minimal_roi = self.custom_strategy.minimal_roi
# Optimal stoploss designed for the strategy
self.stoploss = self.custom_strategy.stoploss self.stoploss = self.custom_strategy.stoploss
self.ticker_interval = self.custom_strategy.ticker_interval self.ticker_interval = self.custom_strategy.ticker_interval
def _load_strategy(self, strategy_name: str) -> None: def _load_strategy(self, strategy_name: str) -> None:
@ -90,7 +113,7 @@ class Strategy(object):
module = importlib.import_module(filename, __package__) module = importlib.import_module(filename, __package__)
custom_strategy = getattr(module, module.class_name) custom_strategy = getattr(module, module.class_name)
self.logger.info("Load strategy class: {} ({}.py)".format(module.class_name, filename)) self.logger.info("Load strategy class: %s (%s.py)", module.class_name, filename)
return custom_strategy() return custom_strategy()
@staticmethod @staticmethod
@ -126,20 +149,6 @@ class Strategy(object):
return path return path
def minimal_roi(self) -> Dict:
"""
Minimal ROI designed for the strategy
:return: Dict: Value for the Minimal ROI
"""
return
def stoploss(self) -> float:
"""
Optimal stoploss designed for the strategy
:return: float | return None to disable it
"""
return self.custom_strategy.stoploss
def populate_indicators(self, dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
""" """
Populate indicators that will be used in the Buy and Sell strategy Populate indicators that will be used in the Buy and Sell strategy

View File

@ -3,10 +3,13 @@ from datetime import datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
from functools import reduce from functools import reduce
import json
import arrow import arrow
import pytest import pytest
from jsonschema import validate from jsonschema import validate
from telegram import Chat, Message, Update from telegram import Chat, Message, Update
from freqtrade.analyze import parse_ticker_dataframe
from freqtrade.strategy.strategy import Strategy
from freqtrade.misc import CONF_SCHEMA from freqtrade.misc import CONF_SCHEMA
@ -257,6 +260,19 @@ def ticker_history_without_bv():
} }
] ]
@pytest.fixture
def result():
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
return parse_ticker_dataframe(json.load(data_file))
@pytest.fixture
def default_strategy():
strategy = Strategy()
strategy.init({'strategy': 'default_strategy'})
return strategy
# FIX: # FIX:
# Create an fixture/function # Create an fixture/function
# that inserts a trade of some type and open-status # that inserts a trade of some type and open-status

View File

@ -1,8 +1,9 @@
# pragma pylint: disable=missing-docstring,C0103 # pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
from unittest.mock import MagicMock from unittest.mock import MagicMock
from requests.exceptions import RequestException
from random import randint from random import randint
import logging import logging
from requests.exceptions import RequestException
import pytest import pytest
from freqtrade import OperationalException from freqtrade import OperationalException
@ -30,7 +31,7 @@ def test_init(default_conf, mocker, caplog):
) in caplog.record_tuples ) in caplog.record_tuples
def test_init_exception(default_conf, mocker): def test_init_exception(default_conf):
default_conf['exchange']['name'] = 'wrong_exchange_name' default_conf['exchange']['name'] = 'wrong_exchange_name'
with pytest.raises( with pytest.raises(
@ -171,7 +172,7 @@ def test_get_balances_prod(default_conf, mocker):
# This test is somewhat redundant with # This test is somewhat redundant with
# test_exchange_bittrex.py::test_exchange_bittrex_get_ticker # test_exchange_bittrex.py::test_exchange_bittrex_get_ticker
def test_get_ticker(default_conf, mocker, ticker): def test_get_ticker(default_conf, mocker):
maybe_init_api(default_conf, mocker) maybe_init_api(default_conf, mocker)
api_mock = MagicMock() api_mock = MagicMock()
tick = {"success": True, 'result': {'Bid': 0.00001098, 'Ask': 0.00001099, 'Last': 0.0001}} tick = {"success": True, 'result': {'Bid': 0.00001098, 'Ask': 0.00001099, 'Last': 0.0001}}
@ -200,7 +201,7 @@ def test_get_ticker(default_conf, mocker, ticker):
assert ticker['ask'] == 1 assert ticker['ask'] == 1
def test_get_ticker_history(default_conf, mocker, ticker): def test_get_ticker_history(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
tick = 123 tick = 123
api_mock.get_ticker_history = MagicMock(return_value=tick) api_mock.get_ticker_history = MagicMock(return_value=tick)
@ -251,7 +252,7 @@ def test_get_order(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.get_order = MagicMock(return_value=456) api_mock.get_order = MagicMock(return_value=456)
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
assert 456 == exchange.get_order('X') assert exchange.get_order('X') == 456
def test_get_name(default_conf, mocker): def test_get_name(default_conf, mocker):
@ -271,16 +272,16 @@ def test_get_fee(default_conf, mocker):
assert get_fee() == 0.0025 assert get_fee() == 0.0025
def test_exchange_misc(default_conf, mocker): def test_exchange_misc(mocker):
api_mock = MagicMock() api_mock = MagicMock()
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
exchange.get_markets() exchange.get_markets()
assert 1 == api_mock.get_markets.call_count assert api_mock.get_markets.call_count == 1
exchange.get_market_summaries() exchange.get_market_summaries()
assert 1 == api_mock.get_market_summaries.call_count assert api_mock.get_market_summaries.call_count == 1
api_mock.name = 123 api_mock.name = 123
assert 123 == exchange.get_name() assert exchange.get_name() == 123
api_mock.fee = 456 api_mock.fee = 456
assert 456 == exchange.get_fee() assert exchange.get_fee() == 456
exchange.get_wallet_health() exchange.get_wallet_health()
assert 1 == api_mock.get_wallet_health.call_count assert api_mock.get_wallet_health.call_count == 1

View File

@ -1,9 +1,8 @@
# pragma pylint: disable=missing-docstring,C0103 # pragma pylint: disable=missing-docstring, C0103, protected-access, unused-argument
import pytest
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest
from requests.exceptions import ContentDecodingError from requests.exceptions import ContentDecodingError
from freqtrade.exchange.bittrex import Bittrex from freqtrade.exchange.bittrex import Bittrex
import freqtrade.exchange.bittrex as btx import freqtrade.exchange.bittrex as btx
@ -88,8 +87,7 @@ class FakeBittrex():
'PricePerUnit': 1, 'PricePerUnit': 1,
'Quantity': 1, 'Quantity': 1,
'QuantityRemaining': 1, 'QuantityRemaining': 1,
'Closed': True 'Closed': True},
},
'message': 'lost'} 'message': 'lost'}
def fake_cancel_order(self, uuid): def fake_cancel_order(self, uuid):
@ -211,24 +209,18 @@ def test_exchange_bittrex_get_ticker():
def test_exchange_bittrex_get_ticker_bad(): def test_exchange_bittrex_get_ticker_bad():
wb = make_wrap_bittrex() wb = make_wrap_bittrex()
fb = FakeBittrex() fb = FakeBittrex()
fb.result = {'success': True, fb.result = {'success': True, 'result': {'Bid': 1, 'Ask': 0}} # incomplete result
'result': {'Bid': 1, 'Ask': 0}} # incomplete result
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'): with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
wb.get_ticker('BTC_ETH') wb.get_ticker('BTC_ETH')
fb.result = {'success': False, fb.result = {'success': False, 'message': 'gone bad'}
'message': 'gone bad'
}
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'): with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
wb.get_ticker('BTC_ETH') wb.get_ticker('BTC_ETH')
fb.result = {'success': True, fb.result = {'success': True, 'result': {}} # incomplete result
'result': {}} # incomplete result
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'): with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
wb.get_ticker('BTC_ETH') wb.get_ticker('BTC_ETH')
fb.result = {'success': False, fb.result = {'success': False, 'message': 'gone bad'}
'message': 'gone bad'
}
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'): with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
wb.get_ticker('BTC_ETH') wb.get_ticker('BTC_ETH')
@ -353,8 +345,3 @@ def test_validate_response_min_trade_requirement_not_met():
} }
with pytest.raises(ContentDecodingError, match=r'.*MIN_TRADE_REQUIREMENT_NOT_MET.*'): with pytest.raises(ContentDecodingError, match=r'.*MIN_TRADE_REQUIREMENT_NOT_MET.*'):
Bittrex._validate_response(response) Bittrex._validate_response(response)
def test_custom_requests(mocker):
mocker.patch('freqtrade.exchange.bittrex.requests', MagicMock())
btx.custom_requests('http://', '')

View File

@ -1,28 +1,19 @@
# pragma pylint: disable=missing-docstring,W0212 # pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103
import logging import logging
import math import math
import pandas as pd
import pytest
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pandas as pd
from freqtrade import exchange, optimize from freqtrade import exchange, optimize
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade.optimize import preprocess from freqtrade.optimize import preprocess
from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe
import freqtrade.optimize.backtesting as backtesting import freqtrade.optimize.backtesting as backtesting
from freqtrade.strategy.strategy import Strategy
@pytest.fixture def trim_dictlist(dict_list, num):
def default_strategy():
strategy = Strategy()
strategy.init({'strategy': 'default_strategy'})
return strategy
def trim_dictlist(dl, num):
new = {} new = {}
for pair, pair_data in dl.items(): for pair, pair_data in dict_list.items():
new[pair] = pair_data[num:] new[pair] = pair_data[num:]
return new return new
@ -193,7 +184,8 @@ def test_backtest_start(default_conf, mocker, caplog):
# check the logs, that will contain the backtest result # check the logs, that will contain the backtest result
exists = ['Using max_open_trades: 1 ...', exists = ['Using max_open_trades: 1 ...',
'Using stake_amount: 0.001 ...', 'Using stake_amount: 0.001 ...',
'Measuring data from 2017-11-14T21:17:00+00:00 up to 2017-11-14T22:59:00+00:00 ...'] 'Measuring data from 2017-11-14T21:17:00+00:00 '
'up to 2017-11-14T22:59:00+00:00 (0 days)..']
for line in exists: for line in exists:
assert ('freqtrade.optimize.backtesting', assert ('freqtrade.optimize.backtesting',
logging.INFO, logging.INFO,

View File

@ -1,8 +1,7 @@
# pragma pylint: disable=missing-docstring,W0212 # pragma pylint: disable=missing-docstring, protected-access, C0103
import os import os
import logging import logging
# from unittest.mock import MagicMock
from shutil import copyfile from shutil import copyfile
from freqtrade import exchange, optimize from freqtrade import exchange, optimize
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
@ -10,7 +9,7 @@ from freqtrade.optimize.__init__ import make_testdata_path, download_pairs,\
download_backtesting_testdata, load_tickerdata_file download_backtesting_testdata, load_tickerdata_file
# Change this if modifying BTC_UNITEST testdatafile # Change this if modifying BTC_UNITEST testdatafile
_btc_unittest_length = 13681 _BTC_UNITTEST_LENGTH = 13681
def _backup_file(file: str, copy_file: bool = False) -> None: def _backup_file(file: str, copy_file: bool = False) -> None:
@ -56,8 +55,7 @@ def test_load_data_30min_ticker(default_conf, ticker_history, mocker, caplog):
assert os.path.isfile(file) is True assert os.path.isfile(file) is True
assert ('freqtrade.optimize', assert ('freqtrade.optimize',
logging.INFO, logging.INFO,
'Download the pair: "BTC_ETH", Interval: 30 min' 'Download the pair: "BTC_ETH", Interval: 30 min') not in caplog.record_tuples
) not in caplog.record_tuples
_clean_test_file(file) _clean_test_file(file)
@ -73,8 +71,7 @@ def test_load_data_5min_ticker(default_conf, ticker_history, mocker, caplog):
assert os.path.isfile(file) is True assert os.path.isfile(file) is True
assert ('freqtrade.optimize', assert ('freqtrade.optimize',
logging.INFO, logging.INFO,
'Download the pair: "BTC_ETH", Interval: 5 min' 'Download the pair: "BTC_ETH", Interval: 5 min') not in caplog.record_tuples
) not in caplog.record_tuples
_clean_test_file(file) _clean_test_file(file)
@ -90,8 +87,7 @@ def test_load_data_1min_ticker(default_conf, ticker_history, mocker, caplog):
assert os.path.isfile(file) is True assert os.path.isfile(file) is True
assert ('freqtrade.optimize', assert ('freqtrade.optimize',
logging.INFO, logging.INFO,
'Download the pair: "BTC_ETH", Interval: 1 min' 'Download the pair: "BTC_ETH", Interval: 1 min') not in caplog.record_tuples
) not in caplog.record_tuples
_clean_test_file(file) _clean_test_file(file)
@ -107,8 +103,7 @@ def test_load_data_with_new_pair_1min(default_conf, ticker_history, mocker, capl
assert os.path.isfile(file) is True assert os.path.isfile(file) is True
assert ('freqtrade.optimize', assert ('freqtrade.optimize',
logging.INFO, logging.INFO,
'Download the pair: "BTC_MEME", Interval: 1 min' 'Download the pair: "BTC_MEME", Interval: 1 min') in caplog.record_tuples
) in caplog.record_tuples
_clean_test_file(file) _clean_test_file(file)
@ -174,8 +169,7 @@ def test_download_pairs_exception(default_conf, ticker_history, mocker, caplog):
_clean_test_file(file1_5) _clean_test_file(file1_5)
assert ('freqtrade.optimize.__init__', assert ('freqtrade.optimize.__init__',
logging.INFO, logging.INFO,
'Failed to download the pair: "BTC-MEME", Interval: 1 min' 'Failed to download the pair: "BTC-MEME", Interval: 1 min') in caplog.record_tuples
) in caplog.record_tuples
def test_download_backtesting_testdata(default_conf, ticker_history, mocker): def test_download_backtesting_testdata(default_conf, ticker_history, mocker):
@ -199,7 +193,7 @@ def test_download_backtesting_testdata(default_conf, ticker_history, mocker):
_clean_test_file(file2) _clean_test_file(file2)
def test_download_backtesting_testdata2(default_conf, mocker): def test_download_backtesting_testdata2(mocker):
tick = [{'T': 'bar'}, {'T': 'foo'}] tick = [{'T': 'bar'}, {'T': 'foo'}]
mocker.patch('freqtrade.misc.file_dump_json', return_value=None) mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick) mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick)
@ -210,7 +204,7 @@ def test_download_backtesting_testdata2(default_conf, mocker):
def test_load_tickerdata_file(): def test_load_tickerdata_file():
assert not load_tickerdata_file(None, 'BTC_UNITEST', 7) assert not load_tickerdata_file(None, 'BTC_UNITEST', 7)
tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 1) tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 1)
assert _btc_unittest_length == len(tickerdata) assert _BTC_UNITTEST_LENGTH == len(tickerdata)
def test_init(default_conf, mocker): def test_init(default_conf, mocker):
@ -225,4 +219,4 @@ def test_tickerdata_to_dataframe():
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange) tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
tickerlist = {'BTC_UNITEST': tick} tickerlist = {'BTC_UNITEST': tick}
data = optimize.tickerdata_to_dataframe(tickerlist) data = optimize.tickerdata_to_dataframe(tickerlist)
assert 100 == len(data['BTC_UNITEST']) assert len(data['BTC_UNITEST']) == 100

View File

@ -1,4 +1,5 @@
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103 # pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103
# pragma pylint: disable=unused-argument
import re import re
from datetime import datetime from datetime import datetime
from random import randint from random import randint

View File

@ -1,14 +1,7 @@
import json # pragma pylint: disable=missing-docstring, protected-access, C0103
import logging import logging
import pytest
from freqtrade.strategy.strategy import Strategy from freqtrade.strategy.strategy import Strategy
from freqtrade.analyze import parse_ticker_dataframe
@pytest.fixture
def result():
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
return parse_ticker_dataframe(json.load(data_file))
def test_sanitize_module_name(): def test_sanitize_module_name():
@ -28,8 +21,6 @@ def test_search_strategy():
def test_strategy_structure(): def test_strategy_structure():
assert hasattr(Strategy, 'init') assert hasattr(Strategy, 'init')
assert hasattr(Strategy, 'minimal_roi')
assert hasattr(Strategy, 'stoploss')
assert hasattr(Strategy, 'populate_indicators') assert hasattr(Strategy, 'populate_indicators')
assert hasattr(Strategy, 'populate_buy_trend') assert hasattr(Strategy, 'populate_buy_trend')
assert hasattr(Strategy, 'populate_sell_trend') assert hasattr(Strategy, 'populate_sell_trend')

View File

@ -1,3 +1,5 @@
# pragma pylint: disable=missing-docstring,C0103
from freqtrade.main import refresh_whitelist, gen_pair_whitelist from freqtrade.main import refresh_whitelist, gen_pair_whitelist
# whitelist, blacklist, filtering, all of that will # whitelist, blacklist, filtering, all of that will
@ -73,16 +75,9 @@ def get_market_summaries():
def get_health(): def get_health():
return [{'Currency': 'ETH', return [{'Currency': 'ETH', 'IsActive': True},
'IsActive': True {'Currency': 'TKN', 'IsActive': True},
}, {'Currency': 'BLK', 'IsActive': True}]
{'Currency': 'TKN',
'IsActive': True
},
{'Currency': 'BLK',
'IsActive': True
}
]
def get_health_empty(): def get_health_empty():

View File

@ -1,25 +1,17 @@
# pragma pylint: disable=missing-docstring,W0621 # pragma pylint: disable=missing-docstring, C0103
import json import datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
import freqtrade.tests.conftest as tt # test tools
import arrow import arrow
import datetime
import pytest
from pandas import DataFrame from pandas import DataFrame
import freqtrade.tests.conftest as tt # test tools
from freqtrade.analyze import (get_signal, parse_ticker_dataframe, from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
populate_buy_trend, populate_indicators, populate_buy_trend, populate_indicators,
populate_sell_trend) populate_sell_trend)
from freqtrade.strategy.strategy import Strategy from freqtrade.strategy.strategy import Strategy
@pytest.fixture
def result():
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
return parse_ticker_dataframe(json.load(data_file))
def test_dataframe_correct_columns(result): def test_dataframe_correct_columns(result):
assert result.columns.tolist() == \ assert result.columns.tolist() == \
['close', 'high', 'low', 'open', 'date', 'volume'] ['close', 'high', 'low', 'open', 'date', 'volume']

View File

@ -1,5 +1,6 @@
import pandas # pragma pylint: disable=missing-docstring, C0103
import pandas
import freqtrade.optimize import freqtrade.optimize
from freqtrade import analyze from freqtrade import analyze

View File

@ -1,4 +1,5 @@
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103 # pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors,
# pragma pylint: disable=protected-access, C0103
import time import time
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -47,16 +48,19 @@ def test_fiat_convert_is_supported():
def test_fiat_convert_add_pair(): def test_fiat_convert_add_pair():
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
assert len(fiat_convert._pairs) == 0 pair_len = len(fiat_convert._pairs)
assert pair_len == 0
fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='usd', price=12345.0) fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='usd', price=12345.0)
assert len(fiat_convert._pairs) == 1 pair_len = len(fiat_convert._pairs)
assert pair_len == 1
assert fiat_convert._pairs[0].crypto_symbol == 'BTC' assert fiat_convert._pairs[0].crypto_symbol == 'BTC'
assert fiat_convert._pairs[0].fiat_symbol == 'USD' assert fiat_convert._pairs[0].fiat_symbol == 'USD'
assert fiat_convert._pairs[0].price == 12345.0 assert fiat_convert._pairs[0].price == 12345.0
fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='Eur', price=13000.2) fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='Eur', price=13000.2)
assert len(fiat_convert._pairs) == 2 pair_len = len(fiat_convert._pairs)
assert pair_len == 2
assert fiat_convert._pairs[1].crypto_symbol == 'BTC' assert fiat_convert._pairs[1].crypto_symbol == 'BTC'
assert fiat_convert._pairs[1].fiat_symbol == 'EUR' assert fiat_convert._pairs[1].fiat_symbol == 'EUR'
assert fiat_convert._pairs[1].price == 13000.2 assert fiat_convert._pairs[1].price == 13000.2
@ -95,7 +99,8 @@ def test_fiat_convert_get_price(mocker):
fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='US Dollar') fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='US Dollar')
# Check the value return by the method # Check the value return by the method
assert len(fiat_convert._pairs) == 0 pair_len = len(fiat_convert._pairs)
assert pair_len == 0
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0 assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0
assert fiat_convert._pairs[0].crypto_symbol == 'BTC' assert fiat_convert._pairs[0].crypto_symbol == 'BTC'
assert fiat_convert._pairs[0].fiat_symbol == 'USD' assert fiat_convert._pairs[0].fiat_symbol == 'USD'

View File

@ -2,7 +2,6 @@
import copy import copy
import logging import logging
from unittest.mock import MagicMock from unittest.mock import MagicMock
import freqtrade.tests.conftest as tt # test tools
import arrow import arrow
import pytest import pytest
@ -10,6 +9,7 @@ import requests
from sqlalchemy import create_engine from sqlalchemy import create_engine
import freqtrade.main as main import freqtrade.main as main
import freqtrade.tests.conftest as tt # test tools
from freqtrade import DependencyException, OperationalException from freqtrade import DependencyException, OperationalException
from freqtrade.exchange import Exchanges from freqtrade.exchange import Exchanges
from freqtrade.main import (_process, check_handle_timedout, create_trade, from freqtrade.main import (_process, check_handle_timedout, create_trade,
@ -50,9 +50,9 @@ def test_main_start_hyperopt(mocker):
def test_process_maybe_execute_buy(default_conf, mocker): def test_process_maybe_execute_buy(default_conf, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.create_trade', return_value=True) mocker.patch('freqtrade.main.create_trade', return_value=True)
assert main.process_maybe_execute_buy(default_conf, int(default_conf['ticker_interval'])) assert main.process_maybe_execute_buy(int(default_conf['ticker_interval']))
mocker.patch('freqtrade.main.create_trade', return_value=False) mocker.patch('freqtrade.main.create_trade', return_value=False)
assert not main.process_maybe_execute_buy(default_conf, int(default_conf['ticker_interval'])) assert not main.process_maybe_execute_buy(int(default_conf['ticker_interval']))
def test_process_maybe_execute_sell(default_conf, mocker): def test_process_maybe_execute_sell(default_conf, mocker):
@ -71,7 +71,7 @@ def test_process_maybe_execute_sell(default_conf, mocker):
def test_process_maybe_execute_buy_exception(default_conf, mocker, caplog): def test_process_maybe_execute_buy_exception(default_conf, mocker, caplog):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.create_trade', MagicMock(side_effect=DependencyException)) mocker.patch('freqtrade.main.create_trade', MagicMock(side_effect=DependencyException))
main.process_maybe_execute_buy(default_conf, int(default_conf['ticker_interval'])) main.process_maybe_execute_buy(int(default_conf['ticker_interval']))
tt.log_has('Unable to create trade:', caplog.record_tuples) tt.log_has('Unable to create trade:', caplog.record_tuples)
@ -256,7 +256,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
create_trade(default_conf['stake_amount'], int(default_conf['ticker_interval'])) create_trade(default_conf['stake_amount'], int(default_conf['ticker_interval']))
def test_create_trade_no_signal(default_conf, ticker, mocker): def test_create_trade_no_signal(default_conf, mocker):
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', MagicMock(return_value=(False, False))) mocker.patch('freqtrade.main.get_signal', MagicMock(return_value=(False, False)))
@ -308,7 +308,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
assert trade.close_date is not None assert trade.close_date is not None
def test_handle_overlpapping_signals(default_conf, ticker, mocker, caplog): def test_handle_overlpapping_signals(default_conf, ticker, mocker):
default_conf.update({'experimental': {'use_sell_signal': True}}) default_conf.update({'experimental': {'use_sell_signal': True}})
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
@ -325,27 +325,31 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker, caplog):
# Buy and Sell triggering, so doing nothing ... # Buy and Sell triggering, so doing nothing ...
trades = Trade.query.all() trades = Trade.query.all()
assert len(trades) == 0 nb_trades = len(trades)
assert nb_trades == 0
# Buy is triggering, so buying ... # Buy is triggering, so buying ...
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False)) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
create_trade(0.001, int(default_conf['ticker_interval'])) create_trade(0.001, int(default_conf['ticker_interval']))
trades = Trade.query.all() trades = Trade.query.all()
assert len(trades) == 1 nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True assert trades[0].is_open is True
# Buy and Sell are not triggering, so doing nothing ... # Buy and Sell are not triggering, so doing nothing ...
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, False)) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (False, False))
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False
trades = Trade.query.all() trades = Trade.query.all()
assert len(trades) == 1 nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True assert trades[0].is_open is True
# Buy and Sell are triggering, so doing nothing ... # Buy and Sell are triggering, so doing nothing ...
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, True)) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, True))
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False
trades = Trade.query.all() trades = Trade.query.all()
assert len(trades) == 1 nb_trades = len(trades)
assert nb_trades == 1
assert trades[0].is_open is True assert trades[0].is_open is True
# Sell is triggering, guess what : we are Selling! # Sell is triggering, guess what : we are Selling!
@ -468,10 +472,11 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_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() trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
assert len(trades) == 0 nb_trades = len(trades)
assert nb_trades == 0
def test_handle_timedout_limit_buy(default_conf, mocker): def test_handle_timedout_limit_buy(mocker):
cancel_order = MagicMock() cancel_order = MagicMock()
mocker.patch('freqtrade.exchange.cancel_order', cancel_order) mocker.patch('freqtrade.exchange.cancel_order', cancel_order)
Trade.session = MagicMock() Trade.session = MagicMock()
@ -519,7 +524,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
assert trade_sell.is_open is True assert trade_sell.is_open is True
def test_handle_timedout_limit_sell(default_conf, mocker): def test_handle_timedout_limit_sell(mocker):
cancel_order = MagicMock() cancel_order = MagicMock()
mocker.patch('freqtrade.exchange.cancel_order', cancel_order) mocker.patch('freqtrade.exchange.cancel_order', cancel_order)
trade = MagicMock() trade = MagicMock()

View File

@ -3,13 +3,13 @@ import argparse
import json import json
import time import time
from copy import deepcopy from copy import deepcopy
from unittest.mock import MagicMock
import pytest import pytest
from unittest.mock import MagicMock
from jsonschema import ValidationError from jsonschema import ValidationError
from freqtrade.misc import (common_args_parser, load_config, parse_args, from freqtrade.misc import (common_args_parser, file_dump_json, load_config,
throttle, file_dump_json, parse_timerange) parse_args, parse_timerange, throttle)
def test_throttle(): def test_throttle():
@ -124,7 +124,7 @@ def test_parse_args_backtesting_custom():
assert call_args.refresh_pairs is True assert call_args.refresh_pairs is True
def test_parse_args_hyperopt_custom(mocker): def test_parse_args_hyperopt_custom():
args = ['-c', 'test_conf.json', 'hyperopt', '--epochs', '20'] args = ['-c', 'test_conf.json', 'hyperopt', '--epochs', '20']
call_args = parse_args(args, '') call_args = parse_args(args, '')
assert call_args.config == 'test_conf.json' assert call_args.config == 'test_conf.json'
@ -134,7 +134,7 @@ def test_parse_args_hyperopt_custom(mocker):
assert call_args.func is not None assert call_args.func is not None
def test_file_dump_json(default_conf, mocker): def test_file_dump_json(mocker):
file_open = mocker.patch('freqtrade.misc.open', MagicMock()) file_open = mocker.patch('freqtrade.misc.open', MagicMock())
json_dump = mocker.patch('json.dump', MagicMock()) json_dump = mocker.patch('json.dump', MagicMock())
file_dump_json('somefile', [1, 2, 3]) file_dump_json('somefile', [1, 2, 3])

View File

@ -1,4 +1,4 @@
# pragma pylint: disable=missing-docstring # pragma pylint: disable=missing-docstring, C0103
import os import os
import pytest import pytest
from sqlalchemy import create_engine from sqlalchemy import create_engine
@ -12,7 +12,7 @@ def test_init_create_session(default_conf, mocker):
# Check if init create a session # Check if init create a session
init(default_conf) init(default_conf)
assert hasattr(Trade, 'session') assert hasattr(Trade, 'session')
assert type(Trade.session).__name__ is 'Session' assert 'Session' in type(Trade.session).__name__
def test_init_dry_run_db(default_conf, mocker): def test_init_dry_run_db(default_conf, mocker):

View File

@ -1,4 +1,4 @@
python-bittrex==0.2.2 python-bittrex==0.3.0
SQLAlchemy==1.2.2 SQLAlchemy==1.2.2
python-telegram-bot==9.0.0 python-telegram-bot==9.0.0
arrow==0.12.1 arrow==0.12.1
@ -19,8 +19,7 @@ hyperopt==0.1
# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325 # do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325
networkx==1.11 networkx==1.11
tabulate==0.8.2 tabulate==0.8.2
pymarketcap==3.3.150 pymarketcap==3.3.153
# Required for plotting data # Required for plotting data
#matplotlib==2.1.0 #plotly==2.3.0
#PYQT5==5.9

View File

@ -1,34 +1,34 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import sys
import talib.abstract as ta import logging
import freqtrade.vendor.qtpylib.indicators as qtpylib import argparse
import os
from pandas import DataFrame from pandas import DataFrame
import talib.abstract as ta
import plotly
from plotly import tools
from plotly.offline import plot
import plotly.graph_objs as go
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade import exchange, analyze from freqtrade import exchange, analyze
from freqtrade.misc import common_args_parser from freqtrade.misc import common_args_parser
from freqtrade.strategy.strategy import Strategy from freqtrade.strategy.strategy import Strategy
import matplotlib.pyplot as plt import freqtrade.misc as misc
import matplotlib # Install PYQT5 manually if you want to test this helper function import freqtrade.optimize as optimize
import freqtrade.analyze as analyze
matplotlib.use("Qt5Agg")
logger = logging.getLogger(__name__)
def plot_parse_args(args): def plot_parse_args(args):
parser = common_args_parser(description='Graph utility') parser = misc.common_args_parser('Graph dataframe')
parser.add_argument( misc.backtesting_options(parser)
'-p', '--pair', misc.scripts_options(parser)
help='What currency pair',
dest='pair',
default='BTC_ETH',
type=str,
)
parser.add_argument(
'-i', '--interval',
help='what interval to use',
dest='interval',
default=5,
type=int,
)
return parser.parse_args(args) return parser.parse_args(args)
@ -38,78 +38,108 @@ def plot_analyzed_dataframe(args) -> None:
:param pair: pair as str :param pair: pair as str
:return: None :return: None
""" """
pair = args.pair.replace('-', '_')
timerange = misc.parse_timerange(args.timerange)
# Init strategy # Init strategy
strategy = Strategy() strategy = Strategy()
strategy.init({'strategy': args.strategy}) strategy.init({'strategy': args.strategy})
tick_interval = strategy.ticker_interval
tickers = {}
if args.live:
logger.info('Downloading pair.')
# Init Bittrex to use public API # Init Bittrex to use public API
exchange._API = exchange.Bittrex({'key': '', 'secret': ''}) exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
ticker = exchange.get_ticker_history(args.pair, args.interval) tickers[pair] = exchange.get_ticker_history(pair, tick_interval)
dataframe = analyze.analyze_ticker(ticker) else:
tickers = optimize.load_data(args.datadir, pairs=[pair],
ticker_interval=tick_interval,
refresh_pairs=False,
timerange=timerange)
dataframes = optimize.tickerdata_to_dataframe(tickers)
dataframe = dataframes[pair]
dataframe = analyze.populate_buy_trend(dataframe)
dataframe = analyze.populate_sell_trend(dataframe)
dates = misc.datesarray_to_datetimearray(dataframe['date'])
dataframe = populate_indicator(dataframe) if (len(dataframe.index) > 750):
logger.warn('Ticker contained more than 750 candles, clipping.')
df = dataframe.tail(750)
# Two subplots sharing x axis candles = go.Candlestick(x=df.date,
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True) open=df.open,
fig.suptitle(args.pair + " " + str(args.interval), fontsize=14, fontweight='bold') high=df.high,
ax1.plot(dataframe.index.values, dataframe['close'], label='close') low=df.low,
# ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell') close=df.close,
ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA') name='Price')
ax1.plot(dataframe.index.values, dataframe['tema'], ':', label='TEMA')
ax1.plot(dataframe.index.values, dataframe['bb_lowerband'], '-.', label='BB low')
ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy')
ax1.legend()
ax2.plot(dataframe.index.values, dataframe['adx'], label='ADX') df_buy = df[df['buy'] == 1]
ax2.plot(dataframe.index.values, dataframe['mfi'], label='MFI') buys = go.Scattergl(
# ax2.plot(dataframe.index.values, [25] * len(dataframe.index.values)) x=df_buy.date,
ax2.legend() y=df_buy.close,
mode='markers',
name='buy',
marker=dict(symbol='x-dot')
)
df_sell = df[df['sell'] == 1]
sells = go.Scattergl(
x=df_sell.date,
y=df_sell.close,
mode='markers',
name='sell',
marker=dict(symbol='diamond')
)
ax3.plot(dataframe.index.values, dataframe['fastk'], label='k') bb_lower = go.Scatter(
ax3.plot(dataframe.index.values, dataframe['fastd'], label='d') x=df.date,
ax3.plot(dataframe.index.values, [20] * len(dataframe.index.values)) y=df.bb_lowerband,
ax3.legend() name='BB lower',
line={'color': "transparent"},
)
bb_upper = go.Scatter(
x=df.date,
y=df.bb_upperband,
name='BB upper',
fill="tonexty",
fillcolor="rgba(0,176,246,0.2)",
line={'color': "transparent"},
)
# Fine-tune figure; make subplots close to each other and hide x ticks for macd = go.Scattergl(
# all but bottom plot. x=df['date'],
fig.subplots_adjust(hspace=0) y=df['macd'],
plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False) name='MACD'
plt.show() )
macdsignal = go.Scattergl(
x=df['date'],
y=df['macdsignal'],
name='MACD signal'
)
volume = go.Bar(
x=df['date'],
y=df['volume'],
name='Volume'
)
def populate_indicator(dataframe: DataFrame) -> DataFrame: fig = tools.make_subplots(rows=3, cols=1, shared_xaxes=True, row_width=[1, 1, 4])
dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close'] fig.append_trace(candles, 1, 1)
dataframe.loc[dataframe['sell'] == 1, 'sell_price'] = dataframe['close'] fig.append_trace(bb_lower, 1, 1)
fig.append_trace(bb_upper, 1, 1)
fig.append_trace(buys, 1, 1)
fig.append_trace(sells, 1, 1)
fig.append_trace(volume, 2, 1)
fig.append_trace(macd, 3, 1)
fig.append_trace(macdsignal, 3, 1)
# ADX fig['layout'].update(title=args.pair)
if 'adx' not in dataframe: fig['layout']['yaxis1'].update(title='Price')
dataframe['adx'] = ta.ADX(dataframe) fig['layout']['yaxis2'].update(title='Volume')
fig['layout']['yaxis3'].update(title='MACD')
# Bollinger bands plot(fig, filename='freqtrade-plot.html')
if 'bb_lowerband' not in dataframe:
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
# Stoch fast
if 'fastd' not in dataframe or 'fastk' not in dataframe:
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# MFI
if 'mfi' not in dataframe:
dataframe['mfi'] = ta.MFI(dataframe)
# SMA - Simple Moving Average
if 'sma' not in dataframe:
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
# TEMA - Triple Exponential Moving Average
if 'tema' not in dataframe:
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
return dataframe
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -2,24 +2,24 @@
import sys import sys
import json import json
import matplotlib.pyplot as plt
import numpy as np import numpy as np
import plotly
from plotly import tools
from plotly.offline import plot
import plotly.graph_objs as go
import freqtrade.optimize as optimize import freqtrade.optimize as optimize
import freqtrade.misc as misc import freqtrade.misc as misc
import freqtrade.exchange as exchange
from freqtrade.strategy.strategy import Strategy from freqtrade.strategy.strategy import Strategy
def plot_parse_args(args): def plot_parse_args(args):
parser = misc.common_args_parser('Graph utility') parser = misc.common_args_parser('Graph profits')
# FIX: perhaps delete those backtesting options that are not feasible (shows up in -h) # FIX: perhaps delete those backtesting options that are not feasible (shows up in -h)
misc.backtesting_options(parser) misc.backtesting_options(parser)
parser.add_argument( misc.scripts_options(parser)
'-p', '--pair',
help='Show profits for only this pairs. Pairs are comma-separated.',
dest='pair',
default=None
)
return parser.parse_args(args) return parser.parse_args(args)
@ -39,7 +39,9 @@ def make_profit_array(data, px, filter_pairs=[]):
profit = trade[1] profit = trade[1]
tim = trade[4] tim = trade[4]
dur = trade[5] dur = trade[5]
pg[tim+dur-1] += profit ix = tim + dur - 1
if ix < px:
pg[ix] += profit
# rewrite the pg array to go from # rewrite the pg array to go from
# total profits at each timeframe # total profits at each timeframe
@ -81,28 +83,21 @@ def plot_profit(args) -> None:
pairs = list(set(pairs) & set(filter_pairs)) pairs = list(set(pairs) & set(filter_pairs))
print('Filter, keep pairs %s' % pairs) print('Filter, keep pairs %s' % pairs)
timerange = misc.parse_timerange(args.timerange)
tickers = optimize.load_data(args.datadir, pairs=pairs, tickers = optimize.load_data(args.datadir, pairs=pairs,
ticker_interval=strategy.ticker_interval, ticker_interval=strategy.ticker_interval,
refresh_pairs=False) refresh_pairs=False,
timerange=timerange)
dataframes = optimize.preprocess(tickers) dataframes = optimize.preprocess(tickers)
# NOTE: the dataframes are of unequal length,
# 'dates' is an merged date array of them all.
dates = misc.common_datearray(dataframes)
max_x = dates.size
# Make an average close price of all the pairs that was involved. # Make an average close price of all the pairs that was involved.
# this could be useful to gauge the overall market trend # this could be useful to gauge the overall market trend
# FIX: since the dataframes are of unequal length,
# andor has different dates, we need to merge them
# But we dont have the date information in the
# backtesting results, this is needed to match the dates
# For now, assume the dataframes are aligned.
max_x = 0
for pair, pair_data in dataframes.items():
n = len(pair_data['close'])
max_x = max(max_x, n)
# if max_x != n:
# raise Exception('Please rerun script. Input data has different lengths %s'
# %('Different pair length: %s <=> %s' %(max_x, n)))
print('max_x: %s' % (max_x))
# We are essentially saying: # We are essentially saying:
# array <- sum dataframes[*]['close'] / num_items dataframes # array <- sum dataframes[*]['close'] / num_items dataframes
# FIX: there should be some onliner numpy/panda for this # FIX: there should be some onliner numpy/panda for this
@ -130,29 +125,32 @@ def plot_profit(args) -> None:
# Plot the pairs average close prices, and total profit growth # Plot the pairs average close prices, and total profit growth
# #
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True) avgclose = go.Scattergl(
fig.suptitle('total profit') x=dates,
ax1.plot(avgclose, label='avgclose') y=avgclose,
ax2.plot(pg, label='profit') name='Avg close price',
ax1.legend(loc='upper left') )
ax2.legend(loc='upper left') profit = go.Scattergl(
x=dates,
y=pg,
name='Profit',
)
fig = tools.make_subplots(rows=3, cols=1, shared_xaxes=True, row_width=[1, 1, 1])
fig.append_trace(avgclose, 1, 1)
fig.append_trace(profit, 2, 1)
# FIX if we have one line pair in paris
# then skip the plotting of the third graph,
# or change what we plot
# In third graph, we plot each profit separately
for pair in pairs: for pair in pairs:
pg = make_profit_array(data, max_x, pair) pg = make_profit_array(data, max_x, pair)
ax3.plot(pg, label=pair) pair_profit = go.Scattergl(
ax3.legend(loc='upper left') x=dates,
# black background to easier see multiple colors y=pg,
ax3.set_facecolor('black') name=pair,
)
fig.append_trace(pair_profit, 3, 1)
# Fine-tune figure; make subplots close to each other and hide x ticks for plot(fig, filename='freqtrade-profit-plot.html')
# all but bottom plot.
fig.subplots_adjust(hspace=0)
plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False)
plt.show()
if __name__ == '__main__': if __name__ == '__main__':