Merge branch 'feat/short' into fs_fix

This commit is contained in:
adriance 2022-03-12 16:50:27 +08:00
commit b5662d6547
36 changed files with 259 additions and 143 deletions

View File

@ -128,7 +128,7 @@ Telegram is not mandatory. However, this is a great way to control your bot. Mor
- `/stopbuy`: Stop entering new trades.
- `/status <trade_id>|[table]`: Lists all or specific open trades.
- `/profit [<n>]`: Lists cumulative profit from all finished trades, over the last n days.
- `/forcesell <trade_id>|all`: Instantly sells the given trade (Ignoring `minimum_roi`).
- `/forceexit <trade_id>|all`: Instantly exits the given trade (Ignoring `minimum_roi`).
- `/performance`: Show performance of each finished trade grouped by pair
- `/balance`: Show account balance per currency.
- `/daily <n>`: Shows profit or loss per day, over the last n days.

View File

@ -51,11 +51,11 @@
"order_book_top": 1
},
"order_types": {
"buy": "limit",
"sell": "limit",
"emergencysell": "market",
"forcesell": "market",
"forcebuy": "market",
"entry": "limit",
"exit": "limit",
"emergencyexit": "market",
"forceexit": "market",
"forceentry": "market",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60,

View File

@ -121,7 +121,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `sell_profit_offset` | Sell-signal is only active above this value. Only active in combination with `sell_profit_only=True`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
| `ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used. <br> **Datatype:** Integer
| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Dict
| `order_types` | Configure order-types depending on the action (`"entry"`, `"exit"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Dict
| `order_time_in_force` | Configure time in force for entry and exit orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
| `custom_price_max_distance_ratio` | Configure maximum distance ratio between current and custom entry or exit price. <br>*Defaults to `0.02` 2%).*<br> **Datatype:** Positive float
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
@ -374,29 +374,28 @@ For example, if your strategy is using a 1h timeframe, and you only want to buy
### Understand order_types
The `order_types` configuration parameter maps actions (`buy`, `sell`, `stoploss`, `emergencysell`, `forcesell`, `forcebuy`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds.
The `order_types` configuration parameter maps actions (`entry`, `exit`, `stoploss`, `emergencyexit`, `forceexit`, `forceentry`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds.
This allows to buy using limit orders, sell using
limit-orders, and create stoplosses using market orders. It also allows to set the
stoploss "on exchange" which means stoploss order would be placed immediately once
the buy order is fulfilled.
`order_types` set in the configuration file overwrites values set in the strategy as a whole, so you need to configure the whole `order_types` dictionary in one place.
If this is configured, the following 4 values (`buy`, `sell`, `stoploss` and
`stoploss_on_exchange`) need to be present, otherwise, the bot will fail to start.
If this is configured, the following 4 values (`entry`, `exit`, `stoploss` and `stoploss_on_exchange`) need to be present, otherwise, the bot will fail to start.
For information on (`emergencysell`,`forcesell`, `forcebuy`, `stoploss_on_exchange`,`stoploss_on_exchange_interval`,`stoploss_on_exchange_limit_ratio`) please see stop loss documentation [stop loss on exchange](stoploss.md)
For information on (`emergencyexit`,`forceexit`, `forceentry`, `stoploss_on_exchange`,`stoploss_on_exchange_interval`,`stoploss_on_exchange_limit_ratio`) please see stop loss documentation [stop loss on exchange](stoploss.md)
Syntax for Strategy:
```python
order_types = {
"buy": "limit",
"sell": "limit",
"emergencysell": "market",
"forcebuy": "market",
"forcesell": "market",
"entry": "limit",
"exit": "limit",
"emergencyexit": "market",
"forceentry": "market",
"forceexit": "market",
"stoploss": "market",
"stoploss_on_exchange": False,
"stoploss_on_exchange_interval": 60,
@ -408,11 +407,11 @@ Configuration:
```json
"order_types": {
"buy": "limit",
"sell": "limit",
"emergencysell": "market",
"forcebuy": "market",
"forcesell": "market",
"entry": "limit",
"exit": "limit",
"emergencyexit": "market",
"forceentry": "market",
"forceexit": "market",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
@ -435,7 +434,7 @@ Configuration:
If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order.
!!! Warning "Warning: stoploss_on_exchange failures"
If stoploss on exchange creation fails for some reason, then an "emergency sell" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencysell` value in the `order_types` dictionary - however, this is not advised.
If stoploss on exchange creation fails for some reason, then an "emergency exit" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencyexit` value in the `order_types` dictionary - however, this is not advised.
### Understand order_time_in_force

View File

@ -77,7 +77,7 @@ You can use "current" market data by using the [dataprovider](strategy-customiza
### Is there a setting to only SELL the coins being held and not perform anymore BUYS?
You can use the `/stopbuy` command in Telegram to prevent future buys, followed by `/forcesell all` (sell all open trades).
You can use the `/stopbuy` command in Telegram to prevent future buys, followed by `/forceexit all` (sell all open trades).
### I want to run multiple bots on the same machine
@ -117,10 +117,10 @@ As the message says, your exchange does not support market orders and you have o
To fix this, redefine order types in the strategy to use "limit" instead of "market":
```
``` python
order_types = {
...
'stoploss': 'limit',
"stoploss": "limit",
...
}
```

View File

@ -101,8 +101,8 @@ Assuming both buy and sell are using market orders, a configuration similar to t
``` jsonc
"order_types": {
"buy": "market",
"sell": "market"
"entry": "market",
"exit": "market"
// ...
},
"bid_strategy": {

View File

@ -145,9 +145,9 @@ python3 scripts/rest_client.py --config rest_config.json <command> [optional par
| `locks` | Displays currently locked pairs.
| `delete_lock <lock_id>` | Deletes (disables) the lock by id.
| `profit` | Display a summary of your profit/loss from close trades and some stats about your performance.
| `forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
| `forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
| `forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
| `forceexit <trade_id>` | Instantly exits the given trade (Ignoring `minimum_roi`).
| `forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`).
| `forceenter <pair> [rate]` | Instantly enters the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
| `forceenter <pair> <side> [rate]` | Instantly longs or shorts the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
| `performance` | Show performance of each finished trade grouped by pair.
| `balance` | Show account balance per currency.

View File

@ -104,8 +104,8 @@ To mitigate this, you can try to match the first order on the opposite orderbook
``` jsonc
"order_types": {
"buy": "limit",
"sell": "limit"
"entry": "limit",
"exit": "limit"
// ...
},
"bid_strategy": {

View File

@ -49,14 +49,14 @@ sqlite3
SELECT * FROM trades;
```
## Fix trade still open after a manual sell on the exchange
## Fix trade still open after a manual exit on the exchange
!!! Warning
Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. Whenever possible, forcesell <tradeid> should be used to accomplish the same thing.
Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. Whenever possible, forceexit <tradeid> should be used to accomplish the same thing.
It is strongly advised to backup your database file before making any manual changes.
!!! Note
This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration.
This should not be necessary after /forceexit, as forceexit orders are closed automatically by the bot on the next iteration.
```sql
UPDATE trades

View File

@ -17,7 +17,7 @@ Those stoploss modes can be *on exchange* or *off exchange*.
These modes can be configured with these values:
``` python
'emergencysell': 'market',
'emergencyexit': 'market',
'stoploss_on_exchange': False
'stoploss_on_exchange_interval': 60,
'stoploss_on_exchange_limit_ratio': 0.99
@ -52,30 +52,30 @@ The bot cannot do these every 5 seconds (at each iteration), otherwise it would
So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute).
This same logic will reapply a stoploss order on the exchange should you cancel it accidentally.
### forcesell
### forceexit
`forcesell` is an optional value, which defaults to the same value as `sell` and is used when sending a `/forcesell` command from Telegram or from the Rest API.
`forceexit` is an optional value, which defaults to the same value as `exit` and is used when sending a `/forceexit` command from Telegram or from the Rest API.
### forcebuy
### forceentry
`forcebuy` is an optional value, which defaults to the same value as `buy` and is used when sending a `/forcebuy` command from Telegram or from the Rest API.
`forceentry` is an optional value, which defaults to the same value as `entry` and is used when sending a `/forceentry` command from Telegram or from the Rest API.
### emergencysell
### emergencyexit
`emergencysell` is an optional value, which defaults to `market` and is used when creating stop loss on exchange orders fails.
`emergencyexit` is an optional value, which defaults to `market` and is used when creating stop loss on exchange orders fails.
The below is the default which is used if not changed in strategy or configuration file.
Example from strategy file:
``` python
order_types = {
'buy': 'limit',
'sell': 'limit',
'emergencysell': 'market',
'stoploss': 'market',
'stoploss_on_exchange': True,
'stoploss_on_exchange_interval': 60,
'stoploss_on_exchange_limit_ratio': 0.99
"entry": "limit",
"exit": "limit",
"emergencyexit": "market",
"stoploss": "market",
"stoploss_on_exchange": True,
"stoploss_on_exchange_interval": 60,
"stoploss_on_exchange_limit_ratio": 0.99
}
```

View File

@ -171,8 +171,8 @@ official commands. You can ask at any moment for help with `/help`.
| `/locks` | Show currently locked pairs.
| `/unlock <pair or lock_id>` | Remove the lock for this pair (or for this lock id).
| `/profit [<n>]` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default)
| `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
| `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
| `/forceexit <trade_id>` | Instantly exits the given trade (Ignoring `minimum_roi`).
| `/forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`).
| `/forcelong <pair> [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`forcebuy_enable` must be set to True)
| `/forceshort <pair> [rate]` | Instantly shorts the given pair. Rate is optional and only applies to limit orders. This will only work on non-spot markets. (`forcebuy_enable` must be set to True)
| `/performance` | Show performance of each finished trade grouped by pair

View File

@ -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)

View File

@ -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:

View File

@ -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',

View File

@ -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']

View File

@ -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:

View File

@ -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,

View File

@ -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]

View File

@ -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,

View File

@ -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))

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,7 +1,7 @@
"order_types": {
"buy": "limit",
"sell": "limit",
"emergencysell": "limit",
"entry": "limit",
"exit": "limit",
"emergencyexit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": false
},

View File

@ -951,8 +951,8 @@ def test_validate_order_types(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
@ -962,8 +962,8 @@ def test_validate_order_types(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
@ -972,8 +972,8 @@ def test_validate_order_types(default_conf, mocker):
Exchange(default_conf)
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}

View File

@ -17,8 +17,8 @@ def test_validate_order_types_gateio(default_conf, mocker):
assert isinstance(exch, Gateio)
default_conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
'entry': 'market',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}

View File

@ -32,14 +32,14 @@ from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re
ORDER_TYPES = [
{
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
},
{
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}]

View File

@ -1382,6 +1382,7 @@ def test_api_strategies(botclient):
'InformativeDecoratorTest',
'StrategyTestV2',
'StrategyTestV3',
'StrategyTestV3Futures',
'TestStrategyLegacyV1',
]}

View File

@ -34,8 +34,8 @@ class HyperoptableStrategy(IStrategy):
# Optional order type mapping
order_types = {
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
}

View File

@ -36,8 +36,8 @@ class StrategyTestV2(IStrategy):
# Optional order type mapping
order_types = {
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
}

View File

@ -37,8 +37,8 @@ class StrategyTestV3(IStrategy):
# Optional order type mapping
order_types = {
'buy': 'limit',
'sell': 'limit',
'entry': 'limit',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
}
@ -187,3 +187,7 @@ class StrategyTestV3(IStrategy):
return round(orders[0].cost, 0)
return None
class StrategyTestV3Futures(StrategyTestV3):
can_short = True

View File

@ -78,6 +78,11 @@ def test_returns_latest_signal(ohlcv_history):
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
_STRATEGY.config['trading_mode'] = 'futures'
# Short signal get's ignored as can_short is not set.
assert _STRATEGY.get_entry_signal('ETH/BTC', '5m', mocked_history) == (None, None)
_STRATEGY.can_short = True
assert _STRATEGY.get_entry_signal(
'ETH/BTC', '5m', mocked_history) == (SignalDirection.SHORT, 'sell_signal_01')
assert _STRATEGY.get_exit_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
@ -93,6 +98,7 @@ def test_returns_latest_signal(ohlcv_history):
assert _STRATEGY.get_exit_signal(
'ETH/BTC', '5m', mocked_history, True) == (False, True, 'sell_signal_02')
_STRATEGY.can_short = False
_STRATEGY.config['trading_mode'] = 'spot'

View File

@ -35,7 +35,7 @@ def test_search_all_strategies_no_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
assert isinstance(strategies, list)
assert len(strategies) == 5
assert len(strategies) == 6
assert isinstance(strategies[0], dict)
@ -43,10 +43,10 @@ def test_search_all_strategies_with_failed():
directory = Path(__file__).parent / "strats"
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
assert isinstance(strategies, list)
assert len(strategies) == 6
assert len(strategies) == 7
# with enum_failed=True search_all_objects() shall find 2 good strategies
# and 1 which fails to load
assert len([x for x in strategies if x['class'] is not None]) == 5
assert len([x for x in strategies if x['class'] is not None]) == 6
assert len([x for x in strategies if x['class'] is None]) == 1
@ -128,6 +128,22 @@ def test_strategy_pre_v3(result, default_conf, strategy_name):
assert 'exit_long' in dataframe.columns
def test_strategy_can_short(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
'strategy': CURRENT_TEST_STRATEGY,
})
strat = StrategyResolver.load_strategy(default_conf)
assert isinstance(strat, IStrategy)
default_conf['strategy'] = 'StrategyTestV3Futures'
with pytest.raises(ImportError, match=""):
StrategyResolver.load_strategy(default_conf)
default_conf['trading_mode'] = 'futures'
strat = StrategyResolver.load_strategy(default_conf)
assert isinstance(strat, IStrategy)
def test_strategy_override_minimal_roi(caplog, default_conf):
caplog.set_level(logging.INFO)
default_conf.update({
@ -223,8 +239,8 @@ def test_strategy_override_order_types(caplog, default_conf):
caplog.set_level(logging.INFO)
order_types = {
'buy': 'market',
'sell': 'limit',
'entry': 'market',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True,
}
@ -235,16 +251,16 @@ def test_strategy_override_order_types(caplog, default_conf):
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.order_types
for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']:
for method in ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']:
assert strategy.order_types[method] == order_types[method]
assert log_has("Override strategy 'order_types' with value in config file:"
" {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit',"
" {'entry': 'market', 'exit': 'limit', 'stoploss': 'limit',"
" 'stoploss_on_exchange': True}.", caplog)
default_conf.update({
'strategy': CURRENT_TEST_STRATEGY,
'order_types': {'buy': 'market'}
'order_types': {'exit': 'market'}
})
# Raise error for invalid configuration
with pytest.raises(ImportError,

View File

@ -798,8 +798,8 @@ def test_validate_max_open_trades(default_conf):
def test_validate_price_side(default_conf):
default_conf['order_types'] = {
"buy": "limit",
"sell": "limit",
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": False,
}
@ -807,21 +807,21 @@ def test_validate_price_side(default_conf):
validate_config_consistency(default_conf)
conf = deepcopy(default_conf)
conf['order_types']['buy'] = 'market'
conf['order_types']['entry'] = 'market'
with pytest.raises(OperationalException,
match='Market buy orders require bid_strategy.price_side = "ask".'):
validate_config_consistency(conf)
conf = deepcopy(default_conf)
conf['order_types']['sell'] = 'market'
conf['order_types']['exit'] = 'market'
with pytest.raises(OperationalException,
match='Market sell orders require ask_strategy.price_side = "bid".'):
validate_config_consistency(conf)
# Validate inversed case
conf = deepcopy(default_conf)
conf['order_types']['sell'] = 'market'
conf['order_types']['buy'] = 'market'
conf['order_types']['exit'] = 'market'
conf['order_types']['entry'] = 'market'
conf['ask_strategy']['price_side'] = 'bid'
conf['bid_strategy']['price_side'] = 'ask'
@ -963,6 +963,41 @@ def test_validate_time_in_force(default_conf, caplog) -> None:
validate_config_consistency(conf)
def test_validate_order_types(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf['order_types'] = {
'buy': 'limit',
'sell': 'market',
'forcesell': 'market',
'forcebuy': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False,
}
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for order_types is.*", caplog)
assert conf['order_types']['entry'] == 'limit'
assert conf['order_types']['exit'] == 'market'
assert conf['order_types']['forceentry'] == 'limit'
assert 'buy' not in conf['order_types']
assert 'sell' not in conf['order_types']
assert 'forcebuy' not in conf['order_types']
assert 'forcesell' not in conf['order_types']
conf = deepcopy(default_conf)
conf['order_types'] = {
'buy': 'limit',
'sell': 'market',
'forcesell': 'market',
'forcebuy': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False,
}
conf['trading_mode'] = 'futures'
with pytest.raises(OperationalException,
match=r"Please migrate your order_types settings to use the new wording\."):
validate_config_consistency(conf)
def test_load_config_test_comments() -> None:
"""
Load config with comments
@ -1280,11 +1315,14 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
# The value of the new setting shall have been set to the
# value of the deprecated one
assert default_conf['sectionA']['new_setting'] == 'valB'
# Old setting is removed
assert 'deprecated_setting' not in default_conf['sectionB']
caplog.clear()
# Delete new setting (deprecated exists)
del default_conf['sectionA']['new_setting']
default_conf['sectionB']['deprecated_setting'] = 'valB'
process_deprecated_setting(default_conf,
'sectionB', 'deprecated_setting',
'sectionA', 'new_setting')
@ -1298,7 +1336,7 @@ def test_process_deprecated_setting(mocker, default_conf, caplog):
# Assign new setting
default_conf['sectionA']['new_setting'] = 'valA'
# Delete deprecated setting
del default_conf['sectionB']['deprecated_setting']
default_conf['sectionB'].pop('deprecated_setting', None)
process_deprecated_setting(default_conf,
'sectionB', 'deprecated_setting',
'sectionA', 'new_setting')

View File

@ -91,8 +91,8 @@ def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
conf = default_conf_usdt.copy()
conf['runmode'] = runmode
conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
'entry': 'market',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True,
}
@ -108,8 +108,8 @@ def test_order_dict(default_conf_usdt, mocker, runmode, caplog) -> None:
conf = default_conf_usdt.copy()
conf['runmode'] = runmode
conf['order_types'] = {
'buy': 'market',
'sell': 'limit',
'entry': 'market',
'exit': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False,
}
@ -2578,7 +2578,7 @@ def test_check_handle_timedout_sell_usercustom(
assert et_mock.call_count == 0
freqtrade.check_handle_timedout()
assert log_has_re('Emergencyselling trade.*', caplog)
assert log_has_re('Emergency exiting trade.*', caplog)
assert et_mock.call_count == 1
@ -3517,7 +3517,7 @@ def test_execute_trade_exit_market_order(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt_sell_up
)
freqtrade.config['order_types']['sell'] = 'market'
freqtrade.config['order_types']['exit'] = 'market'
freqtrade.execute_trade_exit(
trade=trade,

View File

@ -80,7 +80,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
freqtrade = get_patched_freqtradebot(mocker, default_conf)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# Switch ordertype to market to close trade immediately
freqtrade.strategy.order_types['sell'] = 'market'
freqtrade.strategy.order_types['exit'] = 'market'
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
patch_get_signal(freqtrade)
@ -173,7 +173,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
rpc = RPC(freqtrade)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
# Switch ordertype to market to close trade immediately
freqtrade.strategy.order_types['sell'] = 'market'
freqtrade.strategy.order_types['exit'] = 'market'
patch_get_signal(freqtrade)
# Create 4 trades