Merge branch 'develop' into plot_profit
This commit is contained in:
commit
fe2f779c47
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,6 +5,8 @@ config.json
|
|||||||
*.sqlite
|
*.sqlite
|
||||||
.hyperopt
|
.hyperopt
|
||||||
logfile.txt
|
logfile.txt
|
||||||
|
hyperopt_trials.pickle
|
||||||
|
user_data/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
@ -85,5 +87,3 @@ target/
|
|||||||
.venv
|
.venv
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
hyperopt_trials.pickle
|
|
||||||
|
@ -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,
|
||||||
|
@ -3,21 +3,55 @@ This page explains where to customize your strategies, and add new
|
|||||||
indicators.
|
indicators.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Change your strategy](#change-your-strategy)
|
- [Install a custom strategy file](#install-a-custom-strategy-file)
|
||||||
|
- [Customize your strategy](#change-your-strategy)
|
||||||
- [Add more Indicator](#add-more-indicator)
|
- [Add more Indicator](#add-more-indicator)
|
||||||
|
- [Where is the default strategy](#where-is-the-default-strategy)
|
||||||
|
|
||||||
|
Since the version `0.16.0` the bot allows using custom strategy file.
|
||||||
|
|
||||||
|
## Install a custom strategy file
|
||||||
|
This is very simple. Copy paste your strategy file into the folder
|
||||||
|
`user_data/strategies`.
|
||||||
|
|
||||||
|
Let assume you have a strategy file `awesome-strategy.py`:
|
||||||
|
1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py`
|
||||||
|
2. Start the bot with the param `--strategy awesome-strategy` (the parameter is the name of the file without '.py')
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py --strategy awesome_strategy
|
||||||
|
```
|
||||||
|
|
||||||
## Change your strategy
|
## Change your strategy
|
||||||
The bot is using buy and sell strategies to buy and sell your trades.
|
The bot includes a default strategy file. However, we recommend you to
|
||||||
Both are customizable.
|
use your own file to not have to lose your parameters everytime the default
|
||||||
|
strategy file will be updated on Github. Put your custom strategy file
|
||||||
|
into the folder `user_data/strategies`.
|
||||||
|
|
||||||
### Buy strategy
|
A strategy file contains all the information needed to build a good strategy:
|
||||||
The default buy strategy is located in the file
|
- Buy strategy rules
|
||||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73-L92).
|
- Sell strategy rules
|
||||||
Edit the function `populate_buy_trend()` to update your buy strategy.
|
- Minimal ROI recommended
|
||||||
|
- Stoploss recommended
|
||||||
|
- Hyperopt parameter
|
||||||
|
|
||||||
Sample:
|
The bot also include a sample strategy you can update: `user_data/strategies/test_strategy.py`.
|
||||||
|
You can test it with the parameter: `--strategy test_strategy`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py --strategy awesome_strategy
|
||||||
|
```
|
||||||
|
|
||||||
|
**For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
|
||||||
|
file as reference.**
|
||||||
|
|
||||||
|
### Buy strategy
|
||||||
|
Edit the method `populate_buy_trend()` into your strategy file to
|
||||||
|
update your buy strategy.
|
||||||
|
|
||||||
|
Sample from `user_data/strategies/test_strategy.py`:
|
||||||
```python
|
```python
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the buy signal for the given dataframe
|
Based on TA indicators, populates the buy signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
@ -25,14 +59,9 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
|||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe['rsi'] < 35) &
|
|
||||||
(dataframe['fastd'] < 35) &
|
|
||||||
(dataframe['adx'] > 30) &
|
(dataframe['adx'] > 30) &
|
||||||
(dataframe['plus_di'] > 0.5)
|
(dataframe['tema'] <= dataframe['blower']) &
|
||||||
) |
|
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||||
(
|
|
||||||
(dataframe['adx'] > 65) &
|
|
||||||
(dataframe['plus_di'] > 0.5)
|
|
||||||
),
|
),
|
||||||
'buy'] = 1
|
'buy'] = 1
|
||||||
|
|
||||||
@ -40,41 +69,31 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Sell strategy
|
### Sell strategy
|
||||||
The default buy strategy is located in the file
|
Edit the method `populate_sell_trend()` into your strategy file to
|
||||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115)
|
update your sell strategy.
|
||||||
Edit the function `populate_sell_trend()` to update your buy strategy.
|
|
||||||
|
|
||||||
Sample:
|
Sample from `user_data/strategies/test_strategy.py`:
|
||||||
```python
|
```python
|
||||||
def populate_sell_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Based on TA indicators, populates the sell signal for the given dataframe
|
Based on TA indicators, populates the sell signal for the given dataframe
|
||||||
:param dataframe: DataFrame
|
:param dataframe: DataFrame
|
||||||
:return: DataFrame with buy column
|
:return: DataFrame with buy column
|
||||||
"""
|
"""
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
|
||||||
(
|
|
||||||
(crossed_above(dataframe['rsi'], 70)) |
|
|
||||||
(crossed_above(dataframe['fastd'], 70))
|
|
||||||
) &
|
|
||||||
(dataframe['adx'] > 10) &
|
|
||||||
(dataframe['minus_di'] > 0)
|
|
||||||
) |
|
|
||||||
(
|
(
|
||||||
(dataframe['adx'] > 70) &
|
(dataframe['adx'] > 70) &
|
||||||
(dataframe['minus_di'] > 0.5)
|
(dataframe['tema'] > dataframe['blower']) &
|
||||||
|
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||||
),
|
),
|
||||||
'sell'] = 1
|
'sell'] = 1
|
||||||
return dataframe
|
return dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
## Add more Indicator
|
## Add more Indicator
|
||||||
As you have seen, buy and sell strategies need indicators. You can see
|
As you have seen, buy and sell strategies need indicators. You can add
|
||||||
the indicators in the file
|
more indicators by extending the list contained in
|
||||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115).
|
the method `populate_indicators()` from your strategy file.
|
||||||
Of course you can add more indicators by extending the list contained in
|
|
||||||
the function `populate_indicators()`.
|
|
||||||
|
|
||||||
Sample:
|
Sample:
|
||||||
```python
|
```python
|
||||||
@ -111,6 +130,15 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
|
|||||||
return dataframe
|
return dataframe
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Want more indicators example?**
|
||||||
|
Look into the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
|
||||||
|
Then uncomment indicators you need.
|
||||||
|
|
||||||
|
|
||||||
|
### Where is the default strategy?
|
||||||
|
The default buy strategy is located in the file
|
||||||
|
[freqtrade/default_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/strategy/default_strategy.py).
|
||||||
|
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
Now you have a perfect strategy you probably want to backtesting it.
|
Now you have a perfect strategy you probably want to backtesting it.
|
||||||
|
@ -22,19 +22,21 @@ positional arguments:
|
|||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-c PATH, --config PATH
|
|
||||||
specify configuration file (default: config.json)
|
|
||||||
-v, --verbose be verbose
|
-v, --verbose be verbose
|
||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-dd PATH, --datadir PATH
|
-c PATH, --config PATH
|
||||||
Path is from where backtesting and hyperopt will load the
|
specify configuration file (default: config.json)
|
||||||
ticker data files (default freqdata/tests/testdata).
|
-s PATH, --strategy PATH
|
||||||
--dynamic-whitelist [INT]
|
specify strategy file (default:
|
||||||
dynamically generate and update whitelist based on 24h
|
freqtrade/strategy/default_strategy.py)
|
||||||
BaseVolume (Default 20 currencies)
|
|
||||||
--dry-run-db Force dry run to use a local DB
|
--dry-run-db Force dry run to use a local DB
|
||||||
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
"tradesv3.dry_run.sqlite" instead of memory DB. Work
|
||||||
only if dry_run is enabled.
|
only if dry_run is enabled.
|
||||||
|
-dd PATH, --datadir PATH
|
||||||
|
path to backtest data (default freqdata/tests/testdata
|
||||||
|
--dynamic-whitelist [INT]
|
||||||
|
dynamically generate and update whitelist based on 24h
|
||||||
|
BaseVolume (Default 20 currencies)
|
||||||
```
|
```
|
||||||
|
|
||||||
### How to use a different config file?
|
### How to use a different config file?
|
||||||
@ -45,6 +47,33 @@ default, the bot will load the file `./config.json`
|
|||||||
python3 ./freqtrade/main.py -c path/far/far/away/config.json
|
python3 ./freqtrade/main.py -c path/far/far/away/config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### How to use --strategy?
|
||||||
|
This parameter will allow you to load your custom strategy file. Per
|
||||||
|
default without `--strategy` or `-s` the bol will load the
|
||||||
|
`default_strategy` included with the bot (`freqtrade/strategy/default_strategy.py`).
|
||||||
|
|
||||||
|
The bot will search your strategy file into `user_data/strategies` and
|
||||||
|
`freqtrade/strategy`.
|
||||||
|
|
||||||
|
To load a strategy, simply pass the file name (without .py) in this
|
||||||
|
parameters.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
In `user_data/strategies` you have a file `my_awesome_strategy.py` to
|
||||||
|
load it:
|
||||||
|
```bash
|
||||||
|
python3 ./freqtrade/main.py --strategy my_awesome_strategy
|
||||||
|
```
|
||||||
|
|
||||||
|
If the bot does not find your strategy file, it will fallback to the
|
||||||
|
`default_strategy`.
|
||||||
|
|
||||||
|
Learn more about strategy file in [optimize your bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md).
|
||||||
|
|
||||||
|
#### How to install a strategy?
|
||||||
|
This is very simple. Copy paste your strategy file into the folder
|
||||||
|
`user_data/strategies`. And voila, the bot is ready to use it.
|
||||||
|
|
||||||
### How to use --dynamic-whitelist?
|
### How to use --dynamic-whitelist?
|
||||||
Per default `--dynamic-whitelist` will retrieve the 20 currencies based
|
Per default `--dynamic-whitelist` will retrieve the 20 currencies based
|
||||||
on BaseVolume. This value can be changed when you run the script.
|
on BaseVolume. This value can be changed when you run the script.
|
||||||
|
@ -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`)
|
||||||
|
@ -14,14 +14,13 @@ parameters with Hyperopt.
|
|||||||
|
|
||||||
## Prepare Hyperopt
|
## Prepare Hyperopt
|
||||||
Before we start digging in Hyperopt, we recommend you to take a look at
|
Before we start digging in Hyperopt, we recommend you to take a look at
|
||||||
out Hyperopt file
|
your strategy file located into [user_data/strategies/](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
|
||||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py)
|
|
||||||
|
|
||||||
### 1. Configure your Guards and Triggers
|
### 1. Configure your Guards and Triggers
|
||||||
There are two places you need to change to add a new buy strategy for
|
There are two places you need to change in your strategy file to add a
|
||||||
testing:
|
new buy strategy for testing:
|
||||||
- Inside the [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L167-L207).
|
- Inside [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L278-L294).
|
||||||
- Inside the [SPACE dict](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L47-L94).
|
- Inside [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297) known as `SPACE`.
|
||||||
|
|
||||||
There you have two different type of indicators: 1. `guards` and 2.
|
There you have two different type of indicators: 1. `guards` and 2.
|
||||||
`triggers`.
|
`triggers`.
|
||||||
@ -38,10 +37,10 @@ ADX > 10*".
|
|||||||
|
|
||||||
|
|
||||||
If you have updated the buy strategy, means change the content of
|
If you have updated the buy strategy, means change the content of
|
||||||
`populate_buy_trend()` function you have to update the `guards` and
|
`populate_buy_trend()` method you have to update the `guards` and
|
||||||
`triggers` hyperopts must used.
|
`triggers` hyperopts must used.
|
||||||
|
|
||||||
As for an example if your `populate_buy_trend()` function is:
|
As for an example if your `populate_buy_trend()` method is:
|
||||||
```python
|
```python
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
@ -56,10 +55,10 @@ Your hyperopt file must contains `guards` to find the right value for
|
|||||||
`(dataframe['adx'] > 65)` & and `(dataframe['plus_di'] > 0.5)`. That
|
`(dataframe['adx'] > 65)` & and `(dataframe['plus_di'] > 0.5)`. That
|
||||||
means you will need to enable/disable triggers.
|
means you will need to enable/disable triggers.
|
||||||
|
|
||||||
In our case the `SPACE` and `populate_buy_trend` in hyperopt.py file
|
In our case the `SPACE` and `populate_buy_trend` in your strategy file
|
||||||
will be look like:
|
will be look like:
|
||||||
```python
|
```python
|
||||||
SPACE = {
|
space = {
|
||||||
'rsi': hp.choice('rsi', [
|
'rsi': hp.choice('rsi', [
|
||||||
{'enabled': False},
|
{'enabled': False},
|
||||||
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
||||||
@ -82,7 +81,7 @@ SPACE = {
|
|||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
conditions = []
|
conditions = []
|
||||||
# GUARDS AND TRENDS
|
# GUARDS AND TRENDS
|
||||||
if params['adx']['enabled']:
|
if params['adx']['enabled']:
|
||||||
@ -111,13 +110,13 @@ cannot use your config file. It is also made on purpose to allow you
|
|||||||
testing your strategy with different configurations.
|
testing your strategy with different configurations.
|
||||||
|
|
||||||
The Hyperopt configuration is located in
|
The Hyperopt configuration is located in
|
||||||
[freqtrade/optimize/hyperopt_conf.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt_conf.py).
|
[user_data/hyperopt_conf.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/hyperopt_conf.py).
|
||||||
|
|
||||||
|
|
||||||
## Advanced notions
|
## Advanced notions
|
||||||
### Understand the Guards and Triggers
|
### Understand the Guards and Triggers
|
||||||
When you need to add the new guards and triggers to be hyperopt
|
When you need to add the new guards and triggers to be hyperopt
|
||||||
parameters, you do this by adding them into the [SPACE dict](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L47-L94).
|
parameters, you do this by adding them into the [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297).
|
||||||
|
|
||||||
If it's a trigger, you add one line to the 'trigger' choice group and that's it.
|
If it's a trigger, you add one line to the 'trigger' choice group and that's it.
|
||||||
|
|
||||||
@ -149,9 +148,8 @@ for best working algo.
|
|||||||
|
|
||||||
### Add a new Indicators
|
### Add a new Indicators
|
||||||
If you want to test an indicator that isn't used by the bot currently,
|
If you want to test an indicator that isn't used by the bot currently,
|
||||||
you need to add it to
|
you need to add it to your strategy file (example: [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py))
|
||||||
[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L40-L70)
|
inside the `populate_indicators()` method.
|
||||||
inside the `populate_indicators` function.
|
|
||||||
|
|
||||||
## Execute Hyperopt
|
## Execute Hyperopt
|
||||||
Once you have updated your hyperopt configuration you can run it.
|
Once you have updated your hyperopt configuration you can run it.
|
||||||
@ -165,8 +163,8 @@ python3 ./freqtrade/main.py -c config.json hyperopt
|
|||||||
|
|
||||||
### Execute hyperopt with different ticker-data source
|
### Execute hyperopt with different ticker-data source
|
||||||
If you would like to learn parameters using an alternate ticke-data that
|
If you would like to learn parameters using an alternate ticke-data that
|
||||||
you have on-disk, use the --datadir PATH option. Default hyperopt will
|
you have on-disk, use the `--datadir PATH` option. Default hyperopt will
|
||||||
use data from directory freqtrade/tests/testdata.
|
use data from directory `user_data/data`.
|
||||||
|
|
||||||
### Running hyperopt with smaller testset
|
### Running hyperopt with smaller testset
|
||||||
|
|
||||||
@ -270,15 +268,11 @@ customizable value.
|
|||||||
- and so on...
|
- and so on...
|
||||||
|
|
||||||
|
|
||||||
You have to look from
|
You have to look inside your strategy file into `buy_strategy_generator()`
|
||||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L170-L200)
|
method, what those values match to.
|
||||||
what those values match to.
|
|
||||||
|
|
||||||
So for example you had `adx:` with the `value: 15.0` so we would look
|
So for example you had `adx:` with the `value: 15.0` so we would look
|
||||||
at `adx`-block from
|
at `adx`-block, that translates to the following code block:
|
||||||
[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L178-L179).
|
|
||||||
That translates to the following code block to
|
|
||||||
[analyze.populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73)
|
|
||||||
```
|
```
|
||||||
(dataframe['adx'] > 15.0)
|
(dataframe['adx'] > 15.0)
|
||||||
```
|
```
|
||||||
@ -286,7 +280,7 @@ That translates to the following code block to
|
|||||||
So translating your whole hyperopt result to as the new buy-signal
|
So translating your whole hyperopt result to as the new buy-signal
|
||||||
would be the following:
|
would be the following:
|
||||||
```
|
```
|
||||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||||
dataframe.loc[
|
dataframe.loc[
|
||||||
(
|
(
|
||||||
(dataframe['adx'] > 15.0) & # adx-value
|
(dataframe['adx'] > 15.0) & # adx-value
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
""" FreqTrade bot """
|
""" FreqTrade bot """
|
||||||
__version__ = '0.15.1'
|
__version__ = '0.16.0'
|
||||||
|
|
||||||
|
|
||||||
class DependencyException(BaseException):
|
class DependencyException(BaseException):
|
||||||
|
@ -7,11 +7,10 @@ from enum import Enum
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import talib.abstract as ta
|
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
|
||||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
||||||
from freqtrade.exchange import get_ticker_history
|
from freqtrade.exchange import get_ticker_history
|
||||||
|
from freqtrade.strategy.strategy import Strategy
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -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:
|
||||||
|
@ -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):
|
||||||
try:
|
if CryptoToFiatConverter.__instance is None:
|
||||||
self._coinmarketcap = Pymarketcap()
|
CryptoToFiatConverter.__instance = object.__new__(cls)
|
||||||
except BaseException:
|
try:
|
||||||
self._coinmarketcap = None
|
CryptoToFiatConverter._coinmarketcap = Pymarketcap()
|
||||||
|
except BaseException:
|
||||||
|
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:
|
||||||
|
@ -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:
|
||||||
|
@ -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'
|
||||||
]
|
]
|
||||||
|
@ -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:
|
||||||
|
@ -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(
|
||||||
|
@ -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,69 +55,130 @@ 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:
|
||||||
'macd_below_zero': hp.choice('macd_below_zero', [
|
"""
|
||||||
{'enabled': False},
|
Adds several different TA indicators to the given DataFrame
|
||||||
{'enabled': True}
|
"""
|
||||||
]),
|
dataframe['adx'] = ta.ADX(dataframe)
|
||||||
'mfi': hp.choice('mfi', [
|
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
||||||
{'enabled': False},
|
dataframe['cci'] = ta.CCI(dataframe)
|
||||||
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
|
macd = ta.MACD(dataframe)
|
||||||
]),
|
dataframe['macd'] = macd['macd']
|
||||||
'fastd': hp.choice('fastd', [
|
dataframe['macdsignal'] = macd['macdsignal']
|
||||||
{'enabled': False},
|
dataframe['macdhist'] = macd['macdhist']
|
||||||
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)}
|
dataframe['mfi'] = ta.MFI(dataframe)
|
||||||
]),
|
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||||
'adx': hp.choice('adx', [
|
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||||
{'enabled': False},
|
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
||||||
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
|
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||||
]),
|
dataframe['roc'] = ta.ROC(dataframe)
|
||||||
'rsi': hp.choice('rsi', [
|
dataframe['rsi'] = ta.RSI(dataframe)
|
||||||
{'enabled': False},
|
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
||||||
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
rsi = 0.1 * (dataframe['rsi'] - 50)
|
||||||
]),
|
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
||||||
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
|
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
|
||||||
{'enabled': False},
|
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
|
||||||
{'enabled': True}
|
# Stoch
|
||||||
]),
|
stoch = ta.STOCH(dataframe)
|
||||||
'uptrend_short_ema': hp.choice('uptrend_short_ema', [
|
dataframe['slowd'] = stoch['slowd']
|
||||||
{'enabled': False},
|
dataframe['slowk'] = stoch['slowk']
|
||||||
{'enabled': True}
|
# Stoch fast
|
||||||
]),
|
stoch_fast = ta.STOCHF(dataframe)
|
||||||
'over_sar': hp.choice('over_sar', [
|
dataframe['fastd'] = stoch_fast['fastd']
|
||||||
{'enabled': False},
|
dataframe['fastk'] = stoch_fast['fastk']
|
||||||
{'enabled': True}
|
# Stoch RSI
|
||||||
]),
|
stoch_rsi = ta.STOCHRSI(dataframe)
|
||||||
'green_candle': hp.choice('green_candle', [
|
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
||||||
{'enabled': False},
|
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
||||||
{'enabled': True}
|
# Bollinger bands
|
||||||
]),
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||||
'uptrend_sma': hp.choice('uptrend_sma', [
|
dataframe['bb_lowerband'] = bollinger['lower']
|
||||||
{'enabled': False},
|
dataframe['bb_middleband'] = bollinger['mid']
|
||||||
{'enabled': True}
|
dataframe['bb_upperband'] = bollinger['upper']
|
||||||
]),
|
# EMA - Exponential Moving Average
|
||||||
'trigger': hp.choice('trigger', [
|
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
||||||
{'type': 'lower_bb'},
|
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
||||||
{'type': 'lower_bb_tema'},
|
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||||
{'type': 'faststoch10'},
|
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
||||||
{'type': 'ao_cross_zero'},
|
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
||||||
{'type': 'ema3_cross_ema10'},
|
# SAR Parabolic
|
||||||
{'type': 'macd_cross_signal'},
|
dataframe['sar'] = ta.SAR(dataframe)
|
||||||
{'type': 'sar_reversal'},
|
# SMA - Simple Moving Average
|
||||||
{'type': 'ht_sine'},
|
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||||
{'type': 'heiken_reversal_bull'},
|
# TEMA - Triple Exponential Moving Average
|
||||||
{'type': 'di_cross'},
|
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
]),
|
# Hilbert Transform Indicator - SineWave
|
||||||
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
|
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):
|
def save_trials(trials, trials_path=TRIALS_FILE):
|
||||||
@ -158,10 +225,145 @@ def calculate_loss(total_profit: float, trade_count: int, trade_duration: float)
|
|||||||
return trade_loss + profit_loss + duration_loss
|
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', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'mfi': hp.choice('mfi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
|
||||||
|
]),
|
||||||
|
'fastd': hp.choice('fastd', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)}
|
||||||
|
]),
|
||||||
|
'adx': hp.choice('adx', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)}
|
||||||
|
]),
|
||||||
|
'rsi': hp.choice('rsi', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)}
|
||||||
|
]),
|
||||||
|
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_short_ema': hp.choice('uptrend_short_ema', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'over_sar': hp.choice('over_sar', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'green_candle': hp.choice('green_candle', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'uptrend_sma': hp.choice('uptrend_sma', [
|
||||||
|
{'enabled': False},
|
||||||
|
{'enabled': True}
|
||||||
|
]),
|
||||||
|
'trigger': hp.choice('trigger', [
|
||||||
|
{'type': 'lower_bb'},
|
||||||
|
{'type': 'lower_bb_tema'},
|
||||||
|
{'type': 'faststoch10'},
|
||||||
|
{'type': 'ao_cross_zero'},
|
||||||
|
{'type': 'ema3_cross_ema10'},
|
||||||
|
{'type': 'macd_cross_signal'},
|
||||||
|
{'type': 'sar_reversal'},
|
||||||
|
{'type': 'ht_sine'},
|
||||||
|
{'type': 'heiken_reversal_bull'},
|
||||||
|
{'type': 'di_cross'},
|
||||||
|
]),
|
||||||
|
'stoploss': hp.uniform('stoploss', -0.5, -0.02),
|
||||||
|
}
|
||||||
|
return space
|
||||||
|
|
||||||
|
|
||||||
|
def buy_strategy_generator(params) -> None:
|
||||||
|
"""
|
||||||
|
Define the buy strategy parameters to be used by hyperopt
|
||||||
|
"""
|
||||||
|
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||||
|
conditions = []
|
||||||
|
# GUARDS AND TRENDS
|
||||||
|
if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema50'] > dataframe['ema100'])
|
||||||
|
if 'macd_below_zero' in params and params['macd_below_zero']['enabled']:
|
||||||
|
conditions.append(dataframe['macd'] < 0)
|
||||||
|
if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']:
|
||||||
|
conditions.append(dataframe['ema5'] > dataframe['ema10'])
|
||||||
|
if 'mfi' in params and params['mfi']['enabled']:
|
||||||
|
conditions.append(dataframe['mfi'] < params['mfi']['value'])
|
||||||
|
if 'fastd' in params and params['fastd']['enabled']:
|
||||||
|
conditions.append(dataframe['fastd'] < params['fastd']['value'])
|
||||||
|
if 'adx' in params and params['adx']['enabled']:
|
||||||
|
conditions.append(dataframe['adx'] > params['adx']['value'])
|
||||||
|
if 'rsi' in params and params['rsi']['enabled']:
|
||||||
|
conditions.append(dataframe['rsi'] < params['rsi']['value'])
|
||||||
|
if 'over_sar' in params and params['over_sar']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['sar'])
|
||||||
|
if 'green_candle' in params and params['green_candle']['enabled']:
|
||||||
|
conditions.append(dataframe['close'] > dataframe['open'])
|
||||||
|
if 'uptrend_sma' in params and params['uptrend_sma']['enabled']:
|
||||||
|
prevsma = dataframe['sma'].shift(1)
|
||||||
|
conditions.append(dataframe['sma'] > prevsma)
|
||||||
|
|
||||||
|
# TRIGGERS
|
||||||
|
triggers = {
|
||||||
|
'lower_bb': (
|
||||||
|
dataframe['close'] < dataframe['bb_lowerband']
|
||||||
|
),
|
||||||
|
'lower_bb_tema': (
|
||||||
|
dataframe['tema'] < dataframe['bb_lowerband']
|
||||||
|
),
|
||||||
|
'faststoch10': (qtpylib.crossed_above(
|
||||||
|
dataframe['fastd'], 10.0
|
||||||
|
)),
|
||||||
|
'ao_cross_zero': (qtpylib.crossed_above(
|
||||||
|
dataframe['ao'], 0.0
|
||||||
|
)),
|
||||||
|
'ema3_cross_ema10': (qtpylib.crossed_above(
|
||||||
|
dataframe['ema3'], dataframe['ema10']
|
||||||
|
)),
|
||||||
|
'macd_cross_signal': (qtpylib.crossed_above(
|
||||||
|
dataframe['macd'], dataframe['macdsignal']
|
||||||
|
)),
|
||||||
|
'sar_reversal': (qtpylib.crossed_above(
|
||||||
|
dataframe['close'], dataframe['sar']
|
||||||
|
)),
|
||||||
|
'ht_sine': (qtpylib.crossed_above(
|
||||||
|
dataframe['htleadsine'], dataframe['htsine']
|
||||||
|
)),
|
||||||
|
'heiken_reversal_bull': (
|
||||||
|
(qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) &
|
||||||
|
(dataframe['ha_low'] == dataframe['ha_open'])
|
||||||
|
),
|
||||||
|
'di_cross': (qtpylib.crossed_above(
|
||||||
|
dataframe['plus_di'], dataframe['minus_di']
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
conditions.append(triggers.get(params['trigger']['type']))
|
||||||
|
|
||||||
|
dataframe.loc[
|
||||||
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
return populate_buy_trend
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -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'
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
0
freqtrade/strategy/__init__.py
Normal file
0
freqtrade/strategy/__init__.py
Normal file
238
freqtrade/strategy/default_strategy.py
Normal file
238
freqtrade/strategy/default_strategy.py
Normal 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
|
44
freqtrade/strategy/interface.py
Normal file
44
freqtrade/strategy/interface.py
Normal 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
|
||||||
|
"""
|
166
freqtrade/strategy/strategy.py
Normal file
166
freqtrade/strategy/strategy.py
Normal 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)
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
@ -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:
|
||||||
|
@ -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():
|
||||||
|
@ -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
|
||||||
|
36
freqtrade/tests/strategy/test_default_strategy.py
Normal file
36
freqtrade/tests/strategy/test_default_strategy.py
Normal 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
|
141
freqtrade/tests/strategy/test_strategy.py
Normal file
141
freqtrade/tests/strategy/test_strategy.py
Normal 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
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
@ -108,7 +115,7 @@ def plot_profit(args) -> None:
|
|||||||
|
|
||||||
filename = 'backtest-result.json'
|
filename = 'backtest-result.json'
|
||||||
with open(filename) as file:
|
with open(filename) as file:
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
pg = make_profit_array(data, max_x, filter_pairs)
|
pg = make_profit_array(data, max_x, filter_pairs)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
0
user_data/data/.gitkeep
Normal file
0
user_data/data/.gitkeep
Normal file
0
user_data/strategies/__init__.py
Normal file
0
user_data/strategies/__init__.py
Normal file
246
user_data/strategies/test_strategy.py
Normal file
246
user_data/strategies/test_strategy.py
Normal 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
|
Loading…
Reference in New Issue
Block a user