Merge branch 'develop' into plot_profit

This commit is contained in:
kryofly 2018-01-26 10:07:48 +01:00
commit fe2f779c47
37 changed files with 1551 additions and 473 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

@ -4,7 +4,7 @@
"stake_amount": 0.05, "stake_amount": 0.05,
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"dry_run": false, "dry_run": false,
"ticker_interval": "5", "ticker_interval": 5,
"minimal_roi": { "minimal_roi": {
"40": 0.0, "40": 0.0,
"30": 0.01, "30": 0.01,

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

@ -17,11 +17,11 @@ The table below will list all configuration parameters.
| `max_open_trades` | 3 | Yes | Number of trades open your bot will have. | `max_open_trades` | 3 | Yes | Number of trades open your bot will have.
| `stake_currency` | BTC | Yes | Crypto-currency used for trading. | `stake_currency` | BTC | Yes | Crypto-currency used for trading.
| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. | `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged.
| `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__)
@ -30,8 +29,9 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame:
""" """
columns = {'C': 'close', 'V': 'volume', 'O': 'open', 'H': 'high', 'L': 'low', 'T': 'date'} columns = {'C': 'close', 'V': 'volume', 'O': 'open', 'H': 'high', 'L': 'low', 'T': 'date'}
frame = DataFrame(ticker) \ frame = DataFrame(ticker) \
.drop('BV', 1) \
.rename(columns=columns) .rename(columns=columns)
if 'BV' in frame:
frame.drop('BV', 1, inplace=True)
frame['date'] = to_datetime(frame['date'], utc=True, infer_datetime_format=True) frame['date'] = to_datetime(frame['date'], utc=True, infer_datetime_format=True)
frame.sort_values('date', inplace=True) frame.sort_values('date', inplace=True)
return frame return frame
@ -45,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:
@ -229,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:
@ -251,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

@ -48,7 +48,10 @@ class CryptoFiat():
return self._expiration - time.time() <= 0 return self._expiration - time.time() <= 0
class CryptoToFiatConverter(): class CryptoToFiatConverter(object):
__instance = None
_coinmarketcap = None
# Constants # Constants
SUPPORTED_FIAT = [ SUPPORTED_FIAT = [
"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK",
@ -57,12 +60,16 @@ class CryptoToFiatConverter():
"RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD" "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD"
] ]
def __init__(self) -> None: def __new__(cls):
if CryptoToFiatConverter.__instance is None:
CryptoToFiatConverter.__instance = object.__new__(cls)
try: try:
self._coinmarketcap = Pymarketcap() CryptoToFiatConverter._coinmarketcap = Pymarketcap()
except BaseException: except BaseException:
self._coinmarketcap = None CryptoToFiatConverter._coinmarketcap = None
return CryptoToFiatConverter.__instance
def __init__(self) -> None:
self._pairs = [] self._pairs = []
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float: def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:

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')
@ -191,12 +192,25 @@ def execute_sell(trade: Trade, limit: float) -> None:
fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2) fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2)
profit_trade = trade.calc_profit(rate=limit) profit_trade = trade.calc_profit(rate=limit)
current_rate = exchange.get_ticker(trade.pair, False)['bid']
profit = trade.calc_profit_percent(current_rate)
message = '*{exchange}:* Selling [{pair}]({pair_url}) with limit `{limit:.8f}`'.format( message = """*{exchange}:* Selling
*Current Pair:* [{pair}]({pair_url})
*Limit:* `{limit}`
*Amount:* `{amount}`
*Open Rate:* `{open_rate:.8f}`
*Current Rate:* `{current_rate:.8f}`
*Profit:* `{profit:.2f}%`
""".format(
exchange=trade.exchange, exchange=trade.exchange,
pair=trade.pair.replace('_', '/'), pair=trade.pair,
pair_url=exchange.get_pair_detail_url(trade.pair), pair_url=exchange.get_pair_detail_url(trade.pair),
limit=limit limit=limit,
open_rate=trade.open_rate,
current_rate=current_rate,
amount=round(trade.amount, 8),
profit=round(profit * 100, 2),
) )
# For regular case, when the configuration exists # For regular case, when the configuration exists
@ -235,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
@ -378,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:
@ -445,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)')
@ -462,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
@ -476,7 +499,7 @@ def main(sysargv=sys.argv[1:]) -> None:
_process, _process,
min_secs=_CONF['internals'].get('process_throttle_secs', 10), min_secs=_CONF['internals'].get('process_throttle_secs', 10),
nb_assets=args.dynamic_whitelist, nb_assets=args.dynamic_whitelist,
interval=int(_CONF.get('ticker_interval', "5")) interval=int(_CONF.get('ticker_interval', 5))
) )
old_state = new_state old_state = new_state
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@ -159,6 +159,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
@ -328,7 +336,7 @@ CONF_SCHEMA = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'max_open_trades': {'type': 'integer', 'minimum': 1}, 'max_open_trades': {'type': 'integer', 'minimum': 1},
'ticker_interval': {'type': 'string', 'enum': ['1', '5', '30', '60', '1440']}, 'ticker_interval': {'type': 'integer', 'enum': [1, 5, 30, 60, 1440]},
'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']},
'stake_amount': {'type': 'number', 'minimum': 0.0005}, 'stake_amount': {'type': 'number', 'minimum': 0.0005},
'fiat_display_currency': {'type': 'string', 'enum': ['AUD', 'BRL', 'CAD', 'CHF', 'fiat_display_currency': {'type': 'string', 'enum': ['AUD', 'BRL', 'CAD', 'CHF',
@ -419,12 +427,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__)
@ -127,7 +128,6 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) ->
pair=filepair, pair=filepair,
interval=interval, interval=interval,
)) ))
filename = filename.replace('USDT_BTC', 'BTC_FAKEBULL')
if os.path.isfile(filename): if os.path.isfile(filename):
with open(filename, "rt") as fp: with open(filename, "rt") as fp:

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

