Merge branch 'develop' into feat/freqai

This commit is contained in:
Matthias 2022-07-24 16:18:58 +02:00
commit 61c41fd919
27 changed files with 346 additions and 121 deletions

2
.gitignore vendored
View File

@ -84,6 +84,8 @@ instance/
# Sphinx documentation # Sphinx documentation
docs/_build/ docs/_build/
# Mkdocs documentation
site/
# PyBuilder # PyBuilder
target/ target/

View File

@ -15,7 +15,7 @@ repos:
additional_dependencies: additional_dependencies:
- types-cachetools==5.2.1 - types-cachetools==5.2.1
- types-filelock==3.2.7 - types-filelock==3.2.7
- types-requests==2.28.0 - types-requests==2.28.1
- types-tabulate==0.8.11 - types-tabulate==0.8.11
- types-python-dateutil==2.8.18 - types-python-dateutil==2.8.18
# stages: [push] # stages: [push]

View File

@ -116,6 +116,9 @@ This is similar to using multiple `--config` parameters, but simpler in usage as
The table below will list all configuration parameters available. The table below will list all configuration parameters available.
Freqtrade can also load many options via command line (CLI) arguments (check out the commands `--help` output for details). Freqtrade can also load many options via command line (CLI) arguments (check out the commands `--help` output for details).
### Configuration option prevalence
The prevalence for all Options is as follows: The prevalence for all Options is as follows:
- CLI arguments override any other option - CLI arguments override any other option
@ -123,6 +126,8 @@ The prevalence for all Options is as follows:
- Configuration files are used in sequence (the last file wins) and override Strategy configurations. - Configuration files are used in sequence (the last file wins) and override Strategy configurations.
- Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table. - Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table.
### Parameters table
Mandatory parameters are marked as **Required**, which means that they are required to be set in one of the possible ways. Mandatory parameters are marked as **Required**, which means that they are required to be set in one of the possible ways.
| Parameter | Description | | Parameter | Description |
@ -135,7 +140,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **Datatype:** Boolean | `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **Datatype:** Float (as ratio) | `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **Datatype:** Float (as ratio)
| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive Float as ratio. | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive Float as ratio.
| `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String | `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). Usually missing in configuration, and specified in the strategy. [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
@ -148,13 +153,16 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md#trailing-stop-loss-only-once-the-trade-has-reached-a-certain-offset). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> **Datatype:** Float | `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md#trailing-stop-loss-only-once-the-trade-has-reached-a-certain-offset). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> **Datatype:** Float
| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean | `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `fee` | Fee used during backtesting / dry-runs. Should normally not be configured, which has freqtrade fall back to the exchange default fee. Set as ratio (e.g. 0.001 = 0.1%). Fee is applied twice for each trade, once when buying, once when selling. <br> **Datatype:** Float (as ratio) | `fee` | Fee used during backtesting / dry-runs. Should normally not be configured, which has freqtrade fall back to the exchange default fee. Set as ratio (e.g. 0.001 = 0.1%). Fee is applied twice for each trade, once when buying, once when selling. <br> **Datatype:** Float (as ratio)
| `futures_funding_rate` | User-specified funding rate to be used when historical funding rates are not available from the exchange. This does not overwrite real historical rates. It is recommended that this be set to 0 unless you are testing a specific coin and you understand how the funding rate will affect freqtrade's profit calculations. [More information here](leverage.md#unavailable-funding-rates) <br>*Defaults to None.*<br> **Datatype:** Float
| `trading_mode` | Specifies if you want to trade regularly, trade with leverage, or trade contracts whose prices are derived from matching cryptocurrency prices. [leverage documentation](leverage.md). <br>*Defaults to `"spot"`.* <br> **Datatype:** String | `trading_mode` | Specifies if you want to trade regularly, trade with leverage, or trade contracts whose prices are derived from matching cryptocurrency prices. [leverage documentation](leverage.md). <br>*Defaults to `"spot"`.* <br> **Datatype:** String
| `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md). <br> **Datatype:** String | `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md). <br> **Datatype:** String
| `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md). <br>*Defaults to `0.05`.* <br> **Datatype:** Float | `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md). <br>*Defaults to `0.05`.* <br> **Datatype:** Float
| | **Unfilled timeout**
| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer | `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer | `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `minutes`.* <br> **Datatype:** String | `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `minutes`.* <br> **Datatype:** String
| `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency exit is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer | `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency exit is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer
| | **Pricing**
| `entry_pricing.price_side` | Select the side of the spread the bot should look at to get the entry rate. [More information below](#buy-price-side).<br> *Defaults to `same`.* <br> **Datatype:** String (either `ask`, `bid`, `same` or `other`). | `entry_pricing.price_side` | Select the side of the spread the bot should look at to get the entry rate. [More information below](#buy-price-side).<br> *Defaults to `same`.* <br> **Datatype:** String (either `ask`, `bid`, `same` or `other`).
| `entry_pricing.price_last_balance` | **Required.** Interpolate the bidding price. More information [below](#entry-price-without-orderbook-enabled). | `entry_pricing.price_last_balance` | **Required.** Interpolate the bidding price. More information [below](#entry-price-without-orderbook-enabled).
| `entry_pricing.use_order_book` | Enable entering using the rates in [Order Book Entry](#entry-price-with-orderbook-enabled). <br> *Defaults to `True`.*<br> **Datatype:** Boolean | `entry_pricing.use_order_book` | Enable entering using the rates in [Order Book Entry](#entry-price-with-orderbook-enabled). <br> *Defaults to `True`.*<br> **Datatype:** Boolean
@ -165,6 +173,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `exit_pricing.price_last_balance` | Interpolate the exiting price. More information [below](#exit-price-without-orderbook-enabled). | `exit_pricing.price_last_balance` | Interpolate the exiting price. More information [below](#exit-price-without-orderbook-enabled).
| `exit_pricing.use_order_book` | Enable exiting of open trades using [Order Book Exit](#exit-price-with-orderbook-enabled). <br> *Defaults to `True`.*<br> **Datatype:** Boolean | `exit_pricing.use_order_book` | Enable exiting of open trades using [Order Book Exit](#exit-price-with-orderbook-enabled). <br> *Defaults to `True`.*<br> **Datatype:** Boolean
| `exit_pricing.order_book_top` | Bot will use the top N rate in Order Book "price_side" to exit. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Exit](#exit-price-with-orderbook-enabled)<br>*Defaults to `1`.* <br> **Datatype:** Positive Integer | `exit_pricing.order_book_top` | Bot will use the top N rate in Order Book "price_side" to exit. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Exit](#exit-price-with-orderbook-enabled)<br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
| `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
| | **TODO**
| `use_exit_signal` | Use exit signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean | `use_exit_signal` | Use exit signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| `exit_profit_only` | Wait until the bot reaches `exit_profit_offset` before taking an exit decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean | `exit_profit_only` | Wait until the bot reaches `exit_profit_offset` before taking an exit decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `exit_profit_offset` | Exit-signal is only active above this value. Only active in combination with `exit_profit_only=True`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio) | `exit_profit_offset` | Exit-signal is only active above this value. Only active in combination with `exit_profit_only=True`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
@ -172,8 +182,9 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used. <br> **Datatype:** Integer | `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 (`"entry"`, `"exit"`, `"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 | `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 | `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.*<br> **Datatype:** Boolean
| `recursive_strategy_search` | Set to `true` to recursively search sub-directories inside `user_data/strategies` for a strategy. <br> **Datatype:** Boolean | `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `-1`.*<br> **Datatype:** Positive Integer or -1
| | **Exchange**
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> **Datatype:** Boolean | `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> **Datatype:** Boolean
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String | `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
@ -190,14 +201,19 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.<br>*Defaults to `false`<br> **Datatype:** Boolean | `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.<br>*Defaults to `false`<br> **Datatype:** Boolean
| `exchange.unknown_fee_rate` | Fallback value to use when calculating trading fees. This can be useful for exchanges which have fees in non-tradable currencies. The value provided here will be multiplied with the "fee cost".<br>*Defaults to `None`<br> **Datatype:** float | `exchange.unknown_fee_rate` | Fallback value to use when calculating trading fees. This can be useful for exchanges which have fees in non-tradable currencies. The value provided here will be multiplied with the "fee cost".<br>*Defaults to `None`<br> **Datatype:** float
| `exchange.log_responses` | Log relevant exchange responses. For debug mode only - use with care.<br>*Defaults to `false`<br> **Datatype:** Boolean | `exchange.log_responses` | Log relevant exchange responses. For debug mode only - use with care.<br>*Defaults to `false`<br> **Datatype:** Boolean
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean | `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| | **Plugins**
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation of all possible configuration options.
| `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts | `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
| `protections` | Define one or more protections to be used. [More information](plugins.md#protections). <br> **Datatype:** List of Dicts | `protections` | Define one or more protections to be used. [More information](plugins.md#protections). <br> **Datatype:** List of Dicts
| | **Telegram**
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean | `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String | `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String | `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `telegram.balance_dust_level` | Dust-level (in stake currency) - currencies with a balance below this will not be shown by `/balance`. <br> **Datatype:** float | `telegram.balance_dust_level` | Dust-level (in stake currency) - currencies with a balance below this will not be shown by `/balance`. <br> **Datatype:** float
| `telegram.reload` | Allow "reload" buttons on telegram messages. <br>*Defaults to `True`.<br> **Datatype:** boolean
| `telegram.notification_settings.*` | Detailed notification settings. Refer to the [telegram documentation](telegram-usage.md) for details.<br> **Datatype:** dictionary
| | **Webhook**
| `webhook.enabled` | Enable usage of Webhook notifications <br> **Datatype:** Boolean | `webhook.enabled` | Enable usage of Webhook notifications <br> **Datatype:** Boolean
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String | `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhookentry` | Payload to send on entry. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String | `webhook.webhookentry` | Payload to send on entry. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
@ -207,6 +223,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `webhook.webhookexitcancel` | Payload to send on exit order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String | `webhook.webhookexitcancel` | Payload to send on exit order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhookexitfill` | Payload to send on exit order filled. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String | `webhook.webhookexitfill` | Payload to send on exit order filled. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String | `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| | **Rest API / FreqUI**
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean | `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4 | `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>**Datatype:** Integer between 1024 and 65535 | `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>**Datatype:** Integer between 1024 and 65535
@ -214,23 +231,22 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String | `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String | `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
| `bot_name` | Name of the bot. Passed via API to a client - can be shown to distinguish / name bots.<br> *Defaults to `freqtrade`*<br> **Datatype:** String | `bot_name` | Name of the bot. Passed via API to a client - can be shown to distinguish / name bots.<br> *Defaults to `freqtrade`*<br> **Datatype:** String
| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> **Datatype:** String, SQLAlchemy connect string | | **Other**
| `initial_state` | Defines the initial application state. If set to stopped, then the bot has to be explicitly started via `/start` RPC command. <br>*Defaults to `stopped`.* <br> **Datatype:** Enum, either `stopped` or `running` | `initial_state` | Defines the initial application state. If set to stopped, then the bot has to be explicitly started via `/start` RPC command. <br>*Defaults to `stopped`.* <br> **Datatype:** Enum, either `stopped` or `running`
| `force_entry_enable` | Enables the RPC Commands to force a Trade entry. More information below. <br> **Datatype:** Boolean | `force_entry_enable` | Enables the RPC Commands to force a Trade entry. More information below. <br> **Datatype:** Boolean
| `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).<br> *Defaults to `False`*. <br> **Datatype:** Boolean | `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).<br> *Defaults to `False`*. <br> **Datatype:** Boolean
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> **Datatype:** ClassName
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> **Datatype:** String
| `internals.process_throttle_secs` | Set the process throttle, or minimum loop duration for one bot iteration loop. Value in second. <br>*Defaults to `5` seconds.* <br> **Datatype:** Positive Integer | `internals.process_throttle_secs` | Set the process throttle, or minimum loop duration for one bot iteration loop. Value in second. <br>*Defaults to `5` seconds.* <br> **Datatype:** Positive Integer
| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> **Datatype:** Positive Integer or 0 | `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> **Datatype:** Positive Integer or 0
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> **Datatype:** Boolean | `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> **Datatype:** Boolean
| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. <br> **Datatype:** String | `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> **Datatype:** ClassName
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> **Datatype:** String
| `recursive_strategy_search` | Set to `true` to recursively search sub-directories inside `user_data/strategies` for a strategy. <br> **Datatype:** Boolean
| `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> **Datatype:** String | `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> **Datatype:** String
| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> **Datatype:** String, SQLAlchemy connect string
| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. <br> **Datatype:** String
| `add_config_files` | Additional config files. These files will be loaded and merged with the current config file. The files are resolved relative to the initial file.<br> *Defaults to `[]`*. <br> **Datatype:** List of strings | `add_config_files` | Additional config files. These files will be loaded and merged with the current config file. The files are resolved relative to the initial file.<br> *Defaults to `[]`*. <br> **Datatype:** List of strings
| `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `json`*. <br> **Datatype:** String | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `json`*. <br> **Datatype:** String
| `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `jsongz`*. <br> **Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `jsongz`*. <br> **Datatype:** String
| `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.*<br> **Datatype:** Boolean
| `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `-1`.*<br> **Datatype:** Positive Integer or -1
| `futures_funding_rate` | User-specified funding rate to be used when historical funding rates are not available from the exchange. This does not overwrite real historical rates. It is recommended that this be set to 0 unless you are testing a specific coin and you understand how the funding rate will affect freqtrade's profit calculations. [More information here](leverage.md#unavailable-funding-rates) <br>*Defaults to None.*<br> **Datatype:** Float
### Parameters in the strategy ### Parameters in the strategy

View File

@ -40,13 +40,15 @@ pip install -r requirements-hyperopt.txt
``` ```
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TIMEFRAME] [--timerange TIMERANGE] [--recursive-strategy-search] [-i TIMEFRAME]
[--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}] [--data-format-ohlcv {json,jsongz,hdf5}]
[--max-open-trades INT] [--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT] [--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[-p PAIRS [PAIRS ...]] [--hyperopt-path PATH] [-p PAIRS [PAIRS ...]] [--hyperopt-path PATH]
[--eps] [--dmmp] [--enable-protections] [--eps] [--dmmp] [--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET] [-e INT] [--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL] [-e INT]
[--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]] [--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]]
[--print-all] [--no-color] [--print-json] [-j JOBS] [--print-all] [--no-color] [--print-json] [-j JOBS]
[--random-state INT] [--min-trades INT] [--random-state INT] [--min-trades INT]
@ -89,6 +91,9 @@ optional arguments:
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET --dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
Starting balance, used for backtesting / hyperopt and Starting balance, used for backtesting / hyperopt and
dry-runs. dry-runs.
--timeframe-detail TIMEFRAME_DETAIL
Specify detail timeframe for backtesting (`1m`, `5m`,
`30m`, `1h`, `1d`).
-e INT, --epochs INT Specify number of epochs (default: 100). -e INT, --epochs INT Specify number of epochs (default: 100).
--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...] --spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]
Specify which parameters to hyperopt. Space-separated Specify which parameters to hyperopt. Space-separated
@ -146,7 +151,9 @@ Strategy arguments:
Specify strategy class name which will be used by the Specify strategy class name which will be used by the
bot. bot.
--strategy-path PATH Specify additional strategy lookup path. --strategy-path PATH Specify additional strategy lookup path.
--recursive-strategy-search
Recursively search for a strategy in the strategies
folder.
``` ```
### Hyperopt checklist ### Hyperopt checklist
@ -867,10 +874,12 @@ You can also enable position stacking in the configuration file by explicitly se
As hyperopt consumes a lot of memory (the complete data needs to be in memory once per parallel backtesting process), it's likely that you run into "out of memory" errors. As hyperopt consumes a lot of memory (the complete data needs to be in memory once per parallel backtesting process), it's likely that you run into "out of memory" errors.
To combat these, you have multiple options: To combat these, you have multiple options:
* reduce the amount of pairs * Reduce the amount of pairs.
* reduce the timerange used (`--timerange <timerange>`) * Reduce the timerange used (`--timerange <timerange>`).
* reduce the number of parallel processes (`-j <n>`) * Avoid using `--timeframe-detail` (this loads a lot of additional data into memory).
* Increase the memory of your machine * Reduce the number of parallel processes (`-j <n>`).
* Increase the memory of your machine.
## The objective has been evaluated at this point before. ## The objective has been evaluated at this point before.

View File

@ -1,6 +1,6 @@
markdown==3.3.7 markdown==3.4.1
mkdocs==1.3.0 mkdocs==1.3.0
mkdocs-material==8.3.9 mkdocs-material==8.3.9
mdx_truly_sane_lists==1.2 mdx_truly_sane_lists==1.3
pymdown-extensions==9.5 pymdown-extensions==9.5
jinja2==3.1.2 jinja2==3.1.2

View File

@ -175,8 +175,8 @@ Before this, `stoploss` is used for the trailing stoploss.
* assuming the asset now increases to 102$ * assuming the asset now increases to 102$
* the stoploss will now be at 91.8$ - 10% below the highest observed rate * the stoploss will now be at 91.8$ - 10% below the highest observed rate
* assuming the asset now increases to 103.5$ (above the offset configured) * assuming the asset now increases to 103.5$ (above the offset configured)
* the stop loss will now be -2% of 103$ = 101.42$ * the stop loss will now be -2% of 103.5$ = 101.43$
* now the asset drops in value to 102\$, the stop loss will still be 101.42$ and would trigger once price breaks below 101.42$ * now the asset drops in value to 102\$, the stop loss will still be 101.43$ and would trigger once price breaks below 101.43$
### Trailing stop loss only once the trade has reached a certain offset ### Trailing stop loss only once the trade has reached a certain offset

View File

@ -29,7 +29,7 @@ ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_pos
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
"position_stacking", "use_max_market_positions", "position_stacking", "use_max_market_positions",
"enable_protections", "dry_run_wallet", "enable_protections", "dry_run_wallet", "timeframe_detail",
"epochs", "spaces", "print_all", "epochs", "spaces", "print_all",
"print_colorized", "print_json", "hyperopt_jobs", "print_colorized", "print_json", "hyperopt_jobs",
"hyperopt_random_state", "hyperopt_min_trades", "hyperopt_random_state", "hyperopt_min_trades",

View File

@ -591,3 +591,4 @@ TradeList = List[List]
LongShort = Literal['long', 'short'] LongShort = Literal['long', 'short']
EntryExit = Literal['entry', 'exit'] EntryExit = Literal['entry', 'exit']
BuySell = Literal['buy', 'sell'] BuySell = Literal['buy', 'sell']
MakerTaker = Literal['maker', 'taker']

View File

@ -20,7 +20,7 @@ from ccxt import ROUND_DOWN, ROUND_UP, TICK_SIZE, TRUNCATE, Precise, decimal_to_
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BuySell, from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BuySell,
EntryExit, ListPairsWithTimeframes, PairWithTimeframe) EntryExit, ListPairsWithTimeframes, MakerTaker, PairWithTimeframe)
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
@ -88,7 +88,8 @@ class Exchange:
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
] ]
def __init__(self, config: Dict[str, Any], validate: bool = True, freqai: bool = False) -> None: def __init__(self, config: Dict[str, Any], validate: bool = True,
load_leverage_tiers: bool = False) -> None:
""" """
Initializes this module with the given config, Initializes this module with the given config,
it does basic validation whether the specified exchange and pairs are valid. it does basic validation whether the specified exchange and pairs are valid.
@ -186,7 +187,7 @@ class Exchange:
self.markets_refresh_interval: int = exchange_config.get( self.markets_refresh_interval: int = exchange_config.get(
"markets_refresh_interval", 60) * 60 "markets_refresh_interval", 60) * 60
if self.trading_mode != TradingMode.SPOT and freqai is False: if self.trading_mode != TradingMode.SPOT and load_leverage_tiers:
self.fill_leverage_tiers() self.fill_leverage_tiers()
self.additional_exchange_init() self.additional_exchange_init()
@ -850,20 +851,27 @@ class Exchange:
'filled': _amount, 'filled': _amount,
'cost': (dry_order['amount'] * average) / leverage 'cost': (dry_order['amount'] * average) / leverage
}) })
dry_order = self.add_dry_order_fee(pair, dry_order) # market orders will always incurr taker fees
dry_order = self.add_dry_order_fee(pair, dry_order, 'taker')
dry_order = self.check_dry_limit_order_filled(dry_order) dry_order = self.check_dry_limit_order_filled(dry_order, immediate=True)
self._dry_run_open_orders[dry_order["id"]] = dry_order self._dry_run_open_orders[dry_order["id"]] = dry_order
# Copy order and close it - so the returned order is open unless it's a market order # Copy order and close it - so the returned order is open unless it's a market order
return dry_order return dry_order
def add_dry_order_fee(self, pair: str, dry_order: Dict[str, Any]) -> Dict[str, Any]: def add_dry_order_fee(
self,
pair: str,
dry_order: Dict[str, Any],
taker_or_maker: MakerTaker,
) -> Dict[str, Any]:
fee = self.get_fee(pair, taker_or_maker=taker_or_maker)
dry_order.update({ dry_order.update({
'fee': { 'fee': {
'currency': self.get_pair_quote_currency(pair), 'currency': self.get_pair_quote_currency(pair),
'cost': dry_order['cost'] * self.get_fee(pair), 'cost': dry_order['cost'] * fee,
'rate': self.get_fee(pair) 'rate': fee
} }
}) })
return dry_order return dry_order
@ -929,7 +937,8 @@ class Exchange:
pass pass
return False return False
def check_dry_limit_order_filled(self, order: Dict[str, Any]) -> Dict[str, Any]: def check_dry_limit_order_filled(
self, order: Dict[str, Any], immediate: bool = False) -> Dict[str, Any]:
""" """
Check dry-run limit order fill and update fee (if it filled). Check dry-run limit order fill and update fee (if it filled).
""" """
@ -943,7 +952,12 @@ class Exchange:
'filled': order['amount'], 'filled': order['amount'],
'remaining': 0, 'remaining': 0,
}) })
self.add_dry_order_fee(pair, order)
self.add_dry_order_fee(
pair,
order,
'taker' if immediate else 'maker',
)
return order return order
@ -1601,7 +1615,7 @@ class Exchange:
@retrier @retrier
def get_fee(self, symbol: str, type: str = '', side: str = '', amount: float = 1, def get_fee(self, symbol: str, type: str = '', side: str = '', amount: float = 1,
price: float = 1, taker_or_maker: str = 'maker') -> float: price: float = 1, taker_or_maker: MakerTaker = 'maker') -> float:
try: try:
if self._config['dry_run'] and self._config.get('fee', None) is not None: if self._config['dry_run'] and self._config.get('fee', None) is not None:
return self._config['fee'] return self._config['fee']

View File

@ -923,7 +923,7 @@ class FreqaiDataKitchen:
and training the model. and training the model.
""" """
exchange = ExchangeResolver.load_exchange( exchange = ExchangeResolver.load_exchange(
self.config["exchange"]["name"], self.config, validate=False, freqai=True self.config["exchange"]["name"], self.config, validate=False, load_leverage_tiers=False
) )
new_pairs_days = int((timerange.stopts - timerange.startts) / SECONDS_IN_DAY) new_pairs_days = int((timerange.stopts - timerange.startts) / SECONDS_IN_DAY)

View File

@ -65,7 +65,8 @@ class FreqtradeBot(LoggingMixin):
# Check config consistency here since strategies can set certain options # Check config consistency here since strategies can set certain options
validate_config_consistency(config) validate_config_consistency(config)
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) self.exchange = ExchangeResolver.load_exchange(
self.config['exchange']['name'], self.config, load_leverage_tiers=True)
init_db(self.config['db_url']) init_db(self.config['db_url'])

View File

@ -84,7 +84,8 @@ class Backtesting:
self.processed_dfs: Dict[str, Dict] = {} self.processed_dfs: Dict[str, Dict] = {}
self._exchange_name = self.config['exchange']['name'] self._exchange_name = self.config['exchange']['name']
self.exchange = ExchangeResolver.load_exchange(self._exchange_name, self.config) self.exchange = ExchangeResolver.load_exchange(
self._exchange_name, self.config, load_leverage_tiers=True)
self.dataprovider = DataProvider(self.config, self.exchange) self.dataprovider = DataProvider(self.config, self.exchange)
if self.config.get('strategy_list'): if self.config.get('strategy_list'):

View File

@ -255,18 +255,18 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
""" """
# Trades can be empty # Trades can be empty
if trades is not None and len(trades) > 0: if trades is not None and len(trades) > 0:
# Create description for sell summarizing the trade # Create description for exit summarizing the trade
trades['desc'] = trades.apply( trades['desc'] = trades.apply(
lambda row: f"{row['profit_ratio']:.2%}, " + lambda row: f"{row['profit_ratio']:.2%}, " +
(f"{row['enter_tag']}, " if row['enter_tag'] is not None else "") + (f"{row['enter_tag']}, " if row['enter_tag'] is not None else "") +
f"{row['exit_reason']}, " + f"{row['exit_reason']}, " +
f"{row['trade_duration']} min", f"{row['trade_duration']} min",
axis=1) axis=1)
trade_buys = go.Scatter( trade_entries = go.Scatter(
x=trades["open_date"], x=trades["open_date"],
y=trades["open_rate"], y=trades["open_rate"],
mode='markers', mode='markers',
name='Trade buy', name='Trade entry',
text=trades["desc"], text=trades["desc"],
marker=dict( marker=dict(
symbol='circle-open', symbol='circle-open',
@ -277,12 +277,12 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
) )
) )
trade_sells = go.Scatter( trade_exits = go.Scatter(
x=trades.loc[trades['profit_ratio'] > 0, "close_date"], x=trades.loc[trades['profit_ratio'] > 0, "close_date"],
y=trades.loc[trades['profit_ratio'] > 0, "close_rate"], y=trades.loc[trades['profit_ratio'] > 0, "close_rate"],
text=trades.loc[trades['profit_ratio'] > 0, "desc"], text=trades.loc[trades['profit_ratio'] > 0, "desc"],
mode='markers', mode='markers',
name='Sell - Profit', name='Exit - Profit',
marker=dict( marker=dict(
symbol='square-open', symbol='square-open',
size=11, size=11,
@ -290,12 +290,12 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
color='green' color='green'
) )
) )
trade_sells_loss = go.Scatter( trade_exits_loss = go.Scatter(
x=trades.loc[trades['profit_ratio'] <= 0, "close_date"], x=trades.loc[trades['profit_ratio'] <= 0, "close_date"],
y=trades.loc[trades['profit_ratio'] <= 0, "close_rate"], y=trades.loc[trades['profit_ratio'] <= 0, "close_rate"],
text=trades.loc[trades['profit_ratio'] <= 0, "desc"], text=trades.loc[trades['profit_ratio'] <= 0, "desc"],
mode='markers', mode='markers',
name='Sell - Loss', name='Exit - Loss',
marker=dict( marker=dict(
symbol='square-open', symbol='square-open',
size=11, size=11,
@ -303,9 +303,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
color='red' color='red'
) )
) )
fig.add_trace(trade_buys, 1, 1) fig.add_trace(trade_entries, 1, 1)
fig.add_trace(trade_sells, 1, 1) fig.add_trace(trade_exits, 1, 1)
fig.add_trace(trade_sells_loss, 1, 1) fig.add_trace(trade_exits_loss, 1, 1)
else: else:
logger.warning("No trades found.") logger.warning("No trades found.")
return fig return fig
@ -444,7 +444,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
Generate the graph from the data generated by Backtesting or from DB Generate the graph from the data generated by Backtesting or from DB
Volume will always be ploted in row2, so Row 1 and 3 are to our disposal for custom indicators Volume will always be ploted in row2, so Row 1 and 3 are to our disposal for custom indicators
:param pair: Pair to Display on the graph :param pair: Pair to Display on the graph
:param data: OHLCV DataFrame containing indicators and buy/sell signals :param data: OHLCV DataFrame containing indicators and entry/exit signals
:param trades: All trades created :param trades: All trades created
:param indicators1: List containing Main plot indicators :param indicators1: List containing Main plot indicators
:param indicators2: List containing Sub plot indicators :param indicators2: List containing Sub plot indicators

View File

@ -19,7 +19,7 @@ class ExchangeResolver(IResolver):
@staticmethod @staticmethod
def load_exchange(exchange_name: str, config: dict, validate: bool = True, def load_exchange(exchange_name: str, config: dict, validate: bool = True,
freqai: bool = False) -> Exchange: load_leverage_tiers: bool = False) -> Exchange:
""" """
Load the custom class from config parameter Load the custom class from config parameter
:param exchange_name: name of the Exchange to load :param exchange_name: name of the Exchange to load
@ -30,10 +30,13 @@ class ExchangeResolver(IResolver):
exchange_name = exchange_name.title() exchange_name = exchange_name.title()
exchange = None exchange = None
try: try:
exchange = ExchangeResolver._load_exchange(exchange_name, exchange = ExchangeResolver._load_exchange(
kwargs={'config': config, exchange_name,
'validate': validate, kwargs={
'freqai': freqai}) 'config': config,
'validate': validate,
'load_leverage_tiers': load_leverage_tiers}
)
except ImportError: except ImportError:
logger.info( logger.info(
f"No {exchange_name} specific subclass found. Using the generic class instead.") f"No {exchange_name} specific subclass found. Using the generic class instead.")

View File

@ -37,7 +37,7 @@ def get_exchange(config=Depends(get_config)):
if not ApiServer._exchange: if not ApiServer._exchange:
from freqtrade.resolvers import ExchangeResolver from freqtrade.resolvers import ExchangeResolver
ApiServer._exchange = ExchangeResolver.load_exchange( ApiServer._exchange = ExchangeResolver.load_exchange(
config['exchange']['name'], config) config['exchange']['name'], config, load_leverage_tiers=False)
return ApiServer._exchange return ApiServer._exchange

View File

@ -11,7 +11,7 @@ flake8-tidy-imports==4.8.0
mypy==0.961 mypy==0.961
pre-commit==2.20.0 pre-commit==2.20.0
pytest==7.1.2 pytest==7.1.2
pytest-asyncio==0.18.3 pytest-asyncio==0.19.0
pytest-cov==3.0.0 pytest-cov==3.0.0
pytest-mock==3.8.2 pytest-mock==3.8.2
pytest-random-order==1.0.4 pytest-random-order==1.0.4
@ -25,6 +25,6 @@ nbconvert==6.5.0
# mypy types # mypy types
types-cachetools==5.2.1 types-cachetools==5.2.1
types-filelock==3.2.7 types-filelock==3.2.7
types-requests==2.28.0 types-requests==2.28.1
types-tabulate==0.8.11 types-tabulate==0.8.11
types-python-dateutil==2.8.18 types-python-dateutil==2.8.18

View File

@ -2,7 +2,7 @@ numpy==1.23.1
pandas==1.4.3 pandas==1.4.3
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==1.90.47 ccxt==1.90.89
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==37.0.4 cryptography==37.0.4
aiohttp==3.8.1 aiohttp==3.8.1
@ -12,7 +12,7 @@ arrow==1.2.2
cachetools==4.2.2 cachetools==4.2.2
requests==2.28.1 requests==2.28.1
urllib3==1.26.10 urllib3==1.26.10
jsonschema==4.6.2 jsonschema==4.7.2
TA-Lib==0.4.24 TA-Lib==0.4.24
technical==1.3.0 technical==1.3.0
tabulate==0.8.10 tabulate==0.8.10
@ -34,7 +34,7 @@ orjson==3.7.7
sdnotify==0.3.2 sdnotify==0.3.2
# API Server # API Server
fastapi==0.78.0 fastapi==0.79.0
uvicorn==0.18.2 uvicorn==0.18.2
pyjwt==2.4.0 pyjwt==2.4.0
aiofiles==0.8.0 aiofiles==0.8.0

View File

@ -148,7 +148,7 @@ def get_patched_exchange(mocker, config, api_mock=None, id='binance',
patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes) patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes)
config['exchange']['name'] = id config['exchange']['name'] = id
try: try:
exchange = ExchangeResolver.load_exchange(id, config) exchange = ExchangeResolver.load_exchange(id, config, load_leverage_tiers=True)
except ImportError: except ImportError:
exchange = Exchange(config) exchange = Exchange(config)
return exchange return exchange
@ -2609,7 +2609,7 @@ def open_trade_usdt():
pair='ADA/USDT', pair='ADA/USDT',
open_rate=2.0, open_rate=2.0,
exchange='binance', exchange='binance',
open_order_id='123456789', open_order_id='123456789_exit',
amount=30.0, amount=30.0,
fee_open=0.0, fee_open=0.0,
fee_close=0.0, fee_close=0.0,
@ -2634,6 +2634,23 @@ def open_trade_usdt():
cost=trade.open_rate * trade.amount, cost=trade.open_rate * trade.amount,
order_date=trade.open_date, order_date=trade.open_date,
order_filled_date=trade.open_date, order_filled_date=trade.open_date,
),
Order(
ft_order_side='exit',
ft_pair=trade.pair,
ft_is_open=True,
order_id='123456789_exit',
status="open",
symbol=trade.pair,
order_type="limit",
side="sell",
price=trade.open_rate,
average=trade.open_rate,
filled=trade.amount,
remaining=0,
cost=trade.open_rate * trade.amount,
order_date=trade.open_date,
order_filled_date=trade.open_date,
) )
] ]
return trade return trade

View File

@ -137,7 +137,8 @@ def exchange_futures(request, exchange_conf, class_mocker):
'freqtrade.exchange.binance.Binance.fill_leverage_tiers') 'freqtrade.exchange.binance.Binance.fill_leverage_tiers')
class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees') class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees')
class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init') class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init')
exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True) exchange = ExchangeResolver.load_exchange(
request.param, exchange_conf, validate=True, load_leverage_tiers=True)
yield exchange, request.param yield exchange, request.param

View File

@ -1138,6 +1138,57 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverag
assert order["cost"] == 1 * 200 assert order["cost"] == 1 * 200
@pytest.mark.parametrize('side,is_short,order_reason', [
("buy", False, "entry"),
("sell", False, "exit"),
("buy", True, "exit"),
("sell", True, "entry"),
])
@pytest.mark.parametrize("order_type,price_side,fee", [
("limit", "same", 1.0),
("limit", "other", 2.0),
("market", "same", 2.0),
("market", "other", 2.0),
])
def test_create_dry_run_order_fees(
default_conf,
mocker,
side,
order_type,
is_short,
order_reason,
price_side,
fee,
):
mocker.patch(
'freqtrade.exchange.Exchange.get_fee',
side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0
)
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled',
return_value=price_side == 'other')
exchange = get_patched_exchange(mocker, default_conf)
order = exchange.create_dry_run_order(
pair='LTC/USDT',
ordertype=order_type,
side=side,
amount=10,
rate=2.0,
leverage=1.0
)
if price_side == 'other' or order_type == 'market':
assert order['fee']['rate'] == fee
return
else:
assert order['fee'] is None
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled',
return_value=price_side != 'other')
order1 = exchange.fetch_dry_run_order(order['id'])
assert order1['fee']['rate'] == fee
@pytest.mark.parametrize("side,startprice,endprice", [ @pytest.mark.parametrize("side,startprice,endprice", [
("buy", 25.563, 25.566), ("buy", 25.563, 25.566),
("sell", 25.566, 25.563) ("sell", 25.566, 25.563)

View File

@ -90,28 +90,6 @@ def load_data_test(what, testdatadir):
fill_missing=True)} fill_missing=True)}
def simple_backtest(config, contour, mocker, testdatadir) -> None:
patch_exchange(mocker)
config['timeframe'] = '1m'
backtesting = Backtesting(config)
backtesting._set_strategy(backtesting.strategylist[0])
data = load_data_test(contour, testdatadir)
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
assert isinstance(processed, dict)
results = backtesting.backtest(
processed=processed,
start_date=min_date,
end_date=max_date,
max_open_trades=1,
position_stacking=False,
enable_protections=config.get('enable_protections', False),
)
# results :: <class 'pandas.core.frame.DataFrame'>
return results
# FIX: fixturize this? # FIX: fixturize this?
def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'): def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair]) data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair])
@ -942,6 +920,7 @@ def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadi
def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatadir) -> None: def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatadir) -> None:
# While this test IS a copy of test_backtest_pricecontours, it's needed to ensure # While this test IS a copy of test_backtest_pricecontours, it's needed to ensure
# results do not carry-over to the next run, which is not given by using parametrize. # results do not carry-over to the next run, which is not given by using parametrize.
patch_exchange(mocker)
default_conf['protections'] = [ default_conf['protections'] = [
{ {
"method": "CooldownPeriod", "method": "CooldownPeriod",
@ -949,6 +928,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
}] }]
default_conf['enable_protections'] = True default_conf['enable_protections'] = True
default_conf['timeframe'] = '1m'
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf')) mocker.patch("freqtrade.exchange.Exchange.get_max_pair_stake_amount", return_value=float('inf'))
@ -959,12 +939,27 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
['sine', 9], ['sine', 9],
['raise', 10], ['raise', 10],
] ]
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
# While entry-signals are unrealistic, running backtesting # While entry-signals are unrealistic, running backtesting
# over and over again should not cause different results # over and over again should not cause different results
for [contour, numres] in tests: for [contour, numres] in tests:
# Debug output for random test failure # Debug output for random test failure
print(f"{contour}, {numres}") print(f"{contour}, {numres}")
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == numres data = load_data_test(contour, testdatadir)
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
assert isinstance(processed, dict)
results = backtesting.backtest(
processed=processed,
start_date=min_date,
end_date=max_date,
max_open_trades=1,
position_stacking=False,
enable_protections=default_conf.get('enable_protections', False),
)
assert len(results['results']) == numres
@pytest.mark.parametrize('protections,contour,expected', [ @pytest.mark.parametrize('protections,contour,expected', [
@ -990,7 +985,25 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
# While entry-signals are unrealistic, running backtesting # While entry-signals are unrealistic, running backtesting
# over and over again should not cause different results # over and over again should not cause different results
assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == expected
patch_exchange(mocker)
default_conf['timeframe'] = '1m'
backtesting = Backtesting(default_conf)
backtesting._set_strategy(backtesting.strategylist[0])
data = load_data_test(contour, testdatadir)
processed = backtesting.strategy.advise_all_indicators(data)
min_date, max_date = get_timerange(processed)
assert isinstance(processed, dict)
results = backtesting.backtest(
processed=processed,
start_date=min_date,
end_date=max_date,
max_open_trades=1,
position_stacking=False,
enable_protections=default_conf.get('enable_protections', False),
)
assert len(results['results']) == expected
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):

View File

@ -6,6 +6,7 @@ import pytest
from freqtrade import constants from freqtrade import constants
from freqtrade.enums import ExitType from freqtrade.enums import ExitType
from freqtrade.persistence import PairLocks, Trade from freqtrade.persistence import PairLocks, Trade
from freqtrade.persistence.trade_model import Order
from freqtrade.plugins.protectionmanager import ProtectionManager from freqtrade.plugins.protectionmanager import ProtectionManager
from tests.conftest import get_patched_freqtradebot, log_has_re from tests.conftest import get_patched_freqtradebot, log_has_re
@ -30,7 +31,37 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
amount=0.01 / open_rate, amount=0.01 / open_rate,
exchange='binance', exchange='binance',
is_short=is_short, is_short=is_short,
leverage=1,
) )
trade.orders.append(Order(
ft_order_side=trade.entry_side,
order_id=f'{pair}-{trade.entry_side}-{trade.open_date}',
ft_pair=pair,
amount=trade.amount,
filled=trade.amount,
remaining=0,
price=open_rate,
average=open_rate,
status="closed",
order_type="market",
side=trade.entry_side,
))
if not is_open:
trade.orders.append(Order(
ft_order_side=trade.exit_side,
order_id=f'{pair}-{trade.exit_side}-{trade.close_date}',
ft_pair=pair,
amount=trade.amount,
filled=trade.amount,
remaining=0,
price=open_rate * (2 - profit_rate if is_short else profit_rate),
average=open_rate * (2 - profit_rate if is_short else profit_rate),
status="closed",
order_type="market",
side=trade.exit_side,
))
trade.recalc_open_trade_value() trade.recalc_open_trade_value()
if not is_open: if not is_open:
trade.close(open_rate * (2 - profit_rate if is_short else profit_rate)) trade.close(open_rate * (2 - profit_rate if is_short else profit_rate))

View File

@ -830,6 +830,8 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
assert cancel_order_mock.call_count == 2 assert cancel_order_mock.call_count == 2
assert trade.amount == amount assert trade.amount == amount
trade = Trade.query.filter(Trade.id == '3').first()
# make an limit-sell open trade # make an limit-sell open trade
mocker.patch( mocker.patch(
'freqtrade.exchange.Exchange.fetch_order', 'freqtrade.exchange.Exchange.fetch_order',

View File

@ -686,6 +686,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
# Simulate fulfilled LIMIT_SELL order for trade # Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object( oobj = Order.parse_from_ccxt_object(
limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell') limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell')
trade.orders.append(oobj)
trade.update_trade(oobj) trade.update_trade(oobj)
trade.close_date = datetime.now(timezone.utc) trade.close_date = datetime.now(timezone.utc)
@ -707,7 +708,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
assert '*Best Performing:* `ETH/USDT: 9.45%`' in msg_mock.call_args_list[-1][0][0] assert '*Best Performing:* `ETH/USDT: 9.45%`' in msg_mock.call_args_list[-1][0][0]
assert '*Max Drawdown:*' in msg_mock.call_args_list[-1][0][0] assert '*Max Drawdown:*' in msg_mock.call_args_list[-1][0][0]
assert '*Profit factor:*' in msg_mock.call_args_list[-1][0][0] assert '*Profit factor:*' in msg_mock.call_args_list[-1][0][0]
assert '*Trading volume:* `60 USDT`' in msg_mock.call_args_list[-1][0][0] assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0]
@pytest.mark.parametrize('is_short', [True, False]) @pytest.mark.parametrize('is_short', [True, False])

View File

@ -2060,8 +2060,9 @@ def test_update_trade_state_orderexception(mocker, default_conf_usdt, caplog) ->
@pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
def test_update_trade_state_sell( def test_update_trade_state_sell(
default_conf_usdt, trades_for_order, limit_order_open, limit_order, is_short, mocker, default_conf_usdt, trades_for_order, limit_order_open, limit_order, is_short, mocker
): ):
buy_order = limit_order[entry_side(is_short)]
open_order = limit_order_open[exit_side(is_short)] open_order = limit_order_open[exit_side(is_short)]
l_order = limit_order[exit_side(is_short)] l_order = limit_order[exit_side(is_short)]
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
@ -2088,6 +2089,9 @@ def test_update_trade_state_sell(
leverage=1, leverage=1,
is_short=is_short, is_short=is_short,
) )
order = Order.parse_from_ccxt_object(buy_order, 'LTC/ETH', entry_side(is_short))
trade.orders.append(order)
order = Order.parse_from_ccxt_object(open_order, 'LTC/ETH', exit_side(is_short)) order = Order.parse_from_ccxt_object(open_order, 'LTC/ETH', exit_side(is_short))
trade.orders.append(order) trade.orders.append(order)
assert order.status == 'open' assert order.status == 'open'
@ -2135,8 +2139,6 @@ def test_handle_trade(
assert trade assert trade
time.sleep(0.01) # Race condition fix time.sleep(0.01) # Race condition fix
oobj = Order.parse_from_ccxt_object(enter_order, enter_order['symbol'], entry_side(is_short))
trade.update_trade(oobj)
assert trade.is_open is True assert trade.is_open is True
freqtrade.wallets.update() freqtrade.wallets.update()
@ -2146,11 +2148,15 @@ def test_handle_trade(
assert trade.open_order_id == exit_order['id'] assert trade.open_order_id == exit_order['id']
# Simulate fulfilled LIMIT_SELL order for trade # Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(exit_order, exit_order['symbol'], exit_side(is_short)) trade.orders[-1].ft_is_open = False
trade.update_trade(oobj) trade.orders[-1].status = 'closed'
trade.orders[-1].filled = trade.orders[-1].remaining
trade.orders[-1].remaining = 0.0
assert trade.close_rate == 2.0 if is_short else 2.2 trade.update_trade(trade.orders[-1])
assert trade.close_profit == close_profit
assert trade.close_rate == (2.0 if is_short else 2.2)
assert pytest.approx(trade.close_profit) == close_profit
assert trade.calc_profit(trade.close_rate) == 5.685 assert trade.calc_profit(trade.close_rate) == 5.685
assert trade.close_date is not None assert trade.close_date is not None
assert trade.exit_reason == 'sell_signal1' assert trade.exit_reason == 'sell_signal1'
@ -2753,6 +2759,8 @@ def test_check_handle_cancelled_exit(
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
limit_sell_order_old.update({"status": "canceled", 'filled': 0.0}) limit_sell_order_old.update({"status": "canceled", 'filled': 0.0})
limit_sell_order_old['side'] = 'buy' if is_short else 'sell' limit_sell_order_old['side'] = 'buy' if is_short else 'sell'
limit_sell_order_old['id'] = open_trade_usdt.open_order_id
patch_exchange(mocker) patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -2787,6 +2795,7 @@ def test_manage_open_orders_partial(
rpc_mock = patch_RPCManager(mocker) rpc_mock = patch_RPCManager(mocker)
open_trade.is_short = is_short open_trade.is_short = is_short
open_trade.leverage = leverage open_trade.leverage = leverage
open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy'
limit_buy_order_old_partial['id'] = open_trade.open_order_id limit_buy_order_old_partial['id'] = open_trade.open_order_id
limit_buy_order_old_partial['side'] = 'sell' if is_short else 'buy' limit_buy_order_old_partial['side'] = 'sell' if is_short else 'buy'
limit_buy_canceled = deepcopy(limit_buy_order_old_partial) limit_buy_canceled = deepcopy(limit_buy_order_old_partial)
@ -2872,6 +2881,7 @@ def test_manage_open_orders_partial_except(
limit_buy_order_old_partial_canceled, mocker limit_buy_order_old_partial_canceled, mocker
) -> None: ) -> None:
open_trade.is_short = is_short open_trade.is_short = is_short
open_trade.orders[0].ft_order_side = 'sell' if is_short else 'buy'
rpc_mock = patch_RPCManager(mocker) rpc_mock = patch_RPCManager(mocker)
limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id
limit_buy_order_old_partial['id'] = open_trade.open_order_id limit_buy_order_old_partial['id'] = open_trade.open_order_id
@ -3090,7 +3100,27 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None:
close_date=arrow.utcnow().datetime, close_date=arrow.utcnow().datetime,
exit_reason="sell_reason_whatever", exit_reason="sell_reason_whatever",
) )
order = {'remaining': 1, trade.orders = [
Order(
ft_order_side='buy',
ft_pair=trade.pair,
ft_is_open=True,
order_id='123456',
status="closed",
symbol=trade.pair,
order_type="market",
side="buy",
price=trade.open_rate,
average=trade.open_rate,
filled=trade.amount,
remaining=0,
cost=trade.open_rate * trade.amount,
order_date=trade.open_date,
order_filled_date=trade.open_date,
),
]
order = {'id': "123456",
'remaining': 1,
'amount': 1, 'amount': 1,
'status': "open"} 'status': "open"}
reason = CANCEL_REASON['TIMEOUT'] reason = CANCEL_REASON['TIMEOUT']
@ -3626,7 +3656,7 @@ def test_execute_trade_exit_market_order(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt, fetch_ticker=ticker_usdt,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False), _is_dry_limit_order_filled=MagicMock(return_value=True),
) )
patch_whitelist(mocker, default_conf_usdt) patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
@ -3642,7 +3672,8 @@ def test_execute_trade_exit_market_order(
# Increase the price and sell it # Increase the price and sell it
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt_sell_up fetch_ticker=ticker_usdt_sell_up,
_is_dry_limit_order_filled=MagicMock(return_value=False),
) )
freqtrade.config['order_types']['exit'] = 'market' freqtrade.config['order_types']['exit'] = 'market'
@ -3655,7 +3686,7 @@ def test_execute_trade_exit_market_order(
assert not trade.is_open assert not trade.is_open
assert trade.close_profit == profit_ratio assert trade.close_profit == profit_ratio
assert rpc_mock.call_count == 3 assert rpc_mock.call_count == 4
last_msg = rpc_mock.call_args_list[-2][0][0] last_msg = rpc_mock.call_args_list[-2][0][0]
assert { assert {
'type': RPCMessageType.EXIT, 'type': RPCMessageType.EXIT,

View File

@ -481,6 +481,7 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
trade.open_order_id = 'something' trade.open_order_id = 'something'
oobj = Order.parse_from_ccxt_object(enter_order, 'ADA/USDT', entry_side) oobj = Order.parse_from_ccxt_object(enter_order, 'ADA/USDT', entry_side)
trade.orders.append(oobj)
trade.update_trade(oobj) trade.update_trade(oobj)
assert trade.open_order_id is None assert trade.open_order_id is None
assert trade.open_rate == open_rate assert trade.open_rate == open_rate
@ -496,11 +497,12 @@ def test_update_limit_order(fee, caplog, limit_buy_order_usdt, limit_sell_order_
trade.open_order_id = 'something' trade.open_order_id = 'something'
time_machine.move_to("2022-03-31 21:45:05 +00:00") time_machine.move_to("2022-03-31 21:45:05 +00:00")
oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', exit_side) oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', exit_side)
trade.orders.append(oobj)
trade.update_trade(oobj) trade.update_trade(oobj)
assert trade.open_order_id is None assert trade.open_order_id is None
assert trade.close_rate == close_rate assert trade.close_rate == close_rate
assert trade.close_profit == profit assert pytest.approx(trade.close_profit) == profit
assert trade.close_date is not None assert trade.close_date is not None
assert log_has_re(f"LIMIT_{exit_side.upper()} has been fulfilled for " assert log_has_re(f"LIMIT_{exit_side.upper()} has been fulfilled for "
r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, " r"Trade\(id=2, pair=ADA/USDT, amount=30.00000000, "
@ -529,6 +531,7 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
trade.open_order_id = 'something' trade.open_order_id = 'something'
oobj = Order.parse_from_ccxt_object(market_buy_order_usdt, 'ADA/USDT', 'buy') oobj = Order.parse_from_ccxt_object(market_buy_order_usdt, 'ADA/USDT', 'buy')
trade.orders.append(oobj)
trade.update_trade(oobj) trade.update_trade(oobj)
assert trade.open_order_id is None assert trade.open_order_id is None
assert trade.open_rate == 2.0 assert trade.open_rate == 2.0
@ -543,10 +546,11 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
trade.is_open = True trade.is_open = True
trade.open_order_id = 'something' trade.open_order_id = 'something'
oobj = Order.parse_from_ccxt_object(market_sell_order_usdt, 'ADA/USDT', 'sell') oobj = Order.parse_from_ccxt_object(market_sell_order_usdt, 'ADA/USDT', 'sell')
trade.orders.append(oobj)
trade.update_trade(oobj) trade.update_trade(oobj)
assert trade.open_order_id is None assert trade.open_order_id is None
assert trade.close_rate == 2.2 assert trade.close_rate == 2.2
assert trade.close_profit == round(0.0945137157107232, 8) assert pytest.approx(trade.close_profit) == 0.094513715710723
assert trade.close_date is not None assert trade.close_date is not None
assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, " assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, " r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, "
@ -624,14 +628,41 @@ def test_trade_close(limit_buy_order_usdt, limit_sell_order_usdt, fee):
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=10),
interest_rate=0.0005, interest_rate=0.0005,
exchange='binance', exchange='binance',
trading_mode=margin trading_mode=margin,
leverage=1.0,
) )
trade.orders.append(Order(
ft_order_side=trade.entry_side,
order_id=f'{trade.pair}-{trade.entry_side}-{trade.open_date}',
ft_pair=trade.pair,
amount=trade.amount,
filled=trade.amount,
remaining=0,
price=trade.open_rate,
average=trade.open_rate,
status="closed",
order_type="limit",
side=trade.entry_side,
))
trade.orders.append(Order(
ft_order_side=trade.exit_side,
order_id=f'{trade.pair}-{trade.exit_side}-{trade.open_date}',
ft_pair=trade.pair,
amount=trade.amount,
filled=trade.amount,
remaining=0,
price=2.2,
average=2.2,
status="closed",
order_type="limit",
side=trade.exit_side,
))
assert trade.close_profit is None assert trade.close_profit is None
assert trade.close_date is None assert trade.close_date is None
assert trade.is_open is True assert trade.is_open is True
trade.close(2.2) trade.close(2.2)
assert trade.is_open is False assert trade.is_open is False
assert trade.close_profit == round(0.0945137157107232, 8) assert pytest.approx(trade.close_profit) == 0.094513715
assert trade.close_date is not None assert trade.close_date is not None
new_date = arrow.Arrow(2020, 2, 2, 15, 6, 1).datetime, new_date = arrow.Arrow(2020, 2, 2, 15, 6, 1).datetime,

View File

@ -72,7 +72,7 @@ def test_add_indicators(default_conf, testdatadir, caplog):
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
# Generate buy/sell signals and indicators # Generate entry/exit signals and indicators
data = strategy.analyze_ticker(data, {'pair': pair}) data = strategy.analyze_ticker(data, {'pair': pair})
fig = generate_empty_figure() fig = generate_empty_figure()
@ -113,7 +113,7 @@ def test_add_areas(default_conf, testdatadir, caplog):
ind_plain = {"macd": {"fill_to": "macdhist"}} ind_plain = {"macd": {"fill_to": "macdhist"}}
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
# Generate buy/sell signals and indicators # Generate entry/exit signals and indicators
data = strategy.analyze_ticker(data, {'pair': pair}) data = strategy.analyze_ticker(data, {'pair': pair})
fig = generate_empty_figure() fig = generate_empty_figure()
@ -165,24 +165,24 @@ def test_plot_trades(testdatadir, caplog):
fig = plot_trades(fig, trades) fig = plot_trades(fig, trades)
figure = fig1.layout.figure figure = fig1.layout.figure
# Check buys - color, should be in first graph, ... # Check entry - color, should be in first graph, ...
trade_buy = find_trace_in_fig_data(figure.data, 'Trade buy') trade_entries = find_trace_in_fig_data(figure.data, 'Trade entry')
assert isinstance(trade_buy, go.Scatter) assert isinstance(trade_entries, go.Scatter)
assert trade_buy.yaxis == 'y' assert trade_entries.yaxis == 'y'
assert len(trades) == len(trade_buy.x) assert len(trades) == len(trade_entries.x)
assert trade_buy.marker.color == 'cyan' assert trade_entries.marker.color == 'cyan'
assert trade_buy.marker.symbol == 'circle-open' assert trade_entries.marker.symbol == 'circle-open'
assert trade_buy.text[0] == '3.99%, buy_tag, roi, 15 min' assert trade_entries.text[0] == '3.99%, buy_tag, roi, 15 min'
trade_sell = find_trace_in_fig_data(figure.data, 'Sell - Profit') trade_exit = find_trace_in_fig_data(figure.data, 'Exit - Profit')
assert isinstance(trade_sell, go.Scatter) assert isinstance(trade_exit, go.Scatter)
assert trade_sell.yaxis == 'y' assert trade_exit.yaxis == 'y'
assert len(trades.loc[trades['profit_ratio'] > 0]) == len(trade_sell.x) assert len(trades.loc[trades['profit_ratio'] > 0]) == len(trade_exit.x)
assert trade_sell.marker.color == 'green' assert trade_exit.marker.color == 'green'
assert trade_sell.marker.symbol == 'square-open' assert trade_exit.marker.symbol == 'square-open'
assert trade_sell.text[0] == '3.99%, buy_tag, roi, 15 min' assert trade_exit.text[0] == '3.99%, buy_tag, roi, 15 min'
trade_sell_loss = find_trace_in_fig_data(figure.data, 'Sell - Loss') trade_sell_loss = find_trace_in_fig_data(figure.data, 'Exit - Loss')
assert isinstance(trade_sell_loss, go.Scatter) assert isinstance(trade_sell_loss, go.Scatter)
assert trade_sell_loss.yaxis == 'y' assert trade_sell_loss.yaxis == 'y'
assert len(trades.loc[trades['profit_ratio'] <= 0]) == len(trade_sell_loss.x) assert len(trades.loc[trades['profit_ratio'] <= 0]) == len(trade_sell_loss.x)