From 2816b96650b46c2a18859b95527cda0edb5de1d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 6 Feb 2020 20:26:04 +0100 Subject: [PATCH 01/23] Create strategy_wrapper to call user-defined code with --- freqtrade/exceptions.py | 7 +++++++ freqtrade/strategy/interface.py | 25 ++++++++-------------- freqtrade/strategy/strategy_wrapper.py | 29 ++++++++++++++++++++++++++ tests/strategy/test_interface.py | 4 ++-- 4 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 freqtrade/strategy/strategy_wrapper.py diff --git a/freqtrade/exceptions.py b/freqtrade/exceptions.py index 2f05ddb57..553a691ef 100644 --- a/freqtrade/exceptions.py +++ b/freqtrade/exceptions.py @@ -35,3 +35,10 @@ class TemporaryError(FreqtradeException): This could happen when an exchange is congested, unavailable, or the user has networking problems. Usually resolves itself after a time. """ + + +class StrategyError(FreqtradeException): + """ + Errors with custom user-code deteced. + Usually caused by errors in the strategy. + """ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 6e15c5183..c20bf0218 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -3,21 +3,22 @@ IStrategy interface This module defines the interface to apply for strategies """ import logging +import warnings from abc import ABC, abstractmethod from datetime import datetime, timezone from enum import Enum from typing import Dict, List, NamedTuple, Optional, Tuple -import warnings import arrow from pandas import DataFrame from freqtrade.data.dataprovider import DataProvider +from freqtrade.exceptions import StrategyError from freqtrade.exchange import timeframe_to_minutes from freqtrade.persistence import Trade +from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.wallets import Wallets - logger = logging.getLogger(__name__) @@ -255,20 +256,12 @@ class IStrategy(ABC): return False, False try: - dataframe = self._analyze_ticker_internal(dataframe, {'pair': pair}) - except ValueError as error: - logger.warning( - 'Unable to analyze ticker for pair %s: %s', - pair, - str(error) - ) - return False, False - except Exception as error: - logger.exception( - 'Unexpected error when analyzing ticker for pair %s: %s', - pair, - str(error) - ) + dataframe = strategy_safe_wrapper( + self._analyze_ticker_internal, message="" + )(dataframe, {'pair': pair}) + except StrategyError as error: + logger.warning(f"Unable to analyze ticker for pair {pair}: {error}") + return False, False if dataframe.empty: diff --git a/freqtrade/strategy/strategy_wrapper.py b/freqtrade/strategy/strategy_wrapper.py new file mode 100644 index 000000000..61c986732 --- /dev/null +++ b/freqtrade/strategy/strategy_wrapper.py @@ -0,0 +1,29 @@ +import logging + +from freqtrade.exceptions import StrategyError + +logger = logging.getLogger(__name__) + + +def strategy_safe_wrapper(f, message: str, default_retval=None): + def wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except ValueError as error: + logger.warning( + f"{message}" + f"Strategy caused the following exception: {error}" + f"{f}" + ) + if not default_retval: + raise StrategyError(str(error)) from error + return default_retval + except Exception as error: + logger.exception( + f"Unexpected error {error} calling {f}" + ) + if not default_retval: + raise StrategyError(str(error)) from error + return default_retval + + return wrapper diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 89c38bda1..2959fe62c 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -10,8 +10,8 @@ from freqtrade.configuration import TimeRange from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.data.history import load_tickerdata_file from freqtrade.persistence import Trade -from tests.conftest import get_patched_exchange, log_has from freqtrade.strategy.default_strategy import DefaultStrategy +from tests.conftest import get_patched_exchange, log_has, log_has_re # Avoid to reinit the same object again and again _STRATEGY = DefaultStrategy(config={}) @@ -65,7 +65,7 @@ def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_hi ) assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], ticker_history) - assert log_has('Unable to analyze ticker for pair foo: xyz', caplog) + assert log_has_re(r'Strategy caused the following exception: xyz.*', caplog) def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history): From 49dcc561b77461ce22f8f875c49814008592b078 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 6 Feb 2020 20:30:17 +0100 Subject: [PATCH 02/23] POC for check_buy_timeout --- freqtrade/freqtradebot.py | 7 ++++++- freqtrade/strategy/interface.py | 18 ++++++++++++++++++ freqtrade/strategy/strategy_wrapper.py | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e51b3d550..f458f91d6 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -26,6 +26,7 @@ from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.state import State from freqtrade.strategy.interface import IStrategy, SellType +from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.wallets import Wallets logger = logging.getLogger(__name__) @@ -819,7 +820,11 @@ class FreqtradeBot: continue if ((order['side'] == 'buy' and order['status'] == 'canceled') - or (self._check_timed_out('buy', order))): + or self._check_timed_out('buy', order) + or strategy_safe_wrapper(self.strategy.check_buy_timeout, + default_retval=False)(pair=trade.pair, + trade=trade, + order=order)): self.handle_timedout_limit_buy(trade, order) self.wallets.update() diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index c20bf0218..de08b7cda 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -149,6 +149,24 @@ class IStrategy(ABC): :return: DataFrame with sell column """ + def check_buy_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> bool: + """ + Check buy timeout function callback. + This method can be used to override the buy-timeout. + It is called whenever a limit buy order has been created, + and is not yet fully filled. + Configuration options in `unfilledtimeout` will be verified before this, + so ensure to set these timeouts high enough. + + When not implemented by a strategy, this simply returns False. + :param pair: Pair the trade is for + :param trade: trade object. + :param order: Order dictionary as returned from CCXT. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return bool: When True is returned, then the buy-order is cancelled. + """ + return False + def informative_pairs(self) -> List[Tuple[str, str]]: """ Define additional, informative pair/interval combinations to be cached from the exchange. diff --git a/freqtrade/strategy/strategy_wrapper.py b/freqtrade/strategy/strategy_wrapper.py index 61c986732..4f35bfbab 100644 --- a/freqtrade/strategy/strategy_wrapper.py +++ b/freqtrade/strategy/strategy_wrapper.py @@ -5,7 +5,7 @@ from freqtrade.exceptions import StrategyError logger = logging.getLogger(__name__) -def strategy_safe_wrapper(f, message: str, default_retval=None): +def strategy_safe_wrapper(f, message: str = "", default_retval=None): def wrapper(*args, **kwargs): try: return f(*args, **kwargs) From 8c1a9332215bc6dedc813188cae09b3a343e83e0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Feb 2020 20:23:43 +0100 Subject: [PATCH 03/23] cancel_order should return a dict --- freqtrade/exchange/exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b3b347016..7fc2af308 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -894,9 +894,9 @@ class Exchange: until=until, from_id=from_id)) @retrier - def cancel_order(self, order_id: str, pair: str) -> None: + def cancel_order(self, order_id: str, pair: str) -> Dict: if self._config['dry_run']: - return + return {} try: return self._api.cancel_order(order_id, pair) From 6c01542fed34a71f44ae3b4aa75a0c8bce0b302f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Feb 2020 20:27:13 +0100 Subject: [PATCH 04/23] Ad check_sell_timeout --- freqtrade/freqtradebot.py | 6 +++++- freqtrade/strategy/interface.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f458f91d6..aa41c2f2a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -830,7 +830,11 @@ class FreqtradeBot: self.wallets.update() elif ((order['side'] == 'sell' and order['status'] == 'canceled') - or (self._check_timed_out('sell', order))): + or (self._check_timed_out('sell', order)) + or strategy_safe_wrapper(self.strategy.check_sell_timeout, + default_retval=False)(pair=trade.pair, + trade=trade, + order=order)): self.handle_timedout_limit_sell(trade, order) self.wallets.update() diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index de08b7cda..681d2ccfb 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -167,6 +167,24 @@ class IStrategy(ABC): """ return False + def check_sell_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> bool: + """ + Check sell timeout function callback. + This method can be used to override the sell-timeout. + It is called whenever a limit sell order has been created, + and is not yet fully filled. + Configuration options in `unfilledtimeout` will be verified before this, + so ensure to set these timeouts high enough. + + When not implemented by a strategy, this simply returns False. + :param pair: Pair the trade is for + :param trade: trade object. + :param order: Order dictionary as returned from CCXT. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return bool: When True is returned, then the sell-order is cancelled. + """ + return False + def informative_pairs(self) -> List[Tuple[str, str]]: """ Define additional, informative pair/interval combinations to be cached from the exchange. From 135d9ddf7ac4138ebd3b0650cf25655e67ecba32 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Feb 2020 20:35:13 +0100 Subject: [PATCH 05/23] Fix test due to changed dry-run cancel order --- tests/exchange/test_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 8b2e439c3..acef073f1 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1634,7 +1634,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange def test_cancel_order_dry_run(default_conf, mocker, exchange_name): default_conf['dry_run'] = True exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - assert exchange.cancel_order(order_id='123', pair='TKN/BTC') is None + assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} # Ensure that if not dry_run, we should call API From bc30162a31b99078a3866659377210b084fccbbe Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 21 Feb 2020 20:54:21 +0100 Subject: [PATCH 06/23] Add some documentation --- docs/strategy-customization.md | 3 +-- mkdocs.yml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 07833da34..5746cc613 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -1,7 +1,6 @@ # Strategy Customization -This page explains where to customize your strategies, and add new -indicators. +This page explains where to customize your strategies, and add new indicators. ## Install a custom strategy file diff --git a/mkdocs.yml b/mkdocs.yml index d53687c64..528b77eb5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,6 +24,7 @@ nav: - Plotting: plotting.md - SQL Cheatsheet: sql_cheatsheet.md - Advanced Post-installation Tasks: advanced-setup.md + - Advanced Strategy: strategy-advanced.md - Advanced Hyperopt: advanced-hyperopt.md - Sandbox Testing: sandbox-testing.md - Deprecated Features: deprecated.md From 63502ed976a9cd5ffc75d09b8b5295fc34dca1e4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Feb 2020 08:14:08 +0100 Subject: [PATCH 07/23] Add new advanced-strategy documentation file --- docs/strategy-advanced.md | 59 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/strategy-advanced.md diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md new file mode 100644 index 000000000..bdb380276 --- /dev/null +++ b/docs/strategy-advanced.md @@ -0,0 +1,59 @@ +# Advanced Strategies + +This page explains some advanced concepts available for strategies. +If you're just getting started, please be familiar with the methods described in the [Strategy Customization](strategy-customization.md) documentation first. + +## Custom order timeout rules + +Simple, timebased order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section. + +However, freqtrade also offers a custom callback for both ordertypes, which allows you to decide based on custom criteria if a order did time out or not. + +!!! Note: + Unfilled order timeouts are not relevant during backtesting or hyperopt, and are only relevant during real (live) trading. Therefore these methods are only called in these circumstances. + +### Custom order timeout example + +A simple example, which applies different unfilled-timeouts depending on the price of the asset can be seen below. +It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins. + +The function must return either `True` (cancel order) or `False` (keep order alive). + +``` python +from datetime import datetime, timestamp +from freqtrade.persistence import Trade + +class Awesomestrategy(IStrategy): + + # ... populate_* methods + + # Set unfilledtimeout to 25 hours, since our maximum timeout from below is 24 hours. + unfilledtimeout = { + 'buy': 60 * 25, + 'sell': 60 * 25 + } + + def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + if trade.open_rate > 100 and trade.open_date < datetime.utcnow() - timedelta(minutes=5): + return True + elif trade.open_rate > 10 and trade.open_date < datetime.utcnow() - timedelta(minutes=3): + return True + elif trade.open_rate < 1 and trade.open_date < datetime.utcnow() - timedelta(hours=24): + return True + return True + + + def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + if trade.open_rate > 100 and trade.open_date < datetime.utcnow() - timedelta(minutes=5): + return True + elif trade.open_rate > 10 and trade.open_date < datetime.utcnow() - timedelta(minutes=3): + return True + elif trade.open_rate < 1 and trade.open_date < datetime.utcnow() - timedelta(hours=24): + return True + return True +``` + +!!! Note: + For the above example, `unfilledtimeout` must be set to something bigger than 24h, otherwise that type of timeout will apply first. + + From 4a188525ec610b8e0b41bb028f9803bf98abaa8a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Feb 2020 11:28:13 +0100 Subject: [PATCH 08/23] Fix documentation note syntax --- docs/strategy-advanced.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index bdb380276..a60a6ea47 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -9,7 +9,7 @@ Simple, timebased order-timeouts can be configured either via strategy or in the However, freqtrade also offers a custom callback for both ordertypes, which allows you to decide based on custom criteria if a order did time out or not. -!!! Note: +!!! Note Unfilled order timeouts are not relevant during backtesting or hyperopt, and are only relevant during real (live) trading. Therefore these methods are only called in these circumstances. ### Custom order timeout example @@ -53,7 +53,5 @@ class Awesomestrategy(IStrategy): return True ``` -!!! Note: +!!! Note For the above example, `unfilledtimeout` must be set to something bigger than 24h, otherwise that type of timeout will apply first. - - From 365fdf4c3732bdbe588e18f052b81648d0c551f7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Feb 2020 11:41:22 +0100 Subject: [PATCH 09/23] Add docstring to strategy wrapper --- freqtrade/strategy/strategy_wrapper.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/strategy_wrapper.py b/freqtrade/strategy/strategy_wrapper.py index 4f35bfbab..597432255 100644 --- a/freqtrade/strategy/strategy_wrapper.py +++ b/freqtrade/strategy/strategy_wrapper.py @@ -6,6 +6,11 @@ logger = logging.getLogger(__name__) def strategy_safe_wrapper(f, message: str = "", default_retval=None): + """ + Wrapper around user-provided methods and functions. + Caches all exceptions and returns either the default_retval (if it's not None) or raises + a StrategyError exception, which then needs to be handled by the calling method. + """ def wrapper(*args, **kwargs): try: return f(*args, **kwargs) @@ -15,14 +20,14 @@ def strategy_safe_wrapper(f, message: str = "", default_retval=None): f"Strategy caused the following exception: {error}" f"{f}" ) - if not default_retval: + if default_retval is None: raise StrategyError(str(error)) from error return default_retval except Exception as error: logger.exception( f"Unexpected error {error} calling {f}" ) - if not default_retval: + if default_retval is None: raise StrategyError(str(error)) from error return default_retval From 8cd77b2e2708a30fd3c01002f21aca8c23673e7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 22 Feb 2020 11:52:39 +0100 Subject: [PATCH 10/23] Add some tests for strategy_wrapper --- freqtrade/strategy/strategy_wrapper.py | 1 + tests/strategy/test_interface.py | 38 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/freqtrade/strategy/strategy_wrapper.py b/freqtrade/strategy/strategy_wrapper.py index 597432255..7b9da9140 100644 --- a/freqtrade/strategy/strategy_wrapper.py +++ b/freqtrade/strategy/strategy_wrapper.py @@ -25,6 +25,7 @@ def strategy_safe_wrapper(f, message: str = "", default_retval=None): return default_retval except Exception as error: logger.exception( + f"{message}" f"Unexpected error {error} calling {f}" ) if default_retval is None: diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index ca9bdc504..1db01b3ac 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -4,12 +4,15 @@ import logging from unittest.mock import MagicMock import arrow +import pytest from pandas import DataFrame from freqtrade.configuration import TimeRange from freqtrade.data.history import load_data +from freqtrade.exceptions import StrategyError from freqtrade.persistence import Trade from freqtrade.resolvers import StrategyResolver +from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from tests.conftest import get_patched_exchange, log_has, log_has_re from .strats.default_strategy import DefaultStrategy @@ -323,3 +326,38 @@ def test_is_pair_locked(default_conf): pair = 'ETH/BTC' strategy.unlock_pair(pair) assert not strategy.is_pair_locked(pair) + + +@pytest.mark.parametrize('error', [ + ValueError, KeyError, Exception, +]) +def test_strategy_safe_wrapper_error(caplog, error): + def failing_method(): + raise error('This is an error.') + + def working_method(argumentpassedin): + return argumentpassedin + + with pytest.raises(StrategyError, match=r'This is an error.'): + strategy_safe_wrapper(failing_method, message='DeadBeef')() + + assert log_has_re(r'DeadBeef.*', caplog) + ret = strategy_safe_wrapper(failing_method, message='DeadBeef', default_retval=True)() + + assert isinstance(ret, bool) + assert ret + + +@pytest.mark.parametrize('value', [ + 1, 22, 55, True, False, {'a': 1, 'b': '112'}, + [1, 2, 3, 4], (4, 2, 3, 6) +]) +def test_strategy_safe_wrapper(value): + + def working_method(argumentpassedin): + return argumentpassedin + + ret = strategy_safe_wrapper(working_method, message='DeadBeef')(value) + + assert type(ret) == type(value) + assert ret == value From 634e7cc34a7f7adc10216d00418532780753a067 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 13:04:40 +0100 Subject: [PATCH 11/23] Implement handle_buy_trade_customcallback --- tests/test_freqtradebot.py | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 5ed4d296c..68ce733b1 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1914,6 +1914,53 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, freqtrade.handle_trade(trade) +def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_order_old, open_trade, + fee, mocker) -> None: + default_conf["unfilledtimeout"] = {"buy": 1400, "sell": 30} + + rpc_mock = patch_RPCManager(mocker) + cancel_order_mock = MagicMock(return_value=limit_buy_order_old) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_order=MagicMock(return_value=limit_buy_order_old), + cancel_order=cancel_order_mock, + get_fee=fee + ) + freqtrade = FreqtradeBot(default_conf) + + Trade.session.add(open_trade) + + # Return false - trade remains open + freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False) + freqtrade.check_handle_timedout() + assert cancel_order_mock.call_count == 0 + trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + nb_trades = len(trades) + assert nb_trades == 1 + assert freqtrade.strategy.check_buy_timeout.call_count == 1 + + # Raise Keyerror ... (no impact on trade) + freqtrade.strategy.check_buy_timeout = MagicMock(side_effect=KeyError) + freqtrade.check_handle_timedout() + assert cancel_order_mock.call_count == 0 + trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + nb_trades = len(trades) + assert nb_trades == 1 + assert freqtrade.strategy.check_buy_timeout.call_count == 1 + + freqtrade.strategy.check_buy_timeout = MagicMock(return_value=True) + # Trade should be closed since the function returns true + freqtrade.check_handle_timedout() + assert cancel_order_mock.call_count == 1 + assert rpc_mock.call_count == 1 + trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() + nb_trades = len(trades) + assert nb_trades == 0 + assert freqtrade.strategy.check_buy_timeout.call_count == 1 + + def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, open_trade, fee, mocker) -> None: rpc_mock = patch_RPCManager(mocker) From 9301f81fc8f5eca1003586290c86514bc22e72de Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 13:09:46 +0100 Subject: [PATCH 12/23] Add test for user-sell_timeout handling --- tests/test_freqtradebot.py | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 68ce733b1..e4b9a28ce 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2040,6 +2040,51 @@ def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_ord assert nb_trades == 1 +def test_check_handle_timedout_sell_usercustom(default_conf, ticker, limit_sell_order_old, mocker, + open_trade) -> None: + default_conf["unfilledtimeout"] = {"buy": 1440, "sell": 1440} + rpc_mock = patch_RPCManager(mocker) + cancel_order_mock = MagicMock() + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_order=MagicMock(return_value=limit_sell_order_old), + cancel_order=cancel_order_mock + ) + freqtrade = FreqtradeBot(default_conf) + + open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime + open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime + open_trade.is_open = False + + Trade.session.add(open_trade) + + freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False) + # Return false - No impact + freqtrade.check_handle_timedout() + assert cancel_order_mock.call_count == 0 + assert rpc_mock.call_count == 0 + assert open_trade.is_open is False + assert freqtrade.strategy.check_sell_timeout.call_count == 1 + + freqtrade.strategy.check_sell_timeout = MagicMock(side_effect=KeyError) + # Return Error - No impact + freqtrade.check_handle_timedout() + assert cancel_order_mock.call_count == 0 + assert rpc_mock.call_count == 0 + assert open_trade.is_open is False + assert freqtrade.strategy.check_sell_timeout.call_count == 1 + + # Return True - sells! + freqtrade.strategy.check_sell_timeout = MagicMock(return_value=True) + freqtrade.check_handle_timedout() + assert cancel_order_mock.call_count == 1 + assert rpc_mock.call_count == 1 + assert open_trade.is_open is True + assert freqtrade.strategy.check_sell_timeout.call_count == 1 + + def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker, open_trade) -> None: rpc_mock = patch_RPCManager(mocker) From e37f055dad850b366439ff6531777ca17f268ed5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Feb 2020 13:11:33 +0100 Subject: [PATCH 13/23] Improve some tests --- tests/test_freqtradebot.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e4b9a28ce..0766d7f33 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1977,6 +1977,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, op Trade.session.add(open_trade) + freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False) # check it does cancel buy orders over the time limit freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 @@ -1984,6 +1985,8 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, op trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() nb_trades = len(trades) assert nb_trades == 0 + # Custom user buy-timeout is never called + assert freqtrade.strategy.check_buy_timeout.call_count == 0 def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, open_trade, @@ -2104,11 +2107,14 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, Trade.session.add(open_trade) + freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False) # check it does cancel sell orders over the time limit freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 1 assert open_trade.is_open is True + # Custom user sell-timeout is never called + assert freqtrade.strategy.check_sell_timeout.call_count == 0 def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old, open_trade, From 47e46bf205602caaa19b88fdfb863a308ba1a18b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 14:48:46 +0100 Subject: [PATCH 14/23] Add second example using dataprovider and current price --- docs/strategy-advanced.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index a60a6ea47..bdcd29f46 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -40,7 +40,7 @@ class Awesomestrategy(IStrategy): return True elif trade.open_rate < 1 and trade.open_date < datetime.utcnow() - timedelta(hours=24): return True - return True + return False def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: @@ -50,8 +50,43 @@ class Awesomestrategy(IStrategy): return True elif trade.open_rate < 1 and trade.open_date < datetime.utcnow() - timedelta(hours=24): return True - return True + return False ``` !!! Note For the above example, `unfilledtimeout` must be set to something bigger than 24h, otherwise that type of timeout will apply first. + + +### Custom order timeout example (using additional data) + +``` python +from datetime import datetime, timestamp +from freqtrade.persistence import Trade + +class Awesomestrategy(IStrategy): + + # ... populate_* methods + + # Set unfilledtimeout to 25 hours, since our maximum timeout from below is 24 hours. + unfilledtimeout = { + 'buy': 60 * 25, + 'sell': 60 * 25 + } + + def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + ob = self.dp.orderbook(pair, 1) + current_price = ob['bids'][0][0] + # Cancel buy order if price is more than 2% above the order. + if order['price'] * 0.98 < best_bid: + return True + return False + + + def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + ob = self.dp.orderbook(pair, 1) + current_price = ob['asks'][0][0] + # Cancel sell order if price is more than 2% below the order. + if order['price'] * 1.02 > current_price: + return True + return False +``` From d44f6651c4a63030dcaaecbda1431a020cc21ef0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 19:55:23 +0100 Subject: [PATCH 15/23] Fix small parenteses bug --- docs/strategy-advanced.md | 4 ++-- freqtrade/freqtradebot.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index bdcd29f46..8d241cc86 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -77,7 +77,7 @@ class Awesomestrategy(IStrategy): ob = self.dp.orderbook(pair, 1) current_price = ob['bids'][0][0] # Cancel buy order if price is more than 2% above the order. - if order['price'] * 0.98 < best_bid: + if order['price'] > current_price * 1.02: return True return False @@ -86,7 +86,7 @@ class Awesomestrategy(IStrategy): ob = self.dp.orderbook(pair, 1) current_price = ob['asks'][0][0] # Cancel sell order if price is more than 2% below the order. - if order['price'] * 1.02 > current_price: + if order['price'] < current_price * 0.98: return True return False ``` diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5adbe76f4..05cd60e5e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -847,24 +847,24 @@ class FreqtradeBot: self.wallets.update() continue - if ((order['side'] == 'buy' and order['status'] == 'canceled') + if (order['side'] == 'buy' and (order['status'] == 'canceled' or self._check_timed_out('buy', order) or strategy_safe_wrapper(self.strategy.check_buy_timeout, default_retval=False)(pair=trade.pair, trade=trade, - order=order)): + order=order))): self.handle_timedout_limit_buy(trade, order) self.wallets.update() order_type = self.strategy.order_types['buy'] self._notify_buy_cancel(trade, order_type) - elif ((order['side'] == 'sell' and order['status'] == 'canceled') + elif (order['side'] == 'sell' and (order['status'] == 'canceled' or self._check_timed_out('sell', order) or strategy_safe_wrapper(self.strategy.check_sell_timeout, default_retval=False)(pair=trade.pair, trade=trade, - order=order)): + order=order))): self.handle_timedout_limit_sell(trade, order) self.wallets.update() order_type = self.strategy.order_types['sell'] From a030ce9348bd5229005631fbdd2e91908f057f7a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 25 Feb 2020 20:22:59 +0100 Subject: [PATCH 16/23] Reformat if condition --- freqtrade/freqtradebot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 05cd60e5e..fa0981448 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -847,7 +847,8 @@ class FreqtradeBot: self.wallets.update() continue - if (order['side'] == 'buy' and (order['status'] == 'canceled' + if (order['side'] == 'buy' and ( + order['status'] == 'canceled' or self._check_timed_out('buy', order) or strategy_safe_wrapper(self.strategy.check_buy_timeout, default_retval=False)(pair=trade.pair, @@ -859,7 +860,8 @@ class FreqtradeBot: order_type = self.strategy.order_types['buy'] self._notify_buy_cancel(trade, order_type) - elif (order['side'] == 'sell' and (order['status'] == 'canceled' + elif (order['side'] == 'sell' and ( + order['status'] == 'canceled' or self._check_timed_out('sell', order) or strategy_safe_wrapper(self.strategy.check_sell_timeout, default_retval=False)(pair=trade.pair, From eda77aeec8555bf5c27e1a574f63497773e4d38c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Mar 2020 09:30:30 +0100 Subject: [PATCH 17/23] Add render_template fallback --- freqtrade/misc.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 96bac28d8..9eb309e13 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -148,3 +148,15 @@ def render_template(templatefile: str, arguments: dict = {}) -> str: ) template = env.get_template(templatefile) return template.render(**arguments) + + +def render_template_with_fallback(templatefile: str, templatefallbackfile: str, + arguments: dict = {}) -> str: + """ + Use templatefile if possible, otherwise fall back to templatefallbackfile + """ + from jinja2.exceptions import TemplateNotFound + try: + return render_template(templatefile, arguments) + except TemplateNotFound: + return render_template(templatefallbackfile, arguments) From 7736f8d0180f8e4ae86aed14867131443be9557a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Mar 2020 09:34:42 +0100 Subject: [PATCH 18/23] Add tests for fallkback --- tests/test_misc.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_misc.py b/tests/test_misc.py index 83e008466..23775c85e 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -9,7 +9,8 @@ import pytest from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.misc import (datesarray_to_datetimearray, file_dump_json, file_load_json, format_ms_time, pair_to_filename, - plural, shorten_date) + plural, render_template, + render_template_with_fallback, shorten_date) def test_shorten_date() -> None: @@ -123,3 +124,17 @@ def test_plural() -> None: assert plural(1.5, "ox", "oxen") == "oxen" assert plural(-0.5, "ox", "oxen") == "oxen" assert plural(-1.5, "ox", "oxen") == "oxen" + + +def test_render_template_fallback(mocker): + from jinja2.exceptions import TemplateNotFound + with pytest.raises(TemplateNotFound): + val = render_template( + templatefile='subtemplates/indicators_does-not-exist.j2',) + + val = render_template_with_fallback( + templatefile='subtemplates/indicators_does-not-exist.j2', + templatefallbackfile='subtemplates/indicators_minimal.j2', + ) + assert isinstance(val, str) + assert 'if self.dp' in val From 791148176c90ce493952268c6401052c2b6f32ba Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Mar 2020 09:35:53 +0100 Subject: [PATCH 19/23] Add callback functions to new-strategy --template advanced --- freqtrade/commands/cli_options.py | 6 +-- freqtrade/commands/deploy_commands.py | 53 ++++++++++++++----- freqtrade/templates/base_strategy.py.j2 | 1 + .../subtemplates/strategy_methods_advanced.j2 | 36 +++++++++++++ .../subtemplates/strategy_methods_empty.j2 | 0 5 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 freqtrade/templates/subtemplates/strategy_methods_advanced.j2 create mode 100644 freqtrade/templates/subtemplates/strategy_methods_empty.j2 diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index a8d4bc198..2c49d7487 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -379,9 +379,9 @@ AVAILABLE_CLI_OPTIONS = { # Templating options "template": Arg( '--template', - help='Use a template which is either `minimal` or ' - '`full` (containing multiple sample indicators). Default: `%(default)s`.', - choices=['full', 'minimal'], + help='Use a template which is either `minimal`, ' + '`full` (containing multiple sample indicators) or `advanced`. Default: `%(default)s`.', + choices=['full', 'minimal', 'advanced'], default='full', ), # Plot dataframe diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py index f5a68f748..a29ba346f 100644 --- a/freqtrade/commands/deploy_commands.py +++ b/freqtrade/commands/deploy_commands.py @@ -8,7 +8,7 @@ from freqtrade.configuration.directory_operations import (copy_sample_files, create_userdata_dir) from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGIES from freqtrade.exceptions import OperationalException -from freqtrade.misc import render_template +from freqtrade.misc import render_template, render_template_with_fallback from freqtrade.state import RunMode logger = logging.getLogger(__name__) @@ -32,10 +32,27 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st """ Deploy new strategy from template to strategy_path """ - indicators = render_template(templatefile=f"subtemplates/indicators_{subtemplate}.j2",) - buy_trend = render_template(templatefile=f"subtemplates/buy_trend_{subtemplate}.j2",) - sell_trend = render_template(templatefile=f"subtemplates/sell_trend_{subtemplate}.j2",) - plot_config = render_template(templatefile=f"subtemplates/plot_config_{subtemplate}.j2",) + fallback = 'full' + indicators = render_template_with_fallback( + templatefile=f"subtemplates/indicators_{subtemplate}.j2", + templatefallbackfile=f"subtemplates/indicators_{fallback}.j2", + ) + buy_trend = render_template_with_fallback( + templatefile=f"subtemplates/buy_trend_{subtemplate}.j2", + templatefallbackfile=f"subtemplates/buy_trend_{fallback}.j2", + ) + sell_trend = render_template_with_fallback( + templatefile=f"subtemplates/sell_trend_{subtemplate}.j2", + templatefallbackfile=f"subtemplates/sell_trend_{fallback}.j2", + ) + plot_config = render_template_with_fallback( + templatefile=f"subtemplates/plot_config_{subtemplate}.j2", + templatefallbackfile=f"subtemplates/plot_config_{fallback}.j2", + ) + additional_methods = render_template_with_fallback( + templatefile=f"subtemplates/strategy_methods_{subtemplate}.j2", + templatefallbackfile=f"subtemplates/strategy_methods_empty.j2", + ) strategy_text = render_template(templatefile='base_strategy.py.j2', arguments={"strategy": strategy_name, @@ -43,6 +60,7 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st "buy_trend": buy_trend, "sell_trend": sell_trend, "plot_config": plot_config, + "additional_methods": additional_methods, }) logger.info(f"Writing strategy to `{strategy_path}`.") @@ -73,14 +91,23 @@ def deploy_new_hyperopt(hyperopt_name: str, hyperopt_path: Path, subtemplate: st """ Deploys a new hyperopt template to hyperopt_path """ - buy_guards = render_template( - templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2",) - sell_guards = render_template( - templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2",) - buy_space = render_template( - templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2",) - sell_space = render_template( - templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2",) + fallback = 'full' + buy_guards = render_template_with_fallback( + templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2", + templatefallbackfile=f"subtemplates/hyperopt_buy_guards_{fallback}.j2", + ) + sell_guards = render_template_with_fallback( + templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2", + templatefallbackfile=f"subtemplates/hyperopt_sell_guards_{fallback}.j2", + ) + buy_space = render_template_with_fallback( + templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2", + templatefallbackfile=f"subtemplates/hyperopt_buy_space_{fallback}.j2", + ) + sell_space = render_template_with_fallback( + templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2", + templatefallbackfile=f"subtemplates/hyperopt_sell_space_{fallback}.j2", + ) strategy_text = render_template(templatefile='base_hyperopt.py.j2', arguments={"hyperopt": hyperopt_name, diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index fbf083387..a1b9f7388 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -137,3 +137,4 @@ class {{ strategy }}(IStrategy): ), 'sell'] = 1 return dataframe + {{ additional_methods | indent(4) }} diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 new file mode 100644 index 000000000..05541d1c7 --- /dev/null +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -0,0 +1,36 @@ + +def check_buy_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> bool: + """ + Check buy timeout function callback. + This method can be used to override the buy-timeout. + It is called whenever a limit buy order has been created, + and is not yet fully filled. + Configuration options in `unfilledtimeout` will be verified before this, + so ensure to set these timeouts high enough. + + When not implemented by a strategy, this simply returns False. + :param pair: Pair the trade is for + :param trade: trade object. + :param order: Order dictionary as returned from CCXT. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return bool: When True is returned, then the buy-order is cancelled. + """ + return False + +def check_sell_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> bool: + """ + Check sell timeout function callback. + This method can be used to override the sell-timeout. + It is called whenever a limit sell order has been created, + and is not yet fully filled. + Configuration options in `unfilledtimeout` will be verified before this, + so ensure to set these timeouts high enough. + + When not implemented by a strategy, this simply returns False. + :param pair: Pair the trade is for + :param trade: trade object. + :param order: Order dictionary as returned from CCXT. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return bool: When True is returned, then the sell-order is cancelled. + """ + return False diff --git a/freqtrade/templates/subtemplates/strategy_methods_empty.j2 b/freqtrade/templates/subtemplates/strategy_methods_empty.j2 new file mode 100644 index 000000000..e69de29bb From cd54875f03ab8fb2444503bf50ec9bd8837a42fe Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Mar 2020 09:40:07 +0100 Subject: [PATCH 20/23] Add documentation link to advanced functions --- .../templates/subtemplates/strategy_methods_advanced.j2 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index 05541d1c7..20144125c 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -1,5 +1,5 @@ -def check_buy_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> bool: +def check_buy_timeout(self, pair: str, trade: 'Trade', order: Dict, **kwargs) -> bool: """ Check buy timeout function callback. This method can be used to override the buy-timeout. @@ -8,6 +8,8 @@ def check_buy_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> b Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for :param trade: trade object. @@ -17,7 +19,7 @@ def check_buy_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> b """ return False -def check_sell_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> bool: +def check_sell_timeout(self, pair: str, trade: 'Trade', order: Dict, **kwargs) -> bool: """ Check sell timeout function callback. This method can be used to override the sell-timeout. @@ -26,6 +28,8 @@ def check_sell_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for :param trade: trade object. From 4d8430c687de23be59b7914374f5d5c5f72b92c6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Mar 2020 09:43:20 +0100 Subject: [PATCH 21/23] Use string typehints to avoid import errors --- docs/strategy-advanced.md | 4 ++-- freqtrade/strategy/interface.py | 4 ++-- freqtrade/templates/subtemplates/strategy_methods_advanced.j2 | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 8d241cc86..dcb8018f9 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -33,7 +33,7 @@ class Awesomestrategy(IStrategy): 'sell': 60 * 25 } - def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date < datetime.utcnow() - timedelta(minutes=5): return True elif trade.open_rate > 10 and trade.open_date < datetime.utcnow() - timedelta(minutes=3): @@ -43,7 +43,7 @@ class Awesomestrategy(IStrategy): return False - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date < datetime.utcnow() - timedelta(minutes=5): return True elif trade.open_rate > 10 and trade.open_date < datetime.utcnow() - timedelta(minutes=3): diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 95dbbb99f..a5945ae1f 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -149,7 +149,7 @@ class IStrategy(ABC): :return: DataFrame with sell column """ - def check_buy_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> bool: + def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: """ Check buy timeout function callback. This method can be used to override the buy-timeout. @@ -167,7 +167,7 @@ class IStrategy(ABC): """ return False - def check_sell_timeout(self, pair: str, trade: Trade, order: Dict, **kwargs) -> bool: + def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: """ Check sell timeout function callback. This method can be used to override the sell-timeout. diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index 20144125c..0ca35e117 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -1,5 +1,5 @@ -def check_buy_timeout(self, pair: str, trade: 'Trade', order: Dict, **kwargs) -> bool: +def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: """ Check buy timeout function callback. This method can be used to override the buy-timeout. @@ -19,7 +19,7 @@ def check_buy_timeout(self, pair: str, trade: 'Trade', order: Dict, **kwargs) -> """ return False -def check_sell_timeout(self, pair: str, trade: 'Trade', order: Dict, **kwargs) -> bool: +def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: """ Check sell timeout function callback. This method can be used to override the sell-timeout. From 0f2d77163455ed1c163a26bee03592a5c8f3a2d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Mar 2020 09:46:12 +0100 Subject: [PATCH 22/23] update docs --- docs/utils.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index cdf0c31af..78185be38 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -77,7 +77,7 @@ Results will be located in `user_data/strategies/.py`. ``` output usage: freqtrade new-strategy [-h] [--userdir PATH] [-s NAME] - [--template {full,minimal}] + [--template {full,minimal,advanced}] optional arguments: -h, --help show this help message and exit @@ -86,10 +86,10 @@ optional arguments: -s NAME, --strategy NAME Specify strategy class name which will be used by the bot. - --template {full,minimal} - Use a template which is either `minimal` or `full` - (containing multiple sample indicators). Default: - `full`. + --template {full,minimal,advanced} + Use a template which is either `minimal`, `full` + (containing multiple sample indicators) or `advanced`. + Default: `full`. ``` @@ -105,6 +105,12 @@ With custom user directory freqtrade new-strategy --userdir ~/.freqtrade/ --strategy AwesomeStrategy ``` +Using the advanced template (populates all optional functions and methods) + +```bash +freqtrade new-strategy --strategy AwesomeStrategy --template advanced +``` + ## Create new hyperopt Creates a new hyperopt from a template similar to SampleHyperopt. @@ -114,7 +120,7 @@ Results will be located in `user_data/hyperopts/.py`. ``` output usage: freqtrade new-hyperopt [-h] [--userdir PATH] [--hyperopt NAME] - [--template {full,minimal}] + [--template {full,minimal,advanced}] optional arguments: -h, --help show this help message and exit @@ -122,10 +128,10 @@ optional arguments: Path to userdata directory. --hyperopt NAME Specify hyperopt class name which will be used by the bot. - --template {full,minimal} - Use a template which is either `minimal` or `full` - (containing multiple sample indicators). Default: - `full`. + --template {full,minimal,advanced} + Use a template which is either `minimal`, `full` + (containing multiple sample indicators) or `advanced`. + Default: `full`. ``` ### Sample usage of new-hyperopt From 255a43c98e75d18fe0a6bc04c513467c7ce85ffe Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Apr 2020 17:25:18 +0200 Subject: [PATCH 23/23] Update price timeout example --- docs/strategy-advanced.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index dcb8018f9..39e92d651 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -56,7 +56,6 @@ class Awesomestrategy(IStrategy): !!! Note For the above example, `unfilledtimeout` must be set to something bigger than 24h, otherwise that type of timeout will apply first. - ### Custom order timeout example (using additional data) ``` python @@ -77,7 +76,7 @@ class Awesomestrategy(IStrategy): ob = self.dp.orderbook(pair, 1) current_price = ob['bids'][0][0] # Cancel buy order if price is more than 2% above the order. - if order['price'] > current_price * 1.02: + if current_price > order['price'] * 1.02: return True return False @@ -86,7 +85,7 @@ class Awesomestrategy(IStrategy): ob = self.dp.orderbook(pair, 1) current_price = ob['asks'][0][0] # Cancel sell order if price is more than 2% below the order. - if order['price'] < current_price * 0.98: + if current_price < order['price'] * 0.98: return True return False ```