Merge with develop

This commit is contained in:
Anton 2018-06-08 00:29:44 +03:00
commit b4138f29c8
49 changed files with 788 additions and 516 deletions

View File

@ -1,6 +1,6 @@
## Step 1: Have you search for this issue before posting it? ## Step 1: Have you search for this issue before posting it?
If you have discovered a bug in the bot, please [search our issue tracker](https://github.com/gcarq/freqtrade/issues?q=is%3Aissue). If you have discovered a bug in the bot, please [search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue).
If it hasn't been reported, please create a new issue. If it hasn't been reported, please create a new issue.
## Step 2: Describe your environment ## Step 2: Describe your environment

View File

@ -1,5 +1,5 @@
Thank you for sending your pull request. But first, have you included Thank you for sending your pull request. But first, have you included
unit tests, and is your code PEP8 conformant? [More details](https://github.com/gcarq/freqtrade/blob/develop/CONTRIBUTING.md) unit tests, and is your code PEP8 conformant? [More details](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
## Summary ## Summary
Explain in one sentence the goal of this PR Explain in one sentence the goal of this PR

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ logfile.txt
hyperopt_trials.pickle hyperopt_trials.pickle
user_data/ user_data/
freqtrade-plot.html freqtrade-plot.html
freqtrade-profit-plot.html
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

4
.pyup.yml Normal file
View File

@ -0,0 +1,4 @@
# autogenerated pyup.io config file
# see https://pyup.io/docs/configuration/ for all available options
schedule: every day

View File

@ -21,10 +21,10 @@ jobs:
- script: pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ - script: pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/
- script: - script:
- cp config.json.example config.json - cp config.json.example config.json
- python freqtrade/main.py backtesting - python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting
- script: - script:
- cp config.json.example config.json - cp config.json.example config.json
- python freqtrade/main.py hyperopt -e 5 - python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5
- script: flake8 freqtrade - script: flake8 freqtrade
- script: mypy freqtrade - script: mypy freqtrade
after_success: after_success:

View File

@ -7,7 +7,7 @@ Feel like our bot is missing a feature? We welcome your pull requests! Few point
conformant (max-line-length = 100). conformant (max-line-length = 100).
If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE)
or in a [issue](https://github.com/gcarq/freqtrade/issues) before a PR. or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR.
**Before sending the PR:** **Before sending the PR:**

View File

@ -1,14 +1,14 @@
# freqtrade # freqtrade
[![Build Status](https://travis-ci.org/gcarq/freqtrade.svg?branch=develop)](https://travis-ci.org/gcarq/freqtrade) [![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
[![Coverage Status](https://coveralls.io/repos/github/gcarq/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/gcarq/freqtrade?branch=develop) [![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/gcarq/freqtrade/maintainability) [![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
Simple High frequency trading bot for crypto currencies designed to Simple High frequency trading bot for crypto currencies designed to
support multi exchanges and be controlled via Telegram. support multi exchanges and be controlled via Telegram.
![freqtrade](https://raw.githubusercontent.com/gcarq/freqtrade/develop/docs/assets/freqtrade-screenshot.png) ![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade-screenshot.png)
## Disclaimer ## Disclaimer
This software is for educational purposes only. Do not risk money which This software is for educational purposes only. Do not risk money which
@ -25,12 +25,12 @@ hesitate to read the source code and understand the mechanism of this bot.
## Table of Contents ## Table of Contents
- [Features](#features) - [Features](#features)
- [Quick start](#quick-start) - [Quick start](#quick-start)
- [Documentations](https://github.com/gcarq/freqtrade/blob/develop/docs/index.md) - [Documentations](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
- [Installation](https://github.com/gcarq/freqtrade/blob/develop/docs/installation.md) - [Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
- [Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md) - [Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
- [Strategy Optimization](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md) - [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
- [Backtesting](https://github.com/gcarq/freqtrade/blob/develop/docs/backtesting.md) - [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
- [Hyperopt](https://github.com/gcarq/freqtrade/blob/develop/docs/hyperopt.md) - [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
- [Support](#support) - [Support](#support)
- [Help](#help--slack) - [Help](#help--slack)
- [Bugs](#bugs--issues) - [Bugs](#bugs--issues)
@ -73,7 +73,7 @@ strategy parameters with real exchange data.
## Quick start ## Quick start
This quick start section is a very short explanation on how to test the This quick start section is a very short explanation on how to test the
bot in dry-run. We invite you to read the bot in dry-run. We invite you to read the
[bot documentation](https://github.com/gcarq/freqtrade/blob/develop/docs/index.md) [bot documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
to ensure you understand how the bot is working. to ensure you understand how the bot is working.
### Easy installation ### Easy installation
@ -87,7 +87,7 @@ The following steps are made for Linux/MacOS environment
**1. Clone the repo** **1. Clone the repo**
```bash ```bash
git clone git@github.com:gcarq/freqtrade.git git clone git@github.com:freqtrade/freqtrade.git
git checkout develop git checkout develop
cd freqtrade cd freqtrade
``` ```
@ -109,26 +109,26 @@ For any questions not covered by the documentation or for further
information about the bot, we encourage you to join our slack channel. information about the bot, we encourage you to join our slack channel.
- [Click here to join Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE). - [Click here to join Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE).
### [Bugs / Issues](https://github.com/gcarq/freqtrade/issues?q=is%3Aissue) ### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
If you discover a bug in the bot, please If you discover a bug in the bot, please
[search our issue tracker](https://github.com/gcarq/freqtrade/issues?q=is%3Aissue) [search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
first. If it hasn't been reported, please first. If it hasn't been reported, please
[create a new issue](https://github.com/gcarq/freqtrade/issues/new) and [create a new issue](https://github.com/freqtrade/freqtrade/issues/new) and
ensure you follow the template guide so that our team can assist you as ensure you follow the template guide so that our team can assist you as
quickly as possible. quickly as possible.
### [Feature Requests](https://github.com/gcarq/freqtrade/labels/enhancement) ### [Feature Requests](https://github.com/freqtrade/freqtrade/labels/enhancement)
Have you a great idea to improve the bot you want to share? Please, Have you a great idea to improve the bot you want to share? Please,
first search if this feature was not [already discussed](https://github.com/gcarq/freqtrade/labels/enhancement). first search if this feature was not [already discussed](https://github.com/freqtrade/freqtrade/labels/enhancement).
If it hasn't been requested, please If it hasn't been requested, please
[create a new request](https://github.com/gcarq/freqtrade/issues/new) [create a new request](https://github.com/freqtrade/freqtrade/issues/new)
and ensure you follow the template guide so that it does not get lost and ensure you follow the template guide so that it does not get lost
in the bug reports. in the bug reports.
### [Pull Requests](https://github.com/gcarq/freqtrade/pulls) ### [Pull Requests](https://github.com/freqtrade/freqtrade/pulls)
Feel like our bot is missing a feature? We welcome your pull requests! Feel like our bot is missing a feature? We welcome your pull requests!
Please read our Please read our
[Contributing document](https://github.com/gcarq/freqtrade/blob/develop/CONTRIBUTING.md) [Contributing document](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
to understand the requirements before sending your pull-requests. to understand the requirements before sending your pull-requests.
**Important:** Always create your PR against the `develop` branch, not **Important:** Always create your PR against the `develop` branch, not
@ -171,14 +171,14 @@ optional arguments:
only if dry_run is enabled. only if dry_run is enabled.
``` ```
More details on: More details on:
- [How to run the bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#bot-commands) - [How to run the bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
- [How to use Backtesting](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands) - [How to use Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
- [How to use Hyperopt](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands) - [How to use Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
### Telegram RPC commands ### Telegram RPC commands
Telegram is not mandatory. However, this is a great way to control your Telegram is not mandatory. However, this is a great way to control your
bot. More details on our bot. More details on our
[documentation](https://github.com/gcarq/freqtrade/blob/develop/docs/index.md) [documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
- `/start`: Starts the trader - `/start`: Starts the trader
- `/stop`: Stops the trader - `/stop`: Stops the trader

View File

@ -45,6 +45,7 @@
"token": "your_telegram_token", "token": "your_telegram_token",
"chat_id": "your_telegram_chat_id" "chat_id": "your_telegram_chat_id"
}, },
"db_url": "sqlite:///tradesv3.sqlite",
"initial_state": "running", "initial_state": "running",
"internals": { "internals": {
"process_throttle_secs": 5 "process_throttle_secs": 5

View File

@ -14,7 +14,7 @@ real data. This is what we call
Backtesting will use the crypto-currencies (pair) from your config file Backtesting will use the crypto-currencies (pair) from your config file
and load static tickers located in and load static tickers located in
[/freqtrade/tests/testdata](https://github.com/gcarq/freqtrade/tree/develop/freqtrade/tests/testdata). [/freqtrade/tests/testdata](https://github.com/freqtrade/freqtrade/tree/develop/freqtrade/tests/testdata).
If the 5 min and 1 min ticker for the crypto-currencies to test is not If the 5 min and 1 min ticker for the crypto-currencies to test is not
already in the `testdata` folder, backtesting will download them already in the `testdata` folder, backtesting will download them
automatically. Testdata files will not be updated until you specify it. automatically. Testdata files will not be updated until you specify it.
@ -62,6 +62,12 @@ Where `-s TestStrategy` refers to the class name within the strategy file `test_
python3 ./freqtrade/main.py backtesting --export trades python3 ./freqtrade/main.py backtesting --export trades
``` ```
**Exporting trades to file specifying a custom filename**
```bash
python3 ./freqtrade/main.py backtesting --export trades --export-filename=backtest_teststrategy.json
```
**Running backtest with smaller testset** **Running backtest with smaller testset**
Use the `--timerange` argument to change how much of the testset Use the `--timerange` argument to change how much of the testset
you want to use. The last N ticks/timeframes will be used. you want to use. The last N ticks/timeframes will be used.
@ -87,22 +93,31 @@ The full timerange specification:
`--timerange=1527595200-1527618600` `--timerange=1527595200-1527618600`
**Update testdata directory** **Downloading new set of ticker data**
To update your testdata directory, or download into another testdata directory: To download new set of backtesting ticker data, you can use a download script.
```bash
mkdir -p user_data/data/testdata-20180113
cp freqtrade/tests/testdata/pairs.json user_data/data-20180113
cd user_data/data-20180113
```
Possibly edit pairs.json file to include/exclude pairs If you are using Binance for example:
- create a folder `user_data/data/binance` and copy `pairs.json` in that folder.
- update the `pairs.json` to contain the currency pairs you are interested in.
```bash ```bash
python3 freqtrade/tests/testdata/download_backtest_data.py -p pairs.json mkdir -p user_data/data/binance
cp freqtrade/tests/testdata/pairs.json user_data/data/binance
``` ```
The script will read your pairs.json file, and download ticker data Then run:
into the current working directory.
```bash
python scripts/download_backtest_data --exchange binance
```
This will download ticker data for all the currency pairs you defined in `pairs.json`.
- To use a different folder than the exchange specific default, use `--export user_data/data/some_directory`.
- To change the exchange used to download the tickers, use `--exchange`. Default is `bittrex`.
- To use `pairs.json` from some other folder, use `--pairs-file some_other_dir/pairs.json`.
- To download ticker data for only 10 days, use `--days 10`.
- Use `--timeframes` to specify which tickers to download. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute tickers.
For help about backtesting usage, please refer to For help about backtesting usage, please refer to
@ -160,4 +175,4 @@ strategies, your configuration, and the crypto-currency you have set up.
## Next step ## Next step
Great, your strategy is profitable. What if the bot can give your the Great, your strategy is profitable. What if the bot can give your the
optimal parameters to use for your strategy? optimal parameters to use for your strategy?
Your next step is to learn [how to find optimal parameters with Hyperopt](https://github.com/gcarq/freqtrade/blob/develop/docs/hyperopt.md) Your next step is to learn [how to find optimal parameters with Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)

View File

@ -49,7 +49,7 @@ If you want to use a strategy from a different folder you can pass `--strategy-p
python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder
``` ```
**For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py) **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.** file as reference.**
### Buy strategy ### Buy strategy
@ -138,15 +138,15 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
``` ```
**Want more indicators example?** **Want more indicators example?**
Look into the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py). Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
Then uncomment indicators you need. Then uncomment indicators you need.
### Where is the default strategy? ### Where is the default strategy?
The default buy strategy is located in the file The default buy strategy is located in the file
[freqtrade/default_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/strategy/default_strategy.py). [freqtrade/default_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/strategy/default_strategy.py).
## Next step ## Next step
Now you have a perfect strategy you probably want to backtesting it. Now you have a perfect strategy you probably want to backtesting it.
Your next step is to learn [How to use the Backtesting](https://github.com/gcarq/freqtrade/blob/develop/docs/backtesting.md). Your next step is to learn [How to use the Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md).

View File

@ -9,10 +9,10 @@ it.
## Bot commands ## Bot commands
``` ```
usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME] usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--dynamic-whitelist [INT]] [--strategy-path PATH] [--dynamic-whitelist [INT]]
[--dry-run-db] [--db-url PATH]
{backtesting,hyperopt} ... {backtesting,hyperopt} ...
Simple High Frequency Trading Bot for crypto currencies Simple High Frequency Trading Bot for crypto currencies
@ -28,17 +28,16 @@ optional arguments:
-c PATH, --config PATH -c PATH, --config PATH
specify configuration file (default: config.json) specify configuration file (default: config.json)
-d PATH, --datadir PATH -d PATH, --datadir PATH
path to backtest data (default: path to backtest data
freqtrade/tests/testdata
-s NAME, --strategy NAME -s NAME, --strategy NAME
specify strategy class name (default: DefaultStrategy) specify strategy class name (default: DefaultStrategy)
--strategy-path PATH specify additional strategy lookup path --strategy-path PATH specify additional strategy lookup path
--dynamic-whitelist [INT] --dynamic-whitelist [INT]
dynamically generate and update whitelist based on 24h dynamically generate and update whitelist based on 24h
BaseVolume (Default 20 currencies) BaseVolume (default: 20)
--dry-run-db Force dry run to use a local DB --db-url PATH Override trades database URL, this is useful if
"tradesv3.dry_run.sqlite" instead of memory DB. Work dry_run is enabled or in custom deployments (default:
only if dry_run is enabled. sqlite:///tradesv3.sqlite)
``` ```
### How to use a different config file? ### How to use a different config file?
@ -68,7 +67,7 @@ python3 ./freqtrade/main.py --strategy AwesomeStrategy
If the bot does not find your strategy file, it will display in an error If the bot does not find your strategy file, it will display in an error
message the reason (File not found, or errors in your code). message the reason (File not found, or errors in your code).
Learn more about strategy file in [optimize your bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md). Learn more about strategy file in [optimize your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md).
### How to use --strategy-path? ### How to use --strategy-path?
This parameter allows you to add an additional strategy lookup path, which gets This parameter allows you to add an additional strategy lookup path, which gets
@ -102,14 +101,14 @@ python3 ./freqtrade/main.py --dynamic-whitelist 30
negative value (e.g -2), `--dynamic-whitelist` will use the default negative value (e.g -2), `--dynamic-whitelist` will use the default
value (20). value (20).
### How to use --dry-run-db? ### How to use --db-url?
When you run the bot in Dry-run mode, per default no transactions are When you run the bot in Dry-run mode, per default no transactions are
stored in a database. If you want to store your bot actions in a DB stored in a database. If you want to store your bot actions in a DB
using `--dry-run-db`. This command will use a separate database file using `--db-url`. This can also be used to specify a custom database
`tradesv3.dry_run.sqlite` in production mode. Example command:
```bash ```bash
python3 ./freqtrade/main.py -c config.json --dry-run-db python3 ./freqtrade/main.py -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
``` ```
@ -120,6 +119,8 @@ Backtesting also uses the config specified via `-c/--config`.
``` ```
usage: main.py backtesting [-h] [-i TICKER_INTERVAL] [--realistic-simulation] usage: main.py backtesting [-h] [-i TICKER_INTERVAL] [--realistic-simulation]
[--timerange TIMERANGE] [-l] [-r] [--export EXPORT] [--timerange TIMERANGE] [-l] [-r] [--export EXPORT]
[--export-filename EXPORTFILENAME]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@ -137,6 +138,11 @@ optional arguments:
run your backtesting with up-to-date data. run your backtesting with up-to-date data.
--export EXPORT export backtest results, argument are: trades Example --export EXPORT export backtest results, argument are: trades Example
--export=trades --export=trades
--export-filename EXPORTFILENAME
Save backtest results to this filename requires
--export to be set as well Example --export-
filename=backtest_today.json (default: backtest-
result.json
``` ```
### How to use --refresh-pairs-cached parameter? ### How to use --refresh-pairs-cached parameter?
@ -182,9 +188,9 @@ optional arguments:
## A parameter missing in the configuration? ## A parameter missing in the configuration?
All parameters for `main.py`, `backtesting`, `hyperopt` are referenced All parameters for `main.py`, `backtesting`, `hyperopt` are referenced
in [misc.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/misc.py#L84) in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.py#L84)
## Next step ## Next step
The optimal strategy of the bot will change with time depending of the The optimal strategy of the bot will change with time depending of the
market trends. The next step is to market trends. The next step is to
[optimize your bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md). [optimize your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md).

View File

@ -34,13 +34,14 @@ The table below will list all configuration parameters.
| `telegram.enabled` | true | Yes | Enable or not the usage of Telegram. | `telegram.enabled` | true | Yes | Enable or not the usage of Telegram.
| `telegram.token` | token | No | Your Telegram bot token. Only required if `telegram.enabled` is `true`. | `telegram.token` | token | No | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
| `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. | `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
| `db_url` | `sqlite:///tradesv3.sqlite` | No | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`.
| `initial_state` | running | No | Defines the initial application state. More information below. | `initial_state` | running | No | Defines the initial application state. More information below.
| `strategy` | DefaultStrategy | No | Defines Strategy class to use. | `strategy` | DefaultStrategy | No | Defines Strategy class to use.
| `strategy_path` | null | No | Adds an additional strategy lookup path (must be a folder). | `strategy_path` | null | No | Adds an additional strategy lookup path (must be a folder).
| `internals.process_throttle_secs` | 5 | Yes | Set the process throttle. Value in second. | `internals.process_throttle_secs` | 5 | Yes | Set the process throttle. Value in second.
The definition of each config parameters is in The definition of each config parameters is in
[misc.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/misc.py#L205). [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.py#L205).
### Understand stake_amount ### Understand stake_amount
`stake_amount` is an amount of crypto-currency your bot will use for each trade. `stake_amount` is an amount of crypto-currency your bot will use for each trade.
@ -80,6 +81,12 @@ value. This parameter is optional. If you use it, it will take over the
Possible values are `running` or `stopped`. (default=`running`) Possible values are `running` or `stopped`. (default=`running`)
If the value is `stopped` the bot has to be started with `/start` first. If the value is `stopped` the bot has to be started with `/start` first.
### Understand process_throttle_secs
`process_throttle_secs` is an optional field that defines in seconds how long the bot should wait
before asking the strategy if we should buy or a sell an asset. After each wait period, the strategy is asked again for
every opened trade wether or not we should sell, and for all the remaining pairs (either the dynamic list of pairs or
the static list of pairs) if we should buy.
### Understand ask_last_balance ### Understand ask_last_balance
`ask_last_balance` sets the bidding price. Value `0.0` will use `ask` price, `1.0` will `ask_last_balance` sets the bidding price. Value `0.0` will use `ask` price, `1.0` will
use the `last` price and values between those interpolate between ask and last use the `last` price and values between those interpolate between ask and last
@ -99,8 +106,10 @@ The bot was tested with the following exchanges:
Feel free to test other exchanges and submit your PR to improve the bot. Feel free to test other exchanges and submit your PR to improve the bot.
### What values for fiat_display_currency? ### What values for fiat_display_currency?
`fiat_display_currency` set the fiat to use for the conversion form coin to fiat in Telegram. `fiat_display_currency` set the base currency to use for the conversion from coin to fiat in Telegram.
The valid value are: "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD". The valid values are: "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD".
In addition to central bank currencies, a range of cryto currencies are supported.
The valid values are: "BTC", "ETH", "XRP", "LTC", "BCH", "USDT".
## Switch to dry-run mode ## Switch to dry-run mode
We recommend starting the bot in dry-run mode to see how your bot will We recommend starting the bot in dry-run mode to see how your bot will
@ -110,9 +119,10 @@ creating trades.
### To switch your bot in Dry-run mode: ### To switch your bot in Dry-run mode:
1. Edit your `config.json` file 1. Edit your `config.json` file
2. Switch dry-run to true 2. Switch dry-run to true and specify db_url for a persistent db
```json ```json
"dry_run": true, "dry_run": true,
"db_url": "sqlite///tradesv3.dryrun.sqlite",
``` ```
3. Remove your Exchange API key (change them by fake api credentials) 3. Remove your Exchange API key (change them by fake api credentials)
@ -136,7 +146,7 @@ you run it in production mode.
### To switch your bot in production mode: ### To switch your bot in production mode:
1. Edit your `config.json` file 1. Edit your `config.json` file
2. Switch dry-run to false 2. Switch dry-run to false and don't forget to adapt your database URL if set
```json ```json
"dry_run": false, "dry_run": false,
``` ```
@ -151,9 +161,8 @@ you run it in production mode.
} }
``` ```
If you have not your Bittrex API key yet, If you have not your Bittrex API key yet,
[see our tutorial](https://github.com/gcarq/freqtrade/blob/develop/docs/pre-requisite.md). [see our tutorial](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md).
## Next step ## Next step
Now you have configured your config.json, the next step is to Now you have configured your config.json, the next step is to
[start your bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md). [start your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md).

View File

@ -27,7 +27,7 @@ like pauses. You can stop your bot, adjust settings and start it again.
#### I want to improve the bot with a new strategy #### I want to improve the bot with a new strategy
That's great. We have a nice backtesting and hyperoptimizing setup. See That's great. We have a nice backtesting and hyperoptimizing setup. See
the tutorial [here|Testing-new-strategies-with-Hyperopt](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands). the tutorial [here|Testing-new-strategies-with-Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands).
#### Is there a setting to only SELL the coins being held and not #### Is there a setting to only SELL the coins being held and not
perform anymore BUYS? perform anymore BUYS?

View File

@ -14,13 +14,13 @@ parameters with Hyperopt.
## Prepare Hyperopt ## Prepare Hyperopt
Before we start digging in Hyperopt, we recommend you to take a look at Before we start digging in Hyperopt, we recommend you to take a look at
your strategy file located into [user_data/strategies/](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py) your strategy file located into [user_data/strategies/](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
### 1. Configure your Guards and Triggers ### 1. Configure your Guards and Triggers
There are two places you need to change in your strategy file to add a There are two places you need to change in your strategy file to add a
new buy strategy for testing: new buy strategy for testing:
- Inside [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L278-L294). - Inside [populate_buy_trend()](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L278-L294).
- Inside [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297) known as `SPACE`. - Inside [hyperopt_space()](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297) known as `SPACE`.
There you have two different type of indicators: 1. `guards` and 2. There you have two different type of indicators: 1. `guards` and 2.
`triggers`. `triggers`.
@ -110,13 +110,13 @@ cannot use your config file. It is also made on purpose to allow you
testing your strategy with different configurations. testing your strategy with different configurations.
The Hyperopt configuration is located in The Hyperopt configuration is located in
[user_data/hyperopt_conf.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/hyperopt_conf.py). [user_data/hyperopt_conf.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopt_conf.py).
## Advanced notions ## Advanced notions
### Understand the Guards and Triggers ### Understand the Guards and Triggers
When you need to add the new guards and triggers to be hyperopt When you need to add the new guards and triggers to be hyperopt
parameters, you do this by adding them into the [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297). parameters, you do this by adding them into the [hyperopt_space()](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297).
If it's a trigger, you add one line to the 'trigger' choice group and that's it. If it's a trigger, you add one line to the 'trigger' choice group and that's it.
@ -312,4 +312,4 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
## Next step ## Next step
Now you have a perfect bot and want to control it from Telegram. Your Now you have a perfect bot and want to control it from Telegram. Your
next step is to learn the [Telegram usage](https://github.com/gcarq/freqtrade/blob/develop/docs/telegram-usage.md). next step is to learn the [Telegram usage](https://github.com/freqtrade/freqtrade/blob/develop/docs/telegram-usage.md).

View File

@ -6,27 +6,27 @@ Pull-request. Do not hesitate to reach us on
if you do not find the answer to your questions. if you do not find the answer to your questions.
## Table of Contents ## Table of Contents
- [Pre-requisite](https://github.com/gcarq/freqtrade/blob/develop/docs/pre-requisite.md) - [Pre-requisite](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md)
- [Setup your Bittrex account](https://github.com/gcarq/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-bittrex-account) - [Setup your Bittrex account](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-bittrex-account)
- [Setup your Telegram bot](https://github.com/gcarq/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-telegram-bot) - [Setup your Telegram bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-telegram-bot)
- [Bot Installation](https://github.com/gcarq/freqtrade/blob/develop/docs/installation.md) - [Bot Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
- [Install with Docker (all platforms)](https://github.com/gcarq/freqtrade/blob/develop/docs/installation.md#docker) - [Install with Docker (all platforms)](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#docker)
- [Install on Linux Ubuntu](https://github.com/gcarq/freqtrade/blob/develop/docs/installation.md#21-linux---ubuntu-1604) - [Install on Linux Ubuntu](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#21-linux---ubuntu-1604)
- [Install on MacOS](https://github.com/gcarq/freqtrade/blob/develop/docs/installation.md#23-macos-installation) - [Install on MacOS](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#23-macos-installation)
- [Install on Windows](https://github.com/gcarq/freqtrade/blob/develop/docs/installation.md#windows) - [Install on Windows](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#windows)
- [Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md) - [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
- [Bot usage (Start your bot)](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md) - [Bot usage (Start your bot)](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md)
- [Bot commands](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#bot-commands) - [Bot commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
- [Backtesting commands](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands) - [Backtesting commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
- [Hyperopt commands](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands) - [Hyperopt commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
- [Bot Optimization](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md) - [Bot Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
- [Change your strategy](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md#change-your-strategy) - [Change your strategy](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#change-your-strategy)
- [Add more Indicator](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md#add-more-indicator) - [Add more Indicator](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#add-more-indicator)
- [Test your strategy with Backtesting](https://github.com/gcarq/freqtrade/blob/develop/docs/backtesting.md) - [Test your strategy with Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
- [Find optimal parameters with Hyperopt](https://github.com/gcarq/freqtrade/blob/develop/docs/hyperopt.md) - [Find optimal parameters with Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
- [Control the bot with telegram](https://github.com/gcarq/freqtrade/blob/develop/docs/telegram-usage.md) - [Control the bot with telegram](https://github.com/freqtrade/freqtrade/blob/develop/docs/telegram-usage.md)
- [Contribute to the project](https://github.com/gcarq/freqtrade/blob/develop/CONTRIBUTING.md) - [Contribute to the project](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [How to contribute](https://github.com/gcarq/freqtrade/blob/develop/CONTRIBUTING.md) - [How to contribute](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [Run tests & Check PEP8 compliance](https://github.com/gcarq/freqtrade/blob/develop/CONTRIBUTING.md) - [Run tests & Check PEP8 compliance](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [FAQ](https://github.com/gcarq/freqtrade/blob/develop/docs/faq.md) - [FAQ](https://github.com/freqtrade/freqtrade/blob/develop/docs/faq.md)
- [SQL cheatsheet](https://github.com/gcarq/freqtrade/blob/develop/docs/sql_cheatsheet.md) - [SQL cheatsheet](https://github.com/freqtrade/freqtrade/blob/develop/docs/sql_cheatsheet.md)

View File

@ -2,7 +2,7 @@
This page explains how to prepare your environment for running the bot. This page explains how to prepare your environment for running the bot.
To understand how to set up the bot please read the [Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md) page. To understand how to set up the bot please read the [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md) page.
## Table of Contents ## Table of Contents
@ -69,7 +69,7 @@ Once you have Docker installed, simply create the config file (e.g. `config.json
#### 1.1. Clone the git repository #### 1.1. Clone the git repository
```bash ```bash
git clone https://github.com/gcarq/freqtrade.git git clone https://github.com/freqtrade/freqtrade.git
``` ```
#### 1.2. (Optional) Checkout the develop branch #### 1.2. (Optional) Checkout the develop branch
@ -90,7 +90,7 @@ cd freqtrade
cp -n config.json.example config.json cp -n config.json.example config.json
``` ```
> To edit the config please refer to the [Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md) page. > To edit the config please refer to the [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md) page.
#### 1.5. Create your database file *(optional - the bot will create it if it is missing)* #### 1.5. Create your database file *(optional - the bot will create it if it is missing)*
@ -162,10 +162,10 @@ docker run -d \
-v /etc/localtime:/etc/localtime:ro \ -v /etc/localtime:/etc/localtime:ro \
-v ~/.freqtrade/config.json:/freqtrade/config.json \ -v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \ -v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
freqtrade 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.
If you are using `dry_run=True` it's not necessary to mount `tradesv3.sqlite`, but you can mount `tradesv3.dryrun.sqlite` if you plan to use the dry run mode with the param `--dry-run-db`. To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
### 6. Monitor your Docker instance ### 6. Monitor your Docker instance
@ -237,7 +237,7 @@ sudo apt-get install mongodb-org
Clone the git repository: Clone the git repository:
```bash ```bash
git clone https://github.com/gcarq/freqtrade.git git clone https://github.com/freqtrade/freqtrade.git
``` ```
Optionally checkout the develop branch: Optionally checkout the develop branch:
@ -287,7 +287,7 @@ export PATH=<path_freqtrade>/env/mongodb/bin:$PATH
Clone the git repository: Clone the git repository:
```bash ```bash
git clone https://github.com/gcarq/freqtrade.git git clone https://github.com/freqtrade/freqtrade.git
``` ```
Optionally checkout the develop branch: Optionally checkout the develop branch:
@ -306,7 +306,7 @@ cd freqtrade
cp config.json.example config.json cp config.json.example config.json
``` ```
> *To edit the config please refer to [Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md).* > *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) #### 2. Setup your Python virtual environment (virtualenv)
@ -350,8 +350,8 @@ copy paste `config.json` to ``\path\freqtrade-develop\freqtrade`
>python main.py >python main.py
``` ```
> Thanks [Owdr](https://github.com/Owdr) for the commands. Source: [Issue #222](https://github.com/gcarq/freqtrade/issues/222) > 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 Now you have an environment ready, the next step is
[Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md)... [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)...

View File

@ -48,6 +48,12 @@ To plot trades stored in a database use `--db-url` argument:
python scripts/plot_dataframe.py --db-url tradesv3.dry_run.sqlite -p BTC_ETH python scripts/plot_dataframe.py --db-url tradesv3.dry_run.sqlite -p BTC_ETH
``` ```
To plot a test strategy the strategy should have first be backtested.
The results may then be plotted with the -s argument:
```
python scripts/plot_dataframe.py -s Strategy_Name -p BTC/ETH --datadir user_data/data/<exchange_name>/
```
## Plot profit ## Plot profit
The profit plotter show a picture with three plots: The profit plotter show a picture with three plots:

View File

@ -85,7 +85,7 @@ INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, sta
## Fix wrong fees in the table ## Fix wrong fees in the table
If your DB was created before If your DB was created before
[PR#200](https://github.com/gcarq/freqtrade/pull/200) was merged [PR#200](https://github.com/freqtrade/freqtrade/pull/200) was merged
(before 12/23/17). (before 12/23/17).
```sql ```sql

View File

@ -4,7 +4,7 @@ This page explains how to command your bot with Telegram.
## Pre-requisite ## Pre-requisite
To control your bot with Telegram, you need first to To control your bot with Telegram, you need first to
[set up a Telegram bot](https://github.com/gcarq/freqtrade/blob/develop/docs/pre-requisite.md) [set up a Telegram bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md)
and add your Telegram API keys into your config file. and add your Telegram API keys into your config file.
## Telegram commands ## Telegram commands

View File

@ -12,7 +12,8 @@ class DependencyException(BaseException):
class OperationalException(BaseException): class OperationalException(BaseException):
""" """
Requires manual intervention. Requires manual intervention.
This happens when an exchange returns an unexpected error during runtime. This happens when an exchange returns an unexpected error during runtime
or given configuration is invalid.
""" """

View File

@ -2,16 +2,28 @@
This module contains the argument manager class This module contains the argument manager class
""" """
import os
import argparse import argparse
import logging import logging
import os
import re import re
import arrow import arrow
from typing import List, Tuple, Optional from typing import List, Optional, NamedTuple
from freqtrade import __version__, constants from freqtrade import __version__, constants
class TimeRange(NamedTuple):
"""
NamedTuple Defining timerange inputs.
[start/stop]type defines if [start/stop]ts shall be used.
if *type is none, don't use corresponding startvalue.
"""
starttype: Optional[str] = None
stoptype: Optional[str] = None
startts: int = 0
stopts: int = 0
class Arguments(object): class Arguments(object):
""" """
Arguments Class. Manage the arguments received by the cli Arguments Class. Manage the arguments received by the cli
@ -72,9 +84,9 @@ class Arguments(object):
) )
self.parser.add_argument( self.parser.add_argument(
'-d', '--datadir', '-d', '--datadir',
help='path to backtest data (default: %(default)s', help='path to backtest data',
dest='datadir', dest='datadir',
default=os.path.join('freqtrade', 'tests', 'testdata'), default=None,
type=str, type=str,
metavar='PATH', metavar='PATH',
) )
@ -95,8 +107,8 @@ class Arguments(object):
) )
self.parser.add_argument( self.parser.add_argument(
'--dynamic-whitelist', '--dynamic-whitelist',
help='dynamically generate and update whitelist \ help='dynamically generate and update whitelist'
based on 24h BaseVolume (Default 20 currencies)', # noqa ' based on 24h BaseVolume (default: %(const)s)',
dest='dynamic_whitelist', dest='dynamic_whitelist',
const=constants.DYNAMIC_WHITELIST, const=constants.DYNAMIC_WHITELIST,
type=int, type=int,
@ -104,11 +116,13 @@ class Arguments(object):
nargs='?', nargs='?',
) )
self.parser.add_argument( self.parser.add_argument(
'--dry-run-db', '--db-url',
help='Force dry run to use a local DB "tradesv3.dry_run.sqlite" \ help='Override trades database URL, this is useful if dry_run is enabled'
instead of memory DB. Work only if dry_run is enabled.', ' or in custom deployments (default: %(default)s)',
action='store_true', dest='db_url',
dest='dry_run_db', default=constants.DEFAULT_DB_PROD_URL,
type=str,
metavar='PATH',
) )
@staticmethod @staticmethod
@ -124,8 +138,8 @@ class Arguments(object):
) )
parser.add_argument( parser.add_argument(
'-r', '--refresh-pairs-cached', '-r', '--refresh-pairs-cached',
help='refresh the pairs files in tests/testdata with the latest data from the exchange. \ help='refresh the pairs files in tests/testdata with the latest data from the '
Use it if you want to run your backtesting with up-to-date data.', 'exchange. Use it if you want to run your backtesting with up-to-date data.',
action='store_true', action='store_true',
dest='refresh_pairs', dest='refresh_pairs',
) )
@ -137,6 +151,17 @@ class Arguments(object):
default=None, default=None,
dest='export', dest='export',
) )
parser.add_argument(
'--export-filename',
help='Save backtest results to this filename \
requires --export to be set as well\
Example --export-filename=user_data/backtest_data/backtest_today.json\
(default: %(default)s)',
type=str,
default=os.path.join('user_data', 'backtest_data', 'backtest-result.json'),
dest='exportfilename',
metavar='PATH',
)
@staticmethod @staticmethod
def optimizer_shared_options(parser: argparse.ArgumentParser) -> None: def optimizer_shared_options(parser: argparse.ArgumentParser) -> None:
@ -211,15 +236,14 @@ class Arguments(object):
self.hyperopt_options(hyperopt_cmd) self.hyperopt_options(hyperopt_cmd)
@staticmethod @staticmethod
def parse_timerange(text: Optional[str]) -> Optional[Tuple[Tuple, def parse_timerange(text: Optional[str]) -> TimeRange:
Optional[int], Optional[int]]]:
""" """
Parse the value of the argument --timerange to determine what is the range desired Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange :param text: value from --timerange
:return: Start and End range period :return: Start and End range period
""" """
if text is None: if text is None:
return None return TimeRange()
syntax = [(r'^-(\d{8})$', (None, 'date')), syntax = [(r'^-(\d{8})$', (None, 'date')),
(r'^(\d{8})-$', ('date', None)), (r'^(\d{8})-$', ('date', None)),
(r'^(\d{8})-(\d{8})$', ('date', 'date')), (r'^(\d{8})-(\d{8})$', ('date', 'date')),
@ -235,8 +259,8 @@ class Arguments(object):
if match: # Regex has matched if match: # Regex has matched
rvals = match.groups() rvals = match.groups()
index = 0 index = 0
start: Optional[int] = None start: int = 0
stop: Optional[int] = None stop: int = 0
if stype[0]: if stype[0]:
starts = rvals[index] starts = rvals[index]
if stype[0] == 'date' and len(starts) == 8: if stype[0] == 'date' and len(starts) == 8:
@ -250,7 +274,7 @@ class Arguments(object):
stop = arrow.get(stops, 'YYYYMMDD').timestamp stop = arrow.get(stops, 'YYYYMMDD').timestamp
else: else:
stop = int(stops) stop = int(stops)
return stype, start, stop return TimeRange(stype[0], stype[1], start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text) raise Exception('Incorrect syntax for timerange "%s"' % text)
def scripts_options(self) -> None: def scripts_options(self) -> None:
@ -264,13 +288,6 @@ class Arguments(object):
default=None default=None
) )
self.parser.add_argument(
'-db', '--db-url',
help='Show trades stored in database.',
dest='db_url',
default=None
)
def testdata_dl_options(self) -> None: def testdata_dl_options(self) -> None:
""" """
Parses given arguments for testdata download Parses given arguments for testdata download
@ -297,7 +314,18 @@ class Arguments(object):
self.parser.add_argument( self.parser.add_argument(
'--exchange', '--exchange',
help='Exchange name', help='Exchange name (default: %(default)s)',
dest='exchange', dest='exchange',
type=str, type=str,
default='bittrex') default='bittrex')
self.parser.add_argument(
'-t', '--timeframes',
help='Specify which tickers to download. Space separated list. \
Default: %(default)s',
choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h',
'6h', '8h', '12h', '1d', '3d', '1w'],
default=['1m', '5m'],
nargs='+',
dest='timeframes',
)

View File

@ -1,7 +1,7 @@
""" """
This module contains the configuration class This module contains the configuration class
""" """
import os
import json import json
import logging import logging
from argparse import Namespace from argparse import Namespace
@ -97,22 +97,35 @@ class Configuration(object):
'(not applicable with Backtesting and Hyperopt)' '(not applicable with Backtesting and Hyperopt)'
) )
# Add dry_run_db if found and the bot in dry run if self.args.db_url != constants.DEFAULT_DB_PROD_URL:
if self.args.dry_run_db and config.get('dry_run', False): config.update({'db_url': self.args.db_url})
config.update({'dry_run_db': True}) logger.info('Parameter --db-url detected ...')
logger.info('Parameter --dry-run-db detected ...')
if config.get('dry_run_db', False): if config.get('dry_run', False):
if config.get('dry_run', False): logger.info('Dry run is enabled')
logger.info('Dry_run will use the DB file: "tradesv3.dry_run.sqlite"') if config.get('db_url') in [None, constants.DEFAULT_DB_PROD_URL]:
else: # Default to in-memory db for dry_run if not specified
logger.info('Dry run is disabled. (--dry_run_db ignored)') config['db_url'] = constants.DEFAULT_DB_DRYRUN_URL
else:
if not config.get('db_url', None):
config['db_url'] = constants.DEFAULT_DB_PROD_URL
logger.info('Dry run is disabled')
logger.info('Using DB: "{}"'.format(config['db_url']))
# Check if the exchange set by the user is supported # Check if the exchange set by the user is supported
self.check_exchange(config) self.check_exchange(config)
return config return config
def _create_default_datadir(self, config: Dict[str, Any]) -> str:
exchange_name = config.get('exchange', {}).get('name').lower()
default_path = os.path.join('user_data', 'data', exchange_name)
if not os.path.isdir(default_path):
os.makedirs(default_path)
logger.info(f'Created data directory: {default_path}')
return default_path
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]: def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
""" """
Extract information for sys.argv and load Backtesting configuration Extract information for sys.argv and load Backtesting configuration
@ -145,7 +158,9 @@ class Configuration(object):
# If --datadir is used we add it to the configuration # If --datadir is used we add it to the configuration
if 'datadir' in self.args and self.args.datadir: if 'datadir' in self.args and self.args.datadir:
config.update({'datadir': self.args.datadir}) config.update({'datadir': self.args.datadir})
logger.info('Using data folder: %s ...', self.args.datadir) else:
config.update({'datadir': self._create_default_datadir(config)})
logger.info('Using data folder: %s ...', config.get('datadir'))
# If -r/--refresh-pairs-cached is used we add it to the configuration # If -r/--refresh-pairs-cached is used we add it to the configuration
if 'refresh_pairs' in self.args and self.args.refresh_pairs: if 'refresh_pairs' in self.args and self.args.refresh_pairs:
@ -157,6 +172,11 @@ class Configuration(object):
config.update({'export': self.args.export}) config.update({'export': self.args.export})
logger.info('Parameter --export detected: %s ...', self.args.export) logger.info('Parameter --export detected: %s ...', self.args.export)
# If --export-filename is used we add it to the configuration
if 'export' in config and 'exportfilename' in self.args and self.args.exportfilename:
config.update({'exportfilename': self.args.exportfilename})
logger.info('Storing backtest results to %s ...', self.args.exportfilename)
return config return config
def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]: def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]:

View File

@ -9,10 +9,14 @@ TICKER_INTERVAL = 5 # min
HYPEROPT_EPOCH = 100 # epochs HYPEROPT_EPOCH = 100 # epochs
RETRY_TIMEOUT = 30 # sec RETRY_TIMEOUT = 30 # sec
DEFAULT_STRATEGY = 'DefaultStrategy' DEFAULT_STRATEGY = 'DefaultStrategy'
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
UNLIMITED_STAKE_AMOUNT = 'unlimited' UNLIMITED_STAKE_AMOUNT = 'unlimited'
TICKER_INTERVAL_MINUTES = { TICKER_INTERVAL_MINUTES = {
'1m': 1, '1m': 1,
'3m': 3,
'5m': 5, '5m': 5,
'15m': 15, '15m': 15,
'30m': 30, '30m': 30,
@ -20,8 +24,10 @@ TICKER_INTERVAL_MINUTES = {
'2h': 120, '2h': 120,
'4h': 240, '4h': 240,
'6h': 360, '6h': 360,
'8h': 480,
'12h': 720, '12h': 720,
'1d': 1440, '1d': 1440,
'3d': 4320,
'1w': 10080, '1w': 10080,
} }
@ -29,7 +35,8 @@ SUPPORTED_FIAT = [
"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK",
"EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY",
"KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN",
"RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD" "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD",
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
] ]
# Required json-schema for user specified config # Required json-schema for user specified config
@ -84,6 +91,7 @@ CONF_SCHEMA = {
}, },
'required': ['enabled', 'token', 'chat_id'] 'required': ['enabled', 'token', 'chat_id']
}, },
'db_url': {'type': 'string'},
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']}, 'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
'internals': { 'internals': {
'type': 'object', 'type': 'object',

View File

@ -18,6 +18,8 @@ _API: ccxt.Exchange = None
_CONF: Dict = {} _CONF: Dict = {}
API_RETRY_COUNT = 4 API_RETRY_COUNT = 4
_CACHED_TICKER: Dict[str, Any] = {}
# Holds all open sell orders for dry_run # Holds all open sell orders for dry_run
_DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {} _DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {}
@ -264,17 +266,29 @@ def get_tickers() -> Dict:
raise OperationalException(e) raise OperationalException(e)
# TODO: remove refresh argument, keeping it to keep track of where it was intended to be used
@retrier @retrier
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict: def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
try: global _CACHED_TICKER
return _API.fetch_ticker(pair) if refresh or pair not in _CACHED_TICKER.keys():
except (ccxt.NetworkError, ccxt.ExchangeError) as e: try:
raise TemporaryError( data = _API.fetch_ticker(pair)
'Could not load ticker history due to {}. Message: {}'.format( try:
e.__class__.__name__, e)) _CACHED_TICKER[pair] = {
except ccxt.BaseError as e: 'bid': float(data['bid']),
raise OperationalException(e) 'ask': float(data['ask']),
}
except KeyError as e:
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))
except ccxt.BaseError as e:
raise OperationalException(e)
else:
logger.info("returning cached ticker-data for %s", pair)
return _CACHED_TICKER[pair]
@retrier @retrier

View File

@ -33,12 +33,11 @@ class FreqtradeBot(object):
This is from here the bot start its logic. This is from here the bot start its logic.
""" """
def __init__(self, config: Dict[str, Any], db_url: Optional[str] = None)-> None: def __init__(self, config: Dict[str, Any])-> None:
""" """
Init all variables and object the bot need to work Init all variables and object the bot need to work
:param config: configuration dict, you can use the Configuration.get_config() :param config: configuration dict, you can use the Configuration.get_config()
method to get the config dict. method to get the config dict.
:param db_url: database connector string for sqlalchemy (Optional)
""" """
logger.info( logger.info(
@ -57,17 +56,16 @@ class FreqtradeBot(object):
self.persistence = None self.persistence = None
self.exchange = None self.exchange = None
self._init_modules(db_url=db_url) self._init_modules()
def _init_modules(self, db_url: Optional[str] = None) -> None: def _init_modules(self) -> None:
""" """
Initializes all modules and updates the config Initializes all modules and updates the config
:param db_url: database connector string for sqlalchemy (Optional)
:return: None :return: None
""" """
# Initialize all modules # Initialize all modules
persistence.init(self.config, db_url) persistence.init(self.config)
exchange.init(self.config) exchange.init(self.config)
# Set initial application state # Set initial application state

View File

@ -7,6 +7,7 @@ import logging
import sys import sys
from typing import List from typing import List
from freqtrade import OperationalException
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration from freqtrade.configuration import Configuration
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
@ -47,6 +48,9 @@ def main(sysargv: List[str]) -> None:
except KeyboardInterrupt: except KeyboardInterrupt:
logger.info('SIGINT received, aborting ...') logger.info('SIGINT received, aborting ...')
return_code = 0 return_code = 0
except OperationalException as e:
logger.error(str(e))
return_code = 2
except BaseException: except BaseException:
logger.exception('Fatal exception!') logger.exception('Fatal exception!')
finally: finally:

View File

@ -9,39 +9,40 @@ import arrow
from freqtrade import misc, constants from freqtrade import misc, constants
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import get_ticker_history
from freqtrade.arguments import TimeRange
from user_data.hyperopt_conf import hyperopt_optimize_conf from user_data.hyperopt_conf import hyperopt_optimize_conf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) -> List[Dict]: def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
if not tickerlist: if not tickerlist:
return tickerlist return tickerlist
stype, start, stop = timerange
start_index = 0 start_index = 0
stop_index = len(tickerlist) stop_index = len(tickerlist)
if stype[0] == 'line': if timerange.starttype == 'line':
stop_index = start stop_index = timerange.startts
if stype[0] == 'index': if timerange.starttype == 'index':
start_index = start start_index = timerange.startts
elif stype[0] == 'date': elif timerange.starttype == 'date':
while start_index < len(tickerlist) and tickerlist[start_index][0] < start * 1000: while (start_index < len(tickerlist) and
tickerlist[start_index][0] < timerange.startts * 1000):
start_index += 1 start_index += 1
if stype[1] == 'line': if timerange.stoptype == 'line':
start_index = len(tickerlist) + stop start_index = len(tickerlist) + timerange.stopts
if stype[1] == 'index': if timerange.stoptype == 'index':
stop_index = stop stop_index = timerange.stopts
elif stype[1] == 'date': elif timerange.stoptype == 'date':
while stop_index > 0 and tickerlist[stop_index-1][0] > stop * 1000: while (stop_index > 0 and
tickerlist[stop_index-1][0] > timerange.stopts * 1000):
stop_index -= 1 stop_index -= 1
if start_index > stop_index: if start_index > stop_index:
raise ValueError(f'The timerange [{start},{stop}] is incorrect') raise ValueError(f'The timerange [{timerange.startts},{timerange.stopts}] is incorrect')
return tickerlist[start_index:stop_index] return tickerlist[start_index:stop_index]
@ -49,7 +50,7 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: Tuple[Tuple, int, int]) -
def load_tickerdata_file( def load_tickerdata_file(
datadir: str, pair: str, datadir: str, pair: str,
ticker_interval: str, ticker_interval: str,
timerange: Optional[Tuple[Tuple, int, int]] = None) -> Optional[List[Dict]]: timerange: Optional[TimeRange] = None) -> Optional[List[Dict]]:
""" """
Load a pair from file, Load a pair from file,
:return dict OR empty if unsuccesful :return dict OR empty if unsuccesful
@ -84,7 +85,7 @@ def load_data(datadir: str,
ticker_interval: str, ticker_interval: str,
pairs: Optional[List[str]] = None, pairs: Optional[List[str]] = None,
refresh_pairs: Optional[bool] = False, refresh_pairs: Optional[bool] = False,
timerange: Optional[Tuple[Tuple, int, int]] = None) -> Dict[str, List]: timerange: TimeRange = TimeRange()) -> Dict[str, List]:
""" """
Loads ticker history data for the given parameters Loads ticker history data for the given parameters
:return: dict :return: dict
@ -124,7 +125,7 @@ def make_testdata_path(datadir: str) -> str:
def download_pairs(datadir, pairs: List[str], def download_pairs(datadir, pairs: List[str],
ticker_interval: str, ticker_interval: str,
timerange: Optional[Tuple[Tuple, int, int]] = None) -> bool: timerange: TimeRange = TimeRange()) -> bool:
"""For each pairs passed in parameters, download the ticker intervals""" """For each pairs passed in parameters, download the ticker intervals"""
for pair in pairs: for pair in pairs:
try: try:
@ -144,7 +145,7 @@ def download_pairs(datadir, pairs: List[str],
def load_cached_data_for_updating(filename: str, def load_cached_data_for_updating(filename: str,
tick_interval: str, tick_interval: str,
timerange: Optional[Tuple[Tuple, int, int]]) -> Tuple[ timerange: Optional[TimeRange]) -> Tuple[
List[Any], List[Any],
Optional[int]]: Optional[int]]:
""" """
@ -155,10 +156,10 @@ def load_cached_data_for_updating(filename: str,
# user sets timerange, so find the start time # user sets timerange, so find the start time
if timerange: if timerange:
if timerange[0][0] == 'date': if timerange.starttype == 'date':
since_ms = timerange[1] * 1000 since_ms = timerange.startts * 1000
elif timerange[0][1] == 'line': elif timerange.stoptype == 'line':
num_minutes = timerange[2] * constants.TICKER_INTERVAL_MINUTES[tick_interval] num_minutes = timerange.stopts * constants.TICKER_INTERVAL_MINUTES[tick_interval]
since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000 since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000
# read the cached file # read the cached file
@ -188,7 +189,7 @@ def load_cached_data_for_updating(filename: str,
def download_backtesting_testdata(datadir: str, def download_backtesting_testdata(datadir: str,
pair: str, pair: str,
tick_interval: str = '5m', tick_interval: str = '5m',
timerange: Optional[Tuple[Tuple, int, int]] = None) -> None: timerange: Optional[TimeRange] = None) -> None:
""" """
Download the latest ticker intervals from the exchange for the pairs passed in parameters Download the latest ticker intervals from the exchange for the pairs passed in parameters

View File

@ -154,13 +154,22 @@ class Backtesting(object):
max_open_trades = args.get('max_open_trades', 0) max_open_trades = args.get('max_open_trades', 0)
realistic = args.get('realistic', False) realistic = args.get('realistic', False)
record = args.get('record', None) record = args.get('record', None)
recordfilename = args.get('recordfn', 'backtest-result.json')
records = [] records = []
trades = [] trades = []
trade_count_lock: Dict = {} trade_count_lock: Dict = {}
for pair, pair_data in processed.items(): for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
ticker_data = self.populate_sell_trend(self.populate_buy_trend(pair_data))[headers] ticker_data = self.populate_sell_trend(
self.populate_buy_trend(pair_data))[headers].copy()
# to avoid using data from future, we buy/sell with signal from previous candle
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)
ticker_data.loc[:, 'sell'] = ticker_data['sell'].shift(1)
ticker_data.drop(ticker_data.head(1).index, inplace=True)
ticker = [x for x in ticker_data.itertuples()] ticker = [x for x in ticker_data.itertuples()]
lock_pair_until = None lock_pair_until = None
@ -196,8 +205,8 @@ class Backtesting(object):
# For now export inside backtest(), maybe change so that backtest() # For now export inside backtest(), maybe change so that backtest()
# returns a tuple like: (dataframe, records, logs, etc) # returns a tuple like: (dataframe, records, logs, etc)
if record and record.find('trades') >= 0: if record and record.find('trades') >= 0:
logger.info('Dumping backtest results') logger.info('Dumping backtest results to %s', recordfilename)
file_dump_json('backtest-result.json', records) file_dump_json(recordfilename, records)
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration'] labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
return DataFrame.from_records(trades, columns=labels) return DataFrame.from_records(trades, columns=labels)
@ -220,7 +229,7 @@ class Backtesting(object):
timerange = Arguments.parse_timerange(None if self.config.get( timerange = Arguments.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange'))) 'timerange') is None else str(self.config.get('timerange')))
data = optimize.load_data( # type: ignore # timerange will be refactored data = optimize.load_data(
self.config['datadir'], self.config['datadir'],
pairs=pairs, pairs=pairs,
ticker_interval=self.ticker_interval, ticker_interval=self.ticker_interval,
@ -257,7 +266,8 @@ class Backtesting(object):
'realistic': self.config.get('realistic_simulation', False), 'realistic': self.config.get('realistic_simulation', False),
'sell_profit_only': sell_profit_only, 'sell_profit_only': sell_profit_only,
'use_sell_signal': use_sell_signal, 'use_sell_signal': use_sell_signal,
'record': self.config.get('export') 'record': self.config.get('export'),
'recordfn': self.config.get('exportfilename'),
} }
) )
logger.info( logger.info(

View File

@ -497,7 +497,7 @@ class Hyperopt(Backtesting):
def start(self) -> None: def start(self) -> None:
timerange = Arguments.parse_timerange(None if self.config.get( timerange = Arguments.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange'))) 'timerange') is None else str(self.config.get('timerange')))
data = load_data( # type: ignore # timerange will be refactored data = load_data(
datadir=str(self.config.get('datadir')), datadir=str(self.config.get('datadir')),
pairs=self.config['exchange']['pair_whitelist'], pairs=self.config['exchange']['pair_whitelist'],
ticker_interval=self.ticker_interval, ticker_interval=self.ticker_interval,

View File

@ -10,13 +10,14 @@ from typing import Dict, Optional, Any
import arrow import arrow
from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String, from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String,
create_engine) create_engine)
from sqlalchemy.engine import Engine from sqlalchemy import inspect
from sqlalchemy.exc import NoSuchModuleError
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.scoping import scoped_session from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.pool import StaticPool from sqlalchemy.pool import StaticPool
from sqlalchemy import inspect
from freqtrade import OperationalException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -24,29 +25,34 @@ _CONF = {}
_DECL_BASE: Any = declarative_base() _DECL_BASE: Any = declarative_base()
def init(config: dict, engine: Optional[Engine] = None) -> None: def init(config: Dict) -> None:
""" """
Initializes this module with the given config, Initializes this module with the given config,
registers all known command handlers registers all known command handlers
and starts polling for message updates and starts polling for message updates
:param config: config to use :param config: config to use
:param engine: database engine for sqlalchemy (Optional)
:return: None :return: None
""" """
_CONF.update(config) _CONF.update(config)
if not engine:
if _CONF.get('dry_run', False): db_url = _CONF.get('db_url', None)
# the user wants dry run to use a DB kwargs = {}
if _CONF.get('dry_run_db', False):
engine = create_engine('sqlite:///tradesv3.dry_run.sqlite') # Take care of thread ownership if in-memory db
# Otherwise dry run will store in memory if db_url == 'sqlite://':
else: kwargs.update({
engine = create_engine('sqlite://', 'connect_args': {'check_same_thread': False},
connect_args={'check_same_thread': False}, 'poolclass': StaticPool,
poolclass=StaticPool, 'echo': False,
echo=False) })
else:
engine = create_engine('sqlite:///tradesv3.sqlite') try:
engine = create_engine(db_url, **kwargs)
except NoSuchModuleError:
error = 'Given value for db_url: \'{}\' is no valid database URL! (See {}).'.format(
db_url, 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls'
)
raise OperationalException(error)
session = scoped_session(sessionmaker(bind=engine, autoflush=True, autocommit=True)) session = scoped_session(sessionmaker(bind=engine, autoflush=True, autocommit=True))
Trade.session = session() Trade.session = session()
@ -54,8 +60,8 @@ def init(config: dict, engine: Optional[Engine] = None) -> None:
_DECL_BASE.metadata.create_all(engine) _DECL_BASE.metadata.create_all(engine)
check_migrate(engine) check_migrate(engine)
# Clean dry_run DB # Clean dry_run DB if the db is not in-memory
if _CONF.get('dry_run', False) and _CONF.get('dry_run_db', False): if _CONF.get('dry_run', False) and db_url != 'sqlite://':
clean_dry_run_db() clean_dry_run_db()

View File

@ -9,6 +9,7 @@ from typing import Dict, Tuple, Any
import arrow import arrow
import sqlalchemy as sql import sqlalchemy as sql
from pandas import DataFrame from pandas import DataFrame
from numpy import mean, nan_to_num
from freqtrade import exchange from freqtrade import exchange
from freqtrade.misc import shorten_date from freqtrade.misc import shorten_date
@ -209,14 +210,14 @@ class RPC(object):
fiat = self.freqtrade.fiat_converter fiat = self.freqtrade.fiat_converter
# Prepare data to display # Prepare data to display
profit_closed_coin = round(sum(profit_closed_coin), 8) profit_closed_coin = round(sum(profit_closed_coin), 8)
profit_closed_percent = round(sum(profit_closed_percent) * 100, 2) profit_closed_percent = round(nan_to_num(mean(profit_closed_percent)) * 100, 2)
profit_closed_fiat = fiat.convert_amount( profit_closed_fiat = fiat.convert_amount(
profit_closed_coin, profit_closed_coin,
stake_currency, stake_currency,
fiat_display_currency fiat_display_currency
) )
profit_all_coin = round(sum(profit_all_coin), 8) profit_all_coin = round(sum(profit_all_coin), 8)
profit_all_percent = round(sum(profit_all_percent) * 100, 2) profit_all_percent = round(nan_to_num(mean(profit_all_percent)) * 100, 2)
profit_all_fiat = fiat.convert_amount( profit_all_fiat = fiat.convert_amount(
profit_all_coin, profit_all_coin,
stake_currency, stake_currency,

View File

@ -9,7 +9,6 @@ from unittest.mock import MagicMock
import arrow import arrow
import pytest import pytest
from jsonschema import validate from jsonschema import validate
from sqlalchemy import create_engine
from telegram import Chat, Message, Update from telegram import Chat, Message, Update
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
@ -45,7 +44,7 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock())
mocker.patch('freqtrade.freqtradebot.Analyze.get_signal', MagicMock()) mocker.patch('freqtrade.freqtradebot.Analyze.get_signal', MagicMock())
return FreqtradeBot(config, create_engine('sqlite://')) return FreqtradeBot(config)
def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None: def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None:
@ -108,7 +107,8 @@ def default_conf():
"chat_id": "0" "chat_id": "0"
}, },
"initial_state": "running", "initial_state": "running",
"loglevel": logging.DEBUG "db_url": "sqlite://",
"loglevel": logging.DEBUG,
} }
validate(configuration, constants.CONF_SCHEMA) validate(configuration, constants.CONF_SCHEMA)
return configuration return configuration

View File

@ -310,9 +310,19 @@ def test_get_ticker(default_conf, mocker):
# if not fetching a new result we should get the cached ticker # if not fetching a new result we should get the cached ticker
ticker = get_ticker(pair='ETH/BTC') ticker = get_ticker(pair='ETH/BTC')
assert api_mock.fetch_ticker.call_count == 1
assert ticker['bid'] == 0.5 assert ticker['bid'] == 0.5
assert ticker['ask'] == 1 assert ticker['ask'] == 1
assert 'ETH/BTC' in exchange._CACHED_TICKER
assert exchange._CACHED_TICKER['ETH/BTC']['bid'] == 0.5
assert exchange._CACHED_TICKER['ETH/BTC']['ask'] == 1
# Test caching
api_mock.fetch_ticker = MagicMock()
get_ticker(pair='ETH/BTC', refresh=False)
assert api_mock.fetch_ticker.call_count == 0
with pytest.raises(TemporaryError): # test retrier with pytest.raises(TemporaryError): # test retrier
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError) api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError)
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
@ -323,6 +333,10 @@ def test_get_ticker(default_conf, mocker):
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
get_ticker(pair='ETH/BTC', refresh=True) get_ticker(pair='ETH/BTC', refresh=True)
api_mock.fetch_ticker = MagicMock(return_value={})
mocker.patch('freqtrade.exchange._API', api_mock)
get_ticker(pair='ETH/BTC', refresh=True)
def make_fetch_ohlcv_mock(data): def make_fetch_ohlcv_mock(data):
def fetch_ohlcv_mock(pair, timeframe, since): def fetch_ohlcv_mock(pair, timeframe, since):

View File

@ -14,7 +14,7 @@ from arrow import Arrow
from freqtrade import optimize, constants, DependencyException from freqtrade import optimize, constants, DependencyException
from freqtrade.analyze import Analyze from freqtrade.analyze import Analyze
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments, TimeRange
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has
@ -31,7 +31,7 @@ def trim_dictlist(dict_list, num):
def load_data_test(what): def load_data_test(what):
timerange = ((None, 'line'), None, -100) timerange = TimeRange(None, 'line', 0, -101)
data = optimize.load_data(None, ticker_interval='1m', data = optimize.load_data(None, ticker_interval='1m',
pairs=['UNITTEST/BTC'], timerange=timerange) pairs=['UNITTEST/BTC'], timerange=timerange)
pair = data['UNITTEST/BTC'] pair = data['UNITTEST/BTC']
@ -111,14 +111,14 @@ def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=Fals
# use for mock freqtrade.exchange.get_ticker_history' # use for mock freqtrade.exchange.get_ticker_history'
def _load_pair_as_ticks(pair, tickfreq): def _load_pair_as_ticks(pair, tickfreq):
ticks = optimize.load_data(None, ticker_interval=tickfreq, pairs=[pair]) ticks = optimize.load_data(None, ticker_interval=tickfreq, pairs=[pair])
ticks = trim_dictlist(ticks, -200) ticks = trim_dictlist(ticks, -201)
return ticks[pair] return ticks[pair]
# FIX: fixturize this? # FIX: fixturize this?
def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None): def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None):
data = optimize.load_data(None, ticker_interval='8m', pairs=[pair]) data = optimize.load_data(None, ticker_interval='8m', pairs=[pair])
data = trim_dictlist(data, -200) data = trim_dictlist(data, -201)
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
backtesting = Backtesting(conf) backtesting = Backtesting(conf)
return { return {
@ -219,7 +219,8 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
'--realistic-simulation', '--realistic-simulation',
'--refresh-pairs-cached', '--refresh-pairs-cached',
'--timerange', ':100', '--timerange', ':100',
'--export', '/bar/foo' '--export', '/bar/foo',
'--export-filename', 'foo_bar.json'
] ]
config = setup_configuration(get_args(args)) config = setup_configuration(get_args(args))
@ -260,6 +261,11 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
'Parameter --export detected: {} ...'.format(config['export']), 'Parameter --export detected: {} ...'.format(config['export']),
caplog.record_tuples caplog.record_tuples
) )
assert 'exportfilename' in config
assert log_has(
'Storing backtest results to {} ...'.format(config['exportfilename']),
caplog.record_tuples
)
def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None: def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
@ -328,7 +334,7 @@ def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
Test Backtesting.tickerdata_to_dataframe() method Test Backtesting.tickerdata_to_dataframe() method
""" """
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
timerange = ((None, 'line'), None, -100) timerange = TimeRange(None, 'line', 0, -100)
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange) tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}

View File

@ -11,6 +11,7 @@ from freqtrade.misc import file_dump_json
from freqtrade.optimize.__init__ import make_testdata_path, download_pairs, \ from freqtrade.optimize.__init__ import make_testdata_path, download_pairs, \
download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, \ download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, \
load_cached_data_for_updating load_cached_data_for_updating
from freqtrade.arguments import TimeRange
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has
# Change this if modifying UNITTEST/BTC testdatafile # Change this if modifying UNITTEST/BTC testdatafile
@ -176,7 +177,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# timeframe starts earlier than the cached data # timeframe starts earlier than the cached data
# should fully update data # should fully update data
timerange = (('date', None), test_data[0][0] / 1000 - 1, None) timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -187,13 +188,13 @@ def test_load_cached_data_for_updating(mocker) -> None:
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120 num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
((None, 'line'), None, -num_lines)) TimeRange(None, 'line', 0, -num_lines))
assert data == [] assert data == []
assert start_ts < test_data[0][0] - 1 assert start_ts < test_data[0][0] - 1
# timeframe starts in the center of the cached data # timeframe starts in the center of the cached data
# should return the chached data w/o the last item # should return the chached data w/o the last item
timerange = (('date', None), test_data[0][0] / 1000 + 1, None) timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -202,7 +203,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# same with 'line' timeframe # same with 'line' timeframe
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30 num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30
timerange = ((None, 'line'), None, -num_lines) timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -211,7 +212,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# timeframe starts after the chached data # timeframe starts after the chached data
# should return the chached data w/o the last item # should return the chached data w/o the last item
timerange = (('date', None), test_data[-1][0] / 1000 + 1, None) timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -220,7 +221,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# same with 'line' timeframe # same with 'line' timeframe
num_lines = 30 num_lines = 30
timerange = ((None, 'line'), None, -num_lines) timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -230,7 +231,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# no timeframe is set # no timeframe is set
# should return the chached data w/o the last item # should return the chached data w/o the last item
num_lines = 30 num_lines = 30
timerange = ((None, 'line'), None, -num_lines) timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename, data, start_ts = load_cached_data_for_updating(test_filename,
'1m', '1m',
timerange) timerange)
@ -239,7 +240,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# no datafile exist # no datafile exist
# should return timestamp start time # should return timestamp start time
timerange = (('date', None), now_ts - 10000, None) timerange = TimeRange('date', None, now_ts - 10000, 0)
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist', data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
'1m', '1m',
timerange) timerange)
@ -248,7 +249,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
# same with 'line' timeframe # same with 'line' timeframe
num_lines = 30 num_lines = 30
timerange = ((None, 'line'), None, -num_lines) timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist', data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
'1m', '1m',
timerange) timerange)
@ -343,7 +344,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(-\d+)$ # Test the pattern ^(-\d+)$
# This pattern uses the latest N elements # This pattern uses the latest N elements
timerange = ((None, 'line'), None, -5) timerange = TimeRange(None, 'line', 0, -5)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -353,7 +354,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(\d+)-$ # Test the pattern ^(\d+)-$
# This pattern keep X element from the end # This pattern keep X element from the end
timerange = (('line', None), 5, None) timerange = TimeRange('line', None, 5, 0)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -363,7 +364,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(\d+)-(\d+)$ # Test the pattern ^(\d+)-(\d+)$
# This pattern extract a window # This pattern extract a window
timerange = (('index', 'index'), 5, 10) timerange = TimeRange('index', 'index', 5, 10)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -374,7 +375,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(\d{8})-(\d{8})$ # Test the pattern ^(\d{8})-(\d{8})$
# This pattern extract a window between the dates # This pattern extract a window between the dates
timerange = (('date', 'date'), ticker_list[5][0] / 1000, ticker_list[10][0] / 1000 - 1) timerange = TimeRange('date', 'date', ticker_list[5][0] / 1000, ticker_list[10][0] / 1000 - 1)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -385,7 +386,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^-(\d{8})$ # Test the pattern ^-(\d{8})$
# This pattern extracts elements from the start to the date # This pattern extracts elements from the start to the date
timerange = ((None, 'date'), None, ticker_list[10][0] / 1000 - 1) timerange = TimeRange(None, 'date', 0, ticker_list[10][0] / 1000 - 1)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -395,7 +396,7 @@ def test_trim_tickerlist() -> None:
# Test the pattern ^(\d{8})-$ # Test the pattern ^(\d{8})-$
# This pattern extracts elements from the date to now # This pattern extracts elements from the date to now
timerange = (('date', None), ticker_list[10][0] / 1000 - 1, None) timerange = TimeRange('date', None, ticker_list[10][0] / 1000 - 1, None)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)
@ -405,7 +406,7 @@ def test_trim_tickerlist() -> None:
# Test a wrong pattern # Test a wrong pattern
# This pattern must return the list unchanged # This pattern must return the list unchanged
timerange = ((None, None), None, 5) timerange = TimeRange(None, None, None, 5)
ticker = trim_tickerlist(ticker_list, timerange) ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker) ticker_len = len(ticker)

View File

@ -7,8 +7,6 @@ Unit test file for rpc/rpc.py
from datetime import datetime from datetime import datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
from sqlalchemy import create_engine
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc.rpc import RPC from freqtrade.rpc.rpc import RPC
@ -39,7 +37,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
get_fee=fee get_fee=fee
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -88,7 +86,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
get_fee=fee get_fee=fee
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -123,7 +121,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
get_fee=fee get_fee=fee
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
stake_currency = default_conf['stake_currency'] stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency'] fiat_display_currency = default_conf['fiat_display_currency']
@ -180,7 +178,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
get_fee=fee get_fee=fee
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
stake_currency = default_conf['stake_currency'] stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency'] fiat_display_currency = default_conf['fiat_display_currency']
@ -206,15 +204,30 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
trade.close_date = datetime.utcnow() trade.close_date = datetime.utcnow()
trade.is_open = False trade.is_open = False
freqtradebot.create_trade()
trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_up
)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
(error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency) (error, stats) = rpc.rpc_trade_statistics(stake_currency, fiat_display_currency)
assert not error assert not error
assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05) 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_percent'], 6.2)
assert prec_satoshi(stats['profit_closed_fiat'], 0.93255) assert prec_satoshi(stats['profit_closed_fiat'], 0.93255)
assert prec_satoshi(stats['profit_all_coin'], 6.217e-05) assert prec_satoshi(stats['profit_all_coin'], 5.632e-05)
assert prec_satoshi(stats['profit_all_percent'], 6.2) assert prec_satoshi(stats['profit_all_percent'], 2.81)
assert prec_satoshi(stats['profit_all_fiat'], 0.93255) assert prec_satoshi(stats['profit_all_fiat'], 0.8448)
assert stats['trade_count'] == 1 assert stats['trade_count'] == 2
assert stats['first_trade_date'] == 'just now' assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now'
assert stats['avg_duration'] == '0:00:00' assert stats['avg_duration'] == '0:00:00'
@ -243,7 +256,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
get_fee=fee get_fee=fee
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
stake_currency = default_conf['stake_currency'] stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency'] fiat_display_currency = default_conf['fiat_display_currency']
@ -314,7 +327,7 @@ def test_rpc_balance_handle(default_conf, mocker):
get_balances=MagicMock(return_value=mock_balance) get_balances=MagicMock(return_value=mock_balance)
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
(error, res) = rpc.rpc_balance(default_conf['fiat_display_currency']) (error, res) = rpc.rpc_balance(default_conf['fiat_display_currency'])
@ -344,7 +357,7 @@ def test_rpc_start(mocker, default_conf) -> None:
get_ticker=MagicMock() get_ticker=MagicMock()
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -372,7 +385,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
get_ticker=MagicMock() get_ticker=MagicMock()
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -411,7 +424,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
get_fee=fee, get_fee=fee,
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -521,7 +534,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
get_fee=fee get_fee=fee
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
# Create some test data # Create some test data
@ -560,7 +573,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
get_fee=fee, get_fee=fee,
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
(error, trades) = rpc.rpc_count() (error, trades) = rpc.rpc_count()

View File

@ -11,7 +11,6 @@ from datetime import datetime
from random import randint from random import randint
from unittest.mock import MagicMock from unittest.mock import MagicMock
from sqlalchemy import create_engine
from telegram import Update, Message, Chat from telegram import Update, Message, Chat
from telegram.error import NetworkError from telegram.error import NetworkError
@ -156,7 +155,7 @@ def test_authorized_only(default_conf, mocker, caplog) -> None:
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False conf['telegram']['enabled'] = False
dummy = DummyCls(FreqtradeBot(conf, create_engine('sqlite://'))) dummy = DummyCls(FreqtradeBot(conf))
dummy.dummy_handler(bot=MagicMock(), update=update) dummy.dummy_handler(bot=MagicMock(), update=update)
assert dummy.state['called'] is True assert dummy.state['called'] is True
assert log_has( assert log_has(
@ -187,7 +186,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False conf['telegram']['enabled'] = False
dummy = DummyCls(FreqtradeBot(conf, create_engine('sqlite://'))) dummy = DummyCls(FreqtradeBot(conf))
dummy.dummy_handler(bot=MagicMock(), update=update) dummy.dummy_handler(bot=MagicMock(), update=update)
assert dummy.state['called'] is False assert dummy.state['called'] is False
assert not log_has( assert not log_has(
@ -217,7 +216,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['telegram']['enabled'] = False conf['telegram']['enabled'] = False
dummy = DummyCls(FreqtradeBot(conf, create_engine('sqlite://'))) dummy = DummyCls(FreqtradeBot(conf))
dummy.dummy_exception(bot=MagicMock(), update=update) dummy.dummy_exception(bot=MagicMock(), update=update)
assert dummy.state['called'] is False assert dummy.state['called'] is False
assert not log_has( assert not log_has(
@ -263,7 +262,7 @@ def test_status(default_conf, update, mocker, fee, ticker) -> None:
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
@ -301,7 +300,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -348,7 +347,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['stake_amount'] = 15.0 conf['stake_amount'] = 15.0
freqtradebot = FreqtradeBot(conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -402,7 +401,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
@ -470,7 +469,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Try invalid data # Try invalid data
@ -511,7 +510,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._profit(bot=MagicMock(), update=update) telegram._profit(bot=MagicMock(), update=update)
@ -608,7 +607,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
send_msg=msg_mock send_msg=msg_mock
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._balance(bot=MagicMock(), update=update) telegram._balance(bot=MagicMock(), update=update)
@ -638,7 +637,7 @@ def test_zero_balance_handle(default_conf, update, mocker) -> None:
send_msg=msg_mock send_msg=msg_mock
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._balance(bot=MagicMock(), update=update) telegram._balance(bot=MagicMock(), update=update)
@ -661,7 +660,7 @@ def test_start_handle(default_conf, update, mocker) -> None:
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -685,7 +684,7 @@ def test_start_handle_already_running(default_conf, update, mocker) -> None:
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -710,7 +709,7 @@ def test_stop_handle(default_conf, update, mocker) -> None:
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
@ -735,7 +734,7 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -762,7 +761,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, moc
get_fee=fee get_fee=fee
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
@ -802,7 +801,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_do
get_fee=fee get_fee=fee
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
@ -847,7 +846,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
get_fee=fee get_fee=fee
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
@ -880,7 +879,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
) )
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Trader is not running # Trader is not running
@ -927,7 +926,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
get_fee=fee get_fee=fee
) )
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock()) mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Create some test data # Create some test data
@ -962,7 +961,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
send_msg=msg_mock send_msg=msg_mock
) )
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
# Trader is not running # Trader is not running
@ -991,7 +990,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
buy=MagicMock(return_value={'id': 'mocked_order_id'}) buy=MagicMock(return_value={'id': 'mocked_order_id'})
) )
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee) mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
@ -1027,7 +1026,7 @@ def test_help_handle(default_conf, update, mocker) -> None:
_init=MagicMock(), _init=MagicMock(),
send_msg=msg_mock send_msg=msg_mock
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._help(bot=MagicMock(), update=update) telegram._help(bot=MagicMock(), update=update)
@ -1047,7 +1046,7 @@ def test_version_handle(default_conf, update, mocker) -> None:
_init=MagicMock(), _init=MagicMock(),
send_msg=msg_mock send_msg=msg_mock
) )
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(default_conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._version(bot=MagicMock(), update=update) telegram._version(bot=MagicMock(), update=update)
@ -1064,7 +1063,7 @@ def test_send_msg(default_conf, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
bot = MagicMock() bot = MagicMock()
freqtradebot = FreqtradeBot(conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._config['telegram']['enabled'] = False telegram._config['telegram']['enabled'] = False
@ -1087,7 +1086,7 @@ def test_send_msg_network_error(default_conf, mocker, caplog) -> None:
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
bot = MagicMock() bot = MagicMock()
bot.send_message = MagicMock(side_effect=NetworkError('Oh snap')) bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
freqtradebot = FreqtradeBot(conf, create_engine('sqlite://')) freqtradebot = FreqtradeBot(conf)
telegram = Telegram(freqtradebot) telegram = Telegram(freqtradebot)
telegram._config['telegram']['enabled'] = True telegram._config['telegram']['enabled'] = True

View File

@ -13,6 +13,7 @@ from pandas import DataFrame
from freqtrade.analyze import Analyze, SignalType from freqtrade.analyze import Analyze, SignalType
from freqtrade.optimize.__init__ import load_tickerdata_file from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.arguments import TimeRange
from freqtrade.tests.conftest import log_has from freqtrade.tests.conftest import log_has
# Avoid to reinit the same object again and again # Avoid to reinit the same object again and again
@ -183,7 +184,7 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
""" """
analyze = Analyze(default_conf) analyze = Analyze(default_conf)
timerange = ((None, 'line'), None, -100) timerange = TimeRange(None, 'line', 0, -100)
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange) tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': tick} tickerlist = {'UNITTEST/BTC': tick}
data = analyze.tickerdata_to_dataframe(tickerlist) data = analyze.tickerdata_to_dataframe(tickerlist)

View File

@ -9,7 +9,7 @@ import logging
import pytest import pytest
from freqtrade.arguments import Arguments from freqtrade.arguments import Arguments, TimeRange
def test_arguments_object() -> None: def test_arguments_object() -> None:
@ -46,6 +46,11 @@ def test_parse_args_config() -> None:
assert args.config == '/dev/null' assert args.config == '/dev/null'
def test_parse_args_db_url() -> None:
args = Arguments(['--db-url', 'sqlite:///test.sqlite'], '').get_parsed_arg()
assert args.db_url == 'sqlite:///test.sqlite'
def test_parse_args_verbose() -> None: def test_parse_args_verbose() -> None:
args = Arguments(['-v'], '').get_parsed_arg() args = Arguments(['-v'], '').get_parsed_arg()
assert args.loglevel == logging.DEBUG assert args.loglevel == logging.DEBUG
@ -107,20 +112,24 @@ def test_parse_args_dynamic_whitelist_invalid_values() -> None:
def test_parse_timerange_incorrect() -> None: def test_parse_timerange_incorrect() -> None:
assert ((None, 'line'), None, -200) == Arguments.parse_timerange('-200') assert TimeRange(None, 'line', 0, -200) == Arguments.parse_timerange('-200')
assert (('line', None), 200, None) == Arguments.parse_timerange('200-') assert TimeRange('line', None, 200, 0) == Arguments.parse_timerange('200-')
assert (('index', 'index'), 200, 500) == Arguments.parse_timerange('200-500') assert TimeRange('index', 'index', 200, 500) == Arguments.parse_timerange('200-500')
assert (('date', None), 1274486400, None) == Arguments.parse_timerange('20100522-') assert TimeRange('date', None, 1274486400, 0) == Arguments.parse_timerange('20100522-')
assert ((None, 'date'), None, 1274486400) == Arguments.parse_timerange('-20100522') assert TimeRange(None, 'date', 0, 1274486400) == Arguments.parse_timerange('-20100522')
timerange = Arguments.parse_timerange('20100522-20150730') timerange = Arguments.parse_timerange('20100522-20150730')
assert timerange == (('date', 'date'), 1274486400, 1438214400) assert timerange == TimeRange('date', 'date', 1274486400, 1438214400)
# Added test for unix timestamp - BTC genesis date # Added test for unix timestamp - BTC genesis date
assert (('date', None), 1231006505, None) == Arguments.parse_timerange('1231006505-') assert TimeRange('date', None, 1231006505, 0) == Arguments.parse_timerange('1231006505-')
assert ((None, 'date'), None, 1233360000) == Arguments.parse_timerange('-1233360000') assert TimeRange(None, 'date', 0, 1233360000) == Arguments.parse_timerange('-1233360000')
timerange = Arguments.parse_timerange('1231006505-1233360000') timerange = Arguments.parse_timerange('1231006505-1233360000')
assert timerange == (('date', 'date'), 1231006505, 1233360000) assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
# TODO: Find solution for the following case (passing timestamp in ms)
timerange = Arguments.parse_timerange('1231006505000-1233360000000')
assert TimeRange('date', 'date', 1231006505, 1233360000) != timerange
with pytest.raises(Exception, match=r'Incorrect syntax.*'): with pytest.raises(Exception, match=r'Incorrect syntax.*'):
Arguments.parse_timerange('-') Arguments.parse_timerange('-')

View File

@ -130,7 +130,6 @@ def test_load_config(default_conf, mocker) -> None:
assert validated_conf.get('strategy') == 'DefaultStrategy' assert validated_conf.get('strategy') == 'DefaultStrategy'
assert validated_conf.get('strategy_path') is None assert validated_conf.get('strategy_path') is None
assert 'dynamic_whitelist' not in validated_conf assert 'dynamic_whitelist' not in validated_conf
assert 'dry_run_db' not in validated_conf
def test_load_config_with_params(default_conf, mocker) -> None: def test_load_config_with_params(default_conf, mocker) -> None:
@ -145,7 +144,7 @@ def test_load_config_with_params(default_conf, mocker) -> None:
'--dynamic-whitelist', '10', '--dynamic-whitelist', '10',
'--strategy', 'TestStrategy', '--strategy', 'TestStrategy',
'--strategy-path', '/some/path', '--strategy-path', '/some/path',
'--dry-run-db', '--db-url', 'sqlite:///someurl',
] ]
args = Arguments(arglist, '').get_parsed_arg() args = Arguments(arglist, '').get_parsed_arg()
@ -155,7 +154,7 @@ def test_load_config_with_params(default_conf, mocker) -> None:
assert validated_conf.get('dynamic_whitelist') == 10 assert validated_conf.get('dynamic_whitelist') == 10
assert validated_conf.get('strategy') == 'TestStrategy' assert validated_conf.get('strategy') == 'TestStrategy'
assert validated_conf.get('strategy_path') == '/some/path' assert validated_conf.get('strategy_path') == '/some/path'
assert validated_conf.get('dry_run_db') is True assert validated_conf.get('db_url') == 'sqlite:///someurl'
def test_load_custom_strategy(default_conf, mocker) -> None: def test_load_custom_strategy(default_conf, mocker) -> None:
@ -190,7 +189,7 @@ def test_show_info(default_conf, mocker, caplog) -> None:
arglist = [ arglist = [
'--dynamic-whitelist', '10', '--dynamic-whitelist', '10',
'--strategy', 'TestStrategy', '--strategy', 'TestStrategy',
'--dry-run-db' '--db-url', 'sqlite:///tmp/testdb',
] ]
args = Arguments(arglist, '').get_parsed_arg() args = Arguments(arglist, '').get_parsed_arg()
@ -204,23 +203,8 @@ def test_show_info(default_conf, mocker, caplog) -> None:
caplog.record_tuples caplog.record_tuples
) )
assert log_has( assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog.record_tuples)
'Parameter --dry-run-db detected ...', assert log_has('Dry run is enabled', caplog.record_tuples)
caplog.record_tuples
)
assert log_has(
'Dry_run will use the DB file: "tradesv3.dry_run.sqlite"',
caplog.record_tuples
)
# Test the Dry run condition
configuration.config.update({'dry_run': False}) # type: ignore
configuration._load_common_config(configuration.config) # type: ignore
assert log_has(
'Dry run is disabled. (--dry_run_db ignored)',
caplog.record_tuples
)
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None: def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:

View File

@ -13,7 +13,6 @@ from unittest.mock import MagicMock
import arrow import arrow
import pytest import pytest
import requests import requests
from sqlalchemy import create_engine
from freqtrade import constants, DependencyException, OperationalException, TemporaryError from freqtrade import constants, DependencyException, OperationalException, TemporaryError
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
@ -36,7 +35,7 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock()) mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
patch_coinmarketcap(mocker) patch_coinmarketcap(mocker)
return FreqtradeBot(config, create_engine('sqlite://')) return FreqtradeBot(config)
def patch_get_signal(mocker, value=(True, False)) -> None: def patch_get_signal(mocker, value=(True, False)) -> None:
@ -346,7 +345,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> Non
# Save state of current whitelist # Save state of current whitelist
whitelist = deepcopy(default_conf['exchange']['pair_whitelist']) whitelist = deepcopy(default_conf['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
freqtrade.create_trade() freqtrade.create_trade()
trade = Trade.query.first() trade = Trade.query.first()
@ -383,13 +382,34 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, fee,
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['stake_amount'] = 0.0005 conf['stake_amount'] = 0.0005
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2] rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2]
assert rate * amount >= conf['stake_amount'] assert rate * amount >= conf['stake_amount']
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
"""
Test create_trade() method
"""
patch_get_signal(mocker)
patch_RPCManager(mocker)
patch_coinmarketcap(mocker)
mocker.patch.multiple(
'freqtrade.freqtradebot.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5),
get_fee=fee,
)
freqtrade = FreqtradeBot(default_conf)
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
freqtrade.create_trade()
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocker) -> None: def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
""" """
Test create_trade() method Test create_trade() method
@ -408,7 +428,7 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, mocke
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['exchange']['pair_whitelist'] = ["ETH/BTC"] conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
conf['exchange']['pair_blacklist'] = ["ETH/BTC"] conf['exchange']['pair_blacklist'] = ["ETH/BTC"]
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
@ -435,7 +455,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['exchange']['pair_whitelist'] = ["ETH/BTC"] conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
conf['exchange']['pair_blacklist'] = ["ETH/BTC"] conf['exchange']['pair_blacklist'] = ["ETH/BTC"]
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
@ -463,7 +483,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
conf['stake_amount'] = 10 conf['stake_amount'] = 10
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
Trade.query = MagicMock() Trade.query = MagicMock()
Trade.query.filter = MagicMock() Trade.query.filter = MagicMock()
@ -487,7 +507,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
get_order=MagicMock(return_value=limit_buy_order), get_order=MagicMock(return_value=limit_buy_order),
get_fee=fee, get_fee=fee,
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert not trades assert not trades
@ -528,7 +548,7 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non
) )
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None) sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
result = freqtrade._process() result = freqtrade._process()
assert result is False assert result is False
assert sleep_mock.has_calls() assert sleep_mock.has_calls()
@ -548,7 +568,7 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) ->
get_markets=markets, get_markets=markets,
buy=MagicMock(side_effect=OperationalException) buy=MagicMock(side_effect=OperationalException)
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
assert freqtrade.state == State.RUNNING assert freqtrade.state == State.RUNNING
result = freqtrade._process() result = freqtrade._process()
@ -574,7 +594,7 @@ def test_process_trade_handling(
get_order=MagicMock(return_value=limit_buy_order), get_order=MagicMock(return_value=limit_buy_order),
get_fee=fee, get_fee=fee,
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
trades = Trade.query.filter(Trade.is_open.is_(True)).all() trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert not trades assert not trades
@ -691,7 +711,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mock
) )
patch_coinmarketcap(mocker, value={'price_usd': 15000.0}) patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
freqtrade.create_trade() freqtrade.create_trade()
@ -734,7 +754,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, fee,
get_fee=fee, get_fee=fee,
) )
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
@ -793,7 +813,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, fee, mocker, ca
) )
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
trade = Trade.query.first() trade = Trade.query.first()
@ -830,7 +850,7 @@ def test_handle_trade_experimental(
) )
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False) mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
trade = Trade.query.first() trade = Trade.query.first()
@ -858,7 +878,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, fe
buy=MagicMock(return_value={'id': limit_buy_order['id']}), buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee, get_fee=fee,
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Create trade and sell it # Create trade and sell it
freqtrade.create_trade() freqtrade.create_trade()
@ -889,7 +909,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fe
cancel_order=cancel_order_mock, cancel_order=cancel_order_mock,
get_fee=fee get_fee=fee
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
trade_buy = Trade( trade_buy = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -929,7 +949,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
get_order=MagicMock(return_value=limit_sell_order_old), get_order=MagicMock(return_value=limit_sell_order_old),
cancel_order=cancel_order_mock cancel_order=cancel_order_mock
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
trade_sell = Trade( trade_sell = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -969,7 +989,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
get_order=MagicMock(return_value=limit_buy_order_old_partial), get_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order=cancel_order_mock cancel_order=cancel_order_mock
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
trade_buy = Trade( trade_buy = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -1017,7 +1037,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
get_order=MagicMock(side_effect=requests.exceptions.RequestException('Oh snap')), get_order=MagicMock(side_effect=requests.exceptions.RequestException('Oh snap')),
cancel_order=cancel_order_mock cancel_order=cancel_order_mock
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
trade_buy = Trade( trade_buy = Trade(
pair='ETH/BTC', pair='ETH/BTC',
@ -1056,7 +1076,7 @@ def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
cancel_order=cancel_order_mock cancel_order=cancel_order_mock
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
Trade.session = MagicMock() Trade.session = MagicMock()
trade = MagicMock() trade = MagicMock()
@ -1082,7 +1102,7 @@ def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
cancel_order=cancel_order_mock cancel_order=cancel_order_mock
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
trade = MagicMock() trade = MagicMock()
order = {'remaining': 1, order = {'remaining': 1,
@ -1109,7 +1129,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
get_fee=fee get_fee=fee
) )
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trade()
@ -1150,7 +1170,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
get_ticker=ticker, get_ticker=ticker,
get_fee=fee get_fee=fee
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trade()
@ -1190,7 +1210,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
get_ticker=ticker, get_ticker=ticker,
get_fee=fee get_fee=fee
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trade()
@ -1231,7 +1251,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
get_ticker=ticker, get_ticker=ticker,
get_fee=fee get_fee=fee
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Create some test data # Create some test data
freqtrade.create_trade() freqtrade.create_trade()
@ -1280,7 +1300,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, fee, mock
'use_sell_signal': True, 'use_sell_signal': True,
'sell_profit_only': True, 'sell_profit_only': True,
} }
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
trade = Trade.query.first() trade = Trade.query.first()
@ -1313,7 +1333,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, fee, moc
'use_sell_signal': True, 'use_sell_signal': True,
'sell_profit_only': False, 'sell_profit_only': False,
} }
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
trade = Trade.query.first() trade = Trade.query.first()
@ -1346,7 +1366,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, mocker
'use_sell_signal': True, 'use_sell_signal': True,
'sell_profit_only': True, 'sell_profit_only': True,
} }
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
trade = Trade.query.first() trade = Trade.query.first()
@ -1381,7 +1401,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, mocke
'sell_profit_only': False, 'sell_profit_only': False,
} }
freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(conf)
freqtrade.create_trade() freqtrade.create_trade()
trade = Trade.query.first() trade = Trade.query.first()
@ -1409,7 +1429,7 @@ def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, ca
open_rate=0.245441, open_rate=0.245441,
open_order_id="123456" open_order_id="123456"
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Amount is reduced by "fee" # Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001) assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001)
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
@ -1436,7 +1456,7 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker):
open_rate=0.245441, open_rate=0.245441,
open_order_id="123456" open_order_id="123456"
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Amount is reduced by "fee" # Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
@ -1463,7 +1483,7 @@ def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, ca
open_rate=0.245441, open_rate=0.245441,
open_order_id="123456" open_order_id="123456"
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Amount does not change # Amount does not change
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
@ -1489,7 +1509,7 @@ def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, mock
open_rate=0.245441, open_rate=0.245441,
open_order_id="123456" open_order_id="123456"
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Amount does not change # Amount does not change
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount assert freqtrade.get_real_amount(trade, buy_order_fee) == amount
@ -1512,7 +1532,7 @@ def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, c
open_rate=0.245441, open_rate=0.245441,
open_order_id="123456" open_order_id="123456"
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Amount is reduced by "fee" # Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001) assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001)
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
@ -1540,7 +1560,7 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee
open_rate=0.245441, open_rate=0.245441,
open_order_id="123456" open_order_id="123456"
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Amount is reduced by "fee" # Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, limit_buy_order) == amount - 0.004 assert freqtrade.get_real_amount(trade, limit_buy_order) == amount - 0.004
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
@ -1568,7 +1588,7 @@ def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order
open_rate=0.245441, open_rate=0.245441,
open_order_id="123456" open_order_id="123456"
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Amount does not change # Amount does not change
assert freqtrade.get_real_amount(trade, limit_buy_order) == amount assert freqtrade.get_real_amount(trade, limit_buy_order) == amount
@ -1593,6 +1613,6 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee,
open_rate=0.245441, open_rate=0.245441,
open_order_id="123456" open_order_id="123456"
) )
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) freqtrade = FreqtradeBot(default_conf)
# Amount does not change # Amount does not change
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount assert freqtrade.get_real_amount(trade, buy_order_fee) == amount

View File

@ -1,9 +1,11 @@
# pragma pylint: disable=missing-docstring, C0103 # pragma pylint: disable=missing-docstring, C0103
import os from copy import deepcopy
from unittest.mock import MagicMock
import pytest import pytest
from sqlalchemy import create_engine from sqlalchemy import create_engine
from freqtrade import constants, OperationalException
from freqtrade.persistence import Trade, init, clean_dry_run_db from freqtrade.persistence import Trade, init, clean_dry_run_db
@ -21,77 +23,54 @@ def test_init_create_session(default_conf, mocker):
assert 'Session' in type(Trade.session).__name__ assert 'Session' in type(Trade.session).__name__
def test_init_dry_run_db(default_conf, mocker): def test_init_custom_db_url(default_conf, mocker):
default_conf.update({'dry_run_db': True}) conf = deepcopy(default_conf)
mocker.patch.dict('freqtrade.persistence._CONF', default_conf)
# First, protect the existing 'tradesv3.dry_run.sqlite' (Do not delete user data) # Update path to a value other than default, but still in-memory
dry_run_db = 'tradesv3.dry_run.sqlite' conf.update({'db_url': 'sqlite:///tmp/freqtrade2_test.sqlite'})
dry_run_db_swp = dry_run_db + '.swp' create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
mocker.patch.dict('freqtrade.persistence._CONF', conf)
if os.path.isfile(dry_run_db): init(conf)
os.rename(dry_run_db, dry_run_db_swp) assert create_engine_mock.call_count == 1
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tmp/freqtrade2_test.sqlite'
# Check if the new tradesv3.dry_run.sqlite was created
init(default_conf)
assert os.path.isfile(dry_run_db) is True
# Delete the file made for this unitest and rollback to the previous
# tradesv3.dry_run.sqlite file
# 1. Delete file from the test
if os.path.isfile(dry_run_db):
os.remove(dry_run_db)
# 2. Rollback to the initial file
if os.path.isfile(dry_run_db_swp):
os.rename(dry_run_db_swp, dry_run_db)
def test_init_dry_run_without_db(default_conf, mocker): def test_init_invalid_db_url(default_conf, mocker):
default_conf.update({'dry_run_db': False}) conf = deepcopy(default_conf)
mocker.patch.dict('freqtrade.persistence._CONF', default_conf)
# First, protect the existing 'tradesv3.dry_run.sqlite' (Do not delete user data) # Update path to a value other than default, but still in-memory
dry_run_db = 'tradesv3.dry_run.sqlite' conf.update({'db_url': 'unknown:///some.url'})
dry_run_db_swp = dry_run_db + '.swp' mocker.patch.dict('freqtrade.persistence._CONF', conf)
if os.path.isfile(dry_run_db): with pytest.raises(OperationalException, match=r'.*no valid database URL*'):
os.rename(dry_run_db, dry_run_db_swp) init(conf)
# Check if the new tradesv3.dry_run.sqlite was created
init(default_conf)
assert os.path.isfile(dry_run_db) is False
# Rollback to the initial 'tradesv3.dry_run.sqlite' file
if os.path.isfile(dry_run_db_swp):
os.rename(dry_run_db_swp, dry_run_db)
def test_init_prod_db(default_conf, mocker): def test_init_prod_db(default_conf, mocker):
default_conf.update({'dry_run': False}) conf = deepcopy(default_conf)
mocker.patch.dict('freqtrade.persistence._CONF', default_conf) conf.update({'dry_run': False})
conf.update({'db_url': constants.DEFAULT_DB_PROD_URL})
# First, protect the existing 'tradesv3.sqlite' (Do not delete user data) create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
prod_db = 'tradesv3.sqlite' mocker.patch.dict('freqtrade.persistence._CONF', conf)
prod_db_swp = prod_db + '.swp'
if os.path.isfile(prod_db): init(conf)
os.rename(prod_db, prod_db_swp) assert create_engine_mock.call_count == 1
assert create_engine_mock.mock_calls[0][1][0] == 'sqlite:///tradesv3.sqlite'
# Check if the new tradesv3.sqlite was created
init(default_conf)
assert os.path.isfile(prod_db) is True
# Delete the file made for this unitest and rollback to the previous tradesv3.sqlite file def test_init_dryrun_db(default_conf, mocker):
conf = deepcopy(default_conf)
conf.update({'dry_run': True})
conf.update({'db_url': constants.DEFAULT_DB_DRYRUN_URL})
# 1. Delete file from the test create_engine_mock = mocker.patch('freqtrade.persistence.create_engine', MagicMock())
if os.path.isfile(prod_db): mocker.patch.dict('freqtrade.persistence._CONF', conf)
os.remove(prod_db)
# Rollback to the initial 'tradesv3.sqlite' file init(conf)
if os.path.isfile(prod_db_swp): assert create_engine_mock.call_count == 1
os.rename(prod_db_swp, prod_db) assert create_engine_mock.mock_calls[0][1][0] == 'sqlite://'
@pytest.mark.usefixtures("init_persistence") @pytest.mark.usefixtures("init_persistence")
@ -328,7 +307,7 @@ def test_calc_profit_percent(limit_buy_order, limit_sell_order, fee):
def test_clean_dry_run_db(default_conf, fee): def test_clean_dry_run_db(default_conf, fee):
init(default_conf, create_engine('sqlite://')) init(default_conf)
# Simulate dry_run entries # Simulate dry_run entries
trade = Trade( trade = Trade(
@ -377,7 +356,7 @@ def test_clean_dry_run_db(default_conf, fee):
assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 1 assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 1
def test_migrate_old(default_conf, fee): def test_migrate_old(mocker, default_conf, fee):
""" """
Test Database migration(starting with old pairformat) Test Database migration(starting with old pairformat)
""" """
@ -409,11 +388,13 @@ def test_migrate_old(default_conf, fee):
amount=amount amount=amount
) )
engine = create_engine('sqlite://') engine = create_engine('sqlite://')
mocker.patch('freqtrade.persistence.create_engine', lambda *args, **kwargs: engine)
# Create table using the old format # Create table using the old format
engine.execute(create_table_old) engine.execute(create_table_old)
engine.execute(insert_table_old) engine.execute(insert_table_old)
# Run init to test migration # Run init to test migration
init(default_conf, engine) init(default_conf)
assert len(Trade.query.filter(Trade.id == 1).all()) == 1 assert len(Trade.query.filter(Trade.id == 1).all()) == 1
trade = Trade.query.filter(Trade.id == 1).first() trade = Trade.query.filter(Trade.id == 1).first()
@ -428,7 +409,7 @@ def test_migrate_old(default_conf, fee):
assert trade.exchange == "bittrex" assert trade.exchange == "bittrex"
def test_migrate_new(default_conf, fee): def test_migrate_new(mocker, default_conf, fee):
""" """
Test Database migration (starting with new pairformat) Test Database migration (starting with new pairformat)
""" """
@ -460,11 +441,13 @@ def test_migrate_new(default_conf, fee):
amount=amount amount=amount
) )
engine = create_engine('sqlite://') engine = create_engine('sqlite://')
mocker.patch('freqtrade.persistence.create_engine', lambda *args, **kwargs: engine)
# Create table using the old format # Create table using the old format
engine.execute(create_table_old) engine.execute(create_table_old)
engine.execute(insert_table_old) engine.execute(insert_table_old)
# Run init to test migration # Run init to test migration
init(default_conf, engine) init(default_conf)
assert len(Trade.query.filter(Trade.id == 1).all()) == 1 assert len(Trade.query.filter(Trade.id == 1).all()) == 1
trade = Trade.query.filter(Trade.id == 1).first() trade = Trade.query.filter(Trade.id == 1).first()

View File

@ -1,4 +1,4 @@
ccxt==1.14.121 ccxt==1.14.155
SQLAlchemy==1.2.8 SQLAlchemy==1.2.8
python-telegram-bot==10.1.0 python-telegram-bot==10.1.0
arrow==0.12.1 arrow==0.12.1
@ -10,14 +10,14 @@ pandas==0.23.0
scikit-learn==0.19.1 scikit-learn==0.19.1
scipy==1.1.0 scipy==1.1.0
jsonschema==2.6.0 jsonschema==2.6.0
numpy==1.14.3 numpy==1.14.4
TA-Lib==0.4.17 TA-Lib==0.4.17
pytest==3.6.0 pytest==3.6.1
pytest-mock==1.10.0 pytest-mock==1.10.0
pytest-cov==2.5.1 pytest-cov==2.5.1
hyperopt==0.1 hyperopt==0.1
# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325 # do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325
networkx==1.11 networkx==1.11 # pyup: ignore
tabulate==0.8.2 tabulate==0.8.2
coinmarketcap==5.0.3 coinmarketcap==5.0.3

View File

@ -8,24 +8,28 @@ import arrow
from freqtrade import (exchange, arguments, misc) from freqtrade import (exchange, arguments, misc)
DEFAULT_DL_PATH = 'freqtrade/tests/testdata' DEFAULT_DL_PATH = 'user_data/data'
arguments = arguments.Arguments(sys.argv[1:], 'download utility') arguments = arguments.Arguments(sys.argv[1:], 'download utility')
arguments.testdata_dl_options() arguments.testdata_dl_options()
args = arguments.parse_args() args = arguments.parse_args()
TICKER_INTERVALS = ['1m', '5m'] timeframes = args.timeframes
PAIRS = []
if args.pairs_file: dl_path = os.path.join(DEFAULT_DL_PATH, args.exchange)
with open(args.pairs_file) as file: if args.export:
PAIRS = json.load(file)
PAIRS = list(set(PAIRS))
dl_path = DEFAULT_DL_PATH
if args.export and os.path.exists(args.export):
dl_path = args.export dl_path = args.export
if not os.path.isdir(dl_path):
sys.exit(f'Directory {dl_path} does not exist.')
pairs_file = args.pairs_file if args.pairs_file else os.path.join(dl_path, 'pairs.json')
if not os.path.isfile(pairs_file):
sys.exit(f'No pairs file found with path {pairs_file}.')
with open(pairs_file) as file:
PAIRS = list(set(json.load(file)))
since_time = None since_time = None
if args.days: if args.days:
since_time = arrow.utcnow().shift(days=-args.days).timestamp * 1000 since_time = arrow.utcnow().shift(days=-args.days).timestamp * 1000
@ -40,7 +44,7 @@ exchange._API = exchange.init_ccxt({'key': '',
for pair in PAIRS: for pair in PAIRS:
for tick_interval in TICKER_INTERVALS: for tick_interval in timeframes:
print(f'downloading pair {pair}, interval {tick_interval}') print(f'downloading pair {pair}, interval {tick_interval}')
data = exchange.get_ticker_history(pair, tick_interval, since_ms=since_time) data = exchange.get_ticker_history(pair, tick_interval, since_ms=since_time)

View File

@ -5,47 +5,75 @@ Script to display when the bot will buy a specific pair
Mandatory Cli parameters: Mandatory Cli parameters:
-p / --pair: pair to examine -p / --pair: pair to examine
Optional Cli parameters Option but recommended
-s / --strategy: strategy to use -s / --strategy: strategy to use
Optional Cli parameters
-d / --datadir: path to pair backtest data -d / --datadir: path to pair backtest data
--timerange: specify what timerange of data to use. --timerange: specify what timerange of data to use.
-l / --live: Live, to download the latest ticker for the pair -l / --live: Live, to download the latest ticker for the pair
-db / --db-url: Show trades stored in database -db / --db-url: Show trades stored in database
Indicators recommended
Row 1: sma, ema3, ema5, ema10, ema50
Row 3: macd, rsi, fisher_rsi, mfi, slowd, slowk, fastd, fastk
Example of usage:
> python3 scripts/plot_dataframe.py --pair BTC/EUR -d user_data/data/ --indicators1 sma,ema3
--indicators2 fastk,fastd
""" """
import logging import logging
import os
import sys import sys
from argparse import Namespace from argparse import Namespace
from typing import Dict, List, Any
from typing import List import plotly.graph_objs as go
from plotly import tools from plotly import tools
from plotly.offline import plot from plotly.offline import plot
import plotly.graph_objs as go
from typing import Dict, List, Any
from sqlalchemy import create_engine
from freqtrade.arguments import Arguments
from freqtrade.analyze import Analyze
from freqtrade import exchange
import freqtrade.optimize as optimize import freqtrade.optimize as optimize
from freqtrade import exchange
from freqtrade import persistence from freqtrade import persistence
from freqtrade.analyze import Analyze
from freqtrade.arguments import Arguments
from freqtrade.optimize.backtesting import setup_configuration
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_CONF: Dict[str, Any] = {} _CONF: Dict[str, Any] = {}
def plot_analyzed_dataframe(args: Namespace) -> None: def plot_analyzed_dataframe(args: Namespace) -> None:
""" """
Calls analyze() and plots the returned dataframe Calls analyze() and plots the returned dataframe
:return: None :return: None
""" """
pair = args.pair.replace('-', '_') global _CONF
# Load the configuration
_CONF.update(setup_configuration(args))
# Set the pair to audit
pair = args.pair
if pair is None:
logger.critical('Parameter --pair mandatory;. E.g --pair ETH/BTC')
exit()
if '/' not in pair:
logger.critical('--pair format must be XXX/YYY')
exit()
# Set timerange to use
timerange = Arguments.parse_timerange(args.timerange) timerange = Arguments.parse_timerange(args.timerange)
# Init strategy # Load the strategy
try: try:
analyze = Analyze({'strategy': args.strategy}) analyze = Analyze(_CONF)
exchange.init(_CONF)
except AttributeError: except AttributeError:
logger.critical( logger.critical(
'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"', 'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"',
@ -53,37 +81,75 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
) )
exit() exit()
tick_interval = analyze.strategy.ticker_interval # Set the ticker to use
tick_interval = analyze.get_ticker_interval()
# Load pair tickers
tickers = {} tickers = {}
if args.live: if args.live:
logger.info('Downloading pair.') logger.info('Downloading pair.')
# Init Bittrex to use public API
exchange.init({'key': '', 'secret': ''})
tickers[pair] = exchange.get_ticker_history(pair, tick_interval) tickers[pair] = exchange.get_ticker_history(pair, tick_interval)
else: else:
tickers = optimize.load_data( tickers = optimize.load_data(
datadir=args.datadir, datadir=args.datadir,
pairs=[pair], pairs=[pair],
ticker_interval=tick_interval, ticker_interval=tick_interval,
refresh_pairs=False, refresh_pairs=_CONF.get('refresh_pairs', False),
timerange=timerange timerange=timerange
) )
# No ticker found, or impossible to download
if tickers == {}:
exit()
# Get trades already made from the DB
trades: List[Trade] = []
if args.db_url:
persistence.init(_CONF)
trades = Trade.query.filter(Trade.pair.is_(pair)).all()
dataframes = analyze.tickerdata_to_dataframe(tickers) dataframes = analyze.tickerdata_to_dataframe(tickers)
dataframe = dataframes[pair] dataframe = dataframes[pair]
dataframe = analyze.populate_buy_trend(dataframe) dataframe = analyze.populate_buy_trend(dataframe)
dataframe = analyze.populate_sell_trend(dataframe) dataframe = analyze.populate_sell_trend(dataframe)
trades = []
if args.db_url:
engine = create_engine('sqlite:///' + args.db_url)
persistence.init(_CONF, engine)
trades = Trade.query.filter(Trade.pair.is_(pair)).all()
if len(dataframe.index) > 750: if len(dataframe.index) > 750:
logger.warning('Ticker contained more than 750 candles, clipping.') logger.warning('Ticker contained more than 750 candles, clipping.')
data = dataframe.tail(750)
fig = generate_graph(
pair=pair,
trades=trades,
data=dataframe.tail(750),
args=args
)
plot(fig, filename=os.path.join('user_data', 'freqtrade-plot.html'))
def generate_graph(pair, trades, data, args) -> tools.make_subplots:
"""
Generate the graph from the data generated by Backtesting or from DB
:param pair: Pair to Display on the graph
:param trades: All trades created
:param data: Dataframe
:param args: sys.argv that contrains the two params indicators1, and indicators2
:return: None
"""
# Define the graph
fig = tools.make_subplots(
rows=3,
cols=1,
shared_xaxes=True,
row_width=[1, 1, 4],
vertical_spacing=0.0001,
)
fig['layout'].update(title=pair)
fig['layout']['yaxis1'].update(title='Price')
fig['layout']['yaxis2'].update(title='Volume')
fig['layout']['yaxis3'].update(title='Other')
# Common information
candles = go.Candlestick( candles = go.Candlestick(
x=data.date, x=data.date,
open=data.open, open=data.open,
@ -145,49 +211,67 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
) )
) )
bb_lower = go.Scatter( # Row 1
x=data.date,
y=data.bb_lowerband,
name='BB lower',
line={'color': "transparent"},
)
bb_upper = go.Scatter(
x=data.date,
y=data.bb_upperband,
name='BB upper',
fill="tonexty",
fillcolor="rgba(0,176,246,0.2)",
line={'color': "transparent"},
)
macd = go.Scattergl(x=data['date'], y=data['macd'], name='MACD')
macdsignal = go.Scattergl(x=data['date'], y=data['macdsignal'], name='MACD signal')
volume = go.Bar(x=data['date'], y=data['volume'], name='Volume')
fig = tools.make_subplots(
rows=3,
cols=1,
shared_xaxes=True,
row_width=[1, 1, 4],
vertical_spacing=0.0001,
)
fig.append_trace(candles, 1, 1) fig.append_trace(candles, 1, 1)
fig.append_trace(bb_lower, 1, 1)
fig.append_trace(bb_upper, 1, 1) if 'bb_lowerband' in data and 'bb_upperband' in data:
bb_lower = go.Scatter(
x=data.date,
y=data.bb_lowerband,
name='BB lower',
line={'color': "transparent"},
)
bb_upper = go.Scatter(
x=data.date,
y=data.bb_upperband,
name='BB upper',
fill="tonexty",
fillcolor="rgba(0,176,246,0.2)",
line={'color': "transparent"},
)
fig.append_trace(bb_lower, 1, 1)
fig.append_trace(bb_upper, 1, 1)
fig = generate_row(fig=fig, row=1, raw_indicators=args.indicators1, data=data)
fig.append_trace(buys, 1, 1) fig.append_trace(buys, 1, 1)
fig.append_trace(sells, 1, 1) fig.append_trace(sells, 1, 1)
fig.append_trace(volume, 2, 1)
fig.append_trace(macd, 3, 1)
fig.append_trace(macdsignal, 3, 1)
fig.append_trace(trade_buys, 1, 1) fig.append_trace(trade_buys, 1, 1)
fig.append_trace(trade_sells, 1, 1) fig.append_trace(trade_sells, 1, 1)
fig['layout'].update(title=args.pair) # Row 2
fig['layout']['yaxis1'].update(title='Price') volume = go.Bar(
fig['layout']['yaxis2'].update(title='Volume') x=data['date'],
fig['layout']['yaxis3'].update(title='MACD') y=data['volume'],
name='Volume'
)
fig.append_trace(volume, 2, 1)
plot(fig, filename='freqtrade-plot.html') # Row 3
fig = generate_row(fig=fig, row=3, raw_indicators=args.indicators2, data=data)
return fig
def generate_row(fig, row, raw_indicators, data) -> tools.make_subplots:
"""
Generator all the indicator selected by the user for a specific row
"""
for indicator in raw_indicators.split(','):
if indicator in data:
scattergl = go.Scattergl(
x=data['date'],
y=data[indicator],
name=indicator
)
fig.append_trace(scattergl, row, 1)
else:
logger.info(
'Indicator "%s" ignored. Reason: This indicator is not found '
'in your strategy.',
indicator
)
return fig
def plot_parse_args(args: List[str]) -> Namespace: def plot_parse_args(args: List[str]) -> Namespace:
@ -198,6 +282,24 @@ def plot_parse_args(args: List[str]) -> Namespace:
""" """
arguments = Arguments(args, 'Graph dataframe') arguments = Arguments(args, 'Graph dataframe')
arguments.scripts_options() arguments.scripts_options()
arguments.parser.add_argument(
'--indicators1',
help='Set indicators from your strategy you want in the first row of the graph. Separate '
'them with a coma. E.g: ema3,ema5 (default: %(default)s)',
type=str,
default='sma,ema3,ema5',
dest='indicators1',
)
arguments.parser.add_argument(
'--indicators2',
help='Set indicators from your strategy you want in the third row of the graph. Separate '
'them with a coma. E.g: fastd,fastk (default: %(default)s)',
type=str,
default='macd',
dest='indicators2',
)
arguments.common_args_parser() arguments.common_args_parser()
arguments.optimizer_shared_options(arguments.parser) arguments.optimizer_shared_options(arguments.parser)
arguments.backtesting_options(arguments.parser) arguments.backtesting_options(arguments.parser)

View File

@ -8,9 +8,12 @@ Mandatory Cli parameters:
Optional Cli parameters Optional Cli parameters
-c / --config: specify configuration file -c / --config: specify configuration file
-s / --strategy: strategy to use -s / --strategy: strategy to use
--timerange: specify what timerange of data to use. -d / --datadir: path to pair backtest data
--timerange: specify what timerange of data to use
--export-filename: Specify where the backtest export is located.
""" """
import logging import logging
import os
import sys import sys
import json import json
from argparse import Namespace from argparse import Namespace
@ -90,7 +93,18 @@ def plot_profit(args: Namespace) -> None:
'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"', 'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"',
config.get('strategy') config.get('strategy')
) )
exit() exit(0)
# Load the profits results
try:
filename = args.exportfilename
with open(filename) as file:
data = json.load(file)
except FileNotFoundError:
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)
# Take pairs from the cli otherwise switch to the pair in the config file # Take pairs from the cli otherwise switch to the pair in the config file
if args.pair: if args.pair:
@ -140,18 +154,7 @@ def plot_profit(args: Namespace) -> None:
num += 1 num += 1
avgclose /= num avgclose /= num
# Load the profits results # make an profits-growth array
# And make an profits-growth array
try:
filename = 'backtest-result.json'
with open(filename) as file:
data = json.load(file)
except FileNotFoundError:
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)
pg = make_profit_array(data, num_iterations, min_date, tick_interval, filter_pairs) pg = make_profit_array(data, num_iterations, min_date, tick_interval, filter_pairs)
# #
@ -184,7 +187,7 @@ def plot_profit(args: Namespace) -> None:
) )
fig.append_trace(pair_profit, 3, 1) fig.append_trace(pair_profit, 3, 1)
plot(fig, filename='freqtrade-profit-plot.html') plot(fig, filename=os.path.join('user_data', 'freqtrade-profit-plot.html'))
def define_index(min_date: int, max_date: int, interval: str) -> int: def define_index(min_date: int, max_date: int, interval: str) -> int:

View File

@ -12,7 +12,7 @@ from freqtrade import __version__
setup(name='freqtrade', setup(name='freqtrade',
version=__version__, version=__version__,
description='Simple High Frequency Trading Bot for crypto currencies', description='Simple High Frequency Trading Bot for crypto currencies',
url='https://github.com/gcarq/freqtrade', url='https://github.com/freqtrade/freqtrade',
author='gcarq and contributors', author='gcarq and contributors',
author_email='michael.egger@tsn.at', author_email='michael.egger@tsn.at',
license='GPLv3', license='GPLv3',

View File

@ -14,7 +14,7 @@ import numpy # noqa
class TestStrategy(IStrategy): class TestStrategy(IStrategy):
""" """
This is a test strategy to inspire you. This is a test strategy to inspire you.
More information in https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
You can: You can:
- Rename the class name (Do not forget to update class_name) - Rename the class name (Do not forget to update class_name)