Merge branch 'develop' into feature_keyval_storage

This commit is contained in:
eSeR1805 2022-06-05 12:18:30 +03:00
commit 9f1a7209d6
No known key found for this signature in database
GPG Key ID: BA53686259B46936
13 changed files with 123 additions and 72 deletions

View File

@ -98,6 +98,23 @@ class MyAwesomeStrategy(IStrategy):
!!! Note !!! Note
All overrides are optional and can be mixed/matched as necessary. All overrides are optional and can be mixed/matched as necessary.
### Dynamic parameters
Parameters can also be defined dynamically, but must be available to the instance once the * [`bot_start()` callback](strategy-callbacks.md#bot-start) has been called.
``` python
class MyAwesomeStrategy(IStrategy):
def bot_start(self, **kwargs) -> None:
self.buy_adx = IntParameter(20, 30, default=30, optimize=True)
# ...
```
!!! Warning
Parameters created this way will not show up in the `list-strategies` parameter count.
### Overriding Base estimator ### Overriding Base estimator
You can define your own estimator for Hyperopt by implementing `generate_estimator()` in the Hyperopt subclass. You can define your own estimator for Hyperopt by implementing `generate_estimator()` in the Hyperopt subclass.

View File

@ -549,10 +549,11 @@ class AwesomeStrategy(IStrategy):
:param pair: Pair that's about to be bought/shorted. :param pair: Pair that's about to be bought/shorted.
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in target (quote) currency that's going to be traded. :param amount: Amount in target (base) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade :param side: 'long' or 'short' - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :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 placed on the exchange. :return bool: When True is returned, then the buy-order is placed on the exchange.
@ -586,7 +587,7 @@ class AwesomeStrategy(IStrategy):
rate: float, time_in_force: str, exit_reason: str, rate: float, time_in_force: str, exit_reason: str,
current_time: datetime, **kwargs) -> bool: current_time: datetime, **kwargs) -> bool:
""" """
Called right before placing a regular sell order. Called right before placing a regular exit order.
Timing for this function is critical, so avoid doing heavy computations or Timing for this function is critical, so avoid doing heavy computations or
network requests in this method. network requests in this method.
@ -594,9 +595,10 @@ class AwesomeStrategy(IStrategy):
When not implemented by a strategy, returns True (always confirming). When not implemented by a strategy, returns True (always confirming).
:param pair: Pair that's about to be sold. :param pair: Pair for trade that's about to be exited.
:param trade: trade object.
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in quote currency. :param amount: Amount in base currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason. :param exit_reason: Exit reason.
@ -604,7 +606,7 @@ class AwesomeStrategy(IStrategy):
'exit_signal', 'force_exit', 'emergency_exit'] 'exit_signal', 'force_exit', 'emergency_exit']
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the exit-order is placed on the exchange. :return bool: When True, then the exit-order is placed on the exchange.
False aborts the process False aborts the process
""" """
if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0: if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0:
@ -802,17 +804,18 @@ For markets / exchanges that don't support leverage, this method is ignored.
``` python ``` python
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
def leverage(self, pair: str, current_time: 'datetime', current_rate: float, def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, side: str, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float: **kwargs) -> float:
""" """
Customize leverage for each new trade. Customize leverage for each new trade. This method is only called in futures mode.
:param pair: Pair that's currently analyzed :param pair: Pair that's currently analyzed
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param current_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param proposed_leverage: A leverage proposed by the bot. :param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair :param max_leverage: Max leverage allowed on this pair
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade :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: A leverage amount, which is between 1.0 and max_leverage.
""" """

View File

