Merge branch 'develop' into flask_rest

This commit is contained in:
Michael Egger 2018-06-14 18:10:04 +02:00 committed by GitHub
commit f21ef07910
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 946 additions and 743 deletions

View File

@ -6,10 +6,12 @@ If it hasn't been reported, please create a new issue.
## Step 2: Describe your environment
* Python Version: _____ (`python -V`)
* CCXT version: _____ (`pip freeze | grep ccxt`)
* Branch: Master | Develop
* Last Commit ID: _____ (`git log --format="%H" -n 1`)
## Step 3: Describe the problem:
*Explain the problem you have encountered*
### Steps to reproduce:

View File

@ -18,7 +18,9 @@ install:
- pip install -e .
jobs:
include:
- script: pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/
- script:
- pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/
- coveralls
- script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting
@ -27,8 +29,6 @@ jobs:
- python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5
- script: flake8 freqtrade
- script: mypy freqtrade
after_success:
- coveralls
notifications:
slack:
secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q=

View File

@ -42,6 +42,11 @@ pip3.6 install flake8 coveralls
flake8 freqtrade
```
We receive a lot of code that fails the `flake8` checks.
To help with that, we encourage you to install the git pre-commit
hook that will warn you when you try to commit code that fails these checks.
Guide for installing them is [here](http://flake8.pycqa.org/en/latest/user/using-hooks.html).
## 3. Test if all type-hints are correct
**Install packages** (If not already installed)

View File

@ -1,8 +1,10 @@
# Bot Optimization
This page explains where to customize your strategies, and add new
indicators.
This page explains where to customize your strategies, and add new
indicators.
## Table of Contents
- [Install a custom strategy file](#install-a-custom-strategy-file)
- [Customize your strategy](#change-your-strategy)
- [Add more Indicator](#add-more-indicator)
@ -11,10 +13,12 @@ indicators.
Since the version `0.16.0` the bot allows using custom strategy file.
## Install a custom strategy file
This is very simple. Copy paste your strategy file into the folder
This is very simple. Copy paste your strategy file into the folder
`user_data/strategies`.
Let assume you have a class called `AwesomeStrategy` in the file `awesome-strategy.py`:
1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py`
2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name)
@ -23,12 +27,14 @@ python3 ./freqtrade/main.py --strategy AwesomeStrategy
```
## Change your strategy
The bot includes a default strategy file. However, we recommend you to
The bot includes a default strategy file. However, we recommend you to
use your own file to not have to lose your parameters every time the default
strategy file will be updated on Github. Put your custom strategy file
into the folder `user_data/strategies`.
A strategy file contains all the information needed to build a good strategy:
- Buy strategy rules
- Sell strategy rules
- Minimal ROI recommended
@ -37,12 +43,13 @@ A strategy file contains all the information needed to build a good strategy:
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`
```bash
``` bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy
```
### Specify custom strategy location
If you want to use a strategy from a different folder you can pass `--strategy-path`
```bash
@ -52,11 +59,13 @@ python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/fol
**For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
file as reference.**
### Buy strategy
Edit the method `populate_buy_trend()` into your strategy file to
### Buy strategy
Edit the method `populate_buy_trend()` into your strategy file to
update your buy strategy.
Sample from `user_data/strategies/test_strategy.py`:
Sample from `user_data/strategies/test_strategy.py`:
```python
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
"""
@ -76,10 +85,11 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
```
### Sell strategy
Edit the method `populate_sell_trend()` into your strategy file to
update your sell strategy.
Sample from `user_data/strategies/test_strategy.py`:
Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy.
Sample from `user_data/strategies/test_strategy.py`:
```python
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
"""
@ -98,11 +108,13 @@ def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
```
## Add more Indicator
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.
Sample:
```python
def populate_indicators(dataframe: DataFrame) -> DataFrame:
"""
@ -137,16 +149,25 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
return dataframe
```
**Want more indicators example?**
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
### 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).
Then uncomment indicators you need.
### Where is the default strategy?
The default buy strategy is located in the file
[freqtrade/default_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/strategy/default_strategy.py).
The default buy strategy is located in the file
[freqtrade/default_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/strategy/default_strategy.py).
### Further strategy ideas
To get additional Ideas for strategies, head over to our [strategy repository](https://github.com/freqtrade/freqtrade-strategies). Feel free to use them as they are - but results will depend on the current market situation, pairs used etc. - therefore please backtest the strategy for your exchange/desired pairs first, evaluate carefully, use at your own risk.
Feel free to use any of them as inspiration for your own strategies.
We're happy to accept Pull Requests containing new Strategies to that repo.
We also got a *strategy-sharing* channel in our [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) which is a great place to get and/or share ideas.
## Next step
Now you have a perfect strategy you probably want to backtesting it.
Now you have a perfect strategy you probably want to backtest it.
Your next step is to learn [How to use the Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md).

View File

@ -15,7 +15,6 @@ To understand how to set up the bot please read the [Bot Configuration](https://
- [MacOS](#macos)
- [Setup Config and virtual env](#setup-config-and-virtual-env)
* [Windows](#windows)
<!-- /TOC -->
@ -35,7 +34,9 @@ usage:
```
### --install
This script will install everything you need to run the bot:
* Mandatory software as: `Python3`, `ta-lib`, `wget`
* Setup your virtualenv
* Configure your `config.json` file
@ -43,12 +44,15 @@ This script will install everything you need to run the bot:
This script is a combination of `install script` `--reset`, `--config`
### --update
Update parameter will pull the last version of your current branch and update your virtualenv.
### --reset
Reset parameter will hard reset your branch (only if you are on `master` or `develop`) and recreate your virtualenv.
### --config
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
------
@ -63,7 +67,6 @@ Start by downloading Docker for your platform:
Once you have Docker installed, simply create the config file (e.g. `config.json`) and then create a Docker image for `freqtrade` using the Dockerfile in this repo.
### 1. Prepare the Bot
#### 1.1. Clone the git repository
@ -95,16 +98,17 @@ cp -n config.json.example config.json
#### 1.5. Create your database file *(optional - the bot will create it if it is missing)*
Production
```bash
touch tradesv3.sqlite
````
Dry-Run
```bash
touch tradesv3.dryrun.sqlite
```
### 2. Build the Docker image
```bash
@ -114,7 +118,6 @@ docker build -t freqtrade .
For security reasons, your configuration file will not be included in the image, you will need to bind mount it. It is also advised to bind mount an SQLite database file (see the "5. Run a restartable docker image" section) to keep it between updates.
### 3. Verify the Docker image
After the build process you can verify that the image was created with:
@ -123,7 +126,6 @@ After the build process you can verify that the image was created with:
docker images
```
### 4. Run the Docker image
You can run a one-off container that is immediately deleted upon exiting with the following command (`config.json` must be in the current working directory):
@ -132,16 +134,16 @@ You can run a one-off container that is immediately deleted upon exiting with th
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
There is known issue in OSX Docker versions after 17.09.1, whereby /etc/localtime cannot be shared causing Docker to not start. A work-around for this is to start with the following cmd.
There is known issue in OSX Docker versions after 17.09.1, whereby /etc/localtime cannot be shared causing Docker to not start. A work-around for this is to start with the following cmd.
```bash
docker run --rm -e TZ=`ls -la /etc/localtime | cut -d/ -f8-9` -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
More information on this docker issue and work-around can be read here: https://github.com/docker/for-mac/issues/2396
More information on this docker issue and work-around can be read [here](https://github.com/docker/for-mac/issues/2396)
In this example, the database will be created inside the docker instance and will be lost when you will refresh your image.
### 5. Run a restartable docker image
To run a restartable instance in the background (feel free to place your configuration and database files wherever it feels comfortable on your filesystem).
@ -164,6 +166,7 @@ docker run -d \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/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.
To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
@ -190,13 +193,13 @@ We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windo
### Requirements
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
* [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended)
* [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)
### Linux - Ubuntu 16.04
#### 1. Install Python 3.6, Git, and wget
@ -240,17 +243,12 @@ Clone the git repository:
git clone https://github.com/freqtrade/freqtrade.git
```
Optionally checkout the develop branch:
```bash
git checkout develop
```
#### 5. 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.
After that you can start the daemon with:
```bash
systemctl --user start freqtrade
```
@ -261,7 +259,6 @@ For this to be persistent (run when user is logged out) you'll need to enable `l
sudo loginctl enable-linger "$USER"
```
### MacOS
#### 1. Install Python 3.6, git, wget and ta-lib
@ -296,7 +293,6 @@ Optionally checkout the develop branch:
git checkout develop
```
### Setup Config and virtual env
#### 1. Initialize the configuration
@ -308,7 +304,6 @@ 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
@ -331,27 +326,41 @@ python3.6 ./freqtrade/main.py -c config.json
## Windows
We recommend that Windows users use [Docker](#docker) as this will work
much easier and smoother (also more secure).
We recommend that Windows users use [Docker](#docker) as this will work much easier and smoother (also more secure).
### Install freqtrade
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
If that is not available on your system, feel free to try the instructions below, which led to success for some.
### Install freqtrade manually
#### Clone the git repository
```bash
git clone https://github.com/freqtrade/freqtrade.git
```
copy paste `config.json` to ``\path\freqtrade-develop\freqtrade`
#### install ta-lib
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of inofficial precompiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib0.4.17cp36cp36mwin32.whl` (make sure to use the version matching your python version)
```cmd
>cd \path\freqtrade-develop
>python -m venv .env
>cd .env\Scripts
>activate.bat
>cd \path\freqtrade-develop
REM optionally install ta-lib from wheel
REM >pip install TA_Lib0.4.17cp36cp36mwin32.whl
>pip install -r requirements.txt
>pip install -e .
>cd freqtrade
>python main.py
>python freqtrade\main.py
```
> Thanks [Owdr](https://github.com/Owdr) for the commands. Source: [Issue #222](https://github.com/freqtrade/freqtrade/issues/222)
Now you have an environment ready, the next step is
[Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)...

View File

@ -16,6 +16,7 @@ official commands. You can ask at any moment for help with `/help`.
|----------|---------|-------------|
| `/start` | | Starts the trader
| `/stop` | | Stops the trader
| `/reload_conf` | | Reloads the configuration file
| `/status` | | Lists all open trades
| `/status table` | | List all open trades in a table format
| `/count` | | Displays number of trades used and available

View File

@ -62,6 +62,7 @@ class Analyze(object):
'close': 'last',
'volume': 'max',
})
frame.drop(frame.tail(1).index, inplace=True) # eliminate partial candle
return frame
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:

View File

@ -72,7 +72,7 @@ class Arguments(object):
self.parser.add_argument(
'--version',
action='version',
version='%(prog)s {}'.format(__version__),
version=f'%(prog)s {__version__}'
)
self.parser.add_argument(
'-c', '--config',
@ -165,6 +165,11 @@ class Arguments(object):
@staticmethod
def optimizer_shared_options(parser: argparse.ArgumentParser) -> None:
"""
Parses given common arguments for Backtesting and Hyperopt scripts.
:param parser:
:return:
"""
parser.add_argument(
'-i', '--ticker-interval',
help='specify ticker interval (1m, 5m, 30m, 1h, 1d)',
@ -243,7 +248,7 @@ class Arguments(object):
:return: Start and End range period
"""
if text is None:
return TimeRange()
return TimeRange(None, None, 0, 0)
syntax = [(r'^-(\d{8})$', (None, 'date')),
(r'^(\d{8})-$', ('date', None)),
(r'^(\d{8})-(\d{8})$', ('date', 'date')),
@ -298,28 +303,34 @@ class Arguments(object):
'--pairs-file',
help='File containing a list of pairs to download',
dest='pairs_file',
default=None
default=None,
metavar='PATH',
)
self.parser.add_argument(
'--export',
help='Export files to given dir',
dest='export',
default=None)
default=None,
metavar='PATH',
)
self.parser.add_argument(
'--days',
help='Download data for number of days',
dest='days',
type=int,
default=None)
metavar='INT',
default=None
)
self.parser.add_argument(
'--exchange',
help='Exchange name (default: %(default)s)',
dest='exchange',
type=str,
default='bittrex')
default='bittrex'
)
self.parser.add_argument(
'-t', '--timeframes',

View File

@ -61,11 +61,9 @@ class Configuration(object):
with open(path) as file:
conf = json.load(file)
except FileNotFoundError:
logger.critical(
'Config file "%s" not found. Please create your config file',
path
)
exit(0)
raise OperationalException(
'Config file "{}" not found!'
' Please create a config file or check whether it exists.'.format(path))
if 'internals' not in conf:
conf['internals'] = {}
@ -238,9 +236,8 @@ class Configuration(object):
exchange = config.get('exchange', {}).get('name').lower()
if exchange not in ccxt.exchanges:
exception_msg = 'Exchange "{}" not supported.\n' \
'The following exchanges are supported: {}'\
.format(exchange, ', '.join(ccxt.exchanges))
exception_msg = f'Exchange "{exchange}" not supported.\n' \
f'The following exchanges are supported: {", ".join(ccxt.exchanges)}'
logger.critical(exception_msg)
raise OperationalException(

View File

@ -59,7 +59,7 @@ def init_ccxt(exchange_config: dict) -> ccxt.Exchange:
name = exchange_config['name']
if name not in ccxt.exchanges:
raise OperationalException('Exchange {} is not supported'.format(name))
raise OperationalException(f'Exchange {name} is not supported')
try:
api = getattr(ccxt, name.lower())({
'apiKey': exchange_config.get('key'),
@ -69,7 +69,7 @@ def init_ccxt(exchange_config: dict) -> ccxt.Exchange:
'enableRateLimit': True,
})
except (KeyError, AttributeError):
raise OperationalException('Exchange {} is not supported'.format(name))
raise OperationalException(f'Exchange {name} is not supported')
return api
@ -118,11 +118,10 @@ def validate_pairs(pairs: List[str]) -> None:
# TODO: add a support for having coins in BTC/USDT format
if not pair.endswith(stake_cur):
raise OperationalException(
'Pair {} not compatible with stake_currency: {}'.format(pair, stake_cur)
)
f'Pair {pair} not compatible with stake_currency: {stake_cur}')
if pair not in markets:
raise OperationalException(
'Pair {} is not available at {}'.format(pair, get_name()))
f'Pair {pair} is not available at {get_name()}')
def exchange_has(endpoint: str) -> bool:
@ -138,7 +137,7 @@ def exchange_has(endpoint: str) -> bool:
def buy(pair: str, rate: float, amount: float) -> Dict:
if _CONF['dry_run']:
global _DRY_RUN_OPEN_ORDERS
order_id = 'dry_run_buy_{}'.format(randint(0, 10**6))
order_id = f'dry_run_buy_{randint(0, 10**6)}'
_DRY_RUN_OPEN_ORDERS[order_id] = {
'pair': pair,
'price': rate,
@ -156,20 +155,17 @@ def buy(pair: str, rate: float, amount: float) -> Dict:
return _API.create_limit_buy_order(pair, amount, rate)
except ccxt.InsufficientFunds as e:
raise DependencyException(
'Insufficient funds to create limit buy order on market {}.'
'Tried to buy amount {} at rate {} (total {}).'
'Message: {}'.format(pair, amount, rate, rate*amount, e)
)
f'Insufficient funds to create limit buy order on market {pair}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
'Could not create limit buy order on market {}.'
'Tried to buy amount {} at rate {} (total {}).'
'Message: {}'.format(pair, amount, rate, rate*amount, e)
)
f'Could not create limit buy order on market {pair}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not place buy order due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not place buy order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@ -177,7 +173,7 @@ def buy(pair: str, rate: float, amount: float) -> Dict:
def sell(pair: str, rate: float, amount: float) -> Dict:
if _CONF['dry_run']:
global _DRY_RUN_OPEN_ORDERS
order_id = 'dry_run_sell_{}'.format(randint(0, 10**6))
order_id = f'dry_run_sell_{randint(0, 10**6)}'
_DRY_RUN_OPEN_ORDERS[order_id] = {
'pair': pair,
'price': rate,
@ -194,20 +190,17 @@ def sell(pair: str, rate: float, amount: float) -> Dict:
return _API.create_limit_sell_order(pair, amount, rate)
except ccxt.InsufficientFunds as e:
raise DependencyException(
'Insufficient funds to create limit sell order on market {}.'
'Tried to sell amount {} at rate {} (total {}).'
'Message: {}'.format(pair, amount, rate, rate*amount, e)
)
f'Insufficient funds to create limit sell order on market {pair}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
'Could not create limit sell order on market {}.'
'Tried to sell amount {} at rate {} (total {}).'
'Message: {}'.format(pair, amount, rate, rate*amount, e)
)
f'Could not create limit sell order on market {pair}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not place sell order due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@ -222,8 +215,7 @@ def get_balance(currency: str) -> float:
balance = balances.get(currency)
if balance is None:
raise TemporaryError(
'Could not get {} balance due to malformed exchange response: {}'.format(
currency, balances))
f'Could not get {currency} balance due to malformed exchange response: {balances}')
return balance['free']
@ -243,8 +235,7 @@ def get_balances() -> dict:
return balances
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not get balance due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not get balance due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@ -255,13 +246,11 @@ def get_tickers() -> Dict:
return _API.fetch_tickers()
except ccxt.NotSupported as e:
raise OperationalException(
'Exchange {} does not support fetching tickers in batch.'
'Message: {}'.format(_API.name, e)
)
f'Exchange {_API.name} does not support fetching tickers in batch.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not load tickers due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@ -277,13 +266,12 @@ def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
'bid': float(data['bid']),
'ask': float(data['ask']),
}
except KeyError as e:
except KeyError:
logger.debug("Could not cache ticker data for %s", pair)
return data
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not load ticker history due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
else:
@ -327,15 +315,13 @@ def get_ticker_history(pair: str, tick_interval: str, since_ms: Optional[int] =
return data
except ccxt.NotSupported as e:
raise OperationalException(
'Exchange {} does not support fetching historical candlestick data.'
'Message: {}'.format(_API.name, e)
)
f'Exchange {_API.name} does not support fetching historical candlestick data.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not load ticker history due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException('Could not fetch ticker data. Msg: {}'.format(e))
raise OperationalException(f'Could not fetch ticker data. Msg: {e}')
@retrier
@ -347,12 +333,10 @@ def cancel_order(order_id: str, pair: str) -> None:
return _API.cancel_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
'Could not cancel order. Message: {}'.format(e)
)
f'Could not cancel order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not cancel order due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@ -369,12 +353,10 @@ def get_order(order_id: str, pair: str) -> Dict:
return _API.fetch_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
'Could not get order. Message: {}'.format(e)
)
f'Could not get order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not get order due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not get order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@ -393,8 +375,7 @@ def get_trades_for_order(order_id: str, pair: str, since: datetime) -> List:
except ccxt.NetworkError as e:
raise TemporaryError(
'Could not get trades due to networking error. Message: {}'.format(e)
)
f'Could not get trades due to networking error. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@ -416,8 +397,7 @@ def get_markets() -> List[dict]:
return _API.fetch_markets()
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not load markets due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not load markets due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@ -442,8 +422,7 @@ def get_fee(symbol='ETH/BTC', type='', side='', amount=1,
price=price, takerOrMaker=taker_or_maker)['rate']
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
'Could not get fee info due to {}. Message: {}'.format(
e.__class__.__name__, e))
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)

View File

@ -119,7 +119,7 @@ class CryptoToFiatConverter(object):
# Check if the fiat convertion you want is supported
if not self._is_supported_fiat(fiat=fiat_symbol):
raise ValueError('The fiat {} is not supported.'.format(fiat_symbol))
raise ValueError(f'The fiat {fiat_symbol} is not supported.')
# Get the pair that interest us and return the price in fiat
for pair in self._pairs:
@ -182,7 +182,7 @@ class CryptoToFiatConverter(object):
"""
# Check if the fiat convertion you want is supported
if not self._is_supported_fiat(fiat=fiat_symbol):
raise ValueError('The fiat {} is not supported.'.format(fiat_symbol))
raise ValueError(f'The fiat {fiat_symbol} is not supported.')
# No need to convert if both crypto and fiat are the same
if crypto_symbol == fiat_symbol:
@ -192,6 +192,7 @@ class CryptoToFiatConverter(object):
# return 0 for unsupported stake currencies (fiat-convert should not break the bot)
logger.warning("unsupported crypto-symbol %s - returning 0.0", crypto_symbol)
return 0.0
try:
return float(
self._coinmarketcap.ticker(

View File

@ -76,17 +76,14 @@ class FreqtradeBot(object):
else:
self.state = State.STOPPED
def clean(self) -> bool:
def cleanup(self) -> None:
"""
Cleanup the application state und finish all pending tasks
Cleanup pending resources on an already stopped bot
:return: None
"""
self.rpc.send_msg('*Status:* `Stopping trader...`')
logger.info('Stopping trader and cleaning up modules...')
self.state = State.STOPPED
logger.info('Cleaning up modules ...')
self.rpc.cleanup()
persistence.cleanup()
return True
def worker(self, old_state: State = None) -> State:
"""
@ -97,7 +94,7 @@ class FreqtradeBot(object):
# Log state transition
state = self.state
if state != old_state:
self.rpc.send_msg('*Status:* `{}`'.format(state.name.lower()))
self.rpc.send_msg(f'*Status:* `{state.name.lower()}`')
logger.info('Changing state to: %s', state.name)
if state == State.STOPPED:
@ -171,12 +168,10 @@ class FreqtradeBot(object):
logger.warning('%s, retrying in 30 seconds...', error)
time.sleep(constants.RETRY_TIMEOUT)
except OperationalException:
tb = traceback.format_exc()
hint = 'Issue `/start` if you think it is safe to restart.'
self.rpc.send_msg(
'*Status:* OperationalException:\n```\n{traceback}```{hint}'
.format(
traceback=traceback.format_exc(),
hint='Issue `/start` if you think it is safe to restart.'
)
f'*Status:* OperationalException:\n```\n{tb}```{hint}'
)
logger.exception('OperationalException. Stopping trader ...')
self.state = State.STOPPED
@ -254,12 +249,13 @@ class FreqtradeBot(object):
"""
Checks the implemented trading indicator(s) for a randomly picked pair,
if one pair triggers the buy_signal a new trade record gets created
:param stake_amount: amount of btc to spend
:param interval: Ticker interval used for Analyze
:return: True if a trade object has been created and persisted, False otherwise
"""
stake_amount = self.config['stake_amount']
interval = self.analyze.get_ticker_interval()
stake_currency = self.config['stake_currency']
fiat_currency = self.config['fiat_display_currency']
exc_name = exchange.get_name()
logger.info(
'Checking buy signals to create a new trade with stake_amount: %f ...',
@ -267,10 +263,9 @@ class FreqtradeBot(object):
)
whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
# Check if stake_amount is fulfilled
if exchange.get_balance(self.config['stake_currency']) < stake_amount:
if exchange.get_balance(stake_currency) < stake_amount:
raise DependencyException(
'stake amount is not fulfilled (currency={})'.format(self.config['stake_currency'])
)
f'stake amount is not fulfilled (currency={stake_currency})')
# Remove currently opened and latest pairs from whitelist
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
@ -289,7 +284,8 @@ class FreqtradeBot(object):
break
else:
return False
pair_s = pair.replace('_', '/')
pair_url = exchange.get_pair_detail_url(pair)
# Calculate amount
buy_limit = self.get_target_bid(exchange.get_ticker(pair))
amount = stake_amount / buy_limit
@ -298,23 +294,15 @@ class FreqtradeBot(object):
stake_amount_fiat = self.fiat_converter.convert_amount(
stake_amount,
self.config['stake_currency'],
self.config['fiat_display_currency']
stake_currency,
fiat_currency
)
# Create trade entity and return
self.rpc.send_msg(
'*{}:* Buying [{}]({}) with limit `{:.8f} ({:.6f} {}, {:.3f} {})` '
.format(
exchange.get_name(),
pair.replace('_', '/'),
exchange.get_pair_detail_url(pair),
buy_limit,
stake_amount,
self.config['stake_currency'],
stake_amount_fiat,
self.config['fiat_display_currency']
)
f"""*{exc_name}:* Buying [{pair_s}]({pair_url}) \
with limit `{buy_limit:.8f} ({stake_amount:.6f} \
{stake_currency}, {stake_amount_fiat:.3f} {fiat_currency})`"""
)
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
fee = exchange.get_fee(symbol=pair, taker_or_maker='maker')
@ -415,12 +403,12 @@ class FreqtradeBot(object):
fee_abs += exectrade['fee']['cost']
if amount != order_amount:
logger.warning("amount {} does not match amount {}".format(amount, trade.amount))
logger.warning(f"amount {amount} does not match amount {trade.amount}")
raise OperationalException("Half bought? Amounts don't match")
real_amount = amount - fee_abs
if fee_abs != 0:
logger.info("Applying fee on amount for {} (from {} to {}) from Trades".format(
trade, order['amount'], real_amount))
logger.info(f"""Applying fee on amount for {trade} \
(from {order_amount} to {real_amount}) from Trades""")
return real_amount
def handle_trade(self, trade: Trade) -> bool:
@ -429,7 +417,7 @@ class FreqtradeBot(object):
:return: True if trade has been sold, False otherwise
"""
if not trade.is_open:
raise ValueError('attempt to handle closed trade: {}'.format(trade))
raise ValueError(f'attempt to handle closed trade: {trade}')
logger.debug('Handling %s ...', trade)
current_rate = exchange.get_ticker(trade.pair)['bid']
@ -455,6 +443,12 @@ class FreqtradeBot(object):
for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
try:
# FIXME: Somehow the query above returns results
# where the open_order_id is in fact None.
# This is probably because the record got
# updated via /forcesell in a different thread.
if not trade.open_order_id:
continue
order = exchange.get_order(trade.open_order_id, trade.pair)
except requests.exceptions.RequestException:
logger.info(
@ -480,16 +474,14 @@ class FreqtradeBot(object):
"""Buy timeout - cancel order
:return: True if order was fully cancelled
"""
pair_s = trade.pair.replace('_', '/')
exchange.cancel_order(trade.open_order_id, trade.pair)
if order['remaining'] == order['amount']:
# if trade is not partially completed, just delete the trade
Trade.session.delete(trade)
# FIX? do we really need to flush, caller of
# check_handle_timedout will flush afterwards
Trade.session.flush()
logger.info('Buy order timeout for %s.', trade)
self.rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
self.rpc.send_msg(f'*Timeout:* Unfilled buy order for {pair_s} cancelled')
return True
# if trade is partially complete, edit the stake details for the trade
@ -498,8 +490,7 @@ class FreqtradeBot(object):
trade.stake_amount = trade.amount * trade.open_rate
trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade)
self.rpc.send_msg('*Timeout:* Remaining buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
self.rpc.send_msg(f'*Timeout:* Remaining buy order for {pair_s} cancelled')
return False
# FIX: 20180110, should cancel_order() be cond. or unconditionally called?
@ -508,6 +499,7 @@ class FreqtradeBot(object):
Sell timeout - cancel order and update trade
:return: True if order was fully cancelled
"""
pair_s = trade.pair.replace('_', '/')
if order['remaining'] == order['amount']:
# if trade is not partially completed, just cancel the trade
exchange.cancel_order(trade.open_order_id, trade.pair)
@ -516,8 +508,7 @@ class FreqtradeBot(object):
trade.close_date = None
trade.is_open = True
trade.open_order_id = None
self.rpc.send_msg('*Timeout:* Unfilled sell order for {} cancelled'.format(
trade.pair.replace('_', '/')))
self.rpc.send_msg(f'*Timeout:* Unfilled sell order for {pair_s} cancelled')
logger.info('Sell order timeout for %s.', trade)
return True
@ -531,6 +522,8 @@ class FreqtradeBot(object):
:param limit: limit rate for the sell order
:return: None
"""
exc = trade.exchange
pair = trade.pair
# Execute sell and update trade record
order_id = exchange.sell(str(trade.pair), limit, trade.amount)['id']
trade.open_order_id = order_id
@ -540,43 +533,31 @@ class FreqtradeBot(object):
profit_trade = trade.calc_profit(rate=limit)
current_rate = exchange.get_ticker(trade.pair)['bid']
profit = trade.calc_profit_percent(limit)
pair_url = exchange.get_pair_detail_url(trade.pair)
gain = "profit" if fmt_exp_profit > 0 else "loss"
message = "*{exchange}:* Selling\n" \
"*Current Pair:* [{pair}]({pair_url})\n" \
"*Limit:* `{limit}`\n" \
"*Amount:* `{amount}`\n" \
"*Open Rate:* `{open_rate:.8f}`\n" \
"*Current Rate:* `{current_rate:.8f}`\n" \
"*Profit:* `{profit:.2f}%`" \
"".format(
exchange=trade.exchange,
pair=trade.pair,
pair_url=exchange.get_pair_detail_url(trade.pair),
limit=limit,
open_rate=trade.open_rate,
current_rate=current_rate,
amount=round(trade.amount, 8),
profit=round(profit * 100, 2),
)
message = f"*{exc}:* Selling\n" \
f"*Current Pair:* [{pair}]({pair_url})\n" \
f"*Limit:* `{limit}`\n" \
f"*Amount:* `{round(trade.amount, 8)}`\n" \
f"*Open Rate:* `{trade.open_rate:.8f}`\n" \
f"*Current Rate:* `{current_rate:.8f}`\n" \
f"*Profit:* `{round(profit * 100, 2):.2f}%`" \
""
# For regular case, when the configuration exists
if 'stake_currency' in self.config and 'fiat_display_currency' in self.config:
stake = self.config['stake_currency']
fiat = self.config['fiat_display_currency']
fiat_converter = CryptoToFiatConverter()
profit_fiat = fiat_converter.convert_amount(
profit_trade,
self.config['stake_currency'],
self.config['fiat_display_currency']
stake,
fiat
)
message += '` ({gain}: {profit_percent:.2f}%, {profit_coin:.8f} {coin}`' \
'` / {profit_fiat:.3f} {fiat})`' \
''.format(
gain="profit" if fmt_exp_profit > 0 else "loss",
profit_percent=fmt_exp_profit,
profit_coin=profit_trade,
coin=self.config['stake_currency'],
profit_fiat=profit_fiat,
fiat=self.config['fiat_display_currency'],
)
message += f'` ({gain}: {fmt_exp_profit:.2f}%, {profit_trade:.8f} {stake}`' \
f'` / {profit_fiat:.3f} {fiat})`'\
''
# Because telegram._forcesell does not have the configuration
# Ignore the FIAT value and does not show the stake_currency as well
else:

View File

@ -5,12 +5,14 @@ Read the documentation to know what cli arguments you need.
"""
import logging
import sys
from argparse import Namespace
from typing import List
from freqtrade import OperationalException
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.state import State
logger = logging.getLogger('freqtrade')
@ -44,6 +46,8 @@ def main(sysargv: List[str]) -> None:
state = None
while 1:
state = freqtrade.worker(old_state=state)
if state == State.RELOAD_CONF:
freqtrade = reconfigure(freqtrade, args)
except KeyboardInterrupt:
logger.info('SIGINT received, aborting ...')
@ -55,10 +59,28 @@ def main(sysargv: List[str]) -> None:
logger.exception('Fatal exception!')
finally:
if freqtrade:
freqtrade.clean()
freqtrade.rpc.send_msg('*Status:* `Process died ...`')
freqtrade.cleanup()
sys.exit(return_code)
def reconfigure(freqtrade: FreqtradeBot, args: Namespace) -> FreqtradeBot:
"""
Cleans up current instance, reloads the configuration and returns the new instance
"""
# Clean up current modules
freqtrade.cleanup()
# Create new instance
freqtrade = FreqtradeBot(Configuration(args).get_config())
freqtrade.rpc.send_msg(
'*Status:* `Config reloaded ...`'.format(
freqtrade.state.name.lower()
)
)
return freqtrade
def set_loggers() -> None:
"""
Set the logger level for Third party libs

View File

@ -85,7 +85,7 @@ def load_data(datadir: str,
ticker_interval: str,
pairs: Optional[List[str]] = None,
refresh_pairs: Optional[bool] = False,
timerange: TimeRange = TimeRange()) -> Dict[str, List]:
timerange: TimeRange = TimeRange(None, None, 0, 0)) -> Dict[str, List]:
"""
Loads ticker history data for the given parameters
:return: dict
@ -125,7 +125,7 @@ def make_testdata_path(datadir: str) -> str:
def download_pairs(datadir, pairs: List[str],
ticker_interval: str,
timerange: TimeRange = TimeRange()) -> bool:
timerange: TimeRange = TimeRange(None, None, 0, 0)) -> bool:
"""For each pairs passed in parameters, download the ticker intervals"""
for pair in pairs:
try:

View File

@ -237,6 +237,9 @@ class Backtesting(object):
timerange=timerange
)
if not data:
logger.critical("No data found. Terminating.")
return
# Ignore max_open_trades in backtesting, except realistic flag was passed
if self.config.get('realistic_simulation', False):
max_open_trades = self.config['max_open_trades']

View File

@ -2,24 +2,34 @@
This module contains class to define a RPC communications
"""
import logging
from abc import abstractmethod
from datetime import datetime, timedelta, date
from decimal import Decimal
from typing import Dict, Tuple, Any
from typing import Dict, Tuple, Any, List
import arrow
import sqlalchemy as sql
from pandas import DataFrame
from numpy import mean, nan_to_num
from pandas import DataFrame
from freqtrade import exchange
from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade
from freqtrade.state import State
logger = logging.getLogger(__name__)
class RPCException(Exception):
"""
Should be raised with a rpc-formatted message in an _rpc_* method
if the required state is wrong, i.e.:
raise RPCException('*Status:* `no active trade`')
"""
pass
class RPC(object):
"""
RPC class can be used to have extra feature, like bot data, and access to DB data
@ -30,20 +40,32 @@ class RPC(object):
:param freqtrade: Instance of a freqtrade bot
:return: None
"""
self.freqtrade = freqtrade
self._freqtrade = freqtrade
def rpc_trade_status(self) -> Tuple[bool, Any]:
@abstractmethod
def cleanup(self) -> None:
""" Cleanup pending module resources """
@property
@abstractmethod
def name(self) -> str:
""" Returns the lowercase name of this module """
@abstractmethod
def send_msg(self, msg: str) -> None:
""" Sends a message to all registered rpc modules """
def _rpc_trade_status(self) -> List[str]:
"""
Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is
a remotely exposed function
:return:
"""
# Fetch open trade
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
if self.freqtrade.state != State.RUNNING:
return True, '*Status:* `trader is not running`'
if self._freqtrade.state != State.RUNNING:
raise RPCException('*Status:* `trader is not running`')
elif not trades:
return True, '*Status:* `no active trade`'
raise RPCException('*Status:* `no active trade`')
else:
result = []
for trade in trades:
@ -82,14 +104,14 @@ class RPC(object):
) if order else None,
)
result.append(message)
return False, result
return result
def rpc_status_table(self) -> Tuple[bool, Any]:
def _rpc_status_table(self) -> DataFrame:
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
if self.freqtrade.state != State.RUNNING:
return True, '*Status:* `trader is not running`'
if self._freqtrade.state != State.RUNNING:
raise RPCException('*Status:* `trader is not running`')
elif not trades:
return True, '*Status:* `no active order`'
raise RPCException('*Status:* `no active order`')
else:
trades_list = []
for trade in trades:
@ -105,22 +127,18 @@ class RPC(object):
columns = ['ID', 'Pair', 'Since', 'Profit']
df_statuses = DataFrame.from_records(trades_list, columns=columns)
df_statuses = df_statuses.set_index(columns[0])
# The style used throughout is to return a tuple
# consisting of (error_occured?, result)
# Another approach would be to just return the
# result, or raise error
return False, df_statuses
return df_statuses
def rpc_daily_profit(
def _rpc_daily_profit(
self, timescale: int,
stake_currency: str, fiat_display_currency: str) -> Tuple[bool, Any]:
stake_currency: str, fiat_display_currency: str) -> List[List[Any]]:
today = datetime.utcnow().date()
profit_days: Dict[date, Dict] = {}
if not (isinstance(timescale, int) and timescale > 0):
return True, '*Daily [n]:* `must be an integer greater than 0`'
raise RPCException('*Daily [n]:* `must be an integer greater than 0`')
fiat = self.freqtrade.fiat_converter
fiat = self._freqtrade.fiat_converter
for day in range(0, timescale):
profitday = today - timedelta(days=day)
trades = Trade.query \
@ -135,7 +153,7 @@ class RPC(object):
'trades': len(trades)
}
stats = [
return [
[
key,
'{value:.8f} {symbol}'.format(
@ -157,13 +175,10 @@ class RPC(object):
]
for key, value in profit_days.items()
]
return False, stats
def rpc_trade_statistics(
self, stake_currency: str, fiat_display_currency: str) -> Tuple[bool, Any]:
"""
:return: cumulative profit statistics.
"""
def _rpc_trade_statistics(
self, stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
""" Returns cumulative profit statistics """
trades = Trade.query.order_by(Trade.id).all()
profit_all_coin = []
@ -201,13 +216,13 @@ class RPC(object):
.order_by(sql.text('profit_sum DESC')).first()
if not best_pair:
return True, '*Status:* `no closed trade`'
raise RPCException('*Status:* `no closed trade`')
bp_pair, bp_rate = best_pair
# FIX: we want to keep fiatconverter in a state/environment,
# doing this will utilize its caching functionallity, instead we reinitialize it here
fiat = self.freqtrade.fiat_converter
fiat = self._freqtrade.fiat_converter
# Prepare data to display
profit_closed_coin = round(sum(profit_closed_coin), 8)
profit_closed_percent = round(nan_to_num(mean(profit_closed_percent)) * 100, 2)
@ -224,35 +239,29 @@ class RPC(object):
fiat_display_currency
)
num = float(len(durations) or 1)
return (
False,
{
'profit_closed_coin': profit_closed_coin,
'profit_closed_percent': profit_closed_percent,
'profit_closed_fiat': profit_closed_fiat,
'profit_all_coin': profit_all_coin,
'profit_all_percent': profit_all_percent,
'profit_all_fiat': profit_all_fiat,
'trade_count': len(trades),
'first_trade_date': arrow.get(trades[0].open_date).humanize(),
'latest_trade_date': arrow.get(trades[-1].open_date).humanize(),
'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0],
'best_pair': bp_pair,
'best_rate': round(bp_rate * 100, 2)
}
)
return {
'profit_closed_coin': profit_closed_coin,
'profit_closed_percent': profit_closed_percent,
'profit_closed_fiat': profit_closed_fiat,
'profit_all_coin': profit_all_coin,
'profit_all_percent': profit_all_percent,
'profit_all_fiat': profit_all_fiat,
'trade_count': len(trades),
'first_trade_date': arrow.get(trades[0].open_date).humanize(),
'latest_trade_date': arrow.get(trades[-1].open_date).humanize(),
'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0],
'best_pair': bp_pair,
'best_rate': round(bp_rate * 100, 2),
}
def rpc_balance(self, fiat_display_currency: str) -> Tuple[bool, Any]:
"""
:return: current account balance per crypto
"""
def _rpc_balance(self, fiat_display_currency: str) -> Tuple[List[Dict], float, str, float]:
""" Returns current account balance per crypto """
output = []
total = 0.0
for coin, balance in exchange.get_balances().items():
if not balance['total']:
continue
rate = None
if coin == 'BTC':
rate = 1.0
else:
@ -272,39 +281,39 @@ class RPC(object):
}
)
if total == 0.0:
return True, '`All balances are zero.`'
raise RPCException('`All balances are zero.`')
fiat = self.freqtrade.fiat_converter
fiat = self._freqtrade.fiat_converter
symbol = fiat_display_currency
value = fiat.convert_amount(total, 'BTC', symbol)
return False, (output, total, symbol, value)
return output, total, symbol, value
def rpc_start(self) -> Tuple[bool, str]:
"""
Handler for start.
"""
if self.freqtrade.state == State.RUNNING:
return True, '*Status:* `already running`'
def _rpc_start(self) -> str:
""" Handler for start """
if self._freqtrade.state == State.RUNNING:
return '*Status:* `already running`'
self.freqtrade.state = State.RUNNING
return False, '`Starting trader ...`'
self._freqtrade.state = State.RUNNING
return '`Starting trader ...`'
def rpc_stop(self) -> Tuple[bool, str]:
"""
Handler for stop.
"""
if self.freqtrade.state == State.RUNNING:
self.freqtrade.state = State.STOPPED
return False, '`Stopping trader ...`'
def _rpc_stop(self) -> str:
""" Handler for stop """
if self._freqtrade.state == State.RUNNING:
self._freqtrade.state = State.STOPPED
return '`Stopping trader ...`'
return True, '*Status:* `already stopped`'
return '*Status:* `already stopped`'
def _rpc_reload_conf(self) -> str:
""" Handler for reload_conf. """
self._freqtrade.state = State.RELOAD_CONF
return '*Status:* `Reloading config ...`'
# FIX: no test for this!!!!
def rpc_forcesell(self, trade_id) -> Tuple[bool, Any]:
def _rpc_forcesell(self, trade_id) -> None:
"""
Handler for forcesell <id>.
Sells the given trade at current price
:return: error or None
"""
def _exec_forcesell(trade: Trade) -> None:
# Check if there is there is an open order
@ -330,17 +339,17 @@ class RPC(object):
# Get current rate and execute sell
current_rate = exchange.get_ticker(trade.pair, False)['bid']
self.freqtrade.execute_sell(trade, current_rate)
self._freqtrade.execute_sell(trade, current_rate)
# ---- EOF def _exec_forcesell ----
if self.freqtrade.state != State.RUNNING:
return True, '`trader is not running`'
if self._freqtrade.state != State.RUNNING:
raise RPCException('`trader is not running`')
if trade_id == 'all':
# Execute sell for all open orders
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
_exec_forcesell(trade)
return False, ''
return
# Query for trade
trade = Trade.query.filter(
@ -351,18 +360,18 @@ class RPC(object):
).first()
if not trade:
logger.warning('forcesell: Invalid argument received')
return True, 'Invalid argument.'
raise RPCException('Invalid argument.')
_exec_forcesell(trade)
return False, ''
Trade.session.flush()
def rpc_performance(self) -> Tuple[bool, Any]:
def _rpc_performance(self) -> List[Dict]:
"""
Handler for performance.
Shows a performance statistic from finished trades
"""
if self.freqtrade.state != State.RUNNING:
return True, '`trader is not running`'
if self._freqtrade.state != State.RUNNING:
raise RPCException('`trader is not running`')
pair_rates = Trade.session.query(Trade.pair,
sql.func.sum(Trade.close_profit).label('profit_sum'),
@ -371,19 +380,14 @@ class RPC(object):
.group_by(Trade.pair) \
.order_by(sql.text('profit_sum DESC')) \
.all()
trades = []
for (pair, rate, count) in pair_rates:
trades.append({'pair': pair, 'profit': round(rate * 100, 2), 'count': count})
return [
{'pair': pair, 'profit': round(rate * 100, 2), 'count': count}
for pair, rate, count in pair_rates
]
return False, trades
def _rpc_count(self) -> List[Trade]:
""" Returns the number of trades running """
if self._freqtrade.state != State.RUNNING:
raise RPCException('`trader is not running`')
def rpc_count(self) -> Tuple[bool, Any]:
"""
Returns the number of trades running
:return: None
"""
if self.freqtrade.state != State.RUNNING:
return True, '`trader is not running`'
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
return False, trades
return Trade.query.filter(Trade.is_open.is_(True)).all()

View File

@ -12,11 +12,12 @@ from telegram.error import NetworkError, TelegramError
from telegram.ext import CommandHandler, Updater
from freqtrade.__init__ import __version__
from freqtrade.rpc.rpc import RPC
from freqtrade.rpc.rpc import RPC, RPCException
logger = logging.getLogger(__name__)
logger.debug('Included module rpc.telegram ...')
def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Callable[..., Any]:
"""
@ -25,9 +26,7 @@ def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Call
:return: decorated function
"""
def wrapper(self, *args, **kwargs):
"""
Decorator logic
"""
""" Decorator logic """
update = kwargs.get('update') or args[1]
# Reject unauthorized messages
@ -54,9 +53,12 @@ def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Call
class Telegram(RPC):
"""
Telegram, this class send messages to Telegram
"""
""" This class handles all telegram communication """
@property
def name(self) -> str:
return "telegram"
def __init__(self, freqtrade) -> None:
"""
Init the Telegram call, and init the super class RPC
@ -74,12 +76,7 @@ class Telegram(RPC):
Initializes this module with the given config,
registers all known command handlers
and starts polling for message updates
:param config: config to use
:return: None
"""
if not self.is_enabled():
return
self._updater = Updater(token=self._config['telegram']['token'], workers=0)
# Register command handler and start telegram message polling
@ -93,6 +90,7 @@ class Telegram(RPC):
CommandHandler('performance', self._performance),
CommandHandler('daily', self._daily),
CommandHandler('count', self._count),
CommandHandler('reload_conf', self._reload_conf),
CommandHandler('help', self._help),
CommandHandler('version', self._version),
]
@ -114,16 +112,11 @@ class Telegram(RPC):
Stops all running telegram threads.
:return: None
"""
if not self.is_enabled():
return
self._updater.stop()
def is_enabled(self) -> bool:
"""
Returns True if the telegram module is activated, False otherwise
"""
return bool(self._config.get('telegram', {}).get('enabled', False))
def send_msg(self, msg: str) -> None:
""" Send a message to telegram channel """
self._send_msg(msg)
@authorized_only
def _status(self, bot: Bot, update: Update) -> None:
@ -142,13 +135,11 @@ class Telegram(RPC):
self._status_table(bot, update)
return
# Fetch open trade
(error, trades) = self.rpc_trade_status()
if error:
self.send_msg(trades, bot=bot)
else:
for trademsg in trades:
self.send_msg(trademsg, bot=bot)
try:
for trade_msg in self._rpc_trade_status():
self._send_msg(trade_msg, bot=bot)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _status_table(self, bot: Bot, update: Update) -> None:
@ -159,15 +150,12 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
# Fetch open trade
(err, df_statuses) = self.rpc_status_table()
if err:
self.send_msg(df_statuses, bot=bot)
else:
try:
df_statuses = self._rpc_status_table()
message = tabulate(df_statuses, headers='keys', tablefmt='simple')
message = "<pre>{}</pre>".format(message)
self.send_msg(message, parse_mode=ParseMode.HTML)
self._send_msg("<pre>{}</pre>".format(message), parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _daily(self, bot: Bot, update: Update) -> None:
@ -182,14 +170,12 @@ class Telegram(RPC):
timescale = int(update.message.text.replace('/daily', '').strip())
except (TypeError, ValueError):
timescale = 7
(error, stats) = self.rpc_daily_profit(
timescale,
self._config['stake_currency'],
self._config['fiat_display_currency']
)
if error:
self.send_msg(stats, bot=bot)
else:
try:
stats = self._rpc_daily_profit(
timescale,
self._config['stake_currency'],
self._config['fiat_display_currency']
)
stats = tabulate(stats,
headers=[
'Day',
@ -198,11 +184,10 @@ class Telegram(RPC):
],
tablefmt='simple')
message = '<b>Daily Profit over the last {} days</b>:\n<pre>{}</pre>'\
.format(
timescale,
stats
)
self.send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
.format(timescale, stats)
self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _profit(self, bot: Bot, update: Update) -> None:
@ -213,67 +198,63 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
(error, stats) = self.rpc_trade_statistics(
self._config['stake_currency'],
self._config['fiat_display_currency']
)
if error:
self.send_msg(stats, bot=bot)
return
try:
stats = self._rpc_trade_statistics(
self._config['stake_currency'],
self._config['fiat_display_currency'])
# Message to display
markdown_msg = "*ROI:* Close trades\n" \
"∙ `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`\n" \
"∙ `{profit_closed_fiat:.3f} {fiat}`\n" \
"*ROI:* All trades\n" \
"∙ `{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)`\n" \
"∙ `{profit_all_fiat:.3f} {fiat}`\n" \
"*Total Trade Count:* `{trade_count}`\n" \
"*First Trade opened:* `{first_trade_date}`\n" \
"*Latest Trade opened:* `{latest_trade_date}`\n" \
"*Avg. Duration:* `{avg_duration}`\n" \
"*Best Performing:* `{best_pair}: {best_rate:.2f}%`"\
.format(
coin=self._config['stake_currency'],
fiat=self._config['fiat_display_currency'],
profit_closed_coin=stats['profit_closed_coin'],
profit_closed_percent=stats['profit_closed_percent'],
profit_closed_fiat=stats['profit_closed_fiat'],
profit_all_coin=stats['profit_all_coin'],
profit_all_percent=stats['profit_all_percent'],
profit_all_fiat=stats['profit_all_fiat'],
trade_count=stats['trade_count'],
first_trade_date=stats['first_trade_date'],
latest_trade_date=stats['latest_trade_date'],
avg_duration=stats['avg_duration'],
best_pair=stats['best_pair'],
best_rate=stats['best_rate']
)
self.send_msg(markdown_msg, bot=bot)
# Message to display
markdown_msg = "*ROI:* Close trades\n" \
"∙ `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`\n" \
"∙ `{profit_closed_fiat:.3f} {fiat}`\n" \
"*ROI:* All trades\n" \
"∙ `{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)`\n" \
"∙ `{profit_all_fiat:.3f} {fiat}`\n" \
"*Total Trade Count:* `{trade_count}`\n" \
"*First Trade opened:* `{first_trade_date}`\n" \
"*Latest Trade opened:* `{latest_trade_date}`\n" \
"*Avg. Duration:* `{avg_duration}`\n" \
"*Best Performing:* `{best_pair}: {best_rate:.2f}%`"\
.format(
coin=self._config['stake_currency'],
fiat=self._config['fiat_display_currency'],
profit_closed_coin=stats['profit_closed_coin'],
profit_closed_percent=stats['profit_closed_percent'],
profit_closed_fiat=stats['profit_closed_fiat'],
profit_all_coin=stats['profit_all_coin'],
profit_all_percent=stats['profit_all_percent'],
profit_all_fiat=stats['profit_all_fiat'],
trade_count=stats['trade_count'],
first_trade_date=stats['first_trade_date'],
latest_trade_date=stats['latest_trade_date'],
avg_duration=stats['avg_duration'],
best_pair=stats['best_pair'],
best_rate=stats['best_rate']
)
self._send_msg(markdown_msg, bot=bot)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _balance(self, bot: Bot, update: Update) -> None:
"""
Handler for /balance
"""
(error, result) = self.rpc_balance(self._config['fiat_display_currency'])
if error:
self.send_msg('`All balances are zero.`')
return
""" Handler for /balance """
try:
currencys, total, symbol, value = \
self._rpc_balance(self._config['fiat_display_currency'])
output = ''
for currency in currencys:
output += "*{currency}:*\n" \
"\t`Available: {available: .8f}`\n" \
"\t`Balance: {balance: .8f}`\n" \
"\t`Pending: {pending: .8f}`\n" \
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)
(currencys, total, symbol, value) = result
output = ''
for currency in currencys:
output += "*{currency}:*\n" \
"\t`Available: {available: .8f}`\n" \
"\t`Balance: {balance: .8f}`\n" \
"\t`Pending: {pending: .8f}`\n" \
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)
output += "\n*Estimated Value*:\n" \
"\t`BTC: {0: .8f}`\n" \
"\t`{1}: {2: .2f}`\n".format(total, symbol, value)
self.send_msg(output)
output += "\n*Estimated Value*:\n" \
"\t`BTC: {0: .8f}`\n" \
"\t`{1}: {2: .2f}`\n".format(total, symbol, value)
self._send_msg(output, bot=bot)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _start(self, bot: Bot, update: Update) -> None:
@ -284,9 +265,8 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
(error, msg) = self.rpc_start()
if error:
self.send_msg(msg, bot=bot)
msg = self._rpc_start()
self._send_msg(msg, bot=bot)
@authorized_only
def _stop(self, bot: Bot, update: Update) -> None:
@ -297,8 +277,20 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
(error, msg) = self.rpc_stop()
self.send_msg(msg, bot=bot)
msg = self._rpc_stop()
self._send_msg(msg, bot=bot)
@authorized_only
def _reload_conf(self, bot: Bot, update: Update) -> None:
"""
Handler for /reload_conf.
Triggers a config file reload
:param bot: telegram bot
:param update: message update
:return: None
"""
msg = self._rpc_reload_conf()
self._send_msg(msg, bot=bot)
@authorized_only
def _forcesell(self, bot: Bot, update: Update) -> None:
@ -311,10 +303,10 @@ class Telegram(RPC):
"""
trade_id = update.message.text.replace('/forcesell', '').strip()
(error, message) = self.rpc_forcesell(trade_id)
if error:
self.send_msg(message, bot=bot)
return
try:
self._rpc_forcesell(trade_id)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _performance(self, bot: Bot, update: Update) -> None:
@ -325,19 +317,18 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
(error, trades) = self.rpc_performance()
if error:
self.send_msg(trades, bot=bot)
return
stats = '\n'.join('{index}.\t<code>{pair}\t{profit:.2f}% ({count})</code>'.format(
index=i + 1,
pair=trade['pair'],
profit=trade['profit'],
count=trade['count']
) for i, trade in enumerate(trades))
message = '<b>Performance:</b>\n{}'.format(stats)
self.send_msg(message, parse_mode=ParseMode.HTML)
try:
trades = self._rpc_performance()
stats = '\n'.join('{index}.\t<code>{pair}\t{profit:.2f}% ({count})</code>'.format(
index=i + 1,
pair=trade['pair'],
profit=trade['profit'],
count=trade['count']
) for i, trade in enumerate(trades))
message = '<b>Performance:</b>\n{}'.format(stats)
self._send_msg(message, parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _count(self, bot: Bot, update: Update) -> None:
@ -348,19 +339,18 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
(error, trades) = self.rpc_count()
if error:
self.send_msg(trades, bot=bot)
return
message = tabulate({
'current': [len(trades)],
'max': [self._config['max_open_trades']],
'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)]
}, headers=['current', 'max', 'total stake'], tablefmt='simple')
message = "<pre>{}</pre>".format(message)
logger.debug(message)
self.send_msg(message, parse_mode=ParseMode.HTML)
try:
trades = self._rpc_count()
message = tabulate({
'current': [len(trades)],
'max': [self._config['max_open_trades']],
'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)]
}, headers=['current', 'max', 'total stake'], tablefmt='simple')
message = "<pre>{}</pre>".format(message)
logger.debug(message)
self._send_msg(message, parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _help(self, bot: Bot, update: Update) -> None:
@ -386,7 +376,7 @@ class Telegram(RPC):
"*/help:* `This help message`\n" \
"*/version:* `Show version`"
self.send_msg(message, bot=bot)
self._send_msg(message, bot=bot)
@authorized_only
def _version(self, bot: Bot, update: Update) -> None:
@ -397,10 +387,10 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
self.send_msg('*Version:* `{}`'.format(__version__), bot=bot)
self._send_msg('*Version:* `{}`'.format(__version__), bot=bot)
def send_msg(self, msg: str, bot: Bot = None,
parse_mode: ParseMode = ParseMode.MARKDOWN) -> None:
def _send_msg(self, msg: str, bot: Bot = None,
parse_mode: ParseMode = ParseMode.MARKDOWN) -> None:
"""
Send given markdown message
:param msg: message
@ -408,9 +398,6 @@ class Telegram(RPC):
:param parse_mode: telegram parse mode
:return: None
"""
if not self.is_enabled():
return
bot = bot or self._updater.bot
keyboard = [['/daily', '/profit', '/balance'],

View File

@ -8,7 +8,8 @@ import enum
class State(enum.Enum):
"""
Bot running states
Bot application states
"""
RUNNING = 0
STOPPED = 1
RELOAD_CONF = 2

View File

@ -317,7 +317,7 @@ def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
backtesting = Backtesting(default_conf)
data = backtesting.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 100
assert len(data['UNITTEST/BTC']) == 99
# Load Analyze to compare the result between Backtesting function and Analyze are the same
analyze = Analyze(default_conf)
@ -341,7 +341,7 @@ def test_get_timeframe(default_conf, mocker) -> None:
)
min_date, max_date = backtesting.get_timeframe(data)
assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
assert max_date.isoformat() == '2017-11-14T22:59:00+00:00'
assert max_date.isoformat() == '2017-11-14T22:58:00+00:00'
def test_generate_text_table(default_conf, mocker):
@ -416,6 +416,40 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
assert log_has(line, caplog.record_tuples)
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):
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
mocker.patch('freqtrade.optimize.load_data', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.get_ticker_history')
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
backtest=MagicMock(),
_generate_text_table=MagicMock(return_value='1'),
get_timeframe=get_timeframe,
)
conf = deepcopy(default_conf)
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
conf['ticker_interval'] = "1m"
conf['live'] = False
conf['datadir'] = None
conf['export'] = None
conf['timerange'] = '20180101-20180102'
backtesting = Backtesting(conf)
backtesting.start()
# check the logs, that will contain the backtest result
assert log_has('No data found. Terminating.', caplog.record_tuples)
def test_backtest(default_conf, fee, mocker) -> None:
"""
Test Backtesting.backtest() method
@ -478,7 +512,7 @@ def test_processed(default_conf, mocker) -> None:
def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
tests = [['raise', 17], ['lower', 0], ['sine', 17]]
tests = [['raise', 17], ['lower', 0], ['sine', 16]]
for [contour, numres] in tests:
simple_backtest(default_conf, contour, numres, mocker)
@ -606,10 +640,12 @@ def test_backtest_start_live(default_conf, mocker, caplog):
args = [
'--config', 'config.json',
'--strategy', 'DefaultStrategy',
'--datadir', 'freqtrade/tests/testdata',
'backtesting',
'--ticker-interval', '1m',
'--live',
'--timerange', '-100'
'--timerange', '-100',
'--realistic-simulation'
]
args = get_args(args)
start(args)
@ -619,13 +655,14 @@ def test_backtest_start_live(default_conf, mocker, caplog):
'Using ticker_interval: 1m ...',
'Parameter -l/--live detected ...',
'Using max_open_trades: 1 ...',
'Parameter --timerange detected: -100 ..',
'Parameter --timerange detected: -100 ...',
'Using data folder: freqtrade/tests/testdata ...',
'Using stake_currency: BTC ...',
'Using stake_amount: 0.001 ...',
'Downloading data for all pairs in whitelist ...',
'Measuring data from 2017-11-14T19:32:00+00:00 up to 2017-11-14T22:59:00+00:00 (0 days)..'
'Measuring data from 2017-11-14T19:31:00+00:00 up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Parameter --realistic-simulation detected ...'
]
for line in exists:
log_has(line, caplog.record_tuples)
assert log_has(line, caplog.record_tuples)

View File

@ -7,9 +7,11 @@ Unit test file for rpc/rpc.py
from datetime import datetime
from unittest.mock import MagicMock
import pytest
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade
from freqtrade.rpc.rpc import RPC
from freqtrade.rpc.rpc import RPC, RPCException
from freqtrade.state import State
from freqtrade.tests.test_freqtradebot import patch_get_signal, patch_coinmarketcap
@ -29,7 +31,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
"""
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -41,19 +43,16 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
(error, result) = rpc.rpc_trade_status()
assert error
assert 'trader is not running' in result
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_trade_status()
freqtradebot.state = State.RUNNING
(error, result) = rpc.rpc_trade_status()
assert error
assert 'no active trade' in result
with pytest.raises(RPCException, match=r'.*no active trade*'):
rpc._rpc_trade_status()
freqtradebot.create_trade()
(error, result) = rpc.rpc_trade_status()
assert not error
trade = result[0]
trades = rpc._rpc_trade_status()
trade = trades[0]
result_message = [
'*Trade ID:* `1`\n'
@ -68,7 +67,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'*Current Profit:* `-0.59%`\n'
'*Open Order:* `(limit buy rem=0.00000000)`'
]
assert result == result_message
assert trades == result_message
assert trade.find('[ETH/BTC]') >= 0
@ -78,7 +77,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
"""
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -90,17 +89,15 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
(error, result) = rpc.rpc_status_table()
assert error
assert '*Status:* `trader is not running`' in result
with pytest.raises(RPCException, match=r'.*\*Status:\* `trader is not running``*'):
rpc._rpc_status_table()
freqtradebot.state = State.RUNNING
(error, result) = rpc.rpc_status_table()
assert error
assert '*Status:* `no active order`' in result
with pytest.raises(RPCException, match=r'.*\*Status:\* `no active order`*'):
rpc._rpc_status_table()
freqtradebot.create_trade()
(error, result) = rpc.rpc_status_table()
result = rpc._rpc_status_table()
assert 'just now' in result['Since'].all()
assert 'ETH/BTC' in result['Pair'].all()
assert '-0.59%' in result['Profit'].all()
@ -113,7 +110,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
"""
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -140,8 +137,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
# Try valid data
update.message.text = '/daily 2'
(error, days) = rpc.rpc_daily_profit(7, stake_currency, fiat_display_currency)
assert not error
days = rpc._rpc_daily_profit(7, stake_currency, fiat_display_currency)
assert len(days) == 7
for day in days:
# [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD']
@ -154,9 +150,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
assert str(days[0][0]) == str(datetime.utcnow().date())
# Try invalid data
(error, days) = rpc.rpc_daily_profit(0, stake_currency, fiat_display_currency)
assert error
assert days.find('must be an integer greater than 0') >= 0
with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'):
rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency)
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
@ -170,7 +165,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -184,9 +179,8 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
rpc = RPC(freqtradebot)
(error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency)
assert error
assert stats.find('no closed trade') >= 0
with pytest.raises(RPCException, match=r'.*no closed trade*'):
rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
# Create some test data
freqtradebot.create_trade()
@ -219,8 +213,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
trade.close_date = datetime.utcnow()
trade.is_open = False
(error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency)
assert not error
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05)
assert prec_satoshi(stats['profit_closed_percent'], 6.2)
assert prec_satoshi(stats['profit_closed_fiat'], 0.93255)
@ -248,7 +241,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -281,8 +274,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
for trade in Trade.query.order_by(Trade.id).all():
trade.open_rate = None
(error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency)
assert not error
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert prec_satoshi(stats['profit_closed_coin'], 0)
assert prec_satoshi(stats['profit_closed_percent'], 0)
assert prec_satoshi(stats['profit_closed_fiat'], 0)
@ -320,7 +312,7 @@ def test_rpc_balance_handle(default_conf, mocker):
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -330,18 +322,16 @@ def test_rpc_balance_handle(default_conf, mocker):
freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
(error, res) = rpc.rpc_balance(default_conf['fiat_display_currency'])
assert not error
(trade, x, y, z) = res
assert prec_satoshi(x, 12)
assert prec_satoshi(z, 180000)
assert 'USD' in y
assert len(trade) == 1
assert 'BTC' in trade[0]['currency']
assert prec_satoshi(trade[0]['available'], 10)
assert prec_satoshi(trade[0]['balance'], 12)
assert prec_satoshi(trade[0]['pending'], 2)
assert prec_satoshi(trade[0]['est_btc'], 12)
output, total, symbol, value = rpc._rpc_balance(default_conf['fiat_display_currency'])
assert prec_satoshi(total, 12)
assert prec_satoshi(value, 180000)
assert 'USD' in symbol
assert len(output) == 1
assert 'BTC' in output[0]['currency']
assert prec_satoshi(output[0]['available'], 10)
assert prec_satoshi(output[0]['balance'], 12)
assert prec_satoshi(output[0]['pending'], 2)
assert prec_satoshi(output[0]['est_btc'], 12)
def test_rpc_start(mocker, default_conf) -> None:
@ -350,7 +340,7 @@ def test_rpc_start(mocker, default_conf) -> None:
"""
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -361,13 +351,11 @@ def test_rpc_start(mocker, default_conf) -> None:
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
(error, result) = rpc.rpc_start()
assert not error
result = rpc._rpc_start()
assert '`Starting trader ...`' in result
assert freqtradebot.state == State.RUNNING
(error, result) = rpc.rpc_start()
assert error
result = rpc._rpc_start()
assert '*Status:* `already running`' in result
assert freqtradebot.state == State.RUNNING
@ -378,7 +366,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
"""
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -389,13 +377,11 @@ def test_rpc_stop(mocker, default_conf) -> None:
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
(error, result) = rpc.rpc_stop()
assert not error
result = rpc._rpc_stop()
assert '`Stopping trader ...`' in result
assert freqtradebot.state == State.STOPPED
(error, result) = rpc.rpc_stop()
assert error
result = rpc._rpc_stop()
assert '*Status:* `already stopped`' in result
assert freqtradebot.state == State.STOPPED
@ -406,7 +392,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
"""
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
cancel_order_mock = MagicMock()
mocker.patch.multiple(
@ -428,36 +414,26 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
(error, res) = rpc.rpc_forcesell(None)
assert error
assert res == '`trader is not running`'
with pytest.raises(RPCException, match=r'.*`trader is not running`*'):
rpc._rpc_forcesell(None)
freqtradebot.state = State.RUNNING
(error, res) = rpc.rpc_forcesell(None)
assert error
assert res == 'Invalid argument.'
with pytest.raises(RPCException, match=r'.*Invalid argument.*'):
rpc._rpc_forcesell(None)
(error, res) = rpc.rpc_forcesell('all')
assert not error
assert res == ''
rpc._rpc_forcesell('all')
freqtradebot.create_trade()
(error, res) = rpc.rpc_forcesell('all')
assert not error
assert res == ''
rpc._rpc_forcesell('all')
(error, res) = rpc.rpc_forcesell('1')
assert not error
assert res == ''
rpc._rpc_forcesell('1')
freqtradebot.state = State.STOPPED
(error, res) = rpc.rpc_forcesell(None)
assert error
assert res == '`trader is not running`'
with pytest.raises(RPCException, match=r'.*`trader is not running`*'):
rpc._rpc_forcesell(None)
(error, res) = rpc.rpc_forcesell('all')
assert error
assert res == '`trader is not running`'
with pytest.raises(RPCException, match=r'.*`trader is not running`*'):
rpc._rpc_forcesell('all')
freqtradebot.state = State.RUNNING
assert cancel_order_mock.call_count == 0
@ -475,9 +451,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
# and trade amount is updated
(error, res) = rpc.rpc_forcesell('1')
assert not error
assert res == ''
rpc._rpc_forcesell('1')
assert cancel_order_mock.call_count == 1
assert trade.amount == filled_amount
@ -495,9 +469,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
}
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
(error, res) = rpc.rpc_forcesell('2')
assert not error
assert res == ''
rpc._rpc_forcesell('2')
assert cancel_order_mock.call_count == 2
assert trade.amount == amount
@ -511,9 +483,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
'side': 'sell'
}
)
(error, res) = rpc.rpc_forcesell('3')
assert not error
assert res == ''
rpc._rpc_forcesell('3')
# status quo, no exchange calls
assert cancel_order_mock.call_count == 2
@ -525,7 +495,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
"""
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -550,8 +520,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
trade.close_date = datetime.utcnow()
trade.is_open = False
(error, res) = rpc.rpc_performance()
assert not error
res = rpc._rpc_performance()
assert len(res) == 1
assert res[0]['pair'] == 'ETH/BTC'
assert res[0]['count'] == 1
@ -564,7 +533,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
"""
patch_get_signal(mocker, (True, False))
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.rpc_manager.Telegram', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
@ -576,14 +545,12 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
(error, trades) = rpc.rpc_count()
trades = rpc._rpc_count()
nb_trades = len(trades)
assert not error
assert nb_trades == 0
# Create some test data
freqtradebot.create_trade()
(error, trades) = rpc.rpc_count()
trades = rpc._rpc_count()
nb_trades = len(trades)
assert not error
assert nb_trades == 1

View File

@ -7,49 +7,35 @@ from copy import deepcopy
from unittest.mock import MagicMock
from freqtrade.rpc.rpc_manager import RPCManager
from freqtrade.rpc.telegram import Telegram
from freqtrade.tests.conftest import log_has, get_patched_freqtradebot
def test_rpc_manager_object() -> None:
"""
Test the Arguments object has the mandatory methods
:return: None
"""
assert hasattr(RPCManager, '_init')
""" Test the Arguments object has the mandatory methods """
assert hasattr(RPCManager, 'send_msg')
assert hasattr(RPCManager, 'cleanup')
def test__init__(mocker, default_conf) -> None:
"""
Test __init__() method
"""
init_mock = mocker.patch('freqtrade.rpc.rpc_manager.RPCManager._init', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
""" Test __init__() method """
conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False
rpc_manager = RPCManager(freqtradebot)
assert rpc_manager.freqtrade == freqtradebot
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
assert rpc_manager.registered_modules == []
assert rpc_manager.telegram is None
assert init_mock.call_count == 1
def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
"""
Test _init() method with Telegram disabled
"""
""" Test _init() method with Telegram disabled """
caplog.set_level(logging.DEBUG)
conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False
freqtradebot = get_patched_freqtradebot(mocker, conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, conf))
assert not log_has('Enabling rpc.telegram ...', caplog.record_tuples)
assert rpc_manager.registered_modules == []
assert rpc_manager.telegram is None
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
@ -59,14 +45,12 @@ def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
caplog.set_level(logging.DEBUG)
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
assert log_has('Enabling rpc.telegram ...', caplog.record_tuples)
len_modules = len(rpc_manager.registered_modules)
assert len_modules == 1
assert 'telegram' in rpc_manager.registered_modules
assert isinstance(rpc_manager.telegram, Telegram)
assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules]
def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
@ -99,11 +83,11 @@ def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
rpc_manager = RPCManager(freqtradebot)
# Check we have Telegram as a registered modules
assert 'telegram' in rpc_manager.registered_modules
assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules]
rpc_manager.cleanup()
assert log_has('Cleaning up rpc.telegram ...', caplog.record_tuples)
assert 'telegram' not in rpc_manager.registered_modules
assert 'telegram' not in [mod.name for mod in rpc_manager.registered_modules]
assert telegram_mock.call_count == 1
@ -120,7 +104,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg('test')
assert log_has('test', caplog.record_tuples)
assert log_has('Sending rpc message: test', caplog.record_tuples)
assert telegram_mock.call_count == 0
@ -135,5 +119,5 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg('test')
assert log_has('test', caplog.record_tuples)
assert log_has('Sending rpc message: test', caplog.record_tuples)
assert telegram_mock.call_count == 1

View File

@ -32,6 +32,9 @@ class DummyCls(Telegram):
super().__init__(freqtrade)
self.state = {'called': False}
def _init(self):
pass
@authorized_only
def dummy_handler(self, *args, **kwargs) -> None:
"""
@ -60,9 +63,7 @@ def test__init__(default_conf, mocker) -> None:
def test_init(default_conf, mocker, caplog) -> None:
"""
Test _init() method
"""
""" Test _init() method """
start_polling = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling))
@ -70,31 +71,16 @@ def test_init(default_conf, mocker, caplog) -> None:
assert start_polling.call_count == 0
# number of handles registered
assert start_polling.dispatcher.add_handler.call_count == 11
assert start_polling.dispatcher.add_handler.call_count > 0
assert start_polling.start_polling.call_count == 1
message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \
"['balance'], ['start'], ['stop'], ['forcesell'], ['performance'], ['daily'], " \
"['count'], ['help'], ['version']]"
"['count'], ['reload_conf'], ['help'], ['version']]"
assert log_has(message_str, caplog.record_tuples)
def test_init_disabled(default_conf, mocker, caplog) -> None:
"""
Test _init() method when Telegram is disabled
"""
conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False
Telegram(get_patched_freqtradebot(mocker, conf))
message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \
"['balance'], ['start'], ['stop'], ['forcesell'], ['performance'], ['daily'], " \
"['count'], ['help'], ['version']]"
assert not log_has(message_str, caplog.record_tuples)
def test_cleanup(default_conf, mocker) -> None:
"""
Test cleanup() method
@ -103,44 +89,11 @@ def test_cleanup(default_conf, mocker) -> None:
updater_mock.stop = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock)
# not enabled
conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False
telegram = Telegram(get_patched_freqtradebot(mocker, conf))
telegram.cleanup()
assert telegram._updater is None
assert updater_mock.call_count == 0
assert not hasattr(telegram._updater, 'stop')
assert updater_mock.stop.call_count == 0
# enabled
conf['telegram']['enabled'] = True
telegram = Telegram(get_patched_freqtradebot(mocker, conf))
telegram = Telegram(get_patched_freqtradebot(mocker, default_conf))
telegram.cleanup()
assert telegram._updater.stop.call_count == 1
def test_is_enabled(default_conf, mocker) -> None:
"""
Test is_enabled() method
"""
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
telegram = Telegram(get_patched_freqtradebot(mocker, default_conf))
assert telegram.is_enabled()
def test_is_not_enabled(default_conf, mocker) -> None:
"""
Test is_enabled() method
"""
conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False
telegram = Telegram(get_patched_freqtradebot(mocker, conf))
assert not telegram.is_enabled()
def test_authorized_only(default_conf, mocker, caplog) -> None:
"""
Test authorized_only() method when we are authorized
@ -256,9 +209,9 @@ def test_status(default_conf, update, mocker, fee, ticker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
rpc_trade_status=MagicMock(return_value=(False, [1, 2, 3])),
_rpc_trade_status=MagicMock(return_value=[1, 2, 3]),
_status_table=status_table,
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -296,7 +249,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_status_table=status_table,
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -341,7 +294,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -397,7 +350,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -465,7 +418,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -506,7 +459,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -604,7 +557,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
freqtradebot = FreqtradeBot(default_conf)
@ -634,7 +587,7 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
freqtradebot = FreqtradeBot(default_conf)
@ -656,7 +609,7 @@ def test_start_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -667,7 +620,7 @@ def test_start_handle(default_conf, update, mocker) -> None:
assert freqtradebot.state == State.STOPPED
telegram._start(bot=MagicMock(), update=update)
assert freqtradebot.state == State.RUNNING
assert msg_mock.call_count == 0
assert msg_mock.call_count == 1
def test_start_handle_already_running(default_conf, update, mocker) -> None:
@ -680,7 +633,7 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -705,7 +658,7 @@ def test_stop_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -730,7 +683,7 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
@ -745,6 +698,29 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
assert 'already stopped' in msg_mock.call_args_list[0][0][0]
def test_reload_conf_handle(default_conf, update, mocker) -> None:
""" Test _reload_conf() method """
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot)
freqtradebot.state = State.RUNNING
assert freqtradebot.state == State.RUNNING
telegram._reload_conf(bot=MagicMock(), update=update)
assert freqtradebot.state == State.RELOAD_CONF
assert msg_mock.call_count == 1
assert 'Reloading config' in msg_mock.call_args_list[0][0][0]
def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, mocker) -> None:
"""
Test _forcesell() method
@ -875,7 +851,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
@ -917,7 +893,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
@ -958,7 +934,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
@ -981,7 +957,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
@ -1024,7 +1000,7 @@ def test_help_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot)
@ -1044,7 +1020,7 @@ def test_version_handle(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
send_msg=msg_mock
_send_msg=msg_mock
)
freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot)
@ -1066,13 +1042,8 @@ def test_send_msg(default_conf, mocker) -> None:
freqtradebot = FreqtradeBot(conf)
telegram = Telegram(freqtradebot)
telegram._config['telegram']['enabled'] = False
telegram.send_msg('test', bot)
assert not bot.method_calls
bot.reset_mock()
telegram._config['telegram']['enabled'] = True
telegram.send_msg('test', bot)
telegram._send_msg('test', bot)
assert len(bot.method_calls) == 1
@ -1090,7 +1061,7 @@ def test_send_msg_network_error(default_conf, mocker, caplog) -> None:
telegram = Telegram(freqtradebot)
telegram._config['telegram']['enabled'] = True
telegram.send_msg('test', bot)
telegram._send_msg('test', bot)
# Bot should've tried to send it twice
assert len(bot.method_calls) == 2

View File

@ -46,7 +46,7 @@ def test_analyze_object() -> None:
def test_dataframe_correct_length(result):
dataframe = Analyze.parse_ticker_dataframe(result)
assert len(result.index) == len(dataframe.index)
assert len(result.index) - 1 == len(dataframe.index) # last partial candle removed
def test_dataframe_correct_columns(result):
@ -188,4 +188,4 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': tick}
data = analyze.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 100
assert len(data['UNITTEST/BTC']) == 99 # partial candle was removed

View File

@ -174,3 +174,19 @@ def test_parse_args_hyperopt_custom() -> None:
assert call_args.subparser == 'hyperopt'
assert call_args.spaces == ['buy']
assert call_args.func is not None
def test_testdata_dl_options() -> None:
args = [
'--pairs-file', 'file_with_pairs',
'--export', 'export/folder',
'--days', '30',
'--exchange', 'binance'
]
arguments = Arguments(args, '')
arguments.testdata_dl_options()
args = arguments.parse_args()
assert args.pairs_file == 'file_with_pairs'
assert args.export == 'export/folder'
assert args.days == 30
assert args.exchange == 'binance'

View File

@ -85,7 +85,7 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
assert log_has('Validating configuration ...', caplog.record_tuples)
def test_load_config_file_exception(mocker, caplog) -> None:
def test_load_config_file_exception(mocker) -> None:
"""
Test Configuration._load_config_file() method
"""
@ -95,12 +95,8 @@ def test_load_config_file_exception(mocker, caplog) -> None:
)
configuration = Configuration(Namespace())
with pytest.raises(SystemExit):
with pytest.raises(OperationalException, match=r'.*Config file "somefile" not found!*'):
configuration._load_config_file('somefile')
assert log_has(
'Config file "somefile" not found. Please create your config file',
caplog.record_tuples
)
def test_load_config(default_conf, mocker) -> None:

View File

@ -9,7 +9,7 @@ import pytest
from requests.exceptions import RequestException
from freqtrade.fiat_convert import CryptoFiat, CryptoToFiatConverter
from freqtrade.tests.conftest import patch_coinmarketcap
from freqtrade.tests.conftest import log_has, patch_coinmarketcap
def test_pair_convertion_object():
@ -90,6 +90,13 @@ def test_fiat_convert_find_price(mocker):
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 13000.2
def test_fiat_convert_unsupported_crypto(mocker, caplog):
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
fiat_convert = CryptoToFiatConverter()
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples)
def test_fiat_convert_get_price(mocker):
api_mock = MagicMock(return_value={
'price_usd': 28000.0,
@ -161,7 +168,8 @@ def test_fiat_init_network_exception(mocker):
fiat_convert._cryptomap = {}
fiat_convert._load_cryptomap()
assert len(fiat_convert._cryptomap) == 0
length_cryptomap = len(fiat_convert._cryptomap)
assert length_cryptomap == 0
def test_fiat_convert_without_network():
@ -175,3 +183,22 @@ def test_fiat_convert_without_network():
assert fiat_convert._coinmarketcap is None
assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0
CryptoToFiatConverter._coinmarketcap = cmc_temp
def test_convert_amount(mocker):
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
fiat_convert = CryptoToFiatConverter()
result = fiat_convert.convert_amount(
crypto_amount=1.23,
crypto_symbol="BTC",
fiat_symbol="USD"
)
assert result == 15184.35
result = fiat_convert.convert_amount(
crypto_amount=1.23,
crypto_symbol="BTC",
fiat_symbol="BTC"
)
assert result == 1.23

View File

@ -57,7 +57,7 @@ def patch_RPCManager(mocker) -> MagicMock:
:param mocker: mocker to patch RPCManager class
:return: RPCManager.send_msg MagicMock to track if this method is called
"""
mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
rpc_mock = mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock())
return rpc_mock
@ -68,7 +68,7 @@ def test_freqtradebot_object() -> None:
Test the FreqtradeBot object has the mandatory public methods
"""
assert hasattr(FreqtradeBot, 'worker')
assert hasattr(FreqtradeBot, 'clean')
assert hasattr(FreqtradeBot, 'cleanup')
assert hasattr(FreqtradeBot, 'create_trade')
assert hasattr(FreqtradeBot, 'get_target_bid')
assert hasattr(FreqtradeBot, 'process_maybe_execute_buy')
@ -93,7 +93,7 @@ def test_freqtradebot(mocker, default_conf) -> None:
assert freqtrade.state is State.STOPPED
def test_clean(mocker, default_conf, caplog) -> None:
def test_cleanup(mocker, default_conf, caplog) -> None:
"""
Test clean() method
"""
@ -101,11 +101,8 @@ def test_clean(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.persistence.cleanup', mock_cleanup)
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.state == State.RUNNING
assert freqtrade.clean()
assert freqtrade.state == State.STOPPED
assert log_has('Stopping trader and cleaning up modules...', caplog.record_tuples)
freqtrade.cleanup()
assert log_has('Cleaning up modules ...', caplog.record_tuples)
assert mock_cleanup.call_count == 1
@ -569,8 +566,10 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo
trade.open_fee = 0.001
assert not freqtrade.process_maybe_execute_sell(trade)
# Test amount not modified by fee-logic
assert not log_has('Applying fee to amount for Trade {} from 90.99181073 to 90.81'.format(
trade), caplog.record_tuples)
assert not log_has(
'Applying fee to amount for Trade {} from 90.99181073 to 90.81'.format(trade),
caplog.record_tuples
)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
# test amount modified by fee-logic
@ -581,6 +580,38 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo
# Assert we call handle_trade() if trade is feasible for execution
assert freqtrade.process_maybe_execute_sell(trade)
regexp = re.compile('Found open order for.*')
assert filter(regexp.match, caplog.record_tuples)
def test_process_maybe_execute_sell_exception(mocker, default_conf,
limit_buy_order, caplog) -> None:
"""
Test the exceptions in process_maybe_execute_sell()
"""
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.freqtradebot.exchange.get_order', return_value=limit_buy_order)
trade = MagicMock()
trade.open_order_id = '123'
trade.open_fee = 0.001
# Test raise of OperationalException exception
mocker.patch(
'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
side_effect=OperationalException()
)
freqtrade.process_maybe_execute_sell(trade)
assert log_has('could not update trade amount: ', caplog.record_tuples)
# Test raise of DependencyException exception
mocker.patch(
'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
side_effect=DependencyException()
)
freqtrade.process_maybe_execute_sell(trade)
assert log_has('Unable to sell trade: ', caplog.record_tuples)
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mocker) -> None:
"""
@ -1355,7 +1386,7 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker):
caplog.record_tuples)
def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, caplog, mocker):
def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, mocker):
"""
Test get_real_amount - fees in Stake currency
"""
@ -1507,3 +1538,28 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee,
freqtrade = FreqtradeBot(default_conf)
# Amount does not change
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
def test_get_real_amount_open_trade(default_conf, mocker):
"""
Test get_real_amount condition trade.fee_open == 0 or order['status'] == 'open'
"""
patch_get_signal(mocker)
patch_RPCManager(mocker)
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
amount = 12345
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
open_order_id="123456"
)
order = {
'id': 'mocked_order',
'amount': amount,
'status': 'open',
}
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_real_amount(trade, order) == amount

