Merge pull request #400 from gcarq/feature/custom_strategy
Allow custom strategy files
This commit is contained in:
commit
a6cbc1ba16
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,6 +5,8 @@ config.json
|
|||||||
*.sqlite
|
*.sqlite
|
||||||
.hyperopt
|
.hyperopt
|
||||||
logfile.txt
|
logfile.txt
|
||||||
|
hyperopt_trials.pickle
|
||||||
|
user_data/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
@ -85,5 +87,3 @@ target/
|
|||||||
.venv
|
.venv
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
hyperopt_trials.pickle
|
|
||||||
|
@ -3,21 +3,55 @@ This page explains where to customize your strategies, and add new
|
|||||||
indicators.
|
indicators.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Change your strategy](#change-your-strategy)
|
- [Install a custom strategy file](#install-a-custom-strategy-file)
|
||||||
|
- [Customize your strategy](#change-your-strategy)
|
||||||
- [Add more Indicator](#add-more-indicator)
|
- [Add more Indicator](#add-more-indicator)
|
||||||
|
- [Where is the default strategy](#where-is-the-default-strategy)
|
||||||
|
|
||||||
|
Since the version `0.16.0` the bot allows using custom strategy file.
|
||||||
|
|
||||||
|
## Install a custom strategy file
|
||||||
|
This is very simple. Copy paste your strategy file into the folder
|
||||||
|
`user_data/strategies`.
|
||||||
|
|
||||||
|
Let assume you have a strategy file `awesome-strategy.py`:
|
||||||
|
1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py`
|
||||||
|
2. Start the bot with the param `--strategy awesome-strategy` (the parameter is the name of the file without '.py')
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py --strategy awesome_strategy
|
||||||
|
```
|
||||||
|
|
||||||
## Change your strategy
|
## Change your strategy
|
||||||
The bot is using buy and sell strategies to buy and sell your trades.
|
The bot includes a default strategy file. However, we recommend you to
|
||||||
Both are customizable.
|
use your own file to not have to lose your parameters everytime the default
|
||||||
|
strategy file will be updated on Github. Put your custom strategy file
|
||||||
|
into the folder `user_data/strategies`.
|
||||||
|
|
||||||
### Buy strategy
|
A strategy file contains all the information needed to build a good strategy:
|
||||||
The default buy strategy is located in the file
|
- Buy strategy rules
|
||||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73-L92).
|
- Sell strategy rules
|
||||||
Edit the function `populate_buy_trend()` to update your buy strategy.
|
- Minimal ROI recommended
|
||||||
|
- Stoploss recommended
|
||||||
|
- Hyperopt parameter
|
||||||
|
|
||||||
Sample:
|
The bot also include a sample strategy you can update: `user_data/strategies/test_strategy.py`.
|
||||||
|
You can test it with the parameter: `--strategy test_strategy`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py --strategy awesome_strategy
|
||||||
|
```
|
||||||
|
|
||||||
|
**For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
|
||||||
|
file as reference.**
|
||||||
|
|
||||||
|
### Buy strategy
|
||||||
|
Edit the method `populate_buy_trend()` into your strategy file to
|
||||||
|
update your buy strategy.
|
||||||
|
|
||||||
|
Sample from `user_data/strategies/test_strategy.py`:
|
||||||
```python
|
```python
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
@ -25,14 +59,9 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
|||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe['rsi'] < 35) &
|
|
||||||
(dataframe['fastd'] < 35) &
|
|
||||||
(dataframe['adx'] > 30) &
|
(dataframe['adx'] > 30) &
|
||||||
(dataframe['plus_di'] > 0.5)
|
(dataframe['tema'] <= dataframe['blower']) &
|
||||||
) |
|
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||||
(
|
|
||||||
(dataframe['adx'] > 65) &
|
|
||||||
(dataframe['plus_di'] > 0.5)
|
|
||||||
),
|
),
|
||||||
'buy'] = 1
|
'buy'] = 1
|
||||||
|
|
||||||
@ -40,41 +69,31 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Sell strategy
|
### Sell strategy
|
||||||
The default buy strategy is located in the file
|
Edit the method `populate_sell_trend()` into your strategy file to
|
||||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115)
|
update your sell strategy.
|
||||||
Edit the function `populate_sell_trend()` to update your buy strategy.
|
|
||||||
|
|
||||||
Sample:
|
Sample from `user_data/strategies/test_strategy.py`:
|
||||||
```python
|
```python
|
||||||
def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
|
||||||
(
|
|
||||||
(crossed_above(dataframe['rsi'], 70)) |
|
|
||||||
(crossed_above(dataframe['fastd'], 70))
|
|
||||||
) &
|
|
||||||
(dataframe['adx'] > 10) &
|
|
||||||
(dataframe['minus_di'] > 0)
|
|
||||||
) |
|
|
||||||
(
|
(
|
||||||
(dataframe['adx'] > 70) &
|
(dataframe['adx'] > 70) &
|
||||||
(dataframe['minus_di'] > 0.5)
|
(dataframe['tema'] > dataframe['blower']) &
|
||||||
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
),
|
),
|
||||||
'sell'] = 1
|
'sell'] = 1
|
||||||
return dataframe
|
return dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
## Add more Indicator
|
## Add more Indicator
|
||||||
As you have seen, buy and sell strategies need indicators. You can see
|
As you have seen, buy and sell strategies need indicators. You can add
|
||||||
the indicators in the file
|
more indicators by extending the list contained in
|
||||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115).
|
the method `populate_indicators()` from your strategy file.
|
||||||
Of course you can add more indicators by extending the list contained in
|
|
||||||
the function `populate_indicators()`.
|
|
||||||
|
|
||||||
Sample:
|
Sample:
|
||||||
```python
|
```python
|
||||||
@ -111,6 +130,15 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
|||||||
return dataframe
|
return dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Want more indicators example?**
|
||||||
|
Look into the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
|
||||||
|
Then uncomment indicators you need.
|
||||||
|
|
||||||
|
|
||||||
|
### Where is the default strategy?
|
||||||
|
The default buy strategy is located in the file
|
||||||
|
[freqtrade/default_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/strategy/default_strategy.py).
|
||||||
|
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
Now you have a perfect strategy you probably want to backtesting it.
|
Now you have a perfect strategy you probably want to backtesting it.
|
||||||
|
@ -22,19 +22,21 @@ positional arguments:
|
|||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-c PATH, --config PATH
|
|
||||||
specify configuration file (default: config.json)
|
|
||||||
-v, --verbose be verbose
|
-v, --verbose be verbose
|
||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-dd PATH, --datadir PATH
|
-c PATH, --config PATH
|
||||||
Path is from where backtesting and hyperopt will load the
|
specify configuration file (default: config.json)
|
||||||
ticker data files (default freqdata/tests/testdata).
|
-s PATH, --strategy PATH
|
||||||
--dynamic-whitelist [INT]
|
specify strategy file (default:
|
||||||
dynamically generate and update whitelist based on 24h
|
freqtrade/strategy/default_strategy.py)
|
||||||
BaseVolume (Default 20 currencies)
|
|
||||||
--dry-run-db Force dry run to use a local DB
|
--dry-run-db Force dry run to use a local DB
|
||||||
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
||||||
only if dry_run is enabled.
|
only if dry_run is enabled.
|
||||||
|
-dd PATH, --datadir PATH
|
||||||
|
path to backtest data (default freqdata/tests/testdata
|
||||||
|
--dynamic-whitelist [INT]
|
||||||
|
dynamically generate and update whitelist based on 24h
|
||||||
|
BaseVolume (Default 20 currencies)
|
||||||
```
|
```
|
||||||
|
|
||||||
### How to use a different config file?
|
### How to use a different config file?
|
||||||
@ -45,6 +47,33 @@ default, the bot will load the file `./config.json`
|
|||||||
python3 ./freqtrade/main.py -c path/far/far/away/config.json
|
python3 ./freqtrade/main.py -c path/far/far/away/config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### How to use --strategy?
|
||||||
|
This parameter will allow you to load your custom strategy file. Per
|
||||||
|
default without `--strategy` or `-s` the bol will load the
|
||||||
|
`default_strategy` included with the bot (`freqtrade/strategy/default_strategy.py`).
|
||||||
|
|
||||||
|
The bot will search your strategy file into `user_data/strategies` and
|
||||||
|
`freqtrade/strategy`.
|
||||||
|
|
||||||
|
To load a strategy, simply pass the file name (without .py) in this
|
||||||
|
parameters.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
In `user_data/strategies` you have a file `my_awesome_strategy.py` to
|
||||||
|
load it:
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py --strategy my_awesome_strategy
|
||||||
|
```
|
||||||
|
|
||||||
|
If the bot does not find your strategy file, it will fallback to the
|
||||||
|
`default_strategy`.
|
||||||
|
|
||||||
|
Learn more about strategy file in [optimize your bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md).
|
||||||
|
|
||||||
|
#### How to install a strategy?
|
||||||
|
This is very simple. Copy paste your strategy file into the folder
|
||||||
|
`user_data/strategies`. And voila, the bot is ready to use it.
|
||||||
|
|
||||||
### How to use --dynamic-whitelist?
|
### How to use --dynamic-whitelist?
|
||||||
Per default `--dynamic-whitelist` will retrieve the 20 currencies based
|
Per default `--dynamic-whitelist` will retrieve the 20 currencies based
|
||||||
on BaseVolume. This value can be changed when you run the script.
|
on BaseVolume. This value can be changed when you run the script.
|
||||||
|
@ -20,8 +20,8 @@ The table below will list all configuration parameters.
|
|||||||
| `ticker_interval` | [1, 5, 30, 60, 1440] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Defaut is 5 minutes
|
| `ticker_interval` | [1, 5, 30, 60, 1440] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Defaut is 5 minutes
|
||||||
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
|
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
|
||||||
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
|
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
|
||||||
| `minimal_roi` | See below | Yes | Set the threshold in percent the bot will use to sell a trade. More information below.
|
| `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
|
||||||
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below.
|
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
|
||||||
| `unfilledtimeout` | 0 | No | How long (in minutes) the bot will wait for an unfilled order to complete, after which the order will be cancelled.
|
| `unfilledtimeout` | 0 | No | How long (in minutes) the bot will wait for an unfilled order to complete, after which the order will be cancelled.
|
||||||
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
|
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
|
||||||
| `exchange.name` | bittrex | Yes | Name of the exchange class to use.
|
| `exchange.name` | bittrex | Yes | Name of the exchange class to use.
|
||||||
@ -53,11 +53,19 @@ See the example below:
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Most of the strategy files already include the optimal `minimal_roi`
|
||||||
|
value. This parameter is optional. If you use it, it will take over the
|
||||||
|
`minimal_roi` value from the strategy file.
|
||||||
|
|
||||||
### Understand stoploss
|
### Understand stoploss
|
||||||
`stoploss` is loss in percentage that should trigger a sale.
|
`stoploss` is loss in percentage that should trigger a sale.
|
||||||
For example value `-0.10` will cause immediate sell if the
|
For example value `-0.10` will cause immediate sell if the
|
||||||
profit dips below -10% for a given trade. This parameter is optional.
|
profit dips below -10% for a given trade. This parameter is optional.
|
||||||
|
|
||||||
|
Most of the strategy files already include the optimal `stoploss`
|
||||||
|
value. This parameter is optional. If you use it, it will take over the
|
||||||
|
`stoploss` value from the strategy file.
|
||||||
|
|
||||||
### Understand initial_state
|
### Understand initial_state
|
||||||
`initial_state` is an optional field that defines the initial application state.
|
`initial_state` is an optional field that defines the initial application state.
|
||||||
Possible values are `running` or `stopped`. (default=`running`)
|
Possible values are `running` or `stopped`. (default=`running`)
|
||||||
|
@ -14,14 +14,13 @@ parameters with Hyperopt.
|
|||||||
|
|
||||||
## Prepare Hyperopt
|
## Prepare Hyperopt
|
||||||
Before we start digging in Hyperopt, we recommend you to take a look at
|
Before we start digging in Hyperopt, we recommend you to take a look at
|
||||||
out Hyperopt file
|
your strategy file located into [user_data/strategies/](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
|
||||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py)
|
|
||||||
|
|
||||||
### 1. Configure your Guards and Triggers
|
### 1. Configure your Guards and Triggers
|
||||||
There are two places you need to change to add a new buy strategy for
|
There are two places you need to change in your strategy file to add a
|
||||||
testing:
|
new buy strategy for testing:
|
||||||
- Inside the [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L167-L207).
|
- Inside [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L278-L294).
|
||||||
- Inside the [SPACE dict](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L47-L94).
|
- Inside [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297) known as `SPACE`.
|
||||||
|
|
||||||
There you have two different type of indicators: 1. `guards` and 2.
|
There you have two different type of indicators: 1. `guards` and 2.
|
||||||
`triggers`.
|
`triggers`.
|
||||||
@ -38,10 +37,10 @@ ADX > 10*".
|
|||||||
|
|
||||||
|
|
||||||
If you have updated the buy strategy, means change the content of
|
If you have updated the buy strategy, means change the content of
|
||||||
`populate_buy_trend()` function you have to update the `guards` and
|
`populate_buy_trend()` method you have to update the `guards` and
|
||||||
`triggers` hyperopts must used.
|
`triggers` hyperopts must used.
|
||||||
|
|
||||||
As for an example if your `populate_buy_trend()` function is:
|
As for an example if your `populate_buy_trend()` method is:
|
||||||
```python
|
```python
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
@ -56,10 +55,10 @@ Your hyperopt file must contains `guards` to find the right value for
|
|||||||
`(dataframe['adx'] > 65)` & and `(dataframe['plus_di'] > 0.5)`. That
|
`(dataframe['adx'] > 65)` & and `(dataframe['plus_di'] > 0.5)`. That
|
||||||
means you will need to enable/disable triggers.
|
means you will need to enable/disable triggers.
|
||||||
|
|
||||||
In our case the `SPACE` and `populate_buy_trend` in hyperopt.py file
|
In our case the `SPACE` and `populate_buy_trend` in your strategy file
|
||||||
will be look like:
|
will be look like:
|
||||||
```python
|
```python
|
||||||
SPACE = {
|
space = {
|
||||||
'rsi': hp.choice('rsi', [
|
'rsi': hp.choice('rsi', [
|
||||||
{'enabled': False},
|
{'enabled': False},
|
||||||
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
||||||
@ -82,7 +81,7 @@ SPACE = {
|
|||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
conditions = []
|
conditions = []
|
||||||
# GUARDS AND TRENDS
|
# GUARDS AND TRENDS
|
||||||
if params['adx']['enabled']:
|
if params['adx']['enabled']:
|
||||||
@ -111,13 +110,13 @@ cannot use your config file. It is also made on purpose to allow you
|
|||||||
testing your strategy with different configurations.
|
testing your strategy with different configurations.
|
||||||
|
|
||||||
The Hyperopt configuration is located in
|
The Hyperopt configuration is located in
|
||||||
[freqtrade/optimize/hyperopt_conf.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt_conf.py).
|
[user_data/hyperopt_conf.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/hyperopt_conf.py).
|
||||||
|
|
||||||
|
|
||||||
## Advanced notions
|
## Advanced notions
|
||||||
### Understand the Guards and Triggers
|
### Understand the Guards and Triggers
|
||||||
When you need to add the new guards and triggers to be hyperopt
|
When you need to add the new guards and triggers to be hyperopt
|
||||||
parameters, you do this by adding them into the [SPACE dict](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L47-L94).
|
parameters, you do this by adding them into the [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297).
|
||||||
|
|
||||||
If it's a trigger, you add one line to the 'trigger' choice group and that's it.
|
If it's a trigger, you add one line to the 'trigger' choice group and that's it.
|
||||||
|
|
||||||
@ -149,9 +148,8 @@ for best working algo.
|
|||||||
|
|
||||||
### Add a new Indicators
|
### Add a new Indicators
|
||||||
If you want to test an indicator that isn't used by the bot currently,
|
If you want to test an indicator that isn't used by the bot currently,
|
||||||
you need to add it to
|
you need to add it to your strategy file (example: [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py))
|
||||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L40-L70)
|
inside the `populate_indicators()` method.
|
||||||
inside the `populate_indicators` function.
|
|
||||||
|
|
||||||
## Execute Hyperopt
|
## Execute Hyperopt
|
||||||
Once you have updated your hyperopt configuration you can run it.
|
Once you have updated your hyperopt configuration you can run it.
|
||||||
@ -165,8 +163,8 @@ python3 ./freqtrade/main.py -c config.json hyperopt
|
|||||||
|
|
||||||
### Execute hyperopt with different ticker-data source
|
### Execute hyperopt with different ticker-data source
|
||||||
If you would like to learn parameters using an alternate ticke-data that
|
If you would like to learn parameters using an alternate ticke-data that
|
||||||
you have on-disk, use the --datadir PATH option. Default hyperopt will
|
you have on-disk, use the `--datadir PATH` option. Default hyperopt will
|
||||||
use data from directory freqtrade/tests/testdata.
|
use data from directory `user_data/data`.
|
||||||
|
|
||||||
### Running hyperopt with smaller testset
|
### Running hyperopt with smaller testset
|
||||||
|
|
||||||
@ -270,15 +268,11 @@ customizable value.
|
|||||||
- and so on...
|
- and so on...
|
||||||
|
|
||||||
|
|
||||||
You have to look from
|
You have to look inside your strategy file into `buy_strategy_generator()`
|
||||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L170-L200)
|
method, what those values match to.
|
||||||
what those values match to.
|
|
||||||
|
|
||||||
So for example you had `adx:` with the `value: 15.0` so we would look
|
So for example you had `adx:` with the `value: 15.0` so we would look
|
||||||
at `adx`-block from
|
at `adx`-block, that translates to the following code block:
|
||||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L178-L179).
|
|
||||||
That translates to the following code block to
|
|
||||||
[analyze.populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73)
|
|
||||||
```
|
```
|
||||||
(dataframe['adx'] > 15.0)
|
(dataframe['adx'] > 15.0)
|
||||||
```
|
```
|
||||||
@ -286,7 +280,7 @@ That translates to the following code block to
|
|||||||
So translating your whole hyperopt result to as the new buy-signal
|
So translating your whole hyperopt result to as the new buy-signal
|
||||||
would be the following:
|
would be the following:
|
||||||
```
|
```
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe['adx'] > 15.0) & # adx-value
|
(dataframe['adx'] > 15.0) & # adx-value
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
""" FreqTrade bot """
|
""" FreqTrade bot """
|
||||||
__version__ = '0.15.1'
|
__version__ = '0.16.0'
|
||||||
|
|
||||||
|
|
||||||
class DependencyException(BaseException):
|
class DependencyException(BaseException):
|
||||||
|
@ -7,11 +7,10 @@ from enum import Enum
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import talib.abstract as ta
|
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
|
||||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
||||||
from freqtrade.exchange import get_ticker_history
|
from freqtrade.exchange import get_ticker_history
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -46,182 +45,8 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
|||||||
you are using. Let uncomment only the indicator you are using in your strategies
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
"""
|
"""
|
||||||
|
strategy = Strategy()
|
||||||
# Momentum Indicator
|
return strategy.populate_indicators(dataframe=dataframe)
|
||||||
# ------------------------------------
|
|
||||||
|
|
||||||
# ADX
|
|
||||||
dataframe['adx'] = ta.ADX(dataframe)
|
|
||||||
|
|
||||||
# Awesome oscillator
|
|
||||||
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
|
||||||
"""
|
|
||||||
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
|
|
||||||
dataframe['cci'] = ta.CCI(dataframe)
|
|
||||||
"""
|
|
||||||
# MACD
|
|
||||||
macd = ta.MACD(dataframe)
|
|
||||||
dataframe['macd'] = macd['macd']
|
|
||||||
dataframe['macdsignal'] = macd['macdsignal']
|
|
||||||
dataframe['macdhist'] = macd['macdhist']
|
|
||||||
|
|
||||||
# MFI
|
|
||||||
dataframe['mfi'] = ta.MFI(dataframe)
|
|
||||||
|
|
||||||
# Minus Directional Indicator / Movement
|
|
||||||
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
|
||||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
|
||||||
|
|
||||||
# Plus Directional Indicator / Movement
|
|
||||||
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
|
||||||
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
|
||||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
|
||||||
|
|
||||||
"""
|
|
||||||
# ROC
|
|
||||||
dataframe['roc'] = ta.ROC(dataframe)
|
|
||||||
"""
|
|
||||||
# RSI
|
|
||||||
dataframe['rsi'] = ta.RSI(dataframe)
|
|
||||||
"""
|
|
||||||
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
|
||||||
rsi = 0.1 * (dataframe['rsi'] - 50)
|
|
||||||
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
|
||||||
|
|
||||||
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
|
|
||||||
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
|
|
||||||
|
|
||||||
# Stoch
|
|
||||||
stoch = ta.STOCH(dataframe)
|
|
||||||
dataframe['slowd'] = stoch['slowd']
|
|
||||||
dataframe['slowk'] = stoch['slowk']
|
|
||||||
"""
|
|
||||||
# Stoch fast
|
|
||||||
stoch_fast = ta.STOCHF(dataframe)
|
|
||||||
dataframe['fastd'] = stoch_fast['fastd']
|
|
||||||
dataframe['fastk'] = stoch_fast['fastk']
|
|
||||||
"""
|
|
||||||
# Stoch RSI
|
|
||||||
stoch_rsi = ta.STOCHRSI(dataframe)
|
|
||||||
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
|
||||||
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Overlap Studies
|
|
||||||
# ------------------------------------
|
|
||||||
|
|
||||||
# Previous Bollinger bands
|
|
||||||
# Because ta.BBANDS implementation is broken with small numbers, it actually
|
|
||||||
# returns middle band for all the three bands. Switch to qtpylib.bollinger_bands
|
|
||||||
# and use middle band instead.
|
|
||||||
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
|
||||||
"""
|
|
||||||
# Bollinger bands
|
|
||||||
"""
|
|
||||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
|
||||||
dataframe['bb_lowerband'] = bollinger['lower']
|
|
||||||
dataframe['bb_middleband'] = bollinger['mid']
|
|
||||||
dataframe['bb_upperband'] = bollinger['upper']
|
|
||||||
|
|
||||||
# EMA - Exponential Moving Average
|
|
||||||
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
|
||||||
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
|
||||||
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
|
||||||
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
|
||||||
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
|
||||||
|
|
||||||
# SAR Parabol
|
|
||||||
dataframe['sar'] = ta.SAR(dataframe)
|
|
||||||
|
|
||||||
# SMA - Simple Moving Average
|
|
||||||
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
|
||||||
|
|
||||||
# TEMA - Triple Exponential Moving Average
|
|
||||||
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
|
||||||
|
|
||||||
# Cycle Indicator
|
|
||||||
# ------------------------------------
|
|
||||||
# Hilbert Transform Indicator - SineWave
|
|
||||||
hilbert = ta.HT_SINE(dataframe)
|
|
||||||
dataframe['htsine'] = hilbert['sine']
|
|
||||||
dataframe['htleadsine'] = hilbert['leadsine']
|
|
||||||
|
|
||||||
# Pattern Recognition - Bullish candlestick patterns
|
|
||||||
# ------------------------------------
|
|
||||||
"""
|
|
||||||
# Hammer: values [0, 100]
|
|
||||||
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
|
||||||
|
|
||||||
# Inverted Hammer: values [0, 100]
|
|
||||||
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
|
|
||||||
|
|
||||||
# Dragonfly Doji: values [0, 100]
|
|
||||||
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
|
|
||||||
|
|
||||||
# Piercing Line: values [0, 100]
|
|
||||||
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
|
|
||||||
|
|
||||||
# Morningstar: values [0, 100]
|
|
||||||
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
|
||||||
|
|
||||||
# Three White Soldiers: values [0, 100]
|
|
||||||
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Pattern Recognition - Bearish candlestick patterns
|
|
||||||
# ------------------------------------
|
|
||||||
"""
|
|
||||||
# Hanging Man: values [0, 100]
|
|
||||||
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
|
||||||
|
|
||||||
# Shooting Star: values [0, 100]
|
|
||||||
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
|
|
||||||
|
|
||||||
# Gravestone Doji: values [0, 100]
|
|
||||||
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
|
|
||||||
|
|
||||||
# Dark Cloud Cover: values [0, 100]
|
|
||||||
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
|
|
||||||
|
|
||||||
# Evening Doji Star: values [0, 100]
|
|
||||||
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
|
||||||
|
|
||||||
# Evening Star: values [0, 100]
|
|
||||||
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
|
||||||
# ------------------------------------
|
|
||||||
"""
|
|
||||||
# Three Line Strike: values [0, -100, 100]
|
|
||||||
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
|
||||||
|
|
||||||
# Spinning Top: values [0, -100, 100]
|
|
||||||
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
|
|
||||||
|
|
||||||
# Engulfing: values [0, -100, 100]
|
|
||||||
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
|
|
||||||
|
|
||||||
# Harami: values [0, -100, 100]
|
|
||||||
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
|
|
||||||
|
|
||||||
# Three Outside Up/Down: values [0, -100, 100]
|
|
||||||
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
|
||||||
|
|
||||||
# Three Inside Up/Down: values [0, -100, 100]
|
|
||||||
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Chart type
|
|
||||||
# ------------------------------------
|
|
||||||
# Heikinashi stategy
|
|
||||||
heikinashi = qtpylib.heikinashi(dataframe)
|
|
||||||
dataframe['ha_open'] = heikinashi['open']
|
|
||||||
dataframe['ha_close'] = heikinashi['close']
|
|
||||||
dataframe['ha_high'] = heikinashi['high']
|
|
||||||
dataframe['ha_low'] = heikinashi['low']
|
|
||||||
|
|
||||||
return dataframe
|
|
||||||
|
|
||||||
|
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
@ -230,20 +55,8 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
|||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
strategy = Strategy()
|
||||||
(
|
return strategy.populate_buy_trend(dataframe=dataframe)
|
||||||
(dataframe['rsi'] < 35) &
|
|
||||||
(dataframe['fastd'] < 35) &
|
|
||||||
(dataframe['adx'] > 30) &
|
|
||||||
(dataframe['plus_di'] > 0.5)
|
|
||||||
) |
|
|
||||||
(
|
|
||||||
(dataframe['adx'] > 65) &
|
|
||||||
(dataframe['plus_di'] > 0.5)
|
|
||||||
),
|
|
||||||
'buy'] = 1
|
|
||||||
|
|
||||||
return dataframe
|
|
||||||
|
|
||||||
|
|
||||||
def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
@ -252,21 +65,8 @@ def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
|||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
strategy = Strategy()
|
||||||
(
|
return strategy.populate_sell_trend(dataframe=dataframe)
|
||||||
(
|
|
||||||
(qtpylib.crossed_above(dataframe['rsi'], 70)) |
|
|
||||||
(qtpylib.crossed_above(dataframe['fastd'], 70))
|
|
||||||
) &
|
|
||||||
(dataframe['adx'] > 10) &
|
|
||||||
(dataframe['minus_di'] > 0)
|
|
||||||
) |
|
|
||||||
(
|
|
||||||
(dataframe['adx'] > 70) &
|
|
||||||
(dataframe['minus_di'] > 0.5)
|
|
||||||
),
|
|
||||||
'sell'] = 1
|
|
||||||
return dataframe
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:
|
def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:
|
||||||
|
@ -19,6 +19,7 @@ from freqtrade.fiat_convert import CryptoToFiatConverter
|
|||||||
from freqtrade.misc import (State, get_state, load_config, parse_args,
|
from freqtrade.misc import (State, get_state, load_config, parse_args,
|
||||||
throttle, update_state)
|
throttle, update_state)
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
logger = logging.getLogger('freqtrade')
|
logger = logging.getLogger('freqtrade')
|
||||||
|
|
||||||
@ -248,14 +249,16 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) -
|
|||||||
Based an earlier trade and current price and ROI configuration, decides whether bot should sell
|
Based an earlier trade and current price and ROI configuration, decides whether bot should sell
|
||||||
:return True if bot should sell at current rate
|
:return True if bot should sell at current rate
|
||||||
"""
|
"""
|
||||||
|
strategy = Strategy()
|
||||||
|
|
||||||
current_profit = trade.calc_profit_percent(current_rate)
|
current_profit = trade.calc_profit_percent(current_rate)
|
||||||
if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']):
|
if strategy.stoploss is not None and current_profit < float(strategy.stoploss):
|
||||||
logger.debug('Stop loss hit.')
|
logger.debug('Stop loss hit.')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check if time matches and current rate is above threshold
|
# Check if time matches and current rate is above threshold
|
||||||
time_diff = (current_time - trade.open_date).total_seconds() / 60
|
time_diff = (current_time - trade.open_date).total_seconds() / 60
|
||||||
for duration, threshold in sorted(_CONF['minimal_roi'].items()):
|
for duration, threshold in sorted(strategy.minimal_roi.items()):
|
||||||
if time_diff > float(duration) and current_profit > threshold:
|
if time_diff > float(duration) and current_profit > threshold:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -391,6 +394,9 @@ def init(config: dict, db_url: Optional[str] = None) -> None:
|
|||||||
persistence.init(config, db_url)
|
persistence.init(config, db_url)
|
||||||
exchange.init(config)
|
exchange.init(config)
|
||||||
|
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
# Set initial application state
|
# Set initial application state
|
||||||
initial_state = config.get('initial_state')
|
initial_state = config.get('initial_state')
|
||||||
if initial_state:
|
if initial_state:
|
||||||
@ -458,6 +464,9 @@ def main(sysargv=sys.argv[1:]) -> None:
|
|||||||
# Load and validate configuration
|
# Load and validate configuration
|
||||||
_CONF = load_config(args.config)
|
_CONF = load_config(args.config)
|
||||||
|
|
||||||
|
# Add the strategy file to use
|
||||||
|
_CONF.update({'strategy': args.strategy})
|
||||||
|
|
||||||
# Initialize all modules and start main loop
|
# Initialize all modules and start main loop
|
||||||
if args.dynamic_whitelist:
|
if args.dynamic_whitelist:
|
||||||
logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)')
|
logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)')
|
||||||
@ -475,6 +484,7 @@ def main(sysargv=sys.argv[1:]) -> None:
|
|||||||
try:
|
try:
|
||||||
init(_CONF)
|
init(_CONF)
|
||||||
old_state = None
|
old_state = None
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
new_state = get_state()
|
new_state = get_state()
|
||||||
# Log state transition
|
# Log state transition
|
||||||
|
@ -124,6 +124,14 @@ def common_args_parser(description: str):
|
|||||||
type=str,
|
type=str,
|
||||||
metavar='PATH',
|
metavar='PATH',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-s', '--strategy',
|
||||||
|
help='specify strategy file (default: freqtrade/strategy/default_strategy.py)',
|
||||||
|
dest='strategy',
|
||||||
|
default='.default_strategy',
|
||||||
|
type=str,
|
||||||
|
metavar='PATH',
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -375,12 +383,10 @@ CONF_SCHEMA = {
|
|||||||
],
|
],
|
||||||
'required': [
|
'required': [
|
||||||
'max_open_trades',
|
'max_open_trades',
|
||||||
'ticker_interval',
|
|
||||||
'stake_currency',
|
'stake_currency',
|
||||||
'stake_amount',
|
'stake_amount',
|
||||||
'fiat_display_currency',
|
'fiat_display_currency',
|
||||||
'dry_run',
|
'dry_run',
|
||||||
'minimal_roi',
|
|
||||||
'bid_strategy',
|
'bid_strategy',
|
||||||
'telegram'
|
'telegram'
|
||||||
]
|
]
|
||||||
|
@ -6,9 +6,10 @@ import os
|
|||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from freqtrade.exchange import get_ticker_history
|
from freqtrade.exchange import get_ticker_history
|
||||||
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf
|
|
||||||
from freqtrade.analyze import populate_indicators, parse_ticker_dataframe
|
from freqtrade.analyze import populate_indicators, parse_ticker_dataframe
|
||||||
|
|
||||||
from freqtrade import misc
|
from freqtrade import misc
|
||||||
|
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from freqtrade.analyze import populate_buy_trend, populate_sell_trend
|
|||||||
from freqtrade.exchange import Bittrex
|
from freqtrade.exchange import Bittrex
|
||||||
from freqtrade.main import min_roi_reached
|
from freqtrade.main import min_roi_reached
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -199,6 +200,11 @@ def start(args):
|
|||||||
logger.info('Using max_open_trades: %s ...', config['max_open_trades'])
|
logger.info('Using max_open_trades: %s ...', config['max_open_trades'])
|
||||||
max_open_trades = config['max_open_trades']
|
max_open_trades = config['max_open_trades']
|
||||||
|
|
||||||
|
# init the strategy to use
|
||||||
|
config.update({'strategy': args.strategy})
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
# Monkey patch config
|
# Monkey patch config
|
||||||
from freqtrade import main
|
from freqtrade import main
|
||||||
main._CONF = config
|
main._CONF = config
|
||||||
@ -216,7 +222,7 @@ def start(args):
|
|||||||
'realistic': args.realistic_simulation,
|
'realistic': args.realistic_simulation,
|
||||||
'sell_profit_only': sell_profit_only,
|
'sell_profit_only': sell_profit_only,
|
||||||
'use_sell_signal': use_sell_signal,
|
'use_sell_signal': use_sell_signal,
|
||||||
'stoploss': config.get('stoploss'),
|
'stoploss': strategy.stoploss,
|
||||||
'record': args.export
|
'record': args.export
|
||||||
})
|
})
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -7,11 +7,10 @@ import sys
|
|||||||
import pickle
|
import pickle
|
||||||
import signal
|
import signal
|
||||||
import os
|
import os
|
||||||
from functools import reduce
|
|
||||||
from math import exp
|
from math import exp
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
|
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, space_eval, tpe
|
||||||
from hyperopt.mongoexp import MongoTrials
|
from hyperopt.mongoexp import MongoTrials
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
@ -20,8 +19,8 @@ from freqtrade import exchange, optimize
|
|||||||
from freqtrade.exchange import Bittrex
|
from freqtrade.exchange import Bittrex
|
||||||
from freqtrade.misc import load_config
|
from freqtrade.misc import load_config
|
||||||
from freqtrade.optimize.backtesting import backtest
|
from freqtrade.optimize.backtesting import backtest
|
||||||
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf
|
from freqtrade.strategy.strategy import Strategy
|
||||||
from freqtrade.vendor.qtpylib.indicators import crossed_above
|
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
||||||
|
|
||||||
# Remove noisy log messages
|
# Remove noisy log messages
|
||||||
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
|
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
|
||||||
@ -49,7 +48,7 @@ PROCESSED = None # optimize.preprocess(optimize.load_data())
|
|||||||
OPTIMIZE_CONFIG = hyperopt_optimize_conf()
|
OPTIMIZE_CONFIG = hyperopt_optimize_conf()
|
||||||
|
|
||||||
# Hyperopt Trials
|
# Hyperopt Trials
|
||||||
TRIALS_FILE = os.path.join('freqtrade', 'optimize', 'hyperopt_trials.pickle')
|
TRIALS_FILE = os.path.join('user_data', 'hyperopt_trials.pickle')
|
||||||
TRIALS = Trials()
|
TRIALS = Trials()
|
||||||
|
|
||||||
# Monkey patch config
|
# Monkey patch config
|
||||||
@ -57,63 +56,6 @@ from freqtrade import main # noqa
|
|||||||
main._CONF = OPTIMIZE_CONFIG
|
main._CONF = OPTIMIZE_CONFIG
|
||||||
|
|
||||||
|
|
||||||
SPACE = {
|
|
||||||
'macd_below_zero': hp.choice('macd_below_zero', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'mfi': hp.choice('mfi', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
|
|
||||||
]),
|
|
||||||
'fastd': hp.choice('fastd', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)}
|
|
||||||
]),
|
|
||||||
'adx': hp.choice('adx', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
|
|
||||||
]),
|
|
||||||
'rsi': hp.choice('rsi', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
|
||||||
]),
|
|
||||||
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'uptrend_short_ema': hp.choice('uptrend_short_ema', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'over_sar': hp.choice('over_sar', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'green_candle': hp.choice('green_candle', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'uptrend_sma': hp.choice('uptrend_sma', [
|
|
||||||
{'enabled': False},
|
|
||||||
{'enabled': True}
|
|
||||||
]),
|
|
||||||
'trigger': hp.choice('trigger', [
|
|
||||||
{'type': 'lower_bb'},
|
|
||||||
{'type': 'lower_bb_tema'},
|
|
||||||
{'type': 'faststoch10'},
|
|
||||||
{'type': 'ao_cross_zero'},
|
|
||||||
{'type': 'ema3_cross_ema10'},
|
|
||||||
{'type': 'macd_cross_signal'},
|
|
||||||
{'type': 'sar_reversal'},
|
|
||||||
{'type': 'ht_sine'},
|
|
||||||
{'type': 'heiken_reversal_bull'},
|
|
||||||
{'type': 'di_cross'},
|
|
||||||
]),
|
|
||||||
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def save_trials(trials, trials_path=TRIALS_FILE):
|
def save_trials(trials, trials_path=TRIALS_FILE):
|
||||||
"""Save hyperopt trials to file"""
|
"""Save hyperopt trials to file"""
|
||||||
logger.info('Saving Trials to \'{}\''.format(trials_path))
|
logger.info('Saving Trials to \'{}\''.format(trials_path))
|
||||||
@ -162,7 +104,9 @@ def optimizer(params):
|
|||||||
global _CURRENT_TRIES
|
global _CURRENT_TRIES
|
||||||
|
|
||||||
from freqtrade.optimize import backtesting
|
from freqtrade.optimize import backtesting
|
||||||
backtesting.populate_buy_trend = buy_strategy_generator(params)
|
|
||||||
|
strategy = Strategy()
|
||||||
|
backtesting.populate_buy_trend = strategy.buy_strategy_generator(params)
|
||||||
|
|
||||||
results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'],
|
results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'],
|
||||||
'processed': PROCESSED,
|
'processed': PROCESSED,
|
||||||
@ -209,58 +153,8 @@ def format_results(results: DataFrame):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def buy_strategy_generator(params):
|
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
|
||||||
conditions = []
|
|
||||||
# GUARDS AND TRENDS
|
|
||||||
if params['uptrend_long_ema']['enabled']:
|
|
||||||
conditions.append(dataframe['ema50'] > dataframe['ema100'])
|
|
||||||
if params['macd_below_zero']['enabled']:
|
|
||||||
conditions.append(dataframe['macd'] < 0)
|
|
||||||
if params['uptrend_short_ema']['enabled']:
|
|
||||||
conditions.append(dataframe['ema5'] > dataframe['ema10'])
|
|
||||||
if params['mfi']['enabled']:
|
|
||||||
conditions.append(dataframe['mfi'] < params['mfi']['value'])
|
|
||||||
if params['fastd']['enabled']:
|
|
||||||
conditions.append(dataframe['fastd'] < params['fastd']['value'])
|
|
||||||
if params['adx']['enabled']:
|
|
||||||
conditions.append(dataframe['adx'] > params['adx']['value'])
|
|
||||||
if params['rsi']['enabled']:
|
|
||||||
conditions.append(dataframe['rsi'] < params['rsi']['value'])
|
|
||||||
if params['over_sar']['enabled']:
|
|
||||||
conditions.append(dataframe['close'] > dataframe['sar'])
|
|
||||||
if params['green_candle']['enabled']:
|
|
||||||
conditions.append(dataframe['close'] > dataframe['open'])
|
|
||||||
if params['uptrend_sma']['enabled']:
|
|
||||||
prevsma = dataframe['sma'].shift(1)
|
|
||||||
conditions.append(dataframe['sma'] > prevsma)
|
|
||||||
|
|
||||||
# TRIGGERS
|
|
||||||
triggers = {
|
|
||||||
'lower_bb': (dataframe['close'] < dataframe['bb_lowerband']),
|
|
||||||
'lower_bb_tema': (dataframe['tema'] < dataframe['bb_lowerband']),
|
|
||||||
'faststoch10': (crossed_above(dataframe['fastd'], 10.0)),
|
|
||||||
'ao_cross_zero': (crossed_above(dataframe['ao'], 0.0)),
|
|
||||||
'ema3_cross_ema10': (crossed_above(dataframe['ema3'], dataframe['ema10'])),
|
|
||||||
'macd_cross_signal': (crossed_above(dataframe['macd'], dataframe['macdsignal'])),
|
|
||||||
'sar_reversal': (crossed_above(dataframe['close'], dataframe['sar'])),
|
|
||||||
'ht_sine': (crossed_above(dataframe['htleadsine'], dataframe['htsine'])),
|
|
||||||
'heiken_reversal_bull': (crossed_above(dataframe['ha_close'], dataframe['ha_open'])) &
|
|
||||||
(dataframe['ha_low'] == dataframe['ha_open']),
|
|
||||||
'di_cross': (crossed_above(dataframe['plus_di'], dataframe['minus_di'])),
|
|
||||||
}
|
|
||||||
conditions.append(triggers.get(params['trigger']['type']))
|
|
||||||
|
|
||||||
dataframe.loc[
|
|
||||||
reduce(lambda x, y: x & y, conditions),
|
|
||||||
'buy'] = 1
|
|
||||||
|
|
||||||
return dataframe
|
|
||||||
return populate_buy_trend
|
|
||||||
|
|
||||||
|
|
||||||
def start(args):
|
def start(args):
|
||||||
global TOTAL_TRIES, PROCESSED, SPACE, TRIALS, _CURRENT_TRIES
|
global TOTAL_TRIES, PROCESSED, TRIALS, _CURRENT_TRIES
|
||||||
|
|
||||||
TOTAL_TRIES = args.epochs
|
TOTAL_TRIES = args.epochs
|
||||||
|
|
||||||
@ -275,6 +169,12 @@ def start(args):
|
|||||||
logger.info('Using config: %s ...', args.config)
|
logger.info('Using config: %s ...', args.config)
|
||||||
config = load_config(args.config)
|
config = load_config(args.config)
|
||||||
pairs = config['exchange']['pair_whitelist']
|
pairs = config['exchange']['pair_whitelist']
|
||||||
|
|
||||||
|
# init the strategy to use
|
||||||
|
config.update({'strategy': args.strategy})
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
timerange = misc.parse_timerange(args.timerange)
|
timerange = misc.parse_timerange(args.timerange)
|
||||||
data = optimize.load_data(args.datadir, pairs=pairs,
|
data = optimize.load_data(args.datadir, pairs=pairs,
|
||||||
ticker_interval=args.ticker_interval,
|
ticker_interval=args.ticker_interval,
|
||||||
@ -303,7 +203,7 @@ def start(args):
|
|||||||
try:
|
try:
|
||||||
best_parameters = fmin(
|
best_parameters = fmin(
|
||||||
fn=optimizer,
|
fn=optimizer,
|
||||||
space=SPACE,
|
space=strategy.hyperopt_space(),
|
||||||
algo=tpe.suggest,
|
algo=tpe.suggest,
|
||||||
max_evals=TOTAL_TRIES,
|
max_evals=TOTAL_TRIES,
|
||||||
trials=TRIALS
|
trials=TRIALS
|
||||||
@ -319,7 +219,10 @@ def start(args):
|
|||||||
|
|
||||||
# Improve best parameter logging display
|
# Improve best parameter logging display
|
||||||
if best_parameters:
|
if best_parameters:
|
||||||
best_parameters = space_eval(SPACE, best_parameters)
|
best_parameters = space_eval(
|
||||||
|
strategy.hyperopt_space(),
|
||||||
|
best_parameters
|
||||||
|
)
|
||||||
|
|
||||||
logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
|
logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
|
||||||
logger.info('Best Result:\n%s', best_result)
|
logger.info('Best Result:\n%s', best_result)
|
||||||
|
0
freqtrade/strategy/__init__.py
Normal file
0
freqtrade/strategy/__init__.py
Normal file
375
freqtrade/strategy/default_strategy.py
Normal file
375
freqtrade/strategy/default_strategy.py
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
import talib.abstract as ta
|
||||||
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from pandas import DataFrame
|
||||||
|
from hyperopt import hp
|
||||||
|
from functools import reduce
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
class_name = 'DefaultStrategy'
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultStrategy(IStrategy):
|
||||||
|
"""
|
||||||
|
Default Strategy provided by freqtrade bot.
|
||||||
|
You can override it with your own strategy
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy
|
||||||
|
minimal_roi = {
|
||||||
|
"40": 0.0,
|
||||||
|
"30": 0.01,
|
||||||
|
"20": 0.02,
|
||||||
|
"0": 0.04
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
stoploss = -0.10
|
||||||
|
|
||||||
|
# Optimal ticker interval for the strategy
|
||||||
|
ticker_interval = 5
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
|
Performance Note: For the best performance be frugal on the number of indicators
|
||||||
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Momentum Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# ADX
|
||||||
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
|
|
||||||
|
# Awesome oscillator
|
||||||
|
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
||||||
|
"""
|
||||||
|
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
|
||||||
|
dataframe['cci'] = ta.CCI(dataframe)
|
||||||
|
"""
|
||||||
|
# MACD
|
||||||
|
macd = ta.MACD(dataframe)
|
||||||
|
dataframe['macd'] = macd['macd']
|
||||||
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
|
|
||||||
|
# MFI
|
||||||
|
dataframe['mfi'] = ta.MFI(dataframe)
|
||||||
|
|
||||||
|
# Minus Directional Indicator / Movement
|
||||||
|
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# Plus Directional Indicator / Movement
|
||||||
|
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
||||||
|
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# ROC
|
||||||
|
dataframe['roc'] = ta.ROC(dataframe)
|
||||||
|
"""
|
||||||
|
# RSI
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe)
|
||||||
|
"""
|
||||||
|
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
||||||
|
rsi = 0.1 * (dataframe['rsi'] - 50)
|
||||||
|
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
||||||
|
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
|
||||||
|
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
|
||||||
|
# Stoch
|
||||||
|
stoch = ta.STOCH(dataframe)
|
||||||
|
dataframe['slowd'] = stoch['slowd']
|
||||||
|
dataframe['slowk'] = stoch['slowk']
|
||||||
|
"""
|
||||||
|
# Stoch fast
|
||||||
|
stoch_fast = ta.STOCHF(dataframe)
|
||||||
|
dataframe['fastd'] = stoch_fast['fastd']
|
||||||
|
dataframe['fastk'] = stoch_fast['fastk']
|
||||||
|
"""
|
||||||
|
# Stoch RSI
|
||||||
|
stoch_rsi = ta.STOCHRSI(dataframe)
|
||||||
|
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
||||||
|
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Overlap Studies
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# Previous Bollinger bands
|
||||||
|
# Because ta.BBANDS implementation is broken with small numbers, it actually
|
||||||
|
# returns middle band for all the three bands. Switch to qtpylib.bollinger_bands
|
||||||
|
# and use middle band instead.
|
||||||
|
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
||||||
|
|
||||||
|
# Bollinger bands
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
|
||||||
|
# EMA - Exponential Moving Average
|
||||||
|
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
||||||
|
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
||||||
|
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||||
|
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
||||||
|
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
||||||
|
|
||||||
|
# SAR Parabol
|
||||||
|
dataframe['sar'] = ta.SAR(dataframe)
|
||||||
|
|
||||||
|
# SMA - Simple Moving Average
|
||||||
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
|
|
||||||
|
# TEMA - Triple Exponential Moving Average
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
|
||||||
|
# Cycle Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
# Hilbert Transform Indicator - SineWave
|
||||||
|
hilbert = ta.HT_SINE(dataframe)
|
||||||
|
dataframe['htsine'] = hilbert['sine']
|
||||||
|
dataframe['htleadsine'] = hilbert['leadsine']
|
||||||
|
|
||||||
|
# Pattern Recognition - Bullish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Hammer: values [0, 100]
|
||||||
|
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
||||||
|
# Inverted Hammer: values [0, 100]
|
||||||
|
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
|
||||||
|
# Dragonfly Doji: values [0, 100]
|
||||||
|
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
|
||||||
|
# Piercing Line: values [0, 100]
|
||||||
|
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
|
||||||
|
# Morningstar: values [0, 100]
|
||||||
|
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
||||||
|
# Three White Soldiers: values [0, 100]
|
||||||
|
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Hanging Man: values [0, 100]
|
||||||
|
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
||||||
|
# Shooting Star: values [0, 100]
|
||||||
|
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
|
||||||
|
# Gravestone Doji: values [0, 100]
|
||||||
|
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
|
||||||
|
# Dark Cloud Cover: values [0, 100]
|
||||||
|
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
|
||||||
|
# Evening Doji Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
||||||
|
# Evening Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Three Line Strike: values [0, -100, 100]
|
||||||
|
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
||||||
|
# Spinning Top: values [0, -100, 100]
|
||||||
|
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
|
||||||
|
# Engulfing: values [0, -100, 100]
|
||||||
|
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
|
||||||
|
# Harami: values [0, -100, 100]
|
||||||
|
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
|
||||||
|
# Three Outside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
# Three Inside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Chart type
|
||||||
|
# ------------------------------------
|
||||||
|
# Heikinashi stategy
|
||||||
|
heikinashi = qtpylib.heikinashi(dataframe)
|
||||||
|
dataframe['ha_open'] = heikinashi['open']
|
||||||
|
dataframe['ha_close'] = heikinashi['close']
|
||||||
|
dataframe['ha_high'] = heikinashi['high']
|
||||||
|
dataframe['ha_low'] = heikinashi['low']
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['rsi'] < 35) &
|
||||||
|
(dataframe['fastd'] < 35) &
|
||||||
|
(dataframe['adx'] > 30) &
|
||||||
|
(dataframe['plus_di'] > 0.5)
|
||||||
|
) |
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 65) &
|
||||||
|
(dataframe['plus_di'] > 0.5)
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(qtpylib.crossed_above(dataframe['rsi'], 70)) |
|
||||||
|
(qtpylib.crossed_above(dataframe['fastd'], 70))
|
||||||
|
) &
|
||||||
|
(dataframe['adx'] > 10) &
|
||||||
|
(dataframe['minus_di'] > 0)
|
||||||
|
) |
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 70) &
|
||||||
|
(dataframe['minus_di'] > 0.5)
|
||||||
|
),
|
||||||
|
'sell'] = 1
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def hyperopt_space(self) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Define your Hyperopt space for the strategy
|
||||||
|
"""
|
||||||
|
space = {
|
||||||
|
'macd_below_zero': hp.choice('macd_below_zero', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'mfi': hp.choice('mfi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
|
||||||
|
]),
|
||||||
|
'fastd': hp.choice('fastd', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)}
|
||||||
|
]),
|
||||||
|
'adx': hp.choice('adx', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
|
||||||
|
]),
|
||||||
|
'rsi': hp.choice('rsi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
||||||
|
]),
|
||||||
|
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_short_ema': hp.choice('uptrend_short_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'over_sar': hp.choice('over_sar', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'green_candle': hp.choice('green_candle', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_sma': hp.choice('uptrend_sma', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'trigger': hp.choice('trigger', [
|
||||||
|
{'type': 'lower_bb'},
|
||||||
|
{'type': 'lower_bb_tema'},
|
||||||
|
{'type': 'faststoch10'},
|
||||||
|
{'type': 'ao_cross_zero'},
|
||||||
|
{'type': 'ema3_cross_ema10'},
|
||||||
|
{'type': 'macd_cross_signal'},
|
||||||
|
{'type': 'sar_reversal'},
|
||||||
|
{'type': 'ht_sine'},
|
||||||
|
{'type': 'heiken_reversal_bull'},
|
||||||
|
{'type': 'di_cross'},
|
||||||
|
]),
|
||||||
|
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
|
||||||
|
}
|
||||||
|
return space
|
||||||
|
|
||||||
|
def buy_strategy_generator(self, params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
||||||
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
|
conditions = []
|
||||||
|
# GUARDS AND TRENDS
|
||||||
|
if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema50'] > dataframe['ema100'])
|
||||||
|
if 'macd_below_zero' in params and params['macd_below_zero']['enabled']:
|
||||||
|
conditions.append(dataframe['macd'] < 0)
|
||||||
|
if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema5'] > dataframe['ema10'])
|
||||||
|
if 'mfi' in params and params['mfi']['enabled']:
|
||||||
|
conditions.append(dataframe['mfi'] < params['mfi']['value'])
|
||||||
|
if 'fastd' in params and params['fastd']['enabled']:
|
||||||
|
conditions.append(dataframe['fastd'] < params['fastd']['value'])
|
||||||
|
if 'adx' in params and params['adx']['enabled']:
|
||||||
|
conditions.append(dataframe['adx'] > params['adx']['value'])
|
||||||
|
if 'rsi' in params and params['rsi']['enabled']:
|
||||||
|
conditions.append(dataframe['rsi'] < params['rsi']['value'])
|
||||||
|
if 'over_sar' in params and params['over_sar']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['sar'])
|
||||||
|
if 'green_candle' in params and params['green_candle']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['open'])
|
||||||
|
if 'uptrend_sma' in params and params['uptrend_sma']['enabled']:
|
||||||
|
prevsma = dataframe['sma'].shift(1)
|
||||||
|
conditions.append(dataframe['sma'] > prevsma)
|
||||||
|
|
||||||
|
# TRIGGERS
|
||||||
|
triggers = {
|
||||||
|
'lower_bb': (
|
||||||
|
dataframe['close'] < dataframe['bb_lowerband']
|
||||||
|
),
|
||||||
|
'lower_bb_tema': (
|
||||||
|
dataframe['tema'] < dataframe['bb_lowerband']
|
||||||
|
),
|
||||||
|
'faststoch10': (qtpylib.crossed_above(
|
||||||
|
dataframe['fastd'], 10.0
|
||||||
|
)),
|
||||||
|
'ao_cross_zero': (qtpylib.crossed_above(
|
||||||
|
dataframe['ao'], 0.0
|
||||||
|
)),
|
||||||
|
'ema3_cross_ema10': (qtpylib.crossed_above(
|
||||||
|
dataframe['ema3'], dataframe['ema10']
|
||||||
|
)),
|
||||||
|
'macd_cross_signal': (qtpylib.crossed_above(
|
||||||
|
dataframe['macd'], dataframe['macdsignal']
|
||||||
|
)),
|
||||||
|
'sar_reversal': (qtpylib.crossed_above(
|
||||||
|
dataframe['close'], dataframe['sar']
|
||||||
|
)),
|
||||||
|
'ht_sine': (qtpylib.crossed_above(
|
||||||
|
dataframe['htleadsine'], dataframe['htsine']
|
||||||
|
)),
|
||||||
|
'heiken_reversal_bull': (
|
||||||
|
(qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) &
|
||||||
|
(dataframe['ha_low'] == dataframe['ha_open'])
|
||||||
|
),
|
||||||
|
'di_cross': (qtpylib.crossed_above(
|
||||||
|
dataframe['plus_di'], dataframe['minus_di']
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
conditions.append(triggers.get(params['trigger']['type']))
|
||||||
|
|
||||||
|
dataframe.loc[
|
||||||
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
return populate_buy_trend
|
57
freqtrade/strategy/interface.py
Normal file
57
freqtrade/strategy/interface.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from pandas import DataFrame
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
|
||||||
|
class IStrategy(ABC):
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""
|
||||||
|
Name of the strategy.
|
||||||
|
:return: str representation of the class name
|
||||||
|
"""
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
|
"""
|
||||||
|
Attributes you can use:
|
||||||
|
minimal_roi -> Dict: Minimal ROI designed for the strategy
|
||||||
|
stoploss -> float: optimal stoploss designed for the strategy
|
||||||
|
ticker_interval -> int: value of the ticker interval to use for the strategy
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Populate indicators that will be used in the Buy and Sell strategy
|
||||||
|
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def hyperopt_space(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Define your Hyperopt space for the strategy
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def buy_strategy_generator(self, params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
178
freqtrade/strategy/strategy.py
Normal file
178
freqtrade/strategy/strategy.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
from pandas import DataFrame
|
||||||
|
from typing import Dict
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
|
sys.path.insert(0, r'../../user_data/strategies')
|
||||||
|
|
||||||
|
|
||||||
|
class Strategy(object):
|
||||||
|
__instance = None
|
||||||
|
|
||||||
|
DEFAULT_STRATEGY = 'default_strategy'
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
if Strategy.__instance is None:
|
||||||
|
Strategy.__instance = object.__new__(cls)
|
||||||
|
return Strategy.__instance
|
||||||
|
|
||||||
|
def init(self, config):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
|
||||||
|
if 'strategy' in config:
|
||||||
|
strategy = config['strategy']
|
||||||
|
else:
|
||||||
|
strategy = self.DEFAULT_STRATEGY
|
||||||
|
|
||||||
|
# Load the strategy
|
||||||
|
self._load_strategy(strategy)
|
||||||
|
|
||||||
|
# Set attributes
|
||||||
|
# Check if we need to override configuration
|
||||||
|
if 'minimal_roi' in config:
|
||||||
|
self.custom_strategy.minimal_roi = config['minimal_roi']
|
||||||
|
self.logger.info("Override strategy \'minimal_roi\' with value in config file.")
|
||||||
|
|
||||||
|
if 'stoploss' in config:
|
||||||
|
self.custom_strategy.stoploss = config['stoploss']
|
||||||
|
self.logger.info(
|
||||||
|
"Override strategy \'stoploss\' with value in config file: {}.".format(
|
||||||
|
config['stoploss']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'ticker_interval' in config:
|
||||||
|
self.custom_strategy.ticker_interval = config['ticker_interval']
|
||||||
|
self.logger.info(
|
||||||
|
"Override strategy \'ticker_interval\' with value in config file: {}.".format(
|
||||||
|
config['ticker_interval']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.minimal_roi = self.custom_strategy.minimal_roi
|
||||||
|
self.stoploss = self.custom_strategy.stoploss
|
||||||
|
self.ticker_interval = self.custom_strategy.ticker_interval
|
||||||
|
|
||||||
|
def _load_strategy(self, strategy_name: str) -> None:
|
||||||
|
"""
|
||||||
|
Search and load the custom strategy. If no strategy found, fallback on the default strategy
|
||||||
|
Set the object into self.custom_strategy
|
||||||
|
:param strategy_name: name of the module to import
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Start by sanitizing the file name (remove any extensions)
|
||||||
|
strategy_name = self._sanitize_module_name(filename=strategy_name)
|
||||||
|
|
||||||
|
# Search where can be the strategy file
|
||||||
|
path = self._search_strategy(filename=strategy_name)
|
||||||
|
|
||||||
|
# Load the strategy
|
||||||
|
self.custom_strategy = self._load_class(path + strategy_name)
|
||||||
|
|
||||||
|
# Fallback to the default strategy
|
||||||
|
except (ImportError, TypeError):
|
||||||
|
self.custom_strategy = self._load_class('.' + self.DEFAULT_STRATEGY)
|
||||||
|
|
||||||
|
def _load_class(self, filename: str) -> IStrategy:
|
||||||
|
"""
|
||||||
|
Import a strategy as a module
|
||||||
|
:param filename: path to the strategy (path from freqtrade/strategy/)
|
||||||
|
:return: return the strategy class
|
||||||
|
"""
|
||||||
|
module = importlib.import_module(filename, __package__)
|
||||||
|
custom_strategy = getattr(module, module.class_name)
|
||||||
|
|
||||||
|
self.logger.info("Load strategy class: {} ({}.py)".format(module.class_name, filename))
|
||||||
|
return custom_strategy()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sanitize_module_name(filename: str) -> str:
|
||||||
|
"""
|
||||||
|
Remove any extension from filename
|
||||||
|
:param filename: filename to sanatize
|
||||||
|
:return: return the filename without extensions
|
||||||
|
"""
|
||||||
|
filename = os.path.basename(filename)
|
||||||
|
filename = os.path.splitext(filename)[0]
|
||||||
|
return filename
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _search_strategy(filename: str) -> str:
|
||||||
|
"""
|
||||||
|
Search for the Strategy file in different folder
|
||||||
|
1. search into the user_data/strategies folder
|
||||||
|
2. search into the freqtrade/strategy folder
|
||||||
|
3. if nothing found, return None
|
||||||
|
:param strategy_name: module name to search
|
||||||
|
:return: module path where is the strategy
|
||||||
|
"""
|
||||||
|
pwd = os.path.dirname(os.path.realpath(__file__)) + '/'
|
||||||
|
user_data = os.path.join(pwd, '..', '..', 'user_data', 'strategies', filename + '.py')
|
||||||
|
strategy_folder = os.path.join(pwd, filename + '.py')
|
||||||
|
|
||||||
|
path = None
|
||||||
|
if os.path.isfile(user_data):
|
||||||
|
path = 'user_data.strategies.'
|
||||||
|
elif os.path.isfile(strategy_folder):
|
||||||
|
path = '.'
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
def minimal_roi(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Minimal ROI designed for the strategy
|
||||||
|
:return: Dict: Value for the Minimal ROI
|
||||||
|
"""
|
||||||
|
return
|
||||||
|
|
||||||
|
def stoploss(self) -> float:
|
||||||
|
"""
|
||||||
|
Optimal stoploss designed for the strategy
|
||||||
|
:return: float | return None to disable it
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.stoploss
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Populate indicators that will be used in the Buy and Sell strategy
|
||||||
|
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||||
|
:return: a Dataframe with all mandatory indicators for the strategies
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.populate_indicators(dataframe)
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.populate_buy_trend(dataframe)
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.populate_sell_trend(dataframe)
|
||||||
|
|
||||||
|
def hyperopt_space(self) -> Dict:
|
||||||
|
"""
|
||||||
|
Define your Hyperopt space for the strategy
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.hyperopt_space()
|
||||||
|
|
||||||
|
def buy_strategy_generator(self, params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
||||||
|
return self.custom_strategy.buy_strategy_generator(params)
|
@ -1,6 +1,6 @@
|
|||||||
# pragma pylint: disable=missing-docstring,W0212
|
# pragma pylint: disable=missing-docstring,W0212
|
||||||
|
|
||||||
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf
|
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
||||||
|
|
||||||
|
|
||||||
def test_hyperopt_optimize_conf():
|
def test_hyperopt_optimize_conf():
|
||||||
|
40
freqtrade/tests/strategy/test_default_strategy.py
Normal file
40
freqtrade/tests/strategy/test_default_strategy.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import json
|
||||||
|
import pytest
|
||||||
|
from pandas import DataFrame
|
||||||
|
from freqtrade.strategy.default_strategy import DefaultStrategy, class_name
|
||||||
|
from freqtrade.analyze import parse_ticker_dataframe
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def result():
|
||||||
|
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
||||||
|
return parse_ticker_dataframe(json.load(data_file))
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_strategy_class_name():
|
||||||
|
assert class_name == DefaultStrategy.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_strategy_structure():
|
||||||
|
assert hasattr(DefaultStrategy, 'minimal_roi')
|
||||||
|
assert hasattr(DefaultStrategy, 'stoploss')
|
||||||
|
assert hasattr(DefaultStrategy, 'ticker_interval')
|
||||||
|
assert hasattr(DefaultStrategy, 'populate_indicators')
|
||||||
|
assert hasattr(DefaultStrategy, 'populate_buy_trend')
|
||||||
|
assert hasattr(DefaultStrategy, 'populate_sell_trend')
|
||||||
|
assert hasattr(DefaultStrategy, 'hyperopt_space')
|
||||||
|
assert hasattr(DefaultStrategy, 'buy_strategy_generator')
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_strategy(result):
|
||||||
|
strategy = DefaultStrategy()
|
||||||
|
|
||||||
|
assert type(strategy.minimal_roi) is dict
|
||||||
|
assert type(strategy.stoploss) is float
|
||||||
|
assert type(strategy.ticker_interval) is int
|
||||||
|
indicators = strategy.populate_indicators(result)
|
||||||
|
assert type(indicators) is DataFrame
|
||||||
|
assert type(strategy.populate_buy_trend(indicators)) is DataFrame
|
||||||
|
assert type(strategy.populate_sell_trend(indicators)) is DataFrame
|
||||||
|
assert type(strategy.hyperopt_space()) is dict
|
||||||
|
assert callable(strategy.buy_strategy_generator({}))
|
149
freqtrade/tests/strategy/test_strategy.py
Normal file
149
freqtrade/tests/strategy/test_strategy.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import pytest
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
from freqtrade.analyze import parse_ticker_dataframe
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def result():
|
||||||
|
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
||||||
|
return parse_ticker_dataframe(json.load(data_file))
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanitize_module_name():
|
||||||
|
assert Strategy._sanitize_module_name('default_strategy') == 'default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('default_strategy.py') == 'default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('../default_strategy.py') == 'default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('../default_strategy') == 'default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('.default_strategy') == '.default_strategy'
|
||||||
|
assert Strategy._sanitize_module_name('foo-bar') == 'foo-bar'
|
||||||
|
assert Strategy._sanitize_module_name('foo/bar') == 'bar'
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_strategy():
|
||||||
|
assert Strategy._search_strategy('default_strategy') == '.'
|
||||||
|
assert Strategy._search_strategy('super_duper') is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_structure():
|
||||||
|
assert hasattr(Strategy, 'init')
|
||||||
|
assert hasattr(Strategy, 'minimal_roi')
|
||||||
|
assert hasattr(Strategy, 'stoploss')
|
||||||
|
assert hasattr(Strategy, 'populate_indicators')
|
||||||
|
assert hasattr(Strategy, 'populate_buy_trend')
|
||||||
|
assert hasattr(Strategy, 'populate_sell_trend')
|
||||||
|
assert hasattr(Strategy, 'hyperopt_space')
|
||||||
|
assert hasattr(Strategy, 'buy_strategy_generator')
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_strategy(result):
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
assert not hasattr(Strategy, 'custom_strategy')
|
||||||
|
strategy._load_strategy('default_strategy')
|
||||||
|
|
||||||
|
assert not hasattr(Strategy, 'custom_strategy')
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'populate_indicators')
|
||||||
|
assert 'adx' in strategy.populate_indicators(result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy(result):
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init({'strategy': 'default_strategy'})
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'minimal_roi')
|
||||||
|
assert strategy.minimal_roi['0'] == 0.04
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'stoploss')
|
||||||
|
assert strategy.stoploss == -0.10
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'populate_indicators')
|
||||||
|
assert 'adx' in strategy.populate_indicators(result)
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'populate_buy_trend')
|
||||||
|
dataframe = strategy.populate_buy_trend(strategy.populate_indicators(result))
|
||||||
|
assert 'buy' in dataframe.columns
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'populate_sell_trend')
|
||||||
|
dataframe = strategy.populate_sell_trend(strategy.populate_indicators(result))
|
||||||
|
assert 'sell' in dataframe.columns
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'hyperopt_space')
|
||||||
|
assert 'adx' in strategy.hyperopt_space()
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'buy_strategy_generator')
|
||||||
|
assert callable(strategy.buy_strategy_generator({}))
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_override_minimal_roi(caplog):
|
||||||
|
config = {
|
||||||
|
'strategy': 'default_strategy',
|
||||||
|
'minimal_roi': {
|
||||||
|
"0": 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'minimal_roi')
|
||||||
|
assert strategy.minimal_roi['0'] == 0.5
|
||||||
|
assert ('freqtrade.strategy.strategy',
|
||||||
|
logging.INFO,
|
||||||
|
'Override strategy \'minimal_roi\' with value in config file.'
|
||||||
|
) in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_override_stoploss(caplog):
|
||||||
|
config = {
|
||||||
|
'strategy': 'default_strategy',
|
||||||
|
'stoploss': -0.5
|
||||||
|
}
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'stoploss')
|
||||||
|
assert strategy.stoploss == -0.5
|
||||||
|
assert ('freqtrade.strategy.strategy',
|
||||||
|
logging.INFO,
|
||||||
|
'Override strategy \'stoploss\' with value in config file: -0.5.'
|
||||||
|
) in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_override_ticker_interval(caplog):
|
||||||
|
config = {
|
||||||
|
'strategy': 'default_strategy',
|
||||||
|
'ticker_interval': 60
|
||||||
|
}
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
|
assert hasattr(strategy.custom_strategy, 'ticker_interval')
|
||||||
|
assert strategy.ticker_interval == 60
|
||||||
|
assert ('freqtrade.strategy.strategy',
|
||||||
|
logging.INFO,
|
||||||
|
'Override strategy \'ticker_interval\' with value in config file: 60.'
|
||||||
|
) in caplog.record_tuples
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_fallback_default_strategy():
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
assert not hasattr(Strategy, 'custom_strategy')
|
||||||
|
strategy._load_strategy('../../super_duper')
|
||||||
|
assert not hasattr(Strategy, 'custom_strategy')
|
||||||
|
|
||||||
|
|
||||||
|
def test_strategy_singleton():
|
||||||
|
strategy1 = Strategy()
|
||||||
|
strategy1.init({'strategy': 'default_strategy'})
|
||||||
|
|
||||||
|
assert hasattr(strategy1.custom_strategy, 'minimal_roi')
|
||||||
|
assert strategy1.minimal_roi['0'] == 0.04
|
||||||
|
|
||||||
|
strategy2 = Strategy()
|
||||||
|
assert hasattr(strategy2.custom_strategy, 'minimal_roi')
|
||||||
|
assert strategy2.minimal_roi['0'] == 0.04
|
@ -9,6 +9,7 @@ from pandas import DataFrame
|
|||||||
from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
|
from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
|
||||||
populate_buy_trend, populate_indicators,
|
populate_buy_trend, populate_indicators,
|
||||||
populate_sell_trend)
|
populate_sell_trend)
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -27,11 +28,17 @@ def test_dataframe_correct_length(result):
|
|||||||
|
|
||||||
|
|
||||||
def test_populates_buy_trend(result):
|
def test_populates_buy_trend(result):
|
||||||
|
# Load the default strategy for the unit test, because this logic is done in main.py
|
||||||
|
Strategy().init({'strategy': 'default_strategy'})
|
||||||
|
|
||||||
dataframe = populate_buy_trend(populate_indicators(result))
|
dataframe = populate_buy_trend(populate_indicators(result))
|
||||||
assert 'buy' in dataframe.columns
|
assert 'buy' in dataframe.columns
|
||||||
|
|
||||||
|
|
||||||
def test_populates_sell_trend(result):
|
def test_populates_sell_trend(result):
|
||||||
|
# Load the default strategy for the unit test, because this logic is done in main.py
|
||||||
|
Strategy().init({'strategy': 'default_strategy'})
|
||||||
|
|
||||||
dataframe = populate_sell_trend(populate_indicators(result))
|
dataframe = populate_sell_trend(populate_indicators(result))
|
||||||
assert 'sell' in dataframe.columns
|
assert 'sell' in dataframe.columns
|
||||||
|
|
||||||
|
@ -1,47 +1,53 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import talib.abstract as ta
|
||||||
import matplotlib # Install PYQT5 manually if you want to test this helper function
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
matplotlib.use("Qt5Agg")
|
from pandas import DataFrame
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
from freqtrade import exchange, analyze
|
from freqtrade import exchange, analyze
|
||||||
from freqtrade.misc import common_args_parser
|
from freqtrade.misc import common_args_parser
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib # Install PYQT5 manually if you want to test this helper function
|
||||||
|
|
||||||
|
matplotlib.use("Qt5Agg")
|
||||||
|
|
||||||
|
|
||||||
def plot_parse_args(args ):
|
def plot_parse_args(args):
|
||||||
parser = common_args_parser(description='Graph utility')
|
parser = common_args_parser(description='Graph utility')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--pair',
|
'-p', '--pair',
|
||||||
help = 'What currency pair',
|
help='What currency pair',
|
||||||
dest = 'pair',
|
dest='pair',
|
||||||
default = 'BTC_ETH',
|
default='BTC_ETH',
|
||||||
type = str,
|
type=str,
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-i', '--interval',
|
'-i', '--interval',
|
||||||
help = 'what interval to use',
|
help='what interval to use',
|
||||||
dest = 'interval',
|
dest='interval',
|
||||||
default = '5',
|
default=5,
|
||||||
type = int,
|
type=int,
|
||||||
)
|
)
|
||||||
return parser.parse_args(args)
|
return parser.parse_args(args)
|
||||||
|
|
||||||
|
|
||||||
def plot_analyzed_dataframe(args):
|
def plot_analyzed_dataframe(args) -> None:
|
||||||
"""
|
"""
|
||||||
Calls analyze() and plots the returned dataframe
|
Calls analyze() and plots the returned dataframe
|
||||||
:param pair: pair as str
|
:param pair: pair as str
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
# Init strategy
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init({'strategy': args.strategy})
|
||||||
|
|
||||||
# Init Bittrex to use public API
|
# Init Bittrex to use public API
|
||||||
exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
|
exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
|
||||||
ticker = exchange.get_ticker_history(args.pair,args.interval)
|
ticker = exchange.get_ticker_history(args.pair, args.interval)
|
||||||
dataframe = analyze.analyze_ticker(ticker)
|
dataframe = analyze.analyze_ticker(ticker)
|
||||||
|
|
||||||
dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close']
|
dataframe = populate_indicator(dataframe)
|
||||||
dataframe.loc[dataframe['sell'] == 1, 'sell_price'] = dataframe['close']
|
|
||||||
|
|
||||||
# Two subplots sharing x axis
|
# Two subplots sharing x axis
|
||||||
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True)
|
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True)
|
||||||
@ -50,7 +56,7 @@ def plot_analyzed_dataframe(args):
|
|||||||
# ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell')
|
# ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell')
|
||||||
ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA')
|
ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA')
|
||||||
ax1.plot(dataframe.index.values, dataframe['tema'], ':', label='TEMA')
|
ax1.plot(dataframe.index.values, dataframe['tema'], ':', label='TEMA')
|
||||||
ax1.plot(dataframe.index.values, dataframe['blower'], '-.', label='BB low')
|
ax1.plot(dataframe.index.values, dataframe['bb_lowerband'], '-.', label='BB low')
|
||||||
ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy')
|
ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy')
|
||||||
ax1.legend()
|
ax1.legend()
|
||||||
|
|
||||||
@ -71,6 +77,41 @@ def plot_analyzed_dataframe(args):
|
|||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def populate_indicator(dataframe: DataFrame) -> DataFrame:
|
||||||
|
|
||||||
|
dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close']
|
||||||
|
dataframe.loc[dataframe['sell'] == 1, 'sell_price'] = dataframe['close']
|
||||||
|
|
||||||
|
# ADX
|
||||||
|
if 'adx' not in dataframe:
|
||||||
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
|
|
||||||
|
# Bollinger bands
|
||||||
|
if 'bb_lowerband' not in dataframe:
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
|
||||||
|
# Stoch fast
|
||||||
|
if 'fastd' not in dataframe or 'fastk' not in dataframe:
|
||||||
|
stoch_fast = ta.STOCHF(dataframe)
|
||||||
|
dataframe['fastd'] = stoch_fast['fastd']
|
||||||
|
dataframe['fastk'] = stoch_fast['fastk']
|
||||||
|
|
||||||
|
# MFI
|
||||||
|
if 'mfi' not in dataframe:
|
||||||
|
dataframe['mfi'] = ta.MFI(dataframe)
|
||||||
|
|
||||||
|
# SMA - Simple Moving Average
|
||||||
|
if 'sma' not in dataframe:
|
||||||
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
|
|
||||||
|
# TEMA - Triple Exponential Moving Average
|
||||||
|
if 'tema' not in dataframe:
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
args = plot_parse_args(sys.argv[1:])
|
args = plot_parse_args(sys.argv[1:])
|
||||||
plot_analyzed_dataframe(args)
|
plot_analyzed_dataframe(args)
|
||||||
|
@ -1,26 +1,24 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
|
||||||
import json
|
import json
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import freqtrade.optimize as optimize
|
import freqtrade.optimize as optimize
|
||||||
import freqtrade.misc as misc
|
import freqtrade.misc as misc
|
||||||
import freqtrade.exchange as exchange
|
from freqtrade.strategy.strategy import Strategy
|
||||||
import freqtrade.analyze as analyze
|
|
||||||
|
|
||||||
|
|
||||||
def plot_parse_args(args ):
|
def plot_parse_args(args):
|
||||||
parser = misc.common_args_parser('Graph utility')
|
parser = misc.common_args_parser('Graph utility')
|
||||||
# FIX: perhaps delete those backtesting options that are not feasible (shows up in -h)
|
# FIX: perhaps delete those backtesting options that are not feasible (shows up in -h)
|
||||||
misc.backtesting_options(parser)
|
misc.backtesting_options(parser)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--pair',
|
'-p', '--pair',
|
||||||
help = 'Show profits for only this pairs. Pairs are comma-separated.',
|
help='Show profits for only this pairs. Pairs are comma-separated.',
|
||||||
dest = 'pair',
|
dest='pair',
|
||||||
default = None
|
default=None
|
||||||
)
|
)
|
||||||
return parser.parse_args(args)
|
return parser.parse_args(args)
|
||||||
|
|
||||||
@ -47,7 +45,7 @@ def make_profit_array(data, px, filter_pairs=[]):
|
|||||||
# total profits at each timeframe
|
# total profits at each timeframe
|
||||||
# to accumulated profits
|
# to accumulated profits
|
||||||
pa = 0
|
pa = 0
|
||||||
for x in range(0,len(pg)):
|
for x in range(0, len(pg)):
|
||||||
p = pg[x] # Get current total percent
|
p = pg[x] # Get current total percent
|
||||||
pa += p # Add to the accumulated percent
|
pa += p # Add to the accumulated percent
|
||||||
pg[x] = pa # write back to save memory
|
pg[x] = pa # write back to save memory
|
||||||
@ -70,14 +68,21 @@ def plot_profit(args) -> None:
|
|||||||
filter_pairs = args.pair
|
filter_pairs = args.pair
|
||||||
|
|
||||||
config = misc.load_config(args.config)
|
config = misc.load_config(args.config)
|
||||||
|
config.update({'strategy': args.strategy})
|
||||||
|
|
||||||
|
# Init strategy
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.init(config)
|
||||||
|
|
||||||
pairs = config['exchange']['pair_whitelist']
|
pairs = config['exchange']['pair_whitelist']
|
||||||
|
|
||||||
if filter_pairs:
|
if filter_pairs:
|
||||||
filter_pairs = filter_pairs.split(',')
|
filter_pairs = filter_pairs.split(',')
|
||||||
pairs = list(set(pairs) & set(filter_pairs))
|
pairs = list(set(pairs) & set(filter_pairs))
|
||||||
print('Filter, keep pairs %s' % pairs)
|
print('Filter, keep pairs %s' % pairs)
|
||||||
|
|
||||||
tickers = optimize.load_data(args.datadir, pairs=pairs,
|
tickers = optimize.load_data(args.datadir, pairs=pairs,
|
||||||
ticker_interval=args.ticker_interval,
|
ticker_interval=strategy.ticker_interval,
|
||||||
refresh_pairs=False)
|
refresh_pairs=False)
|
||||||
dataframes = optimize.preprocess(tickers)
|
dataframes = optimize.preprocess(tickers)
|
||||||
|
|
||||||
@ -96,7 +101,7 @@ def plot_profit(args) -> None:
|
|||||||
# if max_x != n:
|
# if max_x != n:
|
||||||
# raise Exception('Please rerun script. Input data has different lengths %s'
|
# raise Exception('Please rerun script. Input data has different lengths %s'
|
||||||
# %('Different pair length: %s <=> %s' %(max_x, n)))
|
# %('Different pair length: %s <=> %s' %(max_x, n)))
|
||||||
print('max_x: %s' %(max_x))
|
print('max_x: %s' % (max_x))
|
||||||
|
|
||||||
# We are essentially saying:
|
# We are essentially saying:
|
||||||
# array <- sum dataframes[*]['close'] / num_items dataframes
|
# array <- sum dataframes[*]['close'] / num_items dataframes
|
||||||
@ -106,7 +111,7 @@ def plot_profit(args) -> None:
|
|||||||
for pair, pair_data in dataframes.items():
|
for pair, pair_data in dataframes.items():
|
||||||
close = pair_data['close']
|
close = pair_data['close']
|
||||||
maxprice = max(close) # Normalize price to [0,1]
|
maxprice = max(close) # Normalize price to [0,1]
|
||||||
print('Pair %s has length %s' %(pair, len(close)))
|
print('Pair %s has length %s' % (pair, len(close)))
|
||||||
for x in range(0, len(close)):
|
for x in range(0, len(close)):
|
||||||
avgclose[x] += close[x] / maxprice
|
avgclose[x] += close[x] / maxprice
|
||||||
# avgclose += close
|
# avgclose += close
|
||||||
@ -118,7 +123,7 @@ def plot_profit(args) -> None:
|
|||||||
|
|
||||||
filename = 'backtest-result.json'
|
filename = 'backtest-result.json'
|
||||||
with open(filename) as file:
|
with open(filename) as file:
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
pg = make_profit_array(data, max_x, filter_pairs)
|
pg = make_profit_array(data, max_x, filter_pairs)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
0
user_data/data/.gitkeep
Normal file
0
user_data/data/.gitkeep
Normal file
0
user_data/strategies/__init__.py
Normal file
0
user_data/strategies/__init__.py
Normal file
393
user_data/strategies/test_strategy.py
Normal file
393
user_data/strategies/test_strategy.py
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
|
||||||
|
# --- Do not remove these libs ---
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
from typing import Dict, List
|
||||||
|
from hyperopt import hp
|
||||||
|
from functools import reduce
|
||||||
|
from pandas import DataFrame
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
# Add your lib to import here
|
||||||
|
import talib.abstract as ta
|
||||||
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
import numpy # noqa
|
||||||
|
|
||||||
|
|
||||||
|
# Update this variable if you change the class name
|
||||||
|
class_name = 'TestStrategy'
|
||||||
|
|
||||||
|
|
||||||
|
# This class is a sample. Feel free to customize it.
|
||||||
|
class TestStrategy(IStrategy):
|
||||||
|
"""
|
||||||
|
This is a test strategy to inspire you.
|
||||||
|
More information in https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md
|
||||||
|
|
||||||
|
You can:
|
||||||
|
- Rename the class name (Do not forget to update class_name)
|
||||||
|
- Add any methods you want to build your strategy
|
||||||
|
- Add any lib you need to build your strategy
|
||||||
|
|
||||||
|
You must keep:
|
||||||
|
- the lib in the section "Do not remove these libs"
|
||||||
|
- the prototype for the methods: minimal_roi, stoploss, populate_indicators, populate_buy_trend,
|
||||||
|
populate_sell_trend, hyperopt_space, buy_strategy_generator
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Minimal ROI designed for the strategy.
|
||||||
|
# This attribute will be overridden if the config file contains "minimal_roi"
|
||||||
|
minimal_roi = {
|
||||||
|
"40": 0.0,
|
||||||
|
"30": 0.01,
|
||||||
|
"20": 0.02,
|
||||||
|
"0": 0.04
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optimal stoploss designed for the strategy
|
||||||
|
# This attribute will be overridden if the config file contains "stoploss"
|
||||||
|
stoploss = -0.10
|
||||||
|
|
||||||
|
# Optimal ticker interval for the strategy
|
||||||
|
ticker_interval = 5
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Adds several different TA indicators to the given DataFrame
|
||||||
|
|
||||||
|
Performance Note: For the best performance be frugal on the number of indicators
|
||||||
|
you are using. Let uncomment only the indicator you are using in your strategies
|
||||||
|
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Momentum Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
# ADX
|
||||||
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Awesome oscillator
|
||||||
|
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
||||||
|
|
||||||
|
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
|
||||||
|
dataframe['cci'] = ta.CCI(dataframe)
|
||||||
|
|
||||||
|
# MACD
|
||||||
|
macd = ta.MACD(dataframe)
|
||||||
|
dataframe['macd'] = macd['macd']
|
||||||
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
|
|
||||||
|
# MFI
|
||||||
|
dataframe['mfi'] = ta.MFI(dataframe)
|
||||||
|
|
||||||
|
# Minus Directional Indicator / Movement
|
||||||
|
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# Plus Directional Indicator / Movement
|
||||||
|
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
||||||
|
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||||
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
|
|
||||||
|
# ROC
|
||||||
|
dataframe['roc'] = ta.ROC(dataframe)
|
||||||
|
|
||||||
|
# RSI
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe)
|
||||||
|
|
||||||
|
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
||||||
|
rsi = 0.1 * (dataframe['rsi'] - 50)
|
||||||
|
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
||||||
|
|
||||||
|
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
|
||||||
|
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
|
||||||
|
|
||||||
|
# Stoch
|
||||||
|
stoch = ta.STOCH(dataframe)
|
||||||
|
dataframe['slowd'] = stoch['slowd']
|
||||||
|
dataframe['slowk'] = stoch['slowk']
|
||||||
|
|
||||||
|
# Stoch fast
|
||||||
|
stoch_fast = ta.STOCHF(dataframe)
|
||||||
|
dataframe['fastd'] = stoch_fast['fastd']
|
||||||
|
dataframe['fastk'] = stoch_fast['fastk']
|
||||||
|
|
||||||
|
# Stoch RSI
|
||||||
|
stoch_rsi = ta.STOCHRSI(dataframe)
|
||||||
|
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
||||||
|
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Overlap Studies
|
||||||
|
# ------------------------------------
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Previous Bollinger bands
|
||||||
|
# Because ta.BBANDS implementation is broken with small numbers, it actually
|
||||||
|
# returns middle band for all the three bands. Switch to qtpylib.bollinger_bands
|
||||||
|
# and use middle band instead.
|
||||||
|
|
||||||
|
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Bollinger bands
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
|
|
||||||
|
"""
|
||||||
|
# EMA - Exponential Moving Average
|
||||||
|
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
||||||
|
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
||||||
|
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||||
|
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
||||||
|
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
||||||
|
|
||||||
|
# SAR Parabol
|
||||||
|
dataframe['sar'] = ta.SAR(dataframe)
|
||||||
|
|
||||||
|
# SMA - Simple Moving Average
|
||||||
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TEMA - Triple Exponential Moving Average
|
||||||
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
|
||||||
|
# Cycle Indicator
|
||||||
|
# ------------------------------------
|
||||||
|
# Hilbert Transform Indicator - SineWave
|
||||||
|
hilbert = ta.HT_SINE(dataframe)
|
||||||
|
dataframe['htsine'] = hilbert['sine']
|
||||||
|
dataframe['htleadsine'] = hilbert['leadsine']
|
||||||
|
|
||||||
|
# Pattern Recognition - Bullish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Hammer: values [0, 100]
|
||||||
|
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
||||||
|
# Inverted Hammer: values [0, 100]
|
||||||
|
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
|
||||||
|
# Dragonfly Doji: values [0, 100]
|
||||||
|
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
|
||||||
|
# Piercing Line: values [0, 100]
|
||||||
|
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
|
||||||
|
# Morningstar: values [0, 100]
|
||||||
|
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
||||||
|
# Three White Soldiers: values [0, 100]
|
||||||
|
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Hanging Man: values [0, 100]
|
||||||
|
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
||||||
|
# Shooting Star: values [0, 100]
|
||||||
|
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
|
||||||
|
# Gravestone Doji: values [0, 100]
|
||||||
|
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
|
||||||
|
# Dark Cloud Cover: values [0, 100]
|
||||||
|
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
|
||||||
|
# Evening Doji Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
||||||
|
# Evening Star: values [0, 100]
|
||||||
|
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Three Line Strike: values [0, -100, 100]
|
||||||
|
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
||||||
|
# Spinning Top: values [0, -100, 100]
|
||||||
|
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
|
||||||
|
# Engulfing: values [0, -100, 100]
|
||||||
|
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
|
||||||
|
# Harami: values [0, -100, 100]
|
||||||
|
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
|
||||||
|
# Three Outside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
# Three Inside Up/Down: values [0, -100, 100]
|
||||||
|
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Chart type
|
||||||
|
# ------------------------------------
|
||||||
|
"""
|
||||||
|
# Heikinashi stategy
|
||||||
|
heikinashi = qtpylib.heikinashi(dataframe)
|
||||||
|
dataframe['ha_open'] = heikinashi['open']
|
||||||
|
dataframe['ha_close'] = heikinashi['close']
|
||||||
|
dataframe['ha_high'] = heikinashi['high']
|
||||||
|
dataframe['ha_low'] = heikinashi['low']
|
||||||
|
"""
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 30) &
|
||||||
|
(dataframe['tema'] <= dataframe['blower']) &
|
||||||
|
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
|
:param dataframe: DataFrame
|
||||||
|
:return: DataFrame with buy column
|
||||||
|
"""
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['adx'] > 70) &
|
||||||
|
(dataframe['tema'] > dataframe['blower']) &
|
||||||
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
|
),
|
||||||
|
'sell'] = 1
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def hyperopt_space(self) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Define your Hyperopt space for the strategy
|
||||||
|
:return: Dict
|
||||||
|
"""
|
||||||
|
space = {
|
||||||
|
'macd_below_zero': hp.choice('macd_below_zero', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'mfi': hp.choice('mfi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
|
||||||
|
]),
|
||||||
|
'fastd': hp.choice('fastd', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)}
|
||||||
|
]),
|
||||||
|
'adx': hp.choice('adx', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
|
||||||
|
]),
|
||||||
|
'rsi': hp.choice('rsi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
||||||
|
]),
|
||||||
|
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_short_ema': hp.choice('uptrend_short_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'over_sar': hp.choice('over_sar', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'green_candle': hp.choice('green_candle', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_sma': hp.choice('uptrend_sma', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'trigger': hp.choice('trigger', [
|
||||||
|
{'type': 'lower_bb'},
|
||||||
|
{'type': 'lower_bb_tema'},
|
||||||
|
{'type': 'faststoch10'},
|
||||||
|
{'type': 'ao_cross_zero'},
|
||||||
|
{'type': 'ema3_cross_ema10'},
|
||||||
|
{'type': 'macd_cross_signal'},
|
||||||
|
{'type': 'sar_reversal'},
|
||||||
|
{'type': 'ht_sine'},
|
||||||
|
{'type': 'heiken_reversal_bull'},
|
||||||
|
{'type': 'di_cross'},
|
||||||
|
]),
|
||||||
|
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
|
||||||
|
}
|
||||||
|
return space
|
||||||
|
|
||||||
|
def buy_strategy_generator(self, params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
||||||
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
|
conditions = []
|
||||||
|
# GUARDS AND TRENDS
|
||||||
|
if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema50'] > dataframe['ema100'])
|
||||||
|
if 'macd_below_zero' in params and params['macd_below_zero']['enabled']:
|
||||||
|
conditions.append(dataframe['macd'] < 0)
|
||||||
|
if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema5'] > dataframe['ema10'])
|
||||||
|
if 'mfi' in params and params['mfi']['enabled']:
|
||||||
|
conditions.append(dataframe['mfi'] < params['mfi']['value'])
|
||||||
|
if 'fastd' in params and params['fastd']['enabled']:
|
||||||
|
conditions.append(dataframe['fastd'] < params['fastd']['value'])
|
||||||
|
if 'adx' in params and params['adx']['enabled']:
|
||||||
|
conditions.append(dataframe['adx'] > params['adx']['value'])
|
||||||
|
if 'rsi' in params and params['rsi']['enabled']:
|
||||||
|
conditions.append(dataframe['rsi'] < params['rsi']['value'])
|
||||||
|
if 'over_sar' in params and params['over_sar']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['sar'])
|
||||||
|
if 'green_candle' in params and params['green_candle']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['open'])
|
||||||
|
if 'uptrend_sma' in params and params['uptrend_sma']['enabled']:
|
||||||
|
prevsma = dataframe['sma'].shift(1)
|
||||||
|
conditions.append(dataframe['sma'] > prevsma)
|
||||||
|
|
||||||
|
# TRIGGERS
|
||||||
|
triggers = {
|
||||||
|
'lower_bb': (
|
||||||
|
dataframe['close'] < dataframe['bb_lowerband']
|
||||||
|
),
|
||||||
|
'lower_bb_tema': (
|
||||||
|
dataframe['tema'] < dataframe['bb_lowerband']
|
||||||
|
),
|
||||||
|
'faststoch10': (qtpylib.crossed_above(
|
||||||
|
dataframe['fastd'], 10.0
|
||||||
|
)),
|
||||||
|
'ao_cross_zero': (qtpylib.crossed_above(
|
||||||
|
dataframe['ao'], 0.0
|
||||||
|
)),
|
||||||
|
'ema3_cross_ema10': (qtpylib.crossed_above(
|
||||||
|
dataframe['ema3'], dataframe['ema10']
|
||||||
|
)),
|
||||||
|
'macd_cross_signal': (qtpylib.crossed_above(
|
||||||
|
dataframe['macd'], dataframe['macdsignal']
|
||||||
|
)),
|
||||||
|
'sar_reversal': (qtpylib.crossed_above(
|
||||||
|
dataframe['close'], dataframe['sar']
|
||||||
|
)),
|
||||||
|
'ht_sine': (qtpylib.crossed_above(
|
||||||
|
dataframe['htleadsine'], dataframe['htsine']
|
||||||
|
)),
|
||||||
|
'heiken_reversal_bull': (
|
||||||
|
(qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) &
|
||||||
|
(dataframe['ha_low'] == dataframe['ha_open'])
|
||||||
|
),
|
||||||
|
'di_cross': (qtpylib.crossed_above(
|
||||||
|
dataframe['plus_di'], dataframe['minus_di']
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
conditions.append(triggers.get(params['trigger']['type']))
|
||||||
|
|
||||||
|
dataframe.loc[
|
||||||
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
return populate_buy_trend
|
Loading…
Reference in New Issue
Block a user