@ -781,7 +781,7 @@ class FreqtradeBot(LoggingMixin):
current_rate=enter_limit_requested, current_rate=enter_limit_requested,
proposed_leverage=1.0, proposed_leverage=1.0,
max_leverage=max_leverage, max_leverage=max_leverage,
side=trade_side, side=trade_side, entry_tag=entry_tag,
) if self.trading_mode != TradingMode.SPOT else 1.0 ) if self.trading_mode != TradingMode.SPOT else 1.0
# Cap leverage between 1.0 and max_leverage. # Cap leverage between 1.0 and max_leverage.
leverage = min(max(leverage, 1.0), max_leverage) leverage = min(max(leverage, 1.0), max_leverage)
@ -1203,15 +1203,15 @@ class FreqtradeBot(LoggingMixin):
current_order_rate=order_obj.price, entry_tag=trade.enter_tag, current_order_rate=order_obj.price, entry_tag=trade.enter_tag,
side=trade.entry_side) side=trade.entry_side)
full_cancel = False replacing = True
cancel_reason = constants.CANCEL_REASON['REPLACE'] cancel_reason = constants.CANCEL_REASON['REPLACE']
if not adjusted_entry_price: if not adjusted_entry_price:
full_cancel = True if trade.nr_of_successful_entries == 0 else False replacing = False
cancel_reason = constants.CANCEL_REASON['USER_CANCEL'] cancel_reason = constants.CANCEL_REASON['USER_CANCEL']
if order_obj.price != adjusted_entry_price: if order_obj.price != adjusted_entry_price:
# cancel existing order if new price is supplied or None # cancel existing order if new price is supplied or None
self.handle_cancel_enter(trade, order, cancel_reason, self.handle_cancel_enter(trade, order, cancel_reason,
allow_full_cancel=full_cancel) replacing=replacing)
if adjusted_entry_price: if adjusted_entry_price:
# place new order only if new price is supplied # place new order only if new price is supplied
self.execute_entry( self.execute_entry(
@ -1245,10 +1245,11 @@ class FreqtradeBot(LoggingMixin):
def handle_cancel_enter( def handle_cancel_enter(
self, trade: Trade, order: Dict, reason: str, self, trade: Trade, order: Dict, reason: str,
allow_full_cancel: Optional[bool] = True replacing: Optional[bool] = False
) -> bool: ) -> bool:
""" """
Buy cancel - cancel order Buy cancel - cancel order
:param replacing: Replacing order - prevent trade deletion.
:return: True if order was fully cancelled :return: True if order was fully cancelled
""" """
was_trade_fully_canceled = False was_trade_fully_canceled = False
@ -1286,7 +1287,7 @@ class FreqtradeBot(LoggingMixin):
if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC):
# if trade is not partially completed and it's the only order, just delete the trade # if trade is not partially completed and it's the only order, just delete the trade
open_order_count = len([order for order in trade.orders if order.status == 'open']) open_order_count = len([order for order in trade.orders if order.status == 'open'])
if open_order_count <= 1 and allow_full_cancel: if open_order_count <= 1 and trade.nr_of_successful_entries == 0 and not replacing:
logger.info(f'{side} order fully cancelled. Removing {trade} from database.') logger.info(f'{side} order fully cancelled. Removing {trade} from database.')
trade.delete() trade.delete()
was_trade_fully_canceled = True was_trade_fully_canceled = True
@ -1295,7 +1296,7 @@ class FreqtradeBot(LoggingMixin):
# FIXME TODO: This could possibly reworked to not duplicate the code 15 lines below. # FIXME TODO: This could possibly reworked to not duplicate the code 15 lines below.
self.update_trade_state(trade, trade.open_order_id, corder) self.update_trade_state(trade, trade.open_order_id, corder)
trade.open_order_id = None trade.open_order_id = None
logger.info(f'Partial {side} order timeout for {trade}.') logger.info(f'{side} Order timeout for {trade}.')
else: else:
# if trade is partially complete, edit the stake details for the trade # if trade is partially complete, edit the stake details for the trade
# and close the order # and close the order

View File

@ -188,9 +188,7 @@ class Backtesting:
# since a "perfect" stoploss-exit is assumed anyway # since a "perfect" stoploss-exit is assumed anyway
# And the regular "stoploss" function would not apply to that case # And the regular "stoploss" function would not apply to that case
self.strategy.order_types['stoploss_on_exchange'] = False self.strategy.order_types['stoploss_on_exchange'] = False
if self.dataprovider.runmode == RunMode.BACKTEST:
# in hyperopt mode - don't re-init params
self.strategy.ft_load_hyper_params(False)
self.strategy.ft_bot_start() self.strategy.ft_bot_start()
def _load_protections(self, strategy: IStrategy): def _load_protections(self, strategy: IStrategy):
@ -709,7 +707,7 @@ class Backtesting:
current_rate=row[OPEN_IDX], current_rate=row[OPEN_IDX],
proposed_leverage=1.0, proposed_leverage=1.0,
max_leverage=max_leverage, max_leverage=max_leverage,
side=direction, side=direction, entry_tag=entry_tag,
) if self._can_short else 1.0 ) if self._can_short else 1.0
# Cap leverage between 1.0 and max_leverage. # Cap leverage between 1.0 and max_leverage.
leverage = min(max(leverage, 1.0), max_leverage) leverage = min(max(leverage, 1.0), max_leverage)
@ -900,26 +898,30 @@ class Backtesting:
self.protections.stop_per_pair(pair, current_time, side) self.protections.stop_per_pair(pair, current_time, side)
self.protections.global_stop(current_time, side) self.protections.global_stop(current_time, side)
def manage_open_orders(self, trade: LocalTrade, current_time, row: Tuple) -> bool: def manage_open_orders(self, trade: LocalTrade, current_time: datetime, row: Tuple) -> bool:
""" """
Check if any open order needs to be cancelled or replaced. Check if any open order needs to be cancelled or replaced.
Returns True if the trade should be deleted. Returns True if the trade should be deleted.
""" """
for order in [o for o in trade.orders if o.ft_is_open]: for order in [o for o in trade.orders if o.ft_is_open]:
if self.check_order_cancel(trade, order, current_time): oc = self.check_order_cancel(trade, order, current_time)
if oc:
# delete trade due to order timeout # delete trade due to order timeout
return True return True
elif self.check_order_replace(trade, order, current_time, row): elif oc is None and self.check_order_replace(trade, order, current_time, row):
# delete trade due to user request # delete trade due to user request
self.canceled_trade_entries += 1 self.canceled_trade_entries += 1
return True return True
# default maintain trade # default maintain trade
return False return False
def check_order_cancel(self, trade: LocalTrade, order: Order, current_time) -> bool: def check_order_cancel(
self, trade: LocalTrade, order: Order, current_time: datetime) -> Optional[bool]:
""" """
Check if current analyzed order has to be canceled. Check if current analyzed order has to be canceled.
Returns True if the trade should be Deleted (initial order was canceled). Returns True if the trade should be Deleted (initial order was canceled),
False if it's Canceled
None if the order is still active.
""" """
timedout = self.strategy.ft_check_timed_out( timedout = self.strategy.ft_check_timed_out(
trade, # type: ignore[arg-type] trade, # type: ignore[arg-type]
@ -933,12 +935,15 @@ class Backtesting:
else: else:
# Close additional entry order # Close additional entry order
del trade.orders[trade.orders.index(order)] del trade.orders[trade.orders.index(order)]
trade.open_order_id = None
return False
if order.side == trade.exit_side: if order.side == trade.exit_side:
self.timedout_exit_orders += 1 self.timedout_exit_orders += 1
# Close exit order and retry exiting on next signal. # Close exit order and retry exiting on next signal.
del trade.orders[trade.orders.index(order)] del trade.orders[trade.orders.index(order)]
trade.open_order_id = None
return False return False
return None
def check_order_replace(self, trade: LocalTrade, order: Order, current_time, def check_order_replace(self, trade: LocalTrade, order: Order, current_time,
row: Tuple) -> bool: row: Tuple) -> bool:

View File

@ -4,9 +4,8 @@ This module defines a base class for auto-hyperoptable strategies.
""" """
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Iterator, List, Tuple from typing import Any, Dict, Iterator, List, Tuple, Type, Union
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts, json_load from freqtrade.misc import deep_merge_dicts, json_load
from freqtrade.optimize.hyperopt_tools import HyperoptTools from freqtrade.optimize.hyperopt_tools import HyperoptTools
@ -34,9 +33,7 @@ class HyperStrategyMixin:
params = self.load_params_from_file() params = self.load_params_from_file()
params = params.get('params', {}) params = params.get('params', {})
self._ft_params_from_file = params self._ft_params_from_file = params
# Init/loading of parameters is done as part of ft_bot_start().
if config.get('runmode') != RunMode.BACKTEST:
self.ft_load_hyper_params(config.get('runmode') == RunMode.HYPEROPT)
def enumerate_parameters(self, category: str = None) -> Iterator[Tuple[str, BaseParameter]]: def enumerate_parameters(self, category: str = None) -> Iterator[Tuple[str, BaseParameter]]:
""" """
@ -56,28 +53,13 @@ class HyperStrategyMixin:
for par in params: for par in params:
yield par.name, par yield par.name, par
@classmethod
def detect_parameters(cls, category: str) -> Iterator[Tuple[str, BaseParameter]]:
""" Detect all parameters for 'category' """
for attr_name in dir(cls):
if not attr_name.startswith('__'): # Ignore internals, not strictly necessary.
attr = getattr(cls, attr_name)
if issubclass(attr.__class__, BaseParameter):
if (attr_name.startswith(category + '_')
and attr.category is not None and attr.category != category):
raise OperationalException(
f'Inconclusive parameter name {attr_name}, category: {attr.category}.')
if (category == attr.category or
(attr_name.startswith(category + '_') and attr.category is None)):
yield attr_name, attr
@classmethod @classmethod
def detect_all_parameters(cls) -> Dict: def detect_all_parameters(cls) -> Dict:
""" Detect all parameters and return them as a list""" """ Detect all parameters and return them as a list"""
params: Dict[str, Any] = { params: Dict[str, Any] = {
'buy': list(cls.detect_parameters('buy')), 'buy': list(detect_parameters(cls, 'buy')),
'sell': list(cls.detect_parameters('sell')), 'sell': list(detect_parameters(cls, 'sell')),
'protection': list(cls.detect_parameters('protection')), 'protection': list(detect_parameters(cls, 'protection')),
} }
params.update({ params.update({
'count': len(params['buy'] + params['sell'] + params['protection']) 'count': len(params['buy'] + params['sell'] + params['protection'])
@ -159,7 +141,7 @@ class HyperStrategyMixin:
logger.info(f"No params for {space} found, using default values.") logger.info(f"No params for {space} found, using default values.")
param_container: List[BaseParameter] = getattr(self, f"ft_{space}_params") param_container: List[BaseParameter] = getattr(self, f"ft_{space}_params")
for attr_name, attr in self.detect_parameters(space): for attr_name, attr in detect_parameters(self, space):
attr.name = attr_name attr.name = attr_name
attr.in_space = hyperopt and HyperoptTools.has_space(self.config, space) attr.in_space = hyperopt and HyperoptTools.has_space(self.config, space)
if not attr.category: if not attr.category:
@ -190,3 +172,25 @@ class HyperStrategyMixin:
if not p.optimize or not p.in_space: if not p.optimize or not p.in_space:
params[p.category][name] = p.value params[p.category][name] = p.value
return params return params
def detect_parameters(
obj: Union[HyperStrategyMixin, Type[HyperStrategyMixin]],
category: str
) -> Iterator[Tuple[str, BaseParameter]]:
"""
Detect all parameters for 'category' for "obj"
:param obj: Strategy object or class
:param category: category - usually `'buy', 'sell', 'protection',...
"""
for attr_name in dir(obj):
if not attr_name.startswith('__'): # Ignore internals, not strictly necessary.
attr = getattr(obj, attr_name)
if issubclass(attr.__class__, BaseParameter):
if (attr_name.startswith(category + '_')
and attr.category is not None and attr.category != category):
raise OperationalException(
f'Inconclusive parameter name {attr_name}, category: {attr.category}.')
if (category == attr.category or
(attr_name.startswith(category + '_') and attr.category is None)):
yield attr_name, attr

View File

@ -14,6 +14,7 @@ from freqtrade.constants import ListPairsWithTimeframes
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType, from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType,
SignalType, TradingMode) SignalType, TradingMode)
from freqtrade.enums.runmode import RunMode
from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.persistence import Order, PairLocks, Trade
@ -151,6 +152,8 @@ class IStrategy(ABC, HyperStrategyMixin):
""" """
strategy_safe_wrapper(self.bot_start)() strategy_safe_wrapper(self.bot_start)()
self.ft_load_hyper_params(self.config.get('runmode') == RunMode.HYPEROPT)
@abstractmethod @abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
""" """
@ -284,7 +287,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param pair: Pair that's about to be bought/shorted. :param pair: Pair that's about to be bought/shorted.
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in target (quote) currency that's going to be traded. :param amount: Amount in target (base) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
@ -311,7 +314,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param pair: Pair for trade that's about to be exited. :param pair: Pair for trade that's about to be exited.
:param trade: trade object. :param trade: trade object.
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in quote currency. :param amount: Amount in base currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason. :param exit_reason: Exit reason.
@ -506,8 +509,8 @@ class IStrategy(ABC, HyperStrategyMixin):
return current_order_rate return current_order_rate
def leverage(self, pair: str, current_time: datetime, current_rate: float, def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, side: str, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
**kwargs) -> float: side: str, **kwargs) -> float:
""" """
Customize leverage for each new trade. This method is only called in futures mode. Customize leverage for each new trade. This method is only called in futures mode.
@ -516,6 +519,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param current_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param proposed_leverage: A leverage proposed by the bot. :param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair :param max_leverage: Max leverage allowed on this pair
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade :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: A leverage amount, which is between 1.0 and max_leverage.
""" """

View File

@ -159,7 +159,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
:param pair: Pair that's about to be bought/shorted. :param pair: Pair that's about to be bought/shorted.
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in target (quote) currency that's going to be traded. :param amount: Amount in target (base) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
@ -175,7 +175,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
rate: float, time_in_force: str, exit_reason: str, rate: float, time_in_force: str, exit_reason: str,
current_time: 'datetime', **kwargs) -> bool: current_time: 'datetime', **kwargs) -> bool:
""" """
Called right before placing a regular sell order. Called right before placing a regular exit order.
Timing for this function is critical, so avoid doing heavy computations or Timing for this function is critical, so avoid doing heavy computations or
network requests in this method. network requests in this method.
@ -183,10 +183,10 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
When not implemented by a strategy, returns True (always confirming). When not implemented by a strategy, returns True (always confirming).
:param pair: Pair that's currently analyzed :param pair: Pair for trade that's about to be exited.
:param trade: trade object. :param trade: trade object.
:param order_type: Order type (as configured in order_types). usually limit or market. :param order_type: Order type (as configured in order_types). usually limit or market.
:param amount: Amount in quote currency. :param amount: Amount in base currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason. :param exit_reason: Exit reason.
@ -194,7 +194,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
'exit_signal', 'force_exit', 'emergency_exit'] 'exit_signal', 'force_exit', 'emergency_exit']
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the exit-order is placed on the exchange. :return bool: When True, then the exit-order is placed on the exchange.
False aborts the process False aborts the process
""" """
return True return True
@ -267,8 +267,8 @@ def adjust_trade_position(self, trade: 'Trade', current_time: 'datetime',
return None return None
def leverage(self, pair: str, current_time: datetime, current_rate: float, def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, side: str, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
**kwargs) -> float: side: str, **kwargs) -> float:
""" """
Customize leverage for each new trade. This method is only called in futures mode. Customize leverage for each new trade. This method is only called in futures mode.
@ -277,6 +277,7 @@ def leverage(self, pair: str, current_time: datetime, current_rate: float,
:param current_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
:param proposed_leverage: A leverage proposed by the bot. :param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair :param max_leverage: Max leverage allowed on this pair
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade :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: A leverage amount, which is between 1.0 and max_leverage.
""" """

View File

@ -261,7 +261,7 @@ class FtRestClient():
} }
return self._post("forcebuy", data=data) return self._post("forcebuy", data=data)
def force_enter(self, pair, side, price=None): def forceenter(self, pair, side, price=None):
"""Force entering a trade """Force entering a trade
:param pair: Pair to buy (ETH/BTC) :param pair: Pair to buy (ETH/BTC)
@ -273,7 +273,7 @@ class FtRestClient():
"side": side, "side": side,
"price": price, "price": price,
} }
return self._post("force_enter", data=data) return self._post("forceenter", data=data)
def forceexit(self, tradeid): def forceexit(self, tradeid):
"""Force-exit a trade. """Force-exit a trade.

