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 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
|
||||||
```
|
```
|
||||||
|
@ -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 = {}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
@ -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'
|
||||||
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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://', '')
|
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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')
|
||||||
|
@ -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():
|
||||||
|
@ -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']
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
@ -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()
|
||||||
|
@ -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])
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
|
||||||
|
@ -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__':
|
||||||
|
@ -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__':
|
||||||
|
Loading…
Reference in New Issue
Block a user