mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Merge branch 'develop' into github_actions_tests
This commit is contained in:
commit
6c306c0013
18
.github/workflows/docker_update_readme.yml
vendored
Normal file
18
.github/workflows/docker_update_readme.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
name: Update Docker Hub Description
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dockerHubDescription:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@master
|
||||||
|
- name: Docker Hub Description
|
||||||
|
uses: peter-evans/dockerhub-description@v2.1.0
|
||||||
|
env:
|
||||||
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
DOCKERHUB_REPOSITORY: freqtradeorg/freqtrade
|
||||||
|
|
|
@ -28,11 +28,11 @@ jobs:
|
||||||
name: pytest
|
name: pytest
|
||||||
- script:
|
- script:
|
||||||
- cp config.json.example config.json
|
- cp config.json.example config.json
|
||||||
- freqtrade --datadir tests/testdata backtesting
|
- freqtrade backtesting --datadir tests/testdata --strategy DefaultStrategy
|
||||||
name: backtest
|
name: backtest
|
||||||
- script:
|
- script:
|
||||||
- cp config.json.example config.json
|
- cp config.json.example config.json
|
||||||
- freqtrade --datadir tests/testdata --strategy SampleStrategy hyperopt --customhyperopt SampleHyperOpts -e 5
|
- freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt
|
||||||
name: hyperopt
|
name: hyperopt
|
||||||
- script: flake8
|
- script: flake8
|
||||||
name: flake8
|
name: flake8
|
||||||
|
|
|
@ -24,3 +24,5 @@ RUN pip install numpy --no-cache-dir \
|
||||||
COPY . /freqtrade/
|
COPY . /freqtrade/
|
||||||
RUN pip install -e . --no-cache-dir
|
RUN pip install -e . --no-cache-dir
|
||||||
ENTRYPOINT ["freqtrade"]
|
ENTRYPOINT ["freqtrade"]
|
||||||
|
# Default to trade mode
|
||||||
|
CMD [ "trade" ]
|
||||||
|
|
|
@ -62,7 +62,6 @@ git checkout develop
|
||||||
|
|
||||||
For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/latest/installation/).
|
For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/latest/installation/).
|
||||||
|
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
### Bot commands
|
### Bot commands
|
||||||
|
@ -106,7 +105,7 @@ optional arguments:
|
||||||
|
|
||||||
### Telegram RPC commands
|
### Telegram RPC commands
|
||||||
|
|
||||||
Telegram is not mandatory. However, this is a great way to control your bot. More details on our [documentation](https://www.freqtrade.io/en/latest/telegram-usage/)
|
Telegram is not mandatory. However, this is a great way to control your bot. More details and the full command list on our [documentation](https://www.freqtrade.io/en/latest/telegram-usage/)
|
||||||
|
|
||||||
- `/start`: Starts the trader
|
- `/start`: Starts the trader
|
||||||
- `/stop`: Stops the trader
|
- `/stop`: Stops the trader
|
||||||
|
@ -129,11 +128,6 @@ The project is currently setup in two main branches:
|
||||||
- `master` - This branch contains the latest stable release. The bot 'should' be stable on this branch, and is generally well tested.
|
- `master` - This branch contains the latest stable release. The bot 'should' be stable on this branch, and is generally well tested.
|
||||||
- `feat/*` - These are feature branches, which are being worked on heavily. Please don't use these unless you want to test a specific feature.
|
- `feat/*` - These are feature branches, which are being worked on heavily. Please don't use these unless you want to test a specific feature.
|
||||||
|
|
||||||
## A note on Binance
|
|
||||||
|
|
||||||
For Binance, please add `"BNB/<STAKE>"` to your blacklist to avoid issues.
|
|
||||||
Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB order unsellable as the expected amount is not there anymore.
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
### Help / Slack
|
### Help / Slack
|
||||||
|
|
|
@ -23,7 +23,7 @@ if [ $? -ne 0 ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run backtest
|
# Run backtest
|
||||||
docker run --rm -v $(pwd)/config.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} --datadir /tests/testdata backtesting
|
docker run --rm -v $(pwd)/config.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy DefaultStrategy
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "failed running backtest"
|
echo "failed running backtest"
|
||||||
|
|
|
@ -8,6 +8,9 @@ If you do not know what things mentioned here mean, you probably do not need it.
|
||||||
|
|
||||||
Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.
|
Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
Certain systems (like Raspbian) don't load service unit files from the user directory. In this case, copy `freqtrade.service` into `/etc/systemd/user/` (requires superuser permissions).
|
||||||
|
|
||||||
After that you can start the daemon with:
|
After that you can start the daemon with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -45,7 +45,7 @@ freqtrade --datadir user_data/data/bittrex-20180101 backtesting
|
||||||
#### With a (custom) strategy file
|
#### With a (custom) strategy file
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade -s SampleStrategy backtesting
|
freqtrade backtesting -s SampleStrategy
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `-s SampleStrategy` refers to the class name within the strategy file `sample_strategy.py` found in the `freqtrade/user_data/strategies` directory.
|
Where `-s SampleStrategy` refers to the class name within the strategy file `sample_strategy.py` found in the `freqtrade/user_data/strategies` directory.
|
||||||
|
|
|
@ -5,20 +5,18 @@ This page explains the different parameters of the bot and how to run it.
|
||||||
!!! Note
|
!!! Note
|
||||||
If you've used `setup.sh`, don't forget to activate your virtual environment (`source .env/bin/activate`) before running freqtrade commands.
|
If you've used `setup.sh`, don't forget to activate your virtual environment (`source .env/bin/activate`) before running freqtrade commands.
|
||||||
|
|
||||||
|
|
||||||
## Bot commands
|
## Bot commands
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
usage: freqtrade [-h] [-V]
|
||||||
[--userdir PATH] [-s NAME] [--strategy-path PATH]
|
{trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit}
|
||||||
[--db-url PATH] [--sd-notify]
|
|
||||||
{backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit}
|
|
||||||
...
|
...
|
||||||
|
|
||||||
Free, open source crypto trading bot
|
Free, open source crypto trading bot
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
{backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit}
|
{trade,backtesting,edge,hyperopt,create-userdir,list-exchanges,list-timeframes,download-data,plot-dataframe,plot-profit}
|
||||||
|
trade Trade module.
|
||||||
backtesting Backtesting module.
|
backtesting Backtesting module.
|
||||||
edge Edge module.
|
edge Edge module.
|
||||||
hyperopt Hyperopt module.
|
hyperopt Hyperopt module.
|
||||||
|
@ -32,6 +30,27 @@ positional arguments:
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bot trading commands
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: freqtrade trade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||||
|
[--userdir PATH] [-s NAME] [--strategy-path PATH]
|
||||||
|
[--db-url PATH] [--sd-notify] [--dry-run]
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--db-url PATH Override trades database URL, this is useful in custom
|
||||||
|
deployments (default: `sqlite:///tradesv3.sqlite` for
|
||||||
|
Live Run mode, `sqlite://` for Dry Run).
|
||||||
|
--sd-notify Notify systemd service manager.
|
||||||
|
--dry-run Enforce dry-run for trading (removes Exchange secrets
|
||||||
|
and simulates trades).
|
||||||
|
|
||||||
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
--logfile FILE Log to the file specified.
|
--logfile FILE Log to the file specified.
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
|
@ -43,15 +62,12 @@ optional arguments:
|
||||||
Path to directory with historical backtesting data.
|
Path to directory with historical backtesting data.
|
||||||
--userdir PATH, --user-data-dir PATH
|
--userdir PATH, --user-data-dir PATH
|
||||||
Path to userdata directory.
|
Path to userdata directory.
|
||||||
-s NAME, --strategy NAME
|
|
||||||
Specify strategy class name (default:
|
|
||||||
`DefaultStrategy`).
|
|
||||||
--strategy-path PATH Specify additional strategy lookup path.
|
|
||||||
--db-url PATH Override trades database URL, this is useful in custom
|
|
||||||
deployments (default: `sqlite:///tradesv3.sqlite` for
|
|
||||||
Live Run mode, `sqlite://` for Dry Run).
|
|
||||||
--sd-notify Notify systemd service manager.
|
|
||||||
|
|
||||||
|
Strategy arguments:
|
||||||
|
-s NAME, --strategy NAME
|
||||||
|
Specify strategy class name which will be used by the
|
||||||
|
bot.
|
||||||
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
```
|
```
|
||||||
|
|
||||||
### How to specify which configuration file be used?
|
### How to specify which configuration file be used?
|
||||||
|
@ -60,7 +76,7 @@ The bot allows you to select which configuration file you want to use by means o
|
||||||
the `-c/--config` command line option:
|
the `-c/--config` command line option:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade -c path/far/far/away/config.json
|
freqtrade trade -c path/far/far/away/config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
Per default, the bot loads the `config.json` configuration file from the current
|
Per default, the bot loads the `config.json` configuration file from the current
|
||||||
|
@ -79,13 +95,13 @@ empty key and secrete values while running in the Dry Mode (which does not actua
|
||||||
require them):
|
require them):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade -c ./config.json
|
freqtrade trade -c ./config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
and specify both configuration files when running in the normal Live Trade Mode:
|
and specify both configuration files when running in the normal Live Trade Mode:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade -c ./config.json -c path/to/secrets/keys.config.json
|
freqtrade trade -c ./config.json -c path/to/secrets/keys.config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
This could help you hide your private Exchange key and Exchange secrete on you local machine
|
This could help you hide your private Exchange key and Exchange secrete on you local machine
|
||||||
|
@ -134,7 +150,7 @@ In `user_data/strategies` you have a file `my_awesome_strategy.py` which has
|
||||||
a strategy class called `AwesomeStrategy` to load it:
|
a strategy class called `AwesomeStrategy` to load it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade --strategy AwesomeStrategy
|
freqtrade trade --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
|
||||||
|
@ -149,7 +165,7 @@ This parameter allows you to add an additional strategy lookup path, which gets
|
||||||
checked before the default locations (The passed path must be a directory!):
|
checked before the default locations (The passed path must be a directory!):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade --strategy AwesomeStrategy --strategy-path /some/directory
|
freqtrade trade --strategy AwesomeStrategy --strategy-path /some/directory
|
||||||
```
|
```
|
||||||
|
|
||||||
#### How to install a strategy?
|
#### How to install a strategy?
|
||||||
|
@ -165,7 +181,7 @@ using `--db-url`. This can also be used to specify a custom database
|
||||||
in production mode. Example command:
|
in production mode. Example command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
|
freqtrade trade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
|
||||||
```
|
```
|
||||||
|
|
||||||
## Backtesting commands
|
## Backtesting commands
|
||||||
|
@ -173,8 +189,10 @@ freqtrade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
|
||||||
Backtesting also uses the config specified via `-c/--config`.
|
Backtesting also uses the config specified via `-c/--config`.
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade backtesting [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
|
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
[--max_open_trades INT]
|
[-d PATH] [--userdir PATH] [-s NAME]
|
||||||
|
[--strategy-path PATH] [-i TICKER_INTERVAL]
|
||||||
|
[--timerange TIMERANGE] [--max_open_trades INT]
|
||||||
[--stake_amount STAKE_AMOUNT] [--fee FLOAT]
|
[--stake_amount STAKE_AMOUNT] [--fee FLOAT]
|
||||||
[--eps] [--dmmp]
|
[--eps] [--dmmp]
|
||||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||||
|
@ -211,11 +229,29 @@ optional arguments:
|
||||||
--export EXPORT Export backtest results, argument are: trades.
|
--export EXPORT Export backtest results, argument are: trades.
|
||||||
Example: `--export=trades`
|
Example: `--export=trades`
|
||||||
--export-filename PATH
|
--export-filename PATH
|
||||||
Save backtest results to the file with this filename
|
Save backtest results to the file with this filename.
|
||||||
(default: `user_data/backtest_results/backtest-
|
Requires `--export` to be set as well. Example:
|
||||||
result.json`). Requires `--export` to be set as well.
|
`--export-filename=user_data/backtest_results/backtest
|
||||||
Example: `--export-filename=user_data/backtest_results
|
_today.json`
|
||||||
/backtest_today.json`
|
|
||||||
|
Common arguments:
|
||||||
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified.
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default: `config.json`).
|
||||||
|
Multiple --config options may be used. Can be set to
|
||||||
|
`-` to read config from stdin.
|
||||||
|
-d PATH, --datadir PATH
|
||||||
|
Path to directory with historical backtesting data.
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
|
|
||||||
|
Strategy arguments:
|
||||||
|
-s NAME, --strategy NAME
|
||||||
|
Specify strategy class name which will be used by the
|
||||||
|
bot.
|
||||||
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -223,7 +259,7 @@ optional arguments:
|
||||||
|
|
||||||
The first time your run Backtesting, you will need to download some historic data first.
|
The first time your run Backtesting, you will need to download some historic data first.
|
||||||
This can be accomplished by using `freqtrade download-data`.
|
This can be accomplished by using `freqtrade download-data`.
|
||||||
Check the corresponding [help page section](backtesting.md#Getting-data-for-backtesting-and-hyperopt) for more details
|
Check the corresponding [Data Downloading](data-download.md) section for more details
|
||||||
|
|
||||||
## Hyperopt commands
|
## Hyperopt commands
|
||||||
|
|
||||||
|
@ -231,12 +267,14 @@ To optimize your strategy, you can use hyperopt parameter hyperoptimization
|
||||||
to find optimal parameter values for your stategy.
|
to find optimal parameter values for your stategy.
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
|
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||||
|
[--userdir PATH] [-s NAME] [--strategy-path PATH]
|
||||||
|
[-i TICKER_INTERVAL] [--timerange TIMERANGE]
|
||||||
[--max_open_trades INT]
|
[--max_open_trades INT]
|
||||||
[--stake_amount STAKE_AMOUNT] [--fee FLOAT]
|
[--stake_amount STAKE_AMOUNT] [--fee FLOAT]
|
||||||
[--customhyperopt NAME] [--hyperopt-path PATH]
|
[--hyperopt NAME] [--hyperopt-path PATH] [--eps]
|
||||||
[--eps] [-e INT]
|
[-e INT]
|
||||||
[-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
|
[--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
|
||||||
[--dmmp] [--print-all] [--no-color] [--print-json]
|
[--dmmp] [--print-all] [--no-color] [--print-json]
|
||||||
[-j JOBS] [--random-state INT] [--min-trades INT]
|
[-j JOBS] [--random-state INT] [--min-trades INT]
|
||||||
[--continue] [--hyperopt-loss NAME]
|
[--continue] [--hyperopt-loss NAME]
|
||||||
|
@ -254,16 +292,15 @@ optional arguments:
|
||||||
Specify stake_amount.
|
Specify stake_amount.
|
||||||
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
|
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
|
||||||
entry and exit).
|
entry and exit).
|
||||||
--customhyperopt NAME
|
--hyperopt NAME Specify hyperopt class name which will be used by the
|
||||||
Specify hyperopt class name (default:
|
bot.
|
||||||
`DefaultHyperOpt`).
|
--hyperopt-path PATH Specify additional lookup path for Hyperopt and
|
||||||
--hyperopt-path PATH Specify additional lookup path for Hyperopts and
|
|
||||||
Hyperopt Loss functions.
|
Hyperopt Loss functions.
|
||||||
--eps, --enable-position-stacking
|
--eps, --enable-position-stacking
|
||||||
Allow buying the same pair multiple times (position
|
Allow buying the same pair multiple times (position
|
||||||
stacking).
|
stacking).
|
||||||
-e INT, --epochs INT Specify number of epochs (default: 100).
|
-e INT, --epochs INT Specify number of epochs (default: 100).
|
||||||
-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...], --spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]
|
--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]
|
||||||
Specify which parameters to hyperopt. Space-separated
|
Specify which parameters to hyperopt. Space-separated
|
||||||
list. Default: `all`.
|
list. Default: `all`.
|
||||||
--dmmp, --disable-max-market-positions
|
--dmmp, --disable-max-market-positions
|
||||||
|
@ -292,8 +329,27 @@ optional arguments:
|
||||||
generate completely different results, since the
|
generate completely different results, since the
|
||||||
target for optimization is different. Built-in
|
target for optimization is different. Built-in
|
||||||
Hyperopt-loss-functions are: DefaultHyperOptLoss,
|
Hyperopt-loss-functions are: DefaultHyperOptLoss,
|
||||||
OnlyProfitHyperOptLoss, SharpeHyperOptLoss.(default:
|
OnlyProfitHyperOptLoss, SharpeHyperOptLoss (default:
|
||||||
`DefaultHyperOptLoss`).
|
`DefaultHyperOptLoss`).
|
||||||
|
|
||||||
|
Common arguments:
|
||||||
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified.
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default: `config.json`).
|
||||||
|
Multiple --config options may be used. Can be set to
|
||||||
|
`-` to read config from stdin.
|
||||||
|
-d PATH, --datadir PATH
|
||||||
|
Path to directory with historical backtesting data.
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
|
|
||||||
|
Strategy arguments:
|
||||||
|
-s NAME, --strategy NAME
|
||||||
|
Specify strategy class name which will be used by the
|
||||||
|
bot.
|
||||||
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Edge commands
|
## Edge commands
|
||||||
|
@ -301,7 +357,9 @@ optional arguments:
|
||||||
To know your trade expectancy and winrate against historical data, you can use Edge.
|
To know your trade expectancy and winrate against historical data, you can use Edge.
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
|
usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||||
|
[--userdir PATH] [-s NAME] [--strategy-path PATH]
|
||||||
|
[-i TICKER_INTERVAL] [--timerange TIMERANGE]
|
||||||
[--max_open_trades INT] [--stake_amount STAKE_AMOUNT]
|
[--max_open_trades INT] [--stake_amount STAKE_AMOUNT]
|
||||||
[--fee FLOAT] [--stoplosses STOPLOSS_RANGE]
|
[--fee FLOAT] [--stoplosses STOPLOSS_RANGE]
|
||||||
|
|
||||||
|
@ -324,6 +382,24 @@ optional arguments:
|
||||||
(without any space). Example:
|
(without any space). Example:
|
||||||
`--stoplosses=-0.01,-0.1,-0.001`
|
`--stoplosses=-0.01,-0.1,-0.001`
|
||||||
|
|
||||||
|
Common arguments:
|
||||||
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified.
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default: `config.json`).
|
||||||
|
Multiple --config options may be used. Can be set to
|
||||||
|
`-` to read config from stdin.
|
||||||
|
-d PATH, --datadir PATH
|
||||||
|
Path to directory with historical backtesting data.
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
|
|
||||||
|
Strategy arguments:
|
||||||
|
-s NAME, --strategy NAME
|
||||||
|
Specify strategy class name which will be used by the
|
||||||
|
bot.
|
||||||
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
```
|
```
|
||||||
|
|
||||||
To understand edge and how to read the results, please read the [edge documentation](edge.md).
|
To understand edge and how to read the results, please read the [edge documentation](edge.md).
|
||||||
|
|
|
@ -95,7 +95,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
||||||
| `db_url` | `sqlite:///tradesv3.sqlite`| Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`.
|
| `db_url` | `sqlite:///tradesv3.sqlite`| Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`.
|
||||||
| `initial_state` | running | Defines the initial application state. More information below.
|
| `initial_state` | running | Defines the initial application state. More information below.
|
||||||
| `forcebuy_enable` | false | Enables the RPC Commands to force a buy. More information below.
|
| `forcebuy_enable` | false | Enables the RPC Commands to force a buy. More information below.
|
||||||
| `strategy` | DefaultStrategy | Defines Strategy class to use.
|
| `strategy` | None | **Required** Defines Strategy class to use. Recommended to set via `--strategy NAME`.
|
||||||
| `strategy_path` | null | Adds an additional strategy lookup path (must be a directory).
|
| `strategy_path` | null | Adds an additional strategy lookup path (must be a directory).
|
||||||
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
|
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
|
||||||
| `internals.heartbeat_interval` | 60 | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages.
|
| `internals.heartbeat_interval` | 60 | Print heartbeat message every X seconds. Set to 0 to disable heartbeat messages.
|
||||||
|
@ -357,19 +357,12 @@ For example, to test the order type `FOK` with Kraken, and modify candle_limit t
|
||||||
!!! Warning
|
!!! Warning
|
||||||
Please make sure to fully understand the impacts of these settings before modifying them.
|
Please make sure to fully understand the impacts of these settings before modifying them.
|
||||||
|
|
||||||
#### Random notes for other exchanges
|
|
||||||
|
|
||||||
* The Ocean (ccxt id: 'theocean') exchange uses Web3 functionality and requires web3 package to be installed:
|
|
||||||
```shell
|
|
||||||
$ pip3 install web3
|
|
||||||
```
|
|
||||||
|
|
||||||
### What values can be used for fiat_display_currency?
|
### What values can be used for fiat_display_currency?
|
||||||
|
|
||||||
The `fiat_display_currency` configuration parameter sets the base currency to use for the
|
The `fiat_display_currency` configuration parameter sets the base currency to use for the
|
||||||
conversion from coin to fiat in the bot Telegram reports.
|
conversion from coin to fiat in the bot Telegram reports.
|
||||||
|
|
||||||
The valid values are:
|
The valid values are:p
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"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"
|
"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"
|
||||||
|
@ -476,11 +469,13 @@ you run it in production mode.
|
||||||
"secret": "08a9dc6db3d7b53e1acebd9275677f4b0a04f1a5",
|
"secret": "08a9dc6db3d7b53e1acebd9275677f4b0a04f1a5",
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
If you have an exchange API key yet, [see our tutorial](/pre-requisite).
|
If you have an exchange API key yet, [see our tutorial](/pre-requisite).
|
||||||
|
|
||||||
|
You should also make sure to read the [Exchanges](exchanges.md) section of the documentation to be aware of potential configuration details specific to your exchange.
|
||||||
|
|
||||||
### Using proxy with FreqTrade
|
### Using proxy with FreqTrade
|
||||||
|
|
||||||
To use a proxy with freqtrade, add the kwarg `"aiohttp_trust_env"=true` to the `"ccxt_async_kwargs"` dict in the exchange section of the configuration.
|
To use a proxy with freqtrade, add the kwarg `"aiohttp_trust_env"=true` to the `"ccxt_async_kwargs"` dict in the exchange section of the configuration.
|
||||||
|
|
|
@ -78,10 +78,8 @@ freqtrade download-data --exchange binance --pairs XRP/ETH ETH/BTC --days 20 --d
|
||||||
!!! Warning
|
!!! Warning
|
||||||
The historic trades are not available during Freqtrade dry-run and live trade modes because all exchanges tested provide this data with a delay of few 100 candles, so it's not suitable for real-time trading.
|
The historic trades are not available during Freqtrade dry-run and live trade modes because all exchanges tested provide this data with a delay of few 100 candles, so it's not suitable for real-time trading.
|
||||||
|
|
||||||
### Historic Kraken data
|
!!! Note "Kraken user"
|
||||||
|
Kraken users should read [this](exchanges.md#historic-kraken-data) before starting to download data.
|
||||||
The Kraken API does only provide 720 historic candles, which is sufficient for FreqTrade dry-run and live trade modes, but is a problem for backtesting.
|
|
||||||
To download data for the Kraken exchange, using `--dl-trades` is mandatory, otherwise the bot will download the same 720 candles over and over, and you'll not have enough backtest data.
|
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
|
|
||||||
|
|
|
@ -160,7 +160,7 @@ docker run -d \
|
||||||
-v ~/.freqtrade/config.json:/freqtrade/config.json \
|
-v ~/.freqtrade/config.json:/freqtrade/config.json \
|
||||||
-v ~/.freqtrade/user_data/:/freqtrade/user_data \
|
-v ~/.freqtrade/user_data/:/freqtrade/user_data \
|
||||||
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
|
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
|
||||||
freqtrade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy
|
freqtrade trade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
|
@ -202,7 +202,7 @@ docker run -d \
|
||||||
-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 \
|
||||||
-v ~/.freqtrade/user_data/:/freqtrade/user_data/ \
|
-v ~/.freqtrade/user_data/:/freqtrade/user_data/ \
|
||||||
freqtrade --strategy AwsomelyProfitableStrategy backtesting
|
freqtrade backtesting --strategy AwsomelyProfitableStrategy
|
||||||
```
|
```
|
||||||
|
|
||||||
Head over to the [Backtesting Documentation](backtesting.md) for more details.
|
Head over to the [Backtesting Documentation](backtesting.md) for more details.
|
||||||
|
|
|
@ -235,7 +235,7 @@ An example of its output:
|
||||||
### Update cached pairs with the latest data
|
### Update cached pairs with the latest data
|
||||||
|
|
||||||
Edge requires historic data the same way as backtesting does.
|
Edge requires historic data the same way as backtesting does.
|
||||||
Please refer to the [download section](backtesting.md#Getting-data-for-backtesting-and-hyperopt) of the documentation for details.
|
Please refer to the [Data Downloading](data-download.md) section of the documentation for details.
|
||||||
|
|
||||||
### Precising stoploss range
|
### Precising stoploss range
|
||||||
|
|
||||||
|
|
63
docs/exchanges.md
Normal file
63
docs/exchanges.md
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# Exchange-specific Notes
|
||||||
|
|
||||||
|
This page combines common gotchas and informations which are exchange-specific and most likely don't apply to other exchanges.
|
||||||
|
|
||||||
|
## Binance
|
||||||
|
|
||||||
|
!!! Tip "Stoploss on Exchange"
|
||||||
|
Binance is currently the only exchange supporting `stoploss_on_exchange`. It provides great advantages, so we recommend to benefit from it.
|
||||||
|
|
||||||
|
### Blacklists
|
||||||
|
|
||||||
|
For Binance, please add `"BNB/<STAKE>"` to your blacklist to avoid issues.
|
||||||
|
Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB order unsellable as the expected amount is not there anymore.
|
||||||
|
|
||||||
|
### Binance sites
|
||||||
|
|
||||||
|
Binance has been split into 3, and users must use the correct ccxt exchange ID for their exchange, otherwise API keys are not recognized.
|
||||||
|
|
||||||
|
* [binance.com](https://www.binance.com/) - International users. Use exchange id: `binance`.
|
||||||
|
* [binance.us](https://www.binance.us/) - US based users. Use exchange id: `binanceus`.
|
||||||
|
* [binance.je](https://www.binance.je/) - Binance Jersey, trading fiat currencies. Use exchange id: `binanceje`.
|
||||||
|
|
||||||
|
## Kraken
|
||||||
|
|
||||||
|
### Historic Kraken data
|
||||||
|
|
||||||
|
The Kraken API does only provide 720 historic candles, which is sufficient for Freqtrade dry-run and live trade modes, but is a problem for backtesting.
|
||||||
|
To download data for the Kraken exchange, using `--dl-trades` is mandatory, otherwise the bot will download the same 720 candles over and over, and you'll not have enough backtest data.
|
||||||
|
|
||||||
|
## Bittrex
|
||||||
|
|
||||||
|
### Restricted markets
|
||||||
|
|
||||||
|
Bittrex split its exchange into US and International versions.
|
||||||
|
The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction.
|
||||||
|
|
||||||
|
If you have restricted pairs in your whitelist, you'll get a warning message in the log on Freqtrade startup for each restricted pair.
|
||||||
|
|
||||||
|
The warning message will look similar to the following:
|
||||||
|
|
||||||
|
``` output
|
||||||
|
[...] Message: bittrex {"success":false,"message":"RESTRICTED_MARKET","result":null,"explanation":null}"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're an "International" customer on the Bittrex exchange, then this warning will probably not impact you.
|
||||||
|
If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your whitelist.
|
||||||
|
|
||||||
|
You can get a list of restricted markets by using the following snippet:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
import ccxt
|
||||||
|
ct = ccxt.bittrex()
|
||||||
|
_ = ct.load_markets()
|
||||||
|
res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarkets()['result'] if x['IsRestricted']]
|
||||||
|
print(res)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Random notes for other exchanges
|
||||||
|
|
||||||
|
* The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed:
|
||||||
|
```shell
|
||||||
|
$ pip3 install web3
|
||||||
|
```
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
### The bot does not start
|
### The bot does not start
|
||||||
|
|
||||||
Running the bot with `freqtrade --config config.json` does show the output `freqtrade: command not found`.
|
Running the bot with `freqtrade trade --config config.json` does show the output `freqtrade: command not found`.
|
||||||
|
|
||||||
This could have the following reasons:
|
This could have the following reasons:
|
||||||
|
|
||||||
|
@ -48,12 +48,8 @@ You can use the `/forcesell all` command from Telegram.
|
||||||
### I get the message "RESTRICTED_MARKET"
|
### I get the message "RESTRICTED_MARKET"
|
||||||
|
|
||||||
Currently known to happen for US Bittrex users.
|
Currently known to happen for US Bittrex users.
|
||||||
Bittrex split its exchange into US and International versions.
|
|
||||||
The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction.
|
|
||||||
|
|
||||||
If you have restricted pairs in your whitelist, you'll get a warning message in the log on FreqTrade startup for each restricted pair.
|
Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information.
|
||||||
If you're an "International" Customer on the Bittrex exchange, then this warning will probably not impact you.
|
|
||||||
If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your Whitelist.
|
|
||||||
|
|
||||||
### How do I search the bot logs for something?
|
### How do I search the bot logs for something?
|
||||||
|
|
||||||
|
|
|
@ -245,7 +245,7 @@ Because hyperopt tries a lot of combinations to find the best parameters it will
|
||||||
We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
|
We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade -c config.json hyperopt --customhyperopt <hyperoptname> -e 5000 --spaces all
|
freqtrade hyperopt --config config.json --hyperopt <hyperoptname> -e 5000 --spaces all
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `<hyperoptname>` as the name of the custom hyperopt used.
|
Use `<hyperoptname>` as the name of the custom hyperopt used.
|
||||||
|
@ -281,7 +281,7 @@ freqtrade hyperopt --timerange 20180401-20180501
|
||||||
Hyperopt can reuse `populate_indicators`, `populate_buy_trend`, `populate_sell_trend` from your strategy, assuming these methods are **not** in your custom hyperopt file, and a strategy is provided.
|
Hyperopt can reuse `populate_indicators`, `populate_buy_trend`, `populate_sell_trend` from your strategy, assuming these methods are **not** in your custom hyperopt file, and a strategy is provided.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade --strategy SampleStrategy hyperopt --customhyperopt SampleHyperopt
|
freqtrade hyperopt --strategy SampleStrategy --customhyperopt SampleHyperopt
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running Hyperopt with Smaller Search Space
|
### Running Hyperopt with Smaller Search Space
|
||||||
|
|
|
@ -26,26 +26,32 @@ You will need to create API Keys (Usually you get `key` and `secret`) from the E
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot.
|
Freqtrade provides the Linux/MacOS Easy Installation script to install all dependencies and help you configure the bot.
|
||||||
|
|
||||||
!!! Note
|
|
||||||
Python3.6 or higher and the corresponding pip are assumed to be available. The install-script will warn and stop if that's not the case.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone git@github.com:freqtrade/freqtrade.git
|
|
||||||
cd freqtrade
|
|
||||||
./setup.sh --install
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! Note "Version considerations"
|
|
||||||
When cloning the repository the default working branch is name `develop`. This branch contains the last features (can be considered as relatively stable thanks to automated tests). The `master` branch contains the code of the last release (done once per month with a one week old snapshot of the `develop` branch to prevent packaging bugs so potentially more stable).
|
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Windows installation is explained [here](#windows).
|
Windows installation is explained [here](#windows).
|
||||||
|
|
||||||
## Easy Installation - Linux Script
|
The easiest way to install and run Freqtrade is to clone the bot GitHub repository and then run the Easy Installation script, if it's available for your platform.
|
||||||
|
|
||||||
If you are on Debian, Ubuntu or MacOS freqtrade provides a script to Install, Update, Configure, and Reset your bot.
|
!!! Note "Version considerations"
|
||||||
|
When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests). The `master` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable).
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
Python3.6 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository.
|
||||||
|
|
||||||
|
This can be achieved with the following commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone git@github.com:freqtrade/freqtrade.git
|
||||||
|
cd freqtrade
|
||||||
|
git checkout master # Optional, see (1)
|
||||||
|
./setup.sh --install
|
||||||
|
```
|
||||||
|
(1) This command switches the cloned repository to the use of the `master` branch. It's not needed if you wish to stay on the `develop` branch. You may later switch between branches at any time with the `git checkout master`/`git checkout develop` commands.
|
||||||
|
|
||||||
|
## Easy Installation Script (Linux/MacOS)
|
||||||
|
|
||||||
|
If you are on Debian, Ubuntu or MacOS Freqtrade provides the script to install, update, configure and reset the codebase of your bot.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ ./setup.sh
|
$ ./setup.sh
|
||||||
|
@ -58,25 +64,25 @@ usage:
|
||||||
|
|
||||||
** --install **
|
** --install **
|
||||||
|
|
||||||
This script will install everything you need to run the bot:
|
With this option, the script will install everything you need to run the bot:
|
||||||
|
|
||||||
* Mandatory software as: `ta-lib`
|
* Mandatory software as: `ta-lib`
|
||||||
* Setup your virtualenv
|
* Setup your virtualenv
|
||||||
* Configure your `config.json` file
|
* Configure your `config.json` file
|
||||||
|
|
||||||
This script is a combination of `install script` `--reset`, `--config`
|
This option is a combination of installation tasks, `--reset` and `--config`.
|
||||||
|
|
||||||
** --update **
|
** --update **
|
||||||
|
|
||||||
Update parameter will pull the last version of your current branch and update your virtualenv.
|
This option will pull the last version of your current branch and update your virtualenv. Run the script with this option periodically to update your bot.
|
||||||
|
|
||||||
** --reset **
|
** --reset **
|
||||||
|
|
||||||
Reset parameter will hard reset your branch (only if you are on `master` or `develop`) and recreate your virtualenv.
|
This option will hard reset your branch (only if you are on either `master` or `develop`) and recreate your virtualenv.
|
||||||
|
|
||||||
** --config **
|
** --config **
|
||||||
|
|
||||||
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
|
Use this option to configure the `config.json` configuration file. The script will interactively ask you questions to setup your bot and create your `config.json`.
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -186,7 +192,7 @@ python3 -m pip install -e .
|
||||||
If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
|
If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade -c config.json
|
freqtrade trade -c config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
*Note*: If you run the bot on a server, you should consider using [Docker](docker.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
|
*Note*: If you run the bot on a server, you should consider using [Docker](docker.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
|
||||||
|
|
|
@ -23,13 +23,15 @@ The `freqtrade plot-dataframe` subcommand shows an interactive graph with three
|
||||||
Possible arguments:
|
Possible arguments:
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade plot-dataframe [-h] [-p PAIRS [PAIRS ...]]
|
usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
|
[-d PATH] [--userdir PATH] [-s NAME]
|
||||||
|
[--strategy-path PATH] [-p PAIRS [PAIRS ...]]
|
||||||
[--indicators1 INDICATORS1 [INDICATORS1 ...]]
|
[--indicators1 INDICATORS1 [INDICATORS1 ...]]
|
||||||
[--indicators2 INDICATORS2 [INDICATORS2 ...]]
|
[--indicators2 INDICATORS2 [INDICATORS2 ...]]
|
||||||
[--plot-limit INT] [--db-url PATH]
|
[--plot-limit INT] [--db-url PATH]
|
||||||
[--trade-source {DB,file}] [--export EXPORT]
|
[--trade-source {DB,file}] [--export EXPORT]
|
||||||
[--export-filename PATH]
|
[--export-filename PATH]
|
||||||
[--timerange TIMERANGE]
|
[--timerange TIMERANGE] [-i TICKER_INTERVAL]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
|
@ -62,6 +64,28 @@ optional arguments:
|
||||||
/backtest_today.json`
|
/backtest_today.json`
|
||||||
--timerange TIMERANGE
|
--timerange TIMERANGE
|
||||||
Specify what timerange of data to use.
|
Specify what timerange of data to use.
|
||||||
|
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
|
||||||
|
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
|
||||||
|
`1d`).
|
||||||
|
|
||||||
|
Common arguments:
|
||||||
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified.
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default: `config.json`).
|
||||||
|
Multiple --config options may be used. Can be set to
|
||||||
|
`-` to read config from stdin.
|
||||||
|
-d PATH, --datadir PATH
|
||||||
|
Path to directory with historical backtesting data.
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
|
|
||||||
|
Strategy arguments:
|
||||||
|
-s NAME, --strategy NAME
|
||||||
|
Specify strategy class name (default:
|
||||||
|
`DefaultStrategy`).
|
||||||
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -83,7 +107,7 @@ Use `--indicators1` for the main plot and `--indicators2` for the subplot below
|
||||||
You will almost certainly want to specify a custom strategy! This can be done by adding `-s Classname` / `--strategy ClassName` to the command.
|
You will almost certainly want to specify a custom strategy! This can be done by adding `-s Classname` / `--strategy ClassName` to the command.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH --indicators1 sma ema --indicators2 macd
|
freqtrade plot-dataframe --strategy AwesomeStrategy -p BTC/ETH --indicators1 sma ema --indicators2 macd
|
||||||
```
|
```
|
||||||
|
|
||||||
### Further usage examples
|
### Further usage examples
|
||||||
|
@ -91,25 +115,25 @@ freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH --indicators1 sma
|
||||||
To plot multiple pairs, separate them with a space:
|
To plot multiple pairs, separate them with a space:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH XRP/ETH
|
freqtrade plot-dataframe --strategy AwesomeStrategy -p BTC/ETH XRP/ETH
|
||||||
```
|
```
|
||||||
|
|
||||||
To plot a timerange (to zoom in)
|
To plot a timerange (to zoom in)
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
freqtrade --strategy AwesomeStrategy plot-dataframe -p BTC/ETH --timerange=20180801-20180805
|
freqtrade plot-dataframe --strategy AwesomeStrategy -p BTC/ETH --timerange=20180801-20180805
|
||||||
```
|
```
|
||||||
|
|
||||||
To plot trades stored in a database use `--db-url` in combination with `--trade-source DB`:
|
To plot trades stored in a database use `--db-url` in combination with `--trade-source DB`:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
freqtrade --strategy AwesomeStrategy plot-dataframe --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH --trade-source DB
|
freqtrade plot-dataframe --strategy AwesomeStrategy --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH --trade-source DB
|
||||||
```
|
```
|
||||||
|
|
||||||
To plot trades from a backtesting result, use `--export-filename <filename>`
|
To plot trades from a backtesting result, use `--export-filename <filename>`
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
freqtrade --strategy AwesomeStrategy plot-dataframe --export-filename user_data/backtest_results/backtest-result.json -p BTC/ETH
|
freqtrade plot-dataframe --strategy AwesomeStrategy --export-filename user_data/backtest_results/backtest-result.json -p BTC/ETH
|
||||||
```
|
```
|
||||||
|
|
||||||
## Plot profit
|
## Plot profit
|
||||||
|
@ -133,10 +157,11 @@ The third graph can be useful to spot outliers, events in pairs that cause profi
|
||||||
Possible options for the `freqtrade plot-profit` subcommand:
|
Possible options for the `freqtrade plot-profit` subcommand:
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade plot-profit [-h] [-p PAIRS [PAIRS ...]]
|
usage: freqtrade plot-profit [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
|
[-d PATH] [--userdir PATH] [-p PAIRS [PAIRS ...]]
|
||||||
[--timerange TIMERANGE] [--export EXPORT]
|
[--timerange TIMERANGE] [--export EXPORT]
|
||||||
[--export-filename PATH] [--db-url PATH]
|
[--export-filename PATH] [--db-url PATH]
|
||||||
[--trade-source {DB,file}]
|
[--trade-source {DB,file}] [-i TICKER_INTERVAL]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
|
@ -159,6 +184,22 @@ optional arguments:
|
||||||
--trade-source {DB,file}
|
--trade-source {DB,file}
|
||||||
Specify the source for trades (Can be DB or file
|
Specify the source for trades (Can be DB or file
|
||||||
(backtest file)) Default: file
|
(backtest file)) Default: file
|
||||||
|
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
|
||||||
|
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
|
||||||
|
`1d`).
|
||||||
|
|
||||||
|
Common arguments:
|
||||||
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified.
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default: `config.json`).
|
||||||
|
Multiple --config options may be used. Can be set to
|
||||||
|
`-` to read config from stdin.
|
||||||
|
-d PATH, --datadir PATH
|
||||||
|
Path to directory with historical backtesting data.
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,14 @@ Sample configuration:
|
||||||
!!! Danger "Password selection"
|
!!! Danger "Password selection"
|
||||||
Please make sure to select a very strong, unique password to protect your bot from unauthorized access.
|
Please make sure to select a very strong, unique password to protect your bot from unauthorized access.
|
||||||
|
|
||||||
You can then access the API by going to `http://127.0.0.1:8080/api/v1/version` to check if the API is running correctly.
|
You can then access the API by going to `http://127.0.0.1:8080/api/v1/ping` in a browser to check if the API is running correctly.
|
||||||
|
This should return the response:
|
||||||
|
|
||||||
|
``` output
|
||||||
|
{"status":"pong"}
|
||||||
|
```
|
||||||
|
|
||||||
|
All other endpoints return sensitive info and require authentication, so are not available through a web browser.
|
||||||
|
|
||||||
To generate a secure password, either use a password manager, or use the below code snipped.
|
To generate a secure password, either use a password manager, or use the below code snipped.
|
||||||
|
|
||||||
|
@ -58,7 +65,7 @@ docker run -d \
|
||||||
-v ~/.freqtrade/user_data/:/freqtrade/user_data \
|
-v ~/.freqtrade/user_data/:/freqtrade/user_data \
|
||||||
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
|
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
|
||||||
-p 127.0.0.1:8080:8080 \
|
-p 127.0.0.1:8080:8080 \
|
||||||
freqtrade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy
|
freqtrade trade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Danger "Security warning"
|
!!! Danger "Security warning"
|
||||||
|
|
|
@ -13,7 +13,7 @@ Let assume you have a class called `AwesomeStrategy` in the file `awesome-strate
|
||||||
2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name)
|
2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade --strategy AwesomeStrategy
|
freqtrade trade --strategy AwesomeStrategy
|
||||||
```
|
```
|
||||||
|
|
||||||
## Change your strategy
|
## Change your strategy
|
||||||
|
@ -45,7 +45,7 @@ The current version is 2 - which is also the default when it's not set explicitl
|
||||||
Future versions will require this to be set.
|
Future versions will require this to be set.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade --strategy AwesomeStrategy
|
freqtrade trade --strategy AwesomeStrategy
|
||||||
```
|
```
|
||||||
|
|
||||||
**For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/sample_strategy.py)
|
**For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/sample_strategy.py)
|
||||||
|
@ -314,9 +314,9 @@ Please always check the mode of operation to select the correct method to get da
|
||||||
#### Possible options for DataProvider
|
#### Possible options for DataProvider
|
||||||
|
|
||||||
- `available_pairs` - Property with tuples listing cached pairs with their intervals (pair, interval).
|
- `available_pairs` - Property with tuples listing cached pairs with their intervals (pair, interval).
|
||||||
- `ohlcv(pair, ticker_interval)` - Currently cached ticker data for the pair, returns DataFrame or empty DataFrame.
|
- `ohlcv(pair, timeframe)` - Currently cached ticker data for the pair, returns DataFrame or empty DataFrame.
|
||||||
- `historic_ohlcv(pair, ticker_interval)` - Returns historical data stored on disk.
|
- `historic_ohlcv(pair, timeframe)` - Returns historical data stored on disk.
|
||||||
- `get_pair_dataframe(pair, ticker_interval)` - This is a universal method, which returns either historical data (for backtesting) or cached live data (for the Dry-Run and Live-Run modes).
|
- `get_pair_dataframe(pair, timeframe)` - This is a universal method, which returns either historical data (for backtesting) or cached live data (for the Dry-Run and Live-Run modes).
|
||||||
- `orderbook(pair, maximum)` - Returns latest orderbook data for the pair, a dict with bids/asks with a total of `maximum` entries.
|
- `orderbook(pair, maximum)` - Returns latest orderbook data for the pair, a dict with bids/asks with a total of `maximum` entries.
|
||||||
- `market(pair)` - Returns market data for the pair: fees, limits, precisions, activity flag, etc. See [ccxt documentation](https://github.com/ccxt/ccxt/wiki/Manual#markets) for more details on Market data structure.
|
- `market(pair)` - Returns market data for the pair: fees, limits, precisions, activity flag, etc. See [ccxt documentation](https://github.com/ccxt/ccxt/wiki/Manual#markets) for more details on Market data structure.
|
||||||
- `runmode` - Property containing the current runmode.
|
- `runmode` - Property containing the current runmode.
|
||||||
|
@ -327,7 +327,7 @@ Please always check the mode of operation to select the correct method to get da
|
||||||
if self.dp:
|
if self.dp:
|
||||||
inf_pair, inf_timeframe = self.informative_pairs()[0]
|
inf_pair, inf_timeframe = self.informative_pairs()[0]
|
||||||
informative = self.dp.get_pair_dataframe(pair=inf_pair,
|
informative = self.dp.get_pair_dataframe(pair=inf_pair,
|
||||||
ticker_interval=inf_timeframe)
|
timeframe=inf_timeframe)
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Warning "Warning about backtesting"
|
!!! Warning "Warning about backtesting"
|
||||||
|
@ -485,7 +485,7 @@ The strategy template is located in the file
|
||||||
If you want to use a strategy from a different directory you can pass `--strategy-path`
|
If you want to use a strategy from a different directory you can pass `--strategy-path`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade --strategy AwesomeStrategy --strategy-path /some/directory
|
freqtrade trade --strategy AwesomeStrategy --strategy-path /some/directory
|
||||||
```
|
```
|
||||||
|
|
||||||
### Common mistakes when developing strategies
|
### Common mistakes when developing strategies
|
||||||
|
|
|
@ -10,7 +10,7 @@ from pathlib import Path
|
||||||
# Customize these according to your needs.
|
# Customize these according to your needs.
|
||||||
|
|
||||||
# Define some constants
|
# Define some constants
|
||||||
ticker_interval = "5m"
|
timeframe = "5m"
|
||||||
# Name of the strategy class
|
# Name of the strategy class
|
||||||
strategy_name = 'SampleStrategy'
|
strategy_name = 'SampleStrategy'
|
||||||
# Path to user data
|
# Path to user data
|
||||||
|
@ -29,7 +29,7 @@ pair = "BTC_USDT"
|
||||||
from freqtrade.data.history import load_pair_history
|
from freqtrade.data.history import load_pair_history
|
||||||
|
|
||||||
candles = load_pair_history(datadir=data_location,
|
candles = load_pair_history(datadir=data_location,
|
||||||
ticker_interval=ticker_interval,
|
timeframe=timeframe,
|
||||||
pair=pair)
|
pair=pair)
|
||||||
|
|
||||||
# Confirm success
|
# Confirm success
|
||||||
|
|
|
@ -6,7 +6,7 @@ After=network.target
|
||||||
# Set WorkingDirectory and ExecStart to your file paths accordingly
|
# Set WorkingDirectory and ExecStart to your file paths accordingly
|
||||||
# NOTE: %h will be resolved to /home/<username>
|
# NOTE: %h will be resolved to /home/<username>
|
||||||
WorkingDirectory=%h/freqtrade
|
WorkingDirectory=%h/freqtrade
|
||||||
ExecStart=/usr/bin/freqtrade
|
ExecStart=/usr/bin/freqtrade trade
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|
|
@ -6,7 +6,7 @@ After=network.target
|
||||||
# Set WorkingDirectory and ExecStart to your file paths accordingly
|
# Set WorkingDirectory and ExecStart to your file paths accordingly
|
||||||
# NOTE: %h will be resolved to /home/<username>
|
# NOTE: %h will be resolved to /home/<username>
|
||||||
WorkingDirectory=%h/freqtrade
|
WorkingDirectory=%h/freqtrade
|
||||||
ExecStart=/usr/bin/freqtrade --sd-notify
|
ExecStart=/usr/bin/freqtrade trade --sd-notify
|
||||||
|
|
||||||
Restart=always
|
Restart=always
|
||||||
#Restart=on-failure
|
#Restart=on-failure
|
||||||
|
|
|
@ -13,7 +13,7 @@ ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_dat
|
||||||
|
|
||||||
ARGS_STRATEGY = ["strategy", "strategy_path"]
|
ARGS_STRATEGY = ["strategy", "strategy_path"]
|
||||||
|
|
||||||
ARGS_MAIN = ARGS_COMMON + ARGS_STRATEGY + ["db_url", "sd_notify"]
|
ARGS_TRADE = ["db_url", "sd_notify", "dry_run"]
|
||||||
|
|
||||||
ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange",
|
ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange",
|
||||||
"max_open_trades", "stake_amount", "fee"]
|
"max_open_trades", "stake_amount", "fee"]
|
||||||
|
@ -42,8 +42,9 @@ ARGS_CREATE_USERDIR = ["user_data_dir"]
|
||||||
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchange",
|
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchange",
|
||||||
"timeframes", "erase"]
|
"timeframes", "erase"]
|
||||||
|
|
||||||
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_url",
|
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
|
||||||
"trade_source", "export", "exportfilename", "timerange", "ticker_interval"]
|
"db_url", "trade_source", "export", "exportfilename",
|
||||||
|
"timerange", "ticker_interval"]
|
||||||
|
|
||||||
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
||||||
"trade_source", "ticker_interval"]
|
"trade_source", "ticker_interval"]
|
||||||
|
@ -61,11 +62,6 @@ class Arguments:
|
||||||
def __init__(self, args: Optional[List[str]]) -> None:
|
def __init__(self, args: Optional[List[str]]) -> None:
|
||||||
self.args = args
|
self.args = args
|
||||||
self._parsed_arg: Optional[argparse.Namespace] = None
|
self._parsed_arg: Optional[argparse.Namespace] = None
|
||||||
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
|
|
||||||
|
|
||||||
def _load_args(self) -> None:
|
|
||||||
self._build_args(optionlist=ARGS_MAIN)
|
|
||||||
self._build_subcommands()
|
|
||||||
|
|
||||||
def get_parsed_arg(self) -> Dict[str, Any]:
|
def get_parsed_arg(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -73,7 +69,7 @@ class Arguments:
|
||||||
:return: List[str] List of arguments
|
:return: List[str] List of arguments
|
||||||
"""
|
"""
|
||||||
if self._parsed_arg is None:
|
if self._parsed_arg is None:
|
||||||
self._load_args()
|
self._build_subcommands()
|
||||||
self._parsed_arg = self._parse_args()
|
self._parsed_arg = self._parse_args()
|
||||||
|
|
||||||
return vars(self._parsed_arg)
|
return vars(self._parsed_arg)
|
||||||
|
@ -84,22 +80,17 @@ class Arguments:
|
||||||
"""
|
"""
|
||||||
parsed_arg = self.parser.parse_args(self.args)
|
parsed_arg = self.parser.parse_args(self.args)
|
||||||
|
|
||||||
# When no config is provided, but a config exists, use that configuration!
|
|
||||||
subparser = parsed_arg.subparser if 'subparser' in parsed_arg else None
|
|
||||||
|
|
||||||
# Workaround issue in argparse with action='append' and default value
|
# Workaround issue in argparse with action='append' and default value
|
||||||
# (see https://bugs.python.org/issue16399)
|
# (see https://bugs.python.org/issue16399)
|
||||||
# Allow no-config for certain commands (like downloading / plotting)
|
# Allow no-config for certain commands (like downloading / plotting)
|
||||||
if (parsed_arg.config is None
|
if ('config' in parsed_arg and parsed_arg.config is None and
|
||||||
and subparser not in NO_CONF_ALLOWED
|
((Path.cwd() / constants.DEFAULT_CONFIG).is_file() or
|
||||||
and ((Path.cwd() / constants.DEFAULT_CONFIG).is_file()
|
not ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED))):
|
||||||
or (subparser not in NO_CONF_REQURIED))):
|
|
||||||
parsed_arg.config = [constants.DEFAULT_CONFIG]
|
parsed_arg.config = [constants.DEFAULT_CONFIG]
|
||||||
|
|
||||||
return parsed_arg
|
return parsed_arg
|
||||||
|
|
||||||
def _build_args(self, optionlist, parser=None):
|
def _build_args(self, optionlist, parser):
|
||||||
parser = parser or self.parser
|
|
||||||
|
|
||||||
for val in optionlist:
|
for val in optionlist:
|
||||||
opt = AVAILABLE_CLI_OPTIONS[val]
|
opt = AVAILABLE_CLI_OPTIONS[val]
|
||||||
|
@ -110,38 +101,68 @@ class Arguments:
|
||||||
Builds and attaches all subcommands.
|
Builds and attaches all subcommands.
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
# Build shared arguments (as group Common Options)
|
||||||
|
_common_parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
group = _common_parser.add_argument_group("Common arguments")
|
||||||
|
self._build_args(optionlist=ARGS_COMMON, parser=group)
|
||||||
|
|
||||||
|
_strategy_parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
strategy_group = _strategy_parser.add_argument_group("Strategy arguments")
|
||||||
|
self._build_args(optionlist=ARGS_STRATEGY, parser=strategy_group)
|
||||||
|
|
||||||
|
# Build main command
|
||||||
|
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
|
||||||
|
self._build_args(optionlist=['version'], parser=self.parser)
|
||||||
|
|
||||||
from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge
|
from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge
|
||||||
from freqtrade.utils import (start_create_userdir, start_download_data,
|
from freqtrade.utils import (start_create_userdir, start_download_data,
|
||||||
start_list_exchanges, start_list_timeframes,
|
start_list_exchanges, start_list_markets,
|
||||||
start_list_markets)
|
start_list_timeframes, start_trading)
|
||||||
|
from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit
|
||||||
|
|
||||||
subparsers = self.parser.add_subparsers(dest='subparser')
|
subparsers = self.parser.add_subparsers(dest='command',
|
||||||
|
# Use custom message when no subhandler is added
|
||||||
|
# shown from `main.py`
|
||||||
|
# required=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add trade subcommand
|
||||||
|
trade_cmd = subparsers.add_parser('trade', help='Trade module.',
|
||||||
|
parents=[_common_parser, _strategy_parser])
|
||||||
|
trade_cmd.set_defaults(func=start_trading)
|
||||||
|
self._build_args(optionlist=ARGS_TRADE, parser=trade_cmd)
|
||||||
|
|
||||||
# Add backtesting subcommand
|
# Add backtesting subcommand
|
||||||
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.')
|
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.',
|
||||||
|
parents=[_common_parser, _strategy_parser])
|
||||||
backtesting_cmd.set_defaults(func=start_backtesting)
|
backtesting_cmd.set_defaults(func=start_backtesting)
|
||||||
self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd)
|
self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd)
|
||||||
|
|
||||||
# Add edge subcommand
|
# Add edge subcommand
|
||||||
edge_cmd = subparsers.add_parser('edge', help='Edge module.')
|
edge_cmd = subparsers.add_parser('edge', help='Edge module.',
|
||||||
|
parents=[_common_parser, _strategy_parser])
|
||||||
edge_cmd.set_defaults(func=start_edge)
|
edge_cmd.set_defaults(func=start_edge)
|
||||||
self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd)
|
self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd)
|
||||||
|
|
||||||
# Add hyperopt subcommand
|
# Add hyperopt subcommand
|
||||||
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.')
|
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.',
|
||||||
|
parents=[_common_parser, _strategy_parser],
|
||||||
|
)
|
||||||
hyperopt_cmd.set_defaults(func=start_hyperopt)
|
hyperopt_cmd.set_defaults(func=start_hyperopt)
|
||||||
self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd)
|
self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd)
|
||||||
|
|
||||||
# add create-userdir subcommand
|
# add create-userdir subcommand
|
||||||
create_userdir_cmd = subparsers.add_parser('create-userdir',
|
create_userdir_cmd = subparsers.add_parser('create-userdir',
|
||||||
help="Create user-data directory.")
|
help="Create user-data directory.",
|
||||||
|
)
|
||||||
create_userdir_cmd.set_defaults(func=start_create_userdir)
|
create_userdir_cmd.set_defaults(func=start_create_userdir)
|
||||||
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
|
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
|
||||||
|
|
||||||
# Add list-exchanges subcommand
|
# Add list-exchanges subcommand
|
||||||
list_exchanges_cmd = subparsers.add_parser(
|
list_exchanges_cmd = subparsers.add_parser(
|
||||||
'list-exchanges',
|
'list-exchanges',
|
||||||
help='Print available exchanges.'
|
help='Print available exchanges.',
|
||||||
|
parents=[_common_parser],
|
||||||
)
|
)
|
||||||
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
|
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
|
||||||
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
|
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
|
||||||
|
@ -149,7 +170,8 @@ class Arguments:
|
||||||
# Add list-timeframes subcommand
|
# Add list-timeframes subcommand
|
||||||
list_timeframes_cmd = subparsers.add_parser(
|
list_timeframes_cmd = subparsers.add_parser(
|
||||||
'list-timeframes',
|
'list-timeframes',
|
||||||
help='Print available ticker intervals (timeframes) for the exchange.'
|
help='Print available ticker intervals (timeframes) for the exchange.',
|
||||||
|
parents=[_common_parser],
|
||||||
)
|
)
|
||||||
list_timeframes_cmd.set_defaults(func=start_list_timeframes)
|
list_timeframes_cmd.set_defaults(func=start_list_timeframes)
|
||||||
self._build_args(optionlist=ARGS_LIST_TIMEFRAMES, parser=list_timeframes_cmd)
|
self._build_args(optionlist=ARGS_LIST_TIMEFRAMES, parser=list_timeframes_cmd)
|
||||||
|
@ -157,7 +179,8 @@ class Arguments:
|
||||||
# Add list-markets subcommand
|
# Add list-markets subcommand
|
||||||
list_markets_cmd = subparsers.add_parser(
|
list_markets_cmd = subparsers.add_parser(
|
||||||
'list-markets',
|
'list-markets',
|
||||||
help='Print markets on exchange.'
|
help='Print markets on exchange.',
|
||||||
|
parents=[_common_parser],
|
||||||
)
|
)
|
||||||
list_markets_cmd.set_defaults(func=partial(start_list_markets, pairs_only=False))
|
list_markets_cmd.set_defaults(func=partial(start_list_markets, pairs_only=False))
|
||||||
self._build_args(optionlist=ARGS_LIST_PAIRS, parser=list_markets_cmd)
|
self._build_args(optionlist=ARGS_LIST_PAIRS, parser=list_markets_cmd)
|
||||||
|
@ -165,7 +188,8 @@ class Arguments:
|
||||||
# Add list-pairs subcommand
|
# Add list-pairs subcommand
|
||||||
list_pairs_cmd = subparsers.add_parser(
|
list_pairs_cmd = subparsers.add_parser(
|
||||||
'list-pairs',
|
'list-pairs',
|
||||||
help='Print pairs on exchange.'
|
help='Print pairs on exchange.',
|
||||||
|
parents=[_common_parser],
|
||||||
)
|
)
|
||||||
list_pairs_cmd.set_defaults(func=partial(start_list_markets, pairs_only=True))
|
list_pairs_cmd.set_defaults(func=partial(start_list_markets, pairs_only=True))
|
||||||
self._build_args(optionlist=ARGS_LIST_PAIRS, parser=list_pairs_cmd)
|
self._build_args(optionlist=ARGS_LIST_PAIRS, parser=list_pairs_cmd)
|
||||||
|
@ -173,16 +197,17 @@ class Arguments:
|
||||||
# Add download-data subcommand
|
# Add download-data subcommand
|
||||||
download_data_cmd = subparsers.add_parser(
|
download_data_cmd = subparsers.add_parser(
|
||||||
'download-data',
|
'download-data',
|
||||||
help='Download backtesting data.'
|
help='Download backtesting data.',
|
||||||
|
parents=[_common_parser],
|
||||||
)
|
)
|
||||||
download_data_cmd.set_defaults(func=start_download_data)
|
download_data_cmd.set_defaults(func=start_download_data)
|
||||||
self._build_args(optionlist=ARGS_DOWNLOAD_DATA, parser=download_data_cmd)
|
self._build_args(optionlist=ARGS_DOWNLOAD_DATA, parser=download_data_cmd)
|
||||||
|
|
||||||
# Add Plotting subcommand
|
# Add Plotting subcommand
|
||||||
from freqtrade.plot.plot_utils import start_plot_dataframe, start_plot_profit
|
|
||||||
plot_dataframe_cmd = subparsers.add_parser(
|
plot_dataframe_cmd = subparsers.add_parser(
|
||||||
'plot-dataframe',
|
'plot-dataframe',
|
||||||
help='Plot candles with indicators.'
|
help='Plot candles with indicators.',
|
||||||
|
parents=[_common_parser, _strategy_parser],
|
||||||
)
|
)
|
||||||
plot_dataframe_cmd.set_defaults(func=start_plot_dataframe)
|
plot_dataframe_cmd.set_defaults(func=start_plot_dataframe)
|
||||||
self._build_args(optionlist=ARGS_PLOT_DATAFRAME, parser=plot_dataframe_cmd)
|
self._build_args(optionlist=ARGS_PLOT_DATAFRAME, parser=plot_dataframe_cmd)
|
||||||
|
@ -190,7 +215,8 @@ class Arguments:
|
||||||
# Plot profit
|
# Plot profit
|
||||||
plot_profit_cmd = subparsers.add_parser(
|
plot_profit_cmd = subparsers.add_parser(
|
||||||
'plot-profit',
|
'plot-profit',
|
||||||
help='Generate plot showing profits.'
|
help='Generate plot showing profits.',
|
||||||
|
parents=[_common_parser],
|
||||||
)
|
)
|
||||||
plot_profit_cmd.set_defaults(func=start_plot_profit)
|
plot_profit_cmd.set_defaults(func=start_plot_profit)
|
||||||
self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd)
|
self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd)
|
||||||
|
|
|
@ -65,9 +65,8 @@ AVAILABLE_CLI_OPTIONS = {
|
||||||
# Main options
|
# Main options
|
||||||
"strategy": Arg(
|
"strategy": Arg(
|
||||||
'-s', '--strategy',
|
'-s', '--strategy',
|
||||||
help='Specify strategy class name (default: `%(default)s`).',
|
help='Specify strategy class name which will be used by the bot.',
|
||||||
metavar='NAME',
|
metavar='NAME',
|
||||||
default='DefaultStrategy',
|
|
||||||
),
|
),
|
||||||
"strategy_path": Arg(
|
"strategy_path": Arg(
|
||||||
'--strategy-path',
|
'--strategy-path',
|
||||||
|
@ -86,6 +85,11 @@ AVAILABLE_CLI_OPTIONS = {
|
||||||
help='Notify systemd service manager.',
|
help='Notify systemd service manager.',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
),
|
),
|
||||||
|
"dry_run": Arg(
|
||||||
|
'--dry-run',
|
||||||
|
help='Enforce dry-run for trading (removes Exchange secrets and simulates trades).',
|
||||||
|
action='store_true',
|
||||||
|
),
|
||||||
# Optimize common
|
# Optimize common
|
||||||
"ticker_interval": Arg(
|
"ticker_interval": Arg(
|
||||||
'-i', '--ticker-interval',
|
'-i', '--ticker-interval',
|
||||||
|
@ -136,7 +140,7 @@ AVAILABLE_CLI_OPTIONS = {
|
||||||
),
|
),
|
||||||
"exportfilename": Arg(
|
"exportfilename": Arg(
|
||||||
'--export-filename',
|
'--export-filename',
|
||||||
help='Save backtest results to the file with this filename (default: `%(default)s`). '
|
help='Save backtest results to the file with this filename. '
|
||||||
'Requires `--export` to be set as well. '
|
'Requires `--export` to be set as well. '
|
||||||
'Example: `--export-filename=user_data/backtest_results/backtest_today.json`',
|
'Example: `--export-filename=user_data/backtest_results/backtest_today.json`',
|
||||||
metavar='PATH',
|
metavar='PATH',
|
||||||
|
@ -156,14 +160,13 @@ AVAILABLE_CLI_OPTIONS = {
|
||||||
),
|
),
|
||||||
# Hyperopt
|
# Hyperopt
|
||||||
"hyperopt": Arg(
|
"hyperopt": Arg(
|
||||||
'--customhyperopt',
|
'--hyperopt',
|
||||||
help='Specify hyperopt class name (default: `%(default)s`).',
|
help='Specify hyperopt class name which will be used by the bot.',
|
||||||
metavar='NAME',
|
metavar='NAME',
|
||||||
default=constants.DEFAULT_HYPEROPT,
|
|
||||||
),
|
),
|
||||||
"hyperopt_path": Arg(
|
"hyperopt_path": Arg(
|
||||||
'--hyperopt-path',
|
'--hyperopt-path',
|
||||||
help='Specify additional lookup path for Hyperopts and Hyperopt Loss functions.',
|
help='Specify additional lookup path for Hyperopt and Hyperopt Loss functions.',
|
||||||
metavar='PATH',
|
metavar='PATH',
|
||||||
),
|
),
|
||||||
"epochs": Arg(
|
"epochs": Arg(
|
||||||
|
@ -174,7 +177,7 @@ AVAILABLE_CLI_OPTIONS = {
|
||||||
default=constants.HYPEROPT_EPOCH,
|
default=constants.HYPEROPT_EPOCH,
|
||||||
),
|
),
|
||||||
"spaces": Arg(
|
"spaces": Arg(
|
||||||
'-s', '--spaces',
|
'--spaces',
|
||||||
help='Specify which parameters to hyperopt. Space-separated list. '
|
help='Specify which parameters to hyperopt. Space-separated list. '
|
||||||
'Default: `%(default)s`.',
|
'Default: `%(default)s`.',
|
||||||
choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
|
choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
|
||||||
|
|
|
@ -93,7 +93,7 @@ class Configuration:
|
||||||
:return: Configuration dictionary
|
:return: Configuration dictionary
|
||||||
"""
|
"""
|
||||||
# Load all configs
|
# Load all configs
|
||||||
config: Dict[str, Any] = self.load_from_files(self.args["config"])
|
config: Dict[str, Any] = self.load_from_files(self.args.get("config", []))
|
||||||
|
|
||||||
# Keep a copy of the original configuration file
|
# Keep a copy of the original configuration file
|
||||||
config['original_config'] = deepcopy(config)
|
config['original_config'] = deepcopy(config)
|
||||||
|
@ -153,7 +153,7 @@ class Configuration:
|
||||||
self._process_logging_options(config)
|
self._process_logging_options(config)
|
||||||
|
|
||||||
# Set strategy if not specified in config and or if it's non default
|
# Set strategy if not specified in config and or if it's non default
|
||||||
if self.args.get("strategy") != constants.DEFAULT_STRATEGY or not config.get('strategy'):
|
if self.args.get("strategy") or not config.get('strategy'):
|
||||||
config.update({'strategy': self.args.get("strategy")})
|
config.update({'strategy': self.args.get("strategy")})
|
||||||
|
|
||||||
self._args_to_config(config, argname='strategy_path',
|
self._args_to_config(config, argname='strategy_path',
|
||||||
|
@ -171,6 +171,10 @@ class Configuration:
|
||||||
if 'sd_notify' in self.args and self.args["sd_notify"]:
|
if 'sd_notify' in self.args and self.args["sd_notify"]:
|
||||||
config['internals'].update({'sd_notify': True})
|
config['internals'].update({'sd_notify': True})
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='dry_run',
|
||||||
|
logstring='Parameter --dry-run detected, '
|
||||||
|
'overriding dry_run to: {} ...')
|
||||||
|
|
||||||
def _process_datadir_options(self, config: Dict[str, Any]) -> None:
|
def _process_datadir_options(self, config: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Extract information for sys.argv and load directory configurations
|
Extract information for sys.argv and load directory configurations
|
||||||
|
|
|
@ -39,12 +39,12 @@ class TimeRange:
|
||||||
if self.startts:
|
if self.startts:
|
||||||
self.startts = self.startts - seconds
|
self.startts = self.startts - seconds
|
||||||
|
|
||||||
def adjust_start_if_necessary(self, ticker_interval_secs: int, startup_candles: int,
|
def adjust_start_if_necessary(self, timeframe_secs: int, startup_candles: int,
|
||||||
min_date: arrow.Arrow) -> None:
|
min_date: arrow.Arrow) -> None:
|
||||||
"""
|
"""
|
||||||
Adjust startts by <startup_candles> candles.
|
Adjust startts by <startup_candles> candles.
|
||||||
Applies only if no startup-candles have been available.
|
Applies only if no startup-candles have been available.
|
||||||
:param ticker_interval_secs: Ticker interval in seconds e.g. `timeframe_to_seconds('5m')`
|
:param timeframe_secs: Ticker timeframe in seconds e.g. `timeframe_to_seconds('5m')`
|
||||||
:param startup_candles: Number of candles to move start-date forward
|
:param startup_candles: Number of candles to move start-date forward
|
||||||
:param min_date: Minimum data date loaded. Key kriterium to decide if start-time
|
:param min_date: Minimum data date loaded. Key kriterium to decide if start-time
|
||||||
has to be moved
|
has to be moved
|
||||||
|
@ -55,7 +55,7 @@ class TimeRange:
|
||||||
# If no startts was defined, or backtest-data starts at the defined backtest-date
|
# If no startts was defined, or backtest-data starts at the defined backtest-date
|
||||||
logger.warning("Moving start-date by %s candles to account for startup time.",
|
logger.warning("Moving start-date by %s candles to account for startup time.",
|
||||||
startup_candles)
|
startup_candles)
|
||||||
self.startts = (min_date.timestamp + ticker_interval_secs * startup_candles)
|
self.startts = (min_date.timestamp + timeframe_secs * startup_candles)
|
||||||
self.starttype = 'date'
|
self.starttype = 'date'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
@ -9,8 +9,6 @@ PROCESS_THROTTLE_SECS = 5 # sec
|
||||||
DEFAULT_TICKER_INTERVAL = 5 # min
|
DEFAULT_TICKER_INTERVAL = 5 # min
|
||||||
HYPEROPT_EPOCH = 100 # epochs
|
HYPEROPT_EPOCH = 100 # epochs
|
||||||
RETRY_TIMEOUT = 30 # sec
|
RETRY_TIMEOUT = 30 # sec
|
||||||
DEFAULT_STRATEGY = 'DefaultStrategy'
|
|
||||||
DEFAULT_HYPEROPT = 'DefaultHyperOpt'
|
|
||||||
DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss'
|
DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss'
|
||||||
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
|
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
|
||||||
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
|
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
|
||||||
|
@ -24,7 +22,7 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList']
|
||||||
DRY_RUN_WALLET = 999.9
|
DRY_RUN_WALLET = 999.9
|
||||||
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
|
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
|
||||||
|
|
||||||
TICKER_INTERVALS = [
|
TIMEFRAMES = [
|
||||||
'1m', '3m', '5m', '15m', '30m',
|
'1m', '3m', '5m', '15m', '30m',
|
||||||
'1h', '2h', '4h', '6h', '8h', '12h',
|
'1h', '2h', '4h', '6h', '8h', '12h',
|
||||||
'1d', '3d', '1w',
|
'1d', '3d', '1w',
|
||||||
|
@ -57,7 +55,7 @@ CONF_SCHEMA = {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
'max_open_trades': {'type': 'integer', 'minimum': -1},
|
'max_open_trades': {'type': 'integer', 'minimum': -1},
|
||||||
'ticker_interval': {'type': 'string', 'enum': TICKER_INTERVALS},
|
'ticker_interval': {'type': 'string', 'enum': TIMEFRAMES},
|
||||||
'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']},
|
'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']},
|
||||||
'stake_amount': {
|
'stake_amount': {
|
||||||
"type": ["number", "string"],
|
"type": ["number", "string"],
|
||||||
|
|
|
@ -178,9 +178,9 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
|
||||||
:return: Returns df with one additional column, col_name, containing the cumulative profit.
|
:return: Returns df with one additional column, col_name, containing the cumulative profit.
|
||||||
"""
|
"""
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
ticker_minutes = timeframe_to_minutes(timeframe)
|
timeframe_minutes = timeframe_to_minutes(timeframe)
|
||||||
# Resample to ticker_interval to make sure trades match candles
|
# Resample to timeframe to make sure trades match candles
|
||||||
_trades_sum = trades.resample(f'{ticker_minutes}min', on='close_time')[['profitperc']].sum()
|
_trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_time')[['profitperc']].sum()
|
||||||
df.loc[:, col_name] = _trades_sum.cumsum()
|
df.loc[:, col_name] = _trades_sum.cumsum()
|
||||||
# Set first value to 0
|
# Set first value to 0
|
||||||
df.loc[df.iloc[0].name, col_name] = 0
|
df.loc[df.iloc[0].name, col_name] = 0
|
||||||
|
|
|
@ -10,13 +10,13 @@ from pandas import DataFrame, to_datetime
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def parse_ticker_dataframe(ticker: list, ticker_interval: str, pair: str, *,
|
def parse_ticker_dataframe(ticker: list, timeframe: str, pair: str, *,
|
||||||
fill_missing: bool = True,
|
fill_missing: bool = True,
|
||||||
drop_incomplete: bool = True) -> DataFrame:
|
drop_incomplete: bool = True) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Converts a ticker-list (format ccxt.fetch_ohlcv) to a Dataframe
|
Converts a ticker-list (format ccxt.fetch_ohlcv) to a Dataframe
|
||||||
:param ticker: ticker list, as returned by exchange.async_get_candle_history
|
:param ticker: ticker list, as returned by exchange.async_get_candle_history
|
||||||
:param ticker_interval: ticker_interval (e.g. 5m). Used to fill up eventual missing data
|
:param timeframe: timeframe (e.g. 5m). Used to fill up eventual missing data
|
||||||
:param pair: Pair this data is for (used to warn if fillup was necessary)
|
:param pair: Pair this data is for (used to warn if fillup was necessary)
|
||||||
:param fill_missing: fill up missing candles with 0 candles
|
:param fill_missing: fill up missing candles with 0 candles
|
||||||
(see ohlcv_fill_up_missing_data for details)
|
(see ohlcv_fill_up_missing_data for details)
|
||||||
|
@ -52,12 +52,12 @@ def parse_ticker_dataframe(ticker: list, ticker_interval: str, pair: str, *,
|
||||||
logger.debug('Dropping last candle')
|
logger.debug('Dropping last candle')
|
||||||
|
|
||||||
if fill_missing:
|
if fill_missing:
|
||||||
return ohlcv_fill_up_missing_data(frame, ticker_interval, pair)
|
return ohlcv_fill_up_missing_data(frame, timeframe, pair)
|
||||||
else:
|
else:
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
|
|
||||||
def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str, pair: str) -> DataFrame:
|
def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Fills up missing data with 0 volume rows,
|
Fills up missing data with 0 volume rows,
|
||||||
using the previous close as price for "open", "high" "low" and "close", volume is set to 0
|
using the previous close as price for "open", "high" "low" and "close", volume is set to 0
|
||||||
|
@ -72,7 +72,7 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str, pair:
|
||||||
'close': 'last',
|
'close': 'last',
|
||||||
'volume': 'sum'
|
'volume': 'sum'
|
||||||
}
|
}
|
||||||
ticker_minutes = timeframe_to_minutes(ticker_interval)
|
ticker_minutes = timeframe_to_minutes(timeframe)
|
||||||
# Resample to create "NAN" values
|
# Resample to create "NAN" values
|
||||||
df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict)
|
df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict)
|
||||||
|
|
||||||
|
|
|
@ -37,52 +37,53 @@ class DataProvider:
|
||||||
@property
|
@property
|
||||||
def available_pairs(self) -> List[Tuple[str, str]]:
|
def available_pairs(self) -> List[Tuple[str, str]]:
|
||||||
"""
|
"""
|
||||||
Return a list of tuples containing pair, ticker_interval for which data is currently cached.
|
Return a list of tuples containing (pair, timeframe) for which data is currently cached.
|
||||||
Should be whitelist + open trades.
|
Should be whitelist + open trades.
|
||||||
"""
|
"""
|
||||||
return list(self._exchange._klines.keys())
|
return list(self._exchange._klines.keys())
|
||||||
|
|
||||||
def ohlcv(self, pair: str, ticker_interval: str = None, copy: bool = True) -> DataFrame:
|
def ohlcv(self, pair: str, timeframe: str = None, copy: bool = True) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Get ohlcv data for the given pair as DataFrame
|
Get ohlcv data for the given pair as DataFrame
|
||||||
Please use the `available_pairs` method to verify which pairs are currently cached.
|
Please use the `available_pairs` method to verify which pairs are currently cached.
|
||||||
:param pair: pair to get the data for
|
:param pair: pair to get the data for
|
||||||
:param ticker_interval: ticker interval to get data for
|
:param timeframe: Ticker timeframe to get data for
|
||||||
:param copy: copy dataframe before returning if True.
|
:param copy: copy dataframe before returning if True.
|
||||||
Use False only for read-only operations (where the dataframe is not modified)
|
Use False only for read-only operations (where the dataframe is not modified)
|
||||||
"""
|
"""
|
||||||
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
||||||
return self._exchange.klines((pair, ticker_interval or self._config['ticker_interval']),
|
return self._exchange.klines((pair, timeframe or self._config['ticker_interval']),
|
||||||
copy=copy)
|
copy=copy)
|
||||||
else:
|
else:
|
||||||
return DataFrame()
|
return DataFrame()
|
||||||
|
|
||||||
def historic_ohlcv(self, pair: str, ticker_interval: str = None) -> DataFrame:
|
def historic_ohlcv(self, pair: str, timeframe: str = None) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Get stored historic ohlcv data
|
Get stored historic ohlcv data
|
||||||
:param pair: pair to get the data for
|
:param pair: pair to get the data for
|
||||||
:param ticker_interval: ticker interval to get data for
|
:param timeframe: timeframe to get data for
|
||||||
"""
|
"""
|
||||||
return load_pair_history(pair=pair,
|
return load_pair_history(pair=pair,
|
||||||
ticker_interval=ticker_interval or self._config['ticker_interval'],
|
timeframe=timeframe or self._config['ticker_interval'],
|
||||||
datadir=Path(self._config['datadir'])
|
datadir=Path(self._config['datadir'])
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_pair_dataframe(self, pair: str, ticker_interval: str = None) -> DataFrame:
|
def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Return pair ohlcv data, either live or cached historical -- depending
|
Return pair ohlcv data, either live or cached historical -- depending
|
||||||
on the runmode.
|
on the runmode.
|
||||||
:param pair: pair to get the data for
|
:param pair: pair to get the data for
|
||||||
:param ticker_interval: ticker interval to get data for
|
:param timeframe: timeframe to get data for
|
||||||
|
:return: Dataframe for this pair
|
||||||
"""
|
"""
|
||||||
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
|
||||||
# Get live ohlcv data.
|
# Get live ohlcv data.
|
||||||
data = self.ohlcv(pair=pair, ticker_interval=ticker_interval)
|
data = self.ohlcv(pair=pair, timeframe=timeframe)
|
||||||
else:
|
else:
|
||||||
# Get historic ohlcv data (cached on disk).
|
# Get historic ohlcv data (cached on disk).
|
||||||
data = self.historic_ohlcv(pair=pair, ticker_interval=ticker_interval)
|
data = self.historic_ohlcv(pair=pair, timeframe=timeframe)
|
||||||
if len(data) == 0:
|
if len(data) == 0:
|
||||||
logger.warning(f"No data found for ({pair}, {ticker_interval}).")
|
logger.warning(f"No data found for ({pair}, {timeframe}).")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def market(self, pair: str) -> Optional[Dict[str, Any]]:
|
def market(self, pair: str) -> Optional[Dict[str, Any]]:
|
||||||
|
|
|
@ -50,26 +50,30 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
|
||||||
return tickerlist[start_index:stop_index]
|
return tickerlist[start_index:stop_index]
|
||||||
|
|
||||||
|
|
||||||
def trim_dataframe(df: DataFrame, timerange: TimeRange) -> DataFrame:
|
def trim_dataframe(df: DataFrame, timerange: TimeRange, df_date_col: str = 'date') -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Trim dataframe based on given timerange
|
Trim dataframe based on given timerange
|
||||||
|
:param df: Dataframe to trim
|
||||||
|
:param timerange: timerange (use start and end date if available)
|
||||||
|
:param: df_date_col: Column in the dataframe to use as Date column
|
||||||
|
:return: trimmed dataframe
|
||||||
"""
|
"""
|
||||||
if timerange.starttype == 'date':
|
if timerange.starttype == 'date':
|
||||||
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
|
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
|
||||||
df = df.loc[df['date'] >= start, :]
|
df = df.loc[df[df_date_col] >= start, :]
|
||||||
if timerange.stoptype == 'date':
|
if timerange.stoptype == 'date':
|
||||||
stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
|
stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
|
||||||
df = df.loc[df['date'] <= stop, :]
|
df = df.loc[df[df_date_col] <= stop, :]
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
def load_tickerdata_file(datadir: Path, pair: str, ticker_interval: str,
|
def load_tickerdata_file(datadir: Path, pair: str, timeframe: str,
|
||||||
timerange: Optional[TimeRange] = None) -> Optional[list]:
|
timerange: Optional[TimeRange] = None) -> Optional[list]:
|
||||||
"""
|
"""
|
||||||
Load a pair from file, either .json.gz or .json
|
Load a pair from file, either .json.gz or .json
|
||||||
:return: tickerlist or None if unsuccessful
|
:return: tickerlist or None if unsuccessful
|
||||||
"""
|
"""
|
||||||
filename = pair_data_filename(datadir, pair, ticker_interval)
|
filename = pair_data_filename(datadir, pair, timeframe)
|
||||||
pairdata = misc.file_load_json(filename)
|
pairdata = misc.file_load_json(filename)
|
||||||
if not pairdata:
|
if not pairdata:
|
||||||
return []
|
return []
|
||||||
|
@ -80,11 +84,11 @@ def load_tickerdata_file(datadir: Path, pair: str, ticker_interval: str,
|
||||||
|
|
||||||
|
|
||||||
def store_tickerdata_file(datadir: Path, pair: str,
|
def store_tickerdata_file(datadir: Path, pair: str,
|
||||||
ticker_interval: str, data: list, is_zip: bool = False):
|
timeframe: str, data: list, is_zip: bool = False):
|
||||||
"""
|
"""
|
||||||
Stores tickerdata to file
|
Stores tickerdata to file
|
||||||
"""
|
"""
|
||||||
filename = pair_data_filename(datadir, pair, ticker_interval)
|
filename = pair_data_filename(datadir, pair, timeframe)
|
||||||
misc.file_dump_json(filename, data, is_zip=is_zip)
|
misc.file_dump_json(filename, data, is_zip=is_zip)
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,7 +125,7 @@ def _validate_pairdata(pair, pairdata, timerange: TimeRange):
|
||||||
|
|
||||||
|
|
||||||
def load_pair_history(pair: str,
|
def load_pair_history(pair: str,
|
||||||
ticker_interval: str,
|
timeframe: str,
|
||||||
datadir: Path,
|
datadir: Path,
|
||||||
timerange: Optional[TimeRange] = None,
|
timerange: Optional[TimeRange] = None,
|
||||||
refresh_pairs: bool = False,
|
refresh_pairs: bool = False,
|
||||||
|
@ -133,7 +137,7 @@ def load_pair_history(pair: str,
|
||||||
"""
|
"""
|
||||||
Loads cached ticker history for the given pair.
|
Loads cached ticker history for the given pair.
|
||||||
:param pair: Pair to load data for
|
:param pair: Pair to load data for
|
||||||
:param ticker_interval: Ticker-interval (e.g. "5m")
|
:param timeframe: Ticker timeframe (e.g. "5m")
|
||||||
:param datadir: Path to the data storage location.
|
:param datadir: Path to the data storage location.
|
||||||
:param timerange: Limit data to be loaded to this timerange
|
:param timerange: Limit data to be loaded to this timerange
|
||||||
:param refresh_pairs: Refresh pairs from exchange.
|
:param refresh_pairs: Refresh pairs from exchange.
|
||||||
|
@ -147,34 +151,34 @@ def load_pair_history(pair: str,
|
||||||
|
|
||||||
timerange_startup = deepcopy(timerange)
|
timerange_startup = deepcopy(timerange)
|
||||||
if startup_candles > 0 and timerange_startup:
|
if startup_candles > 0 and timerange_startup:
|
||||||
timerange_startup.subtract_start(timeframe_to_seconds(ticker_interval) * startup_candles)
|
timerange_startup.subtract_start(timeframe_to_seconds(timeframe) * startup_candles)
|
||||||
|
|
||||||
# The user forced the refresh of pairs
|
# The user forced the refresh of pairs
|
||||||
if refresh_pairs:
|
if refresh_pairs:
|
||||||
download_pair_history(datadir=datadir,
|
download_pair_history(datadir=datadir,
|
||||||
exchange=exchange,
|
exchange=exchange,
|
||||||
pair=pair,
|
pair=pair,
|
||||||
ticker_interval=ticker_interval,
|
timeframe=timeframe,
|
||||||
timerange=timerange)
|
timerange=timerange)
|
||||||
|
|
||||||
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange_startup)
|
pairdata = load_tickerdata_file(datadir, pair, timeframe, timerange=timerange_startup)
|
||||||
|
|
||||||
if pairdata:
|
if pairdata:
|
||||||
if timerange_startup:
|
if timerange_startup:
|
||||||
_validate_pairdata(pair, pairdata, timerange_startup)
|
_validate_pairdata(pair, pairdata, timerange_startup)
|
||||||
return parse_ticker_dataframe(pairdata, ticker_interval, pair=pair,
|
return parse_ticker_dataframe(pairdata, timeframe, pair=pair,
|
||||||
fill_missing=fill_up_missing,
|
fill_missing=fill_up_missing,
|
||||||
drop_incomplete=drop_incomplete)
|
drop_incomplete=drop_incomplete)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f'No history data for pair: "{pair}", interval: {ticker_interval}. '
|
f'No history data for pair: "{pair}", timeframe: {timeframe}. '
|
||||||
'Use `freqtrade download-data` to download the data'
|
'Use `freqtrade download-data` to download the data'
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_data(datadir: Path,
|
def load_data(datadir: Path,
|
||||||
ticker_interval: str,
|
timeframe: str,
|
||||||
pairs: List[str],
|
pairs: List[str],
|
||||||
refresh_pairs: bool = False,
|
refresh_pairs: bool = False,
|
||||||
exchange: Optional[Exchange] = None,
|
exchange: Optional[Exchange] = None,
|
||||||
|
@ -186,7 +190,7 @@ def load_data(datadir: Path,
|
||||||
"""
|
"""
|
||||||
Loads ticker history data for a list of pairs
|
Loads ticker history data for a list of pairs
|
||||||
:param datadir: Path to the data storage location.
|
:param datadir: Path to the data storage location.
|
||||||
:param ticker_interval: Ticker-interval (e.g. "5m")
|
:param timeframe: Ticker Timeframe (e.g. "5m")
|
||||||
:param pairs: List of pairs to load
|
:param pairs: List of pairs to load
|
||||||
:param refresh_pairs: Refresh pairs from exchange.
|
:param refresh_pairs: Refresh pairs from exchange.
|
||||||
(Note: Requires exchange to be passed as well.)
|
(Note: Requires exchange to be passed as well.)
|
||||||
|
@ -206,7 +210,7 @@ def load_data(datadir: Path,
|
||||||
logger.info(f'Using indicator startup period: {startup_candles} ...')
|
logger.info(f'Using indicator startup period: {startup_candles} ...')
|
||||||
|
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
hist = load_pair_history(pair=pair, ticker_interval=ticker_interval,
|
hist = load_pair_history(pair=pair, timeframe=timeframe,
|
||||||
datadir=datadir, timerange=timerange,
|
datadir=datadir, timerange=timerange,
|
||||||
refresh_pairs=refresh_pairs,
|
refresh_pairs=refresh_pairs,
|
||||||
exchange=exchange,
|
exchange=exchange,
|
||||||
|
@ -220,9 +224,9 @@ def load_data(datadir: Path,
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def pair_data_filename(datadir: Path, pair: str, ticker_interval: str) -> Path:
|
def pair_data_filename(datadir: Path, pair: str, timeframe: str) -> Path:
|
||||||
pair_s = pair.replace("/", "_")
|
pair_s = pair.replace("/", "_")
|
||||||
filename = datadir.joinpath(f'{pair_s}-{ticker_interval}.json')
|
filename = datadir.joinpath(f'{pair_s}-{timeframe}.json')
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
@ -232,7 +236,7 @@ def pair_trades_filename(datadir: Path, pair: str) -> Path:
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def _load_cached_data_for_updating(datadir: Path, pair: str, ticker_interval: str,
|
def _load_cached_data_for_updating(datadir: Path, pair: str, timeframe: str,
|
||||||
timerange: Optional[TimeRange]) -> Tuple[List[Any],
|
timerange: Optional[TimeRange]) -> Tuple[List[Any],
|
||||||
Optional[int]]:
|
Optional[int]]:
|
||||||
"""
|
"""
|
||||||
|
@ -250,12 +254,12 @@ def _load_cached_data_for_updating(datadir: Path, pair: str, ticker_interval: st
|
||||||
if timerange.starttype == 'date':
|
if timerange.starttype == 'date':
|
||||||
since_ms = timerange.startts * 1000
|
since_ms = timerange.startts * 1000
|
||||||
elif timerange.stoptype == 'line':
|
elif timerange.stoptype == 'line':
|
||||||
num_minutes = timerange.stopts * timeframe_to_minutes(ticker_interval)
|
num_minutes = timerange.stopts * timeframe_to_minutes(timeframe)
|
||||||
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
|
||||||
# Intentionally don't pass timerange in - since we need to load the full dataset.
|
# Intentionally don't pass timerange in - since we need to load the full dataset.
|
||||||
data = load_tickerdata_file(datadir, pair, ticker_interval)
|
data = load_tickerdata_file(datadir, pair, timeframe)
|
||||||
# remove the last item, could be incomplete candle
|
# remove the last item, could be incomplete candle
|
||||||
if data:
|
if data:
|
||||||
data.pop()
|
data.pop()
|
||||||
|
@ -276,18 +280,18 @@ def _load_cached_data_for_updating(datadir: Path, pair: str, ticker_interval: st
|
||||||
def download_pair_history(datadir: Path,
|
def download_pair_history(datadir: Path,
|
||||||
exchange: Optional[Exchange],
|
exchange: Optional[Exchange],
|
||||||
pair: str,
|
pair: str,
|
||||||
ticker_interval: str = '5m',
|
timeframe: str = '5m',
|
||||||
timerange: Optional[TimeRange] = None) -> bool:
|
timerange: Optional[TimeRange] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Download the latest ticker intervals from the exchange for the pair passed in parameters
|
Download latest candles from the exchange for the pair and timeframe passed in parameters
|
||||||
The data is downloaded starting from the last correct ticker interval data that
|
The data is downloaded starting from the last correct data that
|
||||||
exists in a cache. If timerange starts earlier than the data in the cache,
|
exists in a cache. If timerange starts earlier than the data in the cache,
|
||||||
the full data will be redownloaded
|
the full data will be redownloaded
|
||||||
|
|
||||||
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
|
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
|
||||||
|
|
||||||
:param pair: pair to download
|
:param pair: pair to download
|
||||||
:param ticker_interval: ticker interval
|
:param timeframe: Ticker Timeframe (e.g 5m)
|
||||||
:param timerange: range of time to download
|
:param timerange: range of time to download
|
||||||
:return: bool with success state
|
:return: bool with success state
|
||||||
"""
|
"""
|
||||||
|
@ -298,17 +302,17 @@ def download_pair_history(datadir: Path,
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(
|
logger.info(
|
||||||
f'Download history data for pair: "{pair}", interval: {ticker_interval} '
|
f'Download history data for pair: "{pair}", timeframe: {timeframe} '
|
||||||
f'and store in {datadir}.'
|
f'and store in {datadir}.'
|
||||||
)
|
)
|
||||||
|
|
||||||
data, since_ms = _load_cached_data_for_updating(datadir, pair, ticker_interval, timerange)
|
data, since_ms = _load_cached_data_for_updating(datadir, pair, timeframe, timerange)
|
||||||
|
|
||||||
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
|
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
|
||||||
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
|
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
|
||||||
|
|
||||||
# Default since_ms to 30 days if nothing is given
|
# Default since_ms to 30 days if nothing is given
|
||||||
new_data = exchange.get_historic_ohlcv(pair=pair, ticker_interval=ticker_interval,
|
new_data = exchange.get_historic_ohlcv(pair=pair, timeframe=timeframe,
|
||||||
since_ms=since_ms if since_ms
|
since_ms=since_ms if since_ms
|
||||||
else
|
else
|
||||||
int(arrow.utcnow().shift(
|
int(arrow.utcnow().shift(
|
||||||
|
@ -318,12 +322,12 @@ def download_pair_history(datadir: Path,
|
||||||
logger.debug("New Start: %s", misc.format_ms_time(data[0][0]))
|
logger.debug("New Start: %s", misc.format_ms_time(data[0][0]))
|
||||||
logger.debug("New End: %s", misc.format_ms_time(data[-1][0]))
|
logger.debug("New End: %s", misc.format_ms_time(data[-1][0]))
|
||||||
|
|
||||||
store_tickerdata_file(datadir, pair, ticker_interval, data=data)
|
store_tickerdata_file(datadir, pair, timeframe, data=data)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f'Failed to download history data for pair: "{pair}", interval: {ticker_interval}. '
|
f'Failed to download history data for pair: "{pair}", timeframe: {timeframe}. '
|
||||||
f'Error: {e}'
|
f'Error: {e}'
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
@ -343,17 +347,17 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
|
||||||
pairs_not_available.append(pair)
|
pairs_not_available.append(pair)
|
||||||
logger.info(f"Skipping pair {pair}...")
|
logger.info(f"Skipping pair {pair}...")
|
||||||
continue
|
continue
|
||||||
for ticker_interval in timeframes:
|
for timeframe in timeframes:
|
||||||
|
|
||||||
dl_file = pair_data_filename(dl_path, pair, ticker_interval)
|
dl_file = pair_data_filename(dl_path, pair, timeframe)
|
||||||
if erase and dl_file.exists():
|
if erase and dl_file.exists():
|
||||||
logger.info(
|
logger.info(
|
||||||
f'Deleting existing data for pair {pair}, interval {ticker_interval}.')
|
f'Deleting existing data for pair {pair}, interval {timeframe}.')
|
||||||
dl_file.unlink()
|
dl_file.unlink()
|
||||||
|
|
||||||
logger.info(f'Downloading pair {pair}, interval {ticker_interval}.')
|
logger.info(f'Downloading pair {pair}, interval {timeframe}.')
|
||||||
download_pair_history(datadir=dl_path, exchange=exchange,
|
download_pair_history(datadir=dl_path, exchange=exchange,
|
||||||
pair=pair, ticker_interval=str(ticker_interval),
|
pair=pair, timeframe=str(timeframe),
|
||||||
timerange=timerange)
|
timerange=timerange)
|
||||||
return pairs_not_available
|
return pairs_not_available
|
||||||
|
|
||||||
|
@ -459,7 +463,7 @@ def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]
|
||||||
|
|
||||||
|
|
||||||
def validate_backtest_data(data: DataFrame, pair: str, min_date: datetime,
|
def validate_backtest_data(data: DataFrame, pair: str, min_date: datetime,
|
||||||
max_date: datetime, ticker_interval_mins: int) -> bool:
|
max_date: datetime, timeframe_mins: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Validates preprocessed backtesting data for missing values and shows warnings about it that.
|
Validates preprocessed backtesting data for missing values and shows warnings about it that.
|
||||||
|
|
||||||
|
@ -467,10 +471,10 @@ def validate_backtest_data(data: DataFrame, pair: str, min_date: datetime,
|
||||||
:param pair: pair used for log output.
|
:param pair: pair used for log output.
|
||||||
:param min_date: start-date of the data
|
:param min_date: start-date of the data
|
||||||
:param max_date: end-date of the data
|
:param max_date: end-date of the data
|
||||||
:param ticker_interval_mins: ticker interval in minutes
|
:param timeframe_mins: ticker Timeframe in minutes
|
||||||
"""
|
"""
|
||||||
# total difference in minutes / interval-minutes
|
# total difference in minutes / timeframe-minutes
|
||||||
expected_frames = int((max_date - min_date).total_seconds() // 60 // ticker_interval_mins)
|
expected_frames = int((max_date - min_date).total_seconds() // 60 // timeframe_mins)
|
||||||
found_missing = False
|
found_missing = False
|
||||||
dflen = len(data)
|
dflen = len(data)
|
||||||
if dflen < expected_frames:
|
if dflen < expected_frames:
|
||||||
|
|
|
@ -97,7 +97,7 @@ class Edge:
|
||||||
data = history.load_data(
|
data = history.load_data(
|
||||||
datadir=Path(self.config['datadir']),
|
datadir=Path(self.config['datadir']),
|
||||||
pairs=pairs,
|
pairs=pairs,
|
||||||
ticker_interval=self.strategy.ticker_interval,
|
timeframe=self.strategy.ticker_interval,
|
||||||
refresh_pairs=self._refresh_pairs,
|
refresh_pairs=self._refresh_pairs,
|
||||||
exchange=self.exchange,
|
exchange=self.exchange,
|
||||||
timerange=self._timerange,
|
timerange=self._timerange,
|
||||||
|
|
|
@ -15,3 +15,4 @@ from freqtrade.exchange.exchange import (market_is_active, # noqa: F401
|
||||||
symbol_is_pair)
|
symbol_is_pair)
|
||||||
from freqtrade.exchange.kraken import Kraken # noqa: F401
|
from freqtrade.exchange.kraken import Kraken # noqa: F401
|
||||||
from freqtrade.exchange.binance import Binance # noqa: F401
|
from freqtrade.exchange.binance import Binance # noqa: F401
|
||||||
|
from freqtrade.exchange.bibox import Bibox # noqa: F401
|
||||||
|
|
22
freqtrade/exchange/bibox.py
Normal file
22
freqtrade/exchange/bibox.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
""" Bibox exchange subclass """
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from freqtrade.exchange import Exchange
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Bibox(Exchange):
|
||||||
|
"""
|
||||||
|
Bibox exchange class. Contains adjustments needed for Freqtrade to work
|
||||||
|
with this exchange.
|
||||||
|
|
||||||
|
Please note that this exchange is not included in the list of exchanges
|
||||||
|
officially supported by the Freqtrade development team. So some features
|
||||||
|
may still not work as expected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# fetchCurrencies API point requires authentication for Bibox,
|
||||||
|
# so switch it off for Freqtrade load_markets()
|
||||||
|
_ccxt_config: Dict = {"has": {"fetchCurrencies": False}}
|
|
@ -30,6 +30,9 @@ class Exchange:
|
||||||
|
|
||||||
_config: Dict = {}
|
_config: Dict = {}
|
||||||
|
|
||||||
|
# Parameters to add directly to ccxt sync/async initialization.
|
||||||
|
_ccxt_config: Dict = {}
|
||||||
|
|
||||||
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
|
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
|
||||||
_params: Dict = {}
|
_params: Dict = {}
|
||||||
|
|
||||||
|
@ -91,10 +94,17 @@ class Exchange:
|
||||||
self._trades_pagination_arg = self._ft_has['trades_pagination_arg']
|
self._trades_pagination_arg = self._ft_has['trades_pagination_arg']
|
||||||
|
|
||||||
# Initialize ccxt objects
|
# Initialize ccxt objects
|
||||||
|
ccxt_config = self._ccxt_config.copy()
|
||||||
|
ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}),
|
||||||
|
ccxt_config)
|
||||||
self._api = self._init_ccxt(
|
self._api = self._init_ccxt(
|
||||||
exchange_config, ccxt_kwargs=exchange_config.get('ccxt_config'))
|
exchange_config, ccxt_kwargs=ccxt_config)
|
||||||
|
|
||||||
|
ccxt_async_config = self._ccxt_config.copy()
|
||||||
|
ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_async_config', {}),
|
||||||
|
ccxt_async_config)
|
||||||
self._api_async = self._init_ccxt(
|
self._api_async = self._init_ccxt(
|
||||||
exchange_config, ccxt_async, ccxt_kwargs=exchange_config.get('ccxt_async_config'))
|
exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config)
|
||||||
|
|
||||||
logger.info('Using Exchange "%s"', self.name)
|
logger.info('Using Exchange "%s"', self.name)
|
||||||
|
|
||||||
|
@ -536,40 +546,40 @@ class Exchange:
|
||||||
logger.info("returning cached ticker-data for %s", pair)
|
logger.info("returning cached ticker-data for %s", pair)
|
||||||
return self._cached_ticker[pair]
|
return self._cached_ticker[pair]
|
||||||
|
|
||||||
def get_historic_ohlcv(self, pair: str, ticker_interval: str,
|
def get_historic_ohlcv(self, pair: str, timeframe: str,
|
||||||
since_ms: int) -> List:
|
since_ms: int) -> List:
|
||||||
"""
|
"""
|
||||||
Gets candle history using asyncio and returns the list of candles.
|
Gets candle history using asyncio and returns the list of candles.
|
||||||
Handles all async doing.
|
Handles all async doing.
|
||||||
Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call.
|
Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call.
|
||||||
:param pair: Pair to download
|
:param pair: Pair to download
|
||||||
:param ticker_interval: Interval to get
|
:param timeframe: Ticker Timeframe to get
|
||||||
:param since_ms: Timestamp in milliseconds to get history from
|
:param since_ms: Timestamp in milliseconds to get history from
|
||||||
:returns List of tickers
|
:returns List of tickers
|
||||||
"""
|
"""
|
||||||
return asyncio.get_event_loop().run_until_complete(
|
return asyncio.get_event_loop().run_until_complete(
|
||||||
self._async_get_historic_ohlcv(pair=pair, ticker_interval=ticker_interval,
|
self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe,
|
||||||
since_ms=since_ms))
|
since_ms=since_ms))
|
||||||
|
|
||||||
async def _async_get_historic_ohlcv(self, pair: str,
|
async def _async_get_historic_ohlcv(self, pair: str,
|
||||||
ticker_interval: str,
|
timeframe: str,
|
||||||
since_ms: int) -> List:
|
since_ms: int) -> List:
|
||||||
|
|
||||||
one_call = timeframe_to_msecs(ticker_interval) * self._ohlcv_candle_limit
|
one_call = timeframe_to_msecs(timeframe) * self._ohlcv_candle_limit
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"one_call: %s msecs (%s)",
|
"one_call: %s msecs (%s)",
|
||||||
one_call,
|
one_call,
|
||||||
arrow.utcnow().shift(seconds=one_call // 1000).humanize(only_distance=True)
|
arrow.utcnow().shift(seconds=one_call // 1000).humanize(only_distance=True)
|
||||||
)
|
)
|
||||||
input_coroutines = [self._async_get_candle_history(
|
input_coroutines = [self._async_get_candle_history(
|
||||||
pair, ticker_interval, since) for since in
|
pair, timeframe, since) for since in
|
||||||
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
|
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
|
||||||
|
|
||||||
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
|
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
|
||||||
|
|
||||||
# Combine tickers
|
# Combine tickers
|
||||||
data: List = []
|
data: List = []
|
||||||
for p, ticker_interval, ticker in tickers:
|
for p, timeframe, ticker in tickers:
|
||||||
if p == pair:
|
if p == pair:
|
||||||
data.extend(ticker)
|
data.extend(ticker)
|
||||||
# Sort data again after extending the result - above calls return in "async order"
|
# Sort data again after extending the result - above calls return in "async order"
|
||||||
|
@ -589,14 +599,14 @@ class Exchange:
|
||||||
input_coroutines = []
|
input_coroutines = []
|
||||||
|
|
||||||
# Gather coroutines to run
|
# Gather coroutines to run
|
||||||
for pair, ticker_interval in set(pair_list):
|
for pair, timeframe in set(pair_list):
|
||||||
if (not ((pair, ticker_interval) in self._klines)
|
if (not ((pair, timeframe) in self._klines)
|
||||||
or self._now_is_time_to_refresh(pair, ticker_interval)):
|
or self._now_is_time_to_refresh(pair, timeframe)):
|
||||||
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
|
input_coroutines.append(self._async_get_candle_history(pair, timeframe))
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Using cached ohlcv data for pair %s, interval %s ...",
|
"Using cached ohlcv data for pair %s, timeframe %s ...",
|
||||||
pair, ticker_interval
|
pair, timeframe
|
||||||
)
|
)
|
||||||
|
|
||||||
tickers = asyncio.get_event_loop().run_until_complete(
|
tickers = asyncio.get_event_loop().run_until_complete(
|
||||||
|
@ -608,40 +618,40 @@ class Exchange:
|
||||||
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
|
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
|
||||||
continue
|
continue
|
||||||
pair = res[0]
|
pair = res[0]
|
||||||
ticker_interval = res[1]
|
timeframe = res[1]
|
||||||
ticks = res[2]
|
ticks = res[2]
|
||||||
# keeping last candle time as last refreshed time of the pair
|
# keeping last candle time as last refreshed time of the pair
|
||||||
if ticks:
|
if ticks:
|
||||||
self._pairs_last_refresh_time[(pair, ticker_interval)] = ticks[-1][0] // 1000
|
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
|
||||||
# keeping parsed dataframe in cache
|
# keeping parsed dataframe in cache
|
||||||
self._klines[(pair, ticker_interval)] = parse_ticker_dataframe(
|
self._klines[(pair, timeframe)] = parse_ticker_dataframe(
|
||||||
ticks, ticker_interval, pair=pair, fill_missing=True,
|
ticks, timeframe, pair=pair, fill_missing=True,
|
||||||
drop_incomplete=self._ohlcv_partial_candle)
|
drop_incomplete=self._ohlcv_partial_candle)
|
||||||
return tickers
|
return tickers
|
||||||
|
|
||||||
def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool:
|
def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool:
|
||||||
# Calculating ticker interval in seconds
|
# Calculating ticker interval in seconds
|
||||||
interval_in_sec = timeframe_to_seconds(ticker_interval)
|
interval_in_sec = timeframe_to_seconds(timeframe)
|
||||||
|
|
||||||
return not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0)
|
return not ((self._pairs_last_refresh_time.get((pair, timeframe), 0)
|
||||||
+ interval_in_sec) >= arrow.utcnow().timestamp)
|
+ interval_in_sec) >= arrow.utcnow().timestamp)
|
||||||
|
|
||||||
@retrier_async
|
@retrier_async
|
||||||
async def _async_get_candle_history(self, pair: str, ticker_interval: str,
|
async def _async_get_candle_history(self, pair: str, timeframe: str,
|
||||||
since_ms: Optional[int] = None) -> Tuple[str, str, List]:
|
since_ms: Optional[int] = None) -> Tuple[str, str, List]:
|
||||||
"""
|
"""
|
||||||
Asynchronously gets candle histories using fetch_ohlcv
|
Asynchronously gets candle histories using fetch_ohlcv
|
||||||
returns tuple: (pair, ticker_interval, ohlcv_list)
|
returns tuple: (pair, timeframe, ohlcv_list)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# fetch ohlcv asynchronously
|
# fetch ohlcv asynchronously
|
||||||
s = '(' + arrow.get(since_ms // 1000).isoformat() + ') ' if since_ms is not None else ''
|
s = '(' + arrow.get(since_ms // 1000).isoformat() + ') ' if since_ms is not None else ''
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Fetching pair %s, interval %s, since %s %s...",
|
"Fetching pair %s, interval %s, since %s %s...",
|
||||||
pair, ticker_interval, since_ms, s
|
pair, timeframe, since_ms, s
|
||||||
)
|
)
|
||||||
|
|
||||||
data = await self._api_async.fetch_ohlcv(pair, timeframe=ticker_interval,
|
data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe,
|
||||||
since=since_ms)
|
since=since_ms)
|
||||||
|
|
||||||
# Because some exchange sort Tickers ASC and other DESC.
|
# Because some exchange sort Tickers ASC and other DESC.
|
||||||
|
@ -653,9 +663,9 @@ class Exchange:
|
||||||
data = sorted(data, key=lambda x: x[0])
|
data = sorted(data, key=lambda x: x[0])
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logger.exception("Error loading %s. Result was %s.", pair, data)
|
logger.exception("Error loading %s. Result was %s.", pair, data)
|
||||||
return pair, ticker_interval, []
|
return pair, timeframe, []
|
||||||
logger.debug("Done fetching pair %s, interval %s ...", pair, ticker_interval)
|
logger.debug("Done fetching pair %s, interval %s ...", pair, timeframe)
|
||||||
return pair, ticker_interval, data
|
return pair, timeframe, data
|
||||||
|
|
||||||
except ccxt.NotSupported as e:
|
except ccxt.NotSupported as e:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
|
@ -802,7 +812,6 @@ class Exchange:
|
||||||
Handles all async doing.
|
Handles all async doing.
|
||||||
Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call.
|
Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call.
|
||||||
:param pair: Pair to download
|
:param pair: Pair to download
|
||||||
:param ticker_interval: Interval to get
|
|
||||||
:param since: Timestamp in milliseconds to get history from
|
:param since: Timestamp in milliseconds to get history from
|
||||||
:param until: Timestamp in milliseconds. Defaults to current timestamp if not defined.
|
:param until: Timestamp in milliseconds. Defaults to current timestamp if not defined.
|
||||||
:param from_id: Download data starting with ID (if id is known)
|
:param from_id: Download data starting with ID (if id is known)
|
||||||
|
@ -958,27 +967,27 @@ def available_exchanges(ccxt_module=None) -> List[str]:
|
||||||
return [x for x in exchanges if not is_exchange_bad(x)]
|
return [x for x in exchanges if not is_exchange_bad(x)]
|
||||||
|
|
||||||
|
|
||||||
def timeframe_to_seconds(ticker_interval: str) -> int:
|
def timeframe_to_seconds(timeframe: str) -> int:
|
||||||
"""
|
"""
|
||||||
Translates the timeframe interval value written in the human readable
|
Translates the timeframe interval value written in the human readable
|
||||||
form ('1m', '5m', '1h', '1d', '1w', etc.) to the number
|
form ('1m', '5m', '1h', '1d', '1w', etc.) to the number
|
||||||
of seconds for one timeframe interval.
|
of seconds for one timeframe interval.
|
||||||
"""
|
"""
|
||||||
return ccxt.Exchange.parse_timeframe(ticker_interval)
|
return ccxt.Exchange.parse_timeframe(timeframe)
|
||||||
|
|
||||||
|
|
||||||
def timeframe_to_minutes(ticker_interval: str) -> int:
|
def timeframe_to_minutes(timeframe: str) -> int:
|
||||||
"""
|
"""
|
||||||
Same as timeframe_to_seconds, but returns minutes.
|
Same as timeframe_to_seconds, but returns minutes.
|
||||||
"""
|
"""
|
||||||
return ccxt.Exchange.parse_timeframe(ticker_interval) // 60
|
return ccxt.Exchange.parse_timeframe(timeframe) // 60
|
||||||
|
|
||||||
|
|
||||||
def timeframe_to_msecs(ticker_interval: str) -> int:
|
def timeframe_to_msecs(timeframe: str) -> int:
|
||||||
"""
|
"""
|
||||||
Same as timeframe_to_seconds, but returns milliseconds.
|
Same as timeframe_to_seconds, but returns milliseconds.
|
||||||
"""
|
"""
|
||||||
return ccxt.Exchange.parse_timeframe(ticker_interval) * 1000
|
return ccxt.Exchange.parse_timeframe(timeframe) * 1000
|
||||||
|
|
||||||
|
|
||||||
def timeframe_to_prev_date(timeframe: str, date: datetime = None) -> datetime:
|
def timeframe_to_prev_date(timeframe: str, date: datetime = None) -> datetime:
|
||||||
|
|
|
@ -139,7 +139,6 @@ class FreqtradeBot:
|
||||||
if len(trades) < self.config['max_open_trades']:
|
if len(trades) < self.config['max_open_trades']:
|
||||||
self.process_maybe_execute_buys()
|
self.process_maybe_execute_buys()
|
||||||
|
|
||||||
if 'unfilledtimeout' in self.config:
|
|
||||||
# Check and handle any timed out open orders
|
# Check and handle any timed out open orders
|
||||||
self.check_handle_timedout()
|
self.check_handle_timedout()
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
@ -756,23 +755,28 @@ class FreqtradeBot:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _check_timed_out(self, side: str, order: dict) -> bool:
|
||||||
|
"""
|
||||||
|
Check if timeout is active, and if the order is still open and timed out
|
||||||
|
"""
|
||||||
|
timeout = self.config.get('unfilledtimeout', {}).get(side)
|
||||||
|
ordertime = arrow.get(order['datetime']).datetime
|
||||||
|
if timeout is not None:
|
||||||
|
timeout_threshold = arrow.utcnow().shift(minutes=-timeout).datetime
|
||||||
|
|
||||||
|
return (order['status'] == 'open' and order['side'] == side
|
||||||
|
and ordertime < timeout_threshold)
|
||||||
|
return False
|
||||||
|
|
||||||
def check_handle_timedout(self) -> None:
|
def check_handle_timedout(self) -> None:
|
||||||
"""
|
"""
|
||||||
Check if any orders are timed out and cancel if neccessary
|
Check if any orders are timed out and cancel if neccessary
|
||||||
:param timeoutvalue: Number of minutes until order is considered timed out
|
:param timeoutvalue: Number of minutes until order is considered timed out
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
buy_timeout = self.config['unfilledtimeout']['buy']
|
|
||||||
sell_timeout = self.config['unfilledtimeout']['sell']
|
|
||||||
buy_timeout_threshold = arrow.utcnow().shift(minutes=-buy_timeout).datetime
|
|
||||||
sell_timeout_threshold = arrow.utcnow().shift(minutes=-sell_timeout).datetime
|
|
||||||
|
|
||||||
for trade in Trade.get_open_order_trades():
|
for trade in Trade.get_open_order_trades():
|
||||||
try:
|
try:
|
||||||
# FIXME: Somehow the query above returns results
|
|
||||||
# where the open_order_id is in fact None.
|
|
||||||
# This is probably because the record got
|
|
||||||
# updated via /forcesell in a different thread.
|
|
||||||
if not trade.open_order_id:
|
if not trade.open_order_id:
|
||||||
continue
|
continue
|
||||||
order = self.exchange.get_order(trade.open_order_id, trade.pair)
|
order = self.exchange.get_order(trade.open_order_id, trade.pair)
|
||||||
|
@ -782,7 +786,6 @@ class FreqtradeBot:
|
||||||
trade,
|
trade,
|
||||||
traceback.format_exc())
|
traceback.format_exc())
|
||||||
continue
|
continue
|
||||||
ordertime = arrow.get(order['datetime']).datetime
|
|
||||||
|
|
||||||
# Check if trade is still actually open
|
# Check if trade is still actually open
|
||||||
if float(order['remaining']) == 0.0:
|
if float(order['remaining']) == 0.0:
|
||||||
|
@ -790,15 +793,13 @@ class FreqtradeBot:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if ((order['side'] == 'buy' and order['status'] == 'canceled')
|
if ((order['side'] == 'buy' and order['status'] == 'canceled')
|
||||||
or (order['status'] == 'open'
|
or (self._check_timed_out('buy', order))):
|
||||||
and order['side'] == 'buy' and ordertime < buy_timeout_threshold)):
|
|
||||||
|
|
||||||
self.handle_timedout_limit_buy(trade, order)
|
self.handle_timedout_limit_buy(trade, order)
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
|
|
||||||
elif ((order['side'] == 'sell' and order['status'] == 'canceled')
|
elif ((order['side'] == 'sell' and order['status'] == 'canceled')
|
||||||
or (order['status'] == 'open'
|
or (self._check_timed_out('sell', order))):
|
||||||
and order['side'] == 'sell' and ordertime < sell_timeout_threshold)):
|
|
||||||
self.handle_timedout_limit_sell(trade, order)
|
self.handle_timedout_limit_sell(trade, order)
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ from typing import Any, List
|
||||||
|
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException
|
||||||
from freqtrade.configuration import Arguments
|
from freqtrade.configuration import Arguments
|
||||||
from freqtrade.worker import Worker
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('freqtrade')
|
logger = logging.getLogger('freqtrade')
|
||||||
|
@ -33,16 +32,19 @@ def main(sysargv: List[str] = None) -> None:
|
||||||
arguments = Arguments(sysargv)
|
arguments = Arguments(sysargv)
|
||||||
args = arguments.get_parsed_arg()
|
args = arguments.get_parsed_arg()
|
||||||
|
|
||||||
# A subcommand has been issued.
|
# Call subcommand.
|
||||||
# Means if Backtesting or Hyperopt have been called we exit the bot
|
|
||||||
if 'func' in args:
|
if 'func' in args:
|
||||||
args['func'](args)
|
return_code = args['func'](args)
|
||||||
# TODO: fetch return_code as returned by the command function here
|
|
||||||
return_code = 0
|
|
||||||
else:
|
else:
|
||||||
# Load and run worker
|
# No subcommand was issued.
|
||||||
worker = Worker(args)
|
raise OperationalException(
|
||||||
worker.run()
|
"Usage of Freqtrade requires a subcommand to be specified.\n"
|
||||||
|
"To have the previous behavior (bot executing trades in live/dry-run modes, "
|
||||||
|
"depending on the value of the `dry_run` setting in the config), run freqtrade "
|
||||||
|
"as `freqtrade trade [options...]`.\n"
|
||||||
|
"To see the full list of options available, please use "
|
||||||
|
"`freqtrade --help` or `freqtrade <command> --help`."
|
||||||
|
)
|
||||||
|
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
return_code = e
|
return_code = e
|
||||||
|
|
|
@ -78,7 +78,7 @@ def start_hyperopt(args: Dict[str, Any]) -> None:
|
||||||
except Timeout:
|
except Timeout:
|
||||||
logger.info("Another running instance of freqtrade Hyperopt detected.")
|
logger.info("Another running instance of freqtrade Hyperopt detected.")
|
||||||
logger.info("Simultaneous execution of multiple Hyperopt commands is not supported. "
|
logger.info("Simultaneous execution of multiple Hyperopt commands is not supported. "
|
||||||
"Hyperopt module is resource hungry. Please run your Hyperopts sequentially "
|
"Hyperopt module is resource hungry. Please run your Hyperopt sequentially "
|
||||||
"or on separate machines.")
|
"or on separate machines.")
|
||||||
logger.info("Quitting now.")
|
logger.info("Quitting now.")
|
||||||
# TODO: return False here in order to help freqtrade to exit
|
# TODO: return False here in order to help freqtrade to exit
|
||||||
|
|
|
@ -83,8 +83,8 @@ class Backtesting:
|
||||||
if "ticker_interval" not in self.config:
|
if "ticker_interval" not in self.config:
|
||||||
raise OperationalException("Ticker-interval needs to be set in either configuration "
|
raise OperationalException("Ticker-interval needs to be set in either configuration "
|
||||||
"or as cli argument `--ticker-interval 5m`")
|
"or as cli argument `--ticker-interval 5m`")
|
||||||
self.ticker_interval = str(self.config.get('ticker_interval'))
|
self.timeframe = str(self.config.get('ticker_interval'))
|
||||||
self.ticker_interval_mins = timeframe_to_minutes(self.ticker_interval)
|
self.timeframe_mins = timeframe_to_minutes(self.timeframe)
|
||||||
|
|
||||||
# Get maximum required startup period
|
# Get maximum required startup period
|
||||||
self.required_startup = max([strat.startup_candle_count for strat in self.strategylist])
|
self.required_startup = max([strat.startup_candle_count for strat in self.strategylist])
|
||||||
|
@ -108,7 +108,7 @@ class Backtesting:
|
||||||
data = history.load_data(
|
data = history.load_data(
|
||||||
datadir=Path(self.config['datadir']),
|
datadir=Path(self.config['datadir']),
|
||||||
pairs=self.config['exchange']['pair_whitelist'],
|
pairs=self.config['exchange']['pair_whitelist'],
|
||||||
ticker_interval=self.ticker_interval,
|
timeframe=self.timeframe,
|
||||||
timerange=timerange,
|
timerange=timerange,
|
||||||
startup_candles=self.required_startup,
|
startup_candles=self.required_startup,
|
||||||
fail_without_data=True,
|
fail_without_data=True,
|
||||||
|
@ -121,7 +121,7 @@ class Backtesting:
|
||||||
min_date.isoformat(), max_date.isoformat(), (max_date - min_date).days
|
min_date.isoformat(), max_date.isoformat(), (max_date - min_date).days
|
||||||
)
|
)
|
||||||
# Adjust startts forward if not enough data is available
|
# Adjust startts forward if not enough data is available
|
||||||
timerange.adjust_start_if_necessary(timeframe_to_seconds(self.ticker_interval),
|
timerange.adjust_start_if_necessary(timeframe_to_seconds(self.timeframe),
|
||||||
self.required_startup, min_date)
|
self.required_startup, min_date)
|
||||||
|
|
||||||
return data, timerange
|
return data, timerange
|
||||||
|
@ -375,7 +375,7 @@ class Backtesting:
|
||||||
lock_pair_until: Dict = {}
|
lock_pair_until: Dict = {}
|
||||||
# Indexes per pair, so some pairs are allowed to have a missing start.
|
# Indexes per pair, so some pairs are allowed to have a missing start.
|
||||||
indexes: Dict = {}
|
indexes: Dict = {}
|
||||||
tmp = start_date + timedelta(minutes=self.ticker_interval_mins)
|
tmp = start_date + timedelta(minutes=self.timeframe_mins)
|
||||||
|
|
||||||
# Loop timerange and get candle for each pair at that point in time
|
# Loop timerange and get candle for each pair at that point in time
|
||||||
while tmp < end_date:
|
while tmp < end_date:
|
||||||
|
@ -427,7 +427,7 @@ class Backtesting:
|
||||||
lock_pair_until[pair] = end_date.datetime
|
lock_pair_until[pair] = end_date.datetime
|
||||||
|
|
||||||
# Move time one configured time_interval ahead.
|
# Move time one configured time_interval ahead.
|
||||||
tmp += timedelta(minutes=self.ticker_interval_mins)
|
tmp += timedelta(minutes=self.timeframe_mins)
|
||||||
return DataFrame.from_records(trades, columns=BacktestResult._fields)
|
return DataFrame.from_records(trades, columns=BacktestResult._fields)
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""
|
"""
|
||||||
IHyperOpt interface
|
IHyperOpt interface
|
||||||
This module defines the interface to apply for hyperopts
|
This module defines the interface to apply for hyperopt
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
|
@ -27,8 +27,8 @@ def _format_exception_message(method: str, space: str) -> str:
|
||||||
|
|
||||||
class IHyperOpt(ABC):
|
class IHyperOpt(ABC):
|
||||||
"""
|
"""
|
||||||
Interface for freqtrade hyperopts
|
Interface for freqtrade hyperopt
|
||||||
Defines the mandatory structure must follow any custom hyperopts
|
Defines the mandatory structure must follow any custom hyperopt
|
||||||
|
|
||||||
Class attributes you can use:
|
Class attributes you can use:
|
||||||
ticker_interval -> int: value of the ticker interval to use for the strategy
|
ticker_interval -> int: value of the ticker interval to use for the strategy
|
||||||
|
@ -106,10 +106,10 @@ class IHyperOpt(ABC):
|
||||||
roi_t_alpha = 1.0
|
roi_t_alpha = 1.0
|
||||||
roi_p_alpha = 1.0
|
roi_p_alpha = 1.0
|
||||||
|
|
||||||
ticker_interval_mins = timeframe_to_minutes(IHyperOpt.ticker_interval)
|
timeframe_mins = timeframe_to_minutes(IHyperOpt.ticker_interval)
|
||||||
|
|
||||||
# We define here limits for the ROI space parameters automagically adapted to the
|
# We define here limits for the ROI space parameters automagically adapted to the
|
||||||
# ticker_interval used by the bot:
|
# timeframe used by the bot:
|
||||||
#
|
#
|
||||||
# * 'roi_t' (limits for the time intervals in the ROI tables) components
|
# * 'roi_t' (limits for the time intervals in the ROI tables) components
|
||||||
# are scaled linearly.
|
# are scaled linearly.
|
||||||
|
@ -117,8 +117,8 @@ class IHyperOpt(ABC):
|
||||||
#
|
#
|
||||||
# The scaling is designed so that it maps exactly to the legacy Freqtrade roi_space()
|
# The scaling is designed so that it maps exactly to the legacy Freqtrade roi_space()
|
||||||
# method for the 5m ticker interval.
|
# method for the 5m ticker interval.
|
||||||
roi_t_scale = ticker_interval_mins / 5
|
roi_t_scale = timeframe_mins / 5
|
||||||
roi_p_scale = math.log1p(ticker_interval_mins) / math.log1p(5)
|
roi_p_scale = math.log1p(timeframe_mins) / math.log1p(5)
|
||||||
roi_limits = {
|
roi_limits = {
|
||||||
'roi_t1_min': int(10 * roi_t_scale * roi_t_alpha),
|
'roi_t1_min': int(10 * roi_t_scale * roi_t_alpha),
|
||||||
'roi_t1_max': int(120 * roi_t_scale * roi_t_alpha),
|
'roi_t1_max': int(120 * roi_t_scale * roi_t_alpha),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""
|
"""
|
||||||
IHyperOptLoss interface
|
IHyperOptLoss interface
|
||||||
This module defines the interface for the loss-function for hyperopts
|
This module defines the interface for the loss-function for hyperopt
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
@ -11,7 +11,7 @@ from pandas import DataFrame
|
||||||
|
|
||||||
class IHyperOptLoss(ABC):
|
class IHyperOptLoss(ABC):
|
||||||
"""
|
"""
|
||||||
Interface for freqtrade hyperopts Loss functions.
|
Interface for freqtrade hyperopt Loss functions.
|
||||||
Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.)
|
Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.)
|
||||||
"""
|
"""
|
||||||
ticker_interval: str
|
ticker_interval: str
|
||||||
|
|
|
@ -39,7 +39,7 @@ def init_plotscript(config):
|
||||||
tickers = history.load_data(
|
tickers = history.load_data(
|
||||||
datadir=Path(str(config.get("datadir"))),
|
datadir=Path(str(config.get("datadir"))),
|
||||||
pairs=pairs,
|
pairs=pairs,
|
||||||
ticker_interval=config.get('ticker_interval', '5m'),
|
timeframe=config.get('ticker_interval', '5m'),
|
||||||
timerange=timerange,
|
timerange=timerange,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ def init_plotscript(config):
|
||||||
db_url=config.get('db_url'),
|
db_url=config.get('db_url'),
|
||||||
exportfilename=config.get('exportfilename'),
|
exportfilename=config.get('exportfilename'),
|
||||||
)
|
)
|
||||||
|
trades = history.trim_dataframe(trades, timerange, 'open_time')
|
||||||
return {"tickers": tickers,
|
return {"tickers": tickers,
|
||||||
"trades": trades,
|
"trades": trades,
|
||||||
"pairs": pairs,
|
"pairs": pairs,
|
||||||
|
@ -300,12 +300,12 @@ def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame],
|
||||||
return fig
|
return fig
|
||||||
|
|
||||||
|
|
||||||
def generate_plot_filename(pair, ticker_interval) -> str:
|
def generate_plot_filename(pair, timeframe) -> str:
|
||||||
"""
|
"""
|
||||||
Generate filenames per pair/ticker_interval to be used for storing plots
|
Generate filenames per pair/timeframe to be used for storing plots
|
||||||
"""
|
"""
|
||||||
pair_name = pair.replace("/", "_")
|
pair_name = pair.replace("/", "_")
|
||||||
file_name = 'freqtrade-plot-' + pair_name + '-' + ticker_interval + '.html'
|
file_name = 'freqtrade-plot-' + pair_name + '-' + timeframe + '.html'
|
||||||
|
|
||||||
logger.info('Generate plot file for %s', pair)
|
logger.info('Generate plot file for %s', pair)
|
||||||
|
|
||||||
|
@ -316,8 +316,9 @@ def store_plot_file(fig, filename: str, directory: Path, auto_open: bool = False
|
||||||
"""
|
"""
|
||||||
Generate a plot html file from pre populated fig plotly object
|
Generate a plot html file from pre populated fig plotly object
|
||||||
:param fig: Plotly Figure to plot
|
:param fig: Plotly Figure to plot
|
||||||
:param pair: Pair to plot (used as filename and Plot title)
|
:param filename: Name to store the file as
|
||||||
:param ticker_interval: Used as part of the filename
|
:param directory: Directory to store the file in
|
||||||
|
:param auto_open: Automatically open files saved
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
@ -376,12 +377,14 @@ def plot_profit(config: Dict[str, Any]) -> None:
|
||||||
in helping out to find a good algorithm.
|
in helping out to find a good algorithm.
|
||||||
"""
|
"""
|
||||||
plot_elements = init_plotscript(config)
|
plot_elements = init_plotscript(config)
|
||||||
trades = load_trades(config['trade_source'],
|
trades = plot_elements['trades']
|
||||||
db_url=str(config.get('db_url')),
|
|
||||||
exportfilename=str(config.get('exportfilename')),
|
|
||||||
)
|
|
||||||
# Filter trades to relevant pairs
|
# Filter trades to relevant pairs
|
||||||
trades = trades[trades['pair'].isin(plot_elements["pairs"])]
|
# Remove open pairs - we don't know the profit yet so can't calculate profit for these.
|
||||||
|
# Also, If only one open pair is left, then the profit-generation would fail.
|
||||||
|
trades = trades[(trades['pair'].isin(plot_elements["pairs"]))
|
||||||
|
& (~trades['close_time'].isnull())
|
||||||
|
]
|
||||||
|
|
||||||
# Create an average close price of all the pairs that were involved.
|
# Create an average close price of all the pairs that were involved.
|
||||||
# this could be useful to gauge the overall market trend
|
# this could be useful to gauge the overall market trend
|
||||||
fig = generate_profit_graph(plot_elements["pairs"], plot_elements["tickers"],
|
fig = generate_profit_graph(plot_elements["pairs"], plot_elements["tickers"],
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
# pragma pylint: disable=attribute-defined-outside-init
|
# pragma pylint: disable=attribute-defined-outside-init
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module load custom hyperopts
|
This module load custom hyperopt
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Dict
|
from typing import Optional, Dict
|
||||||
|
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException
|
||||||
from freqtrade.constants import DEFAULT_HYPEROPT, DEFAULT_HYPEROPT_LOSS
|
from freqtrade.constants import DEFAULT_HYPEROPT_LOSS
|
||||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
||||||
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
|
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
|
||||||
from freqtrade.resolvers import IResolver
|
from freqtrade.resolvers import IResolver
|
||||||
|
@ -20,7 +20,6 @@ class HyperOptResolver(IResolver):
|
||||||
"""
|
"""
|
||||||
This class contains all the logic to load custom hyperopt class
|
This class contains all the logic to load custom hyperopt class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ['hyperopt']
|
__slots__ = ['hyperopt']
|
||||||
|
|
||||||
def __init__(self, config: Dict) -> None:
|
def __init__(self, config: Dict) -> None:
|
||||||
|
@ -28,9 +27,12 @@ class HyperOptResolver(IResolver):
|
||||||
Load the custom class from config parameter
|
Load the custom class from config parameter
|
||||||
:param config: configuration dictionary
|
:param config: configuration dictionary
|
||||||
"""
|
"""
|
||||||
|
if not config.get('hyperopt'):
|
||||||
|
raise OperationalException("No Hyperopt set. Please use `--hyperopt` to specify "
|
||||||
|
"the Hyperopt class to use.")
|
||||||
|
|
||||||
|
hyperopt_name = config['hyperopt']
|
||||||
|
|
||||||
# Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt
|
|
||||||
hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT
|
|
||||||
self.hyperopt = self._load_hyperopt(hyperopt_name, config,
|
self.hyperopt = self._load_hyperopt(hyperopt_name, config,
|
||||||
extra_dir=config.get('hyperopt_path'))
|
extra_dir=config.get('hyperopt_path'))
|
||||||
|
|
||||||
|
@ -72,27 +74,28 @@ class HyperOptLossResolver(IResolver):
|
||||||
"""
|
"""
|
||||||
This class contains all the logic to load custom hyperopt loss class
|
This class contains all the logic to load custom hyperopt loss class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ['hyperoptloss']
|
__slots__ = ['hyperoptloss']
|
||||||
|
|
||||||
def __init__(self, config: Dict = None) -> None:
|
def __init__(self, config: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
Load the custom class from config parameter
|
Load the custom class from config parameter
|
||||||
:param config: configuration dictionary or None
|
:param config: configuration dictionary
|
||||||
"""
|
"""
|
||||||
config = config or {}
|
|
||||||
|
|
||||||
# Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt
|
# Verify the hyperopt_loss is in the configuration, otherwise fallback to the
|
||||||
hyperopt_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS
|
# default hyperopt loss
|
||||||
|
hyperoptloss_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS
|
||||||
|
|
||||||
self.hyperoptloss = self._load_hyperoptloss(
|
self.hyperoptloss = self._load_hyperoptloss(
|
||||||
hyperopt_name, config, extra_dir=config.get('hyperopt_path'))
|
hyperoptloss_name, config, extra_dir=config.get('hyperopt_path'))
|
||||||
|
|
||||||
# Assign ticker_interval to be used in hyperopt
|
# Assign ticker_interval to be used in hyperopt
|
||||||
self.hyperoptloss.__class__.ticker_interval = str(config['ticker_interval'])
|
self.hyperoptloss.__class__.ticker_interval = str(config['ticker_interval'])
|
||||||
|
|
||||||
if not hasattr(self.hyperoptloss, 'hyperopt_loss_function'):
|
if not hasattr(self.hyperoptloss, 'hyperopt_loss_function'):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f"Found hyperopt {hyperopt_name} does not implement `hyperopt_loss_function`.")
|
f"Found HyperoptLoss class {hyperoptloss_name} does not "
|
||||||
|
"implement `hyperopt_loss_function`.")
|
||||||
|
|
||||||
def _load_hyperoptloss(
|
def _load_hyperoptloss(
|
||||||
self, hyper_loss_name: str, config: Dict,
|
self, hyper_loss_name: str, config: Dict,
|
||||||
|
|
|
@ -32,8 +32,11 @@ class StrategyResolver(IResolver):
|
||||||
"""
|
"""
|
||||||
config = config or {}
|
config = config or {}
|
||||||
|
|
||||||
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
|
if not config.get('strategy'):
|
||||||
strategy_name = config.get('strategy') or constants.DEFAULT_STRATEGY
|
raise OperationalException("No strategy set. Please use `--strategy` to specify "
|
||||||
|
"the strategy class to use.")
|
||||||
|
|
||||||
|
strategy_name = config['strategy']
|
||||||
self.strategy: IStrategy = self._load_strategy(strategy_name,
|
self.strategy: IStrategy = self._load_strategy(strategy_name,
|
||||||
config=config,
|
config=config,
|
||||||
extra_dir=config.get('strategy_path'))
|
extra_dir=config.get('strategy_path'))
|
||||||
|
|
|
@ -169,6 +169,8 @@ class ApiServer(RPC):
|
||||||
view_func=self._status, methods=['GET'])
|
view_func=self._status, methods=['GET'])
|
||||||
self.app.add_url_rule(f'{BASE_URI}/version', 'version',
|
self.app.add_url_rule(f'{BASE_URI}/version', 'version',
|
||||||
view_func=self._version, methods=['GET'])
|
view_func=self._version, methods=['GET'])
|
||||||
|
self.app.add_url_rule(f'{BASE_URI}/ping', 'ping',
|
||||||
|
view_func=self._ping, methods=['GET'])
|
||||||
|
|
||||||
# Combined actions and infos
|
# Combined actions and infos
|
||||||
self.app.add_url_rule(f'{BASE_URI}/blacklist', 'blacklist', view_func=self._blacklist,
|
self.app.add_url_rule(f'{BASE_URI}/blacklist', 'blacklist', view_func=self._blacklist,
|
||||||
|
@ -224,6 +226,13 @@ class ApiServer(RPC):
|
||||||
msg = self._rpc_stopbuy()
|
msg = self._rpc_stopbuy()
|
||||||
return self.rest_dump(msg)
|
return self.rest_dump(msg)
|
||||||
|
|
||||||
|
@rpc_catch_errors
|
||||||
|
def _ping(self):
|
||||||
|
"""
|
||||||
|
simple poing version
|
||||||
|
"""
|
||||||
|
return self.rest_dump({"status": "pong"})
|
||||||
|
|
||||||
@require_login
|
@require_login
|
||||||
@rpc_catch_errors
|
@rpc_catch_errors
|
||||||
def _version(self):
|
def _version(self):
|
||||||
|
@ -265,7 +274,7 @@ class ApiServer(RPC):
|
||||||
|
|
||||||
stats = self._rpc_daily_profit(timescale,
|
stats = self._rpc_daily_profit(timescale,
|
||||||
self._config['stake_currency'],
|
self._config['stake_currency'],
|
||||||
self._config['fiat_display_currency']
|
self._config.get('fiat_display_currency', '')
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.rest_dump(stats)
|
return self.rest_dump(stats)
|
||||||
|
|
|
@ -3,16 +3,15 @@ This module contains class to define a RPC communications
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from datetime import timedelta, datetime, date
|
from datetime import date, datetime, timedelta
|
||||||
from decimal import Decimal
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, Any, List, Optional
|
from math import isnan
|
||||||
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from numpy import mean, NAN
|
from numpy import NAN, mean
|
||||||
from pandas import DataFrame
|
|
||||||
|
|
||||||
from freqtrade import TemporaryError, DependencyException
|
from freqtrade import DependencyException, TemporaryError
|
||||||
from freqtrade.misc import shorten_date
|
from freqtrade.misc import shorten_date
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||||
|
@ -117,7 +116,7 @@ class RPC:
|
||||||
results.append(trade_dict)
|
results.append(trade_dict)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def _rpc_status_table(self) -> DataFrame:
|
def _rpc_status_table(self, stake_currency, fiat_display_currency: str) -> Tuple[List, List]:
|
||||||
trades = Trade.get_open_trades()
|
trades = Trade.get_open_trades()
|
||||||
if not trades:
|
if not trades:
|
||||||
raise RPCException('no active order')
|
raise RPCException('no active order')
|
||||||
|
@ -130,17 +129,28 @@ class RPC:
|
||||||
except DependencyException:
|
except DependencyException:
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
trade_perc = (100 * trade.calc_profit_percent(current_rate))
|
trade_perc = (100 * trade.calc_profit_percent(current_rate))
|
||||||
|
trade_profit = trade.calc_profit(current_rate)
|
||||||
|
profit_str = f'{trade_perc:.2f}%'
|
||||||
|
if self._fiat_converter:
|
||||||
|
fiat_profit = self._fiat_converter.convert_amount(
|
||||||
|
trade_profit,
|
||||||
|
stake_currency,
|
||||||
|
fiat_display_currency
|
||||||
|
)
|
||||||
|
if fiat_profit and not isnan(fiat_profit):
|
||||||
|
profit_str += f" ({fiat_profit:.2f})"
|
||||||
trades_list.append([
|
trades_list.append([
|
||||||
trade.id,
|
trade.id,
|
||||||
trade.pair,
|
trade.pair,
|
||||||
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
|
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
|
||||||
f'{trade_perc:.2f}%'
|
profit_str
|
||||||
])
|
])
|
||||||
|
profitcol = "Profit"
|
||||||
|
if self._fiat_converter:
|
||||||
|
profitcol += " (" + fiat_display_currency + ")"
|
||||||
|
|
||||||
columns = ['ID', 'Pair', 'Since', 'Profit']
|
columns = ['ID', 'Pair', 'Since', profitcol]
|
||||||
df_statuses = DataFrame.from_records(trades_list, columns=columns)
|
return trades_list, columns
|
||||||
df_statuses = df_statuses.set_index(columns[0])
|
|
||||||
return df_statuses
|
|
||||||
|
|
||||||
def _rpc_daily_profit(
|
def _rpc_daily_profit(
|
||||||
self, timescale: int,
|
self, timescale: int,
|
||||||
|
@ -219,7 +229,7 @@ class RPC:
|
||||||
profit_percent = trade.calc_profit_percent(rate=current_rate)
|
profit_percent = trade.calc_profit_percent(rate=current_rate)
|
||||||
|
|
||||||
profit_all_coin.append(
|
profit_all_coin.append(
|
||||||
trade.calc_profit(rate=Decimal(trade.close_rate or current_rate))
|
trade.calc_profit(rate=trade.close_rate or current_rate)
|
||||||
)
|
)
|
||||||
profit_all_perc.append(profit_percent)
|
profit_all_perc.append(profit_percent)
|
||||||
|
|
||||||
|
|
|
@ -234,8 +234,9 @@ class Telegram(RPC):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
df_statuses = self._rpc_status_table()
|
statlist, head = self._rpc_status_table(self._config['stake_currency'],
|
||||||
message = tabulate(df_statuses, headers='keys', tablefmt='simple')
|
self._config.get('fiat_display_currency', ''))
|
||||||
|
message = tabulate(statlist, headers=head, tablefmt='simple')
|
||||||
self._send_msg(f"<pre>{message}</pre>", parse_mode=ParseMode.HTML)
|
self._send_msg(f"<pre>{message}</pre>", parse_mode=ParseMode.HTML)
|
||||||
except RPCException as e:
|
except RPCException as e:
|
||||||
self._send_msg(str(e))
|
self._send_msg(str(e))
|
||||||
|
|
|
@ -39,6 +39,17 @@ def setup_utils_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def start_trading(args: Dict[str, Any]) -> int:
|
||||||
|
"""
|
||||||
|
Main entry point for trading mode
|
||||||
|
"""
|
||||||
|
from freqtrade.worker import Worker
|
||||||
|
# Load and run worker
|
||||||
|
worker = Worker(args)
|
||||||
|
worker.run()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def start_list_exchanges(args: Dict[str, Any]) -> None:
|
def start_list_exchanges(args: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Print available exchanges
|
Print available exchanges
|
||||||
|
@ -57,7 +68,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
def start_create_userdir(args: Dict[str, Any]) -> None:
|
def start_create_userdir(args: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Create "user_data" directory to contain user data strategies, hyperopts, ...)
|
Create "user_data" directory to contain user data strategies, hyperopt, ...)
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -16,6 +16,7 @@ nav:
|
||||||
- Hyperopt: hyperopt.md
|
- Hyperopt: hyperopt.md
|
||||||
- Edge Positioning: edge.md
|
- Edge Positioning: edge.md
|
||||||
- Utility Subcommands: utils.md
|
- Utility Subcommands: utils.md
|
||||||
|
- Exchange-specific Notes: exchanges.md
|
||||||
- FAQ: faq.md
|
- FAQ: faq.md
|
||||||
- Data Analysis:
|
- Data Analysis:
|
||||||
- Jupyter Notebooks: data-analysis.md
|
- Jupyter Notebooks: data-analysis.md
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# requirements without requirements installable via conda
|
# requirements without requirements installable via conda
|
||||||
# mainly used for Raspberry pi installs
|
# mainly used for Raspberry pi installs
|
||||||
ccxt==1.19.14
|
ccxt==1.19.25
|
||||||
SQLAlchemy==1.3.10
|
SQLAlchemy==1.3.10
|
||||||
python-telegram-bot==12.2.0
|
python-telegram-bot==12.2.0
|
||||||
arrow==0.15.4
|
arrow==0.15.4
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
# Required for hyperopt
|
# Required for hyperopt
|
||||||
scipy==1.3.1
|
scipy==1.3.2
|
||||||
scikit-learn==0.21.3
|
scikit-learn==0.21.3
|
||||||
scikit-optimize==0.5.2
|
scikit-optimize==0.5.2
|
||||||
filelock==3.0.12
|
filelock==3.0.12
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Load common requirements
|
# Load common requirements
|
||||||
-r requirements-common.txt
|
-r requirements-common.txt
|
||||||
|
|
||||||
numpy==1.17.3
|
numpy==1.17.4
|
||||||
pandas==0.25.3
|
pandas==0.25.3
|
||||||
|
|
|
@ -252,6 +252,7 @@ def default_conf(testdatadir):
|
||||||
"db_url": "sqlite://",
|
"db_url": "sqlite://",
|
||||||
"user_data_dir": Path("user_data"),
|
"user_data_dir": Path("user_data"),
|
||||||
"verbosity": 3,
|
"verbosity": 3,
|
||||||
|
"strategy": "DefaultStrategy"
|
||||||
}
|
}
|
||||||
return configuration
|
return configuration
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ def test_extract_trades_of_period(testdatadir):
|
||||||
# 2018-11-14 06:07:00
|
# 2018-11-14 06:07:00
|
||||||
timerange = TimeRange('date', None, 1510639620, 0)
|
timerange = TimeRange('date', None, 1510639620, 0)
|
||||||
|
|
||||||
data = load_pair_history(pair=pair, ticker_interval='1m',
|
data = load_pair_history(pair=pair, timeframe='1m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
|
|
||||||
trades = DataFrame(
|
trades = DataFrame(
|
||||||
|
@ -122,7 +122,7 @@ def test_combine_tickers_with_mean(testdatadir):
|
||||||
pairs = ["ETH/BTC", "ADA/BTC"]
|
pairs = ["ETH/BTC", "ADA/BTC"]
|
||||||
tickers = load_data(datadir=testdatadir,
|
tickers = load_data(datadir=testdatadir,
|
||||||
pairs=pairs,
|
pairs=pairs,
|
||||||
ticker_interval='5m'
|
timeframe='5m'
|
||||||
)
|
)
|
||||||
df = combine_tickers_with_mean(tickers)
|
df = combine_tickers_with_mean(tickers)
|
||||||
assert isinstance(df, DataFrame)
|
assert isinstance(df, DataFrame)
|
||||||
|
@ -136,7 +136,7 @@ def test_create_cum_profit(testdatadir):
|
||||||
bt_data = load_backtest_data(filename)
|
bt_data = load_backtest_data(filename)
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||||
|
|
||||||
df = load_pair_history(pair="TRX/BTC", ticker_interval='5m',
|
df = load_pair_history(pair="TRX/BTC", timeframe='5m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
|
|
||||||
cum_profits = create_cum_profit(df.set_index('date'),
|
cum_profits = create_cum_profit(df.set_index('date'),
|
||||||
|
@ -154,7 +154,7 @@ def test_create_cum_profit1(testdatadir):
|
||||||
bt_data.loc[:, 'close_time'] = bt_data.loc[:, 'close_time'] + DateOffset(seconds=20)
|
bt_data.loc[:, 'close_time'] = bt_data.loc[:, 'close_time'] + DateOffset(seconds=20)
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||||
|
|
||||||
df = load_pair_history(pair="TRX/BTC", ticker_interval='5m',
|
df = load_pair_history(pair="TRX/BTC", timeframe='5m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
|
|
||||||
cum_profits = create_cum_profit(df.set_index('date'),
|
cum_profits = create_cum_profit(df.set_index('date'),
|
||||||
|
|
|
@ -23,7 +23,7 @@ def test_parse_ticker_dataframe(ticker_history_list, caplog):
|
||||||
|
|
||||||
def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
|
def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
|
||||||
data = load_pair_history(datadir=testdatadir,
|
data = load_pair_history(datadir=testdatadir,
|
||||||
ticker_interval='1m',
|
timeframe='1m',
|
||||||
pair='UNITTEST/BTC',
|
pair='UNITTEST/BTC',
|
||||||
fill_up_missing=False)
|
fill_up_missing=False)
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
|
@ -42,7 +42,7 @@ def test_ohlcv_fill_up_missing_data(testdatadir, caplog):
|
||||||
|
|
||||||
|
|
||||||
def test_ohlcv_fill_up_missing_data2(caplog):
|
def test_ohlcv_fill_up_missing_data2(caplog):
|
||||||
ticker_interval = '5m'
|
timeframe = '5m'
|
||||||
ticks = [[
|
ticks = [[
|
||||||
1511686200000, # 8:50:00
|
1511686200000, # 8:50:00
|
||||||
8.794e-05, # open
|
8.794e-05, # open
|
||||||
|
@ -78,10 +78,10 @@ def test_ohlcv_fill_up_missing_data2(caplog):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Generate test-data without filling missing
|
# Generate test-data without filling missing
|
||||||
data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", fill_missing=False)
|
data = parse_ticker_dataframe(ticks, timeframe, pair="UNITTEST/BTC", fill_missing=False)
|
||||||
assert len(data) == 3
|
assert len(data) == 3
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
data2 = ohlcv_fill_up_missing_data(data, ticker_interval, "UNITTEST/BTC")
|
data2 = ohlcv_fill_up_missing_data(data, timeframe, "UNITTEST/BTC")
|
||||||
assert len(data2) == 4
|
assert len(data2) == 4
|
||||||
# 3rd candle has been filled
|
# 3rd candle has been filled
|
||||||
row = data2.loc[2, :]
|
row = data2.loc[2, :]
|
||||||
|
@ -99,7 +99,7 @@ def test_ohlcv_fill_up_missing_data2(caplog):
|
||||||
|
|
||||||
|
|
||||||
def test_ohlcv_drop_incomplete(caplog):
|
def test_ohlcv_drop_incomplete(caplog):
|
||||||
ticker_interval = '1d'
|
timeframe = '1d'
|
||||||
ticks = [[
|
ticks = [[
|
||||||
1559750400000, # 2019-06-04
|
1559750400000, # 2019-06-04
|
||||||
8.794e-05, # open
|
8.794e-05, # open
|
||||||
|
@ -134,13 +134,13 @@ def test_ohlcv_drop_incomplete(caplog):
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC",
|
data = parse_ticker_dataframe(ticks, timeframe, pair="UNITTEST/BTC",
|
||||||
fill_missing=False, drop_incomplete=False)
|
fill_missing=False, drop_incomplete=False)
|
||||||
assert len(data) == 4
|
assert len(data) == 4
|
||||||
assert not log_has("Dropping last candle", caplog)
|
assert not log_has("Dropping last candle", caplog)
|
||||||
|
|
||||||
# Drop last candle
|
# Drop last candle
|
||||||
data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC",
|
data = parse_ticker_dataframe(ticks, timeframe, pair="UNITTEST/BTC",
|
||||||
fill_missing=False, drop_incomplete=True)
|
fill_missing=False, drop_incomplete=True)
|
||||||
assert len(data) == 3
|
assert len(data) == 3
|
||||||
|
|
||||||
|
|
|
@ -9,32 +9,32 @@ from tests.conftest import get_patched_exchange
|
||||||
|
|
||||||
def test_ohlcv(mocker, default_conf, ticker_history):
|
def test_ohlcv(mocker, default_conf, ticker_history):
|
||||||
default_conf["runmode"] = RunMode.DRY_RUN
|
default_conf["runmode"] = RunMode.DRY_RUN
|
||||||
ticker_interval = default_conf["ticker_interval"]
|
timeframe = default_conf["ticker_interval"]
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history
|
exchange._klines[("XRP/BTC", timeframe)] = ticker_history
|
||||||
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history
|
exchange._klines[("UNITTEST/BTC", timeframe)] = ticker_history
|
||||||
|
|
||||||
dp = DataProvider(default_conf, exchange)
|
dp = DataProvider(default_conf, exchange)
|
||||||
assert dp.runmode == RunMode.DRY_RUN
|
assert dp.runmode == RunMode.DRY_RUN
|
||||||
assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", ticker_interval))
|
assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", timeframe))
|
||||||
assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame)
|
assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe), DataFrame)
|
||||||
assert dp.ohlcv("UNITTEST/BTC", ticker_interval) is not ticker_history
|
assert dp.ohlcv("UNITTEST/BTC", timeframe) is not ticker_history
|
||||||
assert dp.ohlcv("UNITTEST/BTC", ticker_interval, copy=False) is ticker_history
|
assert dp.ohlcv("UNITTEST/BTC", timeframe, copy=False) is ticker_history
|
||||||
assert not dp.ohlcv("UNITTEST/BTC", ticker_interval).empty
|
assert not dp.ohlcv("UNITTEST/BTC", timeframe).empty
|
||||||
assert dp.ohlcv("NONESENSE/AAA", ticker_interval).empty
|
assert dp.ohlcv("NONESENSE/AAA", timeframe).empty
|
||||||
|
|
||||||
# Test with and without parameter
|
# Test with and without parameter
|
||||||
assert dp.ohlcv("UNITTEST/BTC", ticker_interval).equals(dp.ohlcv("UNITTEST/BTC"))
|
assert dp.ohlcv("UNITTEST/BTC", timeframe).equals(dp.ohlcv("UNITTEST/BTC"))
|
||||||
|
|
||||||
default_conf["runmode"] = RunMode.LIVE
|
default_conf["runmode"] = RunMode.LIVE
|
||||||
dp = DataProvider(default_conf, exchange)
|
dp = DataProvider(default_conf, exchange)
|
||||||
assert dp.runmode == RunMode.LIVE
|
assert dp.runmode == RunMode.LIVE
|
||||||
assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame)
|
assert isinstance(dp.ohlcv("UNITTEST/BTC", timeframe), DataFrame)
|
||||||
|
|
||||||
default_conf["runmode"] = RunMode.BACKTEST
|
default_conf["runmode"] = RunMode.BACKTEST
|
||||||
dp = DataProvider(default_conf, exchange)
|
dp = DataProvider(default_conf, exchange)
|
||||||
assert dp.runmode == RunMode.BACKTEST
|
assert dp.runmode == RunMode.BACKTEST
|
||||||
assert dp.ohlcv("UNITTEST/BTC", ticker_interval).empty
|
assert dp.ohlcv("UNITTEST/BTC", timeframe).empty
|
||||||
|
|
||||||
|
|
||||||
def test_historic_ohlcv(mocker, default_conf, ticker_history):
|
def test_historic_ohlcv(mocker, default_conf, ticker_history):
|
||||||
|
@ -45,7 +45,7 @@ def test_historic_ohlcv(mocker, default_conf, ticker_history):
|
||||||
data = dp.historic_ohlcv("UNITTEST/BTC", "5m")
|
data = dp.historic_ohlcv("UNITTEST/BTC", "5m")
|
||||||
assert isinstance(data, DataFrame)
|
assert isinstance(data, DataFrame)
|
||||||
assert historymock.call_count == 1
|
assert historymock.call_count == 1
|
||||||
assert historymock.call_args_list[0][1]["ticker_interval"] == "5m"
|
assert historymock.call_args_list[0][1]["timeframe"] == "5m"
|
||||||
|
|
||||||
|
|
||||||
def test_get_pair_dataframe(mocker, default_conf, ticker_history):
|
def test_get_pair_dataframe(mocker, default_conf, ticker_history):
|
||||||
|
|
|
@ -64,20 +64,20 @@ def _clean_test_file(file: Path) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_load_data_30min_ticker(mocker, caplog, default_conf, testdatadir) -> None:
|
def test_load_data_30min_ticker(mocker, caplog, default_conf, testdatadir) -> None:
|
||||||
ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='30m', datadir=testdatadir)
|
ld = history.load_pair_history(pair='UNITTEST/BTC', timeframe='30m', datadir=testdatadir)
|
||||||
assert isinstance(ld, DataFrame)
|
assert isinstance(ld, DataFrame)
|
||||||
assert not log_has(
|
assert not log_has(
|
||||||
'Download history data for pair: "UNITTEST/BTC", interval: 30m '
|
'Download history data for pair: "UNITTEST/BTC", timeframe: 30m '
|
||||||
'and store in None.', caplog
|
'and store in None.', caplog
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_load_data_7min_ticker(mocker, caplog, default_conf, testdatadir) -> None:
|
def test_load_data_7min_ticker(mocker, caplog, default_conf, testdatadir) -> None:
|
||||||
ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='7m', datadir=testdatadir)
|
ld = history.load_pair_history(pair='UNITTEST/BTC', timeframe='7m', datadir=testdatadir)
|
||||||
assert not isinstance(ld, DataFrame)
|
assert not isinstance(ld, DataFrame)
|
||||||
assert ld is None
|
assert ld is None
|
||||||
assert log_has(
|
assert log_has(
|
||||||
'No history data for pair: "UNITTEST/BTC", interval: 7m. '
|
'No history data for pair: "UNITTEST/BTC", timeframe: 7m. '
|
||||||
'Use `freqtrade download-data` to download the data', caplog
|
'Use `freqtrade download-data` to download the data', caplog
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog, testdatadir) -> N
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history)
|
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history)
|
||||||
file = testdatadir / 'UNITTEST_BTC-1m.json'
|
file = testdatadir / 'UNITTEST_BTC-1m.json'
|
||||||
_backup_file(file, copy_file=True)
|
_backup_file(file, copy_file=True)
|
||||||
history.load_data(datadir=testdatadir, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'])
|
||||||
assert file.is_file()
|
assert file.is_file()
|
||||||
assert not log_has(
|
assert not log_has(
|
||||||
'Download history data for pair: "UNITTEST/BTC", interval: 1m '
|
'Download history data for pair: "UNITTEST/BTC", interval: 1m '
|
||||||
|
@ -99,7 +99,7 @@ def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) ->
|
||||||
ltfmock = mocker.patch('freqtrade.data.history.load_tickerdata_file',
|
ltfmock = mocker.patch('freqtrade.data.history.load_tickerdata_file',
|
||||||
MagicMock(return_value=None))
|
MagicMock(return_value=None))
|
||||||
timerange = TimeRange('date', None, 1510639620, 0)
|
timerange = TimeRange('date', None, 1510639620, 0)
|
||||||
history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='1m',
|
history.load_pair_history(pair='UNITTEST/BTC', timeframe='1m',
|
||||||
datadir=testdatadir, timerange=timerange,
|
datadir=testdatadir, timerange=timerange,
|
||||||
startup_candles=20,
|
startup_candles=20,
|
||||||
)
|
)
|
||||||
|
@ -122,28 +122,28 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog,
|
||||||
_backup_file(file)
|
_backup_file(file)
|
||||||
# do not download a new pair if refresh_pairs isn't set
|
# do not download a new pair if refresh_pairs isn't set
|
||||||
history.load_pair_history(datadir=testdatadir,
|
history.load_pair_history(datadir=testdatadir,
|
||||||
ticker_interval='1m',
|
timeframe='1m',
|
||||||
pair='MEME/BTC')
|
pair='MEME/BTC')
|
||||||
assert not file.is_file()
|
assert not file.is_file()
|
||||||
assert log_has(
|
assert log_has(
|
||||||
'No history data for pair: "MEME/BTC", interval: 1m. '
|
'No history data for pair: "MEME/BTC", timeframe: 1m. '
|
||||||
'Use `freqtrade download-data` to download the data', caplog
|
'Use `freqtrade download-data` to download the data', caplog
|
||||||
)
|
)
|
||||||
|
|
||||||
# download a new pair if refresh_pairs is set
|
# download a new pair if refresh_pairs is set
|
||||||
history.load_pair_history(datadir=testdatadir,
|
history.load_pair_history(datadir=testdatadir,
|
||||||
ticker_interval='1m',
|
timeframe='1m',
|
||||||
refresh_pairs=True,
|
refresh_pairs=True,
|
||||||
exchange=exchange,
|
exchange=exchange,
|
||||||
pair='MEME/BTC')
|
pair='MEME/BTC')
|
||||||
assert file.is_file()
|
assert file.is_file()
|
||||||
assert log_has_re(
|
assert log_has_re(
|
||||||
'Download history data for pair: "MEME/BTC", interval: 1m '
|
'Download history data for pair: "MEME/BTC", timeframe: 1m '
|
||||||
'and store in .*', caplog
|
'and store in .*', caplog
|
||||||
)
|
)
|
||||||
with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'):
|
with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'):
|
||||||
history.load_pair_history(datadir=testdatadir,
|
history.load_pair_history(datadir=testdatadir,
|
||||||
ticker_interval='1m',
|
timeframe='1m',
|
||||||
refresh_pairs=True,
|
refresh_pairs=True,
|
||||||
exchange=None,
|
exchange=None,
|
||||||
pair='MEME/BTC')
|
pair='MEME/BTC')
|
||||||
|
@ -269,10 +269,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf, testda
|
||||||
|
|
||||||
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||||
pair='MEME/BTC',
|
pair='MEME/BTC',
|
||||||
ticker_interval='1m')
|
timeframe='1m')
|
||||||
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||||
pair='CFI/BTC',
|
pair='CFI/BTC',
|
||||||
ticker_interval='1m')
|
timeframe='1m')
|
||||||
assert not exchange._pairs_last_refresh_time
|
assert not exchange._pairs_last_refresh_time
|
||||||
assert file1_1.is_file()
|
assert file1_1.is_file()
|
||||||
assert file2_1.is_file()
|
assert file2_1.is_file()
|
||||||
|
@ -286,10 +286,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf, testda
|
||||||
|
|
||||||
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||||
pair='MEME/BTC',
|
pair='MEME/BTC',
|
||||||
ticker_interval='5m')
|
timeframe='5m')
|
||||||
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||||
pair='CFI/BTC',
|
pair='CFI/BTC',
|
||||||
ticker_interval='5m')
|
timeframe='5m')
|
||||||
assert not exchange._pairs_last_refresh_time
|
assert not exchange._pairs_last_refresh_time
|
||||||
assert file1_5.is_file()
|
assert file1_5.is_file()
|
||||||
assert file2_5.is_file()
|
assert file2_5.is_file()
|
||||||
|
@ -307,8 +307,8 @@ def test_download_pair_history2(mocker, default_conf, testdatadir) -> None:
|
||||||
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick)
|
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick)
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", ticker_interval='1m')
|
download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", timeframe='1m')
|
||||||
download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", ticker_interval='3m')
|
download_pair_history(testdatadir, exchange, pair="UNITTEST/BTC", timeframe='3m')
|
||||||
assert json_dump_mock.call_count == 2
|
assert json_dump_mock.call_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
@ -326,12 +326,12 @@ def test_download_backtesting_data_exception(ticker_history, mocker, caplog,
|
||||||
|
|
||||||
assert not download_pair_history(datadir=testdatadir, exchange=exchange,
|
assert not download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||||
pair='MEME/BTC',
|
pair='MEME/BTC',
|
||||||
ticker_interval='1m')
|
timeframe='1m')
|
||||||
# clean files freshly downloaded
|
# clean files freshly downloaded
|
||||||
_clean_test_file(file1_1)
|
_clean_test_file(file1_1)
|
||||||
_clean_test_file(file1_5)
|
_clean_test_file(file1_5)
|
||||||
assert log_has(
|
assert log_has(
|
||||||
'Failed to download history data for pair: "MEME/BTC", interval: 1m. '
|
'Failed to download history data for pair: "MEME/BTC", timeframe: 1m. '
|
||||||
'Error: File Error', caplog
|
'Error: File Error', caplog
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -369,7 +369,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None:
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
start = arrow.get('2018-01-10T00:00:00')
|
start = arrow.get('2018-01-10T00:00:00')
|
||||||
end = arrow.get('2018-02-20T00:00:00')
|
end = arrow.get('2018-02-20T00:00:00')
|
||||||
tickerdata = history.load_data(datadir=testdatadir, ticker_interval='5m',
|
tickerdata = history.load_data(datadir=testdatadir, timeframe='5m',
|
||||||
pairs=['UNITTEST/BTC'],
|
pairs=['UNITTEST/BTC'],
|
||||||
timerange=TimeRange('date', 'date',
|
timerange=TimeRange('date', 'date',
|
||||||
start.timestamp, end.timestamp))
|
start.timestamp, end.timestamp))
|
||||||
|
@ -390,7 +390,7 @@ def test_init(default_conf, mocker) -> None:
|
||||||
exchange=exchange,
|
exchange=exchange,
|
||||||
pairs=[],
|
pairs=[],
|
||||||
refresh_pairs=True,
|
refresh_pairs=True,
|
||||||
ticker_interval=default_conf['ticker_interval']
|
timeframe=default_conf['ticker_interval']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -449,7 +449,7 @@ def test_trim_tickerlist(testdatadir) -> None:
|
||||||
def test_trim_dataframe(testdatadir) -> None:
|
def test_trim_dataframe(testdatadir) -> None:
|
||||||
data = history.load_data(
|
data = history.load_data(
|
||||||
datadir=testdatadir,
|
datadir=testdatadir,
|
||||||
ticker_interval='1m',
|
timeframe='1m',
|
||||||
pairs=['UNITTEST/BTC']
|
pairs=['UNITTEST/BTC']
|
||||||
)['UNITTEST/BTC']
|
)['UNITTEST/BTC']
|
||||||
min_date = int(data.iloc[0]['date'].timestamp())
|
min_date = int(data.iloc[0]['date'].timestamp())
|
||||||
|
@ -517,7 +517,7 @@ def test_get_timeframe(default_conf, mocker, testdatadir) -> None:
|
||||||
data = strategy.tickerdata_to_dataframe(
|
data = strategy.tickerdata_to_dataframe(
|
||||||
history.load_data(
|
history.load_data(
|
||||||
datadir=testdatadir,
|
datadir=testdatadir,
|
||||||
ticker_interval='1m',
|
timeframe='1m',
|
||||||
pairs=['UNITTEST/BTC']
|
pairs=['UNITTEST/BTC']
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -533,7 +533,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir)
|
||||||
data = strategy.tickerdata_to_dataframe(
|
data = strategy.tickerdata_to_dataframe(
|
||||||
history.load_data(
|
history.load_data(
|
||||||
datadir=testdatadir,
|
datadir=testdatadir,
|
||||||
ticker_interval='1m',
|
timeframe='1m',
|
||||||
pairs=['UNITTEST/BTC'],
|
pairs=['UNITTEST/BTC'],
|
||||||
fill_up_missing=False
|
fill_up_missing=False
|
||||||
)
|
)
|
||||||
|
@ -556,7 +556,7 @@ def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> No
|
||||||
data = strategy.tickerdata_to_dataframe(
|
data = strategy.tickerdata_to_dataframe(
|
||||||
history.load_data(
|
history.load_data(
|
||||||
datadir=testdatadir,
|
datadir=testdatadir,
|
||||||
ticker_interval='5m',
|
timeframe='5m',
|
||||||
pairs=['UNITTEST/BTC'],
|
pairs=['UNITTEST/BTC'],
|
||||||
timerange=timerange
|
timerange=timerange
|
||||||
)
|
)
|
||||||
|
@ -669,10 +669,10 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog):
|
||||||
file5 = testdatadir / 'XRP_ETH-5m.json'
|
file5 = testdatadir / 'XRP_ETH-5m.json'
|
||||||
# Compare downloaded dataset with converted dataset
|
# Compare downloaded dataset with converted dataset
|
||||||
dfbak_1m = history.load_pair_history(datadir=testdatadir,
|
dfbak_1m = history.load_pair_history(datadir=testdatadir,
|
||||||
ticker_interval="1m",
|
timeframe="1m",
|
||||||
pair=pair)
|
pair=pair)
|
||||||
dfbak_5m = history.load_pair_history(datadir=testdatadir,
|
dfbak_5m = history.load_pair_history(datadir=testdatadir,
|
||||||
ticker_interval="5m",
|
timeframe="5m",
|
||||||
pair=pair)
|
pair=pair)
|
||||||
|
|
||||||
_backup_file(file1, copy_file=True)
|
_backup_file(file1, copy_file=True)
|
||||||
|
@ -686,10 +686,10 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog):
|
||||||
assert log_has("Deleting existing data for pair XRP/ETH, interval 1m.", caplog)
|
assert log_has("Deleting existing data for pair XRP/ETH, interval 1m.", caplog)
|
||||||
# Load new data
|
# Load new data
|
||||||
df_1m = history.load_pair_history(datadir=testdatadir,
|
df_1m = history.load_pair_history(datadir=testdatadir,
|
||||||
ticker_interval="1m",
|
timeframe="1m",
|
||||||
pair=pair)
|
pair=pair)
|
||||||
df_5m = history.load_pair_history(datadir=testdatadir,
|
df_5m = history.load_pair_history(datadir=testdatadir,
|
||||||
ticker_interval="5m",
|
timeframe="5m",
|
||||||
pair=pair)
|
pair=pair)
|
||||||
|
|
||||||
assert df_1m.equals(dfbak_1m)
|
assert df_1m.equals(dfbak_1m)
|
||||||
|
|
|
@ -255,7 +255,7 @@ def test_edge_heartbeat_calculate(mocker, edge_conf):
|
||||||
assert edge.calculate() is False
|
assert edge.calculate() is False
|
||||||
|
|
||||||
|
|
||||||
def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
|
def mocked_load_data(datadir, pairs=[], timeframe='0m', refresh_pairs=False,
|
||||||
timerange=None, exchange=None, *args, **kwargs):
|
timerange=None, exchange=None, *args, **kwargs):
|
||||||
hz = 0.1
|
hz = 0.1
|
||||||
base = 0.001
|
base = 0.001
|
||||||
|
|
|
@ -1047,8 +1047,8 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name):
|
||||||
]
|
]
|
||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
|
|
||||||
async def mock_candle_hist(pair, ticker_interval, since_ms):
|
async def mock_candle_hist(pair, timeframe, since_ms):
|
||||||
return pair, ticker_interval, tick
|
return pair, timeframe, tick
|
||||||
|
|
||||||
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
|
||||||
# one_call calculation * 1.8 should do 2 calls
|
# one_call calculation * 1.8 should do 2 calls
|
||||||
|
@ -1107,7 +1107,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
|
||||||
exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
|
exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
|
||||||
|
|
||||||
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
||||||
assert log_has(f"Using cached ohlcv data for pair {pairs[0][0]}, interval {pairs[0][1]} ...",
|
assert log_has(f"Using cached ohlcv data for pair {pairs[0][0]}, timeframe {pairs[0][1]} ...",
|
||||||
caplog)
|
caplog)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1143,7 +1143,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
|
||||||
# exchange = Exchange(default_conf)
|
# exchange = Exchange(default_conf)
|
||||||
await async_ccxt_exception(mocker, default_conf, MagicMock(),
|
await async_ccxt_exception(mocker, default_conf, MagicMock(),
|
||||||
"_async_get_candle_history", "fetch_ohlcv",
|
"_async_get_candle_history", "fetch_ohlcv",
|
||||||
pair='ABCD/BTC', ticker_interval=default_conf['ticker_interval'])
|
pair='ABCD/BTC', timeframe=default_conf['ticker_interval'])
|
||||||
|
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'):
|
with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'):
|
||||||
|
|
|
@ -7,7 +7,7 @@ from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
|
|
||||||
ticker_start_time = arrow.get(2018, 10, 3)
|
ticker_start_time = arrow.get(2018, 10, 3)
|
||||||
tests_ticker_interval = '1h'
|
tests_timeframe = '1h'
|
||||||
|
|
||||||
|
|
||||||
class BTrade(NamedTuple):
|
class BTrade(NamedTuple):
|
||||||
|
@ -36,7 +36,7 @@ class BTContainer(NamedTuple):
|
||||||
|
|
||||||
|
|
||||||
def _get_frame_time_from_offset(offset):
|
def _get_frame_time_from_offset(offset):
|
||||||
return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_ticker_interval))
|
return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_timeframe))
|
||||||
).datetime
|
).datetime
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from freqtrade.optimize.backtesting import Backtesting
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
from tests.conftest import patch_exchange
|
from tests.conftest import patch_exchange
|
||||||
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
|
from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
|
||||||
_get_frame_time_from_offset, tests_ticker_interval)
|
_get_frame_time_from_offset, tests_timeframe)
|
||||||
|
|
||||||
# Test 0: Sell with signal sell in candle 3
|
# Test 0: Sell with signal sell in candle 3
|
||||||
# Test with Stop-loss at 1%
|
# Test with Stop-loss at 1%
|
||||||
|
@ -293,7 +293,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||||
"""
|
"""
|
||||||
default_conf["stoploss"] = data.stop_loss
|
default_conf["stoploss"] = data.stop_loss
|
||||||
default_conf["minimal_roi"] = data.roi
|
default_conf["minimal_roi"] = data.roi
|
||||||
default_conf["ticker_interval"] = tests_ticker_interval
|
default_conf["ticker_interval"] = tests_timeframe
|
||||||
default_conf["trailing_stop"] = data.trailing_stop
|
default_conf["trailing_stop"] = data.trailing_stop
|
||||||
default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
|
default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
|
||||||
# Only add this to configuration If it's necessary
|
# Only add this to configuration If it's necessary
|
||||||
|
|
|
@ -50,7 +50,7 @@ def trim_dictlist(dict_list, num):
|
||||||
|
|
||||||
def load_data_test(what, testdatadir):
|
def load_data_test(what, testdatadir):
|
||||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||||
pair = history.load_tickerdata_file(testdatadir, ticker_interval='1m',
|
pair = history.load_tickerdata_file(testdatadir, timeframe='1m',
|
||||||
pair='UNITTEST/BTC', timerange=timerange)
|
pair='UNITTEST/BTC', timerange=timerange)
|
||||||
datalen = len(pair)
|
datalen = len(pair)
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ def simple_backtest(config, contour, num_results, mocker, testdatadir) -> None:
|
||||||
assert len(results) == num_results
|
assert len(results) == num_results
|
||||||
|
|
||||||
|
|
||||||
def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
|
def mocked_load_data(datadir, pairs=[], timeframe='0m', refresh_pairs=False,
|
||||||
timerange=None, exchange=None, live=False, *args, **kwargs):
|
timerange=None, exchange=None, live=False, *args, **kwargs):
|
||||||
tickerdata = history.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
tickerdata = history.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||||
pairdata = {'UNITTEST/BTC': parse_ticker_dataframe(tickerdata, '1m', pair="UNITTEST/BTC",
|
pairdata = {'UNITTEST/BTC': parse_ticker_dataframe(tickerdata, '1m', pair="UNITTEST/BTC",
|
||||||
|
@ -126,14 +126,14 @@ def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=Fals
|
||||||
|
|
||||||
# use for mock ccxt.fetch_ohlvc'
|
# use for mock ccxt.fetch_ohlvc'
|
||||||
def _load_pair_as_ticks(pair, tickfreq):
|
def _load_pair_as_ticks(pair, tickfreq):
|
||||||
ticks = history.load_tickerdata_file(None, ticker_interval=tickfreq, pair=pair)
|
ticks = history.load_tickerdata_file(None, timeframe=tickfreq, pair=pair)
|
||||||
ticks = ticks[-201:]
|
ticks = ticks[-201:]
|
||||||
return ticks
|
return ticks
|
||||||
|
|
||||||
|
|
||||||
# FIX: fixturize this?
|
# FIX: fixturize this?
|
||||||
def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC', record=None):
|
def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC', record=None):
|
||||||
data = history.load_data(datadir=datadir, ticker_interval='1m', pairs=[pair])
|
data = history.load_data(datadir=datadir, timeframe='1m', pairs=[pair])
|
||||||
data = trim_dictlist(data, -201)
|
data = trim_dictlist(data, -201)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtesting = Backtesting(conf)
|
backtesting = Backtesting(conf)
|
||||||
|
@ -184,9 +184,9 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'backtesting'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_configuration(get_args(args), RunMode.BACKTEST)
|
config = setup_configuration(get_args(args), RunMode.BACKTEST)
|
||||||
|
@ -217,10 +217,10 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
||||||
)
|
)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'backtesting',
|
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
'--disable-max-market-positions',
|
'--disable-max-market-positions',
|
||||||
|
@ -269,9 +269,9 @@ def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'backtesting'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||||
|
@ -286,9 +286,9 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'backtesting'
|
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start_backtesting(args)
|
start_backtesting(args)
|
||||||
|
@ -307,7 +307,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
|
||||||
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
assert backtesting.config == default_conf
|
assert backtesting.config == default_conf
|
||||||
assert backtesting.ticker_interval == '5m'
|
assert backtesting.timeframe == '5m'
|
||||||
assert callable(backtesting.strategy.tickerdata_to_dataframe)
|
assert callable(backtesting.strategy.tickerdata_to_dataframe)
|
||||||
assert callable(backtesting.strategy.advise_buy)
|
assert callable(backtesting.strategy.advise_buy)
|
||||||
assert callable(backtesting.strategy.advise_sell)
|
assert callable(backtesting.strategy.advise_sell)
|
||||||
|
@ -522,7 +522,7 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
pair = 'UNITTEST/BTC'
|
pair = 'UNITTEST/BTC'
|
||||||
timerange = TimeRange('date', None, 1517227800, 0)
|
timerange = TimeRange('date', None, 1517227800, 0)
|
||||||
data = history.load_data(datadir=testdatadir, ticker_interval='5m', pairs=['UNITTEST/BTC'],
|
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
|
||||||
timerange=timerange)
|
timerange=timerange)
|
||||||
data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
||||||
min_date, max_date = get_timeframe(data_processed)
|
min_date, max_date = get_timeframe(data_processed)
|
||||||
|
@ -576,9 +576,9 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker, testdatadir) -
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
# Run a backtesting for an exiting 1min ticker_interval
|
# Run a backtesting for an exiting 1min timeframe
|
||||||
timerange = TimeRange.parse_timerange('1510688220-1510700340')
|
timerange = TimeRange.parse_timerange('1510688220-1510700340')
|
||||||
data = history.load_data(datadir=testdatadir, ticker_interval='1m', pairs=['UNITTEST/BTC'],
|
data = history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'],
|
||||||
timerange=timerange)
|
timerange=timerange)
|
||||||
processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
||||||
min_date, max_date = get_timeframe(processed)
|
min_date, max_date = get_timeframe(processed)
|
||||||
|
@ -688,7 +688,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
|
pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
|
||||||
data = history.load_data(datadir=testdatadir, ticker_interval='5m', pairs=pairs)
|
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=pairs)
|
||||||
# Only use 500 lines to increase performance
|
# Only use 500 lines to increase performance
|
||||||
data = trim_dictlist(data, -500)
|
data = trim_dictlist(data, -500)
|
||||||
|
|
||||||
|
@ -817,10 +817,10 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'--datadir', str(testdatadir),
|
'--datadir', str(testdatadir),
|
||||||
'backtesting',
|
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--timerange', '1510694220-1510700340',
|
'--timerange', '1510694220-1510700340',
|
||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
|
@ -866,9 +866,9 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--datadir', str(testdatadir),
|
'--datadir', str(testdatadir),
|
||||||
'backtesting',
|
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--timerange', '1510694220-1510700340',
|
'--timerange', '1510694220-1510700340',
|
||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
|
|
|
@ -15,9 +15,9 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'edge',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'edge'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_configuration(get_args(args), RunMode.EDGE)
|
config = setup_configuration(get_args(args), RunMode.EDGE)
|
||||||
|
@ -45,10 +45,10 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N
|
||||||
)
|
)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'edge',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'edge',
|
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--timerange', ':100',
|
'--timerange', ':100',
|
||||||
'--stoplosses=-0.01,-0.10,-0.001'
|
'--stoplosses=-0.01,-0.10,-0.001'
|
||||||
|
@ -79,9 +79,9 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
|
||||||
patched_configuration_load_config_file(mocker, edge_conf)
|
patched_configuration_load_config_file(mocker, edge_conf)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'edge',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'edge'
|
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start_edge(args)
|
start_edge(args)
|
||||||
|
|
|
@ -26,7 +26,10 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def hyperopt(default_conf, mocker):
|
def hyperopt(default_conf, mocker):
|
||||||
default_conf.update({'spaces': ['all']})
|
default_conf.update({
|
||||||
|
'spaces': ['all'],
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
|
})
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
return Hyperopt(default_conf)
|
return Hyperopt(default_conf)
|
||||||
|
|
||||||
|
@ -69,8 +72,9 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'hyperopt'
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_configuration(get_args(args), RunMode.HYPEROPT)
|
config = setup_configuration(get_args(args), RunMode.HYPEROPT)
|
||||||
|
@ -100,9 +104,10 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
||||||
)
|
)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json',
|
|
||||||
'--datadir', '/foo/bar',
|
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
|
'--datadir', '/foo/bar',
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--timerange', ':100',
|
'--timerange', ':100',
|
||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
|
@ -157,7 +162,8 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
||||||
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver._load_hyperopt',
|
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver._load_hyperopt',
|
||||||
MagicMock(return_value=hyperopt(default_conf))
|
MagicMock(return_value=hyperopt(default_conf))
|
||||||
)
|
)
|
||||||
x = HyperOptResolver(default_conf, ).hyperopt
|
default_conf.update({'hyperopt': 'DefaultHyperOpt'})
|
||||||
|
x = HyperOptResolver(default_conf).hyperopt
|
||||||
assert not hasattr(x, 'populate_indicators')
|
assert not hasattr(x, 'populate_indicators')
|
||||||
assert not hasattr(x, 'populate_buy_trend')
|
assert not hasattr(x, 'populate_buy_trend')
|
||||||
assert not hasattr(x, 'populate_sell_trend')
|
assert not hasattr(x, 'populate_sell_trend')
|
||||||
|
@ -174,7 +180,15 @@ def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None:
|
||||||
default_conf.update({'hyperopt': "NonExistingHyperoptClass"})
|
default_conf.update({'hyperopt': "NonExistingHyperoptClass"})
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=r'Impossible to load Hyperopt.*'):
|
with pytest.raises(OperationalException, match=r'Impossible to load Hyperopt.*'):
|
||||||
HyperOptResolver(default_conf, ).hyperopt
|
HyperOptResolver(default_conf).hyperopt
|
||||||
|
|
||||||
|
|
||||||
|
def test_hyperoptresolver_noname(default_conf):
|
||||||
|
default_conf['hyperopt'] = ''
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match="No Hyperopt set. Please use `--hyperopt` to specify "
|
||||||
|
"the Hyperopt class to use."):
|
||||||
|
HyperOptResolver(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None:
|
def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None:
|
||||||
|
@ -184,7 +198,7 @@ def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None:
|
||||||
'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver._load_hyperoptloss',
|
'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver._load_hyperoptloss',
|
||||||
MagicMock(return_value=hl)
|
MagicMock(return_value=hl)
|
||||||
)
|
)
|
||||||
x = HyperOptLossResolver(default_conf, ).hyperoptloss
|
x = HyperOptLossResolver(default_conf).hyperoptloss
|
||||||
assert hasattr(x, "hyperopt_loss_function")
|
assert hasattr(x, "hyperopt_loss_function")
|
||||||
|
|
||||||
|
|
||||||
|
@ -192,7 +206,7 @@ def test_hyperoptlossresolver_wrongname(mocker, default_conf, caplog) -> None:
|
||||||
default_conf.update({'hyperopt_loss': "NonExistingLossClass"})
|
default_conf.update({'hyperopt_loss': "NonExistingLossClass"})
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=r'Impossible to load HyperoptLoss.*'):
|
with pytest.raises(OperationalException, match=r'Impossible to load HyperoptLoss.*'):
|
||||||
HyperOptLossResolver(default_conf, ).hyperopt
|
HyperOptLossResolver(default_conf).hyperopt
|
||||||
|
|
||||||
|
|
||||||
def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None:
|
def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None:
|
||||||
|
@ -203,8 +217,9 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json',
|
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
|
@ -220,8 +235,9 @@ def test_start(mocker, default_conf, caplog) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json',
|
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
|
@ -242,8 +258,9 @@ def test_start_no_data(mocker, default_conf, caplog) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json',
|
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
|
@ -258,8 +275,9 @@ def test_start_filelock(mocker, default_conf, caplog) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json',
|
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
|
@ -412,6 +430,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': 'all',
|
'spaces': 'all',
|
||||||
|
@ -515,10 +534,12 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_generate_optimizer(mocker, default_conf) -> None:
|
def test_generate_optimizer(mocker, default_conf) -> None:
|
||||||
default_conf.update({'config': 'config.json.example'})
|
default_conf.update({'config': 'config.json.example',
|
||||||
default_conf.update({'timerange': None})
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
default_conf.update({'spaces': 'all'})
|
'timerange': None,
|
||||||
default_conf.update({'hyperopt_min_trades': 1})
|
'spaces': 'all',
|
||||||
|
'hyperopt_min_trades': 1,
|
||||||
|
})
|
||||||
|
|
||||||
trades = [
|
trades = [
|
||||||
('TRX/BTC', 0.023117, 0.000233, 100)
|
('TRX/BTC', 0.023117, 0.000233, 100)
|
||||||
|
@ -582,6 +603,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
|
||||||
def test_clean_hyperopt(mocker, default_conf, caplog):
|
def test_clean_hyperopt(mocker, default_conf, caplog):
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': 'all',
|
'spaces': 'all',
|
||||||
|
@ -598,6 +620,7 @@ def test_clean_hyperopt(mocker, default_conf, caplog):
|
||||||
def test_continue_hyperopt(mocker, default_conf, caplog):
|
def test_continue_hyperopt(mocker, default_conf, caplog):
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': 'all',
|
'spaces': 'all',
|
||||||
|
@ -628,6 +651,7 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': 'all',
|
'spaces': 'all',
|
||||||
|
@ -666,6 +690,7 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) ->
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': 'roi stoploss',
|
'spaces': 'roi stoploss',
|
||||||
|
@ -705,6 +730,7 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': 'roi stoploss',
|
'spaces': 'roi stoploss',
|
||||||
|
@ -747,6 +773,7 @@ def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) -
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': 'all',
|
'spaces': 'all',
|
||||||
|
@ -781,6 +808,7 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': 'buy',
|
'spaces': 'buy',
|
||||||
|
@ -827,6 +855,7 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': 'sell',
|
'spaces': 'sell',
|
||||||
|
@ -875,6 +904,7 @@ def test_simplified_interface_failed(mocker, default_conf, caplog, capsys, metho
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'config': 'config.json.example',
|
default_conf.update({'config': 'config.json.example',
|
||||||
|
'hyperopt': 'DefaultHyperOpt',
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
'timerange': None,
|
'timerange': None,
|
||||||
'spaces': space,
|
'spaces': space,
|
||||||
|
|
|
@ -96,6 +96,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.rpc.fiat_convert.Market',
|
||||||
|
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||||
|
)
|
||||||
|
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
|
@ -109,22 +114,34 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||||
|
|
||||||
freqtradebot.state = State.RUNNING
|
freqtradebot.state = State.RUNNING
|
||||||
with pytest.raises(RPCException, match=r'.*no active order*'):
|
with pytest.raises(RPCException, match=r'.*no active order*'):
|
||||||
rpc._rpc_status_table()
|
rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||||
|
|
||||||
freqtradebot.create_trades()
|
freqtradebot.create_trades()
|
||||||
result = rpc._rpc_status_table()
|
|
||||||
assert 'instantly' in result['Since'].all()
|
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||||
assert 'ETH/BTC' in result['Pair'].all()
|
assert "Since" in headers
|
||||||
assert '-0.59%' in result['Profit'].all()
|
assert "Pair" in headers
|
||||||
|
assert 'instantly' == result[0][2]
|
||||||
|
assert 'ETH/BTC' == result[0][1]
|
||||||
|
assert '-0.59%' == result[0][3]
|
||||||
|
# Test with fiatconvert
|
||||||
|
|
||||||
|
rpc._fiat_converter = CryptoToFiatConverter()
|
||||||
|
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||||
|
assert "Since" in headers
|
||||||
|
assert "Pair" in headers
|
||||||
|
assert 'instantly' == result[0][2]
|
||||||
|
assert 'ETH/BTC' == result[0][1]
|
||||||
|
assert '-0.59% (-0.09)' == result[0][3]
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
||||||
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
|
||||||
# invalidate ticker cache
|
# invalidate ticker cache
|
||||||
rpc._freqtrade.exchange._cached_ticker = {}
|
rpc._freqtrade.exchange._cached_ticker = {}
|
||||||
result = rpc._rpc_status_table()
|
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||||
assert 'instantly' in result['Since'].all()
|
assert 'instantly' == result[0][2]
|
||||||
assert 'ETH/BTC' in result['Pair'].all()
|
assert 'ETH/BTC' == result[0][1]
|
||||||
assert 'nan%' in result['Profit'].all()
|
assert 'nan%' == result[0][3]
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||||
|
|
|
@ -64,6 +64,10 @@ def test_api_not_found(botclient):
|
||||||
|
|
||||||
def test_api_unauthorized(botclient):
|
def test_api_unauthorized(botclient):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
|
rc = client.get(f"{BASE_URI}/ping")
|
||||||
|
assert_response(rc)
|
||||||
|
assert rc.json == {'status': 'pong'}
|
||||||
|
|
||||||
# Don't send user/pass information
|
# Don't send user/pass information
|
||||||
rc = client.get(f"{BASE_URI}/version")
|
rc = client.get(f"{BASE_URI}/version")
|
||||||
assert_response(rc, 401)
|
assert_response(rc, 401)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
import time
|
||||||
import logging
|
import logging
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
@ -176,6 +176,8 @@ def test_init_apiserver_enabled(mocker, default_conf, caplog) -> None:
|
||||||
"listen_port": "8080"}
|
"listen_port": "8080"}
|
||||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||||
|
|
||||||
|
# Sleep to allow the thread to start
|
||||||
|
time.sleep(0.5)
|
||||||
assert log_has('Enabling rpc.api_server', caplog)
|
assert log_has('Enabling rpc.api_server', caplog)
|
||||||
assert len(rpc_manager.registered_modules) == 1
|
assert len(rpc_manager.registered_modules) == 1
|
||||||
assert 'apiserver' in [mod.name for mod in rpc_manager.registered_modules]
|
assert 'apiserver' in [mod.name for mod in rpc_manager.registered_modules]
|
||||||
|
|
|
@ -54,21 +54,30 @@ def test_load_strategy_base64(result, caplog, default_conf):
|
||||||
|
|
||||||
|
|
||||||
def test_load_strategy_invalid_directory(result, caplog, default_conf):
|
def test_load_strategy_invalid_directory(result, caplog, default_conf):
|
||||||
|
default_conf['strategy'] = 'SampleStrategy'
|
||||||
resolver = StrategyResolver(default_conf)
|
resolver = StrategyResolver(default_conf)
|
||||||
extra_dir = Path.cwd() / 'some/path'
|
extra_dir = Path.cwd() / 'some/path'
|
||||||
resolver._load_strategy('SampleStrategy', config=default_conf, extra_dir=extra_dir)
|
resolver._load_strategy('SampleStrategy', config=default_conf, extra_dir=extra_dir)
|
||||||
|
|
||||||
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
|
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
|
||||||
|
|
||||||
assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
assert 'rsi' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||||
|
|
||||||
|
|
||||||
def test_load_not_found_strategy(default_conf):
|
def test_load_not_found_strategy(default_conf):
|
||||||
strategy = StrategyResolver(default_conf)
|
default_conf['strategy'] = 'NotFoundStrategy'
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match=r"Impossible to load Strategy 'NotFoundStrategy'. "
|
match=r"Impossible to load Strategy 'NotFoundStrategy'. "
|
||||||
r"This class does not exist or contains Python code errors."):
|
r"This class does not exist or contains Python code errors."):
|
||||||
strategy._load_strategy(strategy_name='NotFoundStrategy', config=default_conf)
|
StrategyResolver(default_conf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_strategy_noname(default_conf):
|
||||||
|
default_conf['strategy'] = ''
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match="No strategy set. Please use `--strategy` to specify "
|
||||||
|
"the strategy class to use."):
|
||||||
|
StrategyResolver(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_strategy(result, default_conf):
|
def test_strategy(result, default_conf):
|
||||||
|
|
|
@ -11,7 +11,7 @@ from freqtrade.configuration.cli_options import check_int_positive
|
||||||
|
|
||||||
# Parse common command-line-arguments. Used for all tools
|
# Parse common command-line-arguments. Used for all tools
|
||||||
def test_parse_args_none() -> None:
|
def test_parse_args_none() -> None:
|
||||||
arguments = Arguments([])
|
arguments = Arguments(['trade'])
|
||||||
assert isinstance(arguments, Arguments)
|
assert isinstance(arguments, Arguments)
|
||||||
x = arguments.get_parsed_arg()
|
x = arguments.get_parsed_arg()
|
||||||
assert isinstance(x, dict)
|
assert isinstance(x, dict)
|
||||||
|
@ -19,7 +19,7 @@ def test_parse_args_none() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_defaults() -> None:
|
def test_parse_args_defaults() -> None:
|
||||||
args = Arguments([]).get_parsed_arg()
|
args = Arguments(['trade']).get_parsed_arg()
|
||||||
assert args["config"] == ['config.json']
|
assert args["config"] == ['config.json']
|
||||||
assert args["strategy_path"] is None
|
assert args["strategy_path"] is None
|
||||||
assert args["datadir"] is None
|
assert args["datadir"] is None
|
||||||
|
@ -27,27 +27,27 @@ def test_parse_args_defaults() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_config() -> None:
|
def test_parse_args_config() -> None:
|
||||||
args = Arguments(['-c', '/dev/null']).get_parsed_arg()
|
args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg()
|
||||||
assert args["config"] == ['/dev/null']
|
assert args["config"] == ['/dev/null']
|
||||||
|
|
||||||
args = Arguments(['--config', '/dev/null']).get_parsed_arg()
|
args = Arguments(['trade', '--config', '/dev/null']).get_parsed_arg()
|
||||||
assert args["config"] == ['/dev/null']
|
assert args["config"] == ['/dev/null']
|
||||||
|
|
||||||
args = Arguments(['--config', '/dev/null',
|
args = Arguments(['trade', '--config', '/dev/null',
|
||||||
'--config', '/dev/zero'],).get_parsed_arg()
|
'--config', '/dev/zero'],).get_parsed_arg()
|
||||||
assert args["config"] == ['/dev/null', '/dev/zero']
|
assert args["config"] == ['/dev/null', '/dev/zero']
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_db_url() -> None:
|
def test_parse_args_db_url() -> None:
|
||||||
args = Arguments(['--db-url', 'sqlite:///test.sqlite']).get_parsed_arg()
|
args = Arguments(['trade', '--db-url', 'sqlite:///test.sqlite']).get_parsed_arg()
|
||||||
assert args["db_url"] == 'sqlite:///test.sqlite'
|
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(['trade', '-v']).get_parsed_arg()
|
||||||
assert args["verbosity"] == 1
|
assert args["verbosity"] == 1
|
||||||
|
|
||||||
args = Arguments(['--verbose']).get_parsed_arg()
|
args = Arguments(['trade', '--verbose']).get_parsed_arg()
|
||||||
assert args["verbosity"] == 1
|
assert args["verbosity"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ def test_parse_args_invalid() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_strategy() -> None:
|
def test_parse_args_strategy() -> None:
|
||||||
args = Arguments(['--strategy', 'SomeStrategy']).get_parsed_arg()
|
args = Arguments(['trade', '--strategy', 'SomeStrategy']).get_parsed_arg()
|
||||||
assert args["strategy"] == 'SomeStrategy'
|
assert args["strategy"] == 'SomeStrategy'
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ def test_parse_args_strategy_invalid() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_strategy_path() -> None:
|
def test_parse_args_strategy_path() -> None:
|
||||||
args = Arguments(['--strategy-path', '/some/path']).get_parsed_arg()
|
args = Arguments(['trade', '--strategy-path', '/some/path']).get_parsed_arg()
|
||||||
assert args["strategy_path"] == '/some/path'
|
assert args["strategy_path"] == '/some/path'
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,8 +98,8 @@ def test_parse_args_backtesting_invalid() -> None:
|
||||||
|
|
||||||
def test_parse_args_backtesting_custom() -> None:
|
def test_parse_args_backtesting_custom() -> None:
|
||||||
args = [
|
args = [
|
||||||
'-c', 'test_conf.json',
|
|
||||||
'backtesting',
|
'backtesting',
|
||||||
|
'-c', 'test_conf.json',
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'DefaultStrategy',
|
||||||
|
@ -108,7 +108,7 @@ def test_parse_args_backtesting_custom() -> None:
|
||||||
call_args = Arguments(args).get_parsed_arg()
|
call_args = Arguments(args).get_parsed_arg()
|
||||||
assert call_args["config"] == ['test_conf.json']
|
assert call_args["config"] == ['test_conf.json']
|
||||||
assert call_args["verbosity"] == 0
|
assert call_args["verbosity"] == 0
|
||||||
assert call_args["subparser"] == 'backtesting'
|
assert call_args["command"] == 'backtesting'
|
||||||
assert call_args["func"] is not None
|
assert call_args["func"] is not None
|
||||||
assert call_args["ticker_interval"] == '1m'
|
assert call_args["ticker_interval"] == '1m'
|
||||||
assert type(call_args["strategy_list"]) is list
|
assert type(call_args["strategy_list"]) is list
|
||||||
|
@ -117,8 +117,8 @@ def test_parse_args_backtesting_custom() -> None:
|
||||||
|
|
||||||
def test_parse_args_hyperopt_custom() -> None:
|
def test_parse_args_hyperopt_custom() -> None:
|
||||||
args = [
|
args = [
|
||||||
'-c', 'test_conf.json',
|
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
|
'-c', 'test_conf.json',
|
||||||
'--epochs', '20',
|
'--epochs', '20',
|
||||||
'--spaces', 'buy'
|
'--spaces', 'buy'
|
||||||
]
|
]
|
||||||
|
@ -126,7 +126,7 @@ def test_parse_args_hyperopt_custom() -> None:
|
||||||
assert call_args["config"] == ['test_conf.json']
|
assert call_args["config"] == ['test_conf.json']
|
||||||
assert call_args["epochs"] == 20
|
assert call_args["epochs"] == 20
|
||||||
assert call_args["verbosity"] == 0
|
assert call_args["verbosity"] == 0
|
||||||
assert call_args["subparser"] == 'hyperopt'
|
assert call_args["command"] == 'hyperopt'
|
||||||
assert call_args["spaces"] == ['buy']
|
assert call_args["spaces"] == ['buy']
|
||||||
assert call_args["func"] is not None
|
assert call_args["func"] is not None
|
||||||
assert callable(call_args["func"])
|
assert callable(call_args["func"])
|
||||||
|
@ -134,8 +134,8 @@ def test_parse_args_hyperopt_custom() -> None:
|
||||||
|
|
||||||
def test_download_data_options() -> None:
|
def test_download_data_options() -> None:
|
||||||
args = [
|
args = [
|
||||||
'--datadir', 'datadir/directory',
|
|
||||||
'download-data',
|
'download-data',
|
||||||
|
'--datadir', 'datadir/directory',
|
||||||
'--pairs-file', 'file_with_pairs',
|
'--pairs-file', 'file_with_pairs',
|
||||||
'--days', '30',
|
'--days', '30',
|
||||||
'--exchange', 'binance'
|
'--exchange', 'binance'
|
||||||
|
@ -150,8 +150,8 @@ def test_download_data_options() -> None:
|
||||||
|
|
||||||
def test_plot_dataframe_options() -> None:
|
def test_plot_dataframe_options() -> None:
|
||||||
args = [
|
args = [
|
||||||
'-c', 'config.json.example',
|
|
||||||
'plot-dataframe',
|
'plot-dataframe',
|
||||||
|
'-c', 'config.json.example',
|
||||||
'--indicators1', 'sma10', 'sma100',
|
'--indicators1', 'sma10', 'sma100',
|
||||||
'--indicators2', 'macd', 'fastd', 'fastk',
|
'--indicators2', 'macd', 'fastd', 'fastk',
|
||||||
'--plot-limit', '30',
|
'--plot-limit', '30',
|
||||||
|
@ -186,7 +186,7 @@ def test_config_notallowed(mocker) -> None:
|
||||||
]
|
]
|
||||||
pargs = Arguments(args).get_parsed_arg()
|
pargs = Arguments(args).get_parsed_arg()
|
||||||
|
|
||||||
assert pargs["config"] is None
|
assert "config" not in pargs
|
||||||
|
|
||||||
# When file exists:
|
# When file exists:
|
||||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||||
|
@ -195,7 +195,7 @@ def test_config_notallowed(mocker) -> None:
|
||||||
]
|
]
|
||||||
pargs = Arguments(args).get_parsed_arg()
|
pargs = Arguments(args).get_parsed_arg()
|
||||||
# config is not added even if it exists, since create-userdir is in the notallowed list
|
# config is not added even if it exists, since create-userdir is in the notallowed list
|
||||||
assert pargs["config"] is None
|
assert "config" not in pargs
|
||||||
|
|
||||||
|
|
||||||
def test_config_notrequired(mocker) -> None:
|
def test_config_notrequired(mocker) -> None:
|
||||||
|
|
|
@ -68,7 +68,7 @@ def test_load_config_file(default_conf, mocker, caplog) -> None:
|
||||||
|
|
||||||
def test__args_to_config(caplog):
|
def test__args_to_config(caplog):
|
||||||
|
|
||||||
arg_list = ['--strategy-path', 'TestTest']
|
arg_list = ['trade', '--strategy-path', 'TestTest']
|
||||||
args = Arguments(arg_list).get_parsed_arg()
|
args = Arguments(arg_list).get_parsed_arg()
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
config = {}
|
config = {}
|
||||||
|
@ -96,7 +96,7 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
|
||||||
default_conf['max_open_trades'] = 0
|
default_conf['max_open_trades'] = 0
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = Arguments([]).get_parsed_arg()
|
args = Arguments(['trade']).get_parsed_arg()
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
validated_conf = configuration.load_config()
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None:
|
||||||
configsmock
|
configsmock
|
||||||
)
|
)
|
||||||
|
|
||||||
arg_list = ['-c', 'test_conf.json', '--config', 'test2_conf.json', ]
|
arg_list = ['trade', '-c', 'test_conf.json', '--config', 'test2_conf.json', ]
|
||||||
args = Arguments(arg_list).get_parsed_arg()
|
args = Arguments(arg_list).get_parsed_arg()
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
validated_conf = configuration.load_config()
|
validated_conf = configuration.load_config()
|
||||||
|
@ -187,7 +187,7 @@ def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) ->
|
||||||
default_conf['max_open_trades'] = -1
|
default_conf['max_open_trades'] = -1
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = Arguments([]).get_parsed_arg()
|
args = Arguments(['trade']).get_parsed_arg()
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
validated_conf = configuration.load_config()
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
|
@ -211,11 +211,10 @@ def test_load_config_file_exception(mocker) -> None:
|
||||||
def test_load_config(default_conf, mocker) -> None:
|
def test_load_config(default_conf, mocker) -> None:
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = Arguments([]).get_parsed_arg()
|
args = Arguments(['trade']).get_parsed_arg()
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
validated_conf = configuration.load_config()
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
assert validated_conf.get('strategy') == 'DefaultStrategy'
|
|
||||||
assert validated_conf.get('strategy_path') is None
|
assert validated_conf.get('strategy_path') is None
|
||||||
assert 'edge' not in validated_conf
|
assert 'edge' not in validated_conf
|
||||||
|
|
||||||
|
@ -224,6 +223,7 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
|
'trade',
|
||||||
'--strategy', 'TestStrategy',
|
'--strategy', 'TestStrategy',
|
||||||
'--strategy-path', '/some/path',
|
'--strategy-path', '/some/path',
|
||||||
'--db-url', 'sqlite:///someurl',
|
'--db-url', 'sqlite:///someurl',
|
||||||
|
@ -243,6 +243,7 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
||||||
patched_configuration_load_config_file(mocker, conf)
|
patched_configuration_load_config_file(mocker, conf)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
|
'trade',
|
||||||
'--strategy', 'TestStrategy',
|
'--strategy', 'TestStrategy',
|
||||||
'--strategy-path', '/some/path'
|
'--strategy-path', '/some/path'
|
||||||
]
|
]
|
||||||
|
@ -259,6 +260,7 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
||||||
patched_configuration_load_config_file(mocker, conf)
|
patched_configuration_load_config_file(mocker, conf)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
|
'trade',
|
||||||
'--strategy', 'TestStrategy',
|
'--strategy', 'TestStrategy',
|
||||||
'--strategy-path', '/some/path'
|
'--strategy-path', '/some/path'
|
||||||
]
|
]
|
||||||
|
@ -275,6 +277,7 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
||||||
patched_configuration_load_config_file(mocker, conf)
|
patched_configuration_load_config_file(mocker, conf)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
|
'trade',
|
||||||
'--strategy', 'TestStrategy',
|
'--strategy', 'TestStrategy',
|
||||||
'--strategy-path', '/some/path'
|
'--strategy-path', '/some/path'
|
||||||
]
|
]
|
||||||
|
@ -293,6 +296,7 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
||||||
patched_configuration_load_config_file(mocker, conf)
|
patched_configuration_load_config_file(mocker, conf)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
|
'trade',
|
||||||
'--strategy', 'TestStrategy',
|
'--strategy', 'TestStrategy',
|
||||||
'--strategy-path', '/some/path'
|
'--strategy-path', '/some/path'
|
||||||
]
|
]
|
||||||
|
@ -303,6 +307,23 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
||||||
assert validated_conf.get('db_url') == DEFAULT_DB_DRYRUN_URL
|
assert validated_conf.get('db_url') == DEFAULT_DB_DRYRUN_URL
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("config_value,expected,arglist", [
|
||||||
|
(True, True, ['trade', '--dry-run']), # Leave config untouched
|
||||||
|
(False, True, ['trade', '--dry-run']), # Override config untouched
|
||||||
|
(False, False, ['trade']), # Leave config untouched
|
||||||
|
(True, True, ['trade']), # Leave config untouched
|
||||||
|
])
|
||||||
|
def test_load_dry_run(default_conf, mocker, config_value, expected, arglist) -> None:
|
||||||
|
|
||||||
|
default_conf['dry_run'] = config_value
|
||||||
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
|
configuration = Configuration(Arguments(arglist).get_parsed_arg())
|
||||||
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
|
assert validated_conf.get('dry_run') is expected
|
||||||
|
|
||||||
|
|
||||||
def test_load_custom_strategy(default_conf, mocker) -> None:
|
def test_load_custom_strategy(default_conf, mocker) -> None:
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'CustomStrategy',
|
'strategy': 'CustomStrategy',
|
||||||
|
@ -310,7 +331,7 @@ def test_load_custom_strategy(default_conf, mocker) -> None:
|
||||||
})
|
})
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = Arguments([]).get_parsed_arg()
|
args = Arguments(['trade']).get_parsed_arg()
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
validated_conf = configuration.load_config()
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
|
@ -322,6 +343,7 @@ def test_show_info(default_conf, mocker, caplog) -> None:
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
|
'trade',
|
||||||
'--strategy', 'TestStrategy',
|
'--strategy', 'TestStrategy',
|
||||||
'--db-url', 'sqlite:///tmp/testdb',
|
'--db-url', 'sqlite:///tmp/testdb',
|
||||||
]
|
]
|
||||||
|
@ -338,9 +360,9 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'backtesting'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
args = Arguments(arglist).get_parsed_arg()
|
args = Arguments(arglist).get_parsed_arg()
|
||||||
|
@ -376,11 +398,11 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||||
lambda x, *args, **kwargs: Path(x)
|
lambda x, *args, **kwargs: Path(x)
|
||||||
)
|
)
|
||||||
arglist = [
|
arglist = [
|
||||||
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'--userdir', "/tmp/freqtrade",
|
'--userdir', "/tmp/freqtrade",
|
||||||
'backtesting',
|
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
'--disable-max-market-positions',
|
'--disable-max-market-positions',
|
||||||
|
@ -427,8 +449,8 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--config', 'config.json',
|
|
||||||
'backtesting',
|
'backtesting',
|
||||||
|
'--config', 'config.json',
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--export', '/bar/foo',
|
'--export', '/bar/foo',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
|
@ -568,7 +590,7 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
|
||||||
|
|
||||||
# Prevent setting loggers
|
# Prevent setting loggers
|
||||||
mocker.patch('freqtrade.loggers._set_loggers', MagicMock)
|
mocker.patch('freqtrade.loggers._set_loggers', MagicMock)
|
||||||
arglist = ['-vvv']
|
arglist = ['trade', '-vvv']
|
||||||
args = Arguments(arglist).get_parsed_arg()
|
args = Arguments(arglist).get_parsed_arg()
|
||||||
|
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
|
@ -620,7 +642,7 @@ def test_set_logfile(default_conf, mocker):
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--logfile', 'test_file.log',
|
'trade', '--logfile', 'test_file.log',
|
||||||
]
|
]
|
||||||
args = Arguments(arglist).get_parsed_arg()
|
args = Arguments(arglist).get_parsed_arg()
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
|
@ -636,7 +658,7 @@ def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None:
|
||||||
default_conf['forcebuy_enable'] = True
|
default_conf['forcebuy_enable'] = True
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
args = Arguments([]).get_parsed_arg()
|
args = Arguments(['trade']).get_parsed_arg()
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
validated_conf = configuration.load_config()
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
|
@ -847,8 +869,8 @@ def test_pairlist_resolving():
|
||||||
def test_pairlist_resolving_with_config(mocker, default_conf):
|
def test_pairlist_resolving_with_config(mocker, default_conf):
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
arglist = [
|
arglist = [
|
||||||
'--config', 'config.json',
|
|
||||||
'download-data',
|
'download-data',
|
||||||
|
'--config', 'config.json',
|
||||||
]
|
]
|
||||||
|
|
||||||
args = Arguments(arglist).get_parsed_arg()
|
args = Arguments(arglist).get_parsed_arg()
|
||||||
|
@ -861,8 +883,8 @@ def test_pairlist_resolving_with_config(mocker, default_conf):
|
||||||
|
|
||||||
# Override pairs
|
# Override pairs
|
||||||
arglist = [
|
arglist = [
|
||||||
'--config', 'config.json',
|
|
||||||
'download-data',
|
'download-data',
|
||||||
|
'--config', 'config.json',
|
||||||
'--pairs', 'ETH/BTC', 'XRP/BTC',
|
'--pairs', 'ETH/BTC', 'XRP/BTC',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -883,8 +905,8 @@ def test_pairlist_resolving_with_config_pl(mocker, default_conf):
|
||||||
mocker.patch.object(Path, "open", MagicMock(return_value=MagicMock()))
|
mocker.patch.object(Path, "open", MagicMock(return_value=MagicMock()))
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--config', 'config.json',
|
|
||||||
'download-data',
|
'download-data',
|
||||||
|
'--config', 'config.json',
|
||||||
'--pairs-file', 'pairs.json',
|
'--pairs-file', 'pairs.json',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -905,8 +927,8 @@ def test_pairlist_resolving_with_config_pl_not_exists(mocker, default_conf):
|
||||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||||
|
|
||||||
arglist = [
|
arglist = [
|
||||||
'--config', 'config.json',
|
|
||||||
'download-data',
|
'download-data',
|
||||||
|
'--config', 'config.json',
|
||||||
'--pairs-file', 'pairs.json',
|
'--pairs-file', 'pairs.json',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -11,10 +11,16 @@ from freqtrade.freqtradebot import FreqtradeBot
|
||||||
from freqtrade.main import main
|
from freqtrade.main import main
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
from freqtrade.worker import Worker
|
from freqtrade.worker import Worker
|
||||||
from tests.conftest import (log_has, patch_exchange,
|
from tests.conftest import (log_has, log_has_re, patch_exchange,
|
||||||
patched_configuration_load_config_file)
|
patched_configuration_load_config_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_args_None(caplog) -> None:
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
main([])
|
||||||
|
assert log_has_re(r"Usage of Freqtrade requires a subcommand.*", caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_backtesting(mocker) -> None:
|
def test_parse_args_backtesting(mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test that main() can start backtesting and also ensure we can pass some specific arguments
|
Test that main() can start backtesting and also ensure we can pass some specific arguments
|
||||||
|
@ -29,7 +35,7 @@ def test_parse_args_backtesting(mocker) -> None:
|
||||||
call_args = backtesting_mock.call_args[0][0]
|
call_args = backtesting_mock.call_args[0][0]
|
||||||
assert call_args["config"] == ['config.json']
|
assert call_args["config"] == ['config.json']
|
||||||
assert call_args["verbosity"] == 0
|
assert call_args["verbosity"] == 0
|
||||||
assert call_args["subparser"] == 'backtesting'
|
assert call_args["command"] == 'backtesting'
|
||||||
assert call_args["func"] is not None
|
assert call_args["func"] is not None
|
||||||
assert callable(call_args["func"])
|
assert callable(call_args["func"])
|
||||||
assert call_args["ticker_interval"] is None
|
assert call_args["ticker_interval"] is None
|
||||||
|
@ -45,7 +51,7 @@ def test_main_start_hyperopt(mocker) -> None:
|
||||||
call_args = hyperopt_mock.call_args[0][0]
|
call_args = hyperopt_mock.call_args[0][0]
|
||||||
assert call_args["config"] == ['config.json']
|
assert call_args["config"] == ['config.json']
|
||||||
assert call_args["verbosity"] == 0
|
assert call_args["verbosity"] == 0
|
||||||
assert call_args["subparser"] == 'hyperopt'
|
assert call_args["command"] == 'hyperopt'
|
||||||
assert call_args["func"] is not None
|
assert call_args["func"] is not None
|
||||||
assert callable(call_args["func"])
|
assert callable(call_args["func"])
|
||||||
|
|
||||||
|
@ -58,7 +64,7 @@ def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
|
||||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||||
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
||||||
|
|
||||||
args = ['-c', 'config.json.example']
|
args = ['trade', '-c', 'config.json.example']
|
||||||
|
|
||||||
# Test Main + the KeyboardInterrupt exception
|
# Test Main + the KeyboardInterrupt exception
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
|
@ -75,7 +81,7 @@ def test_main_keyboard_interrupt(mocker, default_conf, caplog) -> None:
|
||||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||||
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
||||||
|
|
||||||
args = ['-c', 'config.json.example']
|
args = ['trade', '-c', 'config.json.example']
|
||||||
|
|
||||||
# Test Main + the KeyboardInterrupt exception
|
# Test Main + the KeyboardInterrupt exception
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
|
@ -95,7 +101,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None:
|
||||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||||
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
||||||
|
|
||||||
args = ['-c', 'config.json.example']
|
args = ['trade', '-c', 'config.json.example']
|
||||||
|
|
||||||
# Test Main + the KeyboardInterrupt exception
|
# Test Main + the KeyboardInterrupt exception
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
|
@ -114,15 +120,15 @@ def test_main_reload_conf(mocker, default_conf, caplog) -> None:
|
||||||
OperationalException("Oh snap!")])
|
OperationalException("Oh snap!")])
|
||||||
mocker.patch('freqtrade.worker.Worker._worker', worker_mock)
|
mocker.patch('freqtrade.worker.Worker._worker', worker_mock)
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
reconfigure_mock = mocker.patch('freqtrade.main.Worker._reconfigure', MagicMock())
|
reconfigure_mock = mocker.patch('freqtrade.worker.Worker._reconfigure', MagicMock())
|
||||||
|
|
||||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||||
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
||||||
|
|
||||||
args = Arguments(['-c', 'config.json.example']).get_parsed_arg()
|
args = Arguments(['trade', '-c', 'config.json.example']).get_parsed_arg()
|
||||||
worker = Worker(args=args, config=default_conf)
|
worker = Worker(args=args, config=default_conf)
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
main(['-c', 'config.json.example'])
|
main(['trade', '-c', 'config.json.example'])
|
||||||
|
|
||||||
assert log_has('Using config: config.json.example ...', caplog)
|
assert log_has('Using config: config.json.example ...', caplog)
|
||||||
assert worker_mock.call_count == 4
|
assert worker_mock.call_count == 4
|
||||||
|
@ -141,7 +147,7 @@ def test_reconfigure(mocker, default_conf) -> None:
|
||||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||||
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
|
||||||
|
|
||||||
args = Arguments(['-c', 'config.json.example']).get_parsed_arg()
|
args = Arguments(['trade', '-c', 'config.json.example']).get_parsed_arg()
|
||||||
worker = Worker(args=args, config=default_conf)
|
worker = Worker(args=args, config=default_conf)
|
||||||
freqtrade = worker.freqtrade
|
freqtrade = worker.freqtrade
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ def test_add_indicators(default_conf, testdatadir, caplog):
|
||||||
pair = "UNITTEST/BTC"
|
pair = "UNITTEST/BTC"
|
||||||
timerange = TimeRange(None, 'line', 0, -1000)
|
timerange = TimeRange(None, 'line', 0, -1000)
|
||||||
|
|
||||||
data = history.load_pair_history(pair=pair, ticker_interval='1m',
|
data = history.load_pair_history(pair=pair, timeframe='1m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
indicators1 = ["ema10"]
|
indicators1 = ["ema10"]
|
||||||
indicators2 = ["macd"]
|
indicators2 = ["macd"]
|
||||||
|
@ -129,7 +129,7 @@ def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, t
|
||||||
|
|
||||||
pair = "UNITTEST/BTC"
|
pair = "UNITTEST/BTC"
|
||||||
timerange = TimeRange(None, 'line', 0, -1000)
|
timerange = TimeRange(None, 'line', 0, -1000)
|
||||||
data = history.load_pair_history(pair=pair, ticker_interval='1m',
|
data = history.load_pair_history(pair=pair, timeframe='1m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
data['buy'] = 0
|
data['buy'] = 0
|
||||||
data['sell'] = 0
|
data['sell'] = 0
|
||||||
|
@ -164,7 +164,7 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
|
||||||
MagicMock(side_effect=fig_generating_mock))
|
MagicMock(side_effect=fig_generating_mock))
|
||||||
pair = 'UNITTEST/BTC'
|
pair = 'UNITTEST/BTC'
|
||||||
timerange = TimeRange(None, 'line', 0, -1000)
|
timerange = TimeRange(None, 'line', 0, -1000)
|
||||||
data = history.load_pair_history(pair=pair, ticker_interval='1m',
|
data = history.load_pair_history(pair=pair, timeframe='1m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
|
|
||||||
# Generate buy/sell signals and indicators
|
# Generate buy/sell signals and indicators
|
||||||
|
@ -228,7 +228,7 @@ def test_add_profit(testdatadir):
|
||||||
bt_data = load_backtest_data(filename)
|
bt_data = load_backtest_data(filename)
|
||||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||||
|
|
||||||
df = history.load_pair_history(pair="TRX/BTC", ticker_interval='5m',
|
df = history.load_pair_history(pair="TRX/BTC", timeframe='5m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
fig = generate_empty_figure()
|
fig = generate_empty_figure()
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ def test_generate_profit_graph(testdatadir):
|
||||||
|
|
||||||
tickers = history.load_data(datadir=testdatadir,
|
tickers = history.load_data(datadir=testdatadir,
|
||||||
pairs=pairs,
|
pairs=pairs,
|
||||||
ticker_interval='5m',
|
timeframe='5m',
|
||||||
timerange=timerange
|
timerange=timerange
|
||||||
)
|
)
|
||||||
trades = trades[trades['pair'].isin(pairs)]
|
trades = trades[trades['pair'].isin(pairs)]
|
||||||
|
@ -281,8 +281,8 @@ def test_generate_profit_graph(testdatadir):
|
||||||
def test_start_plot_dataframe(mocker):
|
def test_start_plot_dataframe(mocker):
|
||||||
aup = mocker.patch("freqtrade.plot.plotting.load_and_plot_trades", MagicMock())
|
aup = mocker.patch("freqtrade.plot.plotting.load_and_plot_trades", MagicMock())
|
||||||
args = [
|
args = [
|
||||||
"--config", "config.json.example",
|
|
||||||
"plot-dataframe",
|
"plot-dataframe",
|
||||||
|
"--config", "config.json.example",
|
||||||
"--pairs", "ETH/BTC"
|
"--pairs", "ETH/BTC"
|
||||||
]
|
]
|
||||||
start_plot_dataframe(get_args(args))
|
start_plot_dataframe(get_args(args))
|
||||||
|
@ -323,8 +323,8 @@ def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir):
|
||||||
def test_start_plot_profit(mocker):
|
def test_start_plot_profit(mocker):
|
||||||
aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock())
|
aup = mocker.patch("freqtrade.plot.plotting.plot_profit", MagicMock())
|
||||||
args = [
|
args = [
|
||||||
"--config", "config.json.example",
|
|
||||||
"plot-profit",
|
"plot-profit",
|
||||||
|
"--config", "config.json.example",
|
||||||
"--pairs", "ETH/BTC"
|
"--pairs", "ETH/BTC"
|
||||||
]
|
]
|
||||||
start_plot_profit(get_args(args))
|
start_plot_profit(get_args(args))
|
||||||
|
|
|
@ -14,7 +14,7 @@ from tests.conftest import get_args, log_has, patch_exchange
|
||||||
|
|
||||||
def test_setup_utils_configuration():
|
def test_setup_utils_configuration():
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
'list-exchanges', '--config', 'config.json.example',
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_utils_configuration(get_args(args), RunMode.OTHER)
|
config = setup_utils_configuration(get_args(args), RunMode.OTHER)
|
||||||
|
@ -95,8 +95,8 @@ def test_list_timeframes(mocker, capsys):
|
||||||
|
|
||||||
# Test with --config config.json.example
|
# Test with --config config.json.example
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-timeframes",
|
"list-timeframes",
|
||||||
|
'--config', 'config.json.example',
|
||||||
]
|
]
|
||||||
start_list_timeframes(get_args(args))
|
start_list_timeframes(get_args(args))
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
|
@ -139,8 +139,8 @@ def test_list_timeframes(mocker, capsys):
|
||||||
|
|
||||||
# Test with --one-column
|
# Test with --one-column
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-timeframes",
|
"list-timeframes",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--one-column",
|
"--one-column",
|
||||||
]
|
]
|
||||||
start_list_timeframes(get_args(args))
|
start_list_timeframes(get_args(args))
|
||||||
|
@ -182,8 +182,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# Test with --config config.json.example
|
# Test with --config config.json.example
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
|
@ -208,8 +208,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
patch_exchange(mocker, api_mock=api_mock, id="bittrex")
|
patch_exchange(mocker, api_mock=api_mock, id="bittrex")
|
||||||
# Test with --all: all markets
|
# Test with --all: all markets
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets", "--all",
|
"list-markets", "--all",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
|
@ -221,8 +221,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# Test list-pairs subcommand: active pairs
|
# Test list-pairs subcommand: active pairs
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-pairs",
|
"list-pairs",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
start_list_markets(get_args(args), True)
|
start_list_markets(get_args(args), True)
|
||||||
|
@ -233,8 +233,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# Test list-pairs subcommand with --all: all pairs
|
# Test list-pairs subcommand with --all: all pairs
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-pairs", "--all",
|
"list-pairs", "--all",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
start_list_markets(get_args(args), True)
|
start_list_markets(get_args(args), True)
|
||||||
|
@ -246,8 +246,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# active markets, base=ETH, LTC
|
# active markets, base=ETH, LTC
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--base", "ETH", "LTC",
|
"--base", "ETH", "LTC",
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
|
@ -259,8 +259,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# active markets, base=LTC
|
# active markets, base=LTC
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--base", "LTC",
|
"--base", "LTC",
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
|
@ -272,8 +272,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# active markets, quote=USDT, USD
|
# active markets, quote=USDT, USD
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--quote", "USDT", "USD",
|
"--quote", "USDT", "USD",
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
|
@ -285,8 +285,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# active markets, quote=USDT
|
# active markets, quote=USDT
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--quote", "USDT",
|
"--quote", "USDT",
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
|
@ -298,8 +298,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# active markets, base=LTC, quote=USDT
|
# active markets, base=LTC, quote=USDT
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--base", "LTC", "--quote", "USDT",
|
"--base", "LTC", "--quote", "USDT",
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
|
@ -311,8 +311,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# active pairs, base=LTC, quote=USDT
|
# active pairs, base=LTC, quote=USDT
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-pairs",
|
"list-pairs",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--base", "LTC", "--quote", "USD",
|
"--base", "LTC", "--quote", "USD",
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
|
@ -324,8 +324,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# active markets, base=LTC, quote=USDT, NONEXISTENT
|
# active markets, base=LTC, quote=USDT, NONEXISTENT
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--base", "LTC", "--quote", "USDT", "NONEXISTENT",
|
"--base", "LTC", "--quote", "USDT", "NONEXISTENT",
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
|
@ -337,8 +337,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# active markets, base=LTC, quote=NONEXISTENT
|
# active markets, base=LTC, quote=NONEXISTENT
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--base", "LTC", "--quote", "NONEXISTENT",
|
"--base", "LTC", "--quote", "NONEXISTENT",
|
||||||
"--print-list",
|
"--print-list",
|
||||||
]
|
]
|
||||||
|
@ -350,8 +350,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# Test tabular output
|
# Test tabular output
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
|
@ -360,8 +360,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# Test tabular output, no markets found
|
# Test tabular output, no markets found
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--base", "LTC", "--quote", "NONEXISTENT",
|
"--base", "LTC", "--quote", "NONEXISTENT",
|
||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
|
@ -372,8 +372,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# Test --print-json
|
# Test --print-json
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--print-json"
|
"--print-json"
|
||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
|
@ -384,8 +384,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# Test --print-csv
|
# Test --print-csv
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--print-csv"
|
"--print-csv"
|
||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
|
@ -396,8 +396,8 @@ def test_list_markets(mocker, markets, capsys):
|
||||||
|
|
||||||
# Test --one-column
|
# Test --one-column
|
||||||
args = [
|
args = [
|
||||||
'--config', 'config.json.example',
|
|
||||||
"list-markets",
|
"list-markets",
|
||||||
|
'--config', 'config.json.example',
|
||||||
"--one-column"
|
"--one-column"
|
||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
|
|
|
@ -12,7 +12,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
||||||
|
|
||||||
|
|
||||||
class SampleHyperOpts(IHyperOpt):
|
class SampleHyperOpt(IHyperOpt):
|
||||||
"""
|
"""
|
||||||
This is a sample Hyperopt to inspire you.
|
This is a sample Hyperopt to inspire you.
|
||||||
Feel free to customize it.
|
Feel free to customize it.
|
||||||
|
|
|
@ -14,7 +14,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
||||||
|
|
||||||
|
|
||||||
class AdvancedSampleHyperOpts(IHyperOpt):
|
class AdvancedSampleHyperOpt(IHyperOpt):
|
||||||
"""
|
"""
|
||||||
This is a sample hyperopt to inspire you.
|
This is a sample hyperopt to inspire you.
|
||||||
Feel free to customize it.
|
Feel free to customize it.
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
"# Customize these according to your needs.\n",
|
"# Customize these according to your needs.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Define some constants\n",
|
"# Define some constants\n",
|
||||||
"ticker_interval = \"5m\"\n",
|
"timeframe = \"5m\"\n",
|
||||||
"# Name of the strategy class\n",
|
"# Name of the strategy class\n",
|
||||||
"strategy_name = 'SampleStrategy'\n",
|
"strategy_name = 'SampleStrategy'\n",
|
||||||
"# Path to user data\n",
|
"# Path to user data\n",
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
"from freqtrade.data.history import load_pair_history\n",
|
"from freqtrade.data.history import load_pair_history\n",
|
||||||
"\n",
|
"\n",
|
||||||
"candles = load_pair_history(datadir=data_location,\n",
|
"candles = load_pair_history(datadir=data_location,\n",
|
||||||
" ticker_interval=ticker_interval,\n",
|
" timeframe=timeframe,\n",
|
||||||
" pair=pair)\n",
|
" pair=pair)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Confirm success\n",
|
"# Confirm success\n",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user