From 9f171193ef2b893b7f7c9269b9a2c6b796f0d71c Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Wed, 30 Mar 2022 09:39:07 +0100 Subject: [PATCH] Revert "Merge branch 'plot_hyperopt_stats' into opt-ask-force-new-points" This reverts commit 4eb9cc6e8b7bc99b543c4acbfa6c2b07f67d54e5, reversing changes made to a3b401a762bbdda75191956ea251d097c08e7a32. --- .github/workflows/ci.yml | 10 +-- .github/workflows/docker_update_readme.yml | 2 +- docs/includes/pricing.md | 8 +- docs/requirements-docs.txt | 7 +- docs/strategy-advanced.md | 17 ++-- freqtrade/__init__.py | 17 +++- freqtrade/optimize/hyperopt.py | 89 +------------------ freqtrade/resolvers/iresolver.py | 60 ++++--------- requirements-dev.txt | 12 +-- requirements-hyperopt.txt | 1 - requirements.txt | 8 +- tests/conftest.py | 30 +++---- tests/exchange/test_exchange.py | 2 +- .../strategy/strats/hyperoptable_strategy.py | 88 +++++++++++++++++- tests/strategy/strats/strategy_test_v2.py | 2 +- 15 files changed, 169 insertions(+), 184 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8df7ab10..216a53bc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,14 +31,14 @@ jobs: python-version: ${{ matrix.python-version }} - name: Cache_dependencies - uses: actions/cache@v3 + uses: actions/cache@v2 id: cache with: path: ~/dependencies/ key: ${{ runner.os }}-dependencies - name: pip cache (linux) - uses: actions/cache@v3 + uses: actions/cache@v2 if: runner.os == 'Linux' with: path: ~/.cache/pip @@ -126,14 +126,14 @@ jobs: python-version: ${{ matrix.python-version }} - name: Cache_dependencies - uses: actions/cache@v3 + uses: actions/cache@v2 id: cache with: path: ~/dependencies/ key: ${{ runner.os }}-dependencies - name: pip cache (macOS) - uses: actions/cache@v3 + uses: actions/cache@v2 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@v3 + uses: actions/cache@preview with: path: ~\AppData\Local\pip\Cache key: ${{ matrix.os }}-${{ matrix.python-version }}-pip diff --git a/.github/workflows/docker_update_readme.yml b/.github/workflows/docker_update_readme.yml index 822533ee2..ebb773ad7 100644 --- a/.github/workflows/docker_update_readme.yml +++ b/.github/workflows/docker_update_readme.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v3 + uses: peter-evans/dockerhub-description@v2.4.3 env: DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} diff --git a/docs/includes/pricing.md b/docs/includes/pricing.md index 103df6cd3..ed8a45e68 100644 --- a/docs/includes/pricing.md +++ b/docs/includes/pricing.md @@ -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` (defaults to `"bid"`). +The following section uses `side` as the configured `bid_strategy.price_side`. -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`.. +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. 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 -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`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell 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`. +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. 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. diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 1f7db75c5..0ca0e4b63 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,5 +1,4 @@ -mkdocs==1.3.0 -mkdocs-material==8.2.8 +mkdocs==1.2.3 +mkdocs-material==8.2.5 mdx_truly_sane_lists==1.2 -pymdown-extensions==9.3 -jinja2==3.1.1 +pymdown-extensions==9.2 diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index b1f154355..3793abacf 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -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 title="user_data/strategies/myawesomestrategy.py" +``` python class MyAwesomeStrategy(IStrategy): ... stoploss = 0.13 @@ -155,10 +155,6 @@ 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 @@ -167,7 +163,16 @@ class MyAwesomeStrategy2(MyAwesomeStrategy): Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need. -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. +!!! 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 + ``` ## Embedding Strategies diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index f8be8f66f..2747efc96 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,14 +1,27 @@ """ Freqtrade bot """ __version__ = 'develop' -if 'dev' in __version__: +if __version__ == 'develop': + try: import subprocess - __version__ = __version__ + '-' + subprocess.check_output( + __version__ = 'develop-' + 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: diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 223673113..35f382469 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -32,24 +32,20 @@ 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 = 32 +INITIAL_POINTS = 30 # Keep no more than SKOPT_MODEL_QUEUE_SIZE models # in the skopt model queue, to optimize memory consumption @@ -413,35 +409,6 @@ 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]]: ''' @@ -548,13 +515,7 @@ class Hyperopt: asked, is_random = self.get_asked_points(n_points=current_jobs) f_val = self.run_optimizer_parallel(parallel, asked, i) - 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()) + self.opt.tell(asked, [v['loss'] for v in f_val]) # Calculate progressbar outputs for j, val in enumerate(f_val): @@ -600,47 +561,3 @@ 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') diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 3ab461041..c6f97c976 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -6,7 +6,6 @@ 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 @@ -16,22 +15,6 @@ 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 @@ -74,32 +57,27 @@ class IResolver: # Generate spec based on absolute path # Pass object_name as first argument to have logging print a reasonable name. - 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: + 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: 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: - 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 + 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 @classmethod def _search_object(cls, directory: Path, *, object_name: str, add_source: bool = False diff --git a/requirements-dev.txt b/requirements-dev.txt index 063cfaa45..c2f3eae8a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,9 +6,9 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 -mypy==0.942 -pytest==7.1.1 -pytest-asyncio==0.18.3 +mypy==0.940 +pytest==7.1.0 +pytest-asyncio==0.18.2 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.15 -types-tabulate==0.8.6 +types-requests==2.27.12 +types-tabulate==0.8.5 # Extensions to datetime library -types-python-dateutil==2.8.10 \ No newline at end of file +types-python-dateutil==2.8.9 \ No newline at end of file diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index ad85ac71a..aeb7be035 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -8,4 +8,3 @@ scikit-optimize==0.9.0 filelock==3.6.0 joblib==1.1.0 progressbar2==4.0.0 -matplotlib \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index de05b3f7c..f0f030e78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,22 +2,22 @@ numpy==1.22.3 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.77.36 +ccxt==1.76.5 # Pin cryptography for now due to rust build errors with piwheels -cryptography==36.0.2 +cryptography==36.0.1 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.9 +urllib3==1.26.8 jsonschema==4.4.0 TA-Lib==0.4.24 technical==1.3.0 tabulate==0.8.9 pycoingecko==2.2.0 -jinja2==3.1.1 +jinja2==3.0.3 tables==3.7.0 blosc==1.10.6 diff --git a/tests/conftest.py b/tests/conftest.py index 809342c03..1dd6e8869 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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,7 +1046,6 @@ 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, @@ -1063,7 +1062,6 @@ 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, @@ -1080,8 +1078,7 @@ def limit_buy_order_old(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'datetime': str(arrow.utcnow().shift(minutes=-601).datetime), 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1097,7 +1094,6 @@ 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, @@ -1114,7 +1110,6 @@ 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, @@ -1144,7 +1139,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1165,7 +1160,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': 'AZNPFF-4AC4N-7MKTAT', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'status': 'canceled', @@ -1186,7 +1181,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1207,7 +1202,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1233,7 +1228,7 @@ def limit_sell_order_open(): 'side': 'sell', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp * 1000, + 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001173, 'amount': 90.99181073, 'filled': 0.0, @@ -1399,7 +1394,7 @@ def tickers(): 'BLK/BTC': { 'symbol': 'BLK/BTC', 'timestamp': 1522014806072, - 'datetime': '2018-03-25T21:53:26.072Z', + 'datetime': '2018-03-25T21:53:26.720Z', 'high': 0.007745, 'low': 0.007512, 'bid': 0.007729, @@ -1895,8 +1890,7 @@ def buy_order_fee(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), + 'datetime': str(arrow.utcnow().shift(minutes=-601).datetime), 'price': 0.245441, 'amount': 8.0, 'cost': 1.963528, @@ -2205,7 +2199,7 @@ def limit_buy_order_usdt_open(): 'side': 'buy', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp * 1000, + 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.00, 'amount': 30.0, 'filled': 0.0, @@ -2232,7 +2226,7 @@ def limit_sell_order_usdt_open(): 'side': 'sell', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp * 1000, + 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.20, 'amount': 30.0, 'filled': 0.0, @@ -2257,7 +2251,6 @@ 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, @@ -2314,7 +2307,6 @@ 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, diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b76cb23e6..ff8383997 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -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=rate) + pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=200) assert 'id' in order assert 'info' in order diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index dc6b03a3e..88bdd078e 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -1,13 +1,14 @@ # 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, RealParameter +from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy, + RealParameter) -class HyperoptableStrategy(StrategyTestV2): +class HyperoptableStrategy(IStrategy): """ Default Strategy provided by freqtrade bot. Please do not modify this strategy, it's intended for internal use only. @@ -15,6 +16,38 @@ class HyperoptableStrategy(StrategyTestV2): 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, @@ -58,6 +91,55 @@ class HyperoptableStrategy(StrategyTestV2): """ 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 diff --git a/tests/strategy/strats/strategy_test_v2.py b/tests/strategy/strats/strategy_test_v2.py index 59f1f569e..c57becdad 100644 --- a/tests/strategy/strats/strategy_test_v2.py +++ b/tests/strategy/strats/strategy_test_v2.py @@ -7,7 +7,7 @@ from pandas import DataFrame import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.persistence import Trade -from freqtrade.strategy import IStrategy +from freqtrade.strategy.interface import IStrategy class StrategyTestV2(IStrategy):