merge develop
This commit is contained in:
commit
a1f88cca80
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.10.6-slim-bullseye as base
|
FROM python:3.10.7-slim-bullseye as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
|
@ -107,7 +107,7 @@ Strategy arguments:
|
|||||||
|
|
||||||
## Test your strategy with Backtesting
|
## Test your strategy with Backtesting
|
||||||
|
|
||||||
Now you have good Buy and Sell strategies and some historic data, you want to test it against
|
Now you have good Entry and exit strategies and some historic data, you want to test it against
|
||||||
real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
||||||
|
|
||||||
Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHLCV) data from `user_data/data/<exchange>` by default.
|
Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHLCV) data from `user_data/data/<exchange>` by default.
|
||||||
@ -215,7 +215,7 @@ Sometimes your account has certain fee rebates (fee reductions starting with a c
|
|||||||
To account for this in backtesting, you can use the `--fee` command line option to supply this value to backtesting.
|
To account for this in backtesting, you can use the `--fee` command line option to supply this value to backtesting.
|
||||||
This fee must be a ratio, and will be applied twice (once for trade entry, and once for trade exit).
|
This fee must be a ratio, and will be applied twice (once for trade entry, and once for trade exit).
|
||||||
|
|
||||||
For example, if the buying and selling commission fee is 0.1% (i.e., 0.001 written as ratio), then you would run backtesting as the following:
|
For example, if the commission fee per order is 0.1% (i.e., 0.001 written as ratio), then you would run backtesting as the following:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade backtesting --fee 0.001
|
freqtrade backtesting --fee 0.001
|
||||||
@ -252,9 +252,9 @@ The most important in the backtesting is to understand the result.
|
|||||||
A backtesting result will look like that:
|
A backtesting result will look like that:
|
||||||
|
|
||||||
```
|
```
|
||||||
========================================================= BACKTESTING REPORT ==========================================================
|
========================================================= BACKTESTING REPORT =========================================================
|
||||||
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% |
|
| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% |
|
||||||
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:|
|
|:---------|--------:|---------------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:|
|
||||||
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 |
|
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 |
|
||||||
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 |
|
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 |
|
||||||
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 |
|
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 |
|
||||||
@ -275,15 +275,15 @@ A backtesting result will look like that:
|
|||||||
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
|
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
|
||||||
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
||||||
========================================================= EXIT REASON STATS ==========================================================
|
========================================================= EXIT REASON STATS ==========================================================
|
||||||
| Exit Reason | Sells | Wins | Draws | Losses |
|
| Exit Reason | Exits | Wins | Draws | Losses |
|
||||||
|:-------------------|--------:|------:|-------:|--------:|
|
|:-------------------|--------:|------:|-------:|--------:|
|
||||||
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
||||||
| stop_loss | 166 | 0 | 0 | 166 |
|
| stop_loss | 166 | 0 | 0 | 166 |
|
||||||
| exit_signal | 56 | 36 | 0 | 20 |
|
| exit_signal | 56 | 36 | 0 | 20 |
|
||||||
| force_exit | 2 | 0 | 0 | 2 |
|
| force_exit | 2 | 0 | 0 | 2 |
|
||||||
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
||||||
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
||||||
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
|:---------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
||||||
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
|
||||||
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
|
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
|
||||||
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
|
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
|
||||||
@ -356,7 +356,7 @@ The column `Avg Profit %` shows the average profit for all trades made while the
|
|||||||
The column `Tot Profit %` shows instead the total profit % in relation to the starting balance.
|
The column `Tot Profit %` shows instead the total profit % in relation to the starting balance.
|
||||||
In the above results, we have a starting balance of 0.01 BTC and an absolute profit of 0.00762792 BTC - so the `Tot Profit %` will be `(0.00762792 / 0.01) * 100 ~= 76.2%`.
|
In the above results, we have a starting balance of 0.01 BTC and an absolute profit of 0.00762792 BTC - so the `Tot Profit %` will be `(0.00762792 / 0.01) * 100 ~= 76.2%`.
|
||||||
|
|
||||||
Your strategy performance is influenced by your buy strategy, your exit strategy, and also by the `minimal_roi` and `stop_loss` you have set.
|
Your strategy performance is influenced by your entry strategy, your exit strategy, and also by the `minimal_roi` and `stop_loss` you have set.
|
||||||
|
|
||||||
For example, if your `minimal_roi` is only `"0": 0.01` you cannot expect the bot to make more profit than 1% (because it will exit every time a trade reaches 1%).
|
For example, if your `minimal_roi` is only `"0": 0.01` you cannot expect the bot to make more profit than 1% (because it will exit every time a trade reaches 1%).
|
||||||
|
|
||||||
@ -515,7 +515,7 @@ You can then load the trades to perform further analysis as shown in the [data a
|
|||||||
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
|
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
|
||||||
|
|
||||||
- Exchange [trading limits](#trading-limits-in-backtesting) are respected
|
- Exchange [trading limits](#trading-limits-in-backtesting) are respected
|
||||||
- Buys happen at open-price
|
- Entries happen at open-price
|
||||||
- All orders are filled at the requested price (no slippage, no unfilled orders)
|
- All orders are filled at the requested price (no slippage, no unfilled orders)
|
||||||
- Exit-signal exits happen at open-price of the consecutive candle
|
- Exit-signal exits happen at open-price of the consecutive candle
|
||||||
- Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open
|
- Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open
|
||||||
@ -612,9 +612,9 @@ There will be an additional table comparing win/losses of the different strategi
|
|||||||
Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy.
|
Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy.
|
||||||
|
|
||||||
```
|
```
|
||||||
=========================================================== STRATEGY SUMMARY =========================================================================
|
=========================================================== STRATEGY SUMMARY ===========================================================================
|
||||||
| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % |
|
| Strategy | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % |
|
||||||
|:------------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:|
|
|:------------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:|
|
||||||
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 |
|
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 |
|
||||||
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 |
|
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 |
|
||||||
```
|
```
|
||||||
|
15
docs/faq.md
15
docs/faq.md
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Freqtrade supports spot trading only.
|
Freqtrade supports spot trading only.
|
||||||
|
|
||||||
### Can I open short positions?
|
### Can my bot open short positions?
|
||||||
|
|
||||||
Freqtrade can open short positions in futures markets.
|
Freqtrade can open short positions in futures markets.
|
||||||
This requires the strategy to be made for this - and `"trading_mode": "futures"` in the configuration.
|
This requires the strategy to be made for this - and `"trading_mode": "futures"` in the configuration.
|
||||||
@ -12,9 +12,9 @@ Please make sure to read the [relevant documentation page](leverage.md) first.
|
|||||||
|
|
||||||
In spot markets, you can in some cases use leveraged spot tokens, which reflect an inverted pair (eg. BTCUP/USD, BTCDOWN/USD, ETHBULL/USD, ETHBEAR/USD,...) which can be traded with Freqtrade.
|
In spot markets, you can in some cases use leveraged spot tokens, which reflect an inverted pair (eg. BTCUP/USD, BTCDOWN/USD, ETHBULL/USD, ETHBEAR/USD,...) which can be traded with Freqtrade.
|
||||||
|
|
||||||
### Can I trade options or futures?
|
### Can my bot trade options or futures?
|
||||||
|
|
||||||
Futures trading is supported for selected exchanges.
|
Futures trading is supported for selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an uptodate list of supported exchanges.
|
||||||
|
|
||||||
## Beginner Tips & Tricks
|
## Beginner Tips & Tricks
|
||||||
|
|
||||||
@ -22,6 +22,13 @@ Futures trading is supported for selected exchanges.
|
|||||||
|
|
||||||
## Freqtrade common issues
|
## Freqtrade common issues
|
||||||
|
|
||||||
|
### Can freqtrade open multiple positions on the same pair in parallel?
|
||||||
|
|
||||||
|
No. Freqtrade will only open one position per pair at a time.
|
||||||
|
You can however use the [`adjust_trade_position()` callback](strategy-callbacks.md#adjust-trade-position) to adjust an open position.
|
||||||
|
|
||||||
|
Backtesting provides an option for this in `--eps` - however this is only there to highlight "hidden" signals, and will not work in live.
|
||||||
|
|
||||||
### The bot does not start
|
### The bot does not start
|
||||||
|
|
||||||
Running the bot with `freqtrade trade --config config.json` shows the output `freqtrade: command not found`.
|
Running the bot with `freqtrade trade --config config.json` shows the output `freqtrade: command not found`.
|
||||||
@ -30,7 +37,7 @@ This could be caused by the following reasons:
|
|||||||
|
|
||||||
* The virtual environment is not active.
|
* The virtual environment is not active.
|
||||||
* Run `source .env/bin/activate` to activate the virtual environment.
|
* Run `source .env/bin/activate` to activate the virtual environment.
|
||||||
* The installation did not work correctly.
|
* The installation did not complete successfully.
|
||||||
* Please check the [Installation documentation](installation.md).
|
* Please check the [Installation documentation](installation.md).
|
||||||
|
|
||||||
### I have waited 5 minutes, why hasn't the bot made any trades yet?
|
### I have waited 5 minutes, why hasn't the bot made any trades yet?
|
||||||
|
@ -90,7 +90,8 @@ Example configuration showing the different settings:
|
|||||||
"trailing_stop_loss": "on",
|
"trailing_stop_loss": "on",
|
||||||
"stop_loss": "on",
|
"stop_loss": "on",
|
||||||
"stoploss_on_exchange": "on",
|
"stoploss_on_exchange": "on",
|
||||||
"custom_exit": "silent"
|
"custom_exit": "silent",
|
||||||
|
"partial_exit": "on"
|
||||||
},
|
},
|
||||||
"entry_cancel": "silent",
|
"entry_cancel": "silent",
|
||||||
"exit_cancel": "on",
|
"exit_cancel": "on",
|
||||||
|
@ -4,7 +4,7 @@ from typing import Any, Dict
|
|||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
from freqtrade.configuration.config_setup import setup_utils_configuration
|
from freqtrade.configuration.config_setup import setup_utils_configuration
|
||||||
from freqtrade.enums.runmode import RunMode
|
from freqtrade.enums import RunMode
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -228,9 +228,9 @@ def _download_pair_history(pair: str, *,
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("Current Start: %s",
|
logger.debug("Current Start: %s",
|
||||||
f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None')
|
f"{data.iloc[0]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None')
|
||||||
logger.debug("Current End: %s",
|
logger.debug("Current End: %s",
|
||||||
f"{data.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None')
|
f"{data.iloc[-1]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None')
|
||||||
|
|
||||||
# Default since_ms to 30 days if nothing is given
|
# Default since_ms to 30 days if nothing is given
|
||||||
new_data = exchange.get_historic_ohlcv(pair=pair,
|
new_data = exchange.get_historic_ohlcv(pair=pair,
|
||||||
@ -254,9 +254,9 @@ def _download_pair_history(pair: str, *,
|
|||||||
fill_missing=False, drop_incomplete=False)
|
fill_missing=False, drop_incomplete=False)
|
||||||
|
|
||||||
logger.debug("New Start: %s",
|
logger.debug("New Start: %s",
|
||||||
f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None')
|
f"{data.iloc[0]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None')
|
||||||
logger.debug("New End: %s",
|
logger.debug("New End: %s",
|
||||||
f"{data.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None')
|
f"{data.iloc[-1]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None')
|
||||||
|
|
||||||
data_handler.ohlcv_store(pair, timeframe, data=data, candle_type=candle_type)
|
data_handler.ohlcv_store(pair, timeframe, data=data, candle_type=candle_type)
|
||||||
return True
|
return True
|
||||||
|
@ -2509,8 +2509,13 @@ class Exchange:
|
|||||||
cache=False,
|
cache=False,
|
||||||
drop_incomplete=False,
|
drop_incomplete=False,
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
# we can't assume we always get histories - for example during exchange downtimes
|
||||||
funding_rates = candle_histories[funding_comb]
|
funding_rates = candle_histories[funding_comb]
|
||||||
mark_rates = candle_histories[mark_comb]
|
mark_rates = candle_histories[mark_comb]
|
||||||
|
except KeyError:
|
||||||
|
raise ExchangeError("Could not find funding rates.") from None
|
||||||
|
|
||||||
funding_mark_rates = self.combine_funding_and_mark(
|
funding_mark_rates = self.combine_funding_and_mark(
|
||||||
funding_rates=funding_rates, mark_rates=mark_rates)
|
funding_rates=funding_rates, mark_rates=mark_rates)
|
||||||
|
|
||||||
@ -2590,6 +2595,8 @@ class Exchange:
|
|||||||
:param is_short: trade direction
|
:param is_short: trade direction
|
||||||
:param amount: Trade amount
|
:param amount: Trade amount
|
||||||
:param open_date: Open date of the trade
|
:param open_date: Open date of the trade
|
||||||
|
:return: funding fee since open_date
|
||||||
|
:raies: ExchangeError if something goes wrong.
|
||||||
"""
|
"""
|
||||||
if self.trading_mode == TradingMode.FUTURES:
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
|
@ -4,8 +4,7 @@ from typing import Dict, List, Optional, Tuple
|
|||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
from freqtrade.constants import BuySell
|
from freqtrade.constants import BuySell
|
||||||
from freqtrade.enums import MarginMode, TradingMode
|
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||||
from freqtrade.enums.candletype import CandleType
|
|
||||||
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
|
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
|
||||||
from freqtrade.exchange import Exchange, date_minus_candles
|
from freqtrade.exchange import Exchange, date_minus_candles
|
||||||
from freqtrade.exchange.common import retrier
|
from freqtrade.exchange.common import retrier
|
||||||
|
@ -14,6 +14,7 @@ from numpy.typing import NDArray
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
|
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_seconds
|
||||||
@ -232,10 +233,10 @@ class IFreqaiModel(ABC):
|
|||||||
trained_timestamp = tr_train
|
trained_timestamp = tr_train
|
||||||
tr_train_startts_str = datetime.fromtimestamp(
|
tr_train_startts_str = datetime.fromtimestamp(
|
||||||
tr_train.startts,
|
tr_train.startts,
|
||||||
tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT)
|
||||||
tr_train_stopts_str = datetime.fromtimestamp(
|
tr_train_stopts_str = datetime.fromtimestamp(
|
||||||
tr_train.stopts,
|
tr_train.stopts,
|
||||||
tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Training {metadata['pair']}, {self.pair_it}/{self.total_pairs} pairs"
|
f"Training {metadata['pair']}, {self.pair_it}/{self.total_pairs} pairs"
|
||||||
f" from {tr_train_startts_str} to {tr_train_stopts_str}, {train_it}/{total_trains} "
|
f" from {tr_train_startts_str} to {tr_train_stopts_str}, {train_it}/{total_trains} "
|
||||||
@ -427,6 +428,11 @@ class IFreqaiModel(ABC):
|
|||||||
|
|
||||||
ft_params = self.freqai_info["feature_parameters"]
|
ft_params = self.freqai_info["feature_parameters"]
|
||||||
|
|
||||||
|
if ft_params.get('inlier_metric_window', 0):
|
||||||
|
dk.compute_inlier_metric(set_='train')
|
||||||
|
if self.freqai_info["data_split_parameters"]["test_size"] > 0:
|
||||||
|
dk.compute_inlier_metric(set_='test')
|
||||||
|
|
||||||
if ft_params.get(
|
if ft_params.get(
|
||||||
"principal_component_analysis", False
|
"principal_component_analysis", False
|
||||||
):
|
):
|
||||||
@ -446,11 +452,6 @@ class IFreqaiModel(ABC):
|
|||||||
dk.use_DBSCAN_to_remove_outliers(predict=False, eps=eps)
|
dk.use_DBSCAN_to_remove_outliers(predict=False, eps=eps)
|
||||||
self.dd.old_DBSCAN_eps[dk.pair] = dk.data['DBSCAN_eps']
|
self.dd.old_DBSCAN_eps[dk.pair] = dk.data['DBSCAN_eps']
|
||||||
|
|
||||||
if ft_params.get('inlier_metric_window', 0):
|
|
||||||
dk.compute_inlier_metric(set_='train')
|
|
||||||
if self.freqai_info["data_split_parameters"]["test_size"] > 0:
|
|
||||||
dk.compute_inlier_metric(set_='test')
|
|
||||||
|
|
||||||
if self.freqai_info["feature_parameters"].get('noise_standard_deviation', 0):
|
if self.freqai_info["feature_parameters"].get('noise_standard_deviation', 0):
|
||||||
dk.add_noise_to_training_features()
|
dk.add_noise_to_training_features()
|
||||||
|
|
||||||
@ -467,7 +468,7 @@ class IFreqaiModel(ABC):
|
|||||||
if ft_params.get(
|
if ft_params.get(
|
||||||
"principal_component_analysis", False
|
"principal_component_analysis", False
|
||||||
):
|
):
|
||||||
dk.pca_transform(dataframe)
|
dk.pca_transform(self.dk.data_dictionary['prediction_features'])
|
||||||
|
|
||||||
if ft_params.get("use_SVM_to_remove_outliers", False):
|
if ft_params.get("use_SVM_to_remove_outliers", False):
|
||||||
dk.use_SVM_to_remove_outliers(predict=True)
|
dk.use_SVM_to_remove_outliers(predict=True)
|
||||||
|
@ -281,6 +281,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
def update_funding_fees(self):
|
def update_funding_fees(self):
|
||||||
if self.trading_mode == TradingMode.FUTURES:
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
trades = Trade.get_open_trades()
|
trades = Trade.get_open_trades()
|
||||||
|
try:
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
funding_fees = self.exchange.get_funding_fees(
|
funding_fees = self.exchange.get_funding_fees(
|
||||||
pair=trade.pair,
|
pair=trade.pair,
|
||||||
@ -289,8 +290,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
open_date=trade.date_last_filled_utc
|
open_date=trade.date_last_filled_utc
|
||||||
)
|
)
|
||||||
trade.funding_fees = funding_fees
|
trade.funding_fees = funding_fees
|
||||||
else:
|
except ExchangeError:
|
||||||
return 0.0
|
logger.warning("Could not update funding fees for open trades.")
|
||||||
|
|
||||||
def startup_backpopulate_precision(self):
|
def startup_backpopulate_precision(self):
|
||||||
|
|
||||||
@ -671,14 +672,12 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
if not stake_amount:
|
if not stake_amount:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if pos_adjust:
|
msg = (f"Position adjust: about to create a new order for {pair} with stake: "
|
||||||
logger.info(f"Position adjust: about to create a new order for {pair} with stake: "
|
f"{stake_amount} for {trade}" if pos_adjust
|
||||||
f"{stake_amount} for {trade}")
|
else
|
||||||
else:
|
|
||||||
logger.info(
|
|
||||||
f"{name} signal found: about create a new trade for {pair} with stake_amount: "
|
f"{name} signal found: about create a new trade for {pair} with stake_amount: "
|
||||||
f"{stake_amount} ...")
|
f"{stake_amount} ...")
|
||||||
|
logger.info(msg)
|
||||||
amount = (stake_amount / enter_limit_requested) * leverage
|
amount = (stake_amount / enter_limit_requested) * leverage
|
||||||
order_type = ordertype or self.strategy.order_types['entry']
|
order_type = ordertype or self.strategy.order_types['entry']
|
||||||
|
|
||||||
@ -741,8 +740,13 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
# This is a new trade
|
# This is a new trade
|
||||||
if trade is None:
|
if trade is None:
|
||||||
|
funding_fees = 0.0
|
||||||
|
try:
|
||||||
funding_fees = self.exchange.get_funding_fees(
|
funding_fees = self.exchange.get_funding_fees(
|
||||||
pair=pair, amount=amount, is_short=is_short, open_date=open_date)
|
pair=pair, amount=amount, is_short=is_short, open_date=open_date)
|
||||||
|
except ExchangeError:
|
||||||
|
logger.warning("Could not find funding fee.")
|
||||||
|
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
base_currency=base_currency,
|
base_currency=base_currency,
|
||||||
@ -1493,12 +1497,16 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
:param exit_check: CheckTuple with signal and reason
|
:param exit_check: CheckTuple with signal and reason
|
||||||
:return: True if it succeeds False
|
:return: True if it succeeds False
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
trade.funding_fees = self.exchange.get_funding_fees(
|
trade.funding_fees = self.exchange.get_funding_fees(
|
||||||
pair=trade.pair,
|
pair=trade.pair,
|
||||||
amount=trade.amount,
|
amount=trade.amount,
|
||||||
is_short=trade.is_short,
|
is_short=trade.is_short,
|
||||||
open_date=trade.date_last_filled_utc,
|
open_date=trade.date_last_filled_utc,
|
||||||
)
|
)
|
||||||
|
except ExchangeError:
|
||||||
|
logger.warning("Could not update funding fee.")
|
||||||
|
|
||||||
exit_type = 'exit'
|
exit_type = 'exit'
|
||||||
exit_reason = exit_tag or exit_check.exit_reason
|
exit_reason = exit_tag or exit_check.exit_reason
|
||||||
if exit_check.exit_type in (
|
if exit_check.exit_type in (
|
||||||
|
@ -75,7 +75,8 @@ def _get_line_floatfmt(stake_currency: str) -> List[str]:
|
|||||||
'.2f', 'd', 's', 's']
|
'.2f', 'd', 's', 's']
|
||||||
|
|
||||||
|
|
||||||
def _get_line_header(first_column: str, stake_currency: str, direction: str = 'Buys') -> List[str]:
|
def _get_line_header(first_column: str, stake_currency: str,
|
||||||
|
direction: str = 'Entries') -> List[str]:
|
||||||
"""
|
"""
|
||||||
Generate header lines (goes in line with _generate_result_line())
|
Generate header lines (goes in line with _generate_result_line())
|
||||||
"""
|
"""
|
||||||
@ -642,7 +643,7 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
|
|||||||
if (tag_type == "enter_tag"):
|
if (tag_type == "enter_tag"):
|
||||||
headers = _get_line_header("TAG", stake_currency)
|
headers = _get_line_header("TAG", stake_currency)
|
||||||
else:
|
else:
|
||||||
headers = _get_line_header("TAG", stake_currency, 'Sells')
|
headers = _get_line_header("TAG", stake_currency, 'Exits')
|
||||||
floatfmt = _get_line_floatfmt(stake_currency)
|
floatfmt = _get_line_floatfmt(stake_currency)
|
||||||
output = [
|
output = [
|
||||||
[
|
[
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
from freqtrade.enums import RPCMessageType
|
||||||
from freqtrade.rpc import RPC
|
from freqtrade.rpc import RPC
|
||||||
from freqtrade.rpc.webhook import Webhook
|
from freqtrade.rpc.webhook import Webhook
|
||||||
|
|
||||||
|
@ -12,9 +12,8 @@ from pandas import DataFrame
|
|||||||
|
|
||||||
from freqtrade.constants import ListPairsWithTimeframes
|
from freqtrade.constants import ListPairsWithTimeframes
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType,
|
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection,
|
||||||
SignalType, TradingMode)
|
SignalTagType, SignalType, TradingMode)
|
||||||
from freqtrade.enums.runmode import RunMode
|
|
||||||
from freqtrade.exceptions import OperationalException, StrategyError
|
from freqtrade.exceptions import OperationalException, StrategyError
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
|
||||||
from freqtrade.persistence import Order, PairLocks, Trade
|
from freqtrade.persistence import Order, PairLocks, Trade
|
||||||
|
@ -7,7 +7,7 @@ from abc import ABC, abstractmethod
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Any, Optional, Sequence, Union
|
from typing import Any, Optional, Sequence, Union
|
||||||
|
|
||||||
from freqtrade.enums.hyperoptstate import HyperoptState
|
from freqtrade.enums import HyperoptState
|
||||||
from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer
|
from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
@ -6,7 +8,8 @@ from freqtrade.exchange import timeframe_to_minutes
|
|||||||
def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
|
def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
|
||||||
timeframe: str, timeframe_inf: str, ffill: bool = True,
|
timeframe: str, timeframe_inf: str, ffill: bool = True,
|
||||||
append_timeframe: bool = True,
|
append_timeframe: bool = True,
|
||||||
date_column: str = 'date') -> pd.DataFrame:
|
date_column: str = 'date',
|
||||||
|
suffix: Optional[str] = None) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Correctly merge informative samples to the original dataframe, avoiding lookahead bias.
|
Correctly merge informative samples to the original dataframe, avoiding lookahead bias.
|
||||||
|
|
||||||
@ -28,6 +31,8 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
|
|||||||
:param ffill: Forwardfill missing values - optional but usually required
|
:param ffill: Forwardfill missing values - optional but usually required
|
||||||
:param append_timeframe: Rename columns by appending timeframe.
|
:param append_timeframe: Rename columns by appending timeframe.
|
||||||
:param date_column: A custom date column name.
|
:param date_column: A custom date column name.
|
||||||
|
:param suffix: A string suffix to add at the end of the informative columns. If specified,
|
||||||
|
append_timeframe must be false.
|
||||||
:return: Merged dataframe
|
:return: Merged dataframe
|
||||||
:raise: ValueError if the secondary timeframe is shorter than the dataframe timeframe
|
:raise: ValueError if the secondary timeframe is shorter than the dataframe timeframe
|
||||||
"""
|
"""
|
||||||
@ -50,10 +55,16 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
|
|||||||
|
|
||||||
# Rename columns to be unique
|
# Rename columns to be unique
|
||||||
date_merge = 'date_merge'
|
date_merge = 'date_merge'
|
||||||
if append_timeframe:
|
if suffix and append_timeframe:
|
||||||
|
raise ValueError("You can not specify `append_timeframe` as True and a `suffix`.")
|
||||||
|
elif append_timeframe:
|
||||||
date_merge = f'date_merge_{timeframe_inf}'
|
date_merge = f'date_merge_{timeframe_inf}'
|
||||||
informative.columns = [f"{col}_{timeframe_inf}" for col in informative.columns]
|
informative.columns = [f"{col}_{timeframe_inf}" for col in informative.columns]
|
||||||
|
|
||||||
|
elif suffix:
|
||||||
|
date_merge = f'date_merge_{suffix}'
|
||||||
|
informative.columns = [f"{col}_{suffix}" for col in informative.columns]
|
||||||
|
|
||||||
# Combine the 2 dataframes
|
# Combine the 2 dataframes
|
||||||
# all indicators on the informative sample MUST be calculated before this point
|
# all indicators on the informative sample MUST be calculated before this point
|
||||||
if ffill:
|
if ffill:
|
||||||
|
@ -13,7 +13,7 @@ from pandas import DataFrame
|
|||||||
from pandas.testing import assert_frame_equal
|
from pandas.testing import assert_frame_equal
|
||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import AVAILABLE_DATAHANDLERS
|
from freqtrade.constants import AVAILABLE_DATAHANDLERS, DATETIME_PRINT_FORMAT
|
||||||
from freqtrade.data.converter import ohlcv_to_dataframe
|
from freqtrade.data.converter import ohlcv_to_dataframe
|
||||||
from freqtrade.data.history.hdf5datahandler import HDF5DataHandler
|
from freqtrade.data.history.hdf5datahandler import HDF5DataHandler
|
||||||
from freqtrade.data.history.history_utils import (_download_pair_history, _download_trades_history,
|
from freqtrade.data.history.history_utils import (_download_pair_history, _download_trades_history,
|
||||||
@ -386,7 +386,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None:
|
|||||||
assert td != len(data['UNITTEST/BTC'])
|
assert td != len(data['UNITTEST/BTC'])
|
||||||
start_real = data['UNITTEST/BTC'].iloc[0, 0]
|
start_real = data['UNITTEST/BTC'].iloc[0, 0]
|
||||||
assert log_has(f'UNITTEST/BTC, spot, 5m, '
|
assert log_has(f'UNITTEST/BTC, spot, 5m, '
|
||||||
f'data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}',
|
f'data starts at {start_real.strftime(DATETIME_PRINT_FORMAT)}',
|
||||||
caplog)
|
caplog)
|
||||||
# Make sure we start fresh - test missing data at end
|
# Make sure we start fresh - test missing data at end
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
@ -401,7 +401,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None:
|
|||||||
# Shift endtime with +5 - as last candle is dropped (partial candle)
|
# Shift endtime with +5 - as last candle is dropped (partial candle)
|
||||||
end_real = arrow.get(data['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5)
|
end_real = arrow.get(data['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5)
|
||||||
assert log_has(f'UNITTEST/BTC, spot, 5m, '
|
assert log_has(f'UNITTEST/BTC, spot, 5m, '
|
||||||
f'data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}',
|
f'data ends at {end_real.strftime(DATETIME_PRINT_FORMAT)}',
|
||||||
caplog)
|
caplog)
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,8 +11,9 @@ import pytest
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||||
from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
||||||
OperationalException, PricingError, TemporaryError)
|
InvalidOrderException, OperationalException, PricingError,
|
||||||
|
TemporaryError)
|
||||||
from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision,
|
from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision,
|
||||||
date_minus_candles, market_is_active, price_to_precision,
|
date_minus_candles, market_is_active, price_to_precision,
|
||||||
timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
|
timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
|
||||||
@ -4179,17 +4180,24 @@ def test__fetch_and_calculate_funding_fees(
|
|||||||
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
|
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
|
||||||
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
|
type(api_mock).has = PropertyMock(return_value={'fetchFundingRateHistory': True})
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
|
ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock(
|
mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock(
|
||||||
return_value=['1h', '4h', '8h']))
|
return_value=['1h', '4h', '8h']))
|
||||||
funding_fees = exchange._fetch_and_calculate_funding_fees(
|
funding_fees = ex._fetch_and_calculate_funding_fees(
|
||||||
pair='ADA/USDT', amount=amount, is_short=True, open_date=d1, close_date=d2)
|
pair='ADA/USDT', amount=amount, is_short=True, open_date=d1, close_date=d2)
|
||||||
assert pytest.approx(funding_fees) == expected_fees
|
assert pytest.approx(funding_fees) == expected_fees
|
||||||
# Fees for Longs are inverted
|
# Fees for Longs are inverted
|
||||||
funding_fees = exchange._fetch_and_calculate_funding_fees(
|
funding_fees = ex._fetch_and_calculate_funding_fees(
|
||||||
pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
|
pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
|
||||||
assert pytest.approx(funding_fees) == -expected_fees
|
assert pytest.approx(funding_fees) == -expected_fees
|
||||||
|
|
||||||
|
# Return empty "refresh_latest"
|
||||||
|
mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", return_value={})
|
||||||
|
ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
|
||||||
|
with pytest.raises(ExchangeError, match="Could not find funding rates."):
|
||||||
|
ex._fetch_and_calculate_funding_fees(
|
||||||
|
pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('exchange,expected_fees', [
|
@pytest.mark.parametrize('exchange,expected_fees', [
|
||||||
('binance', -0.0009140999999999999),
|
('binance', -0.0009140999999999999),
|
||||||
|
@ -4,8 +4,7 @@ from unittest.mock import MagicMock, PropertyMock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.enums import MarginMode, TradingMode
|
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||||
from freqtrade.enums.candletype import CandleType
|
|
||||||
from freqtrade.exchange.exchange import timeframe_to_minutes
|
from freqtrade.exchange.exchange import timeframe_to_minutes
|
||||||
from tests.conftest import get_mock_coro, get_patched_exchange, log_has
|
from tests.conftest import get_mock_coro, get_patched_exchange, log_has
|
||||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||||
|
@ -40,14 +40,14 @@ def test_text_table_bt_results():
|
|||||||
)
|
)
|
||||||
|
|
||||||
result_str = (
|
result_str = (
|
||||||
'| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |'
|
'| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | '
|
||||||
' Avg Duration | Win Draw Loss Win% |\n'
|
'Tot Profit % | Avg Duration | Win Draw Loss Win% |\n'
|
||||||
'|---------+--------+----------------+----------------+------------------+----------------+'
|
'|---------+-----------+----------------+----------------+------------------+'
|
||||||
'----------------+-------------------------|\n'
|
'----------------+----------------+-------------------------|\n'
|
||||||
'| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
|
'| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | '
|
||||||
' 0:20:00 | 2 0 1 66.7 |\n'
|
'12.50 | 0:20:00 | 2 0 1 66.7 |\n'
|
||||||
'| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
|
'| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | '
|
||||||
' 0:20:00 | 2 0 1 66.7 |'
|
'12.50 | 0:20:00 | 2 0 1 66.7 |'
|
||||||
)
|
)
|
||||||
|
|
||||||
pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC',
|
pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC',
|
||||||
@ -402,9 +402,9 @@ def test_text_table_strategy(testdatadir):
|
|||||||
bt_res_data_comparison = bt_res_data.pop('strategy_comparison')
|
bt_res_data_comparison = bt_res_data.pop('strategy_comparison')
|
||||||
|
|
||||||
result_str = (
|
result_str = (
|
||||||
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
|
'| Strategy | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC |'
|
||||||
' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n'
|
' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n'
|
||||||
'|----------------+--------+----------------+----------------+------------------+'
|
'|----------------+-----------+----------------+----------------+------------------+'
|
||||||
'----------------+----------------+-------------------------+-----------------------|\n'
|
'----------------+----------------+-------------------------+-----------------------|\n'
|
||||||
'| StrategyTestV2 | 179 | 0.08 | 14.39 | 0.02608550 |'
|
'| StrategyTestV2 | 179 | 0.08 | 14.39 | 0.02608550 |'
|
||||||
' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n'
|
' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n'
|
||||||
|
@ -11,8 +11,7 @@ from pandas import DataFrame
|
|||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.data.history import load_data
|
from freqtrade.data.history import load_data
|
||||||
from freqtrade.enums import ExitCheckTuple, ExitType, SignalDirection
|
from freqtrade.enums import ExitCheckTuple, ExitType, HyperoptState, SignalDirection
|
||||||
from freqtrade.enums.hyperoptstate import HyperoptState
|
|
||||||
from freqtrade.exceptions import OperationalException, StrategyError
|
from freqtrade.exceptions import OperationalException, StrategyError
|
||||||
from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer
|
from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer
|
||||||
from freqtrade.optimize.space import SKDecimal
|
from freqtrade.optimize.space import SKDecimal
|
||||||
|
@ -117,6 +117,29 @@ def test_merge_informative_pair_lower():
|
|||||||
merge_informative_pair(data, informative, '1h', '15m', ffill=True)
|
merge_informative_pair(data, informative, '1h', '15m', ffill=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_merge_informative_pair_suffix():
|
||||||
|
data = generate_test_data('15m', 20)
|
||||||
|
informative = generate_test_data('1h', 20)
|
||||||
|
|
||||||
|
result = merge_informative_pair(data, informative, '15m', '1h',
|
||||||
|
append_timeframe=False, suffix="suf")
|
||||||
|
|
||||||
|
assert 'date' in result.columns
|
||||||
|
assert result['date'].equals(data['date'])
|
||||||
|
assert 'date_suf' in result.columns
|
||||||
|
|
||||||
|
assert 'open_suf' in result.columns
|
||||||
|
assert 'open_1h' not in result.columns
|
||||||
|
|
||||||
|
|
||||||
|
def test_merge_informative_pair_suffix_append_timeframe():
|
||||||
|
data = generate_test_data('15m', 20)
|
||||||
|
informative = generate_test_data('1h', 20)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match=r"You can not specify `append_timeframe` .*"):
|
||||||
|
merge_informative_pair(data, informative, '15m', '1h', suffix="suf")
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss_from_open():
|
def test_stoploss_from_open():
|
||||||
open_price_ranges = [
|
open_price_ranges = [
|
||||||
[0.01, 1.00, 30],
|
[0.01, 1.00, 30],
|
||||||
|
@ -506,7 +506,7 @@ def test_create_trades_multiple_trades(
|
|||||||
|
|
||||||
|
|
||||||
def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
|
def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
|
||||||
limit_buy_order_usdt_open) -> None:
|
limit_buy_order_usdt_open, caplog) -> None:
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
default_conf_usdt['max_open_trades'] = 4
|
default_conf_usdt['max_open_trades'] = 4
|
||||||
@ -515,6 +515,7 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
|
|||||||
fetch_ticker=ticker_usdt,
|
fetch_ticker=ticker_usdt,
|
||||||
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
|
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_funding_fees=MagicMock(side_effect=ExchangeError()),
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
patch_get_signal(freqtrade)
|
patch_get_signal(freqtrade)
|
||||||
@ -522,6 +523,7 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
|
|||||||
# Create 2 existing trades
|
# Create 2 existing trades
|
||||||
freqtrade.execute_entry('ETH/USDT', default_conf_usdt['stake_amount'])
|
freqtrade.execute_entry('ETH/USDT', default_conf_usdt['stake_amount'])
|
||||||
freqtrade.execute_entry('NEO/BTC', default_conf_usdt['stake_amount'])
|
freqtrade.execute_entry('NEO/BTC', default_conf_usdt['stake_amount'])
|
||||||
|
assert log_has("Could not find funding fee.", caplog)
|
||||||
|
|
||||||
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
|
||||||
@ -3666,7 +3668,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
|
|||||||
(True, 29.70297029, 2.2, 2.3, -8.63762376, -0.1443212, 'loss'),
|
(True, 29.70297029, 2.2, 2.3, -8.63762376, -0.1443212, 'loss'),
|
||||||
])
|
])
|
||||||
def test_execute_trade_exit_market_order(
|
def test_execute_trade_exit_market_order(
|
||||||
default_conf_usdt, ticker_usdt, fee, is_short, current_rate, amount,
|
default_conf_usdt, ticker_usdt, fee, is_short, current_rate, amount, caplog,
|
||||||
limit, profit_amount, profit_ratio, profit_or_loss, ticker_usdt_sell_up, mocker
|
limit, profit_amount, profit_ratio, profit_or_loss, ticker_usdt_sell_up, mocker
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@ -3694,6 +3696,7 @@ def test_execute_trade_exit_market_order(
|
|||||||
fetch_ticker=ticker_usdt,
|
fetch_ticker=ticker_usdt,
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
_is_dry_limit_order_filled=MagicMock(return_value=True),
|
_is_dry_limit_order_filled=MagicMock(return_value=True),
|
||||||
|
get_funding_fees=MagicMock(side_effect=ExchangeError()),
|
||||||
)
|
)
|
||||||
patch_whitelist(mocker, default_conf_usdt)
|
patch_whitelist(mocker, default_conf_usdt)
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
@ -3719,6 +3722,7 @@ def test_execute_trade_exit_market_order(
|
|||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||||
exit_check=ExitCheckTuple(exit_type=ExitType.ROI)
|
exit_check=ExitCheckTuple(exit_type=ExitType.ROI)
|
||||||
)
|
)
|
||||||
|
assert log_has("Could not update funding fee.", caplog)
|
||||||
|
|
||||||
assert not trade.is_open
|
assert not trade.is_open
|
||||||
assert pytest.approx(trade.close_profit) == profit_ratio
|
assert pytest.approx(trade.close_profit) == profit_ratio
|
||||||
@ -5430,6 +5434,16 @@ def test_update_funding_fees(
|
|||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_funding_fees_error(mocker, default_conf, caplog):
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_funding_fees', side_effect=ExchangeError())
|
||||||
|
default_conf['trading_mode'] = 'futures'
|
||||||
|
default_conf['margin_mode'] = 'isolated'
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
freqtrade.update_funding_fees()
|
||||||
|
|
||||||
|
log_has("Could not update funding fees for open trades.", caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
@ -9,7 +9,7 @@ import arrow
|
|||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import create_engine, text
|
from sqlalchemy import create_engine, text
|
||||||
|
|
||||||
from freqtrade import constants
|
from freqtrade.constants import DATETIME_PRINT_FORMAT, DEFAULT_DB_PROD_URL
|
||||||
from freqtrade.enums import TradingMode
|
from freqtrade.enums import TradingMode
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
|
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
|
||||||
@ -52,7 +52,7 @@ def test_init_invalid_db_url():
|
|||||||
|
|
||||||
def test_init_prod_db(default_conf, mocker):
|
def test_init_prod_db(default_conf, mocker):
|
||||||
default_conf.update({'dry_run': False})
|
default_conf.update({'dry_run': False})
|
||||||
default_conf.update({'db_url': constants.DEFAULT_DB_PROD_URL})
|
default_conf.update({'db_url': DEFAULT_DB_PROD_URL})
|
||||||
|
|
||||||
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
|
create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock())
|
||||||
|
|
||||||
@ -1739,7 +1739,7 @@ def test_to_json(fee):
|
|||||||
'base_currency': 'ADA',
|
'base_currency': 'ADA',
|
||||||
'quote_currency': 'USDT',
|
'quote_currency': 'USDT',
|
||||||
'is_open': None,
|
'is_open': None,
|
||||||
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||||
'open_order_id': 'dry_run_buy_12345',
|
'open_order_id': 'dry_run_buy_12345',
|
||||||
'close_date': None,
|
'close_date': None,
|
||||||
@ -1817,9 +1817,9 @@ def test_to_json(fee):
|
|||||||
'pair': 'XRP/BTC',
|
'pair': 'XRP/BTC',
|
||||||
'base_currency': 'XRP',
|
'base_currency': 'XRP',
|
||||||
'quote_currency': 'BTC',
|
'quote_currency': 'BTC',
|
||||||
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
|
||||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||||
'close_date': trade.close_date.strftime("%Y-%m-%d %H:%M:%S"),
|
'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
|
||||||
'close_timestamp': int(trade.close_date.timestamp() * 1000),
|
'close_timestamp': int(trade.close_date.timestamp() * 1000),
|
||||||
'open_rate': 0.123,
|
'open_rate': 0.123,
|
||||||
'close_rate': 0.125,
|
'close_rate': 0.125,
|
||||||
|
Loading…
Reference in New Issue
Block a user