Merge branch 'develop' into interface_ordertimeoutcallback

This commit is contained in:
Matthias
2020-02-21 20:35:07 +01:00
104 changed files with 3583 additions and 1065 deletions

View File

@@ -0,0 +1,156 @@
# 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
class DefaultStrategy(IStrategy):
"""
Default Strategy provided by freqtrade bot.
Please do not modify this strategy, it's intended for internal use only.
Please look at the SampleStrategy in the user_data/strategy directory
or strategy repository https://github.com/freqtrade/freqtrade-strategies
for samples and inspiration.
"""
INTERFACE_VERSION = 2
# Minimal ROI designed for the strategy
minimal_roi = {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
}
# Optimal stoploss designed for the strategy
stoploss = -0.10
# Optimal ticker interval for the strategy
ticker_interval = '5m'
# Optional order type mapping
order_types = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
}
# Number of candles the strategy requires before producing valid signals
startup_candle_count: int = 20
# Optional time in force for orders
order_time_in_force = {
'buy': 'gtc',
'sell': 'gtc',
}
def informative_pairs(self):
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
These pair/interval combinations are non-tradeable, unless they are part
of the whitelist as well.
For more information, please consult the documentation
:return: List of tuples in the format (pair, interval)
Sample: return [("ETH/USDT", "5m"),
("BTC/USDT", "15m"),
]
"""
return []
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
# Momentum Indicator
# ------------------------------------
# ADX
dataframe['adx'] = ta.ADX(dataframe)
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# Minus Directional Indicator / Movement
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# Plus Directional Indicator / Movement
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# Stoch fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
# EMA - Exponential Moving Average
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['rsi'] < 35) &
(dataframe['fastd'] < 35) &
(dataframe['adx'] > 30) &
(dataframe['plus_di'] > 0.5)
) |
(
(dataframe['adx'] > 65) &
(dataframe['plus_di'] > 0.5)
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
(
(qtpylib.crossed_above(dataframe['rsi'], 70)) |
(qtpylib.crossed_above(dataframe['fastd'], 70))
) &
(dataframe['adx'] > 10) &
(dataframe['minus_di'] > 0)
) |
(
(dataframe['adx'] > 70) &
(dataframe['minus_di'] > 0.5)
),
'sell'] = 1
return dataframe

View File

@@ -0,0 +1,9 @@
# The strategy which fails to load due to non-existent dependency
import nonexiting_module # noqa
from freqtrade.strategy.interface import IStrategy
class TestStrategyLegacy(IStrategy):
pass

View File

@@ -1,6 +1,6 @@
from pandas import DataFrame
from freqtrade.strategy.default_strategy import DefaultStrategy
from .strats.default_strategy import DefaultStrategy
def test_default_strategy_structure():

View File

@@ -7,12 +7,13 @@ import arrow
from pandas import DataFrame
from freqtrade.configuration import TimeRange
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.history import load_tickerdata_file
from freqtrade.data.history import load_data
from freqtrade.persistence import Trade
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.resolvers import StrategyResolver
from tests.conftest import get_patched_exchange, log_has, log_has_re
from .strats.default_strategy import DefaultStrategy
# Avoid to reinit the same object again and again
_STRATEGY = DefaultStrategy(config={})
@@ -104,12 +105,12 @@ def test_get_signal_handles_exceptions(mocker, default_conf):
def test_tickerdata_to_dataframe(default_conf, testdatadir) -> None:
strategy = DefaultStrategy(default_conf)
default_conf.update({'strategy': 'DefaultStrategy'})
strategy = StrategyResolver.load_strategy(default_conf)
timerange = TimeRange.parse_timerange('1510694220-1510700340')
tick = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
fill_missing=True)}
tickerlist = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
fill_up_missing=True)
data = strategy.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 102 # partial candle was removed
@@ -120,7 +121,8 @@ def test_min_roi_reached(default_conf, fee) -> None:
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
{0: 0.1, 20: 0.05, 55: 0.01}]
for roi in min_roi_list:
strategy = DefaultStrategy(default_conf)
default_conf.update({'strategy': 'DefaultStrategy'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = roi
trade = Trade(
pair='ETH/BTC',
@@ -158,7 +160,8 @@ def test_min_roi_reached2(default_conf, fee) -> None:
},
]
for roi in min_roi_list:
strategy = DefaultStrategy(default_conf)
default_conf.update({'strategy': 'DefaultStrategy'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = roi
trade = Trade(
pair='ETH/BTC',
@@ -192,7 +195,8 @@ def test_min_roi_reached3(default_conf, fee) -> None:
30: 0.05,
55: 0.30,
}
strategy = DefaultStrategy(default_conf)
default_conf.update({'strategy': 'DefaultStrategy'})
strategy = StrategyResolver.load_strategy(default_conf)
strategy.minimal_roi = min_roi
trade = Trade(
pair='ETH/BTC',
@@ -292,7 +296,8 @@ def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) -
def test_is_pair_locked(default_conf):
strategy = DefaultStrategy(default_conf)
default_conf.update({'strategy': 'DefaultStrategy'})
strategy = StrategyResolver.load_strategy(default_conf)
# dict should be empty
assert not strategy._pair_locked_until

View File

@@ -2,7 +2,6 @@
import logging
import warnings
from base64 import urlsafe_b64encode
from os import path
from pathlib import Path
import pytest
@@ -15,7 +14,7 @@ from tests.conftest import log_has, log_has_re
def test_search_strategy():
default_location = Path(__file__).parent.parent.joinpath('strategy').resolve()
default_location = Path(__file__).parent / 'strats'
s, _ = StrategyResolver._search_object(
directory=default_location,
@@ -30,12 +29,23 @@ def test_search_strategy():
assert s is None
def test_search_all_strategies():
directory = Path(__file__).parent
strategies = StrategyResolver.search_all_objects(directory)
def test_search_all_strategies_no_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
assert isinstance(strategies, list)
assert len(strategies) == 2
assert isinstance(strategies[0], dict)
def test_search_all_strategies_with_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
assert isinstance(strategies, list)
assert len(strategies) == 3
assert isinstance(strategies[0], dict)
# with enum_failed=True search_all_objects() shall find 2 good strategies
# and 1 which fails to load
assert len([x for x in strategies if x['class'] is not None]) == 2
assert len([x for x in strategies if x['class'] is None]) == 1
def test_load_strategy(default_conf, result):
@@ -61,13 +71,12 @@ def test_load_strategy_base64(result, caplog, default_conf):
def test_load_strategy_invalid_directory(result, caplog, default_conf):
default_conf['strategy'] = 'DefaultStrategy'
extra_dir = Path.cwd() / 'some/path'
strategy = StrategyResolver._load_strategy('DefaultStrategy', config=default_conf,
extra_dir=extra_dir)
with pytest.raises(OperationalException):
StrategyResolver._load_strategy('DefaultStrategy', config=default_conf,
extra_dir=extra_dir)
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
assert 'rsi' in strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
def test_load_not_found_strategy(default_conf):
default_conf['strategy'] = 'NotFoundStrategy'
@@ -315,7 +324,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
@pytest.mark.filterwarnings("ignore:deprecated")
def test_deprecate_populate_indicators(result, default_conf):
default_location = path.join(path.dirname(path.realpath(__file__)))
default_location = Path(__file__).parent / "strats"
default_conf.update({'strategy': 'TestStrategyLegacy',
'strategy_path': default_location})
strategy = StrategyResolver.load_strategy(default_conf)
@@ -349,7 +358,7 @@ def test_deprecate_populate_indicators(result, default_conf):
@pytest.mark.filterwarnings("ignore:deprecated")
def test_call_deprecated_function(result, monkeypatch, default_conf):
default_location = path.join(path.dirname(path.realpath(__file__)))
default_location = Path(__file__).parent / "strats"
default_conf.update({'strategy': 'TestStrategyLegacy',
'strategy_path': default_location})
strategy = StrategyResolver.load_strategy(default_conf)