Merge branch 'plot_hyperopt_stats' into opt-ask-force-new-points

This commit is contained in:
Italo 2022-03-30 00:30:33 +01:00 committed by GitHub
commit 4eb9cc6e8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 184 additions and 169 deletions

View File

@ -31,14 +31,14 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Cache_dependencies
uses: actions/cache@v2
uses: actions/cache@v3
id: cache
with:
path: ~/dependencies/
key: ${{ runner.os }}-dependencies
- name: pip cache (linux)
uses: actions/cache@v2
uses: actions/cache@v3
if: runner.os == 'Linux'
with:
path: ~/.cache/pip
@ -126,14 +126,14 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Cache_dependencies
uses: actions/cache@v2
uses: actions/cache@v3
id: cache
with:
path: ~/dependencies/
key: ${{ runner.os }}-dependencies
- name: pip cache (macOS)
uses: actions/cache@v2
uses: actions/cache@v3
if: runner.os == 'macOS'
with:
path: ~/Library/Caches/pip
@ -218,7 +218,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Pip cache (Windows)
uses: actions/cache@preview
uses: actions/cache@v3
with:
path: ~\AppData\Local\pip\Cache
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip

View File

@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2.4.3
uses: peter-evans/dockerhub-description@v3
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

View File

@ -51,9 +51,9 @@ When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Fre
#### Buy price without Orderbook enabled
The following section uses `side` as the configured `bid_strategy.price_side`.
The following section uses `side` as the configured `bid_strategy.price_side` (defaults to `"bid"`).
When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price.
When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price based on `bid_strategy.ask_last_balance`..
The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price.
@ -88,9 +88,9 @@ When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Fr
#### Sell price without Orderbook enabled
When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price.
The following section uses `side` as the configured `ask_strategy.price_side` (defaults to `"ask"`).
When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price.
When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price based on `ask_strategy.bid_last_balance`.
The `ask_strategy.bid_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price.

View File

@ -1,4 +1,5 @@
mkdocs==1.2.3
mkdocs-material==8.2.5
mkdocs==1.3.0
mkdocs-material==8.2.8
mdx_truly_sane_lists==1.2
pymdown-extensions==9.2
pymdown-extensions==9.3
jinja2==3.1.1

View File

@ -146,7 +146,7 @@ def version(self) -> str:
The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched:
``` python
``` python title="user_data/strategies/myawesomestrategy.py"
class MyAwesomeStrategy(IStrategy):
...
stoploss = 0.13
@ -155,6 +155,10 @@ class MyAwesomeStrategy(IStrategy):
# should be in any custom strategy...
...
```
``` python title="user_data/strategies/MyAwesomeStrategy2.py"
from myawesomestrategy import MyAwesomeStrategy
class MyAwesomeStrategy2(MyAwesomeStrategy):
# Override something
stoploss = 0.08
@ -163,16 +167,7 @@ class MyAwesomeStrategy2(MyAwesomeStrategy):
Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need.
!!! Note "Parent-strategy in different files"
If you have the parent-strategy in a different file, you'll need to add the following to the top of your "child"-file to ensure proper loading, otherwise freqtrade may not be able to load the parent strategy correctly.
``` python
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent))
from myawesomestrategy import MyAwesomeStrategy
```
While keeping the subclass in the same file is technically possible, it can lead to some problems with hyperopt parameter files, we therefore recommend to use separate strategy files, and import the parent strategy as shown above.
## Embedding Strategies

View File

@ -1,27 +1,14 @@
""" Freqtrade bot """
__version__ = 'develop'
if __version__ == 'develop':
if 'dev' in __version__:
try:
import subprocess
__version__ = 'develop-' + subprocess.check_output(
__version__ = __version__ + '-' + subprocess.check_output(
['git', 'log', '--format="%h"', '-n 1'],
stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"')
# from datetime import datetime
# last_release = subprocess.check_output(
# ['git', 'tag']
# ).decode('utf-8').split()[-1].split(".")
# # Releases are in the format "2020.1" - we increment the latest version for dev.
# prefix = f"{last_release[0]}.{int(last_release[1]) + 1}"
# dev_version = int(datetime.now().timestamp() // 1000)
# __version__ = f"{prefix}.dev{dev_version}"
# subprocess.check_output(
# ['git', 'log', '--format="%h"', '-n 1'],
# stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"')
except Exception: # pragma: no cover
# git not available, ignore
try:

View File

@ -32,20 +32,24 @@ from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4
from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer
from freqtrade.optimize.optimize_reports import generate_strategy_stats
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
import matplotlib.pyplot as plt
import numpy as np
import random
# Suppress scikit-learn FutureWarnings from skopt
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=FutureWarning)
from skopt import Optimizer
from skopt.space import Dimension
from sklearn.model_selection import cross_val_score
from skopt.plots import plot_convergence, plot_regret, plot_evaluations, plot_objective
progressbar.streams.wrap_stderr()
progressbar.streams.wrap_stdout()
logger = logging.getLogger(__name__)
INITIAL_POINTS = 30
INITIAL_POINTS = 32
# Keep no more than SKOPT_MODEL_QUEUE_SIZE models
# in the skopt model queue, to optimize memory consumption
@ -409,6 +413,35 @@ class Hyperopt:
f'({(self.max_date - self.min_date).days} days)..')
# Store non-trimmed data - will be trimmed after signal generation.
dump(preprocessed, self.data_pickle_file)
def get_asked_points(self, n_points: int) -> List[List[Any]]:
'''
Enforce points returned from `self.opt.ask` have not been already evaluated
Steps:
1. Try to get points using `self.opt.ask` first
2. Discard the points that have already been evaluated
3. Retry using `self.opt.ask` up to 3 times
4. If still some points are missing in respect to `n_points`, random sample some points
5. Repeat until at least `n_points` points in the `asked_non_tried` list
6. Return a list with length truncated at `n_points`
'''
i = 0
asked_non_tried: List[List[Any]] = []
while i < 100 and len(asked_non_tried) < n_points:
if i < 3:
self.opt.cache_ = {}
asked = self.opt.ask(n_points=n_points * 5)
else:
asked = self.opt.space.rvs(n_samples=n_points * 5)
asked_non_tried += [x for x in asked
if x not in self.opt.Xi
and x not in asked_non_tried]
i += 1
if asked_non_tried:
return asked_non_tried[:min(len(asked_non_tried), n_points)]
else:
return self.opt.ask(n_points=n_points)
def get_asked_points(self, n_points: int) -> Tuple[List[List[Any]], List[bool]]:
'''
@ -515,7 +548,13 @@ class Hyperopt:
asked, is_random = self.get_asked_points(n_points=current_jobs)
f_val = self.run_optimizer_parallel(parallel, asked, i)
self.opt.tell(asked, [v['loss'] for v in f_val])
res = self.opt.tell(asked, [v['loss'] for v in f_val])
self.plot_optimizer(res, path='user_data/scripts', convergence=False, regret=False, r2=False, objective=True, jobs=jobs)
if res.models and hasattr(res.models[-1], "kernel_"):
print(f'kernel: {res.models[-1].kernel_}')
print(datetime.now())
# Calculate progressbar outputs
for j, val in enumerate(f_val):
@ -561,3 +600,47 @@ class Hyperopt:
# This is printed when Ctrl+C is pressed quickly, before first epochs have
# a chance to be evaluated.
print("No epochs evaluated yet, no best result.")
def plot_r2(self, res, ax, jobs):
if len(res.x_iters) < 10:
return
if not hasattr(self, 'r2_list'):
self.r2_list = []
model = res.models[-1]
model.criterion = 'squared_error'
r2 = cross_val_score(model, X=res.x_iters, y=res.func_vals, scoring='r2', cv=5, n_jobs=jobs).mean()
r2 = r2 if r2 > -5 else -5
self.r2_list.append(r2)
ax.plot(range(INITIAL_POINTS, INITIAL_POINTS + jobs * len(self.r2_list), jobs), self.r2_list, label='R2', marker=".", markersize=12, lw=2)
def plot_optimizer(self, res, path, jobs, convergence=True, regret=True, evaluations=True, objective=True, r2=True):
path = Path(path)
if convergence:
ax = plot_convergence(res)
ax.flatten()[0].figure.savefig(path / 'convergence.png')
if regret:
ax = plot_regret(res)
ax.flatten()[0].figure.savefig(path / 'regret.png')
if evaluations:
# print('evaluations')
ax = plot_evaluations(res)
ax.flatten()[0].figure.savefig(path / 'evaluations.png')
if objective and res.models:
# print('objective')
ax = plot_objective(res, sample_source='result', n_samples=50, n_points=10)
ax.flatten()[0].figure.savefig(path / 'objective.png')
if r2 and res.models:
fig, ax = plt.subplots()
ax.set_ylabel('R2')
ax.set_xlabel('Epoch')
ax.set_title('R2')
ax = self.plot_r2(res, ax, jobs)
fig.savefig(path / 'r2.png')

View File

@ -6,6 +6,7 @@ This module load custom objects
import importlib.util
import inspect
import logging
import sys
from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union
@ -15,6 +16,22 @@ from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
class PathModifier:
def __init__(self, path: Path):
self.path = path
def __enter__(self):
"""Inject path to allow importing with relative imports."""
sys.path.insert(0, str(self.path))
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Undo insertion of local path."""
str_path = str(self.path)
if str_path in sys.path:
sys.path.remove(str_path)
class IResolver:
"""
This class contains all the logic to load custom classes
@ -57,27 +74,32 @@ class IResolver:
# Generate spec based on absolute path
# Pass object_name as first argument to have logging print a reasonable name.
spec = importlib.util.spec_from_file_location(object_name or "", str(module_path))
if not spec:
return iter([None])
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err:
# Catch errors in case a specific module is not installed
logger.warning(f"Could not import {module_path} due to '{err}'")
if enum_failed:
with PathModifier(module_path.parent):
module_name = module_path.stem or ""
spec = importlib.util.spec_from_file_location(module_name, str(module_path))
if not spec:
return iter([None])
valid_objects_gen = (
(obj, inspect.getsource(module)) for
name, obj in inspect.getmembers(
module, inspect.isclass) if ((object_name is None or object_name == name)
and issubclass(obj, cls.object_type)
and obj is not cls.object_type)
)
return valid_objects_gen
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err:
# Catch errors in case a specific module is not installed
logger.warning(f"Could not import {module_path} due to '{err}'")
if enum_failed:
return iter([None])
valid_objects_gen = (
(obj, inspect.getsource(module)) for
name, obj in inspect.getmembers(
module, inspect.isclass) if ((object_name is None or object_name == name)
and issubclass(obj, cls.object_type)
and obj is not cls.object_type
and obj.__module__ == module_name
)
)
# The __module__ check ensures we only use strategies that are defined in this folder.
return valid_objects_gen
@classmethod
def _search_object(cls, directory: Path, *, object_name: str, add_source: bool = False

View File

@ -6,9 +6,9 @@
coveralls==3.3.1
flake8==4.0.1
flake8-tidy-imports==4.6.0
mypy==0.940
pytest==7.1.0
pytest-asyncio==0.18.2
mypy==0.942
pytest==7.1.1
pytest-asyncio==0.18.3
pytest-cov==3.0.0
pytest-mock==3.7.0
pytest-random-order==1.0.4
@ -22,8 +22,8 @@ nbconvert==6.4.4
# mypy types
types-cachetools==5.0.0
types-filelock==3.2.5
types-requests==2.27.12
types-tabulate==0.8.5
types-requests==2.27.15
types-tabulate==0.8.6
# Extensions to datetime library
types-python-dateutil==2.8.9
types-python-dateutil==2.8.10

View File

@ -8,3 +8,4 @@ scikit-optimize==0.9.0
filelock==3.6.0
joblib==1.1.0
progressbar2==4.0.0
matplotlib

View File

@ -2,22 +2,22 @@ numpy==1.22.3
pandas==1.4.1
pandas-ta==0.3.14b
ccxt==1.76.5
ccxt==1.77.36
# Pin cryptography for now due to rust build errors with piwheels
cryptography==36.0.1
cryptography==36.0.2
aiohttp==3.8.1
SQLAlchemy==1.4.32
python-telegram-bot==13.11
arrow==1.2.2
cachetools==4.2.2
requests==2.27.1
urllib3==1.26.8
urllib3==1.26.9
jsonschema==4.4.0
TA-Lib==0.4.24
technical==1.3.0
tabulate==0.8.9
pycoingecko==2.2.0
jinja2==3.0.3
jinja2==3.1.1
tables==3.7.0
blosc==1.10.6

View File

@ -1019,8 +1019,8 @@ def limit_buy_order_open():
'type': 'limit',
'side': 'buy',
'symbol': 'mocked',
'timestamp': arrow.utcnow().int_timestamp * 1000,
'datetime': arrow.utcnow().isoformat(),
'timestamp': arrow.utcnow().int_timestamp,
'price': 0.00001099,
'amount': 90.99181073,
'filled': 0.0,
@ -1046,6 +1046,7 @@ def market_buy_order():
'type': 'market',
'side': 'buy',
'symbol': 'mocked',
'timestamp': arrow.utcnow().int_timestamp * 1000,
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004099,
'amount': 91.99181073,
@ -1062,6 +1063,7 @@ def market_sell_order():
'type': 'market',
'side': 'sell',
'symbol': 'mocked',
'timestamp': arrow.utcnow().int_timestamp * 1000,
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004173,
'amount': 91.99181073,
@ -1078,7 +1080,8 @@ def limit_buy_order_old():
'type': 'limit',
'side': 'buy',
'symbol': 'mocked',
'datetime': str(arrow.utcnow().shift(minutes=-601).datetime),
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000,
'price': 0.00001099,
'amount': 90.99181073,
'filled': 0.0,
@ -1094,6 +1097,7 @@ def limit_sell_order_old():
'type': 'limit',
'side': 'sell',
'symbol': 'ETH/BTC',
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000,
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
'price': 0.00001099,
'amount': 90.99181073,
@ -1110,6 +1114,7 @@ def limit_buy_order_old_partial():
'type': 'limit',
'side': 'buy',
'symbol': 'ETH/BTC',
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000,
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
'price': 0.00001099,
'amount': 90.99181073,
@ -1139,7 +1144,7 @@ def limit_buy_order_canceled_empty(request):
'info': {},
'id': '1234512345',
'clientOrderId': None,
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp,
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000,
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
'lastTradeTimestamp': None,
'symbol': 'LTC/USDT',
@ -1160,7 +1165,7 @@ def limit_buy_order_canceled_empty(request):
'info': {},
'id': 'AZNPFF-4AC4N-7MKTAT',
'clientOrderId': None,
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp,
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000,
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
'lastTradeTimestamp': None,
'status': 'canceled',
@ -1181,7 +1186,7 @@ def limit_buy_order_canceled_empty(request):
'info': {},
'id': '1234512345',
'clientOrderId': 'alb1234123',
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp,
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000,
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
'lastTradeTimestamp': None,
'symbol': 'LTC/USDT',
@ -1202,7 +1207,7 @@ def limit_buy_order_canceled_empty(request):
'info': {},
'id': '1234512345',
'clientOrderId': 'alb1234123',
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp,
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000,
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
'lastTradeTimestamp': None,
'symbol': 'LTC/USDT',
@ -1228,7 +1233,7 @@ def limit_sell_order_open():
'side': 'sell',
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'timestamp': arrow.utcnow().int_timestamp,
'timestamp': arrow.utcnow().int_timestamp * 1000,
'price': 0.00001173,
'amount': 90.99181073,
'filled': 0.0,
@ -1394,7 +1399,7 @@ def tickers():
'BLK/BTC': {
'symbol': 'BLK/BTC',
'timestamp': 1522014806072,
'datetime': '2018-03-25T21:53:26.720Z',
'datetime': '2018-03-25T21:53:26.072Z',
'high': 0.007745,
'low': 0.007512,
'bid': 0.007729,
@ -1890,7 +1895,8 @@ def buy_order_fee():
'type': 'limit',
'side': 'buy',
'symbol': 'mocked',
'datetime': str(arrow.utcnow().shift(minutes=-601).datetime),
'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000,
'datetime': arrow.utcnow().shift(minutes=-601).isoformat(),
'price': 0.245441,
'amount': 8.0,
'cost': 1.963528,
@ -2199,7 +2205,7 @@ def limit_buy_order_usdt_open():
'side': 'buy',
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'timestamp': arrow.utcnow().int_timestamp,
'timestamp': arrow.utcnow().int_timestamp * 1000,
'price': 2.00,
'amount': 30.0,
'filled': 0.0,
@ -2226,7 +2232,7 @@ def limit_sell_order_usdt_open():
'side': 'sell',
'symbol': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'timestamp': arrow.utcnow().int_timestamp,
'timestamp': arrow.utcnow().int_timestamp * 1000,
'price': 2.20,
'amount': 30.0,
'filled': 0.0,
@ -2251,6 +2257,7 @@ def market_buy_order_usdt():
'type': 'market',
'side': 'buy',
'symbol': 'mocked',
'timestamp': arrow.utcnow().int_timestamp * 1000,
'datetime': arrow.utcnow().isoformat(),
'price': 2.00,
'amount': 30.0,
@ -2307,6 +2314,7 @@ def market_sell_order_usdt():
'type': 'market',
'side': 'sell',
'symbol': 'mocked',
'timestamp': arrow.utcnow().int_timestamp * 1000,
'datetime': arrow.utcnow().isoformat(),
'price': 2.20,
'amount': 30.0,

View File

@ -1098,7 +1098,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
order = exchange.create_order(
pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=200)
pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=rate)
assert 'id' in order
assert 'info' in order

View File

@ -1,14 +1,13 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
import talib.abstract as ta
from pandas import DataFrame
from strategy_test_v2 import StrategyTestV2
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy,
RealParameter)
from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter
class HyperoptableStrategy(IStrategy):
class HyperoptableStrategy(StrategyTestV2):
"""
Default Strategy provided by freqtrade bot.
Please do not modify this strategy, it's intended for internal use only.
@ -16,38 +15,6 @@ class HyperoptableStrategy(IStrategy):
or strategy repository https://github.com/freqtrade/freqtrade-strategies
for samples and inspiration.
"""
INTERFACE_VERSION = 2
# 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
timeframe = '5m'
# Optional order type mapping
order_types = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
}
# Number of candles the strategy requires before producing valid signals
startup_candle_count: int = 20
# Optional time in force for orders
order_time_in_force = {
'buy': 'gtc',
'sell': 'gtc',
}
buy_params = {
'buy_rsi': 35,
@ -91,55 +58,6 @@ class HyperoptableStrategy(IStrategy):
"""
return []
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> 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.
:param dataframe: Dataframe with data from the exchange
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
# Momentum Indicator
# ------------------------------------
# ADX
dataframe['adx'] = ta.ADX(dataframe)
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# Minus Directional Indicator / Movement
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# Plus Directional Indicator / Movement
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# Stoch fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
# EMA - Exponential Moving Average
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
return dataframe
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe

View File

@ -7,7 +7,7 @@ from pandas import DataFrame
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy import IStrategy
class StrategyTestV2(IStrategy):