Merge branch 'lev-strat' into lev-freqtradebot
This commit is contained in:
@@ -6,6 +6,7 @@ from copy import deepcopy
|
||||
from datetime import datetime, timedelta
|
||||
from functools import reduce
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from unittest.mock import MagicMock, Mock, PropertyMock
|
||||
|
||||
import arrow
|
||||
@@ -18,6 +19,7 @@ from freqtrade.commands import Arguments
|
||||
from freqtrade.data.converter import ohlcv_to_dataframe
|
||||
from freqtrade.edge import Edge, PairInfo
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.enums.signaltype import SignalDirection
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.persistence import LocalTrade, Trade, init_db
|
||||
@@ -182,13 +184,35 @@ def get_patched_worker(mocker, config) -> Worker:
|
||||
return Worker(args=None, config=config)
|
||||
|
||||
|
||||
def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, None)) -> None:
|
||||
def patch_get_signal(freqtrade: FreqtradeBot, enter_long=True, exit_long=False,
|
||||
enter_short=False, exit_short=False, enter_tag: Optional[str] = None) -> None:
|
||||
"""
|
||||
:param mocker: mocker to patch IStrategy class
|
||||
:param value: which value IStrategy.get_signal() must return
|
||||
(buy, sell, buy_tag)
|
||||
:return: None
|
||||
"""
|
||||
freqtrade.strategy.get_signal = lambda e, s, x: value
|
||||
# returns (Signal-direction, signaname)
|
||||
def patched_get_entry_signal(*args, **kwargs):
|
||||
direction = None
|
||||
if enter_long and not any([exit_long, enter_short]):
|
||||
direction = SignalDirection.LONG
|
||||
if enter_short and not any([exit_short, enter_long]):
|
||||
direction = SignalDirection.SHORT
|
||||
|
||||
return direction, enter_tag
|
||||
|
||||
freqtrade.strategy.get_entry_signal = patched_get_entry_signal
|
||||
|
||||
def patched_get_exit_signal(pair, timeframe, dataframe, is_short):
|
||||
if is_short:
|
||||
return enter_short, exit_short
|
||||
else:
|
||||
return enter_long, exit_long
|
||||
|
||||
# returns (enter, exit)
|
||||
freqtrade.strategy.get_exit_signal = patched_get_exit_signal
|
||||
|
||||
freqtrade.exchange.refresh_latest_ohlcv = lambda p: None
|
||||
|
||||
|
||||
|
@@ -44,14 +44,20 @@ def _get_frame_time_from_offset(offset):
|
||||
|
||||
|
||||
def _build_backtest_dataframe(data):
|
||||
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell']
|
||||
columns = columns + ['buy_tag'] if len(data[0]) == 9 else columns
|
||||
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'enter_long', 'exit_long',
|
||||
'enter_short', 'exit_short']
|
||||
if len(data[0]) == 8:
|
||||
# No short columns
|
||||
data = [d + [0, 0] for d in data]
|
||||
columns = columns + ['long_tag'] if len(data[0]) == 11 else columns
|
||||
|
||||
frame = DataFrame.from_records(data, columns=columns)
|
||||
frame['date'] = frame['date'].apply(_get_frame_time_from_offset)
|
||||
# Ensure floats are in place
|
||||
for column in ['open', 'high', 'low', 'close', 'volume']:
|
||||
frame[column] = frame[column].astype('float64')
|
||||
if 'buy_tag' not in columns:
|
||||
frame['buy_tag'] = None
|
||||
if 'long_tag' not in columns:
|
||||
frame['long_tag'] = None
|
||||
if 'short_tag' not in columns:
|
||||
frame['short_tag'] = None
|
||||
return frame
|
||||
|
271
tests/optimize/hyperopts/short_hyperopt.py
Normal file
271
tests/optimize/hyperopts/short_hyperopt.py
Normal file
@@ -0,0 +1,271 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
|
||||
from functools import reduce
|
||||
from typing import Any, Callable, Dict, List
|
||||
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame
|
||||
from skopt.space import Categorical, Dimension, Integer
|
||||
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
||||
|
||||
|
||||
class DefaultHyperOpt(IHyperOpt):
|
||||
"""
|
||||
Default hyperopt provided by the Freqtrade bot.
|
||||
You can override it with your own Hyperopt
|
||||
"""
|
||||
@staticmethod
|
||||
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Add several indicators needed for buy and sell strategies defined below.
|
||||
"""
|
||||
# ADX
|
||||
dataframe['adx'] = ta.ADX(dataframe)
|
||||
# MACD
|
||||
macd = ta.MACD(dataframe)
|
||||
dataframe['macd'] = macd['macd']
|
||||
dataframe['macdsignal'] = macd['macdsignal']
|
||||
# MFI
|
||||
dataframe['mfi'] = ta.MFI(dataframe)
|
||||
# RSI
|
||||
dataframe['rsi'] = ta.RSI(dataframe)
|
||||
# Stochastic Fast
|
||||
stoch_fast = ta.STOCHF(dataframe)
|
||||
dataframe['fastd'] = stoch_fast['fastd']
|
||||
# Minus-DI
|
||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||
# Bollinger bands
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||
dataframe['bb_lowerband'] = bollinger['lower']
|
||||
dataframe['bb_upperband'] = bollinger['upper']
|
||||
# SAR
|
||||
dataframe['sar'] = ta.SAR(dataframe)
|
||||
|
||||
return dataframe
|
||||
|
||||
@staticmethod
|
||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Define the buy strategy parameters to be used by Hyperopt.
|
||||
"""
|
||||
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Buy strategy Hyperopt will build and use.
|
||||
"""
|
||||
long_conditions = []
|
||||
short_conditions = []
|
||||
|
||||
# GUARDS AND TRENDS
|
||||
if 'mfi-enabled' in params and params['mfi-enabled']:
|
||||
long_conditions.append(dataframe['mfi'] < params['mfi-value'])
|
||||
short_conditions.append(dataframe['mfi'] > params['short-mfi-value'])
|
||||
if 'fastd-enabled' in params and params['fastd-enabled']:
|
||||
long_conditions.append(dataframe['fastd'] < params['fastd-value'])
|
||||
short_conditions.append(dataframe['fastd'] > params['short-fastd-value'])
|
||||
if 'adx-enabled' in params and params['adx-enabled']:
|
||||
long_conditions.append(dataframe['adx'] > params['adx-value'])
|
||||
short_conditions.append(dataframe['adx'] < params['short-adx-value'])
|
||||
if 'rsi-enabled' in params and params['rsi-enabled']:
|
||||
long_conditions.append(dataframe['rsi'] < params['rsi-value'])
|
||||
short_conditions.append(dataframe['rsi'] > params['short-rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if 'trigger' in params:
|
||||
if params['trigger'] == 'boll':
|
||||
long_conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
short_conditions.append(dataframe['close'] > dataframe['bb_upperband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
long_conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'],
|
||||
dataframe['macdsignal']
|
||||
))
|
||||
short_conditions.append(qtpylib.crossed_below(
|
||||
dataframe['macd'],
|
||||
dataframe['macdsignal']
|
||||
))
|
||||
if params['trigger'] == 'sar_reversal':
|
||||
long_conditions.append(qtpylib.crossed_above(
|
||||
dataframe['close'],
|
||||
dataframe['sar']
|
||||
))
|
||||
short_conditions.append(qtpylib.crossed_below(
|
||||
dataframe['close'],
|
||||
dataframe['sar']
|
||||
))
|
||||
|
||||
if long_conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, long_conditions),
|
||||
'buy'] = 1
|
||||
|
||||
if short_conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, short_conditions),
|
||||
'enter_short'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
return populate_buy_trend
|
||||
|
||||
@staticmethod
|
||||
def indicator_space() -> List[Dimension]:
|
||||
"""
|
||||
Define your Hyperopt space for searching buy strategy parameters.
|
||||
"""
|
||||
return [
|
||||
Integer(10, 25, name='mfi-value'),
|
||||
Integer(15, 45, name='fastd-value'),
|
||||
Integer(20, 50, name='adx-value'),
|
||||
Integer(20, 40, name='rsi-value'),
|
||||
Integer(75, 90, name='short-mfi-value'),
|
||||
Integer(55, 85, name='short-fastd-value'),
|
||||
Integer(50, 80, name='short-adx-value'),
|
||||
Integer(60, 80, name='short-rsi-value'),
|
||||
Categorical([True, False], name='mfi-enabled'),
|
||||
Categorical([True, False], name='fastd-enabled'),
|
||||
Categorical([True, False], name='adx-enabled'),
|
||||
Categorical([True, False], name='rsi-enabled'),
|
||||
Categorical(['boll', 'macd_cross_signal', 'sar_reversal'], name='trigger')
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Define the sell strategy parameters to be used by Hyperopt.
|
||||
"""
|
||||
def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Sell strategy Hyperopt will build and use.
|
||||
"""
|
||||
exit_long_conditions = []
|
||||
exit_short_conditions = []
|
||||
|
||||
# GUARDS AND TRENDS
|
||||
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
|
||||
exit_long_conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
|
||||
exit_short_conditions.append(dataframe['mfi'] < params['exit-short-mfi-value'])
|
||||
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
|
||||
exit_long_conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
|
||||
exit_short_conditions.append(dataframe['fastd'] < params['exit-short-fastd-value'])
|
||||
if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
|
||||
exit_long_conditions.append(dataframe['adx'] < params['sell-adx-value'])
|
||||
exit_short_conditions.append(dataframe['adx'] > params['exit-short-adx-value'])
|
||||
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
|
||||
exit_long_conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
|
||||
exit_short_conditions.append(dataframe['rsi'] < params['exit-short-rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if 'sell-trigger' in params:
|
||||
if params['sell-trigger'] == 'sell-boll':
|
||||
exit_long_conditions.append(dataframe['close'] > dataframe['bb_upperband'])
|
||||
exit_short_conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['sell-trigger'] == 'sell-macd_cross_signal':
|
||||
exit_long_conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macdsignal'],
|
||||
dataframe['macd']
|
||||
))
|
||||
exit_short_conditions.append(qtpylib.crossed_below(
|
||||
dataframe['macdsignal'],
|
||||
dataframe['macd']
|
||||
))
|
||||
if params['sell-trigger'] == 'sell-sar_reversal':
|
||||
exit_long_conditions.append(qtpylib.crossed_above(
|
||||
dataframe['sar'],
|
||||
dataframe['close']
|
||||
))
|
||||
exit_short_conditions.append(qtpylib.crossed_below(
|
||||
dataframe['sar'],
|
||||
dataframe['close']
|
||||
))
|
||||
|
||||
if exit_long_conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, exit_long_conditions),
|
||||
'sell'] = 1
|
||||
|
||||
if exit_short_conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, exit_short_conditions),
|
||||
'exit-short'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
return populate_sell_trend
|
||||
|
||||
@staticmethod
|
||||
def sell_indicator_space() -> List[Dimension]:
|
||||
"""
|
||||
Define your Hyperopt space for searching sell strategy parameters.
|
||||
"""
|
||||
return [
|
||||
Integer(75, 100, name='sell-mfi-value'),
|
||||
Integer(50, 100, name='sell-fastd-value'),
|
||||
Integer(50, 100, name='sell-adx-value'),
|
||||
Integer(60, 100, name='sell-rsi-value'),
|
||||
Integer(1, 25, name='exit-short-mfi-value'),
|
||||
Integer(1, 50, name='exit-short-fastd-value'),
|
||||
Integer(1, 50, name='exit-short-adx-value'),
|
||||
Integer(1, 40, name='exit-short-rsi-value'),
|
||||
Categorical([True, False], name='sell-mfi-enabled'),
|
||||
Categorical([True, False], name='sell-fastd-enabled'),
|
||||
Categorical([True, False], name='sell-adx-enabled'),
|
||||
Categorical([True, False], name='sell-rsi-enabled'),
|
||||
Categorical(['sell-boll',
|
||||
'sell-macd_cross_signal',
|
||||
'sell-sar_reversal'],
|
||||
name='sell-trigger')
|
||||
]
|
||||
|
||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators. Should be a copy of same method from strategy.
|
||||
Must align to populate_indicators in this file.
|
||||
Only used when --spaces does not include buy space.
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['close'] < dataframe['bb_lowerband']) &
|
||||
(dataframe['mfi'] < 16) &
|
||||
(dataframe['adx'] > 25) &
|
||||
(dataframe['rsi'] < 21)
|
||||
),
|
||||
'buy'] = 1
|
||||
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['close'] > dataframe['bb_upperband']) &
|
||||
(dataframe['mfi'] < 84) &
|
||||
(dataframe['adx'] > 75) &
|
||||
(dataframe['rsi'] < 79)
|
||||
),
|
||||
'enter_short'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators. Should be a copy of same method from strategy.
|
||||
Must align to populate_indicators in this file.
|
||||
Only used when --spaces does not include sell space.
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(qtpylib.crossed_above(
|
||||
dataframe['macdsignal'], dataframe['macd']
|
||||
)) &
|
||||
(dataframe['fastd'] > 54)
|
||||
),
|
||||
'sell'] = 1
|
||||
|
||||
dataframe.loc[
|
||||
(
|
||||
(qtpylib.crossed_below(
|
||||
dataframe['macdsignal'], dataframe['macd']
|
||||
)) &
|
||||
(dataframe['fastd'] < 46)
|
||||
),
|
||||
'exit_short'] = 1
|
||||
|
||||
return dataframe
|
@@ -519,12 +519,12 @@ tc32 = BTContainer(data=[
|
||||
# Test 33: trailing_stop should be triggered immediately on trade open candle.
|
||||
# stop-loss: 1%, ROI: 10% (should not apply)
|
||||
tc33 = BTContainer(data=[
|
||||
# D O H L C V B S BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 'buy_signal_01'],
|
||||
[1, 5000, 5500, 5000, 4900, 6172, 0, 0, None], # enter trade (signal on last candle) and stop
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, None],
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, None],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, None]],
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 0, 0, 'buy_signal_01'],
|
||||
[1, 5000, 5500, 5000, 4900, 6172, 0, 0, 0, 0, None], # enter trade and stop
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, 0, 0, None],
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0, None],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, 0, 0, None]],
|
||||
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
|
||||
trailing_stop_positive=0.01, use_custom_stoploss=True,
|
||||
@@ -571,6 +571,7 @@ TESTS = [
|
||||
tc31,
|
||||
tc32,
|
||||
tc33,
|
||||
# TODO-lev: Add tests for short here
|
||||
]
|
||||
|
||||
|
||||
|
@@ -123,12 +123,14 @@ def _trend(signals, buy_value, sell_value):
|
||||
n = len(signals['low'])
|
||||
buy = np.zeros(n)
|
||||
sell = np.zeros(n)
|
||||
for i in range(0, len(signals['buy'])):
|
||||
for i in range(0, len(signals['date'])):
|
||||
if random.random() > 0.5: # Both buy and sell signals at same timeframe
|
||||
buy[i] = buy_value
|
||||
sell[i] = sell_value
|
||||
signals['buy'] = buy
|
||||
signals['sell'] = sell
|
||||
signals['enter_long'] = buy
|
||||
signals['exit_long'] = sell
|
||||
signals['enter_short'] = 0
|
||||
signals['exit_short'] = 0
|
||||
return signals
|
||||
|
||||
|
||||
@@ -143,8 +145,10 @@ def _trend_alternate(dataframe=None, metadata=None):
|
||||
buy[i] = 1
|
||||
else:
|
||||
sell[i] = 1
|
||||
signals['buy'] = buy
|
||||
signals['sell'] = sell
|
||||
signals['enter_long'] = buy
|
||||
signals['exit_long'] = sell
|
||||
signals['enter_short'] = 0
|
||||
signals['exit_short'] = 0
|
||||
return dataframe
|
||||
|
||||
|
||||
@@ -508,41 +512,47 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
0.0012, # High
|
||||
'', # Buy Signal Name
|
||||
]
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert isinstance(trade, LocalTrade)
|
||||
assert trade.stake_amount == 495
|
||||
|
||||
# Fake 2 trades, so there's not enough amount for the next trade left.
|
||||
LocalTrade.trades_open.append(trade)
|
||||
LocalTrade.trades_open.append(trade)
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert trade is None
|
||||
LocalTrade.trades_open.pop()
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert trade is not None
|
||||
|
||||
backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert trade
|
||||
assert trade.stake_amount == 123.5
|
||||
|
||||
# In case of error - use proposed stake
|
||||
backtesting.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert trade
|
||||
assert trade.stake_amount == 495
|
||||
assert trade.is_short is False
|
||||
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='short')
|
||||
assert trade
|
||||
assert trade.stake_amount == 495
|
||||
assert trade.is_short is True
|
||||
|
||||
# Stake-amount too high!
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0)
|
||||
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert trade is None
|
||||
|
||||
# Stake-amount throwing error
|
||||
mocker.patch("freqtrade.wallets.Wallets.get_trade_stake_amount",
|
||||
side_effect=DependencyException)
|
||||
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert trade is None
|
||||
|
||||
backtesting.cleanup()
|
||||
@@ -560,47 +570,54 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
|
||||
pair = 'UNITTEST/BTC'
|
||||
row = [
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=55, tzinfo=timezone.utc),
|
||||
1, # Buy
|
||||
200, # Open
|
||||
201, # Close
|
||||
0, # Sell
|
||||
195, # Low
|
||||
201.5, # High
|
||||
'', # Buy Signal Name
|
||||
195, # Low
|
||||
201, # Close
|
||||
1, # enter_long
|
||||
0, # exit_long
|
||||
0, # enter_short
|
||||
0, # exit_hsort
|
||||
'', # Long Signal Name
|
||||
'', # Short Signal Name
|
||||
]
|
||||
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert isinstance(trade, LocalTrade)
|
||||
|
||||
row_sell = [
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
|
||||
0, # Buy
|
||||
200, # Open
|
||||
201, # Close
|
||||
0, # Sell
|
||||
195, # Low
|
||||
210.5, # High
|
||||
'', # Buy Signal Name
|
||||
195, # Low
|
||||
201, # Close
|
||||
0, # enter_long
|
||||
0, # exit_long
|
||||
0, # enter_short
|
||||
0, # exit_short
|
||||
'', # long Signal Name
|
||||
'', # Short Signal Name
|
||||
]
|
||||
row_detail = pd.DataFrame(
|
||||
[
|
||||
[
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
|
||||
1, 200, 199, 0, 197, 200.1, '',
|
||||
200, 200.1, 197, 199, 1, 0, 0, 0, '', '',
|
||||
], [
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=1, tzinfo=timezone.utc),
|
||||
0, 199, 199.5, 0, 199, 199.7, '',
|
||||
199, 199.7, 199, 199.5, 0, 0, 0, 0, '', ''
|
||||
], [
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=2, tzinfo=timezone.utc),
|
||||
0, 199.5, 200.5, 0, 199, 200.8, '',
|
||||
199.5, 200.8, 199, 200.9, 0, 0, 0, 0, '', ''
|
||||
], [
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=3, tzinfo=timezone.utc),
|
||||
0, 200.5, 210.5, 0, 193, 210.5, '', # ROI sell (?)
|
||||
200.5, 210.5, 193, 210.5, 0, 0, 0, 0, '', '' # ROI sell (?)
|
||||
], [
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=4, tzinfo=timezone.utc),
|
||||
0, 200, 199, 0, 193, 200.1, '',
|
||||
200, 200.1, 193, 199, 0, 0, 0, 0, '', ''
|
||||
],
|
||||
], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"]
|
||||
], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
|
||||
'enter_short', 'exit_short', 'long_tag', 'short_tag']
|
||||
)
|
||||
|
||||
# No data available.
|
||||
@@ -610,11 +627,12 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
|
||||
assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc)
|
||||
|
||||
# Enter new trade
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
trade = backtesting._enter_trade(pair, row=row, direction='long')
|
||||
assert isinstance(trade, LocalTrade)
|
||||
# Assign empty ... no result.
|
||||
backtesting.detail_data[pair] = pd.DataFrame(
|
||||
[], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"])
|
||||
[], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
|
||||
'enter_short', 'exit_short', 'long_tag', 'short_tag'])
|
||||
|
||||
res = backtesting._get_sell_trade_entry(trade, row)
|
||||
assert res is None
|
||||
@@ -857,8 +875,10 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
multi = 20
|
||||
else:
|
||||
multi = 18
|
||||
dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
|
||||
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
|
||||
dataframe['enter_long'] = np.where(dataframe.index % multi == 0, 1, 0)
|
||||
dataframe['exit_long'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
|
||||
dataframe['enter_short'] = 0
|
||||
dataframe['exit_short'] = 0
|
||||
return dataframe
|
||||
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||
|
@@ -25,6 +25,9 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
from .hyperopts.hyperopt_test_sep_file import HyperoptTestSepFile
|
||||
|
||||
|
||||
# TODO-lev: This file
|
||||
|
||||
|
||||
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
@@ -448,6 +451,10 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None:
|
||||
'fastd-value': 20,
|
||||
'mfi-value': 20,
|
||||
'rsi-value': 20,
|
||||
'short-adx-value': 80,
|
||||
'short-fastd-value': 80,
|
||||
'short-mfi-value': 80,
|
||||
'short-rsi-value': 80,
|
||||
'adx-enabled': True,
|
||||
'fastd-enabled': True,
|
||||
'mfi-enabled': True,
|
||||
@@ -473,6 +480,10 @@ def test_sell_strategy_generator(hyperopt, testdatadir) -> None:
|
||||
'sell-fastd-value': 75,
|
||||
'sell-mfi-value': 80,
|
||||
'sell-rsi-value': 20,
|
||||
'exit-short-adx-value': 80,
|
||||
'exit-short-fastd-value': 25,
|
||||
'exit-short-mfi-value': 20,
|
||||
'exit-short-rsi-value': 80,
|
||||
'sell-adx-enabled': True,
|
||||
'sell-fastd-enabled': True,
|
||||
'sell-mfi-enabled': True,
|
||||
|
@@ -42,5 +42,6 @@ def test_strategy_test_v2(result, fee):
|
||||
rate=20000, time_in_force='gtc', sell_reason='roi',
|
||||
current_time=datetime.utcnow()) is True
|
||||
|
||||
# TODO-lev: Test for shorts?
|
||||
assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
|
||||
current_rate=20_000, current_profit=0.05) == strategy.stoploss
|
||||
|
@@ -1,4 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
from freqtrade.enums.signaltype import SignalDirection
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
@@ -30,28 +31,56 @@ _STRATEGY = StrategyTestV2(config={})
|
||||
_STRATEGY.dp = DataProvider({}, None, None)
|
||||
|
||||
|
||||
def test_returns_latest_signal(mocker, default_conf, ohlcv_history):
|
||||
def test_returns_latest_signal(ohlcv_history):
|
||||
ohlcv_history.loc[1, 'date'] = arrow.utcnow()
|
||||
# Take a copy to correctly modify the call
|
||||
mocked_history = ohlcv_history.copy()
|
||||
mocked_history['sell'] = 0
|
||||
mocked_history['buy'] = 0
|
||||
mocked_history.loc[1, 'sell'] = 1
|
||||
mocked_history['enter_long'] = 0
|
||||
mocked_history['exit_long'] = 0
|
||||
mocked_history['enter_short'] = 0
|
||||
mocked_history['exit_short'] = 0
|
||||
mocked_history.loc[1, 'exit_long'] = 1
|
||||
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None)
|
||||
mocked_history.loc[1, 'sell'] = 0
|
||||
mocked_history.loc[1, 'buy'] = 1
|
||||
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, True)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False)
|
||||
mocked_history.loc[1, 'exit_long'] = 0
|
||||
mocked_history.loc[1, 'enter_long'] = 1
|
||||
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None)
|
||||
mocked_history.loc[1, 'sell'] = 0
|
||||
mocked_history.loc[1, 'buy'] = 0
|
||||
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history
|
||||
) == (SignalDirection.LONG, None)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (True, False)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False)
|
||||
mocked_history.loc[1, 'exit_long'] = 0
|
||||
mocked_history.loc[1, 'enter_long'] = 0
|
||||
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
|
||||
mocked_history.loc[1, 'sell'] = 0
|
||||
mocked_history.loc[1, 'buy'] = 1
|
||||
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False)
|
||||
mocked_history.loc[1, 'exit_long'] = 0
|
||||
mocked_history.loc[1, 'enter_long'] = 1
|
||||
mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01'
|
||||
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01')
|
||||
assert _STRATEGY.get_entry_signal(
|
||||
'ETH/BTC', '5m', mocked_history) == (SignalDirection.LONG, 'buy_signal_01')
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (True, False)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, False)
|
||||
|
||||
mocked_history.loc[1, 'exit_long'] = 0
|
||||
mocked_history.loc[1, 'enter_long'] = 0
|
||||
mocked_history.loc[1, 'enter_short'] = 1
|
||||
mocked_history.loc[1, 'exit_short'] = 0
|
||||
assert _STRATEGY.get_entry_signal(
|
||||
'ETH/BTC', '5m', mocked_history) == (SignalDirection.SHORT, None)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (True, False)
|
||||
|
||||
mocked_history.loc[1, 'enter_short'] = 0
|
||||
mocked_history.loc[1, 'exit_short'] = 1
|
||||
assert _STRATEGY.get_entry_signal(
|
||||
'ETH/BTC', '5m', mocked_history) == (None, None)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False)
|
||||
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history, True) == (False, True)
|
||||
|
||||
|
||||
def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
|
||||
@@ -67,18 +96,18 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
|
||||
assert log_has('Empty dataframe for pair ETH/BTC', caplog)
|
||||
|
||||
|
||||
def test_get_signal_empty(default_conf, mocker, caplog):
|
||||
assert (False, False, None) == _STRATEGY.get_signal(
|
||||
def test_get_signal_empty(default_conf, caplog):
|
||||
assert (None, None) == _STRATEGY.get_latest_candle(
|
||||
'foo', default_conf['timeframe'], DataFrame()
|
||||
)
|
||||
assert log_has('Empty candle (OHLCV) data for pair foo', caplog)
|
||||
caplog.clear()
|
||||
|
||||
assert (False, False, None) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None)
|
||||
assert (None, None) == _STRATEGY.get_latest_candle('bar', default_conf['timeframe'], None)
|
||||
assert log_has('Empty candle (OHLCV) data for pair bar', caplog)
|
||||
caplog.clear()
|
||||
|
||||
assert (False, False, None) == _STRATEGY.get_signal(
|
||||
assert (None, None) == _STRATEGY.get_latest_candle(
|
||||
'baz',
|
||||
default_conf['timeframe'],
|
||||
DataFrame([])
|
||||
@@ -86,7 +115,7 @@ def test_get_signal_empty(default_conf, mocker, caplog):
|
||||
assert log_has('Empty candle (OHLCV) data for pair baz', caplog)
|
||||
|
||||
|
||||
def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ohlcv_history):
|
||||
def test_get_signal_exception_valueerror(mocker, caplog, ohlcv_history):
|
||||
caplog.set_level(logging.INFO)
|
||||
mocker.patch.object(_STRATEGY.dp, 'ohlcv', return_value=ohlcv_history)
|
||||
mocker.patch.object(
|
||||
@@ -111,14 +140,14 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history):
|
||||
ohlcv_history.loc[1, 'date'] = arrow.utcnow().shift(minutes=-16)
|
||||
# Take a copy to correctly modify the call
|
||||
mocked_history = ohlcv_history.copy()
|
||||
mocked_history['sell'] = 0
|
||||
mocked_history['buy'] = 0
|
||||
mocked_history.loc[1, 'buy'] = 1
|
||||
mocked_history['exit_long'] = 0
|
||||
mocked_history['enter_long'] = 0
|
||||
mocked_history.loc[1, 'enter_long'] = 1
|
||||
|
||||
caplog.set_level(logging.INFO)
|
||||
mocker.patch.object(_STRATEGY, 'assert_df')
|
||||
|
||||
assert (False, False, None) == _STRATEGY.get_signal(
|
||||
assert (None, None) == _STRATEGY.get_latest_candle(
|
||||
'xyz',
|
||||
default_conf['timeframe'],
|
||||
mocked_history
|
||||
@@ -134,13 +163,13 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
|
||||
mocked_history = ohlcv_history.copy()
|
||||
# Intentionally don't set sell column
|
||||
# mocked_history['sell'] = 0
|
||||
mocked_history['buy'] = 0
|
||||
mocked_history.loc[1, 'buy'] = 1
|
||||
mocked_history['enter_long'] = 0
|
||||
mocked_history.loc[1, 'enter_long'] = 1
|
||||
|
||||
caplog.set_level(logging.INFO)
|
||||
mocker.patch.object(_STRATEGY, 'assert_df')
|
||||
|
||||
assert (True, False, None) == _STRATEGY.get_signal(
|
||||
assert (SignalDirection.LONG, None) == _STRATEGY.get_entry_signal(
|
||||
'xyz',
|
||||
default_conf['timeframe'],
|
||||
mocked_history
|
||||
@@ -452,27 +481,35 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
|
||||
)
|
||||
|
||||
now = arrow.utcnow().datetime
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter=False, exit_=False,
|
||||
low=None, high=None)
|
||||
|
||||
assert res.sell_flag is False
|
||||
assert res.sell_type == SellType.NONE
|
||||
|
||||
strategy.custom_sell = MagicMock(return_value=True)
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter=False, exit_=False,
|
||||
low=None, high=None)
|
||||
assert res.sell_flag is True
|
||||
assert res.sell_type == SellType.CUSTOM_SELL
|
||||
assert res.sell_reason == 'custom_sell'
|
||||
|
||||
strategy.custom_sell = MagicMock(return_value='hello world')
|
||||
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter=False, exit_=False,
|
||||
low=None, high=None)
|
||||
assert res.sell_type == SellType.CUSTOM_SELL
|
||||
assert res.sell_flag is True
|
||||
assert res.sell_reason == 'hello world'
|
||||
|
||||
caplog.clear()
|
||||
strategy.custom_sell = MagicMock(return_value='h' * 100)
|
||||
res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
|
||||
res = strategy.should_exit(trade, 1, now,
|
||||
enter=False, exit_=False,
|
||||
low=None, high=None)
|
||||
assert res.sell_type == SellType.CUSTOM_SELL
|
||||
assert res.sell_flag is True
|
||||
assert res.sell_reason == 'h' * 64
|
||||
|
@@ -118,10 +118,12 @@ def test_strategy(result, default_conf):
|
||||
assert 'adx' in df_indicators
|
||||
|
||||
dataframe = strategy.advise_buy(df_indicators, metadata=metadata)
|
||||
assert 'buy' in dataframe.columns
|
||||
assert 'buy' not in dataframe.columns
|
||||
assert 'enter_long' in dataframe.columns
|
||||
|
||||
dataframe = strategy.advise_sell(df_indicators, metadata=metadata)
|
||||
assert 'sell' in dataframe.columns
|
||||
assert 'sell' not in dataframe.columns
|
||||
assert 'exit_long' in dataframe.columns
|
||||
|
||||
|
||||
def test_strategy_override_minimal_roi(caplog, default_conf):
|
||||
@@ -394,7 +396,7 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
||||
caplog)
|
||||
|
||||
|
||||
def test_strategy_interface_versioning(result, monkeypatch, default_conf):
|
||||
def test_strategy_interface_versioning(result, default_conf):
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
metadata = {'pair': 'ETH/BTC'}
|
||||
@@ -411,8 +413,11 @@ def test_strategy_interface_versioning(result, monkeypatch, default_conf):
|
||||
|
||||
enterdf = strategy.advise_buy(result, metadata=metadata)
|
||||
assert isinstance(enterdf, DataFrame)
|
||||
assert 'buy' in enterdf.columns
|
||||
|
||||
assert 'buy' not in enterdf.columns
|
||||
assert 'enter_long' in enterdf.columns
|
||||
|
||||
exitdf = strategy.advise_sell(result, metadata=metadata)
|
||||
assert isinstance(exitdf, DataFrame)
|
||||
assert 'sell' in exitdf
|
||||
assert 'sell' not in exitdf
|
||||
assert 'exit_long' in exitdf
|
||||
|
@@ -256,7 +256,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf
|
||||
|
||||
# stoploss shoud be hit
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert log_has('Executing Sell for NEO/BTC. Reason: stop_loss', caplog)
|
||||
assert log_has('Exit for NEO/BTC detected. Reason: stop_loss', caplog)
|
||||
assert trade.sell_reason == SellType.STOP_LOSS.value
|
||||
|
||||
|
||||
@@ -542,7 +542,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
|
||||
)
|
||||
default_conf['stake_amount'] = 10
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade, value=(False, False, None))
|
||||
patch_get_signal(freqtrade, enter_long=False)
|
||||
|
||||
Trade.query = MagicMock()
|
||||
Trade.query.filter = MagicMock()
|
||||
@@ -763,9 +763,10 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
|
||||
refresh_latest_ohlcv=refresh_mock,
|
||||
)
|
||||
inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")])
|
||||
mocker.patch(
|
||||
'freqtrade.strategy.interface.IStrategy.get_signal',
|
||||
return_value=(False, False, '')
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.strategy.interface.IStrategy',
|
||||
get_exit_signal=MagicMock(return_value=(False, False)),
|
||||
get_entry_signal=MagicMock(return_value=(None, None))
|
||||
)
|
||||
mocker.patch('time.sleep', return_value=None)
|
||||
|
||||
@@ -1944,7 +1945,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order_open, limi
|
||||
assert trade.is_open is True
|
||||
freqtrade.wallets.update()
|
||||
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.open_order_id == limit_sell_order['id']
|
||||
|
||||
@@ -1972,7 +1973,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade, value=(True, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
|
||||
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
|
||||
|
||||
freqtrade.enter_positions()
|
||||
@@ -1991,7 +1992,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
||||
assert trades[0].is_open is True
|
||||
|
||||
# Buy and Sell are not triggering, so doing nothing ...
|
||||
patch_get_signal(freqtrade, value=(False, False, None))
|
||||
patch_get_signal(freqtrade, enter_long=False)
|
||||
assert freqtrade.handle_trade(trades[0]) is False
|
||||
trades = Trade.query.all()
|
||||
nb_trades = len(trades)
|
||||
@@ -1999,7 +2000,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
||||
assert trades[0].is_open is True
|
||||
|
||||
# Buy and Sell are triggering, so doing nothing ...
|
||||
patch_get_signal(freqtrade, value=(True, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
|
||||
assert freqtrade.handle_trade(trades[0]) is False
|
||||
trades = Trade.query.all()
|
||||
nb_trades = len(trades)
|
||||
@@ -2007,7 +2008,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
||||
assert trades[0].is_open is True
|
||||
|
||||
# Sell is triggering, guess what : we are Selling!
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
trades = Trade.query.all()
|
||||
assert freqtrade.handle_trade(trades[0]) is True
|
||||
|
||||
@@ -2041,7 +2042,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
|
||||
# we might just want to check if we are in a sell condition without
|
||||
# executing
|
||||
# if ROI is reached we must sell
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade)
|
||||
assert log_has("ETH/BTC - Required profit reached. sell_type=SellType.ROI",
|
||||
caplog)
|
||||
@@ -2071,10 +2072,10 @@ def test_handle_trade_use_sell_signal(default_conf, ticker, limit_buy_order_open
|
||||
trade = Trade.query.first()
|
||||
trade.is_open = True
|
||||
|
||||
patch_get_signal(freqtrade, value=(False, False, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=False)
|
||||
assert not freqtrade.handle_trade(trade)
|
||||
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade)
|
||||
assert log_has("ETH/BTC - Sell signal received. sell_type=SellType.SELL_SIGNAL",
|
||||
caplog)
|
||||
@@ -3196,7 +3197,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
freqtrade.wallets.update()
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
freqtrade.strategy.sell_profit_offset = 0.0
|
||||
@@ -3234,7 +3235,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_bu
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
freqtrade.wallets.update()
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
|
||||
@@ -3268,7 +3269,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o
|
||||
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
|
||||
@@ -3303,7 +3304,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
freqtrade.wallets.update()
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
|
||||
@@ -3335,7 +3336,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
|
||||
trade = Trade.query.first()
|
||||
amnt = trade.amount
|
||||
trade.update(limit_buy_order)
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985))
|
||||
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
@@ -3460,11 +3461,11 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
freqtrade.wallets.update()
|
||||
patch_get_signal(freqtrade, value=(True, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
# Test if buy-signal is absent (should sell due to roi = true)
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.ROI.value
|
||||
|
||||
@@ -3745,11 +3746,11 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_b
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
# Sell due to min_roi_reached
|
||||
patch_get_signal(freqtrade, value=(True, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=True, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
|
||||
# Test if buy-signal is absent
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
|
||||
@@ -4297,7 +4298,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order_open, limit_buy_o
|
||||
freqtrade.wallets.update()
|
||||
assert trade.is_open is True
|
||||
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
patch_get_signal(freqtrade, enter_long=False, exit_long=True)
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0]
|
||||
|
||||
|
@@ -72,7 +72,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
_notify_sell=MagicMock(),
|
||||
)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
|
||||
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
|
||||
mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=1000))
|
||||
|
||||
@@ -163,7 +163,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
SellCheckTuple(sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_type=SellType.NONE)]
|
||||
)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc = RPC(freqtrade)
|
||||
|
@@ -201,8 +201,8 @@ def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, t
|
||||
timerange = TimeRange(None, 'line', 0, -1000)
|
||||
data = history.load_pair_history(pair=pair, timeframe='1m',
|
||||
datadir=testdatadir, timerange=timerange)
|
||||
data['buy'] = 0
|
||||
data['sell'] = 0
|
||||
data['enter_long'] = 0
|
||||
data['exit_long'] = 0
|
||||
|
||||
indicators1 = []
|
||||
indicators2 = []
|
||||
@@ -261,12 +261,12 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
|
||||
buy = find_trace_in_fig_data(figure.data, "buy")
|
||||
assert isinstance(buy, go.Scatter)
|
||||
# All buy-signals should be plotted
|
||||
assert int(data.buy.sum()) == len(buy.x)
|
||||
assert int(data['enter_long'].sum()) == len(buy.x)
|
||||
|
||||
sell = find_trace_in_fig_data(figure.data, "sell")
|
||||
assert isinstance(sell, go.Scatter)
|
||||
# All buy-signals should be plotted
|
||||
assert int(data.sell.sum()) == len(sell.x)
|
||||
assert int(data['exit_long'].sum()) == len(sell.x)
|
||||
|
||||
assert find_trace_in_fig_data(figure.data, "Bollinger Band")
|
||||
|
||||
|
Reference in New Issue
Block a user