@ -3,25 +3,31 @@
import json import json
import logging import logging
import sys import os
import pickle import pickle
import signal import signal
import os import sys
from functools import reduce from functools import reduce
from math import exp from math import exp
from operator import itemgetter from operator import itemgetter
from typing import Dict, List
import numpy
import talib.abstract as ta
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
from hyperopt.mongoexp import MongoTrials from hyperopt.mongoexp import MongoTrials
from pandas import DataFrame from pandas import DataFrame
from freqtrade import main, misc # noqa import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade import exchange, optimize # Monkey patch config
from freqtrade import main # noqa; noqa
from freqtrade import exchange, misc, 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 import backtesting
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,15 +55,181 @@ 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
from freqtrade import main # noqa
main._CONF = OPTIMIZE_CONFIG main._CONF = OPTIMIZE_CONFIG
SPACE = { def populate_indicators(dataframe: DataFrame) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame
"""
dataframe['adx'] = ta.ADX(dataframe)
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
dataframe['cci'] = ta.CCI(dataframe)
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
dataframe['mfi'] = ta.MFI(dataframe)
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
dataframe['roc'] = ta.ROC(dataframe)
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']
# 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 Parabolic
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)
# 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 save_trials(trials, trials_path=TRIALS_FILE):
"""Save hyperopt trials to file"""
logger.info('Saving Trials to \'{}\''.format(trials_path))
pickle.dump(trials, open(trials_path, 'wb'))
def read_trials(trials_path=TRIALS_FILE):
"""Read hyperopt trials file"""
logger.info('Reading Trials from \'{}\''.format(trials_path))
trials = pickle.load(open(trials_path, 'rb'))
os.remove(trials_path)
return trials
def log_trials_result(trials):
vals = json.dumps(trials.best_trial['misc']['vals'], indent=4)
results = trials.best_trial['result']['result']
logger.info('Best result:\n%s\nwith values:\n%s', results, vals)
def log_results(results):
""" log results if it is better than any previous evaluation """
global CURRENT_BEST_LOSS
if results['loss'] < CURRENT_BEST_LOSS:
CURRENT_BEST_LOSS = results['loss']
logger.info('{:5d}/{}: {}. Loss {:.5f}'.format(
results['current_tries'],
results['total_tries'],
results['result'],
results['loss']))
else:
print('.', end='')
sys.stdout.flush()
def calculate_loss(total_profit: float, trade_count: int, trade_duration: float):
""" objective function, returns smaller number for more optimal results """
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
duration_loss = 0.7 + 0.3 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
return trade_loss + profit_loss + duration_loss
def hyperopt_space() -> List[Dict]:
"""
Define your Hyperopt space for searching strategy parameters
"""
space = {
'macd_below_zero': hp.choice('macd_below_zero', [ 'macd_below_zero': hp.choice('macd_below_zero', [
{'enabled': False}, {'enabled': False},
{'enabled': True} {'enabled': True}
@ -111,57 +283,87 @@ SPACE = {
{'type': 'di_cross'}, {'type': 'di_cross'},
]), ]),
'stoploss': hp.uniform('stoploss', -0.5, -0.02), 'stoploss': hp.uniform('stoploss', -0.5, -0.02),
} }
return space
def save_trials(trials, trials_path=TRIALS_FILE): def buy_strategy_generator(params) -> None:
"""Save hyperopt trials to file""" """
logger.info('Saving Trials to \'{}\''.format(trials_path)) Define the buy strategy parameters to be used by hyperopt
pickle.dump(trials, open(trials_path, 'wb')) """
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']))
def read_trials(trials_path=TRIALS_FILE): dataframe.loc[
"""Read hyperopt trials file""" reduce(lambda x, y: x & y, conditions),
logger.info('Reading Trials from \'{}\''.format(trials_path)) 'buy'] = 1
trials = pickle.load(open(trials_path, 'rb'))
os.remove(trials_path)
return trials
return dataframe
def log_trials_result(trials): return populate_buy_trend
vals = json.dumps(trials.best_trial['misc']['vals'], indent=4)
results = trials.best_trial['result']['result']
logger.info('Best result:\n%s\nwith values:\n%s', results, vals)
def log_results(results):
""" log results if it is better than any previous evaluation """
global CURRENT_BEST_LOSS
if results['loss'] < CURRENT_BEST_LOSS:
CURRENT_BEST_LOSS = results['loss']
logger.info('{:5d}/{}: {}. Loss {:.5f}'.format(
results['current_tries'],
results['total_tries'],
results['result'],
results['loss']))
else:
print('.', end='')
sys.stdout.flush()
def calculate_loss(total_profit: float, trade_count: int, trade_duration: float):
""" objective function, returns smaller number for more optimal results """
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
duration_loss = 0.7 + 0.3 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
return trade_loss + profit_loss + duration_loss
def optimizer(params): def optimizer(params):
global _CURRENT_TRIES global _CURRENT_TRIES
from freqtrade.optimize import backtesting
backtesting.populate_buy_trend = buy_strategy_generator(params) backtesting.populate_buy_trend = buy_strategy_generator(params)
results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'], results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'],
@ -209,58 +411,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,10 +427,17 @@ 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,
timerange=timerange) timerange=timerange)
optimize.populate_indicators = populate_indicators
PROCESSED = optimize.tickerdata_to_dataframe(data) PROCESSED = optimize.tickerdata_to_dataframe(data)
if args.mongodb: if args.mongodb:
@ -303,7 +462,7 @@ def start(args):
try: try:
best_parameters = fmin( best_parameters = fmin(
fn=optimizer, fn=optimizer,
space=SPACE, space=hyperopt_space(),
algo=tpe.suggest, algo=tpe.suggest,
max_evals=TOTAL_TRIES, max_evals=TOTAL_TRIES,
trials=TRIALS trials=TRIALS
@ -319,7 +478,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(
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

@ -47,6 +47,10 @@ def init(config: dict, engine: Optional[Engine] = None) -> None:
Trade.query = session.query_property() Trade.query = session.query_property()
_DECL_BASE.metadata.create_all(engine) _DECL_BASE.metadata.create_all(engine)
# Clean dry_run DB
if _CONF.get('dry_run', False) and _CONF.get('dry_run_db', False):
clean_dry_run_db()
def cleanup() -> None: def cleanup() -> None:
""" """
@ -56,6 +60,17 @@ def cleanup() -> None:
Trade.session.flush() Trade.session.flush()
def clean_dry_run_db() -> None:
"""
Remove open_order_id from a Dry_run DB
:return: None
"""
for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
# Check we are updating only a dry_run order not a prod one
if 'dry_run' in trade.open_order_id:
trade.open_order_id = None
class Trade(_DECL_BASE): class Trade(_DECL_BASE):
__tablename__ = 'trades' __tablename__ = 'trades'

View File

@ -147,7 +147,7 @@ def _status(bot: Bot, update: Update) -> None:
) if trade.close_profit else None ) if trade.close_profit else None
message = """ message = """
*Trade ID:* `{trade_id}` *Trade ID:* `{trade_id}`
*Current Pair:* [{pair}]({market_url}) *Current Pair:* [{pair}]({pair_url})
*Open Since:* `{date}` *Open Since:* `{date}`
*Amount:* `{amount}` *Amount:* `{amount}`
*Open Rate:* `{open_rate:.8f}` *Open Rate:* `{open_rate:.8f}`
@ -156,10 +156,11 @@ def _status(bot: Bot, update: Update) -> None:
*Close Profit:* `{close_profit}` *Close Profit:* `{close_profit}`
*Current Profit:* `{current_profit:.2f}%` *Current Profit:* `{current_profit:.2f}%`
*Open Order:* `{open_order}` *Open Order:* `{open_order}`
*Total Open Trades:* `{total_trades}`
""".format( """.format(
trade_id=trade.id, trade_id=trade.id,
pair=trade.pair, pair=trade.pair,
market_url=exchange.get_pair_detail_url(trade.pair), pair_url=exchange.get_pair_detail_url(trade.pair),
date=arrow.get(trade.open_date).humanize(), date=arrow.get(trade.open_date).humanize(),
open_rate=trade.open_rate, open_rate=trade.open_rate,
close_rate=trade.close_rate, close_rate=trade.close_rate,
@ -170,6 +171,7 @@ def _status(bot: Bot, update: Update) -> None:
open_order='({} rem={:.8f})'.format( open_order='({} rem={:.8f})'.format(
order['type'], order['remaining'] order['type'], order['remaining']
) if order else None, ) if order else None,
total_trades=len(trades)
) )
send_msg(message, bot=bot) send_msg(message, bot=bot)

View File

View File

@ -0,0 +1,238 @@
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
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

View File

@ -0,0 +1,44 @@
from abc import ABC, abstractmethod
from pandas import DataFrame
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
"""

View File

@ -0,0 +1,166 @@
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)

View File

@ -18,7 +18,7 @@ def default_conf():
"stake_currency": "BTC", "stake_currency": "BTC",
"stake_amount": 0.001, "stake_amount": 0.001,
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"ticker_interval": "5", "ticker_interval": 5,
"dry_run": True, "dry_run": True,
"minimal_roi": { "minimal_roi": {
"40": 0.0, "40": 0.0,
@ -217,3 +217,33 @@ def ticker_history():
"BV": 0.7039405 "BV": 0.7039405
} }
] ]
@pytest.fixture
def ticker_history_without_bv():
return [
{
"O": 8.794e-05,
"H": 8.948e-05,
"L": 8.794e-05,
"C": 8.88e-05,
"V": 991.09056638,
"T": "2017-11-26T08:50:00"
},
{
"O": 8.88e-05,
"H": 8.942e-05,
"L": 8.88e-05,
"C": 8.893e-05,
"V": 658.77935965,
"T": "2017-11-26T08:55:00"
},
{
"O": 8.891e-05,
"H": 8.893e-05,
"L": 8.875e-05,
"C": 8.877e-05,
"V": 7920.73570705,
"T": "2017-11-26T09:00:00"
}
]

