Merge branch 'feat/short' into fs_fix
This commit is contained in:
@@ -6,6 +6,7 @@ from jsonschema import Draft4Validator, validators
|
||||
from jsonschema.exceptions import ValidationError, best_match
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.configuration.deprecated_settings import process_deprecated_setting
|
||||
from freqtrade.enums import RunMode, TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
|
||||
@@ -102,11 +103,12 @@ def _validate_price_config(conf: Dict[str, Any]) -> None:
|
||||
"""
|
||||
When using market orders, price sides must be using the "other" side of the price
|
||||
"""
|
||||
if (conf.get('order_types', {}).get('buy') == 'market'
|
||||
# TODO-lev: check this again when determining how to migrate pricing strategies!
|
||||
if (conf.get('order_types', {}).get('entry') == 'market'
|
||||
and conf.get('bid_strategy', {}).get('price_side') != 'ask'):
|
||||
raise OperationalException('Market buy orders require bid_strategy.price_side = "ask".')
|
||||
|
||||
if (conf.get('order_types', {}).get('sell') == 'market'
|
||||
if (conf.get('order_types', {}).get('exit') == 'market'
|
||||
and conf.get('ask_strategy', {}).get('price_side') != 'bid'):
|
||||
raise OperationalException('Market sell orders require ask_strategy.price_side = "bid".')
|
||||
|
||||
@@ -213,6 +215,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
||||
def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None:
|
||||
|
||||
_validate_time_in_force(conf)
|
||||
_validate_order_types(conf)
|
||||
|
||||
|
||||
def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
||||
@@ -227,5 +230,31 @@ def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
||||
"DEPRECATED: Using 'buy' and 'sell' for time_in_force is deprecated."
|
||||
"Please migrate your time_in_force settings to use 'entry' and 'exit'."
|
||||
)
|
||||
time_in_force['entry'] = time_in_force.pop('buy')
|
||||
time_in_force['exit'] = time_in_force.pop('sell')
|
||||
process_deprecated_setting(
|
||||
conf, 'order_time_in_force', 'buy', 'order_time_in_force', 'entry')
|
||||
|
||||
process_deprecated_setting(
|
||||
conf, 'order_time_in_force', 'sell', 'order_time_in_force', 'exit')
|
||||
|
||||
|
||||
def _validate_order_types(conf: Dict[str, Any]) -> None:
|
||||
|
||||
order_types = conf.get('order_types', {})
|
||||
if any(x in order_types for x in ['buy', 'sell', 'emergencysell', 'forcebuy', 'forcesell']):
|
||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||
raise OperationalException(
|
||||
"Please migrate your order_types settings to use the new wording.")
|
||||
else:
|
||||
logger.warning(
|
||||
"DEPRECATED: Using 'buy' and 'sell' for order_types is deprecated."
|
||||
"Please migrate your order_types settings to use 'entry' and 'exit' wording."
|
||||
)
|
||||
for o, n in [
|
||||
('buy', 'entry'),
|
||||
('sell', 'exit'),
|
||||
('emergencysell', 'emergencyexit'),
|
||||
('forcesell', 'forceexit'),
|
||||
('forcebuy', 'forceentry'),
|
||||
]:
|
||||
|
||||
process_deprecated_setting(conf, 'order_types', o, 'order_types', n)
|
||||
|
@@ -64,6 +64,7 @@ def process_deprecated_setting(config: Dict[str, Any],
|
||||
|
||||
section_new_config = config.get(section_new, {}) if section_new else config
|
||||
section_new_config[name_new] = section_old_config[name_old]
|
||||
del section_old_config[name_old]
|
||||
|
||||
|
||||
def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
||||
|
@@ -20,7 +20,7 @@ DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite'
|
||||
UNLIMITED_STAKE_AMOUNT = 'unlimited'
|
||||
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
|
||||
REQUIRED_ORDERTIF = ['entry', 'exit']
|
||||
REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
|
||||
REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']
|
||||
ORDERBOOK_SIDES = ['ask', 'bid']
|
||||
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
|
||||
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
||||
@@ -214,11 +214,11 @@ CONF_SCHEMA = {
|
||||
'order_types': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'forcesell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'forcebuy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'emergencysell': {
|
||||
'entry': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'exit': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'forceexit': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'forceentry': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'emergencyexit': {
|
||||
'type': 'string',
|
||||
'enum': ORDERTYPE_POSSIBILITIES,
|
||||
'default': 'market'},
|
||||
@@ -228,7 +228,7 @@ CONF_SCHEMA = {
|
||||
'stoploss_on_exchange_limit_ratio': {'type': 'number', 'minimum': 0.0,
|
||||
'maximum': 1.0}
|
||||
},
|
||||
'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
|
||||
'required': ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']
|
||||
},
|
||||
'order_time_in_force': {
|
||||
'type': 'object',
|
||||
|
@@ -629,7 +629,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
f"{stake_amount} ...")
|
||||
|
||||
amount = (stake_amount / enter_limit_requested) * leverage
|
||||
order_type = ordertype or self.strategy.order_types['buy']
|
||||
order_type = ordertype or self.strategy.order_types['entry']
|
||||
|
||||
if not pos_adjust and not strategy_safe_wrapper(
|
||||
self.strategy.confirm_trade_entry, default_retval=True)(
|
||||
@@ -1155,7 +1155,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
max_timeouts = self.config.get(
|
||||
'unfilledtimeout', {}).get('exit_timeout_count', 0)
|
||||
if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:
|
||||
logger.warning(f'Emergencyselling trade {trade}, as the sell order '
|
||||
logger.warning(f'Emergency exiting trade {trade}, as the exit order '
|
||||
f'timed out {max_timeouts} times.')
|
||||
try:
|
||||
self.execute_trade_exit(
|
||||
@@ -1248,11 +1248,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.update_trade_state(trade, trade.open_order_id, corder)
|
||||
|
||||
trade.open_order_id = None
|
||||
logger.info('Partial %s order timeout for %s.', trade.enter_side, trade)
|
||||
logger.info(f'Partial {trade.enter_side} order timeout for {trade}.')
|
||||
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
||||
|
||||
self.wallets.update()
|
||||
self._notify_enter_cancel(trade, order_type=self.strategy.order_types[trade.enter_side],
|
||||
self._notify_enter_cancel(trade, order_type=self.strategy.order_types['entry'],
|
||||
reason=reason)
|
||||
return was_trade_fully_canceled
|
||||
|
||||
@@ -1297,7 +1297,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.wallets.update()
|
||||
self._notify_exit_cancel(
|
||||
trade,
|
||||
order_type=self.strategy.order_types[trade.exit_side],
|
||||
order_type=self.strategy.order_types['exit'],
|
||||
reason=reason
|
||||
)
|
||||
return cancelled
|
||||
@@ -1353,7 +1353,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
is_short=trade.is_short,
|
||||
open_date=trade.open_date,
|
||||
)
|
||||
exit_type = 'sell'
|
||||
exit_type = 'exit'
|
||||
if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
||||
exit_type = 'stoploss'
|
||||
|
||||
@@ -1380,7 +1380,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
order_type = ordertype or self.strategy.order_types[exit_type]
|
||||
if sell_reason.sell_type == SellType.EMERGENCY_SELL:
|
||||
# Emergency sells (default to market!)
|
||||
order_type = self.strategy.order_types.get("emergencysell", "market")
|
||||
order_type = self.strategy.order_types.get("emergencyexit", "market")
|
||||
|
||||
amount = self._safe_exit_amount(trade.pair, trade.amount)
|
||||
time_in_force = self.strategy.order_time_in_force['exit']
|
||||
|
@@ -127,10 +127,9 @@ class Backtesting:
|
||||
self.config['startup_candle_count'] = self.required_startup
|
||||
self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe)
|
||||
|
||||
# TODO-lev: This should come from the configuration setting or better a
|
||||
# TODO-lev: combination of config/strategy "use_shorts"(?) and "can_short" from the exchange
|
||||
self.trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT)
|
||||
self.margin_mode: MarginMode = config.get('margin_mode', MarginMode.NONE)
|
||||
# strategies which define "can_short=True" will fail to load in Spot mode.
|
||||
self._can_short = self.trading_mode != TradingMode.SPOT
|
||||
|
||||
self.progress = BTProgress()
|
||||
@@ -542,7 +541,7 @@ class Backtesting:
|
||||
return None
|
||||
# call the custom exit price,with default value as previous closerate
|
||||
current_profit = trade.calc_profit_ratio(closerate)
|
||||
order_type = self.strategy.order_types['sell']
|
||||
order_type = self.strategy.order_types['exit']
|
||||
if sell.sell_type in (SellType.SELL_SIGNAL, SellType.CUSTOM_SELL):
|
||||
# Custom exit pricing only for sell-signals
|
||||
if order_type == 'limit':
|
||||
@@ -650,7 +649,7 @@ class Backtesting:
|
||||
current_time = row[DATE_IDX].to_pydatetime()
|
||||
entry_tag = row[ENTER_TAG_IDX] if len(row) >= ENTER_TAG_IDX + 1 else None
|
||||
# let's call the custom entry price, using the open price as default price
|
||||
order_type = self.strategy.order_types['buy']
|
||||
order_type = self.strategy.order_types['entry']
|
||||
propose_rate = row[OPEN_IDX]
|
||||
if order_type == 'limit':
|
||||
propose_rate = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||
@@ -693,7 +692,7 @@ class Backtesting:
|
||||
# In case of pos adjust, still return the original trade
|
||||
# If not pos adjust, trade is None
|
||||
return trade
|
||||
order_type = self.strategy.order_types['buy']
|
||||
order_type = self.strategy.order_types['entry']
|
||||
time_in_force = self.strategy.order_time_in_force['entry']
|
||||
|
||||
if not pos_adjust:
|
||||
|
@@ -12,6 +12,7 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from freqtrade.configuration.config_validation import validate_migrated_strategy_settings
|
||||
from freqtrade.constants import REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES, USERPATH_STRATEGIES
|
||||
from freqtrade.enums import TradingMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.resolvers import IResolver
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
@@ -160,7 +161,7 @@ class StrategyResolver(IResolver):
|
||||
return strategy
|
||||
|
||||
@staticmethod
|
||||
def _strategy_sanity_validations(strategy):
|
||||
def _strategy_sanity_validations(strategy: IStrategy):
|
||||
# Ensure necessary migrations are performed first.
|
||||
validate_migrated_strategy_settings(strategy.config)
|
||||
|
||||
@@ -170,6 +171,15 @@ class StrategyResolver(IResolver):
|
||||
if not all(k in strategy.order_time_in_force for k in REQUIRED_ORDERTIF):
|
||||
raise ImportError(f"Impossible to load Strategy '{strategy.__class__.__name__}'. "
|
||||
f"Order-time-in-force mapping is incomplete.")
|
||||
trading_mode = strategy.config.get('trading_mode', TradingMode.SPOT)
|
||||
|
||||
if (strategy.can_short and trading_mode == TradingMode.SPOT):
|
||||
raise ImportError(
|
||||
"Short strategies cannot run in spot markets. Please make sure that this "
|
||||
"is the correct strategy and that your trading mode configuration is correct. "
|
||||
"You can run this strategy in spot markets by setting `can_short=False`"
|
||||
" in your strategy. Please note that short signals will be ignored in that case."
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _load_strategy(strategy_name: str,
|
||||
|
@@ -138,11 +138,11 @@ class UnfilledTimeout(BaseModel):
|
||||
|
||||
|
||||
class OrderTypes(BaseModel):
|
||||
buy: OrderTypeValues
|
||||
sell: OrderTypeValues
|
||||
emergencysell: Optional[OrderTypeValues]
|
||||
forcesell: Optional[OrderTypeValues]
|
||||
forcebuy: Optional[OrderTypeValues]
|
||||
entry: OrderTypeValues
|
||||
exit: OrderTypeValues
|
||||
emergencyexit: Optional[OrderTypeValues]
|
||||
forceexit: Optional[OrderTypeValues]
|
||||
forceentry: Optional[OrderTypeValues]
|
||||
stoploss: OrderTypeValues
|
||||
stoploss_on_exchange: bool
|
||||
stoploss_on_exchange_interval: Optional[int]
|
||||
|
@@ -712,7 +712,7 @@ class RPC:
|
||||
trade.pair, refresh=False, side=trade.exit_side)
|
||||
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
||||
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
||||
"forcesell", self._freqtrade.strategy.order_types["sell"])
|
||||
"forceexit", self._freqtrade.strategy.order_types["exit"])
|
||||
|
||||
self._freqtrade.execute_trade_exit(
|
||||
trade, current_rate, sell_reason, ordertype=order_type)
|
||||
@@ -735,7 +735,7 @@ class RPC:
|
||||
trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True), ]
|
||||
).first()
|
||||
if not trade:
|
||||
logger.warning('forcesell: Invalid argument received')
|
||||
logger.warning('forceexit: Invalid argument received')
|
||||
raise RPCException('invalid argument')
|
||||
|
||||
_exec_forcesell(trade)
|
||||
@@ -784,7 +784,7 @@ class RPC:
|
||||
# execute buy
|
||||
if not order_type:
|
||||
order_type = self._freqtrade.strategy.order_types.get(
|
||||
'forcebuy', self._freqtrade.strategy.order_types['buy'])
|
||||
'forceentry', self._freqtrade.strategy.order_types['entry'])
|
||||
if self._freqtrade.execute_entry(pair, stake_amount, price,
|
||||
ordertype=order_type, trade=trade,
|
||||
is_short=is_short,
|
||||
|
@@ -944,7 +944,7 @@ class Telegram(RPCHandler):
|
||||
return
|
||||
try:
|
||||
msg = self._rpc._rpc_forceexit(trade_id)
|
||||
self._send_msg('Forcesell Result: `{result}`'.format(**msg))
|
||||
self._send_msg('Forceexit Result: `{result}`'.format(**msg))
|
||||
|
||||
except RPCException as e:
|
||||
self._send_msg(str(e))
|
||||
|
@@ -81,14 +81,17 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
trailing_only_offset_is_reached = False
|
||||
use_custom_stoploss: bool = False
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = False
|
||||
|
||||
# associated timeframe
|
||||
ticker_interval: str # DEPRECATED
|
||||
timeframe: str
|
||||
|
||||
# Optional order types
|
||||
order_types: Dict = {
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'entry': 'limit',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': False,
|
||||
'stoploss_on_exchange_interval': 60,
|
||||
@@ -766,6 +769,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
enter_signal = SignalDirection.LONG
|
||||
enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
|
||||
if (self.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT
|
||||
and self.can_short
|
||||
and enter_short == 1 and not any([exit_short, enter_long])):
|
||||
enter_signal = SignalDirection.SHORT
|
||||
enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
|
||||
|
@@ -40,6 +40,9 @@ class {{ strategy }}(IStrategy):
|
||||
# Optimal timeframe for the strategy.
|
||||
timeframe = '5m'
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = False
|
||||
|
||||
# Minimal ROI designed for the strategy.
|
||||
# This attribute will be overridden if the config file contains "minimal_roi".
|
||||
minimal_roi = {
|
||||
@@ -75,8 +78,8 @@ class {{ strategy }}(IStrategy):
|
||||
|
||||
# Optional order type mapping.
|
||||
order_types = {
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'entry': 'limit',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'market',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
|
@@ -38,6 +38,9 @@ class SampleShortStrategy(IStrategy):
|
||||
# Check the documentation or the Sample strategy to get the latest version.
|
||||
INTERFACE_VERSION = 2
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = True
|
||||
|
||||
# Minimal ROI designed for the strategy.
|
||||
# This attribute will be overridden if the config file contains "minimal_roi".
|
||||
minimal_roi = {
|
||||
@@ -76,8 +79,8 @@ class SampleShortStrategy(IStrategy):
|
||||
|
||||
# Optional order type mapping.
|
||||
order_types = {
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'entry': 'limit',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'market',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
|
@@ -37,6 +37,9 @@ class SampleStrategy(IStrategy):
|
||||
# Check the documentation or the Sample strategy to get the latest version.
|
||||
INTERFACE_VERSION = 2
|
||||
|
||||
# Can this strategy go short?
|
||||
can_short: bool = False
|
||||
|
||||
# Minimal ROI designed for the strategy.
|
||||
# This attribute will be overridden if the config file contains "minimal_roi".
|
||||
minimal_roi = {
|
||||
@@ -55,12 +58,6 @@ class SampleStrategy(IStrategy):
|
||||
# trailing_stop_positive = 0.01
|
||||
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
|
||||
|
||||
# Hyperoptable parameters
|
||||
buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True)
|
||||
short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True)
|
||||
exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
|
||||
# Optimal timeframe for the strategy.
|
||||
timeframe = '5m'
|
||||
|
||||
@@ -72,13 +69,19 @@ class SampleStrategy(IStrategy):
|
||||
sell_profit_only = False
|
||||
ignore_roi_if_buy_signal = False
|
||||
|
||||
# Hyperoptable parameters
|
||||
buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True)
|
||||
short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True)
|
||||
exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
|
||||
# Number of candles the strategy requires before producing valid signals
|
||||
startup_candle_count: int = 30
|
||||
|
||||
# Optional order type mapping.
|
||||
order_types = {
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'entry': 'limit',
|
||||
'exit': 'limit',
|
||||
'stoploss': 'market',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
"order_types": {
|
||||
"buy": "limit",
|
||||
"sell": "limit",
|
||||
"emergencysell": "limit",
|
||||
"entry": "limit",
|
||||
"exit": "limit",
|
||||
"emergencyexit": "limit",
|
||||
"stoploss": "limit",
|
||||
"stoploss_on_exchange": false
|
||||
},
|
||||
|
Reference in New Issue
Block a user