Merge branch 'develop' into feat/backtest_detail
This commit is contained in:
commit
b0c4f079c2
@ -42,7 +42,7 @@ docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_I
|
|||||||
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
||||||
|
|
||||||
# Run backtest
|
# Run backtest
|
||||||
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy
|
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "failed running backtest"
|
echo "failed running backtest"
|
||||||
|
@ -53,7 +53,7 @@ docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE
|
|||||||
docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT
|
docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT
|
||||||
|
|
||||||
# Run backtest
|
# Run backtest
|
||||||
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy
|
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "failed running backtest"
|
echo "failed running backtest"
|
||||||
|
@ -174,7 +174,7 @@
|
|||||||
"heartbeat_interval": 60
|
"heartbeat_interval": 60
|
||||||
},
|
},
|
||||||
"disable_dataframe_checks": false,
|
"disable_dataframe_checks": false,
|
||||||
"strategy": "DefaultStrategy",
|
"strategy": "SampleStrategy",
|
||||||
"strategy_path": "user_data/strategies/",
|
"strategy_path": "user_data/strategies/",
|
||||||
"dataformat_ohlcv": "json",
|
"dataformat_ohlcv": "json",
|
||||||
"dataformat_trades": "jsongz"
|
"dataformat_trades": "jsongz"
|
||||||
|
@ -335,7 +335,7 @@ Once the optimized parameters and conditions have been implemented into your str
|
|||||||
|
|
||||||
To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
||||||
|
|
||||||
Should results don't match, please double-check to make sure you transferred all conditions correctly.
|
Should results not match, please double-check to make sure you transferred all conditions correctly.
|
||||||
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
||||||
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ optional arguments:
|
|||||||
this together with `--export trades`, the strategy-
|
this together with `--export trades`, the strategy-
|
||||||
name is injected into the filename (so `backtest-
|
name is injected into the filename (so `backtest-
|
||||||
data.json` becomes `backtest-data-
|
data.json` becomes `backtest-data-
|
||||||
DefaultStrategy.json`
|
SampleStrategy.json`
|
||||||
--export {none,trades}
|
--export {none,trades}
|
||||||
Export backtest results (default: trades).
|
Export backtest results (default: trades).
|
||||||
--export-filename PATH
|
--export-filename PATH
|
||||||
|
@ -7,7 +7,7 @@ This page provides you some basic concepts on how Freqtrade works and operates.
|
|||||||
* **Strategy**: Your trading strategy, telling the bot what to do.
|
* **Strategy**: Your trading strategy, telling the bot what to do.
|
||||||
* **Trade**: Open position.
|
* **Trade**: Open position.
|
||||||
* **Open Order**: Order which is currently placed on the exchange, and is not yet complete.
|
* **Open Order**: Order which is currently placed on the exchange, and is not yet complete.
|
||||||
* **Pair**: Tradable pair, usually in the format of Quote/Base (e.g. XRP/USDT).
|
* **Pair**: Tradable pair, usually in the format of Base/Quote (e.g. XRP/USDT).
|
||||||
* **Timeframe**: Candle length to use (e.g. `"5m"`, `"1h"`, ...).
|
* **Timeframe**: Candle length to use (e.g. `"5m"`, `"1h"`, ...).
|
||||||
* **Indicators**: Technical indicators (SMA, EMA, RSI, ...).
|
* **Indicators**: Technical indicators (SMA, EMA, RSI, ...).
|
||||||
* **Limit order**: Limit orders which execute at the defined limit price or better.
|
* **Limit order**: Limit orders which execute at the defined limit price or better.
|
||||||
@ -36,11 +36,12 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and
|
|||||||
* Calls `check_sell_timeout()` strategy callback for open sell orders.
|
* Calls `check_sell_timeout()` strategy callback for open sell orders.
|
||||||
* Verifies existing positions and eventually places sell orders.
|
* Verifies existing positions and eventually places sell orders.
|
||||||
* Considers stoploss, ROI and sell-signal, `custom_sell()` and `custom_stoploss()`.
|
* Considers stoploss, ROI and sell-signal, `custom_sell()` and `custom_stoploss()`.
|
||||||
* Determine sell-price based on `ask_strategy` configuration setting.
|
* Determine sell-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback.
|
||||||
* Before a sell order is placed, `confirm_trade_exit()` strategy callback is called.
|
* Before a sell order is placed, `confirm_trade_exit()` strategy callback is called.
|
||||||
* Check if trade-slots are still available (if `max_open_trades` is reached).
|
* Check if trade-slots are still available (if `max_open_trades` is reached).
|
||||||
* Verifies buy signal trying to enter new positions.
|
* Verifies buy signal trying to enter new positions.
|
||||||
* Determine buy-price based on `bid_strategy` configuration setting.
|
* Determine buy-price based on `bid_strategy` configuration setting, or by using the `custom_entry_price()` callback.
|
||||||
|
* Determine stake size by calling the `custom_stake_amount()` callback.
|
||||||
* Before a buy order is placed, `confirm_trade_entry()` strategy callback is called.
|
* Before a buy order is placed, `confirm_trade_entry()` strategy callback is called.
|
||||||
|
|
||||||
This loop will be repeated again and again until the bot is stopped.
|
This loop will be repeated again and again until the bot is stopped.
|
||||||
|
@ -105,11 +105,12 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `ask_strategy.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Asks](#sell-price-with-orderbook-enabled)<br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
| `ask_strategy.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Asks](#sell-price-with-orderbook-enabled)<br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
||||||
| `use_sell_signal` | Use sell 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_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
|
| `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_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
|
| `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 (`"buy"`, `"sell"`, `"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 buy and sell 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 buy and sell 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
|
| `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
|
||||||
|
@ -105,7 +105,7 @@ To use subaccounts with FTX, you need to edit the configuration and add the foll
|
|||||||
|
|
||||||
## Kucoin
|
## Kucoin
|
||||||
|
|
||||||
Kucoin requries a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
Kucoin requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"exchange": {
|
"exchange": {
|
||||||
|
@ -48,7 +48,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
|||||||
[--hyperopt-path PATH] [--eps] [--dmmp]
|
[--hyperopt-path PATH] [--eps] [--dmmp]
|
||||||
[--enable-protections]
|
[--enable-protections]
|
||||||
[--dry-run-wallet DRY_RUN_WALLET] [-e INT]
|
[--dry-run-wallet DRY_RUN_WALLET] [-e INT]
|
||||||
[--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,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]
|
||||||
[--hyperopt-loss NAME] [--disable-param-export]
|
[--hyperopt-loss NAME] [--disable-param-export]
|
||||||
@ -92,7 +92,7 @@ optional arguments:
|
|||||||
Starting balance, used for backtesting / hyperopt and
|
Starting balance, used for backtesting / hyperopt and
|
||||||
dry-runs.
|
dry-runs.
|
||||||
-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,default} [{all,buy,sell,roi,stoploss,trailing,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
|
||||||
list.
|
list.
|
||||||
--print-all Print all results, not only the best ones.
|
--print-all Print all results, not only the best ones.
|
||||||
@ -456,7 +456,7 @@ class MyAwesomeStrategy(IStrategy):
|
|||||||
"only_per_pair": False
|
"only_per_pair": False
|
||||||
})
|
})
|
||||||
|
|
||||||
return protection
|
return prot
|
||||||
|
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
# ...
|
# ...
|
||||||
@ -576,7 +576,8 @@ Legal values are:
|
|||||||
* `roi`: just optimize the minimal profit table for your strategy
|
* `roi`: just optimize the minimal profit table for your strategy
|
||||||
* `stoploss`: search for the best stoploss value
|
* `stoploss`: search for the best stoploss value
|
||||||
* `trailing`: search for the best trailing stop values
|
* `trailing`: search for the best trailing stop values
|
||||||
* `default`: `all` except `trailing`
|
* `protection`: search for the best protection parameters (read the [protections section](#optimizing-protections) on how to properly define these)
|
||||||
|
* `default`: `all` except `trailing` and `protection`
|
||||||
* space-separated list of any of the above values for example `--spaces roi stoploss`
|
* space-separated list of any of the above values for example `--spaces roi stoploss`
|
||||||
|
|
||||||
The default Hyperopt Search Space, used when no `--space` command line option is specified, does not include the `trailing` hyperspace. We recommend you to run optimization for the `trailing` hyperspace separately, when the best parameters for other hyperspaces were found, validated and pasted into your custom strategy.
|
The default Hyperopt Search Space, used when no `--space` command line option is specified, does not include the `trailing` hyperspace. We recommend you to run optimization for the `trailing` hyperspace separately, when the best parameters for other hyperspaces were found, validated and pasted into your custom strategy.
|
||||||
@ -826,8 +827,8 @@ After you run Hyperopt for the desired amount of epochs, you can later list all
|
|||||||
|
|
||||||
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
|
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
|
||||||
|
|
||||||
To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
||||||
|
|
||||||
Should results don't match, please double-check to make sure you transferred all conditions correctly.
|
Should results not match, please double-check to make sure you transferred all conditions correctly.
|
||||||
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
||||||
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
mkdocs==1.2.2
|
mkdocs==1.2.2
|
||||||
mkdocs-material==7.2.2
|
mkdocs-material==7.2.5
|
||||||
mdx_truly_sane_lists==1.2
|
mdx_truly_sane_lists==1.2
|
||||||
pymdown-extensions==8.2
|
pymdown-extensions==8.2
|
||||||
|
@ -357,6 +357,55 @@ See [Dataframe access](#dataframe-access) for more information about dataframe u
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Custom order price rules
|
||||||
|
|
||||||
|
By default, freqtrade use the orderbook to automatically set an order price([Relevant documentation](configuration.md#prices-used-for-orders)), you also have the option to create custom order prices based on your strategy.
|
||||||
|
|
||||||
|
You can use this feature by creating a `custom_entry_price()` function in your strategy file to customize entry prices and `custom_exit_price()` for exits.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
If your custom pricing function return None or an invalid value, price will fall back to `proposed_rate`, which is based on the regular pricing configuration.
|
||||||
|
|
||||||
|
### Custom order entry and exit price example
|
||||||
|
|
||||||
|
``` python
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from freqtrade.persistence import Trade
|
||||||
|
|
||||||
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
|
# ... populate_* methods
|
||||||
|
|
||||||
|
def custom_entry_price(self, pair: str, current_time: datetime,
|
||||||
|
proposed_rate, **kwargs) -> float:
|
||||||
|
|
||||||
|
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
|
||||||
|
timeframe=self.timeframe)
|
||||||
|
new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1]
|
||||||
|
|
||||||
|
return new_entryprice
|
||||||
|
|
||||||
|
def custom_exit_price(self, pair: str, trade: Trade,
|
||||||
|
current_time: datetime, proposed_rate: float,
|
||||||
|
current_profit: float, **kwargs) -> float:
|
||||||
|
|
||||||
|
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
|
||||||
|
timeframe=self.timeframe)
|
||||||
|
new_exitprice = dataframe['bollinger_10_upperband'].iat[-1]
|
||||||
|
|
||||||
|
return new_exitprice
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_ratio` parameter.
|
||||||
|
|
||||||
|
!!! Example
|
||||||
|
If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98.
|
||||||
|
|
||||||
|
!!! Warning "No backtesting support"
|
||||||
|
Custom entry-prices are currently not supported during backtesting.
|
||||||
|
|
||||||
## Custom order timeout rules
|
## Custom order timeout rules
|
||||||
|
|
||||||
Simple, time-based order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section.
|
Simple, time-based order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section.
|
||||||
|
@ -228,7 +228,7 @@ graph = generate_candlestick_graph(pair=pair,
|
|||||||
# Show graph inline
|
# Show graph inline
|
||||||
# graph.show()
|
# graph.show()
|
||||||
|
|
||||||
# Render graph in a seperate window
|
# Render graph in a separate window
|
||||||
graph.show(renderer="browser")
|
graph.show(renderer="browser")
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -66,16 +66,22 @@ def ask_user_config() -> Dict[str, Any]:
|
|||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "stake_amount",
|
"name": "stake_amount",
|
||||||
"message": "Please insert your stake amount:",
|
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||||
"default": "0.01",
|
"default": "0.01",
|
||||||
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
|
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
|
||||||
|
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
|
||||||
|
if val == UNLIMITED_STAKE_AMOUNT
|
||||||
|
else val
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "max_open_trades",
|
"name": "max_open_trades",
|
||||||
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
|
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||||
"default": "3",
|
"default": "3",
|
||||||
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val)
|
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val),
|
||||||
|
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
|
||||||
|
if val == UNLIMITED_STAKE_AMOUNT
|
||||||
|
else val
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -166,7 +166,7 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
'Please note that ticker-interval needs to be set either in config '
|
'Please note that ticker-interval needs to be set either in config '
|
||||||
'or via command line. When using this together with `--export trades`, '
|
'or via command line. When using this together with `--export trades`, '
|
||||||
'the strategy-name is injected into the filename '
|
'the strategy-name is injected into the filename '
|
||||||
'(so `backtest-data.json` becomes `backtest-data-DefaultStrategy.json`',
|
'(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`',
|
||||||
nargs='+',
|
nargs='+',
|
||||||
),
|
),
|
||||||
"export": Arg(
|
"export": Arg(
|
||||||
|
@ -74,8 +74,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
|
|||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
if "strategy" in args and args["strategy"]:
|
if "strategy" in args and args["strategy"]:
|
||||||
if args["strategy"] == "DefaultStrategy":
|
|
||||||
raise OperationalException("DefaultStrategy is not allowed as name.")
|
|
||||||
|
|
||||||
new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py')
|
new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py')
|
||||||
|
|
||||||
@ -128,8 +126,6 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None:
|
|||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
if 'hyperopt' in args and args['hyperopt']:
|
if 'hyperopt' in args and args['hyperopt']:
|
||||||
if args['hyperopt'] == 'DefaultHyperopt':
|
|
||||||
raise OperationalException("DefaultHyperopt is not allowed as name.")
|
|
||||||
|
|
||||||
new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py')
|
new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py')
|
||||||
|
|
||||||
|
@ -49,6 +49,8 @@ USERPATH_NOTEBOOKS = 'notebooks'
|
|||||||
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
||||||
ENV_VAR_PREFIX = 'FREQTRADE__'
|
ENV_VAR_PREFIX = 'FREQTRADE__'
|
||||||
|
|
||||||
|
NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired')
|
||||||
|
|
||||||
|
|
||||||
# Define decimals per coin for outputs
|
# Define decimals per coin for outputs
|
||||||
# Only used for outputs.
|
# Only used for outputs.
|
||||||
@ -191,6 +193,9 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
'required': ['price_side']
|
'required': ['price_side']
|
||||||
},
|
},
|
||||||
|
'custom_price_max_distance_ratio': {
|
||||||
|
'type': 'number', 'minimum': 0.0
|
||||||
|
},
|
||||||
'order_types': {
|
'order_types': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
|
@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
|||||||
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
|
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
|
||||||
"trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"]
|
"trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"]
|
||||||
|
|
||||||
# Mid-term format, crated by BacktestResult Named Tuple
|
# Mid-term format, created by BacktestResult Named Tuple
|
||||||
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
|
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
|
||||||
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open',
|
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open',
|
||||||
'fee_close', 'amount', 'profit_abs', 'profit_ratio']
|
'fee_close', 'amount', 'profit_abs', 'profit_ratio']
|
||||||
|
@ -242,7 +242,7 @@ def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to:
|
|||||||
:param config: Config dictionary
|
:param config: Config dictionary
|
||||||
:param convert_from: Source format
|
:param convert_from: Source format
|
||||||
:param convert_to: Target format
|
:param convert_to: Target format
|
||||||
:param erase: Erase souce data (does not apply if source and target format are identical)
|
:param erase: Erase source data (does not apply if source and target format are identical)
|
||||||
"""
|
"""
|
||||||
from freqtrade.data.history.idatahandler import get_datahandler
|
from freqtrade.data.history.idatahandler import get_datahandler
|
||||||
src = get_datahandler(config['datadir'], convert_from)
|
src = get_datahandler(config['datadir'], convert_from)
|
||||||
@ -267,7 +267,7 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to:
|
|||||||
:param config: Config dictionary
|
:param config: Config dictionary
|
||||||
:param convert_from: Source format
|
:param convert_from: Source format
|
||||||
:param convert_to: Target format
|
:param convert_to: Target format
|
||||||
:param erase: Erase souce data (does not apply if source and target format are identical)
|
:param erase: Erase source data (does not apply if source and target format are identical)
|
||||||
"""
|
"""
|
||||||
from freqtrade.data.history.idatahandler import get_datahandler
|
from freqtrade.data.history.idatahandler import get_datahandler
|
||||||
src = get_datahandler(config['datadir'], convert_from)
|
src = get_datahandler(config['datadir'], convert_from)
|
||||||
|
@ -117,10 +117,11 @@ def refresh_data(datadir: Path,
|
|||||||
:param timerange: Limit data to be loaded to this timerange
|
:param timerange: Limit data to be loaded to this timerange
|
||||||
"""
|
"""
|
||||||
data_handler = get_datahandler(datadir, data_format)
|
data_handler = get_datahandler(datadir, data_format)
|
||||||
for pair in pairs:
|
for idx, pair in enumerate(pairs):
|
||||||
_download_pair_history(pair=pair, timeframe=timeframe,
|
process = f'{idx}/{len(pairs)}'
|
||||||
datadir=datadir, timerange=timerange,
|
_download_pair_history(pair=pair, process=process,
|
||||||
exchange=exchange, data_handler=data_handler)
|
timeframe=timeframe, datadir=datadir,
|
||||||
|
timerange=timerange, exchange=exchange, data_handler=data_handler)
|
||||||
|
|
||||||
|
|
||||||
def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optional[TimeRange],
|
def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optional[TimeRange],
|
||||||
@ -153,13 +154,14 @@ def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optiona
|
|||||||
return data, start_ms
|
return data, start_ms
|
||||||
|
|
||||||
|
|
||||||
def _download_pair_history(datadir: Path,
|
def _download_pair_history(pair: str, *,
|
||||||
|
datadir: Path,
|
||||||
exchange: Exchange,
|
exchange: Exchange,
|
||||||
pair: str, *,
|
|
||||||
new_pairs_days: int = 30,
|
|
||||||
timeframe: str = '5m',
|
timeframe: str = '5m',
|
||||||
timerange: Optional[TimeRange] = None,
|
process: str = '',
|
||||||
data_handler: IDataHandler = None) -> bool:
|
new_pairs_days: int = 30,
|
||||||
|
data_handler: IDataHandler = None,
|
||||||
|
timerange: Optional[TimeRange] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Download latest candles from the exchange for the pair and timeframe passed in parameters
|
Download latest candles from the exchange for the pair and timeframe passed in parameters
|
||||||
The data is downloaded starting from the last correct data that
|
The data is downloaded starting from the last correct data that
|
||||||
@ -177,7 +179,7 @@ def _download_pair_history(datadir: Path,
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(
|
logger.info(
|
||||||
f'Download history data for pair: "{pair}", timeframe: {timeframe} '
|
f'Download history data for pair: "{pair}" ({process}), timeframe: {timeframe} '
|
||||||
f'and store in {datadir}.'
|
f'and store in {datadir}.'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -234,7 +236,7 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
|
|||||||
"""
|
"""
|
||||||
pairs_not_available = []
|
pairs_not_available = []
|
||||||
data_handler = get_datahandler(datadir, data_format)
|
data_handler = get_datahandler(datadir, data_format)
|
||||||
for pair in pairs:
|
for idx, pair in enumerate(pairs, start=1):
|
||||||
if pair not in exchange.markets:
|
if pair not in exchange.markets:
|
||||||
pairs_not_available.append(pair)
|
pairs_not_available.append(pair)
|
||||||
logger.info(f"Skipping pair {pair}...")
|
logger.info(f"Skipping pair {pair}...")
|
||||||
@ -247,10 +249,11 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
|
|||||||
f'Deleting existing data for pair {pair}, interval {timeframe}.')
|
f'Deleting existing data for pair {pair}, interval {timeframe}.')
|
||||||
|
|
||||||
logger.info(f'Downloading pair {pair}, interval {timeframe}.')
|
logger.info(f'Downloading pair {pair}, interval {timeframe}.')
|
||||||
_download_pair_history(datadir=datadir, exchange=exchange,
|
process = f'{idx}/{len(pairs)}'
|
||||||
pair=pair, timeframe=str(timeframe),
|
_download_pair_history(pair=pair, process=process,
|
||||||
new_pairs_days=new_pairs_days,
|
datadir=datadir, exchange=exchange,
|
||||||
timerange=timerange, data_handler=data_handler)
|
timerange=timerange, data_handler=data_handler,
|
||||||
|
timeframe=str(timeframe), new_pairs_days=new_pairs_days)
|
||||||
return pairs_not_available
|
return pairs_not_available
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
|
|||||||
timeframe_to_seconds, validate_exchange,
|
timeframe_to_seconds, validate_exchange,
|
||||||
validate_exchanges)
|
validate_exchanges)
|
||||||
from freqtrade.exchange.ftx import Ftx
|
from freqtrade.exchange.ftx import Ftx
|
||||||
|
from freqtrade.exchange.gateio import Gateio
|
||||||
from freqtrade.exchange.hitbtc import Hitbtc
|
from freqtrade.exchange.hitbtc import Hitbtc
|
||||||
from freqtrade.exchange.kraken import Kraken
|
from freqtrade.exchange.kraken import Kraken
|
||||||
from freqtrade.exchange.kucoin import Kucoin
|
from freqtrade.exchange.kucoin import Kucoin
|
||||||
|
@ -19,7 +19,8 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU
|
|||||||
decimal_to_precision)
|
decimal_to_precision)
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes
|
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES,
|
||||||
|
ListPairsWithTimeframes)
|
||||||
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.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
||||||
InvalidOrderException, OperationalException, PricingError,
|
InvalidOrderException, OperationalException, PricingError,
|
||||||
@ -351,9 +352,16 @@ class Exchange:
|
|||||||
def validate_stakecurrency(self, stake_currency: str) -> None:
|
def validate_stakecurrency(self, stake_currency: str) -> None:
|
||||||
"""
|
"""
|
||||||
Checks stake-currency against available currencies on the exchange.
|
Checks stake-currency against available currencies on the exchange.
|
||||||
|
Only runs on startup. If markets have not been loaded, there's been a problem with
|
||||||
|
the connection to the exchange.
|
||||||
:param stake_currency: Stake-currency to validate
|
:param stake_currency: Stake-currency to validate
|
||||||
:raise: OperationalException if stake-currency is not available.
|
:raise: OperationalException if stake-currency is not available.
|
||||||
"""
|
"""
|
||||||
|
if not self._markets:
|
||||||
|
raise OperationalException(
|
||||||
|
'Could not load markets, therefore cannot start. '
|
||||||
|
'Please investigate the above error for more details.'
|
||||||
|
)
|
||||||
quote_currencies = self.get_quote_currencies()
|
quote_currencies = self.get_quote_currencies()
|
||||||
if stake_currency not in quote_currencies:
|
if stake_currency not in quote_currencies:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
@ -804,7 +812,7 @@ class Exchange:
|
|||||||
:param order: Order dict as returned from fetch_order()
|
:param order: Order dict as returned from fetch_order()
|
||||||
:return: True if order has been cancelled without being filled, False otherwise.
|
:return: True if order has been cancelled without being filled, False otherwise.
|
||||||
"""
|
"""
|
||||||
return (order.get('status') in ('closed', 'canceled', 'cancelled')
|
return (order.get('status') in NON_OPEN_EXCHANGE_STATES
|
||||||
and order.get('filled') == 0.0)
|
and order.get('filled') == 0.0)
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
@ -1038,7 +1046,7 @@ class Exchange:
|
|||||||
logger.debug(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price")
|
logger.debug(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price")
|
||||||
ticker = self.fetch_ticker(pair)
|
ticker = self.fetch_ticker(pair)
|
||||||
ticker_rate = ticker[conf_strategy['price_side']]
|
ticker_rate = ticker[conf_strategy['price_side']]
|
||||||
if ticker['last']:
|
if ticker['last'] and ticker_rate:
|
||||||
if side == 'buy' and ticker_rate > ticker['last']:
|
if side == 'buy' and ticker_rate > ticker['last']:
|
||||||
balance = conf_strategy['ask_last_balance']
|
balance = conf_strategy['ask_last_balance']
|
||||||
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
||||||
@ -1253,7 +1261,7 @@ class Exchange:
|
|||||||
logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list))
|
logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list))
|
||||||
|
|
||||||
input_coroutines = []
|
input_coroutines = []
|
||||||
|
cached_pairs = []
|
||||||
# Gather coroutines to run
|
# Gather coroutines to run
|
||||||
for pair, timeframe in set(pair_list):
|
for pair, timeframe in set(pair_list):
|
||||||
if (((pair, timeframe) not in self._klines)
|
if (((pair, timeframe) not in self._klines)
|
||||||
@ -1265,6 +1273,7 @@ class Exchange:
|
|||||||
"Using cached candle (OHLCV) data for pair %s, timeframe %s ...",
|
"Using cached candle (OHLCV) data for pair %s, timeframe %s ...",
|
||||||
pair, timeframe
|
pair, timeframe
|
||||||
)
|
)
|
||||||
|
cached_pairs.append((pair, timeframe))
|
||||||
|
|
||||||
results = asyncio.get_event_loop().run_until_complete(
|
results = asyncio.get_event_loop().run_until_complete(
|
||||||
asyncio.gather(*input_coroutines, return_exceptions=True))
|
asyncio.gather(*input_coroutines, return_exceptions=True))
|
||||||
@ -1287,6 +1296,10 @@ class Exchange:
|
|||||||
results_df[(pair, timeframe)] = ohlcv_df
|
results_df[(pair, timeframe)] = ohlcv_df
|
||||||
if cache:
|
if cache:
|
||||||
self._klines[(pair, timeframe)] = ohlcv_df
|
self._klines[(pair, timeframe)] = ohlcv_df
|
||||||
|
# Return cached klines
|
||||||
|
for pair, timeframe in cached_pairs:
|
||||||
|
results_df[(pair, timeframe)] = self.klines((pair, timeframe), copy=False)
|
||||||
|
|
||||||
return results_df
|
return results_df
|
||||||
|
|
||||||
def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool:
|
def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool:
|
||||||
@ -1497,7 +1510,7 @@ class Exchange:
|
|||||||
:returns List of trade data
|
:returns List of trade data
|
||||||
"""
|
"""
|
||||||
if not self.exchange_has("fetchTrades"):
|
if not self.exchange_has("fetchTrades"):
|
||||||
raise OperationalException("This exchange does not suport downloading Trades.")
|
raise OperationalException("This exchange does not support downloading Trades.")
|
||||||
|
|
||||||
return asyncio.get_event_loop().run_until_complete(
|
return asyncio.get_event_loop().run_until_complete(
|
||||||
self._async_get_trade_history(pair=pair, since=since,
|
self._async_get_trade_history(pair=pair, since=since,
|
||||||
|
23
freqtrade/exchange/gateio.py
Normal file
23
freqtrade/exchange/gateio.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
""" Gate.io exchange subclass """
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from freqtrade.exchange import Exchange
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Gateio(Exchange):
|
||||||
|
"""
|
||||||
|
Gate.io exchange class. Contains adjustments needed for Freqtrade to work
|
||||||
|
with this exchange.
|
||||||
|
|
||||||
|
Please note that this exchange is not included in the list of exchanges
|
||||||
|
officially supported by the Freqtrade development team. So some features
|
||||||
|
may still not work as expected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_ft_has: Dict = {
|
||||||
|
"ohlcv_candle_limit": 1000,
|
||||||
|
}
|
@ -433,11 +433,11 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if ((bid_check_dom.get('enabled', False)) and
|
if ((bid_check_dom.get('enabled', False)) and
|
||||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||||
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
||||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
return self.execute_entry(pair, stake_amount, buy_tag=buy_tag)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
return self.execute_entry(pair, stake_amount, buy_tag=buy_tag)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -465,8 +465,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
|
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
||||||
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
|
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Executes a limit buy for the given pair
|
Executes a limit buy for the given pair
|
||||||
:param pair: pair for which we want to create a LIMIT_BUY
|
:param pair: pair for which we want to create a LIMIT_BUY
|
||||||
@ -479,7 +479,13 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
buy_limit_requested = price
|
buy_limit_requested = price
|
||||||
else:
|
else:
|
||||||
# Calculate price
|
# Calculate price
|
||||||
buy_limit_requested = self.exchange.get_rate(pair, refresh=True, side="buy")
|
proposed_buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
|
||||||
|
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||||
|
default_retval=proposed_buy_rate)(
|
||||||
|
pair=pair, current_time=datetime.now(timezone.utc),
|
||||||
|
proposed_rate=proposed_buy_rate)
|
||||||
|
|
||||||
|
buy_limit_requested = self.get_valid_price(custom_entry_price, proposed_buy_rate)
|
||||||
|
|
||||||
if not buy_limit_requested:
|
if not buy_limit_requested:
|
||||||
raise PricingError('Could not determine buy price.')
|
raise PricingError('Could not determine buy price.')
|
||||||
@ -739,7 +745,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||||
logger.warning('Selling the trade forcefully')
|
logger.warning('Selling the trade forcefully')
|
||||||
self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
||||||
sell_type=SellType.EMERGENCY_SELL))
|
sell_type=SellType.EMERGENCY_SELL))
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
@ -857,7 +863,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
if should_sell.sell_flag:
|
if should_sell.sell_flag:
|
||||||
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
|
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
|
||||||
self.execute_sell(trade, sell_rate, should_sell)
|
self.execute_trade_exit(trade, sell_rate, should_sell)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -939,7 +945,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
was_trade_fully_canceled = False
|
was_trade_fully_canceled = False
|
||||||
|
|
||||||
# Cancelled orders may have the status of 'canceled' or 'closed'
|
# Cancelled orders may have the status of 'canceled' or 'closed'
|
||||||
if order['status'] not in ('cancelled', 'canceled', 'closed'):
|
if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES:
|
||||||
filled_val = order.get('filled', 0.0) or 0.0
|
filled_val = order.get('filled', 0.0) or 0.0
|
||||||
filled_stake = filled_val * trade.open_rate
|
filled_stake = filled_val * trade.open_rate
|
||||||
minstake = self.exchange.get_min_pair_stake_amount(
|
minstake = self.exchange.get_min_pair_stake_amount(
|
||||||
@ -955,7 +961,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# Avoid race condition where the order could not be cancelled coz its already filled.
|
# Avoid race condition where the order could not be cancelled coz its already filled.
|
||||||
# Simply bailing here is the only safe way - as this order will then be
|
# Simply bailing here is the only safe way - as this order will then be
|
||||||
# handled in the next iteration.
|
# handled in the next iteration.
|
||||||
if corder.get('status') not in ('cancelled', 'canceled', 'closed'):
|
if corder.get('status') not in constants.NON_OPEN_EXCHANGE_STATES:
|
||||||
logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.")
|
logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -977,7 +983,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# if trade is partially complete, edit the stake details for the trade
|
# if trade is partially complete, edit the stake details for the trade
|
||||||
# and close the order
|
# and close the order
|
||||||
# cancel_order may not contain the full order dict, so we need to fallback
|
# cancel_order may not contain the full order dict, so we need to fallback
|
||||||
# to the order dict aquired before cancelling.
|
# to the order dict acquired before cancelling.
|
||||||
# we need to fall back to the values from order if corder does not contain these keys.
|
# we need to fall back to the values from order if corder does not contain these keys.
|
||||||
trade.amount = filled_amount
|
trade.amount = filled_amount
|
||||||
trade.stake_amount = trade.amount * trade.open_rate
|
trade.stake_amount = trade.amount * trade.open_rate
|
||||||
@ -1058,9 +1064,9 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
|
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
|
||||||
|
|
||||||
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
|
def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
|
||||||
"""
|
"""
|
||||||
Executes a limit sell for the given trade and limit
|
Executes a trade exit for the given trade and limit
|
||||||
:param trade: Trade instance
|
:param trade: Trade instance
|
||||||
:param limit: limit rate for the sell order
|
:param limit: limit rate for the sell order
|
||||||
:param sell_reason: Reason the sell was triggered
|
:param sell_reason: Reason the sell was triggered
|
||||||
@ -1076,6 +1082,17 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
and self.strategy.order_types['stoploss_on_exchange']:
|
and self.strategy.order_types['stoploss_on_exchange']:
|
||||||
limit = trade.stop_loss
|
limit = trade.stop_loss
|
||||||
|
|
||||||
|
# set custom_exit_price if available
|
||||||
|
proposed_limit_rate = limit
|
||||||
|
current_profit = trade.calc_profit_ratio(limit)
|
||||||
|
custom_exit_price = strategy_safe_wrapper(self.strategy.custom_exit_price,
|
||||||
|
default_retval=proposed_limit_rate)(
|
||||||
|
pair=trade.pair, trade=trade,
|
||||||
|
current_time=datetime.now(timezone.utc),
|
||||||
|
proposed_rate=proposed_limit_rate, current_profit=current_profit)
|
||||||
|
|
||||||
|
limit = self.get_valid_price(custom_exit_price, proposed_limit_rate)
|
||||||
|
|
||||||
# First cancelling stoploss on exchange ...
|
# First cancelling stoploss on exchange ...
|
||||||
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
|
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
|
||||||
try:
|
try:
|
||||||
@ -1125,7 +1142,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.close_rate_requested = limit
|
trade.close_rate_requested = limit
|
||||||
trade.sell_reason = sell_reason.sell_reason
|
trade.sell_reason = sell_reason.sell_reason
|
||||||
# In case of market sell orders the order can be closed immediately
|
# In case of market sell orders the order can be closed immediately
|
||||||
if order.get('status', 'unknown') == 'closed':
|
if order.get('status', 'unknown') in ('closed', 'expired'):
|
||||||
self.update_trade_state(trade, trade.open_order_id, order)
|
self.update_trade_state(trade, trade.open_order_id, order)
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
|
|
||||||
@ -1364,7 +1381,9 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if fee_currency:
|
if fee_currency:
|
||||||
# fee_rate should use mean
|
# fee_rate should use mean
|
||||||
fee_rate = sum(fee_rate_array) / float(len(fee_rate_array)) if fee_rate_array else None
|
fee_rate = sum(fee_rate_array) / float(len(fee_rate_array)) if fee_rate_array else None
|
||||||
trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
|
if fee_rate is not None and fee_rate < 0.02:
|
||||||
|
# Only update if fee-rate is < 2%
|
||||||
|
trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
|
||||||
|
|
||||||
if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
|
if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
|
||||||
logger.warning(f"Amount {amount} does not match amount {trade.amount}")
|
logger.warning(f"Amount {amount} does not match amount {trade.amount}")
|
||||||
@ -1375,3 +1394,26 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
amount=amount, fee_abs=fee_abs)
|
amount=amount, fee_abs=fee_abs)
|
||||||
else:
|
else:
|
||||||
return amount
|
return amount
|
||||||
|
|
||||||
|
def get_valid_price(self, custom_price: float, proposed_price: float) -> float:
|
||||||
|
"""
|
||||||
|
Return the valid price.
|
||||||
|
Check if the custom price is of the good type if not return proposed_price
|
||||||
|
:return: valid price for the order
|
||||||
|
"""
|
||||||
|
if custom_price:
|
||||||
|
try:
|
||||||
|
valid_custom_price = float(custom_price)
|
||||||
|
except ValueError:
|
||||||
|
valid_custom_price = proposed_price
|
||||||
|
else:
|
||||||
|
valid_custom_price = proposed_price
|
||||||
|
|
||||||
|
cust_p_max_dist_r = self.config.get('custom_price_max_distance_ratio', 0.02)
|
||||||
|
min_custom_price_allowed = proposed_price - (proposed_price * cust_p_max_dist_r)
|
||||||
|
max_custom_price_allowed = proposed_price + (proposed_price * cust_p_max_dist_r)
|
||||||
|
|
||||||
|
# Bracket between min_custom_price_allowed and max_custom_price_allowed
|
||||||
|
return max(
|
||||||
|
min(valid_custom_price, max_custom_price_allowed),
|
||||||
|
min_custom_price_allowed)
|
||||||
|
@ -107,13 +107,25 @@ class Hyperopt:
|
|||||||
# Populate "fallback" functions here
|
# Populate "fallback" functions here
|
||||||
# (hasattr is slow so should not be run during "regular" operations)
|
# (hasattr is slow so should not be run during "regular" operations)
|
||||||
if hasattr(self.custom_hyperopt, 'populate_indicators'):
|
if hasattr(self.custom_hyperopt, 'populate_indicators'):
|
||||||
self.backtesting.strategy.advise_indicators = ( # type: ignore
|
logger.warning(
|
||||||
|
"DEPRECATED: Using `populate_indicators()` in the hyperopt file is deprecated. "
|
||||||
|
"Please move these methods to your strategy."
|
||||||
|
)
|
||||||
|
self.backtesting.strategy.populate_indicators = ( # type: ignore
|
||||||
self.custom_hyperopt.populate_indicators) # type: ignore
|
self.custom_hyperopt.populate_indicators) # type: ignore
|
||||||
if hasattr(self.custom_hyperopt, 'populate_buy_trend'):
|
if hasattr(self.custom_hyperopt, 'populate_buy_trend'):
|
||||||
self.backtesting.strategy.advise_buy = ( # type: ignore
|
logger.warning(
|
||||||
|
"DEPRECATED: Using `populate_buy_trend()` in the hyperopt file is deprecated. "
|
||||||
|
"Please move these methods to your strategy."
|
||||||
|
)
|
||||||
|
self.backtesting.strategy.populate_buy_trend = ( # type: ignore
|
||||||
self.custom_hyperopt.populate_buy_trend) # type: ignore
|
self.custom_hyperopt.populate_buy_trend) # type: ignore
|
||||||
if hasattr(self.custom_hyperopt, 'populate_sell_trend'):
|
if hasattr(self.custom_hyperopt, 'populate_sell_trend'):
|
||||||
self.backtesting.strategy.advise_sell = ( # type: ignore
|
logger.warning(
|
||||||
|
"DEPRECATED: Using `populate_sell_trend()` in the hyperopt file is deprecated. "
|
||||||
|
"Please move these methods to your strategy."
|
||||||
|
)
|
||||||
|
self.backtesting.strategy.populate_sell_trend = ( # type: ignore
|
||||||
self.custom_hyperopt.populate_sell_trend) # type: ignore
|
self.custom_hyperopt.populate_sell_trend) # type: ignore
|
||||||
|
|
||||||
# Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set
|
# Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set
|
||||||
|
@ -74,7 +74,7 @@ class HyperOptAuto(IHyperOpt):
|
|||||||
return self._get_indicator_space('sell', 'sell_indicator_space')
|
return self._get_indicator_space('sell', 'sell_indicator_space')
|
||||||
|
|
||||||
def protection_space(self) -> List['Dimension']:
|
def protection_space(self) -> List['Dimension']:
|
||||||
return self._get_indicator_space('protection', 'indicator_space')
|
return self._get_indicator_space('protection', 'protection_space')
|
||||||
|
|
||||||
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
|
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
|
||||||
return self._get_func('generate_roi_table')(params)
|
return self._get_func('generate_roi_table')(params)
|
||||||
|
@ -13,7 +13,7 @@ from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session
|
|||||||
from sqlalchemy.pool import StaticPool
|
from sqlalchemy.pool import StaticPool
|
||||||
from sqlalchemy.sql.schema import UniqueConstraint
|
from sqlalchemy.sql.schema import UniqueConstraint
|
||||||
|
|
||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
|
||||||
from freqtrade.enums import SellType
|
from freqtrade.enums import SellType
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.misc import safe_value_fallback
|
from freqtrade.misc import safe_value_fallback
|
||||||
@ -159,7 +159,7 @@ class Order(_DECL_BASE):
|
|||||||
self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc)
|
self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc)
|
||||||
|
|
||||||
self.ft_is_open = True
|
self.ft_is_open = True
|
||||||
if self.status in ('closed', 'canceled', 'cancelled'):
|
if self.status in NON_OPEN_EXCHANGE_STATES:
|
||||||
self.ft_is_open = False
|
self.ft_is_open = False
|
||||||
if (order.get('filled', 0.0) or 0.0) > 0:
|
if (order.get('filled', 0.0) or 0.0) > 0:
|
||||||
self.order_filled_date = datetime.now(timezone.utc)
|
self.order_filled_date = datetime.now(timezone.utc)
|
||||||
|
@ -538,7 +538,7 @@ def load_and_plot_trades(config: Dict[str, Any]):
|
|||||||
- Initializes plot-script
|
- Initializes plot-script
|
||||||
- Get candle (OHLCV) data
|
- Get candle (OHLCV) data
|
||||||
- Generate Dafaframes populated with indicators and signals based on configured strategy
|
- Generate Dafaframes populated with indicators and signals based on configured strategy
|
||||||
- Load trades excecuted during the selected period
|
- Load trades executed during the selected period
|
||||||
- Generate Plotly plot objects
|
- Generate Plotly plot objects
|
||||||
- Generate plot files
|
- Generate plot files
|
||||||
:return: None
|
:return: None
|
||||||
|
@ -150,18 +150,20 @@ class IPairList(LoggingMixin, ABC):
|
|||||||
for pair in pairlist:
|
for pair in pairlist:
|
||||||
# pair is not in the generated dynamic market or has the wrong stake currency
|
# pair is not in the generated dynamic market or has the wrong stake currency
|
||||||
if pair not in markets:
|
if pair not in markets:
|
||||||
logger.warning(f"Pair {pair} is not compatible with exchange "
|
self.log_once(f"Pair {pair} is not compatible with exchange "
|
||||||
f"{self._exchange.name}. Removing it from whitelist..")
|
f"{self._exchange.name}. Removing it from whitelist..",
|
||||||
|
logger.warning)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not self._exchange.market_is_tradable(markets[pair]):
|
if not self._exchange.market_is_tradable(markets[pair]):
|
||||||
logger.warning(f"Pair {pair} is not tradable with Freqtrade."
|
self.log_once(f"Pair {pair} is not tradable with Freqtrade."
|
||||||
"Removing it from whitelist..")
|
"Removing it from whitelist..", logger.warning)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']:
|
if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']:
|
||||||
logger.warning(f"Pair {pair} is not compatible with your stake currency "
|
self.log_once(f"Pair {pair} is not compatible with your stake currency "
|
||||||
f"{self._config['stake_currency']}. Removing it from whitelist..")
|
f"{self._config['stake_currency']}. Removing it from whitelist..",
|
||||||
|
logger.warning)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check if market is active
|
# Check if market is active
|
||||||
|
@ -4,6 +4,7 @@ Volume PairList provider
|
|||||||
Provides dynamic pair list based on trade volumes
|
Provides dynamic pair list based on trade volumes
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
from functools import partial
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
@ -115,7 +116,7 @@ class VolumePairList(IPairList):
|
|||||||
pairlist = self._pair_cache.get('pairlist')
|
pairlist = self._pair_cache.get('pairlist')
|
||||||
if pairlist:
|
if pairlist:
|
||||||
# Item found - no refresh necessary
|
# Item found - no refresh necessary
|
||||||
return pairlist
|
return pairlist.copy()
|
||||||
else:
|
else:
|
||||||
# Use fresh pairlist
|
# Use fresh pairlist
|
||||||
# Check if pair quote currency equals to the stake currency.
|
# Check if pair quote currency equals to the stake currency.
|
||||||
@ -126,7 +127,7 @@ class VolumePairList(IPairList):
|
|||||||
pairlist = [s['symbol'] for s in filtered_tickers]
|
pairlist = [s['symbol'] for s in filtered_tickers]
|
||||||
|
|
||||||
pairlist = self.filter_pairlist(pairlist, tickers)
|
pairlist = self.filter_pairlist(pairlist, tickers)
|
||||||
self._pair_cache['pairlist'] = pairlist
|
self._pair_cache['pairlist'] = pairlist.copy()
|
||||||
|
|
||||||
return pairlist
|
return pairlist
|
||||||
|
|
||||||
@ -203,7 +204,7 @@ class VolumePairList(IPairList):
|
|||||||
|
|
||||||
# Validate whitelist to only have active market pairs
|
# Validate whitelist to only have active market pairs
|
||||||
pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers])
|
pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers])
|
||||||
pairs = self.verify_blacklist(pairs, logger.info)
|
pairs = self.verify_blacklist(pairs, partial(self.log_once, logmethod=logger.info))
|
||||||
# Limit pairlist to the requested number of pairs
|
# Limit pairlist to the requested number of pairs
|
||||||
pairs = pairs[:self._number_pairs]
|
pairs = pairs[:self._number_pairs]
|
||||||
|
|
||||||
|
@ -120,5 +120,6 @@ class RangeStabilityFilter(IPairList):
|
|||||||
logger.info)
|
logger.info)
|
||||||
result = False
|
result = False
|
||||||
self._pair_cache[pair] = result
|
self._pair_cache[pair] = result
|
||||||
|
else:
|
||||||
|
self.log_once(f"Removed {pair} from whitelist, no candles found.", logger.info)
|
||||||
return result
|
return result
|
||||||
|
@ -32,8 +32,11 @@ class UvicornServer(uvicorn.Server):
|
|||||||
asyncio_setup()
|
asyncio_setup()
|
||||||
else:
|
else:
|
||||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||||
|
try:
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
# When running in a thread, we'll not have an eventloop yet.
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
loop.run_until_complete(self.serve(sockets=sockets))
|
loop.run_until_complete(self.serve(sockets=sockets))
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
@ -29,6 +29,16 @@ async def ui_version():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def is_relative_to(path, base) -> bool:
|
||||||
|
# Helper function simulating behaviour of is_relative_to, which was only added in python 3.9
|
||||||
|
try:
|
||||||
|
path.relative_to(base)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
@router_ui.get('/{rest_of_path:path}', include_in_schema=False)
|
@router_ui.get('/{rest_of_path:path}', include_in_schema=False)
|
||||||
async def index_html(rest_of_path: str):
|
async def index_html(rest_of_path: str):
|
||||||
"""
|
"""
|
||||||
@ -37,8 +47,11 @@ async def index_html(rest_of_path: str):
|
|||||||
if rest_of_path.startswith('api') or rest_of_path.startswith('.'):
|
if rest_of_path.startswith('api') or rest_of_path.startswith('.'):
|
||||||
raise HTTPException(status_code=404, detail="Not Found")
|
raise HTTPException(status_code=404, detail="Not Found")
|
||||||
uibase = Path(__file__).parent / 'ui/installed/'
|
uibase = Path(__file__).parent / 'ui/installed/'
|
||||||
if (uibase / rest_of_path).is_file():
|
filename = uibase / rest_of_path
|
||||||
return FileResponse(str(uibase / rest_of_path))
|
# It's security relevant to check "relative_to".
|
||||||
|
# Without this, Directory-traversal is possible.
|
||||||
|
if filename.is_file() and is_relative_to(filename, uibase):
|
||||||
|
return FileResponse(str(filename))
|
||||||
|
|
||||||
index_file = uibase / 'index.html'
|
index_file = uibase / 'index.html'
|
||||||
if not index_file.is_file():
|
if not index_file.is_file():
|
||||||
|
@ -5,7 +5,7 @@ e.g BTC to USD
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict
|
from typing import Dict, List
|
||||||
|
|
||||||
from cachetools.ttl import TTLCache
|
from cachetools.ttl import TTLCache
|
||||||
from pycoingecko import CoinGeckoAPI
|
from pycoingecko import CoinGeckoAPI
|
||||||
@ -25,8 +25,7 @@ class CryptoToFiatConverter:
|
|||||||
"""
|
"""
|
||||||
__instance = None
|
__instance = None
|
||||||
_coingekko: CoinGeckoAPI = None
|
_coingekko: CoinGeckoAPI = None
|
||||||
|
_coinlistings: List[Dict] = []
|
||||||
_cryptomap: Dict = {}
|
|
||||||
_backoff: float = 0.0
|
_backoff: float = 0.0
|
||||||
|
|
||||||
def __new__(cls):
|
def __new__(cls):
|
||||||
@ -49,9 +48,8 @@ class CryptoToFiatConverter:
|
|||||||
|
|
||||||
def _load_cryptomap(self) -> None:
|
def _load_cryptomap(self) -> None:
|
||||||
try:
|
try:
|
||||||
coinlistings = self._coingekko.get_coins_list()
|
# Use list-comprehension to ensure we get a list.
|
||||||
# Create mapping table from symbol to coingekko_id
|
self._coinlistings = [x for x in self._coingekko.get_coins_list()]
|
||||||
self._cryptomap = {x['symbol']: x['id'] for x in coinlistings}
|
|
||||||
except RequestException as request_exception:
|
except RequestException as request_exception:
|
||||||
if "429" in str(request_exception):
|
if "429" in str(request_exception):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@ -69,6 +67,24 @@ class CryptoToFiatConverter:
|
|||||||
logger.error(
|
logger.error(
|
||||||
f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")
|
f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")
|
||||||
|
|
||||||
|
def _get_gekko_id(self, crypto_symbol):
|
||||||
|
if not self._coinlistings:
|
||||||
|
if self._backoff <= datetime.datetime.now().timestamp():
|
||||||
|
self._load_cryptomap()
|
||||||
|
# Still not loaded.
|
||||||
|
if not self._coinlistings:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
found = [x for x in self._coinlistings if x['symbol'] == crypto_symbol]
|
||||||
|
if len(found) == 1:
|
||||||
|
return found[0]['id']
|
||||||
|
|
||||||
|
if len(found) > 0:
|
||||||
|
# Wrong!
|
||||||
|
logger.warning(f"Found multiple mappings in goingekko for {crypto_symbol}.")
|
||||||
|
return None
|
||||||
|
|
||||||
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:
|
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:
|
||||||
"""
|
"""
|
||||||
Convert an amount of crypto-currency to fiat
|
Convert an amount of crypto-currency to fiat
|
||||||
@ -143,22 +159,14 @@ class CryptoToFiatConverter:
|
|||||||
if crypto_symbol == fiat_symbol:
|
if crypto_symbol == fiat_symbol:
|
||||||
return 1.0
|
return 1.0
|
||||||
|
|
||||||
if self._cryptomap == {}:
|
_gekko_id = self._get_gekko_id(crypto_symbol)
|
||||||
if self._backoff <= datetime.datetime.now().timestamp():
|
|
||||||
self._load_cryptomap()
|
|
||||||
# return 0.0 if we still don't have data to check, no reason to proceed
|
|
||||||
if self._cryptomap == {}:
|
|
||||||
return 0.0
|
|
||||||
else:
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
if crypto_symbol not in self._cryptomap:
|
if not _gekko_id:
|
||||||
# return 0 for unsupported stake currencies (fiat-convert should not break the bot)
|
# return 0 for unsupported stake currencies (fiat-convert should not break the bot)
|
||||||
logger.warning("unsupported crypto-symbol %s - returning 0.0", crypto_symbol)
|
logger.warning("unsupported crypto-symbol %s - returning 0.0", crypto_symbol)
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_gekko_id = self._cryptomap[crypto_symbol]
|
|
||||||
return float(
|
return float(
|
||||||
self._coingekko.get_price(
|
self._coingekko.get_price(
|
||||||
ids=_gekko_id,
|
ids=_gekko_id,
|
||||||
|
@ -557,7 +557,7 @@ class RPC:
|
|||||||
current_rate = self._freqtrade.exchange.get_rate(
|
current_rate = self._freqtrade.exchange.get_rate(
|
||||||
trade.pair, refresh=False, side="sell")
|
trade.pair, refresh=False, side="sell")
|
||||||
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
||||||
self._freqtrade.execute_sell(trade, current_rate, sell_reason)
|
self._freqtrade.execute_trade_exit(trade, current_rate, sell_reason)
|
||||||
# ---- EOF def _exec_forcesell ----
|
# ---- EOF def _exec_forcesell ----
|
||||||
|
|
||||||
if self._freqtrade.state != State.RUNNING:
|
if self._freqtrade.state != State.RUNNING:
|
||||||
@ -613,7 +613,7 @@ class RPC:
|
|||||||
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
|
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
|
||||||
|
|
||||||
# execute buy
|
# execute buy
|
||||||
if self._freqtrade.execute_buy(pair, stakeamount, price, forcebuy=True):
|
if self._freqtrade.execute_entry(pair, stakeamount, price, forcebuy=True):
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
||||||
return trade
|
return trade
|
||||||
@ -776,7 +776,7 @@ class RPC:
|
|||||||
if has_content:
|
if has_content:
|
||||||
|
|
||||||
dataframe.loc[:, '__date_ts'] = dataframe.loc[:, 'date'].view(int64) // 1000 // 1000
|
dataframe.loc[:, '__date_ts'] = dataframe.loc[:, 'date'].view(int64) // 1000 // 1000
|
||||||
# Move open to seperate column when signal for easy plotting
|
# Move open to separate column when signal for easy plotting
|
||||||
if 'buy' in dataframe.columns:
|
if 'buy' in dataframe.columns:
|
||||||
buy_mask = (dataframe['buy'] == 1)
|
buy_mask = (dataframe['buy'] == 1)
|
||||||
buy_signals = int(buy_mask.sum())
|
buy_signals = int(buy_mask.sum())
|
||||||
|
@ -120,6 +120,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
# and wallets - access to the current balance.
|
# and wallets - access to the current balance.
|
||||||
dp: Optional[DataProvider] = None
|
dp: Optional[DataProvider] = None
|
||||||
wallets: Optional[Wallets] = None
|
wallets: Optional[Wallets] = None
|
||||||
|
# Filled from configuration
|
||||||
|
stake_currency: str
|
||||||
# container variable for strategy source code
|
# container variable for strategy source code
|
||||||
__source__: str = ''
|
__source__: str = ''
|
||||||
|
|
||||||
@ -280,6 +282,43 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
"""
|
"""
|
||||||
return self.stoploss
|
return self.stoploss
|
||||||
|
|
||||||
|
def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float,
|
||||||
|
**kwargs) -> float:
|
||||||
|
"""
|
||||||
|
Custom entry price logic, returning the new entry price.
|
||||||
|
|
||||||
|
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
|
||||||
|
|
||||||
|
When not implemented by a strategy, returns None, orderbook is used to set entry price
|
||||||
|
|
||||||
|
:param pair: Pair that's currently analyzed
|
||||||
|
:param current_time: datetime object, containing the current datetime
|
||||||
|
:param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||||
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
|
:return float: New entry price value if provided
|
||||||
|
"""
|
||||||
|
return proposed_rate
|
||||||
|
|
||||||
|
def custom_exit_price(self, pair: str, trade: Trade,
|
||||||
|
current_time: datetime, proposed_rate: float,
|
||||||
|
current_profit: float, **kwargs) -> float:
|
||||||
|
"""
|
||||||
|
Custom exit price logic, returning the new exit price.
|
||||||
|
|
||||||
|
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
|
||||||
|
|
||||||
|
When not implemented by a strategy, returns None, orderbook is used to set exit price
|
||||||
|
|
||||||
|
:param pair: Pair that's currently analyzed
|
||||||
|
:param trade: trade object.
|
||||||
|
:param current_time: datetime object, containing the current datetime
|
||||||
|
:param proposed_rate: Rate, calculated based on pricing settings in ask_strategy.
|
||||||
|
:param current_profit: Current profit (as ratio), calculated based on current_rate.
|
||||||
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
|
:return float: New exit price value if provided
|
||||||
|
"""
|
||||||
|
return proposed_rate
|
||||||
|
|
||||||
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
||||||
current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
|
current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
|
||||||
"""
|
"""
|
||||||
|
@ -36,6 +36,6 @@
|
|||||||
"BNB/TUSD",
|
"BNB/TUSD",
|
||||||
"BNB/USDC",
|
"BNB/USDC",
|
||||||
"BNB/USDS",
|
"BNB/USDS",
|
||||||
"BNB/USDT",
|
"BNB/USDT"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
coveralls==3.2.0
|
coveralls==3.2.0
|
||||||
flake8==3.9.2
|
flake8==3.9.2
|
||||||
flake8-type-annotations==0.1.0
|
flake8-type-annotations==0.1.0
|
||||||
flake8-tidy-imports==4.3.0
|
flake8-tidy-imports==4.4.1
|
||||||
mypy==0.910
|
mypy==0.910
|
||||||
pytest==6.2.4
|
pytest==6.2.4
|
||||||
pytest-asyncio==0.15.1
|
pytest-asyncio==0.15.1
|
||||||
@ -19,7 +19,7 @@ isort==5.9.3
|
|||||||
nbconvert==6.1.0
|
nbconvert==6.1.0
|
||||||
|
|
||||||
# mypy types
|
# mypy types
|
||||||
types-cachetools==0.1.10
|
types-cachetools==4.2.0
|
||||||
types-filelock==0.1.5
|
types-filelock==0.1.5
|
||||||
types-requests==2.25.6
|
types-requests==2.25.6
|
||||||
types-tabulate==0.8.2
|
types-tabulate==0.8.2
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Include all requirements to run the bot.
|
# Include all requirements to run the bot.
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
plotly==5.1.0
|
plotly==5.3.0
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
numpy==1.21.1
|
numpy==1.21.2
|
||||||
pandas==1.3.1
|
pandas==1.3.2
|
||||||
|
|
||||||
ccxt==1.54.74
|
ccxt==1.55.56
|
||||||
# Pin cryptography for now due to rust build errors with piwheels
|
# Pin cryptography for now due to rust build errors with piwheels
|
||||||
cryptography==3.4.7
|
cryptography==3.4.8
|
||||||
aiohttp==3.7.4.post0
|
aiohttp==3.7.4.post0
|
||||||
SQLAlchemy==1.4.22
|
SQLAlchemy==1.4.23
|
||||||
python-telegram-bot==13.7
|
python-telegram-bot==13.7
|
||||||
arrow==1.1.1
|
arrow==1.1.1
|
||||||
cachetools==4.2.2
|
cachetools==4.2.2
|
||||||
@ -31,8 +31,8 @@ python-rapidjson==1.4
|
|||||||
sdnotify==0.3.2
|
sdnotify==0.3.2
|
||||||
|
|
||||||
# API Server
|
# API Server
|
||||||
fastapi==0.68.0
|
fastapi==0.68.1
|
||||||
uvicorn==0.14.0
|
uvicorn==0.15.0
|
||||||
pyjwt==2.1.0
|
pyjwt==2.1.0
|
||||||
aiofiles==0.7.0
|
aiofiles==0.7.0
|
||||||
|
|
||||||
@ -40,4 +40,4 @@ aiofiles==0.7.0
|
|||||||
colorama==0.4.4
|
colorama==0.4.4
|
||||||
# Building config files interactively
|
# Building config files interactively
|
||||||
questionary==1.10.0
|
questionary==1.10.0
|
||||||
prompt-toolkit==3.0.19
|
prompt-toolkit==3.0.20
|
||||||
|
2
setup.sh
2
setup.sh
@ -163,7 +163,7 @@ function update() {
|
|||||||
# Reset Develop or Stable branch
|
# Reset Develop or Stable branch
|
||||||
function reset() {
|
function reset() {
|
||||||
echo "----------------------------"
|
echo "----------------------------"
|
||||||
echo "Reseting branch and virtual env"
|
echo "Resetting branch and virtual env"
|
||||||
echo "----------------------------"
|
echo "----------------------------"
|
||||||
|
|
||||||
if [ "1" == $(git branch -vv |grep -cE "\* develop|\* stable") ]
|
if [ "1" == $(git branch -vv |grep -cE "\* develop|\* stable") ]
|
||||||
|
@ -510,17 +510,6 @@ def test_start_new_strategy(mocker, caplog):
|
|||||||
start_new_strategy(get_args(args))
|
start_new_strategy(get_args(args))
|
||||||
|
|
||||||
|
|
||||||
def test_start_new_strategy_DefaultStrat(mocker, caplog):
|
|
||||||
args = [
|
|
||||||
"new-strategy",
|
|
||||||
"--strategy",
|
|
||||||
"DefaultStrategy"
|
|
||||||
]
|
|
||||||
with pytest.raises(OperationalException,
|
|
||||||
match=r"DefaultStrategy is not allowed as name\."):
|
|
||||||
start_new_strategy(get_args(args))
|
|
||||||
|
|
||||||
|
|
||||||
def test_start_new_strategy_no_arg(mocker, caplog):
|
def test_start_new_strategy_no_arg(mocker, caplog):
|
||||||
args = [
|
args = [
|
||||||
"new-strategy",
|
"new-strategy",
|
||||||
@ -552,17 +541,6 @@ def test_start_new_hyperopt(mocker, caplog):
|
|||||||
start_new_hyperopt(get_args(args))
|
start_new_hyperopt(get_args(args))
|
||||||
|
|
||||||
|
|
||||||
def test_start_new_hyperopt_DefaultHyperopt(mocker, caplog):
|
|
||||||
args = [
|
|
||||||
"new-hyperopt",
|
|
||||||
"--hyperopt",
|
|
||||||
"DefaultHyperopt"
|
|
||||||
]
|
|
||||||
with pytest.raises(OperationalException,
|
|
||||||
match=r"DefaultHyperopt is not allowed as name\."):
|
|
||||||
start_new_hyperopt(get_args(args))
|
|
||||||
|
|
||||||
|
|
||||||
def test_start_new_hyperopt_no_arg(mocker):
|
def test_start_new_hyperopt_no_arg(mocker):
|
||||||
args = [
|
args = [
|
||||||
"new-hyperopt",
|
"new-hyperopt",
|
||||||
@ -827,9 +805,9 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
|||||||
# pargs['config'] = None
|
# pargs['config'] = None
|
||||||
start_list_strategies(pargs)
|
start_list_strategies(pargs)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert "TestStrategyLegacy" in captured.out
|
assert "TestStrategyLegacyV1" in captured.out
|
||||||
assert "legacy_strategy.py" not in captured.out
|
assert "legacy_strategy_v1.py" not in captured.out
|
||||||
assert "DefaultStrategy" in captured.out
|
assert "StrategyTestV2" in captured.out
|
||||||
|
|
||||||
# Test regular output
|
# Test regular output
|
||||||
args = [
|
args = [
|
||||||
@ -842,9 +820,9 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
|||||||
# pargs['config'] = None
|
# pargs['config'] = None
|
||||||
start_list_strategies(pargs)
|
start_list_strategies(pargs)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert "TestStrategyLegacy" in captured.out
|
assert "TestStrategyLegacyV1" in captured.out
|
||||||
assert "legacy_strategy.py" in captured.out
|
assert "legacy_strategy_v1.py" in captured.out
|
||||||
assert "DefaultStrategy" in captured.out
|
assert "StrategyTestV2" in captured.out
|
||||||
|
|
||||||
|
|
||||||
def test_start_list_hyperopts(mocker, caplog, capsys):
|
def test_start_list_hyperopts(mocker, caplog, capsys):
|
||||||
@ -861,7 +839,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys):
|
|||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert "TestHyperoptLegacy" not in captured.out
|
assert "TestHyperoptLegacy" not in captured.out
|
||||||
assert "legacy_hyperopt.py" not in captured.out
|
assert "legacy_hyperopt.py" not in captured.out
|
||||||
assert "DefaultHyperOpt" in captured.out
|
assert "HyperoptTestSepFile" in captured.out
|
||||||
assert "test_hyperopt.py" not in captured.out
|
assert "test_hyperopt.py" not in captured.out
|
||||||
|
|
||||||
# Test regular output
|
# Test regular output
|
||||||
@ -876,7 +854,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys):
|
|||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert "TestHyperoptLegacy" not in captured.out
|
assert "TestHyperoptLegacy" not in captured.out
|
||||||
assert "legacy_hyperopt.py" not in captured.out
|
assert "legacy_hyperopt.py" not in captured.out
|
||||||
assert "DefaultHyperOpt" in captured.out
|
assert "HyperoptTestSepFile" in captured.out
|
||||||
|
|
||||||
|
|
||||||
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||||
|
@ -323,7 +323,7 @@ def get_default_conf(testdatadir):
|
|||||||
"user_data_dir": Path("user_data"),
|
"user_data_dir": Path("user_data"),
|
||||||
"verbosity": 3,
|
"verbosity": 3,
|
||||||
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
|
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
|
||||||
"strategy": "DefaultStrategy",
|
"strategy": "StrategyTestV2",
|
||||||
"disableparamexport": True,
|
"disableparamexport": True,
|
||||||
"internals": {},
|
"internals": {},
|
||||||
"export": "none",
|
"export": "none",
|
||||||
|
@ -33,7 +33,7 @@ def mock_trade_1(fee):
|
|||||||
open_rate=0.123,
|
open_rate=0.123,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
open_order_id='dry_run_buy_12345',
|
open_order_id='dry_run_buy_12345',
|
||||||
strategy='DefaultStrategy',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
)
|
)
|
||||||
o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy')
|
o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy')
|
||||||
@ -87,7 +87,7 @@ def mock_trade_2(fee):
|
|||||||
exchange='binance',
|
exchange='binance',
|
||||||
is_open=False,
|
is_open=False,
|
||||||
open_order_id='dry_run_sell_12345',
|
open_order_id='dry_run_sell_12345',
|
||||||
strategy='DefaultStrategy',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
sell_reason='sell_signal',
|
sell_reason='sell_signal',
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||||
@ -146,7 +146,7 @@ def mock_trade_3(fee):
|
|||||||
close_profit_abs=0.000155,
|
close_profit_abs=0.000155,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
is_open=False,
|
is_open=False,
|
||||||
strategy='DefaultStrategy',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
sell_reason='roi',
|
sell_reason='roi',
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||||
@ -189,7 +189,7 @@ def mock_trade_4(fee):
|
|||||||
open_rate=0.123,
|
open_rate=0.123,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
open_order_id='prod_buy_12345',
|
open_order_id='prod_buy_12345',
|
||||||
strategy='DefaultStrategy',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
)
|
)
|
||||||
o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy')
|
o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy')
|
||||||
|
@ -93,7 +93,7 @@ def test_load_backtest_data_new_format(testdatadir):
|
|||||||
def test_load_backtest_data_multi(testdatadir):
|
def test_load_backtest_data_multi(testdatadir):
|
||||||
|
|
||||||
filename = testdatadir / "backtest-result_multistrat.json"
|
filename = testdatadir / "backtest-result_multistrat.json"
|
||||||
for strategy in ('DefaultStrategy', 'TestStrategy'):
|
for strategy in ('StrategyTestV2', 'TestStrategy'):
|
||||||
bt_data = load_backtest_data(filename, strategy=strategy)
|
bt_data = load_backtest_data(filename, strategy=strategy)
|
||||||
assert isinstance(bt_data, DataFrame)
|
assert isinstance(bt_data, DataFrame)
|
||||||
assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID)
|
assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID)
|
||||||
@ -128,7 +128,7 @@ def test_load_trades_from_db(default_conf, fee, mocker):
|
|||||||
for col in BT_DATA_COLUMNS:
|
for col in BT_DATA_COLUMNS:
|
||||||
if col not in ['index', 'open_at_end']:
|
if col not in ['index', 'open_at_end']:
|
||||||
assert col in trades.columns
|
assert col in trades.columns
|
||||||
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='DefaultStrategy')
|
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='StrategyTestV2')
|
||||||
assert len(trades) == 4
|
assert len(trades) == 4
|
||||||
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy')
|
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy')
|
||||||
assert len(trades) == 0
|
assert len(trades) == 0
|
||||||
@ -186,7 +186,7 @@ def test_load_trades(default_conf, mocker):
|
|||||||
db_url=default_conf.get('db_url'),
|
db_url=default_conf.get('db_url'),
|
||||||
exportfilename=default_conf.get('exportfilename'),
|
exportfilename=default_conf.get('exportfilename'),
|
||||||
no_trades=False,
|
no_trades=False,
|
||||||
strategy="DefaultStrategy",
|
strategy="StrategyTestV2",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert db_mock.call_count == 1
|
assert db_mock.call_count == 1
|
||||||
|
@ -119,7 +119,7 @@ def test_ohlcv_fill_up_missing_data2(caplog):
|
|||||||
# 3rd candle has been filled
|
# 3rd candle has been filled
|
||||||
row = data2.loc[2, :]
|
row = data2.loc[2, :]
|
||||||
assert row['volume'] == 0
|
assert row['volume'] == 0
|
||||||
# close shoult match close of previous candle
|
# close should match close of previous candle
|
||||||
assert row['close'] == data.loc[1, 'close']
|
assert row['close'] == data.loc[1, 'close']
|
||||||
assert row['open'] == row['close']
|
assert row['open'] == row['close']
|
||||||
assert row['high'] == row['close']
|
assert row['high'] == row['close']
|
||||||
|
@ -66,7 +66,7 @@ def test_historic_ohlcv_dataformat(mocker, default_conf, ohlcv_history):
|
|||||||
hdf5loadmock.assert_not_called()
|
hdf5loadmock.assert_not_called()
|
||||||
jsonloadmock.assert_called_once()
|
jsonloadmock.assert_called_once()
|
||||||
|
|
||||||
# Swiching to dataformat hdf5
|
# Switching to dataformat hdf5
|
||||||
hdf5loadmock.reset_mock()
|
hdf5loadmock.reset_mock()
|
||||||
jsonloadmock.reset_mock()
|
jsonloadmock.reset_mock()
|
||||||
default_conf["dataformat_ohlcv"] = "hdf5"
|
default_conf["dataformat_ohlcv"] = "hdf5"
|
||||||
|
@ -133,8 +133,8 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog,
|
|||||||
load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC')
|
load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC')
|
||||||
assert file.is_file()
|
assert file.is_file()
|
||||||
assert log_has_re(
|
assert log_has_re(
|
||||||
'Download history data for pair: "MEME/BTC", timeframe: 1m '
|
r'Download history data for pair: "MEME/BTC" \(0/1\), timeframe: 1m '
|
||||||
'and store in .*', caplog
|
r'and store in .*', caplog
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -200,15 +200,15 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None:
|
|||||||
assert start_ts == test_data[0][0] - 1000
|
assert start_ts == test_data[0][0] - 1000
|
||||||
|
|
||||||
# timeframe starts in the center of the cached data
|
# timeframe starts in the center of the cached data
|
||||||
# should return the chached data w/o the last item
|
# should return the cached data w/o the last item
|
||||||
timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
|
timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
|
||||||
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
|
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
|
||||||
|
|
||||||
assert_frame_equal(data, test_data_df.iloc[:-1])
|
assert_frame_equal(data, test_data_df.iloc[:-1])
|
||||||
assert test_data[-2][0] <= start_ts < test_data[-1][0]
|
assert test_data[-2][0] <= start_ts < test_data[-1][0]
|
||||||
|
|
||||||
# timeframe starts after the chached data
|
# timeframe starts after the cached data
|
||||||
# should return the chached data w/o the last item
|
# should return the cached data w/o the last item
|
||||||
timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 100, 0)
|
timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 100, 0)
|
||||||
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
|
data, start_ts = _load_cached_data_for_updating('UNITTEST/BTC', '1m', timerange, data_handler)
|
||||||
assert_frame_equal(data, test_data_df.iloc[:-1])
|
assert_frame_equal(data, test_data_df.iloc[:-1])
|
||||||
@ -278,8 +278,10 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None:
|
|||||||
return_value=None)
|
return_value=None)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick)
|
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick)
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
_download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", timeframe='1m')
|
_download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC",
|
||||||
_download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", timeframe='3m')
|
timeframe='1m')
|
||||||
|
_download_pair_history(datadir=testdatadir, exchange=exchange, pair="UNITTEST/BTC",
|
||||||
|
timeframe='3m')
|
||||||
assert json_dump_mock.call_count == 2
|
assert json_dump_mock.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
@ -378,7 +380,7 @@ def test_file_dump_json_tofile(testdatadir) -> None:
|
|||||||
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
data = strategy.advise_all_indicators(
|
data = strategy.advise_all_indicators(
|
||||||
@ -396,7 +398,7 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
|||||||
def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None:
|
def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
data = strategy.advise_all_indicators(
|
data = strategy.advise_all_indicators(
|
||||||
@ -420,7 +422,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir)
|
|||||||
def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None:
|
def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
timerange = TimeRange('index', 'index', 200, 250)
|
timerange = TimeRange('index', 'index', 200, 250)
|
||||||
|
@ -42,6 +42,11 @@ EXCHANGES = {
|
|||||||
'hasQuoteVolume': True,
|
'hasQuoteVolume': True,
|
||||||
'timeframe': '5m',
|
'timeframe': '5m',
|
||||||
},
|
},
|
||||||
|
'gateio': {
|
||||||
|
'pair': 'BTC/USDT',
|
||||||
|
'hasQuoteVolume': True,
|
||||||
|
'timeframe': '5m',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -142,8 +147,8 @@ class TestCCXTExchange():
|
|||||||
def test_ccxt_get_fee(self, exchange):
|
def test_ccxt_get_fee(self, exchange):
|
||||||
exchange, exchangename = exchange
|
exchange, exchangename = exchange
|
||||||
pair = EXCHANGES[exchangename]['pair']
|
pair = EXCHANGES[exchangename]['pair']
|
||||||
|
threshold = 0.01
|
||||||
assert 0 < exchange.get_fee(pair, 'limit', 'buy') < 1
|
assert 0 < exchange.get_fee(pair, 'limit', 'buy') < threshold
|
||||||
assert 0 < exchange.get_fee(pair, 'limit', 'sell') < 1
|
assert 0 < exchange.get_fee(pair, 'limit', 'sell') < threshold
|
||||||
assert 0 < exchange.get_fee(pair, 'market', 'buy') < 1
|
assert 0 < exchange.get_fee(pair, 'market', 'buy') < threshold
|
||||||
assert 0 < exchange.get_fee(pair, 'market', 'sell') < 1
|
assert 0 < exchange.get_fee(pair, 'market', 'sell') < threshold
|
||||||
|
@ -557,7 +557,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT'])
|
@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT'])
|
||||||
def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog):
|
def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
|
||||||
default_conf['stake_currency'] = stake_currency
|
default_conf['stake_currency'] = stake_currency
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
type(api_mock).load_markets = MagicMock(return_value={
|
type(api_mock).load_markets = MagicMock(return_value={
|
||||||
@ -571,7 +571,7 @@ def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog):
|
|||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_stake_currency_error(default_conf, mocker, caplog):
|
def test_validate_stakecurrency_error(default_conf, mocker, caplog):
|
||||||
default_conf['stake_currency'] = 'XRP'
|
default_conf['stake_currency'] = 'XRP'
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
type(api_mock).load_markets = MagicMock(return_value={
|
type(api_mock).load_markets = MagicMock(return_value={
|
||||||
@ -587,6 +587,13 @@ def test_validate_stake_currency_error(default_conf, mocker, caplog):
|
|||||||
'Available currencies are: BTC, ETH, USDT'):
|
'Available currencies are: BTC, ETH, USDT'):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
type(api_mock).load_markets = MagicMock(side_effect=ccxt.NetworkError('No connection.'))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r'Could not load markets, therefore cannot start\. Please.*'):
|
||||||
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_get_quote_currencies(default_conf, mocker):
|
def test_get_quote_currencies(default_conf, mocker):
|
||||||
ex = get_patched_exchange(mocker, default_conf)
|
ex = get_patched_exchange(mocker, default_conf)
|
||||||
@ -1564,13 +1571,16 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
|
|||||||
pairs = [('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]
|
pairs = [('IOTA/ETH', '5m'), ('XRP/ETH', '5m')]
|
||||||
# empty dicts
|
# empty dicts
|
||||||
assert not exchange._klines
|
assert not exchange._klines
|
||||||
exchange.refresh_latest_ohlcv(pairs, cache=False)
|
res = exchange.refresh_latest_ohlcv(pairs, cache=False)
|
||||||
# No caching
|
# No caching
|
||||||
assert not exchange._klines
|
assert not exchange._klines
|
||||||
|
|
||||||
|
assert len(res) == len(pairs)
|
||||||
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
||||||
exchange._api_async.fetch_ohlcv.reset_mock()
|
exchange._api_async.fetch_ohlcv.reset_mock()
|
||||||
|
|
||||||
exchange.refresh_latest_ohlcv(pairs)
|
res = exchange.refresh_latest_ohlcv(pairs)
|
||||||
|
assert len(res) == len(pairs)
|
||||||
|
|
||||||
assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog)
|
assert log_has(f'Refreshing candle (OHLCV) data for {len(pairs)} pairs', caplog)
|
||||||
assert exchange._klines
|
assert exchange._klines
|
||||||
@ -1587,12 +1597,16 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
|
|||||||
assert exchange.klines(pair, copy=False) is exchange.klines(pair, copy=False)
|
assert exchange.klines(pair, copy=False) is exchange.klines(pair, copy=False)
|
||||||
|
|
||||||
# test caching
|
# test caching
|
||||||
exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
|
res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
|
||||||
|
assert len(res) == len(pairs)
|
||||||
|
|
||||||
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
||||||
assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, "
|
assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, "
|
||||||
f"timeframe {pairs[0][1]} ...",
|
f"timeframe {pairs[0][1]} ...",
|
||||||
caplog)
|
caplog)
|
||||||
|
res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m'), ('XRP/ETH', '1d')],
|
||||||
|
cache=False)
|
||||||
|
assert len(res) == 3
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@ -1844,6 +1858,31 @@ def test_get_sell_rate(default_conf, mocker, caplog, side, bid, ask,
|
|||||||
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
assert log_has("Using cached sell rate for ETH/BTC.", caplog)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("entry,side,ask,bid,last,last_ab,expected", [
|
||||||
|
('buy', 'ask', None, 4, 4, 0, 4), # ask not available
|
||||||
|
('buy', 'ask', None, None, 4, 0, 4), # ask not available
|
||||||
|
('buy', 'bid', 6, None, 4, 0, 5), # bid not available
|
||||||
|
('buy', 'bid', None, None, 4, 0, 5), # No rate available
|
||||||
|
('sell', 'ask', None, 4, 4, 0, 4), # ask not available
|
||||||
|
('sell', 'ask', None, None, 4, 0, 4), # ask not available
|
||||||
|
('sell', 'bid', 6, None, 4, 0, 5), # bid not available
|
||||||
|
('sell', 'bid', None, None, 4, 0, 5), # bid not available
|
||||||
|
])
|
||||||
|
def test_get_ticker_rate_error(mocker, entry, default_conf, caplog, side, ask, bid,
|
||||||
|
last, last_ab, expected) -> None:
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
|
default_conf['bid_strategy']['ask_last_balance'] = last_ab
|
||||||
|
default_conf['bid_strategy']['price_side'] = side
|
||||||
|
default_conf['ask_strategy']['price_side'] = side
|
||||||
|
default_conf['ask_strategy']['ask_last_balance'] = last_ab
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
|
return_value={'ask': ask, 'last': last, 'bid': bid})
|
||||||
|
|
||||||
|
with pytest.raises(PricingError):
|
||||||
|
exchange.get_rate('ETH/BTC', refresh=True, side=entry)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('side,expected', [
|
@pytest.mark.parametrize('side,expected', [
|
||||||
('bid', 0.043936), # Value from order_book_l2 fiture - bids side
|
('bid', 0.043936), # Value from order_book_l2 fiture - bids side
|
||||||
('ask', 0.043949), # Value from order_book_l2 fiture - asks side
|
('ask', 0.043949), # Value from order_book_l2 fiture - asks side
|
||||||
@ -2182,7 +2221,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange
|
|||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
|
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match="This exchange does not suport downloading Trades."):
|
match="This exchange does not support downloading Trades."):
|
||||||
exchange.get_historic_trades(pair, since=trades_history[0][0],
|
exchange.get_historic_trades(pair, since=trades_history[0][0],
|
||||||
until=trades_history[-1][0])
|
until=trades_history[-1][0])
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ def hyperopt_conf(default_conf):
|
|||||||
hyperconf.update({
|
hyperconf.update({
|
||||||
'datadir': Path(default_conf['datadir']),
|
'datadir': Path(default_conf['datadir']),
|
||||||
'runmode': RunMode.HYPEROPT,
|
'runmode': RunMode.HYPEROPT,
|
||||||
'hyperopt': 'DefaultHyperOpt',
|
'hyperopt': 'HyperoptTestSepFile',
|
||||||
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
|
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
|
||||||
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
|
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
|
@ -11,7 +11,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|||||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
||||||
|
|
||||||
|
|
||||||
class DefaultHyperOpt(IHyperOpt):
|
class HyperoptTestSepFile(IHyperOpt):
|
||||||
"""
|
"""
|
||||||
Default hyperopt provided by the Freqtrade bot.
|
Default hyperopt provided by the Freqtrade bot.
|
||||||
You can override it with your own Hyperopt
|
You can override it with your own Hyperopt
|
@ -155,7 +155,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--export', 'none'
|
'--export', 'none'
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -190,7 +190,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'--timeframe', '1m',
|
'--timeframe', '1m',
|
||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
@ -240,7 +240,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--stake-amount', '1',
|
'--stake-amount', '1',
|
||||||
'--starting-balance', '2'
|
'--starting-balance', '2'
|
||||||
]
|
]
|
||||||
@ -251,7 +251,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--stake-amount', '1',
|
'--stake-amount', '1',
|
||||||
'--starting-balance', '0.5'
|
'--starting-balance', '0.5'
|
||||||
]
|
]
|
||||||
@ -269,7 +269,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
]
|
]
|
||||||
pargs = get_args(args)
|
pargs = get_args(args)
|
||||||
start_backtesting(pargs)
|
start_backtesting(pargs)
|
||||||
@ -302,7 +302,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
|
|||||||
def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
|
def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
del default_conf['timeframe']
|
del default_conf['timeframe']
|
||||||
default_conf['strategy_list'] = ['DefaultStrategy',
|
default_conf['strategy_list'] = ['StrategyTestV2',
|
||||||
'SampleStrategy']
|
'SampleStrategy']
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
||||||
@ -340,7 +340,7 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
|
|||||||
assert len(processed['UNITTEST/BTC']) == 102
|
assert len(processed['UNITTEST/BTC']) == 102
|
||||||
|
|
||||||
# Load strategy to compare the result between Backtesting function and strategy are the same
|
# Load strategy to compare the result between Backtesting function and strategy are the same
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
processed2 = strategy.advise_all_indicators(data)
|
processed2 = strategy.advise_all_indicators(data)
|
||||||
@ -482,7 +482,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
|
|||||||
Backtesting(default_conf)
|
Backtesting(default_conf)
|
||||||
|
|
||||||
# Multiple strategies
|
# Multiple strategies
|
||||||
default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacy']
|
default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1']
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
|
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
|
||||||
Backtesting(default_conf)
|
Backtesting(default_conf)
|
||||||
@ -785,7 +785,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
|
|||||||
|
|
||||||
|
|
||||||
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our StrategyTestV2
|
||||||
def fun(dataframe=None, pair=None):
|
def fun(dataframe=None, pair=None):
|
||||||
buy_value = 1
|
buy_value = 1
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
@ -801,7 +801,7 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
|||||||
|
|
||||||
|
|
||||||
def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our StrategyTestV2
|
||||||
def fun(dataframe=None, pair=None):
|
def fun(dataframe=None, pair=None):
|
||||||
buy_value = 0
|
buy_value = 0
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
@ -928,7 +928,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--datadir', str(testdatadir),
|
'--datadir', str(testdatadir),
|
||||||
'--timeframe', '1m',
|
'--timeframe', '1m',
|
||||||
'--timerange', '1510694220-1510700340',
|
'--timerange', '1510694220-1510700340',
|
||||||
@ -999,8 +999,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
|||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
'--disable-max-market-positions',
|
'--disable-max-market-positions',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'StrategyTestV2',
|
||||||
'TestStrategyLegacy',
|
'TestStrategyLegacyV1',
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start_backtesting(args)
|
start_backtesting(args)
|
||||||
@ -1022,8 +1022,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
|||||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||||
'up to 2017-11-14 22:58:00 (0 days).',
|
'up to 2017-11-14 22:58:00 (0 days).',
|
||||||
'Parameter --enable-position-stacking detected ...',
|
'Parameter --enable-position-stacking detected ...',
|
||||||
'Running backtesting for Strategy DefaultStrategy',
|
'Running backtesting for Strategy StrategyTestV2',
|
||||||
'Running backtesting for Strategy TestStrategyLegacy',
|
'Running backtesting for Strategy TestStrategyLegacyV1',
|
||||||
]
|
]
|
||||||
|
|
||||||
for line in exists:
|
for line in exists:
|
||||||
@ -1103,8 +1103,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
'--disable-max-market-positions',
|
'--disable-max-market-positions',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'StrategyTestV2',
|
||||||
'TestStrategyLegacy',
|
'TestStrategyLegacyV1',
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start_backtesting(args)
|
start_backtesting(args)
|
||||||
@ -1120,8 +1120,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||||
'up to 2017-11-14 22:58:00 (0 days).',
|
'up to 2017-11-14 22:58:00 (0 days).',
|
||||||
'Parameter --enable-position-stacking detected ...',
|
'Parameter --enable-position-stacking detected ...',
|
||||||
'Running backtesting for Strategy DefaultStrategy',
|
'Running backtesting for Strategy StrategyTestV2',
|
||||||
'Running backtesting for Strategy TestStrategyLegacy',
|
'Running backtesting for Strategy TestStrategyLegacyV1',
|
||||||
]
|
]
|
||||||
|
|
||||||
for line in exists:
|
for line in exists:
|
||||||
@ -1208,7 +1208,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
|
|||||||
'--timeframe', '5m',
|
'--timeframe', '5m',
|
||||||
'--timeframe-detail', '1m',
|
'--timeframe-detail', '1m',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy'
|
'StrategyTestV2'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start_backtesting(args)
|
start_backtesting(args)
|
||||||
@ -1222,7 +1222,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
|
|||||||
'up to 2019-10-13 11:10:00 (2 days).',
|
'up to 2019-10-13 11:10:00 (2 days).',
|
||||||
'Backtesting with data from 2019-10-11 01:40:00 '
|
'Backtesting with data from 2019-10-11 01:40:00 '
|
||||||
'up to 2019-10-13 11:10:00 (2 days).',
|
'up to 2019-10-13 11:10:00 (2 days).',
|
||||||
'Running backtesting for Strategy DefaultStrategy',
|
'Running backtesting for Strategy StrategyTestV2',
|
||||||
]
|
]
|
||||||
|
|
||||||
for line in exists:
|
for line in exists:
|
||||||
|
@ -16,7 +16,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
|
|||||||
args = [
|
args = [
|
||||||
'edge',
|
'edge',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_optimize_configuration(get_args(args), RunMode.EDGE)
|
config = setup_optimize_configuration(get_args(args), RunMode.EDGE)
|
||||||
@ -46,7 +46,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N
|
|||||||
args = [
|
args = [
|
||||||
'edge',
|
'edge',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'--timeframe', '1m',
|
'--timeframe', '1m',
|
||||||
'--timerange', ':100',
|
'--timerange', ':100',
|
||||||
@ -80,7 +80,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'edge',
|
'edge',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
]
|
]
|
||||||
pargs = get_args(args)
|
pargs = get_args(args)
|
||||||
start_edge(pargs)
|
start_edge(pargs)
|
||||||
|
@ -22,7 +22,7 @@ from freqtrade.strategy.hyper import IntParameter
|
|||||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||||
patched_configuration_load_config_file)
|
patched_configuration_load_config_file)
|
||||||
|
|
||||||
from .hyperopts.default_hyperopt import DefaultHyperOpt
|
from .hyperopts.hyperopt_test_sep_file import HyperoptTestSepFile
|
||||||
|
|
||||||
|
|
||||||
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||||
@ -31,7 +31,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
config = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
||||||
@ -63,7 +63,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'--timeframe', '1m',
|
'--timeframe', '1m',
|
||||||
'--timerange', ':100',
|
'--timerange', ':100',
|
||||||
@ -115,7 +115,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--stake-amount', '1',
|
'--stake-amount', '1',
|
||||||
'--starting-balance', '2'
|
'--starting-balance', '2'
|
||||||
]
|
]
|
||||||
@ -125,7 +125,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--stake-amount', '1',
|
'--stake-amount', '1',
|
||||||
'--starting-balance', '0.5'
|
'--starting-balance', '0.5'
|
||||||
]
|
]
|
||||||
@ -136,7 +136,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
|||||||
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
hyperopt = DefaultHyperOpt
|
hyperopt = HyperoptTestSepFile
|
||||||
delattr(hyperopt, 'populate_indicators')
|
delattr(hyperopt, 'populate_indicators')
|
||||||
delattr(hyperopt, 'populate_buy_trend')
|
delattr(hyperopt, 'populate_buy_trend')
|
||||||
delattr(hyperopt, 'populate_sell_trend')
|
delattr(hyperopt, 'populate_sell_trend')
|
||||||
@ -144,7 +144,7 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
|||||||
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver.load_object',
|
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver.load_object',
|
||||||
MagicMock(return_value=hyperopt(default_conf))
|
MagicMock(return_value=hyperopt(default_conf))
|
||||||
)
|
)
|
||||||
default_conf.update({'hyperopt': 'DefaultHyperOpt'})
|
default_conf.update({'hyperopt': 'HyperoptTestSepFile'})
|
||||||
x = HyperOptResolver.load_hyperopt(default_conf)
|
x = HyperOptResolver.load_hyperopt(default_conf)
|
||||||
assert not hasattr(x, 'populate_indicators')
|
assert not hasattr(x, 'populate_indicators')
|
||||||
assert not hasattr(x, 'populate_buy_trend')
|
assert not hasattr(x, 'populate_buy_trend')
|
||||||
@ -184,7 +184,7 @@ def test_start_not_installed(mocker, default_conf, import_fails) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--hyperopt-path',
|
'--hyperopt-path',
|
||||||
str(Path(__file__).parent / "hyperopts"),
|
str(Path(__file__).parent / "hyperopts"),
|
||||||
'--epochs', '5',
|
'--epochs', '5',
|
||||||
@ -205,7 +205,7 @@ def test_start(mocker, hyperopt_conf, caplog) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
@ -229,7 +229,7 @@ def test_start_no_data(mocker, hyperopt_conf) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
@ -247,7 +247,7 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
|
@ -64,34 +64,53 @@ def test_load_previous_results2(mocker, testdatadir, caplog) -> None:
|
|||||||
|
|
||||||
@pytest.mark.parametrize("spaces, expected_results", [
|
@pytest.mark.parametrize("spaces, expected_results", [
|
||||||
(['buy'],
|
(['buy'],
|
||||||
{'buy': True, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False}),
|
{'buy': True, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False,
|
||||||
|
'protection': False}),
|
||||||
(['sell'],
|
(['sell'],
|
||||||
{'buy': False, 'sell': True, 'roi': False, 'stoploss': False, 'trailing': False}),
|
{'buy': False, 'sell': True, 'roi': False, 'stoploss': False, 'trailing': False,
|
||||||
|
'protection': False}),
|
||||||
(['roi'],
|
(['roi'],
|
||||||
{'buy': False, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False}),
|
{'buy': False, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False,
|
||||||
|
'protection': False}),
|
||||||
(['stoploss'],
|
(['stoploss'],
|
||||||
{'buy': False, 'sell': False, 'roi': False, 'stoploss': True, 'trailing': False}),
|
{'buy': False, 'sell': False, 'roi': False, 'stoploss': True, 'trailing': False,
|
||||||
|
'protection': False}),
|
||||||
(['trailing'],
|
(['trailing'],
|
||||||
{'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': True}),
|
{'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': True,
|
||||||
|
'protection': False}),
|
||||||
(['buy', 'sell', 'roi', 'stoploss'],
|
(['buy', 'sell', 'roi', 'stoploss'],
|
||||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}),
|
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False,
|
||||||
|
'protection': False}),
|
||||||
(['buy', 'sell', 'roi', 'stoploss', 'trailing'],
|
(['buy', 'sell', 'roi', 'stoploss', 'trailing'],
|
||||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}),
|
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
|
||||||
|
'protection': False}),
|
||||||
(['buy', 'roi'],
|
(['buy', 'roi'],
|
||||||
{'buy': True, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False}),
|
{'buy': True, 'sell': False, 'roi': True, 'stoploss': False, 'trailing': False,
|
||||||
|
'protection': False}),
|
||||||
(['all'],
|
(['all'],
|
||||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}),
|
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
|
||||||
|
'protection': True}),
|
||||||
(['default'],
|
(['default'],
|
||||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}),
|
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False,
|
||||||
|
'protection': False}),
|
||||||
(['default', 'trailing'],
|
(['default', 'trailing'],
|
||||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}),
|
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
|
||||||
|
'protection': False}),
|
||||||
(['all', 'buy'],
|
(['all', 'buy'],
|
||||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True}),
|
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
|
||||||
|
'protection': True}),
|
||||||
(['default', 'buy'],
|
(['default', 'buy'],
|
||||||
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}),
|
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False,
|
||||||
|
'protection': False}),
|
||||||
|
(['all'],
|
||||||
|
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': True,
|
||||||
|
'protection': True}),
|
||||||
|
(['protection'],
|
||||||
|
{'buy': False, 'sell': False, 'roi': False, 'stoploss': False, 'trailing': False,
|
||||||
|
'protection': True}),
|
||||||
])
|
])
|
||||||
def test_has_space(hyperopt_conf, spaces, expected_results):
|
def test_has_space(hyperopt_conf, spaces, expected_results):
|
||||||
for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing']:
|
for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing', 'protection']:
|
||||||
hyperopt_conf.update({'spaces': spaces})
|
hyperopt_conf.update({'spaces': spaces})
|
||||||
assert HyperoptTools.has_space(hyperopt_conf, s) == expected_results[s]
|
assert HyperoptTools.has_space(hyperopt_conf, s) == expected_results[s]
|
||||||
|
|
||||||
@ -148,9 +167,9 @@ def test__pprint_dict():
|
|||||||
|
|
||||||
def test_get_strategy_filename(default_conf):
|
def test_get_strategy_filename(default_conf):
|
||||||
|
|
||||||
x = HyperoptTools.get_strategy_filename(default_conf, 'DefaultStrategy')
|
x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV2')
|
||||||
assert isinstance(x, Path)
|
assert isinstance(x, Path)
|
||||||
assert x == Path(__file__).parents[1] / 'strategy/strats/default_strategy.py'
|
assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v2.py'
|
||||||
|
|
||||||
x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy')
|
x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy')
|
||||||
assert x is None
|
assert x is None
|
||||||
@ -158,7 +177,7 @@ def test_get_strategy_filename(default_conf):
|
|||||||
|
|
||||||
def test_export_params(tmpdir):
|
def test_export_params(tmpdir):
|
||||||
|
|
||||||
filename = Path(tmpdir) / "DefaultStrategy.json"
|
filename = Path(tmpdir) / "StrategyTestV2.json"
|
||||||
assert not filename.is_file()
|
assert not filename.is_file()
|
||||||
params = {
|
params = {
|
||||||
"params_details": {
|
"params_details": {
|
||||||
@ -186,12 +205,12 @@ def test_export_params(tmpdir):
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
HyperoptTools.export_params(params, "DefaultStrategy", filename)
|
HyperoptTools.export_params(params, "StrategyTestV2", filename)
|
||||||
|
|
||||||
assert filename.is_file()
|
assert filename.is_file()
|
||||||
|
|
||||||
content = rapidjson.load(filename.open('r'))
|
content = rapidjson.load(filename.open('r'))
|
||||||
assert content['strategy_name'] == 'DefaultStrategy'
|
assert content['strategy_name'] == 'StrategyTestV2'
|
||||||
assert 'params' in content
|
assert 'params' in content
|
||||||
assert "buy" in content["params"]
|
assert "buy" in content["params"]
|
||||||
assert "sell" in content["params"]
|
assert "sell" in content["params"]
|
||||||
@ -204,7 +223,7 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
|
|||||||
default_conf['disableparamexport'] = False
|
default_conf['disableparamexport'] = False
|
||||||
export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params")
|
export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params")
|
||||||
|
|
||||||
filename = Path(tmpdir) / "DefaultStrategy.json"
|
filename = Path(tmpdir) / "StrategyTestV2.json"
|
||||||
assert not filename.is_file()
|
assert not filename.is_file()
|
||||||
params = {
|
params = {
|
||||||
"params_details": {
|
"params_details": {
|
||||||
@ -233,17 +252,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
|
|||||||
FTHYPT_FILEVERSION: 2,
|
FTHYPT_FILEVERSION: 2,
|
||||||
|
|
||||||
}
|
}
|
||||||
HyperoptTools.try_export_params(default_conf, "DefaultStrategy22", params)
|
HyperoptTools.try_export_params(default_conf, "StrategyTestV222", params)
|
||||||
|
|
||||||
assert log_has("Strategy not found, not exporting parameter file.", caplog)
|
assert log_has("Strategy not found, not exporting parameter file.", caplog)
|
||||||
assert export_mock.call_count == 0
|
assert export_mock.call_count == 0
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
HyperoptTools.try_export_params(default_conf, "DefaultStrategy", params)
|
HyperoptTools.try_export_params(default_conf, "StrategyTestV2", params)
|
||||||
|
|
||||||
assert export_mock.call_count == 1
|
assert export_mock.call_count == 1
|
||||||
assert export_mock.call_args_list[0][0][1] == 'DefaultStrategy'
|
assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2'
|
||||||
assert export_mock.call_args_list[0][0][2].name == 'default_strategy.json'
|
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json'
|
||||||
|
|
||||||
|
|
||||||
def test_params_print(capsys):
|
def test_params_print(capsys):
|
||||||
|
@ -4,7 +4,7 @@ from unittest.mock import MagicMock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.optimize.default_hyperopt_loss import ShortTradeDurHyperOptLoss
|
from freqtrade.optimize.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss
|
||||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
|
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ def test_text_table_bt_results():
|
|||||||
|
|
||||||
|
|
||||||
def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
results = {'DefStrat': {
|
results = {'DefStrat': {
|
||||||
|
@ -22,7 +22,7 @@ def test_fiat_convert_is_supported(mocker):
|
|||||||
def test_fiat_convert_find_price(mocker):
|
def test_fiat_convert_find_price(mocker):
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
fiat_convert._cryptomap = {}
|
fiat_convert._coinlistings = {}
|
||||||
fiat_convert._backoff = 0
|
fiat_convert._backoff = 0
|
||||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap',
|
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap',
|
||||||
return_value=None)
|
return_value=None)
|
||||||
@ -44,7 +44,7 @@ def test_fiat_convert_find_price(mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
||||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
|
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._coinlistings', return_value=[])
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
|
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
|
||||||
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog)
|
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog)
|
||||||
@ -88,9 +88,9 @@ def test_fiat_convert_two_FIAT(mocker):
|
|||||||
def test_loadcryptomap(mocker):
|
def test_loadcryptomap(mocker):
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
assert len(fiat_convert._cryptomap) == 2
|
assert len(fiat_convert._coinlistings) == 2
|
||||||
|
|
||||||
assert fiat_convert._cryptomap["btc"] == "bitcoin"
|
assert fiat_convert._get_gekko_id("btc") == "bitcoin"
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_init_network_exception(mocker):
|
def test_fiat_init_network_exception(mocker):
|
||||||
@ -102,11 +102,10 @@ def test_fiat_init_network_exception(mocker):
|
|||||||
)
|
)
|
||||||
# with pytest.raises(RequestEsxception):
|
# with pytest.raises(RequestEsxception):
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
fiat_convert._cryptomap = {}
|
fiat_convert._coinlistings = {}
|
||||||
fiat_convert._load_cryptomap()
|
fiat_convert._load_cryptomap()
|
||||||
|
|
||||||
length_cryptomap = len(fiat_convert._cryptomap)
|
assert len(fiat_convert._coinlistings) == 0
|
||||||
assert length_cryptomap == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_without_network(mocker):
|
def test_fiat_convert_without_network(mocker):
|
||||||
@ -132,11 +131,10 @@ def test_fiat_too_many_requests_response(mocker, caplog):
|
|||||||
)
|
)
|
||||||
# with pytest.raises(RequestEsxception):
|
# with pytest.raises(RequestEsxception):
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
fiat_convert._cryptomap = {}
|
fiat_convert._coinlistings = {}
|
||||||
fiat_convert._load_cryptomap()
|
fiat_convert._load_cryptomap()
|
||||||
|
|
||||||
length_cryptomap = len(fiat_convert._cryptomap)
|
assert len(fiat_convert._coinlistings) == 0
|
||||||
assert length_cryptomap == 0
|
|
||||||
assert fiat_convert._backoff > datetime.datetime.now().timestamp()
|
assert fiat_convert._backoff > datetime.datetime.now().timestamp()
|
||||||
assert log_has(
|
assert log_has(
|
||||||
'Too many requests for Coingecko API, backing off and trying again later.',
|
'Too many requests for Coingecko API, backing off and trying again later.',
|
||||||
@ -144,20 +142,33 @@ def test_fiat_too_many_requests_response(mocker, caplog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fiat_multiple_coins(mocker, caplog):
|
||||||
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
fiat_convert._coinlistings = [
|
||||||
|
{'id': 'helium', 'symbol': 'hnt', 'name': 'Helium'},
|
||||||
|
{'id': 'hymnode', 'symbol': 'hnt', 'name': 'Hymnode'},
|
||||||
|
{'id': 'bitcoin', 'symbol': 'btc', 'name': 'Bitcoin'},
|
||||||
|
]
|
||||||
|
|
||||||
|
assert fiat_convert._get_gekko_id('btc') == 'bitcoin'
|
||||||
|
assert fiat_convert._get_gekko_id('hnt') is None
|
||||||
|
|
||||||
|
assert log_has('Found multiple mappings in goingekko for hnt.', caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_invalid_response(mocker, caplog):
|
def test_fiat_invalid_response(mocker, caplog):
|
||||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||||
listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}")
|
listmock = MagicMock(return_value=None)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
|
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
|
||||||
get_coins_list=listmock,
|
get_coins_list=listmock,
|
||||||
)
|
)
|
||||||
# with pytest.raises(RequestEsxception):
|
# with pytest.raises(RequestEsxception):
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
fiat_convert._cryptomap = {}
|
fiat_convert._coinlistings = []
|
||||||
fiat_convert._load_cryptomap()
|
fiat_convert._load_cryptomap()
|
||||||
|
|
||||||
length_cryptomap = len(fiat_convert._cryptomap)
|
assert len(fiat_convert._coinlistings) == 0
|
||||||
assert length_cryptomap == 0
|
|
||||||
assert log_has_re('Could not load FIAT Cryptocurrency map for the following problem: .*',
|
assert log_has_re('Could not load FIAT Cryptocurrency map for the following problem: .*',
|
||||||
caplog)
|
caplog)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
|
|
||||||
freqtradebot.state = State.RUNNING
|
freqtradebot.state = State.RUNNING
|
||||||
@ -192,7 +192,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
|||||||
)
|
)
|
||||||
del default_conf['fiat_display_currency']
|
del default_conf['fiat_display_currency']
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
|
|
||||||
freqtradebot.state = State.RUNNING
|
freqtradebot.state = State.RUNNING
|
||||||
@ -239,7 +239,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
stake_currency = default_conf['stake_currency']
|
stake_currency = default_conf['stake_currency']
|
||||||
fiat_display_currency = default_conf['fiat_display_currency']
|
fiat_display_currency = default_conf['fiat_display_currency']
|
||||||
|
|
||||||
@ -371,7 +371,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
stake_currency = default_conf['stake_currency']
|
stake_currency = default_conf['stake_currency']
|
||||||
fiat_display_currency = default_conf['fiat_display_currency']
|
fiat_display_currency = default_conf['fiat_display_currency']
|
||||||
|
|
||||||
@ -459,7 +459,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
stake_currency = default_conf['stake_currency']
|
stake_currency = default_conf['stake_currency']
|
||||||
fiat_display_currency = default_conf['fiat_display_currency']
|
fiat_display_currency = default_conf['fiat_display_currency']
|
||||||
|
|
||||||
@ -526,7 +526,7 @@ def test_rpc_balance_handle_error(default_conf, mocker):
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
rpc._fiat_converter = CryptoToFiatConverter()
|
rpc._fiat_converter = CryptoToFiatConverter()
|
||||||
with pytest.raises(RPCException, match="Error getting current tickers."):
|
with pytest.raises(RPCException, match="Error getting current tickers."):
|
||||||
@ -567,7 +567,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
|
|||||||
)
|
)
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
rpc._fiat_converter = CryptoToFiatConverter()
|
rpc._fiat_converter = CryptoToFiatConverter()
|
||||||
|
|
||||||
@ -612,7 +612,7 @@ def test_rpc_start(mocker, default_conf) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
freqtradebot.state = State.STOPPED
|
freqtradebot.state = State.STOPPED
|
||||||
|
|
||||||
@ -633,7 +633,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
freqtradebot.state = State.RUNNING
|
freqtradebot.state = State.RUNNING
|
||||||
|
|
||||||
@ -655,7 +655,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
freqtradebot.state = State.RUNNING
|
freqtradebot.state = State.RUNNING
|
||||||
|
|
||||||
@ -687,7 +687,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
|||||||
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000)
|
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
|
|
||||||
freqtradebot.state = State.STOPPED
|
freqtradebot.state = State.STOPPED
|
||||||
@ -805,7 +805,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
@ -838,7 +838,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
|
|
||||||
counts = rpc._rpc_count()
|
counts = rpc._rpc_count()
|
||||||
@ -863,7 +863,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
trade = rpc._rpc_forcebuy(pair, None)
|
trade = rpc._rpc_forcebuy(pair, None)
|
||||||
@ -889,7 +889,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
|
|||||||
# Test not buying
|
# Test not buying
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
freqtradebot.config['stake_amount'] = 0
|
freqtradebot.config['stake_amount'] = 0
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
pair = 'TKN/BTC'
|
pair = 'TKN/BTC'
|
||||||
trade = rpc._rpc_forcebuy(pair, None)
|
trade = rpc._rpc_forcebuy(pair, None)
|
||||||
@ -902,7 +902,7 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
|
|||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
with pytest.raises(RPCException, match=r'trader is not running'):
|
with pytest.raises(RPCException, match=r'trader is not running'):
|
||||||
@ -913,7 +913,7 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
|
|||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
with pytest.raises(RPCException, match=r'Forcebuy not enabled.'):
|
with pytest.raises(RPCException, match=r'Forcebuy not enabled.'):
|
||||||
|
@ -109,6 +109,11 @@ def test_api_ui_fallback(botclient):
|
|||||||
rc = client_get(client, "/something")
|
rc = client_get(client, "/something")
|
||||||
assert rc.status_code == 200
|
assert rc.status_code == 200
|
||||||
|
|
||||||
|
# Test directory traversal
|
||||||
|
rc = client_get(client, '%2F%2F%2Fetc/passwd')
|
||||||
|
assert rc.status_code == 200
|
||||||
|
assert '`freqtrade install-ui`' in rc.text
|
||||||
|
|
||||||
|
|
||||||
def test_api_ui_version(botclient, mocker):
|
def test_api_ui_version(botclient, mocker):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
@ -442,7 +447,7 @@ def test_api_balance(botclient, mocker, rpc_balance):
|
|||||||
|
|
||||||
def test_api_count(botclient, mocker, ticker, fee, markets):
|
def test_api_count(botclient, mocker, ticker, fee, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
get_balances=MagicMock(return_value=ticker),
|
get_balances=MagicMock(return_value=ticker),
|
||||||
@ -504,7 +509,7 @@ def test_api_locks(botclient):
|
|||||||
|
|
||||||
def test_api_show_config(botclient, mocker):
|
def test_api_show_config(botclient, mocker):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
|
|
||||||
rc = client_get(client, f"{BASE_URI}/show_config")
|
rc = client_get(client, f"{BASE_URI}/show_config")
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
@ -522,7 +527,7 @@ def test_api_show_config(botclient, mocker):
|
|||||||
|
|
||||||
def test_api_daily(botclient, mocker, ticker, fee, markets):
|
def test_api_daily(botclient, mocker, ticker, fee, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
get_balances=MagicMock(return_value=ticker),
|
get_balances=MagicMock(return_value=ticker),
|
||||||
@ -540,7 +545,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
|
|||||||
|
|
||||||
def test_api_trades(botclient, mocker, fee, markets):
|
def test_api_trades(botclient, mocker, fee, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
markets=PropertyMock(return_value=markets)
|
markets=PropertyMock(return_value=markets)
|
||||||
@ -568,7 +573,7 @@ def test_api_trades(botclient, mocker, fee, markets):
|
|||||||
|
|
||||||
def test_api_trade_single(botclient, mocker, fee, ticker, markets):
|
def test_api_trade_single(botclient, mocker, fee, ticker, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
markets=PropertyMock(return_value=markets),
|
markets=PropertyMock(return_value=markets),
|
||||||
@ -588,7 +593,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets):
|
|||||||
|
|
||||||
def test_api_delete_trade(botclient, mocker, fee, markets):
|
def test_api_delete_trade(botclient, mocker, fee, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
stoploss_mock = MagicMock()
|
stoploss_mock = MagicMock()
|
||||||
cancel_mock = MagicMock()
|
cancel_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -662,7 +667,7 @@ def test_api_logs(botclient):
|
|||||||
|
|
||||||
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
get_balances=MagicMock(return_value=ticker),
|
get_balances=MagicMock(return_value=ticker),
|
||||||
@ -678,7 +683,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
|||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_api_profit(botclient, mocker, ticker, fee, markets):
|
def test_api_profit(botclient, mocker, ticker, fee, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
get_balances=MagicMock(return_value=ticker),
|
get_balances=MagicMock(return_value=ticker),
|
||||||
@ -729,7 +734,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets):
|
|||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_api_stats(botclient, mocker, ticker, fee, markets,):
|
def test_api_stats(botclient, mocker, ticker, fee, markets,):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
get_balances=MagicMock(return_value=ticker),
|
get_balances=MagicMock(return_value=ticker),
|
||||||
@ -757,7 +762,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
|
|||||||
|
|
||||||
def test_api_performance(botclient, fee):
|
def test_api_performance(botclient, fee):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
|
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='LTC/ETH',
|
pair='LTC/ETH',
|
||||||
@ -803,7 +808,7 @@ def test_api_performance(botclient, fee):
|
|||||||
|
|
||||||
def test_api_status(botclient, mocker, ticker, fee, markets):
|
def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
get_balances=MagicMock(return_value=ticker),
|
get_balances=MagicMock(return_value=ticker),
|
||||||
@ -874,7 +879,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
|||||||
'open_trade_value': 15.1668225,
|
'open_trade_value': 15.1668225,
|
||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
'sell_order_status': None,
|
'sell_order_status': None,
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'buy_tag': None,
|
'buy_tag': None,
|
||||||
'timeframe': 5,
|
'timeframe': 5,
|
||||||
'exchange': 'binance',
|
'exchange': 'binance',
|
||||||
@ -979,7 +984,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
|||||||
close_rate=0.265441,
|
close_rate=0.265441,
|
||||||
id=22,
|
id=22,
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
strategy="DefaultStrategy"
|
strategy="StrategyTestV2"
|
||||||
))
|
))
|
||||||
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
|
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
|
||||||
|
|
||||||
@ -1029,7 +1034,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
|||||||
'open_trade_value': 0.24605460,
|
'open_trade_value': 0.24605460,
|
||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
'sell_order_status': None,
|
'sell_order_status': None,
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'buy_tag': None,
|
'buy_tag': None,
|
||||||
'timeframe': 5,
|
'timeframe': 5,
|
||||||
'exchange': 'binance',
|
'exchange': 'binance',
|
||||||
@ -1046,7 +1051,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets):
|
|||||||
markets=PropertyMock(return_value=markets),
|
markets=PropertyMock(return_value=markets),
|
||||||
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
||||||
)
|
)
|
||||||
patch_get_signal(ftbot, (True, False, None))
|
patch_get_signal(ftbot)
|
||||||
|
|
||||||
rc = client_post(client, f"{BASE_URI}/forcesell",
|
rc = client_post(client, f"{BASE_URI}/forcesell",
|
||||||
data='{"tradeid": "1"}')
|
data='{"tradeid": "1"}')
|
||||||
@ -1096,7 +1101,7 @@ def test_api_pair_candles(botclient, ohlcv_history):
|
|||||||
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
|
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert 'strategy' in rc.json()
|
assert 'strategy' in rc.json()
|
||||||
assert rc.json()['strategy'] == 'DefaultStrategy'
|
assert rc.json()['strategy'] == 'StrategyTestV2'
|
||||||
assert 'columns' in rc.json()
|
assert 'columns' in rc.json()
|
||||||
assert 'data_start_ts' in rc.json()
|
assert 'data_start_ts' in rc.json()
|
||||||
assert 'data_start' in rc.json()
|
assert 'data_start' in rc.json()
|
||||||
@ -1134,19 +1139,19 @@ def test_api_pair_history(botclient, ohlcv_history):
|
|||||||
# No pair
|
# No pair
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?timeframe={timeframe}"
|
f"{BASE_URI}/pair_history?timeframe={timeframe}"
|
||||||
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
"&timerange=20180111-20180112&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 422)
|
assert_response(rc, 422)
|
||||||
|
|
||||||
# No Timeframe
|
# No Timeframe
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
|
||||||
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
"&timerange=20180111-20180112&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 422)
|
assert_response(rc, 422)
|
||||||
|
|
||||||
# No timerange
|
# No timerange
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||||
"&strategy=DefaultStrategy")
|
"&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 422)
|
assert_response(rc, 422)
|
||||||
|
|
||||||
# No strategy
|
# No strategy
|
||||||
@ -1158,14 +1163,14 @@ def test_api_pair_history(botclient, ohlcv_history):
|
|||||||
# Working
|
# Working
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||||
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
"&timerange=20180111-20180112&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 200)
|
assert_response(rc, 200)
|
||||||
assert rc.json()['length'] == 289
|
assert rc.json()['length'] == 289
|
||||||
assert len(rc.json()['data']) == rc.json()['length']
|
assert len(rc.json()['data']) == rc.json()['length']
|
||||||
assert 'columns' in rc.json()
|
assert 'columns' in rc.json()
|
||||||
assert 'data' in rc.json()
|
assert 'data' in rc.json()
|
||||||
assert rc.json()['pair'] == 'UNITTEST/BTC'
|
assert rc.json()['pair'] == 'UNITTEST/BTC'
|
||||||
assert rc.json()['strategy'] == 'DefaultStrategy'
|
assert rc.json()['strategy'] == 'StrategyTestV2'
|
||||||
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
|
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
|
||||||
assert rc.json()['data_start_ts'] == 1515628800000
|
assert rc.json()['data_start_ts'] == 1515628800000
|
||||||
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
|
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
|
||||||
@ -1174,7 +1179,7 @@ def test_api_pair_history(botclient, ohlcv_history):
|
|||||||
# No data found
|
# No data found
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||||
"&timerange=20200111-20200112&strategy=DefaultStrategy")
|
"&timerange=20200111-20200112&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 502)
|
assert_response(rc, 502)
|
||||||
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
|
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
|
||||||
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
|
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
|
||||||
@ -1212,21 +1217,21 @@ def test_api_strategies(botclient):
|
|||||||
|
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert rc.json() == {'strategies': [
|
assert rc.json() == {'strategies': [
|
||||||
'DefaultStrategy',
|
|
||||||
'HyperoptableStrategy',
|
'HyperoptableStrategy',
|
||||||
'TestStrategyLegacy'
|
'StrategyTestV2',
|
||||||
|
'TestStrategyLegacyV1'
|
||||||
]}
|
]}
|
||||||
|
|
||||||
|
|
||||||
def test_api_strategy(botclient):
|
def test_api_strategy(botclient):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
|
|
||||||
rc = client_get(client, f"{BASE_URI}/strategy/DefaultStrategy")
|
rc = client_get(client, f"{BASE_URI}/strategy/StrategyTestV2")
|
||||||
|
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert rc.json()['strategy'] == 'DefaultStrategy'
|
assert rc.json()['strategy'] == 'StrategyTestV2'
|
||||||
|
|
||||||
data = (Path(__file__).parents[1] / "strategy/strats/default_strategy.py").read_text()
|
data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v2.py").read_text()
|
||||||
assert rc.json()['code'] == data
|
assert rc.json()['code'] == data
|
||||||
|
|
||||||
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
|
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
|
||||||
@ -1283,7 +1288,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog):
|
|||||||
|
|
||||||
# start backtesting
|
# start backtesting
|
||||||
data = {
|
data = {
|
||||||
"strategy": "DefaultStrategy",
|
"strategy": "StrategyTestV2",
|
||||||
"timeframe": "5m",
|
"timeframe": "5m",
|
||||||
"timerange": "20180110-20180111",
|
"timerange": "20180110-20180111",
|
||||||
"max_open_trades": 3,
|
"max_open_trades": 3,
|
||||||
|
@ -119,7 +119,7 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None:
|
|||||||
rpc = RPC(bot)
|
rpc = RPC(bot)
|
||||||
dummy = DummyCls(rpc, default_conf)
|
dummy = DummyCls(rpc, default_conf)
|
||||||
|
|
||||||
patch_get_signal(bot, (True, False, None))
|
patch_get_signal(bot)
|
||||||
dummy.dummy_handler(update=update, context=MagicMock())
|
dummy.dummy_handler(update=update, context=MagicMock())
|
||||||
assert dummy.state['called'] is True
|
assert dummy.state['called'] is True
|
||||||
assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog)
|
assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog)
|
||||||
@ -139,7 +139,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
|||||||
rpc = RPC(bot)
|
rpc = RPC(bot)
|
||||||
dummy = DummyCls(rpc, default_conf)
|
dummy = DummyCls(rpc, default_conf)
|
||||||
|
|
||||||
patch_get_signal(bot, (True, False, None))
|
patch_get_signal(bot)
|
||||||
dummy.dummy_handler(update=update, context=MagicMock())
|
dummy.dummy_handler(update=update, context=MagicMock())
|
||||||
assert dummy.state['called'] is False
|
assert dummy.state['called'] is False
|
||||||
assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog)
|
assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog)
|
||||||
@ -155,7 +155,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None
|
|||||||
bot = FreqtradeBot(default_conf)
|
bot = FreqtradeBot(default_conf)
|
||||||
rpc = RPC(bot)
|
rpc = RPC(bot)
|
||||||
dummy = DummyCls(rpc, default_conf)
|
dummy = DummyCls(rpc, default_conf)
|
||||||
patch_get_signal(bot, (True, False, None))
|
patch_get_signal(bot)
|
||||||
|
|
||||||
dummy.dummy_exception(update=update, context=MagicMock())
|
dummy.dummy_exception(update=update, context=MagicMock())
|
||||||
assert dummy.state['called'] is False
|
assert dummy.state['called'] is False
|
||||||
@ -229,7 +229,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
|||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
freqtradebot.state = State.STOPPED
|
freqtradebot.state = State.STOPPED
|
||||||
# Status is also enabled when stopped
|
# Status is also enabled when stopped
|
||||||
@ -286,7 +286,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
|
|||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
freqtradebot.state = State.STOPPED
|
freqtradebot.state = State.STOPPED
|
||||||
# Status table is also enabled when stopped
|
# Status table is also enabled when stopped
|
||||||
@ -330,7 +330,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
freqtradebot.enter_positions()
|
freqtradebot.enter_positions()
|
||||||
@ -401,7 +401,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
# Try invalid data
|
# Try invalid data
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
@ -433,7 +433,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
|||||||
)
|
)
|
||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
telegram._profit(update=update, context=MagicMock())
|
telegram._profit(update=update, context=MagicMock())
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
@ -488,7 +488,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee,
|
|||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
)
|
)
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
telegram._stats(update=update, context=MagicMock())
|
telegram._stats(update=update, context=MagicMock())
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
@ -514,7 +514,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
|
|||||||
side_effect=lambda a, b: f"{a}/{b}")
|
side_effect=lambda a, b: f"{a}/{b}")
|
||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
telegram._balance(update=update, context=MagicMock())
|
telegram._balance(update=update, context=MagicMock())
|
||||||
result = msg_mock.call_args_list[0][0][0]
|
result = msg_mock.call_args_list[0][0][0]
|
||||||
@ -537,7 +537,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
|
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
|
||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
freqtradebot.config['dry_run'] = False
|
freqtradebot.config['dry_run'] = False
|
||||||
telegram._balance(update=update, context=MagicMock())
|
telegram._balance(update=update, context=MagicMock())
|
||||||
@ -550,7 +550,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
|
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
|
||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
telegram._balance(update=update, context=MagicMock())
|
telegram._balance(update=update, context=MagicMock())
|
||||||
result = msg_mock.call_args_list[0][0][0]
|
result = msg_mock.call_args_list[0][0][0]
|
||||||
@ -579,7 +579,7 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
|
|||||||
})
|
})
|
||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
telegram._balance(update=update, context=MagicMock())
|
telegram._balance(update=update, context=MagicMock())
|
||||||
assert msg_mock.call_count > 1
|
assert msg_mock.call_count > 1
|
||||||
@ -678,7 +678,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
|
|||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
telegram = Telegram(rpc, default_conf)
|
telegram = Telegram(rpc, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
freqtradebot.enter_positions()
|
freqtradebot.enter_positions()
|
||||||
@ -737,7 +737,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
|
|||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
telegram = Telegram(rpc, default_conf)
|
telegram = Telegram(rpc, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
freqtradebot.enter_positions()
|
freqtradebot.enter_positions()
|
||||||
@ -798,7 +798,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
|||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
telegram = Telegram(rpc, default_conf)
|
telegram = Telegram(rpc, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
freqtradebot.enter_positions()
|
freqtradebot.enter_positions()
|
||||||
@ -839,7 +839,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
|||||||
return_value=15000.0)
|
return_value=15000.0)
|
||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
# Trader is not running
|
# Trader is not running
|
||||||
freqtradebot.state = State.STOPPED
|
freqtradebot.state = State.STOPPED
|
||||||
@ -877,7 +877,7 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None:
|
|||||||
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
|
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
|
||||||
|
|
||||||
telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
# /forcebuy ETH/BTC
|
# /forcebuy ETH/BTC
|
||||||
context = MagicMock()
|
context = MagicMock()
|
||||||
@ -906,7 +906,7 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None:
|
|||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
update.message.text = '/forcebuy ETH/Nonepair'
|
update.message.text = '/forcebuy ETH/Nonepair'
|
||||||
telegram._forcebuy(update=update, context=MagicMock())
|
telegram._forcebuy(update=update, context=MagicMock())
|
||||||
@ -923,7 +923,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
context = MagicMock()
|
context = MagicMock()
|
||||||
context.args = []
|
context.args = []
|
||||||
@ -951,7 +951,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
|
|||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
)
|
)
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
# Create some test data
|
# Create some test data
|
||||||
freqtradebot.enter_positions()
|
freqtradebot.enter_positions()
|
||||||
@ -979,7 +979,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
|
|||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
)
|
)
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
|
|
||||||
freqtradebot.state = State.STOPPED
|
freqtradebot.state = State.STOPPED
|
||||||
telegram._count(update=update, context=MagicMock())
|
telegram._count(update=update, context=MagicMock())
|
||||||
@ -1008,7 +1008,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
|
|||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
)
|
)
|
||||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False, None))
|
patch_get_signal(freqtradebot)
|
||||||
telegram._locks(update=update, context=MagicMock())
|
telegram._locks(update=update, context=MagicMock())
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert 'No active locks.' in msg_mock.call_args_list[0][0][0]
|
assert 'No active locks.' in msg_mock.call_args_list[0][0][0]
|
||||||
@ -1236,7 +1236,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
|||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
|
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
@ -1245,7 +1245,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
|||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
|
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,5 +5,5 @@ import nonexiting_module # noqa
|
|||||||
from freqtrade.strategy.interface import IStrategy
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
class TestStrategyLegacy(IStrategy):
|
class TestStrategyLegacyV1(IStrategy):
|
||||||
pass
|
pass
|
||||||
|
@ -10,7 +10,7 @@ from freqtrade.strategy.interface import IStrategy
|
|||||||
# --------------------------------
|
# --------------------------------
|
||||||
|
|
||||||
# This class is a sample. Feel free to customize it.
|
# This class is a sample. Feel free to customize it.
|
||||||
class TestStrategyLegacy(IStrategy):
|
class TestStrategyLegacyV1(IStrategy):
|
||||||
"""
|
"""
|
||||||
This is a test strategy using the legacy function headers, which will be
|
This is a test strategy using the legacy function headers, which will be
|
||||||
removed in a future update.
|
removed in a future update.
|
@ -7,9 +7,9 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|||||||
from freqtrade.strategy.interface import IStrategy
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
class DefaultStrategy(IStrategy):
|
class StrategyTestV2(IStrategy):
|
||||||
"""
|
"""
|
||||||
Default Strategy provided by freqtrade bot.
|
Strategy used by tests freqtrade bot.
|
||||||
Please do not modify this strategy, it's intended for internal use only.
|
Please do not modify this strategy, it's intended for internal use only.
|
||||||
Please look at the SampleStrategy in the user_data/strategy directory
|
Please look at the SampleStrategy in the user_data/strategy directory
|
||||||
or strategy repository https://github.com/freqtrade/freqtrade-strategies
|
or strategy repository https://github.com/freqtrade/freqtrade-strategies
|
@ -4,20 +4,20 @@ from pandas import DataFrame
|
|||||||
|
|
||||||
from freqtrade.persistence.models import Trade
|
from freqtrade.persistence.models import Trade
|
||||||
|
|
||||||
from .strats.default_strategy import DefaultStrategy
|
from .strats.strategy_test_v2 import StrategyTestV2
|
||||||
|
|
||||||
|
|
||||||
def test_default_strategy_structure():
|
def test_strategy_test_v2_structure():
|
||||||
assert hasattr(DefaultStrategy, 'minimal_roi')
|
assert hasattr(StrategyTestV2, 'minimal_roi')
|
||||||
assert hasattr(DefaultStrategy, 'stoploss')
|
assert hasattr(StrategyTestV2, 'stoploss')
|
||||||
assert hasattr(DefaultStrategy, 'timeframe')
|
assert hasattr(StrategyTestV2, 'timeframe')
|
||||||
assert hasattr(DefaultStrategy, 'populate_indicators')
|
assert hasattr(StrategyTestV2, 'populate_indicators')
|
||||||
assert hasattr(DefaultStrategy, 'populate_buy_trend')
|
assert hasattr(StrategyTestV2, 'populate_buy_trend')
|
||||||
assert hasattr(DefaultStrategy, 'populate_sell_trend')
|
assert hasattr(StrategyTestV2, 'populate_sell_trend')
|
||||||
|
|
||||||
|
|
||||||
def test_default_strategy(result, fee):
|
def test_strategy_test_v2(result, fee):
|
||||||
strategy = DefaultStrategy({})
|
strategy = StrategyTestV2({})
|
||||||
|
|
||||||
metadata = {'pair': 'ETH/BTC'}
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
assert type(strategy.minimal_roi) is dict
|
assert type(strategy.minimal_roi) is dict
|
||||||
|
@ -22,11 +22,11 @@ from freqtrade.strategy.interface import SellCheckTuple
|
|||||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||||
from tests.conftest import log_has, log_has_re
|
from tests.conftest import log_has, log_has_re
|
||||||
|
|
||||||
from .strats.default_strategy import DefaultStrategy
|
from .strats.strategy_test_v2 import StrategyTestV2
|
||||||
|
|
||||||
|
|
||||||
# Avoid to reinit the same object again and again
|
# Avoid to reinit the same object again and again
|
||||||
_STRATEGY = DefaultStrategy(config={})
|
_STRATEGY = StrategyTestV2(config={})
|
||||||
_STRATEGY.dp = DataProvider({}, None, None)
|
_STRATEGY.dp = DataProvider({}, None, None)
|
||||||
|
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
|
|||||||
|
|
||||||
|
|
||||||
def test_ignore_expired_candle(default_conf):
|
def test_ignore_expired_candle(default_conf):
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.ignore_buying_expired_candle_after = 60
|
strategy.ignore_buying_expired_candle_after = 60
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ def test_assert_df(ohlcv_history, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_advise_all_indicators(default_conf, testdatadir) -> None:
|
def test_advise_all_indicators(default_conf, testdatadir) -> None:
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||||
@ -240,7 +240,7 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None:
|
def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None:
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators')
|
aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators')
|
||||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||||
@ -258,7 +258,7 @@ def test_min_roi_reached(default_conf, fee) -> None:
|
|||||||
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
|
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
|
||||||
{0: 0.1, 20: 0.05, 55: 0.01}]
|
{0: 0.1, 20: 0.05, 55: 0.01}]
|
||||||
for roi in min_roi_list:
|
for roi in min_roi_list:
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.minimal_roi = roi
|
strategy.minimal_roi = roi
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -297,7 +297,7 @@ def test_min_roi_reached2(default_conf, fee) -> None:
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
for roi in min_roi_list:
|
for roi in min_roi_list:
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.minimal_roi = roi
|
strategy.minimal_roi = roi
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -332,7 +332,7 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
|||||||
30: 0.05,
|
30: 0.05,
|
||||||
55: 0.30,
|
55: 0.30,
|
||||||
}
|
}
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.minimal_roi = min_roi
|
strategy.minimal_roi = min_roi
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -385,7 +385,7 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
|||||||
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
|
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
|
||||||
profit2, adjusted2, expected2, custom_stop) -> None:
|
profit2, adjusted2, expected2, custom_stop) -> None:
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -433,7 +433,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
|||||||
|
|
||||||
def test_custom_sell(default_conf, fee, caplog) -> None:
|
def test_custom_sell(default_conf, fee, caplog) -> None:
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -487,7 +487,7 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
|
|||||||
advise_sell=sell_mock,
|
advise_sell=sell_mock,
|
||||||
|
|
||||||
)
|
)
|
||||||
strategy = DefaultStrategy({})
|
strategy = StrategyTestV2({})
|
||||||
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
|
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
|
||||||
assert ind_mock.call_count == 1
|
assert ind_mock.call_count == 1
|
||||||
assert buy_mock.call_count == 1
|
assert buy_mock.call_count == 1
|
||||||
@ -518,7 +518,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
|
|||||||
advise_sell=sell_mock,
|
advise_sell=sell_mock,
|
||||||
|
|
||||||
)
|
)
|
||||||
strategy = DefaultStrategy({})
|
strategy = StrategyTestV2({})
|
||||||
strategy.dp = DataProvider({}, None, None)
|
strategy.dp = DataProvider({}, None, None)
|
||||||
strategy.process_only_new_candles = True
|
strategy.process_only_new_candles = True
|
||||||
|
|
||||||
@ -550,7 +550,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
|
|||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_is_pair_locked(default_conf):
|
def test_is_pair_locked(default_conf):
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
PairLocks.timeframe = default_conf['timeframe']
|
PairLocks.timeframe = default_conf['timeframe']
|
||||||
PairLocks.use_db = True
|
PairLocks.use_db = True
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -603,7 +603,7 @@ def test_is_pair_locked(default_conf):
|
|||||||
|
|
||||||
|
|
||||||
def test_is_informative_pairs_callback(default_conf):
|
def test_is_informative_pairs_callback(default_conf):
|
||||||
default_conf.update({'strategy': 'TestStrategyLegacy'})
|
default_conf.update({'strategy': 'TestStrategyLegacyV1'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
# Should return empty
|
# Should return empty
|
||||||
# Uses fallback to base implementation
|
# Uses fallback to base implementation
|
||||||
@ -630,7 +630,7 @@ def test_strategy_safe_wrapper_error(caplog, error):
|
|||||||
assert ret
|
assert ret
|
||||||
|
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
# Test supressing error
|
# Test suppressing error
|
||||||
ret = strategy_safe_wrapper(failing_method, message='DeadBeef', supress_error=True)()
|
ret = strategy_safe_wrapper(failing_method, message='DeadBeef', supress_error=True)()
|
||||||
assert log_has_re(r'DeadBeef.*', caplog)
|
assert log_has_re(r'DeadBeef.*', caplog)
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ def test_search_strategy():
|
|||||||
|
|
||||||
s, _ = StrategyResolver._search_object(
|
s, _ = StrategyResolver._search_object(
|
||||||
directory=default_location,
|
directory=default_location,
|
||||||
object_name='DefaultStrategy',
|
object_name='StrategyTestV2',
|
||||||
add_source=True,
|
add_source=True,
|
||||||
)
|
)
|
||||||
assert issubclass(s, IStrategy)
|
assert issubclass(s, IStrategy)
|
||||||
@ -74,10 +74,10 @@ def test_load_strategy_base64(result, caplog, default_conf):
|
|||||||
|
|
||||||
|
|
||||||
def test_load_strategy_invalid_directory(result, caplog, default_conf):
|
def test_load_strategy_invalid_directory(result, caplog, default_conf):
|
||||||
default_conf['strategy'] = 'DefaultStrategy'
|
default_conf['strategy'] = 'StrategyTestV2'
|
||||||
extra_dir = Path.cwd() / 'some/path'
|
extra_dir = Path.cwd() / 'some/path'
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
StrategyResolver._load_strategy('DefaultStrategy', config=default_conf,
|
StrategyResolver._load_strategy('StrategyTestV2', config=default_conf,
|
||||||
extra_dir=extra_dir)
|
extra_dir=extra_dir)
|
||||||
|
|
||||||
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
|
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
|
||||||
@ -100,7 +100,7 @@ def test_load_strategy_noname(default_conf):
|
|||||||
|
|
||||||
|
|
||||||
def test_strategy(result, default_conf):
|
def test_strategy(result, default_conf):
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
metadata = {'pair': 'ETH/BTC'}
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
@ -127,7 +127,7 @@ def test_strategy(result, default_conf):
|
|||||||
def test_strategy_override_minimal_roi(caplog, default_conf):
|
def test_strategy_override_minimal_roi(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'minimal_roi': {
|
'minimal_roi': {
|
||||||
"20": 0.1,
|
"20": 0.1,
|
||||||
"0": 0.5
|
"0": 0.5
|
||||||
@ -144,7 +144,7 @@ def test_strategy_override_minimal_roi(caplog, default_conf):
|
|||||||
def test_strategy_override_stoploss(caplog, default_conf):
|
def test_strategy_override_stoploss(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'stoploss': -0.5
|
'stoploss': -0.5
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -156,7 +156,7 @@ def test_strategy_override_stoploss(caplog, default_conf):
|
|||||||
def test_strategy_override_trailing_stop(caplog, default_conf):
|
def test_strategy_override_trailing_stop(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'trailing_stop': True
|
'trailing_stop': True
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -169,7 +169,7 @@ def test_strategy_override_trailing_stop(caplog, default_conf):
|
|||||||
def test_strategy_override_trailing_stop_positive(caplog, default_conf):
|
def test_strategy_override_trailing_stop_positive(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'trailing_stop_positive': -0.1,
|
'trailing_stop_positive': -0.1,
|
||||||
'trailing_stop_positive_offset': -0.2
|
'trailing_stop_positive_offset': -0.2
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ def test_strategy_override_timeframe(caplog, default_conf):
|
|||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'timeframe': 60,
|
'timeframe': 60,
|
||||||
'stake_currency': 'ETH'
|
'stake_currency': 'ETH'
|
||||||
})
|
})
|
||||||
@ -205,7 +205,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf):
|
|||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'process_only_new_candles': True
|
'process_only_new_candles': True
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -225,7 +225,7 @@ def test_strategy_override_order_types(caplog, default_conf):
|
|||||||
'stoploss_on_exchange': True,
|
'stoploss_on_exchange': True,
|
||||||
}
|
}
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'order_types': order_types
|
'order_types': order_types
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -239,12 +239,12 @@ def test_strategy_override_order_types(caplog, default_conf):
|
|||||||
" 'stoploss_on_exchange': True}.", caplog)
|
" 'stoploss_on_exchange': True}.", caplog)
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'order_types': {'buy': 'market'}
|
'order_types': {'buy': 'market'}
|
||||||
})
|
})
|
||||||
# Raise error for invalid configuration
|
# Raise error for invalid configuration
|
||||||
with pytest.raises(ImportError,
|
with pytest.raises(ImportError,
|
||||||
match=r"Impossible to load Strategy 'DefaultStrategy'. "
|
match=r"Impossible to load Strategy 'StrategyTestV2'. "
|
||||||
r"Order-types mapping is incomplete."):
|
r"Order-types mapping is incomplete."):
|
||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
@ -258,7 +258,7 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
|||||||
}
|
}
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'order_time_in_force': order_time_in_force
|
'order_time_in_force': order_time_in_force
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -271,12 +271,12 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
|||||||
" {'buy': 'fok', 'sell': 'gtc'}.", caplog)
|
" {'buy': 'fok', 'sell': 'gtc'}.", caplog)
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'order_time_in_force': {'buy': 'fok'}
|
'order_time_in_force': {'buy': 'fok'}
|
||||||
})
|
})
|
||||||
# Raise error for invalid configuration
|
# Raise error for invalid configuration
|
||||||
with pytest.raises(ImportError,
|
with pytest.raises(ImportError,
|
||||||
match=r"Impossible to load Strategy 'DefaultStrategy'. "
|
match=r"Impossible to load Strategy 'StrategyTestV2'. "
|
||||||
r"Order-time-in-force mapping is incomplete."):
|
r"Order-time-in-force mapping is incomplete."):
|
||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
@ -284,7 +284,7 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
|||||||
def test_strategy_override_use_sell_signal(caplog, default_conf):
|
def test_strategy_override_use_sell_signal(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
assert strategy.use_sell_signal
|
assert strategy.use_sell_signal
|
||||||
@ -294,7 +294,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
|
|||||||
assert default_conf['use_sell_signal']
|
assert default_conf['use_sell_signal']
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'use_sell_signal': False,
|
'use_sell_signal': False,
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -307,7 +307,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
|
|||||||
def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
assert not strategy.sell_profit_only
|
assert not strategy.sell_profit_only
|
||||||
@ -317,7 +317,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
|||||||
assert not default_conf['sell_profit_only']
|
assert not default_conf['sell_profit_only']
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'sell_profit_only': True,
|
'sell_profit_only': True,
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -330,7 +330,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
|||||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||||
def test_deprecate_populate_indicators(result, default_conf):
|
def test_deprecate_populate_indicators(result, default_conf):
|
||||||
default_location = Path(__file__).parent / "strats"
|
default_location = Path(__file__).parent / "strats"
|
||||||
default_conf.update({'strategy': 'TestStrategyLegacy',
|
default_conf.update({'strategy': 'TestStrategyLegacyV1',
|
||||||
'strategy_path': default_location})
|
'strategy_path': default_location})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
with warnings.catch_warnings(record=True) as w:
|
with warnings.catch_warnings(record=True) as w:
|
||||||
@ -365,7 +365,7 @@ def test_deprecate_populate_indicators(result, default_conf):
|
|||||||
def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
||||||
default_location = Path(__file__).parent / "strats"
|
default_location = Path(__file__).parent / "strats"
|
||||||
del default_conf['timeframe']
|
del default_conf['timeframe']
|
||||||
default_conf.update({'strategy': 'TestStrategyLegacy',
|
default_conf.update({'strategy': 'TestStrategyLegacyV1',
|
||||||
'strategy_path': default_location})
|
'strategy_path': default_location})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
metadata = {'pair': 'ETH/BTC'}
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
@ -395,7 +395,7 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_strategy_interface_versioning(result, monkeypatch, default_conf):
|
def test_strategy_interface_versioning(result, monkeypatch, default_conf):
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
metadata = {'pair': 'ETH/BTC'}
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ def test_parse_args_backtesting_custom() -> None:
|
|||||||
'-c', 'test_conf.json',
|
'-c', 'test_conf.json',
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'StrategyTestV2',
|
||||||
'SampleStrategy'
|
'SampleStrategy'
|
||||||
]
|
]
|
||||||
call_args = Arguments(args).get_parsed_arg()
|
call_args = Arguments(args).get_parsed_arg()
|
||||||
|
@ -404,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
|||||||
arglist = [
|
arglist = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
]
|
]
|
||||||
|
|
||||||
args = Arguments(arglist).get_parsed_arg()
|
args = Arguments(arglist).get_parsed_arg()
|
||||||
@ -441,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
|||||||
arglist = [
|
arglist = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'--userdir', "/tmp/freqtrade",
|
'--userdir', "/tmp/freqtrade",
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
@ -498,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
|
|||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--export', 'trades',
|
'--export', 'trades',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'StrategyTestV2',
|
||||||
'TestStrategy'
|
'TestStrategy'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b
|
|||||||
limit_buy_order_open['id'] = str(i)
|
limit_buy_order_open['id'] = str(i)
|
||||||
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||||
assert pytest.approx(result) == expected[i]
|
assert pytest.approx(result) == expected[i]
|
||||||
freqtrade.execute_buy('ETH/BTC', result)
|
freqtrade.execute_entry('ETH/BTC', result)
|
||||||
else:
|
else:
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||||
@ -584,8 +584,8 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker, limit_buy_orde
|
|||||||
patch_get_signal(freqtrade)
|
patch_get_signal(freqtrade)
|
||||||
|
|
||||||
# Create 2 existing trades
|
# Create 2 existing trades
|
||||||
freqtrade.execute_buy('ETH/BTC', default_conf['stake_amount'])
|
freqtrade.execute_entry('ETH/BTC', default_conf['stake_amount'])
|
||||||
freqtrade.execute_buy('NEO/BTC', default_conf['stake_amount'])
|
freqtrade.execute_entry('NEO/BTC', default_conf['stake_amount'])
|
||||||
|
|
||||||
assert len(Trade.get_open_trades()) == 2
|
assert len(Trade.get_open_trades()) == 2
|
||||||
# Change order_id for new orders
|
# Change order_id for new orders
|
||||||
@ -776,7 +776,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
|
|||||||
assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0]
|
assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None:
|
def test_execute_entry(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None:
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -799,7 +799,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
)
|
)
|
||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
|
|
||||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
assert not freqtrade.execute_entry(pair, stake_amount)
|
||||||
assert buy_rate_mock.call_count == 1
|
assert buy_rate_mock.call_count == 1
|
||||||
assert buy_mm.call_count == 0
|
assert buy_mm.call_count == 0
|
||||||
assert freqtrade.strategy.confirm_trade_entry.call_count == 1
|
assert freqtrade.strategy.confirm_trade_entry.call_count == 1
|
||||||
@ -807,7 +807,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
|
|
||||||
limit_buy_order_open['id'] = '22'
|
limit_buy_order_open['id'] = '22'
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
assert buy_rate_mock.call_count == 1
|
assert buy_rate_mock.call_count == 1
|
||||||
assert buy_mm.call_count == 1
|
assert buy_mm.call_count == 1
|
||||||
call_args = buy_mm.call_args_list[0][1]
|
call_args = buy_mm.call_args_list[0][1]
|
||||||
@ -826,7 +826,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
# Test calling with price
|
# Test calling with price
|
||||||
limit_buy_order_open['id'] = '33'
|
limit_buy_order_open['id'] = '33'
|
||||||
fix_price = 0.06
|
fix_price = 0.06
|
||||||
assert freqtrade.execute_buy(pair, stake_amount, fix_price)
|
assert freqtrade.execute_entry(pair, stake_amount, fix_price)
|
||||||
# Make sure get_rate wasn't called again
|
# Make sure get_rate wasn't called again
|
||||||
assert buy_rate_mock.call_count == 0
|
assert buy_rate_mock.call_count == 0
|
||||||
|
|
||||||
@ -844,7 +844,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
MagicMock(return_value=limit_buy_order))
|
MagicMock(return_value=limit_buy_order))
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[2]
|
trade = Trade.query.all()[2]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.open_order_id is None
|
assert trade.open_order_id is None
|
||||||
@ -861,7 +861,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
limit_buy_order['id'] = '555'
|
limit_buy_order['id'] = '555'
|
||||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
MagicMock(return_value=limit_buy_order))
|
MagicMock(return_value=limit_buy_order))
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[3]
|
trade = Trade.query.all()[3]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.open_order_id == '555'
|
assert trade.open_order_id == '555'
|
||||||
@ -873,7 +873,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
limit_buy_order['id'] = '556'
|
limit_buy_order['id'] = '556'
|
||||||
|
|
||||||
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
|
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[4]
|
trade = Trade.query.all()[4]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.stake_amount == 150
|
assert trade.stake_amount == 150
|
||||||
@ -881,7 +881,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
# Exception case
|
# Exception case
|
||||||
limit_buy_order['id'] = '557'
|
limit_buy_order['id'] = '557'
|
||||||
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
|
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[5]
|
trade = Trade.query.all()[5]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.stake_amount == 2.0
|
assert trade.stake_amount == 2.0
|
||||||
@ -896,16 +896,50 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
limit_buy_order['id'] = '66'
|
limit_buy_order['id'] = '66'
|
||||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
MagicMock(return_value=limit_buy_order))
|
MagicMock(return_value=limit_buy_order))
|
||||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
assert not freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
# Fail to get price...
|
# Fail to get price...
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
|
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
|
||||||
|
|
||||||
with pytest.raises(PricingError, match="Could not determine buy price."):
|
with pytest.raises(PricingError, match="Could not determine buy price."):
|
||||||
freqtrade.execute_buy(pair, stake_amount)
|
freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
|
# In case of custom entry price
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50)
|
||||||
|
limit_buy_order['status'] = 'open'
|
||||||
|
limit_buy_order['id'] = '5566'
|
||||||
|
freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508
|
||||||
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
trade = Trade.query.all()[6]
|
||||||
|
assert trade
|
||||||
|
assert trade.open_rate_requested == 0.508
|
||||||
|
|
||||||
|
# In case of custom entry price set to None
|
||||||
|
limit_buy_order['status'] = 'open'
|
||||||
|
limit_buy_order['id'] = '5567'
|
||||||
|
freqtrade.strategy.custom_entry_price = lambda **kwargs: None
|
||||||
|
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
get_rate=MagicMock(return_value=10),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
trade = Trade.query.all()[7]
|
||||||
|
assert trade
|
||||||
|
assert trade.open_rate_requested == 10
|
||||||
|
|
||||||
|
# In case of custom entry price not float type
|
||||||
|
limit_buy_order['status'] = 'open'
|
||||||
|
limit_buy_order['id'] = '5568'
|
||||||
|
freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price"
|
||||||
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
trade = Trade.query.all()[8]
|
||||||
|
assert trade
|
||||||
|
assert trade.open_rate_requested == 10
|
||||||
|
|
||||||
|
|
||||||
def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None:
|
def test_execute_entry_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -923,18 +957,18 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -
|
|||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
|
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
limit_buy_order['id'] = '222'
|
limit_buy_order['id'] = '222'
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception)
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
limit_buy_order['id'] = '2223'
|
limit_buy_order['id'] = '2223'
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
|
||||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
assert not freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
|
|
||||||
def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None:
|
def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None:
|
||||||
@ -1920,7 +1954,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
|||||||
assert nb_trades == 0
|
assert nb_trades == 0
|
||||||
|
|
||||||
# Buy is triggering, so buying ...
|
# Buy is triggering, so buying ...
|
||||||
patch_get_signal(freqtrade, value=(True, False, None))
|
patch_get_signal(freqtrade)
|
||||||
freqtrade.enter_positions()
|
freqtrade.enter_positions()
|
||||||
trades = Trade.query.all()
|
trades = Trade.query.all()
|
||||||
nb_trades = len(trades)
|
nb_trades = len(trades)
|
||||||
@ -1965,7 +1999,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
|
|||||||
)
|
)
|
||||||
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
patch_get_signal(freqtrade, value=(True, False, None))
|
patch_get_signal(freqtrade)
|
||||||
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
|
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
|
||||||
|
|
||||||
freqtrade.enter_positions()
|
freqtrade.enter_positions()
|
||||||
@ -1973,7 +2007,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
|
|||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
|
|
||||||
# FIX: sniffing logs, suggest handle_trade should not execute_sell
|
# FIX: sniffing logs, suggest handle_trade should not execute_trade_exit
|
||||||
# instead that responsibility should be moved out of handle_trade(),
|
# instead that responsibility should be moved out of handle_trade(),
|
||||||
# we might just want to check if we are in a sell condition without
|
# we might just want to check if we are in a sell condition without
|
||||||
# executing
|
# executing
|
||||||
@ -2599,7 +2633,7 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
|||||||
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
def test_execute_trade_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2627,16 +2661,16 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
|||||||
fetch_ticker=ticker_sell_up
|
fetch_ticker=ticker_sell_up
|
||||||
)
|
)
|
||||||
# Prevented sell ...
|
# Prevented sell ...
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||||
assert rpc_mock.call_count == 0
|
assert rpc_mock.call_count == 0
|
||||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||||
|
|
||||||
# Repatch with true
|
# Repatch with true
|
||||||
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
|
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||||
|
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 1
|
||||||
@ -2663,7 +2697,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None:
|
def test_execute_trade_exit_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2688,8 +2722,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
|||||||
fetch_ticker=ticker_sell_down
|
fetch_ticker=ticker_sell_down
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||||
@ -2715,8 +2749,73 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee,
|
def test_execute_trade_exit_custom_exit_price(default_conf, ticker, fee, ticker_sell_up,
|
||||||
ticker_sell_down, mocker) -> None:
|
mocker) -> None:
|
||||||
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
|
patch_exchange(mocker)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
fetch_ticker=ticker,
|
||||||
|
get_fee=fee,
|
||||||
|
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
||||||
|
)
|
||||||
|
patch_whitelist(mocker, default_conf)
|
||||||
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
patch_get_signal(freqtrade)
|
||||||
|
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=False)
|
||||||
|
|
||||||
|
# Create some test data
|
||||||
|
freqtrade.enter_positions()
|
||||||
|
rpc_mock.reset_mock()
|
||||||
|
|
||||||
|
trade = Trade.query.first()
|
||||||
|
assert trade
|
||||||
|
assert freqtrade.strategy.confirm_trade_exit.call_count == 0
|
||||||
|
|
||||||
|
# Increase the price and sell it
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
fetch_ticker=ticker_sell_up
|
||||||
|
)
|
||||||
|
|
||||||
|
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
|
||||||
|
|
||||||
|
# Set a custom exit price
|
||||||
|
freqtrade.strategy.custom_exit_price = lambda **kwargs: 1.170e-05
|
||||||
|
|
||||||
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
|
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL))
|
||||||
|
|
||||||
|
# Sell price must be different to default bid price
|
||||||
|
|
||||||
|
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||||
|
|
||||||
|
assert rpc_mock.call_count == 1
|
||||||
|
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||||
|
assert {
|
||||||
|
'trade_id': 1,
|
||||||
|
'type': RPCMessageType.SELL,
|
||||||
|
'exchange': 'Binance',
|
||||||
|
'pair': 'ETH/BTC',
|
||||||
|
'gain': 'profit',
|
||||||
|
'limit': 1.170e-05,
|
||||||
|
'amount': 91.07468123,
|
||||||
|
'order_type': 'limit',
|
||||||
|
'open_rate': 1.098e-05,
|
||||||
|
'current_rate': 1.173e-05,
|
||||||
|
'profit_amount': 6.041e-05,
|
||||||
|
'profit_ratio': 0.06025919,
|
||||||
|
'stake_currency': 'BTC',
|
||||||
|
'fiat_currency': 'USD',
|
||||||
|
'sell_reason': SellType.SELL_SIGNAL.value,
|
||||||
|
'open_date': ANY,
|
||||||
|
'close_date': ANY,
|
||||||
|
'close_rate': ANY,
|
||||||
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee,
|
||||||
|
ticker_sell_down, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2746,8 +2845,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
|||||||
# Setting trade stoploss to 0.01
|
# Setting trade stoploss to 0.01
|
||||||
|
|
||||||
trade.stop_loss = 0.00001099 * 0.99
|
trade.stop_loss = 0.00001099 * 0.99
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||||
@ -2774,7 +2873,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None:
|
def test_execute_trade_exit_sloe_cancel_exception(
|
||||||
|
mocker, default_conf, ticker, fee, caplog) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||||
side_effect=InvalidOrderException())
|
side_effect=InvalidOrderException())
|
||||||
@ -2801,14 +2901,14 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
|
|||||||
freqtrade.config['dry_run'] = False
|
freqtrade.config['dry_run'] = False
|
||||||
trade.stoploss_order_id = "abcd"
|
trade.stoploss_order_id = "abcd"
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=1234,
|
freqtrade.execute_trade_exit(trade=trade, limit=1234,
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
assert create_order_mock.call_count == 2
|
assert create_order_mock.call_count == 2
|
||||||
assert log_has('Could not cancel stoploss order abcd', caplog)
|
assert log_has('Could not cancel stoploss order abcd', caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up,
|
def test_execute_trade_exit_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up,
|
||||||
mocker) -> None:
|
mocker) -> None:
|
||||||
|
|
||||||
default_conf['exchange']['name'] = 'binance'
|
default_conf['exchange']['name'] = 'binance'
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
@ -2852,8 +2952,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
|
|||||||
fetch_ticker=ticker_sell_up
|
fetch_ticker=ticker_sell_up
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
@ -2861,8 +2961,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
|
|||||||
assert rpc_mock.call_count == 3
|
assert rpc_mock.call_count == 3
|
||||||
|
|
||||||
|
|
||||||
def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
|
def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
|
||||||
mocker) -> None:
|
mocker) -> None:
|
||||||
default_conf['exchange']['name'] = 'binance'
|
default_conf['exchange']['name'] = 'binance'
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
@ -2933,8 +3033,8 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f
|
|||||||
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_market_order(default_conf, ticker, fee,
|
def test_execute_trade_exit_market_order(default_conf, ticker, fee,
|
||||||
ticker_sell_up, mocker) -> None:
|
ticker_sell_up, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2960,8 +3060,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
|
|||||||
)
|
)
|
||||||
freqtrade.config['order_types']['sell'] = 'market'
|
freqtrade.config['order_types']['sell'] = 'market'
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||||
|
|
||||||
assert not trade.is_open
|
assert not trade.is_open
|
||||||
assert trade.close_profit == 0.0620716
|
assert trade.close_profit == 0.0620716
|
||||||
@ -2991,8 +3091,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
def test_execute_trade_exit_insufficient_funds_error(default_conf, ticker, fee,
|
||||||
ticker_sell_up, mocker) -> None:
|
ticker_sell_up, mocker) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
|
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -3019,8 +3119,8 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
|||||||
)
|
)
|
||||||
|
|
||||||
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
|
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
|
||||||
assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=sell_reason)
|
sell_reason=sell_reason)
|
||||||
assert mock_insuf.call_count == 1
|
assert mock_insuf.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@ -3276,8 +3376,8 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo
|
|||||||
fetch_ticker=ticker_sell_down
|
fetch_ticker=ticker_sell_down
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
trade.close(ticker_sell_down()['bid'])
|
trade.close(ticker_sell_down()['bid'])
|
||||||
assert freqtrade.strategy.is_pair_locked(trade.pair)
|
assert freqtrade.strategy.is_pair_locked(trade.pair)
|
||||||
|
|
||||||
@ -4491,3 +4591,43 @@ def test_refind_lost_order(mocker, default_conf, fee, caplog):
|
|||||||
|
|
||||||
freqtrade.refind_lost_order(trades[4])
|
freqtrade.refind_lost_order(trades[4])
|
||||||
assert log_has(f"Error updating {order['id']}.", caplog)
|
assert log_has(f"Error updating {order['id']}.", caplog)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_valid_price(mocker, default_conf) -> None:
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_exchange(mocker)
|
||||||
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
freqtrade.config['custom_price_max_distance_ratio'] = 0.02
|
||||||
|
|
||||||
|
custom_price_string = "10"
|
||||||
|
custom_price_badstring = "10abc"
|
||||||
|
custom_price_float = 10.0
|
||||||
|
custom_price_int = 10
|
||||||
|
|
||||||
|
custom_price_over_max_alwd = 11.0
|
||||||
|
custom_price_under_min_alwd = 9.0
|
||||||
|
proposed_price = 10.1
|
||||||
|
|
||||||
|
valid_price_from_string = freqtrade.get_valid_price(custom_price_string, proposed_price)
|
||||||
|
valid_price_from_badstring = freqtrade.get_valid_price(custom_price_badstring, proposed_price)
|
||||||
|
valid_price_from_int = freqtrade.get_valid_price(custom_price_int, proposed_price)
|
||||||
|
valid_price_from_float = freqtrade.get_valid_price(custom_price_float, proposed_price)
|
||||||
|
|
||||||
|
valid_price_at_max_alwd = freqtrade.get_valid_price(custom_price_over_max_alwd, proposed_price)
|
||||||
|
valid_price_at_min_alwd = freqtrade.get_valid_price(custom_price_under_min_alwd, proposed_price)
|
||||||
|
|
||||||
|
assert isinstance(valid_price_from_string, float)
|
||||||
|
assert isinstance(valid_price_from_badstring, float)
|
||||||
|
assert isinstance(valid_price_from_int, float)
|
||||||
|
assert isinstance(valid_price_from_float, float)
|
||||||
|
|
||||||
|
assert valid_price_from_string == custom_price_float
|
||||||
|
assert valid_price_from_badstring == proposed_price
|
||||||
|
assert valid_price_from_int == custom_price_int
|
||||||
|
assert valid_price_from_float == custom_price_float
|
||||||
|
|
||||||
|
assert valid_price_at_max_alwd < custom_price_over_max_alwd
|
||||||
|
assert valid_price_at_max_alwd > proposed_price
|
||||||
|
|
||||||
|
assert valid_price_at_min_alwd > custom_price_under_min_alwd
|
||||||
|
assert valid_price_at_min_alwd < proposed_price
|
||||||
|
@ -9,7 +9,7 @@ from freqtrade.strategy.interface import SellCheckTuple
|
|||||||
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
||||||
|
|
||||||
|
|
||||||
def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||||
limit_buy_order, mocker) -> None:
|
limit_buy_order, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Tests workflow of selling stoploss_on_exchange.
|
Tests workflow of selling stoploss_on_exchange.
|
||||||
|
@ -70,7 +70,6 @@ def test_add_indicators(default_conf, testdatadir, caplog):
|
|||||||
indicators1 = {"ema10": {}}
|
indicators1 = {"ema10": {}}
|
||||||
indicators2 = {"macd": {"color": "red"}}
|
indicators2 = {"macd": {"color": "red"}}
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
# Generate buy/sell signals and indicators
|
# Generate buy/sell signals and indicators
|
||||||
@ -112,7 +111,6 @@ def test_add_areas(default_conf, testdatadir, caplog):
|
|||||||
"fill_to": "macdhist"}}
|
"fill_to": "macdhist"}}
|
||||||
|
|
||||||
ind_plain = {"macd": {"fill_to": "macdhist"}}
|
ind_plain = {"macd": {"fill_to": "macdhist"}}
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
# Generate buy/sell signals and indicators
|
# Generate buy/sell signals and indicators
|
||||||
@ -239,7 +237,6 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
|
|||||||
data = history.load_pair_history(pair=pair, timeframe='1m',
|
data = history.load_pair_history(pair=pair, timeframe='1m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
# Generate buy/sell signals and indicators
|
# Generate buy/sell signals and indicators
|
||||||
|
@ -157,13 +157,13 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
|
|||||||
assert result == result1
|
assert result == result1
|
||||||
|
|
||||||
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
|
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
|
||||||
freqtrade.execute_buy('ETH/USDT', result)
|
freqtrade.execute_entry('ETH/USDT', result)
|
||||||
|
|
||||||
result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT')
|
result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT')
|
||||||
assert result == result1
|
assert result == result1
|
||||||
|
|
||||||
# create 2 trades, order amount should be None
|
# create 2 trades, order amount should be None
|
||||||
freqtrade.execute_buy('LTC/BTC', result)
|
freqtrade.execute_entry('LTC/BTC', result)
|
||||||
|
|
||||||
result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT')
|
result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT')
|
||||||
assert result == 0
|
assert result == 0
|
||||||
|
File diff suppressed because one or more lines are too long
2
tests/testdata/backtest-result_new.json
vendored
2
tests/testdata/backtest-result_new.json
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user