From 5928ba9c883a6b47bfd425eb497c2ba70f1cfa9c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 22 Sep 2021 20:14:52 +0200 Subject: [PATCH] Test and document leverage strategy callback --- docs/strategy-advanced.md | 28 +++++++++++++++++ freqtrade/strategy/interface.py | 37 +++++++++++------------ tests/conftest.py | 1 + tests/strategy/strats/strategy_test_v3.py | 9 ++++++ tests/strategy/test_interface.py | 28 ++++++++++++++++- 5 files changed, 83 insertions(+), 20 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 2b9517f3b..13dec60ca 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -642,6 +642,34 @@ Freqtrade will fall back to the `proposed_stake` value should your code raise an !!! Tip Returning `0` or `None` will prevent trades from being placed. +## Leverage Callback + +When trading in markets that allow leverage, this method must return the desired Leverage (Defaults to 1 -> No leverage). + +Assuming a capital of 500USDT, a trade with leverage=3 would result in a position with 500 x 3 = 1500 USDT. + +Values that are above `max_leverage` will be adjusted to `max_leverage`. +For markets / exchanges that don't support leverage, this method is ignored. + +``` python +class AwesomeStrategy(IStrategy): + def leverage(self, pair: str, current_time: 'datetime', current_rate: float, + proposed_leverage: float, max_leverage: float, side: str, + **kwargs) -> float: + """ + Customize leverage for each new trade. + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param proposed_leverage: A leverage proposed by the bot. + :param max_leverage: Max leverage allowed on this pair + :param side: 'long' or 'short' - indicating the direction of the proposed trade + :return: A leverage amount, which is between 1.0 and max_leverage. + """ + return 1.0 +``` + --- ## Derived strategies diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 139729910..d852c7269 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -370,8 +370,7 @@ class IStrategy(ABC, HyperStrategyMixin): proposed_stake: float, min_stake: float, max_stake: float, **kwargs) -> float: """ - Customize stake size for each new trade. This method is not called when edge module is - enabled. + Customize stake size for each new trade. :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime @@ -383,6 +382,23 @@ class IStrategy(ABC, HyperStrategyMixin): """ return proposed_stake + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, side: str, + **kwargs) -> float: + """ + Customize leverage for each new trade. This method is not called when edge module is + enabled. + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param proposed_leverage: A leverage proposed by the bot. + :param max_leverage: Max leverage allowed on this pair + :param side: 'long' or 'short' - indicating the direction of the proposed trade + :return: A leverage amount, which is between 1.0 and max_leverage. + """ + return 1.0 + def informative_pairs(self) -> ListPairsWithTimeframes: """ Define additional, informative pair/interval combinations to be cached from the exchange. @@ -971,20 +987,3 @@ class IStrategy(ABC, HyperStrategyMixin): if 'exit_long' not in df.columns: df = df.rename({'sell': 'exit_long'}, axis='columns') return df - - def leverage(self, pair: str, current_time: datetime, current_rate: float, - proposed_leverage: float, max_leverage: float, side: str, - **kwargs) -> float: - """ - Customize leverage for each new trade. This method is not called when edge module is - enabled. - - :param pair: Pair that's currently analyzed - :param current_time: datetime object, containing the current datetime - :param current_rate: Rate, calculated based on pricing settings in ask_strategy. - :param proposed_leverage: A leverage proposed by the bot. - :param max_leverage: Max leverage allowed on this pair - :param side: 'long' or 'short' - indicating the direction of the proposed trade - :return: A leverage amount, which is between 1.0 and max_leverage. - """ - return 1.0 diff --git a/tests/conftest.py b/tests/conftest.py index a9fd42a05..b35ff17d6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,6 +36,7 @@ logging.getLogger('').setLevel(logging.INFO) np.seterr(all='raise') CURRENT_TEST_STRATEGY = 'StrategyTestV3' +TRADE_SIDES = ('long', 'short') def pytest_addoption(parser): diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index db294d4e9..18c4ec93f 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -1,5 +1,6 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement +from datetime import datetime import talib.abstract as ta from pandas import DataFrame @@ -159,3 +160,11 @@ class StrategyTestV3(IStrategy): # TODO-lev: Add short logic return dataframe + + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, side: str, + **kwargs) -> float: + # Return 3.0 in all cases. + # Bot-logic must make sure it's an allowed leverage and eventually adjust accordingly. + + return 3.0 diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 65e7da9db..ad393d6a4 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -21,7 +21,7 @@ from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, Categoric DecimalParameter, IntParameter, RealParameter) from freqtrade.strategy.interface import SellCheckTuple from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper -from tests.conftest import log_has, log_has_re +from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re from .strats.strategy_test_v3 import StrategyTestV3 @@ -506,6 +506,32 @@ def test_custom_sell(default_conf, fee, caplog) -> None: assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog) +@pytest.mark.parametrize('side', TRADE_SIDES) +def test_leverage_callback(default_conf, side) -> None: + default_conf['strategy'] = 'StrategyTestV2' + strategy = StrategyResolver.load_strategy(default_conf) + + assert strategy.leverage( + pair='XRP/USDT', + current_time=datetime.now(timezone.utc), + current_rate=2.2, + proposed_leverage=1.0, + max_leverage=5.0, + side=side, + ) == 1 + + default_conf['strategy'] = CURRENT_TEST_STRATEGY + strategy = StrategyResolver.load_strategy(default_conf) + assert strategy.leverage( + pair='XRP/USDT', + current_time=datetime.now(timezone.utc), + current_rate=2.2, + proposed_leverage=1.0, + max_leverage=5.0, + side=side, + ) == 3 + + def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: caplog.set_level(logging.DEBUG) ind_mock = MagicMock(side_effect=lambda x, meta: x)