Merge branch 'develop' into rpc-refactor
This commit is contained in:
commit
aa22db9fd9
@ -5,18 +5,44 @@ This page explains how to plot prices, indicator, profits.
|
||||
- [Plot price and indicators](#plot-price-and-indicators)
|
||||
- [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
|
||||
Usage for the price plotter:
|
||||
script/plot_dataframe.py [-h] [-p pair]
|
||||
|
||||
```
|
||||
script/plot_dataframe.py [-h] [-p pair] [--live]
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
**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
|
||||
|
||||
@ -38,9 +64,12 @@ The third graph can be useful to spot outliers, events in pairs
|
||||
that makes profit spikes.
|
||||
|
||||
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
|
||||
```
|
||||
|
@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import requests
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from bittrex.bittrex import Bittrex as _Bittrex
|
||||
@ -15,20 +14,6 @@ _API: _Bittrex = None
|
||||
_API_V2: _Bittrex = None
|
||||
_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):
|
||||
"""
|
||||
@ -47,14 +32,12 @@ class Bittrex(Exchange):
|
||||
api_secret=_EXCHANGE_CONF['secret'],
|
||||
calls_per_second=1,
|
||||
api_version=API_V1_1,
|
||||
dispatch=custom_requests
|
||||
)
|
||||
_API_V2 = _Bittrex(
|
||||
api_key=_EXCHANGE_CONF['key'],
|
||||
api_secret=_EXCHANGE_CONF['secret'],
|
||||
calls_per_second=1,
|
||||
api_version=API_V2_0,
|
||||
dispatch=custom_requests
|
||||
)
|
||||
self.cached_ticker = {}
|
||||
|
||||
|
@ -1,12 +1,19 @@
|
||||
"""
|
||||
Module that define classes to convert Crypto-currency to FIAT
|
||||
e.g BTC to USD
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
from pymarketcap import Pymarketcap
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CryptoFiat():
|
||||
"""
|
||||
Object to describe what is the price of Crypto-currency in a FIAT
|
||||
"""
|
||||
# Constants
|
||||
CACHE_DURATION = 6 * 60 * 60 # 6 hours
|
||||
|
||||
@ -49,6 +56,11 @@ class CryptoFiat():
|
||||
|
||||
|
||||
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
|
||||
_coinmarketcap = None
|
||||
|
||||
|
@ -6,7 +6,7 @@ import sys
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
import arrow
|
||||
import requests
|
||||
@ -23,7 +23,7 @@ from freqtrade.strategy.strategy import Strategy
|
||||
|
||||
logger = logging.getLogger('freqtrade')
|
||||
|
||||
_CONF = {}
|
||||
_CONF: Dict[str, Any] = {}
|
||||
|
||||
|
||||
def refresh_whitelist(whitelist: List[str]) -> List[str]:
|
||||
@ -55,7 +55,7 @@ def refresh_whitelist(whitelist: List[str]) -> List[str]:
|
||||
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
|
||||
:return: True if executed
|
||||
@ -64,12 +64,12 @@ def process_maybe_execute_buy(conf, interval):
|
||||
# Create entity and execute trade
|
||||
if create_trade(float(_CONF['stake_amount']), interval):
|
||||
return True
|
||||
else:
|
||||
logger.info(
|
||||
'Checked all whitelisted currencies. '
|
||||
'Found no suitable entry positions for buying. Will keep looking ...'
|
||||
)
|
||||
return False
|
||||
|
||||
logger.info(
|
||||
'Checked all whitelisted currencies. '
|
||||
'Found no suitable entry positions for buying. Will keep looking ...'
|
||||
)
|
||||
return False
|
||||
except DependencyException as exception:
|
||||
logger.warning('Unable to create trade: %s', exception)
|
||||
return False
|
||||
@ -115,7 +115,7 @@ def _process(interval: int, nb_assets: Optional[int] = 0) -> bool:
|
||||
# Query trades from persistence layer
|
||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||
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:
|
||||
state_changed |= process_maybe_execute_sell(trade, interval)
|
||||
@ -159,16 +159,16 @@ def handle_timedout_limit_buy(trade: Trade, order: Dict) -> bool:
|
||||
rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format(
|
||||
trade.pair.replace('_', '/')))
|
||||
return True
|
||||
else:
|
||||
# if trade is partially complete, edit the stake details for the trade
|
||||
# and close the order
|
||||
trade.amount = order['amount'] - order['remaining']
|
||||
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('_', '/')))
|
||||
return False
|
||||
|
||||
# if trade is partially complete, edit the stake details for the trade
|
||||
# and close the order
|
||||
trade.amount = order['amount'] - order['remaining']
|
||||
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('_', '/')))
|
||||
return False
|
||||
|
||||
|
||||
# FIX: 20180110, should cancel_order() be cond. or unconditionally called?
|
||||
@ -189,9 +189,9 @@ def handle_timedout_limit_sell(trade: Trade, order: Dict) -> bool:
|
||||
trade.pair.replace('_', '/')))
|
||||
logger.info('Sell order timeout for %s.', trade)
|
||||
return True
|
||||
else:
|
||||
# TODO: figure out how to handle partially complete sell orders
|
||||
return False
|
||||
|
||||
# TODO: figure out how to handle partially complete sell orders
|
||||
return False
|
||||
|
||||
|
||||
def check_handle_timedout(timeoutvalue: int) -> None:
|
||||
|
@ -5,8 +5,10 @@ import logging
|
||||
import time
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable, Dict, List
|
||||
|
||||
import numpy as np
|
||||
from jsonschema import Draft4Validator, validate
|
||||
from jsonschema.exceptions import ValidationError, best_match
|
||||
from wrapt import synchronized
|
||||
@ -16,11 +18,6 @@ from freqtrade import __version__
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def file_dump_json(filename, data):
|
||||
with open(filename, 'w') as fp:
|
||||
json.dump(data, fp)
|
||||
|
||||
|
||||
class State(enum.Enum):
|
||||
RUNNING = 0
|
||||
STOPPED = 1
|
||||
@ -30,6 +27,44 @@ class State(enum.Enum):
|
||||
_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
|
||||
def update_state(state: State) -> None:
|
||||
"""
|
||||
@ -163,6 +198,15 @@ def parse_args(args: List[str], description: str):
|
||||
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:
|
||||
parser.add_argument(
|
||||
'-l', '--live',
|
||||
|
@ -22,8 +22,8 @@ def trim_tickerlist(tickerlist, timerange):
|
||||
return tickerlist[0:start]
|
||||
elif stype == ('index', 'index'):
|
||||
return tickerlist[start:stop]
|
||||
else:
|
||||
return tickerlist
|
||||
|
||||
return tickerlist
|
||||
|
||||
|
||||
def load_tickerdata_file(datadir, pair, ticker_interval,
|
||||
|
@ -26,7 +26,7 @@ def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]
|
||||
:return: tuple containing min_date, max_date
|
||||
"""
|
||||
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.sort_values(inplace=True)
|
||||
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)
|
||||
# Print timeframe
|
||||
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
|
||||
sell_profit_only = config.get('experimental', {}).get('sell_profit_only', False)
|
||||
use_sell_signal = config.get('experimental', {}).get('use_sell_signal', False)
|
||||
|
@ -10,7 +10,7 @@ import sys
|
||||
from functools import reduce
|
||||
from math import exp
|
||||
from operator import itemgetter
|
||||
from typing import Dict, List
|
||||
from typing import Dict, Any, Callable
|
||||
|
||||
import numpy
|
||||
import talib.abstract as ta
|
||||
@ -35,7 +35,7 @@ logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
|
||||
|
||||
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
|
||||
TOTAL_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 """
|
||||
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
|
||||
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
|
||||
|
||||
|
||||
def generate_roi_table(params):
|
||||
def generate_roi_table(params) -> Dict[str, float]:
|
||||
roi_table = {}
|
||||
roi_table["0"] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
|
||||
roi_table[str(params['roi_t3'])] = params['roi_p1'] + params['roi_p2']
|
||||
@ -235,24 +235,24 @@ def generate_roi_table(params):
|
||||
return roi_table
|
||||
|
||||
|
||||
def roi_space() -> List[Dict]:
|
||||
def roi_space() -> Dict[str, Any]:
|
||||
return {
|
||||
'roi_t1': hp.quniform('roi_t1', 10, 220, 10),
|
||||
'roi_t2': hp.quniform('roi_t2', 10, 120, 10),
|
||||
'roi_t3': hp.quniform('roi_t3', 10, 120, 10),
|
||||
'roi_p1': hp.quniform('roi_p1', 0.01, 0.05, 0.01),
|
||||
'roi_p2': hp.quniform('roi_p2', 0.01, 0.10, 0.01),
|
||||
'roi_p3': hp.quniform('roi_p3', 0.01, 0.30, 0.01),
|
||||
'roi_t1': hp.quniform('roi_t1', 10, 120, 20),
|
||||
'roi_t2': hp.quniform('roi_t2', 10, 60, 15),
|
||||
'roi_t3': hp.quniform('roi_t3', 10, 40, 10),
|
||||
'roi_p1': hp.quniform('roi_p1', 0.01, 0.04, 0.01),
|
||||
'roi_p2': hp.quniform('roi_p2', 0.01, 0.07, 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 {
|
||||
'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
|
||||
"""
|
||||
@ -263,19 +263,19 @@ def indicator_space() -> List[Dict]:
|
||||
]),
|
||||
'mfi': hp.choice('mfi', [
|
||||
{'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', [
|
||||
{'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', [
|
||||
{'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', [
|
||||
{'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', [
|
||||
{'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()}
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
|
@ -1,7 +1,9 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
from pandas import DataFrame
|
||||
|
||||
|
||||
class_name = 'DefaultStrategy'
|
||||
|
@ -1,8 +1,22 @@
|
||||
"""
|
||||
IStrategy interface
|
||||
This module defines the interface to apply for strategies
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from pandas import DataFrame
|
||||
|
||||
|
||||
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
|
||||
def name(self) -> str:
|
||||
"""
|
||||
@ -11,13 +25,6 @@ class IStrategy(ABC):
|
||||
"""
|
||||
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
|
||||
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||
"""
|
||||
|
@ -1,10 +1,12 @@
|
||||
"""
|
||||
This module load custom strategies
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import importlib
|
||||
|
||||
from pandas import DataFrame
|
||||
from typing import Dict
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
|
||||
|
||||
@ -12,16 +14,36 @@ sys.path.insert(0, r'../../user_data/strategies')
|
||||
|
||||
|
||||
class Strategy(object):
|
||||
"""
|
||||
This class contains all the logic to load custom strategy class
|
||||
"""
|
||||
__instance = None
|
||||
|
||||
DEFAULT_STRATEGY = 'default_strategy'
|
||||
|
||||
def __new__(cls):
|
||||
"""
|
||||
Used to create the Singleton
|
||||
:return: Strategy object
|
||||
"""
|
||||
if Strategy.__instance is None:
|
||||
Strategy.__instance = object.__new__(cls)
|
||||
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):
|
||||
"""
|
||||
Load the custom class from config parameter
|
||||
:param config:
|
||||
:return:
|
||||
"""
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
|
||||
@ -42,21 +64,22 @@ class Strategy(object):
|
||||
if 'stoploss' in config:
|
||||
self.custom_strategy.stoploss = config['stoploss']
|
||||
self.logger.info(
|
||||
"Override strategy \'stoploss\' with value in config file: {}.".format(
|
||||
config['stoploss']
|
||||
)
|
||||
"Override strategy \'stoploss\' with value in config file: %s.", config['stoploss']
|
||||
)
|
||||
|
||||
if 'ticker_interval' in config:
|
||||
self.custom_strategy.ticker_interval = config['ticker_interval']
|
||||
self.logger.info(
|
||||
"Override strategy \'ticker_interval\' with value in config file: {}.".format(
|
||||
config['ticker_interval']
|
||||
)
|
||||
"Override strategy \'ticker_interval\' with value in config file: %s.",
|
||||
config['ticker_interval']
|
||||
)
|
||||
|
||||
# Minimal ROI designed for the strategy
|
||||
self.minimal_roi = self.custom_strategy.minimal_roi
|
||||
|
||||
# Optimal stoploss designed for the strategy
|
||||
self.stoploss = self.custom_strategy.stoploss
|
||||
|
||||
self.ticker_interval = self.custom_strategy.ticker_interval
|
||||
|
||||
def _load_strategy(self, strategy_name: str) -> None:
|
||||
@ -90,7 +113,7 @@ class Strategy(object):
|
||||
module = importlib.import_module(filename, __package__)
|
||||
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()
|
||||
|
||||
@staticmethod
|
||||
@ -126,20 +149,6 @@ class Strategy(object):
|
||||
|
||||
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:
|
||||
"""
|
||||
Populate indicators that will be used in the Buy and Sell strategy
|
||||
|
@ -3,10 +3,13 @@ from datetime import datetime
|
||||
from unittest.mock import MagicMock
|
||||
from functools import reduce
|
||||
|
||||
import json
|
||||
import arrow
|
||||
import pytest
|
||||
from jsonschema import validate
|
||||
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
|
||||
|
||||
@ -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:
|
||||
# Create an fixture/function
|
||||
# that inserts a trade of some type and open-status
|
||||
|
@ -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 requests.exceptions import RequestException
|
||||
from random import randint
|
||||
import logging
|
||||
from requests.exceptions import RequestException
|
||||
import pytest
|
||||
|
||||
from freqtrade import OperationalException
|
||||
@ -30,7 +31,7 @@ def test_init(default_conf, mocker, caplog):
|
||||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_init_exception(default_conf, mocker):
|
||||
def test_init_exception(default_conf):
|
||||
default_conf['exchange']['name'] = 'wrong_exchange_name'
|
||||
|
||||
with pytest.raises(
|
||||
@ -171,7 +172,7 @@ def test_get_balances_prod(default_conf, mocker):
|
||||
|
||||
# This test is somewhat redundant with
|
||||
# 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)
|
||||
api_mock = MagicMock()
|
||||
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
|
||||
|
||||
|
||||
def test_get_ticker_history(default_conf, mocker, ticker):
|
||||
def test_get_ticker_history(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
tick = 123
|
||||
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.get_order = MagicMock(return_value=456)
|
||||
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):
|
||||
@ -271,16 +272,16 @@ def test_get_fee(default_conf, mocker):
|
||||
assert get_fee() == 0.0025
|
||||
|
||||
|
||||
def test_exchange_misc(default_conf, mocker):
|
||||
def test_exchange_misc(mocker):
|
||||
api_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
exchange.get_markets()
|
||||
assert 1 == api_mock.get_markets.call_count
|
||||
assert api_mock.get_markets.call_count == 1
|
||||
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
|
||||
assert 123 == exchange.get_name()
|
||||
assert exchange.get_name() == 123
|
||||
api_mock.fee = 456
|
||||
assert 456 == exchange.get_fee()
|
||||
assert exchange.get_fee() == 456
|
||||
exchange.get_wallet_health()
|
||||
assert 1 == api_mock.get_wallet_health.call_count
|
||||
assert api_mock.get_wallet_health.call_count == 1
|
||||
|
@ -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
|
||||
import pytest
|
||||
from requests.exceptions import ContentDecodingError
|
||||
|
||||
from freqtrade.exchange.bittrex import Bittrex
|
||||
import freqtrade.exchange.bittrex as btx
|
||||
|
||||
@ -88,8 +87,7 @@ class FakeBittrex():
|
||||
'PricePerUnit': 1,
|
||||
'Quantity': 1,
|
||||
'QuantityRemaining': 1,
|
||||
'Closed': True
|
||||
},
|
||||
'Closed': True},
|
||||
'message': 'lost'}
|
||||
|
||||
def fake_cancel_order(self, uuid):
|
||||
@ -211,24 +209,18 @@ def test_exchange_bittrex_get_ticker():
|
||||
def test_exchange_bittrex_get_ticker_bad():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
fb.result = {'success': True,
|
||||
'result': {'Bid': 1, 'Ask': 0}} # incomplete result
|
||||
fb.result = {'success': True, 'result': {'Bid': 1, 'Ask': 0}} # incomplete result
|
||||
|
||||
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
|
||||
wb.get_ticker('BTC_ETH')
|
||||
fb.result = {'success': False,
|
||||
'message': 'gone bad'
|
||||
}
|
||||
fb.result = {'success': False, 'message': 'gone bad'}
|
||||
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
|
||||
wb.get_ticker('BTC_ETH')
|
||||
|
||||
fb.result = {'success': True,
|
||||
'result': {}} # incomplete result
|
||||
fb.result = {'success': True, 'result': {}} # incomplete result
|
||||
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
|
||||
wb.get_ticker('BTC_ETH')
|
||||
fb.result = {'success': False,
|
||||
'message': 'gone bad'
|
||||
}
|
||||
fb.result = {'success': False, 'message': 'gone bad'}
|
||||
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
|
||||
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.*'):
|
||||
Bittrex._validate_response(response)
|
||||
|
||||
|
||||
def test_custom_requests(mocker):
|
||||
mocker.patch('freqtrade.exchange.bittrex.requests', MagicMock())
|
||||
btx.custom_requests('http://', '')
|
||||
|
@ -1,28 +1,19 @@
|
||||
# pragma pylint: disable=missing-docstring,W0212
|
||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103
|
||||
|
||||
import logging
|
||||
import math
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
import pandas as pd
|
||||
from freqtrade import exchange, optimize
|
||||
from freqtrade.exchange import Bittrex
|
||||
from freqtrade.optimize import preprocess
|
||||
from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe
|
||||
import freqtrade.optimize.backtesting as backtesting
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def default_strategy():
|
||||
strategy = Strategy()
|
||||
strategy.init({'strategy': 'default_strategy'})
|
||||
return strategy
|
||||
|
||||
|
||||
def trim_dictlist(dl, num):
|
||||
def trim_dictlist(dict_list, num):
|
||||
new = {}
|
||||
for pair, pair_data in dl.items():
|
||||
for pair, pair_data in dict_list.items():
|
||||
new[pair] = pair_data[num:]
|
||||
return new
|
||||
|
||||
@ -193,7 +184,8 @@ def test_backtest_start(default_conf, mocker, caplog):
|
||||
# check the logs, that will contain the backtest result
|
||||
exists = ['Using max_open_trades: 1 ...',
|
||||
'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:
|
||||
assert ('freqtrade.optimize.backtesting',
|
||||
logging.INFO,
|
||||
|
@ -1,8 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring,W0212
|
||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
||||
|
||||
import os
|
||||
import logging
|
||||
# from unittest.mock import MagicMock
|
||||
from shutil import copyfile
|
||||
from freqtrade import exchange, optimize
|
||||
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
|
||||
|
||||
# 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:
|
||||
@ -56,8 +55,7 @@ def test_load_data_30min_ticker(default_conf, ticker_history, mocker, caplog):
|
||||
assert os.path.isfile(file) is True
|
||||
assert ('freqtrade.optimize',
|
||||
logging.INFO,
|
||||
'Download the pair: "BTC_ETH", Interval: 30 min'
|
||||
) not in caplog.record_tuples
|
||||
'Download the pair: "BTC_ETH", Interval: 30 min') not in caplog.record_tuples
|
||||
_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 ('freqtrade.optimize',
|
||||
logging.INFO,
|
||||
'Download the pair: "BTC_ETH", Interval: 5 min'
|
||||
) not in caplog.record_tuples
|
||||
'Download the pair: "BTC_ETH", Interval: 5 min') not in caplog.record_tuples
|
||||
_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 ('freqtrade.optimize',
|
||||
logging.INFO,
|
||||
'Download the pair: "BTC_ETH", Interval: 1 min'
|
||||
) not in caplog.record_tuples
|
||||
'Download the pair: "BTC_ETH", Interval: 1 min') not in caplog.record_tuples
|
||||
_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 ('freqtrade.optimize',
|
||||
logging.INFO,
|
||||
'Download the pair: "BTC_MEME", Interval: 1 min'
|
||||
) in caplog.record_tuples
|
||||
'Download the pair: "BTC_MEME", Interval: 1 min') in caplog.record_tuples
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
@ -174,8 +169,7 @@ def test_download_pairs_exception(default_conf, ticker_history, mocker, caplog):
|
||||
_clean_test_file(file1_5)
|
||||
assert ('freqtrade.optimize.__init__',
|
||||
logging.INFO,
|
||||
'Failed to download the pair: "BTC-MEME", Interval: 1 min'
|
||||
) in caplog.record_tuples
|
||||
'Failed to download the pair: "BTC-MEME", Interval: 1 min') in caplog.record_tuples
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_download_backtesting_testdata2(default_conf, mocker):
|
||||
def test_download_backtesting_testdata2(mocker):
|
||||
tick = [{'T': 'bar'}, {'T': 'foo'}]
|
||||
mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
||||
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():
|
||||
assert not load_tickerdata_file(None, 'BTC_UNITEST', 7)
|
||||
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):
|
||||
@ -225,4 +219,4 @@ def test_tickerdata_to_dataframe():
|
||||
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'])
|
||||
assert len(data['BTC_UNITEST']) == 100
|
||||
|
@ -1,4 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors, C0103
|
||||
# pragma pylint: disable=unused-argument
|
||||
import re
|
||||
from datetime import datetime
|
||||
from random import randint
|
||||
|
@ -1,14 +1,7 @@
|
||||
import json
|
||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
||||
|
||||
import logging
|
||||
import pytest
|
||||
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():
|
||||
@ -28,8 +21,6 @@ def test_search_strategy():
|
||||
|
||||
def test_strategy_structure():
|
||||
assert hasattr(Strategy, 'init')
|
||||
assert hasattr(Strategy, 'minimal_roi')
|
||||
assert hasattr(Strategy, 'stoploss')
|
||||
assert hasattr(Strategy, 'populate_indicators')
|
||||
assert hasattr(Strategy, 'populate_buy_trend')
|
||||
assert hasattr(Strategy, 'populate_sell_trend')
|
||||
|
@ -1,3 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring,C0103
|
||||
|
||||
from freqtrade.main import refresh_whitelist, gen_pair_whitelist
|
||||
|
||||
# whitelist, blacklist, filtering, all of that will
|
||||
@ -73,16 +75,9 @@ def get_market_summaries():
|
||||
|
||||
|
||||
def get_health():
|
||||
return [{'Currency': 'ETH',
|
||||
'IsActive': True
|
||||
},
|
||||
{'Currency': 'TKN',
|
||||
'IsActive': True
|
||||
},
|
||||
{'Currency': 'BLK',
|
||||
'IsActive': True
|
||||
}
|
||||
]
|
||||
return [{'Currency': 'ETH', 'IsActive': True},
|
||||
{'Currency': 'TKN', 'IsActive': True},
|
||||
{'Currency': 'BLK', 'IsActive': True}]
|
||||
|
||||
|
||||
def get_health_empty():
|
||||
|
@ -1,25 +1,17 @@
|
||||
# pragma pylint: disable=missing-docstring,W0621
|
||||
import json
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import datetime
|
||||
from unittest.mock import MagicMock
|
||||
import freqtrade.tests.conftest as tt # test tools
|
||||
|
||||
import arrow
|
||||
import datetime
|
||||
import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
import freqtrade.tests.conftest as tt # test tools
|
||||
from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
|
||||
populate_buy_trend, populate_indicators,
|
||||
populate_sell_trend)
|
||||
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):
|
||||
assert result.columns.tolist() == \
|
||||
['close', 'high', 'low', 'open', 'date', 'volume']
|
||||
|
@ -1,5 +1,6 @@
|
||||
import pandas
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
|
||||
import pandas
|
||||
import freqtrade.optimize
|
||||
from freqtrade import analyze
|
||||
|
||||
|
@ -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
|
||||
from unittest.mock import MagicMock
|
||||
@ -47,16 +48,19 @@ def test_fiat_convert_is_supported():
|
||||
def test_fiat_convert_add_pair():
|
||||
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)
|
||||
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].fiat_symbol == 'USD'
|
||||
assert fiat_convert._pairs[0].price == 12345.0
|
||||
|
||||
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].fiat_symbol == 'EUR'
|
||||
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')
|
||||
|
||||
# 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._pairs[0].crypto_symbol == 'BTC'
|
||||
assert fiat_convert._pairs[0].fiat_symbol == 'USD'
|
||||
|
@ -1,8 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring,C0103
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import copy
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
import freqtrade.tests.conftest as tt # test tools
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
@ -10,6 +9,7 @@ import requests
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
import freqtrade.main as main
|
||||
import freqtrade.tests.conftest as tt # test tools
|
||||
from freqtrade import DependencyException, OperationalException
|
||||
from freqtrade.exchange import Exchanges
|
||||
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):
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
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)
|
||||
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):
|
||||
@ -71,7 +71,7 @@ def test_process_maybe_execute_sell(default_conf, mocker):
|
||||
def test_process_maybe_execute_buy_exception(default_conf, mocker, caplog):
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
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)
|
||||
|
||||
|
||||
@ -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']))
|
||||
|
||||
|
||||
def test_create_trade_no_signal(default_conf, ticker, mocker):
|
||||
def test_create_trade_no_signal(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
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
|
||||
|
||||
|
||||
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}})
|
||||
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 ...
|
||||
trades = Trade.query.all()
|
||||
assert len(trades) == 0
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 0
|
||||
|
||||
# Buy is triggering, so buying ...
|
||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
|
||||
create_trade(0.001, int(default_conf['ticker_interval']))
|
||||
trades = Trade.query.all()
|
||||
assert len(trades) == 1
|
||||
nb_trades = len(trades)
|
||||
assert nb_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, t: (False, False))
|
||||
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False
|
||||
trades = Trade.query.all()
|
||||
assert len(trades) == 1
|
||||
nb_trades = len(trades)
|
||||
assert nb_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, t: (True, True))
|
||||
assert handle_trade(trades[0], int(default_conf['ticker_interval'])) is False
|
||||
trades = Trade.query.all()
|
||||
assert len(trades) == 1
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 1
|
||||
assert trades[0].is_open is True
|
||||
|
||||
# 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 rpc_mock.call_count == 1
|
||||
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()
|
||||
mocker.patch('freqtrade.exchange.cancel_order', cancel_order)
|
||||
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
|
||||
|
||||
|
||||
def test_handle_timedout_limit_sell(default_conf, mocker):
|
||||
def test_handle_timedout_limit_sell(mocker):
|
||||
cancel_order = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.cancel_order', cancel_order)
|
||||
trade = MagicMock()
|
||||
|
@ -3,13 +3,13 @@ import argparse
|
||||
import json
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
from jsonschema import ValidationError
|
||||
|
||||
from freqtrade.misc import (common_args_parser, load_config, parse_args,
|
||||
throttle, file_dump_json, parse_timerange)
|
||||
from freqtrade.misc import (common_args_parser, file_dump_json, load_config,
|
||||
parse_args, parse_timerange, throttle)
|
||||
|
||||
|
||||
def test_throttle():
|
||||
@ -124,7 +124,7 @@ def test_parse_args_backtesting_custom():
|
||||
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']
|
||||
call_args = parse_args(args, '')
|
||||
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
|
||||
|
||||
|
||||
def test_file_dump_json(default_conf, mocker):
|
||||
def test_file_dump_json(mocker):
|
||||
file_open = mocker.patch('freqtrade.misc.open', MagicMock())
|
||||
json_dump = mocker.patch('json.dump', MagicMock())
|
||||
file_dump_json('somefile', [1, 2, 3])
|
||||
|
@ -1,4 +1,4 @@
|
||||
# pragma pylint: disable=missing-docstring
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import os
|
||||
import pytest
|
||||
from sqlalchemy import create_engine
|
||||
@ -12,7 +12,7 @@ def test_init_create_session(default_conf, mocker):
|
||||
# Check if init create a session
|
||||
init(default_conf)
|
||||
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):
|
||||
|
@ -1,4 +1,4 @@
|
||||
python-bittrex==0.2.2
|
||||
python-bittrex==0.3.0
|
||||
SQLAlchemy==1.2.2
|
||||
python-telegram-bot==9.0.0
|
||||
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
|
||||
networkx==1.11
|
||||
tabulate==0.8.2
|
||||
pymarketcap==3.3.150
|
||||
pymarketcap==3.3.153
|
||||
|
||||
# Required for plotting data
|
||||
#matplotlib==2.1.0
|
||||
#PYQT5==5.9
|
||||
#plotly==2.3.0
|
||||
|
@ -1,34 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import talib.abstract as ta
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
import logging
|
||||
import argparse
|
||||
import os
|
||||
|
||||
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.misc import common_args_parser
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib # Install PYQT5 manually if you want to test this helper function
|
||||
import freqtrade.misc as misc
|
||||
import freqtrade.optimize as optimize
|
||||
import freqtrade.analyze as analyze
|
||||
|
||||
matplotlib.use("Qt5Agg")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def plot_parse_args(args):
|
||||
parser = common_args_parser(description='Graph utility')
|
||||
parser.add_argument(
|
||||
'-p', '--pair',
|
||||
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,
|
||||
)
|
||||
parser = misc.common_args_parser('Graph dataframe')
|
||||
misc.backtesting_options(parser)
|
||||
misc.scripts_options(parser)
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
||||
@ -38,78 +38,108 @@ def plot_analyzed_dataframe(args) -> None:
|
||||
:param pair: pair as str
|
||||
:return: None
|
||||
"""
|
||||
pair = args.pair.replace('-', '_')
|
||||
timerange = misc.parse_timerange(args.timerange)
|
||||
|
||||
# Init strategy
|
||||
strategy = Strategy()
|
||||
strategy.init({'strategy': args.strategy})
|
||||
tick_interval = strategy.ticker_interval
|
||||
|
||||
# Init Bittrex to use public API
|
||||
exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
|
||||
ticker = exchange.get_ticker_history(args.pair, args.interval)
|
||||
dataframe = analyze.analyze_ticker(ticker)
|
||||
tickers = {}
|
||||
if args.live:
|
||||
logger.info('Downloading pair.')
|
||||
# Init Bittrex to use public API
|
||||
exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
|
||||
tickers[pair] = exchange.get_ticker_history(pair, tick_interval)
|
||||
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
|
||||
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True)
|
||||
fig.suptitle(args.pair + " " + str(args.interval), fontsize=14, fontweight='bold')
|
||||
ax1.plot(dataframe.index.values, dataframe['close'], label='close')
|
||||
# ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell')
|
||||
ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA')
|
||||
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()
|
||||
candles = go.Candlestick(x=df.date,
|
||||
open=df.open,
|
||||
high=df.high,
|
||||
low=df.low,
|
||||
close=df.close,
|
||||
name='Price')
|
||||
|
||||
ax2.plot(dataframe.index.values, dataframe['adx'], label='ADX')
|
||||
ax2.plot(dataframe.index.values, dataframe['mfi'], label='MFI')
|
||||
# ax2.plot(dataframe.index.values, [25] * len(dataframe.index.values))
|
||||
ax2.legend()
|
||||
df_buy = df[df['buy'] == 1]
|
||||
buys = go.Scattergl(
|
||||
x=df_buy.date,
|
||||
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')
|
||||
ax3.plot(dataframe.index.values, dataframe['fastd'], label='d')
|
||||
ax3.plot(dataframe.index.values, [20] * len(dataframe.index.values))
|
||||
ax3.legend()
|
||||
bb_lower = go.Scatter(
|
||||
x=df.date,
|
||||
y=df.bb_lowerband,
|
||||
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
|
||||
# all but bottom plot.
|
||||
fig.subplots_adjust(hspace=0)
|
||||
plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False)
|
||||
plt.show()
|
||||
macd = go.Scattergl(
|
||||
x=df['date'],
|
||||
y=df['macd'],
|
||||
name='MACD'
|
||||
)
|
||||
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']
|
||||
dataframe.loc[dataframe['sell'] == 1, 'sell_price'] = dataframe['close']
|
||||
fig.append_trace(candles, 1, 1)
|
||||
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
|
||||
if 'adx' not in dataframe:
|
||||
dataframe['adx'] = ta.ADX(dataframe)
|
||||
fig['layout'].update(title=args.pair)
|
||||
fig['layout']['yaxis1'].update(title='Price')
|
||||
fig['layout']['yaxis2'].update(title='Volume')
|
||||
fig['layout']['yaxis3'].update(title='MACD')
|
||||
|
||||
# Bollinger bands
|
||||
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
|
||||
plot(fig, filename='freqtrade-plot.html')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -2,24 +2,24 @@
|
||||
|
||||
import sys
|
||||
import json
|
||||
import matplotlib.pyplot as plt
|
||||
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.misc as misc
|
||||
import freqtrade.exchange as exchange
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
|
||||
|
||||
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)
|
||||
misc.backtesting_options(parser)
|
||||
parser.add_argument(
|
||||
'-p', '--pair',
|
||||
help='Show profits for only this pairs. Pairs are comma-separated.',
|
||||
dest='pair',
|
||||
default=None
|
||||
)
|
||||
misc.scripts_options(parser)
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
||||
@ -39,7 +39,9 @@ def make_profit_array(data, px, filter_pairs=[]):
|
||||
profit = trade[1]
|
||||
tim = trade[4]
|
||||
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
|
||||
# total profits at each timeframe
|
||||
@ -81,28 +83,21 @@ def plot_profit(args) -> None:
|
||||
pairs = list(set(pairs) & set(filter_pairs))
|
||||
print('Filter, keep pairs %s' % pairs)
|
||||
|
||||
timerange = misc.parse_timerange(args.timerange)
|
||||
tickers = optimize.load_data(args.datadir, pairs=pairs,
|
||||
ticker_interval=strategy.ticker_interval,
|
||||
refresh_pairs=False)
|
||||
refresh_pairs=False,
|
||||
timerange=timerange)
|
||||
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.
|
||||
# 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:
|
||||
# array <- sum dataframes[*]['close'] / num_items dataframes
|
||||
# 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
|
||||
#
|
||||
|
||||
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True)
|
||||
fig.suptitle('total profit')
|
||||
ax1.plot(avgclose, label='avgclose')
|
||||
ax2.plot(pg, label='profit')
|
||||
ax1.legend(loc='upper left')
|
||||
ax2.legend(loc='upper left')
|
||||
avgclose = go.Scattergl(
|
||||
x=dates,
|
||||
y=avgclose,
|
||||
name='Avg close price',
|
||||
)
|
||||
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:
|
||||
pg = make_profit_array(data, max_x, pair)
|
||||
ax3.plot(pg, label=pair)
|
||||
ax3.legend(loc='upper left')
|
||||
# black background to easier see multiple colors
|
||||
ax3.set_facecolor('black')
|
||||
pair_profit = go.Scattergl(
|
||||
x=dates,
|
||||
y=pg,
|
||||
name=pair,
|
||||
)
|
||||
fig.append_trace(pair_profit, 3, 1)
|
||||
|
||||
# Fine-tune figure; make subplots close to each other and hide x ticks for
|
||||
# all but bottom plot.
|
||||
fig.subplots_adjust(hspace=0)
|
||||
plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False)
|
||||
plt.show()
|
||||
plot(fig, filename='freqtrade-profit-plot.html')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
Loading…
Reference in New Issue
Block a user