Merge branch 'develop' of https://github.com/freqtrade/freqtrade into develop

This commit is contained in:
Axel-CH 2018-09-01 20:39:00 +02:00
commit 77935b833b
12 changed files with 569 additions and 114 deletions

View File

@ -11,7 +11,18 @@
"sell": 30 "sell": 30
}, },
"bid_strategy": { "bid_strategy": {
"ask_last_balance": 0.0 "ask_last_balance": 0.0,
"use_order_book": false,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9
}, },
"exchange": { "exchange": {
"name": "bittrex", "name": "bittrex",

View File

@ -20,7 +20,18 @@
"sell": 30 "sell": 30
}, },
"bid_strategy": { "bid_strategy": {
"ask_last_balance": 0.0 "ask_last_balance": 0.0,
"use_order_book": false,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9
}, },
"exchange": { "exchange": {
"name": "bittrex", "name": "bittrex",

View File

@ -25,12 +25,19 @@ The table below will list all configuration parameters.
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
| `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file. | `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file. | `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
| `trailing_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). | `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
| `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached. | `trailing_stop_positve` | 0 | No | Changes stop-loss once profit has been reached.
| `trailing_stoploss_positve_offset` | 0 | No | Offset on when to apply `trailing_stoploss_positive`. Percentage value which should be positive. | `trailing_stop_positve_offset` | 0 | No | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive.
| `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. | `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
| `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. | `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below. | `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
| `bid_strategy.use_order_book` | false | No | Allows buying of pair using the rates in Order Book Bids.
| `bid_strategy.order_book_top` | 0 | No | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids.
| `bid_strategy.check_depth_of_market.enabled` | false | No | Does not buy if the % difference of buy orders and sell orders is met in Order Book.
| `bid_strategy.check_depth_of_market.bids_to_ask_delta` | 0 | No | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher.
| `ask_strategy.use_order_book` | false | No | Allows selling of open traded pair using the rates in Order Book Asks.
| `ask_strategy.order_book_min` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `ask_strategy.order_book_max` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). | `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
| `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode. | `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode.
| `exchange.secret` | secret | No | API secret to use for the exchange. Only required when you are in production mode. | `exchange.secret` | secret | No | API secret to use for the exchange. Only required when you are in production mode.

View File

@ -8,7 +8,6 @@ To understand how to set up the bot please read the [Bot Configuration](https://
* [Table of Contents](#table-of-contents) * [Table of Contents](#table-of-contents)
* [Easy Installation - Linux Script](#easy-installation---linux-script) * [Easy Installation - Linux Script](#easy-installation---linux-script)
* [Manual installation](#manual-installation)
* [Automatic Installation - Docker](#automatic-installation---docker) * [Automatic Installation - Docker](#automatic-installation---docker)
* [Custom Linux MacOS Installation](#custom-installation) * [Custom Linux MacOS Installation](#custom-installation)
- [Requirements](#requirements) - [Requirements](#requirements)
@ -56,34 +55,6 @@ Reset parameter will hard reset your branch (only if you are on `master` or `dev
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`. Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
## Manual installation - Linux/MacOS
The following steps are made for Linux/MacOS environment
### 1. Clone the repo
```bash
git clone git@github.com:freqtrade/freqtrade.git
git checkout develop
cd freqtrade
```
### 2. Create the config file
Switch `"dry_run": true,`
```bash
cp config.json.example config.json
vi config.json
```
### 3. Build your docker image and run it
```bash
docker build -t freqtrade .
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
------ ------
## Automatic Installation - Docker ## Automatic Installation - Docker
@ -196,7 +167,7 @@ docker run -d \
freqtrade --db-url sqlite:///tradesv3.sqlite freqtrade --db-url sqlite:///tradesv3.sqlite
``` ```
NOTE: db-url defaults to `sqlite:///tradesv3.sqlite` but it defaults to `sqlite://` if `dry_run=True` is being used. *Note*: db-url defaults to `sqlite:///tradesv3.sqlite` but it defaults to `sqlite://` if `dry_run=True` is being used.
To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite` To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
### 6. Monitor your Docker instance ### 6. Monitor your Docker instance
@ -211,14 +182,15 @@ docker stop freqtrade
docker start freqtrade docker start freqtrade
``` ```
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container. For more information on how to operate Docker, please refer to the [official Docker documentation](https://docs.docker.com/).
*Note*: You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
### 7. Backtest with docker ### 7. Backtest with docker
The following assumes that the above steps (1-4) have been completed successfully. The following assumes that the above steps (1-4) have been completed successfully.
Also, backtest-data should be available at `~/.freqtrade/user_data/`. Also, backtest-data should be available at `~/.freqtrade/user_data/`.
``` bash ``` bash
docker run -d \ docker run -d \
--name freqtrade \ --name freqtrade \
@ -238,12 +210,13 @@ Head over to the [Backtesting Documentation](https://github.com/freqtrade/freqtr
## Custom Installation ## Custom Installation
We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windows. These are guidelines and your success may vary with other distros. We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windows. These are guidelines and your success may vary with other distros.
OS Specific steps are listed first, the [common](#common) section below is necessary for all systems.
### Requirements ### Requirements
Click each one for install guide: Click each one for install guide:
* [Python 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/), note the bot was not tested on Python >= 3.7.x * [Python >= 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/)
* [pip](https://pip.pypa.io/en/stable/installing/) * [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) * [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended) * [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended)
@ -251,7 +224,7 @@ Click each one for install guide:
### Linux - Ubuntu 16.04 ### Linux - Ubuntu 16.04
#### 1. Install Python 3.6, Git, and wget #### Install Python 3.6, Git, and wget
```bash ```bash
sudo add-apt-repository ppa:jonathonf/python-3.6 sudo add-apt-repository ppa:jonathonf/python-3.6
@ -259,7 +232,34 @@ sudo apt-get update
sudo apt-get install python3.6 python3.6-venv python3.6-dev build-essential autoconf libtool pkg-config make wget git sudo apt-get install python3.6 python3.6-venv python3.6-dev build-essential autoconf libtool pkg-config make wget git
``` ```
#### 2. Install TA-Lib #### Raspberry Pi / Raspbian
Before installing FreqTrade on a Raspberry Pi running the official Raspbian Image, make sure you have at least Python 3.6 installed. The default image only provides Python 3.5. Probably the easiest way to get a recent version of python is [miniconda](https://repo.continuum.io/miniconda/).
The following assumes that miniconda3 is installed and available in your environment, and is installed.
It's recommended to use (mini)conda for this as installation/compilation of `scipy` and `pandas` takes a long time.
``` bash
conda config --add channels rpi
conda install python=3.6
conda create -n freqtrade python=3.6
conda install scipy pandas
pip install -r requirements.txt
pip install -e .
```
### MacOS
#### Install Python 3.6, git, wget and ta-lib
```bash
brew install python3 git wget
```
### common
#### 1. Install TA-Lib
Official webpage: https://mrjbq7.github.io/ta-lib/install.html Official webpage: https://mrjbq7.github.io/ta-lib/install.html
@ -275,15 +275,60 @@ cd ..
rm -rf ./ta-lib* rm -rf ./ta-lib*
``` ```
*Note*: An already downloaded version of ta-lib is included in the repository, as the sourceforge.net source seems to have problems frequently.
#### 2. Setup your Python virtual environment (virtualenv)
*Note*: This step is optional but strongly recommended to keep your system organized
```bash
python3 -m venv .env
source .env/bin/activate
```
#### 3. Install FreqTrade #### 3. Install FreqTrade
Clone the git repository: Clone the git repository:
```bash ```bash
git clone https://github.com/freqtrade/freqtrade.git git clone https://github.com/freqtrade/freqtrade.git
``` ```
#### 4. Configure `freqtrade` as a `systemd` service Optionally checkout the stable/master branch:
```bash
git checkout master
```
#### 4. Initialize the configuration
```bash
cd freqtrade
cp config.json.example config.json
```
> *To edit the config please refer to [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md).*
#### 5. Install python dependencies
``` bash
pip3 install --upgrade pip
pip3 install -r requirements.txt
pip3 install -e .
```
#### 6. Run the Bot
If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
```bash
python3.6 ./freqtrade/main.py -c config.json
```
*Note*: If you run the bot on a server, you should consider using [Docker](#automatic-installation---docker) a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
#### 7. [Optional] Configure `freqtrade` as a `systemd` service
From the freqtrade repo... copy `freqtrade.service` to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup. From the freqtrade repo... copy `freqtrade.service` to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.
@ -299,57 +344,6 @@ For this to be persistent (run when user is logged out) you'll need to enable `l
sudo loginctl enable-linger "$USER" sudo loginctl enable-linger "$USER"
``` ```
### MacOS
#### 1. Install Python 3.6, git, wget and ta-lib
```bash
brew install python3 git wget ta-lib
```
#### 2. Install FreqTrade
Clone the git repository:
```bash
git clone https://github.com/freqtrade/freqtrade.git
```
Optionally checkout the develop branch:
```bash
git checkout develop
```
### Setup Config and virtual env
#### 1. Initialize the configuration
```bash
cd freqtrade
cp config.json.example config.json
```
> *To edit the config please refer to [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md).*
#### 2. Setup your Python virtual environment (virtualenv)
```bash
python3.6 -m venv .env
source .env/bin/activate
pip3.6 install --upgrade pip
pip3.6 install -r requirements.txt
pip3.6 install -e .
```
#### 3. Run the Bot
If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
```bash
python3.6 ./freqtrade/main.py -c config.json
```
------ ------
## Windows ## Windows
@ -369,7 +363,7 @@ git clone https://github.com/freqtrade/freqtrade.git
copy paste `config.json` to ``\path\freqtrade-develop\freqtrade` copy paste `config.json` to ``\path\freqtrade-develop\freqtrade`
#### install ta-lib #### Install ta-lib
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows). Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
@ -390,5 +384,17 @@ REM >pip install TA_Lib0.4.17cp36cp36mwin32.whl
> Thanks [Owdr](https://github.com/Owdr) for the commands. Source: [Issue #222](https://github.com/freqtrade/freqtrade/issues/222) > Thanks [Owdr](https://github.com/Owdr) for the commands. Source: [Issue #222](https://github.com/freqtrade/freqtrade/issues/222)
#### Error during installation under Windows
``` bash
error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
```
Unfortunately, many packages requiring compilation don't provide a pre-build wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use.
The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building c code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or docker first.
---
Now you have an environment ready, the next step is Now you have an environment ready, the next step is
[Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)... [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)...

View File

@ -78,18 +78,35 @@ CONF_SCHEMA = {
'type': 'number', 'type': 'number',
'minimum': 0, 'minimum': 0,
'maximum': 1, 'maximum': 1,
'exclusiveMaximum': False 'exclusiveMaximum': False,
'use_order_book': {'type': 'boolean'},
'order_book_top': {'type': 'number', 'maximum': 20, 'minimum': 1},
'check_depth_of_market': {
'type': 'object',
'properties': {
'enabled': {'type': 'boolean'},
'bids_to_ask_delta': {'type': 'number', 'minimum': 0},
}
},
}, },
}, },
'required': ['ask_last_balance'] 'required': ['ask_last_balance']
}, },
'ask_strategy': {
'type': 'object',
'properties': {
'use_order_book': {'type': 'boolean'},
'order_book_min': {'type': 'number', 'minimum': 1},
'order_book_max': {'type': 'number', 'minimum': 1, 'maximum': 50}
}
},
'exchange': {'$ref': '#/definitions/exchange'}, 'exchange': {'$ref': '#/definitions/exchange'},
'experimental': { 'experimental': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'use_sell_signal': {'type': 'boolean'}, 'use_sell_signal': {'type': 'boolean'},
'sell_profit_only': {'type': 'boolean'}, 'sell_profit_only': {'type': 'boolean'},
"ignore_roi_if_buy_signal_true": {'type': 'boolean'} 'ignore_roi_if_buy_signal_true': {'type': 'boolean'}
} }
}, },
'telegram': { 'telegram': {

View File

@ -116,7 +116,7 @@ class Exchange(object):
api.urls['api'] = api.urls['test'] api.urls['api'] = api.urls['test']
logger.info("Enabled Sandbox API on %s", name) logger.info("Enabled Sandbox API on %s", name)
else: else:
logger.warning(self._api.name, "No Sandbox URL in CCXT, exiting. " logger.warning(name, "No Sandbox URL in CCXT, exiting. "
"Please check your config.json") "Please check your config.json")
raise OperationalException(f'Exchange {name} does not provide a sandbox api') raise OperationalException(f'Exchange {name} does not provide a sandbox api')
@ -409,6 +409,37 @@ class Exchange(object):
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@retrier
def get_order_book(self, pair: str, limit: int = 100) -> dict:
"""
get order book level 2 from exchange
Notes:
20180619: bittrex doesnt support limits -.-
20180619: binance support limits but only on specific range
"""
try:
if self._api.name == 'Binance':
limit_range = [5, 10, 20, 50, 100, 500, 1000]
# get next-higher step in the limit_range list
limit = min(list(filter(lambda x: limit <= x, limit_range)))
# above script works like loop below (but with slightly better performance):
# for limitx in limit_range:
# if limit <= limitx:
# limit = limitx
# break
return self._api.fetch_l2_order_book(pair, limit)
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching order book.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order book due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier @retrier
def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List:
if self._conf['dry_run']: if self._conf['dry_run']:

View File

@ -2,6 +2,7 @@
Functions to analyze ticker data with indicators and produce buy and sell signals Functions to analyze ticker data with indicators and produce buy and sell signals
""" """
import logging import logging
import pandas as pd
from pandas import DataFrame, to_datetime from pandas import DataFrame, to_datetime
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -31,3 +32,27 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame:
}) })
frame.drop(frame.tail(1).index, inplace=True) # eliminate partial candle frame.drop(frame.tail(1).index, inplace=True) # eliminate partial candle
return frame return frame
def order_book_to_dataframe(bids: list, asks: list) -> DataFrame:
"""
Gets order book list, returns dataframe with below format per suggested by creslin
-------------------------------------------------------------------
b_sum b_size bids asks a_size a_sum
-------------------------------------------------------------------
"""
cols = ['bids', 'b_size']
bids_frame = DataFrame(bids, columns=cols)
# add cumulative sum column
bids_frame['b_sum'] = bids_frame['b_size'].cumsum()
cols2 = ['asks', 'a_size']
asks_frame = DataFrame(asks, columns=cols2)
# add cumulative sum column
asks_frame['a_sum'] = asks_frame['a_size'].cumsum()
frame = pd.concat([bids_frame['b_sum'], bids_frame['b_size'], bids_frame['bids'],
asks_frame['asks'], asks_frame['a_size'], asks_frame['a_sum']], axis=1,
keys=['b_sum', 'b_size', 'bids', 'asks', 'a_size', 'a_sum'])
# logger.info('order book %s', frame )
return frame

