Merge branch 'feat/short' into pr/samgermain/5378
This commit is contained in:
@@ -10,10 +10,10 @@ import pytest
|
||||
|
||||
from freqtrade.commands import (start_convert_data, start_create_userdir, start_download_data,
|
||||
start_hyperopt_list, start_hyperopt_show, start_install_ui,
|
||||
start_list_data, start_list_exchanges, start_list_hyperopts,
|
||||
start_list_markets, start_list_strategies, start_list_timeframes,
|
||||
start_new_hyperopt, start_new_strategy, start_show_trades,
|
||||
start_test_pairlist, start_trading, start_webserver)
|
||||
start_list_data, start_list_exchanges, start_list_markets,
|
||||
start_list_strategies, start_list_timeframes, start_new_strategy,
|
||||
start_show_trades, start_test_pairlist, start_trading,
|
||||
start_webserver)
|
||||
from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui,
|
||||
get_ui_download_url, read_ui_version)
|
||||
from freqtrade.configuration import setup_utils_configuration
|
||||
@@ -32,8 +32,6 @@ def test_setup_utils_configuration():
|
||||
config = setup_utils_configuration(get_args(args), RunMode.OTHER)
|
||||
assert "exchange" in config
|
||||
assert config['dry_run'] is True
|
||||
assert config['exchange']['key'] == ''
|
||||
assert config['exchange']['secret'] == ''
|
||||
|
||||
|
||||
def test_start_trading_fail(mocker, caplog):
|
||||
@@ -519,37 +517,6 @@ def test_start_new_strategy_no_arg(mocker, caplog):
|
||||
start_new_strategy(get_args(args))
|
||||
|
||||
|
||||
def test_start_new_hyperopt(mocker, caplog):
|
||||
wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
|
||||
args = [
|
||||
"new-hyperopt",
|
||||
"--hyperopt",
|
||||
"CoolNewhyperopt"
|
||||
]
|
||||
start_new_hyperopt(get_args(args))
|
||||
|
||||
assert wt_mock.call_count == 1
|
||||
assert "CoolNewhyperopt" in wt_mock.call_args_list[0][0][0]
|
||||
assert log_has_re("Writing hyperopt to .*", caplog)
|
||||
|
||||
mocker.patch('freqtrade.commands.deploy_commands.setup_utils_configuration')
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
with pytest.raises(OperationalException,
|
||||
match=r".* already exists. Please choose another Hyperopt Name\."):
|
||||
start_new_hyperopt(get_args(args))
|
||||
|
||||
|
||||
def test_start_new_hyperopt_no_arg(mocker):
|
||||
args = [
|
||||
"new-hyperopt",
|
||||
]
|
||||
with pytest.raises(OperationalException,
|
||||
match="`new-hyperopt` requires --hyperopt to be set."):
|
||||
start_new_hyperopt(get_args(args))
|
||||
|
||||
|
||||
def test_start_install_ui(mocker):
|
||||
clean_mock = mocker.patch('freqtrade.commands.deploy_commands.clean_ui_subdir')
|
||||
get_url_mock = mocker.patch('freqtrade.commands.deploy_commands.get_ui_download_url',
|
||||
@@ -824,37 +791,20 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
||||
assert "legacy_strategy_v1.py" in captured.out
|
||||
assert "StrategyTestV2" in captured.out
|
||||
|
||||
|
||||
def test_start_list_hyperopts(mocker, caplog, capsys):
|
||||
|
||||
# Test color output
|
||||
args = [
|
||||
"list-hyperopts",
|
||||
"--hyperopt-path",
|
||||
str(Path(__file__).parent.parent / "optimize" / "hyperopts"),
|
||||
"-1"
|
||||
"list-strategies",
|
||||
"--strategy-path",
|
||||
str(Path(__file__).parent.parent / "strategy" / "strats"),
|
||||
]
|
||||
pargs = get_args(args)
|
||||
# pargs['config'] = None
|
||||
start_list_hyperopts(pargs)
|
||||
start_list_strategies(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "TestHyperoptLegacy" not in captured.out
|
||||
assert "legacy_hyperopt.py" not in captured.out
|
||||
assert "HyperoptTestSepFile" in captured.out
|
||||
assert "test_hyperopt.py" not in captured.out
|
||||
|
||||
# Test regular output
|
||||
args = [
|
||||
"list-hyperopts",
|
||||
"--hyperopt-path",
|
||||
str(Path(__file__).parent.parent / "optimize" / "hyperopts"),
|
||||
]
|
||||
pargs = get_args(args)
|
||||
# pargs['config'] = None
|
||||
start_list_hyperopts(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "TestHyperoptLegacy" not in captured.out
|
||||
assert "legacy_hyperopt.py" not in captured.out
|
||||
assert "HyperoptTestSepFile" in captured.out
|
||||
assert "TestStrategyLegacyV1" in captured.out
|
||||
assert "legacy_strategy_v1.py" in captured.out
|
||||
assert "StrategyTestV2" in captured.out
|
||||
assert "LOAD FAILED" in captured.out
|
||||
|
||||
|
||||
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||
|
@@ -6,7 +6,7 @@ from copy import deepcopy
|
||||
from datetime import datetime, timedelta
|
||||
from functools import reduce
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Optional, Tuple
|
||||
from unittest.mock import MagicMock, Mock, PropertyMock
|
||||
|
||||
import arrow
|
||||
@@ -286,6 +286,10 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True):
|
||||
Trade.query.session.flush()
|
||||
|
||||
|
||||
def get_sides(is_short: bool) -> Tuple[str, str]:
|
||||
return ("sell", "buy") if is_short else ("buy", "sell")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_coingekko(mocker) -> None:
|
||||
"""
|
||||
|
@@ -1,3 +1,4 @@
|
||||
from datetime import datetime, timezone
|
||||
from random import randint
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -5,7 +6,7 @@ import ccxt
|
||||
import pytest
|
||||
|
||||
from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException
|
||||
from tests.conftest import get_patched_exchange
|
||||
from tests.conftest import get_mock_coro, get_patched_exchange, log_has_re
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
||||
|
||||
@@ -105,3 +106,35 @@ def test_stoploss_adjust_binance(mocker, default_conf):
|
||||
# Test with invalid order case
|
||||
order['type'] = 'stop_loss'
|
||||
assert not exchange.stoploss_adjust(1501, order)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog):
|
||||
ohlcv = [
|
||||
[
|
||||
int((datetime.now(timezone.utc).timestamp() - 1000) * 1000),
|
||||
1, # open
|
||||
2, # high
|
||||
3, # low
|
||||
4, # close
|
||||
5, # volume (in quote currency)
|
||||
]
|
||||
]
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, id='binance')
|
||||
# Monkey-patch async function
|
||||
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
res = await exchange._async_get_historic_ohlcv(pair, "5m",
|
||||
1500000000000, is_new_pair=False)
|
||||
# Call with very old timestamp - causes tons of requests
|
||||
assert exchange._api_async.fetch_ohlcv.call_count > 400
|
||||
# assert res == ohlcv
|
||||
exchange._api_async.fetch_ohlcv.reset_mock()
|
||||
res = await exchange._async_get_historic_ohlcv(pair, "5m", 1500000000000, is_new_pair=True)
|
||||
|
||||
# Called twice - one "init" call - and one to get the actual data.
|
||||
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
||||
assert res == ohlcv
|
||||
assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog)
|
||||
|
@@ -54,6 +54,8 @@ EXCHANGES = {
|
||||
def exchange_conf():
|
||||
config = get_default_conf((Path(__file__).parent / "testdata").resolve())
|
||||
config['exchange']['pair_whitelist'] = []
|
||||
config['exchange']['key'] = ''
|
||||
config['exchange']['secret'] = ''
|
||||
config['dry_run'] = False
|
||||
return config
|
||||
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import copy
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from math import isclose
|
||||
from random import randint
|
||||
@@ -14,7 +15,7 @@ from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOr
|
||||
OperationalException, PricingError, TemporaryError)
|
||||
from freqtrade.exchange import Binance, Bittrex, Exchange, Kraken
|
||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, API_RETRY_COUNT,
|
||||
calculate_backoff)
|
||||
calculate_backoff, remove_credentials)
|
||||
from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_msecs,
|
||||
timeframe_to_next_date, timeframe_to_prev_date,
|
||||
timeframe_to_seconds)
|
||||
@@ -78,6 +79,22 @@ def test_init(default_conf, mocker, caplog):
|
||||
assert log_has('Instance is running with dry_run enabled', caplog)
|
||||
|
||||
|
||||
def test_remove_credentials(default_conf, caplog) -> None:
|
||||
conf = deepcopy(default_conf)
|
||||
conf['dry_run'] = False
|
||||
remove_credentials(conf)
|
||||
|
||||
assert conf['exchange']['key'] != ''
|
||||
assert conf['exchange']['secret'] != ''
|
||||
|
||||
conf['dry_run'] = True
|
||||
remove_credentials(conf)
|
||||
assert conf['exchange']['key'] == ''
|
||||
assert conf['exchange']['secret'] == ''
|
||||
assert conf['exchange']['password'] == ''
|
||||
assert conf['exchange']['uid'] == ''
|
||||
|
||||
|
||||
def test_init_ccxt_kwargs(default_conf, mocker, caplog):
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||
@@ -108,6 +125,13 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
|
||||
assert hasattr(ex._api_async, 'TestKWARG')
|
||||
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
|
||||
assert log_has(asynclogmsg, caplog)
|
||||
# Test additional headers case
|
||||
Exchange._headers = {'hello': 'world'}
|
||||
ex = Exchange(conf)
|
||||
|
||||
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
|
||||
assert ex._api.headers == {'hello': 'world'}
|
||||
Exchange._headers = {}
|
||||
|
||||
|
||||
def test_destroy(default_conf, mocker, caplog):
|
||||
@@ -178,7 +202,7 @@ def test_exchange_resolver(default_conf, mocker, caplog):
|
||||
|
||||
def test_validate_order_time_in_force(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
# explicitly test bittrex, exchanges implementing other policies need seperate tests
|
||||
# explicitly test bittrex, exchanges implementing other policies need separate tests
|
||||
ex = get_patched_exchange(mocker, default_conf, id="bittrex")
|
||||
tif = {
|
||||
"buy": "gtc",
|
||||
@@ -1544,6 +1568,32 @@ def test_get_historic_ohlcv_as_df(default_conf, mocker, exchange_name):
|
||||
assert 'high' in ret.columns
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
|
||||
ohlcv = [
|
||||
[
|
||||
int((datetime.now(timezone.utc).timestamp() - 1000) * 1000),
|
||||
1, # open
|
||||
2, # high
|
||||
3, # low
|
||||
4, # close
|
||||
5, # volume (in quote currency)
|
||||
]
|
||||
]
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
# Monkey-patch async function
|
||||
exchange._api_async.fetch_ohlcv = get_mock_coro(ohlcv)
|
||||
|
||||
pair = 'ETH/USDT'
|
||||
res = await exchange._async_get_historic_ohlcv(pair, "5m",
|
||||
1500000000000, is_new_pair=False)
|
||||
# Call with very old timestamp - causes tons of requests
|
||||
assert exchange._api_async.fetch_ohlcv.call_count > 200
|
||||
assert res[0] == ohlcv[0]
|
||||
assert log_has_re(r'Downloaded data for .* with length .*\.', caplog)
|
||||
|
||||
|
||||
def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
|
||||
ohlcv = [
|
||||
[
|
||||
@@ -2431,7 +2481,7 @@ def test_fetch_order(default_conf, mocker, exchange_name, caplog):
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
|
||||
# Don't test FTX here - that needs a seperate test
|
||||
# Don't test FTX here - that needs a separate test
|
||||
if exchange_name == 'ftx':
|
||||
return
|
||||
default_conf['dry_run'] = True
|
||||
|
@@ -16,7 +16,7 @@ def hyperopt_conf(default_conf):
|
||||
hyperconf.update({
|
||||
'datadir': Path(default_conf['datadir']),
|
||||
'runmode': RunMode.HYPEROPT,
|
||||
'hyperopt': 'HyperoptTestSepFile',
|
||||
'strategy': 'HyperoptableStrategy',
|
||||
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
|
||||
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
|
||||
'epochs': 1,
|
||||
|
@@ -1,207 +0,0 @@
|
||||
# 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 HyperoptTestSepFile(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.
|
||||
"""
|
||||
conditions = []
|
||||
|
||||
# GUARDS AND TRENDS
|
||||
if 'mfi-enabled' in params and params['mfi-enabled']:
|
||||
conditions.append(dataframe['mfi'] < params['mfi-value'])
|
||||
if 'fastd-enabled' in params and params['fastd-enabled']:
|
||||
conditions.append(dataframe['fastd'] < params['fastd-value'])
|
||||
if 'adx-enabled' in params and params['adx-enabled']:
|
||||
conditions.append(dataframe['adx'] > params['adx-value'])
|
||||
if 'rsi-enabled' in params and params['rsi-enabled']:
|
||||
conditions.append(dataframe['rsi'] < params['rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if 'trigger' in params:
|
||||
if params['trigger'] == 'boll':
|
||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'],
|
||||
dataframe['macdsignal']
|
||||
))
|
||||
if params['trigger'] == 'sar_reversal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['close'],
|
||||
dataframe['sar']
|
||||
))
|
||||
|
||||
if conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, conditions),
|
||||
'buy'] = 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'),
|
||||
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.
|
||||
"""
|
||||
conditions = []
|
||||
|
||||
# GUARDS AND TRENDS
|
||||
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
|
||||
conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
|
||||
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
|
||||
conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
|
||||
if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
|
||||
conditions.append(dataframe['adx'] < params['sell-adx-value'])
|
||||
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
|
||||
conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if 'sell-trigger' in params:
|
||||
if params['sell-trigger'] == 'sell-boll':
|
||||
conditions.append(dataframe['close'] > dataframe['bb_upperband'])
|
||||
if params['sell-trigger'] == 'sell-macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macdsignal'],
|
||||
dataframe['macd']
|
||||
))
|
||||
if params['sell-trigger'] == 'sell-sar_reversal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['sar'],
|
||||
dataframe['close']
|
||||
))
|
||||
|
||||
if conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, conditions),
|
||||
'sell'] = 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'),
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
return dataframe
|
@@ -1,271 +0,0 @@
|
||||
# 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
|
@@ -17,13 +17,10 @@ from freqtrade.optimize.hyperopt_auto import HyperOptAuto
|
||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||
from freqtrade.optimize.optimize_reports import generate_strategy_stats
|
||||
from freqtrade.optimize.space import SKDecimal
|
||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
|
||||
from freqtrade.strategy.hyper import IntParameter
|
||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
|
||||
from .hyperopts.hyperopt_test_sep_file import HyperoptTestSepFile
|
||||
|
||||
|
||||
# TODO-lev: This file
|
||||
|
||||
@@ -34,7 +31,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--strategy', 'HyperoptableStrategy',
|
||||
]
|
||||
|
||||
config = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
||||
@@ -66,7 +63,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--strategy', 'HyperoptableStrategy',
|
||||
'--datadir', '/foo/bar',
|
||||
'--timeframe', '1m',
|
||||
'--timerange', ':100',
|
||||
@@ -118,7 +115,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--strategy', 'HyperoptableStrategy',
|
||||
'--stake-amount', '1',
|
||||
'--starting-balance', '2'
|
||||
]
|
||||
@@ -136,47 +133,6 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
||||
setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
||||
|
||||
|
||||
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
hyperopt = HyperoptTestSepFile
|
||||
delattr(hyperopt, 'populate_indicators')
|
||||
delattr(hyperopt, 'populate_buy_trend')
|
||||
delattr(hyperopt, 'populate_sell_trend')
|
||||
mocker.patch(
|
||||
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver.load_object',
|
||||
MagicMock(return_value=hyperopt(default_conf))
|
||||
)
|
||||
default_conf.update({'hyperopt': 'HyperoptTestSepFile'})
|
||||
x = HyperOptResolver.load_hyperopt(default_conf)
|
||||
assert not hasattr(x, 'populate_indicators')
|
||||
assert not hasattr(x, 'populate_buy_trend')
|
||||
assert not hasattr(x, 'populate_sell_trend')
|
||||
assert log_has("Hyperopt class does not provide populate_indicators() method. "
|
||||
"Using populate_indicators from the strategy.", caplog)
|
||||
assert log_has("Hyperopt class does not provide populate_sell_trend() method. "
|
||||
"Using populate_sell_trend from the strategy.", caplog)
|
||||
assert log_has("Hyperopt class does not provide populate_buy_trend() method. "
|
||||
"Using populate_buy_trend from the strategy.", caplog)
|
||||
assert hasattr(x, "ticker_interval") # DEPRECATED
|
||||
assert hasattr(x, "timeframe")
|
||||
|
||||
|
||||
def test_hyperoptresolver_wrongname(default_conf) -> None:
|
||||
default_conf.update({'hyperopt': "NonExistingHyperoptClass"})
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Impossible to load Hyperopt.*'):
|
||||
HyperOptResolver.load_hyperopt(default_conf)
|
||||
|
||||
|
||||
def test_hyperoptresolver_noname(default_conf):
|
||||
default_conf['hyperopt'] = ''
|
||||
with pytest.raises(OperationalException,
|
||||
match="No Hyperopt set. Please use `--hyperopt` to specify "
|
||||
"the Hyperopt class to use."):
|
||||
HyperOptResolver.load_hyperopt(default_conf)
|
||||
|
||||
|
||||
def test_start_not_installed(mocker, default_conf, import_fails) -> None:
|
||||
start_mock = MagicMock()
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
@@ -187,9 +143,7 @@ def test_start_not_installed(mocker, default_conf, import_fails) -> None:
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--hyperopt-path',
|
||||
str(Path(__file__).parent / "hyperopts"),
|
||||
'--strategy', 'HyperoptableStrategy',
|
||||
'--epochs', '5',
|
||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||
]
|
||||
@@ -199,7 +153,7 @@ def test_start_not_installed(mocker, default_conf, import_fails) -> None:
|
||||
start_hyperopt(pargs)
|
||||
|
||||
|
||||
def test_start(mocker, hyperopt_conf, caplog) -> None:
|
||||
def test_start_no_hyperopt_allowed(mocker, hyperopt_conf, caplog) -> None:
|
||||
start_mock = MagicMock()
|
||||
patched_configuration_load_config_file(mocker, hyperopt_conf)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
|
||||
@@ -213,10 +167,8 @@ def test_start(mocker, hyperopt_conf, caplog) -> None:
|
||||
'--epochs', '5'
|
||||
]
|
||||
pargs = get_args(args)
|
||||
start_hyperopt(pargs)
|
||||
|
||||
assert log_has('Starting freqtrade in Hyperopt mode', caplog)
|
||||
assert start_mock.call_count == 1
|
||||
with pytest.raises(OperationalException, match=r"Using separate Hyperopt files has been.*"):
|
||||
start_hyperopt(pargs)
|
||||
|
||||
|
||||
def test_start_no_data(mocker, hyperopt_conf) -> None:
|
||||
@@ -228,11 +180,11 @@ def test_start_no_data(mocker, hyperopt_conf) -> None:
|
||||
)
|
||||
|
||||
patch_exchange(mocker)
|
||||
|
||||
# TODO: migrate to strategy-based hyperopt
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--strategy', 'HyperoptableStrategy',
|
||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||
'--epochs', '5'
|
||||
]
|
||||
@@ -250,7 +202,7 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None:
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--strategy', 'HyperoptableStrategy',
|
||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||
'--epochs', '5'
|
||||
]
|
||||
@@ -430,74 +382,14 @@ def test_hyperopt_format_results(hyperopt):
|
||||
def test_populate_indicators(hyperopt, testdatadir) -> None:
|
||||
data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True)
|
||||
dataframes = hyperopt.backtesting.strategy.advise_all_indicators(data)
|
||||
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
|
||||
{'pair': 'UNITTEST/BTC'})
|
||||
dataframe = dataframes['UNITTEST/BTC']
|
||||
|
||||
# Check if some indicators are generated. We will not test all of them
|
||||
assert 'adx' in dataframe
|
||||
assert 'mfi' in dataframe
|
||||
assert 'macd' in dataframe
|
||||
assert 'rsi' in dataframe
|
||||
|
||||
|
||||
def test_buy_strategy_generator(hyperopt, testdatadir) -> None:
|
||||
data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True)
|
||||
dataframes = hyperopt.backtesting.strategy.advise_all_indicators(data)
|
||||
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
|
||||
{'pair': 'UNITTEST/BTC'})
|
||||
|
||||
populate_buy_trend = hyperopt.custom_hyperopt.buy_strategy_generator(
|
||||
{
|
||||
'adx-value': 20,
|
||||
'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,
|
||||
'rsi-enabled': True,
|
||||
'trigger': 'bb_lower'
|
||||
}
|
||||
)
|
||||
result = populate_buy_trend(dataframe, {'pair': 'UNITTEST/BTC'})
|
||||
# Check if some indicators are generated. We will not test all of them
|
||||
assert 'buy' in result
|
||||
assert 1 in result['buy']
|
||||
|
||||
|
||||
def test_sell_strategy_generator(hyperopt, testdatadir) -> None:
|
||||
data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], fill_up_missing=True)
|
||||
dataframes = hyperopt.backtesting.strategy.advise_all_indicators(data)
|
||||
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
|
||||
{'pair': 'UNITTEST/BTC'})
|
||||
|
||||
populate_sell_trend = hyperopt.custom_hyperopt.sell_strategy_generator(
|
||||
{
|
||||
'sell-adx-value': 20,
|
||||
'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,
|
||||
'sell-rsi-enabled': True,
|
||||
'sell-trigger': 'sell-bb_upper'
|
||||
}
|
||||
)
|
||||
result = populate_sell_trend(dataframe, {'pair': 'UNITTEST/BTC'})
|
||||
# Check if some indicators are generated. We will not test all of them
|
||||
print(result)
|
||||
assert 'sell' in result
|
||||
assert 1 in result['sell']
|
||||
|
||||
|
||||
def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
hyperopt_conf.update({'spaces': 'all',
|
||||
'hyperopt_min_trades': 1,
|
||||
@@ -538,24 +430,12 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load', return_value={'XRP/BTC': None})
|
||||
|
||||
optimizer_param = {
|
||||
'adx-value': 0,
|
||||
'fastd-value': 35,
|
||||
'mfi-value': 0,
|
||||
'rsi-value': 0,
|
||||
'adx-enabled': False,
|
||||
'fastd-enabled': True,
|
||||
'mfi-enabled': False,
|
||||
'rsi-enabled': False,
|
||||
'trigger': 'macd_cross_signal',
|
||||
'sell-adx-value': 0,
|
||||
'sell-fastd-value': 75,
|
||||
'sell-mfi-value': 0,
|
||||
'sell-rsi-value': 0,
|
||||
'sell-adx-enabled': False,
|
||||
'sell-fastd-enabled': True,
|
||||
'sell-mfi-enabled': False,
|
||||
'sell-rsi-enabled': False,
|
||||
'sell-trigger': 'macd_cross_signal',
|
||||
'buy_plusdi': 0.02,
|
||||
'buy_rsi': 35,
|
||||
'sell_minusdi': 0.02,
|
||||
'sell_rsi': 75,
|
||||
'protection_cooldown_lookback': 20,
|
||||
'protection_enabled': True,
|
||||
'roi_t1': 60.0,
|
||||
'roi_t2': 30.0,
|
||||
'roi_t3': 20.0,
|
||||
@@ -575,29 +455,19 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
|
||||
'0.00003100 BTC ( 0.00%). '
|
||||
'Avg duration 0:50:00 min.'
|
||||
),
|
||||
'params_details': {'buy': {'adx-enabled': False,
|
||||
'adx-value': 0,
|
||||
'fastd-enabled': True,
|
||||
'fastd-value': 35,
|
||||
'mfi-enabled': False,
|
||||
'mfi-value': 0,
|
||||
'rsi-enabled': False,
|
||||
'rsi-value': 0,
|
||||
'trigger': 'macd_cross_signal'},
|
||||
'params_details': {'buy': {'buy_plusdi': 0.02,
|
||||
'buy_rsi': 35,
|
||||
},
|
||||
'roi': {"0": 0.12000000000000001,
|
||||
"20.0": 0.02,
|
||||
"50.0": 0.01,
|
||||
"110.0": 0},
|
||||
'protection': {},
|
||||
'sell': {'sell-adx-enabled': False,
|
||||
'sell-adx-value': 0,
|
||||
'sell-fastd-enabled': True,
|
||||
'sell-fastd-value': 75,
|
||||
'sell-mfi-enabled': False,
|
||||
'sell-mfi-value': 0,
|
||||
'sell-rsi-enabled': False,
|
||||
'sell-rsi-value': 0,
|
||||
'sell-trigger': 'macd_cross_signal'},
|
||||
'protection': {'protection_cooldown_lookback': 20,
|
||||
'protection_enabled': True,
|
||||
},
|
||||
'sell': {'sell_minusdi': 0.02,
|
||||
'sell_rsi': 75,
|
||||
},
|
||||
'stoploss': {'stoploss': -0.4},
|
||||
'trailing': {'trailing_only_offset_is_reached': False,
|
||||
'trailing_stop': True,
|
||||
@@ -819,11 +689,6 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
del hyperopt.custom_hyperopt.__class__.buy_strategy_generator
|
||||
del hyperopt.custom_hyperopt.__class__.sell_strategy_generator
|
||||
del hyperopt.custom_hyperopt.__class__.indicator_space
|
||||
del hyperopt.custom_hyperopt.__class__.sell_indicator_space
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
parallel.assert_called_once()
|
||||
@@ -854,16 +719,14 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None:
|
||||
|
||||
hyperopt_conf.update({'spaces': 'all', })
|
||||
|
||||
mocker.patch('freqtrade.optimize.hyperopt_auto.HyperOptAuto._generate_indicator_space',
|
||||
return_value=[])
|
||||
|
||||
hyperopt = Hyperopt(hyperopt_conf)
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
del hyperopt.custom_hyperopt.__class__.buy_strategy_generator
|
||||
del hyperopt.custom_hyperopt.__class__.sell_strategy_generator
|
||||
del hyperopt.custom_hyperopt.__class__.indicator_space
|
||||
del hyperopt.custom_hyperopt.__class__.sell_indicator_space
|
||||
|
||||
with pytest.raises(OperationalException, match=r"The 'buy' space is included into *"):
|
||||
with pytest.raises(OperationalException, match=r"The 'protection' space is included into *"):
|
||||
hyperopt.start()
|
||||
|
||||
|
||||
@@ -900,11 +763,6 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
# TODO: sell_strategy_generator() is actually not called because
|
||||
# run_optimizer_parallel() is mocked
|
||||
del hyperopt.custom_hyperopt.__class__.sell_strategy_generator
|
||||
del hyperopt.custom_hyperopt.__class__.sell_indicator_space
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
parallel.assert_called_once()
|
||||
@@ -954,11 +812,6 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
# TODO: buy_strategy_generator() is actually not called because
|
||||
# run_optimizer_parallel() is mocked
|
||||
del hyperopt.custom_hyperopt.__class__.buy_strategy_generator
|
||||
del hyperopt.custom_hyperopt.__class__.indicator_space
|
||||
|
||||
hyperopt.start()
|
||||
|
||||
parallel.assert_called_once()
|
||||
@@ -975,13 +828,12 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
|
||||
assert hasattr(hyperopt, "position_stacking")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method,space", [
|
||||
('buy_strategy_generator', 'buy'),
|
||||
('indicator_space', 'buy'),
|
||||
('sell_strategy_generator', 'sell'),
|
||||
('sell_indicator_space', 'sell'),
|
||||
@pytest.mark.parametrize("space", [
|
||||
('buy'),
|
||||
('sell'),
|
||||
('protection'),
|
||||
])
|
||||
def test_simplified_interface_failed(mocker, hyperopt_conf, method, space) -> None:
|
||||
def test_simplified_interface_failed(mocker, hyperopt_conf, space) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
|
||||
@@ -990,6 +842,8 @@ def test_simplified_interface_failed(mocker, hyperopt_conf, method, space) -> No
|
||||
'freqtrade.optimize.hyperopt.get_timerange',
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
|
||||
)
|
||||
mocker.patch('freqtrade.optimize.hyperopt_auto.HyperOptAuto._generate_indicator_space',
|
||||
return_value=[])
|
||||
|
||||
patch_exchange(mocker)
|
||||
|
||||
@@ -999,8 +853,6 @@ def test_simplified_interface_failed(mocker, hyperopt_conf, method, space) -> No
|
||||
hyperopt.backtesting.strategy.advise_all_indicators = MagicMock()
|
||||
hyperopt.custom_hyperopt.generate_roi_table = MagicMock(return_value={})
|
||||
|
||||
delattr(hyperopt.custom_hyperopt.__class__, method)
|
||||
|
||||
with pytest.raises(OperationalException, match=f"The '{space}' space is included into *"):
|
||||
hyperopt.start()
|
||||
|
||||
@@ -1010,7 +862,6 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
(Path(tmpdir) / 'hyperopt_results').mkdir(parents=True)
|
||||
# No hyperopt needed
|
||||
del hyperopt_conf['hyperopt']
|
||||
hyperopt_conf.update({
|
||||
'strategy': 'HyperoptableStrategy',
|
||||
'user_data_dir': Path(tmpdir),
|
||||
@@ -1036,6 +887,10 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
|
||||
assert hyperopt.backtesting.strategy.buy_rsi.value != 35
|
||||
assert hyperopt.backtesting.strategy.sell_rsi.value != 74
|
||||
|
||||
hyperopt.custom_hyperopt.generate_estimator = lambda *args, **kwargs: 'ET1'
|
||||
with pytest.raises(OperationalException, match="Estimator ET1 not supported."):
|
||||
hyperopt.get_optimizer([], 2)
|
||||
|
||||
|
||||
def test_SKDecimal():
|
||||
space = SKDecimal(1, 2, decimals=2)
|
||||
|
@@ -4,6 +4,7 @@ import time
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
import time_machine
|
||||
|
||||
from freqtrade.constants import AVAILABLE_PAIRLISTS
|
||||
from freqtrade.exceptions import OperationalException
|
||||
@@ -815,32 +816,63 @@ def test_agefilter_min_days_listed_too_large(mocker, default_conf, markets, tick
|
||||
|
||||
|
||||
def test_agefilter_caching(mocker, markets, whitelist_conf_agefilter, tickers, ohlcv_history):
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
}
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data),
|
||||
)
|
||||
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
}
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
exchange_has=MagicMock(return_value=True),
|
||||
get_tickers=tickers,
|
||||
refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data),
|
||||
)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_agefilter)
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 0
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count > 0
|
||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_agefilter)
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 0
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count > 0
|
||||
|
||||
previous_call_count = freqtrade.exchange.refresh_latest_ohlcv.call_count
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
# Called once for XRP/BTC
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == previous_call_count + 1
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
# Call to XRP/BTC cached
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 2
|
||||
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
('XRP/BTC', '1d'): ohlcv_history.iloc[[0]],
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1
|
||||
|
||||
# Move to next day
|
||||
t.move_to("2021-09-02 01:00:00 +00:00")
|
||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 3
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1
|
||||
|
||||
# Move another day with fresh mocks (now the pair is old enough)
|
||||
t.move_to("2021-09-03 01:00:00 +00:00")
|
||||
# Called once for XRP/BTC
|
||||
ohlcv_data = {
|
||||
('ETH/BTC', '1d'): ohlcv_history,
|
||||
('TKN/BTC', '1d'): ohlcv_history,
|
||||
('LTC/BTC', '1d'): ohlcv_history,
|
||||
('XRP/BTC', '1d'): ohlcv_history,
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', return_value=ohlcv_data)
|
||||
freqtrade.pairlists.refresh_pairlist()
|
||||
assert len(freqtrade.pairlists.whitelist) == 4
|
||||
# Called once (only for XRP/BTC)
|
||||
assert freqtrade.exchange.refresh_latest_ohlcv.call_count == 1
|
||||
|
||||
|
||||
def test_OffsetFilter_error(mocker, whitelist_conf) -> None:
|
||||
|
@@ -68,7 +68,7 @@ def test_PairLocks(use_db):
|
||||
# Global lock
|
||||
PairLocks.lock_pair('*', lock_time)
|
||||
assert PairLocks.is_global_lock(lock_time + timedelta(minutes=-50))
|
||||
# Global lock also locks every pair seperately
|
||||
# Global lock also locks every pair separately
|
||||
assert PairLocks.is_pair_locked(pair, lock_time + timedelta(minutes=-50))
|
||||
assert PairLocks.is_pair_locked('XRP/USDT', lock_time + timedelta(minutes=-50))
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
from freqtrade.enums.signaltype import SignalDirection
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
@@ -13,6 +12,7 @@ from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.enums import SellType
|
||||
from freqtrade.enums.signaltype import SignalDirection
|
||||
from freqtrade.exceptions import OperationalException, StrategyError
|
||||
from freqtrade.optimize.space import SKDecimal
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
@@ -48,7 +48,7 @@ def test_returns_latest_signal(ohlcv_history):
|
||||
mocked_history.loc[1, 'enter_long'] = 1
|
||||
|
||||
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history
|
||||
) == (SignalDirection.LONG, None)
|
||||
) == (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
|
||||
@@ -776,11 +776,16 @@ def test_auto_hyperopt_interface(default_conf):
|
||||
PairLocks.timeframe = default_conf['timeframe']
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
next(strategy.enumerate_parameters('deadBeef'))
|
||||
|
||||
assert strategy.buy_rsi.value == strategy.buy_params['buy_rsi']
|
||||
# PlusDI is NOT in the buy-params, so default should be used
|
||||
assert strategy.buy_plusdi.value == 0.5
|
||||
assert strategy.sell_rsi.value == strategy.sell_params['sell_rsi']
|
||||
|
||||
assert repr(strategy.sell_rsi) == 'IntParameter(74)'
|
||||
|
||||
# Parameter is disabled - so value from sell_param dict will NOT be used.
|
||||
assert strategy.sell_minusdi.value == 0.5
|
||||
all_params = strategy.detect_all_parameters()
|
||||
|
@@ -11,8 +11,7 @@ import pytest
|
||||
from jsonschema import ValidationError
|
||||
|
||||
from freqtrade.commands import Arguments
|
||||
from freqtrade.configuration import (Configuration, check_exchange, remove_credentials,
|
||||
validate_config_consistency)
|
||||
from freqtrade.configuration import Configuration, check_exchange, validate_config_consistency
|
||||
from freqtrade.configuration.config_validation import validate_config_schema
|
||||
from freqtrade.configuration.deprecated_settings import (check_conflicting_settings,
|
||||
process_deprecated_setting,
|
||||
@@ -617,18 +616,6 @@ def test_check_exchange(default_conf, caplog) -> None:
|
||||
check_exchange(default_conf)
|
||||
|
||||
|
||||
def test_remove_credentials(default_conf, caplog) -> None:
|
||||
conf = deepcopy(default_conf)
|
||||
conf['dry_run'] = False
|
||||
remove_credentials(conf)
|
||||
|
||||
assert conf['dry_run'] is True
|
||||
assert conf['exchange']['key'] == ''
|
||||
assert conf['exchange']['secret'] == ''
|
||||
assert conf['exchange']['password'] == ''
|
||||
assert conf['exchange']['uid'] == ''
|
||||
|
||||
|
||||
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
|
@@ -74,16 +74,12 @@ def test_copy_sample_files(mocker, default_conf, caplog) -> None:
|
||||
copymock = mocker.patch('shutil.copy', MagicMock())
|
||||
|
||||
copy_sample_files(Path('/tmp/bar'))
|
||||
assert copymock.call_count == 5
|
||||
assert copymock.call_count == 3
|
||||
assert copymock.call_args_list[0][0][1] == str(
|
||||
Path('/tmp/bar') / 'strategies/sample_strategy.py')
|
||||
assert copymock.call_args_list[1][0][1] == str(
|
||||
Path('/tmp/bar') / 'hyperopts/sample_hyperopt_advanced.py')
|
||||
assert copymock.call_args_list[2][0][1] == str(
|
||||
Path('/tmp/bar') / 'hyperopts/sample_hyperopt_loss.py')
|
||||
assert copymock.call_args_list[3][0][1] == str(
|
||||
Path('/tmp/bar') / 'hyperopts/sample_hyperopt.py')
|
||||
assert copymock.call_args_list[4][0][1] == str(
|
||||
assert copymock.call_args_list[2][0][1] == str(
|
||||
Path('/tmp/bar') / 'notebooks/strategy_analysis_example.ipynb')
|
||||
|
||||
|
||||
|
@@ -518,6 +518,7 @@ def test_enter_positions_global_pairlock(default_conf, ticker, limit_buy_order,
|
||||
# 0 trades, but it's not because of pairlock.
|
||||
assert n == 0
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because')
|
||||
n = freqtrade.enter_positions()
|
||||
@@ -1087,6 +1088,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog)
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.is_open is False
|
||||
caplog.clear()
|
||||
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Binance.stoploss',
|
||||
@@ -1191,7 +1193,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
|
||||
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
||||
assert log_has("Selling the trade forcefully", caplog)
|
||||
assert log_has("Exiting the trade forcefully", caplog)
|
||||
|
||||
# Should call a market sell
|
||||
assert create_order_mock.call_count == 2
|
||||
@@ -1660,7 +1662,7 @@ def test_enter_positions(mocker, default_conf, caplog) -> None:
|
||||
MagicMock(return_value=False))
|
||||
n = freqtrade.enter_positions()
|
||||
assert n == 0
|
||||
assert log_has('Found no buy signals for whitelisted currencies. Trying again...', caplog)
|
||||
assert log_has('Found no enter signals for whitelisted currencies. Trying again...', caplog)
|
||||
# create_trade should be called once for every pair in the whitelist.
|
||||
assert mock_ct.call_count == len(default_conf['exchange']['pair_whitelist'])
|
||||
|
||||
@@ -1721,7 +1723,7 @@ def test_exit_positions_exception(mocker, default_conf, limit_buy_order, caplog)
|
||||
)
|
||||
n = freqtrade.exit_positions(trades)
|
||||
assert n == 0
|
||||
assert log_has('Unable to sell trade ETH/BTC: ', caplog)
|
||||
assert log_has('Unable to exit trade ETH/BTC: ', caplog)
|
||||
|
||||
|
||||
def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> None:
|
||||
@@ -1744,10 +1746,12 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No
|
||||
)
|
||||
assert not freqtrade.update_trade_state(trade, None)
|
||||
assert log_has_re(r'Orderid for trade .* is empty.', caplog)
|
||||
caplog.clear()
|
||||
# Add datetime explicitly since sqlalchemy defaults apply only once written to database
|
||||
freqtrade.update_trade_state(trade, '123')
|
||||
# Test amount not modified by fee-logic
|
||||
assert not log_has_re(r'Applying fee to .*', caplog)
|
||||
caplog.clear()
|
||||
assert trade.open_order_id is None
|
||||
assert trade.amount == limit_buy_order['amount']
|
||||
|
||||
@@ -2454,8 +2458,8 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
handle_cancel_buy=MagicMock(),
|
||||
handle_cancel_sell=MagicMock(),
|
||||
handle_cancel_enter=MagicMock(),
|
||||
handle_cancel_exit=MagicMock(),
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@@ -2476,7 +2480,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
||||
caplog)
|
||||
|
||||
|
||||
def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
def test_handle_cancel_enter(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_buy_order = deepcopy(limit_buy_order)
|
||||
@@ -2487,7 +2491,7 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
freqtrade._notify_enter_cancel = MagicMock()
|
||||
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/USDT'
|
||||
@@ -2495,46 +2499,46 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
caplog.clear()
|
||||
limit_buy_order['filled'] = 0.01
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog)
|
||||
|
||||
caplog.clear()
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['filled'] = 2
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
# Order remained open for some reason (cancel failed)
|
||||
cancel_buy_order['status'] = 'open'
|
||||
cancel_order_mock = MagicMock(return_value=cancel_buy_order)
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
|
||||
indirect=['limit_buy_order_canceled_empty'])
|
||||
def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
|
||||
limit_buy_order_canceled_empty) -> None:
|
||||
def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf,
|
||||
limit_buy_order_canceled_empty) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = mocker.patch(
|
||||
'freqtrade.exchange.Exchange.cancel_order_with_result',
|
||||
return_value=limit_buy_order_canceled_empty)
|
||||
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_buy_cancel')
|
||||
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel')
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order_canceled_empty, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
|
||||
assert nofiy_mock.call_count == 1
|
||||
@@ -2546,8 +2550,8 @@ def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
|
||||
'String Return value',
|
||||
123
|
||||
])
|
||||
def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
def test_handle_cancel_enter_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=cancelorder)
|
||||
@@ -2557,7 +2561,7 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
freqtrade._notify_enter_cancel = MagicMock()
|
||||
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/USDT'
|
||||
@@ -2565,16 +2569,16 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['filled'] = 1.0
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
||||
def test_handle_cancel_exit_limit(mocker, default_conf, fee) -> None:
|
||||
send_msg_mock = patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
@@ -2600,26 +2604,26 @@ def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason)
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert send_msg_mock.call_count == 1
|
||||
|
||||
send_msg_mock.reset_mock()
|
||||
|
||||
order['amount'] = 2
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason
|
||||
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
# Assert cancel_order was not called (callcount remains unchanged)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert send_msg_mock.call_count == 1
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason
|
||||
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
# Message should not be iterated again
|
||||
assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
assert send_msg_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
||||
def test_handle_cancel_exit_cancel_exception(mocker, default_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(
|
||||
@@ -2632,7 +2636,7 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
||||
order = {'remaining': 1,
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order'
|
||||
|
||||
|
||||
def test_execute_trade_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||
@@ -3304,7 +3308,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
|
||||
assert trade.amount != amnt
|
||||
|
||||
|
||||
def test__safe_sell_amount(default_conf, fee, caplog, mocker):
|
||||
def test__safe_exit_amount(default_conf, fee, caplog, mocker):
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
amount = 95.33
|
||||
@@ -3324,17 +3328,17 @@ def test__safe_sell_amount(default_conf, fee, caplog, mocker):
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
wallet_update.reset_mock()
|
||||
assert freqtrade._safe_sell_amount(trade.pair, trade.amount) == amount_wallet
|
||||
assert freqtrade._safe_exit_amount(trade.pair, trade.amount) == amount_wallet
|
||||
assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
||||
assert wallet_update.call_count == 1
|
||||
caplog.clear()
|
||||
wallet_update.reset_mock()
|
||||
assert freqtrade._safe_sell_amount(trade.pair, amount_wallet) == amount_wallet
|
||||
assert freqtrade._safe_exit_amount(trade.pair, amount_wallet) == amount_wallet
|
||||
assert not log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
||||
assert wallet_update.call_count == 1
|
||||
|
||||
|
||||
def test__safe_sell_amount_error(default_conf, fee, caplog, mocker):
|
||||
def test__safe_exit_amount_error(default_conf, fee, caplog, mocker):
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
amount = 95.33
|
||||
@@ -3351,8 +3355,8 @@ def test__safe_sell_amount_error(default_conf, fee, caplog, mocker):
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
with pytest.raises(DependencyException, match=r"Not enough amount to sell."):
|
||||
assert freqtrade._safe_sell_amount(trade.pair, trade.amount)
|
||||
with pytest.raises(DependencyException, match=r"Not enough amount to exit."):
|
||||
assert freqtrade._safe_exit_amount(trade.pair, trade.amount)
|
||||
|
||||
|
||||
def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None:
|
||||
@@ -3526,6 +3530,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, limit_buy_or
|
||||
assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0 profit: 0.2666%", caplog)
|
||||
assert log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||
assert trade.stop_loss == 0.0000138501
|
||||
caplog.clear()
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
MagicMock(return_value={
|
||||
@@ -3586,6 +3591,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, limit_buy_orde
|
||||
assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0.011 profit: 0.2666%", caplog)
|
||||
assert log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||
assert trade.stop_loss == 0.0000138501
|
||||
caplog.clear()
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
MagicMock(return_value={
|
||||
@@ -3650,6 +3656,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, limit_buy_order_
|
||||
|
||||
assert not log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||
assert trade.stop_loss == 0.0000098910
|
||||
caplog.clear()
|
||||
|
||||
# price rises above the offset (rises 12% when the offset is 5.5%)
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
@@ -4317,8 +4324,8 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||
side_effect=[
|
||||
ExchangeError(), limit_sell_order, limit_buy_order, limit_sell_order])
|
||||
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_buy')
|
||||
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell')
|
||||
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
|
||||
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit')
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
create_mock_trades(fee)
|
||||
@@ -4352,6 +4359,7 @@ def test_update_open_orders(mocker, default_conf, fee, caplog):
|
||||
|
||||
freqtrade.update_open_orders()
|
||||
assert not log_has_re(r"Error updating Order .*", caplog)
|
||||
caplog.clear()
|
||||
|
||||
freqtrade.config['dry_run'] = False
|
||||
freqtrade.update_open_orders()
|
||||
@@ -4433,14 +4441,14 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf, fee):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
def test_reupdate_enter_order_fees(mocker, default_conf, fee, caplog):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
|
||||
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.get_trades().all()
|
||||
|
||||
freqtrade.reupdate_buy_order_fees(trades[0])
|
||||
freqtrade.reupdate_enter_order_fees(trades[0])
|
||||
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
||||
assert mock_uts.call_count == 1
|
||||
assert mock_uts.call_args_list[0][0][0] == trades[0]
|
||||
@@ -4463,7 +4471,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
)
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
freqtrade.reupdate_buy_order_fees(trade)
|
||||
freqtrade.reupdate_enter_order_fees(trade)
|
||||
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
||||
assert mock_uts.call_count == 0
|
||||
assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
|
||||
@@ -4473,7 +4481,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
def test_handle_insufficient_funds(mocker, default_conf, fee):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
|
||||
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_buy_order_fees')
|
||||
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees')
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.get_trades().all()
|
||||
|
||||
|
@@ -70,7 +70,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
_notify_sell=MagicMock(),
|
||||
_notify_exit=MagicMock(),
|
||||
)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
|
||||
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
|
||||
@@ -154,7 +154,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
_notify_sell=MagicMock(),
|
||||
_notify_exit=MagicMock(),
|
||||
)
|
||||
should_sell_mock = MagicMock(side_effect=[
|
||||
SellCheckTuple(sell_type=SellType.NONE),
|
||||
|
32
tests/test_periodiccache.py
Normal file
32
tests/test_periodiccache.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import time_machine
|
||||
|
||||
from freqtrade.configuration import PeriodicCache
|
||||
|
||||
|
||||
def test_ttl_cache():
|
||||
|
||||
with time_machine.travel("2021-09-01 05:00:00 +00:00") as t:
|
||||
|
||||
cache = PeriodicCache(5, ttl=60)
|
||||
cache1h = PeriodicCache(5, ttl=3600)
|
||||
|
||||
assert cache.timer() == 1630472400.0
|
||||
cache['a'] = 1235
|
||||
cache1h['a'] = 555123
|
||||
assert 'a' in cache
|
||||
assert 'a' in cache1h
|
||||
|
||||
t.move_to("2021-09-01 05:00:59 +00:00")
|
||||
assert 'a' in cache
|
||||
assert 'a' in cache1h
|
||||
|
||||
# Cache expired
|
||||
t.move_to("2021-09-01 05:01:00 +00:00")
|
||||
assert 'a' not in cache
|
||||
assert 'a' in cache1h
|
||||
|
||||
t.move_to("2021-09-01 05:59:59 +00:00")
|
||||
assert 'a' in cache1h
|
||||
|
||||
t.move_to("2021-09-01 06:00:00 +00:00")
|
||||
assert 'a' not in cache1h
|
@@ -13,7 +13,8 @@ from sqlalchemy import create_engine, inspect, text
|
||||
from freqtrade import constants
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db
|
||||
from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re
|
||||
from tests.conftest import (create_mock_trades, create_mock_trades_with_leverage, get_sides,
|
||||
log_has, log_has_re)
|
||||
|
||||
|
||||
def test_init_create_session(default_conf):
|
||||
@@ -64,8 +65,10 @@ def test_init_dryrun_db(default_conf, tmpdir):
|
||||
assert Path(filename).is_file()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short', [False, True])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_enter_exit_side(fee):
|
||||
def test_enter_exit_side(fee, is_short):
|
||||
enter_side, exit_side = get_sides(is_short)
|
||||
trade = Trade(
|
||||
id=2,
|
||||
pair='ADA/USDT',
|
||||
@@ -77,20 +80,15 @@ def test_enter_exit_side(fee):
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
is_short=False,
|
||||
is_short=is_short,
|
||||
leverage=2.0
|
||||
)
|
||||
assert trade.enter_side == 'buy'
|
||||
assert trade.exit_side == 'sell'
|
||||
|
||||
trade.is_short = True
|
||||
|
||||
assert trade.enter_side == 'sell'
|
||||
assert trade.exit_side == 'buy'
|
||||
assert trade.enter_side == enter_side
|
||||
assert trade.exit_side == exit_side
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test__set_stop_loss_isolated_liq(fee):
|
||||
def test_set_stop_loss_isolated_liq(fee):
|
||||
trade = Trade(
|
||||
id=2,
|
||||
pair='ADA/USDT',
|
||||
@@ -170,8 +168,32 @@ def test__set_stop_loss_isolated_liq(fee):
|
||||
assert trade.initial_stop_loss == 0.09
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,minutes,rate,interest', [
|
||||
("binance", False, 3, 10, 0.0005, round(0.0008333333333333334, 8)),
|
||||
("binance", True, 3, 10, 0.0005, 0.000625),
|
||||
("binance", False, 3, 295, 0.0005, round(0.004166666666666667, 8)),
|
||||
("binance", True, 3, 295, 0.0005, round(0.0031249999999999997, 8)),
|
||||
("binance", False, 3, 295, 0.00025, round(0.0020833333333333333, 8)),
|
||||
("binance", True, 3, 295, 0.00025, round(0.0015624999999999999, 8)),
|
||||
("binance", False, 5, 295, 0.0005, 0.005),
|
||||
("binance", True, 5, 295, 0.0005, round(0.0031249999999999997, 8)),
|
||||
("binance", False, 1, 295, 0.0005, 0.0),
|
||||
("binance", True, 1, 295, 0.0005, 0.003125),
|
||||
|
||||
("kraken", False, 3, 10, 0.0005, 0.040),
|
||||
("kraken", True, 3, 10, 0.0005, 0.030),
|
||||
("kraken", False, 3, 295, 0.0005, 0.06),
|
||||
("kraken", True, 3, 295, 0.0005, 0.045),
|
||||
("kraken", False, 3, 295, 0.00025, 0.03),
|
||||
("kraken", True, 3, 295, 0.00025, 0.0225),
|
||||
("kraken", False, 5, 295, 0.0005, round(0.07200000000000001, 8)),
|
||||
("kraken", True, 5, 295, 0.0005, 0.045),
|
||||
("kraken", False, 1, 295, 0.0005, 0.0),
|
||||
("kraken", True, 1, 295, 0.0005, 0.045),
|
||||
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_interest(market_buy_order_usdt, fee):
|
||||
def test_interest(market_buy_order_usdt, fee, exchange, is_short, lev, minutes, rate, interest):
|
||||
"""
|
||||
10min, 5hr limit trade on Binance/Kraken at 3x,5x leverage
|
||||
fee: 0.25 % quote
|
||||
@@ -230,114 +252,27 @@ def test_interest(market_buy_order_usdt, fee):
|
||||
stake_amount=20.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
open_date=datetime.utcnow() - timedelta(hours=0, minutes=10),
|
||||
open_date=datetime.utcnow() - timedelta(minutes=minutes),
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
leverage=3.0,
|
||||
interest_rate=0.0005,
|
||||
exchange=exchange,
|
||||
leverage=lev,
|
||||
interest_rate=rate,
|
||||
is_short=is_short
|
||||
)
|
||||
|
||||
# 10min, 3x leverage
|
||||
# binance
|
||||
assert round(float(trade.calculate_interest()), 8) == round(0.0008333333333333334, 8)
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert float(trade.calculate_interest()) == 0.040
|
||||
# Short
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
# binace
|
||||
trade.exchange = "binance"
|
||||
assert float(trade.calculate_interest()) == 0.000625
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert isclose(float(trade.calculate_interest()), 0.030)
|
||||
|
||||
# 5hr, long
|
||||
trade.open_date = datetime.utcnow() - timedelta(hours=4, minutes=55)
|
||||
trade.is_short = False
|
||||
trade.recalc_open_trade_value()
|
||||
# binance
|
||||
trade.exchange = "binance"
|
||||
assert round(float(trade.calculate_interest()), 8) == round(0.004166666666666667, 8)
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert float(trade.calculate_interest()) == 0.06
|
||||
# short
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
# binace
|
||||
trade.exchange = "binance"
|
||||
assert round(float(trade.calculate_interest()), 8) == round(0.0031249999999999997, 8)
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert float(trade.calculate_interest()) == 0.045
|
||||
|
||||
# 0.00025 interest, 5hr, long
|
||||
trade.is_short = False
|
||||
trade.recalc_open_trade_value()
|
||||
# binance
|
||||
trade.exchange = "binance"
|
||||
assert round(float(trade.calculate_interest(interest_rate=0.00025)),
|
||||
8) == round(0.0020833333333333333, 8)
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert isclose(float(trade.calculate_interest(interest_rate=0.00025)), 0.03)
|
||||
# short
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
# binace
|
||||
trade.exchange = "binance"
|
||||
assert round(float(trade.calculate_interest(interest_rate=0.00025)),
|
||||
8) == round(0.0015624999999999999, 8)
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert float(trade.calculate_interest(interest_rate=0.00025)) == 0.0225
|
||||
|
||||
# 5x leverage, 0.0005 interest, 5hr, long
|
||||
trade.is_short = False
|
||||
trade.recalc_open_trade_value()
|
||||
trade.leverage = 5.0
|
||||
# binance
|
||||
trade.exchange = "binance"
|
||||
assert round(float(trade.calculate_interest()), 8) == 0.005
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert float(trade.calculate_interest()) == round(0.07200000000000001, 8)
|
||||
# short
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
# binace
|
||||
trade.exchange = "binance"
|
||||
assert round(float(trade.calculate_interest()), 8) == round(0.0031249999999999997, 8)
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert float(trade.calculate_interest()) == 0.045
|
||||
|
||||
# 1x leverage, 0.0005 interest, 5hr
|
||||
trade.is_short = False
|
||||
trade.recalc_open_trade_value()
|
||||
trade.leverage = 1.0
|
||||
# binance
|
||||
trade.exchange = "binance"
|
||||
assert float(trade.calculate_interest()) == 0.0
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert float(trade.calculate_interest()) == 0.0
|
||||
# short
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
# binace
|
||||
trade.exchange = "binance"
|
||||
assert float(trade.calculate_interest()) == 0.003125
|
||||
# kraken
|
||||
trade.exchange = "kraken"
|
||||
assert float(trade.calculate_interest()) == 0.045
|
||||
assert round(float(trade.calculate_interest()), 8) == interest
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short,lev,borrowed', [
|
||||
(False, 1.0, 0.0),
|
||||
(True, 1.0, 30.0),
|
||||
(False, 3.0, 40.0),
|
||||
(True, 3.0, 30.0),
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog):
|
||||
def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee,
|
||||
caplog, is_short, lev, borrowed):
|
||||
"""
|
||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||
fee: 0.25% quote
|
||||
@@ -411,20 +346,19 @@ def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog):
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
is_short=is_short,
|
||||
leverage=lev
|
||||
)
|
||||
assert trade.borrowed == 0
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
assert trade.borrowed == 30.0
|
||||
trade.leverage = 3.0
|
||||
assert trade.borrowed == 30.0
|
||||
trade.is_short = False
|
||||
trade.recalc_open_trade_value()
|
||||
assert trade.borrowed == 40.0
|
||||
assert trade.borrowed == borrowed
|
||||
|
||||
|
||||
@pytest.mark.parametrize('is_short,open_rate,close_rate,lev,profit', [
|
||||
(False, 2.0, 2.2, 1.0, round(0.0945137157107232, 8)),
|
||||
(True, 2.2, 2.0, 3.0, round(0.2589996297562085, 8))
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog):
|
||||
def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt,
|
||||
is_short, open_rate, close_rate, lev, profit):
|
||||
"""
|
||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||
fee: 0.25% quote
|
||||
@@ -494,84 +428,52 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca
|
||||
|
||||
"""
|
||||
|
||||
enter_order = limit_sell_order_usdt if is_short else limit_buy_order_usdt
|
||||
exit_order = limit_buy_order_usdt if is_short else limit_sell_order_usdt
|
||||
enter_side, exit_side = get_sides(is_short)
|
||||
|
||||
trade = Trade(
|
||||
id=2,
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
is_open=True,
|
||||
open_date=arrow.utcnow().datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance'
|
||||
)
|
||||
assert trade.open_order_id is None
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
trade.update(limit_buy_order_usdt)
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate == 2.00
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, "
|
||||
r'pair=ADA/USDT, amount=30.00000000, '
|
||||
r"is_short=False, leverage=1.0, open_rate=2.00000000, open_since=.*\).",
|
||||
caplog)
|
||||
|
||||
caplog.clear()
|
||||
trade.open_order_id = 'something'
|
||||
trade.update(limit_sell_order_usdt)
|
||||
assert trade.open_order_id is None
|
||||
assert trade.close_rate == 2.20
|
||||
assert trade.close_profit == round(0.0945137157107232, 8)
|
||||
assert trade.close_date is not None
|
||||
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
|
||||
r"pair=ADA/USDT, amount=30.00000000, "
|
||||
r"is_short=False, leverage=1.0, open_rate=2.00000000, open_since=.*\).",
|
||||
caplog)
|
||||
caplog.clear()
|
||||
|
||||
trade = Trade(
|
||||
id=226531,
|
||||
pair='ADA/USDT',
|
||||
stake_amount=20.0,
|
||||
open_rate=2.0,
|
||||
open_rate=open_rate,
|
||||
amount=30.0,
|
||||
is_open=True,
|
||||
open_date=arrow.utcnow().datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
is_short=True,
|
||||
leverage=3.0,
|
||||
is_short=is_short,
|
||||
interest_rate=0.0005,
|
||||
leverage=lev
|
||||
)
|
||||
trade.open_order_id = 'something'
|
||||
trade.update(limit_sell_order_usdt)
|
||||
|
||||
assert trade.open_order_id is None
|
||||
assert trade.open_rate == 2.20
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
|
||||
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=226531, "
|
||||
r"pair=ADA/USDT, amount=30.00000000, "
|
||||
r"is_short=True, leverage=3.0, open_rate=2.20000000, open_since=.*\).",
|
||||
caplog)
|
||||
caplog.clear()
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
trade.update(limit_buy_order_usdt)
|
||||
trade.update(enter_order)
|
||||
assert trade.open_order_id is None
|
||||
assert trade.close_rate == 2.00
|
||||
assert trade.close_profit == round(0.2589996297562085, 8)
|
||||
assert trade.open_rate == open_rate
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
assert log_has_re(f"LIMIT_{enter_side.upper()} has been fulfilled for "
|
||||
r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, "
|
||||
f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, "
|
||||
r"open_since=.*\).",
|
||||
caplog)
|
||||
|
||||
caplog.clear()
|
||||
trade.open_order_id = 'something'
|
||||
trade.update(exit_order)
|
||||
assert trade.open_order_id is None
|
||||
assert trade.close_rate == close_rate
|
||||
assert trade.close_profit == profit
|
||||
assert trade.close_date is not None
|
||||
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=226531, "
|
||||
r"pair=ADA/USDT, amount=30.00000000, "
|
||||
r"is_short=True, leverage=3.0, open_rate=2.20000000, open_since=.*\).",
|
||||
assert log_has_re(f"LIMIT_{exit_side.upper()} has been fulfilled for "
|
||||
r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, "
|
||||
f"is_short={is_short}, leverage={lev}, open_rate={open_rate}0000000, "
|
||||
r"open_since=.*\).",
|
||||
caplog)
|
||||
caplog.clear()
|
||||
|
||||
@@ -616,9 +518,21 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,open_value,close_value,profit,profit_ratio', [
|
||||
("binance", False, 1, 60.15, 65.835, 5.685, 0.0945137157107232),
|
||||
("binance", True, 1, 59.850, 66.1663784375, -6.316378437500013, -0.1055368159983292),
|
||||
("binance", False, 3, 60.15, 65.83416667, 5.684166670000003, 0.2834995845386534),
|
||||
("binance", True, 3, 59.85, 66.1663784375, -6.316378437500013, -0.3166104479949876),
|
||||
|
||||
("kraken", False, 1, 60.15, 65.835, 5.685, 0.0945137157107232),
|
||||
("kraken", True, 1, 59.850, 66.231165, -6.381165, -0.106619298245614),
|
||||
("kraken", False, 3, 60.15, 65.795, 5.645, 0.2815461346633419),
|
||||
("kraken", True, 3, 59.850, 66.231165, -6.381165000000003, -0.319857894736842),
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
trade = Trade(
|
||||
def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee, exchange,
|
||||
is_short, lev, open_value, close_value, profit, profit_ratio):
|
||||
trade: Trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
@@ -627,55 +541,22 @@ def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt
|
||||
interest_rate=0.0005,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
exchange=exchange,
|
||||
is_short=is_short,
|
||||
leverage=lev
|
||||
)
|
||||
|
||||
trade.open_order_id = 'something'
|
||||
trade.open_order_id = f'something-{is_short}-{lev}-{exchange}'
|
||||
|
||||
trade.update(limit_buy_order_usdt)
|
||||
trade.update(limit_sell_order_usdt)
|
||||
# 1x leverage, binance
|
||||
assert trade._calc_open_trade_value() == 60.15
|
||||
assert isclose(trade.calc_close_trade_value(), 65.835)
|
||||
assert trade.calc_profit() == 5.685
|
||||
assert trade.calc_profit_ratio() == round(0.0945137157107232, 8)
|
||||
# 3x leverage, binance
|
||||
trade.leverage = 3
|
||||
trade.exchange = "binance"
|
||||
assert trade._calc_open_trade_value() == 60.15
|
||||
assert round(trade.calc_close_trade_value(), 8) == 65.83416667
|
||||
assert trade.calc_profit() == round(5.684166670000003, 8)
|
||||
assert trade.calc_profit_ratio() == round(0.2834995845386534, 8)
|
||||
trade.exchange = "kraken"
|
||||
# 3x leverage, kraken
|
||||
assert trade._calc_open_trade_value() == 60.15
|
||||
assert trade.calc_close_trade_value() == 65.795
|
||||
assert trade.calc_profit() == 5.645
|
||||
assert trade.calc_profit_ratio() == round(0.2815461346633419, 8)
|
||||
trade.is_short = True
|
||||
trade.open_rate = 2.0
|
||||
trade.close_rate = 2.2
|
||||
trade.recalc_open_trade_value()
|
||||
# 3x leverage, short, kraken
|
||||
assert trade._calc_open_trade_value() == 59.850
|
||||
assert trade.calc_close_trade_value() == 66.231165
|
||||
assert trade.calc_profit() == round(-6.381165000000003, 8)
|
||||
assert trade.calc_profit_ratio() == round(-0.319857894736842, 8)
|
||||
trade.exchange = "binance"
|
||||
# 3x leverage, short, binance
|
||||
assert trade._calc_open_trade_value() == 59.85
|
||||
assert trade.calc_close_trade_value() == 66.1663784375
|
||||
assert trade.calc_profit() == round(-6.316378437500013, 8)
|
||||
assert trade.calc_profit_ratio() == round(-0.3166104479949876, 8)
|
||||
# 1x leverage, short, binance
|
||||
trade.leverage = 1.0
|
||||
assert trade._calc_open_trade_value() == 59.850
|
||||
assert trade.calc_close_trade_value() == 66.1663784375
|
||||
assert trade.calc_profit() == round(-6.316378437500013, 8)
|
||||
assert trade.calc_profit_ratio() == round(-0.1055368159983292, 8)
|
||||
# 1x leverage, short, kraken
|
||||
trade.exchange = "kraken"
|
||||
assert trade._calc_open_trade_value() == 59.850
|
||||
assert trade.calc_close_trade_value() == 66.231165
|
||||
assert trade.calc_profit() == -6.381165
|
||||
assert trade.calc_profit_ratio() == round(-0.106619298245614, 8)
|
||||
assert isclose(trade._calc_open_trade_value(), open_value)
|
||||
assert isclose(trade.calc_close_trade_value(), close_value)
|
||||
assert isclose(trade.calc_profit(), round(profit, 8))
|
||||
assert isclose(trade.calc_profit_ratio(), round(profit_ratio, 8))
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
@@ -766,8 +647,27 @@ def test_update_invalid_order(limit_buy_order_usdt):
|
||||
trade.update(limit_buy_order_usdt)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange', ['binance', 'kraken'])
|
||||
@pytest.mark.parametrize('lev', [1, 3])
|
||||
@pytest.mark.parametrize('is_short,fee_rate,result', [
|
||||
(False, 0.003, 60.18),
|
||||
(False, 0.0025, 60.15),
|
||||
(False, 0.003, 60.18),
|
||||
(False, 0.0025, 60.15),
|
||||
(True, 0.003, 59.82),
|
||||
(True, 0.0025, 59.85),
|
||||
(True, 0.003, 59.82),
|
||||
(True, 0.0025, 59.85)
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_open_trade_value(limit_buy_order_usdt, fee):
|
||||
def test_calc_open_trade_value(
|
||||
limit_buy_order_usdt,
|
||||
exchange,
|
||||
lev,
|
||||
is_short,
|
||||
fee_rate,
|
||||
result
|
||||
):
|
||||
# 10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||
# fee: 0.25 %, 0.3% quote
|
||||
# open_rate: 2.00 quote
|
||||
@@ -787,90 +687,104 @@ def test_calc_open_trade_value(limit_buy_order_usdt, fee):
|
||||
stake_amount=60.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
||||
fee_open=fee_rate,
|
||||
fee_close=fee_rate,
|
||||
exchange=exchange,
|
||||
leverage=lev,
|
||||
is_short=is_short
|
||||
)
|
||||
trade.open_order_id = 'open_trade'
|
||||
trade.update(limit_buy_order_usdt)
|
||||
|
||||
# Get the open rate price with the standard fee rate
|
||||
assert trade._calc_open_trade_value() == 60.15
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
assert trade._calc_open_trade_value() == 59.85
|
||||
trade.leverage = 3
|
||||
trade.exchange = "binance"
|
||||
assert trade._calc_open_trade_value() == 59.85
|
||||
trade.is_short = False
|
||||
trade.recalc_open_trade_value()
|
||||
assert trade._calc_open_trade_value() == 60.15
|
||||
|
||||
# Get the open rate price with a custom fee rate
|
||||
trade.fee_open = 0.003
|
||||
|
||||
assert trade._calc_open_trade_value() == 60.18
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
assert trade._calc_open_trade_value() == 59.82
|
||||
assert trade._calc_open_trade_value() == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,open_rate,close_rate,fee_rate,result', [
|
||||
('binance', False, 1, 2.0, 2.5, 0.0025, 74.8125),
|
||||
('binance', False, 1, 2.0, 2.5, 0.003, 74.775),
|
||||
('binance', False, 1, 2.0, 2.2, 0.005, 65.67),
|
||||
('binance', False, 3, 2.0, 2.5, 0.0025, 74.81166667),
|
||||
('binance', False, 3, 2.0, 2.5, 0.003, 74.77416667),
|
||||
('kraken', False, 3, 2.0, 2.5, 0.0025, 74.7725),
|
||||
('kraken', False, 3, 2.0, 2.5, 0.003, 74.735),
|
||||
('kraken', True, 3, 2.2, 2.5, 0.0025, 75.2626875),
|
||||
('kraken', True, 3, 2.2, 2.5, 0.003, 75.300225),
|
||||
('binance', True, 3, 2.2, 2.5, 0.0025, 75.18906641),
|
||||
('binance', True, 3, 2.2, 2.5, 0.003, 75.22656719),
|
||||
('binance', True, 1, 2.2, 2.5, 0.0025, 75.18906641),
|
||||
('binance', True, 1, 2.2, 2.5, 0.003, 75.22656719),
|
||||
('kraken', True, 1, 2.2, 2.5, 0.0025, 75.2626875),
|
||||
('kraken', True, 1, 2.2, 2.5, 0.003, 75.300225),
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, open_rate,
|
||||
exchange, is_short, lev, close_rate, fee_rate, result):
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
open_rate=open_rate,
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
fee_open=fee_rate,
|
||||
fee_close=fee_rate,
|
||||
exchange=exchange,
|
||||
interest_rate=0.0005,
|
||||
is_short=is_short,
|
||||
leverage=lev
|
||||
)
|
||||
trade.open_order_id = 'close_trade'
|
||||
trade.update(limit_buy_order_usdt)
|
||||
|
||||
# 1x leverage binance
|
||||
assert trade.calc_close_trade_value(rate=2.5) == 74.8125
|
||||
assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 74.775
|
||||
trade.update(limit_sell_order_usdt)
|
||||
assert trade.calc_close_trade_value(fee=0.005) == 65.67
|
||||
|
||||
# 3x leverage binance
|
||||
trade.leverage = 3.0
|
||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 74.81166667
|
||||
assert round(trade.calc_close_trade_value(rate=2.5, fee=0.003), 8) == 74.77416667
|
||||
|
||||
# 3x leverage kraken
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_close_trade_value(rate=2.5) == 74.7725
|
||||
assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 74.735
|
||||
|
||||
# 3x leverage kraken, short
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 75.2626875
|
||||
assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 75.300225
|
||||
|
||||
# 3x leverage binance, short
|
||||
trade.exchange = "binance"
|
||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 75.18906641
|
||||
assert round(trade.calc_close_trade_value(rate=2.5, fee=0.003), 8) == 75.22656719
|
||||
|
||||
trade.leverage = 1.0
|
||||
# 1x leverage binance, short
|
||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 75.18906641
|
||||
assert round(trade.calc_close_trade_value(rate=2.5, fee=0.003), 8) == 75.22656719
|
||||
|
||||
# 1x leverage kraken, short
|
||||
trade.exchange = "kraken"
|
||||
assert round(trade.calc_close_trade_value(rate=2.5), 8) == 75.2626875
|
||||
assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 75.300225
|
||||
assert round(trade.calc_close_trade_value(rate=close_rate, fee=fee_rate), 8) == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('exchange,is_short,lev,close_rate,fee_close,profit,profit_ratio', [
|
||||
('binance', False, 1, 2.1, 0.0025, 2.6925, 0.04476309226932673),
|
||||
('binance', False, 3, 2.1, 0.0025, 2.69166667, 0.13424771421446402),
|
||||
('binance', True, 1, 2.1, 0.0025, -3.308815781249997, -0.05528514254385963),
|
||||
('binance', True, 3, 2.1, 0.0025, -3.308815781249997, -0.1658554276315789),
|
||||
|
||||
('binance', False, 1, 1.9, 0.0025, -3.2925, -0.05473815461346632),
|
||||
('binance', False, 3, 1.9, 0.0025, -3.29333333, -0.16425602643391513),
|
||||
('binance', True, 1, 1.9, 0.0025, 2.7063095312499996, 0.045218204365079395),
|
||||
('binance', True, 3, 1.9, 0.0025, 2.7063095312499996, 0.13565461309523819),
|
||||
|
||||
('binance', False, 1, 2.2, 0.0025, 5.685, 0.0945137157107232),
|
||||
('binance', False, 3, 2.2, 0.0025, 5.68416667, 0.2834995845386534),
|
||||
('binance', True, 1, 2.2, 0.0025, -6.316378437499999, -0.1055368159983292),
|
||||
('binance', True, 3, 2.2, 0.0025, -6.316378437499999, -0.3166104479949876),
|
||||
|
||||
('kraken', False, 1, 2.1, 0.0025, 2.6925, 0.04476309226932673),
|
||||
('kraken', False, 3, 2.1, 0.0025, 2.6525, 0.13229426433915248),
|
||||
('kraken', True, 1, 2.1, 0.0025, -3.3706575, -0.05631842105263152),
|
||||
('kraken', True, 3, 2.1, 0.0025, -3.3706575, -0.16895526315789455),
|
||||
|
||||
('kraken', False, 1, 1.9, 0.0025, -3.2925, -0.05473815461346632),
|
||||
('kraken', False, 3, 1.9, 0.0025, -3.3325, -0.16620947630922667),
|
||||
('kraken', True, 1, 1.9, 0.0025, 2.6503575, 0.04428333333333334),
|
||||
('kraken', True, 3, 1.9, 0.0025, 2.6503575, 0.13285000000000002),
|
||||
|
||||
('kraken', False, 1, 2.2, 0.0025, 5.685, 0.0945137157107232),
|
||||
('kraken', False, 3, 2.2, 0.0025, 5.645, 0.2815461346633419),
|
||||
('kraken', True, 1, 2.2, 0.0025, -6.381165, -0.106619298245614),
|
||||
('kraken', True, 3, 2.2, 0.0025, -6.381165, -0.319857894736842),
|
||||
|
||||
('binance', False, 1, 2.1, 0.003, 2.6610000000000014, 0.04423940149625927),
|
||||
('binance', False, 1, 1.9, 0.003, -3.320999999999998, -0.05521197007481293),
|
||||
('binance', False, 1, 2.2, 0.003, 5.652000000000008, 0.09396508728179565),
|
||||
])
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
def test_calc_profit(
|
||||
limit_buy_order_usdt,
|
||||
limit_sell_order_usdt,
|
||||
fee,
|
||||
exchange,
|
||||
is_short,
|
||||
lev,
|
||||
close_rate,
|
||||
fee_close,
|
||||
profit,
|
||||
profit_ratio
|
||||
):
|
||||
"""
|
||||
10 minute limit trade on Binance/Kraken at 1x, 3x leverage
|
||||
arguments:
|
||||
@@ -1007,198 +921,16 @@ def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
open_rate=2.0,
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
||||
interest_rate=0.0005,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance'
|
||||
exchange=exchange,
|
||||
is_short=is_short,
|
||||
leverage=lev,
|
||||
fee_open=0.0025,
|
||||
fee_close=fee_close
|
||||
)
|
||||
trade.open_order_id = 'something'
|
||||
trade.update(limit_buy_order_usdt) # Buy @ 2.0
|
||||
|
||||
# 1x Leverage, long
|
||||
# Custom closing rate and regular fee rate
|
||||
# Higher than open rate - 2.1 quote
|
||||
assert trade.calc_profit(rate=2.1) == 2.6925
|
||||
# Lower than open rate - 1.9 quote
|
||||
assert trade.calc_profit(rate=1.9) == round(-3.292499999999997, 8)
|
||||
|
||||
# fee 0.003
|
||||
# Higher than open rate - 2.1 quote
|
||||
assert trade.calc_profit(rate=2.1, fee=0.003) == 2.661
|
||||
# Lower than open rate - 1.9 quote
|
||||
assert trade.calc_profit(rate=1.9, fee=0.003) == round(-3.320999999999998, 8)
|
||||
|
||||
# Test when we apply a Sell order. Sell higher than open rate @ 2.2
|
||||
trade.update(limit_sell_order_usdt)
|
||||
assert trade.calc_profit() == round(5.684999999999995, 8)
|
||||
|
||||
# Test with a custom fee rate on the close trade
|
||||
assert trade.calc_profit(fee=0.003) == round(5.652000000000008, 8)
|
||||
|
||||
trade.open_trade_value = 0.0
|
||||
trade.open_trade_value = trade._calc_open_trade_value()
|
||||
|
||||
# 3x leverage, long ###################################################
|
||||
trade.leverage = 3.0
|
||||
# Higher than open rate - 2.1 quote
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == 2.69166667
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == 2.6525
|
||||
|
||||
# 1.9 quote
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == -3.29333333
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == -3.3325
|
||||
|
||||
# 2.2 quote
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit(fee=0.0025) == 5.68416667
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit(fee=0.0025) == 5.645
|
||||
|
||||
# 3x leverage, short ###################################################
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
# 2.1 quote - Higher than open rate
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == round(-3.308815781249997, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == -3.3706575
|
||||
|
||||
# 1.9 quote - Lower than open rate
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == round(2.7063095312499996, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == 2.6503575
|
||||
|
||||
# Test when we apply a Sell order. Uses sell order used above
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit(fee=0.0025) == round(-6.316378437499999, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit(fee=0.0025) == -6.381165
|
||||
|
||||
# 1x leverage, short ###################################################
|
||||
trade.leverage = 1.0
|
||||
# 2.1 quote - Higher than open rate
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == round(-3.308815781249997, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit(rate=2.1, fee=0.0025) == -3.3706575
|
||||
|
||||
# 1.9 quote - Lower than open rate
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == round(2.7063095312499996, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit(rate=1.9, fee=0.0025) == 2.6503575
|
||||
|
||||
# Test when we apply a Sell order. Uses sell order used above
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit(fee=0.0025) == round(-6.316378437499999, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit(fee=0.0025) == -6.381165
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_profit_ratio(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
trade = Trade(
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
|
||||
interest_rate=0.0005,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance'
|
||||
)
|
||||
trade.open_order_id = 'something'
|
||||
trade.update(limit_buy_order_usdt) # Buy @ 2.0
|
||||
|
||||
# 1x Leverage, long
|
||||
# Custom closing rate and regular fee rate
|
||||
# Higher than open rate - 2.1 quote
|
||||
assert trade.calc_profit_ratio(rate=2.1) == round(0.04476309226932673, 8)
|
||||
# Lower than open rate - 1.9 quote
|
||||
assert trade.calc_profit_ratio(rate=1.9) == round(-0.05473815461346632, 8)
|
||||
|
||||
# fee 0.003
|
||||
# Higher than open rate - 2.1 quote
|
||||
assert trade.calc_profit_ratio(rate=2.1, fee=0.003) == round(0.04423940149625927, 8)
|
||||
# Lower than open rate - 1.9 quote
|
||||
assert trade.calc_profit_ratio(rate=1.9, fee=0.003) == round(-0.05521197007481293, 8)
|
||||
|
||||
# Test when we apply a Sell order. Sell higher than open rate @ 2.2
|
||||
trade.update(limit_sell_order_usdt)
|
||||
assert trade.calc_profit_ratio() == round(0.0945137157107232, 8)
|
||||
|
||||
# Test with a custom fee rate on the close trade
|
||||
assert trade.calc_profit_ratio(fee=0.003) == round(0.09396508728179565, 8)
|
||||
|
||||
trade.open_trade_value = 0.0
|
||||
assert trade.calc_profit_ratio(fee=0.003) == 0.0
|
||||
trade.open_trade_value = trade._calc_open_trade_value()
|
||||
|
||||
# 3x leverage, long ###################################################
|
||||
trade.leverage = 3.0
|
||||
# 2.1 quote - Higher than open rate
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit_ratio(rate=2.1) == round(0.13424771421446402, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit_ratio(rate=2.1) == round(0.13229426433915248, 8)
|
||||
|
||||
# 1.9 quote - Lower than open rate
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit_ratio(rate=1.9) == round(-0.16425602643391513, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit_ratio(rate=1.9) == round(-0.16620947630922667, 8)
|
||||
|
||||
# Test when we apply a Sell order. Uses sell order used above
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit_ratio() == round(0.2834995845386534, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit_ratio() == round(0.2815461346633419, 8)
|
||||
|
||||
# 3x leverage, short ###################################################
|
||||
trade.is_short = True
|
||||
trade.recalc_open_trade_value()
|
||||
# 2.1 quote - Higher than open rate
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit_ratio(rate=2.1) == round(-0.1658554276315789, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit_ratio(rate=2.1) == round(-0.16895526315789455, 8)
|
||||
|
||||
# 1.9 quote - Lower than open rate
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit_ratio(rate=1.9) == round(0.13565461309523819, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit_ratio(rate=1.9) == round(0.13285000000000002, 8)
|
||||
|
||||
# Test when we apply a Sell order. Uses sell order used above
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit_ratio() == round(-0.3166104479949876, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit_ratio() == round(-0.319857894736842, 8)
|
||||
|
||||
# 1x leverage, short ###################################################
|
||||
trade.leverage = 1.0
|
||||
# 2.1 quote - Higher than open rate
|
||||
trade.exchange = "binance" # binance
|
||||
assert trade.calc_profit_ratio(rate=2.1) == round(-0.05528514254385963, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit_ratio(rate=2.1) == round(-0.05631842105263152, 8)
|
||||
|
||||
# 1.9 quote - Lower than open rate
|
||||
trade.exchange = "binance"
|
||||
assert trade.calc_profit_ratio(rate=1.9) == round(0.045218204365079395, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit_ratio(rate=1.9) == round(0.04428333333333334, 8)
|
||||
|
||||
# Test when we apply a Sell order. Uses sell order used above
|
||||
trade.exchange = "binance"
|
||||
assert trade.calc_profit_ratio() == round(-0.1055368159983292, 8)
|
||||
trade.exchange = "kraken"
|
||||
assert trade.calc_profit_ratio() == round(-0.106619298245614, 8)
|
||||
assert trade.calc_profit(rate=close_rate) == round(profit, 8)
|
||||
assert trade.calc_profit_ratio(rate=close_rate) == round(profit_ratio, 8)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
|
Reference in New Issue
Block a user