View File

@ -3,11 +3,16 @@ Unit test file for main.py
"""
import logging
from copy import deepcopy
from unittest.mock import MagicMock
import pytest
from freqtrade.main import main, set_loggers
from freqtrade import OperationalException
from freqtrade.arguments import Arguments
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.main import main, set_loggers, reconfigure
from freqtrade.state import State
from freqtrade.tests.conftest import log_has
@ -60,7 +65,7 @@ def test_set_loggers() -> None:
assert value2 is logging.INFO
def test_main(mocker, caplog) -> None:
def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
"""
Test main() function
In this test we are skipping the while True loop by throwing an exception.
@ -68,26 +73,140 @@ def test_main(mocker, caplog) -> None:
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(),
worker=MagicMock(
side_effect=KeyboardInterrupt
),
clean=MagicMock(),
worker=MagicMock(side_effect=Exception),
cleanup=MagicMock(),
)
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
args = ['-c', 'config.json.example']
# Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit) as pytest_wrapped_e:
main(args)
log_has('Starting freqtrade', caplog.record_tuples)
log_has('Got SIGINT, aborting ...', caplog.record_tuples)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 42
# Test the BaseException case
mocker.patch(
'freqtrade.freqtradebot.FreqtradeBot.worker',
MagicMock(side_effect=BaseException)
)
with pytest.raises(SystemExit):
main(args)
log_has('Got fatal exception!', caplog.record_tuples)
assert log_has('Using config: config.json.example ...', caplog.record_tuples)
assert log_has('Fatal exception!', caplog.record_tuples)
def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
"""
Test main() function
In this test we are skipping the while True loop by throwing an exception.
"""
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(),
worker=MagicMock(side_effect=KeyboardInterrupt),
cleanup=MagicMock(),
)
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
args = ['-c', 'config.json.example']
# Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit):
main(args)
assert log_has('Using config: config.json.example ...', caplog.record_tuples)
assert log_has('SIGINT received, aborting ...', caplog.record_tuples)
def test_main_operational_exception(mocker, default_conf, caplog) -> None:
"""
Test main() function
In this test we are skipping the while True loop by throwing an exception.
"""
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(),
worker=MagicMock(side_effect=OperationalException('Oh snap!')),
cleanup=MagicMock(),
)
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
args = ['-c', 'config.json.example']
# Test Main + the KeyboardInterrupt exception
with pytest.raises(SystemExit):
main(args)
assert log_has('Using config: config.json.example ...', caplog.record_tuples)
assert log_has('Oh snap!', caplog.record_tuples)
def test_main_reload_conf(mocker, default_conf, caplog) -> None:
"""
Test main() function
In this test we are skipping the while True loop by throwing an exception.
"""
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(),
worker=MagicMock(return_value=State.RELOAD_CONF),
cleanup=MagicMock(),
)
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
# Raise exception as side effect to avoid endless loop
reconfigure_mock = mocker.patch(
'freqtrade.main.reconfigure', MagicMock(side_effect=Exception)
)
with pytest.raises(SystemExit):
main(['-c', 'config.json.example'])
assert reconfigure_mock.call_count == 1
assert log_has('Using config: config.json.example ...', caplog.record_tuples)
def test_reconfigure(mocker, default_conf) -> None:
""" Test recreate() function """
mocker.patch.multiple(
'freqtrade.freqtradebot.FreqtradeBot',
_init_modules=MagicMock(),
worker=MagicMock(side_effect=OperationalException('Oh snap!')),
cleanup=MagicMock(),
)
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
mocker.patch('freqtrade.freqtradebot.CryptoToFiatConverter', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtrade = FreqtradeBot(default_conf)
# Renew mock to return modified data
conf = deepcopy(default_conf)
conf['stake_amount'] += 1
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: conf
)
# reconfigure should return a new instance
freqtrade2 = reconfigure(
freqtrade,
Arguments(['-c', 'config.json.example'], '').get_parsed_arg()
)
# Verify we have a new instance with the new config
assert freqtrade is not freqtrade2
assert freqtrade.config['stake_amount'] + 1 == freqtrade2.config['stake_amount']

