merged latest development branch
This commit is contained in:
commit
bf0b1af878
@ -50,6 +50,7 @@ hesitate to read the source code and understand the mechanism of this bot.
|
|||||||
- [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.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)
|
- [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
|
||||||
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
|
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
|
||||||
|
- [Sandbox Testing](https://github.com/freqtrade/freqtrade/blob/develop/docs/sandbox-testing.md)
|
||||||
- [Basic Usage](#basic-usage)
|
- [Basic Usage](#basic-usage)
|
||||||
- [Bot commands](#bot-commands)
|
- [Bot commands](#bot-commands)
|
||||||
- [Telegram RPC commands](#telegram-rpc-commands)
|
- [Telegram RPC commands](#telegram-rpc-commands)
|
||||||
@ -62,6 +63,7 @@ hesitate to read the source code and understand the mechanism of this bot.
|
|||||||
- [Min hardware required](#min-hardware-required)
|
- [Min hardware required](#min-hardware-required)
|
||||||
- [Software requirements](#software-requirements)
|
- [Software requirements](#software-requirements)
|
||||||
|
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot.
|
Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot.
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"ticker_interval": "5m",
|
"ticker_interval": "5m",
|
||||||
"trailing_stop": false,
|
"trailing_stop": false,
|
||||||
"trailing_stop_positive": 0.005,
|
"trailing_stop_positive": 0.005,
|
||||||
|
"trailing_stop_positive_offset": 0.0051,
|
||||||
"minimal_roi": {
|
"minimal_roi": {
|
||||||
"40": 0.0,
|
"40": 0.0,
|
||||||
"30": 0.01,
|
"30": 0.01,
|
||||||
|
@ -39,7 +39,6 @@ A strategy file contains all the information needed to build a good strategy:
|
|||||||
- Sell strategy rules
|
- Sell strategy rules
|
||||||
- Minimal ROI recommended
|
- Minimal ROI recommended
|
||||||
- Stoploss recommended
|
- Stoploss recommended
|
||||||
- Hyperopt parameter
|
|
||||||
|
|
||||||
The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`.
|
The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`.
|
||||||
You can test it with the parameter: `--strategy TestStrategy`
|
You can test it with the parameter: `--strategy TestStrategy`
|
||||||
@ -61,22 +60,22 @@ file as reference.**
|
|||||||
|
|
||||||
### Buy strategy
|
### Buy strategy
|
||||||
|
|
||||||
Edit the method `populate_buy_trend()` into your strategy file to
|
Edit the method `populate_buy_trend()` into your strategy file to update your buy strategy.
|
||||||
update your buy strategy.
|
|
||||||
|
|
||||||
Sample from `user_data/strategies/test_strategy.py`:
|
Sample from `user_data/strategies/test_strategy.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame populated with indicators
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe['adx'] > 30) &
|
(dataframe['adx'] > 30) &
|
||||||
(dataframe['tema'] <= dataframe['blower']) &
|
(dataframe['tema'] <= dataframe['bb_middleband']) &
|
||||||
(dataframe['tema'] > dataframe['tema'].shift(1))
|
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||||
),
|
),
|
||||||
'buy'] = 1
|
'buy'] = 1
|
||||||
@ -87,38 +86,47 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
|||||||
### Sell strategy
|
### Sell strategy
|
||||||
|
|
||||||
Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy.
|
Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy.
|
||||||
|
Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration.
|
||||||
|
|
||||||
Sample from `user_data/strategies/test_strategy.py`:
|
Sample from `user_data/strategies/test_strategy.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame populated with indicators
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe['adx'] > 70) &
|
(dataframe['adx'] > 70) &
|
||||||
(dataframe['tema'] > dataframe['blower']) &
|
(dataframe['tema'] > dataframe['bb_middleband']) &
|
||||||
(dataframe['tema'] < dataframe['tema'].shift(1))
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
),
|
),
|
||||||
'sell'] = 1
|
'sell'] = 1
|
||||||
return dataframe
|
return dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
## Add more Indicator
|
## Add more Indicators
|
||||||
|
|
||||||
As you have seen, buy and sell strategies need indicators. You can add
|
As you have seen, buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
|
||||||
more indicators by extending the list contained in
|
|
||||||
the method `populate_indicators()` from your strategy file.
|
You should only add the indicators used in either `populate_buy_trend()`, `populate_sell_trend()`, or to populate another indicator, otherwise performance may suffer.
|
||||||
|
|
||||||
Sample:
|
Sample:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Adds several different TA indicators to the given DataFrame
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
|
Performance Note: For the best performance be frugal on the number of indicators
|
||||||
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
|
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
"""
|
"""
|
||||||
dataframe['sar'] = ta.SAR(dataframe)
|
dataframe['sar'] = ta.SAR(dataframe)
|
||||||
dataframe['adx'] = ta.ADX(dataframe)
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
@ -149,6 +157,11 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
|||||||
return dataframe
|
return dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Metadata dict
|
||||||
|
|
||||||
|
The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `populate_indicators`) contains additional information.
|
||||||
|
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
|
||||||
|
|
||||||
### Want more indicator examples
|
### Want more indicator examples
|
||||||
|
|
||||||
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
|
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
|
||||||
|
@ -27,6 +27,7 @@ The table below will list all configuration parameters.
|
|||||||
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
|
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
|
||||||
| `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
|
| `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
|
||||||
| `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached.
|
| `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached.
|
||||||
|
| `trailing_stoploss_positve_offset` | 0 | No | Offset on when to apply `trailing_stoploss_positive`. Percentage value which should be positive.
|
||||||
| `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
|
| `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
|
||||||
| `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
|
| `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
|
||||||
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
|
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
|
||||||
|
@ -33,3 +33,4 @@ Pull-request. Do not hesitate to reach us on
|
|||||||
- [Run tests & Check PEP8 compliance](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
|
- [Run tests & Check PEP8 compliance](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
|
||||||
- [FAQ](https://github.com/freqtrade/freqtrade/blob/develop/docs/faq.md)
|
- [FAQ](https://github.com/freqtrade/freqtrade/blob/develop/docs/faq.md)
|
||||||
- [SQL cheatsheet](https://github.com/freqtrade/freqtrade/blob/develop/docs/sql_cheatsheet.md)
|
- [SQL cheatsheet](https://github.com/freqtrade/freqtrade/blob/develop/docs/sql_cheatsheet.md)
|
||||||
|
- [Sandbox Testing](https://github.com/freqtrade/freqtrade/blob/develop/docs/sandbox-testing.md))
|
||||||
|
151
docs/sandbox-testing.md
Normal file
151
docs/sandbox-testing.md
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# Sandbox API testing
|
||||||
|
Where an exchange provides a sandbox for risk-free integration, or end-to-end, testing CCXT provides access to these.
|
||||||
|
|
||||||
|
This document is a *light overview of configuring Freqtrade and GDAX sandbox.
|
||||||
|
This can be useful to developers and trader alike as Freqtrade is quite customisable.
|
||||||
|
|
||||||
|
When testing your API connectivity, make sure to use the following URLs.
|
||||||
|
***Website**
|
||||||
|
https://public.sandbox.gdax.com
|
||||||
|
***REST API**
|
||||||
|
https://api-public.sandbox.gdax.com
|
||||||
|
|
||||||
|
---
|
||||||
|
# Configure a Sandbox account on Gdax
|
||||||
|
Aim of this document section
|
||||||
|
- An sanbox account
|
||||||
|
- create 2FA (needed to create an API)
|
||||||
|
- Add test 50BTC to account
|
||||||
|
- Create :
|
||||||
|
- - API-KEY
|
||||||
|
- - API-Secret
|
||||||
|
- - API Password
|
||||||
|
|
||||||
|
## Acccount
|
||||||
|
|
||||||
|
This link will redirect to the sandbox main page to login / create account dialogues:
|
||||||
|
https://public.sandbox.pro.coinbase.com/orders/
|
||||||
|
|
||||||
|
After registration and Email confimation you wil be redirected into your sanbox account. It is easy to verify you're in sandbox by checking the URL bar.
|
||||||
|
> https://public.sandbox.pro.coinbase.com/
|
||||||
|
|
||||||
|
## Enable 2Fa (a prerequisite to creating sandbox API Keys)
|
||||||
|
From within sand box site select your profile, top right.
|
||||||
|
>Or as a direct link: https://public.sandbox.pro.coinbase.com/profile
|
||||||
|
|
||||||
|
From the menu panel to the left of the screen select
|
||||||
|
> Security: "*View or Update*"
|
||||||
|
|
||||||
|
In the new site select "enable authenticator" as typical google Authenticator.
|
||||||
|
- open Google Authenticator on your phone
|
||||||
|
- scan barcode
|
||||||
|
- enter your generated 2fa
|
||||||
|
|
||||||
|
## Enable API Access
|
||||||
|
From within sandbox select profile>api>create api-keys
|
||||||
|
>or as a direct link: https://public.sandbox.pro.coinbase.com/profile/api
|
||||||
|
|
||||||
|
Click on "create one" and ensure **view** and **trade** are "checked" and sumbit your 2Fa
|
||||||
|
- **Copy and paste the Passphase** into a notepade this will be needed later
|
||||||
|
- **Copy and paste the API Secret** popup into a notepad this will needed later
|
||||||
|
- **Copy and paste the API Key** into a notepad this will needed later
|
||||||
|
|
||||||
|
## Add 50 BTC test funds
|
||||||
|
To add funds, use the web interface deposit and withdraw buttons.
|
||||||
|
|
||||||
|
|
||||||
|
To begin select 'Wallets' from the top menu.
|
||||||
|
> Or as a direct link: https://public.sandbox.pro.coinbase.com/wallets
|
||||||
|
|
||||||
|
- Deposits (bottom left of screen)
|
||||||
|
- - Deposit Funds Bitcoin
|
||||||
|
- - - Coinbase BTC Wallet
|
||||||
|
- - - - Max (50 BTC)
|
||||||
|
- - - - - Deposit
|
||||||
|
|
||||||
|
*This process may be repeated for other currencies, ETH as example*
|
||||||
|
---
|
||||||
|
# Configure Freqtrade to use Gax Sandbox
|
||||||
|
|
||||||
|
The aim of this document section
|
||||||
|
- Enable sandbox URLs in Freqtrade
|
||||||
|
- Configure API
|
||||||
|
- - secret
|
||||||
|
- - key
|
||||||
|
- - passphrase
|
||||||
|
|
||||||
|
## Sandbox URLs
|
||||||
|
Freqtrade makes use of CCXT which in turn provides a list of URLs to Freqtrade.
|
||||||
|
These include `['test']` and `['api']`.
|
||||||
|
- `[Test]` if available will point to an Exchanges sandbox.
|
||||||
|
- `[Api]` normally used, and resolves to live API target on the exchange
|
||||||
|
|
||||||
|
To make use of sandbox / test add "sandbox": true, to your config.json
|
||||||
|
```
|
||||||
|
"exchange": {
|
||||||
|
"name": "gdax",
|
||||||
|
"sandbox": true,
|
||||||
|
"key": "5wowfxemogxeowo;heiohgmd",
|
||||||
|
"secret": "/ZMH1P62rCVmwefewrgcewX8nh4gob+lywxfwfxwwfxwfNsH1ySgvWCUR/w==",
|
||||||
|
"password": "1bkjfkhfhfu6sr",
|
||||||
|
"pair_whitelist": [
|
||||||
|
"BTC/USD"
|
||||||
|
```
|
||||||
|
Also insert your
|
||||||
|
- api-key (noted earlier)
|
||||||
|
- api-secret (noted earlier)
|
||||||
|
- password (the passphrase - noted earlier)
|
||||||
|
|
||||||
|
---
|
||||||
|
## You should now be ready to test your sandbox!
|
||||||
|
Ensure Freqtrade logs show the sandbox URL, and trades made are shown in sandbox.
|
||||||
|
** Typically the BTC/USD has the most activity in sandbox to test against.
|
||||||
|
|
||||||
|
## GDAX - Old Candles problem
|
||||||
|
It is my experience that GDAX sandbox candles may be 20+- minutes out of date. This can cause trades to fail as one of Freqtrades safety checks
|
||||||
|
|
||||||
|
To disable this check, edit:
|
||||||
|
>strategy/interface.py
|
||||||
|
Look for the following section:
|
||||||
|
```
|
||||||
|
# Check if dataframe is out of date
|
||||||
|
signal_date = arrow.get(latest['date'])
|
||||||
|
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
|
||||||
|
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))):
|
||||||
|
logger.warning(
|
||||||
|
'Outdated history for pair %s. Last tick is %s minutes old',
|
||||||
|
pair,
|
||||||
|
(arrow.utcnow() - signal_date).seconds // 60
|
||||||
|
)
|
||||||
|
return False, False
|
||||||
|
```
|
||||||
|
|
||||||
|
You could Hash out the entire check as follows:
|
||||||
|
```
|
||||||
|
# # Check if dataframe is out of date
|
||||||
|
# signal_date = arrow.get(latest['date'])
|
||||||
|
# interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
|
||||||
|
# if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))):
|
||||||
|
# logger.warning(
|
||||||
|
# 'Outdated history for pair %s. Last tick is %s minutes old',
|
||||||
|
# pair,
|
||||||
|
# (arrow.utcnow() - signal_date).seconds // 60
|
||||||
|
# )
|
||||||
|
# return False, False
|
||||||
|
```
|
||||||
|
|
||||||
|
Or inrease the timeout to offer a level of protection/alignment of this test to freqtrade in live.
|
||||||
|
|
||||||
|
As example, to allow an additional 30 minutes. "(interval_minutes * 2 + 5 + 30)"
|
||||||
|
```
|
||||||
|
# Check if dataframe is out of date
|
||||||
|
signal_date = arrow.get(latest['date'])
|
||||||
|
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
|
||||||
|
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5 + 30))):
|
||||||
|
logger.warning(
|
||||||
|
'Outdated history for pair %s. Last tick is %s minutes old',
|
||||||
|
pair,
|
||||||
|
(arrow.utcnow() - signal_date).seconds // 60
|
||||||
|
)
|
||||||
|
return False, False
|
||||||
|
```
|
@ -35,14 +35,17 @@ basically what this means is that your stop loss will be adjusted to be always b
|
|||||||
|
|
||||||
### Custom positive loss
|
### Custom positive loss
|
||||||
|
|
||||||
Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your buy turns positive,
|
Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your profit surpasses a certain percentage,
|
||||||
the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you are in the
|
the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you have 1.1% profit,
|
||||||
black, it will be changed to be only a 1% stop loss
|
it will be changed to be only a 1% stop loss, which trails the green candles until it goes below them.
|
||||||
|
|
||||||
This can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true.
|
Both values can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true.
|
||||||
|
|
||||||
``` json
|
``` json
|
||||||
"trailing_stop_positive": 0.01,
|
"trailing_stop_positive": 0.01,
|
||||||
|
"trailing_stop_positive_offset": 0.011,
|
||||||
```
|
```
|
||||||
|
|
||||||
The 0.01 would translate to a 1% stop loss, once you hit profit.
|
The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit.
|
||||||
|
|
||||||
|
You should also make sure to have this value higher than your minimal ROI, otherwise minimal ROI will apply first and sell your trade.
|
||||||
|
@ -63,6 +63,7 @@ CONF_SCHEMA = {
|
|||||||
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True},
|
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True},
|
||||||
'trailing_stop': {'type': 'boolean'},
|
'trailing_stop': {'type': 'boolean'},
|
||||||
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
||||||
|
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
||||||
'unfilledtimeout': {
|
'unfilledtimeout': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
@ -124,6 +125,7 @@ CONF_SCHEMA = {
|
|||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'name': {'type': 'string'},
|
'name': {'type': 'string'},
|
||||||
|
'sandbox': {'type': 'boolean'},
|
||||||
'key': {'type': 'string'},
|
'key': {'type': 'string'},
|
||||||
'secret': {'type': 'string'},
|
'secret': {'type': 'string'},
|
||||||
'password': {'type': 'string'},
|
'password': {'type': 'string'},
|
||||||
|
@ -4,6 +4,7 @@ import logging
|
|||||||
from random import randint
|
from random import randint
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from math import floor, ceil
|
||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
import arrow
|
import arrow
|
||||||
@ -95,6 +96,8 @@ class Exchange(object):
|
|||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
raise OperationalException(f'Exchange {name} is not supported')
|
raise OperationalException(f'Exchange {name} is not supported')
|
||||||
|
|
||||||
|
self.set_sandbox(api, exchange_config, name)
|
||||||
|
|
||||||
return api
|
return api
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -107,6 +110,16 @@ class Exchange(object):
|
|||||||
"""exchange ccxt id"""
|
"""exchange ccxt id"""
|
||||||
return self._api.id
|
return self._api.id
|
||||||
|
|
||||||
|
def set_sandbox(self, api, exchange_config: dict, name: str):
|
||||||
|
if exchange_config.get('sandbox'):
|
||||||
|
if api.urls.get('test'):
|
||||||
|
api.urls['api'] = api.urls['test']
|
||||||
|
logger.info("Enabled Sandbox API on %s", name)
|
||||||
|
else:
|
||||||
|
logger.warning(self, "No Sandbox URL in CCXT, exiting. "
|
||||||
|
"Please check your config.json")
|
||||||
|
raise OperationalException(f'Exchange {name} does not provide a sandbox api')
|
||||||
|
|
||||||
def validate_pairs(self, pairs: List[str]) -> None:
|
def validate_pairs(self, pairs: List[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Checks if all given pairs are tradable on the current exchange.
|
Checks if all given pairs are tradable on the current exchange.
|
||||||
@ -150,6 +163,28 @@ class Exchange(object):
|
|||||||
"""
|
"""
|
||||||
return endpoint in self._api.has and self._api.has[endpoint]
|
return endpoint in self._api.has and self._api.has[endpoint]
|
||||||
|
|
||||||
|
def symbol_amount_prec(self, pair, amount: float):
|
||||||
|
'''
|
||||||
|
Returns the amount to buy or sell to a precision the Exchange accepts
|
||||||
|
Rounded down
|
||||||
|
'''
|
||||||
|
if self._api.markets[pair]['precision']['amount']:
|
||||||
|
symbol_prec = self._api.markets[pair]['precision']['amount']
|
||||||
|
big_amount = amount * pow(10, symbol_prec)
|
||||||
|
amount = floor(big_amount) / pow(10, symbol_prec)
|
||||||
|
return amount
|
||||||
|
|
||||||
|
def symbol_price_prec(self, pair, price: float):
|
||||||
|
'''
|
||||||
|
Returns the price buying or selling with to the precision the Exchange accepts
|
||||||
|
Rounds up
|
||||||
|
'''
|
||||||
|
if self._api.markets[pair]['precision']['price']:
|
||||||
|
symbol_prec = self._api.markets[pair]['precision']['price']
|
||||||
|
big_price = price * pow(10, symbol_prec)
|
||||||
|
price = ceil(big_price) / pow(10, symbol_prec)
|
||||||
|
return price
|
||||||
|
|
||||||
def buy(self, pair: str, rate: float, amount: float) -> Dict:
|
def buy(self, pair: str, rate: float, amount: float) -> Dict:
|
||||||
if self._conf['dry_run']:
|
if self._conf['dry_run']:
|
||||||
order_id = f'dry_run_buy_{randint(0, 10**6)}'
|
order_id = f'dry_run_buy_{randint(0, 10**6)}'
|
||||||
@ -167,6 +202,10 @@ class Exchange(object):
|
|||||||
return {'id': order_id}
|
return {'id': order_id}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Set the precision for amount and price(rate) as accepted by the exchange
|
||||||
|
amount = self.symbol_amount_prec(pair, amount)
|
||||||
|
rate = self.symbol_price_prec(pair, rate)
|
||||||
|
|
||||||
return self._api.create_limit_buy_order(pair, amount, rate)
|
return self._api.create_limit_buy_order(pair, amount, rate)
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
@ -200,6 +239,10 @@ class Exchange(object):
|
|||||||
return {'id': order_id}
|
return {'id': order_id}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Set the precision for amount and price(rate) as accepted by the exchange
|
||||||
|
amount = self.symbol_amount_prec(pair, amount)
|
||||||
|
rate = self.symbol_price_prec(pair, rate)
|
||||||
|
|
||||||
return self._api.create_limit_sell_order(pair, amount, rate)
|
return self._api.create_limit_sell_order(pair, amount, rate)
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
|
@ -63,8 +63,8 @@ class Backtesting(object):
|
|||||||
self.strategy: IStrategy = StrategyResolver(self.config).strategy
|
self.strategy: IStrategy = StrategyResolver(self.config).strategy
|
||||||
self.ticker_interval = self.strategy.ticker_interval
|
self.ticker_interval = self.strategy.ticker_interval
|
||||||
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
|
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
|
||||||
self.populate_buy_trend = self.strategy.populate_buy_trend
|
self.advise_buy = self.strategy.advise_buy
|
||||||
self.populate_sell_trend = self.strategy.populate_sell_trend
|
self.advise_sell = self.strategy.advise_sell
|
||||||
|
|
||||||
# Reset keys for backtesting
|
# Reset keys for backtesting
|
||||||
self.config['exchange']['key'] = ''
|
self.config['exchange']['key'] = ''
|
||||||
@ -292,8 +292,8 @@ class Backtesting(object):
|
|||||||
|
|
||||||
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
|
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
|
||||||
|
|
||||||
ticker_data = self.populate_sell_trend(
|
ticker_data = self.advise_sell(
|
||||||
self.populate_buy_trend(pair_data))[headers].copy()
|
self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
|
||||||
|
|
||||||
# to avoid using data from future, we buy/sell with signal from previous candle
|
# to avoid using data from future, we buy/sell with signal from previous candle
|
||||||
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)
|
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)
|
||||||
|
@ -75,7 +75,7 @@ class Hyperopt(Backtesting):
|
|||||||
return arg_dict
|
return arg_dict
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
dataframe['adx'] = ta.ADX(dataframe)
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
macd = ta.MACD(dataframe)
|
macd = ta.MACD(dataframe)
|
||||||
dataframe['macd'] = macd['macd']
|
dataframe['macd'] = macd['macd']
|
||||||
@ -228,7 +228,7 @@ class Hyperopt(Backtesting):
|
|||||||
"""
|
"""
|
||||||
Define the buy strategy parameters to be used by hyperopt
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
"""
|
"""
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Buy strategy Hyperopt will build and use
|
Buy strategy Hyperopt will build and use
|
||||||
"""
|
"""
|
||||||
@ -270,7 +270,7 @@ class Hyperopt(Backtesting):
|
|||||||
self.strategy.minimal_roi = self.generate_roi_table(params)
|
self.strategy.minimal_roi = self.generate_roi_table(params)
|
||||||
|
|
||||||
if self.has_space('buy'):
|
if self.has_space('buy'):
|
||||||
self.populate_buy_trend = self.buy_strategy_generator(params)
|
self.advise_buy = self.buy_strategy_generator(params)
|
||||||
|
|
||||||
if self.has_space('stoploss'):
|
if self.has_space('stoploss'):
|
||||||
self.strategy.stoploss = params['stoploss']
|
self.strategy.stoploss = params['stoploss']
|
||||||
@ -351,7 +351,7 @@ class Hyperopt(Backtesting):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.has_space('buy'):
|
if self.has_space('buy'):
|
||||||
self.strategy.populate_indicators = Hyperopt.populate_indicators # type: ignore
|
self.strategy.advise_indicators = Hyperopt.populate_indicators # type: ignore
|
||||||
dump(self.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
|
dump(self.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
|
||||||
self.exchange = None # type: ignore
|
self.exchange = None # type: ignore
|
||||||
self.load_previous_results()
|
self.load_previous_results()
|
||||||
@ -360,7 +360,7 @@ class Hyperopt(Backtesting):
|
|||||||
logger.info(f'Found {cpus} CPU cores. Let\'s make them scream!')
|
logger.info(f'Found {cpus} CPU cores. Let\'s make them scream!')
|
||||||
|
|
||||||
opt = self.get_optimizer(cpus)
|
opt = self.get_optimizer(cpus)
|
||||||
EVALS = max(self.total_tries//cpus, 1)
|
EVALS = max(self.total_tries // cpus, 1)
|
||||||
try:
|
try:
|
||||||
with Parallel(n_jobs=cpus) as parallel:
|
with Parallel(n_jobs=cpus) as parallel:
|
||||||
for i in range(EVALS):
|
for i in range(EVALS):
|
||||||
|
@ -28,13 +28,16 @@ class DefaultStrategy(IStrategy):
|
|||||||
# Optimal ticker interval for the strategy
|
# Optimal ticker interval for the strategy
|
||||||
ticker_interval = '5m'
|
ticker_interval = '5m'
|
||||||
|
|
||||||
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Adds several different TA indicators to the given DataFrame
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
Performance Note: For the best performance be frugal on the number of indicators
|
Performance Note: For the best performance be frugal on the number of indicators
|
||||||
you are using. Let uncomment only the indicator you are using in your strategies
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
|
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Momentum Indicator
|
# Momentum Indicator
|
||||||
@ -196,10 +199,11 @@ class DefaultStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
@ -217,10 +221,11 @@ class DefaultStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
|
@ -7,6 +7,7 @@ from abc import ABC, abstractmethod
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List, NamedTuple, Tuple
|
from typing import Dict, List, NamedTuple, Tuple
|
||||||
|
import warnings
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
@ -57,34 +58,45 @@ class IStrategy(ABC):
|
|||||||
ticker_interval -> str: value of the ticker interval to use for the strategy
|
ticker_interval -> str: value of the ticker interval to use for the strategy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_populate_fun_len: int = 0
|
||||||
|
_buy_fun_len: int = 0
|
||||||
|
_sell_fun_len: int = 0
|
||||||
|
# associated minimal roi
|
||||||
minimal_roi: Dict
|
minimal_roi: Dict
|
||||||
|
|
||||||
|
# associated stoploss
|
||||||
stoploss: float
|
stoploss: float
|
||||||
|
|
||||||
|
# associated ticker interval
|
||||||
ticker_interval: str
|
ticker_interval: str
|
||||||
|
|
||||||
def __init__(self, config: dict) -> None:
|
def __init__(self, config: dict) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Populate indicators that will be used in the Buy and Sell strategy
|
Populate indicators that will be used in the Buy and Sell strategy
|
||||||
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: a Dataframe with all mandatory indicators for the strategies
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with sell column
|
:return: DataFrame with sell column
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -94,16 +106,16 @@ class IStrategy(ABC):
|
|||||||
"""
|
"""
|
||||||
return self.__class__.__name__
|
return self.__class__.__name__
|
||||||
|
|
||||||
def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame:
|
def analyze_ticker(self, ticker_history: List[Dict], metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Parses the given ticker history and returns a populated DataFrame
|
Parses the given ticker history and returns a populated DataFrame
|
||||||
add several TA indicators and buy signal to it
|
add several TA indicators and buy signal to it
|
||||||
:return DataFrame with ticker data and indicator data
|
:return DataFrame with ticker data and indicator data
|
||||||
"""
|
"""
|
||||||
dataframe = parse_ticker_dataframe(ticker_history)
|
dataframe = parse_ticker_dataframe(ticker_history)
|
||||||
dataframe = self.populate_indicators(dataframe)
|
dataframe = self.advise_indicators(dataframe, metadata)
|
||||||
dataframe = self.populate_buy_trend(dataframe)
|
dataframe = self.advise_buy(dataframe, metadata)
|
||||||
dataframe = self.populate_sell_trend(dataframe)
|
dataframe = self.advise_sell(dataframe, metadata)
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]:
|
def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]:
|
||||||
@ -118,7 +130,7 @@ class IStrategy(ABC):
|
|||||||
return False, False
|
return False, False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dataframe = self.analyze_ticker(ticker_hist)
|
dataframe = self.analyze_ticker(ticker_hist, {'pair': pair})
|
||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'Unable to analyze ticker for pair %s: %s',
|
'Unable to analyze ticker for pair %s: %s',
|
||||||
@ -200,6 +212,7 @@ class IStrategy(ABC):
|
|||||||
"""
|
"""
|
||||||
Based on current profit of the trade and configured (trailing) stoploss,
|
Based on current profit of the trade and configured (trailing) stoploss,
|
||||||
decides to sell or not
|
decides to sell or not
|
||||||
|
:param current_profit: current profit in percent
|
||||||
"""
|
"""
|
||||||
|
|
||||||
trailing_stop = self.config.get('trailing_stop', False)
|
trailing_stop = self.config.get('trailing_stop', False)
|
||||||
@ -227,12 +240,15 @@ class IStrategy(ABC):
|
|||||||
# check if we have a special stop loss for positive condition
|
# check if we have a special stop loss for positive condition
|
||||||
# and if profit is positive
|
# and if profit is positive
|
||||||
stop_loss_value = self.stoploss
|
stop_loss_value = self.stoploss
|
||||||
if 'trailing_stop_positive' in self.config and current_profit > 0:
|
sl_offset = self.config.get('trailing_stop_positive_offset', 0.0)
|
||||||
|
|
||||||
|
if 'trailing_stop_positive' in self.config and current_profit > sl_offset:
|
||||||
|
|
||||||
# Ignore mypy error check in configuration that this is a float
|
# Ignore mypy error check in configuration that this is a float
|
||||||
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
|
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
|
||||||
logger.debug(f"using positive stop loss mode: {stop_loss_value} "
|
logger.debug(f"using positive stop loss mode: {stop_loss_value} "
|
||||||
f"since we have profit {current_profit}")
|
f"with offset {sl_offset:.4g} "
|
||||||
|
f"since we have profit {current_profit:.4f}%")
|
||||||
|
|
||||||
trade.adjust_stop_loss(current_rate, stop_loss_value)
|
trade.adjust_stop_loss(current_rate, stop_loss_value)
|
||||||
|
|
||||||
@ -259,5 +275,50 @@ class IStrategy(ABC):
|
|||||||
"""
|
"""
|
||||||
Creates a dataframe and populates indicators for given ticker data
|
Creates a dataframe and populates indicators for given ticker data
|
||||||
"""
|
"""
|
||||||
return {pair: self.populate_indicators(parse_ticker_dataframe(pair_data))
|
return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), {'pair': pair})
|
||||||
for pair, pair_data in tickerdata.items()}
|
for pair, pair_data in tickerdata.items()}
|
||||||
|
|
||||||
|
def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Populate indicators that will be used in the Buy and Sell strategy
|
||||||
|
This method should not be overridden.
|
||||||
|
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
|
"""
|
||||||
|
if self._populate_fun_len == 2:
|
||||||
|
warnings.warn("deprecated - check out the Sample strategy to see "
|
||||||
|
"the current function headers!", DeprecationWarning)
|
||||||
|
return self.populate_indicators(dataframe) # type: ignore
|
||||||
|
else:
|
||||||
|
return self.populate_indicators(dataframe, metadata)
|
||||||
|
|
||||||
|
def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
This method should not be overridden.
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:param pair: Additional information, like the currently traded pair
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
if self._buy_fun_len == 2:
|
||||||
|
warnings.warn("deprecated - check out the Sample strategy to see "
|
||||||
|
"the current function headers!", DeprecationWarning)
|
||||||
|
return self.populate_buy_trend(dataframe) # type: ignore
|
||||||
|
else:
|
||||||
|
return self.populate_buy_trend(dataframe, metadata)
|
||||||
|
|
||||||
|
def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
This method should not be overridden.
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:param pair: Additional information, like the currently traded pair
|
||||||
|
:return: DataFrame with sell column
|
||||||
|
"""
|
||||||
|
if self._sell_fun_len == 2:
|
||||||
|
warnings.warn("deprecated - check out the Sample strategy to see "
|
||||||
|
"the current function headers!", DeprecationWarning)
|
||||||
|
return self.populate_sell_trend(dataframe) # type: ignore
|
||||||
|
else:
|
||||||
|
return self.populate_sell_trend(dataframe, metadata)
|
||||||
|
@ -92,6 +92,13 @@ class StrategyResolver(object):
|
|||||||
strategy = self._search_strategy(path, strategy_name=strategy_name, config=config)
|
strategy = self._search_strategy(path, strategy_name=strategy_name, config=config)
|
||||||
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)
|
||||||
|
strategy._populate_fun_len = len(
|
||||||
|
inspect.getfullargspec(strategy.populate_indicators).args)
|
||||||
|
strategy._buy_fun_len = len(
|
||||||
|
inspect.getfullargspec(strategy.populate_buy_trend).args)
|
||||||
|
strategy._sell_fun_len = len(
|
||||||
|
inspect.getfullargspec(strategy.populate_sell_trend).args)
|
||||||
|
|
||||||
return import_strategy(strategy, config=config)
|
return import_strategy(strategy, config=config)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.warning('Path "%s" does not exist', path)
|
logger.warning('Path "%s" does not exist', path)
|
||||||
|
@ -52,6 +52,93 @@ def test_init_exception(default_conf, mocker):
|
|||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_symbol_amount_prec(default_conf, mocker):
|
||||||
|
'''
|
||||||
|
Test rounds down to 4 Decimal places
|
||||||
|
'''
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
|
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
|
||||||
|
})
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='binance'))
|
||||||
|
|
||||||
|
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 4}}})
|
||||||
|
type(api_mock).markets = markets
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||||
|
exchange = Exchange(default_conf)
|
||||||
|
|
||||||
|
amount = 2.34559
|
||||||
|
pair = 'ETH/BTC'
|
||||||
|
amount = exchange.symbol_amount_prec(pair, amount)
|
||||||
|
assert amount == 2.3455
|
||||||
|
|
||||||
|
|
||||||
|
def test_symbol_price_prec(default_conf, mocker):
|
||||||
|
'''
|
||||||
|
Test rounds up to 4 decimal places
|
||||||
|
'''
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
|
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
|
||||||
|
})
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='binance'))
|
||||||
|
|
||||||
|
markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': 4}}})
|
||||||
|
type(api_mock).markets = markets
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||||
|
exchange = Exchange(default_conf)
|
||||||
|
|
||||||
|
price = 2.34559
|
||||||
|
pair = 'ETH/BTC'
|
||||||
|
price = exchange.symbol_price_prec(pair, price)
|
||||||
|
assert price == 2.3456
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_sandbox(default_conf, mocker):
|
||||||
|
"""
|
||||||
|
Test working scenario
|
||||||
|
"""
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
|
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
|
||||||
|
})
|
||||||
|
url_mock = PropertyMock(return_value={'test': "api-public.sandbox.gdax.com",
|
||||||
|
'api': 'https://api.gdax.com'})
|
||||||
|
type(api_mock).urls = url_mock
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||||
|
|
||||||
|
exchange = Exchange(default_conf)
|
||||||
|
liveurl = exchange._api.urls['api']
|
||||||
|
default_conf['exchange']['sandbox'] = True
|
||||||
|
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
|
||||||
|
assert exchange._api.urls['api'] != liveurl
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_sandbox_exception(default_conf, mocker):
|
||||||
|
"""
|
||||||
|
Test Fail scenario
|
||||||
|
"""
|
||||||
|
api_mock = MagicMock()
|
||||||
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
|
'ETH/BTC': '', 'LTC/BTC': '', 'XRP/BTC': '', 'NEO/BTC': ''
|
||||||
|
})
|
||||||
|
url_mock = PropertyMock(return_value={'api': 'https://api.gdax.com'})
|
||||||
|
type(api_mock).urls = url_mock
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException, match=r'does not provide a sandbox api'):
|
||||||
|
exchange = Exchange(default_conf)
|
||||||
|
default_conf['exchange']['sandbox'] = True
|
||||||
|
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
|
||||||
|
|
||||||
|
|
||||||
def test_validate_pairs(default_conf, mocker):
|
def test_validate_pairs(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.load_markets = MagicMock(return_value={
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
@ -173,7 +260,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
|
|||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_exchangehas(default_conf, mocker):
|
def test_exchange_has(default_conf, mocker):
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
assert not exchange.exchange_has('ASDFASDF')
|
assert not exchange.exchange_has('ASDFASDF')
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
|
||||||
"""
|
|
||||||
Unit test file for exchange_helpers.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
||||||
|
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ def _trend(signals, buy_value, sell_value):
|
|||||||
return signals
|
return signals
|
||||||
|
|
||||||
|
|
||||||
def _trend_alternate(dataframe=None):
|
def _trend_alternate(dataframe=None, metadata=None):
|
||||||
signals = dataframe
|
signals = dataframe
|
||||||
low = signals['low']
|
low = signals['low']
|
||||||
n = len(low)
|
n = len(low)
|
||||||
@ -332,8 +332,8 @@ def test_backtesting_init(mocker, default_conf) -> None:
|
|||||||
assert backtesting.config == default_conf
|
assert backtesting.config == default_conf
|
||||||
assert backtesting.ticker_interval == '5m'
|
assert backtesting.ticker_interval == '5m'
|
||||||
assert callable(backtesting.tickerdata_to_dataframe)
|
assert callable(backtesting.tickerdata_to_dataframe)
|
||||||
assert callable(backtesting.populate_buy_trend)
|
assert callable(backtesting.advise_buy)
|
||||||
assert callable(backtesting.populate_sell_trend)
|
assert callable(backtesting.advise_sell)
|
||||||
get_fee.assert_called()
|
get_fee.assert_called()
|
||||||
assert backtesting.fee == 0.5
|
assert backtesting.fee == 0.5
|
||||||
|
|
||||||
@ -611,42 +611,42 @@ def test_backtest_ticks(default_conf, fee, mocker):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
ticks = [1, 5]
|
ticks = [1, 5]
|
||||||
fun = Backtesting(default_conf).populate_buy_trend
|
fun = Backtesting(default_conf).advise_buy
|
||||||
for _ in ticks:
|
for _ in ticks:
|
||||||
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.advise_buy = fun # Override
|
||||||
backtesting.populate_sell_trend = fun # Override
|
backtesting.advise_sell = fun # Override
|
||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_clash_buy_sell(mocker, default_conf):
|
def test_backtest_clash_buy_sell(mocker, default_conf):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our default_strategy
|
||||||
def fun(dataframe=None):
|
def fun(dataframe=None, pair=None):
|
||||||
buy_value = 1
|
buy_value = 1
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
return _trend(dataframe, buy_value, sell_value)
|
return _trend(dataframe, buy_value, sell_value)
|
||||||
|
|
||||||
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.advise_buy = fun # Override
|
||||||
backtesting.populate_sell_trend = fun # Override
|
backtesting.advise_sell = fun # Override
|
||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert results.empty
|
assert results.empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_only_sell(mocker, default_conf):
|
def test_backtest_only_sell(mocker, default_conf):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our default_strategy
|
||||||
def fun(dataframe=None):
|
def fun(dataframe=None, pair=None):
|
||||||
buy_value = 0
|
buy_value = 0
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
return _trend(dataframe, buy_value, sell_value)
|
return _trend(dataframe, buy_value, sell_value)
|
||||||
|
|
||||||
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.advise_buy = fun # Override
|
||||||
backtesting.populate_sell_trend = fun # Override
|
backtesting.advise_sell = fun # Override
|
||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert results.empty
|
assert results.empty
|
||||||
|
|
||||||
@ -655,8 +655,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
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.advise_buy = _trend_alternate # Override
|
||||||
backtesting.populate_sell_trend = _trend_alternate # Override
|
backtesting.advise_sell = _trend_alternate # Override
|
||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
backtesting._store_backtest_result("test_.json", results)
|
backtesting._store_backtest_result("test_.json", results)
|
||||||
assert len(results) == 4
|
assert len(results) == 4
|
||||||
|
@ -77,9 +77,6 @@ def test_start(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
|
def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
|
||||||
"""
|
|
||||||
Test Hyperopt.calculate_loss()
|
|
||||||
"""
|
|
||||||
hyperopt = _HYPEROPT
|
hyperopt = _HYPEROPT
|
||||||
StrategyResolver({'strategy': 'DefaultStrategy'})
|
StrategyResolver({'strategy': 'DefaultStrategy'})
|
||||||
|
|
||||||
@ -91,9 +88,6 @@ def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_prefer_shorter_trades(init_hyperopt) -> None:
|
def test_loss_calculation_prefer_shorter_trades(init_hyperopt) -> None:
|
||||||
"""
|
|
||||||
Test Hyperopt.calculate_loss()
|
|
||||||
"""
|
|
||||||
hyperopt = _HYPEROPT
|
hyperopt = _HYPEROPT
|
||||||
|
|
||||||
shorter = hyperopt.calculate_loss(1, 100, 20)
|
shorter = hyperopt.calculate_loss(1, 100, 20)
|
||||||
@ -123,7 +117,7 @@ def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
assert ' 1/2: foo. Loss 1.00000'in out
|
assert ' 1/2: foo. Loss 1.00000' in out
|
||||||
|
|
||||||
|
|
||||||
def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
|
def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
|
||||||
@ -240,9 +234,6 @@ def test_format_results(init_hyperopt):
|
|||||||
|
|
||||||
|
|
||||||
def test_has_space(init_hyperopt):
|
def test_has_space(init_hyperopt):
|
||||||
"""
|
|
||||||
Test Hyperopt.has_space() method
|
|
||||||
"""
|
|
||||||
_HYPEROPT.config.update({'spaces': ['buy', 'roi']})
|
_HYPEROPT.config.update({'spaces': ['buy', 'roi']})
|
||||||
assert _HYPEROPT.has_space('roi')
|
assert _HYPEROPT.has_space('roi')
|
||||||
assert _HYPEROPT.has_space('buy')
|
assert _HYPEROPT.has_space('buy')
|
||||||
@ -253,13 +244,10 @@ def test_has_space(init_hyperopt):
|
|||||||
|
|
||||||
|
|
||||||
def test_populate_indicators(init_hyperopt) -> None:
|
def test_populate_indicators(init_hyperopt) -> None:
|
||||||
"""
|
|
||||||
Test Hyperopt.populate_indicators()
|
|
||||||
"""
|
|
||||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||||
tickerlist = {'UNITTEST/BTC': tick}
|
tickerlist = {'UNITTEST/BTC': tick}
|
||||||
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
||||||
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'])
|
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
|
||||||
|
|
||||||
# Check if some indicators are generated. We will not test all of them
|
# Check if some indicators are generated. We will not test all of them
|
||||||
assert 'adx' in dataframe
|
assert 'adx' in dataframe
|
||||||
@ -268,13 +256,10 @@ def test_populate_indicators(init_hyperopt) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_buy_strategy_generator(init_hyperopt) -> None:
|
def test_buy_strategy_generator(init_hyperopt) -> None:
|
||||||
"""
|
|
||||||
Test Hyperopt.buy_strategy_generator()
|
|
||||||
"""
|
|
||||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||||
tickerlist = {'UNITTEST/BTC': tick}
|
tickerlist = {'UNITTEST/BTC': tick}
|
||||||
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
dataframes = _HYPEROPT.tickerdata_to_dataframe(tickerlist)
|
||||||
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'])
|
dataframe = _HYPEROPT.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
|
||||||
|
|
||||||
populate_buy_trend = _HYPEROPT.buy_strategy_generator(
|
populate_buy_trend = _HYPEROPT.buy_strategy_generator(
|
||||||
{
|
{
|
||||||
@ -289,16 +274,13 @@ def test_buy_strategy_generator(init_hyperopt) -> None:
|
|||||||
'trigger': 'bb_lower'
|
'trigger': 'bb_lower'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
result = populate_buy_trend(dataframe)
|
result = populate_buy_trend(dataframe, {'pair': 'UNITTEST/BTC'})
|
||||||
# Check if some indicators are generated. We will not test all of them
|
# Check if some indicators are generated. We will not test all of them
|
||||||
assert 'buy' in result
|
assert 'buy' in result
|
||||||
assert 1 in result['buy']
|
assert 1 in result['buy']
|
||||||
|
|
||||||
|
|
||||||
def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
|
def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
|
||||||
"""
|
|
||||||
Test Hyperopt.generate_optimizer() function
|
|
||||||
"""
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf.update({'config': 'config.json.example'})
|
conf.update({'config': 'config.json.example'})
|
||||||
conf.update({'timerange': None})
|
conf.update({'timerange': None})
|
||||||
@ -335,7 +317,6 @@ def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
|
|||||||
'roi_p3': 0.1,
|
'roi_p3': 0.1,
|
||||||
'stoploss': -0.4,
|
'stoploss': -0.4,
|
||||||
}
|
}
|
||||||
|
|
||||||
response_expected = {
|
response_expected = {
|
||||||
'loss': 1.9840569076926293,
|
'loss': 1.9840569076926293,
|
||||||
'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC '
|
'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC '
|
||||||
|
@ -53,9 +53,6 @@ def _clean_test_file(file: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
|
def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
|
||||||
"""
|
|
||||||
Test load_data() with 30 min ticker
|
|
||||||
"""
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.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)
|
||||||
@ -66,9 +63,6 @@ def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) ->
|
|||||||
|
|
||||||
|
|
||||||
def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
|
def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
|
||||||
"""
|
|
||||||
Test load_data() with 5 min ticker
|
|
||||||
"""
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.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')
|
||||||
@ -80,11 +74,7 @@ def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) ->
|
|||||||
|
|
||||||
|
|
||||||
def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
|
def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test load_data() with 1 min ticker
|
|
||||||
"""
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.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)
|
||||||
optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
||||||
@ -421,10 +411,6 @@ def test_trim_tickerlist() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_file_dump_json() -> None:
|
def test_file_dump_json() -> None:
|
||||||
"""
|
|
||||||
Test file_dump_json()
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
|
file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
|
||||||
'test_{id}.json'.format(id=str(uuid.uuid4())))
|
'test_{id}.json'.format(id=str(uuid.uuid4())))
|
||||||
data = {'bar': 'foo'}
|
data = {'bar': 'foo'}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
|
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
|
||||||
|
|
||||||
"""
|
|
||||||
Unit test file for rpc/rpc.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from unittest.mock import MagicMock, ANY
|
from unittest.mock import MagicMock, ANY
|
||||||
|
|
||||||
@ -28,9 +25,6 @@ def prec_satoshi(a, b) -> float:
|
|||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test rpc_trade_status() method
|
|
||||||
"""
|
|
||||||
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(
|
||||||
@ -72,9 +66,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
|
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test rpc_status_table() method
|
|
||||||
"""
|
|
||||||
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(
|
||||||
@ -106,9 +97,6 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
|
|||||||
|
|
||||||
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test rpc_daily_profit() method
|
|
||||||
"""
|
|
||||||
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(
|
||||||
@ -158,13 +146,11 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
|||||||
|
|
||||||
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test rpc_trade_statistics() method
|
|
||||||
"""
|
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.fiat_convert.Market',
|
'freqtrade.fiat_convert.Market',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
)
|
)
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.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(
|
||||||
@ -236,9 +222,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
|||||||
# trade.open_rate (it is set to None)
|
# trade.open_rate (it is set to None)
|
||||||
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
|
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
|
||||||
ticker_sell_up, limit_buy_order, limit_sell_order):
|
ticker_sell_up, limit_buy_order, limit_sell_order):
|
||||||
"""
|
|
||||||
Test rpc_trade_statistics() method
|
|
||||||
"""
|
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.fiat_convert.Market',
|
'freqtrade.fiat_convert.Market',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
@ -315,6 +298,7 @@ def test_rpc_balance_handle(default_conf, mocker):
|
|||||||
'freqtrade.fiat_convert.Market',
|
'freqtrade.fiat_convert.Market',
|
||||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
)
|
)
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.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(
|
||||||
@ -342,9 +326,6 @@ def test_rpc_balance_handle(default_conf, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_start(mocker, default_conf) -> None:
|
def test_rpc_start(mocker, default_conf) -> None:
|
||||||
"""
|
|
||||||
Test rpc_start() method
|
|
||||||
"""
|
|
||||||
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(
|
||||||
@ -368,9 +349,6 @@ def test_rpc_start(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_stop(mocker, default_conf) -> None:
|
def test_rpc_stop(mocker, default_conf) -> None:
|
||||||
"""
|
|
||||||
Test rpc_stop() method
|
|
||||||
"""
|
|
||||||
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(
|
||||||
@ -395,9 +373,6 @@ def test_rpc_stop(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
|
def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
|
||||||
"""
|
|
||||||
Test rpc_forcesell() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
|
||||||
@ -499,9 +474,6 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
|
|||||||
|
|
||||||
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||||
limit_sell_order, markets, mocker) -> None:
|
limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test rpc_performance() method
|
|
||||||
"""
|
|
||||||
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(
|
||||||
@ -538,9 +510,6 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
|
def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
|
||||||
"""
|
|
||||||
Test rpc_count() method
|
|
||||||
"""
|
|
||||||
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(
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
Unit test file for rpc/rpc_manager.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@ -10,14 +8,7 @@ from freqtrade.rpc import RPCMessageType, RPCManager
|
|||||||
from freqtrade.tests.conftest import log_has, get_patched_freqtradebot
|
from freqtrade.tests.conftest import log_has, get_patched_freqtradebot
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_manager_object() -> None:
|
|
||||||
""" Test the Arguments object has the mandatory methods """
|
|
||||||
assert hasattr(RPCManager, 'send_msg')
|
|
||||||
assert hasattr(RPCManager, 'cleanup')
|
|
||||||
|
|
||||||
|
|
||||||
def test__init__(mocker, default_conf) -> None:
|
def test__init__(mocker, default_conf) -> None:
|
||||||
""" Test __init__() method """
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['telegram']['enabled'] = False
|
conf['telegram']['enabled'] = False
|
||||||
|
|
||||||
@ -26,12 +17,9 @@ def test__init__(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
|
def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||||
""" Test _init() method with Telegram disabled """
|
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['telegram']['enabled'] = False
|
conf['telegram']['enabled'] = False
|
||||||
|
|
||||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
|
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
|
||||||
|
|
||||||
assert not log_has('Enabling rpc.telegram ...', caplog.record_tuples)
|
assert not log_has('Enabling rpc.telegram ...', caplog.record_tuples)
|
||||||
@ -39,12 +27,8 @@ def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
|
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test _init() method with Telegram enabled
|
|
||||||
"""
|
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
|
|
||||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||||
|
|
||||||
assert log_has('Enabling rpc.telegram ...', caplog.record_tuples)
|
assert log_has('Enabling rpc.telegram ...', caplog.record_tuples)
|
||||||
@ -54,12 +38,8 @@ def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
|
def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test cleanup() method with Telegram disabled
|
|
||||||
"""
|
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
|
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['telegram']['enabled'] = False
|
conf['telegram']['enabled'] = False
|
||||||
|
|
||||||
@ -72,9 +52,6 @@ def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
|
def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test cleanup() method with Telegram enabled
|
|
||||||
"""
|
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
|
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
|
||||||
@ -92,11 +69,7 @@ def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
|
def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test send_msg() method with Telegram disabled
|
|
||||||
"""
|
|
||||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['telegram']['enabled'] = False
|
conf['telegram']['enabled'] = False
|
||||||
|
|
||||||
@ -112,9 +85,6 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
|
def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test send_msg() method with Telegram disabled
|
|
||||||
"""
|
|
||||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
telegram_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())
|
||||||
|
|
||||||
@ -130,13 +100,10 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_init_webhook_disabled(mocker, default_conf, caplog) -> None:
|
def test_init_webhook_disabled(mocker, default_conf, caplog) -> None:
|
||||||
""" Test _init() method with Webhook disabled """
|
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['telegram']['enabled'] = False
|
conf['telegram']['enabled'] = False
|
||||||
conf['webhook'] = {'enabled': False}
|
conf['webhook'] = {'enabled': False}
|
||||||
|
|
||||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
|
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
|
||||||
|
|
||||||
assert not log_has('Enabling rpc.webhook ...', caplog.record_tuples)
|
assert not log_has('Enabling rpc.webhook ...', caplog.record_tuples)
|
||||||
@ -144,16 +111,11 @@ def test_init_webhook_disabled(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_init_webhook_enabled(mocker, default_conf, caplog) -> None:
|
def test_init_webhook_enabled(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test _init() method with Webhook enabled
|
|
||||||
"""
|
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
default_conf['telegram']['enabled'] = False
|
default_conf['telegram']['enabled'] = False
|
||||||
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
|
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
|
||||||
|
|
||||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||||
|
|
||||||
assert log_has('Enabling rpc.webhook ...', caplog.record_tuples)
|
assert log_has('Enabling rpc.webhook ...', caplog.record_tuples)
|
||||||
len_modules = len(rpc_manager.registered_modules)
|
assert len(rpc_manager.registered_modules) == 1
|
||||||
assert len_modules == 1
|
|
||||||
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
|
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
# pragma pylint: disable=protected-access, unused-argument, invalid-name
|
# pragma pylint: disable=protected-access, unused-argument, invalid-name
|
||||||
# pragma pylint: disable=too-many-lines, too-many-arguments
|
# pragma pylint: disable=too-many-lines, too-many-arguments
|
||||||
|
|
||||||
"""
|
|
||||||
Unit test file for rpc/telegram.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -55,9 +52,6 @@ class DummyCls(Telegram):
|
|||||||
|
|
||||||
|
|
||||||
def test__init__(default_conf, mocker) -> None:
|
def test__init__(default_conf, mocker) -> None:
|
||||||
"""
|
|
||||||
Test __init__() method
|
|
||||||
"""
|
|
||||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
|
|
||||||
@ -67,7 +61,6 @@ def test__init__(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_init(default_conf, mocker, caplog) -> None:
|
def test_init(default_conf, mocker, caplog) -> None:
|
||||||
""" Test _init() method """
|
|
||||||
start_polling = MagicMock()
|
start_polling = MagicMock()
|
||||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling))
|
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling))
|
||||||
|
|
||||||
@ -86,9 +79,6 @@ def test_init(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_cleanup(default_conf, mocker) -> None:
|
def test_cleanup(default_conf, mocker) -> None:
|
||||||
"""
|
|
||||||
Test cleanup() method
|
|
||||||
"""
|
|
||||||
updater_mock = MagicMock()
|
updater_mock = MagicMock()
|
||||||
updater_mock.stop = MagicMock()
|
updater_mock.stop = MagicMock()
|
||||||
mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock)
|
mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock)
|
||||||
@ -99,9 +89,6 @@ def test_cleanup(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_authorized_only(default_conf, mocker, caplog) -> None:
|
def test_authorized_only(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test authorized_only() method when we are authorized
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
patch_exchange(mocker, None)
|
patch_exchange(mocker, None)
|
||||||
|
|
||||||
@ -131,9 +118,6 @@ def test_authorized_only(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test authorized_only() method when we are unauthorized
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
patch_exchange(mocker, None)
|
patch_exchange(mocker, None)
|
||||||
chat = Chat(0xdeadbeef, 0)
|
chat = Chat(0xdeadbeef, 0)
|
||||||
@ -162,9 +146,6 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test authorized_only() method when an exception is thrown
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
@ -195,9 +176,6 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
|
def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
|
||||||
"""
|
|
||||||
Test _status() method
|
|
||||||
"""
|
|
||||||
update.message.chat.id = 123
|
update.message.chat.id = 123
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['telegram']['enabled'] = False
|
conf['telegram']['enabled'] = False
|
||||||
@ -254,9 +232,6 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _status() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -302,9 +277,6 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
|
|||||||
|
|
||||||
|
|
||||||
def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _status_table() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -357,9 +329,6 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
|
|||||||
|
|
||||||
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
||||||
limit_sell_order, markets, mocker) -> None:
|
limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _daily() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
|
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
|
||||||
@ -431,9 +400,6 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _daily() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -470,9 +436,6 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
|||||||
|
|
||||||
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _profit() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -531,10 +494,6 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _balance() method
|
|
||||||
"""
|
|
||||||
|
|
||||||
mock_balance = {
|
mock_balance = {
|
||||||
'BTC': {
|
'BTC': {
|
||||||
'total': 12.0,
|
'total': 12.0,
|
||||||
@ -559,9 +518,6 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def mock_ticker(symbol, refresh):
|
def mock_ticker(symbol, refresh):
|
||||||
"""
|
|
||||||
Mock Bittrex.get_ticker() response
|
|
||||||
"""
|
|
||||||
if symbol == 'BTC/USDT':
|
if symbol == 'BTC/USDT':
|
||||||
return {
|
return {
|
||||||
'bid': 10000.00,
|
'bid': 10000.00,
|
||||||
@ -602,10 +558,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
|||||||
assert 'BTC: 14.00000000' in result
|
assert 'BTC: 14.00000000' in result
|
||||||
|
|
||||||
|
|
||||||
def test_zero_balance_handle(default_conf, update, mocker) -> None:
|
def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _balance() method when the Exchange platform returns nothing
|
|
||||||
"""
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
|
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
|
||||||
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
@ -627,9 +580,6 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_start_handle(default_conf, update, mocker) -> None:
|
def test_start_handle(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _start() method
|
|
||||||
"""
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -648,9 +598,6 @@ def test_start_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_start_handle_already_running(default_conf, update, mocker) -> None:
|
def test_start_handle_already_running(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _start() method
|
|
||||||
"""
|
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.rpc.telegram.Telegram',
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
@ -670,9 +617,6 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_stop_handle(default_conf, update, mocker) -> None:
|
def test_stop_handle(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _stop() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -693,9 +637,6 @@ def test_stop_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
|
def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _stop() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -716,7 +657,6 @@ 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 """
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -738,9 +678,6 @@ def test_reload_conf_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
def test_forcesell_handle(default_conf, update, ticker, fee,
|
def test_forcesell_handle(default_conf, update, ticker, fee,
|
||||||
ticker_sell_up, markets, mocker) -> None:
|
ticker_sell_up, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _forcesell() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
mocker.patch('freqtrade.rpc.rpc.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())
|
||||||
@ -790,9 +727,6 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
|
|||||||
|
|
||||||
def test_forcesell_down_handle(default_conf, update, ticker, fee,
|
def test_forcesell_down_handle(default_conf, update, ticker, fee,
|
||||||
ticker_sell_down, markets, mocker) -> None:
|
ticker_sell_down, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _forcesell() method
|
|
||||||
"""
|
|
||||||
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)
|
||||||
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||||
@ -846,9 +780,6 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _forcesell() method
|
|
||||||
"""
|
|
||||||
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)
|
||||||
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||||
@ -894,9 +825,6 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
|
|||||||
|
|
||||||
|
|
||||||
def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _forcesell() method
|
|
||||||
"""
|
|
||||||
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)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
@ -937,9 +865,6 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
def test_performance_handle(default_conf, update, ticker, fee,
|
def test_performance_handle(default_conf, update, ticker, fee,
|
||||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _performance() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -979,9 +904,6 @@ def test_performance_handle(default_conf, update, ticker, fee,
|
|||||||
|
|
||||||
|
|
||||||
def test_performance_handle_invalid(default_conf, update, mocker) -> None:
|
def test_performance_handle_invalid(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _performance() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -1002,9 +924,6 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _count() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -1046,9 +965,6 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
|
|||||||
|
|
||||||
|
|
||||||
def test_help_handle(default_conf, update, mocker) -> None:
|
def test_help_handle(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _help() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -1066,9 +982,6 @@ def test_help_handle(default_conf, update, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_version_handle(default_conf, update, mocker) -> None:
|
def test_version_handle(default_conf, update, mocker) -> None:
|
||||||
"""
|
|
||||||
Test _version() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -1266,9 +1179,6 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test__send_msg(default_conf, mocker) -> None:
|
def test__send_msg(default_conf, mocker) -> None:
|
||||||
"""
|
|
||||||
Test send_msg() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
@ -1282,9 +1192,6 @@ def test__send_msg(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
|
def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test send_msg() method
|
|
||||||
"""
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
|
# pragma pylint: disable=missing-docstring, C0103, protected-access
|
||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from requests import RequestException
|
from requests import RequestException
|
||||||
|
|
||||||
|
|
||||||
from freqtrade.rpc import RPCMessageType
|
from freqtrade.rpc import RPCMessageType
|
||||||
from freqtrade.rpc.webhook import Webhook
|
from freqtrade.rpc.webhook import Webhook
|
||||||
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has
|
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has
|
||||||
@ -32,23 +33,12 @@ def get_webhook_dict() -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def test__init__(mocker, default_conf):
|
def test__init__(mocker, default_conf):
|
||||||
"""
|
|
||||||
Test __init__() method
|
|
||||||
"""
|
|
||||||
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
|
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
|
||||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||||
assert webhook._config == default_conf
|
assert webhook._config == default_conf
|
||||||
|
|
||||||
|
|
||||||
def test_cleanup(default_conf, mocker) -> None:
|
|
||||||
"""
|
|
||||||
Test cleanup() method - not needed for webhook
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def test_send_msg(default_conf, mocker):
|
def test_send_msg(default_conf, mocker):
|
||||||
""" Test send_msg for Webhook rpc class"""
|
|
||||||
default_conf["webhook"] = get_webhook_dict()
|
default_conf["webhook"] = get_webhook_dict()
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||||
@ -118,7 +108,6 @@ def test_send_msg(default_conf, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_exception_send_msg(default_conf, mocker, caplog):
|
def test_exception_send_msg(default_conf, mocker, caplog):
|
||||||
"""Test misconfigured notification"""
|
|
||||||
default_conf["webhook"] = get_webhook_dict()
|
default_conf["webhook"] = get_webhook_dict()
|
||||||
default_conf["webhook"]["webhookbuy"] = None
|
default_conf["webhook"]["webhookbuy"] = None
|
||||||
|
|
||||||
@ -158,8 +147,6 @@ def test_exception_send_msg(default_conf, mocker, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test__send_msg(default_conf, mocker, caplog):
|
def test__send_msg(default_conf, mocker, caplog):
|
||||||
"""Test internal method - calling the actual api"""
|
|
||||||
|
|
||||||
default_conf["webhook"] = get_webhook_dict()
|
default_conf["webhook"] = get_webhook_dict()
|
||||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||||
msg = {'value1': 'DEADBEEF',
|
msg = {'value1': 'DEADBEEF',
|
||||||
|
235
freqtrade/tests/strategy/legacy_strategy.py
Normal file
235
freqtrade/tests/strategy/legacy_strategy.py
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
|
||||||
|
# --- Do not remove these libs ---
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from pandas import DataFrame
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
# Add your lib to import here
|
||||||
|
import talib.abstract as ta
|
||||||
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
import numpy # noqa
|
||||||
|
|
||||||
|
|
||||||
|
# This class is a sample. Feel free to customize it.
|
||||||
|
class TestStrategyLegacy(IStrategy):
|
||||||
|
"""
|
||||||
|
This is a test strategy using the legacy function headers, which will be
|
||||||
|
removed in a future update.
|
||||||
|
Please do not use this as a template, but refer to user_data/strategy/TestStrategy.py
|
||||||
|
for a uptodate version of this template.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy.
|
||||||
|
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||||
|
minimal_roi = {
|
||||||
|
"40": 0.0,
|
||||||
|
"30": 0.01,
|
||||||
|
"20": 0.02,
|
||||||
|
"0": 0.04
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
# This attribute will be overridden if the config file contains "stoploss"
|
||||||
|
stoploss = -0.10
|
||||||
|
|
||||||
|
# Optimal ticker interval for the strategy
|
||||||
|
ticker_interval = '5m'
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
|
Performance Note: For the best performance be frugal on the number of indicators
|
||||||
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Momentum Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# ADX
|
||||||
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Awesome oscillator
|
||||||
|
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
||||||
|
|
||||||
|
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
|
||||||
|
dataframe['cci'] = ta.CCI(dataframe)
|
||||||
|
|
||||||
|
# MACD
|
||||||
|
macd = ta.MACD(dataframe)
|
||||||
|
dataframe['macd'] = macd['macd']
|
||||||
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
|
|
||||||
|
# MFI
|
||||||
|
dataframe['mfi'] = ta.MFI(dataframe)
|
||||||
|
|
||||||
|
# Minus Directional Indicator / Movement
|
||||||
|
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# Plus Directional Indicator / Movement
|
||||||
|
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
||||||
|
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# ROC
|
||||||
|
dataframe['roc'] = ta.ROC(dataframe)
|
||||||
|
|
||||||
|
# RSI
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe)
|
||||||
|
|
||||||
|
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
||||||
|
rsi = 0.1 * (dataframe['rsi'] - 50)
|
||||||
|
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
||||||
|
|
||||||
|
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
|
||||||
|
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
|
||||||
|
|
||||||
|
# Stoch
|
||||||
|
stoch = ta.STOCH(dataframe)
|
||||||
|
dataframe['slowd'] = stoch['slowd']
|
||||||
|
dataframe['slowk'] = stoch['slowk']
|
||||||
|
|
||||||
|
# Stoch fast
|
||||||
|
stoch_fast = ta.STOCHF(dataframe)
|
||||||
|
dataframe['fastd'] = stoch_fast['fastd']
|
||||||
|
dataframe['fastk'] = stoch_fast['fastk']
|
||||||
|
|
||||||
|
# Stoch RSI
|
||||||
|
stoch_rsi = ta.STOCHRSI(dataframe)
|
||||||
|
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
||||||
|
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Overlap Studies
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# Bollinger bands
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
|
||||||
|
"""
|
||||||
|
# EMA - Exponential Moving Average
|
||||||
|
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
||||||
|
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
||||||
|
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||||
|
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
||||||
|
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
||||||
|
|
||||||
|
# SAR Parabol
|
||||||
|
dataframe['sar'] = ta.SAR(dataframe)
|
||||||
|
|
||||||
|
# SMA - Simple Moving Average
|
||||||
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TEMA - Triple Exponential Moving Average
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
|
||||||
|
# Cycle Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
# Hilbert Transform Indicator - SineWave
|
||||||
|
hilbert = ta.HT_SINE(dataframe)
|
||||||
|
dataframe['htsine'] = hilbert['sine']
|
||||||
|
dataframe['htleadsine'] = hilbert['leadsine']
|
||||||
|
|
||||||
|
# Pattern Recognition - Bullish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Hammer: values [0, 100]
|
||||||
|
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
||||||
|
# Inverted Hammer: values [0, 100]
|
||||||
|
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
|
||||||
|
# Dragonfly Doji: values [0, 100]
|
||||||
|
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
|
||||||
|
# Piercing Line: values [0, 100]
|
||||||
|
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
|
||||||
|
# Morningstar: values [0, 100]
|
||||||
|
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
||||||
|
# Three White Soldiers: values [0, 100]
|
||||||
|
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Hanging Man: values [0, 100]
|
||||||
|
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
||||||
|
# Shooting Star: values [0, 100]
|
||||||
|
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
|
||||||
|
# Gravestone Doji: values [0, 100]
|
||||||
|
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
|
||||||
|
# Dark Cloud Cover: values [0, 100]
|
||||||
|
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
|
||||||
|
# Evening Doji Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
||||||
|
# Evening Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Three Line Strike: values [0, -100, 100]
|
||||||
|
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
||||||
|
# Spinning Top: values [0, -100, 100]
|
||||||
|
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
|
||||||
|
# Engulfing: values [0, -100, 100]
|
||||||
|
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
|
||||||
|
# Harami: values [0, -100, 100]
|
||||||
|
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
|
||||||
|
# Three Outside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
# Three Inside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Chart type
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Heikinashi stategy
|
||||||
|
heikinashi = qtpylib.heikinashi(dataframe)
|
||||||
|
dataframe['ha_open'] = heikinashi['open']
|
||||||
|
dataframe['ha_close'] = heikinashi['close']
|
||||||
|
dataframe['ha_high'] = heikinashi['high']
|
||||||
|
dataframe['ha_low'] = heikinashi['low']
|
||||||
|
"""
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 30) &
|
||||||
|
(dataframe['tema'] <= dataframe['bb_middleband']) &
|
||||||
|
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 70) &
|
||||||
|
(dataframe['tema'] > dataframe['bb_middleband']) &
|
||||||
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
|
),
|
||||||
|
'sell'] = 1
|
||||||
|
return dataframe
|
@ -25,10 +25,11 @@ def test_default_strategy_structure():
|
|||||||
def test_default_strategy(result):
|
def test_default_strategy(result):
|
||||||
strategy = DefaultStrategy({})
|
strategy = DefaultStrategy({})
|
||||||
|
|
||||||
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
assert type(strategy.minimal_roi) is dict
|
assert type(strategy.minimal_roi) is dict
|
||||||
assert type(strategy.stoploss) is float
|
assert type(strategy.stoploss) is float
|
||||||
assert type(strategy.ticker_interval) is str
|
assert type(strategy.ticker_interval) is str
|
||||||
indicators = strategy.populate_indicators(result)
|
indicators = strategy.populate_indicators(result, metadata)
|
||||||
assert type(indicators) is DataFrame
|
assert type(indicators) is DataFrame
|
||||||
assert type(strategy.populate_buy_trend(indicators)) is DataFrame
|
assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame
|
||||||
assert type(strategy.populate_sell_trend(indicators)) is DataFrame
|
assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
|
||||||
"""
|
|
||||||
Unit test file for analyse.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
||||||
import logging
|
import logging
|
||||||
import os
|
from os import path
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.strategy import import_strategy
|
from freqtrade.strategy import import_strategy
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||||
@ -37,8 +39,8 @@ def test_import_strategy(caplog):
|
|||||||
|
|
||||||
def test_search_strategy():
|
def test_search_strategy():
|
||||||
default_config = {}
|
default_config = {}
|
||||||
default_location = os.path.join(os.path.dirname(
|
default_location = path.join(path.dirname(
|
||||||
os.path.realpath(__file__)), '..', '..', 'strategy'
|
path.realpath(__file__)), '..', '..', 'strategy'
|
||||||
)
|
)
|
||||||
assert isinstance(
|
assert isinstance(
|
||||||
StrategyResolver._search_strategy(
|
StrategyResolver._search_strategy(
|
||||||
@ -57,13 +59,13 @@ def test_search_strategy():
|
|||||||
|
|
||||||
def test_load_strategy(result):
|
def test_load_strategy(result):
|
||||||
resolver = StrategyResolver({'strategy': 'TestStrategy'})
|
resolver = StrategyResolver({'strategy': 'TestStrategy'})
|
||||||
assert hasattr(resolver.strategy, 'populate_indicators')
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
assert 'adx' in resolver.strategy.advise_indicators(result, metadata=metadata)
|
||||||
|
|
||||||
|
|
||||||
def test_load_strategy_invalid_directory(result, caplog):
|
def test_load_strategy_invalid_directory(result, caplog):
|
||||||
resolver = StrategyResolver()
|
resolver = StrategyResolver()
|
||||||
extra_dir = os.path.join('some', 'path')
|
extra_dir = path.join('some', 'path')
|
||||||
resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir)
|
resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
@ -72,8 +74,7 @@ def test_load_strategy_invalid_directory(result, caplog):
|
|||||||
'Path "{}" does not exist'.format(extra_dir),
|
'Path "{}" does not exist'.format(extra_dir),
|
||||||
) in caplog.record_tuples
|
) in caplog.record_tuples
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'populate_indicators')
|
assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_not_found_strategy():
|
def test_load_not_found_strategy():
|
||||||
@ -88,28 +89,23 @@ def test_strategy(result):
|
|||||||
config = {'strategy': 'DefaultStrategy'}
|
config = {'strategy': 'DefaultStrategy'}
|
||||||
|
|
||||||
resolver = StrategyResolver(config)
|
resolver = StrategyResolver(config)
|
||||||
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
assert hasattr(resolver.strategy, 'minimal_roi')
|
|
||||||
assert resolver.strategy.minimal_roi[0] == 0.04
|
assert resolver.strategy.minimal_roi[0] == 0.04
|
||||||
assert config["minimal_roi"]['0'] == 0.04
|
assert config["minimal_roi"]['0'] == 0.04
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'stoploss')
|
|
||||||
assert resolver.strategy.stoploss == -0.10
|
assert resolver.strategy.stoploss == -0.10
|
||||||
assert config['stoploss'] == -0.10
|
assert config['stoploss'] == -0.10
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'ticker_interval')
|
|
||||||
assert resolver.strategy.ticker_interval == '5m'
|
assert resolver.strategy.ticker_interval == '5m'
|
||||||
assert config['ticker_interval'] == '5m'
|
assert config['ticker_interval'] == '5m'
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'populate_indicators')
|
df_indicators = resolver.strategy.advise_indicators(result, metadata=metadata)
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
assert 'adx' in df_indicators
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'populate_buy_trend')
|
dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata)
|
||||||
dataframe = resolver.strategy.populate_buy_trend(resolver.strategy.populate_indicators(result))
|
|
||||||
assert 'buy' in dataframe.columns
|
assert 'buy' in dataframe.columns
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'populate_sell_trend')
|
dataframe = resolver.strategy.advise_sell(df_indicators, metadata=metadata)
|
||||||
dataframe = resolver.strategy.populate_sell_trend(resolver.strategy.populate_indicators(result))
|
|
||||||
assert 'sell' in dataframe.columns
|
assert 'sell' in dataframe.columns
|
||||||
|
|
||||||
|
|
||||||
@ -123,7 +119,6 @@ def test_strategy_override_minimal_roi(caplog):
|
|||||||
}
|
}
|
||||||
resolver = StrategyResolver(config)
|
resolver = StrategyResolver(config)
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'minimal_roi')
|
|
||||||
assert resolver.strategy.minimal_roi[0] == 0.5
|
assert resolver.strategy.minimal_roi[0] == 0.5
|
||||||
assert ('freqtrade.strategy.resolver',
|
assert ('freqtrade.strategy.resolver',
|
||||||
logging.INFO,
|
logging.INFO,
|
||||||
@ -139,7 +134,6 @@ def test_strategy_override_stoploss(caplog):
|
|||||||
}
|
}
|
||||||
resolver = StrategyResolver(config)
|
resolver = StrategyResolver(config)
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'stoploss')
|
|
||||||
assert resolver.strategy.stoploss == -0.5
|
assert resolver.strategy.stoploss == -0.5
|
||||||
assert ('freqtrade.strategy.resolver',
|
assert ('freqtrade.strategy.resolver',
|
||||||
logging.INFO,
|
logging.INFO,
|
||||||
@ -156,9 +150,64 @@ def test_strategy_override_ticker_interval(caplog):
|
|||||||
}
|
}
|
||||||
resolver = StrategyResolver(config)
|
resolver = StrategyResolver(config)
|
||||||
|
|
||||||
assert hasattr(resolver.strategy, 'ticker_interval')
|
|
||||||
assert resolver.strategy.ticker_interval == 60
|
assert resolver.strategy.ticker_interval == 60
|
||||||
assert ('freqtrade.strategy.resolver',
|
assert ('freqtrade.strategy.resolver',
|
||||||
logging.INFO,
|
logging.INFO,
|
||||||
'Override strategy \'ticker_interval\' with value in config file: 60.'
|
'Override strategy \'ticker_interval\' with value in config file: 60.'
|
||||||
) in caplog.record_tuples
|
) in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
|
def test_deprecate_populate_indicators(result):
|
||||||
|
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||||
|
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
|
||||||
|
'strategy_path': default_location})
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
indicators = resolver.strategy.advise_indicators(result, 'ETH/BTC')
|
||||||
|
assert len(w) == 1
|
||||||
|
assert issubclass(w[-1].category, DeprecationWarning)
|
||||||
|
assert "deprecated - check out the Sample strategy to see the current function headers!" \
|
||||||
|
in str(w[-1].message)
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
resolver.strategy.advise_buy(indicators, 'ETH/BTC')
|
||||||
|
assert len(w) == 1
|
||||||
|
assert issubclass(w[-1].category, DeprecationWarning)
|
||||||
|
assert "deprecated - check out the Sample strategy to see the current function headers!" \
|
||||||
|
in str(w[-1].message)
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
resolver.strategy.advise_sell(indicators, 'ETH_BTC')
|
||||||
|
assert len(w) == 1
|
||||||
|
assert issubclass(w[-1].category, DeprecationWarning)
|
||||||
|
assert "deprecated - check out the Sample strategy to see the current function headers!" \
|
||||||
|
in str(w[-1].message)
|
||||||
|
|
||||||
|
|
||||||
|
def test_call_deprecated_function(result, monkeypatch):
|
||||||
|
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||||
|
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
|
||||||
|
'strategy_path': default_location})
|
||||||
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
|
|
||||||
|
# Make sure we are using a legacy function
|
||||||
|
assert resolver.strategy._populate_fun_len == 2
|
||||||
|
assert resolver.strategy._buy_fun_len == 2
|
||||||
|
assert resolver.strategy._sell_fun_len == 2
|
||||||
|
|
||||||
|
indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata)
|
||||||
|
assert type(indicator_df) is DataFrame
|
||||||
|
assert 'adx' in indicator_df.columns
|
||||||
|
|
||||||
|
buydf = resolver.strategy.advise_buy(result, metadata=metadata)
|
||||||
|
assert type(buydf) is DataFrame
|
||||||
|
assert 'buy' in buydf.columns
|
||||||
|
|
||||||
|
selldf = resolver.strategy.advise_sell(result, metadata=metadata)
|
||||||
|
assert type(selldf) is DataFrame
|
||||||
|
assert 'sell' in selldf
|
||||||
|
@ -11,7 +11,6 @@ import freqtrade.tests.conftest as tt # test tools
|
|||||||
|
|
||||||
def whitelist_conf():
|
def whitelist_conf():
|
||||||
config = tt.default_conf()
|
config = tt.default_conf()
|
||||||
|
|
||||||
config['stake_currency'] = 'BTC'
|
config['stake_currency'] = 'BTC'
|
||||||
config['exchange']['pair_whitelist'] = [
|
config['exchange']['pair_whitelist'] = [
|
||||||
'ETH/BTC',
|
'ETH/BTC',
|
||||||
@ -20,7 +19,6 @@ def whitelist_conf():
|
|||||||
'SWT/BTC',
|
'SWT/BTC',
|
||||||
'BCC/BTC'
|
'BCC/BTC'
|
||||||
]
|
]
|
||||||
|
|
||||||
config['exchange']['pair_blacklist'] = [
|
config['exchange']['pair_blacklist'] = [
|
||||||
'BLK/BTC'
|
'BLK/BTC'
|
||||||
]
|
]
|
||||||
|
@ -11,23 +11,11 @@ import pytest
|
|||||||
from freqtrade.arguments import Arguments, TimeRange
|
from freqtrade.arguments import Arguments, TimeRange
|
||||||
|
|
||||||
|
|
||||||
def test_arguments_object() -> None:
|
|
||||||
"""
|
|
||||||
Test the Arguments object has the mandatory methods
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
assert hasattr(Arguments, 'get_parsed_arg')
|
|
||||||
assert hasattr(Arguments, 'parse_args')
|
|
||||||
assert hasattr(Arguments, 'parse_timerange')
|
|
||||||
assert hasattr(Arguments, 'scripts_options')
|
|
||||||
|
|
||||||
|
|
||||||
# Parse common command-line-arguments. Used for all tools
|
# Parse common command-line-arguments. Used for all tools
|
||||||
def test_parse_args_none() -> None:
|
def test_parse_args_none() -> None:
|
||||||
arguments = Arguments([], '')
|
arguments = Arguments([], '')
|
||||||
assert isinstance(arguments, Arguments)
|
assert isinstance(arguments, Arguments)
|
||||||
assert isinstance(arguments.parser, argparse.ArgumentParser)
|
assert isinstance(arguments.parser, argparse.ArgumentParser)
|
||||||
assert isinstance(arguments.parser, argparse.ArgumentParser)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_defaults() -> None:
|
def test_parse_args_defaults() -> None:
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
# pragma pylint: disable=protected-access, invalid-name
|
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
|
||||||
|
|
||||||
"""
|
|
||||||
Unit test file for configuration.py
|
|
||||||
"""
|
|
||||||
import json
|
import json
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@ -19,23 +16,7 @@ from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
|
|||||||
from freqtrade.tests.conftest import log_has
|
from freqtrade.tests.conftest import log_has
|
||||||
|
|
||||||
|
|
||||||
def test_configuration_object() -> None:
|
|
||||||
"""
|
|
||||||
Test the Constants object has the mandatory Constants
|
|
||||||
"""
|
|
||||||
assert hasattr(Configuration, 'load_config')
|
|
||||||
assert hasattr(Configuration, '_load_config_file')
|
|
||||||
assert hasattr(Configuration, '_validate_config')
|
|
||||||
assert hasattr(Configuration, '_load_common_config')
|
|
||||||
assert hasattr(Configuration, '_load_backtesting_config')
|
|
||||||
assert hasattr(Configuration, '_load_hyperopt_config')
|
|
||||||
assert hasattr(Configuration, 'get_config')
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_invalid_pair(default_conf) -> None:
|
def test_load_config_invalid_pair(default_conf) -> None:
|
||||||
"""
|
|
||||||
Test the configuration validator with an invalid PAIR format
|
|
||||||
"""
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['exchange']['pair_whitelist'].append('ETH-BTC')
|
conf['exchange']['pair_whitelist'].append('ETH-BTC')
|
||||||
|
|
||||||
@ -45,9 +26,6 @@ def test_load_config_invalid_pair(default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_load_config_missing_attributes(default_conf) -> None:
|
def test_load_config_missing_attributes(default_conf) -> None:
|
||||||
"""
|
|
||||||
Test the configuration validator with a missing attribute
|
|
||||||
"""
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf.pop('exchange')
|
conf.pop('exchange')
|
||||||
|
|
||||||
@ -57,9 +35,6 @@ def test_load_config_missing_attributes(default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_load_config_incorrect_stake_amount(default_conf) -> None:
|
def test_load_config_incorrect_stake_amount(default_conf) -> None:
|
||||||
"""
|
|
||||||
Test the configuration validator with a missing attribute
|
|
||||||
"""
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['stake_amount'] = 'fake'
|
conf['stake_amount'] = 'fake'
|
||||||
|
|
||||||
@ -69,9 +44,6 @@ def test_load_config_incorrect_stake_amount(default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_load_config_file(default_conf, mocker, caplog) -> None:
|
def test_load_config_file(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test Configuration._load_config_file() method
|
|
||||||
"""
|
|
||||||
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(default_conf)
|
read_data=json.dumps(default_conf)
|
||||||
))
|
))
|
||||||
@ -85,9 +57,6 @@ def test_load_config_file(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
|
def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test Configuration._load_config_file() method
|
|
||||||
"""
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['max_open_trades'] = 0
|
conf['max_open_trades'] = 0
|
||||||
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
@ -100,9 +69,6 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_load_config_file_exception(mocker) -> None:
|
def test_load_config_file_exception(mocker) -> None:
|
||||||
"""
|
|
||||||
Test Configuration._load_config_file() method
|
|
||||||
"""
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.configuration.open',
|
'freqtrade.configuration.open',
|
||||||
MagicMock(side_effect=FileNotFoundError('File not found'))
|
MagicMock(side_effect=FileNotFoundError('File not found'))
|
||||||
@ -114,9 +80,6 @@ def test_load_config_file_exception(mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_load_config(default_conf, mocker) -> None:
|
def test_load_config(default_conf, mocker) -> None:
|
||||||
"""
|
|
||||||
Test Configuration.load_config() without any cli params
|
|
||||||
"""
|
|
||||||
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)
|
||||||
))
|
))
|
||||||
@ -131,13 +94,9 @@ def test_load_config(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_load_config_with_params(default_conf, mocker) -> None:
|
def test_load_config_with_params(default_conf, mocker) -> None:
|
||||||
"""
|
|
||||||
Test Configuration.load_config() with cli params used
|
|
||||||
"""
|
|
||||||
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)
|
||||||
))
|
))
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--dynamic-whitelist', '10',
|
'--dynamic-whitelist', '10',
|
||||||
'--strategy', 'TestStrategy',
|
'--strategy', 'TestStrategy',
|
||||||
@ -145,7 +104,6 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
|||||||
'--db-url', 'sqlite:///someurl',
|
'--db-url', 'sqlite:///someurl',
|
||||||
]
|
]
|
||||||
args = Arguments(arglist, '').get_parsed_arg()
|
args = Arguments(arglist, '').get_parsed_arg()
|
||||||
|
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
validated_conf = configuration.load_config()
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
@ -193,9 +151,6 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_load_custom_strategy(default_conf, mocker) -> None:
|
def test_load_custom_strategy(default_conf, mocker) -> None:
|
||||||
"""
|
|
||||||
Test Configuration.load_config() without any cli params
|
|
||||||
"""
|
|
||||||
custom_conf = deepcopy(default_conf)
|
custom_conf = deepcopy(default_conf)
|
||||||
custom_conf.update({
|
custom_conf.update({
|
||||||
'strategy': 'CustomStrategy',
|
'strategy': 'CustomStrategy',
|
||||||
@ -214,13 +169,9 @@ def test_load_custom_strategy(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_show_info(default_conf, mocker, caplog) -> None:
|
def test_show_info(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test Configuration.show_info()
|
|
||||||
"""
|
|
||||||
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)
|
||||||
))
|
))
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--dynamic-whitelist', '10',
|
'--dynamic-whitelist', '10',
|
||||||
'--strategy', 'TestStrategy',
|
'--strategy', 'TestStrategy',
|
||||||
@ -237,19 +188,14 @@ def test_show_info(default_conf, mocker, caplog) -> None:
|
|||||||
'(not applicable with Backtesting and Hyperopt)',
|
'(not applicable with Backtesting and Hyperopt)',
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
)
|
)
|
||||||
|
|
||||||
assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog.record_tuples)
|
assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog.record_tuples)
|
||||||
assert log_has('Dry run is enabled', caplog.record_tuples)
|
assert log_has('Dry run is enabled', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test setup_configuration() function
|
|
||||||
"""
|
|
||||||
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)
|
||||||
))
|
))
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
@ -287,9 +233,6 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
|||||||
|
|
||||||
|
|
||||||
def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None:
|
def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test setup_configuration() function
|
|
||||||
"""
|
|
||||||
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)
|
||||||
))
|
))
|
||||||
@ -355,19 +298,14 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
|||||||
|
|
||||||
|
|
||||||
def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
|
def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test setup_configuration() function
|
|
||||||
"""
|
|
||||||
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)
|
||||||
))
|
))
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--epochs', '10',
|
'--epochs', '10',
|
||||||
'--spaces', 'all',
|
'--spaces', 'all',
|
||||||
]
|
]
|
||||||
|
|
||||||
args = Arguments(arglist, '').get_parsed_arg()
|
args = Arguments(arglist, '').get_parsed_arg()
|
||||||
|
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
@ -410,10 +348,6 @@ def test_check_exchange(default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
|
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test Configuration.load_config() with cli params used
|
|
||||||
"""
|
|
||||||
|
|
||||||
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)))
|
||||||
# Prevent setting loggers
|
# Prevent setting loggers
|
||||||
@ -429,9 +363,6 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_set_loggers() -> None:
|
def test_set_loggers() -> None:
|
||||||
"""
|
|
||||||
Test set_loggers() update the logger level for third-party libraries
|
|
||||||
"""
|
|
||||||
# Reset Logging to Debug, otherwise this fails randomly as it's set globally
|
# Reset Logging to Debug, otherwise this fails randomly as it's set globally
|
||||||
logging.getLogger('requests').setLevel(logging.DEBUG)
|
logging.getLogger('requests').setLevel(logging.DEBUG)
|
||||||
logging.getLogger("urllib3").setLevel(logging.DEBUG)
|
logging.getLogger("urllib3").setLevel(logging.DEBUG)
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
"""
|
|
||||||
Unit test file for constants.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
from freqtrade import constants
|
|
||||||
|
|
||||||
|
|
||||||
def test_constant_object() -> None:
|
|
||||||
"""
|
|
||||||
Test the Constants object has the mandatory Constants
|
|
||||||
"""
|
|
||||||
assert hasattr(constants, 'CONF_SCHEMA')
|
|
||||||
assert hasattr(constants, 'DYNAMIC_WHITELIST')
|
|
||||||
assert hasattr(constants, 'PROCESS_THROTTLE_SECS')
|
|
||||||
assert hasattr(constants, 'TICKER_INTERVAL')
|
|
||||||
assert hasattr(constants, 'HYPEROPT_EPOCH')
|
|
||||||
assert hasattr(constants, 'RETRY_TIMEOUT')
|
|
||||||
assert hasattr(constants, 'DEFAULT_STRATEGY')
|
|
||||||
|
|
||||||
|
|
||||||
def test_conf_schema() -> None:
|
|
||||||
"""
|
|
||||||
Test the CONF_SCHEMA is from the right type
|
|
||||||
"""
|
|
||||||
assert isinstance(constants.CONF_SCHEMA, dict)
|
|
@ -14,7 +14,7 @@ def load_dataframe_pair(pairs, strategy):
|
|||||||
assert isinstance(pairs[0], str)
|
assert isinstance(pairs[0], str)
|
||||||
dataframe = ld[pairs[0]]
|
dataframe = ld[pairs[0]]
|
||||||
|
|
||||||
dataframe = strategy.analyze_ticker(dataframe)
|
dataframe = strategy.analyze_ticker(dataframe, pairs[0])
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,22 +62,6 @@ def patch_RPCManager(mocker) -> MagicMock:
|
|||||||
|
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
def test_freqtradebot_object() -> None:
|
|
||||||
"""
|
|
||||||
Test the FreqtradeBot object has the mandatory public methods
|
|
||||||
"""
|
|
||||||
assert hasattr(FreqtradeBot, 'worker')
|
|
||||||
assert hasattr(FreqtradeBot, 'cleanup')
|
|
||||||
assert hasattr(FreqtradeBot, 'create_trade')
|
|
||||||
assert hasattr(FreqtradeBot, 'get_target_bid')
|
|
||||||
assert hasattr(FreqtradeBot, 'process_maybe_execute_buy')
|
|
||||||
assert hasattr(FreqtradeBot, 'process_maybe_execute_sell')
|
|
||||||
assert hasattr(FreqtradeBot, 'handle_trade')
|
|
||||||
assert hasattr(FreqtradeBot, 'check_handle_timedout')
|
|
||||||
assert hasattr(FreqtradeBot, 'handle_timedout_limit_buy')
|
|
||||||
assert hasattr(FreqtradeBot, 'handle_timedout_limit_sell')
|
|
||||||
assert hasattr(FreqtradeBot, 'execute_sell')
|
|
||||||
|
|
||||||
|
|
||||||
def test_freqtradebot(mocker, default_conf) -> None:
|
def test_freqtradebot(mocker, default_conf) -> None:
|
||||||
"""
|
"""
|
||||||
@ -1814,7 +1798,71 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets
|
|||||||
}))
|
}))
|
||||||
# stop-loss not reached, adjusted stoploss
|
# stop-loss not reached, adjusted stoploss
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f'using positive stop loss mode: 0.01 since we have profit 0.26662643',
|
assert log_has(f'using positive stop loss mode: 0.01 with offset 0 '
|
||||||
|
f'since we have profit 0.2666%',
|
||||||
|
caplog.record_tuples)
|
||||||
|
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
||||||
|
assert trade.stop_loss == 0.0000138501
|
||||||
|
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
||||||
|
MagicMock(return_value={
|
||||||
|
'bid': buy_price + 0.000002,
|
||||||
|
'ask': buy_price + 0.000002,
|
||||||
|
'last': buy_price + 0.000002
|
||||||
|
}))
|
||||||
|
# Lower price again (but still positive)
|
||||||
|
assert freqtrade.handle_trade(trade) is True
|
||||||
|
assert log_has(
|
||||||
|
f'HIT STOP: current price at {buy_price + 0.000002:.6f}, '
|
||||||
|
f'stop loss is {trade.stop_loss:.6f}, '
|
||||||
|
f'initial stop loss was at 0.000010, trade opened at 0.000011', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
|
def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, caplog, mocker) -> None:
|
||||||
|
"""
|
||||||
|
Test sell_profit_only feature when enabled and we have a loss
|
||||||
|
"""
|
||||||
|
buy_price = limit_buy_order['price']
|
||||||
|
patch_RPCManager(mocker)
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
validate_pairs=MagicMock(),
|
||||||
|
get_ticker=MagicMock(return_value={
|
||||||
|
'bid': buy_price - 0.000001,
|
||||||
|
'ask': buy_price - 0.000001,
|
||||||
|
'last': buy_price - 0.000001
|
||||||
|
}),
|
||||||
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
|
get_fee=fee,
|
||||||
|
)
|
||||||
|
|
||||||
|
conf = deepcopy(default_conf)
|
||||||
|
conf['trailing_stop'] = True
|
||||||
|
conf['trailing_stop_positive'] = 0.01
|
||||||
|
conf['trailing_stop_positive_offset'] = 0.011
|
||||||
|
freqtrade = FreqtradeBot(conf)
|
||||||
|
patch_get_signal(freqtrade)
|
||||||
|
freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False
|
||||||
|
freqtrade.create_trade()
|
||||||
|
|
||||||
|
trade = Trade.query.first()
|
||||||
|
trade.update(limit_buy_order)
|
||||||
|
caplog.set_level(logging.DEBUG)
|
||||||
|
# stop-loss not reached
|
||||||
|
assert freqtrade.handle_trade(trade) is False
|
||||||
|
|
||||||
|
# Raise ticker above buy price
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
||||||
|
MagicMock(return_value={
|
||||||
|
'bid': buy_price + 0.000003,
|
||||||
|
'ask': buy_price + 0.000003,
|
||||||
|
'last': buy_price + 0.000003
|
||||||
|
}))
|
||||||
|
# stop-loss not reached, adjusted stoploss
|
||||||
|
assert freqtrade.handle_trade(trade) is False
|
||||||
|
assert log_has(f'using positive stop loss mode: 0.01 with offset 0.011 '
|
||||||
|
f'since we have profit 0.2666%',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
assert log_has(f'adjusted stop loss', caplog.record_tuples)
|
||||||
assert trade.stop_loss == 0.0000138501
|
assert trade.stop_loss == 0.0000138501
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
# pragma pylint: disable=missing-docstring
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from freqtrade.indicator_helpers import went_down, went_up
|
from freqtrade.indicator_helpers import went_down, went_up
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
# pragma pylint: disable=missing-docstring
|
||||||
Unit test file for main.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
@ -33,9 +31,6 @@ def test_parse_args_backtesting(mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_main_start_hyperopt(mocker) -> None:
|
def test_main_start_hyperopt(mocker) -> None:
|
||||||
"""
|
|
||||||
Test that main() can start hyperopt
|
|
||||||
"""
|
|
||||||
hyperopt_mock = mocker.patch('freqtrade.optimize.hyperopt.start', MagicMock())
|
hyperopt_mock = mocker.patch('freqtrade.optimize.hyperopt.start', MagicMock())
|
||||||
main(['hyperopt'])
|
main(['hyperopt'])
|
||||||
assert hyperopt_mock.call_count == 1
|
assert hyperopt_mock.call_count == 1
|
||||||
@ -47,10 +42,6 @@ def test_main_start_hyperopt(mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
|
def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test main() function
|
|
||||||
In this test we are skipping the while True loop by throwing an exception.
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.freqtradebot.FreqtradeBot',
|
'freqtrade.freqtradebot.FreqtradeBot',
|
||||||
@ -74,10 +65,6 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
|
def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test main() function
|
|
||||||
In this test we are skipping the while True loop by throwing an exception.
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.freqtradebot.FreqtradeBot',
|
'freqtrade.freqtradebot.FreqtradeBot',
|
||||||
@ -101,10 +88,6 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_main_operational_exception(mocker, default_conf, caplog) -> None:
|
def test_main_operational_exception(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test main() function
|
|
||||||
In this test we are skipping the while True loop by throwing an exception.
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.freqtradebot.FreqtradeBot',
|
'freqtrade.freqtradebot.FreqtradeBot',
|
||||||
@ -128,10 +111,6 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_main_reload_conf(mocker, default_conf, caplog) -> None:
|
def test_main_reload_conf(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test main() function
|
|
||||||
In this test we are skipping the while True loop by throwing an exception.
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.freqtradebot.FreqtradeBot',
|
'freqtrade.freqtradebot.FreqtradeBot',
|
||||||
@ -158,7 +137,6 @@ 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 """
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.freqtradebot.FreqtradeBot',
|
'freqtrade.freqtradebot.FreqtradeBot',
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
# pragma pylint: disable=missing-docstring,C0103
|
# pragma pylint: disable=missing-docstring,C0103
|
||||||
|
|
||||||
"""
|
|
||||||
Unit test file for misc.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
@ -15,20 +11,12 @@ from freqtrade.strategy.default_strategy import DefaultStrategy
|
|||||||
|
|
||||||
|
|
||||||
def test_shorten_date() -> None:
|
def test_shorten_date() -> None:
|
||||||
"""
|
|
||||||
Test shorten_date() function
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
str_data = '1 day, 2 hours, 3 minutes, 4 seconds ago'
|
str_data = '1 day, 2 hours, 3 minutes, 4 seconds ago'
|
||||||
str_shorten_data = '1 d, 2 h, 3 min, 4 sec ago'
|
str_shorten_data = '1 d, 2 h, 3 min, 4 sec ago'
|
||||||
assert shorten_date(str_data) == str_shorten_data
|
assert shorten_date(str_data) == str_shorten_data
|
||||||
|
|
||||||
|
|
||||||
def test_datesarray_to_datetimearray(ticker_history):
|
def test_datesarray_to_datetimearray(ticker_history):
|
||||||
"""
|
|
||||||
Test datesarray_to_datetimearray() function
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
dataframes = parse_ticker_dataframe(ticker_history)
|
dataframes = parse_ticker_dataframe(ticker_history)
|
||||||
dates = datesarray_to_datetimearray(dataframes['date'])
|
dates = datesarray_to_datetimearray(dataframes['date'])
|
||||||
|
|
||||||
@ -44,10 +32,6 @@ def test_datesarray_to_datetimearray(ticker_history):
|
|||||||
|
|
||||||
|
|
||||||
def test_common_datearray(default_conf) -> None:
|
def test_common_datearray(default_conf) -> None:
|
||||||
"""
|
|
||||||
Test common_datearray()
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
strategy = DefaultStrategy(default_conf)
|
strategy = DefaultStrategy(default_conf)
|
||||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||||
tickerlist = {'UNITTEST/BTC': tick}
|
tickerlist = {'UNITTEST/BTC': tick}
|
||||||
@ -61,10 +45,6 @@ def test_common_datearray(default_conf) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_file_dump_json(mocker) -> None:
|
def test_file_dump_json(mocker) -> None:
|
||||||
"""
|
|
||||||
Test file_dump_json()
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
file_open = mocker.patch('freqtrade.misc.open', MagicMock())
|
file_open = mocker.patch('freqtrade.misc.open', MagicMock())
|
||||||
json_dump = mocker.patch('json.dump', MagicMock())
|
json_dump = mocker.patch('json.dump', MagicMock())
|
||||||
file_dump_json('somefile', [1, 2, 3])
|
file_dump_json('somefile', [1, 2, 3])
|
||||||
@ -78,10 +58,6 @@ def test_file_dump_json(mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_format_ms_time() -> None:
|
def test_format_ms_time() -> None:
|
||||||
"""
|
|
||||||
test format_ms_time()
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
# Date 2018-04-10 18:02:01
|
# Date 2018-04-10 18:02:01
|
||||||
date_in_epoch_ms = 1523383321000
|
date_in_epoch_ms = 1523383321000
|
||||||
date = format_ms_time(date_in_epoch_ms)
|
date = format_ms_time(date_in_epoch_ms)
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
"""
|
|
||||||
Unit test file for constants.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
from freqtrade.state import State
|
|
||||||
|
|
||||||
|
|
||||||
def test_state_object() -> None:
|
|
||||||
"""
|
|
||||||
Test the State object has the mandatory states
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
assert hasattr(State, 'RUNNING')
|
|
||||||
assert hasattr(State, 'STOPPED')
|
|
@ -1,4 +1,4 @@
|
|||||||
ccxt==1.17.39
|
ccxt==1.17.49
|
||||||
SQLAlchemy==1.2.10
|
SQLAlchemy==1.2.10
|
||||||
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.15.0
|
numpy==1.15.0
|
||||||
TA-Lib==0.4.17
|
TA-Lib==0.4.17
|
||||||
pytest==3.6.3
|
pytest==3.6.4
|
||||||
pytest-mock==1.10.0
|
pytest-mock==1.10.0
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.5.1
|
||||||
tabulate==0.8.2
|
tabulate==0.8.2
|
||||||
|
@ -159,8 +159,8 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
|
|||||||
dataframes = strategy.tickerdata_to_dataframe(tickers)
|
dataframes = strategy.tickerdata_to_dataframe(tickers)
|
||||||
|
|
||||||
dataframe = dataframes[pair]
|
dataframe = dataframes[pair]
|
||||||
dataframe = strategy.populate_buy_trend(dataframe)
|
dataframe = strategy.advise_buy(dataframe, {'pair': pair})
|
||||||
dataframe = strategy.populate_sell_trend(dataframe)
|
dataframe = strategy.advise_sell(dataframe, {'pair': pair})
|
||||||
|
|
||||||
if len(dataframe.index) > args.plot_limit:
|
if len(dataframe.index) > args.plot_limit:
|
||||||
logger.warning('Ticker contained more than %s candles as defined '
|
logger.warning('Ticker contained more than %s candles as defined '
|
||||||
|
@ -18,6 +18,7 @@ class TestStrategy(IStrategy):
|
|||||||
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
|
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
|
||||||
|
|
||||||
You can:
|
You can:
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
- Rename the class name (Do not forget to update class_name)
|
- Rename the class name (Do not forget to update class_name)
|
||||||
- Add any methods you want to build your strategy
|
- Add any methods you want to build your strategy
|
||||||
- Add any lib you need to build your strategy
|
- Add any lib you need to build your strategy
|
||||||
@ -44,13 +45,16 @@ class TestStrategy(IStrategy):
|
|||||||
# Optimal ticker interval for the strategy
|
# Optimal ticker interval for the strategy
|
||||||
ticker_interval = '5m'
|
ticker_interval = '5m'
|
||||||
|
|
||||||
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Adds several different TA indicators to the given DataFrame
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
Performance Note: For the best performance be frugal on the number of indicators
|
Performance Note: For the best performance be frugal on the number of indicators
|
||||||
you are using. Let uncomment only the indicator you are using in your strategies
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
|
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Momentum Indicator
|
# Momentum Indicator
|
||||||
@ -211,10 +215,11 @@ class TestStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame populated with indicators
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
@ -227,10 +232,11 @@ class TestStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame populated with indicators
|
||||||
|
:param metadata: Additional information, like the currently traded pair
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
|
Loading…
Reference in New Issue
Block a user