Merge branch 'develop' of https://github.com/freqtrade/freqtrade into feature/flask-rest
This commit is contained in:
commit
85851bd026
171
README.md
171
README.md
@ -22,33 +22,10 @@ expect.
|
|||||||
We strongly recommend you to have coding and Python knowledge. Do not
|
We strongly recommend you to have coding and Python knowledge. Do not
|
||||||
hesitate to read the source code and understand the mechanism of this bot.
|
hesitate to read the source code and understand the mechanism of this bot.
|
||||||
|
|
||||||
## Table of Contents
|
## Exchange marketplaces supported
|
||||||
- [Features](#features)
|
- [X] [Bittrex](https://bittrex.com/)
|
||||||
- [Quick start](#quick-start)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [Documentations](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
|
- [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
||||||
- [Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
|
|
||||||
- [Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
|
|
||||||
- [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
|
|
||||||
- [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
|
|
||||||
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
|
|
||||||
- [Support](#support)
|
|
||||||
- [Help](#help--slack)
|
|
||||||
- [Bugs](#bugs--issues)
|
|
||||||
- [Feature Requests](#feature-requests)
|
|
||||||
- [Pull Requests](#pull-requests)
|
|
||||||
- [Basic Usage](#basic-usage)
|
|
||||||
- [Bot commands](#bot-commands)
|
|
||||||
- [Telegram RPC commands](#telegram-rpc-commands)
|
|
||||||
- [Requirements](#requirements)
|
|
||||||
- [Min hardware required](#min-hardware-required)
|
|
||||||
- [Software requirements](#software-requirements)
|
|
||||||
|
|
||||||
## Branches
|
|
||||||
The project is currently setup in two main branches:
|
|
||||||
- `develop` - This branch has often new features, but might also cause
|
|
||||||
breaking changes.
|
|
||||||
- `master` - This branch contains the latest stable release. The bot
|
|
||||||
'should' be stable on this branch, and is generally well tested.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- [x] **Based on Python 3.6+**: For botting on any operating system -
|
- [x] **Based on Python 3.6+**: For botting on any operating system -
|
||||||
@ -65,74 +42,50 @@ strategy parameters with real exchange data.
|
|||||||
- [x] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss.
|
- [x] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss.
|
||||||
- [x] **Performance status report**: Provide a performance status of your current trades.
|
- [x] **Performance status report**: Provide a performance status of your current trades.
|
||||||
|
|
||||||
### Exchange marketplaces supported
|
## Table of Contents
|
||||||
- [X] [Bittrex](https://bittrex.com/)
|
- [Quick start](#quick-start)
|
||||||
- [X] [Binance](https://www.binance.com/)
|
- [Documentations](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
|
||||||
- [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
- [Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
|
||||||
|
- [Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
|
||||||
|
- [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
|
||||||
|
- [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
|
||||||
|
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
|
||||||
|
- [Basic Usage](#basic-usage)
|
||||||
|
- [Bot commands](#bot-commands)
|
||||||
|
- [Telegram RPC commands](#telegram-rpc-commands)
|
||||||
|
- [Support](#support)
|
||||||
|
- [Help](#help--slack)
|
||||||
|
- [Bugs](#bugs--issues)
|
||||||
|
- [Feature Requests](#feature-requests)
|
||||||
|
- [Pull Requests](#pull-requests)
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Min hardware required](#min-hardware-required)
|
||||||
|
- [Software requirements](#software-requirements)
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
This quick start section is a very short explanation on how to test the
|
Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot.
|
||||||
bot in dry-run. We invite you to read the
|
|
||||||
[bot documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
|
|
||||||
to ensure you understand how the bot is working.
|
|
||||||
|
|
||||||
### Easy installation
|
|
||||||
The script below will install all dependencies and help you to configure the bot.
|
|
||||||
```bash
|
|
||||||
./setup.sh --install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual installation
|
|
||||||
The following steps are made for Linux/MacOS environment
|
|
||||||
|
|
||||||
**1. Clone the repo**
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:freqtrade/freqtrade.git
|
git clone git@github.com:freqtrade/freqtrade.git
|
||||||
git checkout develop
|
git checkout develop
|
||||||
cd freqtrade
|
cd freqtrade
|
||||||
|
./setup.sh --install
|
||||||
```
|
```
|
||||||
**2. Create the config file**
|
_Windows installation is explained in [Installation doc](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)_
|
||||||
Switch `"dry_run": true,`
|
|
||||||
```bash
|
|
||||||
cp config.json.example config.json
|
|
||||||
vi config.json
|
|
||||||
```
|
|
||||||
**3. Build your docker image and run it**
|
|
||||||
```bash
|
|
||||||
docker build -t freqtrade .
|
|
||||||
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Help / Slack
|
## Documentation
|
||||||
For any questions not covered by the documentation or for further
|
We invite you to read the bot documentation to ensure you understand how the bot is working.
|
||||||
information about the bot, we encourage you to join our slack channel.
|
- [Index](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
|
||||||
- [Click here to join Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE).
|
- [Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
|
||||||
|
- [Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
|
||||||
|
- [Bot usage](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md)
|
||||||
|
- [How to run the bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
|
||||||
|
- [How to use Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
|
||||||
|
- [How to use Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
|
||||||
|
- [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
|
||||||
|
- [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
|
||||||
|
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
|
||||||
|
|
||||||
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
|
|
||||||
If you discover a bug in the bot, please
|
|
||||||
[search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
|
|
||||||
first. If it hasn't been reported, please
|
|
||||||
[create a new issue](https://github.com/freqtrade/freqtrade/issues/new) and
|
|
||||||
ensure you follow the template guide so that our team can assist you as
|
|
||||||
quickly as possible.
|
|
||||||
|
|
||||||
### [Feature Requests](https://github.com/freqtrade/freqtrade/labels/enhancement)
|
|
||||||
Have you a great idea to improve the bot you want to share? Please,
|
|
||||||
first search if this feature was not [already discussed](https://github.com/freqtrade/freqtrade/labels/enhancement).
|
|
||||||
If it hasn't been requested, please
|
|
||||||
[create a new request](https://github.com/freqtrade/freqtrade/issues/new)
|
|
||||||
and ensure you follow the template guide so that it does not get lost
|
|
||||||
in the bug reports.
|
|
||||||
|
|
||||||
### [Pull Requests](https://github.com/freqtrade/freqtrade/pulls)
|
|
||||||
Feel like our bot is missing a feature? We welcome your pull requests!
|
|
||||||
Please read our
|
|
||||||
[Contributing document](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
|
|
||||||
to understand the requirements before sending your pull-requests.
|
|
||||||
|
|
||||||
**Important:** Always create your PR against the `develop` branch, not
|
|
||||||
`master`.
|
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
@ -170,11 +123,7 @@ optional arguments:
|
|||||||
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
||||||
only if dry_run is enabled.
|
only if dry_run is enabled.
|
||||||
```
|
```
|
||||||
More details on:
|
|
||||||
- [How to run the bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
|
|
||||||
- [How to use Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
|
|
||||||
- [How to use Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
|
|
||||||
|
|
||||||
### Telegram RPC commands
|
### Telegram RPC commands
|
||||||
Telegram is not mandatory. However, this is a great way to control your
|
Telegram is not mandatory. However, this is a great way to control your
|
||||||
bot. More details on our
|
bot. More details on our
|
||||||
@ -193,6 +142,48 @@ bot. More details on our
|
|||||||
- `/help`: Show help message
|
- `/help`: Show help message
|
||||||
- `/version`: Show version
|
- `/version`: Show version
|
||||||
|
|
||||||
|
|
||||||
|
## Development branches
|
||||||
|
The project is currently setup in two main branches:
|
||||||
|
- `develop` - This branch has often new features, but might also cause
|
||||||
|
breaking changes.
|
||||||
|
- `master` - This branch contains the latest stable release. The bot
|
||||||
|
'should' be stable on this branch, and is generally well tested.
|
||||||
|
|
||||||
|
|
||||||
|
## Support
|
||||||
|
### Help / Slack
|
||||||
|
For any questions not covered by the documentation or for further
|
||||||
|
information about the bot, we encourage you to join our slack channel.
|
||||||
|
- [Click here to join Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE).
|
||||||
|
|
||||||
|
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
|
||||||
|
If you discover a bug in the bot, please
|
||||||
|
[search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
|
||||||
|
first. If it hasn't been reported, please
|
||||||
|
[create a new issue](https://github.com/freqtrade/freqtrade/issues/new) and
|
||||||
|
ensure you follow the template guide so that our team can assist you as
|
||||||
|
quickly as possible.
|
||||||
|
|
||||||
|
### [Feature Requests](https://github.com/freqtrade/freqtrade/labels/enhancement)
|
||||||
|
Have you a great idea to improve the bot you want to share? Please,
|
||||||
|
first search if this feature was not [already discussed](https://github.com/freqtrade/freqtrade/labels/enhancement).
|
||||||
|
If it hasn't been requested, please
|
||||||
|
[create a new request](https://github.com/freqtrade/freqtrade/issues/new)
|
||||||
|
and ensure you follow the template guide so that it does not get lost
|
||||||
|
in the bug reports.
|
||||||
|
|
||||||
|
### [Pull Requests](https://github.com/freqtrade/freqtrade/pulls)
|
||||||
|
Feel like our bot is missing a feature? We welcome your pull requests!
|
||||||
|
Please read our
|
||||||
|
[Contributing document](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
|
||||||
|
to understand the requirements before sending your pull-requests.
|
||||||
|
|
||||||
|
**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
|
||||||
|
|
||||||
|
**Important:** Always create your PR against the `develop` branch, not
|
||||||
|
`master`.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
### Min hardware required
|
### Min hardware required
|
||||||
|
@ -31,7 +31,8 @@
|
|||||||
},
|
},
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"use_sell_signal": false,
|
"use_sell_signal": false,
|
||||||
"sell_profit_only": false
|
"sell_profit_only": false,
|
||||||
|
"ignore_roi_if_buy_signal": false
|
||||||
},
|
},
|
||||||
"telegram": {
|
"telegram": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
@ -38,7 +38,8 @@
|
|||||||
},
|
},
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"use_sell_signal": false,
|
"use_sell_signal": false,
|
||||||
"sell_profit_only": false
|
"sell_profit_only": false,
|
||||||
|
"ignore_roi_if_buy_signal": false
|
||||||
},
|
},
|
||||||
"telegram": {
|
"telegram": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
@ -16,7 +16,7 @@ The table below will list all configuration parameters.
|
|||||||
|----------|---------|----------|-------------|
|
|----------|---------|----------|-------------|
|
||||||
| `max_open_trades` | 3 | Yes | Number of trades open your bot will have.
|
| `max_open_trades` | 3 | Yes | Number of trades open your bot will have.
|
||||||
| `stake_currency` | BTC | Yes | Crypto-currency used for trading.
|
| `stake_currency` | BTC | Yes | Crypto-currency used for trading.
|
||||||
| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged.
|
| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to 'unlimited' to allow the bot to use all avaliable balance.
|
||||||
| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes
|
| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes
|
||||||
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
|
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
|
||||||
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
|
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
|
||||||
@ -31,6 +31,7 @@ The table below will list all configuration parameters.
|
|||||||
| `exchange.pair_blacklist` | [] | No | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param.
|
| `exchange.pair_blacklist` | [] | No | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param.
|
||||||
| `experimental.use_sell_signal` | false | No | Use your sell strategy in addition of the `minimal_roi`.
|
| `experimental.use_sell_signal` | false | No | Use your sell strategy in addition of the `minimal_roi`.
|
||||||
| `experimental.sell_profit_only` | false | No | waits until you have made a positive profit before taking a sell decision.
|
| `experimental.sell_profit_only` | false | No | waits until you have made a positive profit before taking a sell decision.
|
||||||
|
| `experimental.ignore_roi_if_buy_signal` | false | No | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`
|
||||||
| `telegram.enabled` | true | Yes | Enable or not the usage of Telegram.
|
| `telegram.enabled` | true | Yes | Enable or not the usage of Telegram.
|
||||||
| `telegram.token` | token | No | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
|
| `telegram.token` | token | No | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
|
||||||
| `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
|
| `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
|
||||||
@ -43,6 +44,13 @@ The table below will list all configuration parameters.
|
|||||||
The definition of each config parameters is in
|
The definition of each config parameters is in
|
||||||
[misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.py#L205).
|
[misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.py#L205).
|
||||||
|
|
||||||
|
### Understand stake_amount
|
||||||
|
`stake_amount` is an amount of crypto-currency your bot will use for each trade.
|
||||||
|
The minimal value is 0.0005. If there is not enough crypto-currency in
|
||||||
|
the account an exception is generated.
|
||||||
|
To allow the bot to trade all the avaliable `stake_currency` in your account set `stake_amount` = `unlimited`.
|
||||||
|
In this case a trade amount is calclulated as `currency_balanse / (max_open_trades - current_open_trades)`.
|
||||||
|
|
||||||
### Understand minimal_roi
|
### Understand minimal_roi
|
||||||
`minimal_roi` is a JSON object where the key is a duration
|
`minimal_roi` is a JSON object where the key is a duration
|
||||||
in minutes and the value is the minimum ROI in percent.
|
in minutes and the value is the minimum ROI in percent.
|
||||||
|
@ -8,6 +8,7 @@ To understand how to set up the bot please read the [Bot Configuration](https://
|
|||||||
|
|
||||||
* [Table of Contents](#table-of-contents)
|
* [Table of Contents](#table-of-contents)
|
||||||
* [Easy Installation - Linux Script](#easy-installation---linux-script)
|
* [Easy Installation - Linux Script](#easy-installation---linux-script)
|
||||||
|
* [Manual installation](#manual-installation)
|
||||||
* [Automatic Installation - Docker](#automatic-installation---docker)
|
* [Automatic Installation - Docker](#automatic-installation---docker)
|
||||||
* [Custom Linux MacOS Installation](#custom-installation)
|
* [Custom Linux MacOS Installation](#custom-installation)
|
||||||
- [Requirements](#requirements)
|
- [Requirements](#requirements)
|
||||||
@ -55,6 +56,28 @@ Reset parameter will hard reset your branch (only if you are on `master` or `dev
|
|||||||
|
|
||||||
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
|
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
|
||||||
|
|
||||||
|
|
||||||
|
## Manual installation - Linux/MacOS
|
||||||
|
The following steps are made for Linux/MacOS environment
|
||||||
|
|
||||||
|
**1. Clone the repo**
|
||||||
|
```bash
|
||||||
|
git clone git@github.com:freqtrade/freqtrade.git
|
||||||
|
git checkout develop
|
||||||
|
cd freqtrade
|
||||||
|
```
|
||||||
|
**2. Create the config file**
|
||||||
|
Switch `"dry_run": true,`
|
||||||
|
```bash
|
||||||
|
cp config.json.example config.json
|
||||||
|
vi config.json
|
||||||
|
```
|
||||||
|
**3. Build your docker image and run it**
|
||||||
|
```bash
|
||||||
|
docker build -t freqtrade .
|
||||||
|
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
||||||
|
```
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## Automatic Installation - Docker
|
## Automatic Installation - Docker
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
""" FreqTrade bot """
|
""" FreqTrade bot """
|
||||||
__version__ = '0.17.0'
|
__version__ = '0.17.1'
|
||||||
|
|
||||||
|
|
||||||
class DependencyException(BaseException):
|
class DependencyException(BaseException):
|
||||||
|
@ -98,6 +98,13 @@ class Analyze(object):
|
|||||||
"""
|
"""
|
||||||
return self.strategy.ticker_interval
|
return self.strategy.ticker_interval
|
||||||
|
|
||||||
|
def get_stoploss(self) -> float:
|
||||||
|
"""
|
||||||
|
Return stoploss to use
|
||||||
|
:return: Strategy stoploss value to use
|
||||||
|
"""
|
||||||
|
return self.strategy.stoploss
|
||||||
|
|
||||||
def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame:
|
def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Parses the given ticker history and returns a populated DataFrame
|
Parses the given ticker history and returns a populated DataFrame
|
||||||
@ -172,33 +179,45 @@ class Analyze(object):
|
|||||||
if the threshold is reached and updates the trade record.
|
if the threshold is reached and updates the trade record.
|
||||||
:return: True if trade should be sold, False otherwise
|
:return: True if trade should be sold, False otherwise
|
||||||
"""
|
"""
|
||||||
|
current_profit = trade.calc_profit_percent(rate)
|
||||||
|
if self.stop_loss_reached(current_profit=current_profit):
|
||||||
|
return True
|
||||||
|
|
||||||
|
experimental = self.config.get('experimental', {})
|
||||||
|
|
||||||
|
if buy and experimental.get('ignore_roi_if_buy_signal', False):
|
||||||
|
logger.debug('Buy signal still active - not selling.')
|
||||||
|
return False
|
||||||
|
|
||||||
# Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee)
|
# Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee)
|
||||||
if self.min_roi_reached(trade=trade, current_rate=rate, current_time=date):
|
if self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date):
|
||||||
logger.debug('Required profit reached. Selling..')
|
logger.debug('Required profit reached. Selling..')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
|
if experimental.get('sell_profit_only', False):
|
||||||
if self.config.get('experimental', {}).get('sell_profit_only', False):
|
|
||||||
logger.debug('Checking if trade is profitable..')
|
logger.debug('Checking if trade is profitable..')
|
||||||
if trade.calc_profit(rate=rate) <= 0:
|
if trade.calc_profit(rate=rate) <= 0:
|
||||||
return False
|
return False
|
||||||
|
if sell and not buy and experimental.get('use_sell_signal', False):
|
||||||
if sell and not buy and self.config.get('experimental', {}).get('use_sell_signal', False):
|
|
||||||
logger.debug('Sell signal received. Selling..')
|
logger.debug('Sell signal received. Selling..')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def min_roi_reached(self, trade: Trade, current_rate: float, current_time: datetime) -> bool:
|
def stop_loss_reached(self, current_profit: float) -> bool:
|
||||||
|
"""Based on current profit of the trade and configured stoploss, decides to sell or not"""
|
||||||
|
|
||||||
|
if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss:
|
||||||
|
logger.debug('Stop loss hit.')
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
|
||||||
"""
|
"""
|
||||||
Based an earlier trade and current price and ROI configuration, decides whether bot should
|
Based an earlier trade and current price and ROI configuration, decides whether bot should
|
||||||
sell
|
sell
|
||||||
:return True if bot should sell at current rate
|
:return True if bot should sell at current rate
|
||||||
"""
|
"""
|
||||||
current_profit = trade.calc_profit_percent(current_rate)
|
|
||||||
if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss:
|
|
||||||
logger.debug('Stop loss hit.')
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Check if time matches and current rate is above threshold
|
# Check if time matches and current rate is above threshold
|
||||||
time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60
|
time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60
|
||||||
|
@ -262,17 +262,15 @@ class Arguments(object):
|
|||||||
stop: int = 0
|
stop: int = 0
|
||||||
if stype[0]:
|
if stype[0]:
|
||||||
starts = rvals[index]
|
starts = rvals[index]
|
||||||
if stype[0] == 'date':
|
if stype[0] == 'date' and len(starts) == 8:
|
||||||
start = int(starts) if len(starts) == 10 \
|
start = arrow.get(starts, 'YYYYMMDD').timestamp
|
||||||
else arrow.get(starts, 'YYYYMMDD').timestamp
|
|
||||||
else:
|
else:
|
||||||
start = int(starts)
|
start = int(starts)
|
||||||
index += 1
|
index += 1
|
||||||
if stype[1]:
|
if stype[1]:
|
||||||
stops = rvals[index]
|
stops = rvals[index]
|
||||||
if stype[1] == 'date':
|
if stype[1] == 'date' and len(stops) == 8:
|
||||||
stop = int(stops) if len(stops) == 10 \
|
stop = arrow.get(stops, 'YYYYMMDD').timestamp
|
||||||
else arrow.get(stops, 'YYYYMMDD').timestamp
|
|
||||||
else:
|
else:
|
||||||
stop = int(stops)
|
stop = int(stops)
|
||||||
return TimeRange(stype[0], stype[1], start, stop)
|
return TimeRange(stype[0], stype[1], start, stop)
|
||||||
@ -336,3 +334,10 @@ class Arguments(object):
|
|||||||
nargs='+',
|
nargs='+',
|
||||||
dest='timeframes',
|
dest='timeframes',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.parser.add_argument(
|
||||||
|
'--erase',
|
||||||
|
help='Clean all existing data for the selected exchange/pairs/timeframes',
|
||||||
|
dest='erase',
|
||||||
|
action='store_true'
|
||||||
|
)
|
||||||
|
@ -11,6 +11,8 @@ RETRY_TIMEOUT = 30 # sec
|
|||||||
DEFAULT_STRATEGY = 'DefaultStrategy'
|
DEFAULT_STRATEGY = 'DefaultStrategy'
|
||||||
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
|
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
|
||||||
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
|
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
|
||||||
|
UNLIMITED_STAKE_AMOUNT = 'unlimited'
|
||||||
|
|
||||||
|
|
||||||
TICKER_INTERVAL_MINUTES = {
|
TICKER_INTERVAL_MINUTES = {
|
||||||
'1m': 1,
|
'1m': 1,
|
||||||
@ -44,7 +46,11 @@ CONF_SCHEMA = {
|
|||||||
'max_open_trades': {'type': 'integer', 'minimum': 0},
|
'max_open_trades': {'type': 'integer', 'minimum': 0},
|
||||||
'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())},
|
'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())},
|
||||||
'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT', 'EUR', 'USD']},
|
'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT', 'EUR', 'USD']},
|
||||||
'stake_amount': {'type': 'number', 'minimum': 0.0005},
|
'stake_amount': {
|
||||||
|
"type": ["number", "string"],
|
||||||
|
"minimum": 0.0005,
|
||||||
|
"pattern": UNLIMITED_STAKE_AMOUNT
|
||||||
|
},
|
||||||
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
||||||
'dry_run': {'type': 'boolean'},
|
'dry_run': {'type': 'boolean'},
|
||||||
'minimal_roi': {
|
'minimal_roi': {
|
||||||
@ -73,7 +79,8 @@ CONF_SCHEMA = {
|
|||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'use_sell_signal': {'type': 'boolean'},
|
'use_sell_signal': {'type': 'boolean'},
|
||||||
'sell_profit_only': {'type': 'boolean'}
|
'sell_profit_only': {'type': 'boolean'},
|
||||||
|
"ignore_roi_if_buy_signal_true": {'type': 'boolean'}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'telegram': {
|
'telegram': {
|
||||||
|
@ -244,14 +244,66 @@ class FreqtradeBot(object):
|
|||||||
balance = self.config['bid_strategy']['ask_last_balance']
|
balance = self.config['bid_strategy']['ask_last_balance']
|
||||||
return ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
|
return ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
|
||||||
|
|
||||||
|
def _get_trade_stake_amount(self) -> Optional[float]:
|
||||||
|
stake_amount = self.config['stake_amount']
|
||||||
|
avaliable_amount = self.exchange.get_balance(self.config['stake_currency'])
|
||||||
|
|
||||||
|
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
|
||||||
|
open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all())
|
||||||
|
if open_trades >= self.config['max_open_trades']:
|
||||||
|
logger.warning('Can\'t open a new trade: max number of trades is reached')
|
||||||
|
return None
|
||||||
|
return avaliable_amount / (self.config['max_open_trades'] - open_trades)
|
||||||
|
|
||||||
|
# Check if stake_amount is fulfilled
|
||||||
|
if avaliable_amount < stake_amount:
|
||||||
|
raise DependencyException(
|
||||||
|
'Available balance(%f %s) is lower than stake amount(%f %s)' % (
|
||||||
|
avaliable_amount, self.config['stake_currency'],
|
||||||
|
stake_amount, self.config['stake_currency'])
|
||||||
|
)
|
||||||
|
|
||||||
|
return stake_amount
|
||||||
|
|
||||||
|
def _get_min_pair_stake_amount(self, pair: str, price: float) -> Optional[float]:
|
||||||
|
markets = self.exchange.get_markets()
|
||||||
|
markets = [m for m in markets if m['symbol'] == pair]
|
||||||
|
if not markets:
|
||||||
|
raise ValueError(f'Can\'t get market information for symbol {pair}')
|
||||||
|
|
||||||
|
market = markets[0]
|
||||||
|
|
||||||
|
if 'limits' not in market:
|
||||||
|
return None
|
||||||
|
|
||||||
|
min_stake_amounts = []
|
||||||
|
if 'cost' in market['limits'] and 'min' in market['limits']['cost']:
|
||||||
|
min_stake_amounts.append(market['limits']['cost']['min'])
|
||||||
|
|
||||||
|
if 'amount' in market['limits'] and 'min' in market['limits']['amount']:
|
||||||
|
min_stake_amounts.append(market['limits']['amount']['min'] * price)
|
||||||
|
|
||||||
|
if not min_stake_amounts:
|
||||||
|
return None
|
||||||
|
|
||||||
|
amount_reserve_percent = 1 - 0.05 # reserve 5% + stoploss
|
||||||
|
if self.analyze.get_stoploss() is not None:
|
||||||
|
amount_reserve_percent += self.analyze.get_stoploss()
|
||||||
|
# it should not be more than 50%
|
||||||
|
amount_reserve_percent = max(amount_reserve_percent, 0.5)
|
||||||
|
return min(min_stake_amounts)/amount_reserve_percent
|
||||||
|
|
||||||
def create_trade(self) -> bool:
|
def create_trade(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks the implemented trading indicator(s) for a randomly picked pair,
|
Checks the implemented trading indicator(s) for a randomly picked pair,
|
||||||
if one pair triggers the buy_signal a new trade record gets created
|
if one pair triggers the buy_signal a new trade record gets created
|
||||||
:return: True if a trade object has been created and persisted, False otherwise
|
:return: True if a trade object has been created and persisted, False otherwise
|
||||||
"""
|
"""
|
||||||
stake_amount = self.config['stake_amount']
|
|
||||||
interval = self.analyze.get_ticker_interval()
|
interval = self.analyze.get_ticker_interval()
|
||||||
|
stake_amount = self._get_trade_stake_amount()
|
||||||
|
|
||||||
|
if not stake_amount:
|
||||||
|
return False
|
||||||
stake_currency = self.config['stake_currency']
|
stake_currency = self.config['stake_currency']
|
||||||
fiat_currency = self.config['fiat_display_currency']
|
fiat_currency = self.config['fiat_display_currency']
|
||||||
exc_name = self.exchange.name
|
exc_name = self.exchange.name
|
||||||
@ -261,10 +313,6 @@ class FreqtradeBot(object):
|
|||||||
stake_amount
|
stake_amount
|
||||||
)
|
)
|
||||||
whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
|
whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
|
||||||
# Check if stake_amount is fulfilled
|
|
||||||
if self.exchange.get_balance(stake_currency) < stake_amount:
|
|
||||||
raise DependencyException(
|
|
||||||
f'stake amount is not fulfilled (currency={stake_currency})')
|
|
||||||
|
|
||||||
# Remove currently opened and latest pairs from whitelist
|
# Remove currently opened and latest pairs from whitelist
|
||||||
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
|
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
|
||||||
@ -285,8 +333,18 @@ class FreqtradeBot(object):
|
|||||||
return False
|
return False
|
||||||
pair_s = pair.replace('_', '/')
|
pair_s = pair.replace('_', '/')
|
||||||
pair_url = self.exchange.get_pair_detail_url(pair)
|
pair_url = self.exchange.get_pair_detail_url(pair)
|
||||||
|
|
||||||
# Calculate amount
|
# Calculate amount
|
||||||
buy_limit = self.get_target_bid(self.exchange.get_ticker(pair))
|
buy_limit = self.get_target_bid(self.exchange.get_ticker(pair))
|
||||||
|
|
||||||
|
min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit)
|
||||||
|
if min_stake_amount is not None and min_stake_amount > stake_amount:
|
||||||
|
logger.warning(
|
||||||
|
f'Can\'t open a new trade for {pair_s}: stake amount'
|
||||||
|
f' is too small ({stake_amount} < {min_stake_amount})'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
amount = stake_amount / buy_limit
|
amount = stake_amount / buy_limit
|
||||||
|
|
||||||
order_id = self.exchange.buy(pair, buy_limit, amount)['id']
|
order_id = self.exchange.buy(pair, buy_limit, amount)['id']
|
||||||
@ -423,8 +481,8 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
|
|||||||
current_rate = self.exchange.get_ticker(trade.pair)['bid']
|
current_rate = self.exchange.get_ticker(trade.pair)['bid']
|
||||||
|
|
||||||
(buy, sell) = (False, False)
|
(buy, sell) = (False, False)
|
||||||
|
experimental = self.config.get('experimental', {})
|
||||||
if self.config.get('experimental', {}).get('use_sell_signal'):
|
if experimental.get('use_sell_signal') or experimental.get('ignore_roi_if_buy_signal'):
|
||||||
(buy, sell) = self.analyze.get_signal(self.exchange,
|
(buy, sell) = self.analyze.get_signal(self.exchange,
|
||||||
trade.pair, self.analyze.get_ticker_interval())
|
trade.pair, self.analyze.get_ticker_interval())
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from pandas import DataFrame
|
|||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
|
||||||
import freqtrade.optimize as optimize
|
import freqtrade.optimize as optimize
|
||||||
|
from freqtrade import constants, DependencyException
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.analyze import Analyze
|
from freqtrade.analyze import Analyze
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
@ -341,6 +342,10 @@ def setup_configuration(args: Namespace) -> Dict[str, Any]:
|
|||||||
config['exchange']['key'] = ''
|
config['exchange']['key'] = ''
|
||||||
config['exchange']['secret'] = ''
|
config['exchange']['secret'] = ''
|
||||||
|
|
||||||
|
if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT:
|
||||||
|
raise DependencyException('stake amount could not be "%s" for backtesting' %
|
||||||
|
constants.UNLIMITED_STAKE_AMOUNT)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +39,6 @@ class Hyperopt(Backtesting):
|
|||||||
hyperopt.start()
|
hyperopt.start()
|
||||||
"""
|
"""
|
||||||
def __init__(self, config: Dict[str, Any]) -> None:
|
def __init__(self, config: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
# set TARGET_TRADES to suit your number concurrent trades so its realistic
|
# set TARGET_TRADES to suit your number concurrent trades so its realistic
|
||||||
# to the number of days
|
# to the number of days
|
||||||
|
@ -21,7 +21,6 @@ from freqtrade import OperationalException
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_CONF = {}
|
|
||||||
_DECL_BASE: Any = declarative_base()
|
_DECL_BASE: Any = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
@ -33,9 +32,7 @@ def init(config: Dict) -> None:
|
|||||||
:param config: config to use
|
:param config: config to use
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
_CONF.update(config)
|
db_url = config.get('db_url', None)
|
||||||
|
|
||||||
db_url = _CONF.get('db_url', None)
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
# Take care of thread ownership if in-memory db
|
# Take care of thread ownership if in-memory db
|
||||||
@ -61,7 +58,7 @@ def init(config: Dict) -> None:
|
|||||||
check_migrate(engine)
|
check_migrate(engine)
|
||||||
|
|
||||||
# Clean dry_run DB if the db is not in-memory
|
# Clean dry_run DB if the db is not in-memory
|
||||||
if _CONF.get('dry_run', False) and db_url != 'sqlite://':
|
if config.get('dry_run', False) and db_url != 'sqlite://':
|
||||||
clean_dry_run_db()
|
clean_dry_run_db()
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import logging
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def import_strategy(strategy: IStrategy) -> IStrategy:
|
||||||
|
"""
|
||||||
|
Imports given Strategy instance to global scope
|
||||||
|
of freqtrade.strategy and returns an instance of it
|
||||||
|
"""
|
||||||
|
# Copy all attributes from base class and class
|
||||||
|
attr = deepcopy({**strategy.__class__.__dict__, **strategy.__dict__})
|
||||||
|
# Adjust module name
|
||||||
|
attr['__module__'] = 'freqtrade.strategy'
|
||||||
|
|
||||||
|
name = strategy.__class__.__name__
|
||||||
|
clazz = type(name, (IStrategy,), attr)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
'Imported strategy %s.%s as %s.%s',
|
||||||
|
strategy.__module__, strategy.__class__.__name__,
|
||||||
|
clazz.__module__, strategy.__class__.__name__,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Modify global scope to declare class
|
||||||
|
globals()[name] = clazz
|
||||||
|
|
||||||
|
return clazz()
|
@ -11,6 +11,7 @@ from collections import OrderedDict
|
|||||||
from typing import Optional, Dict, Type
|
from typing import Optional, Dict, Type
|
||||||
|
|
||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
|
from freqtrade.strategy import import_strategy
|
||||||
from freqtrade.strategy.interface import IStrategy
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ class StrategyResolver(object):
|
|||||||
"""
|
"""
|
||||||
current_path = os.path.dirname(os.path.realpath(__file__))
|
current_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
abs_paths = [
|
abs_paths = [
|
||||||
os.path.join(current_path, '..', '..', 'user_data', 'strategies'),
|
os.path.join(os.getcwd(), 'user_data', 'strategies'),
|
||||||
current_path,
|
current_path,
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -80,10 +81,13 @@ class StrategyResolver(object):
|
|||||||
abs_paths.insert(0, extra_dir)
|
abs_paths.insert(0, extra_dir)
|
||||||
|
|
||||||
for path in abs_paths:
|
for path in abs_paths:
|
||||||
strategy = self._search_strategy(path, strategy_name)
|
try:
|
||||||
if strategy:
|
strategy = self._search_strategy(path, strategy_name)
|
||||||
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
|
if strategy:
|
||||||
return strategy
|
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
|
||||||
|
return import_strategy(strategy)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.warning('Path "%s" does not exist', path)
|
||||||
|
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
"Impossible to load Strategy '{}'. This class does not exist"
|
"Impossible to load Strategy '{}'. This class does not exist"
|
||||||
@ -100,7 +104,7 @@ class StrategyResolver(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Generate spec based on absolute path
|
# Generate spec based on absolute path
|
||||||
spec = importlib.util.spec_from_file_location('user_data.strategies', module_path)
|
spec = importlib.util.spec_from_file_location('unknown', module_path)
|
||||||
module = importlib.util.module_from_spec(spec)
|
module = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
|
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
|
||||||
|
|
||||||
|
@ -198,7 +198,10 @@ def markets():
|
|||||||
'max': 1000,
|
'max': 1000,
|
||||||
},
|
},
|
||||||
'price': 500000,
|
'price': 500000,
|
||||||
'cost': 500000,
|
'cost': {
|
||||||
|
'min': 1,
|
||||||
|
'max': 500000,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'info': '',
|
'info': '',
|
||||||
},
|
},
|
||||||
@ -220,7 +223,10 @@ def markets():
|
|||||||
'max': 1000,
|
'max': 1000,
|
||||||
},
|
},
|
||||||
'price': 500000,
|
'price': 500000,
|
||||||
'cost': 500000,
|
'cost': {
|
||||||
|
'min': 1,
|
||||||
|
'max': 500000,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'info': '',
|
'info': '',
|
||||||
},
|
},
|
||||||
@ -242,7 +248,85 @@ def markets():
|
|||||||
'max': 1000,
|
'max': 1000,
|
||||||
},
|
},
|
||||||
'price': 500000,
|
'price': 500000,
|
||||||
'cost': 500000,
|
'cost': {
|
||||||
|
'min': 1,
|
||||||
|
'max': 500000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'info': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'ltcbtc',
|
||||||
|
'symbol': 'LTC/BTC',
|
||||||
|
'base': 'LTC',
|
||||||
|
'quote': 'BTC',
|
||||||
|
'active': False,
|
||||||
|
'precision': {
|
||||||
|
'price': 8,
|
||||||
|
'amount': 8,
|
||||||
|
'cost': 8,
|
||||||
|
},
|
||||||
|
'lot': 0.00000001,
|
||||||
|
'limits': {
|
||||||
|
'amount': {
|
||||||
|
'min': 0.01,
|
||||||
|
'max': 1000,
|
||||||
|
},
|
||||||
|
'price': 500000,
|
||||||
|
'cost': {
|
||||||
|
'min': 1,
|
||||||
|
'max': 500000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'info': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'xrpbtc',
|
||||||
|
'symbol': 'XRP/BTC',
|
||||||
|
'base': 'XRP',
|
||||||
|
'quote': 'BTC',
|
||||||
|
'active': False,
|
||||||
|
'precision': {
|
||||||
|
'price': 8,
|
||||||
|
'amount': 8,
|
||||||
|
'cost': 8,
|
||||||
|
},
|
||||||
|
'lot': 0.00000001,
|
||||||
|
'limits': {
|
||||||
|
'amount': {
|
||||||
|
'min': 0.01,
|
||||||
|
'max': 1000,
|
||||||
|
},
|
||||||
|
'price': 500000,
|
||||||
|
'cost': {
|
||||||
|
'min': 1,
|
||||||
|
'max': 500000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'info': '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'neobtc',
|
||||||
|
'symbol': 'NEO/BTC',
|
||||||
|
'base': 'NEO',
|
||||||
|
'quote': 'BTC',
|
||||||
|
'active': False,
|
||||||
|
'precision': {
|
||||||
|
'price': 8,
|
||||||
|
'amount': 8,
|
||||||
|
'cost': 8,
|
||||||
|
},
|
||||||
|
'lot': 0.00000001,
|
||||||
|
'limits': {
|
||||||
|
'amount': {
|
||||||
|
'min': 0.01,
|
||||||
|
'max': 1000,
|
||||||
|
},
|
||||||
|
'price': 500000,
|
||||||
|
'cost': {
|
||||||
|
'min': 1,
|
||||||
|
'max': 500000,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'info': '',
|
'info': '',
|
||||||
}
|
}
|
||||||
|
@ -510,7 +510,6 @@ def test_cancel_order_dry_run(default_conf, mocker):
|
|||||||
# Ensure that if not dry_run, we should call API
|
# Ensure that if not dry_run, we should call API
|
||||||
def test_cancel_order(default_conf, mocker):
|
def test_cancel_order(default_conf, mocker):
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
# mocker.patch.dict('freqtrade.exchange.._CONF', default_conf)
|
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.cancel_order = MagicMock(return_value=123)
|
api_mock.cancel_order = MagicMock(return_value=123)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
@ -673,7 +672,7 @@ def test_get_markets(default_conf, mocker, markets):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
ret = exchange.get_markets()
|
ret = exchange.get_markets()
|
||||||
assert isinstance(ret, list)
|
assert isinstance(ret, list)
|
||||||
assert len(ret) == 3
|
assert len(ret) == 6
|
||||||
|
|
||||||
assert ret[0]["id"] == "ethbtc"
|
assert ret[0]["id"] == "ethbtc"
|
||||||
assert ret[0]["symbol"] == "ETH/BTC"
|
assert ret[0]["symbol"] == "ETH/BTC"
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
|
import pytest
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import List
|
from typing import List
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
@ -11,7 +12,7 @@ import numpy as np
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from arrow import Arrow
|
from arrow import Arrow
|
||||||
|
|
||||||
from freqtrade import optimize
|
from freqtrade import optimize, constants, DependencyException
|
||||||
from freqtrade.analyze import Analyze
|
from freqtrade.analyze import Analyze
|
||||||
from freqtrade.arguments import Arguments, TimeRange
|
from freqtrade.arguments import Arguments, TimeRange
|
||||||
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
|
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
|
||||||
@ -268,6 +269,28 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
|
||||||
|
"""
|
||||||
|
Test setup_configuration() function
|
||||||
|
"""
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
|
read_data=json.dumps(conf)
|
||||||
|
))
|
||||||
|
|
||||||
|
args = [
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--strategy', 'DefaultStrategy',
|
||||||
|
'backtesting'
|
||||||
|
]
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||||
|
setup_configuration(get_args(args))
|
||||||
|
|
||||||
|
|
||||||
def test_start(mocker, fee, default_conf, caplog) -> None:
|
def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||||
"""
|
"""
|
||||||
Test start() function
|
Test start() function
|
||||||
|
@ -26,7 +26,7 @@ def prec_satoshi(a, b) -> float:
|
|||||||
|
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test rpc_trade_status() method
|
Test rpc_trade_status() method
|
||||||
"""
|
"""
|
||||||
@ -37,7 +37,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
now = arrow.utcnow()
|
now = arrow.utcnow()
|
||||||
@ -74,7 +75,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test rpc_daily_profit() method
|
Test rpc_daily_profit() method
|
||||||
"""
|
"""
|
||||||
@ -85,7 +86,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -125,7 +127,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test rpc_trade_statistics() method
|
Test rpc_trade_statistics() method
|
||||||
"""
|
"""
|
||||||
@ -140,7 +142,8 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -200,7 +203,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
|||||||
|
|
||||||
# Test that rpc_trade_statistics can handle trades that lacks
|
# Test that rpc_trade_statistics can handle trades that lacks
|
||||||
# trade.open_rate (it is set to None)
|
# trade.open_rate (it is set to None)
|
||||||
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
|
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
|
||||||
ticker_sell_up, limit_buy_order, limit_sell_order):
|
ticker_sell_up, limit_buy_order, limit_sell_order):
|
||||||
"""
|
"""
|
||||||
Test rpc_trade_statistics() method
|
Test rpc_trade_statistics() method
|
||||||
@ -216,7 +219,8 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -358,7 +362,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
|
|||||||
assert freqtradebot.state == State.STOPPED
|
assert freqtradebot.state == State.STOPPED
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
|
||||||
"""
|
"""
|
||||||
Test rpc_forcesell() method
|
Test rpc_forcesell() method
|
||||||
"""
|
"""
|
||||||
@ -380,6 +384,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -461,7 +466,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||||
limit_sell_order, mocker) -> None:
|
limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test rpc_performance() method
|
Test rpc_performance() method
|
||||||
"""
|
"""
|
||||||
@ -473,7 +478,8 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
|||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_balances=MagicMock(return_value=ticker),
|
get_balances=MagicMock(return_value=ticker),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -499,7 +505,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
|||||||
assert prec_satoshi(res[0]['profit'], 6.2)
|
assert prec_satoshi(res[0]['profit'], 6.2)
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
|
def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
|
||||||
"""
|
"""
|
||||||
Test rpc_count() method
|
Test rpc_count() method
|
||||||
"""
|
"""
|
||||||
@ -512,6 +518,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
|
|||||||
get_balances=MagicMock(return_value=ticker),
|
get_balances=MagicMock(return_value=ticker),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
|
@ -185,7 +185,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
def test_status_handle(default_conf, markets, update, ticker, fee, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test _status() method
|
Test _status() method
|
||||||
"""
|
"""
|
||||||
@ -196,6 +196,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
|||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
status_table = MagicMock()
|
status_table = MagicMock()
|
||||||
@ -231,7 +232,8 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
|||||||
assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0]
|
assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_status_table_handle(default_conf, limit_buy_order, update, ticker, fee, mocker) -> None:
|
def test_status_table_handle(
|
||||||
|
default_conf, markets, limit_buy_order, update, ticker, fee, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test _status_table() method
|
Test _status_table() method
|
||||||
"""
|
"""
|
||||||
@ -244,6 +246,7 @@ def test_status_table_handle(default_conf, limit_buy_order, update, ticker, fee,
|
|||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_order=MagicMock(return_value=limit_buy_order),
|
get_order=MagicMock(return_value=limit_buy_order),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -285,7 +288,7 @@ def test_status_table_handle(default_conf, limit_buy_order, update, ticker, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
||||||
limit_sell_order, mocker) -> None:
|
limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test _daily() method
|
Test _daily() method
|
||||||
"""
|
"""
|
||||||
@ -299,7 +302,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -397,7 +401,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test _profit() method
|
Test _profit() method
|
||||||
"""
|
"""
|
||||||
@ -408,7 +412,8 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -661,7 +666,8 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None:
|
|||||||
assert 'reloading config' in msg_mock.call_args_list[0][0][0]
|
assert 'reloading config' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, mocker) -> None:
|
def test_forcesell_handle(default_conf, update, ticker, fee,
|
||||||
|
ticker_sell_up, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test _forcesell() method
|
Test _forcesell() method
|
||||||
"""
|
"""
|
||||||
@ -674,7 +680,8 @@ def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, moc
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -701,7 +708,8 @@ def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, moc
|
|||||||
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
|
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_down, mocker) -> None:
|
def test_forcesell_down_handle(default_conf, update, ticker, fee,
|
||||||
|
ticker_sell_down, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test _forcesell() method
|
Test _forcesell() method
|
||||||
"""
|
"""
|
||||||
@ -714,7 +722,8 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_do
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -745,7 +754,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_do
|
|||||||
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0]
|
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None:
|
def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test _forcesell() method
|
Test _forcesell() method
|
||||||
"""
|
"""
|
||||||
@ -759,7 +768,8 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -823,7 +833,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_performance_handle(default_conf, update, ticker, fee,
|
def test_performance_handle(default_conf, update, ticker, fee,
|
||||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test _performance() method
|
Test _performance() method
|
||||||
"""
|
"""
|
||||||
@ -839,7 +849,8 @@ def test_performance_handle(default_conf, update, ticker, fee,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
@ -887,7 +898,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
|
|||||||
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
assert 'not running' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
|
def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test _count() method
|
Test _count() method
|
||||||
"""
|
"""
|
||||||
@ -903,7 +914,8 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': 'mocked_order_id'})
|
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
freqtradebot = FreqtradeBot(default_conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
|
@ -1,14 +1,39 @@
|
|||||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from freqtrade.strategy import import_strategy
|
||||||
|
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||||
from freqtrade.strategy.interface import IStrategy
|
from freqtrade.strategy.interface import IStrategy
|
||||||
from freqtrade.strategy.resolver import StrategyResolver
|
from freqtrade.strategy.resolver import StrategyResolver
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_strategy(caplog):
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
|
|
||||||
|
strategy = DefaultStrategy()
|
||||||
|
strategy.some_method = lambda *args, **kwargs: 42
|
||||||
|
|
||||||
|
assert strategy.__module__ == 'freqtrade.strategy.default_strategy'
|
||||||
|
assert strategy.some_method() == 42
|
||||||
|
|
||||||
|
imported_strategy = import_strategy(strategy)
|
||||||
|
|
||||||
|
assert dir(strategy) == dir(imported_strategy)
|
||||||
|
|
||||||
|
assert imported_strategy.__module__ == 'freqtrade.strategy'
|
||||||
|
assert imported_strategy.some_method() == 42
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'freqtrade.strategy',
|
||||||
|
logging.DEBUG,
|
||||||
|
'Imported strategy freqtrade.strategy.default_strategy.DefaultStrategy '
|
||||||
|
'as freqtrade.strategy.DefaultStrategy',
|
||||||
|
) in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
def test_search_strategy():
|
def test_search_strategy():
|
||||||
default_location = os.path.join(os.path.dirname(
|
default_location = os.path.join(os.path.dirname(
|
||||||
os.path.realpath(__file__)), '..', '..', 'strategy'
|
os.path.realpath(__file__)), '..', '..', 'strategy'
|
||||||
@ -20,19 +45,21 @@ def test_search_strategy():
|
|||||||
|
|
||||||
|
|
||||||
def test_load_strategy(result):
|
def test_load_strategy(result):
|
||||||
resolver = StrategyResolver()
|
resolver = StrategyResolver({'strategy': 'TestStrategy'})
|
||||||
resolver._load_strategy('TestStrategy')
|
|
||||||
assert hasattr(resolver.strategy, 'populate_indicators')
|
assert hasattr(resolver.strategy, 'populate_indicators')
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
assert 'adx' in resolver.strategy.populate_indicators(result)
|
||||||
|
|
||||||
|
|
||||||
def test_load_strategy_custom_directory(result):
|
def test_load_strategy_invalid_directory(result, caplog):
|
||||||
resolver = StrategyResolver()
|
resolver = StrategyResolver()
|
||||||
extra_dir = os.path.join('some', 'path')
|
extra_dir = os.path.join('some', 'path')
|
||||||
with pytest.raises(
|
resolver._load_strategy('TestStrategy', extra_dir)
|
||||||
FileNotFoundError,
|
|
||||||
match=r".*No such file or directory: '{}'".format(extra_dir)):
|
assert (
|
||||||
resolver._load_strategy('TestStrategy', extra_dir)
|
'freqtrade.strategy.resolver',
|
||||||
|
logging.WARNING,
|
||||||
|
'Path "{}" does not exist'.format(extra_dir),
|
||||||
|
) in caplog.record_tuples
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'populate_indicators')
|
assert hasattr(resolver.strategy, 'populate_indicators')
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
assert 'adx' in resolver.strategy.populate_indicators(result)
|
||||||
|
@ -55,6 +55,18 @@ def test_load_config_missing_attributes(default_conf) -> None:
|
|||||||
configuration._validate_config(conf)
|
configuration._validate_config(conf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_config_incorrect_stake_amount(default_conf) -> None:
|
||||||
|
"""
|
||||||
|
Test the configuration validator with a missing attribute
|
||||||
|
"""
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['stake_amount'] = 'fake'
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError, match=r'.*\'fake\' does not match \'unlimited\'.*'):
|
||||||
|
configuration = Configuration(Namespace())
|
||||||
|
configuration._validate_config(conf)
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_file(default_conf, mocker, caplog) -> None:
|
def test_load_config_file(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
"""
|
||||||
Test Configuration._load_config_file() method
|
Test Configuration._load_config_file() method
|
||||||
|
@ -14,7 +14,7 @@ import arrow
|
|||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from freqtrade import DependencyException, OperationalException, TemporaryError
|
from freqtrade import constants, DependencyException, OperationalException, TemporaryError
|
||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
@ -216,7 +216,210 @@ def test_refresh_whitelist() -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
def test_get_trade_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
||||||
|
"""
|
||||||
|
Test get_trade_stake_amount() method
|
||||||
|
"""
|
||||||
|
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
|
result = freqtrade._get_trade_stake_amount()
|
||||||
|
assert(result == default_conf['stake_amount'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_trade_stake_amount_no_stake_amount(default_conf,
|
||||||
|
ticker,
|
||||||
|
limit_buy_order,
|
||||||
|
fee,
|
||||||
|
mocker) -> None:
|
||||||
|
"""
|
||||||
|
Test get_trade_stake_amount() method
|
||||||
|
"""
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5)
|
||||||
|
)
|
||||||
|
|
||||||
|
# test defined stake amount
|
||||||
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
|
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||||
|
freqtrade._get_trade_stake_amount()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_trade_stake_amount_unlimited_amount(default_conf,
|
||||||
|
ticker,
|
||||||
|
limit_buy_order,
|
||||||
|
fee,
|
||||||
|
markets,
|
||||||
|
mocker) -> None:
|
||||||
|
"""
|
||||||
|
Test get_trade_stake_amount() method
|
||||||
|
"""
|
||||||
|
patch_get_signal(mocker)
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_ticker=ticker,
|
||||||
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
|
get_balance=MagicMock(return_value=default_conf['stake_amount']),
|
||||||
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
|
)
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||||
|
conf['max_open_trades'] = 2
|
||||||
|
|
||||||
|
freqtrade = FreqtradeBot(conf)
|
||||||
|
|
||||||
|
# no open trades, order amount should be 'balance / max_open_trades'
|
||||||
|
result = freqtrade._get_trade_stake_amount()
|
||||||
|
assert result == default_conf['stake_amount'] / conf['max_open_trades']
|
||||||
|
|
||||||
|
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
|
||||||
|
freqtrade.create_trade()
|
||||||
|
|
||||||
|
result = freqtrade._get_trade_stake_amount()
|
||||||
|
assert result == default_conf['stake_amount'] / (conf['max_open_trades'] - 1)
|
||||||
|
|
||||||
|
# create 2 trades, order amount should be None
|
||||||
|
freqtrade.create_trade()
|
||||||
|
|
||||||
|
result = freqtrade._get_trade_stake_amount()
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
# set max_open_trades = None, so do not trade
|
||||||
|
conf['max_open_trades'] = 0
|
||||||
|
freqtrade = FreqtradeBot(conf)
|
||||||
|
result = freqtrade._get_trade_stake_amount()
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||||
|
"""
|
||||||
|
Test get_trade_stake_amount() method
|
||||||
|
"""
|
||||||
|
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
|
||||||
|
mocker.patch('freqtrade.freqtradebot.Analyze.get_stoploss', MagicMock(return_value=-0.05))
|
||||||
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
|
# no pair found
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC'
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
with pytest.raises(ValueError, match=r'.*get market information.*'):
|
||||||
|
freqtrade._get_min_pair_stake_amount('BNB/BTC', 1)
|
||||||
|
|
||||||
|
# no 'limits' section
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC'
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1)
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
# empty 'limits' section
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC',
|
||||||
|
'limits': {}
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1)
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
# empty 'cost'/'amount' section
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC',
|
||||||
|
'limits': {
|
||||||
|
'cost': {},
|
||||||
|
'amount': {}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1)
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
# min cost is set
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC',
|
||||||
|
'limits': {
|
||||||
|
'cost': {'min': 2},
|
||||||
|
'amount': {}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 1)
|
||||||
|
assert result == 2 / 0.9
|
||||||
|
|
||||||
|
# min amount is set
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC',
|
||||||
|
'limits': {
|
||||||
|
'cost': {},
|
||||||
|
'amount': {'min': 2}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2)
|
||||||
|
assert result == 2 * 2 / 0.9
|
||||||
|
|
||||||
|
# min amount and cost are set (cost is minimal)
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC',
|
||||||
|
'limits': {
|
||||||
|
'cost': {'min': 2},
|
||||||
|
'amount': {'min': 2}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2)
|
||||||
|
assert result == min(2, 2 * 2) / 0.9
|
||||||
|
|
||||||
|
# min amount and cost are set (amount is minial)
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.exchange.Exchange.get_markets',
|
||||||
|
MagicMock(return_value=[{
|
||||||
|
'symbol': 'ETH/BTC',
|
||||||
|
'limits': {
|
||||||
|
'cost': {'min': 8},
|
||||||
|
'amount': {'min': 2}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
)
|
||||||
|
result = freqtrade._get_min_pair_stake_amount('ETH/BTC', 2)
|
||||||
|
assert result == min(8, 2 * 2) / 0.9
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test create_trade() method
|
Test create_trade() method
|
||||||
"""
|
"""
|
||||||
@ -229,6 +432,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> Non
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save state of current whitelist
|
# Save state of current whitelist
|
||||||
@ -252,32 +456,8 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> Non
|
|||||||
assert whitelist == default_conf['exchange']['pair_whitelist']
|
assert whitelist == default_conf['exchange']['pair_whitelist']
|
||||||
|
|
||||||
|
|
||||||
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order,
|
||||||
"""
|
fee, markets, mocker) -> None:
|
||||||
Test create_trade() method
|
|
||||||
"""
|
|
||||||
patch_get_signal(mocker)
|
|
||||||
patch_RPCManager(mocker)
|
|
||||||
patch_coinmarketcap(mocker)
|
|
||||||
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
|
|
||||||
mocker.patch.multiple(
|
|
||||||
'freqtrade.exchange.Exchange',
|
|
||||||
validate_pairs=MagicMock(),
|
|
||||||
get_ticker=ticker,
|
|
||||||
buy=buy_mock,
|
|
||||||
get_fee=fee,
|
|
||||||
)
|
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
|
||||||
conf['stake_amount'] = 0.0005
|
|
||||||
freqtrade = FreqtradeBot(conf)
|
|
||||||
|
|
||||||
freqtrade.create_trade()
|
|
||||||
rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2]
|
|
||||||
assert rate * amount >= conf['stake_amount']
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
|
||||||
"""
|
"""
|
||||||
Test create_trade() method
|
Test create_trade() method
|
||||||
"""
|
"""
|
||||||
@ -291,6 +471,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee
|
|||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5),
|
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
@ -298,7 +479,87 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee
|
|||||||
freqtrade.create_trade()
|
freqtrade.create_trade()
|
||||||
|
|
||||||
|
|
||||||
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order,
|
||||||
|
fee, markets, mocker) -> None:
|
||||||
|
"""
|
||||||
|
Test create_trade() method
|
||||||
|
"""
|
||||||
|
patch_get_signal(mocker)
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
|
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_ticker=ticker,
|
||||||
|
buy=buy_mock,
|
||||||
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
|
)
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['stake_amount'] = 0.0005
|
||||||
|
freqtrade = FreqtradeBot(conf)
|
||||||
|
|
||||||
|
freqtrade.create_trade()
|
||||||
|
rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2]
|
||||||
|
assert rate * amount >= conf['stake_amount']
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order,
|
||||||
|
fee, markets, mocker) -> None:
|
||||||
|
"""
|
||||||
|
Test create_trade() method
|
||||||
|
"""
|
||||||
|
patch_get_signal(mocker)
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
|
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_ticker=ticker,
|
||||||
|
buy=buy_mock,
|
||||||
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
|
)
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['stake_amount'] = 0.000000005
|
||||||
|
freqtrade = FreqtradeBot(conf)
|
||||||
|
|
||||||
|
result = freqtrade.create_trade()
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
|
||||||
|
fee, markets, mocker) -> None:
|
||||||
|
"""
|
||||||
|
Test create_trade() method
|
||||||
|
"""
|
||||||
|
patch_get_signal(mocker)
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_ticker=ticker,
|
||||||
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
|
get_balance=MagicMock(return_value=default_conf['stake_amount']),
|
||||||
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
|
)
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['max_open_trades'] = 0
|
||||||
|
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||||
|
|
||||||
|
freqtrade = FreqtradeBot(conf)
|
||||||
|
|
||||||
|
assert freqtrade.create_trade() is False
|
||||||
|
assert freqtrade._get_trade_stake_amount() is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test create_trade() method
|
Test create_trade() method
|
||||||
"""
|
"""
|
||||||
@ -311,6 +572,7 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocke
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
@ -325,7 +587,7 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocke
|
|||||||
|
|
||||||
|
|
||||||
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
|
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
|
||||||
limit_buy_order, fee, mocker) -> None:
|
limit_buy_order, fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test create_trade() method
|
Test create_trade() method
|
||||||
"""
|
"""
|
||||||
@ -338,6 +600,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
@ -616,7 +879,8 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf,
|
|||||||
assert log_has('Unable to sell trade: ', caplog.record_tuples)
|
assert log_has('Unable to sell trade: ', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mocker) -> None:
|
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
|
||||||
|
fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test check_handle() method
|
Test check_handle() method
|
||||||
"""
|
"""
|
||||||
@ -632,7 +896,8 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock
|
|||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||||
|
|
||||||
@ -660,7 +925,8 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock
|
|||||||
assert trade.close_date is not None
|
assert trade.close_date is not None
|
||||||
|
|
||||||
|
|
||||||
def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
|
def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order,
|
||||||
|
fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test check_handle() method
|
Test check_handle() method
|
||||||
"""
|
"""
|
||||||
@ -677,6 +943,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee,
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(conf)
|
freqtrade = FreqtradeBot(conf)
|
||||||
@ -718,7 +985,8 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee,
|
|||||||
assert freqtrade.handle_trade(trades[0]) is True
|
assert freqtrade.handle_trade(trades[0]) is True
|
||||||
|
|
||||||
|
|
||||||
def test_handle_trade_roi(default_conf, ticker, limit_buy_order, fee, mocker, caplog) -> None:
|
def test_handle_trade_roi(default_conf, ticker, limit_buy_order,
|
||||||
|
fee, mocker, markets, caplog) -> None:
|
||||||
"""
|
"""
|
||||||
Test check_handle() method
|
Test check_handle() method
|
||||||
"""
|
"""
|
||||||
@ -735,6 +1003,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, fee, mocker, ca
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
|
|
||||||
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
|
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
|
||||||
@ -755,7 +1024,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, fee, mocker, ca
|
|||||||
|
|
||||||
|
|
||||||
def test_handle_trade_experimental(
|
def test_handle_trade_experimental(
|
||||||
default_conf, ticker, limit_buy_order, fee, mocker, caplog) -> None:
|
default_conf, ticker, limit_buy_order, fee, mocker, markets, caplog) -> None:
|
||||||
"""
|
"""
|
||||||
Test check_handle() method
|
Test check_handle() method
|
||||||
"""
|
"""
|
||||||
@ -772,6 +1041,7 @@ def test_handle_trade_experimental(
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
|
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
|
||||||
|
|
||||||
@ -789,7 +1059,8 @@ def test_handle_trade_experimental(
|
|||||||
assert log_has('Sell signal received. Selling..', caplog.record_tuples)
|
assert log_has('Sell signal received. Selling..', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, fee, mocker) -> None:
|
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
|
||||||
|
fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test check_handle() method
|
Test check_handle() method
|
||||||
"""
|
"""
|
||||||
@ -802,6 +1073,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, fe
|
|||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
@ -1040,7 +1312,7 @@ def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
|
|||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test execute_sell() method with a ticker going UP
|
Test execute_sell() method with a ticker going UP
|
||||||
"""
|
"""
|
||||||
@ -1051,7 +1323,8 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -1081,7 +1354,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
|||||||
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
|
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None:
|
def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test execute_sell() method with a ticker going DOWN
|
Test execute_sell() method with a ticker going DOWN
|
||||||
"""
|
"""
|
||||||
@ -1093,7 +1366,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
@ -1122,7 +1396,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
|||||||
|
|
||||||
|
|
||||||
def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
|
def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
|
||||||
ticker_sell_up, mocker) -> None:
|
ticker_sell_up, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test execute_sell() method with a ticker going DOWN and with a bot config empty
|
Test execute_sell() method with a ticker going DOWN and with a bot config empty
|
||||||
"""
|
"""
|
||||||
@ -1133,7 +1407,8 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
@ -1163,7 +1438,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
|
def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
|
||||||
ticker_sell_down, mocker) -> None:
|
ticker_sell_down, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test execute_sell() method with a ticker going DOWN and with a bot config empty
|
Test execute_sell() method with a ticker going DOWN and with a bot config empty
|
||||||
"""
|
"""
|
||||||
@ -1174,7 +1449,8 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_fee=fee
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
|
|
||||||
@ -1201,7 +1477,8 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
|
|||||||
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, fee, mocker) -> None:
|
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,
|
||||||
|
fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test sell_profit_only feature when enabled
|
Test sell_profit_only feature when enabled
|
||||||
"""
|
"""
|
||||||
@ -1219,6 +1496,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, fee, mock
|
|||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['experimental'] = {
|
conf['experimental'] = {
|
||||||
@ -1234,7 +1512,8 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, fee, mock
|
|||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, fee, mocker) -> None:
|
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order,
|
||||||
|
fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test sell_profit_only feature when disabled
|
Test sell_profit_only feature when disabled
|
||||||
"""
|
"""
|
||||||
@ -1252,6 +1531,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, fee, moc
|
|||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['experimental'] = {
|
conf['experimental'] = {
|
||||||
@ -1267,14 +1547,14 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, fee, moc
|
|||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker) -> None:
|
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test sell_profit_only feature when enabled and we have a loss
|
Test sell_profit_only feature when enabled and we have a loss
|
||||||
"""
|
"""
|
||||||
patch_get_signal(mocker)
|
patch_get_signal(mocker)
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
|
mocker.patch('freqtrade.freqtradebot.Analyze.stop_loss_reached', return_value=False)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
@ -1285,6 +1565,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker
|
|||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
)
|
)
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['experimental'] = {
|
conf['experimental'] = {
|
||||||
@ -1300,7 +1581,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker
|
|||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
|
|
||||||
|
|
||||||
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocker) -> None:
|
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, markets, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test sell_profit_only feature when enabled and we have a loss
|
Test sell_profit_only feature when enabled and we have a loss
|
||||||
"""
|
"""
|
||||||
@ -1312,9 +1593,9 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocke
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=MagicMock(return_value={
|
get_ticker=MagicMock(return_value={
|
||||||
'bid': 0.00000172,
|
'bid': 0.0000172,
|
||||||
'ask': 0.00000173,
|
'ask': 0.0000173,
|
||||||
'last': 0.00000172
|
'last': 0.0000172
|
||||||
}),
|
}),
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
@ -1335,6 +1616,85 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocke
|
|||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, mocker) -> None:
|
||||||
|
"""
|
||||||
|
Test sell_profit_only feature when enabled and we have a loss
|
||||||
|
"""
|
||||||
|
patch_get_signal(mocker)
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
|
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_ticker=MagicMock(return_value={
|
||||||
|
'bid': 0.0000172,
|
||||||
|
'ask': 0.0000173,
|
||||||
|
'last': 0.0000172
|
||||||
|
}),
|
||||||
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
|
get_fee=fee,
|
||||||
|
)
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['experimental'] = {
|
||||||
|
'ignore_roi_if_buy_signal': True
|
||||||
|
}
|
||||||
|
|
||||||
|
freqtrade = FreqtradeBot(conf)
|
||||||
|
freqtrade.create_trade()
|
||||||
|
|
||||||
|
trade = Trade.query.first()
|
||||||
|
trade.update(limit_buy_order)
|
||||||
|
patch_get_signal(mocker, value=(True, True))
|
||||||
|
assert freqtrade.handle_trade(trade) is False
|
||||||
|
|
||||||
|
# Test if buy-signal is absent (should sell due to roi = true)
|
||||||
|
patch_get_signal(mocker, value=(False, True))
|
||||||
|
assert freqtrade.handle_trade(trade) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order,
|
||||||
|
fee, markets, mocker) -> None:
|
||||||
|
"""
|
||||||
|
Test sell_profit_only feature when enabled and we have a loss
|
||||||
|
"""
|
||||||
|
patch_get_signal(mocker)
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
|
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_ticker=MagicMock(return_value={
|
||||||
|
'bid': 0.00000172,
|
||||||
|
'ask': 0.00000173,
|
||||||
|
'last': 0.00000172
|
||||||
|
}),
|
||||||
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
|
get_fee=fee,
|
||||||
|
get_markets=markets
|
||||||
|
)
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['experimental'] = {
|
||||||
|
'ignore_roi_if_buy_signal': False
|
||||||
|
}
|
||||||
|
|
||||||
|
freqtrade = FreqtradeBot(conf)
|
||||||
|
freqtrade.create_trade()
|
||||||
|
|
||||||
|
trade = Trade.query.first()
|
||||||
|
trade.update(limit_buy_order)
|
||||||
|
# Sell due to min_roi_reached
|
||||||
|
patch_get_signal(mocker, value=(True, True))
|
||||||
|
assert freqtrade.handle_trade(trade) is True
|
||||||
|
|
||||||
|
# Test if buy-signal is absent
|
||||||
|
patch_get_signal(mocker, value=(False, True))
|
||||||
|
assert freqtrade.handle_trade(trade) is True
|
||||||
|
|
||||||
|
|
||||||
def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, caplog, mocker):
|
def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, caplog, mocker):
|
||||||
"""
|
"""
|
||||||
Test get_real_amount - fee in quote currency
|
Test get_real_amount - fee in quote currency
|
||||||
|
@ -14,9 +14,7 @@ def init_persistence(default_conf):
|
|||||||
init(default_conf)
|
init(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_init_create_session(default_conf, mocker):
|
def test_init_create_session(default_conf):
|
||||||
mocker.patch.dict('freqtrade.persistence._CONF', default_conf)
|
|
||||||
|
|
||||||
# Check if init create a session
|
# Check if init create a session
|
||||||
init(default_conf)
|
init(default_conf)
|
||||||
assert hasattr(Trade, 'session')
|
assert hasattr(Trade, 'session')
|
||||||
@ -29,20 +27,17 @@ def test_init_custom_db_url(default_conf, mocker):
|
|||||||
# Update path to a value other than default, but still in-memory
|
# Update path to a value other than default, but still in-memory
|
||||||
conf.update({'db_url': 'sqlite:///tmp/freqtrade2_test.sqlite'})
|
conf.update({'db_url': 'sqlite:///tmp/freqtrade2_test.sqlite'})
|
||||||
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
||||||
mocker.patch.dict('freqtrade.persistence._CONF', conf)
|
|
||||||
|
|
||||||
init(conf)
|
init(conf)
|
||||||
assert create_engine_mock.call_count == 1
|
assert create_engine_mock.call_count == 1
|
||||||
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tmp/freqtrade2_test.sqlite'
|
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tmp/freqtrade2_test.sqlite'
|
||||||
|
|
||||||
|
|
||||||
def test_init_invalid_db_url(default_conf, mocker):
|
def test_init_invalid_db_url(default_conf):
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
|
|
||||||
# Update path to a value other than default, but still in-memory
|
# Update path to a value other than default, but still in-memory
|
||||||
conf.update({'db_url': 'unknown:///some.url'})
|
conf.update({'db_url': 'unknown:///some.url'})
|
||||||
mocker.patch.dict('freqtrade.persistence._CONF', conf)
|
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
|
with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
|
||||||
init(conf)
|
init(conf)
|
||||||
|
|
||||||
@ -53,7 +48,6 @@ def test_init_prod_db(default_conf, mocker):
|
|||||||
conf.update({'db_url': constants.DEFAULT_DB_PROD_URL})
|
conf.update({'db_url': constants.DEFAULT_DB_PROD_URL})
|
||||||
|
|
||||||
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
||||||
mocker.patch.dict('freqtrade.persistence._CONF', conf)
|
|
||||||
|
|
||||||
init(conf)
|
init(conf)
|
||||||
assert create_engine_mock.call_count == 1
|
assert create_engine_mock.call_count == 1
|
||||||
@ -66,7 +60,6 @@ def test_init_dryrun_db(default_conf, mocker):
|
|||||||
conf.update({'db_url': constants.DEFAULT_DB_DRYRUN_URL})
|
conf.update({'db_url': constants.DEFAULT_DB_DRYRUN_URL})
|
||||||
|
|
||||||
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
||||||
mocker.patch.dict('freqtrade.persistence._CONF', conf)
|
|
||||||
|
|
||||||
init(conf)
|
init(conf)
|
||||||
assert create_engine_mock.call_count == 1
|
assert create_engine_mock.call_count == 1
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
ccxt==1.14.253
|
ccxt==1.14.257
|
||||||
SQLAlchemy==1.2.8
|
SQLAlchemy==1.2.8
|
||||||
python-telegram-bot==10.1.0
|
python-telegram-bot==10.1.0
|
||||||
arrow==0.12.1
|
arrow==0.12.1
|
||||||
@ -22,7 +22,7 @@ tabulate==0.8.2
|
|||||||
coinmarketcap==5.0.3
|
coinmarketcap==5.0.3
|
||||||
|
|
||||||
# Required for plotting data
|
# Required for plotting data
|
||||||
#plotly==2.3.0
|
#plotly==2.7.0
|
||||||
|
|
||||||
#Added for local rest client
|
#Added for local rest client
|
||||||
Flask==1.0.2
|
Flask==1.0.2
|
||||||
|
@ -3,11 +3,14 @@
|
|||||||
"""This script generate json data from bittrex"""
|
"""This script generate json data from bittrex"""
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import os
|
from pathlib import Path
|
||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
from freqtrade import (arguments, misc)
|
from freqtrade import arguments
|
||||||
|
from freqtrade.arguments import TimeRange
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
|
from freqtrade.optimize import download_backtesting_testdata
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_DL_PATH = 'user_data/data'
|
DEFAULT_DL_PATH = 'user_data/data'
|
||||||
|
|
||||||
@ -17,25 +20,27 @@ args = arguments.parse_args()
|
|||||||
|
|
||||||
timeframes = args.timeframes
|
timeframes = args.timeframes
|
||||||
|
|
||||||
dl_path = os.path.join(DEFAULT_DL_PATH, args.exchange)
|
dl_path = Path(DEFAULT_DL_PATH).joinpath(args.exchange)
|
||||||
if args.export:
|
if args.export:
|
||||||
dl_path = args.export
|
dl_path = Path(args.export)
|
||||||
|
|
||||||
if not os.path.isdir(dl_path):
|
if not dl_path.is_dir():
|
||||||
sys.exit(f'Directory {dl_path} does not exist.')
|
sys.exit(f'Directory {dl_path} does not exist.')
|
||||||
|
|
||||||
pairs_file = args.pairs_file if args.pairs_file else os.path.join(dl_path, 'pairs.json')
|
pairs_file = Path(args.pairs_file) if args.pairs_file else dl_path.joinpath('pairs.json')
|
||||||
if not os.path.isfile(pairs_file):
|
if not pairs_file.exists():
|
||||||
sys.exit(f'No pairs file found with path {pairs_file}.')
|
sys.exit(f'No pairs file found with path {pairs_file}.')
|
||||||
|
|
||||||
with open(pairs_file) as file:
|
with pairs_file.open() as file:
|
||||||
PAIRS = list(set(json.load(file)))
|
PAIRS = list(set(json.load(file)))
|
||||||
|
|
||||||
PAIRS.sort()
|
PAIRS.sort()
|
||||||
|
|
||||||
since_time = None
|
|
||||||
|
timerange = TimeRange()
|
||||||
if args.days:
|
if args.days:
|
||||||
since_time = arrow.utcnow().shift(days=-args.days).timestamp * 1000
|
time_since = arrow.utcnow().shift(days=-args.days).strftime("%Y%m%d")
|
||||||
|
timerange = arguments.parse_timerange(f'{time_since}-')
|
||||||
|
|
||||||
|
|
||||||
print(f'About to download pairs: {PAIRS} to {dl_path}')
|
print(f'About to download pairs: {PAIRS} to {dl_path}')
|
||||||
@ -59,21 +64,18 @@ for pair in PAIRS:
|
|||||||
print(f"skipping pair {pair}")
|
print(f"skipping pair {pair}")
|
||||||
continue
|
continue
|
||||||
for tick_interval in timeframes:
|
for tick_interval in timeframes:
|
||||||
print(f'downloading pair {pair}, interval {tick_interval}')
|
|
||||||
|
|
||||||
data = exchange.get_ticker_history(pair, tick_interval, since_ms=since_time)
|
|
||||||
if not data:
|
|
||||||
print('\tNo data was downloaded')
|
|
||||||
break
|
|
||||||
|
|
||||||
print('\tData was downloaded for period %s - %s' % (
|
|
||||||
arrow.get(data[0][0] / 1000).format(),
|
|
||||||
arrow.get(data[-1][0] / 1000).format()))
|
|
||||||
|
|
||||||
# save data
|
|
||||||
pair_print = pair.replace('/', '_')
|
pair_print = pair.replace('/', '_')
|
||||||
filename = f'{pair_print}-{tick_interval}.json'
|
filename = f'{pair_print}-{tick_interval}.json'
|
||||||
misc.file_dump_json(os.path.join(dl_path, filename), data)
|
dl_file = dl_path.joinpath(filename)
|
||||||
|
if args.erase and dl_file.exists():
|
||||||
|
print(f'Deleting existing data for pair {pair}, interval {tick_interval}')
|
||||||
|
dl_file.unlink()
|
||||||
|
|
||||||
|
print(f'downloading pair {pair}, interval {tick_interval}')
|
||||||
|
download_backtesting_testdata(str(dl_path), exchange=exchange,
|
||||||
|
pair=pair,
|
||||||
|
tick_interval=tick_interval,
|
||||||
|
timerange=timerange)
|
||||||
|
|
||||||
|
|
||||||
if pairs_not_available:
|
if pairs_not_available:
|
||||||
|
Loading…
Reference in New Issue
Block a user