View File

@ -509,7 +509,6 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
hyperopt.min_date = Arrow(2017, 12, 10) hyperopt.min_date = Arrow(2017, 12, 10)
hyperopt.max_date = Arrow(2017, 12, 13) hyperopt.max_date = Arrow(2017, 12, 13)
hyperopt.init_spaces() hyperopt.init_spaces()
hyperopt.dimensions = hyperopt.dimensions
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values())) generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
assert generate_optimizer_value == response_expected assert generate_optimizer_value == response_expected

View File

@ -27,7 +27,6 @@ class HyperoptableStrategy(StrategyTestV2):
'sell_minusdi': 0.4 'sell_minusdi': 0.4
} }
buy_rsi = IntParameter([0, 50], default=30, space='buy')
buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy') buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy')
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') sell_rsi = IntParameter(low=50, high=100, default=70, space='sell')
sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell', sell_minusdi = DecimalParameter(low=0, high=1, default=0.5001, decimals=3, space='sell',
@ -45,6 +44,12 @@ class HyperoptableStrategy(StrategyTestV2):
}) })
return prot return prot
def bot_start(self, **kwargs) -> None:
"""
Parameters can also be defined here ...
"""
self.buy_rsi = IntParameter([0, 50], default=30, space='buy')
def informative_pairs(self): def informative_pairs(self):
""" """
Define additional, informative pair/interval combinations to be cached from the exchange. Define additional, informative pair/interval combinations to be cached from the exchange.