View File

@ -3,12 +3,21 @@
import logging import logging
import math import math
import pandas as pd import pandas as pd
import pytest
from unittest.mock import MagicMock from unittest.mock import MagicMock
from freqtrade import exchange, optimize from freqtrade import exchange, optimize
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade.optimize import preprocess from freqtrade.optimize import preprocess
from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe
import freqtrade.optimize.backtesting as backtesting import freqtrade.optimize.backtesting as backtesting
from freqtrade.strategy.strategy import Strategy
@pytest.fixture
def default_strategy():
strategy = Strategy()
strategy.init({'strategy': 'default_strategy'})
return strategy
def trim_dictlist(dl, num): def trim_dictlist(dl, num):
@ -37,7 +46,7 @@ def test_generate_text_table():
'TOTAL 2 15.00 0.60000000 100.0 2 0') # noqa 'TOTAL 2 15.00 0.60000000 100.0 2 0') # noqa
def test_get_timeframe(): def test_get_timeframe(default_strategy):
data = preprocess(optimize.load_data( data = preprocess(optimize.load_data(
None, ticker_interval=1, pairs=['BTC_UNITEST'])) None, ticker_interval=1, pairs=['BTC_UNITEST']))
min_date, max_date = get_timeframe(data) min_date, max_date = get_timeframe(data)
@ -45,7 +54,7 @@ def test_get_timeframe():
assert max_date.isoformat() == '2017-11-14T22:59:00+00:00' assert max_date.isoformat() == '2017-11-14T22:59:00+00:00'
def test_backtest(default_conf, mocker): def test_backtest(default_strategy, default_conf, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
exchange._API = Bittrex({'key': '', 'secret': ''}) exchange._API = Bittrex({'key': '', 'secret': ''})
@ -58,7 +67,7 @@ def test_backtest(default_conf, mocker):
assert not results.empty assert not results.empty
def test_backtest_1min_ticker_interval(default_conf, mocker): def test_backtest_1min_ticker_interval(default_strategy, default_conf, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
exchange._API = Bittrex({'key': '', 'secret': ''}) exchange._API = Bittrex({'key': '', 'secret': ''})
@ -131,7 +140,7 @@ def simple_backtest(config, contour, num_results):
# loaded by freqdata/optimize/__init__.py::load_data() # loaded by freqdata/optimize/__init__.py::load_data()
def test_backtest2(default_conf, mocker): def test_backtest2(default_conf, mocker, default_strategy):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH']) data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
data = trim_dictlist(data, -200) data = trim_dictlist(data, -200)
@ -142,7 +151,7 @@ def test_backtest2(default_conf, mocker):
assert not results.empty assert not results.empty
def test_processed(default_conf, mocker): def test_processed(default_conf, mocker, default_strategy):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
dict_of_tickerrows = load_data_test('raise') dict_of_tickerrows = load_data_test('raise')
dataframes = optimize.preprocess(dict_of_tickerrows) dataframes = optimize.preprocess(dict_of_tickerrows)
@ -154,7 +163,7 @@ def test_processed(default_conf, mocker):
assert col in cols assert col in cols
def test_backtest_pricecontours(default_conf, mocker): def test_backtest_pricecontours(default_conf, mocker, default_strategy):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
tests = [['raise', 17], ['lower', 0], ['sine', 17]] tests = [['raise', 17], ['lower', 0], ['sine', 17]]
for [contour, numres] in tests: for [contour, numres] in tests:

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

@ -219,9 +219,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker) get_ticker=ticker)
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
init(default_conf, create_engine('sqlite://')) init(default_conf, create_engine('sqlite://'))
# Create some test data # Create some test data
@ -239,7 +237,9 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
_forcesell(bot=MagicMock(), update=update) _forcesell(bot=MagicMock(), update=update)
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0]
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0] assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
@ -256,9 +256,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker) get_ticker=ticker)
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
init(default_conf, create_engine('sqlite://')) init(default_conf, create_engine('sqlite://'))
# Create some test data # Create some test data
@ -276,7 +274,9 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m
_forcesell(bot=MagicMock(), update=update) _forcesell(bot=MagicMock(), update=update)
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0] assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0]
@ -317,9 +317,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker):
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker) get_ticker=ticker)
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
init(default_conf, create_engine('sqlite://')) init(default_conf, create_engine('sqlite://'))
# Create some test data # Create some test data

