mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Merge branch 'feat/short' into fs_fix
This commit is contained in:
commit
b5662d6547
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}]
|
||||
|
|
|
@ -1382,6 +1382,7 @@ def test_api_strategies(botclient):
|
|||
'InformativeDecoratorTest',
|
||||
'StrategyTestV2',
|
||||
'StrategyTestV3',
|
||||
'StrategyTestV3Futures',
|
||||
'TestStrategyLegacyV1',
|
||||
]}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user