View File

@ -178,8 +178,8 @@ class StrategyTestV3(IStrategy):
return dataframe return dataframe
def leverage(self, pair: str, current_time: datetime, current_rate: float, def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, side: str, proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
**kwargs) -> float: side: str, **kwargs) -> float:
# Return 3.0 in all cases. # Return 3.0 in all cases.
# Bot-logic must make sure it's an allowed leverage and eventually adjust accordingly. # Bot-logic must make sure it's an allowed leverage and eventually adjust accordingly.

View File

@ -16,6 +16,7 @@ from freqtrade.exceptions import OperationalException, StrategyError
from freqtrade.optimize.space import SKDecimal from freqtrade.optimize.space import SKDecimal
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.hyper import detect_parameters
from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter, from freqtrade.strategy.parameters import (BaseParameter, BooleanParameter, CategoricalParameter,
DecimalParameter, IntParameter, RealParameter) DecimalParameter, IntParameter, RealParameter)
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
@ -614,6 +615,7 @@ def test_leverage_callback(default_conf, side) -> None:
proposed_leverage=1.0, proposed_leverage=1.0,
max_leverage=5.0, max_leverage=5.0,
side=side, side=side,
entry_tag=None,
) == 1 ) == 1
default_conf['strategy'] = CURRENT_TEST_STRATEGY default_conf['strategy'] = CURRENT_TEST_STRATEGY
@ -625,6 +627,7 @@ def test_leverage_callback(default_conf, side) -> None:
proposed_leverage=1.0, proposed_leverage=1.0,
max_leverage=5.0, max_leverage=5.0,
side=side, side=side,
entry_tag='entry_tag_test',
) == 3 ) == 3
@ -893,7 +896,7 @@ def test_auto_hyperopt_interface(default_conf):
default_conf.update({'strategy': 'HyperoptableStrategy'}) default_conf.update({'strategy': 'HyperoptableStrategy'})
PairLocks.timeframe = default_conf['timeframe'] PairLocks.timeframe = default_conf['timeframe']
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
strategy.ft_bot_start()
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
next(strategy.enumerate_parameters('deadBeef')) next(strategy.enumerate_parameters('deadBeef'))
@ -908,15 +911,18 @@ def test_auto_hyperopt_interface(default_conf):
assert strategy.sell_minusdi.value == 0.5 assert strategy.sell_minusdi.value == 0.5
all_params = strategy.detect_all_parameters() all_params = strategy.detect_all_parameters()
assert isinstance(all_params, dict) assert isinstance(all_params, dict)
assert len(all_params['buy']) == 2 # Only one buy param at class level
assert len(all_params['buy']) == 1
# Running detect params at instance level reveals both parameters.
assert len(list(detect_parameters(strategy, 'buy'))) == 2
assert len(all_params['sell']) == 2 assert len(all_params['sell']) == 2
# Number of Hyperoptable parameters # Number of Hyperoptable parameters
assert all_params['count'] == 6 assert all_params['count'] == 5
strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy') strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy')
with pytest.raises(OperationalException, match=r"Inconclusive parameter.*"): with pytest.raises(OperationalException, match=r"Inconclusive parameter.*"):
[x for x in strategy.detect_parameters('sell')] [x for x in detect_parameters(strategy, 'sell')]
def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog): def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):

