Merge pull request #400 from gcarq/feature/custom_strategy

Allow custom strategy files
This commit is contained in:
Janne Sinivirta 2018-01-23 15:25:18 +02:00 committed by GitHub
commit a6cbc1ba16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1464 additions and 434 deletions

4
.gitignore vendored
View File

@ -5,6 +5,8 @@ config.json
*.sqlite *.sqlite
.hyperopt .hyperopt
logfile.txt logfile.txt
hyperopt_trials.pickle
user_data/
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
@ -85,5 +87,3 @@ target/
.venv .venv
.idea .idea
.vscode .vscode
hyperopt_trials.pickle

View File

@ -3,21 +3,55 @@ This page explains where to customize your strategies, and add new
indicators. indicators.
## Table of Contents ## Table of Contents
- [Change your strategy](#change-your-strategy) - [Install a custom strategy file](#install-a-custom-strategy-file)
- [Customize your strategy](#change-your-strategy)
- [Add more Indicator](#add-more-indicator) - [Add more Indicator](#add-more-indicator)
- [Where is the default strategy](#where-is-the-default-strategy)
Since the version `0.16.0` the bot allows using custom strategy file.
## Install a custom strategy file
This is very simple. Copy paste your strategy file into the folder
`user_data/strategies`.
Let assume you have a strategy file `awesome-strategy.py`:
1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py`
2. Start the bot with the param `--strategy awesome-strategy` (the parameter is the name of the file without '.py')
```bash
python3 ./freqtrade/main.py --strategy awesome_strategy
```
## Change your strategy ## Change your strategy
The bot is using buy and sell strategies to buy and sell your trades. The bot includes a default strategy file. However, we recommend you to
Both are customizable. use your own file to not have to lose your parameters everytime the default
strategy file will be updated on Github. Put your custom strategy file
into the folder `user_data/strategies`.
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
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 ### Buy strategy
The default buy strategy is located in the file Edit the method `populate_buy_trend()` into your strategy file to
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73-L92). update your buy strategy.
Edit the function `populate_buy_trend()` to update your buy strategy.
Sample: Sample from `user_data/strategies/test_strategy.py`:
```python ```python
def populate_buy_trend(dataframe: DataFrame) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
""" """
Based on TA indicators, populates the buy signal for the given dataframe Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
@ -25,14 +59,9 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
""" """
dataframe.loc[ dataframe.loc[
( (
(dataframe['rsi'] < 35) &
(dataframe['fastd'] < 35) &
(dataframe['adx'] > 30) & (dataframe['adx'] > 30) &
(dataframe['plus_di'] > 0.5) (dataframe['tema'] <= dataframe['blower']) &
) | (dataframe['tema'] > dataframe['tema'].shift(1))
(
(dataframe['adx'] > 65) &
(dataframe['plus_di'] > 0.5)
), ),
'buy'] = 1 'buy'] = 1
@ -40,41 +69,31 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
``` ```
### Sell strategy ### Sell strategy
The default buy strategy is located in the file Edit the method `populate_sell_trend()` into your strategy file to
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115) update your sell strategy.
Edit the function `populate_sell_trend()` to update your buy strategy.
Sample: Sample from `user_data/strategies/test_strategy.py`:
```python ```python
def populate_sell_trend(dataframe: DataFrame) -> DataFrame: def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
""" """
Based on TA indicators, populates the sell signal for the given dataframe Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame :param dataframe: DataFrame
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ dataframe.loc[
(
(
(crossed_above(dataframe['rsi'], 70)) |
(crossed_above(dataframe['fastd'], 70))
) &
(dataframe['adx'] > 10) &
(dataframe['minus_di'] > 0)
) |
( (
(dataframe['adx'] > 70) & (dataframe['adx'] > 70) &
(dataframe['minus_di'] > 0.5) (dataframe['tema'] > dataframe['blower']) &
(dataframe['tema'] < dataframe['tema'].shift(1))
), ),
'sell'] = 1 'sell'] = 1
return dataframe return dataframe
``` ```
## Add more Indicator ## Add more Indicator
As you have seen, buy and sell strategies need indicators. You can see As you have seen, buy and sell strategies need indicators. You can add
the indicators in the file more indicators by extending the list contained in
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115). the method `populate_indicators()` from your strategy file.
Of course you can add more indicators by extending the list contained in
the function `populate_indicators()`.
Sample: Sample:
```python ```python
@ -111,6 +130,15 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
return dataframe return dataframe
``` ```
**Want more indicators example?**
Look into the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
Then uncomment indicators you need.
### Where is the default strategy?
The default buy strategy is located in the file
[freqtrade/default_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/strategy/default_strategy.py).
## Next step ## Next step
Now you have a perfect strategy you probably want to backtesting it. Now you have a perfect strategy you probably want to backtesting it.

View File

@ -22,19 +22,21 @@ positional arguments:
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-c PATH, --config PATH
specify configuration file (default: config.json)
-v, --verbose be verbose -v, --verbose be verbose
--version show program's version number and exit --version show program's version number and exit
-dd PATH, --datadir PATH -c PATH, --config PATH
Path is from where backtesting and hyperopt will load the specify configuration file (default: config.json)
ticker data files (default freqdata/tests/testdata). -s PATH, --strategy PATH
--dynamic-whitelist [INT] specify strategy file (default:
dynamically generate and update whitelist based on 24h freqtrade/strategy/default_strategy.py)
BaseVolume (Default 20 currencies)
--dry-run-db Force dry run to use a local DB --dry-run-db Force dry run to use a local DB
"tradesv3.dry_run.sqlite" instead of memory DB. Work "tradesv3.dry_run.sqlite" instead of memory DB. Work
only if dry_run is enabled. only if dry_run is enabled.
-dd PATH, --datadir PATH
path to backtest data (default freqdata/tests/testdata
--dynamic-whitelist [INT]
dynamically generate and update whitelist based on 24h
BaseVolume (Default 20 currencies)
``` ```
### How to use a different config file? ### How to use a different config file?
@ -45,6 +47,33 @@ default, the bot will load the file `./config.json`
python3 ./freqtrade/main.py -c path/far/far/away/config.json python3 ./freqtrade/main.py -c path/far/far/away/config.json
``` ```
### How to use --strategy?
This parameter will allow you to load your custom strategy file. Per
default without `--strategy` or `-s` the bol will load the
`default_strategy` included with the bot (`freqtrade/strategy/default_strategy.py`).
The bot will search your strategy file into `user_data/strategies` and
`freqtrade/strategy`.
To load a strategy, simply pass the file name (without .py) in this
parameters.
**Example:**
In `user_data/strategies` you have a file `my_awesome_strategy.py` to
load it:
```bash
python3 ./freqtrade/main.py --strategy my_awesome_strategy
```
If the bot does not find your strategy file, it will fallback to the
`default_strategy`.
Learn more about strategy file in [optimize your bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md).
#### How to install a strategy?
This is very simple. Copy paste your strategy file into the folder
`user_data/strategies`. And voila, the bot is ready to use it.
### How to use --dynamic-whitelist? ### How to use --dynamic-whitelist?
Per default `--dynamic-whitelist` will retrieve the 20 currencies based Per default `--dynamic-whitelist` will retrieve the 20 currencies based
on BaseVolume. This value can be changed when you run the script. on BaseVolume. This value can be changed when you run the script.

View File

@ -20,8 +20,8 @@ The table below will list all configuration parameters.
| `ticker_interval` | [1, 5, 30, 60, 1440] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Defaut is 5 minutes | `ticker_interval` | [1, 5, 30, 60, 1440] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Defaut is 5 minutes
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below. | `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
| `minimal_roi` | See below | Yes | Set the threshold in percent the bot will use to sell a trade. More information below. | `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. | `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
| `unfilledtimeout` | 0 | No | How long (in minutes) the bot will wait for an unfilled order to complete, after which the order will be cancelled. | `unfilledtimeout` | 0 | No | How long (in minutes) the bot will wait for an unfilled order to complete, after which the order will be cancelled.
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below. | `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
| `exchange.name` | bittrex | Yes | Name of the exchange class to use. | `exchange.name` | bittrex | Yes | Name of the exchange class to use.
@ -53,11 +53,19 @@ See the example below:
}, },
``` ```
Most of the strategy files already include the optimal `minimal_roi`
value. This parameter is optional. If you use it, it will take over the
`minimal_roi` value from the strategy file.
### Understand stoploss ### Understand stoploss
`stoploss` is loss in percentage that should trigger a sale. `stoploss` is loss in percentage that should trigger a sale.
For example value `-0.10` will cause immediate sell if the For example value `-0.10` will cause immediate sell if the
profit dips below -10% for a given trade. This parameter is optional. profit dips below -10% for a given trade. This parameter is optional.
Most of the strategy files already include the optimal `stoploss`
value. This parameter is optional. If you use it, it will take over the
`stoploss` value from the strategy file.
### Understand initial_state ### Understand initial_state
`initial_state` is an optional field that defines the initial application state. `initial_state` is an optional field that defines the initial application state.
Possible values are `running` or `stopped`. (default=`running`) Possible values are `running` or `stopped`. (default=`running`)

View File

@ -14,14 +14,13 @@ parameters with Hyperopt.
## Prepare Hyperopt ## Prepare Hyperopt
Before we start digging in Hyperopt, we recommend you to take a look at Before we start digging in Hyperopt, we recommend you to take a look at
out Hyperopt file your strategy file located into [user_data/strategies/](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py)
### 1. Configure your Guards and Triggers ### 1. Configure your Guards and Triggers
There are two places you need to change to add a new buy strategy for There are two places you need to change in your strategy file to add a
testing: new buy strategy for testing:
- Inside the [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L167-L207). - Inside [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L278-L294).
- Inside the [SPACE dict](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L47-L94). - Inside [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297) known as `SPACE`.
There you have two different type of indicators: 1. `guards` and 2. There you have two different type of indicators: 1. `guards` and 2.
`triggers`. `triggers`.
@ -38,10 +37,10 @@ ADX > 10*".
If you have updated the buy strategy, means change the content of If you have updated the buy strategy, means change the content of
`populate_buy_trend()` function you have to update the `guards` and `populate_buy_trend()` method you have to update the `guards` and
`triggers` hyperopts must used. `triggers` hyperopts must used.
As for an example if your `populate_buy_trend()` function is: As for an example if your `populate_buy_trend()` method is:
```python ```python
def populate_buy_trend(dataframe: DataFrame) -> DataFrame: def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
dataframe.loc[ dataframe.loc[
@ -56,10 +55,10 @@ Your hyperopt file must contains `guards` to find the right value for
`(dataframe['adx'] > 65)` & and `(dataframe['plus_di'] > 0.5)`. That `(dataframe['adx'] > 65)` & and `(dataframe['plus_di'] > 0.5)`. That
means you will need to enable/disable triggers. means you will need to enable/disable triggers.
In our case the `SPACE` and `populate_buy_trend` in hyperopt.py file In our case the `SPACE` and `populate_buy_trend` in your strategy file
will be look like: will be look like:
```python ```python
SPACE = { space = {
'rsi': hp.choice('rsi', [ 'rsi': hp.choice('rsi', [
{'enabled': False}, {'enabled': False},
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)} {'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
@ -82,7 +81,7 @@ SPACE = {
... ...
def populate_buy_trend(dataframe: DataFrame) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
conditions = [] conditions = []
# GUARDS AND TRENDS # GUARDS AND TRENDS
if params['adx']['enabled']: if params['adx']['enabled']:
@ -111,13 +110,13 @@ cannot use your config file. It is also made on purpose to allow you
testing your strategy with different configurations. testing your strategy with different configurations.
The Hyperopt configuration is located in The Hyperopt configuration is located in
[freqtrade/optimize/hyperopt_conf.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt_conf.py). [user_data/hyperopt_conf.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/hyperopt_conf.py).
## Advanced notions ## Advanced notions
### Understand the Guards and Triggers ### Understand the Guards and Triggers
When you need to add the new guards and triggers to be hyperopt When you need to add the new guards and triggers to be hyperopt
parameters, you do this by adding them into the [SPACE dict](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L47-L94). parameters, you do this by adding them into the [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297).
If it's a trigger, you add one line to the 'trigger' choice group and that's it. If it's a trigger, you add one line to the 'trigger' choice group and that's it.
@ -149,9 +148,8 @@ for best working algo.
### Add a new Indicators ### Add a new Indicators
If you want to test an indicator that isn't used by the bot currently, If you want to test an indicator that isn't used by the bot currently,
you need to add it to you need to add it to your strategy file (example: [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py))
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L40-L70) inside the `populate_indicators()` method.
inside the `populate_indicators` function.
## Execute Hyperopt ## Execute Hyperopt
Once you have updated your hyperopt configuration you can run it. Once you have updated your hyperopt configuration you can run it.
@ -165,8 +163,8 @@ python3 ./freqtrade/main.py -c config.json hyperopt
### Execute hyperopt with different ticker-data source ### Execute hyperopt with different ticker-data source
If you would like to learn parameters using an alternate ticke-data that If you would like to learn parameters using an alternate ticke-data that
you have on-disk, use the --datadir PATH option. Default hyperopt will you have on-disk, use the `--datadir PATH` option. Default hyperopt will
use data from directory freqtrade/tests/testdata. use data from directory `user_data/data`.
### Running hyperopt with smaller testset ### Running hyperopt with smaller testset
@ -270,15 +268,11 @@ customizable value.
- and so on... - and so on...
You have to look from You have to look inside your strategy file into `buy_strategy_generator()`
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L170-L200) method, what those values match to.
what those values match to.
So for example you had `adx:` with the `value: 15.0` so we would look So for example you had `adx:` with the `value: 15.0` so we would look
at `adx`-block from at `adx`-block, that translates to the following code block:
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L178-L179).
That translates to the following code block to
[analyze.populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73)
``` ```
(dataframe['adx'] > 15.0) (dataframe['adx'] > 15.0)
``` ```
@ -286,7 +280,7 @@ That translates to the following code block to
So translating your whole hyperopt result to as the new buy-signal So translating your whole hyperopt result to as the new buy-signal
would be the following: would be the following:
``` ```
def populate_buy_trend(dataframe: DataFrame) -> DataFrame: def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
dataframe.loc[ dataframe.loc[
( (
(dataframe['adx'] > 15.0) & # adx-value (dataframe['adx'] > 15.0) & # adx-value

View File

@ -1,5 +1,5 @@
""" FreqTrade bot """ """ FreqTrade bot """
__version__ = '0.15.1' __version__ = '0.16.0'
class DependencyException(BaseException): class DependencyException(BaseException):

View File

@ -7,11 +7,10 @@ from enum import Enum
from typing import Dict, List from typing import Dict, List
import arrow import arrow
import talib.abstract as ta
from pandas import DataFrame, to_datetime from pandas import DataFrame, to_datetime
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import get_ticker_history
from freqtrade.strategy.strategy import Strategy
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -46,182 +45,8 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
you are using. Let uncomment only the indicator you are using in your strategies you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage. or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
""" """
strategy = Strategy()
# Momentum Indicator return strategy.populate_indicators(dataframe=dataframe)
# ------------------------------------
# ADX
dataframe['adx'] = ta.ADX(dataframe)
# Awesome oscillator
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
"""
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
dataframe['cci'] = ta.CCI(dataframe)
"""
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# MFI
dataframe['mfi'] = ta.MFI(dataframe)
# Minus Directional Indicator / Movement
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# Plus Directional Indicator / Movement
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
"""
# ROC
dataframe['roc'] = ta.ROC(dataframe)
"""
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
"""
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
rsi = 0.1 * (dataframe['rsi'] - 50)
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# Stoch
stoch = ta.STOCH(dataframe)
dataframe['slowd'] = stoch['slowd']
dataframe['slowk'] = stoch['slowk']
"""
# Stoch fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
"""
# Stoch RSI
stoch_rsi = ta.STOCHRSI(dataframe)
dataframe['fastd_rsi'] = stoch_rsi['fastd']
dataframe['fastk_rsi'] = stoch_rsi['fastk']
"""
# Overlap Studies
# ------------------------------------
# Previous Bollinger bands
# Because ta.BBANDS implementation is broken with small numbers, it actually
# returns middle band for all the three bands. Switch to qtpylib.bollinger_bands
# and use middle band instead.
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
"""
# Bollinger bands
"""
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
# EMA - Exponential Moving Average
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# SAR Parabol
dataframe['sar'] = ta.SAR(dataframe)
# SMA - Simple Moving Average
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
# TEMA - Triple Exponential Moving Average
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
# Cycle Indicator
# ------------------------------------
# Hilbert Transform Indicator - SineWave
hilbert = ta.HT_SINE(dataframe)
dataframe['htsine'] = hilbert['sine']
dataframe['htleadsine'] = hilbert['leadsine']
# Pattern Recognition - Bullish candlestick patterns
# ------------------------------------
"""
# Hammer: values [0, 100]
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
# Inverted Hammer: values [0, 100]
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
# Dragonfly Doji: values [0, 100]
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
# Piercing Line: values [0, 100]
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
# Morningstar: values [0, 100]
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# Three White Soldiers: values [0, 100]
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
"""
# Pattern Recognition - Bearish candlestick patterns
# ------------------------------------
"""
# Hanging Man: values [0, 100]
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
# Shooting Star: values [0, 100]
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
# Gravestone Doji: values [0, 100]
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
# Dark Cloud Cover: values [0, 100]
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
# Evening Doji Star: values [0, 100]
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
# Evening Star: values [0, 100]
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
"""
# Pattern Recognition - Bullish/Bearish candlestick patterns
# ------------------------------------
"""
# Three Line Strike: values [0, -100, 100]
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
# Spinning Top: values [0, -100, 100]
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# Engulfing: values [0, -100, 100]
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# Harami: values [0, -100, 100]
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# Three Outside Up/Down: values [0, -100, 100]
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# Three Inside Up/Down: values [0, -100, 100]
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
"""
# Chart type
# ------------------------------------
# Heikinashi stategy
heikinashi = qtpylib.heikinashi(dataframe)
dataframe['ha_open'] = heikinashi['open']
dataframe['ha_close'] = heikinashi['close']
dataframe['ha_high'] = heikinashi['high']
dataframe['ha_low'] = heikinashi['low']
return dataframe
def populate_buy_trend(dataframe: DataFrame) -> DataFrame: def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
@ -230,20 +55,8 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
:param dataframe: DataFrame :param dataframe: DataFrame
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ strategy = Strategy()
( return strategy.populate_buy_trend(dataframe=dataframe)
(dataframe['rsi'] < 35) &
(dataframe['fastd'] < 35) &
(dataframe['adx'] > 30) &
(dataframe['plus_di'] > 0.5)
) |
(
(dataframe['adx'] > 65) &
(dataframe['plus_di'] > 0.5)
),
'buy'] = 1
return dataframe
def populate_sell_trend(dataframe: DataFrame) -> DataFrame: def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
@ -252,21 +65,8 @@ def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
:param dataframe: DataFrame :param dataframe: DataFrame
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.loc[ strategy = Strategy()
( return strategy.populate_sell_trend(dataframe=dataframe)
(
(qtpylib.crossed_above(dataframe['rsi'], 70)) |
(qtpylib.crossed_above(dataframe['fastd'], 70))
) &
(dataframe['adx'] > 10) &
(dataframe['minus_di'] > 0)
) |
(
(dataframe['adx'] > 70) &
(dataframe['minus_di'] > 0.5)
),
'sell'] = 1
return dataframe
def analyze_ticker(ticker_history: List[Dict]) -> DataFrame: def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:

View File

@ -19,6 +19,7 @@ from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.misc import (State, get_state, load_config, parse_args, from freqtrade.misc import (State, get_state, load_config, parse_args,
throttle, update_state) throttle, update_state)
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.strategy.strategy import Strategy
logger = logging.getLogger('freqtrade') logger = logging.getLogger('freqtrade')
@ -248,14 +249,16 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) -
Based an earlier trade and current price and ROI configuration, decides whether bot should sell Based an earlier trade and current price and ROI configuration, decides whether bot should sell
:return True if bot should sell at current rate :return True if bot should sell at current rate
""" """
strategy = Strategy()
current_profit = trade.calc_profit_percent(current_rate) current_profit = trade.calc_profit_percent(current_rate)
if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']): if strategy.stoploss is not None and current_profit < float(strategy.stoploss):
logger.debug('Stop loss hit.') logger.debug('Stop loss hit.')
return True return True
# Check if time matches and current rate is above threshold # Check if time matches and current rate is above threshold
time_diff = (current_time - trade.open_date).total_seconds() / 60 time_diff = (current_time - trade.open_date).total_seconds() / 60
for duration, threshold in sorted(_CONF['minimal_roi'].items()): for duration, threshold in sorted(strategy.minimal_roi.items()):
if time_diff > float(duration) and current_profit > threshold: if time_diff > float(duration) and current_profit > threshold:
return True return True
@ -391,6 +394,9 @@ def init(config: dict, db_url: Optional[str] = None) -> None:
persistence.init(config, db_url) persistence.init(config, db_url)
exchange.init(config) exchange.init(config)
strategy = Strategy()
strategy.init(config)
# Set initial application state # Set initial application state
initial_state = config.get('initial_state') initial_state = config.get('initial_state')
if initial_state: if initial_state:
@ -458,6 +464,9 @@ def main(sysargv=sys.argv[1:]) -> None:
# Load and validate configuration # Load and validate configuration
_CONF = load_config(args.config) _CONF = load_config(args.config)
# Add the strategy file to use
_CONF.update({'strategy': args.strategy})
# Initialize all modules and start main loop # Initialize all modules and start main loop
if args.dynamic_whitelist: if args.dynamic_whitelist:
logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)') logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)')
@ -475,6 +484,7 @@ def main(sysargv=sys.argv[1:]) -> None:
try: try:
init(_CONF) init(_CONF)
old_state = None old_state = None
while True: while True:
new_state = get_state() new_state = get_state()
# Log state transition # Log state transition

View File

@ -124,6 +124,14 @@ def common_args_parser(description: str):
type=str, type=str,
metavar='PATH', metavar='PATH',
) )
parser.add_argument(
'-s', '--strategy',
help='specify strategy file (default: freqtrade/strategy/default_strategy.py)',
dest='strategy',
default='.default_strategy',
type=str,
metavar='PATH',
)
return parser return parser
@ -375,12 +383,10 @@ CONF_SCHEMA = {
], ],
'required': [ 'required': [
'max_open_trades', 'max_open_trades',
'ticker_interval',
'stake_currency', 'stake_currency',
'stake_amount', 'stake_amount',
'fiat_display_currency', 'fiat_display_currency',
'dry_run', 'dry_run',
'minimal_roi',
'bid_strategy', 'bid_strategy',
'telegram' 'telegram'
] ]

View File

@ -6,9 +6,10 @@ import os
from typing import Optional, List, Dict from typing import Optional, List, Dict
from pandas import DataFrame from pandas import DataFrame
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import get_ticker_history
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf
from freqtrade.analyze import populate_indicators, parse_ticker_dataframe from freqtrade.analyze import populate_indicators, parse_ticker_dataframe
from freqtrade import misc from freqtrade import misc
from user_data.hyperopt_conf import hyperopt_optimize_conf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -14,6 +14,7 @@ from freqtrade.analyze import populate_buy_trend, populate_sell_trend
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade.main import min_roi_reached from freqtrade.main import min_roi_reached
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.strategy.strategy import Strategy
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -199,6 +200,11 @@ def start(args):
logger.info('Using max_open_trades: %s ...', config['max_open_trades']) logger.info('Using max_open_trades: %s ...', config['max_open_trades'])
max_open_trades = config['max_open_trades'] max_open_trades = config['max_open_trades']
# init the strategy to use
config.update({'strategy': args.strategy})
strategy = Strategy()
strategy.init(config)
# Monkey patch config # Monkey patch config
from freqtrade import main from freqtrade import main
main._CONF = config main._CONF = config
@ -216,7 +222,7 @@ def start(args):
'realistic': args.realistic_simulation, 'realistic': args.realistic_simulation,
'sell_profit_only': sell_profit_only, 'sell_profit_only': sell_profit_only,
'use_sell_signal': use_sell_signal, 'use_sell_signal': use_sell_signal,
'stoploss': config.get('stoploss'), 'stoploss': strategy.stoploss,
'record': args.export 'record': args.export
}) })
logger.info( logger.info(

View File

@ -7,11 +7,10 @@ import sys
import pickle import pickle
import signal import signal
import os import os
from functools import reduce
from math import exp from math import exp
from operator import itemgetter from operator import itemgetter
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, space_eval, tpe
from hyperopt.mongoexp import MongoTrials from hyperopt.mongoexp import MongoTrials
from pandas import DataFrame from pandas import DataFrame
@ -20,8 +19,8 @@ from freqtrade import exchange, optimize
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade.misc import load_config from freqtrade.misc import load_config
from freqtrade.optimize.backtesting import backtest from freqtrade.optimize.backtesting import backtest
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf from freqtrade.strategy.strategy import Strategy
from freqtrade.vendor.qtpylib.indicators import crossed_above from user_data.hyperopt_conf import hyperopt_optimize_conf
# Remove noisy log messages # Remove noisy log messages
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING) logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
@ -49,7 +48,7 @@ PROCESSED = None # optimize.preprocess(optimize.load_data())
OPTIMIZE_CONFIG = hyperopt_optimize_conf() OPTIMIZE_CONFIG = hyperopt_optimize_conf()
# Hyperopt Trials # Hyperopt Trials
TRIALS_FILE = os.path.join('freqtrade', 'optimize', 'hyperopt_trials.pickle') TRIALS_FILE = os.path.join('user_data', 'hyperopt_trials.pickle')
TRIALS = Trials() TRIALS = Trials()
# Monkey patch config # Monkey patch config
@ -57,63 +56,6 @@ from freqtrade import main # noqa
main._CONF = OPTIMIZE_CONFIG main._CONF = OPTIMIZE_CONFIG
SPACE = {
'macd_below_zero': hp.choice('macd_below_zero', [
{'enabled': False},
{'enabled': True}
]),
'mfi': hp.choice('mfi', [
{'enabled': False},
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
]),
'fastd': hp.choice('fastd', [
{'enabled': False},
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)}
]),
'adx': hp.choice('adx', [
{'enabled': False},
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
]),
'rsi': hp.choice('rsi', [
{'enabled': False},
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
]),
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
{'enabled': False},
{'enabled': True}
]),
'uptrend_short_ema': hp.choice('uptrend_short_ema', [
{'enabled': False},
{'enabled': True}
]),
'over_sar': hp.choice('over_sar', [
{'enabled': False},
{'enabled': True}
]),
'green_candle': hp.choice('green_candle', [
{'enabled': False},
{'enabled': True}
]),
'uptrend_sma': hp.choice('uptrend_sma', [
{'enabled': False},
{'enabled': True}
]),
'trigger': hp.choice('trigger', [
{'type': 'lower_bb'},
{'type': 'lower_bb_tema'},
{'type': 'faststoch10'},
{'type': 'ao_cross_zero'},
{'type': 'ema3_cross_ema10'},
{'type': 'macd_cross_signal'},
{'type': 'sar_reversal'},
{'type': 'ht_sine'},
{'type': 'heiken_reversal_bull'},
{'type': 'di_cross'},
]),
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
}
def save_trials(trials, trials_path=TRIALS_FILE): def save_trials(trials, trials_path=TRIALS_FILE):
"""Save hyperopt trials to file""" """Save hyperopt trials to file"""
logger.info('Saving Trials to \'{}\''.format(trials_path)) logger.info('Saving Trials to \'{}\''.format(trials_path))
@ -162,7 +104,9 @@ def optimizer(params):
global _CURRENT_TRIES global _CURRENT_TRIES
from freqtrade.optimize import backtesting from freqtrade.optimize import backtesting
backtesting.populate_buy_trend = buy_strategy_generator(params)
strategy = Strategy()
backtesting.populate_buy_trend = strategy.buy_strategy_generator(params)
results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'], results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'],
'processed': PROCESSED, 'processed': PROCESSED,
@ -209,58 +153,8 @@ def format_results(results: DataFrame):
) )
def buy_strategy_generator(params):
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
conditions = []
# GUARDS AND TRENDS
if params['uptrend_long_ema']['enabled']:
conditions.append(dataframe['ema50'] > dataframe['ema100'])
if params['macd_below_zero']['enabled']:
conditions.append(dataframe['macd'] < 0)
if params['uptrend_short_ema']['enabled']:
conditions.append(dataframe['ema5'] > dataframe['ema10'])
if params['mfi']['enabled']:
conditions.append(dataframe['mfi'] < params['mfi']['value'])
if params['fastd']['enabled']:
conditions.append(dataframe['fastd'] < params['fastd']['value'])
if params['adx']['enabled']:
conditions.append(dataframe['adx'] > params['adx']['value'])
if params['rsi']['enabled']:
conditions.append(dataframe['rsi'] < params['rsi']['value'])
if params['over_sar']['enabled']:
conditions.append(dataframe['close'] > dataframe['sar'])
if params['green_candle']['enabled']:
conditions.append(dataframe['close'] > dataframe['open'])
if params['uptrend_sma']['enabled']:
prevsma = dataframe['sma'].shift(1)
conditions.append(dataframe['sma'] > prevsma)
# TRIGGERS
triggers = {
'lower_bb': (dataframe['close'] < dataframe['bb_lowerband']),
'lower_bb_tema': (dataframe['tema'] < dataframe['bb_lowerband']),
'faststoch10': (crossed_above(dataframe['fastd'], 10.0)),
'ao_cross_zero': (crossed_above(dataframe['ao'], 0.0)),
'ema3_cross_ema10': (crossed_above(dataframe['ema3'], dataframe['ema10'])),
'macd_cross_signal': (crossed_above(dataframe['macd'], dataframe['macdsignal'])),
'sar_reversal': (crossed_above(dataframe['close'], dataframe['sar'])),
'ht_sine': (crossed_above(dataframe['htleadsine'], dataframe['htsine'])),
'heiken_reversal_bull': (crossed_above(dataframe['ha_close'], dataframe['ha_open'])) &
(dataframe['ha_low'] == dataframe['ha_open']),
'di_cross': (crossed_above(dataframe['plus_di'], dataframe['minus_di'])),
}
conditions.append(triggers.get(params['trigger']['type']))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
return populate_buy_trend
def start(args): def start(args):
global TOTAL_TRIES, PROCESSED, SPACE, TRIALS, _CURRENT_TRIES global TOTAL_TRIES, PROCESSED, TRIALS, _CURRENT_TRIES
TOTAL_TRIES = args.epochs TOTAL_TRIES = args.epochs
@ -275,6 +169,12 @@ def start(args):
logger.info('Using config: %s ...', args.config) logger.info('Using config: %s ...', args.config)
config = load_config(args.config) config = load_config(args.config)
pairs = config['exchange']['pair_whitelist'] pairs = config['exchange']['pair_whitelist']
# init the strategy to use
config.update({'strategy': args.strategy})
strategy = Strategy()
strategy.init(config)
timerange = misc.parse_timerange(args.timerange) timerange = misc.parse_timerange(args.timerange)
data = optimize.load_data(args.datadir, pairs=pairs, data = optimize.load_data(args.datadir, pairs=pairs,
ticker_interval=args.ticker_interval, ticker_interval=args.ticker_interval,
@ -303,7 +203,7 @@ def start(args):
try: try:
best_parameters = fmin( best_parameters = fmin(
fn=optimizer, fn=optimizer,
space=SPACE, space=strategy.hyperopt_space(),
algo=tpe.suggest, algo=tpe.suggest,
max_evals=TOTAL_TRIES, max_evals=TOTAL_TRIES,
trials=TRIALS trials=TRIALS
@ -319,7 +219,10 @@ def start(args):
# Improve best parameter logging display # Improve best parameter logging display
if best_parameters: if best_parameters:
best_parameters = space_eval(SPACE, best_parameters) best_parameters = space_eval(
strategy.hyperopt_space(),
best_parameters
)
logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4)) logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4))
logger.info('Best Result:\n%s', best_result) logger.info('Best Result:\n%s', best_result)

View File

View 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

View 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
"""

View 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)

View File

@ -1,6 +1,6 @@
# pragma pylint: disable=missing-docstring,W0212 # pragma pylint: disable=missing-docstring,W0212
from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf from user_data.hyperopt_conf import hyperopt_optimize_conf
def test_hyperopt_optimize_conf(): def test_hyperopt_optimize_conf():

View 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({}))

View 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

View File

@ -9,6 +9,7 @@ from pandas import DataFrame
from freqtrade.analyze import (get_signal, parse_ticker_dataframe, from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
populate_buy_trend, populate_indicators, populate_buy_trend, populate_indicators,
populate_sell_trend) populate_sell_trend)
from freqtrade.strategy.strategy import Strategy
@pytest.fixture @pytest.fixture
@ -27,11 +28,17 @@ def test_dataframe_correct_length(result):
def test_populates_buy_trend(result): def test_populates_buy_trend(result):
# Load the default strategy for the unit test, because this logic is done in main.py
Strategy().init({'strategy': 'default_strategy'})
dataframe = populate_buy_trend(populate_indicators(result)) dataframe = populate_buy_trend(populate_indicators(result))
assert 'buy' in dataframe.columns assert 'buy' in dataframe.columns
def test_populates_sell_trend(result): def test_populates_sell_trend(result):
# Load the default strategy for the unit test, because this logic is done in main.py
Strategy().init({'strategy': 'default_strategy'})
dataframe = populate_sell_trend(populate_indicators(result)) dataframe = populate_sell_trend(populate_indicators(result))
assert 'sell' in dataframe.columns assert 'sell' in dataframe.columns

View File

@ -1,47 +1,53 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import sys
import argparse import talib.abstract as ta
import matplotlib # Install PYQT5 manually if you want to test this helper function import freqtrade.vendor.qtpylib.indicators as qtpylib
matplotlib.use("Qt5Agg") from pandas import DataFrame
import matplotlib.pyplot as plt
from freqtrade import exchange, analyze from freqtrade import exchange, analyze
from freqtrade.misc import common_args_parser from freqtrade.misc import common_args_parser
from freqtrade.strategy.strategy import Strategy
import matplotlib.pyplot as plt
import matplotlib # Install PYQT5 manually if you want to test this helper function
matplotlib.use("Qt5Agg")
def plot_parse_args(args ): def plot_parse_args(args):
parser = common_args_parser(description='Graph utility') parser = common_args_parser(description='Graph utility')
parser.add_argument( parser.add_argument(
'-p', '--pair', '-p', '--pair',
help = 'What currency pair', help='What currency pair',
dest = 'pair', dest='pair',
default = 'BTC_ETH', default='BTC_ETH',
type = str, type=str,
) )
parser.add_argument( parser.add_argument(
'-i', '--interval', '-i', '--interval',
help = 'what interval to use', help='what interval to use',
dest = 'interval', dest='interval',
default = '5', default=5,
type = int, type=int,
) )
return parser.parse_args(args) return parser.parse_args(args)
def plot_analyzed_dataframe(args): def plot_analyzed_dataframe(args) -> None:
""" """
Calls analyze() and plots the returned dataframe Calls analyze() and plots the returned dataframe
:param pair: pair as str :param pair: pair as str
:return: None :return: None
""" """
# Init strategy
strategy = Strategy()
strategy.init({'strategy': args.strategy})
# Init Bittrex to use public API # Init Bittrex to use public API
exchange._API = exchange.Bittrex({'key': '', 'secret': ''}) exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
ticker = exchange.get_ticker_history(args.pair,args.interval) ticker = exchange.get_ticker_history(args.pair, args.interval)
dataframe = analyze.analyze_ticker(ticker) dataframe = analyze.analyze_ticker(ticker)
dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close'] dataframe = populate_indicator(dataframe)
dataframe.loc[dataframe['sell'] == 1, 'sell_price'] = dataframe['close']
# Two subplots sharing x axis # Two subplots sharing x axis
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True) fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True)
@ -50,7 +56,7 @@ def plot_analyzed_dataframe(args):
# ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell') # ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell')
ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA') ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA')
ax1.plot(dataframe.index.values, dataframe['tema'], ':', label='TEMA') ax1.plot(dataframe.index.values, dataframe['tema'], ':', label='TEMA')
ax1.plot(dataframe.index.values, dataframe['blower'], '-.', label='BB low') ax1.plot(dataframe.index.values, dataframe['bb_lowerband'], '-.', label='BB low')
ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy') ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy')
ax1.legend() ax1.legend()
@ -71,6 +77,41 @@ def plot_analyzed_dataframe(args):
plt.show() plt.show()
def populate_indicator(dataframe: DataFrame) -> DataFrame:
dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close']
dataframe.loc[dataframe['sell'] == 1, 'sell_price'] = dataframe['close']
# ADX
if 'adx' not in dataframe:
dataframe['adx'] = ta.ADX(dataframe)
# Bollinger bands
if 'bb_lowerband' not in dataframe:
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
# Stoch fast
if 'fastd' not in dataframe or 'fastk' not in dataframe:
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# MFI
if 'mfi' not in dataframe:
dataframe['mfi'] = ta.MFI(dataframe)
# SMA - Simple Moving Average
if 'sma' not in dataframe:
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
# TEMA - Triple Exponential Moving Average
if 'tema' not in dataframe:
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
return dataframe
if __name__ == '__main__': if __name__ == '__main__':
args = plot_parse_args(sys.argv[1:]) args = plot_parse_args(sys.argv[1:])
plot_analyzed_dataframe(args) plot_analyzed_dataframe(args)

View File

@ -1,26 +1,24 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import sys
import argparse
import json import json
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
import freqtrade.optimize as optimize import freqtrade.optimize as optimize
import freqtrade.misc as misc import freqtrade.misc as misc
import freqtrade.exchange as exchange from freqtrade.strategy.strategy import Strategy
import freqtrade.analyze as analyze
def plot_parse_args(args ): def plot_parse_args(args):
parser = misc.common_args_parser('Graph utility') parser = misc.common_args_parser('Graph utility')
# FIX: perhaps delete those backtesting options that are not feasible (shows up in -h) # FIX: perhaps delete those backtesting options that are not feasible (shows up in -h)
misc.backtesting_options(parser) misc.backtesting_options(parser)
parser.add_argument( parser.add_argument(
'-p', '--pair', '-p', '--pair',
help = 'Show profits for only this pairs. Pairs are comma-separated.', help='Show profits for only this pairs. Pairs are comma-separated.',
dest = 'pair', dest='pair',
default = None default=None
) )
return parser.parse_args(args) return parser.parse_args(args)
@ -47,7 +45,7 @@ def make_profit_array(data, px, filter_pairs=[]):
# total profits at each timeframe # total profits at each timeframe
# to accumulated profits # to accumulated profits
pa = 0 pa = 0
for x in range(0,len(pg)): for x in range(0, len(pg)):
p = pg[x] # Get current total percent p = pg[x] # Get current total percent
pa += p # Add to the accumulated percent pa += p # Add to the accumulated percent
pg[x] = pa # write back to save memory pg[x] = pa # write back to save memory
@ -70,14 +68,21 @@ def plot_profit(args) -> None:
filter_pairs = args.pair filter_pairs = args.pair
config = misc.load_config(args.config) config = misc.load_config(args.config)
config.update({'strategy': args.strategy})
# Init strategy
strategy = Strategy()
strategy.init(config)
pairs = config['exchange']['pair_whitelist'] pairs = config['exchange']['pair_whitelist']
if filter_pairs: if filter_pairs:
filter_pairs = filter_pairs.split(',') filter_pairs = filter_pairs.split(',')
pairs = list(set(pairs) & set(filter_pairs)) pairs = list(set(pairs) & set(filter_pairs))
print('Filter, keep pairs %s' % pairs) print('Filter, keep pairs %s' % pairs)
tickers = optimize.load_data(args.datadir, pairs=pairs, tickers = optimize.load_data(args.datadir, pairs=pairs,
ticker_interval=args.ticker_interval, ticker_interval=strategy.ticker_interval,
refresh_pairs=False) refresh_pairs=False)
dataframes = optimize.preprocess(tickers) dataframes = optimize.preprocess(tickers)
@ -96,7 +101,7 @@ def plot_profit(args) -> None:
# if max_x != n: # if max_x != n:
# raise Exception('Please rerun script. Input data has different lengths %s' # raise Exception('Please rerun script. Input data has different lengths %s'
# %('Different pair length: %s <=> %s' %(max_x, n))) # %('Different pair length: %s <=> %s' %(max_x, n)))
print('max_x: %s' %(max_x)) print('max_x: %s' % (max_x))
# We are essentially saying: # We are essentially saying:
# array <- sum dataframes[*]['close'] / num_items dataframes # array <- sum dataframes[*]['close'] / num_items dataframes
@ -106,7 +111,7 @@ def plot_profit(args) -> None:
for pair, pair_data in dataframes.items(): for pair, pair_data in dataframes.items():
close = pair_data['close'] close = pair_data['close']
maxprice = max(close) # Normalize price to [0,1] maxprice = max(close) # Normalize price to [0,1]
print('Pair %s has length %s' %(pair, len(close))) print('Pair %s has length %s' % (pair, len(close)))
for x in range(0, len(close)): for x in range(0, len(close)):
avgclose[x] += close[x] / maxprice avgclose[x] += close[x] / maxprice
# avgclose += close # avgclose += close
@ -118,7 +123,7 @@ def plot_profit(args) -> None:
filename = 'backtest-result.json' filename = 'backtest-result.json'
with open(filename) as file: with open(filename) as file:
data = json.load(file) data = json.load(file)
pg = make_profit_array(data, max_x, filter_pairs) pg = make_profit_array(data, max_x, filter_pairs)
# #

0
user_data/data/.gitkeep Normal file
View File

View File

View 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