View File

@ -0,0 +1,36 @@
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')
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

View File

@ -0,0 +1,141 @@
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')
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
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
@ -72,3 +79,16 @@ def test_get_signal_handles_exceptions(mocker):
side_effect=Exception('invalid ticker history ')) side_effect=Exception('invalid ticker history '))
assert get_signal('BTC-ETH', 5) == (False, False) assert get_signal('BTC-ETH', 5) == (False, False)
def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv):
columns = ['close', 'high', 'low', 'open', 'date', 'volume']
# Test file with BV data
dataframe = parse_ticker_dataframe(ticker_history)
assert dataframe.columns.tolist() == columns
# Test file without BV data
dataframe = parse_ticker_dataframe(ticker_history_without_bv)
assert dataframe.columns.tolist() == columns

View File

@ -116,9 +116,9 @@ def test_fiat_convert_get_price(mocker):
assert fiat_convert._pairs[0]._expiration is not expiration assert fiat_convert._pairs[0]._expiration is not expiration
def test_fiat_convert_without_network(mocker): def test_fiat_convert_without_network():
pymarketcap = MagicMock(side_effect=ImportError('Oh boy, you have no network!')) # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
mocker.patch('freqtrade.fiat_convert.Pymarketcap', pymarketcap) CryptoToFiatConverter._coinmarketcap = None
fiat_convert = CryptoToFiatConverter() fiat_convert = CryptoToFiatConverter()
assert fiat_convert._coinmarketcap is None assert fiat_convert._coinmarketcap is None