View File

@ -21,6 +21,7 @@ from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.state import State from freqtrade.state import State
from freqtrade.strategy.interface import SellType from freqtrade.strategy.interface import SellType
from freqtrade.strategy.resolver import IStrategy, StrategyResolver from freqtrade.strategy.resolver import IStrategy, StrategyResolver
from freqtrade.exchange.exchange_helpers import order_book_to_dataframe
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -267,16 +268,40 @@ class FreqtradeBot(object):
return final_list return final_list
def get_target_bid(self, ticker: Dict[str, float]) -> float: def get_target_bid(self, pair: str, ticker: Dict[str, float]) -> float:
""" """
Calculates bid target between current ask price and last price Calculates bid target between current ask price and last price
:param ticker: Ticker to use for getting Ask and Last Price :param ticker: Ticker to use for getting Ask and Last Price
:return: float: Price :return: float: Price
""" """
if ticker['ask'] < ticker['last']: if ticker['ask'] < ticker['last']:
return ticker['ask'] ticker_rate = ticker['ask']
else:
balance = self.config['bid_strategy']['ask_last_balance'] balance = self.config['bid_strategy']['ask_last_balance']
return ticker['ask'] + balance * (ticker['last'] - ticker['ask']) ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
used_rate = ticker_rate
config_bid_strategy = self.config.get('bid_strategy', {})
if 'use_order_book' in config_bid_strategy and\
config_bid_strategy.get('use_order_book', False):
logger.info('Getting price from order book')
order_book_top = config_bid_strategy.get('order_book_top', 1)
order_book = self.exchange.get_order_book(pair, order_book_top)
logger.debug('order_book %s', order_book)
# top 1 = index 0
order_book_rate = order_book['bids'][order_book_top - 1][0]
# if ticker has lower rate, then use ticker ( usefull if down trending )
logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate)
if ticker_rate < order_book_rate:
logger.info('...using ticker rate instead %0.8f', ticker_rate)
used_rate = ticker_rate
else:
used_rate = order_book_rate
else:
logger.info('Using Last Ask / Last Price')
used_rate = ticker_rate
return used_rate
def _get_trade_stake_amount(self) -> Optional[float]: def _get_trade_stake_amount(self) -> Optional[float]:
""" """
@ -368,7 +393,32 @@ class FreqtradeBot(object):
(buy, sell) = self.strategy.get_signal(_pair, interval, thistory) (buy, sell) = self.strategy.get_signal(_pair, interval, thistory)
if buy and not sell: if buy and not sell:
bidstrat_check_depth_of_market = self.config.get('bid_strategy', {}).\
get('check_depth_of_market', {})
if (bidstrat_check_depth_of_market.get('enabled', False)) and\
(bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0):
if self._check_depth_of_market_buy(_pair, bidstrat_check_depth_of_market):
return self.execute_buy(_pair, stake_amount) return self.execute_buy(_pair, stake_amount)
else:
return False
return self.execute_buy(_pair, stake_amount)
return False
def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool:
"""
Checks depth of market before executing a buy
"""
conf_bids_to_ask_delta = conf.get('bids_to_ask_delta', 0)
logger.info('checking depth of market for %s', pair)
order_book = self.exchange.get_order_book(pair, 1000)
order_book_data_frame = order_book_to_dataframe(order_book['bids'], order_book['asks'])
order_book_bids = order_book_data_frame['b_size'].sum()
order_book_asks = order_book_data_frame['a_size'].sum()
bids_ask_delta = order_book_bids / order_book_asks
logger.info('bids: %s, asks: %s, delta: %s', order_book_bids,
order_book_asks, bids_ask_delta)
if bids_ask_delta >= conf_bids_to_ask_delta:
return True
return False return False
def execute_buy(self, pair: str, stake_amount: float) -> bool: def execute_buy(self, pair: str, stake_amount: float) -> bool:
@ -383,7 +433,7 @@ class FreqtradeBot(object):
fiat_currency = self.config.get('fiat_display_currency', None) fiat_currency = self.config.get('fiat_display_currency', None)
# Calculate amount # Calculate amount
buy_limit = self.get_target_bid(self.exchange.get_ticker(pair)) buy_limit = self.get_target_bid(pair, self.exchange.get_ticker(pair))
min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit) min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit)
if min_stake_amount is not None and min_stake_amount > stake_amount: if min_stake_amount is not None and min_stake_amount > stake_amount:
@ -526,7 +576,7 @@ class FreqtradeBot(object):
raise ValueError(f'attempt to handle closed trade: {trade}') raise ValueError(f'attempt to handle closed trade: {trade}')
logger.debug('Handling %s ...', trade) logger.debug('Handling %s ...', trade)
current_rate = self.exchange.get_ticker(trade.pair)['bid'] sell_rate = self.exchange.get_ticker(trade.pair)['bid']
(buy, sell) = (False, False) (buy, sell) = (False, False)
experimental = self.config.get('experimental', {}) experimental = self.config.get('experimental', {})
@ -535,13 +585,43 @@ class FreqtradeBot(object):
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval, (buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval,
ticker) ticker)
should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell) config_ask_strategy = self.config.get('ask_strategy', {})
if should_sell.sell_flag: if config_ask_strategy.get('use_order_book', False):
self.execute_sell(trade, current_rate, should_sell.sell_type) logger.info('Using order book for selling...')
# logger.debug('Order book %s',orderBook)
order_book_min = config_ask_strategy.get('order_book_min', 1)
order_book_max = config_ask_strategy.get('order_book_max', 1)
order_book = self.exchange.get_order_book(trade.pair, order_book_max)
for i in range(order_book_min, order_book_max + 1):
order_book_rate = order_book['asks'][i - 1][0]
# if orderbook has higher rate (high profit),
# use orderbook, otherwise just use bids rate
logger.info(' order book asks top %s: %0.8f', i, order_book_rate)
if sell_rate < order_book_rate:
sell_rate = order_book_rate
if self.check_sell(trade, sell_rate, buy, sell):
return True return True
break
else:
logger.info('checking sell')
if self.check_sell(trade, sell_rate, buy, sell):
return True
logger.info('Found no sell signals for whitelisted currencies. Trying again..') logger.info('Found no sell signals for whitelisted currencies. Trying again..')
return False return False
def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool:
should_sell = self.strategy.should_sell(trade, sell_rate, datetime.utcnow(), buy, sell)
if should_sell.sell_flag:
self.execute_sell(trade, sell_rate, should_sell.sell_type)
logger.info('excuted sell')
return True
return False
def check_handle_timedout(self) -> None: def check_handle_timedout(self) -> None:
""" """
Check if any orders are timed out and cancel if neccessary Check if any orders are timed out and cancel if neccessary

