merge develop

This commit is contained in:
Wagner Costa Santos 2022-09-10 10:35:16 -03:00
commit a1f88cca80
23 changed files with 253 additions and 176 deletions

View File

@ -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

View File

@ -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,41 +252,41 @@ 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 |
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 |
================== SUMMARY METRICS ================== ================== SUMMARY METRICS ==================
| Metric | Value | | 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. 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,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. 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 |
``` ```
## Next step ## Next step

View File

@ -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?
@ -67,7 +74,7 @@ This is not a bot-problem, but will also happen while manual trading.
While freqtrade can handle this (it'll sell 99 COIN), fees are often below the minimum tradable lot-size (you can only trade full COIN, not 0.9 COIN). While freqtrade can handle this (it'll sell 99 COIN), fees are often below the minimum tradable lot-size (you can only trade full COIN, not 0.9 COIN).
Leaving the dust (0.9 COIN) on the exchange makes usually sense, as the next time freqtrade buys COIN, it'll eat into the remaining small balance, this time selling everything it bought, and therefore slowly declining the dust balance (although it most likely will never reach exactly 0). Leaving the dust (0.9 COIN) on the exchange makes usually sense, as the next time freqtrade buys COIN, it'll eat into the remaining small balance, this time selling everything it bought, and therefore slowly declining the dust balance (although it most likely will never reach exactly 0).
Where possible (e.g. on binance), the use of the exchange's dedicated fee currency will fix this. Where possible (e.g. on binance), the use of the exchange's dedicated fee currency will fix this.
On binance, it's sufficient to have BNB in your account, and have "Pay fees in BNB" enabled in your profile. Your BNB balance will slowly decline (as it's used to pay fees) - but you'll no longer encounter dust (Freqtrade will include the fees in the profit calculations). On binance, it's sufficient to have BNB in your account, and have "Pay fees in BNB" enabled in your profile. Your BNB balance will slowly decline (as it's used to pay fees) - but you'll no longer encounter dust (Freqtrade will include the fees in the profit calculations).
Other exchanges don't offer such possibilities, where it's simply something you'll have to accept or move to a different exchange. Other exchanges don't offer such possibilities, where it's simply something you'll have to accept or move to a different exchange.
@ -109,7 +116,7 @@ This warning can point to one of the below problems:
### I'm getting the "RESTRICTED_MARKET" message in the log ### I'm getting the "RESTRICTED_MARKET" message in the log
Currently known to happen for US Bittrex users. Currently known to happen for US Bittrex users.
Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information. Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information.
@ -177,8 +184,8 @@ The GPU improvements would only apply to pandas-native calculations - or ones wr
For hyperopt, freqtrade is using scikit-optimize, which is built on top of scikit-learn. For hyperopt, freqtrade is using scikit-optimize, which is built on top of scikit-learn.
Their statement about GPU support is [pretty clear](https://scikit-learn.org/stable/faq.html#will-you-add-gpu-support). Their statement about GPU support is [pretty clear](https://scikit-learn.org/stable/faq.html#will-you-add-gpu-support).
GPU's also are only good at crunching numbers (floating point operations). GPU's also are only good at crunching numbers (floating point operations).
For hyperopt, we need both number-crunching (find next parameters) and running python code (running backtesting). For hyperopt, we need both number-crunching (find next parameters) and running python code (running backtesting).
As such, GPU's are not too well suited for most parts of hyperopt. As such, GPU's are not too well suited for most parts of hyperopt.
The benefit of using GPU would therefore be pretty slim - and will not justify the complexity introduced by trying to add GPU support. The benefit of using GPU would therefore be pretty slim - and will not justify the complexity introduced by trying to add GPU support.
@ -219,9 +226,9 @@ already 8\*10^9\*10 evaluations. A roughly total of 80 billion evaluations.
Did you run 100 000 evaluations? Congrats, you've done roughly 1 / 100 000 th Did you run 100 000 evaluations? Congrats, you've done roughly 1 / 100 000 th
of the search space, assuming that the bot never tests the same parameters more than once. of the search space, assuming that the bot never tests the same parameters more than once.
* The time it takes to run 1000 hyperopt epochs depends on things like: The available cpu, hard-disk, ram, timeframe, timerange, indicator settings, indicator count, amount of coins that hyperopt test strategies on and the resulting trade count - which can be 650 trades in a year or 100000 trades depending if the strategy aims for big profits by trading rarely or for many low profit trades. * The time it takes to run 1000 hyperopt epochs depends on things like: The available cpu, hard-disk, ram, timeframe, timerange, indicator settings, indicator count, amount of coins that hyperopt test strategies on and the resulting trade count - which can be 650 trades in a year or 100000 trades depending if the strategy aims for big profits by trading rarely or for many low profit trades.
Example: 4% profit 650 times vs 0,3% profit a trade 10000 times in a year. If we assume you set the --timerange to 365 days. Example: 4% profit 650 times vs 0,3% profit a trade 10000 times in a year. If we assume you set the --timerange to 365 days.
Example: Example:
`freqtrade --config config.json --strategy SampleStrategy --hyperopt SampleHyperopt -e 1000 --timerange 20190601-20200601` `freqtrade --config config.json --strategy SampleStrategy --hyperopt SampleHyperopt -e 1000 --timerange 20190601-20200601`

View File

@ -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",
@ -138,7 +139,7 @@ You can create your own keyboard in `config.json`:
"enabled": true, "enabled": true,
"token": "your_telegram_token", "token": "your_telegram_token",
"chat_id": "your_telegram_chat_id", "chat_id": "your_telegram_chat_id",
"keyboard": [ "keyboard": [
["/daily", "/stats", "/balance", "/profit"], ["/daily", "/stats", "/balance", "/profit"],
["/status table", "/performance"], ["/status table", "/performance"],
["/reload_config", "/count", "/logs"] ["/reload_config", "/count", "/logs"]
@ -225,16 +226,16 @@ Once all positions are sold, run `/stop` to completely stop the bot.
For each open trade, the bot will send you the following message. For each open trade, the bot will send you the following message.
Enter Tag is configurable via Strategy. Enter Tag is configurable via Strategy.
> **Trade ID:** `123` `(since 1 days ago)` > **Trade ID:** `123` `(since 1 days ago)`
> **Current Pair:** CVC/BTC > **Current Pair:** CVC/BTC
> **Direction:** Long > **Direction:** Long
> **Leverage:** 1.0 > **Leverage:** 1.0
> **Amount:** `26.64180098` > **Amount:** `26.64180098`
> **Enter Tag:** Awesome Long Signal > **Enter Tag:** Awesome Long Signal
> **Open Rate:** `0.00007489` > **Open Rate:** `0.00007489`
> **Current Rate:** `0.00007489` > **Current Rate:** `0.00007489`
> **Current Profit:** `12.95%` > **Current Profit:** `12.95%`
> **Stoploss:** `0.00007389 (-0.02%)` > **Stoploss:** `0.00007389 (-0.02%)`
### /status table ### /status table
@ -261,26 +262,26 @@ current max
Return a summary of your profit/loss and performance. Return a summary of your profit/loss and performance.
> **ROI:** Close trades > **ROI:** Close trades
> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)` > ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
> ∙ `62.968 USD` > ∙ `62.968 USD`
> **ROI:** All trades > **ROI:** All trades
> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)` > ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
> ∙ `33.095 EUR` > ∙ `33.095 EUR`
> >
> **Total Trade Count:** `138` > **Total Trade Count:** `138`
> **First Trade opened:** `3 days ago` > **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago` > **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45` > **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%` > **Best Performing:** `PAY/BTC: 50.23%`
> **Trading volume:** `0.5 BTC` > **Trading volume:** `0.5 BTC`
> **Profit factor:** `1.04` > **Profit factor:** `1.04`
> **Max Drawdown:** `9.23% (0.01255 BTC)` > **Max Drawdown:** `9.23% (0.01255 BTC)`
The relative profit of `1.2%` is the average profit per trade. The relative profit of `1.2%` is the average profit per trade.
The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`. The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits. Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy. Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`. Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
### /forceexit <trade_id> ### /forceexit <trade_id>
@ -309,27 +310,27 @@ Note that for this to work, `force_entry_enable` needs to be set to true.
### /performance ### /performance
Return the performance of each crypto-currency the bot has sold. Return the performance of each crypto-currency the bot has sold.
> Performance: > Performance:
> 1. `RCN/BTC 0.003 BTC (57.77%) (1)` > 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)` > 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)` > 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)` > 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)` > 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
> ... > ...
### /balance ### /balance
Return the balance of all crypto-currency your have on the exchange. Return the balance of all crypto-currency your have on the exchange.
> **Currency:** BTC > **Currency:** BTC
> **Available:** 3.05890234 > **Available:** 3.05890234
> **Balance:** 3.05890234 > **Balance:** 3.05890234
> **Pending:** 0.0 > **Pending:** 0.0
> **Currency:** CVC > **Currency:** CVC
> **Available:** 86.64180098 > **Available:** 86.64180098
> **Balance:** 86.64180098 > **Balance:** 86.64180098
> **Pending:** 0.0 > **Pending:** 0.0
### /daily <n> ### /daily <n>
@ -376,7 +377,7 @@ Month (count) Profit BTC Profit USD Profit %
Shows the current whitelist Shows the current whitelist
> Using whitelist `StaticPairList` with 22 pairs > Using whitelist `StaticPairList` with 22 pairs
> `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC` > `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC`
### /blacklist [pair] ### /blacklist [pair]
@ -386,7 +387,7 @@ If Pair is set, then this pair will be added to the pairlist.
Also supports multiple pairs, separated by a space. Also supports multiple pairs, separated by a space.
Use `/reload_config` to reset the blacklist. Use `/reload_config` to reset the blacklist.
> Using blacklist `StaticPairList` with 2 pairs > Using blacklist `StaticPairList` with 2 pairs
>`DODGE/BTC`, `HOT/BTC`. >`DODGE/BTC`, `HOT/BTC`.
### /edge ### /edge

View File

@ -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__)

View File

@ -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

View File

@ -2509,8 +2509,13 @@ class Exchange:
cache=False, cache=False,
drop_incomplete=False, drop_incomplete=False,
) )
funding_rates = candle_histories[funding_comb] try:
mark_rates = candle_histories[mark_comb] # 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_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']:

View File

@ -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

View File

@ -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)

View File

@ -281,16 +281,17 @@ 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()
for trade in trades: try:
funding_fees = self.exchange.get_funding_fees( for trade in trades:
pair=trade.pair, funding_fees = self.exchange.get_funding_fees(
amount=trade.amount, pair=trade.pair,
is_short=trade.is_short, amount=trade.amount,
open_date=trade.date_last_filled_utc is_short=trade.is_short,
) open_date=trade.date_last_filled_utc
trade.funding_fees = funding_fees )
else: trade.funding_fees = funding_fees
return 0.0 except ExchangeError:
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: f"{name} signal found: about create a new trade for {pair} with stake_amount: "
logger.info( f"{stake_amount} ...")
f"{name} signal found: about create a new trade for {pair} with stake_amount: " logger.info(msg)
f"{stake_amount} ...")
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 = self.exchange.get_funding_fees( funding_fees = 0.0
pair=pair, amount=amount, is_short=is_short, open_date=open_date) 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( 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
""" """
trade.funding_fees = self.exchange.get_funding_fees( try:
pair=trade.pair, trade.funding_fees = self.exchange.get_funding_fees(
amount=trade.amount, pair=trade.pair,
is_short=trade.is_short, amount=trade.amount,
open_date=trade.date_last_filled_utc, 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_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 (

View File

@ -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 = [
[ [

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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),

View File

@ -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

View File

@ -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,13 +402,13 @@ 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'
'| 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% |' ' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |'
) )

View File

@ -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

View File

@ -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],

View File

@ -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)

View File

@ -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,