From 0b90e1d30921b8cec6e97e612e44e51ea225b0c8 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 1 Apr 2022 08:39:56 -0600 Subject: [PATCH 01/18] Added bot_start callback to strategy interface --- freqtrade/freqtradebot.py | 2 ++ freqtrade/strategy/interface.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 57d7cac3c..bee6c4746 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -122,6 +122,8 @@ class FreqtradeBot(LoggingMixin): self._schedule.every().day.at(t).do(update) self.last_process = datetime(1970, 1, 1, tzinfo=timezone.utc) + self.strategy.bot_start() + def notify_status(self, msg: str) -> None: """ Public method for users of this class (worker, etc.) to send notifications diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 300010b83..40744ed08 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -193,6 +193,13 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.populate_sell_trend(dataframe, metadata) + def bot_start(self, **kwargs) -> None: + """ + Called only once after bot instantiation. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + pass + def bot_loop_start(self, **kwargs) -> None: """ Called at the start of the bot iteration (one loop). From bf7da35e31a3d9a98fff35cd098ecaf83f6d91f2 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 1 Apr 2022 09:25:03 -0600 Subject: [PATCH 02/18] strategy callback on_whitelist_update --- freqtrade/freqtradebot.py | 4 ++++ freqtrade/strategy/interface.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index bee6c4746..949d821b3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -179,6 +179,7 @@ class FreqtradeBot(LoggingMixin): # Query trades from persistence layer trades = Trade.get_open_trades() + current_pair_whitelist = self.active_pair_whitelist self.active_pair_whitelist = self._refresh_active_whitelist(trades) # Refreshing candles @@ -214,6 +215,9 @@ class FreqtradeBot(LoggingMixin): Trade.commit() self.last_process = datetime.now(timezone.utc) + if current_pair_whitelist != self.active_pair_whitelist: + self.strategy.on_whitelist_update() + def process_stopped(self) -> None: """ Close all orders that were left open diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 40744ed08..00d18fa7b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -209,6 +209,13 @@ class IStrategy(ABC, HyperStrategyMixin): """ pass + def on_whitelist_update(self, **kwargs) -> None: + """ + Called every time the whitelist updates + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + pass + def check_buy_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ From d92761b2b168a19eea764864c5fb9612a379c510 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 3 Apr 2022 02:53:44 -0600 Subject: [PATCH 03/18] Revert "strategy callback on_whitelist_update" This reverts commit 39798dc1192161c3060830dd4684571aa86b7821. --- freqtrade/freqtradebot.py | 4 ---- freqtrade/strategy/interface.py | 7 ------- 2 files changed, 11 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 949d821b3..bee6c4746 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -179,7 +179,6 @@ class FreqtradeBot(LoggingMixin): # Query trades from persistence layer trades = Trade.get_open_trades() - current_pair_whitelist = self.active_pair_whitelist self.active_pair_whitelist = self._refresh_active_whitelist(trades) # Refreshing candles @@ -215,9 +214,6 @@ class FreqtradeBot(LoggingMixin): Trade.commit() self.last_process = datetime.now(timezone.utc) - if current_pair_whitelist != self.active_pair_whitelist: - self.strategy.on_whitelist_update() - def process_stopped(self) -> None: """ Close all orders that were left open diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 00d18fa7b..40744ed08 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -209,13 +209,6 @@ class IStrategy(ABC, HyperStrategyMixin): """ pass - def on_whitelist_update(self, **kwargs) -> None: - """ - Called every time the whitelist updates - :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - """ - pass - def check_buy_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ From 4fd904e0a91ecf03020060c3d718382775bd143b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 3 Apr 2022 10:28:09 -0600 Subject: [PATCH 04/18] added bot_start to backtesting --- freqtrade/optimize/backtesting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 27e14ba93..1789290bc 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -182,6 +182,7 @@ class Backtesting: # since a "perfect" stoploss-exit is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False + self.strategy.bot_start() def _load_protections(self, strategy: IStrategy): if self.config.get('enable_protections', False): From e09b4498fa419f0088b38cec2cef6c78307ee2e6 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:30:49 -0600 Subject: [PATCH 05/18] added bot_start call to plot/plotting --- freqtrade/plot/plotting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 747248be7..5337016f3 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -610,6 +610,7 @@ def load_and_plot_trades(config: Dict[str, Any]): exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) IStrategy.dp = DataProvider(config, exchange) + strategy.bot_start() plot_elements = init_plotscript(config, list(exchange.markets), strategy.startup_candle_count) timerange = plot_elements['timerange'] trades = plot_elements['trades'] From e76c6e8ad31a525802ec35fa0dd8c6e264d32328 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:32:48 -0600 Subject: [PATCH 06/18] added bot_start call to edge_positioning.__init__ --- freqtrade/edge/edge_positioning.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 2fe41a17b..5dc84f172 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -90,6 +90,8 @@ class Edge: self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0]) except IndexError: self.fee = None + + strategy.bot_start() def calculate(self, pairs: List[str]) -> bool: if self.fee is None and pairs: From 810e190e164be61a530e4d550c246dddbcdb4f1c Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:46:40 -0600 Subject: [PATCH 07/18] added tests for bot_start --- tests/optimize/test_backtesting.py | 1 + tests/optimize/test_edge_cli.py | 1 + tests/strategy/strats/strategy_test_v3.py | 5 +++++ tests/strategy/test_default_strategy.py | 1 + tests/test_plotting.py | 1 + 5 files changed, 9 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index d7ee4a042..16d1aa2a5 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -312,6 +312,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: get_fee.assert_called() assert backtesting.fee == 0.5 assert not backtesting.strategy.order_types["stoploss_on_exchange"] + assert backtesting.strategy.bot_started is True def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index f0f436a43..d9711b318 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -94,6 +94,7 @@ def test_edge_init(mocker, edge_conf) -> None: assert edge_cli.config == edge_conf assert edge_cli.config['stake_amount'] == 'unlimited' assert callable(edge_cli.edge.calculate) + assert edge_conf['strategy'].bot_started is True def test_edge_init_fee(mocker, edge_conf) -> None: diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 372e29412..df83d3663 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -82,6 +82,11 @@ class StrategyTestV3(IStrategy): # }) # return prot + bot_started = False + + def bot_start(self): + self.bot_started = True + def informative_pairs(self): return [] diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index 5cb8fce16..a60274afd 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -32,6 +32,7 @@ def test_strategy_test_v3(result, fee, is_short, side): assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame + assert strategy.bot_started is True trade = Trade( open_rate=19_000, diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 97f367608..f0a28c4eb 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -58,6 +58,7 @@ def test_init_plotscript(default_conf, mocker, testdatadir): assert "ohlcv" in ret assert "TRX/BTC" in ret["ohlcv"] assert "ADA/BTC" in ret["ohlcv"] + assert default_conf['strategy'].bot_started is True def test_add_indicators(default_conf, testdatadir, caplog): From 7f035a9d5384993dc195a0a5d8f2e22c5fc74d75 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:59:50 -0600 Subject: [PATCH 08/18] added docs for bot_start --- docs/strategy-callbacks.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 7ec600a58..a53a6992c 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -7,6 +7,7 @@ Depending on the callback used, they may be called when entering / exiting a tra Currently available callbacks: +* [`bot_start()`](#bot-start) * [`bot_loop_start()`](#bot-loop-start) * [`custom_stake_amount()`](#stake-size-management) * [`custom_exit()`](#custom-exit-signal) @@ -21,6 +22,31 @@ Currently available callbacks: !!! Tip "Callback calling sequence" You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) +## Bot start + +A simple callback which is called once when the bot starts. +This can be used to perform actions that must only be performed once and runs after dataprovider and wallet are set + +``` python +import asyncio + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + async def some_asynchronous_task(self): + self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') + + def bot_start(self, **kwargs) -> None: + """ + Called only once after bot instantiation. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + self.loop.run_until_complete(self.some_asynchronous_task()) + +``` ## Bot loop start A simple callback which is called once at the start of every bot throttling iteration (roughly every 5 seconds, unless configured differently). From a35dc843ea75fe8a4c5452ab8534b0c077bfc106 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 22:39:58 -0600 Subject: [PATCH 09/18] removed asyncio from bot_start example --- docs/strategy-callbacks.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index a53a6992c..e0cfbe8c1 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -34,17 +34,12 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods - async def some_asynchronous_task(self): - self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') - def bot_start(self, **kwargs) -> None: """ Called only once after bot instantiation. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - self.loop.run_until_complete(self.some_asynchronous_task()) + self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') ``` ## Bot loop start From 21df1b0db32f04bbb362c33afa483f17c328bfb7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Apr 2022 13:13:11 +0200 Subject: [PATCH 10/18] Use ORJSON for http responses --- freqtrade/rpc/api_server/webserver.py | 4 ++-- requirements.txt | 2 ++ setup.py | 1 + tests/rpc/test_rpc_apiserver.py | 3 +-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index 63812f52f..0da129583 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -2,7 +2,7 @@ import logging from ipaddress import IPv4Address from typing import Any, Dict -import rapidjson +import orjson import uvicorn from fastapi import Depends, FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -24,7 +24,7 @@ class FTJSONResponse(JSONResponse): Use rapidjson for responses Handles NaN and Inf / -Inf in a javascript way by default. """ - return rapidjson.dumps(content).encode("utf-8") + return orjson.dumps(content, option=orjson.OPT_SERIALIZE_NUMPY) class ApiServer(RPCHandler): diff --git a/requirements.txt b/requirements.txt index de14b9f2c..ab8329979 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,6 +27,8 @@ py_find_1st==1.1.5 # Load ticker files 30% faster python-rapidjson==1.6 +# Properly format api responses +orjson==3.6.8 # Notify systemd sdnotify==0.3.2 diff --git a/setup.py b/setup.py index 250cafdc9..c5e418d0d 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ setup( 'pycoingecko', 'py_find_1st', 'python-rapidjson', + 'orjson', 'sdnotify', 'colorama', 'jinja2', diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 4910213b4..43f783a53 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -13,7 +13,6 @@ import uvicorn from fastapi import FastAPI from fastapi.exceptions import HTTPException from fastapi.testclient import TestClient -from numpy import isnan from requests.auth import _basic_auth_str from freqtrade.__init__ import __version__ @@ -985,7 +984,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, assert_response(rc) resp_values = rc.json() assert len(resp_values) == 4 - assert isnan(resp_values[0]['profit_abs']) + assert resp_values[0]['profit_abs'] is None def test_api_version(botclient): From da7a6f58f94db81d3f436dacadbdf2c7a903ee1d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Apr 2022 17:46:33 +0200 Subject: [PATCH 11/18] Revert requiring stoploss for backtest/hyperopt --- freqtrade/constants.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 3b98ce56b..1a21ec77f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -459,8 +459,6 @@ SCHEMA_BACKTEST_REQUIRED = [ 'stake_currency', 'stake_amount', 'dry_run_wallet', - 'stoploss', - 'minimal_roi', 'dataformat_ohlcv', 'dataformat_trades', ] From fbd142844fff0065bdac6a9e8770990faf289ab3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Apr 2022 19:37:13 +0200 Subject: [PATCH 12/18] Refactor bt-caching stuff to it's own module --- freqtrade/data/btanalysis.py | 3 +- freqtrade/misc.py | 35 +----------------------- freqtrade/optimize/backtest_caching.py | 38 ++++++++++++++++++++++++++ freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/optimize_reports.py | 4 +-- tests/optimize/test_backtesting.py | 2 +- 6 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 freqtrade/optimize/backtest_caching.py diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 206a6f5f3..0c8e721c0 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -12,7 +12,8 @@ import pandas as pd from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.exceptions import OperationalException -from freqtrade.misc import get_backtest_metadata_filename, json_load +from freqtrade.misc import json_load +from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename from freqtrade.persistence import LocalTrade, Trade, init_db diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 55a533725..c3968e61c 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -2,13 +2,11 @@ Various tool function for Freqtrade and scripts """ import gzip -import hashlib import logging import re -from copy import deepcopy from datetime import datetime from pathlib import Path -from typing import Any, Iterator, List, Union +from typing import Any, Iterator, List from typing.io import IO from urllib.parse import urlparse @@ -251,34 +249,3 @@ def parse_db_uri_for_logging(uri: str): return uri pwd = parsed_db_uri.netloc.split(':')[1].split('@')[0] return parsed_db_uri.geturl().replace(f':{pwd}@', ':*****@') - - -def get_strategy_run_id(strategy) -> str: - """ - Generate unique identification hash for a backtest run. Identical config and strategy file will - always return an identical hash. - :param strategy: strategy object. - :return: hex string id. - """ - digest = hashlib.sha1() - config = deepcopy(strategy.config) - - # Options that have no impact on results of individual backtest. - not_important_keys = ('strategy_list', 'original_config', 'telegram', 'api_server') - for k in not_important_keys: - if k in config: - del config[k] - - # Explicitly allow NaN values (e.g. max_open_trades). - # as it does not matter for getting the hash. - digest.update(rapidjson.dumps(config, default=str, - number_mode=rapidjson.NM_NAN).encode('utf-8')) - with open(strategy.__file__, 'rb') as fp: - digest.update(fp.read()) - return digest.hexdigest().lower() - - -def get_backtest_metadata_filename(filename: Union[Path, str]) -> Path: - """Return metadata filename for specified backtest results file.""" - filename = Path(filename) - return filename.parent / Path(f'{filename.stem}.meta{filename.suffix}') diff --git a/freqtrade/optimize/backtest_caching.py b/freqtrade/optimize/backtest_caching.py new file mode 100644 index 000000000..c2a9903fa --- /dev/null +++ b/freqtrade/optimize/backtest_caching.py @@ -0,0 +1,38 @@ +import hashlib +from copy import deepcopy +from pathlib import Path +from typing import Union + +import rapidjson + + +def get_strategy_run_id(strategy) -> str: + """ + Generate unique identification hash for a backtest run. Identical config and strategy file will + always return an identical hash. + :param strategy: strategy object. + :return: hex string id. + """ + digest = hashlib.sha1() + config = deepcopy(strategy.config) + + # Options that have no impact on results of individual backtest. + not_important_keys = ('strategy_list', 'original_config', 'telegram', 'api_server') + for k in not_important_keys: + if k in config: + del config[k] + + # Explicitly allow NaN values (e.g. max_open_trades). + # as it does not matter for getting the hash. + digest.update(rapidjson.dumps(config, default=str, + number_mode=rapidjson.NM_NAN).encode('utf-8')) + + with open(strategy.__file__, 'rb') as fp: + digest.update(fp.read()) + return digest.hexdigest().lower() + + +def get_backtest_metadata_filename(filename: Union[Path, str]) -> Path: + """Return metadata filename for specified backtest results file.""" + filename = Path(filename) + return filename.parent / Path(f'{filename.stem}.meta{filename.suffix}') diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 0f816f295..db4496f0f 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -24,8 +24,8 @@ from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType TradingMode) from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds -from freqtrade.misc import get_strategy_run_id from freqtrade.mixins import LoggingMixin +from freqtrade.optimize.backtest_caching import get_strategy_run_id from freqtrade.optimize.bt_progress import BTProgress from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results, store_backtest_signal_candles, diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index dd058aff4..1d58dc339 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -11,8 +11,8 @@ from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT from freqtrade.data.btanalysis import (calculate_cagr, calculate_csum, calculate_market_change, calculate_max_drawdown) -from freqtrade.misc import (decimals_per_coin, file_dump_joblib, file_dump_json, - get_backtest_metadata_filename, round_coin_value) +from freqtrade.misc import decimals_per_coin, file_dump_joblib, file_dump_json, round_coin_value +from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename logger = logging.getLogger(__name__) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index a51e1b654..7494155b4 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -22,7 +22,7 @@ from freqtrade.data.history import get_timerange from freqtrade.enums import ExitType, RunMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange.exchange import timeframe_to_next_date -from freqtrade.misc import get_strategy_run_id +from freqtrade.optimize.backtest_caching import get_strategy_run_id from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence import LocalTrade from freqtrade.resolvers import StrategyResolver From 43049e04652483ff039ee7765a0181d2ed1ca337 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Apr 2022 19:44:17 +0200 Subject: [PATCH 13/18] Evict cache if parameter file changed closes #6735 --- freqtrade/optimize/backtest_caching.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtest_caching.py b/freqtrade/optimize/backtest_caching.py index c2a9903fa..d9d270072 100644 --- a/freqtrade/optimize/backtest_caching.py +++ b/freqtrade/optimize/backtest_caching.py @@ -26,7 +26,9 @@ def get_strategy_run_id(strategy) -> str: # as it does not matter for getting the hash. digest.update(rapidjson.dumps(config, default=str, number_mode=rapidjson.NM_NAN).encode('utf-8')) - + # Include _ft_params_from_file - so changing parameter files cause cache eviction + digest.update(rapidjson.dumps( + strategy._ft_params_from_file, default=str, number_mode=rapidjson.NM_NAN).encode('utf-8')) with open(strategy.__file__, 'rb') as fp: digest.update(fp.read()) return digest.hexdigest().lower() From f23faac36880027896de8d7bcc21f5af9edd6714 Mon Sep 17 00:00:00 2001 From: erdieee <58039191+erdieee@users.noreply.github.com> Date: Fri, 29 Apr 2022 20:10:50 +0200 Subject: [PATCH 14/18] Fix config_examples typo --- config_examples/config_binance.example.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_examples/config_binance.example.json b/config_examples/config_binance.example.json index ad8862afa..35b9fcd20 100644 --- a/config_examples/config_binance.example.json +++ b/config_examples/config_binance.example.json @@ -90,7 +90,7 @@ }, "bot_name": "freqtrade", "initial_state": "running", - "force_enter_enable": false, + "force_entry_enable": false, "internals": { "process_throttle_secs": 5 } From 788d9f5b55cef5657ad476d6feef8cbd227a3dec Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 29 Apr 2022 22:20:26 -0600 Subject: [PATCH 15/18] updated bot_start documentation with working example --- docs/strategy-callbacks.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index e0cfbe8c1..b01959e10 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -39,7 +39,10 @@ class AwesomeStrategy(IStrategy): Called only once after bot instantiation. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ - self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') + if self.config['runmode'].value in ('live', 'dry_run'): + # Assign this to the class by using self.* + # can then be used by populate_* methods + self.remote_data = requests.get('https://some_remote_source.example.com') ``` ## Bot loop start From 23431a71064367496d8250ca7a7dd20b97da63d4 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 29 Apr 2022 22:21:22 -0600 Subject: [PATCH 16/18] removed invalid plotting and test_default_strategy tests for bot_start, edited edge test --- tests/optimize/test_edge_cli.py | 2 +- tests/strategy/test_default_strategy.py | 1 - tests/test_plotting.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index d9711b318..8241a5362 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -94,7 +94,7 @@ def test_edge_init(mocker, edge_conf) -> None: assert edge_cli.config == edge_conf assert edge_cli.config['stake_amount'] == 'unlimited' assert callable(edge_cli.edge.calculate) - assert edge_conf['strategy'].bot_started is True + assert edge_cli.strategy.bot_started is True def test_edge_init_fee(mocker, edge_conf) -> None: diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index a60274afd..5cb8fce16 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -32,7 +32,6 @@ def test_strategy_test_v3(result, fee, is_short, side): assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame - assert strategy.bot_started is True trade = Trade( open_rate=19_000, diff --git a/tests/test_plotting.py b/tests/test_plotting.py index f0a28c4eb..97f367608 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -58,7 +58,6 @@ def test_init_plotscript(default_conf, mocker, testdatadir): assert "ohlcv" in ret assert "TRX/BTC" in ret["ohlcv"] assert "ADA/BTC" in ret["ohlcv"] - assert default_conf['strategy'].bot_started is True def test_add_indicators(default_conf, testdatadir, caplog): From 8756e7d9a1febecfae12649cc8ed21952d2f0086 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 29 Apr 2022 23:35:08 -0600 Subject: [PATCH 17/18] flake8 linting --- freqtrade/edge/edge_positioning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 5dc84f172..fb9041574 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -90,7 +90,7 @@ class Edge: self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0]) except IndexError: self.fee = None - + strategy.bot_start() def calculate(self, pairs: List[str]) -> bool: From 09b74cebce95c80bec0f6982bf9b865523e49874 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 08:55:07 +0200 Subject: [PATCH 18/18] Move edge bot_loop_start to edge_cli (otherwise it's called twice when running trade mode with edge on). --- docs/strategy-callbacks.md | 6 +++--- freqtrade/edge/edge_positioning.py | 2 -- freqtrade/optimize/edge_cli.py | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 27777c2ce..5ff499b01 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -24,11 +24,11 @@ Currently available callbacks: ## Bot start -A simple callback which is called once when the bot starts. +A simple callback which is called once when the strategy is loaded. This can be used to perform actions that must only be performed once and runs after dataprovider and wallet are set ``` python -import asyncio +import requests class AwesomeStrategy(IStrategy): @@ -42,7 +42,7 @@ class AwesomeStrategy(IStrategy): if self.config['runmode'].value in ('live', 'dry_run'): # Assign this to the class by using self.* # can then be used by populate_* methods - self.remote_data = requests.get('https://some_remote_source.example.com') + self.cust_remote_data = requests.get('https://some_remote_source.example.com') ``` ## Bot loop start diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index fb9041574..2fe41a17b 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -91,8 +91,6 @@ class Edge: except IndexError: self.fee = None - strategy.bot_start() - def calculate(self, pairs: List[str]) -> bool: if self.fee is None and pairs: self.fee = self.exchange.get_fee(pairs[0]) diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index cc9bafb0b..30eabecd0 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -44,6 +44,7 @@ class EdgeCli: self.edge._timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) + self.strategy.bot_start() def start(self) -> None: result = self.edge.calculate(self.config['exchange']['pair_whitelist'])