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
|
||||
.hyperopt
|
||||
logfile.txt
|
||||
hyperopt_trials.pickle
|
||||
user_data/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
@ -85,5 +87,3 @@ target/
|
||||
.venv
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
hyperopt_trials.pickle
|
||||
|
@ -3,21 +3,55 @@ This page explains where to customize your strategies, and add new
|
||||
indicators.
|
||||
|
||||
## 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)
|
||||
- [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
|
||||
The bot is using buy and sell strategies to buy and sell your trades.
|
||||
Both are customizable.
|
||||
The bot includes a default strategy file. However, we recommend you to
|
||||
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
|
||||
The default buy strategy is located in the file
|
||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73-L92).
|
||||
Edit the function `populate_buy_trend()` to update your buy strategy.
|
||||
A strategy file contains all the information needed to build a good strategy:
|
||||
- Buy strategy rules
|
||||
- Sell strategy rules
|
||||
- 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
|
||||
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
|
||||
:param dataframe: DataFrame
|
||||
@ -25,14 +59,9 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['rsi'] < 35) &
|
||||
(dataframe['fastd'] < 35) &
|
||||
(dataframe['adx'] > 30) &
|
||||
(dataframe['plus_di'] > 0.5)
|
||||
) |
|
||||
(
|
||||
(dataframe['adx'] > 65) &
|
||||
(dataframe['plus_di'] > 0.5)
|
||||
(dataframe['tema'] <= dataframe['blower']) &
|
||||
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||
),
|
||||
'buy'] = 1
|
||||
|
||||
@ -40,41 +69,31 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||
```
|
||||
|
||||
### Sell strategy
|
||||
The default buy strategy is located in the file
|
||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115)
|
||||
Edit the function `populate_sell_trend()` to update your buy strategy.
|
||||
Edit the method `populate_sell_trend()` into your strategy file to
|
||||
update your sell strategy.
|
||||
|
||||
Sample:
|
||||
Sample from `user_data/strategies/test_strategy.py`:
|
||||
```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
|
||||
:param dataframe: DataFrame
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(
|
||||
(crossed_above(dataframe['rsi'], 70)) |
|
||||
(crossed_above(dataframe['fastd'], 70))
|
||||
) &
|
||||
(dataframe['adx'] > 10) &
|
||||
(dataframe['minus_di'] > 0)
|
||||
) |
|
||||
(
|
||||
(dataframe['adx'] > 70) &
|
||||
(dataframe['minus_di'] > 0.5)
|
||||
(dataframe['tema'] > dataframe['blower']) &
|
||||
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||
),
|
||||
'sell'] = 1
|
||||
return dataframe
|
||||
```
|
||||
|
||||
## Add more Indicator
|
||||
As you have seen, buy and sell strategies need indicators. You can see
|
||||
the indicators in the file
|
||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115).
|
||||
Of course you can add more indicators by extending the list contained in
|
||||
the function `populate_indicators()`.
|
||||
As you have seen, buy and sell strategies need indicators. You can add
|
||||
more indicators by extending the list contained in
|
||||
the method `populate_indicators()` from your strategy file.
|
||||
|
||||
Sample:
|
||||
```python
|
||||
@ -111,6 +130,15 @@ def populate_indicators(dataframe: DataFrame) -> 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
|
||||
Now you have a perfect strategy you probably want to backtesting it.
|
||||
|
@ -22,19 +22,21 @@ positional arguments:
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-c PATH, --config PATH
|
||||
specify configuration file (default: config.json)
|
||||
-v, --verbose be verbose
|
||||
--version show program's version number and exit
|
||||
-dd PATH, --datadir PATH
|
||||
Path is from where backtesting and hyperopt will load the
|
||||
ticker data files (default freqdata/tests/testdata).
|
||||
--dynamic-whitelist [INT]
|
||||
dynamically generate and update whitelist based on 24h
|
||||
BaseVolume (Default 20 currencies)
|
||||
-c PATH, --config PATH
|
||||
specify configuration file (default: config.json)
|
||||
-s PATH, --strategy PATH
|
||||
specify strategy file (default:
|
||||
freqtrade/strategy/default_strategy.py)
|
||||
--dry-run-db Force dry run to use a local DB
|
||||
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
||||
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?
|
||||
@ -45,6 +47,33 @@ default, the bot will load the file `./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?
|
||||
Per default `--dynamic-whitelist` will retrieve the 20 currencies based
|
||||
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
|
||||
| `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.
|
||||
| `minimal_roi` | See below | Yes | Set the threshold in percent the bot will use to sell a trade. More information below.
|
||||
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. 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. 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.
|
||||
| `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.
|
||||
@ -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
|
||||
`stoploss` is loss in percentage that should trigger a sale.
|
||||
For example value `-0.10` will cause immediate sell if the
|
||||
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
|
||||
`initial_state` is an optional field that defines the initial application state.
|
||||
Possible values are `running` or `stopped`. (default=`running`)
|
||||
|
@ -14,14 +14,13 @@ parameters with Hyperopt.
|
||||
|
||||
## Prepare Hyperopt
|
||||
Before we start digging in Hyperopt, we recommend you to take a look at
|
||||
out Hyperopt file
|
||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py)
|
||||
your strategy file located into [user_data/strategies/](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
|
||||
|
||||
### 1. Configure your Guards and Triggers
|
||||
There are two places you need to change to add a new buy strategy for
|
||||
testing:
|
||||
- Inside the [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L167-L207).
|
||||
- Inside the [SPACE dict](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L47-L94).
|
||||
There are two places you need to change in your strategy file to add a
|
||||
new buy strategy for testing:
|
||||
- Inside [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L278-L294).
|
||||
- Inside [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.
|
||||
`triggers`.
|
||||
@ -38,10 +37,10 @@ ADX > 10*".
|
||||
|
||||
|
||||
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.
|
||||
|
||||
As for an example if your `populate_buy_trend()` function is:
|
||||
As for an example if your `populate_buy_trend()` method is:
|
||||
```python
|
||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||
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
|
||||
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:
|
||||
```python
|
||||
SPACE = {
|
||||
space = {
|
||||
'rsi': hp.choice('rsi', [
|
||||
{'enabled': False},
|
||||
{'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 = []
|
||||
# GUARDS AND TRENDS
|
||||
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.
|
||||
|
||||
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
|
||||
### Understand the Guards and Triggers
|
||||
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.
|
||||
|
||||
@ -149,9 +148,8 @@ for best working algo.
|
||||
|
||||
### Add a new Indicators
|
||||
If you want to test an indicator that isn't used by the bot currently,
|
||||
you need to add it to
|
||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L40-L70)
|
||||
inside the `populate_indicators` function.
|
||||
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))
|
||||
inside the `populate_indicators()` method.
|
||||
|
||||
## Execute Hyperopt
|
||||
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
|
||||
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
|
||||
use data from directory freqtrade/tests/testdata.
|
||||
you have on-disk, use the `--datadir PATH` option. Default hyperopt will
|
||||
use data from directory `user_data/data`.
|
||||
|
||||
### Running hyperopt with smaller testset
|
||||
|
||||
@ -270,15 +268,11 @@ customizable value.
|
||||
- and so on...
|
||||
|
||||
|
||||
You have to look from
|
||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L170-L200)
|
||||
what those values match to.
|
||||
You have to look inside your strategy file into `buy_strategy_generator()`
|
||||
method, what those values match to.
|
||||
|
||||
So for example you had `adx:` with the `value: 15.0` so we would look
|
||||
at `adx`-block from
|
||||
[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)
|
||||
at `adx`-block, that translates to the following code block:
|
||||
```
|
||||
(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
|
||||
would be the following:
|
||||
```
|
||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['adx'] > 15.0) & # adx-value
|
||||
|
@ -1,5 +1,5 @@
|
||||
""" FreqTrade bot """
|
||||
__version__ = '0.15.1'
|
||||
__version__ = '0.16.0'
|
||||
|
||||
|
||||
class DependencyException(BaseException):
|
||||
|
@ -7,11 +7,10 @@ from enum import Enum
|
||||
from typing import Dict, List
|
||||
|
||||
import arrow
|
||||
import talib.abstract as ta
|
||||
from pandas import DataFrame, to_datetime
|
||||
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.exchange import get_ticker_history
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
|
||||
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
|
||||
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
|
||||
strategy = Strategy()
|
||||
return strategy.populate_indicators(dataframe=dataframe)
|
||||
|
||||
|
||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||
@ -230,20 +55,8 @@ def populate_buy_trend(dataframe: DataFrame) -> 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
|
||||
strategy = Strategy()
|
||||
return strategy.populate_buy_trend(dataframe=dataframe)
|
||||
|
||||
|
||||
def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
||||
@ -252,21 +65,8 @@ def populate_sell_trend(dataframe: DataFrame) -> 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
|
||||
strategy = Strategy()
|
||||
return strategy.populate_sell_trend(dataframe=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,
|
||||
throttle, update_state)
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
|
||||
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
|
||||
:return True if bot should sell at current rate
|
||||
"""
|
||||
strategy = Strategy()
|
||||
|
||||
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.')
|
||||
return True
|
||||
|
||||
# Check if time matches and current rate is above threshold
|
||||
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:
|
||||
return True
|
||||
|
||||
@ -391,6 +394,9 @@ def init(config: dict, db_url: Optional[str] = None) -> None:
|
||||
persistence.init(config, db_url)
|
||||
exchange.init(config)
|
||||
|
||||
strategy = Strategy()
|
||||
strategy.init(config)
|
||||
|
||||
# Set initial application state
|
||||
initial_state = config.get('initial_state')
|
||||
if initial_state:
|
||||
@ -458,6 +464,9 @@ def main(sysargv=sys.argv[1:]) -> None:
|
||||
# Load and validate configuration
|
||||
_CONF = load_config(args.config)
|
||||
|
||||
# Add the strategy file to use
|
||||
_CONF.update({'strategy': args.strategy})
|
||||
|
||||
# Initialize all modules and start main loop
|
||||
if args.dynamic_whitelist:
|
||||
logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)')
|
||||
@ -475,6 +484,7 @@ def main(sysargv=sys.argv[1:]) -> None:
|
||||
try:
|
||||
init(_CONF)
|
||||
old_state = None
|
||||
|
||||
while True:
|
||||
new_state = get_state()
|
||||
# Log state transition
|
||||
|
@ -124,6 +124,14 @@ def common_args_parser(description: str):
|
||||
type=str,
|
||||
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
|
||||
|
||||
|
||||
@ -375,12 +383,10 @@ CONF_SCHEMA = {
|
||||
],
|
||||
'required': [
|
||||
'max_open_trades',
|
||||
'ticker_interval',
|
||||
'stake_currency',
|
||||
'stake_amount',
|
||||
'fiat_display_currency',
|
||||
'dry_run',
|
||||
'minimal_roi',
|
||||
'bid_strategy',
|
||||
'telegram'
|
||||
]
|
||||
|
@ -6,9 +6,10 @@ import os
|
||||
from typing import Optional, List, Dict
|
||||
from pandas import DataFrame
|
||||
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 import misc
|
||||
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
||||
|
||||
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.main import min_roi_reached
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -199,6 +200,11 @@ def start(args):
|
||||
logger.info('Using max_open_trades: %s ...', 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
|
||||
from freqtrade import main
|
||||
main._CONF = config
|
||||
@ -216,7 +222,7 @@ def start(args):
|
||||
'realistic': args.realistic_simulation,
|
||||
'sell_profit_only': sell_profit_only,
|
||||
'use_sell_signal': use_sell_signal,
|
||||
'stoploss': config.get('stoploss'),
|
||||
'stoploss': strategy.stoploss,
|
||||
'record': args.export
|
||||
})
|
||||
logger.info(
|
||||
|
@ -7,11 +7,10 @@ import sys
|
||||
import pickle
|
||||
import signal
|
||||
import os
|
||||
from functools import reduce
|
||||
from math import exp
|
||||
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 pandas import DataFrame
|
||||
|
||||
@ -20,8 +19,8 @@ from freqtrade import exchange, optimize
|
||||
from freqtrade.exchange import Bittrex
|
||||
from freqtrade.misc import load_config
|
||||
from freqtrade.optimize.backtesting import backtest
|
||||
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf
|
||||
from freqtrade.vendor.qtpylib.indicators import crossed_above
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
||||
|
||||
# Remove noisy log messages
|
||||
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
|
||||
@ -49,7 +48,7 @@ PROCESSED = None # optimize.preprocess(optimize.load_data())
|
||||
OPTIMIZE_CONFIG = hyperopt_optimize_conf()
|
||||
|
||||
# Hyperopt Trials
|
||||
TRIALS_FILE = os.path.join('freqtrade', 'optimize', 'hyperopt_trials.pickle')
|
||||
TRIALS_FILE = os.path.join('user_data', 'hyperopt_trials.pickle')
|
||||
TRIALS = Trials()
|
||||
|
||||
# Monkey patch config
|
||||
@ -57,63 +56,6 @@ from freqtrade import main # noqa
|
||||
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):
|
||||
"""Save hyperopt trials to file"""
|
||||
logger.info('Saving Trials to \'{}\''.format(trials_path))
|
||||
@ -162,7 +104,9 @@ def optimizer(params):
|
||||
global _CURRENT_TRIES
|
||||
|
||||
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'],
|
||||
'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):
|
||||
global TOTAL_TRIES, PROCESSED, SPACE, TRIALS, _CURRENT_TRIES
|
||||
global TOTAL_TRIES, PROCESSED, TRIALS, _CURRENT_TRIES
|
||||
|
||||
TOTAL_TRIES = args.epochs
|
||||
|
||||
@ -275,6 +169,12 @@ def start(args):
|
||||
logger.info('Using config: %s ...', args.config)
|
||||
config = load_config(args.config)
|
||||
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)
|
||||
data = optimize.load_data(args.datadir, pairs=pairs,
|
||||
ticker_interval=args.ticker_interval,
|
||||
@ -303,7 +203,7 @@ def start(args):
|
||||
try:
|
||||
best_parameters = fmin(
|
||||
fn=optimizer,
|
||||
space=SPACE,
|
||||
space=strategy.hyperopt_space(),
|
||||
algo=tpe.suggest,
|
||||
max_evals=TOTAL_TRIES,
|
||||
trials=TRIALS
|
||||
@ -319,7 +219,10 @@ def start(args):
|
||||
|
||||
# Improve best parameter logging display
|
||||
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 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
|
||||
|
||||
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf
|
||||
from user_data.hyperopt_conf import 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,
|
||||
populate_buy_trend, populate_indicators,
|
||||
populate_sell_trend)
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -27,11 +28,17 @@ def test_dataframe_correct_length(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))
|
||||
assert 'buy' in dataframe.columns
|
||||
|
||||
|
||||
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))
|
||||
assert 'sell' in dataframe.columns
|
||||
|
||||
|
@ -1,47 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import matplotlib # Install PYQT5 manually if you want to test this helper function
|
||||
matplotlib.use("Qt5Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
import talib.abstract as ta
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from pandas import DataFrame
|
||||
from freqtrade import exchange, analyze
|
||||
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.add_argument(
|
||||
'-p', '--pair',
|
||||
help = 'What currency pair',
|
||||
dest = 'pair',
|
||||
default = 'BTC_ETH',
|
||||
type = str,
|
||||
help='What currency pair',
|
||||
dest='pair',
|
||||
default='BTC_ETH',
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument(
|
||||
'-i', '--interval',
|
||||
help = 'what interval to use',
|
||||
dest = 'interval',
|
||||
default = '5',
|
||||
type = int,
|
||||
help='what interval to use',
|
||||
dest='interval',
|
||||
default=5,
|
||||
type=int,
|
||||
)
|
||||
return parser.parse_args(args)
|
||||
|
||||
|
||||
def plot_analyzed_dataframe(args):
|
||||
def plot_analyzed_dataframe(args) -> None:
|
||||
"""
|
||||
Calls analyze() and plots the returned dataframe
|
||||
:param pair: pair as str
|
||||
:return: None
|
||||
"""
|
||||
# Init strategy
|
||||
strategy = Strategy()
|
||||
strategy.init({'strategy': args.strategy})
|
||||
|
||||
# Init Bittrex to use public API
|
||||
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.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close']
|
||||
dataframe.loc[dataframe['sell'] == 1, 'sell_price'] = dataframe['close']
|
||||
dataframe = populate_indicator(dataframe)
|
||||
|
||||
# Two subplots sharing x axis
|
||||
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['sma'], '--', label='SMA')
|
||||
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.legend()
|
||||
|
||||
@ -71,6 +77,41 @@ def plot_analyzed_dataframe(args):
|
||||
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__':
|
||||
args = plot_parse_args(sys.argv[1:])
|
||||
plot_analyzed_dataframe(args)
|
||||
|
@ -1,26 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
import freqtrade.optimize as optimize
|
||||
import freqtrade.misc as misc
|
||||
import freqtrade.exchange as exchange
|
||||
import freqtrade.analyze as analyze
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
|
||||
|
||||
def plot_parse_args(args ):
|
||||
def plot_parse_args(args):
|
||||
parser = misc.common_args_parser('Graph utility')
|
||||
# FIX: perhaps delete those backtesting options that are not feasible (shows up in -h)
|
||||
misc.backtesting_options(parser)
|
||||
parser.add_argument(
|
||||
'-p', '--pair',
|
||||
help = 'Show profits for only this pairs. Pairs are comma-separated.',
|
||||
dest = 'pair',
|
||||
default = None
|
||||
help='Show profits for only this pairs. Pairs are comma-separated.',
|
||||
dest='pair',
|
||||
default=None
|
||||
)
|
||||
return parser.parse_args(args)
|
||||
|
||||
@ -47,7 +45,7 @@ def make_profit_array(data, px, filter_pairs=[]):
|
||||
# total profits at each timeframe
|
||||
# to accumulated profits
|
||||
pa = 0
|
||||
for x in range(0,len(pg)):
|
||||
for x in range(0, len(pg)):
|
||||
p = pg[x] # Get current total percent
|
||||
pa += p # Add to the accumulated percent
|
||||
pg[x] = pa # write back to save memory
|
||||
@ -70,14 +68,21 @@ def plot_profit(args) -> None:
|
||||
filter_pairs = args.pair
|
||||
|
||||
config = misc.load_config(args.config)
|
||||
config.update({'strategy': args.strategy})
|
||||
|
||||
# Init strategy
|
||||
strategy = Strategy()
|
||||
strategy.init(config)
|
||||
|
||||
pairs = config['exchange']['pair_whitelist']
|
||||
|
||||
if filter_pairs:
|
||||
filter_pairs = filter_pairs.split(',')
|
||||
pairs = list(set(pairs) & set(filter_pairs))
|
||||
print('Filter, keep pairs %s' % pairs)
|
||||
|
||||
tickers = optimize.load_data(args.datadir, pairs=pairs,
|
||||
ticker_interval=args.ticker_interval,
|
||||
ticker_interval=strategy.ticker_interval,
|
||||
refresh_pairs=False)
|
||||
dataframes = optimize.preprocess(tickers)
|
||||
|
||||
@ -96,7 +101,7 @@ def plot_profit(args) -> None:
|
||||
# if max_x != n:
|
||||
# raise Exception('Please rerun script. Input data has different lengths %s'
|
||||
# %('Different pair length: %s <=> %s' %(max_x, n)))
|
||||
print('max_x: %s' %(max_x))
|
||||
print('max_x: %s' % (max_x))
|
||||
|
||||
# We are essentially saying:
|
||||
# array <- sum dataframes[*]['close'] / num_items dataframes
|
||||
@ -106,7 +111,7 @@ def plot_profit(args) -> None:
|
||||
for pair, pair_data in dataframes.items():
|
||||
close = pair_data['close']
|
||||
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)):
|
||||
avgclose[x] += close[x] / maxprice
|
||||
# avgclose += close
|
||||
@ -118,7 +123,7 @@ def plot_profit(args) -> None:
|
||||
|
||||
filename = 'backtest-result.json'
|
||||
with open(filename) as file:
|
||||
data = json.load(file)
|
||||
data = json.load(file)
|
||||
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