View File

@ -525,9 +525,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker):
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker) get_ticker=ticker)
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
init(default_conf, create_engine('sqlite://')) init(default_conf, create_engine('sqlite://'))
# Create some test data # Create some test data
@ -544,7 +542,10 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker):
execute_sell(trade=trade, limit=ticker_sell_up()['bid']) execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert 'Profit' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0]
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0] assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
@ -562,9 +563,7 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker):
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker) get_ticker=ticker)
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
init(default_conf, create_engine('sqlite://')) init(default_conf, create_engine('sqlite://'))
# Create some test data # Create some test data
@ -581,7 +580,9 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker):
execute_sell(trade=trade, limit=ticker_sell_down()['bid']) execute_sell(trade=trade, limit=ticker_sell_down()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0] assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0]
@ -611,10 +612,9 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, ticker_sell_d
execute_sell(trade=trade, limit=ticker_sell_down()['bid']) execute_sell(trade=trade, limit=ticker_sell_down()['bid'])
print(rpc_mock.call_args_list[-1][0][0])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
@ -644,7 +644,9 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up,
execute_sell(trade=trade, limit=ticker_sell_up()['bid']) execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0]
assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0] assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0]
assert 'USD' not in rpc_mock.call_args_list[-1][0][0] assert 'USD' not in rpc_mock.call_args_list[-1][0][0]