View File

@ -39,7 +39,7 @@ def test_datesarray_to_datetimearray(ticker_history):
assert dates[0].minute == 50
date_len = len(dates)
assert date_len == 3
assert date_len == 2
def test_common_datearray(default_conf) -> None:

View File

@ -110,10 +110,13 @@ def heikinashi(bars):
bars = bars.copy()
bars['ha_close'] = (bars['open'] + bars['high'] +
bars['low'] + bars['close']) / 4
bars['ha_open'] = (bars['open'].shift(1) + bars['close'].shift(1)) / 2
bars.loc[:1, 'ha_open'] = bars['open'].values[0]
bars.loc[1:, 'ha_open'] = (
(bars['ha_open'].shift(1) + bars['ha_close'].shift(1)) / 2)[1:]
for x in range(2):
bars.loc[1:, 'ha_open'] = (
(bars['ha_open'].shift(1) + bars['ha_close'].shift(1)) / 2)[1:]
bars['ha_high'] = bars.loc[:, ['high', 'ha_open', 'ha_close']].max(axis=1)
bars['ha_low'] = bars.loc[:, ['low', 'ha_open', 'ha_close']].min(axis=1)
@ -248,45 +251,36 @@ def crossed_below(series1, series2):
def rolling_std(series, window=200, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
if min_periods == window:
return numpy_rolling_std(series, window, True)
else:
try:
return series.rolling(window=window, min_periods=min_periods).std()
except BaseException:
return pd.Series(series).rolling(window=window, min_periods=min_periods).std()
except BaseException:
return pd.rolling_std(series, window=window, min_periods=min_periods)
if min_periods == window and len(series) > window:
return numpy_rolling_std(series, window, True)
else:
try:
return series.rolling(window=window, min_periods=min_periods).std()
except BaseException:
return pd.Series(series).rolling(window=window, min_periods=min_periods).std()
# ---------------------------------------------
def rolling_mean(series, window=200, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
if min_periods == window:
return numpy_rolling_mean(series, window, True)
else:
try:
return series.rolling(window=window, min_periods=min_periods).mean()
except BaseException:
return pd.Series(series).rolling(window=window, min_periods=min_periods).mean()
except BaseException:
return pd.rolling_mean(series, window=window, min_periods=min_periods)
if min_periods == window and len(series) > window:
return numpy_rolling_mean(series, window, True)
else:
try:
return series.rolling(window=window, min_periods=min_periods).mean()
except BaseException:
return pd.Series(series).rolling(window=window, min_periods=min_periods).mean()
# ---------------------------------------------
def rolling_min(series, window=14, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
try:
return series.rolling(window=window, min_periods=min_periods).min()
except BaseException:
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
return series.rolling(window=window, min_periods=min_periods).min()
except BaseException:
return pd.rolling_min(series, window=window, min_periods=min_periods)
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
# ---------------------------------------------
@ -294,12 +288,9 @@ def rolling_min(series, window=14, min_periods=None):
def rolling_max(series, window=14, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
try:
return series.rolling(window=window, min_periods=min_periods).min()
except BaseException:
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
return series.rolling(window=window, min_periods=min_periods).min()
except BaseException:
return pd.rolling_min(series, window=window, min_periods=min_periods)
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
# ---------------------------------------------
@ -566,9 +557,9 @@ def stoch(df, window=14, d=3, k=3, fast=False):
return pd.DataFrame(index=df.index, data=data)
# ---------------------------------------------
def zscore(bars, window=20, stds=1, col='close'):
""" get zscore of price """
std = numpy_rolling_std(bars[col], window)

View File

@ -1,5 +1,4 @@
if [ ! -f "ta-lib/CHANGELOG.TXT" ]; then
curl -O -L http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
tar zxvf ta-lib-0.4.0-src.tar.gz
cd ta-lib && ./configure && make && sudo make install && cd ..
else

View File

@ -1,16 +1,16 @@
ccxt==1.14.155
ccxt==1.14.196
SQLAlchemy==1.2.8
python-telegram-bot==10.1.0
arrow==0.12.1
cachetools==2.1.0
requests==2.18.4
requests==2.19.0
urllib3==1.22
wrapt==1.10.11
pandas==0.23.0
pandas==0.23.1
scikit-learn==0.19.1
scipy==1.1.0
jsonschema==2.6.0
numpy==1.14.4
numpy==1.14.5
TA-Lib==0.4.17
pytest==3.6.1
pytest-mock==1.10.0

View File

@ -30,6 +30,8 @@ if not os.path.isfile(pairs_file):
with open(pairs_file) as file:
PAIRS = list(set(json.load(file)))
PAIRS.sort()
since_time = None
if args.days:
since_time = arrow.utcnow().shift(days=-args.days).timestamp * 1000
@ -41,9 +43,15 @@ print(f'About to download pairs: {PAIRS} to {dl_path}')
exchange._API = exchange.init_ccxt({'key': '',
'secret': '',
'name': args.exchange})
pairs_not_available = []
# Make sure API markets is initialized
exchange._API.load_markets()
for pair in PAIRS:
if pair not in exchange._API.markets:
pairs_not_available.append(pair)
print(f"skipping pair {pair}")
continue
for tick_interval in timeframes:
print(f'downloading pair {pair}, interval {tick_interval}')
@ -60,3 +68,7 @@ for pair in PAIRS:
pair_print = pair.replace('/', '_')
filename = f'{pair_print}-{tick_interval}.json'
misc.file_dump_json(os.path.join(dl_path, filename), data)
if pairs_not_available:
print(f"Pairs [{','.join(pairs_not_available)}] not availble.")

View File

@ -91,7 +91,7 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
tickers[pair] = exchange.get_ticker_history(pair, tick_interval)
else:
tickers = optimize.load_data(
datadir=args.datadir,
datadir=_CONF.get("datadir"),
pairs=[pair],
ticker_interval=tick_interval,
refresh_pairs=_CONF.get('refresh_pairs', False),

View File

@ -93,7 +93,7 @@ def plot_profit(args: Namespace) -> None:
'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"',
config.get('strategy')
)
exit(0)
exit(1)
# Load the profits results
try:
@ -104,7 +104,7 @@ def plot_profit(args: Namespace) -> None:
logger.critical(
'File "backtest-result.json" not found. This script require backtesting '
'results to run.\nPlease run a backtesting with the parameter --export.')
exit(0)
exit(1)
# Take pairs from the cli otherwise switch to the pair in the config file
if args.pair:
@ -121,7 +121,7 @@ def plot_profit(args: Namespace) -> None:
logger.info('Filter, keep pairs %s' % pairs)
tickers = optimize.load_data(
datadir=args.datadir,
datadir=config.get('datadir'),
pairs=pairs,
ticker_interval=tick_interval,
refresh_pairs=False,

View File

@ -5,3 +5,6 @@ max-complexity = 12
[mypy]
ignore_missing_imports = True
[mypy-freqtrade.tests.*]
ignore_errors = True

BIN
ta-lib-0.4.0-src.tar.gz Normal file

Binary file not shown.