Merge branch 'develop' of https://github.com/freqtrade/freqtrade into feature/flask-rest

This commit is contained in:
gcarq 2018-06-24 23:39:04 +02:00
commit 85851bd026
26 changed files with 928 additions and 259 deletions

169
README.md
View File

@ -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,10 +123,6 @@ 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
@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
""" FreqTrade bot """ """ FreqTrade bot """
__version__ = '0.17.0' __version__ = '0.17.1'
class DependencyException(BaseException): class DependencyException(BaseException):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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': '',
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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