mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 18:23:55 +00:00
Merge branch 'develop' of https://github.com/freqtrade/freqtrade into freqtrade-develop
This commit is contained in:
commit
328b969801
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-18.04, ubuntu-20.04 ]
|
os: [ ubuntu-18.04, ubuntu-20.04 ]
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.8", "3.9", "3.10"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -115,7 +115,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos-latest ]
|
os: [ macos-latest ]
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.8", "3.9", "3.10"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -207,7 +207,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ windows-latest ]
|
os: [ windows-latest ]
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.8", "3.9", "3.10"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
|
@ -49,7 +49,7 @@ Please find the complete documentation on the [freqtrade website](https://www.fr
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- [x] **Based on Python 3.7+**: For botting on any operating system - Windows, macOS and Linux.
|
- [x] **Based on Python 3.8+**: For botting on any operating system - Windows, macOS and Linux.
|
||||||
- [x] **Persistence**: Persistence is achieved through sqlite.
|
- [x] **Persistence**: Persistence is achieved through sqlite.
|
||||||
- [x] **Dry-run**: Run the bot without paying money.
|
- [x] **Dry-run**: Run the bot without paying money.
|
||||||
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
|
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
|
||||||
|
@ -197,7 +197,7 @@ To run this bot we recommend you a cloud instance with a minimum of:
|
||||||
|
|
||||||
### Software requirements
|
### Software requirements
|
||||||
|
|
||||||
- [Python >= 3.7](http://docs.python-guide.org/en/latest/starting/installation/)
|
- [Python >= 3.8](http://docs.python-guide.org/en/latest/starting/installation/)
|
||||||
- [pip](https://pip.pypa.io/en/stable/installing/)
|
- [pip](https://pip.pypa.io/en/stable/installing/)
|
||||||
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||||
- [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)
|
- [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)
|
||||||
|
|
Binary file not shown.
|
@ -5,9 +5,6 @@ python -m pip install --upgrade pip wheel
|
||||||
|
|
||||||
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||||
|
|
||||||
if ($pyv -eq '3.7') {
|
|
||||||
pip install build_helpers\TA_Lib-0.4.24-cp37-cp37m-win_amd64.whl
|
|
||||||
}
|
|
||||||
if ($pyv -eq '3.8') {
|
if ($pyv -eq '3.8') {
|
||||||
pip install build_helpers\TA_Lib-0.4.24-cp38-cp38-win_amd64.whl
|
pip install build_helpers\TA_Lib-0.4.24-cp38-cp38-win_amd64.whl
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ You can define your own estimator for Hyperopt by implementing `generate_estimat
|
||||||
```python
|
```python
|
||||||
class MyAwesomeStrategy(IStrategy):
|
class MyAwesomeStrategy(IStrategy):
|
||||||
class HyperOpt:
|
class HyperOpt:
|
||||||
def generate_estimator():
|
def generate_estimator(dimensions: List['Dimension'], **kwargs):
|
||||||
return "RF"
|
return "RF"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -119,13 +119,34 @@ Example for `ExtraTreesRegressor` ("ET") with additional parameters:
|
||||||
```python
|
```python
|
||||||
class MyAwesomeStrategy(IStrategy):
|
class MyAwesomeStrategy(IStrategy):
|
||||||
class HyperOpt:
|
class HyperOpt:
|
||||||
def generate_estimator():
|
def generate_estimator(dimensions: List['Dimension'], **kwargs):
|
||||||
from skopt.learning import ExtraTreesRegressor
|
from skopt.learning import ExtraTreesRegressor
|
||||||
# Corresponds to "ET" - but allows additional parameters.
|
# Corresponds to "ET" - but allows additional parameters.
|
||||||
return ExtraTreesRegressor(n_estimators=100)
|
return ExtraTreesRegressor(n_estimators=100)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `dimensions` parameter is the list of `skopt.space.Dimension` objects corresponding to the parameters to be optimized. It can be used to create isotropic kernels for the `skopt.learning.GaussianProcessRegressor` estimator. Here's an example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MyAwesomeStrategy(IStrategy):
|
||||||
|
class HyperOpt:
|
||||||
|
def generate_estimator(dimensions: List['Dimension'], **kwargs):
|
||||||
|
from skopt.utils import cook_estimator
|
||||||
|
from skopt.learning.gaussian_process.kernels import (Matern, ConstantKernel)
|
||||||
|
kernel_bounds = (0.0001, 10000)
|
||||||
|
kernel = (
|
||||||
|
ConstantKernel(1.0, kernel_bounds) *
|
||||||
|
Matern(length_scale=np.ones(len(dimensions)), length_scale_bounds=[kernel_bounds for d in dimensions], nu=2.5)
|
||||||
|
)
|
||||||
|
kernel += (
|
||||||
|
ConstantKernel(1.0, kernel_bounds) *
|
||||||
|
Matern(length_scale=np.ones(len(dimensions)), length_scale_bounds=[kernel_bounds for d in dimensions], nu=1.5)
|
||||||
|
)
|
||||||
|
|
||||||
|
return cook_estimator("GP", space=dimensions, kernel=kernel, n_restarts_optimizer=2)
|
||||||
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
While custom estimators can be provided, it's up to you as User to do research on possible parameters and analyze / understand which ones should be used.
|
While custom estimators can be provided, it's up to you as User to do research on possible parameters and analyze / understand which ones should be used.
|
||||||
If you're unsure about this, best use one of the Defaults (`"ET"` has proven to be the most versatile) without further parameters.
|
If you're unsure about this, best use one of the Defaults (`"ET"` has proven to be the most versatile) without further parameters.
|
||||||
|
|
|
@ -173,6 +173,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
||||||
| `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `json`*. <br> **Datatype:** String
|
| `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `json`*. <br> **Datatype:** String
|
||||||
| `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `jsongz`*. <br> **Datatype:** String
|
| `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `jsongz`*. <br> **Datatype:** String
|
||||||
| `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.*<br> **Datatype:** Boolean
|
| `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.*<br> **Datatype:** Boolean
|
||||||
|
| `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `-1`.*<br> **Datatype:** Positive Integer or -1
|
||||||
|
|
||||||
### Parameters in the strategy
|
### Parameters in the strategy
|
||||||
|
|
||||||
|
@ -198,6 +199,7 @@ Values set in the configuration file always overwrite values set in the strategy
|
||||||
* `ignore_roi_if_buy_signal`
|
* `ignore_roi_if_buy_signal`
|
||||||
* `ignore_buying_expired_candle_after`
|
* `ignore_buying_expired_candle_after`
|
||||||
* `position_adjustment_enable`
|
* `position_adjustment_enable`
|
||||||
|
* `max_entry_position_adjustment`
|
||||||
|
|
||||||
### Configuring amount per trade
|
### Configuring amount per trade
|
||||||
|
|
||||||
|
|
|
@ -126,6 +126,12 @@ All freqtrade arguments will be available by running `docker-compose run --rm fr
|
||||||
!!! Note "`docker-compose run --rm`"
|
!!! Note "`docker-compose run --rm`"
|
||||||
Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
|
Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
|
||||||
|
|
||||||
|
??? Note "Using docker without docker-compose"
|
||||||
|
"`docker-compose run --rm`" will require a compose file to be provided.
|
||||||
|
Some freqtrade commands that don't require authentication such as `list-pairs` can be run with "`docker run --rm`" instead.
|
||||||
|
For example `docker run --rm freqtradeorg/freqtrade:stable list-pairs --exchange binance --quote BTC --print-json`.
|
||||||
|
This can be useful for fetching exchange information to add to your `config.json` without affecting your running containers.
|
||||||
|
|
||||||
#### Example: Download data with docker-compose
|
#### Example: Download data with docker-compose
|
||||||
|
|
||||||
Download backtesting data for 5 days for the pair ETH/BTC and 1h timeframe from Binance. The data will be stored in the directory `user_data/data/` on the host.
|
Download backtesting data for 5 days for the pair ETH/BTC and 1h timeframe from Binance. The data will be stored in the directory `user_data/data/` on the host.
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Freqtrade is a crypto-currency algorithmic trading software developed in python (3.7+) and supported on Windows, macOS and Linux.
|
Freqtrade is a crypto-currency algorithmic trading software developed in python (3.8+) and supported on Windows, macOS and Linux.
|
||||||
|
|
||||||
!!! Danger "DISCLAIMER"
|
!!! Danger "DISCLAIMER"
|
||||||
This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
|
This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
|
||||||
|
@ -67,7 +67,7 @@ To run this bot we recommend you a linux cloud instance with a minimum of:
|
||||||
|
|
||||||
Alternatively
|
Alternatively
|
||||||
|
|
||||||
- Python 3.7+
|
- Python 3.8+
|
||||||
- pip (pip3)
|
- pip (pip3)
|
||||||
- git
|
- git
|
||||||
- TA-Lib
|
- TA-Lib
|
||||||
|
|
|
@ -42,7 +42,7 @@ These requirements apply to both [Script Installation](#script-installation) and
|
||||||
|
|
||||||
### Install guide
|
### Install guide
|
||||||
|
|
||||||
* [Python >= 3.7.x](http://docs.python-guide.org/en/latest/starting/installation/)
|
* [Python >= 3.8.x](http://docs.python-guide.org/en/latest/starting/installation/)
|
||||||
* [pip](https://pip.pypa.io/en/stable/installing/)
|
* [pip](https://pip.pypa.io/en/stable/installing/)
|
||||||
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||||
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
|
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mkdocs==1.2.3
|
mkdocs==1.2.3
|
||||||
mkdocs-material==8.1.8
|
mkdocs-material==8.1.9
|
||||||
mdx_truly_sane_lists==1.2
|
mdx_truly_sane_lists==1.2
|
||||||
pymdown-extensions==9.1
|
pymdown-extensions==9.1
|
||||||
|
|
|
@ -74,7 +74,7 @@ class AwesomeStrategy(IStrategy):
|
||||||
Freqtrade will fall back to the `proposed_stake` value should your code raise an exception. The exception itself will be logged.
|
Freqtrade will fall back to the `proposed_stake` value should your code raise an exception. The exception itself will be logged.
|
||||||
|
|
||||||
!!! Tip
|
!!! Tip
|
||||||
You do not _have_ to ensure that `min_stake <= returned_value <= max_stake`. Trades will succeed as the returned value will be clamped to supported range and this acton will be logged.
|
You do not _have_ to ensure that `min_stake <= returned_value <= max_stake`. Trades will succeed as the returned value will be clamped to supported range and this action will be logged.
|
||||||
|
|
||||||
!!! Tip
|
!!! Tip
|
||||||
Returning `0` or `None` will prevent trades from being placed.
|
Returning `0` or `None` will prevent trades from being placed.
|
||||||
|
@ -579,11 +579,13 @@ The `position_adjustment_enable` strategy property enables the usage of `adjust_
|
||||||
For performance reasons, it's disabled by default and freqtrade will show a warning message on startup if enabled.
|
For performance reasons, it's disabled by default and freqtrade will show a warning message on startup if enabled.
|
||||||
`adjust_trade_position()` can be used to perform additional orders, for example to manage risk with DCA (Dollar Cost Averaging).
|
`adjust_trade_position()` can be used to perform additional orders, for example to manage risk with DCA (Dollar Cost Averaging).
|
||||||
|
|
||||||
|
`max_entry_position_adjustment` property is used to limit the number of additional buys per trade (on top of the first buy) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment buys.
|
||||||
|
|
||||||
The strategy is expected to return a stake_amount (in stake currency) between `min_stake` and `max_stake` if and when an additional buy order should be made (position is increased).
|
The strategy is expected to return a stake_amount (in stake currency) between `min_stake` and `max_stake` if and when an additional buy order should be made (position is increased).
|
||||||
If there are not enough funds in the wallet (the return value is above `max_stake`) then the signal will be ignored.
|
If there are not enough funds in the wallet (the return value is above `max_stake`) then the signal will be ignored.
|
||||||
Additional orders also result in additional fees and those orders don't count towards `max_open_trades`.
|
Additional orders also result in additional fees and those orders don't count towards `max_open_trades`.
|
||||||
|
|
||||||
This callback is **not** called when there is an open order (either buy or sell) waiting for execution.
|
This callback is **not** called when there is an open order (either buy or sell) waiting for execution, or when you have reached the maximum amount of extra buys that you have set on `max_entry_position_adjustment`.
|
||||||
`adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible.
|
`adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible.
|
||||||
|
|
||||||
!!! Note "About stake size"
|
!!! Note "About stake size"
|
||||||
|
@ -614,7 +616,7 @@ class DigDeeperStrategy(IStrategy):
|
||||||
# ... populate_* methods
|
# ... populate_* methods
|
||||||
|
|
||||||
# Example specific variables
|
# Example specific variables
|
||||||
max_dca_orders = 3
|
max_entry_position_adjustment = 3
|
||||||
# This number is explained a bit further down
|
# This number is explained a bit further down
|
||||||
max_dca_multiplier = 5.5
|
max_dca_multiplier = 5.5
|
||||||
|
|
||||||
|
@ -656,8 +658,7 @@ class DigDeeperStrategy(IStrategy):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
filled_buys = trade.select_filled_orders('buy')
|
filled_buys = trade.select_filled_orders('buy')
|
||||||
count_of_buys = len(filled_buys)
|
count_of_buys = trade.nr_of_successful_buys
|
||||||
|
|
||||||
# Allow up to 3 additional increasingly larger buys (4 in total)
|
# Allow up to 3 additional increasingly larger buys (4 in total)
|
||||||
# Initial buy is 1x
|
# Initial buy is 1x
|
||||||
# If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2%
|
# If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2%
|
||||||
|
@ -666,15 +667,14 @@ class DigDeeperStrategy(IStrategy):
|
||||||
# Total stake for this trade would be 1 + 1.25 + 1.5 + 1.75 = 5.5x of the initial allowed stake.
|
# Total stake for this trade would be 1 + 1.25 + 1.5 + 1.75 = 5.5x of the initial allowed stake.
|
||||||
# That is why max_dca_multiplier is 5.5
|
# That is why max_dca_multiplier is 5.5
|
||||||
# Hope you have a deep wallet!
|
# Hope you have a deep wallet!
|
||||||
if 0 < count_of_buys <= self.max_dca_orders:
|
try:
|
||||||
try:
|
# This returns first order stake size
|
||||||
# This returns first order stake size
|
stake_amount = filled_buys[0].cost
|
||||||
stake_amount = filled_buys[0].cost
|
# This then calculates current safety order size
|
||||||
# This then calculates current safety order size
|
stake_amount = stake_amount * (1 + (count_of_buys * 0.25))
|
||||||
stake_amount = stake_amount * (1 + (count_of_buys * 0.25))
|
return stake_amount
|
||||||
return stake_amount
|
except Exception as exception:
|
||||||
except Exception as exception:
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ $ freqtrade new-config --config config_binance.json
|
||||||
? Do you want to enable Dry-run (simulated trades)? Yes
|
? Do you want to enable Dry-run (simulated trades)? Yes
|
||||||
? Please insert your stake currency: BTC
|
? Please insert your stake currency: BTC
|
||||||
? Please insert your stake amount: 0.05
|
? Please insert your stake amount: 0.05
|
||||||
? Please insert max_open_trades (Integer or 'unlimited'): 3
|
? Please insert max_open_trades (Integer or -1 for unlimited open trades): 3
|
||||||
? Please insert your desired timeframe (e.g. 5m): 5m
|
? Please insert your desired timeframe (e.g. 5m): 5m
|
||||||
? Please insert your display Currency (for reporting): USD
|
? Please insert your display Currency (for reporting): USD
|
||||||
? Select exchange binance
|
? Select exchange binance
|
||||||
|
|
|
@ -25,7 +25,7 @@ Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7
|
||||||
|
|
||||||
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.24-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version).
|
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.24-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version).
|
||||||
|
|
||||||
Freqtrade provides these dependencies for the latest 3 Python versions (3.7, 3.8, 3.9 and 3.10) and for 64bit Windows.
|
Freqtrade provides these dependencies for the latest 3 Python versions (3.8, 3.9 and 3.10) and for 64bit Windows.
|
||||||
Other versions must be downloaded from the above link.
|
Other versions must be downloaded from the above link.
|
||||||
|
|
||||||
``` powershell
|
``` powershell
|
||||||
|
|
|
@ -4,7 +4,7 @@ channels:
|
||||||
# - defaults
|
# - defaults
|
||||||
dependencies:
|
dependencies:
|
||||||
# 1/4 req main
|
# 1/4 req main
|
||||||
- python>=3.7,<3.9
|
- python>=3.8,<=3.10
|
||||||
- numpy
|
- numpy
|
||||||
- pandas
|
- pandas
|
||||||
- pip
|
- pip
|
||||||
|
@ -25,9 +25,12 @@ dependencies:
|
||||||
- fastapi
|
- fastapi
|
||||||
- uvicorn
|
- uvicorn
|
||||||
- pyjwt
|
- pyjwt
|
||||||
|
- aiofiles
|
||||||
|
- psutil
|
||||||
- colorama
|
- colorama
|
||||||
- questionary
|
- questionary
|
||||||
- prompt-toolkit
|
- prompt-toolkit
|
||||||
|
- python-dateutil
|
||||||
|
|
||||||
|
|
||||||
# ============================
|
# ============================
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
__main__.py for Freqtrade
|
__main__.py for Freqtrade
|
||||||
To launch Freqtrade as a module
|
To launch Freqtrade as a module
|
||||||
|
|
||||||
> python -m freqtrade (with Python >= 3.7)
|
> python -m freqtrade (with Python >= 3.8)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from freqtrade import main
|
from freqtrade import main
|
||||||
|
|
|
@ -76,12 +76,9 @@ def ask_user_config() -> Dict[str, Any]:
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "max_open_trades",
|
"name": "max_open_trades",
|
||||||
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
|
"message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):",
|
||||||
"default": "3",
|
"default": "3",
|
||||||
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val),
|
"validate": lambda val: validate_is_int(val)
|
||||||
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
|
|
||||||
if val == UNLIMITED_STAKE_AMOUNT
|
|
||||||
else val
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
|
|
@ -371,7 +371,9 @@ CONF_SCHEMA = {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'enum': AVAILABLE_DATAHANDLERS,
|
'enum': AVAILABLE_DATAHANDLERS,
|
||||||
'default': 'jsongz'
|
'default': 'jsongz'
|
||||||
}
|
},
|
||||||
|
'position_adjustment_enable': {'type': 'boolean'},
|
||||||
|
'max_entry_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1},
|
||||||
},
|
},
|
||||||
'definitions': {
|
'definitions': {
|
||||||
'exchange': {
|
'exchange': {
|
||||||
|
|
|
@ -953,7 +953,7 @@ class Exchange:
|
||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
def get_tickers(self, cached: bool = False) -> Dict:
|
def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict:
|
||||||
"""
|
"""
|
||||||
:param cached: Allow cached result
|
:param cached: Allow cached result
|
||||||
:return: fetch_tickers result
|
:return: fetch_tickers result
|
||||||
|
@ -963,7 +963,7 @@ class Exchange:
|
||||||
if tickers:
|
if tickers:
|
||||||
return tickers
|
return tickers
|
||||||
try:
|
try:
|
||||||
tickers = self._api.fetch_tickers()
|
tickers = self._api.fetch_tickers(symbols)
|
||||||
self._fetch_tickers_cache['fetch_tickers'] = tickers
|
self._fetch_tickers_cache['fetch_tickers'] = tickers
|
||||||
return tickers
|
return tickers
|
||||||
except ccxt.NotSupported as e:
|
except ccxt.NotSupported as e:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
""" Kraken exchange subclass """
|
""" Kraken exchange subclass """
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
|
@ -33,6 +33,12 @@ class Kraken(Exchange):
|
||||||
return (parent_check and
|
return (parent_check and
|
||||||
market.get('darkpool', False) is False)
|
market.get('darkpool', False) is False)
|
||||||
|
|
||||||
|
def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict:
|
||||||
|
# Only fetch tickers for current stake currency
|
||||||
|
# Otherwise the request for kraken becomes too large.
|
||||||
|
symbols = list(self.get_markets(quote_currencies=[self._config['stake_currency']]))
|
||||||
|
return super().get_tickers(symbols=symbols, cached=cached)
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
def get_balances(self) -> dict:
|
def get_balances(self) -> dict:
|
||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
|
|
|
@ -462,8 +462,8 @@ class FreqtradeBot(LoggingMixin):
|
||||||
try:
|
try:
|
||||||
self.check_and_call_adjust_trade_position(trade)
|
self.check_and_call_adjust_trade_position(trade)
|
||||||
except DependencyException as exception:
|
except DependencyException as exception:
|
||||||
logger.warning('Unable to adjust position of trade for %s: %s',
|
logger.warning(
|
||||||
trade.pair, exception)
|
f"Unable to adjust position of trade for {trade.pair}: {exception}")
|
||||||
|
|
||||||
def check_and_call_adjust_trade_position(self, trade: Trade):
|
def check_and_call_adjust_trade_position(self, trade: Trade):
|
||||||
"""
|
"""
|
||||||
|
@ -471,6 +471,13 @@ class FreqtradeBot(LoggingMixin):
|
||||||
If the strategy triggers the adjustment, a new order gets issued.
|
If the strategy triggers the adjustment, a new order gets issued.
|
||||||
Once that completes, the existing trade is modified to match new data.
|
Once that completes, the existing trade is modified to match new data.
|
||||||
"""
|
"""
|
||||||
|
if self.strategy.max_entry_position_adjustment > -1:
|
||||||
|
count_of_buys = trade.nr_of_successful_buys
|
||||||
|
if count_of_buys > self.strategy.max_entry_position_adjustment:
|
||||||
|
logger.debug(f"Max adjustment entries for {trade.pair} has been reached.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
logger.debug("Max adjustment entries is set to unlimited.")
|
||||||
current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy")
|
current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy")
|
||||||
current_profit = trade.calc_profit_ratio(current_rate)
|
current_profit = trade.calc_profit_ratio(current_rate)
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ from typing import Any, List
|
||||||
|
|
||||||
|
|
||||||
# check min. python version
|
# check min. python version
|
||||||
if sys.version_info < (3, 7): # pragma: no cover
|
if sys.version_info < (3, 8): # pragma: no cover
|
||||||
sys.exit("Freqtrade requires Python version >= 3.7")
|
sys.exit("Freqtrade requires Python version >= 3.8")
|
||||||
|
|
||||||
from freqtrade.commands import Arguments
|
from freqtrade.commands import Arguments
|
||||||
from freqtrade.exceptions import FreqtradeException, OperationalException
|
from freqtrade.exceptions import FreqtradeException, OperationalException
|
||||||
|
|
|
@ -381,7 +381,12 @@ class Backtesting:
|
||||||
|
|
||||||
# Check if we need to adjust our current positions
|
# Check if we need to adjust our current positions
|
||||||
if self.strategy.position_adjustment_enable:
|
if self.strategy.position_adjustment_enable:
|
||||||
trade = self._get_adjust_trade_entry_for_candle(trade, sell_row)
|
check_adjust_buy = True
|
||||||
|
if self.strategy.max_entry_position_adjustment > -1:
|
||||||
|
count_of_buys = trade.nr_of_successful_buys
|
||||||
|
check_adjust_buy = (count_of_buys <= self.strategy.max_entry_position_adjustment)
|
||||||
|
if check_adjust_buy:
|
||||||
|
trade = self._get_adjust_trade_entry_for_candle(trade, sell_row)
|
||||||
|
|
||||||
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
||||||
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
|
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
|
||||||
|
|
|
@ -372,7 +372,7 @@ class Hyperopt:
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_optimizer(self, dimensions: List[Dimension], cpu_count) -> Optimizer:
|
def get_optimizer(self, dimensions: List[Dimension], cpu_count) -> Optimizer:
|
||||||
estimator = self.custom_hyperopt.generate_estimator(dimensions)
|
estimator = self.custom_hyperopt.generate_estimator(dimensions=dimensions)
|
||||||
|
|
||||||
acq_optimizer = "sampling"
|
acq_optimizer = "sampling"
|
||||||
if isinstance(estimator, str):
|
if isinstance(estimator, str):
|
||||||
|
|
|
@ -91,5 +91,5 @@ class HyperOptAuto(IHyperOpt):
|
||||||
def trailing_space(self) -> List['Dimension']:
|
def trailing_space(self) -> List['Dimension']:
|
||||||
return self._get_func('trailing_space')()
|
return self._get_func('trailing_space')()
|
||||||
|
|
||||||
def generate_estimator(self, dimensions: List['Dimension']) -> EstimatorType:
|
def generate_estimator(self, dimensions: List['Dimension'], **kwargs) -> EstimatorType:
|
||||||
return self._get_func('generate_estimator')(dimensions)
|
return self._get_func('generate_estimator')(dimensions=dimensions, **kwargs)
|
||||||
|
|
|
@ -40,7 +40,7 @@ class IHyperOpt(ABC):
|
||||||
IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
|
IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
|
||||||
IHyperOpt.timeframe = str(config['timeframe'])
|
IHyperOpt.timeframe = str(config['timeframe'])
|
||||||
|
|
||||||
def generate_estimator(self) -> EstimatorType:
|
def generate_estimator(self, dimensions: List[Dimension], **kwargs) -> EstimatorType:
|
||||||
"""
|
"""
|
||||||
Return base_estimator.
|
Return base_estimator.
|
||||||
Can be any of "GP", "RF", "ET", "GBRT" or an instance of a class
|
Can be any of "GP", "RF", "ET", "GBRT" or an instance of a class
|
||||||
|
|
|
@ -569,8 +569,8 @@ class LocalTrade():
|
||||||
return float(f"{profit_ratio:.8f}")
|
return float(f"{profit_ratio:.8f}")
|
||||||
|
|
||||||
def recalc_trade_from_orders(self):
|
def recalc_trade_from_orders(self):
|
||||||
# We need at least 2 orders for averaging amounts and rates.
|
# We need at least 2 entry orders for averaging amounts and rates.
|
||||||
if len(self.orders) < 2:
|
if len(self.select_filled_orders('buy')) < 2:
|
||||||
# Just in case, still recalc open trade value
|
# Just in case, still recalc open trade value
|
||||||
self.recalc_open_trade_value()
|
self.recalc_open_trade_value()
|
||||||
return
|
return
|
||||||
|
|
|
@ -97,7 +97,8 @@ class StrategyResolver(IResolver):
|
||||||
("sell_profit_offset", 0.0),
|
("sell_profit_offset", 0.0),
|
||||||
("disable_dataframe_checks", False),
|
("disable_dataframe_checks", False),
|
||||||
("ignore_buying_expired_candle_after", 0),
|
("ignore_buying_expired_candle_after", 0),
|
||||||
("position_adjustment_enable", False)
|
("position_adjustment_enable", False),
|
||||||
|
("max_entry_position_adjustment", -1),
|
||||||
]
|
]
|
||||||
for attribute, default in attributes:
|
for attribute, default in attributes:
|
||||||
StrategyResolver._override_attribute_helper(strategy, config,
|
StrategyResolver._override_attribute_helper(strategy, config,
|
||||||
|
|
|
@ -173,6 +173,8 @@ class ShowConfig(BaseModel):
|
||||||
bot_name: str
|
bot_name: str
|
||||||
state: str
|
state: str
|
||||||
runmode: str
|
runmode: str
|
||||||
|
position_adjustment_enable: bool
|
||||||
|
max_entry_position_adjustment: int
|
||||||
|
|
||||||
|
|
||||||
class TradeSchema(BaseModel):
|
class TradeSchema(BaseModel):
|
||||||
|
|
|
@ -214,7 +214,8 @@ def reload_config(rpc: RPC = Depends(get_rpc)):
|
||||||
|
|
||||||
|
|
||||||
@router.get('/pair_candles', response_model=PairHistory, tags=['candle data'])
|
@router.get('/pair_candles', response_model=PairHistory, tags=['candle data'])
|
||||||
def pair_candles(pair: str, timeframe: str, limit: Optional[int], rpc: RPC = Depends(get_rpc)):
|
def pair_candles(
|
||||||
|
pair: str, timeframe: str, limit: Optional[int] = None, rpc: RPC = Depends(get_rpc)):
|
||||||
return rpc._rpc_analysed_dataframe(pair, timeframe, limit)
|
return rpc._rpc_analysed_dataframe(pair, timeframe, limit)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,9 @@ class CryptoToFiatConverter:
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
found = [x for x in self._coinlistings if x['symbol'] == crypto_symbol]
|
found = [x for x in self._coinlistings if x['symbol'] == crypto_symbol]
|
||||||
|
if crypto_symbol == 'eth':
|
||||||
|
found = [x for x in self._coinlistings if x['id'] == 'ethereum']
|
||||||
|
|
||||||
if len(found) == 1:
|
if len(found) == 1:
|
||||||
return found[0]['id']
|
return found[0]['id']
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,12 @@ class RPC:
|
||||||
'ask_strategy': config.get('ask_strategy', {}),
|
'ask_strategy': config.get('ask_strategy', {}),
|
||||||
'bid_strategy': config.get('bid_strategy', {}),
|
'bid_strategy': config.get('bid_strategy', {}),
|
||||||
'state': str(botstate),
|
'state': str(botstate),
|
||||||
'runmode': config['runmode'].value
|
'runmode': config['runmode'].value,
|
||||||
|
'position_adjustment_enable': config.get('position_adjustment_enable', False),
|
||||||
|
'max_entry_position_adjustment': (
|
||||||
|
config.get('max_entry_position_adjustment', -1)
|
||||||
|
if config.get('max_entry_position_adjustment') != float('inf')
|
||||||
|
else -1)
|
||||||
}
|
}
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
@ -247,8 +252,11 @@ class RPC:
|
||||||
profit_str
|
profit_str
|
||||||
]
|
]
|
||||||
if self._config.get('position_adjustment_enable', False):
|
if self._config.get('position_adjustment_enable', False):
|
||||||
filled_buys = trade.select_filled_orders('buy')
|
max_buy_str = ''
|
||||||
detail_trade.append(str(len(filled_buys)))
|
if self._config.get('max_entry_position_adjustment', -1) > 0:
|
||||||
|
max_buy_str = f"/{self._config['max_entry_position_adjustment'] + 1}"
|
||||||
|
filled_buys = trade.nr_of_successful_buys
|
||||||
|
detail_trade.append(f"{filled_buys}{max_buy_str}")
|
||||||
trades_list.append(detail_trade)
|
trades_list.append(detail_trade)
|
||||||
profitcol = "Profit"
|
profitcol = "Profit"
|
||||||
if self._fiat_converter:
|
if self._fiat_converter:
|
||||||
|
|
|
@ -1347,6 +1347,14 @@ class Telegram(RPCHandler):
|
||||||
else:
|
else:
|
||||||
sl_info = f"*Stoploss:* `{val['stoploss']}`\n"
|
sl_info = f"*Stoploss:* `{val['stoploss']}`\n"
|
||||||
|
|
||||||
|
if val['position_adjustment_enable']:
|
||||||
|
pa_info = (
|
||||||
|
f"*Position adjustment:* On\n"
|
||||||
|
f"*Max enter position adjustment:* `{val['max_entry_position_adjustment']}`\n"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pa_info = "*Position adjustment:* Off\n"
|
||||||
|
|
||||||
self._send_msg(
|
self._send_msg(
|
||||||
f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n"
|
f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n"
|
||||||
f"*Exchange:* `{val['exchange']}`\n"
|
f"*Exchange:* `{val['exchange']}`\n"
|
||||||
|
@ -1356,6 +1364,7 @@ class Telegram(RPCHandler):
|
||||||
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
|
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
|
||||||
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
|
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
|
||||||
f"{sl_info}"
|
f"{sl_info}"
|
||||||
|
f"{pa_info}"
|
||||||
f"*Timeframe:* `{val['timeframe']}`\n"
|
f"*Timeframe:* `{val['timeframe']}`\n"
|
||||||
f"*Strategy:* `{val['strategy']}`\n"
|
f"*Strategy:* `{val['strategy']}`\n"
|
||||||
f"*Current state:* `{val['state']}`"
|
f"*Current state:* `{val['state']}`"
|
||||||
|
|
|
@ -108,6 +108,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||||
|
|
||||||
# Position adjustment is disabled by default
|
# Position adjustment is disabled by default
|
||||||
position_adjustment_enable: bool = False
|
position_adjustment_enable: bool = False
|
||||||
|
max_entry_position_adjustment: int = -1
|
||||||
|
|
||||||
# Number of seconds after which the candle will no longer result in a buy on expired candles
|
# Number of seconds after which the candle will no longer result in a buy on expired candles
|
||||||
ignore_buying_expired_candle_after: int = 0
|
ignore_buying_expired_candle_after: int = 0
|
||||||
|
|
|
@ -10,14 +10,14 @@ mypy==0.931
|
||||||
pytest==6.2.5
|
pytest==6.2.5
|
||||||
pytest-asyncio==0.17.2
|
pytest-asyncio==0.17.2
|
||||||
pytest-cov==3.0.0
|
pytest-cov==3.0.0
|
||||||
pytest-mock==3.6.1
|
pytest-mock==3.7.0
|
||||||
pytest-random-order==1.0.4
|
pytest-random-order==1.0.4
|
||||||
isort==5.10.1
|
isort==5.10.1
|
||||||
# For datetime mocking
|
# For datetime mocking
|
||||||
time-machine==2.6.0
|
time-machine==2.6.0
|
||||||
|
|
||||||
# Convert jupyter notebooks to markdown documents
|
# Convert jupyter notebooks to markdown documents
|
||||||
nbconvert==6.4.0
|
nbconvert==6.4.1
|
||||||
|
|
||||||
# mypy types
|
# mypy types
|
||||||
types-cachetools==4.2.9
|
types-cachetools==4.2.9
|
||||||
|
@ -26,4 +26,4 @@ types-requests==2.27.7
|
||||||
types-tabulate==0.8.5
|
types-tabulate==0.8.5
|
||||||
|
|
||||||
# Extensions to datetime library
|
# Extensions to datetime library
|
||||||
types-python-dateutil==2.8.8
|
types-python-dateutil==2.8.9
|
|
@ -1,15 +1,14 @@
|
||||||
numpy==1.21.5; python_version <= '3.7'
|
numpy==1.22.1
|
||||||
numpy==1.22.1; python_version > '3.7'
|
pandas==1.4.0
|
||||||
pandas==1.3.5
|
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==1.68.20
|
ccxt==1.71.46
|
||||||
# Pin cryptography for now due to rust build errors with piwheels
|
# Pin cryptography for now due to rust build errors with piwheels
|
||||||
cryptography==36.0.1
|
cryptography==36.0.1
|
||||||
aiohttp==3.8.1
|
aiohttp==3.8.1
|
||||||
SQLAlchemy==1.4.31
|
SQLAlchemy==1.4.31
|
||||||
python-telegram-bot==13.10
|
python-telegram-bot==13.10
|
||||||
arrow==1.2.1
|
arrow==1.2.2
|
||||||
cachetools==4.2.2
|
cachetools==4.2.2
|
||||||
requests==2.27.1
|
requests==2.27.1
|
||||||
urllib3==1.26.8
|
urllib3==1.26.8
|
||||||
|
@ -33,7 +32,7 @@ sdnotify==0.3.2
|
||||||
|
|
||||||
# API Server
|
# API Server
|
||||||
fastapi==0.73.0
|
fastapi==0.73.0
|
||||||
uvicorn==0.17.0
|
uvicorn==0.17.1
|
||||||
pyjwt==2.3.0
|
pyjwt==2.3.0
|
||||||
aiofiles==0.8.0
|
aiofiles==0.8.0
|
||||||
psutil==5.9.0
|
psutil==5.9.0
|
||||||
|
@ -42,6 +41,6 @@ psutil==5.9.0
|
||||||
colorama==0.4.4
|
colorama==0.4.4
|
||||||
# Building config files interactively
|
# Building config files interactively
|
||||||
questionary==1.10.0
|
questionary==1.10.0
|
||||||
prompt-toolkit==3.0.24
|
prompt-toolkit==3.0.26
|
||||||
# Extensions to datetime library
|
# Extensions to datetime library
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
|
|
|
@ -14,7 +14,6 @@ classifiers =
|
||||||
Environment :: Console
|
Environment :: Console
|
||||||
Intended Audience :: Science/Research
|
Intended Audience :: Science/Research
|
||||||
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
||||||
Programming Language :: Python :: 3.7
|
|
||||||
Programming Language :: Python :: 3.8
|
Programming Language :: Python :: 3.8
|
||||||
Programming Language :: Python :: 3.9
|
Programming Language :: Python :: 3.9
|
||||||
Programming Language :: Python :: 3.10
|
Programming Language :: Python :: 3.10
|
||||||
|
|
6
setup.sh
6
setup.sh
|
@ -25,7 +25,7 @@ function check_installed_python() {
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for v in 9 10 8 7
|
for v in 9 10 8
|
||||||
do
|
do
|
||||||
PYTHON="python3.${v}"
|
PYTHON="python3.${v}"
|
||||||
which $PYTHON
|
which $PYTHON
|
||||||
|
@ -219,7 +219,7 @@ function install() {
|
||||||
install_redhat
|
install_redhat
|
||||||
else
|
else
|
||||||
echo "This script does not support your OS."
|
echo "This script does not support your OS."
|
||||||
echo "If you have Python version 3.7 - 3.10, pip, virtualenv, ta-lib you can continue."
|
echo "If you have Python version 3.8 - 3.10, pip, virtualenv, ta-lib you can continue."
|
||||||
echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell."
|
echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell."
|
||||||
sleep 10
|
sleep 10
|
||||||
fi
|
fi
|
||||||
|
@ -246,7 +246,7 @@ function help() {
|
||||||
echo " -p,--plot Install dependencies for Plotting scripts."
|
echo " -p,--plot Install dependencies for Plotting scripts."
|
||||||
}
|
}
|
||||||
|
|
||||||
# Verify if 3.7 or 3.8 is installed
|
# Verify if 3.8+ is installed
|
||||||
check_installed_python
|
check_installed_python
|
||||||
|
|
||||||
case $* in
|
case $* in
|
||||||
|
|
|
@ -148,10 +148,13 @@ def test_fiat_multiple_coins(mocker, caplog):
|
||||||
{'id': 'helium', 'symbol': 'hnt', 'name': 'Helium'},
|
{'id': 'helium', 'symbol': 'hnt', 'name': 'Helium'},
|
||||||
{'id': 'hymnode', 'symbol': 'hnt', 'name': 'Hymnode'},
|
{'id': 'hymnode', 'symbol': 'hnt', 'name': 'Hymnode'},
|
||||||
{'id': 'bitcoin', 'symbol': 'btc', 'name': 'Bitcoin'},
|
{'id': 'bitcoin', 'symbol': 'btc', 'name': 'Bitcoin'},
|
||||||
|
{'id': 'ethereum', 'symbol': 'eth', 'name': 'Ethereum'},
|
||||||
|
{'id': 'ethereum-wormhole', 'symbol': 'eth', 'name': 'Ethereum Wormhole'},
|
||||||
]
|
]
|
||||||
|
|
||||||
assert fiat_convert._get_gekko_id('btc') == 'bitcoin'
|
assert fiat_convert._get_gekko_id('btc') == 'bitcoin'
|
||||||
assert fiat_convert._get_gekko_id('hnt') is None
|
assert fiat_convert._get_gekko_id('hnt') is None
|
||||||
|
assert fiat_convert._get_gekko_id('eth') == 'ethereum'
|
||||||
|
|
||||||
assert log_has('Found multiple mappings in goingekko for hnt.', caplog)
|
assert log_has('Found multiple mappings in goingekko for hnt.', caplog)
|
||||||
|
|
||||||
|
|
|
@ -221,9 +221,13 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||||
assert '-0.06' == f'{fiat_profit_sum:.2f}'
|
assert '-0.06' == f'{fiat_profit_sum:.2f}'
|
||||||
|
|
||||||
rpc._config['position_adjustment_enable'] = True
|
rpc._config['position_adjustment_enable'] = True
|
||||||
|
rpc._config['max_entry_position_adjustment'] = 3
|
||||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||||
assert "# Buys" in headers
|
assert "# Buys" in headers
|
||||||
assert len(result[0]) == 5
|
assert len(result[0]) == 5
|
||||||
|
# 4th column should be 1/4 - as 1 order filled (a total of 4 is possible)
|
||||||
|
# 3 on top of the initial one.
|
||||||
|
assert result[0][4] == '1/4'
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
mocker.patch('freqtrade.exchange.Exchange.get_rate',
|
||||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||||
|
|
|
@ -4550,3 +4550,32 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
|
||||||
# Make sure the closed order is found as the second order.
|
# Make sure the closed order is found as the second order.
|
||||||
order = trade.select_order('buy', False)
|
order = trade.select_order('buy', False)
|
||||||
assert order.order_id == '652'
|
assert order.order_id == '652'
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||||
|
default_conf_usdt.update({
|
||||||
|
"position_adjustment_enable": True,
|
||||||
|
})
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.check_and_call_adjust_trade_position',
|
||||||
|
side_effect=DependencyException())
|
||||||
|
|
||||||
|
create_mock_trades(fee)
|
||||||
|
|
||||||
|
freqtrade.process_open_trade_positions()
|
||||||
|
assert log_has_re(r"Unable to adjust position of trade for .*", caplog)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, caplog) -> None:
|
||||||
|
default_conf_usdt.update({
|
||||||
|
"position_adjustment_enable": True,
|
||||||
|
"max_entry_position_adjustment": 0,
|
||||||
|
})
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||||
|
|
||||||
|
create_mock_trades(fee)
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
|
|
||||||
|
freqtrade.process_open_trade_positions()
|
||||||
|
assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user