View File

@ -102,7 +102,18 @@ def default_conf():
"sell": 30 "sell": 30
}, },
"bid_strategy": { "bid_strategy": {
"ask_last_balance": 0.0 "ask_last_balance": 0.0,
"use_order_book": False,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": False,
"bids_to_ask_delta": 1
}
},
"ask_strategy": {
"use_order_book": False,
"order_book_min": 1,
"order_book_max": 1
}, },
"exchange": { "exchange": {
"name": "bittrex", "name": "bittrex",
@ -403,6 +414,39 @@ def limit_sell_order():
} }
@pytest.fixture
def order_book_l2():
return MagicMock(return_value={
'bids': [
[0.043936, 10.442],
[0.043935, 31.865],
[0.043933, 11.212],
[0.043928, 0.088],
[0.043925, 10.0],
[0.043921, 10.0],
[0.04392, 37.64],
[0.043899, 0.066],
[0.043885, 0.676],
[0.04387, 22.758]
],
'asks': [
[0.043949, 0.346],
[0.04395, 0.608],
[0.043951, 3.948],
[0.043954, 0.288],
[0.043958, 9.277],
[0.043995, 1.566],
[0.044, 0.588],
[0.044002, 0.992],
[0.044003, 0.095],
[0.04402, 37.64]
],
'timestamp': None,
'datetime': None,
'nonce': 288004540
})
@pytest.fixture @pytest.fixture
def ticker_history(): def ticker_history():
return [ return [

View File

@ -515,6 +515,35 @@ def test_get_ticker(default_conf, mocker):
exchange.get_ticker(pair='ETH/BTC', refresh=True) exchange.get_ticker(pair='ETH/BTC', refresh=True)
def test_get_order_book(default_conf, mocker, order_book_l2):
default_conf['exchange']['name'] = 'binance'
api_mock = MagicMock()
api_mock.fetch_l2_order_book = order_book_l2
exchange = get_patched_exchange(mocker, default_conf, api_mock)
order_book = exchange.get_order_book(pair='ETH/BTC', limit=10)
assert 'bids' in order_book
assert 'asks' in order_book
assert len(order_book['bids']) == 10
assert len(order_book['asks']) == 10
def test_get_order_book_exception(default_conf, mocker):
api_mock = MagicMock()
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order_book(pair='ETH/BTC', limit=50)
with pytest.raises(TemporaryError):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order_book(pair='ETH/BTC', limit=50)
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order_book(pair='ETH/BTC', limit=50)
def make_fetch_ohlcv_mock(data): def make_fetch_ohlcv_mock(data):
def fetch_ohlcv_mock(pair, timeframe, since): def fetch_ohlcv_mock(pair, timeframe, since):
if since: if since:

View File

@ -159,6 +159,15 @@ def test_gen_pair_whitelist(mocker, default_conf, tickers) -> None:
assert whitelist == [] assert whitelist == []
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False))
with pytest.raises(OperationalException):
freqtrade._gen_pair_whitelist(base_currency='BTC')
@pytest.mark.skip(reason="Test not implemented") @pytest.mark.skip(reason="Test not implemented")
def test_refresh_whitelist() -> None: def test_refresh_whitelist() -> None:
pass pass
@ -664,21 +673,21 @@ def test_balance_fully_ask_side(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 0.0 default_conf['bid_strategy']['ask_last_balance'] = 0.0
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 20 assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) == 20
def test_balance_fully_last_side(mocker, default_conf) -> None: def test_balance_fully_last_side(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 1.0 default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 10 assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) == 10
def test_balance_bigger_last_ask(mocker, default_conf) -> None: def test_balance_bigger_last_ask(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 1.0 default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 5, 'last': 10}) == 5 assert freqtrade.get_target_bid('ETH/BTC', {'ask': 5, 'last': 10}) == 5
def test_process_maybe_execute_buy(mocker, default_conf) -> None: def test_process_maybe_execute_buy(mocker, default_conf) -> None:
@ -1878,6 +1887,191 @@ def test_get_real_amount_open_trade(default_conf, mocker):
assert freqtrade.get_real_amount(trade, order) == amount assert freqtrade.get_real_amount(trade, order) == amount
def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, markets, mocker,
order_book_l2):
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 0.1
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
get_markets=markets
)
# Save state of current whitelist
whitelist = deepcopy(default_conf['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
assert trade is not None
assert trade.stake_amount == 0.001
assert trade.is_open
assert trade.open_date is not None
assert trade.exchange == 'bittrex'
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
assert trade.open_rate == 0.00001099
assert whitelist == default_conf['exchange']['pair_whitelist']
def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_order,
fee, markets, mocker, order_book_l2):
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
# delta is 100 which is impossible to reach. hence check_depth_of_market will return false
default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
get_markets=markets
)
# Save state of current whitelist
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
assert trade is None
def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2, markets) -> None:
"""
test if function get_target_bid will return the order book price
instead of the ask rate
"""
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 2
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.045, 'last': 0.046}) == 0.043935
def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2, markets) -> None:
"""
test if function get_target_bid will return the ask rate (since its value is lower)
instead of the order book rate (even if enabled)
"""
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 2
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.042, 'last': 0.046}) == 0.042
def test_order_book_bid_strategy3(default_conf, mocker, order_book_l2, markets) -> None:
"""
test if function get_target_bid will return ask rate instead
of the order book rate
"""
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 1
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.03, 'last': 0.029}) == 0.03
def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2, markets) -> None:
"""
test check depth of market
"""
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['telegram']['enabled'] = False
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
# delta is 100 which is impossible to reach. hence function will return false
default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100
freqtrade = FreqtradeBot(default_conf)
conf = default_conf['bid_strategy']['check_depth_of_market']
assert freqtrade._check_depth_of_market_buy('ETH/BTC', conf) is False
def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order,
fee, markets, mocker, order_book_l2) -> None:
"""
test order book ask strategy
"""
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
default_conf['exchange']['name'] = 'binance'
default_conf['ask_strategy']['use_order_book'] = True
default_conf['ask_strategy']['order_book_min'] = 1
default_conf['ask_strategy']['order_book_max'] = 2
default_conf['telegram']['enabled'] = False
patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
}),
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee,
get_markets=markets
)
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
assert trade
time.sleep(0.01) # Race condition fix
trade.update(limit_buy_order)
assert trade.is_open is True
patch_get_signal(freqtrade, value=(False, True))
assert freqtrade.handle_trade(trade) is True
def test_startup_messages(default_conf, mocker): def test_startup_messages(default_conf, mocker):
default_conf['dynamic_whitelist'] = 20 default_conf['dynamic_whitelist'] = 20
freqtrade = get_patched_freqtradebot(mocker, default_conf) freqtrade = get_patched_freqtradebot(mocker, default_conf)

View File

@ -1,6 +1,6 @@
ccxt==1.17.199 ccxt==1.17.216
SQLAlchemy==1.2.11 SQLAlchemy==1.2.11
python-telegram-bot==10.1.0 python-telegram-bot==11.0.0
arrow==0.12.1 arrow==0.12.1
cachetools==2.1.0 cachetools==2.1.0
requests==2.19.1 requests==2.19.1
@ -12,7 +12,7 @@ scipy==1.1.0
jsonschema==2.6.0 jsonschema==2.6.0
numpy==1.15.1 numpy==1.15.1
TA-Lib==0.4.17 TA-Lib==0.4.17
pytest==3.7.3 pytest==3.7.4
pytest-mock==1.10.0 pytest-mock==1.10.0
pytest-cov==2.5.1 pytest-cov==2.5.1
tabulate==0.8.2 tabulate==0.8.2