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
|
||||
ENV LANG C.UTF-8
|
||||
|
@ -107,7 +107,7 @@ Strategy arguments:
|
||||
|
||||
## 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).
|
||||
|
||||
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.
|
||||
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
|
||||
freqtrade backtesting --fee 0.001
|
||||
@ -252,41 +252,41 @@ The most important in the backtesting is to understand the result.
|
||||
A backtesting result will look like that:
|
||||
|
||||
```
|
||||
========================================================= BACKTESTING REPORT ==========================================================
|
||||
| Pair | Buys | 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 |
|
||||
| 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 |
|
||||
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 |
|
||||
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 |
|
||||
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 |
|
||||
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 |
|
||||
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 |
|
||||
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 |
|
||||
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 |
|
||||
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 |
|
||||
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 |
|
||||
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 |
|
||||
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 |
|
||||
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 |
|
||||
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 |
|
||||
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
|
||||
| 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 |
|
||||
========================================================= BACKTESTING REPORT =========================================================
|
||||
| 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 |
|
||||
| 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 |
|
||||
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 |
|
||||
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 |
|
||||
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 |
|
||||
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 |
|
||||
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 |
|
||||
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 |
|
||||
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 |
|
||||
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 |
|
||||
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 |
|
||||
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 |
|
||||
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 |
|
||||
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 |
|
||||
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 |
|
||||
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
|
||||
| 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 |
|
||||
========================================================= EXIT REASON STATS ==========================================================
|
||||
| Exit Reason | Sells | Wins | Draws | Losses |
|
||||
| Exit Reason | Exits | Wins | Draws | Losses |
|
||||
|:-------------------|--------:|------:|-------:|--------:|
|
||||
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
||||
| stop_loss | 166 | 0 | 0 | 166 |
|
||||
| exit_signal | 56 | 36 | 0 | 20 |
|
||||
| force_exit | 2 | 0 | 0 | 2 |
|
||||
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
||||
| Pair | Buys | 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
================== SUMMARY METRICS ==================
|
||||
| Metric | Value |
|
||||
|-----------------------------+---------------------|
|
||||
@ -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.
|
||||
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%).
|
||||
|
||||
@ -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:
|
||||
|
||||
- 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)
|
||||
- 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
|
||||
@ -612,11 +612,11 @@ 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.
|
||||
|
||||
```
|
||||
=========================================================== STRATEGY SUMMARY =========================================================================
|
||||
| Strategy | Buys | 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 |
|
||||
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 |
|
||||
=========================================================== STRATEGY SUMMARY ===========================================================================
|
||||
| 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 |
|
||||
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 |
|
||||
```
|
||||
|
||||
## Next step
|
||||
|
15
docs/faq.md
15
docs/faq.md
@ -4,7 +4,7 @@
|
||||
|
||||
Freqtrade supports spot trading only.
|
||||
|
||||
### Can I open short positions?
|
||||
### Can my bot open short positions?
|
||||
|
||||
Freqtrade can open short positions in futures markets.
|
||||
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.
|
||||
|
||||
### 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
|
||||
|
||||
@ -22,6 +22,13 @@ Futures trading is supported for selected exchanges.
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
* 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).
|
||||
|
||||
### 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",
|
||||
"stop_loss": "on",
|
||||
"stoploss_on_exchange": "on",
|
||||
"custom_exit": "silent"
|
||||
"custom_exit": "silent",
|
||||
"partial_exit": "on"
|
||||
},
|
||||
"entry_cancel": "silent",
|
||||
"exit_cancel": "on",
|
||||
|
@ -4,7 +4,7 @@ from typing import Any, Dict
|
||||
from sqlalchemy import func
|
||||
|
||||
from freqtrade.configuration.config_setup import setup_utils_configuration
|
||||
from freqtrade.enums.runmode import RunMode
|
||||
from freqtrade.enums import RunMode
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -228,9 +228,9 @@ def _download_pair_history(pair: str, *,
|
||||
)
|
||||
|
||||
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",
|
||||
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
|
||||
new_data = exchange.get_historic_ohlcv(pair=pair,
|
||||
@ -254,9 +254,9 @@ def _download_pair_history(pair: str, *,
|
||||
fill_missing=False, drop_incomplete=False)
|
||||
|
||||
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",
|
||||
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)
|
||||
return True
|
||||
|
@ -2509,8 +2509,13 @@ class Exchange:
|
||||
cache=False,
|
||||
drop_incomplete=False,
|
||||
)
|
||||
funding_rates = candle_histories[funding_comb]
|
||||
mark_rates = candle_histories[mark_comb]
|
||||
try:
|
||||
# we can't assume we always get histories - for example during exchange downtimes
|
||||
funding_rates = candle_histories[funding_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_rates=funding_rates, mark_rates=mark_rates)
|
||||
|
||||
@ -2590,6 +2595,8 @@ class Exchange:
|
||||
:param is_short: trade direction
|
||||
:param amount: Trade amount
|
||||
: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._config['dry_run']:
|
||||
|
@ -4,8 +4,7 @@ from typing import Dict, List, Optional, Tuple
|
||||
import ccxt
|
||||
|
||||
from freqtrade.constants import BuySell
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from freqtrade.enums.candletype import CandleType
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
|
||||
from freqtrade.exchange import Exchange, date_minus_candles
|
||||
from freqtrade.exchange.common import retrier
|
||||
|
@ -14,6 +14,7 @@ from numpy.typing import NDArray
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange import timeframe_to_seconds
|
||||
@ -232,10 +233,10 @@ class IFreqaiModel(ABC):
|
||||
trained_timestamp = tr_train
|
||||
tr_train_startts_str = datetime.fromtimestamp(
|
||||
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,
|
||||
tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||||
tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT)
|
||||
logger.info(
|
||||
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} "
|
||||
@ -427,6 +428,11 @@ class IFreqaiModel(ABC):
|
||||
|
||||
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(
|
||||
"principal_component_analysis", False
|
||||
):
|
||||
@ -446,11 +452,6 @@ class IFreqaiModel(ABC):
|
||||
dk.use_DBSCAN_to_remove_outliers(predict=False, eps=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):
|
||||
dk.add_noise_to_training_features()
|
||||
|
||||
@ -467,7 +468,7 @@ class IFreqaiModel(ABC):
|
||||
if ft_params.get(
|
||||
"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):
|
||||
dk.use_SVM_to_remove_outliers(predict=True)
|
||||
|
@ -281,16 +281,17 @@ class FreqtradeBot(LoggingMixin):
|
||||
def update_funding_fees(self):
|
||||
if self.trading_mode == TradingMode.FUTURES:
|
||||
trades = Trade.get_open_trades()
|
||||
for trade in trades:
|
||||
funding_fees = self.exchange.get_funding_fees(
|
||||
pair=trade.pair,
|
||||
amount=trade.amount,
|
||||
is_short=trade.is_short,
|
||||
open_date=trade.date_last_filled_utc
|
||||
)
|
||||
trade.funding_fees = funding_fees
|
||||
else:
|
||||
return 0.0
|
||||
try:
|
||||
for trade in trades:
|
||||
funding_fees = self.exchange.get_funding_fees(
|
||||
pair=trade.pair,
|
||||
amount=trade.amount,
|
||||
is_short=trade.is_short,
|
||||
open_date=trade.date_last_filled_utc
|
||||
)
|
||||
trade.funding_fees = funding_fees
|
||||
except ExchangeError:
|
||||
logger.warning("Could not update funding fees for open trades.")
|
||||
|
||||
def startup_backpopulate_precision(self):
|
||||
|
||||
@ -671,14 +672,12 @@ class FreqtradeBot(LoggingMixin):
|
||||
if not stake_amount:
|
||||
return False
|
||||
|
||||
if pos_adjust:
|
||||
logger.info(f"Position adjust: about to create a new order for {pair} with stake: "
|
||||
f"{stake_amount} for {trade}")
|
||||
else:
|
||||
logger.info(
|
||||
f"{name} signal found: about create a new trade for {pair} with stake_amount: "
|
||||
f"{stake_amount} ...")
|
||||
|
||||
msg = (f"Position adjust: about to create a new order for {pair} with stake: "
|
||||
f"{stake_amount} for {trade}" if pos_adjust
|
||||
else
|
||||
f"{name} signal found: about create a new trade for {pair} with stake_amount: "
|
||||
f"{stake_amount} ...")
|
||||
logger.info(msg)
|
||||
amount = (stake_amount / enter_limit_requested) * leverage
|
||||
order_type = ordertype or self.strategy.order_types['entry']
|
||||
|
||||
@ -741,8 +740,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
# This is a new trade
|
||||
if trade is None:
|
||||
funding_fees = self.exchange.get_funding_fees(
|
||||
pair=pair, amount=amount, is_short=is_short, open_date=open_date)
|
||||
funding_fees = 0.0
|
||||
try:
|
||||
funding_fees = self.exchange.get_funding_fees(
|
||||
pair=pair, amount=amount, is_short=is_short, open_date=open_date)
|
||||
except ExchangeError:
|
||||
logger.warning("Could not find funding fee.")
|
||||
|
||||
trade = Trade(
|
||||
pair=pair,
|
||||
base_currency=base_currency,
|
||||
@ -1493,12 +1497,16 @@ class FreqtradeBot(LoggingMixin):
|
||||
:param exit_check: CheckTuple with signal and reason
|
||||
:return: True if it succeeds False
|
||||
"""
|
||||
trade.funding_fees = self.exchange.get_funding_fees(
|
||||
pair=trade.pair,
|
||||
amount=trade.amount,
|
||||
is_short=trade.is_short,
|
||||
open_date=trade.date_last_filled_utc,
|
||||
)
|
||||
try:
|
||||
trade.funding_fees = self.exchange.get_funding_fees(
|
||||
pair=trade.pair,
|
||||
amount=trade.amount,
|
||||
is_short=trade.is_short,
|
||||
open_date=trade.date_last_filled_utc,
|
||||
)
|
||||
except ExchangeError:
|
||||
logger.warning("Could not update funding fee.")
|
||||
|
||||
exit_type = 'exit'
|
||||
exit_reason = exit_tag or exit_check.exit_reason
|
||||
if exit_check.exit_type in (
|
||||
|
@ -75,7 +75,8 @@ def _get_line_floatfmt(stake_currency: str) -> List[str]:
|
||||
'.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())
|
||||
"""
|
||||
@ -642,7 +643,7 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
|
||||
if (tag_type == "enter_tag"):
|
||||
headers = _get_line_header("TAG", stake_currency)
|
||||
else:
|
||||
headers = _get_line_header("TAG", stake_currency, 'Sells')
|
||||
headers = _get_line_header("TAG", stake_currency, 'Exits')
|
||||
floatfmt = _get_line_floatfmt(stake_currency)
|
||||
output = [
|
||||
[
|
||||
|
@ -1,7 +1,7 @@
|
||||
import logging
|
||||
from typing import Any, Dict
|
||||
|
||||
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
||||
from freqtrade.enums import RPCMessageType
|
||||
from freqtrade.rpc import RPC
|
||||
from freqtrade.rpc.webhook import Webhook
|
||||
|
||||
|
@ -12,9 +12,8 @@ from pandas import DataFrame
|
||||
|
||||
from freqtrade.constants import ListPairsWithTimeframes
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType,
|
||||
SignalType, TradingMode)
|
||||
from freqtrade.enums.runmode import RunMode
|
||||
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection,
|
||||
SignalTagType, SignalType, TradingMode)
|
||||
from freqtrade.exceptions import OperationalException, StrategyError
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
|
||||
from freqtrade.persistence import Order, PairLocks, Trade
|
||||
|
@ -7,7 +7,7 @@ from abc import ABC, abstractmethod
|
||||
from contextlib import suppress
|
||||
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
|
||||
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
|
||||
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,
|
||||
timeframe: str, timeframe_inf: str, ffill: 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.
|
||||
|
||||
@ -28,6 +31,8 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
|
||||
:param ffill: Forwardfill missing values - optional but usually required
|
||||
:param append_timeframe: Rename columns by appending timeframe.
|
||||
: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
|
||||
: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
|
||||
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}'
|
||||
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
|
||||
# all indicators on the informative sample MUST be calculated before this point
|
||||
if ffill:
|
||||
|
@ -13,7 +13,7 @@ from pandas import DataFrame
|
||||
from pandas.testing import assert_frame_equal
|
||||
|
||||
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.history.hdf5datahandler import HDF5DataHandler
|
||||
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'])
|
||||
start_real = data['UNITTEST/BTC'].iloc[0, 0]
|
||||
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)
|
||||
# Make sure we start fresh - test missing data at end
|
||||
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)
|
||||
end_real = arrow.get(data['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5)
|
||||
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)
|
||||
|
||||
|
||||
|
@ -11,8 +11,9 @@ import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exceptions import (DDosProtection, DependencyException, InvalidOrderException,
|
||||
OperationalException, PricingError, TemporaryError)
|
||||
from freqtrade.exceptions import (DDosProtection, DependencyException, ExchangeError,
|
||||
InvalidOrderException, OperationalException, PricingError,
|
||||
TemporaryError)
|
||||
from freqtrade.exchange import (Binance, Bittrex, Exchange, Kraken, amount_to_precision,
|
||||
date_minus_candles, market_is_active, price_to_precision,
|
||||
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={'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(
|
||||
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)
|
||||
assert pytest.approx(funding_fees) == expected_fees
|
||||
# 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)
|
||||
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', [
|
||||
('binance', -0.0009140999999999999),
|
||||
|
@ -4,8 +4,7 @@ from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
|
||||
from freqtrade.enums import MarginMode, TradingMode
|
||||
from freqtrade.enums.candletype import CandleType
|
||||
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||
from freqtrade.exchange.exchange import timeframe_to_minutes
|
||||
from tests.conftest import get_mock_coro, get_patched_exchange, log_has
|
||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||
|
@ -40,14 +40,14 @@ def test_text_table_bt_results():
|
||||
)
|
||||
|
||||
result_str = (
|
||||
'| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |'
|
||||
' Avg Duration | Win Draw Loss Win% |\n'
|
||||
'|---------+--------+----------------+----------------+------------------+----------------+'
|
||||
'----------------+-------------------------|\n'
|
||||
'| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
|
||||
' 0:20:00 | 2 0 1 66.7 |\n'
|
||||
'| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
|
||||
' 0:20:00 | 2 0 1 66.7 |'
|
||||
'| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | '
|
||||
'Tot Profit % | Avg Duration | Win Draw Loss Win% |\n'
|
||||
'|---------+-----------+----------------+----------------+------------------+'
|
||||
'----------------+----------------+-------------------------|\n'
|
||||
'| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | '
|
||||
'12.50 | 0:20:00 | 2 0 1 66.7 |\n'
|
||||
'| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | '
|
||||
'12.50 | 0:20:00 | 2 0 1 66.7 |'
|
||||
)
|
||||
|
||||
pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC',
|
||||
@ -402,13 +402,13 @@ def test_text_table_strategy(testdatadir):
|
||||
bt_res_data_comparison = bt_res_data.pop('strategy_comparison')
|
||||
|
||||
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'
|
||||
'|----------------+--------+----------------+----------------+------------------+'
|
||||
'|----------------+-----------+----------------+----------------+------------------+'
|
||||
'----------------+----------------+-------------------------+-----------------------|\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'
|
||||
'| TestStrategy | 179 | 0.08 | 14.39 | 0.02608550 |'
|
||||
'| TestStrategy | 179 | 0.08 | 14.39 | 0.02608550 |'
|
||||
' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |'
|
||||
)
|
||||
|
||||
|
@ -11,8 +11,7 @@ from pandas import DataFrame
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.enums import ExitCheckTuple, ExitType, SignalDirection
|
||||
from freqtrade.enums.hyperoptstate import HyperoptState
|
||||
from freqtrade.enums import ExitCheckTuple, ExitType, HyperoptState, SignalDirection
|
||||
from freqtrade.exceptions import OperationalException, StrategyError
|
||||
from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer
|
||||
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)
|
||||
|
||||
|
||||
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():
|
||||
open_price_ranges = [
|
||||
[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,
|
||||
limit_buy_order_usdt_open) -> None:
|
||||
limit_buy_order_usdt_open, caplog) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
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,
|
||||
create_order=MagicMock(return_value=limit_buy_order_usdt_open),
|
||||
get_fee=fee,
|
||||
get_funding_fees=MagicMock(side_effect=ExchangeError()),
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
patch_get_signal(freqtrade)
|
||||
@ -522,6 +523,7 @@ def test_create_trades_preopen(default_conf_usdt, ticker_usdt, fee, mocker,
|
||||
# Create 2 existing trades
|
||||
freqtrade.execute_entry('ETH/USDT', 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
|
||||
# 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'),
|
||||
])
|
||||
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
|
||||
) -> None:
|
||||
"""
|
||||
@ -3694,6 +3696,7 @@ def test_execute_trade_exit_market_order(
|
||||
fetch_ticker=ticker_usdt,
|
||||
get_fee=fee,
|
||||
_is_dry_limit_order_filled=MagicMock(return_value=True),
|
||||
get_funding_fees=MagicMock(side_effect=ExchangeError()),
|
||||
)
|
||||
patch_whitelist(mocker, 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'],
|
||||
exit_check=ExitCheckTuple(exit_type=ExitType.ROI)
|
||||
)
|
||||
assert log_has("Could not update funding fee.", caplog)
|
||||
|
||||
assert not trade.is_open
|
||||
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:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
|
@ -9,7 +9,7 @@ import arrow
|
||||
import pytest
|
||||
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.exceptions import DependencyException, OperationalException
|
||||
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):
|
||||
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())
|
||||
|
||||
@ -1739,7 +1739,7 @@ def test_to_json(fee):
|
||||
'base_currency': 'ADA',
|
||||
'quote_currency': 'USDT',
|
||||
'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_order_id': 'dry_run_buy_12345',
|
||||
'close_date': None,
|
||||
@ -1817,9 +1817,9 @@ def test_to_json(fee):
|
||||
'pair': 'XRP/BTC',
|
||||
'base_currency': 'XRP',
|
||||
'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),
|
||||
'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),
|
||||
'open_rate': 0.123,
|
||||
'close_rate': 0.125,
|
||||
|
Loading…
Reference in New Issue
Block a user