Merge branch 'develop' into ccxt-async
This commit is contained in:
commit
154e4569d7
@ -15,7 +15,8 @@ WORKDIR /freqtrade
|
|||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
COPY requirements.txt /freqtrade/
|
COPY requirements.txt /freqtrade/
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install numpy \
|
||||||
|
&& pip install -r requirements.txt
|
||||||
|
|
||||||
# Install and execute
|
# Install and execute
|
||||||
COPY . /freqtrade/
|
COPY . /freqtrade/
|
||||||
|
@ -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)
|
||||||
@ -61,6 +62,7 @@ hesitate to read the source code and understand the mechanism of this bot.
|
|||||||
- [Requirements](#requirements)
|
- [Requirements](#requirements)
|
||||||
- [Min hardware required](#min-hardware-required)
|
- [Min hardware required](#min-hardware-required)
|
||||||
- [Software requirements](#software-requirements)
|
- [Software requirements](#software-requirements)
|
||||||
|
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
@ -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).
|
||||||
|
@ -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))
|
||||||
|
@ -56,23 +56,29 @@ Reset parameter will hard reset your branch (only if you are on `master` or `dev
|
|||||||
|
|
||||||
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
|
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
|
||||||
|
|
||||||
|
|
||||||
## Manual installation - Linux/MacOS
|
## Manual installation - Linux/MacOS
|
||||||
|
|
||||||
The following steps are made for Linux/MacOS environment
|
The following steps are made for Linux/MacOS environment
|
||||||
|
|
||||||
**1. Clone the repo**
|
### 1. Clone the repo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:freqtrade/freqtrade.git
|
git clone git@github.com:freqtrade/freqtrade.git
|
||||||
git checkout develop
|
git checkout develop
|
||||||
cd freqtrade
|
cd freqtrade
|
||||||
```
|
```
|
||||||
**2. Create the config file**
|
|
||||||
|
### 2. Create the config file
|
||||||
|
|
||||||
Switch `"dry_run": true,`
|
Switch `"dry_run": true,`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp config.json.example config.json
|
cp config.json.example config.json
|
||||||
vi config.json
|
vi config.json
|
||||||
```
|
```
|
||||||
**3. Build your docker image and run it**
|
|
||||||
|
### 3. Build your docker image and run it
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t freqtrade .
|
docker build -t freqtrade .
|
||||||
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
||||||
|
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
|
||||||
|
```
|
@ -125,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 ccxt.async_support as ccxt_async
|
import ccxt.async_support as ccxt_async
|
||||||
@ -100,6 +101,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
|
||||||
@ -112,6 +115,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.
|
||||||
@ -155,6 +168,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)}'
|
||||||
@ -172,6 +207,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(
|
||||||
@ -205,6 +244,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(
|
||||||
|
@ -57,8 +57,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'] = ''
|
||||||
@ -229,8 +229,8 @@ class Backtesting(object):
|
|||||||
for pair, pair_data in processed.items():
|
for pair, pair_data in processed.items():
|
||||||
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',
|
||||||
@ -263,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)
|
||||||
|
@ -8,10 +8,8 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import pytest
|
import pytest
|
||||||
from jsonschema import validate
|
|
||||||
from telegram import Chat, Message, Update
|
from telegram import Chat, Message, Update
|
||||||
|
|
||||||
from freqtrade import constants
|
|
||||||
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
@ -127,7 +125,6 @@ def default_conf():
|
|||||||
"db_url": "sqlite://",
|
"db_url": "sqlite://",
|
||||||
"loglevel": logging.DEBUG,
|
"loglevel": logging.DEBUG,
|
||||||
}
|
}
|
||||||
validate(configuration, constants.CONF_SCHEMA)
|
|
||||||
return configuration
|
return configuration
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
|
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
|
||||||
# pragma pylint: disable=protected-access
|
# pragma pylint: disable=protected-access
|
||||||
import logging
|
import logging
|
||||||
from copy import deepcopy
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from random import randint
|
from random import randint
|
||||||
from unittest.mock import MagicMock, PropertyMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
@ -15,8 +14,6 @@ from freqtrade.tests.conftest import get_patched_exchange, log_has
|
|||||||
|
|
||||||
|
|
||||||
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs):
|
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs):
|
||||||
"""Function to test ccxt exception handling """
|
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError)
|
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
@ -52,6 +49,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={
|
||||||
@ -80,12 +164,11 @@ def test_validate_pairs_not_compatible(default_conf, mocker):
|
|||||||
api_mock.load_markets = MagicMock(return_value={
|
api_mock.load_markets = MagicMock(return_value={
|
||||||
'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': ''
|
'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': ''
|
||||||
})
|
})
|
||||||
conf = deepcopy(default_conf)
|
default_conf['stake_currency'] = 'ETH'
|
||||||
conf['stake_currency'] = 'ETH'
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||||
with pytest.raises(OperationalException, match=r'not compatible'):
|
with pytest.raises(OperationalException, match=r'not compatible'):
|
||||||
Exchange(conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_pairs_exception(default_conf, mocker, caplog):
|
def test_validate_pairs_exception(default_conf, mocker, caplog):
|
||||||
@ -110,8 +193,7 @@ def test_validate_pairs_exception(default_conf, mocker, caplog):
|
|||||||
|
|
||||||
def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
|
def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
conf = deepcopy(default_conf)
|
default_conf['stake_currency'] = 'ETH'
|
||||||
conf['stake_currency'] = 'ETH'
|
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.name = MagicMock(return_value='binance')
|
api_mock.name = MagicMock(return_value='binance')
|
||||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
|
||||||
@ -121,7 +203,7 @@ def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
|
|||||||
OperationalException,
|
OperationalException,
|
||||||
match=r'Pair ETH/BTC not compatible with stake_currency: ETH'
|
match=r'Pair ETH/BTC not compatible with stake_currency: ETH'
|
||||||
):
|
):
|
||||||
Exchange(conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_timeframes(default_conf, mocker):
|
def test_validate_timeframes(default_conf, mocker):
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
from copy import deepcopy
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
@ -146,7 +145,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)
|
||||||
@ -164,9 +163,6 @@ def _trend_alternate(dataframe=None):
|
|||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
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)
|
||||||
))
|
))
|
||||||
@ -205,9 +201,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)
|
||||||
))
|
))
|
||||||
@ -276,15 +269,10 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
|||||||
|
|
||||||
|
|
||||||
def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
|
def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||||
Test setup_configuration() function
|
|
||||||
"""
|
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
|
||||||
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
|
||||||
|
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(conf)
|
read_data=json.dumps(default_conf)
|
||||||
))
|
))
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
@ -298,9 +286,6 @@ def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog
|
|||||||
|
|
||||||
|
|
||||||
def test_start(mocker, fee, default_conf, caplog) -> None:
|
def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test start() function
|
|
||||||
"""
|
|
||||||
start_mock = MagicMock()
|
start_mock = MagicMock()
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
@ -323,25 +308,19 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_backtesting_init(mocker, default_conf) -> None:
|
def test_backtesting_init(mocker, default_conf) -> None:
|
||||||
"""
|
|
||||||
Test Backtesting._init() method
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
assert backtesting.config == default_conf
|
assert backtesting.config == default_conf
|
||||||
assert 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
|
||||||
|
|
||||||
|
|
||||||
def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
|
def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
|
||||||
"""
|
|
||||||
Test Backtesting.tickerdata_to_dataframe() method
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
timerange = TimeRange(None, 'line', 0, -100)
|
timerange = TimeRange(None, 'line', 0, -100)
|
||||||
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
|
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||||
@ -358,9 +337,6 @@ def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_get_timeframe(default_conf, mocker) -> None:
|
def test_get_timeframe(default_conf, mocker) -> None:
|
||||||
"""
|
|
||||||
Test Backtesting.get_timeframe() method
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
@ -377,9 +353,6 @@ def test_get_timeframe(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_generate_text_table(default_conf, mocker):
|
def test_generate_text_table(default_conf, mocker):
|
||||||
"""
|
|
||||||
Test Backtesting.generate_text_table() method
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
@ -408,9 +381,6 @@ def test_generate_text_table(default_conf, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_generate_text_table_sell_reason(default_conf, mocker):
|
def test_generate_text_table_sell_reason(default_conf, mocker):
|
||||||
"""
|
|
||||||
Test Backtesting.generate_text_table_sell_reason() method
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
@ -437,10 +407,6 @@ def test_generate_text_table_sell_reason(default_conf, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test Backtesting.start() method
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_timeframe(input1, input2):
|
def get_timeframe(input1, input2):
|
||||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||||
|
|
||||||
@ -454,15 +420,14 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
|||||||
get_timeframe=get_timeframe,
|
get_timeframe=get_timeframe,
|
||||||
)
|
)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||||
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
default_conf['ticker_interval'] = 1
|
||||||
conf['ticker_interval'] = 1
|
default_conf['live'] = False
|
||||||
conf['live'] = False
|
default_conf['datadir'] = None
|
||||||
conf['datadir'] = None
|
default_conf['export'] = None
|
||||||
conf['export'] = None
|
default_conf['timerange'] = '-100'
|
||||||
conf['timerange'] = '-100'
|
|
||||||
|
|
||||||
backtesting = Backtesting(conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.start()
|
backtesting.start()
|
||||||
# check the logs, that will contain the backtest result
|
# check the logs, that will contain the backtest result
|
||||||
exists = [
|
exists = [
|
||||||
@ -477,10 +442,6 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None:
|
def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None:
|
||||||
"""
|
|
||||||
Test Backtesting.start() method if no data is found
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_timeframe(input1, input2):
|
def get_timeframe(input1, input2):
|
||||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||||
|
|
||||||
@ -494,15 +455,14 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None:
|
|||||||
get_timeframe=get_timeframe,
|
get_timeframe=get_timeframe,
|
||||||
)
|
)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||||
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
default_conf['ticker_interval'] = "1m"
|
||||||
conf['ticker_interval'] = "1m"
|
default_conf['live'] = False
|
||||||
conf['live'] = False
|
default_conf['datadir'] = None
|
||||||
conf['datadir'] = None
|
default_conf['export'] = None
|
||||||
conf['export'] = None
|
default_conf['timerange'] = '20180101-20180102'
|
||||||
conf['timerange'] = '20180101-20180102'
|
|
||||||
|
|
||||||
backtesting = Backtesting(conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.start()
|
backtesting.start()
|
||||||
# check the logs, that will contain the backtest result
|
# check the logs, that will contain the backtest result
|
||||||
|
|
||||||
@ -510,9 +470,6 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_backtest(default_conf, fee, mocker) -> None:
|
def test_backtest(default_conf, fee, mocker) -> None:
|
||||||
"""
|
|
||||||
Test Backtesting.backtest() method
|
|
||||||
"""
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
@ -560,9 +517,6 @@ def test_backtest(default_conf, fee, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
|
def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
|
||||||
"""
|
|
||||||
Test Backtesting.backtest() method with 1 min ticker
|
|
||||||
"""
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
@ -583,9 +537,6 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_processed(default_conf, mocker) -> None:
|
def test_processed(default_conf, mocker) -> None:
|
||||||
"""
|
|
||||||
Test Backtesting.backtest() method with offline data
|
|
||||||
"""
|
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
@ -611,42 +562,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 +606,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
|
||||||
@ -725,15 +676,14 @@ def test_backtest_record(default_conf, fee, mocker):
|
|||||||
|
|
||||||
|
|
||||||
def test_backtest_start_live(default_conf, mocker, caplog):
|
def test_backtest_start_live(default_conf, mocker, caplog):
|
||||||
conf = deepcopy(default_conf)
|
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||||
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history',
|
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history',
|
||||||
new=lambda s, n, i: _load_pair_as_ticks(n, i))
|
new=lambda s, n, i: _load_pair_as_ticks(n, i))
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
|
||||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', MagicMock())
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', MagicMock())
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(conf)
|
read_data=json.dumps(default_conf)
|
||||||
))
|
))
|
||||||
|
|
||||||
args = MagicMock()
|
args = MagicMock()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# pragma pylint: disable=missing-docstring,W0212,C0103
|
# pragma pylint: disable=missing-docstring,W0212,C0103
|
||||||
import os
|
import os
|
||||||
from copy import deepcopy
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -12,29 +11,22 @@ from freqtrade.strategy.resolver import StrategyResolver
|
|||||||
from freqtrade.tests.conftest import log_has, patch_exchange
|
from freqtrade.tests.conftest import log_has, patch_exchange
|
||||||
from freqtrade.tests.optimize.test_backtesting import get_args
|
from freqtrade.tests.optimize.test_backtesting import get_args
|
||||||
|
|
||||||
# Avoid to reinit the same object again and again
|
|
||||||
_HYPEROPT_INITIALIZED = False
|
|
||||||
_HYPEROPT = None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def init_hyperopt(default_conf, mocker):
|
def hyperopt(default_conf, mocker):
|
||||||
global _HYPEROPT_INITIALIZED, _HYPEROPT
|
patch_exchange(mocker)
|
||||||
if not _HYPEROPT_INITIALIZED:
|
return Hyperopt(default_conf)
|
||||||
patch_exchange(mocker)
|
|
||||||
_HYPEROPT = Hyperopt(default_conf)
|
|
||||||
_HYPEROPT_INITIALIZED = True
|
|
||||||
|
|
||||||
|
|
||||||
# Functions for recurrent object patching
|
# Functions for recurrent object patching
|
||||||
def create_trials(mocker) -> None:
|
def create_trials(mocker, hyperopt) -> None:
|
||||||
"""
|
"""
|
||||||
When creating trials, mock the hyperopt Trials so that *by default*
|
When creating trials, mock the hyperopt Trials so that *by default*
|
||||||
- we don't create any pickle'd files in the filesystem
|
- we don't create any pickle'd files in the filesystem
|
||||||
- we might have a pickle'd file so make sure that we return
|
- we might have a pickle'd file so make sure that we return
|
||||||
false when looking for it
|
false when looking for it
|
||||||
"""
|
"""
|
||||||
_HYPEROPT.trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
hyperopt.trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
||||||
|
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.os.path.exists', return_value=False)
|
mocker.patch('freqtrade.optimize.hyperopt.os.path.exists', return_value=False)
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.os.path.getsize', return_value=1)
|
mocker.patch('freqtrade.optimize.hyperopt.os.path.getsize', return_value=1)
|
||||||
@ -45,9 +37,6 @@ def create_trials(mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_start(mocker, default_conf, caplog) -> None:
|
def test_start(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
|
||||||
Test start() function
|
|
||||||
"""
|
|
||||||
start_mock = MagicMock()
|
start_mock = MagicMock()
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.configuration.Configuration._load_config_file',
|
'freqtrade.configuration.Configuration._load_config_file',
|
||||||
@ -76,8 +65,7 @@ def test_start(mocker, default_conf, caplog) -> None:
|
|||||||
assert start_mock.call_count == 1
|
assert start_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
|
def test_loss_calculation_prefer_correct_trade_count(hyperopt) -> None:
|
||||||
hyperopt = _HYPEROPT
|
|
||||||
StrategyResolver({'strategy': 'DefaultStrategy'})
|
StrategyResolver({'strategy': 'DefaultStrategy'})
|
||||||
|
|
||||||
correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20)
|
correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20)
|
||||||
@ -87,17 +75,13 @@ def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
|
|||||||
assert under > correct
|
assert under > correct
|
||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_prefer_shorter_trades(init_hyperopt) -> None:
|
def test_loss_calculation_prefer_shorter_trades(hyperopt) -> None:
|
||||||
hyperopt = _HYPEROPT
|
|
||||||
|
|
||||||
shorter = hyperopt.calculate_loss(1, 100, 20)
|
shorter = hyperopt.calculate_loss(1, 100, 20)
|
||||||
longer = hyperopt.calculate_loss(1, 100, 30)
|
longer = hyperopt.calculate_loss(1, 100, 30)
|
||||||
assert shorter < longer
|
assert shorter < longer
|
||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_has_limited_profit(init_hyperopt) -> None:
|
def test_loss_calculation_has_limited_profit(hyperopt) -> None:
|
||||||
hyperopt = _HYPEROPT
|
|
||||||
|
|
||||||
correct = hyperopt.calculate_loss(hyperopt.expected_max_profit, hyperopt.target_trades, 20)
|
correct = hyperopt.calculate_loss(hyperopt.expected_max_profit, hyperopt.target_trades, 20)
|
||||||
over = hyperopt.calculate_loss(hyperopt.expected_max_profit * 2, hyperopt.target_trades, 20)
|
over = hyperopt.calculate_loss(hyperopt.expected_max_profit * 2, hyperopt.target_trades, 20)
|
||||||
under = hyperopt.calculate_loss(hyperopt.expected_max_profit / 2, hyperopt.target_trades, 20)
|
under = hyperopt.calculate_loss(hyperopt.expected_max_profit / 2, hyperopt.target_trades, 20)
|
||||||
@ -105,8 +89,7 @@ def test_loss_calculation_has_limited_profit(init_hyperopt) -> None:
|
|||||||
assert under > correct
|
assert under > correct
|
||||||
|
|
||||||
|
|
||||||
def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
|
def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
|
||||||
hyperopt = _HYPEROPT
|
|
||||||
hyperopt.current_best_loss = 2
|
hyperopt.current_best_loss = 2
|
||||||
hyperopt.log_results(
|
hyperopt.log_results(
|
||||||
{
|
{
|
||||||
@ -117,11 +100,10 @@ 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(hyperopt, caplog) -> None:
|
||||||
hyperopt = _HYPEROPT
|
|
||||||
hyperopt.current_best_loss = 2
|
hyperopt.current_best_loss = 2
|
||||||
hyperopt.log_results(
|
hyperopt.log_results(
|
||||||
{
|
{
|
||||||
@ -131,13 +113,10 @@ def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
|
|||||||
assert caplog.record_tuples == []
|
assert caplog.record_tuples == []
|
||||||
|
|
||||||
|
|
||||||
def test_save_trials_saves_trials(mocker, init_hyperopt, caplog) -> None:
|
def test_save_trials_saves_trials(mocker, hyperopt, caplog) -> None:
|
||||||
trials = create_trials(mocker)
|
trials = create_trials(mocker, hyperopt)
|
||||||
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
|
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
|
||||||
|
hyperopt.trials = trials
|
||||||
hyperopt = _HYPEROPT
|
|
||||||
_HYPEROPT.trials = trials
|
|
||||||
|
|
||||||
hyperopt.save_trials()
|
hyperopt.save_trials()
|
||||||
|
|
||||||
trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
||||||
@ -148,11 +127,9 @@ def test_save_trials_saves_trials(mocker, init_hyperopt, caplog) -> None:
|
|||||||
mock_dump.assert_called_once()
|
mock_dump.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_read_trials_returns_trials_file(mocker, init_hyperopt, caplog) -> None:
|
def test_read_trials_returns_trials_file(mocker, hyperopt, caplog) -> None:
|
||||||
trials = create_trials(mocker)
|
trials = create_trials(mocker, hyperopt)
|
||||||
mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=trials)
|
mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=trials)
|
||||||
|
|
||||||
hyperopt = _HYPEROPT
|
|
||||||
hyperopt_trial = hyperopt.read_trials()
|
hyperopt_trial = hyperopt.read_trials()
|
||||||
trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
||||||
assert log_has(
|
assert log_has(
|
||||||
@ -163,7 +140,7 @@ def test_read_trials_returns_trials_file(mocker, init_hyperopt, caplog) -> None:
|
|||||||
mock_load.assert_called_once()
|
mock_load.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_roi_table_generation(init_hyperopt) -> None:
|
def test_roi_table_generation(hyperopt) -> None:
|
||||||
params = {
|
params = {
|
||||||
'roi_t1': 5,
|
'roi_t1': 5,
|
||||||
'roi_t2': 10,
|
'roi_t2': 10,
|
||||||
@ -173,11 +150,10 @@ def test_roi_table_generation(init_hyperopt) -> None:
|
|||||||
'roi_p3': 3,
|
'roi_p3': 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
hyperopt = _HYPEROPT
|
|
||||||
assert hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
|
assert hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
|
||||||
|
|
||||||
|
|
||||||
def test_start_calls_optimizer(mocker, init_hyperopt, default_conf, caplog) -> None:
|
def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
|
||||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.multiprocessing.cpu_count', MagicMock(return_value=1))
|
mocker.patch('freqtrade.optimize.hyperopt.multiprocessing.cpu_count', MagicMock(return_value=1))
|
||||||
@ -187,13 +163,12 @@ def test_start_calls_optimizer(mocker, init_hyperopt, default_conf, caplog) -> N
|
|||||||
)
|
)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
default_conf.update({'config': 'config.json.example'})
|
||||||
conf.update({'config': 'config.json.example'})
|
default_conf.update({'epochs': 1})
|
||||||
conf.update({'epochs': 1})
|
default_conf.update({'timerange': None})
|
||||||
conf.update({'timerange': None})
|
default_conf.update({'spaces': 'all'})
|
||||||
conf.update({'spaces': 'all'})
|
|
||||||
|
|
||||||
hyperopt = Hyperopt(conf)
|
hyperopt = Hyperopt(default_conf)
|
||||||
hyperopt.tickerdata_to_dataframe = MagicMock()
|
hyperopt.tickerdata_to_dataframe = MagicMock()
|
||||||
|
|
||||||
hyperopt.start()
|
hyperopt.start()
|
||||||
@ -203,11 +178,7 @@ def test_start_calls_optimizer(mocker, init_hyperopt, default_conf, caplog) -> N
|
|||||||
assert dumper.called
|
assert dumper.called
|
||||||
|
|
||||||
|
|
||||||
def test_format_results(init_hyperopt):
|
def test_format_results(hyperopt):
|
||||||
"""
|
|
||||||
Test Hyperopt.format_results()
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Test with BTC as stake_currency
|
# Test with BTC as stake_currency
|
||||||
trades = [
|
trades = [
|
||||||
('ETH/BTC', 2, 2, 123),
|
('ETH/BTC', 2, 2, 123),
|
||||||
@ -217,7 +188,7 @@ def test_format_results(init_hyperopt):
|
|||||||
labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration']
|
labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration']
|
||||||
df = pd.DataFrame.from_records(trades, columns=labels)
|
df = pd.DataFrame.from_records(trades, columns=labels)
|
||||||
|
|
||||||
result = _HYPEROPT.format_results(df)
|
result = hyperopt.format_results(df)
|
||||||
assert result.find(' 66.67%')
|
assert result.find(' 66.67%')
|
||||||
assert result.find('Total profit 1.00000000 BTC')
|
assert result.find('Total profit 1.00000000 BTC')
|
||||||
assert result.find('2.0000Σ %')
|
assert result.find('2.0000Σ %')
|
||||||
@ -229,25 +200,25 @@ def test_format_results(init_hyperopt):
|
|||||||
('XPR/EUR', -1, -2, -246)
|
('XPR/EUR', -1, -2, -246)
|
||||||
]
|
]
|
||||||
df = pd.DataFrame.from_records(trades, columns=labels)
|
df = pd.DataFrame.from_records(trades, columns=labels)
|
||||||
result = _HYPEROPT.format_results(df)
|
result = hyperopt.format_results(df)
|
||||||
assert result.find('Total profit 1.00000000 EUR')
|
assert result.find('Total profit 1.00000000 EUR')
|
||||||
|
|
||||||
|
|
||||||
def test_has_space(init_hyperopt):
|
def test_has_space(hyperopt):
|
||||||
_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')
|
||||||
assert not _HYPEROPT.has_space('stoploss')
|
assert not hyperopt.has_space('stoploss')
|
||||||
|
|
||||||
_HYPEROPT.config.update({'spaces': ['all']})
|
hyperopt.config.update({'spaces': ['all']})
|
||||||
assert _HYPEROPT.has_space('buy')
|
assert hyperopt.has_space('buy')
|
||||||
|
|
||||||
|
|
||||||
def test_populate_indicators(init_hyperopt) -> None:
|
def test_populate_indicators(hyperopt) -> None:
|
||||||
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
|
||||||
@ -255,13 +226,13 @@ def test_populate_indicators(init_hyperopt) -> None:
|
|||||||
assert 'rsi' in dataframe
|
assert 'rsi' in dataframe
|
||||||
|
|
||||||
|
|
||||||
def test_buy_strategy_generator(init_hyperopt) -> None:
|
def test_buy_strategy_generator(hyperopt) -> None:
|
||||||
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(
|
||||||
{
|
{
|
||||||
'adx-value': 20,
|
'adx-value': 20,
|
||||||
'fastd-value': 20,
|
'fastd-value': 20,
|
||||||
@ -274,17 +245,16 @@ 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, default_conf) -> None:
|
||||||
conf = deepcopy(default_conf)
|
default_conf.update({'config': 'config.json.example'})
|
||||||
conf.update({'config': 'config.json.example'})
|
default_conf.update({'timerange': None})
|
||||||
conf.update({'timerange': None})
|
default_conf.update({'spaces': 'all'})
|
||||||
conf.update({'spaces': 'all'})
|
|
||||||
|
|
||||||
trades = [
|
trades = [
|
||||||
('POWR/BTC', 0.023117, 0.000233, 100)
|
('POWR/BTC', 0.023117, 0.000233, 100)
|
||||||
@ -324,6 +294,6 @@ def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
|
|||||||
'params': optimizer_param
|
'params': optimizer_param
|
||||||
}
|
}
|
||||||
|
|
||||||
hyperopt = Hyperopt(conf)
|
hyperopt = Hyperopt(default_conf)
|
||||||
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
|
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
|
||||||
assert generate_optimizer_value == response_expected
|
assert generate_optimizer_value == response_expected
|
||||||
|
@ -11,8 +11,8 @@ from freqtrade.freqtradebot import FreqtradeBot
|
|||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.rpc import RPC, RPCException
|
from freqtrade.rpc import RPC, RPCException
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
from freqtrade.tests.test_freqtradebot import (patch_coinmarketcap,
|
from freqtrade.tests.test_freqtradebot import patch_get_signal
|
||||||
patch_get_signal)
|
from freqtrade.tests.conftest import patch_coinmarketcap
|
||||||
|
|
||||||
|
|
||||||
# Functions for recurrent object patching
|
# Functions for recurrent object patching
|
||||||
@ -278,9 +278,6 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
|
|||||||
|
|
||||||
|
|
||||||
def test_rpc_balance_handle(default_conf, mocker):
|
def test_rpc_balance_handle(default_conf, mocker):
|
||||||
"""
|
|
||||||
Test rpc_balance() method
|
|
||||||
"""
|
|
||||||
mock_balance = {
|
mock_balance = {
|
||||||
'BTC': {
|
'BTC': {
|
||||||
'free': 10.0,
|
'free': 10.0,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from copy import deepcopy
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from freqtrade.rpc import RPCMessageType, RPCManager
|
from freqtrade.rpc import RPCMessageType, RPCManager
|
||||||
@ -9,18 +8,16 @@ from freqtrade.tests.conftest import log_has, get_patched_freqtradebot
|
|||||||
|
|
||||||
|
|
||||||
def test__init__(mocker, default_conf) -> None:
|
def test__init__(mocker, default_conf) -> None:
|
||||||
conf = deepcopy(default_conf)
|
default_conf['telegram']['enabled'] = False
|
||||||
conf['telegram']['enabled'] = False
|
|
||||||
|
|
||||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
|
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||||
assert rpc_manager.registered_modules == []
|
assert rpc_manager.registered_modules == []
|
||||||
|
|
||||||
|
|
||||||
def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
|
def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
conf = deepcopy(default_conf)
|
default_conf['telegram']['enabled'] = False
|
||||||
conf['telegram']['enabled'] = False
|
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_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)
|
||||||
assert rpc_manager.registered_modules == []
|
assert rpc_manager.registered_modules == []
|
||||||
@ -40,10 +37,9 @@ 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:
|
||||||
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)
|
default_conf['telegram']['enabled'] = False
|
||||||
conf['telegram']['enabled'] = False
|
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
rpc_manager = RPCManager(freqtradebot)
|
rpc_manager = RPCManager(freqtradebot)
|
||||||
rpc_manager.cleanup()
|
rpc_manager.cleanup()
|
||||||
|
|
||||||
@ -70,10 +66,9 @@ 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:
|
||||||
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)
|
default_conf['telegram']['enabled'] = False
|
||||||
conf['telegram']['enabled'] = False
|
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
rpc_manager = RPCManager(freqtradebot)
|
rpc_manager = RPCManager(freqtradebot)
|
||||||
rpc_manager.send_msg({
|
rpc_manager.send_msg({
|
||||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||||
@ -101,10 +96,9 @@ 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:
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
conf = deepcopy(default_conf)
|
default_conf['telegram']['enabled'] = False
|
||||||
conf['telegram']['enabled'] = False
|
default_conf['webhook'] = {'enabled': False}
|
||||||
conf['webhook'] = {'enabled': False}
|
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_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)
|
||||||
assert rpc_manager.registered_modules == []
|
assert rpc_manager.registered_modules == []
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
# pragma pylint: disable=too-many-lines, too-many-arguments
|
# pragma pylint: disable=too-many-lines, too-many-arguments
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from copy import deepcopy
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from random import randint
|
from random import randint
|
||||||
from unittest.mock import MagicMock, ANY
|
from unittest.mock import MagicMock, ANY
|
||||||
@ -21,8 +20,8 @@ from freqtrade.rpc.telegram import Telegram, authorized_only
|
|||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has,
|
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has,
|
||||||
patch_exchange)
|
patch_exchange)
|
||||||
from freqtrade.tests.test_freqtradebot import (patch_coinmarketcap,
|
from freqtrade.tests.test_freqtradebot import patch_get_signal
|
||||||
patch_get_signal)
|
from freqtrade.tests.conftest import patch_coinmarketcap
|
||||||
|
|
||||||
|
|
||||||
class DummyCls(Telegram):
|
class DummyCls(Telegram):
|
||||||
@ -96,9 +95,8 @@ def test_authorized_only(default_conf, mocker, caplog) -> None:
|
|||||||
update = Update(randint(1, 100))
|
update = Update(randint(1, 100))
|
||||||
update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
|
update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
default_conf['telegram']['enabled'] = False
|
||||||
conf['telegram']['enabled'] = False
|
bot = FreqtradeBot(default_conf)
|
||||||
bot = FreqtradeBot(conf)
|
|
||||||
patch_get_signal(bot, (True, False))
|
patch_get_signal(bot, (True, False))
|
||||||
dummy = DummyCls(bot)
|
dummy = DummyCls(bot)
|
||||||
dummy.dummy_handler(bot=MagicMock(), update=update)
|
dummy.dummy_handler(bot=MagicMock(), update=update)
|
||||||
@ -124,9 +122,8 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
|||||||
update = Update(randint(1, 100))
|
update = Update(randint(1, 100))
|
||||||
update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
|
update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
default_conf['telegram']['enabled'] = False
|
||||||
conf['telegram']['enabled'] = False
|
bot = FreqtradeBot(default_conf)
|
||||||
bot = FreqtradeBot(conf)
|
|
||||||
patch_get_signal(bot, (True, False))
|
patch_get_signal(bot, (True, False))
|
||||||
dummy = DummyCls(bot)
|
dummy = DummyCls(bot)
|
||||||
dummy.dummy_handler(bot=MagicMock(), update=update)
|
dummy.dummy_handler(bot=MagicMock(), update=update)
|
||||||
@ -152,10 +149,9 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
|||||||
update = Update(randint(1, 100))
|
update = Update(randint(1, 100))
|
||||||
update.message = Message(randint(1, 100), 0, datetime.utcnow(), Chat(0, 0))
|
update.message = Message(randint(1, 100), 0, datetime.utcnow(), Chat(0, 0))
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
default_conf['telegram']['enabled'] = False
|
||||||
conf['telegram']['enabled'] = False
|
|
||||||
|
|
||||||
bot = FreqtradeBot(conf)
|
bot = FreqtradeBot(default_conf)
|
||||||
patch_get_signal(bot, (True, False))
|
patch_get_signal(bot, (True, False))
|
||||||
dummy = DummyCls(bot)
|
dummy = DummyCls(bot)
|
||||||
|
|
||||||
@ -177,9 +173,8 @@ 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:
|
||||||
update.message.chat.id = 123
|
update.message.chat.id = 123
|
||||||
conf = deepcopy(default_conf)
|
default_conf['telegram']['enabled'] = False
|
||||||
conf['telegram']['enabled'] = False
|
default_conf['telegram']['chat_id'] = 123
|
||||||
conf['telegram']['chat_id'] = 123
|
|
||||||
|
|
||||||
patch_coinmarketcap(mocker)
|
patch_coinmarketcap(mocker)
|
||||||
|
|
||||||
@ -214,7 +209,7 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
|
|||||||
)
|
)
|
||||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||||
|
|
||||||
freqtradebot = FreqtradeBot(conf)
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
patch_get_signal(freqtradebot, (True, False))
|
patch_get_signal(freqtradebot, (True, False))
|
||||||
telegram = Telegram(freqtradebot)
|
telegram = Telegram(freqtradebot)
|
||||||
|
|
||||||
@ -294,9 +289,8 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
|
|||||||
)
|
)
|
||||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
default_conf['stake_amount'] = 15.0
|
||||||
conf['stake_amount'] = 15.0
|
freqtradebot = FreqtradeBot(default_conf)
|
||||||
freqtradebot = FreqtradeBot(conf)
|
|
||||||
patch_get_signal(freqtradebot, (True, False))
|
patch_get_signal(freqtradebot, (True, False))
|
||||||
|
|
||||||
telegram = Telegram(freqtradebot)
|
telegram = Telegram(freqtradebot)
|
||||||
@ -1181,9 +1175,8 @@ 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:
|
||||||
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)
|
|
||||||
bot = MagicMock()
|
bot = MagicMock()
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
telegram = Telegram(freqtradebot)
|
telegram = Telegram(freqtradebot)
|
||||||
|
|
||||||
telegram._config['telegram']['enabled'] = True
|
telegram._config['telegram']['enabled'] = True
|
||||||
@ -1194,10 +1187,9 @@ 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:
|
||||||
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)
|
|
||||||
bot = MagicMock()
|
bot = MagicMock()
|
||||||
bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
|
bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
telegram = Telegram(freqtradebot)
|
telegram = Telegram(freqtradebot)
|
||||||
|
|
||||||
telegram._config['telegram']['enabled'] = True
|
telegram._config['telegram']['enabled'] = True
|
||||||
|
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
|
||||||
|
@ -98,9 +98,6 @@ def test_get_signal_handles_exceptions(mocker, default_conf):
|
|||||||
|
|
||||||
|
|
||||||
def test_tickerdata_to_dataframe(default_conf) -> None:
|
def test_tickerdata_to_dataframe(default_conf) -> None:
|
||||||
"""
|
|
||||||
Test Analyze.tickerdata_to_dataframe() method
|
|
||||||
"""
|
|
||||||
strategy = DefaultStrategy(default_conf)
|
strategy = DefaultStrategy(default_conf)
|
||||||
|
|
||||||
timerange = TimeRange(None, 'line', 0, -100)
|
timerange = TimeRange(None, 'line', 0, -100)
|
||||||
|
@ -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,12 +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 'adx' in resolver.strategy.populate_indicators(result)
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
|
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 (
|
||||||
@ -70,7 +73,8 @@ def test_load_strategy_invalid_directory(result, caplog):
|
|||||||
logging.WARNING,
|
logging.WARNING,
|
||||||
'Path "{}" does not exist'.format(extra_dir),
|
'Path "{}" does not exist'.format(extra_dir),
|
||||||
) in caplog.record_tuples
|
) in caplog.record_tuples
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
|
||||||
|
assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||||
|
|
||||||
|
|
||||||
def test_load_not_found_strategy():
|
def test_load_not_found_strategy():
|
||||||
@ -85,7 +89,7 @@ def test_strategy(result):
|
|||||||
config = {'strategy': 'DefaultStrategy'}
|
config = {'strategy': 'DefaultStrategy'}
|
||||||
|
|
||||||
resolver = StrategyResolver(config)
|
resolver = StrategyResolver(config)
|
||||||
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
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
|
||||||
|
|
||||||
@ -95,12 +99,13 @@ def test_strategy(result):
|
|||||||
assert resolver.strategy.ticker_interval == '5m'
|
assert resolver.strategy.ticker_interval == '5m'
|
||||||
assert config['ticker_interval'] == '5m'
|
assert config['ticker_interval'] == '5m'
|
||||||
|
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
df_indicators = resolver.strategy.advise_indicators(result, metadata=metadata)
|
||||||
|
assert 'adx' in df_indicators
|
||||||
|
|
||||||
dataframe = resolver.strategy.populate_buy_trend(resolver.strategy.populate_indicators(result))
|
dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata)
|
||||||
assert 'buy' in dataframe.columns
|
assert 'buy' in dataframe.columns
|
||||||
|
|
||||||
dataframe = resolver.strategy.populate_sell_trend(resolver.strategy.populate_indicators(result))
|
dataframe = resolver.strategy.advise_sell(df_indicators, metadata=metadata)
|
||||||
assert 'sell' in dataframe.columns
|
assert 'sell' in dataframe.columns
|
||||||
|
|
||||||
|
|
||||||
@ -150,3 +155,59 @@ def test_strategy_override_ticker_interval(caplog):
|
|||||||
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
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
|
||||||
"""
|
|
||||||
Unit test file for arguments.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from copy import deepcopy
|
|
||||||
import logging
|
import logging
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from jsonschema import ValidationError
|
from jsonschema import validate, ValidationError
|
||||||
|
|
||||||
|
from freqtrade import constants
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.configuration import Configuration, set_loggers
|
from freqtrade.configuration import Configuration, set_loggers
|
||||||
@ -17,30 +17,27 @@ from freqtrade.tests.conftest import log_has
|
|||||||
|
|
||||||
|
|
||||||
def test_load_config_invalid_pair(default_conf) -> None:
|
def test_load_config_invalid_pair(default_conf) -> None:
|
||||||
conf = deepcopy(default_conf)
|
default_conf['exchange']['pair_whitelist'].append('ETH-BTC')
|
||||||
conf['exchange']['pair_whitelist'].append('ETH-BTC')
|
|
||||||
|
|
||||||
with pytest.raises(ValidationError, match=r'.*does not match.*'):
|
with pytest.raises(ValidationError, match=r'.*does not match.*'):
|
||||||
configuration = Configuration(Namespace())
|
configuration = Configuration(Namespace())
|
||||||
configuration._validate_config(conf)
|
configuration._validate_config(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_missing_attributes(default_conf) -> None:
|
def test_load_config_missing_attributes(default_conf) -> None:
|
||||||
conf = deepcopy(default_conf)
|
default_conf.pop('exchange')
|
||||||
conf.pop('exchange')
|
|
||||||
|
|
||||||
with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'):
|
with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'):
|
||||||
configuration = Configuration(Namespace())
|
configuration = Configuration(Namespace())
|
||||||
configuration._validate_config(conf)
|
configuration._validate_config(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_incorrect_stake_amount(default_conf) -> None:
|
def test_load_config_incorrect_stake_amount(default_conf) -> None:
|
||||||
conf = deepcopy(default_conf)
|
default_conf['stake_amount'] = 'fake'
|
||||||
conf['stake_amount'] = 'fake'
|
|
||||||
|
|
||||||
with pytest.raises(ValidationError, match=r'.*\'fake\' does not match \'unlimited\'.*'):
|
with pytest.raises(ValidationError, match=r'.*\'fake\' does not match \'unlimited\'.*'):
|
||||||
configuration = Configuration(Namespace())
|
configuration = Configuration(Namespace())
|
||||||
configuration._validate_config(conf)
|
configuration._validate_config(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_file(default_conf, mocker, caplog) -> None:
|
def test_load_config_file(default_conf, mocker, caplog) -> None:
|
||||||
@ -57,10 +54,9 @@ 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:
|
||||||
conf = deepcopy(default_conf)
|
default_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(
|
||||||
read_data=json.dumps(conf)
|
read_data=json.dumps(default_conf)
|
||||||
))
|
))
|
||||||
|
|
||||||
Configuration(Namespace())._load_config_file('somefile')
|
Configuration(Namespace())._load_config_file('somefile')
|
||||||
@ -151,13 +147,12 @@ 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:
|
||||||
custom_conf = deepcopy(default_conf)
|
default_conf.update({
|
||||||
custom_conf.update({
|
|
||||||
'strategy': 'CustomStrategy',
|
'strategy': 'CustomStrategy',
|
||||||
'strategy_path': '/tmp/strategies',
|
'strategy_path': '/tmp/strategies',
|
||||||
})
|
})
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(custom_conf)
|
read_data=json.dumps(default_conf)
|
||||||
))
|
))
|
||||||
|
|
||||||
args = Arguments([], '').get_parsed_arg()
|
args = Arguments([], '').get_parsed_arg()
|
||||||
@ -322,29 +317,25 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_check_exchange(default_conf) -> None:
|
def test_check_exchange(default_conf) -> None:
|
||||||
"""
|
|
||||||
Test the configuration validator with a missing attribute
|
|
||||||
"""
|
|
||||||
conf = deepcopy(default_conf)
|
|
||||||
configuration = Configuration(Namespace())
|
configuration = Configuration(Namespace())
|
||||||
|
|
||||||
# Test a valid exchange
|
# Test a valid exchange
|
||||||
conf.get('exchange').update({'name': 'BITTREX'})
|
default_conf.get('exchange').update({'name': 'BITTREX'})
|
||||||
assert configuration.check_exchange(conf)
|
assert configuration.check_exchange(default_conf)
|
||||||
|
|
||||||
# Test a valid exchange
|
# Test a valid exchange
|
||||||
conf.get('exchange').update({'name': 'binance'})
|
default_conf.get('exchange').update({'name': 'binance'})
|
||||||
assert configuration.check_exchange(conf)
|
assert configuration.check_exchange(default_conf)
|
||||||
|
|
||||||
# Test a invalid exchange
|
# Test a invalid exchange
|
||||||
conf.get('exchange').update({'name': 'unknown_exchange'})
|
default_conf.get('exchange').update({'name': 'unknown_exchange'})
|
||||||
configuration.config = conf
|
configuration.config = default_conf
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
OperationalException,
|
OperationalException,
|
||||||
match=r'.*Exchange "unknown_exchange" not supported.*'
|
match=r'.*Exchange "unknown_exchange" not supported.*'
|
||||||
):
|
):
|
||||||
configuration.check_exchange(conf)
|
configuration.check_exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
|
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
|
||||||
@ -398,3 +389,7 @@ def test_set_loggers() -> None:
|
|||||||
assert logging.getLogger('requests').level is logging.DEBUG
|
assert logging.getLogger('requests').level is logging.DEBUG
|
||||||
assert logging.getLogger('ccxt.base.exchange').level is logging.DEBUG
|
assert logging.getLogger('ccxt.base.exchange').level is logging.DEBUG
|
||||||
assert logging.getLogger('telegram').level is logging.INFO
|
assert logging.getLogger('telegram').level is logging.INFO
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_default_conf(default_conf) -> None:
|
||||||
|
validate(default_conf, constants.CONF_SCHEMA)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,4 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
from copy import deepcopy
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -23,46 +22,40 @@ def test_init_create_session(default_conf):
|
|||||||
|
|
||||||
|
|
||||||
def test_init_custom_db_url(default_conf, mocker):
|
def test_init_custom_db_url(default_conf, mocker):
|
||||||
conf = deepcopy(default_conf)
|
|
||||||
|
|
||||||
# Update path to a value other than default, but still in-memory
|
# Update path to a value other than default, but still in-memory
|
||||||
conf.update({'db_url': 'sqlite:///tmp/freqtrade2_test.sqlite'})
|
default_conf.update({'db_url': 'sqlite:///tmp/freqtrade2_test.sqlite'})
|
||||||
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
||||||
|
|
||||||
init(conf)
|
init(default_conf)
|
||||||
assert create_engine_mock.call_count == 1
|
assert create_engine_mock.call_count == 1
|
||||||
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tmp/freqtrade2_test.sqlite'
|
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tmp/freqtrade2_test.sqlite'
|
||||||
|
|
||||||
|
|
||||||
def test_init_invalid_db_url(default_conf):
|
def test_init_invalid_db_url(default_conf):
|
||||||
conf = deepcopy(default_conf)
|
|
||||||
|
|
||||||
# Update path to a value other than default, but still in-memory
|
# Update path to a value other than default, but still in-memory
|
||||||
conf.update({'db_url': 'unknown:///some.url'})
|
default_conf.update({'db_url': 'unknown:///some.url'})
|
||||||
with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
|
with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
|
||||||
init(conf)
|
init(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_init_prod_db(default_conf, mocker):
|
def test_init_prod_db(default_conf, mocker):
|
||||||
conf = deepcopy(default_conf)
|
default_conf.update({'dry_run': False})
|
||||||
conf.update({'dry_run': False})
|
default_conf.update({'db_url': constants.DEFAULT_DB_PROD_URL})
|
||||||
conf.update({'db_url': constants.DEFAULT_DB_PROD_URL})
|
|
||||||
|
|
||||||
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
||||||
|
|
||||||
init(conf)
|
init(default_conf)
|
||||||
assert create_engine_mock.call_count == 1
|
assert create_engine_mock.call_count == 1
|
||||||
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.sqlite'
|
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.sqlite'
|
||||||
|
|
||||||
|
|
||||||
def test_init_dryrun_db(default_conf, mocker):
|
def test_init_dryrun_db(default_conf, mocker):
|
||||||
conf = deepcopy(default_conf)
|
default_conf.update({'dry_run': True})
|
||||||
conf.update({'dry_run': True})
|
default_conf.update({'db_url': constants.DEFAULT_DB_DRYRUN_URL})
|
||||||
conf.update({'db_url': constants.DEFAULT_DB_DRYRUN_URL})
|
|
||||||
|
|
||||||
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
|
||||||
|
|
||||||
init(conf)
|
init(default_conf)
|
||||||
assert create_engine_mock.call_count == 1
|
assert create_engine_mock.call_count == 1
|
||||||
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite://'
|
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite://'
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
ccxt==1.17.45
|
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
|
||||||
|
@ -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 '
|
||||||
|
2
setup.py
2
setup.py
@ -18,7 +18,7 @@ setup(name='freqtrade',
|
|||||||
license='GPLv3',
|
license='GPLv3',
|
||||||
packages=['freqtrade'],
|
packages=['freqtrade'],
|
||||||
scripts=['bin/freqtrade'],
|
scripts=['bin/freqtrade'],
|
||||||
setup_requires=['pytest-runner'],
|
setup_requires=['pytest-runner', 'numpy'],
|
||||||
tests_require=['pytest', 'pytest-mock', 'pytest-cov'],
|
tests_require=['pytest', 'pytest-mock', 'pytest-cov'],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'ccxt',
|
'ccxt',
|
||||||
|
@ -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