Merge pull request #2872 from freqtrade/interface_ordertimeoutcallback
Buy order timeout callback
This commit is contained in:
@@ -387,9 +387,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
|
||||
|
@@ -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,
|
||||
|
@@ -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.
|
||||
"""
|
||||
|
@@ -27,6 +27,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__)
|
||||
@@ -869,15 +870,24 @@ class FreqtradeBot:
|
||||
|
||||
if (order['side'] == 'buy' and (
|
||||
trade_state_update
|
||||
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()
|
||||
order_type = self.strategy.order_types['buy']
|
||||
self._notify_buy_cancel(trade, order_type)
|
||||
|
||||
elif (order['side'] == 'sell' and (
|
||||
trade_state_update
|
||||
or self._check_timed_out('sell', order))):
|
||||
trade_state_update
|
||||
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))):
|
||||
reason = self.handle_timedout_limit_sell(trade, order)
|
||||
self.wallets.update()
|
||||
order_type = self.strategy.order_types['sell']
|
||||
|
@@ -163,3 +163,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)
|
||||
|
@@ -3,21 +3,21 @@ 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
|
||||
from freqtrade.exceptions import DependencyException
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -149,6 +149,42 @@ 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 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.
|
||||
@@ -258,8 +294,7 @@ class IStrategy(ABC):
|
||||
elif df_date != dataframe["date"].iloc[-1]:
|
||||
message = "last date"
|
||||
if message:
|
||||
raise DependencyException("Dataframe returned from strategy has mismatching "
|
||||
f"{message}.")
|
||||
raise StrategyError(f"Dataframe returned from strategy has mismatching {message}.")
|
||||
|
||||
def get_signal(self, pair: str, interval: str, dataframe: DataFrame) -> Tuple[bool, bool]:
|
||||
"""
|
||||
@@ -276,22 +311,13 @@ class IStrategy(ABC):
|
||||
latest_date = dataframe['date'].max()
|
||||
try:
|
||||
df_len, df_close, df_date = self.preserve_df(dataframe)
|
||||
dataframe = self._analyze_ticker_internal(dataframe, {'pair': pair})
|
||||
dataframe = strategy_safe_wrapper(
|
||||
self._analyze_ticker_internal, message=""
|
||||
)(dataframe, {'pair': pair})
|
||||
self.assert_df(dataframe, df_len, df_close, df_date)
|
||||
except ValueError as error:
|
||||
logger.warning('Unable to analyze candle (OHLCV) data for pair %s: %s',
|
||||
pair, str(error))
|
||||
return False, False
|
||||
except DependencyException as error:
|
||||
logger.warning("Unable to analyze candle (OHLCV) data for pair %s: %s",
|
||||
pair, str(error))
|
||||
return False, False
|
||||
except Exception as error:
|
||||
logger.exception(
|
||||
'Unexpected error when analyzing candle (OHLCV) data for pair %s: %s',
|
||||
pair,
|
||||
str(error)
|
||||
)
|
||||
except StrategyError as error:
|
||||
logger.warning(f"Unable to analyze candle (OHLCV) data for pair {pair}: {error}")
|
||||
|
||||
return False, False
|
||||
|
||||
if dataframe.empty:
|
||||
|
35
freqtrade/strategy/strategy_wrapper.py
Normal file
35
freqtrade/strategy/strategy_wrapper.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import logging
|
||||
|
||||
from freqtrade.exceptions import StrategyError
|
||||
|
||||
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)
|
||||
except ValueError as error:
|
||||
logger.warning(
|
||||
f"{message}"
|
||||
f"Strategy caused the following exception: {error}"
|
||||
f"{f}"
|
||||
)
|
||||
if default_retval is None:
|
||||
raise StrategyError(str(error)) from error
|
||||
return default_retval
|
||||
except Exception as error:
|
||||
logger.exception(
|
||||
f"{message}"
|
||||
f"Unexpected error {error} calling {f}"
|
||||
)
|
||||
if default_retval is None:
|
||||
raise StrategyError(str(error)) from error
|
||||
return default_retval
|
||||
|
||||
return wrapper
|
@@ -137,3 +137,4 @@ class {{ strategy }}(IStrategy):
|
||||
),
|
||||
'sell'] = 1
|
||||
return dataframe
|
||||
{{ additional_methods | indent(4) }}
|
||||
|
@@ -0,0 +1,40 @@
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
: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.
|
||||
|
||||
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.
|
||||
: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
|
Reference in New Issue
Block a user