View File

@ -4,7 +4,7 @@ import os
import pytest import pytest
from freqtrade.exchange import Exchanges from freqtrade.exchange import Exchanges
from freqtrade.persistence import Trade, init from freqtrade.persistence import Trade, init, clean_dry_run_db
def test_init_create_session(default_conf, mocker): def test_init_create_session(default_conf, mocker):
@ -310,3 +310,50 @@ def test_calc_profit_percent(limit_buy_order, limit_sell_order):
# Test with a custom fee rate on the close trade # Test with a custom fee rate on the close trade
assert trade.calc_profit_percent(fee=0.003) == 0.0614782 assert trade.calc_profit_percent(fee=0.003) == 0.0614782
def test_clean_dry_run_db(default_conf, mocker):
init(default_conf)
# Simulate dry_run entries
trade = Trade(
pair='BTC_ETH',
stake_amount=0.001,
amount=123.0,
fee=0.0025,
open_rate=0.123,
exchange='BITTREX',
open_order_id='dry_run_buy_12345'
)
Trade.session.add(trade)
trade = Trade(
pair='BTC_ETC',
stake_amount=0.001,
amount=123.0,
fee=0.0025,
open_rate=0.123,
exchange='BITTREX',
open_order_id='dry_run_sell_12345'
)
Trade.session.add(trade)
# Simulate prod entry
trade = Trade(
pair='BTC_ETC',
stake_amount=0.001,
amount=123.0,
fee=0.0025,
open_rate=0.123,
exchange='BITTREX',
open_order_id='prod_buy_12345'
)
Trade.session.add(trade)
# We have 3 entries: 2 dry_run, 1 prod
assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 3
clean_dry_run_db()
# We have now only the prod
assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 1

View File

@ -19,7 +19,7 @@ hyperopt==0.1
# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325 # do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325
networkx==1.11 networkx==1.11
tabulate==0.8.2 tabulate==0.8.2
pymarketcap==3.3.148 pymarketcap==3.3.150
# Required for plotting data # Required for plotting data
#matplotlib==2.1.0 #matplotlib==2.1.0

View File