View File

@ -2572,6 +2572,7 @@ def test_check_handle_cancelled_buy(
get_fee=fee get_fee=fee
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
open_trade.orders = []
open_trade.is_short = is_short open_trade.is_short = is_short
Trade.query.session.add(open_trade) Trade.query.session.add(open_trade)
@ -2954,6 +2955,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade._notify_enter_cancel = MagicMock() freqtrade._notify_enter_cancel = MagicMock()
# TODO: Convert to real trade
trade = MagicMock() trade = MagicMock()
trade.pair = 'LTC/USDT' trade.pair = 'LTC/USDT'
trade.open_rate = 200 trade.open_rate = 200
@ -2961,6 +2963,7 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_order, is_
trade.entry_side = "buy" trade.entry_side = "buy"
l_order['filled'] = 0.0 l_order['filled'] = 0.0
l_order['status'] = 'open' l_order['status'] = 'open'
trade.nr_of_successful_entries = 0
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_enter(trade, l_order, reason) assert freqtrade.handle_cancel_enter(trade, l_order, reason)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
@ -3003,7 +3006,9 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf_usdt, is_sho
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
# TODO: Convert to real trade
trade = MagicMock() trade = MagicMock()
trade.nr_of_successful_entries = 0
trade.pair = 'LTC/ETH' trade.pair = 'LTC/ETH'
trade.entry_side = "sell" if is_short else "buy" trade.entry_side = "sell" if is_short else "buy"
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason) assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
@ -3036,13 +3041,14 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf_usdt, limit_order
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade._notify_enter_cancel = MagicMock() freqtrade._notify_enter_cancel = MagicMock()
# TODO: Convert to real trade
trade = MagicMock() trade = MagicMock()
trade.pair = 'LTC/USDT' trade.pair = 'LTC/USDT'
trade.entry_side = "buy" trade.entry_side = "buy"
trade.open_rate = 200 trade.open_rate = 200
trade.entry_side = "buy" trade.entry_side = "buy"
trade.open_order_id = "open_order_noop" trade.open_order_id = "open_order_noop"
trade.nr_of_successful_entries = 0
l_order['filled'] = 0.0 l_order['filled'] = 0.0
l_order['status'] = 'open' l_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']