Merge with develop

This commit is contained in:
Anton 2018-06-23 16:50:27 +03:00
commit f82b809fcf
36 changed files with 1336 additions and 987 deletions

View File

@ -2,4 +2,5 @@
omit = omit =
scripts/* scripts/*
freqtrade/tests/* freqtrade/tests/*
freqtrade/vendor/* freqtrade/vendor/*
freqtrade/__main__.py

View File

@ -16,15 +16,16 @@ install:
- pip install --upgrade flake8 coveralls pytest-random-order mypy - pip install --upgrade flake8 coveralls pytest-random-order mypy
- pip install -r requirements.txt - pip install -r requirements.txt
- pip install -e . - pip install -e .
- cp config.json.example config.json
jobs: jobs:
include: include:
- script: - script:
- pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/
- coveralls - coveralls
- script: - script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting - python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting
- script: - script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5 - python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5
- script: flake8 freqtrade - script: flake8 freqtrade
- script: mypy freqtrade - script: mypy freqtrade

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,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,46 @@ 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.
**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

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

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
@ -184,6 +207,26 @@ docker start freqtrade
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container. You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
### 7. Backtest with docker
The following assumes that the above steps (1-4) have been completed successfully.
Also, backtest-data should be available at `~/.freqtrade/user_data/`.
``` bash
docker run -d \
--name freqtrade \
-v /etc/localtime:/etc/localtime:ro \
-v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
-v ~/.freqtrade/user_data/:/freqtrade/user_data/ \
freqtrade --strategy AwsomelyProfitableStrategy backtesting
```
Head over to the [Backtesting Documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md) for more details.
*Note*: Additional parameters can be appended after the image name (`freqtrade` in the above example).
------ ------
## Custom Installation ## Custom Installation

View File

@ -10,7 +10,7 @@ import arrow
from pandas import DataFrame, to_datetime from pandas import DataFrame, to_datetime
from freqtrade import constants from freqtrade import constants
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import Exchange
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.strategy.resolver import StrategyResolver, IStrategy from freqtrade.strategy.resolver import StrategyResolver, IStrategy
@ -117,14 +117,14 @@ class Analyze(object):
dataframe = self.populate_sell_trend(dataframe) dataframe = self.populate_sell_trend(dataframe)
return dataframe return dataframe
def get_signal(self, pair: str, interval: str) -> Tuple[bool, bool]: def get_signal(self, exchange: Exchange, pair: str, interval: str) -> Tuple[bool, bool]:
""" """
Calculates current signal based several technical analysis indicators Calculates current signal based several technical analysis indicators
:param pair: pair in format ANT/BTC :param pair: pair in format ANT/BTC
:param interval: Interval to use (in min) :param interval: Interval to use (in min)
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal :return: (Buy, Sell) A bool-tuple indicating buy/sell signal
""" """
ticker_hist = get_ticker_history(pair, interval) ticker_hist = exchange.get_ticker_history(pair, interval)
if not ticker_hist: if not ticker_hist:
logger.warning('Empty ticker history for pair %s', pair) logger.warning('Empty ticker history for pair %s', pair)
return False, False return False, False
@ -155,7 +155,7 @@ class Analyze(object):
# Check if dataframe is out of date # Check if dataframe is out of date
signal_date = arrow.get(latest['date']) signal_date = arrow.get(latest['date'])
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval] interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
if signal_date < arrow.utcnow() - timedelta(minutes=(interval_minutes + 5)): if signal_date < (arrow.utcnow() - timedelta(minutes=(interval_minutes + 5))):
logger.warning( logger.warning(
'Outdated history for pair %s. Last tick is %s minutes old', 'Outdated history for pair %s. Last tick is %s minutes old',
pair, pair,
@ -179,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

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

@ -12,16 +12,8 @@ from freqtrade import constants, OperationalException, DependencyException, Temp
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Current selected exchange
_API: ccxt.Exchange = None
_CONF: Dict = {}
API_RETRY_COUNT = 4 API_RETRY_COUNT = 4
_CACHED_TICKER: Dict[str, Any] = {}
# Holds all open sell orders for dry_run
_DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {}
# Urls to exchange markets, insert quote and base with .format() # Urls to exchange markets, insert quote and base with .format()
_EXCHANGE_URLS = { _EXCHANGE_URLS = {
@ -48,390 +40,378 @@ def retrier(f):
return wrapper return wrapper
def init_ccxt(exchange_config: dict) -> ccxt.Exchange: class Exchange(object):
"""
Initialize ccxt with given config and return valid
ccxt instance.
:param config: config to use
:return: ccxt
"""
# Find matching class for the given exchange name
name = exchange_config['name']
if name not in ccxt.exchanges: # Current selected exchange
raise OperationalException(f'Exchange {name} is not supported') _api: ccxt.Exchange = None
try: _conf: Dict = {}
api = getattr(ccxt, name.lower())({ _cached_ticker: Dict[str, Any] = {}
'apiKey': exchange_config.get('key'),
'secret': exchange_config.get('secret'),
'password': exchange_config.get('password'),
'uid': exchange_config.get('uid', ''),
'enableRateLimit': True,
})
except (KeyError, AttributeError):
raise OperationalException(f'Exchange {name} is not supported')
return api # Holds all open sell orders for dry_run
_dry_run_open_orders: Dict[str, Any] = {}
def __init__(self, config: dict) -> None:
"""
Initializes this module with the given config,
it does basic validation whether the specified
exchange and pairs are valid.
:return: None
"""
self._conf.update(config)
def init(config: dict) -> None: if config['dry_run']:
""" logger.info('Instance is running with dry_run enabled')
Initializes this module with the given config,
it does basic validation whether the specified
exchange and pairs are valid.
:param config: config to use
:return: None
"""
global _CONF, _API
_CONF.update(config) exchange_config = config['exchange']
self._api = self._init_ccxt(exchange_config)
if config['dry_run']: logger.info('Using Exchange "%s"', self.name)
logger.info('Instance is running with dry_run enabled')
exchange_config = config['exchange'] # Check if all pairs are available
_API = init_ccxt(exchange_config) self.validate_pairs(config['exchange']['pair_whitelist'])
logger.info('Using Exchange "%s"', get_name()) def _init_ccxt(self, exchange_config: dict) -> ccxt.Exchange:
"""
Initialize ccxt with given config and return valid
ccxt instance.
"""
# Find matching class for the given exchange name
name = exchange_config['name']
# Check if all pairs are available if name not in ccxt.exchanges:
validate_pairs(config['exchange']['pair_whitelist']) raise OperationalException(f'Exchange {name} is not supported')
def validate_pairs(pairs: List[str]) -> None:
"""
Checks if all given pairs are tradable on the current exchange.
Raises OperationalException if one pair is not available.
:param pairs: list of pairs
:return: None
"""
try:
markets = _API.load_markets()
except ccxt.BaseError as e:
logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e)
return
stake_cur = _CONF['stake_currency']
for pair in pairs:
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
# TODO: add a support for having coins in BTC/USDT format
if not pair.endswith(stake_cur):
raise OperationalException(
f'Pair {pair} not compatible with stake_currency: {stake_cur}')
if pair not in markets:
raise OperationalException(
f'Pair {pair} is not available at {get_name()}')
def exchange_has(endpoint: str) -> bool:
"""
Checks if exchange implements a specific API endpoint.
Wrapper around ccxt 'has' attribute
:param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers')
:return: bool
"""
return endpoint in _API.has and _API.has[endpoint]
def buy(pair: str, rate: float, amount: float) -> Dict:
if _CONF['dry_run']:
global _DRY_RUN_OPEN_ORDERS
order_id = f'dry_run_buy_{randint(0, 10**6)}'
_DRY_RUN_OPEN_ORDERS[order_id] = {
'pair': pair,
'price': rate,
'amount': amount,
'type': 'limit',
'side': 'buy',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
'status': 'closed',
'fee': None
}
return {'id': order_id}
try:
return _API.create_limit_buy_order(pair, amount, rate)
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create limit buy order on market {pair}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create limit buy order on market {pair}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place buy order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def sell(pair: str, rate: float, amount: float) -> Dict:
if _CONF['dry_run']:
global _DRY_RUN_OPEN_ORDERS
order_id = f'dry_run_sell_{randint(0, 10**6)}'
_DRY_RUN_OPEN_ORDERS[order_id] = {
'pair': pair,
'price': rate,
'amount': amount,
'type': 'limit',
'side': 'sell',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
'status': 'closed'
}
return {'id': order_id}
try:
return _API.create_limit_sell_order(pair, amount, rate)
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create limit sell order on market {pair}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create limit sell order on market {pair}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_balance(currency: str) -> float:
if _CONF['dry_run']:
return 999.9
# ccxt exception is already handled by get_balances
balances = get_balances()
balance = balances.get(currency)
if balance is None:
raise TemporaryError(
f'Could not get {currency} balance due to malformed exchange response: {balances}')
return balance['free']
@retrier
def get_balances() -> dict:
if _CONF['dry_run']:
return {}
try:
balances = _API.fetch_balance()
# Remove additional info from ccxt results
balances.pop("info", None)
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
return balances
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get balance due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_tickers() -> Dict:
try:
return _API.fetch_tickers()
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {_API.name} does not support fetching tickers in batch.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
global _CACHED_TICKER
if refresh or pair not in _CACHED_TICKER.keys():
try: try:
data = _API.fetch_ticker(pair) api = getattr(ccxt, name.lower())({
'apiKey': exchange_config.get('key'),
'secret': exchange_config.get('secret'),
'password': exchange_config.get('password'),
'uid': exchange_config.get('uid', ''),
'enableRateLimit': True,
})
except (KeyError, AttributeError):
raise OperationalException(f'Exchange {name} is not supported')
return api
@property
def name(self) -> str:
"""exchange Name (from ccxt)"""
return self._api.name
@property
def id(self) -> str:
"""exchange ccxt id"""
return self._api.id
def validate_pairs(self, pairs: List[str]) -> None:
"""
Checks if all given pairs are tradable on the current exchange.
Raises OperationalException if one pair is not available.
:param pairs: list of pairs
:return: None
"""
try:
markets = self._api.load_markets()
except ccxt.BaseError as e:
logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e)
return
stake_cur = self._conf['stake_currency']
for pair in pairs:
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
# TODO: add a support for having coins in BTC/USDT format
if not pair.endswith(stake_cur):
raise OperationalException(
f'Pair {pair} not compatible with stake_currency: {stake_cur}')
if pair not in markets:
raise OperationalException(
f'Pair {pair} is not available at {self.name}')
def exchange_has(self, endpoint: str) -> bool:
"""
Checks if exchange implements a specific API endpoint.
Wrapper around ccxt 'has' attribute
:param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers')
:return: bool
"""
return endpoint in self._api.has and self._api.has[endpoint]
def buy(self, pair: str, rate: float, amount: float) -> Dict:
if self._conf['dry_run']:
order_id = f'dry_run_buy_{randint(0, 10**6)}'
self._dry_run_open_orders[order_id] = {
'pair': pair,
'price': rate,
'amount': amount,
'type': 'limit',
'side': 'buy',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
'status': 'closed',
'fee': None
}
return {'id': order_id}
try:
return self._api.create_limit_buy_order(pair, amount, rate)
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create limit buy order on market {pair}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create limit buy order on market {pair}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place buy order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def sell(self, pair: str, rate: float, amount: float) -> Dict:
if self._conf['dry_run']:
order_id = f'dry_run_sell_{randint(0, 10**6)}'
self._dry_run_open_orders[order_id] = {
'pair': pair,
'price': rate,
'amount': amount,
'type': 'limit',
'side': 'sell',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
'status': 'closed'
}
return {'id': order_id}
try:
return self._api.create_limit_sell_order(pair, amount, rate)
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create limit sell order on market {pair}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create limit sell order on market {pair}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_balance(self, currency: str) -> float:
if self._conf['dry_run']:
return 999.9
# ccxt exception is already handled by get_balances
balances = self.get_balances()
balance = balances.get(currency)
if balance is None:
raise TemporaryError(
f'Could not get {currency} balance due to malformed exchange response: {balances}')
return balance['free']
@retrier
def get_balances(self) -> dict:
if self._conf['dry_run']:
return {}
try:
balances = self._api.fetch_balance()
# Remove additional info from ccxt results
balances.pop("info", None)
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
return balances
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get balance due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_tickers(self) -> Dict:
try:
return self._api.fetch_tickers()
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching tickers in batch.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
if refresh or pair not in self._cached_ticker.keys():
try: try:
_CACHED_TICKER[pair] = { data = self._api.fetch_ticker(pair)
'bid': float(data['bid']), try:
'ask': float(data['ask']), self._cached_ticker[pair] = {
} 'bid': float(data['bid']),
except KeyError: 'ask': float(data['ask']),
logger.debug("Could not cache ticker data for %s", pair) }
except KeyError:
logger.debug("Could not cache ticker data for %s", pair)
return data
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
else:
logger.info("returning cached ticker-data for %s", pair)
return self._cached_ticker[pair]
@retrier
def get_ticker_history(self, pair: str, tick_interval: str,
since_ms: Optional[int] = None) -> List[Dict]:
try:
# last item should be in the time interval [now - tick_interval, now]
till_time_ms = arrow.utcnow().shift(
minutes=-constants.TICKER_INTERVAL_MINUTES[tick_interval]
).timestamp * 1000
# it looks as if some exchanges return cached data
# and they update it one in several minute, so 10 mins interval
# is necessary to skeep downloading of an empty array when all
# chached data was already downloaded
till_time_ms = min(till_time_ms, arrow.utcnow().shift(minutes=-10).timestamp * 1000)
data: List[Dict[Any, Any]] = []
while not since_ms or since_ms < till_time_ms:
data_part = self._api.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms)
# Because some exchange sort Tickers ASC and other DESC.
# Ex: Bittrex returns a list of tickers ASC (oldest first, newest last)
# when GDAX returns a list of tickers DESC (newest first, oldest last)
data_part = sorted(data_part, key=lambda x: x[0])
if not data_part:
break
logger.debug('Downloaded data for %s time range [%s, %s]',
pair,
arrow.get(data_part[0][0] / 1000).format(),
arrow.get(data_part[-1][0] / 1000).format())
data.extend(data_part)
since_ms = data[-1][0] + 1
return data return data
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching historical candlestick data.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError( raise TemporaryError(
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}') f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(f'Could not fetch ticker data. Msg: {e}')
@retrier
def cancel_order(self, order_id: str, pair: str) -> None:
if self._conf['dry_run']:
return
try:
return self._api.cancel_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not cancel order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
else:
logger.info("returning cached ticker-data for %s", pair)
return _CACHED_TICKER[pair]
@retrier
def get_order(self, order_id: str, pair: str) -> Dict:
if self._conf['dry_run']:
order = self._dry_run_open_orders[order_id]
order.update({
'id': order_id
})
return order
try:
return self._api.fetch_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not get order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier @retrier
def get_ticker_history(pair: str, tick_interval: str, since_ms: Optional[int] = None) -> List[Dict]: def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List:
try: if self._conf['dry_run']:
# last item should be in the time interval [now - tick_interval, now] return []
till_time_ms = arrow.utcnow().shift( if not self.exchange_has('fetchMyTrades'):
minutes=-constants.TICKER_INTERVAL_MINUTES[tick_interval] return []
).timestamp * 1000 try:
# it looks as if some exchanges return cached data my_trades = self._api.fetch_my_trades(pair, since.timestamp())
# and they update it one in several minute, so 10 mins interval matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
# is necessary to skeep downloading of an empty array when all
# chached data was already downloaded
till_time_ms = min(till_time_ms, arrow.utcnow().shift(minutes=-10).timestamp * 1000)
data: List[Dict[Any, Any]] = [] return matched_trades
while not since_ms or since_ms < till_time_ms:
data_part = _API.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms)
# Because some exchange sort Tickers ASC and other DESC. except ccxt.NetworkError as e:
# Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) raise TemporaryError(
# when GDAX returns a list of tickers DESC (newest first, oldest last) f'Could not get trades due to networking error. Message: {e}')
data_part = sorted(data_part, key=lambda x: x[0]) except ccxt.BaseError as e:
raise OperationalException(e)
if not data_part: def get_pair_detail_url(self, pair: str) -> str:
break try:
url_base = self._api.urls.get('www')
base, quote = pair.split('/')
logger.debug('Downloaded data for %s time range [%s, %s]', return url_base + _EXCHANGE_URLS[self._api.id].format(base=base, quote=quote)
pair, except KeyError:
arrow.get(data_part[0][0] / 1000).format(), logger.warning('Could not get exchange url for %s', self.name)
arrow.get(data_part[-1][0] / 1000).format()) return ""
data.extend(data_part) @retrier
since_ms = data[-1][0] + 1 def get_markets(self) -> List[dict]:
try:
return self._api.fetch_markets()
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load markets due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
return data @retrier
except ccxt.NotSupported as e: def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1,
raise OperationalException( price=1, taker_or_maker='maker') -> float:
f'Exchange {_API.name} does not support fetching historical candlestick data.' try:
f'Message: {e}') # validate that markets are loaded before trying to get fee
except (ccxt.NetworkError, ccxt.ExchangeError) as e: if self._api.markets is None or len(self._api.markets) == 0:
raise TemporaryError( self._api.load_markets()
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(f'Could not fetch ticker data. Msg: {e}')
return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
price=price, takerOrMaker=taker_or_maker)['rate']
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier def get_amount_lots(self, pair: str, amount: float) -> float:
def cancel_order(order_id: str, pair: str) -> None: """
if _CONF['dry_run']: get buyable amount rounding, ..
return """
try:
return _API.cancel_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not cancel order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_order(order_id: str, pair: str) -> Dict:
if _CONF['dry_run']:
order = _DRY_RUN_OPEN_ORDERS[order_id]
order.update({
'id': order_id
})
return order
try:
return _API.fetch_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not get order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_trades_for_order(order_id: str, pair: str, since: datetime) -> List:
if _CONF['dry_run']:
return []
if not exchange_has('fetchMyTrades'):
return []
try:
my_trades = _API.fetch_my_trades(pair, since.timestamp())
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
return matched_trades
except ccxt.NetworkError as e:
raise TemporaryError(
f'Could not get trades due to networking error. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def get_pair_detail_url(pair: str) -> str:
try:
url_base = _API.urls.get('www')
base, quote = pair.split('/')
return url_base + _EXCHANGE_URLS[_API.id].format(base=base, quote=quote)
except KeyError:
logger.warning('Could not get exchange url for %s', get_name())
return ""
@retrier
def get_markets() -> List[dict]:
try:
return _API.fetch_markets()
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load markets due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def get_name() -> str:
return _API.name
def get_id() -> str:
return _API.id
@retrier
def get_fee(symbol='ETH/BTC', type='', side='', amount=1,
price=1, taker_or_maker='maker') -> float:
try:
# validate that markets are loaded before trying to get fee # validate that markets are loaded before trying to get fee
if _API.markets is None or len(_API.markets) == 0: if not self._api.markets:
_API.load_markets() self._api.load_markets()
return self._api.amount_to_lots(pair, amount)
return _API.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
price=price, takerOrMaker=taker_or_maker)['rate']
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def get_amount_lots(pair: str, amount: float) -> float:
"""
get buyable amount rounding, ..
"""
# validate that markets are loaded before trying to get fee
if not _API.markets:
_API.load_markets()
return _API.amount_to_lots(pair, amount)

View File

@ -14,11 +14,11 @@ import requests
from cachetools import TTLCache, cached from cachetools import TTLCache, cached
from freqtrade import ( from freqtrade import (
DependencyException, OperationalException, TemporaryError, DependencyException, OperationalException, TemporaryError, persistence, __version__,
exchange, persistence, __version__,
) )
from freqtrade import constants from freqtrade import constants
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade.exchange import Exchange
from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc.rpc_manager import RPCManager from freqtrade.rpc.rpc_manager import RPCManager
@ -54,7 +54,7 @@ class FreqtradeBot(object):
self.fiat_converter = CryptoToFiatConverter() self.fiat_converter = CryptoToFiatConverter()
self.rpc: RPCManager = RPCManager(self) self.rpc: RPCManager = RPCManager(self)
self.persistence = None self.persistence = None
self.exchange = None self.exchange = Exchange(self.config)
self._init_modules() self._init_modules()
@ -66,7 +66,6 @@ class FreqtradeBot(object):
# Initialize all modules # Initialize all modules
persistence.init(self.config) persistence.init(self.config)
exchange.init(self.config)
# Set initial application state # Set initial application state
initial_state = self.config.get('initial_state') initial_state = self.config.get('initial_state')
@ -186,13 +185,13 @@ class FreqtradeBot(object):
:return: List of pairs :return: List of pairs
""" """
if not exchange.exchange_has('fetchTickers'): if not self.exchange.exchange_has('fetchTickers'):
raise OperationalException( raise OperationalException(
'Exchange does not support dynamic whitelist.' 'Exchange does not support dynamic whitelist.'
'Please edit your config and restart the bot' 'Please edit your config and restart the bot'
) )
tickers = exchange.get_tickers() tickers = self.exchange.get_tickers()
# check length so that we make sure that '/' is actually in the string # check length so that we make sure that '/' is actually in the string
tickers = [v for k, v in tickers.items() tickers = [v for k, v in tickers.items()
if len(k.split('/')) == 2 and k.split('/')[1] == base_currency] if len(k.split('/')) == 2 and k.split('/')[1] == base_currency]
@ -210,7 +209,7 @@ class FreqtradeBot(object):
black_listed black_listed
""" """
sanitized_whitelist = whitelist sanitized_whitelist = whitelist
markets = exchange.get_markets() markets = self.exchange.get_markets()
markets = [m for m in markets if m['quote'] == self.config['stake_currency']] markets = [m for m in markets if m['quote'] == self.config['stake_currency']]
known_pairs = set() known_pairs = set()
@ -247,7 +246,7 @@ class FreqtradeBot(object):
def _get_trade_stake_amount(self) -> Optional[float]: def _get_trade_stake_amount(self) -> Optional[float]:
stake_amount = self.config['stake_amount'] stake_amount = self.config['stake_amount']
avaliable_amount = exchange.get_balance(self.config['stake_currency']) avaliable_amount = self.exchange.get_balance(self.config['stake_currency'])
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT: if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all()) open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all())
@ -267,7 +266,7 @@ class FreqtradeBot(object):
return stake_amount return stake_amount
def _get_min_pair_stake_amount(self, pair: str, price: float) -> Optional[float]: def _get_min_pair_stake_amount(self, pair: str, price: float) -> Optional[float]:
markets = exchange.get_markets() markets = self.exchange.get_markets()
markets = [m for m in markets if m['symbol'] == pair] markets = [m for m in markets if m['symbol'] == pair]
if not markets: if not markets:
raise ValueError(f'Can\'t get market information for symbol {pair}') raise ValueError(f'Can\'t get market information for symbol {pair}')
@ -307,7 +306,7 @@ class FreqtradeBot(object):
return False 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 = exchange.get_name() exc_name = self.exchange.name
logger.info( logger.info(
'Checking buy signals to create a new trade with stake_amount: %f ...', 'Checking buy signals to create a new trade with stake_amount: %f ...',
@ -326,16 +325,17 @@ class FreqtradeBot(object):
# Pick pair based on buy signals # Pick pair based on buy signals
for _pair in whitelist: for _pair in whitelist:
(buy, sell) = self.analyze.get_signal(_pair, interval) (buy, sell) = self.analyze.get_signal(self.exchange, _pair, interval)
if buy and not sell: if buy and not sell:
pair = _pair pair = _pair
break break
else: else:
return False return False
pair_s = pair.replace('_', '/') pair_s = pair.replace('_', '/')
pair_url = 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(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) 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: if min_stake_amount is not None and min_stake_amount > stake_amount:
@ -346,7 +346,8 @@ class FreqtradeBot(object):
return False return False
amount = stake_amount / buy_limit amount = stake_amount / buy_limit
order_id = exchange.buy(pair, buy_limit, amount)['id']
order_id = self.exchange.buy(pair, buy_limit, amount)['id']
stake_amount_fiat = self.fiat_converter.convert_amount( stake_amount_fiat = self.fiat_converter.convert_amount(
stake_amount, stake_amount,
@ -361,7 +362,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
{stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`""" {stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`"""
) )
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
fee = exchange.get_fee(symbol=pair, taker_or_maker='maker') fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
trade = Trade( trade = Trade(
pair=pair, pair=pair,
stake_amount=stake_amount, stake_amount=stake_amount,
@ -371,7 +372,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
open_rate=buy_limit, open_rate=buy_limit,
open_rate_requested=buy_limit, open_rate_requested=buy_limit,
open_date=datetime.utcnow(), open_date=datetime.utcnow(),
exchange=exchange.get_id(), exchange=self.exchange.id,
open_order_id=order_id open_order_id=order_id
) )
Trade.session.add(trade) Trade.session.add(trade)
@ -404,7 +405,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
if trade.open_order_id: if trade.open_order_id:
# Update trade with order values # Update trade with order values
logger.info('Found open order for %s', trade) logger.info('Found open order for %s', trade)
order = exchange.get_order(trade.open_order_id, trade.pair) order = self.exchange.get_order(trade.open_order_id, trade.pair)
# Try update amount (binance-fix) # Try update amount (binance-fix)
try: try:
new_amount = self.get_real_amount(trade, order) new_amount = self.get_real_amount(trade, order)
@ -428,7 +429,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
def get_real_amount(self, trade: Trade, order: Dict) -> float: def get_real_amount(self, trade: Trade, order: Dict) -> float:
""" """
Get real amount for the trade Get real amount for the trade
Necessary for exchanges which charge fees in base currency (e.g. binance) Necessary for self.exchanges which charge fees in base currency (e.g. binance)
""" """
order_amount = order['amount'] order_amount = order['amount']
# Only run for closed orders # Only run for closed orders
@ -444,7 +445,8 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
return new_amount return new_amount
# Fallback to Trades # Fallback to Trades
trades = exchange.get_trades_for_order(trade.open_order_id, trade.pair, trade.open_date) trades = self.exchange.get_trades_for_order(trade.open_order_id, trade.pair,
trade.open_date)
if len(trades) == 0: if len(trades) == 0:
logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade)
@ -476,12 +478,13 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
raise ValueError(f'attempt to handle closed trade: {trade}') raise ValueError(f'attempt to handle closed trade: {trade}')
logger.debug('Handling %s ...', trade) logger.debug('Handling %s ...', trade)
current_rate = 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(trade.pair, self.analyze.get_ticker_interval()) (buy, sell) = self.analyze.get_signal(self.exchange,
trade.pair, self.analyze.get_ticker_interval())
if self.analyze.should_sell(trade, current_rate, datetime.utcnow(), buy, sell): if self.analyze.should_sell(trade, current_rate, datetime.utcnow(), buy, sell):
self.execute_sell(trade, current_rate) self.execute_sell(trade, current_rate)
@ -505,7 +508,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
# updated via /forcesell in a different thread. # updated via /forcesell in a different thread.
if not trade.open_order_id: if not trade.open_order_id:
continue continue
order = exchange.get_order(trade.open_order_id, trade.pair) order = self.exchange.get_order(trade.open_order_id, trade.pair)
except requests.exceptions.RequestException: except requests.exceptions.RequestException:
logger.info( logger.info(
'Cannot query order for %s due to %s', 'Cannot query order for %s due to %s',
@ -531,7 +534,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
:return: True if order was fully cancelled :return: True if order was fully cancelled
""" """
pair_s = trade.pair.replace('_', '/') pair_s = trade.pair.replace('_', '/')
exchange.cancel_order(trade.open_order_id, trade.pair) self.exchange.cancel_order(trade.open_order_id, trade.pair)
if order['remaining'] == order['amount']: if order['remaining'] == order['amount']:
# if trade is not partially completed, just delete the trade # if trade is not partially completed, just delete the trade
Trade.session.delete(trade) Trade.session.delete(trade)
@ -558,7 +561,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
pair_s = trade.pair.replace('_', '/') pair_s = trade.pair.replace('_', '/')
if order['remaining'] == order['amount']: if order['remaining'] == order['amount']:
# if trade is not partially completed, just cancel the trade # if trade is not partially completed, just cancel the trade
exchange.cancel_order(trade.open_order_id, trade.pair) self.exchange.cancel_order(trade.open_order_id, trade.pair)
trade.close_rate = None trade.close_rate = None
trade.close_profit = None trade.close_profit = None
trade.close_date = None trade.close_date = None
@ -581,15 +584,15 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
exc = trade.exchange exc = trade.exchange
pair = trade.pair pair = trade.pair
# Execute sell and update trade record # Execute sell and update trade record
order_id = exchange.sell(str(trade.pair), limit, trade.amount)['id'] order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id']
trade.open_order_id = order_id trade.open_order_id = order_id
trade.close_rate_requested = limit trade.close_rate_requested = limit
fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2) fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2)
profit_trade = trade.calc_profit(rate=limit) profit_trade = trade.calc_profit(rate=limit)
current_rate = exchange.get_ticker(trade.pair)['bid'] current_rate = self.exchange.get_ticker(trade.pair)['bid']
profit = trade.calc_profit_percent(limit) profit = trade.calc_profit_percent(limit)
pair_url = exchange.get_pair_detail_url(trade.pair) pair_url = self.exchange.get_pair_detail_url(trade.pair)
gain = "profit" if fmt_exp_profit > 0 else "loss" gain = "profit" if fmt_exp_profit > 0 else "loss"
message = f"*{exc}:* Selling\n" \ message = f"*{exc}:* Selling\n" \

View File

@ -7,8 +7,8 @@ import os
from typing import Optional, List, Dict, Tuple, Any from typing import Optional, List, Dict, Tuple, Any
import arrow import arrow
from freqtrade import misc, constants from freqtrade import misc, constants, OperationalException
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import Exchange
from freqtrade.arguments import TimeRange from freqtrade.arguments import TimeRange
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -83,6 +83,7 @@ def load_data(datadir: str,
ticker_interval: str, ticker_interval: str,
pairs: List[str], pairs: List[str],
refresh_pairs: Optional[bool] = False, refresh_pairs: Optional[bool] = False,
exchange: Optional[Exchange] = None,
timerange: TimeRange = TimeRange(None, None, 0, 0)) -> Dict[str, List]: timerange: TimeRange = TimeRange(None, None, 0, 0)) -> Dict[str, List]:
""" """
Loads ticker history data for the given parameters Loads ticker history data for the given parameters
@ -93,7 +94,10 @@ def load_data(datadir: str,
# If the user force the refresh of pairs # If the user force the refresh of pairs
if refresh_pairs: if refresh_pairs:
logger.info('Download data for all pairs and store them in %s', datadir) logger.info('Download data for all pairs and store them in %s', datadir)
download_pairs(datadir, pairs, ticker_interval, timerange=timerange) if not exchange:
raise OperationalException("Exchange needs to be initialized when "
"calling load_data with refresh_pairs=True")
download_pairs(datadir, exchange, pairs, ticker_interval, timerange=timerange)
for pair in pairs: for pair in pairs:
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange) pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
@ -119,13 +123,14 @@ def make_testdata_path(datadir: str) -> str:
) )
def download_pairs(datadir, pairs: List[str], def download_pairs(datadir, exchange: Exchange, pairs: List[str],
ticker_interval: str, ticker_interval: str,
timerange: TimeRange = TimeRange(None, None, 0, 0)) -> bool: timerange: TimeRange = TimeRange(None, None, 0, 0)) -> bool:
"""For each pairs passed in parameters, download the ticker intervals""" """For each pairs passed in parameters, download the ticker intervals"""
for pair in pairs: for pair in pairs:
try: try:
download_backtesting_testdata(datadir, download_backtesting_testdata(datadir,
exchange=exchange,
pair=pair, pair=pair,
tick_interval=ticker_interval, tick_interval=ticker_interval,
timerange=timerange) timerange=timerange)
@ -183,6 +188,7 @@ def load_cached_data_for_updating(filename: str,
def download_backtesting_testdata(datadir: str, def download_backtesting_testdata(datadir: str,
exchange: Exchange,
pair: str, pair: str,
tick_interval: str = '5m', tick_interval: str = '5m',
timerange: Optional[TimeRange] = None) -> None: timerange: Optional[TimeRange] = None) -> None:
@ -216,7 +222,8 @@ def download_backtesting_testdata(datadir: str,
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None') logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None') logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
new_data = get_ticker_history(pair=pair, tick_interval=tick_interval, since_ms=since_ms) new_data = exchange.get_ticker_history(pair=pair, tick_interval=tick_interval,
since_ms=since_ms)
data.extend(new_data) data.extend(new_data)
logger.debug("New Start: %s", misc.format_ms_time(data[0][0])) logger.debug("New Start: %s", misc.format_ms_time(data[0][0]))

View File

@ -14,7 +14,8 @@ 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 exchange, constants, DependencyException from freqtrade import constants, DependencyException
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
from freqtrade.configuration import Configuration from freqtrade.configuration import Configuration
@ -61,7 +62,8 @@ class Backtesting(object):
self.config['exchange']['password'] = '' self.config['exchange']['password'] = ''
self.config['exchange']['uid'] = '' self.config['exchange']['uid'] = ''
self.config['dry_run'] = True self.config['dry_run'] = True
exchange.init(self.config) self.exchange = Exchange(self.config)
self.fee = self.exchange.get_fee()
@staticmethod @staticmethod
def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]: def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
@ -130,14 +132,13 @@ class Backtesting(object):
stake_amount = args['stake_amount'] stake_amount = args['stake_amount']
max_open_trades = args.get('max_open_trades', 0) max_open_trades = args.get('max_open_trades', 0)
fee = exchange.get_fee()
trade = Trade( trade = Trade(
open_rate=buy_row.close, open_rate=buy_row.close,
open_date=buy_row.date, open_date=buy_row.date,
stake_amount=stake_amount, stake_amount=stake_amount,
amount=stake_amount / buy_row.open, amount=stake_amount / buy_row.open,
fee_open=fee, fee_open=self.fee,
fee_close=fee fee_close=self.fee
) )
# calculate win/lose forwards from buy point # calculate win/lose forwards from buy point
@ -256,7 +257,7 @@ class Backtesting(object):
if self.config.get('live'): if self.config.get('live'):
logger.info('Downloading data for all pairs in whitelist ...') logger.info('Downloading data for all pairs in whitelist ...')
for pair in pairs: for pair in pairs:
data[pair] = exchange.get_ticker_history(pair, self.ticker_interval) data[pair] = self.exchange.get_ticker_history(pair, self.ticker_interval)
else: else:
logger.info('Using local backtesting data (using whitelist in given config) ...') logger.info('Using local backtesting data (using whitelist in given config) ...')
@ -267,6 +268,7 @@ class Backtesting(object):
pairs=pairs, pairs=pairs,
ticker_interval=self.ticker_interval, ticker_interval=self.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False), refresh_pairs=self.config.get('refresh_pairs', False),
exchange=self.exchange,
timerange=timerange timerange=timerange
) )

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

@ -12,7 +12,6 @@ import sqlalchemy as sql
from numpy import mean, nan_to_num from numpy import mean, nan_to_num
from pandas import DataFrame from pandas import DataFrame
from freqtrade import exchange
from freqtrade.misc import shorten_date from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.state import State from freqtrade.state import State
@ -71,9 +70,9 @@ class RPC(object):
for trade in trades: for trade in trades:
order = None order = None
if trade.open_order_id: if trade.open_order_id:
order = exchange.get_order(trade.open_order_id, trade.pair) order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair)
# calculate profit and send message to user # calculate profit and send message to user
current_rate = exchange.get_ticker(trade.pair, False)['bid'] current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
current_profit = trade.calc_profit_percent(current_rate) current_profit = trade.calc_profit_percent(current_rate)
fmt_close_profit = '{:.2f}%'.format( fmt_close_profit = '{:.2f}%'.format(
round(trade.close_profit * 100, 2) round(trade.close_profit * 100, 2)
@ -91,7 +90,7 @@ class RPC(object):
.format( .format(
trade_id=trade.id, trade_id=trade.id,
pair=trade.pair, pair=trade.pair,
market_url=exchange.get_pair_detail_url(trade.pair), market_url=self._freqtrade.exchange.get_pair_detail_url(trade.pair),
date=arrow.get(trade.open_date).humanize(), date=arrow.get(trade.open_date).humanize(),
open_rate=trade.open_rate, open_rate=trade.open_rate,
close_rate=trade.close_rate, close_rate=trade.close_rate,
@ -116,7 +115,7 @@ class RPC(object):
trades_list = [] trades_list = []
for trade in trades: for trade in trades:
# calculate profit and send message to user # calculate profit and send message to user
current_rate = exchange.get_ticker(trade.pair, False)['bid'] current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
trades_list.append([ trades_list.append([
trade.id, trade.id,
trade.pair, trade.pair,
@ -201,7 +200,7 @@ class RPC(object):
profit_closed_percent.append(profit_percent) profit_closed_percent.append(profit_percent)
else: else:
# Get current rate # Get current rate
current_rate = exchange.get_ticker(trade.pair, False)['bid'] current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
profit_percent = trade.calc_profit_percent(rate=current_rate) profit_percent = trade.calc_profit_percent(rate=current_rate)
profit_all_coin.append( profit_all_coin.append(
@ -258,7 +257,7 @@ class RPC(object):
""" Returns current account balance per crypto """ """ Returns current account balance per crypto """
output = [] output = []
total = 0.0 total = 0.0
for coin, balance in exchange.get_balances().items(): for coin, balance in self._freqtrade.exchange.get_balances().items():
if not balance['total']: if not balance['total']:
continue continue
@ -266,9 +265,9 @@ class RPC(object):
rate = 1.0 rate = 1.0
else: else:
if coin == 'USDT': if coin == 'USDT':
rate = 1.0 / exchange.get_ticker('BTC/USDT', False)['bid'] rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid']
else: else:
rate = exchange.get_ticker(coin + '/BTC', False)['bid'] rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid']
est_btc: float = rate * balance['total'] est_btc: float = rate * balance['total']
total = total + est_btc total = total + est_btc
output.append( output.append(
@ -318,13 +317,13 @@ class RPC(object):
def _exec_forcesell(trade: Trade) -> None: def _exec_forcesell(trade: Trade) -> None:
# Check if there is there is an open order # Check if there is there is an open order
if trade.open_order_id: if trade.open_order_id:
order = exchange.get_order(trade.open_order_id, trade.pair) order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair)
# Cancel open LIMIT_BUY orders and close trade # Cancel open LIMIT_BUY orders and close trade
if order and order['status'] == 'open' \ if order and order['status'] == 'open' \
and order['type'] == 'limit' \ and order['type'] == 'limit' \
and order['side'] == 'buy': and order['side'] == 'buy':
exchange.cancel_order(trade.open_order_id, trade.pair) self._freqtrade.exchange.cancel_order(trade.open_order_id, trade.pair)
trade.close(order.get('price') or trade.open_rate) trade.close(order.get('price') or trade.open_rate)
# Do the best effort, if we don't know 'filled' amount, don't try selling # Do the best effort, if we don't know 'filled' amount, don't try selling
if order['filled'] is None: if order['filled'] is None:
@ -338,7 +337,7 @@ class RPC(object):
return return
# Get current rate and execute sell # Get current rate and execute sell
current_rate = exchange.get_ticker(trade.pair, False)['bid'] current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
self._freqtrade.execute_sell(trade, current_rate) self._freqtrade.execute_sell(trade, current_rate)
# ---- EOF def _exec_forcesell ---- # ---- EOF def _exec_forcesell ----

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
@ -83,7 +84,7 @@ class StrategyResolver(object):
strategy = self._search_strategy(path, strategy_name) strategy = self._search_strategy(path, strategy_name)
if strategy: if strategy:
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
return strategy return import_strategy(strategy)
raise ImportError( raise ImportError(
"Impossible to load Strategy '{}'. This class does not exist" "Impossible to load Strategy '{}'. This class does not exist"
@ -100,7 +101,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

@ -13,6 +13,7 @@ from telegram import Chat, Message, Update
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade import constants from freqtrade import constants
from freqtrade.exchange import Exchange
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
logging.getLogger('').setLevel(logging.INFO) logging.getLogger('').setLevel(logging.INFO)
@ -26,6 +27,20 @@ def log_has(line, logs):
False) False)
def patch_exchange(mocker, api_mock=None) -> None:
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
if api_mock:
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
else:
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock())
def get_patched_exchange(mocker, config, api_mock=None) -> Exchange:
patch_exchange(mocker, api_mock)
exchange = Exchange(config)
return exchange
# Functions for recurrent object patching # Functions for recurrent object patching
def get_patched_freqtradebot(mocker, config) -> FreqtradeBot: def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
""" """
@ -39,7 +54,7 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock()) mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) patch_exchange(mocker, None)
mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock())
mocker.patch('freqtrade.freqtradebot.Analyze.get_signal', MagicMock()) mocker.patch('freqtrade.freqtradebot.Analyze.get_signal', MagicMock())

View File

@ -3,33 +3,20 @@
import logging import logging
from copy import deepcopy from copy import deepcopy
from random import randint from random import randint
from datetime import datetime
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock, PropertyMock
import ccxt import ccxt
import pytest import pytest
import freqtrade.exchange as exchange
from freqtrade import OperationalException, DependencyException, TemporaryError from freqtrade import OperationalException, DependencyException, TemporaryError
from freqtrade.exchange import (init, validate_pairs, buy, sell, get_balance, get_balances, from freqtrade.exchange import Exchange, API_RETRY_COUNT
get_ticker, get_ticker_history, cancel_order, get_name, get_fee, from freqtrade.tests.conftest import log_has, get_patched_exchange
get_id, get_pair_detail_url, get_amount_lots)
from freqtrade.tests.conftest import log_has
API_INIT = False
def maybe_init_api(conf, mocker, force=False):
global API_INIT
if force or not API_INIT:
mocker.patch('freqtrade.exchange.validate_pairs',
side_effect=lambda s: True)
init(config=conf)
API_INIT = True
def test_init(default_conf, mocker, caplog): def test_init(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
maybe_init_api(default_conf, mocker, True) get_patched_exchange(mocker, default_conf)
assert log_has('Instance is running with dry_run enabled', caplog.record_tuples) assert log_has('Instance is running with dry_run enabled', caplog.record_tuples)
@ -39,7 +26,7 @@ def test_init_exception(default_conf):
with pytest.raises( with pytest.raises(
OperationalException, OperationalException,
match='Exchange {} is not supported'.format(default_conf['exchange']['name'])): match='Exchange {} is not supported'.format(default_conf['exchange']['name'])):
init(config=default_conf) Exchange(default_conf)
def test_validate_pairs(default_conf, mocker): def test_validate_pairs(default_conf, mocker):
@ -50,18 +37,17 @@ def test_validate_pairs(default_conf, mocker):
id_mock = PropertyMock(return_value='test_exchange') id_mock = PropertyMock(return_value='test_exchange')
type(api_mock).id = id_mock type(api_mock).id = id_mock
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch.dict('freqtrade.exchange._CONF', default_conf) Exchange(default_conf)
validate_pairs(default_conf['exchange']['pair_whitelist'])
def test_validate_pairs_not_available(default_conf, mocker): def test_validate_pairs_not_available(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={}) api_mock.load_markets = MagicMock(return_value={})
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
with pytest.raises(OperationalException, match=r'not available'): with pytest.raises(OperationalException, match=r'not available'):
validate_pairs(default_conf['exchange']['pair_whitelist']) Exchange(default_conf)
def test_validate_pairs_not_compatible(default_conf, mocker): def test_validate_pairs_not_compatible(default_conf, mocker):
@ -71,25 +57,27 @@ def test_validate_pairs_not_compatible(default_conf, mocker):
}) })
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['stake_currency'] = 'ETH' conf['stake_currency'] = 'ETH'
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch.dict('freqtrade.exchange._CONF', conf)
with pytest.raises(OperationalException, match=r'not compatible'): with pytest.raises(OperationalException, match=r'not compatible'):
validate_pairs(conf['exchange']['pair_whitelist']) Exchange(conf)
def test_validate_pairs_exception(default_conf, mocker, caplog): def test_validate_pairs_exception(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
api_mock = MagicMock() api_mock = MagicMock()
api_mock.name = 'Binance' mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance'))
mocker.patch('freqtrade.exchange._API', api_mock)
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
api_mock.load_markets = MagicMock(return_value={}) api_mock.load_markets = MagicMock(return_value={})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available at Binance'): with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available at Binance'):
validate_pairs(default_conf['exchange']['pair_whitelist']) Exchange(default_conf)
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError()) api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError())
validate_pairs(default_conf['exchange']['pair_whitelist'])
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
Exchange(default_conf)
assert log_has('Unable to validate pairs (assuming they are correct). Reason: ', assert log_has('Unable to validate pairs (assuming they are correct). Reason: ',
caplog.record_tuples) caplog.record_tuples)
@ -99,22 +87,21 @@ def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['stake_currency'] = 'ETH' conf['stake_currency'] = 'ETH'
api_mock = MagicMock() api_mock = MagicMock()
api_mock.name = 'binance' api_mock.name = MagicMock(return_value='binance')
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch.dict('freqtrade.exchange._CONF', conf)
with pytest.raises( with pytest.raises(
OperationalException, OperationalException,
match=r'Pair ETH/BTC not compatible with stake_currency: ETH' match=r'Pair ETH/BTC not compatible with stake_currency: ETH'
): ):
validate_pairs(default_conf['exchange']['pair_whitelist']) Exchange(conf)
def test_buy_dry_run(default_conf, mocker): def test_buy_dry_run(default_conf, mocker):
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf) exchange = get_patched_exchange(mocker, default_conf)
order = buy(pair='ETH/BTC', rate=200, amount=1) order = exchange.buy(pair='ETH/BTC', rate=200, amount=1)
assert 'id' in order assert 'id' in order
assert 'dry_run_buy_' in order['id'] assert 'dry_run_buy_' in order['id']
@ -128,12 +115,10 @@ def test_buy_prod(default_conf, mocker):
'foo': 'bar' 'foo': 'bar'
} }
}) })
mocker.patch('freqtrade.exchange._API', api_mock)
default_conf['dry_run'] = False default_conf['dry_run'] = False
mocker.patch.dict('freqtrade.exchange._CONF', default_conf) exchange = get_patched_exchange(mocker, default_conf, api_mock)
order = buy(pair='ETH/BTC', rate=200, amount=1) order = exchange.buy(pair='ETH/BTC', rate=200, amount=1)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
assert order['id'] == order_id assert order['id'] == order_id
@ -141,30 +126,30 @@ def test_buy_prod(default_conf, mocker):
# test exception handling # test exception handling
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InsufficientFunds) api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InsufficientFunds)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
buy(pair='ETH/BTC', rate=200, amount=1) exchange.buy(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
buy(pair='ETH/BTC', rate=200, amount=1) exchange.buy(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
buy(pair='ETH/BTC', rate=200, amount=1) exchange.buy(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError) api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
buy(pair='ETH/BTC', rate=200, amount=1) exchange.buy(pair='ETH/BTC', rate=200, amount=1)
def test_sell_dry_run(default_conf, mocker): def test_sell_dry_run(default_conf, mocker):
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf) exchange = get_patched_exchange(mocker, default_conf)
order = sell(pair='ETH/BTC', rate=200, amount=1) order = exchange.sell(pair='ETH/BTC', rate=200, amount=1)
assert 'id' in order assert 'id' in order
assert 'dry_run_sell_' in order['id'] assert 'dry_run_sell_' in order['id']
@ -178,12 +163,11 @@ def test_sell_prod(default_conf, mocker):
'foo': 'bar' 'foo': 'bar'
} }
}) })
mocker.patch('freqtrade.exchange._API', api_mock)
default_conf['dry_run'] = False default_conf['dry_run'] = False
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
order = sell(pair='ETH/BTC', rate=200, amount=1) exchange = get_patched_exchange(mocker, default_conf, api_mock)
order = exchange.sell(pair='ETH/BTC', rate=200, amount=1)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
assert order['id'] == order_id assert order['id'] == order_id
@ -191,53 +175,52 @@ def test_sell_prod(default_conf, mocker):
# test exception handling # test exception handling
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InsufficientFunds) api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InsufficientFunds)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
sell(pair='ETH/BTC', rate=200, amount=1) exchange.sell(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
sell(pair='ETH/BTC', rate=200, amount=1) exchange.sell(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
sell(pair='ETH/BTC', rate=200, amount=1) exchange.sell(pair='ETH/BTC', rate=200, amount=1)
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError) api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
sell(pair='ETH/BTC', rate=200, amount=1) exchange.sell(pair='ETH/BTC', rate=200, amount=1)
def test_get_balance_dry_run(default_conf, mocker): def test_get_balance_dry_run(default_conf, mocker):
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
assert get_balance(currency='BTC') == 999.9 exchange = get_patched_exchange(mocker, default_conf)
assert exchange.get_balance(currency='BTC') == 999.9
def test_get_balance_prod(default_conf, mocker): def test_get_balance_prod(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}}) api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4}})
mocker.patch('freqtrade.exchange._API', api_mock)
default_conf['dry_run'] = False default_conf['dry_run'] = False
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
assert get_balance(currency='BTC') == 123.4 exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.get_balance(currency='BTC') == 123.4
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError) api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
get_balance(currency='BTC')
exchange.get_balance(currency='BTC')
def test_get_balances_dry_run(default_conf, mocker): def test_get_balances_dry_run(default_conf, mocker):
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf) exchange = get_patched_exchange(mocker, default_conf)
assert exchange.get_balances() == {}
assert get_balances() == {}
def test_get_balances_prod(default_conf, mocker): def test_get_balances_prod(default_conf, mocker):
@ -253,33 +236,73 @@ def test_get_balances_prod(default_conf, mocker):
'2ST': balance_item, '2ST': balance_item,
'3ST': balance_item '3ST': balance_item
}) })
mocker.patch('freqtrade.exchange._API', api_mock)
default_conf['dry_run'] = False default_conf['dry_run'] = False
mocker.patch.dict('freqtrade.exchange._CONF', default_conf) exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert len(exchange.get_balances()) == 3
assert len(get_balances()) == 3 assert exchange.get_balances()['1ST']['free'] == 10.0
assert get_balances()['1ST']['free'] == 10.0 assert exchange.get_balances()['1ST']['total'] == 10.0
assert get_balances()['1ST']['total'] == 10.0 assert exchange.get_balances()['1ST']['used'] == 0.0
assert get_balances()['1ST']['used'] == 0.0
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError) api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
get_balances() exchange.get_balances()
assert api_mock.fetch_balance.call_count == exchange.API_RETRY_COUNT + 1 assert api_mock.fetch_balance.call_count == API_RETRY_COUNT + 1
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError) api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
get_balances() exchange.get_balances()
assert api_mock.fetch_balance.call_count == 1 assert api_mock.fetch_balance.call_count == 1
# This test is somewhat redundant with def test_get_tickers(default_conf, mocker):
# test_exchange_bittrex.py::test_exchange_bittrex_get_ticker api_mock = MagicMock()
tick = {'ETH/BTC': {
'symbol': 'ETH/BTC',
'bid': 0.5,
'ask': 1,
'last': 42,
}, 'BCH/BTC': {
'symbol': 'BCH/BTC',
'bid': 0.6,
'ask': 0.5,
'last': 41,
}
}
api_mock.fetch_tickers = MagicMock(return_value=tick)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# retrieve original ticker
tickers = exchange.get_tickers()
assert 'ETH/BTC' in tickers
assert 'BCH/BTC' in tickers
assert tickers['ETH/BTC']['bid'] == 0.5
assert tickers['ETH/BTC']['ask'] == 1
assert tickers['BCH/BTC']['bid'] == 0.6
assert tickers['BCH/BTC']['ask'] == 0.5
with pytest.raises(TemporaryError): # test retrier
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_tickers()
with pytest.raises(OperationalException):
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_tickers()
with pytest.raises(OperationalException):
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_tickers()
api_mock.fetch_tickers = MagicMock(return_value={})
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_tickers()
def test_get_ticker(default_conf, mocker): def test_get_ticker(default_conf, mocker):
maybe_init_api(default_conf, mocker)
api_mock = MagicMock() api_mock = MagicMock()
tick = { tick = {
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
@ -288,10 +311,9 @@ def test_get_ticker(default_conf, mocker):
'last': 0.0001, 'last': 0.0001,
} }
api_mock.fetch_ticker = MagicMock(return_value=tick) api_mock.fetch_ticker = MagicMock(return_value=tick)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
# retrieve original ticker # retrieve original ticker
ticker = get_ticker(pair='ETH/BTC') ticker = exchange.get_ticker(pair='ETH/BTC')
assert ticker['bid'] == 0.00001098 assert ticker['bid'] == 0.00001098
assert ticker['ask'] == 0.00001099 assert ticker['ask'] == 0.00001099
@ -304,38 +326,38 @@ def test_get_ticker(default_conf, mocker):
'last': 42, 'last': 42,
} }
api_mock.fetch_ticker = MagicMock(return_value=tick) api_mock.fetch_ticker = MagicMock(return_value=tick)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
# if not caching the result we should get the same ticker # if not caching the result we should get the same ticker
# if not fetching a new result we should get the cached ticker # if not fetching a new result we should get the cached ticker
ticker = get_ticker(pair='ETH/BTC') ticker = exchange.get_ticker(pair='ETH/BTC')
assert api_mock.fetch_ticker.call_count == 1 assert api_mock.fetch_ticker.call_count == 1
assert ticker['bid'] == 0.5 assert ticker['bid'] == 0.5
assert ticker['ask'] == 1 assert ticker['ask'] == 1
assert 'ETH/BTC' in exchange._CACHED_TICKER assert 'ETH/BTC' in exchange._cached_ticker
assert exchange._CACHED_TICKER['ETH/BTC']['bid'] == 0.5 assert exchange._cached_ticker['ETH/BTC']['bid'] == 0.5
assert exchange._CACHED_TICKER['ETH/BTC']['ask'] == 1 assert exchange._cached_ticker['ETH/BTC']['ask'] == 1
# Test caching # Test caching
api_mock.fetch_ticker = MagicMock() api_mock.fetch_ticker = MagicMock()
get_ticker(pair='ETH/BTC', refresh=False) exchange.get_ticker(pair='ETH/BTC', refresh=False)
assert api_mock.fetch_ticker.call_count == 0 assert api_mock.fetch_ticker.call_count == 0
with pytest.raises(TemporaryError): # test retrier with pytest.raises(TemporaryError): # test retrier
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError) api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
get_ticker(pair='ETH/BTC', refresh=True) exchange.get_ticker(pair='ETH/BTC', refresh=True)
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.BaseError) api_mock.fetch_ticker = MagicMock(side_effect=ccxt.BaseError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
get_ticker(pair='ETH/BTC', refresh=True) exchange.get_ticker(pair='ETH/BTC', refresh=True)
api_mock.fetch_ticker = MagicMock(return_value={}) api_mock.fetch_ticker = MagicMock(return_value={})
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
get_ticker(pair='ETH/BTC', refresh=True) exchange.get_ticker(pair='ETH/BTC', refresh=True)
def make_fetch_ohlcv_mock(data): def make_fetch_ohlcv_mock(data):
@ -361,10 +383,10 @@ def test_get_ticker_history(default_conf, mocker):
] ]
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
# retrieve original ticker # retrieve original ticker
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1511686200000 assert ticks[0][0] == 1511686200000
assert ticks[0][1] == 1 assert ticks[0][1] == 1
assert ticks[0][2] == 2 assert ticks[0][2] == 2
@ -384,9 +406,9 @@ def test_get_ticker_history(default_conf, mocker):
] ]
] ]
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(new_tick)) api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(new_tick))
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1511686210000 assert ticks[0][0] == 1511686210000
assert ticks[0][1] == 6 assert ticks[0][1] == 6
assert ticks[0][2] == 7 assert ticks[0][2] == 7
@ -396,15 +418,15 @@ def test_get_ticker_history(default_conf, mocker):
with pytest.raises(TemporaryError): # test retrier with pytest.raises(TemporaryError): # test retrier
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NetworkError) api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
# new symbol to get around cache # new symbol to get around cache
get_ticker_history('ABCD/BTC', default_conf['ticker_interval']) exchange.get_ticker_history('ABCD/BTC', default_conf['ticker_interval'])
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError) api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
# new symbol to get around cache # new symbol to get around cache
get_ticker_history('EFGH/BTC', default_conf['ticker_interval']) exchange.get_ticker_history('EFGH/BTC', default_conf['ticker_interval'])
def test_get_ticker_history_sort(default_conf, mocker): def test_get_ticker_history_sort(default_conf, mocker):
@ -426,10 +448,11 @@ def test_get_ticker_history_sort(default_conf, mocker):
] ]
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
mocker.patch('freqtrade.exchange._API', api_mock)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# Test the ticker history sort # Test the ticker history sort
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1527830400000 assert ticks[0][0] == 1527830400000
assert ticks[0][1] == 0.07649 assert ticks[0][1] == 0.07649
assert ticks[0][2] == 0.07651 assert ticks[0][2] == 0.07651
@ -460,10 +483,9 @@ def test_get_ticker_history_sort(default_conf, mocker):
] ]
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
# Test the ticker history sort # Test the ticker history sort
ticks = get_ticker_history('ETH/BTC', default_conf['ticker_interval']) ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1527827700000 assert ticks[0][0] == 1527827700000
assert ticks[0][1] == 0.07659999 assert ticks[0][1] == 0.07659999
assert ticks[0][2] == 0.0766 assert ticks[0][2] == 0.0766
@ -481,117 +503,194 @@ def test_get_ticker_history_sort(default_conf, mocker):
def test_cancel_order_dry_run(default_conf, mocker): def test_cancel_order_dry_run(default_conf, mocker):
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf) exchange = get_patched_exchange(mocker, default_conf)
assert exchange.cancel_order(order_id='123', pair='TKN/BTC') is None
assert cancel_order(order_id='123', pair='TKN/BTC') is None
# 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)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert cancel_order(order_id='_', pair='TKN/BTC') == 123 assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock)
cancel_order(order_id='_', pair='TKN/BTC') exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert api_mock.cancel_order.call_count == exchange.API_RETRY_COUNT + 1 exchange.cancel_order(order_id='_', pair='TKN/BTC')
assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
cancel_order(order_id='_', pair='TKN/BTC') exchange.cancel_order(order_id='_', pair='TKN/BTC')
assert api_mock.cancel_order.call_count == exchange.API_RETRY_COUNT + 1 assert api_mock.cancel_order.call_count == API_RETRY_COUNT + 1
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.BaseError) api_mock.cancel_order = MagicMock(side_effect=ccxt.BaseError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
cancel_order(order_id='_', pair='TKN/BTC') exchange.cancel_order(order_id='_', pair='TKN/BTC')
assert api_mock.cancel_order.call_count == 1 assert api_mock.cancel_order.call_count == 1
def test_get_order(default_conf, mocker): def test_get_order(default_conf, mocker):
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
order = MagicMock() order = MagicMock()
order.myid = 123 order.myid = 123
exchange._DRY_RUN_OPEN_ORDERS['X'] = order exchange = get_patched_exchange(mocker, default_conf)
exchange._dry_run_open_orders['X'] = order
print(exchange.get_order('X', 'TKN/BTC')) print(exchange.get_order('X', 'TKN/BTC'))
assert exchange.get_order('X', 'TKN/BTC').myid == 123 assert exchange.get_order('X', 'TKN/BTC').myid == 123
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.fetch_order = MagicMock(return_value=456) api_mock.fetch_order = MagicMock(return_value=456)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.get_order('X', 'TKN/BTC') == 456 assert exchange.get_order('X', 'TKN/BTC') == 456
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order(order_id='_', pair='TKN/BTC') exchange.get_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_order.call_count == exchange.API_RETRY_COUNT + 1 assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order(order_id='_', pair='TKN/BTC') exchange.get_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_order.call_count == exchange.API_RETRY_COUNT + 1 assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.BaseError) api_mock.fetch_order = MagicMock(side_effect=ccxt.BaseError)
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order(order_id='_', pair='TKN/BTC') exchange.get_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_order.call_count == 1 assert api_mock.fetch_order.call_count == 1
def test_get_name(default_conf, mocker): def test_name(default_conf, mocker):
mocker.patch('freqtrade.exchange.validate_pairs', mocker.patch('freqtrade.exchange.Exchange.validate_pairs',
side_effect=lambda s: True) side_effect=lambda s: True)
default_conf['exchange']['name'] = 'binance' default_conf['exchange']['name'] = 'binance'
init(default_conf) exchange = Exchange(default_conf)
assert get_name() == 'Binance' assert exchange.name == 'Binance'
def test_get_id(default_conf, mocker): def test_id(default_conf, mocker):
mocker.patch('freqtrade.exchange.validate_pairs', mocker.patch('freqtrade.exchange.Exchange.validate_pairs',
side_effect=lambda s: True) side_effect=lambda s: True)
default_conf['exchange']['name'] = 'binance' default_conf['exchange']['name'] = 'binance'
init(default_conf) exchange = Exchange(default_conf)
assert exchange.id == 'binance'
assert get_id() == 'binance'
def test_get_pair_detail_url(default_conf, mocker): def test_get_pair_detail_url(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.validate_pairs', mocker.patch('freqtrade.exchange.Exchange.validate_pairs',
side_effect=lambda s: True) side_effect=lambda s: True)
default_conf['exchange']['name'] = 'binance' default_conf['exchange']['name'] = 'binance'
init(default_conf) exchange = Exchange(default_conf)
url = get_pair_detail_url('TKN/ETH') url = exchange.get_pair_detail_url('TKN/ETH')
assert 'TKN' in url assert 'TKN' in url
assert 'ETH' in url assert 'ETH' in url
url = get_pair_detail_url('LOOONG/BTC') url = exchange.get_pair_detail_url('LOOONG/BTC')
assert 'LOOONG' in url assert 'LOOONG' in url
assert 'BTC' in url assert 'BTC' in url
default_conf['exchange']['name'] = 'bittrex' default_conf['exchange']['name'] = 'bittrex'
init(default_conf) exchange = Exchange(default_conf)
url = get_pair_detail_url('TKN/ETH') url = exchange.get_pair_detail_url('TKN/ETH')
assert 'TKN' in url assert 'TKN' in url
assert 'ETH' in url assert 'ETH' in url
url = get_pair_detail_url('LOOONG/BTC') url = exchange.get_pair_detail_url('LOOONG/BTC')
assert 'LOOONG' in url assert 'LOOONG' in url
assert 'BTC' in url assert 'BTC' in url
default_conf['exchange']['name'] = 'poloniex'
exchange = Exchange(default_conf)
url = exchange.get_pair_detail_url('LOOONG/BTC')
assert '' == url
assert log_has('Could not get exchange url for Poloniex', caplog.record_tuples)
def test_get_trades_for_order(default_conf, mocker):
order_id = 'ABCD-ABCD'
since = datetime(2018, 5, 5)
default_conf["dry_run"] = False
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
api_mock = MagicMock()
api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV',
'order': 'ABCD-ABCD',
'info': {'pair': 'XLTCZBTC',
'time': 1519860024.4388,
'type': 'buy',
'ordertype': 'limit',
'price': '20.00000',
'cost': '38.62000',
'fee': '0.06179',
'vol': '5',
'id': 'ABCD-ABCD'},
'timestamp': 1519860024438,
'datetime': '2018-02-28T23:20:24.438Z',
'symbol': 'LTC/BTC',
'type': 'limit',
'side': 'buy',
'price': 165.0,
'amount': 0.2340606,
'fee': {'cost': 0.06179, 'currency': 'BTC'}
}])
exchange = get_patched_exchange(mocker, default_conf, api_mock)
orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since)
assert len(orders) == 1
assert orders[0]['price'] == 165
# test Exceptions
with pytest.raises(OperationalException):
api_mock = MagicMock()
api_mock.fetch_my_trades = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_trades_for_order(order_id, 'LTC/BTC', since)
with pytest.raises(TemporaryError):
api_mock = MagicMock()
api_mock.fetch_my_trades = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_trades_for_order(order_id, 'LTC/BTC', since)
assert api_mock.fetch_my_trades.call_count == API_RETRY_COUNT + 1
def test_get_markets(default_conf, mocker, markets):
api_mock = MagicMock()
api_mock.fetch_markets = markets
exchange = get_patched_exchange(mocker, default_conf, api_mock)
ret = exchange.get_markets()
assert isinstance(ret, list)
assert len(ret) == 6
assert ret[0]["id"] == "ethbtc"
assert ret[0]["symbol"] == "ETH/BTC"
# test Exceptions
with pytest.raises(OperationalException):
api_mock = MagicMock()
api_mock.fetch_markets = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_markets()
with pytest.raises(TemporaryError):
api_mock = MagicMock()
api_mock.fetch_markets = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_markets()
assert api_mock.fetch_markets.call_count == API_RETRY_COUNT + 1
def test_get_fee(default_conf, mocker): def test_get_fee(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
@ -601,12 +700,32 @@ def test_get_fee(default_conf, mocker):
'rate': 0.025, 'rate': 0.025,
'cost': 0.05 'cost': 0.05
}) })
mocker.patch('freqtrade.exchange._API', api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert get_fee() == 0.025
assert exchange.get_fee() == 0.025
# test Exceptions
with pytest.raises(OperationalException):
api_mock = MagicMock()
api_mock.calculate_fee = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_fee()
with pytest.raises(TemporaryError):
api_mock = MagicMock()
api_mock.calculate_fee = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_fee()
assert api_mock.calculate_fee.call_count == API_RETRY_COUNT + 1
def test_get_amount_lots(default_conf, mocker): def test_get_amount_lots(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.amount_to_lots = MagicMock(return_value=1.0) api_mock.amount_to_lots = MagicMock(return_value=1.0)
mocker.patch('freqtrade.exchange._API', api_mock) api_mock.markets = None
assert get_amount_lots('LTC/BTC', 1.54) == 1 marketmock = MagicMock()
api_mock.load_markets = marketmock
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.get_amount_lots('LTC/BTC', 1.54) == 1
assert marketmock.call_count == 1

View File

@ -16,7 +16,7 @@ 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
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has, patch_exchange
def get_args(args) -> List[str]: def get_args(args) -> List[str]:
@ -84,7 +84,7 @@ def load_data_test(what):
def simple_backtest(config, contour, num_results, mocker) -> None: def simple_backtest(config, contour, num_results, mocker) -> None:
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
backtesting = Backtesting(config) backtesting = Backtesting(config)
data = load_data_test(contour) data = load_data_test(contour)
@ -102,7 +102,8 @@ def simple_backtest(config, contour, num_results, mocker) -> None:
assert len(results) == num_results assert len(results) == num_results
def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False, timerange=None): def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
timerange=None, exchange=None):
tickerdata = optimize.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange) tickerdata = optimize.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
pairdata = {'UNITTEST/BTC': tickerdata} pairdata = {'UNITTEST/BTC': tickerdata}
return pairdata return pairdata
@ -119,7 +120,7 @@ def _load_pair_as_ticks(pair, tickfreq):
def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None): def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None):
data = optimize.load_data(None, ticker_interval='8m', pairs=[pair]) data = optimize.load_data(None, ticker_interval='8m', pairs=[pair])
data = trim_dictlist(data, -201) data = trim_dictlist(data, -201)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
backtesting = Backtesting(conf) backtesting = Backtesting(conf)
return { return {
'stake_amount': conf['stake_amount'], 'stake_amount': conf['stake_amount'],
@ -295,8 +296,8 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
Test start() function Test start() function
""" """
start_mock = MagicMock() start_mock = MagicMock()
mocker.patch('freqtrade.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock) mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock)
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
@ -319,7 +320,8 @@ def test_backtesting_init(mocker, default_conf) -> None:
""" """
Test Backtesting._init() method Test Backtesting._init() method
""" """
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
assert backtesting.config == default_conf assert backtesting.config == default_conf
assert isinstance(backtesting.analyze, Analyze) assert isinstance(backtesting.analyze, Analyze)
@ -327,13 +329,15 @@ def test_backtesting_init(mocker, default_conf) -> None:
assert callable(backtesting.tickerdata_to_dataframe) assert callable(backtesting.tickerdata_to_dataframe)
assert callable(backtesting.populate_buy_trend) assert callable(backtesting.populate_buy_trend)
assert callable(backtesting.populate_sell_trend) assert callable(backtesting.populate_sell_trend)
get_fee.assert_called()
assert backtesting.fee == 0.5
def test_tickerdata_to_dataframe(default_conf, mocker) -> None: def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
""" """
Test Backtesting.tickerdata_to_dataframe() method Test Backtesting.tickerdata_to_dataframe() method
""" """
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
timerange = TimeRange(None, 'line', 0, -100) timerange = TimeRange(None, 'line', 0, -100)
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange) tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}
@ -352,7 +356,7 @@ def test_get_timeframe(default_conf, mocker) -> None:
""" """
Test Backtesting.get_timeframe() method Test Backtesting.get_timeframe() method
""" """
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
data = backtesting.tickerdata_to_dataframe( data = backtesting.tickerdata_to_dataframe(
@ -371,7 +375,7 @@ def test_generate_text_table(default_conf, mocker):
""" """
Test Backtesting.generate_text_table() method Test Backtesting.generate_text_table() method
""" """
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
results = pd.DataFrame( results = pd.DataFrame(
@ -408,8 +412,8 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock()) mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
mocker.patch('freqtrade.optimize.load_data', mocked_load_data) mocker.patch('freqtrade.optimize.load_data', mocked_load_data)
mocker.patch('freqtrade.exchange.get_ticker_history') mocker.patch('freqtrade.exchange.Exchange.get_ticker_history')
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting', 'freqtrade.optimize.backtesting.Backtesting',
backtest=MagicMock(), backtest=MagicMock(),
@ -449,8 +453,8 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None:
mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock()) mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
mocker.patch('freqtrade.optimize.load_data', MagicMock(return_value={})) mocker.patch('freqtrade.optimize.load_data', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.get_ticker_history') mocker.patch('freqtrade.exchange.Exchange.get_ticker_history')
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting', 'freqtrade.optimize.backtesting.Backtesting',
backtest=MagicMock(), backtest=MagicMock(),
@ -477,8 +481,8 @@ def test_backtest(default_conf, fee, mocker) -> None:
""" """
Test Backtesting.backtest() method Test Backtesting.backtest() method
""" """
mocker.patch('freqtrade.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC']) data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
@ -499,8 +503,8 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
""" """
Test Backtesting.backtest() method with 1 min ticker Test Backtesting.backtest() method with 1 min ticker
""" """
mocker.patch('freqtrade.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
# Run a backtesting for an exiting 5min ticker_interval # Run a backtesting for an exiting 5min ticker_interval
@ -522,7 +526,7 @@ def test_processed(default_conf, mocker) -> None:
""" """
Test Backtesting.backtest() method with offline data Test Backtesting.backtest() method with offline data
""" """
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
dict_of_tickerrows = load_data_test('raise') dict_of_tickerrows = load_data_test('raise')
@ -536,7 +540,7 @@ def test_processed(default_conf, mocker) -> None:
def test_backtest_pricecontours(default_conf, fee, mocker) -> None: def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
tests = [['raise', 18], ['lower', 0], ['sine', 16]] tests = [['raise', 18], ['lower', 0], ['sine', 16]]
for [contour, numres] in tests: for [contour, numres] in tests:
simple_backtest(default_conf, contour, numres, mocker) simple_backtest(default_conf, contour, numres, mocker)
@ -544,8 +548,8 @@ def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
# Test backtest using offline data (testdata directory) # Test backtest using offline data (testdata directory)
def test_backtest_ticks(default_conf, fee, mocker): def test_backtest_ticks(default_conf, fee, mocker):
mocker.patch('freqtrade.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
ticks = [1, 5] ticks = [1, 5]
fun = Backtesting(default_conf).populate_buy_trend fun = Backtesting(default_conf).populate_buy_trend
for _ in ticks: for _ in ticks:
@ -564,7 +568,6 @@ def test_backtest_clash_buy_sell(mocker, default_conf):
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = fun # Override backtesting.populate_buy_trend = fun # Override
@ -580,7 +583,6 @@ def test_backtest_only_sell(mocker, default_conf):
sell_value = 1 sell_value = 1
return _trend(dataframe, buy_value, sell_value) return _trend(dataframe, buy_value, sell_value)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtest_conf = _make_backtest_conf(mocker, conf=default_conf) backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = fun # Override backtesting.populate_buy_trend = fun # Override
@ -590,8 +592,7 @@ def test_backtest_only_sell(mocker, default_conf):
def test_backtest_alternate_buy_sell(default_conf, fee, mocker): def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC') backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
backtesting = Backtesting(default_conf) backtesting = Backtesting(default_conf)
backtesting.populate_buy_trend = _trend_alternate # Override backtesting.populate_buy_trend = _trend_alternate # Override
@ -606,8 +607,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
def test_backtest_record(default_conf, fee, mocker): def test_backtest_record(default_conf, fee, mocker):
names = [] names = []
records = [] records = []
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch( mocker.patch(
'freqtrade.optimize.backtesting.file_dump_json', 'freqtrade.optimize.backtesting.file_dump_json',
new=lambda n, r: (names.append(n), records.append(r)) new=lambda n, r: (names.append(n), records.append(r))
@ -655,9 +656,9 @@ def test_backtest_record(default_conf, fee, mocker):
def test_backtest_start_live(default_conf, mocker, caplog): def test_backtest_start_live(default_conf, mocker, caplog):
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC'] conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
mocker.patch('freqtrade.exchange.get_ticker_history', mocker.patch('freqtrade.exchange.Exchange.get_ticker_history',
new=lambda n, i: _load_pair_as_ticks(n, i)) new=lambda s, n, i: _load_pair_as_ticks(n, i))
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock()) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', MagicMock()) mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', MagicMock())
mocker.patch('freqtrade.configuration.open', mocker.mock_open( mocker.patch('freqtrade.configuration.open', mocker.mock_open(

View File

@ -10,7 +10,7 @@ import pytest
from freqtrade.optimize.__init__ import load_tickerdata_file from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.optimize.hyperopt import Hyperopt, start from freqtrade.optimize.hyperopt import Hyperopt, start
from freqtrade.strategy.resolver import StrategyResolver from freqtrade.strategy.resolver import StrategyResolver
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has, patch_exchange
from freqtrade.tests.optimize.test_backtesting import get_args from freqtrade.tests.optimize.test_backtesting import get_args
# Avoid to reinit the same object again and again # Avoid to reinit the same object again and again
@ -22,8 +22,7 @@ _HYPEROPT = None
def init_hyperopt(default_conf, mocker): def init_hyperopt(default_conf, mocker):
global _HYPEROPT_INITIALIZED, _HYPEROPT global _HYPEROPT_INITIALIZED, _HYPEROPT
if not _HYPEROPT_INITIALIZED: if not _HYPEROPT_INITIALIZED:
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) patch_exchange(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
_HYPEROPT = Hyperopt(default_conf) _HYPEROPT = Hyperopt(default_conf)
_HYPEROPT_INITIALIZED = True _HYPEROPT_INITIALIZED = True
@ -61,8 +60,12 @@ def test_start(mocker, default_conf, caplog) -> None:
Test start() function Test start() function
""" """
start_mock = MagicMock() start_mock = MagicMock()
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock) mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) patch_exchange(mocker)
args = [ args = [
'--config', 'config.json', '--config', 'config.json',
@ -178,7 +181,7 @@ def test_fmin_best_results(mocker, init_hyperopt, default_conf, caplog) -> None:
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) patch_exchange(mocker)
StrategyResolver({'strategy': 'DefaultStrategy'}) StrategyResolver({'strategy': 'DefaultStrategy'})
hyperopt = Hyperopt(conf) hyperopt = Hyperopt(conf)
@ -222,7 +225,7 @@ def test_fmin_throw_value_error(mocker, init_hyperopt, default_conf, caplog) ->
conf.update({'epochs': 1}) conf.update({'epochs': 1})
conf.update({'timerange': None}) conf.update({'timerange': None})
conf.update({'spaces': 'all'}) conf.update({'spaces': 'all'})
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) patch_exchange(mocker)
StrategyResolver({'strategy': 'DefaultStrategy'}) StrategyResolver({'strategy': 'DefaultStrategy'})
hyperopt = Hyperopt(conf) hyperopt = Hyperopt(conf)
@ -263,7 +266,7 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, defa
mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results) mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results)
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) patch_exchange(mocker)
StrategyResolver({'strategy': 'DefaultStrategy'}) StrategyResolver({'strategy': 'DefaultStrategy'})
hyperopt = Hyperopt(conf) hyperopt = Hyperopt(conf)
@ -334,7 +337,7 @@ def test_start_calls_fmin(mocker, init_hyperopt, default_conf) -> None:
trials = create_trials(mocker) trials = create_trials(mocker)
mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results) mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results)
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) patch_exchange(mocker)
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
@ -499,7 +502,7 @@ def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
'freqtrade.optimize.hyperopt.Hyperopt.backtest', 'freqtrade.optimize.hyperopt.Hyperopt.backtest',
MagicMock(return_value=backtest_result) MagicMock(return_value=backtest_result)
) )
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock()) patch_exchange(mocker)
optimizer_param = { optimizer_param = {
'adx': {'enabled': False}, 'adx': {'enabled': False},

View File

@ -12,7 +12,7 @@ from freqtrade.optimize.__init__ import make_testdata_path, download_pairs, \
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, \ download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, \
load_cached_data_for_updating load_cached_data_for_updating
from freqtrade.arguments import TimeRange from freqtrade.arguments import TimeRange
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has, get_patched_exchange
# Change this if modifying UNITTEST/BTC testdatafile # Change this if modifying UNITTEST/BTC testdatafile
_BTC_UNITTEST_LENGTH = 13681 _BTC_UNITTEST_LENGTH = 13681
@ -49,12 +49,11 @@ def _clean_test_file(file: str) -> None:
os.rename(file_swp, file) os.rename(file_swp, file)
def test_load_data_30min_ticker(ticker_history, mocker, caplog) -> None: def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
""" """
Test load_data() with 30 min ticker Test load_data() with 30 min ticker
""" """
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json') file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json')
_backup_file(file, copy_file=True) _backup_file(file, copy_file=True)
optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='30m') optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='30m')
@ -63,11 +62,11 @@ def test_load_data_30min_ticker(ticker_history, mocker, caplog) -> None:
_clean_test_file(file) _clean_test_file(file)
def test_load_data_5min_ticker(ticker_history, mocker, caplog) -> None: def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
""" """
Test load_data() with 5 min ticker Test load_data() with 5 min ticker
""" """
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json') file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json')
_backup_file(file, copy_file=True) _backup_file(file, copy_file=True)
@ -81,7 +80,7 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
""" """
Test load_data() with 1 min ticker Test load_data() with 1 min ticker
""" """
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json') file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
_backup_file(file, copy_file=True) _backup_file(file, copy_file=True)
@ -91,12 +90,12 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
_clean_test_file(file) _clean_test_file(file)
def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog) -> None: def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog, default_conf) -> None:
""" """
Test load_data() with 1 min ticker Test load_data() with 1 min ticker
""" """
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json') file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
_backup_file(file) _backup_file(file)
@ -114,6 +113,7 @@ def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog) -> None:
optimize.load_data(None, optimize.load_data(None,
ticker_interval='1m', ticker_interval='1m',
refresh_pairs=True, refresh_pairs=True,
exchange=exchange,
pairs=['MEME/BTC']) pairs=['MEME/BTC'])
assert os.path.isfile(file) is True assert os.path.isfile(file) is True
assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples) assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
@ -124,9 +124,9 @@ def test_testdata_path() -> None:
assert os.path.join('freqtrade', 'tests', 'testdata') in make_testdata_path(None) assert os.path.join('freqtrade', 'tests', 'testdata') in make_testdata_path(None)
def test_download_pairs(ticker_history, mocker) -> None: def test_download_pairs(ticker_history, mocker, default_conf) -> None:
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json') file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json') file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json')
file2_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-1m.json') file2_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-1m.json')
@ -140,7 +140,8 @@ def test_download_pairs(ticker_history, mocker) -> None:
assert os.path.isfile(file1_1) is False assert os.path.isfile(file1_1) is False
assert os.path.isfile(file2_1) is False assert os.path.isfile(file2_1) is False
assert download_pairs(None, pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='1m') is True assert download_pairs(None, exchange,
pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='1m') is True
assert os.path.isfile(file1_1) is True assert os.path.isfile(file1_1) is True
assert os.path.isfile(file2_1) is True assert os.path.isfile(file2_1) is True
@ -152,7 +153,8 @@ def test_download_pairs(ticker_history, mocker) -> None:
assert os.path.isfile(file1_5) is False assert os.path.isfile(file1_5) is False
assert os.path.isfile(file2_5) is False assert os.path.isfile(file2_5) is False
assert download_pairs(None, pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='5m') is True assert download_pairs(None, exchange,
pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='5m') is True
assert os.path.isfile(file1_5) is True assert os.path.isfile(file1_5) is True
assert os.path.isfile(file2_5) is True assert os.path.isfile(file2_5) is True
@ -265,30 +267,32 @@ def test_load_cached_data_for_updating(mocker) -> None:
assert start_ts is None assert start_ts is None
def test_download_pairs_exception(ticker_history, mocker, caplog) -> None: def test_download_pairs_exception(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata', mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata',
side_effect=BaseException('File Error')) side_effect=BaseException('File Error'))
exchange = get_patched_exchange(mocker, default_conf)
file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json') file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json') file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json')
_backup_file(file1_1) _backup_file(file1_1)
_backup_file(file1_5) _backup_file(file1_5)
download_pairs(None, pairs=['MEME/BTC'], ticker_interval='1m') download_pairs(None, exchange, pairs=['MEME/BTC'], ticker_interval='1m')
# clean files freshly downloaded # clean files freshly downloaded
_clean_test_file(file1_1) _clean_test_file(file1_1)
_clean_test_file(file1_5) _clean_test_file(file1_5)
assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples) assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
def test_download_backtesting_testdata(ticker_history, mocker) -> None: def test_download_backtesting_testdata(ticker_history, mocker, default_conf) -> None:
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
# Download a 1 min ticker file # Download a 1 min ticker file
file1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'XEL_BTC-1m.json') file1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'XEL_BTC-1m.json')
_backup_file(file1) _backup_file(file1)
download_backtesting_testdata(None, pair="XEL/BTC", tick_interval='1m') download_backtesting_testdata(None, exchange, pair="XEL/BTC", tick_interval='1m')
assert os.path.isfile(file1) is True assert os.path.isfile(file1) is True
_clean_test_file(file1) _clean_test_file(file1)
@ -296,21 +300,21 @@ def test_download_backtesting_testdata(ticker_history, mocker) -> None:
file2 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'STORJ_BTC-5m.json') file2 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'STORJ_BTC-5m.json')
_backup_file(file2) _backup_file(file2)
download_backtesting_testdata(None, pair="STORJ/BTC", tick_interval='5m') download_backtesting_testdata(None, exchange, pair="STORJ/BTC", tick_interval='5m')
assert os.path.isfile(file2) is True assert os.path.isfile(file2) is True
_clean_test_file(file2) _clean_test_file(file2)
def test_download_backtesting_testdata2(mocker) -> None: def test_download_backtesting_testdata2(mocker, default_conf) -> None:
tick = [ tick = [
[1509836520000, 0.00162008, 0.00162008, 0.00162008, 0.00162008, 108.14853839], [1509836520000, 0.00162008, 0.00162008, 0.00162008, 0.00162008, 108.14853839],
[1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199] [1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199]
] ]
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None) json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=tick)
exchange = get_patched_exchange(mocker, default_conf)
download_backtesting_testdata(None, pair="UNITTEST/BTC", tick_interval='1m') download_backtesting_testdata(None, exchange, pair="UNITTEST/BTC", tick_interval='1m')
download_backtesting_testdata(None, pair="UNITTEST/BTC", tick_interval='3m') download_backtesting_testdata(None, exchange, pair="UNITTEST/BTC", tick_interval='3m')
assert json_dump_mock.call_count == 2 assert json_dump_mock.call_count == 2
@ -326,8 +330,10 @@ def test_load_tickerdata_file() -> None:
def test_init(default_conf, mocker) -> None: def test_init(default_conf, mocker) -> None:
exchange = get_patched_exchange(mocker, default_conf)
assert {} == optimize.load_data( assert {} == optimize.load_data(
'', '',
exchange=exchange,
pairs=[], pairs=[],
refresh_pairs=True, refresh_pairs=True,
ticker_interval=default_conf['ticker_interval'] ticker_interval=default_conf['ticker_interval']

View File

@ -33,7 +33,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -80,7 +80,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -114,7 +114,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -170,7 +170,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -194,7 +194,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
# Update the ticker with a market going up # Update the ticker with a market going up
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker_sell_up get_ticker=ticker_sell_up
) )
@ -209,7 +209,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
# Update the ticker with a market going up # Update the ticker with a market going up
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker_sell_up get_ticker=ticker_sell_up
) )
@ -247,7 +247,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, 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)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -267,7 +267,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
trade.update(limit_buy_order) trade.update(limit_buy_order)
# Update the ticker with a market going up # Update the ticker with a market going up
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker_sell_up, get_ticker=ticker_sell_up,
get_fee=fee get_fee=fee
@ -319,7 +319,7 @@ def test_rpc_balance_handle(default_conf, mocker):
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_balances=MagicMock(return_value=mock_balance) get_balances=MagicMock(return_value=mock_balance)
) )
@ -347,7 +347,7 @@ def test_rpc_start(mocker, default_conf) -> None:
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=MagicMock() get_ticker=MagicMock()
) )
@ -373,7 +373,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=MagicMock() get_ticker=MagicMock()
) )
@ -401,7 +401,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
cancel_order=cancel_order_mock, cancel_order=cancel_order_mock,
@ -447,7 +447,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
trade = Trade.query.filter(Trade.id == '1').first() trade = Trade.query.filter(Trade.id == '1').first()
filled_amount = trade.amount / 2 filled_amount = trade.amount / 2
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_order', 'freqtrade.exchange.Exchange.get_order',
return_value={ return_value={
'status': 'open', 'status': 'open',
'type': 'limit', 'type': 'limit',
@ -466,7 +466,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
amount = trade.amount amount = trade.amount
# make an limit-buy open trade, if there is no 'filled', don't sell it # make an limit-buy open trade, if there is no 'filled', don't sell it
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_order', 'freqtrade.exchange.Exchange.get_order',
return_value={ return_value={
'status': 'open', 'status': 'open',
'type': 'limit', 'type': 'limit',
@ -482,7 +482,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
freqtradebot.create_trade() freqtradebot.create_trade()
# make an limit-sell open trade # make an limit-sell open trade
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_order', 'freqtrade.exchange.Exchange.get_order',
return_value={ return_value={
'status': 'open', 'status': 'open',
'type': 'limit', 'type': 'limit',
@ -503,7 +503,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
get_ticker=ticker, get_ticker=ticker,
@ -542,7 +542,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_balances=MagicMock(return_value=ticker), get_balances=MagicMock(return_value=ticker),
get_ticker=ticker, get_ticker=ticker,

View File

@ -20,7 +20,7 @@ from freqtrade.persistence import Trade
from freqtrade.rpc.telegram import Telegram from freqtrade.rpc.telegram import Telegram
from freqtrade.rpc.telegram import authorized_only from freqtrade.rpc.telegram import authorized_only
from freqtrade.state import State from freqtrade.state import State
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has from freqtrade.tests.conftest import get_patched_freqtradebot, patch_exchange, log_has
from freqtrade.tests.test_freqtradebot import patch_get_signal, patch_coinmarketcap from freqtrade.tests.test_freqtradebot import patch_get_signal, patch_coinmarketcap
@ -100,7 +100,7 @@ def test_authorized_only(default_conf, mocker, caplog) -> None:
""" """
patch_get_signal(mocker, (True, False)) patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) patch_exchange(mocker, None)
chat = Chat(0, 0) chat = Chat(0, 0)
update = Update(randint(1, 100)) update = Update(randint(1, 100))
@ -131,8 +131,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
""" """
patch_get_signal(mocker, (True, False)) patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) patch_exchange(mocker, None)
chat = Chat(0xdeadbeef, 0) chat = Chat(0xdeadbeef, 0)
update = Update(randint(1, 100)) update = Update(randint(1, 100))
update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat) update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
@ -162,7 +161,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
""" """
patch_get_signal(mocker, (True, False)) patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) patch_exchange(mocker)
update = Update(randint(1, 100)) update = Update(randint(1, 100))
update.message = Message(randint(1, 100), 0, datetime.utcnow(), Chat(0, 0)) update.message = Message(randint(1, 100), 0, datetime.utcnow(), Chat(0, 0))
@ -198,7 +197,7 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
patch_get_signal(mocker, (True, False)) patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_pair_detail_url=MagicMock(), get_pair_detail_url=MagicMock(),
@ -239,7 +238,7 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
patch_get_signal(mocker, (True, False)) patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -286,7 +285,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
patch_get_signal(mocker, (True, False)) patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.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'}),
@ -344,7 +343,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
return_value=15000.0 return_value=15000.0
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -414,7 +413,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
patch_get_signal(mocker, (True, False)) patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker get_ticker=ticker
) )
@ -454,7 +453,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -489,7 +488,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
msg_mock.reset_mock() msg_mock.reset_mock()
# Update the ticker with a market going up # Update the ticker with a market going up
mocker.patch('freqtrade.freqtradebot.exchange.get_ticker', ticker_sell_up) mocker.patch('freqtrade.exchange.Exchange.get_ticker', ticker_sell_up)
trade.update(limit_sell_order) trade.update(limit_sell_order)
trade.close_date = datetime.utcnow() trade.close_date = datetime.utcnow()
@ -554,9 +553,8 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
patch_get_signal(mocker, (True, False)) patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=mock_balance)
mocker.patch('freqtrade.freqtradebot.exchange.get_balances', return_value=mock_balance) mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker)
mocker.patch('freqtrade.freqtradebot.exchange.get_ticker', side_effect=mock_ticker)
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -565,7 +563,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
_send_msg=msg_mock _send_msg=msg_mock
) )
freqtradebot = FreqtradeBot(default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._balance(bot=MagicMock(), update=update) telegram._balance(bot=MagicMock(), update=update)
@ -584,9 +582,7 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None:
Test _balance() method when the Exchange platform returns nothing Test _balance() method when the Exchange platform returns nothing
""" """
patch_get_signal(mocker, (True, False)) patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
mocker.patch('freqtrade.freqtradebot.exchange.get_balances', return_value={})
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -595,7 +591,7 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None:
_send_msg=msg_mock _send_msg=msg_mock
) )
freqtradebot = FreqtradeBot(default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._balance(bot=MagicMock(), update=update) telegram._balance(bot=MagicMock(), update=update)
@ -608,17 +604,14 @@ def test_start_handle(default_conf, update, mocker) -> None:
""" """
Test _start() method Test _start() method
""" """
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -632,17 +625,14 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None:
""" """
Test _start() method Test _start() method
""" """
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -658,16 +648,14 @@ def test_stop_handle(default_conf, update, mocker) -> None:
Test _stop() method Test _stop() method
""" """
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -683,16 +671,14 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
Test _stop() method Test _stop() method
""" """
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -706,16 +692,14 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
def test_reload_conf_handle(default_conf, update, mocker) -> None: def test_reload_conf_handle(default_conf, update, mocker) -> None:
""" Test _reload_conf() method """ """ Test _reload_conf() method """
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -737,7 +721,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -754,7 +738,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
assert trade assert trade
# Increase the price and sell it # Increase the price and sell it
mocker.patch('freqtrade.freqtradebot.exchange.get_ticker', ticker_sell_up) mocker.patch('freqtrade.exchange.Exchange.get_ticker', ticker_sell_up)
update.message.text = '/forcesell 1' update.message.text = '/forcesell 1'
telegram._forcesell(bot=MagicMock(), update=update) telegram._forcesell(bot=MagicMock(), update=update)
@ -779,7 +763,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -794,7 +778,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
# Decrease the price and sell it # Decrease the price and sell it
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker_sell_down get_ticker=ticker_sell_down
) )
@ -823,9 +807,9 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch('freqtrade.exchange.get_pair_detail_url', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.get_pair_detail_url', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -863,7 +847,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
@ -906,7 +890,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
_send_msg=msg_mock _send_msg=msg_mock
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -947,7 +931,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
@ -971,13 +955,13 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
_send_msg=msg_mock _send_msg=msg_mock
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.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 get_markets=markets
) )
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
@ -1007,14 +991,14 @@ def test_help_handle(default_conf, update, mocker) -> None:
Test _help() method Test _help() method
""" """
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
freqtradebot = FreqtradeBot(default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._help(bot=MagicMock(), update=update) telegram._help(bot=MagicMock(), update=update)
@ -1027,14 +1011,13 @@ def test_version_handle(default_conf, update, mocker) -> None:
Test _version() method Test _version() method
""" """
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram', 'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(), _init=MagicMock(),
_send_msg=msg_mock _send_msg=msg_mock
) )
freqtradebot = FreqtradeBot(default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._version(bot=MagicMock(), update=update) telegram._version(bot=MagicMock(), update=update)
@ -1047,11 +1030,10 @@ def test_send_msg(default_conf, mocker) -> None:
Test send_msg() method Test send_msg() method
""" """
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
bot = MagicMock() bot = MagicMock()
freqtradebot = FreqtradeBot(conf) freqtradebot = get_patched_freqtradebot(mocker, conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._config['telegram']['enabled'] = True telegram._config['telegram']['enabled'] = True
@ -1064,12 +1046,11 @@ def test_send_msg_network_error(default_conf, mocker, caplog) -> None:
Test send_msg() method Test send_msg() method
""" """
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
bot = MagicMock() bot = MagicMock()
bot.send_message = MagicMock(side_effect=NetworkError('Oh snap')) bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
freqtradebot = FreqtradeBot(conf) freqtradebot = get_patched_freqtradebot(mocker, conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._config['telegram']['enabled'] = True telegram._config['telegram']['enabled'] = True

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,8 +45,7 @@ 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)

View File

@ -32,7 +32,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets):
freqtradebot = tt.get_patched_freqtradebot(mocker, conf) freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets) mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
refreshedwhitelist = freqtradebot._refresh_whitelist( refreshedwhitelist = freqtradebot._refresh_whitelist(
conf['exchange']['pair_whitelist'] + ['XXX/BTC'] conf['exchange']['pair_whitelist'] + ['XXX/BTC']
) )
@ -46,7 +46,7 @@ def test_refresh_whitelist(mocker, markets):
conf = whitelist_conf() conf = whitelist_conf()
freqtradebot = tt.get_patched_freqtradebot(mocker, conf) freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets) mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist']) refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist'])
# List ordered by BaseVolume # List ordered by BaseVolume
@ -59,7 +59,7 @@ def test_refresh_whitelist_dynamic(mocker, markets, tickers):
conf = whitelist_conf() conf = whitelist_conf()
freqtradebot = tt.get_patched_freqtradebot(mocker, conf) freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
get_markets=markets, get_markets=markets,
get_tickers=tickers, get_tickers=tickers,
exchange_has=MagicMock(return_value=True) exchange_has=MagicMock(return_value=True)
@ -78,7 +78,7 @@ def test_refresh_whitelist_dynamic(mocker, markets, tickers):
def test_refresh_whitelist_dynamic_empty(mocker, markets_empty): def test_refresh_whitelist_dynamic_empty(mocker, markets_empty):
conf = whitelist_conf() conf = whitelist_conf()
freqtradebot = tt.get_patched_freqtradebot(mocker, conf) freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets_empty) mocker.patch('freqtrade.exchange.Exchange.get_markets', markets_empty)
# argument: use the whitelist dynamically by exchange-volume # argument: use the whitelist dynamically by exchange-volume
whitelist = [] whitelist = []

View File

@ -14,7 +14,7 @@ from pandas import DataFrame
from freqtrade.analyze import Analyze, SignalType from freqtrade.analyze import Analyze, SignalType
from freqtrade.optimize.__init__ import load_tickerdata_file from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.arguments import TimeRange from freqtrade.arguments import TimeRange
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has, get_patched_exchange
# Avoid to reinit the same object again and again # Avoid to reinit the same object again and again
_ANALYZE = Analyze({'strategy': 'DefaultStrategy'}) _ANALYZE = Analyze({'strategy': 'DefaultStrategy'})
@ -66,16 +66,16 @@ def test_populates_sell_trend(result):
assert 'sell' in dataframe.columns assert 'sell' in dataframe.columns
def test_returns_latest_buy_signal(mocker): def test_returns_latest_buy_signal(mocker, default_conf):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock())
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.analyze.Analyze', 'freqtrade.analyze.Analyze',
analyze_ticker=MagicMock( analyze_ticker=MagicMock(
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}]) return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
) )
) )
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (True, False) assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (True, False)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.analyze.Analyze', 'freqtrade.analyze.Analyze',
@ -83,11 +83,12 @@ def test_returns_latest_buy_signal(mocker):
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}]) return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
) )
) )
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, True) assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (False, True)
def test_returns_latest_sell_signal(mocker): def test_returns_latest_sell_signal(mocker, default_conf):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock())
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.analyze.Analyze', 'freqtrade.analyze.Analyze',
analyze_ticker=MagicMock( analyze_ticker=MagicMock(
@ -95,7 +96,7 @@ def test_returns_latest_sell_signal(mocker):
) )
) )
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, True) assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (False, True)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.analyze.Analyze', 'freqtrade.analyze.Analyze',
@ -103,45 +104,49 @@ def test_returns_latest_sell_signal(mocker):
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}]) return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
) )
) )
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (True, False) assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (True, False)
def test_get_signal_empty(default_conf, mocker, caplog): def test_get_signal_empty(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=None)
assert (False, False) == _ANALYZE.get_signal('foo', default_conf['ticker_interval']) exchange = get_patched_exchange(mocker, default_conf)
assert (False, False) == _ANALYZE.get_signal(exchange, 'foo', default_conf['ticker_interval'])
assert log_has('Empty ticker history for pair foo', caplog.record_tuples) assert log_has('Empty ticker history for pair foo', caplog.record_tuples)
def test_get_signal_exception_valueerror(default_conf, mocker, caplog): def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1)
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.analyze.Analyze', 'freqtrade.analyze.Analyze',
analyze_ticker=MagicMock( analyze_ticker=MagicMock(
side_effect=ValueError('xyz') side_effect=ValueError('xyz')
) )
) )
assert (False, False) == _ANALYZE.get_signal('foo', default_conf['ticker_interval']) assert (False, False) == _ANALYZE.get_signal(exchange, 'foo', default_conf['ticker_interval'])
assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples) assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples)
def test_get_signal_empty_dataframe(default_conf, mocker, caplog): def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1)
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.analyze.Analyze', 'freqtrade.analyze.Analyze',
analyze_ticker=MagicMock( analyze_ticker=MagicMock(
return_value=DataFrame([]) return_value=DataFrame([])
) )
) )
assert (False, False) == _ANALYZE.get_signal('xyz', default_conf['ticker_interval']) assert (False, False) == _ANALYZE.get_signal(exchange, 'xyz', default_conf['ticker_interval'])
assert log_has('Empty dataframe for pair xyz', caplog.record_tuples) assert log_has('Empty dataframe for pair xyz', caplog.record_tuples)
def test_get_signal_old_dataframe(default_conf, mocker, caplog): def test_get_signal_old_dataframe(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=1)
exchange = get_patched_exchange(mocker, default_conf)
# FIX: The get_signal function has hardcoded 10, which we must inturn hardcode # FIX: The get_signal function has hardcoded 10, which we must inturn hardcode
oldtime = arrow.utcnow() - datetime.timedelta(minutes=11) oldtime = arrow.utcnow() - datetime.timedelta(minutes=11)
ticks = DataFrame([{'buy': 1, 'date': oldtime}]) ticks = DataFrame([{'buy': 1, 'date': oldtime}])
@ -151,15 +156,16 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog):
return_value=DataFrame(ticks) return_value=DataFrame(ticks)
) )
) )
assert (False, False) == _ANALYZE.get_signal('xyz', default_conf['ticker_interval']) assert (False, False) == _ANALYZE.get_signal(exchange, 'xyz', default_conf['ticker_interval'])
assert log_has( assert log_has(
'Outdated history for pair xyz. Last tick is 11 minutes old', 'Outdated history for pair xyz. Last tick is 11 minutes old',
caplog.record_tuples caplog.record_tuples
) )
def test_get_signal_handles_exceptions(mocker): def test_get_signal_handles_exceptions(mocker, default_conf):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock())
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.analyze.Analyze', 'freqtrade.analyze.Analyze',
analyze_ticker=MagicMock( analyze_ticker=MagicMock(
@ -167,7 +173,7 @@ def test_get_signal_handles_exceptions(mocker):
) )
) )
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, False) assert _ANALYZE.get_signal(exchange, 'ETH/BTC', '5m') == (False, False)
def test_parse_ticker_dataframe(ticker_history): def test_parse_ticker_dataframe(ticker_history):

View File

@ -13,6 +13,7 @@ from jsonschema import ValidationError
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration from freqtrade.configuration import Configuration
from freqtrade.constants import DEFAULT_DB_PROD_URL, DEFAULT_DB_DRYRUN_URL
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has
from freqtrade import OperationalException from freqtrade import OperationalException
@ -152,6 +153,43 @@ def test_load_config_with_params(default_conf, mocker) -> None:
assert validated_conf.get('strategy_path') == '/some/path' assert validated_conf.get('strategy_path') == '/some/path'
assert validated_conf.get('db_url') == 'sqlite:///someurl' assert validated_conf.get('db_url') == 'sqlite:///someurl'
conf = default_conf.copy()
conf["dry_run"] = False
del conf["db_url"]
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(conf)
))
arglist = [
'--dynamic-whitelist', '10',
'--strategy', 'TestStrategy',
'--strategy-path', '/some/path'
]
args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get('db_url') == DEFAULT_DB_PROD_URL
# Test dry=run with ProdURL
conf = default_conf.copy()
conf["dry_run"] = True
conf["db_url"] = DEFAULT_DB_PROD_URL
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(conf)
))
arglist = [
'--dynamic-whitelist', '10',
'--strategy', 'TestStrategy',
'--strategy-path', '/some/path'
]
args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get('db_url') == DEFAULT_DB_DRYRUN_URL
def test_load_custom_strategy(default_conf, mocker) -> None: def test_load_custom_strategy(default_conf, mocker) -> None:
""" """

View File

@ -40,7 +40,8 @@ def test_pair_convertion_object():
assert pair_convertion.price == 30000.123 assert pair_convertion.price == 30000.123
def test_fiat_convert_is_supported(): def test_fiat_convert_is_supported(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
assert fiat_convert._is_supported_fiat(fiat='USD') is True assert fiat_convert._is_supported_fiat(fiat='USD') is True
assert fiat_convert._is_supported_fiat(fiat='usd') is True assert fiat_convert._is_supported_fiat(fiat='usd') is True
@ -48,7 +49,9 @@ def test_fiat_convert_is_supported():
assert fiat_convert._is_supported_fiat(fiat='ABC') is False assert fiat_convert._is_supported_fiat(fiat='ABC') is False
def test_fiat_convert_add_pair(): def test_fiat_convert_add_pair(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
pair_len = len(fiat_convert._pairs) pair_len = len(fiat_convert._pairs)
@ -70,11 +73,8 @@ def test_fiat_convert_add_pair():
def test_fiat_convert_find_price(mocker): def test_fiat_convert_find_price(mocker):
api_mock = MagicMock(return_value={ patch_coinmarketcap(mocker)
'price_usd': 12345.0,
'price_eur': 13000.2
})
mocker.patch('freqtrade.fiat_convert.Market.ticker', api_mock)
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'): with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
@ -92,17 +92,15 @@ def test_fiat_convert_find_price(mocker):
def test_fiat_convert_unsupported_crypto(mocker, caplog): def test_fiat_convert_unsupported_crypto(mocker, caplog):
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[]) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0 assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples) assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples)
def test_fiat_convert_get_price(mocker): def test_fiat_convert_get_price(mocker):
api_mock = MagicMock(return_value={ patch_coinmarketcap(mocker)
'price_usd': 28000.0,
'price_eur': 15000.0
})
mocker.patch('freqtrade.fiat_convert.Market.ticker', api_mock)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=28000.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=28000.0)
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
@ -172,8 +170,9 @@ def test_fiat_init_network_exception(mocker):
assert length_cryptomap == 0 assert length_cryptomap == 0
def test_fiat_convert_without_network(): def test_fiat_convert_without_network(mocker):
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
@ -186,6 +185,7 @@ def test_fiat_convert_without_network():
def test_convert_amount(mocker): def test_convert_amount(mocker):
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()

View File

@ -18,7 +18,7 @@ from freqtrade import constants, DependencyException, OperationalException, Temp
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
from freqtrade.tests.conftest import log_has, patch_coinmarketcap from freqtrade.tests.conftest import log_has, patch_coinmarketcap, patch_exchange
# Functions for recurrent object patching # Functions for recurrent object patching
@ -32,7 +32,7 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock()) mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock()) mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) patch_exchange(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
return FreqtradeBot(config) return FreqtradeBot(config)
@ -47,7 +47,7 @@ def patch_get_signal(mocker, value=(True, False)) -> None:
""" """
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.Analyze.get_signal', 'freqtrade.freqtradebot.Analyze.get_signal',
side_effect=lambda s, t: value side_effect=lambda e, s, t: value
) )
@ -187,9 +187,9 @@ def test_gen_pair_whitelist(mocker, default_conf, tickers) -> None:
Test _gen_pair_whitelist() method Test _gen_pair_whitelist() method
""" """
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.exchange.get_tickers', tickers) mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.freqtradebot.exchange.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) # mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
# Test to retrieved BTC sorted on quoteVolume (default) # Test to retrieved BTC sorted on quoteVolume (default)
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC') whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC')
@ -223,7 +223,7 @@ def test_get_trade_stake_amount(default_conf, ticker, limit_buy_order, fee, mock
patch_RPCManager(mocker) patch_RPCManager(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2) get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2)
) )
@ -244,7 +244,7 @@ def test_get_trade_stake_amount_no_stake_amount(default_conf,
""" """
patch_RPCManager(mocker) patch_RPCManager(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5) get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5)
) )
@ -269,7 +269,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -313,13 +313,13 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
""" """
patch_RPCManager(mocker) patch_RPCManager(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.freqtradebot.Analyze.get_stoploss', MagicMock(return_value=-0.05)) mocker.patch('freqtrade.freqtradebot.Analyze.get_stoploss', MagicMock(return_value=-0.05))
freqtrade = FreqtradeBot(default_conf) freqtrade = FreqtradeBot(default_conf)
# no pair found # no pair found
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_markets', 'freqtrade.exchange.Exchange.get_markets',
MagicMock(return_value=[{ MagicMock(return_value=[{
'symbol': 'ETH/BTC' 'symbol': 'ETH/BTC'
}]) }])
@ -329,7 +329,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# no 'limits' section # no 'limits' section
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_markets', 'freqtrade.exchange.Exchange.get_markets',
MagicMock(return_value=[{ MagicMock(return_value=[{
'symbol': 'ETH/BTC' 'symbol': 'ETH/BTC'
}]) }])
@ -339,7 +339,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# empty 'limits' section # empty 'limits' section
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_markets', 'freqtrade.exchange.Exchange.get_markets',
MagicMock(return_value=[{ MagicMock(return_value=[{
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
'limits': {} 'limits': {}
@ -350,7 +350,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# empty 'cost'/'amount' section # empty 'cost'/'amount' section
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_markets', 'freqtrade.exchange.Exchange.get_markets',
MagicMock(return_value=[{ MagicMock(return_value=[{
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
'limits': { 'limits': {
@ -364,7 +364,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# min cost is set # min cost is set
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_markets', 'freqtrade.exchange.Exchange.get_markets',
MagicMock(return_value=[{ MagicMock(return_value=[{
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
'limits': { 'limits': {
@ -378,7 +378,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# min amount is set # min amount is set
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_markets', 'freqtrade.exchange.Exchange.get_markets',
MagicMock(return_value=[{ MagicMock(return_value=[{
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
'limits': { 'limits': {
@ -392,7 +392,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# min amount and cost are set (cost is minimal) # min amount and cost are set (cost is minimal)
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_markets', 'freqtrade.exchange.Exchange.get_markets',
MagicMock(return_value=[{ MagicMock(return_value=[{
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
'limits': { 'limits': {
@ -406,7 +406,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# min amount and cost are set (amount is minial) # min amount and cost are set (amount is minial)
mocker.patch( mocker.patch(
'freqtrade.freqtradebot.exchange.get_markets', 'freqtrade.exchange.Exchange.get_markets',
MagicMock(return_value=[{ MagicMock(return_value=[{
'symbol': 'ETH/BTC', 'symbol': 'ETH/BTC',
'limits': { 'limits': {
@ -427,7 +427,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocke
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -465,7 +465,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -489,7 +489,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order,
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']}) buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=buy_mock, buy=buy_mock,
@ -516,7 +516,7 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']}) buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=buy_mock, buy=buy_mock,
@ -541,7 +541,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -567,7 +567,7 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, marke
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -595,7 +595,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -625,7 +625,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker_history=MagicMock(return_value=20), get_ticker_history=MagicMock(return_value=20),
get_balance=MagicMock(return_value=20), get_balance=MagicMock(return_value=20),
@ -650,7 +650,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_markets=markets, get_markets=markets,
@ -691,7 +691,7 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_markets=markets, get_markets=markets,
@ -713,7 +713,7 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) ->
msg_mock = patch_RPCManager(mocker) msg_mock = patch_RPCManager(mocker)
patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_markets=markets, get_markets=markets,
@ -737,7 +737,7 @@ def test_process_trade_handling(
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_markets=markets, get_markets=markets,
@ -758,29 +758,32 @@ def test_process_trade_handling(
assert result is False assert result is False
def test_balance_fully_ask_side(mocker) -> None: def test_balance_fully_ask_side(mocker, default_conf) -> None:
""" """
Test get_target_bid() method Test get_target_bid() method
""" """
freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'ask_last_balance': 0.0}}) default_conf['bid_strategy']['ask_last_balance'] = 0.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 20 assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 20
def test_balance_fully_last_side(mocker) -> None: def test_balance_fully_last_side(mocker, default_conf) -> None:
""" """
Test get_target_bid() method Test get_target_bid() method
""" """
freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'ask_last_balance': 1.0}}) default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 10 assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 10
def test_balance_bigger_last_ask(mocker) -> None: def test_balance_bigger_last_ask(mocker, default_conf) -> None:
""" """
Test get_target_bid() method Test get_target_bid() method
""" """
freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'ask_last_balance': 1.0}}) default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 5, 'last': 10}) == 5 assert freqtrade.get_target_bid({'ask': 5, 'last': 10}) == 5
@ -819,8 +822,8 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True)) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
mocker.patch('freqtrade.freqtradebot.exchange.get_order', return_value=limit_buy_order) mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
mocker.patch('freqtrade.freqtradebot.exchange.get_trades_for_order', return_value=[]) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
return_value=limit_buy_order['amount']) return_value=limit_buy_order['amount'])
@ -853,7 +856,7 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf,
Test the exceptions in process_maybe_execute_sell() Test the exceptions in process_maybe_execute_sell()
""" """
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.exchange.get_order', return_value=limit_buy_order) mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
trade = MagicMock() trade = MagicMock()
trade.open_order_id = '123' trade.open_order_id = '123'
@ -884,7 +887,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
'bid': 0.00001172, 'bid': 0.00001172,
@ -935,7 +938,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order,
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -995,7 +998,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -1033,7 +1036,7 @@ def test_handle_trade_experimental(
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -1065,7 +1068,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@ -1096,7 +1099,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fe
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_order=MagicMock(return_value=limit_buy_order_old), get_order=MagicMock(return_value=limit_buy_order_old),
@ -1137,7 +1140,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_order=MagicMock(return_value=limit_sell_order_old), get_order=MagicMock(return_value=limit_sell_order_old),
@ -1177,7 +1180,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_order=MagicMock(return_value=limit_buy_order_old_partial), get_order=MagicMock(return_value=limit_buy_order_old_partial),
@ -1225,7 +1228,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
handle_timedout_limit_sell=MagicMock(), handle_timedout_limit_sell=MagicMock(),
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_order=MagicMock(side_effect=requests.exceptions.RequestException('Oh snap')), get_order=MagicMock(side_effect=requests.exceptions.RequestException('Oh snap')),
@ -1265,7 +1268,7 @@ def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
cancel_order=cancel_order_mock cancel_order=cancel_order_mock
) )
@ -1291,7 +1294,7 @@ def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
cancel_order=cancel_order_mock cancel_order=cancel_order_mock
) )
@ -1317,7 +1320,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc
rpc_mock = patch_RPCManager(mocker) rpc_mock = patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -1334,7 +1337,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc
# Increase the price and sell it # Increase the price and sell it
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker_sell_up get_ticker=ticker_sell_up
) )
@ -1360,7 +1363,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets,
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -1376,7 +1379,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets,
# Decrease the price and sell it # Decrease the price and sell it
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker_sell_down get_ticker=ticker_sell_down
) )
@ -1401,7 +1404,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
rpc_mock = patch_RPCManager(mocker) rpc_mock = patch_RPCManager(mocker)
patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -1417,7 +1420,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
# Increase the price and sell it # Increase the price and sell it
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker_sell_up get_ticker=ticker_sell_up
) )
@ -1443,7 +1446,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
rpc_mock = patch_RPCManager(mocker) rpc_mock = patch_RPCManager(mocker)
patch_coinmarketcap(mocker, value={'price_usd': 12345.0}) patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
get_fee=fee, get_fee=fee,
@ -1459,7 +1462,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
# Decrease the price and sell it # Decrease the price and sell it
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker_sell_down get_ticker=ticker_sell_down
) )
@ -1484,7 +1487,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
'bid': 0.00002172, 'bid': 0.00002172,
@ -1519,7 +1522,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order,
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.exchange', 'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={ get_ticker=MagicMock(return_value={
'bid': 0.00002172, 'bid': 0.00002172,
@ -1551,9 +1554,9 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market
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.freqtradebot.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.00000172,
@ -1587,16 +1590,15 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.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,
get_markets=markets
) )
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
@ -1614,17 +1616,96 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke
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
""" """
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
amount = sum(x['amount'] for x in trades_for_order) amount = sum(x['amount'] for x in trades_for_order)
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',
@ -1646,12 +1727,12 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker):
Test get_real_amount - fee in quote currency Test get_real_amount - fee in quote currency
""" """
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=[]) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
amount = buy_order_fee['amount'] amount = buy_order_fee['amount']
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',
@ -1677,8 +1758,8 @@ def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, mo
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
amount = sum(x['amount'] for x in trades_for_order) amount = sum(x['amount'] for x in trades_for_order)
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',
@ -1703,8 +1784,8 @@ def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, mock
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
amount = sum(x['amount'] for x in trades_for_order) amount = sum(x['amount'] for x in trades_for_order)
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',
@ -1726,8 +1807,8 @@ def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, c
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order2) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order2)
amount = float(sum(x['amount'] for x in trades_for_order2)) amount = float(sum(x['amount'] for x in trades_for_order2))
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',
@ -1754,8 +1835,9 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=[trades_for_order]) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order',
return_value=[trades_for_order])
amount = float(sum(x['amount'] for x in trades_for_order)) amount = float(sum(x['amount'] for x in trades_for_order))
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',
@ -1782,8 +1864,8 @@ def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=[]) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
amount = float(sum(x['amount'] for x in trades_for_order)) amount = float(sum(x['amount'] for x in trades_for_order))
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',
@ -1807,8 +1889,8 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee,
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
mocker.patch('freqtrade.exchange.get_trades_for_order', return_value=trades_for_order) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
amount = sum(x['amount'] for x in trades_for_order) amount = sum(x['amount'] for x in trades_for_order)
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',
@ -1829,7 +1911,7 @@ def test_get_real_amount_open_trade(default_conf, mocker):
patch_get_signal(mocker) patch_get_signal(mocker)
patch_RPCManager(mocker) patch_RPCManager(mocker)
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
amount = 12345 amount = 12345
trade = Trade( trade = Trade(
pair='LTC/ETH', pair='LTC/ETH',

View File

@ -13,7 +13,7 @@ from freqtrade.arguments import Arguments
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.main import main, set_loggers, reconfigure from freqtrade.main import main, set_loggers, reconfigure
from freqtrade.state import State from freqtrade.state import State
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has, patch_exchange
def test_parse_args_backtesting(mocker) -> None: def test_parse_args_backtesting(mocker) -> None:
@ -70,6 +70,7 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
Test main() function Test main() function
In this test we are skipping the while True loop by throwing an exception. In this test we are skipping the while True loop by throwing an exception.
""" """
patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(), _init_modules=MagicMock(),
@ -97,6 +98,7 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
Test main() function Test main() function
In this test we are skipping the while True loop by throwing an exception. In this test we are skipping the while True loop by throwing an exception.
""" """
patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(), _init_modules=MagicMock(),
@ -124,6 +126,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
Test main() function Test main() function
In this test we are skipping the while True loop by throwing an exception. In this test we are skipping the while True loop by throwing an exception.
""" """
patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(), _init_modules=MagicMock(),
@ -151,6 +154,7 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None:
Test main() function Test main() function
In this test we are skipping the while True loop by throwing an exception. In this test we are skipping the while True loop by throwing an exception.
""" """
patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(), _init_modules=MagicMock(),
@ -178,6 +182,7 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None:
def test_reconfigure(mocker, default_conf) -> None: def test_reconfigure(mocker, default_conf) -> None:
""" Test recreate() function """ """ Test recreate() function """
patch_exchange(mocker)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot', 'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(), _init_modules=MagicMock(),

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.201 ccxt==1.14.256
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
@ -12,7 +12,7 @@ scipy==1.1.0
jsonschema==2.6.0 jsonschema==2.6.0
numpy==1.14.5 numpy==1.14.5
TA-Lib==0.4.17 TA-Lib==0.4.17
pytest==3.6.1 pytest==3.6.2
pytest-mock==1.10.0 pytest-mock==1.10.0
pytest-cov==2.5.1 pytest-cov==2.5.1
hyperopt==0.1 hyperopt==0.1
@ -22,4 +22,4 @@ 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

View File

@ -6,7 +6,8 @@ import sys
import os import os
import arrow import arrow
from freqtrade import (exchange, arguments, misc) from freqtrade import (arguments, misc)
from freqtrade.exchange import Exchange
DEFAULT_DL_PATH = 'user_data/data' DEFAULT_DL_PATH = 'user_data/data'
@ -39,16 +40,21 @@ if args.days:
print(f'About to download pairs: {PAIRS} to {dl_path}') print(f'About to download pairs: {PAIRS} to {dl_path}')
# Init exchange # Init exchange
exchange._API = exchange.init_ccxt({'key': '', exchange = Exchange({'key': '',
'secret': '', 'secret': '',
'name': args.exchange}) 'stake_currency': '',
'dry_run': True,
'exchange': {
'name': args.exchange,
'pair_whitelist': []
}
})
pairs_not_available = [] pairs_not_available = []
# Make sure API markets is initialized
exchange._API.load_markets()
for pair in PAIRS: for pair in PAIRS:
if pair not in exchange._API.markets: if pair not in exchange._api.markets:
pairs_not_available.append(pair) pairs_not_available.append(pair)
print(f"skipping pair {pair}") print(f"skipping pair {pair}")
continue continue

View File

@ -35,10 +35,10 @@ from plotly import tools
from plotly.offline import plot from plotly.offline import plot
import freqtrade.optimize as optimize import freqtrade.optimize as optimize
from freqtrade import exchange
from freqtrade import persistence from freqtrade import persistence
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
from freqtrade.exchange import Exchange
from freqtrade.optimize.backtesting import setup_configuration from freqtrade.optimize.backtesting import setup_configuration
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
@ -73,7 +73,7 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
# Load the strategy # Load the strategy
try: try:
analyze = Analyze(_CONF) analyze = Analyze(_CONF)
exchange.init(_CONF) exchange = Exchange(_CONF)
except AttributeError: except AttributeError:
logger.critical( logger.critical(
'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"', 'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"',