@ -3,14 +3,23 @@
import sys import sys
import logging import logging
import argparse import argparse
import matplotlib import matplotlib
# matplotlib.use("Qt5Agg")
import matplotlib.dates as mdates import matplotlib.dates as mdates
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade import exchange, analyze from freqtrade import exchange, analyze
from freqtrade.misc import common_args_parser
from freqtrade.strategy.strategy import Strategy
import freqtrade.misc as misc import freqtrade.misc as misc
import freqtrade.optimize as optimize import freqtrade.optimize as optimize
import freqtrade.analyze as analyze import freqtrade.analyze as analyze
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -21,7 +30,7 @@ def plot_parse_args(args):
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
@ -31,35 +40,40 @@ def plot_analyzed_dataframe(args):
pairs = [pair] pairs = [pair]
timerange = misc.parse_timerange(args.timerange) timerange = misc.parse_timerange(args.timerange)
# Init strategy
strategy = Strategy()
strategy.init({'strategy': args.strategy})
tick_interval = strategy.ticker_interval
tickers = {} tickers = {}
if args.live: if args.live:
logger.info('Downloading pair.') logger.info('Downloading pair.')
# Init Bittrex to use public API
exchange._API = exchange.Bittrex({'key': '', 'secret': ''}) exchange._API = exchange.Bittrex({'key': '', 'secret': ''})
tickers[pair] = exchange.get_ticker_history(pair, args.ticker_interval) tickers[pair] = exchange.get_ticker_history(pair, tick_interval)
else: else:
tickers = optimize.load_data(args.datadir, pairs=pairs, tickers = optimize.load_data(args.datadir, pairs=pairs,
ticker_interval=args.ticker_interval, ticker_interval=tick_interval,
refresh_pairs=False, refresh_pairs=False,
timerange=timerange) timerange=timerange)
dataframes = optimize.tickerdata_to_dataframe(tickers) dataframes = optimize.tickerdata_to_dataframe(tickers)
dataframe = dataframes[pair] dataframe = dataframes[pair]
dataframe = analyze.populate_buy_trend(dataframe) dataframe = analyze.populate_buy_trend(dataframe)
dataframe = analyze.populate_sell_trend(dataframe) dataframe = analyze.populate_sell_trend(dataframe)
dates = misc.datesarray_to_datetimearray(dataframe['date']) dates = misc.datesarray_to_datetimearray(dataframe['date'])
dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close']
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)
fig.suptitle(pair + " " + str(args.ticker_interval), fontsize=14, fontweight='bold') fig.suptitle(pair + " " + str(tick_interval), fontsize=14, fontweight='bold')
ax1.plot(dates, dataframe['close'], label='close') ax1.plot(dates, dataframe['close'], label='close')
# ax1.plot(dates, dataframe['sell'], 'ro', label='sell') # ax1.plot(dates, dataframe['sell'], 'ro', label='sell')
ax1.plot(dates, dataframe['sma'], '--', label='SMA') ax1.plot(dates, dataframe['sma'], '--', label='SMA')
ax1.plot(dates, dataframe['tema'], ':', label='TEMA') ax1.plot(dates, dataframe['tema'], ':', label='TEMA')
ax1.plot(dates, dataframe['blower'], '-.', label='BB low') ax1.plot(dates, dataframe['blower'], '-.', label='BB low')
ax1.plot(dates, dataframe['buy_price'], 'bo', label='buy') ax1.plot(dates, dataframe['close'] * dataframe['buy'], 'bo', label='buy')
ax1.plot(dates, dataframe['close'] * dataframe['sell'], 'ro', label='sell')
ax1.legend() ax1.legend()
ax2.plot(dates, dataframe['adx'], label='ADX') ax2.plot(dates, dataframe['adx'], label='ADX')
@ -81,7 +95,6 @@ def plot_analyzed_dataframe(args):
plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False) plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False)
plt.show() plt.show()
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,7 +1,6 @@
#!/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 matplotlib.dates as mdates import matplotlib.dates as mdates
@ -10,6 +9,7 @@ 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 import freqtrade.exchange as exchange
from freqtrade.strategy.strategy import Strategy
def plot_parse_args(args): def plot_parse_args(args):
@ -44,7 +44,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
@ -67,7 +67,14 @@ 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))
@ -75,7 +82,7 @@ def plot_profit(args) -> None:
timerange = misc.parse_timerange(args.timerange) timerange = misc.parse_timerange(args.timerange)
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,
timerange=timerange) timerange=timerange)
dataframes = optimize.preprocess(tickers) dataframes = optimize.preprocess(tickers)
@ -96,7 +103,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

0
user_data/data/.gitkeep Normal file
View File

View File

View File

@ -0,0 +1,246 @@
# --- Do not remove these libs ---
from freqtrade.strategy.interface import IStrategy
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
# ------------------------------------
# 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['bb_middleband']) &
(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['bb_middleband']) &
(dataframe['tema'] < dataframe['tema'].shift(1))
),
'sell'] = 1
return dataframe