From ac85c3527b2bc28a0fce5b8e4f27c4fdd6b82a50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:21:12 +0000 Subject: [PATCH 01/30] Bump ccxt from 4.0.47 to 4.0.48 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.0.47 to 4.0.48. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.0.47...4.0.48) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dd079329f..45cecbed3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ numpy==1.24.3; python_version <= '3.8' pandas==2.0.3 pandas-ta==0.3.14b -ccxt==4.0.47 +ccxt==4.0.48 cryptography==41.0.3; platform_machine != 'armv7l' cryptography==40.0.1; platform_machine == 'armv7l' aiohttp==3.8.5 From 0e63335d2e8ff1cdd839d3e54d869b53de957223 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Aug 2023 20:01:31 +0200 Subject: [PATCH 02/30] Remove bitvavo temp. workaround --- tests/exchange/test_ccxt_compat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index c57e32633..51d016d11 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -544,8 +544,6 @@ class TestCCXTExchange: if exchangename in ('bittrex'): # For some weired reason, this test returns random lengths for bittrex. pytest.skip("Exchange doesn't provide stable ohlcv history") - if exchangename in ('bitvavo'): - pytest.skip("Exchange Downtime ") if not exc._ft_has['ohlcv_has_history']: pytest.skip("Exchange does not support candle history") From 53c0d30f3627b7bd20641762227cf818972a6e02 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Aug 2023 20:04:41 +0200 Subject: [PATCH 03/30] Update test for new kucoin behavior related: https://github.com/ccxt/ccxt/pull/18745 --- tests/exchange/test_ccxt_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 51d016d11..0f8c43876 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -391,7 +391,7 @@ class TestCCXTExchange: assert po['id'] is not None if len(order.keys()) < 5: # Kucoin case - assert po['status'] == 'closed' + assert po['status'] is None continue assert po['timestamp'] == 1674493798550 assert isinstance(po['datetime'], str) From 2f95c44777c2f4a6d6be50e23d54922b1a6236f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 31 Jul 2023 21:15:33 +0200 Subject: [PATCH 04/30] Add "notes" to backtest result output --- freqtrade/data/btanalysis.py | 16 ++++++++++++++++ freqtrade/rpc/api_server/api_schemas.py | 1 + freqtrade/types/backtest_result_type.py | 3 ++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 206588d37..edac74d51 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -175,6 +175,21 @@ def _get_backtest_files(dirname: Path) -> List[Path]: return list(reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json')))) +def get_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]: + """ + Get backtest result read from metadata file + """ + return [ + { + 'filename': filename.stem, + 'strategy': s, + 'notes': v.get('notes', ''), + 'run_id': v['run_id'], + 'backtest_start_time': v['backtest_start_time'], + } for s, v in load_backtest_metadata(filename).items() + ] + + def get_backtest_resultlist(dirname: Path) -> List[BacktestHistoryEntryType]: """ Get list of backtest results read from metadata files @@ -184,6 +199,7 @@ def get_backtest_resultlist(dirname: Path) -> List[BacktestHistoryEntryType]: 'filename': filename.stem, 'strategy': s, 'run_id': v['run_id'], + 'notes': v.get('notes', ''), 'backtest_start_time': v['backtest_start_time'], } for filename in _get_backtest_files(dirname) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index bd405d22b..f96e586bb 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -526,6 +526,7 @@ class BacktestHistoryEntry(BaseModel): strategy: str run_id: str backtest_start_time: int + notes: Optional[str] = '' class SysInfo(BaseModel): diff --git a/freqtrade/types/backtest_result_type.py b/freqtrade/types/backtest_result_type.py index bc53097ab..1b66e6b1c 100644 --- a/freqtrade/types/backtest_result_type.py +++ b/freqtrade/types/backtest_result_type.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from typing_extensions import TypedDict @@ -6,6 +6,7 @@ from typing_extensions import TypedDict class BacktestMetadataType(TypedDict): run_id: str backtest_start_time: int + notes: Optional[str] class BacktestResultType(TypedDict): From 78972604d0b374a0667111bf847114bf7531b4ec Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 31 Jul 2023 21:16:25 +0200 Subject: [PATCH 05/30] Allow metadata file updating --- freqtrade/data/btanalysis.py | 17 +++++++++++++- freqtrade/rpc/api_server/api_backtest.py | 30 ++++++++++++++++++++---- freqtrade/rpc/api_server/api_schemas.py | 5 ++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index edac74d51..96ab4927e 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -12,7 +12,7 @@ import pandas as pd from freqtrade.constants import LAST_BT_RESULT_FN, IntOrInf from freqtrade.exceptions import OperationalException -from freqtrade.misc import json_load +from freqtrade.misc import file_dump_json, json_load from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename from freqtrade.persistence import LocalTrade, Trade, init_db from freqtrade.types import BacktestHistoryEntryType, BacktestResultType @@ -219,6 +219,21 @@ def delete_backtest_result(file_abs: Path): file_abs_meta.unlink() +def update_backtest_metadata(filename: Path, strategy: str, content: Dict[str, Any]): + """ + Updates backtest metadata file with new content. + :raises: ValueError if metadata file does not exist, or strategy is not in this file. + """ + metadata = load_backtest_metadata(filename) + if not metadata: + raise ValueError("File does not exist.") + if strategy not in metadata: + raise ValueError("Strategy not in metadata.") + metadata[strategy].update(content) + # Write data again. + file_dump_json(get_backtest_metadata_filename(filename), metadata) + + def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, str], min_backtest_date: Optional[datetime] = None) -> Dict[str, Any]: """ diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 6d3174a5a..3bfad2f56 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -10,14 +10,15 @@ from fastapi.exceptions import HTTPException from freqtrade.configuration.config_validation import validate_config_consistency from freqtrade.constants import Config -from freqtrade.data.btanalysis import (delete_backtest_result, get_backtest_resultlist, - load_and_merge_backtest_result) +from freqtrade.data.btanalysis import (delete_backtest_result, get_backtest_result, + get_backtest_resultlist, load_and_merge_backtest_result, + update_backtest_metadata) from freqtrade.enums import BacktestState from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange.common import remove_exchange_credentials from freqtrade.misc import deep_merge_dicts, is_file_in_dir -from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, - BacktestResponse) +from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestMetadataUpdate, + BacktestRequest, BacktestResponse) from freqtrade.rpc.api_server.deps import get_config from freqtrade.rpc.api_server.webserver_bgwork import ApiBG from freqtrade.rpc.rpc import RPCException @@ -281,3 +282,24 @@ def api_delete_backtest_history_entry(file: str, config=Depends(get_config)): delete_backtest_result(file_abs) return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results') + + +@router.patch('/backtest/history/{file}', response_model=List[BacktestHistoryEntry], + tags=['webserver', 'backtest']) +def api_update_backtest_history_entry(file: str, body: BacktestMetadataUpdate, + config=Depends(get_config)): + # Get backtest result history, read from metadata files + bt_results_base: Path = config['user_data_dir'] / 'backtest_results' + file_abs = (bt_results_base / file).with_suffix('.json') + # Ensure file is in backtest_results directory + if not is_file_in_dir(file_abs, bt_results_base): + raise HTTPException(status_code=404, detail="File not found.") + content = { + 'notes': body.notes + } + try: + update_backtest_metadata(file_abs, body.strategy, content) + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + + return get_backtest_result(file_abs) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index f96e586bb..ca39b11fd 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -529,6 +529,11 @@ class BacktestHistoryEntry(BaseModel): notes: Optional[str] = '' +class BacktestMetadataUpdate(BaseModel): + strategy: str + notes: Optional[str] + + class SysInfo(BaseModel): cpu_pct: List[float] ram_pct: float From 0d71a74d8aa726f20d6a0bcdae0e93de336cd5f0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Aug 2023 06:30:23 +0200 Subject: [PATCH 06/30] Bump api version to 2.32 --- freqtrade/rpc/api_server/api_v1.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 3e5d55f71..bc0c88fe4 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -50,7 +50,8 @@ logger = logging.getLogger(__name__) # 2.29: Add /exchanges endpoint # 2.30: new /pairlists endpoint # 2.31: new /backtest/history/ delete endpoint -API_VERSION = 2.31 +# 2.32: new /backtest/history/ patch endpoint +API_VERSION = 2.32 # Public API, requires no auth. router_public = APIRouter() From 51879ffd2caaa6c53d0a77e0b0e0e59d9b8301ff Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Aug 2023 06:57:02 +0200 Subject: [PATCH 07/30] move Notes to be a "API only" type --- freqtrade/types/backtest_result_type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/types/backtest_result_type.py b/freqtrade/types/backtest_result_type.py index 1b66e6b1c..1043899f7 100644 --- a/freqtrade/types/backtest_result_type.py +++ b/freqtrade/types/backtest_result_type.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List from typing_extensions import TypedDict @@ -6,7 +6,6 @@ from typing_extensions import TypedDict class BacktestMetadataType(TypedDict): run_id: str backtest_start_time: int - notes: Optional[str] class BacktestResultType(TypedDict): @@ -26,3 +25,4 @@ def get_BacktestResultType_default() -> BacktestResultType: class BacktestHistoryEntryType(BacktestMetadataType): filename: str strategy: str + notes: str From 36b84241b159e4ef88b3bfd220341ccb007024cc Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Aug 2023 06:28:28 +0200 Subject: [PATCH 08/30] Don't allow null as notes --- freqtrade/rpc/api_server/api_schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index ca39b11fd..f1ac9db54 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -531,7 +531,7 @@ class BacktestHistoryEntry(BaseModel): class BacktestMetadataUpdate(BaseModel): strategy: str - notes: Optional[str] + notes: str = '' class SysInfo(BaseModel): From 23a2b9599463866a004daf6a1d641fccaf75a33c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Aug 2023 06:39:27 +0200 Subject: [PATCH 09/30] Add test for updating metadata --- tests/rpc/test_rpc_apiserver.py | 82 ++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index abbaa421e..095e4ac3f 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -10,6 +10,7 @@ from unittest.mock import ANY, MagicMock, PropertyMock import pandas as pd import pytest +import rapidjson import uvicorn from fastapi import FastAPI, WebSocketDisconnect from fastapi.exceptions import HTTPException @@ -79,6 +80,14 @@ def client_post(client: TestClient, url, data={}): 'content-type': 'application/json' }) +def client_patch(client: TestClient, url, data={}): + + return client.patch(url, + json=data, + headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), + 'Origin': 'http://example.com', + 'content-type': 'application/json' + }) def client_get(client: TestClient, url): # Add fake Origin to ensure CORS kicks in @@ -1763,7 +1772,7 @@ def test_api_pairlists_evaluate(botclient, tmpdir, mocker): rc = client_get(client, f"{BASE_URI}/pairlists/evaluate/{job_id}") assert_response(rc) response = rc.json() - assert response['result']['whitelist'] == ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC',] + assert response['result']['whitelist'] == ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'] assert response['result']['length'] == 4 # Restart with additional filter, reducing the list to 2 @@ -2023,7 +2032,7 @@ def test_api_backtest_history(botclient, mocker, testdatadir): assert result2['backtest_result']['strategy'][strategy] -def test_api_delete_backtest_history_entry(botclient, mocker, tmp_path: Path): +def test_api_delete_backtest_history_entry(botclient, tmp_path: Path): ftbot, client = botclient # Create a temporary directory and file @@ -2051,6 +2060,75 @@ def test_api_delete_backtest_history_entry(botclient, mocker, tmp_path: Path): assert not meta_path.exists() +def test_api_patch_backtest_history_entry(botclient, tmp_path: Path): + ftbot, client = botclient + + # Create a temporary directory and file + bt_results_base = tmp_path / "backtest_results" + bt_results_base.mkdir() + file_path = bt_results_base / "test.json" + file_path.touch() + meta_path = file_path.with_suffix('.meta.json') + with meta_path.open('w') as metafile: + rapidjson.dump({ + CURRENT_TEST_STRATEGY: { + "run_id": "6e542efc8d5e62cef6e5be0ffbc29be81a6e751d", + "backtest_start_time": 1690176003} + }, metafile) + + def read_metadata(): + with meta_path.open('r') as metafile: + return rapidjson.load(metafile) + + rc = client_patch(client, f"{BASE_URI}/backtest/history/randomFile.json") + assert_response(rc, 503) + + ftbot.config['user_data_dir'] = tmp_path + ftbot.config['runmode'] = RunMode.WEBSERVER + + rc = client_patch(client, f"{BASE_URI}/backtest/history/randomFile.json", { + "strategy": CURRENT_TEST_STRATEGY, + }) + assert rc.status_code == 404 + + # Nonexisting strategy + rc = client_patch(client, f"{BASE_URI}/backtest/history/{file_path.name}", { + "strategy": f"{CURRENT_TEST_STRATEGY}xxx", + }) + assert rc.status_code == 400 + assert rc.json()['detail'] == 'Strategy not in metadata.' + + # no Notes + rc = client_patch(client, f"{BASE_URI}/backtest/history/{file_path.name}", { + "strategy": CURRENT_TEST_STRATEGY, + }) + assert rc.status_code == 200 + res = rc.json() + assert isinstance(res, list) + assert len(res) == 1 + assert res[0]['strategy'] == CURRENT_TEST_STRATEGY + assert res[0]['notes'] == '' + + fileres = read_metadata() + assert fileres[CURRENT_TEST_STRATEGY]['run_id'] == res[0]['run_id'] + assert fileres[CURRENT_TEST_STRATEGY]['notes'] == '' + + rc = client_patch(client, f"{BASE_URI}/backtest/history/{file_path.name}", { + "strategy": CURRENT_TEST_STRATEGY, + "notes": "FooBar", + }) + assert rc.status_code == 200 + res = rc.json() + assert isinstance(res, list) + assert len(res) == 1 + assert res[0]['strategy'] == CURRENT_TEST_STRATEGY + assert res[0]['notes'] == 'FooBar' + + fileres = read_metadata() + assert fileres[CURRENT_TEST_STRATEGY]['run_id'] == res[0]['run_id'] + assert fileres[CURRENT_TEST_STRATEGY]['notes'] == 'FooBar' + + def test_health(botclient): ftbot, client = botclient From 6d6111864e3f74e3d32eebe90f703c568af17970 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Aug 2023 06:42:15 +0200 Subject: [PATCH 10/30] Test also backtest result list --- tests/rpc/test_rpc_apiserver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 095e4ac3f..0c9c964cf 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -80,6 +80,7 @@ def client_post(client: TestClient, url, data={}): 'content-type': 'application/json' }) + def client_patch(client: TestClient, url, data={}): return client.patch(url, @@ -89,6 +90,7 @@ def client_patch(client: TestClient, url, data={}): 'content-type': 'application/json' }) + def client_get(client: TestClient, url): # Add fake Origin to ensure CORS kicks in return client.get(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), @@ -2019,6 +2021,7 @@ def test_api_backtest_history(botclient, mocker, testdatadir): assert len(result) == 3 fn = result[0]['filename'] assert fn == "backtest-result_multistrat" + assert result[0]['notes'] == '' strategy = result[0]['strategy'] rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}&strategy={strategy}") assert_response(rc) From 81cd2419548310c7816567bffd067610a5d35dc5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Aug 2023 07:05:57 +0200 Subject: [PATCH 11/30] Update API backtest to return proper metadata --- freqtrade/optimize/optimize_reports/bt_storage.py | 4 +++- freqtrade/rpc/api_server/api_backtest.py | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_storage.py b/freqtrade/optimize/optimize_reports/bt_storage.py index 71c6dc130..6b50412b3 100644 --- a/freqtrade/optimize/optimize_reports/bt_storage.py +++ b/freqtrade/optimize/optimize_reports/bt_storage.py @@ -12,7 +12,7 @@ logger = logging.getLogger(__name__) def store_backtest_stats( - recordfilename: Path, stats: BacktestResultType, dtappendix: str) -> None: + recordfilename: Path, stats: BacktestResultType, dtappendix: str) -> Path: """ Stores backtest results :param recordfilename: Path object, which can either be a filename or a directory. @@ -41,6 +41,8 @@ def store_backtest_stats( latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN) file_dump_json(latest_filename, {'latest_backtest': str(filename.name)}) + return filename + def _store_backtest_analysis_data( recordfilename: Path, data: Dict[str, Dict], diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 3bfad2f56..f387a9ac8 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -75,10 +75,11 @@ def __run_backtest_bg(btconfig: Config): ApiBG.bt['bt'].load_prior_backtest() ApiBG.bt['bt'].abort = False + strategy_name = strat.get_strategy_name() if (ApiBG.bt['bt'].results and - strat.get_strategy_name() in ApiBG.bt['bt'].results['strategy']): + strategy_name in ApiBG.bt['bt'].results['strategy']): # When previous result hash matches - reuse that result and skip backtesting. - logger.info(f'Reusing result of previous backtest for {strat.get_strategy_name()}') + logger.info(f'Reusing result of previous backtest for {strategy_name}') else: min_date, max_date = ApiBG.bt['bt'].backtest_one_strategy( strat, ApiBG.bt['data'], ApiBG.bt['timerange']) @@ -88,10 +89,12 @@ def __run_backtest_bg(btconfig: Config): min_date=min_date, max_date=max_date) if btconfig.get('export', 'none') == 'trades': - store_backtest_stats( + fn = store_backtest_stats( btconfig['exportfilename'], ApiBG.bt['bt'].results, datetime.now().strftime("%Y-%m-%d_%H-%M-%S") ) + ApiBG.bt['bt'].results['metadata'][strategy_name]['filename'] = str(fn.name) + ApiBG.bt['bt'].results['metadata'][strategy_name]['strategy'] = strategy_name logger.info("Backtest finished.") From e174f9640d1038edfcd619aeba61bd03244986a6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Aug 2023 11:14:19 +0200 Subject: [PATCH 12/30] Improve docker docs with some hints about windows usage --- docs/docker_quickstart.md | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/docker_quickstart.md b/docs/docker_quickstart.md index 89f737d71..0c9e8001a 100644 --- a/docs/docker_quickstart.md +++ b/docs/docker_quickstart.md @@ -14,6 +14,9 @@ Start by downloading and installing Docker / Docker Desktop for your platform: Freqtrade documentation assumes the use of Docker desktop (or the docker compose plugin). While the docker-compose standalone installation still works, it will require changing all `docker compose` commands from `docker compose` to `docker-compose` to work (e.g. `docker compose up -d` will become `docker-compose up -d`). +??? Warning "Docker on windows" + If you just installed docker on a windows system, make sure to reboot your system, otherwise you might encounter unexplainable Problems related to network connectivity to docker containers. + ## Freqtrade with docker Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/), as well as a [docker compose file](https://github.com/freqtrade/freqtrade/blob/stable/docker-compose.yml) ready for usage. @@ -78,7 +81,7 @@ If you've selected to enable FreqUI in the `new-config` step, you will have freq You can now access the UI by typing localhost:8080 in your browser. -??? Note "UI Access on a remote servers" +??? Note "UI Access on a remote server" If you're running on a VPS, you should consider using either a ssh tunnel, or setup a VPN (openVPN, wireguard) to connect to your bot. This will ensure that freqUI is not directly exposed to the internet, which is not recommended for security reasons (freqUI does not support https out of the box). Setup of these tools is not part of this tutorial, however many good tutorials can be found on the internet. @@ -128,7 +131,7 @@ All freqtrade arguments will be available by running `docker compose run --rm fr !!! Note "`docker compose run --rm`" Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command). -??? Note "Using docker without docker" +??? Note "Using docker without docker compose" "`docker compose run --rm`" will require a compose file to be provided. Some freqtrade commands that don't require authentication such as `list-pairs` can be run with "`docker run --rm`" instead. For example `docker run --rm freqtradeorg/freqtrade:stable list-pairs --exchange binance --quote BTC --print-json`. @@ -172,7 +175,7 @@ You can then run `docker compose build --pull` to build the docker image, and ru ### Plotting with docker -Commands `freqtrade plot-profit` and `freqtrade plot-dataframe` ([Documentation](plotting.md)) are available by changing the image to `*_plot` in your docker-compose.yml file. +Commands `freqtrade plot-profit` and `freqtrade plot-dataframe` ([Documentation](plotting.md)) are available by changing the image to `*_plot` in your `docker-compose.yml` file. You can then use these commands as follows: ``` bash @@ -203,16 +206,20 @@ docker compose -f docker/docker-compose-jupyter.yml build --no-cache ### Docker on Windows -* Error: `"Timestamp for this request is outside of the recvWindow."` - * The market api requests require a synchronized clock but the time in the docker container shifts a bit over time into the past. - To fix this issue temporarily you need to run `wsl --shutdown` and restart docker again (a popup on windows 10 will ask you to do so). - A permanent solution is either to host the docker container on a linux host or restart the wsl from time to time with the scheduler. +* Error: `"Timestamp for this request is outside of the recvWindow."` + The market api requests require a synchronized clock but the time in the docker container shifts a bit over time into the past. + To fix this issue temporarily you need to run `wsl --shutdown` and restart docker again (a popup on windows 10 will ask you to do so). + A permanent solution is either to host the docker container on a linux host or restart the wsl from time to time with the scheduler. - ``` bash - taskkill /IM "Docker Desktop.exe" /F - wsl --shutdown - start "" "C:\Program Files\Docker\Docker\Docker Desktop.exe" - ``` + ``` bash + taskkill /IM "Docker Desktop.exe" /F + wsl --shutdown + start "" "C:\Program Files\Docker\Docker\Docker Desktop.exe" + ``` + +* Cannot connect to the API (Windows) + If you're on windows and just installed Docker (desktop), make sure to reboot your System. Docker can have problems with network connectivity without a restart. + You should obviously also make sure to have your [settings](#accessing-the-ui) accordingly. !!! Warning Due to the above, we do not recommend the usage of docker on windows for production setups, but only for experimentation, datadownload and backtesting. From 72d9e8a094333ce1204a9effb38ea78d76a61aa8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Aug 2023 11:31:01 +0200 Subject: [PATCH 13/30] Fix indentation of strategy-updater in utils --- docs/utils.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utils.md b/docs/utils.md index 900856af4..65ab50b9e 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -967,7 +967,7 @@ Print trades with id 2 and 3 as json freqtrade show-trades --db-url sqlite:///tradesv3.sqlite --trade-ids 2 3 --print-json ``` -### Strategy-Updater +## Strategy-Updater Updates listed strategies or all strategies within the strategies folder to be v3 compliant. If the command runs without --strategy-list then all strategies inside the strategies folder will be converted. From 7d18261f58c9dc7be46a5d0d8ddd72f922a53f3b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 6 Aug 2023 17:10:38 +0200 Subject: [PATCH 14/30] Improve FAQ wording --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 7b8cc2580..ecf4f45a9 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -20,7 +20,7 @@ Futures trading is supported for selected exchanges. Please refer to the [docume * When you work with your strategy & hyperopt file you should use a proper code editor like VSCode or PyCharm. A good code editor will provide syntax highlighting as well as line numbers, making it easy to find syntax errors (most likely pointed out by Freqtrade during startup). -## Freqtrade common issues +## Freqtrade common questions ### Can freqtrade open multiple positions on the same pair in parallel? From cd6fc1652ed8d3406e3905ef73f96e9ae658ed14 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 6 Aug 2023 17:11:17 +0200 Subject: [PATCH 15/30] Add rate-limited wallets call before adjust_trade-Position calls closes #8998 --- freqtrade/freqtradebot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 9d35aee0f..beca1f09c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -613,6 +613,8 @@ class FreqtradeBot(LoggingMixin): for trade in Trade.get_open_trades(): # If there is any open orders, wait for them to finish. if trade.open_order_id is None: + # Do a wallets update (will be ratelimited to once per hour) + self.wallets.update(False) try: self.check_and_call_adjust_trade_position(trade) except DependencyException as exception: From 813ace12c925dfeb975fa81620de53ce1e6f210a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 6 Aug 2023 17:11:33 +0200 Subject: [PATCH 16/30] Explain behavior in case of deposits related to #8998 --- docs/faq.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index ecf4f45a9..0a3b152cb 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -78,6 +78,14 @@ Where possible (e.g. on binance), the use of the exchange's dedicated fee curren On binance, it's sufficient to have BNB in your account, and have "Pay fees in BNB" enabled in your profile. Your BNB balance will slowly decline (as it's used to pay fees) - but you'll no longer encounter dust (Freqtrade will include the fees in the profit calculations). Other exchanges don't offer such possibilities, where it's simply something you'll have to accept or move to a different exchange. +### I deposited more funds to the exchange, but my bot doesn't recognize this + +Freqtrade will update the exchange balance when necessary (Before placing an order). +RPC calls (Telegram's `/balance`, API calls to `/balance`) can trigger an update at max. once per hour. + +If `adjust_trade_position` is enabled (and the bot has open trades eligible for position adjustments) - then the wallets will be refreshed once per hour. +To force an immediate update, you can use `/reload_config` - which will restart the bot. + ### I want to use incomplete candles Freqtrade will not provide incomplete candles to strategies. Using incomplete candles will lead to repainting and consequently to strategies with "ghost" buys, which are impossible to both backtest, and verify after they happened. From 67ea6512ef3c4c44e946bafc8ef28b976c50e66f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 03:05:10 +0000 Subject: [PATCH 17/30] Bump ccxt from 4.0.48 to 4.0.50 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.0.48 to 4.0.50. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md) - [Commits](https://github.com/ccxt/ccxt/compare/4.0.48...4.0.50) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e271b7e3a..0c8aba5b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ numpy==1.24.3; python_version <= '3.8' pandas==2.0.3 pandas-ta==0.3.14b -ccxt==4.0.48 +ccxt==4.0.50 cryptography==41.0.3; platform_machine != 'armv7l' cryptography==40.0.1; platform_machine == 'armv7l' aiohttp==3.8.5 From 8c3a0cf6182936494f6c36c6959f6fc432068a99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 03:05:18 +0000 Subject: [PATCH 18/30] Bump orjson from 3.9.2 to 3.9.3 Bumps [orjson](https://github.com/ijl/orjson) from 3.9.2 to 3.9.3. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.9.2...3.9.3) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e271b7e3a..3fd4ce7de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,7 +33,7 @@ py_find_1st==1.1.5 # Load ticker files 30% faster python-rapidjson==1.10 # Properly format api responses -orjson==3.9.2 +orjson==3.9.3 # Notify systemd sdnotify==0.3.2 From 8deedc31c58a915c45fd430a8f864a09f1322fea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 03:05:27 +0000 Subject: [PATCH 19/30] Bump fastapi from 0.100.1 to 0.101.0 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.100.1 to 0.101.0. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.100.1...0.101.0) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e271b7e3a..835334cef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ orjson==3.9.2 sdnotify==0.3.2 # API Server -fastapi==0.100.1 +fastapi==0.101.0 pydantic==1.10.11 uvicorn==0.23.2 pyjwt==2.8.0 From ae3c3d81c1613ea5f3ef7594025e93d6c6bf6dfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 03:05:47 +0000 Subject: [PATCH 20/30] Bump jsonschema from 4.18.5 to 4.18.6 Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.18.5 to 4.18.6. - [Release notes](https://github.com/python-jsonschema/jsonschema/releases) - [Changelog](https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst) - [Commits](https://github.com/python-jsonschema/jsonschema/compare/v4.18.5...v4.18.6) --- updated-dependencies: - dependency-name: jsonschema dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e271b7e3a..0e9c0f624 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ arrow==1.2.3 cachetools==5.3.1 requests==2.31.0 urllib3==2.0.4 -jsonschema==4.18.5 +jsonschema==4.18.6 TA-Lib==0.4.27 technical==1.4.0 tabulate==0.9.0 From 4b07720d0bb82a3398df8c3b02caba4ec1f9bd91 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 7 Aug 2023 06:59:16 +0200 Subject: [PATCH 21/30] Update test strategy to ensure we're using stake_amount --- tests/strategy/strats/strategy_test_v3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 2d5121403..571427fb1 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -197,7 +197,7 @@ class StrategyTestV3(IStrategy): if current_profit < -0.0075: orders = trade.select_filled_orders(trade.entry_side) - return round(orders[0].safe_cost, 0) + return round(orders[0].stake_amount, 0) return None From 03150ee09a9f0ff3cc7d11e8804d8ab9733c76ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 7 Aug 2023 06:59:35 +0200 Subject: [PATCH 22/30] Ensure backpopulated "trade" attribute is immediately loaded. --- freqtrade/persistence/trade_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index f686e4f8c..ab170d307 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -48,7 +48,7 @@ class Order(ModelBase): id: Mapped[int] = mapped_column(Integer, primary_key=True) ft_trade_id: Mapped[int] = mapped_column(Integer, ForeignKey('trades.id'), index=True) - _trade_live: Mapped["Trade"] = relationship("Trade", back_populates="orders") + _trade_live: Mapped["Trade"] = relationship("Trade", back_populates="orders", lazy="immediate") _trade_bt: "LocalTrade" = None # type: ignore # order_side can only be 'buy', 'sell' or 'stoploss' From c88f71c63846aa8b81cf75dd361ad68309a901af Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Tue, 8 Aug 2023 14:57:48 +0900 Subject: [PATCH 23/30] add timeout to discord --- freqtrade/rpc/discord.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/rpc/discord.py b/freqtrade/rpc/discord.py index 8be0eab68..36ef37d01 100644 --- a/freqtrade/rpc/discord.py +++ b/freqtrade/rpc/discord.py @@ -20,6 +20,7 @@ class Discord(Webhook): self._format = 'json' self._retries = 1 self._retry_delay = 0.1 + self._timeout = self._config['discord'].get('timeout', 10) def cleanup(self) -> None: """ From 1f23727ff79052bc59bd29d756250a5ae93bef59 Mon Sep 17 00:00:00 2001 From: Jan Smets Date: Tue, 8 Aug 2023 11:36:48 +0200 Subject: [PATCH 24/30] Increase bybit ohlcv_candle_limit to 1000 --- freqtrade/exchange/bybit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index 145a501c9..84779ea72 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -27,7 +27,7 @@ class Bybit(Exchange): """ _ft_has: Dict = { - "ohlcv_candle_limit": 200, + "ohlcv_candle_limit": 1000, "ohlcv_has_history": True, } _ft_has_futures: Dict = { From ab156b6ad7b216486a795008f977d934cb3621d2 Mon Sep 17 00:00:00 2001 From: Jan Smets Date: Tue, 8 Aug 2023 12:28:28 +0200 Subject: [PATCH 25/30] Increase bybit ohlcv_candle_limit to 1000 in tests --- tests/exchange/test_bybit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py index d0d5114a1..a8c902fd6 100644 --- a/tests/exchange/test_bybit.py +++ b/tests/exchange/test_bybit.py @@ -26,7 +26,7 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker): api_mock = MagicMock() api_mock.fetch_funding_rate_history = get_mock_coro(return_value=[]) exchange = get_patched_exchange(mocker, default_conf, id='bybit', api_mock=api_mock) - limit = 200 + limit = 1000 # Test fetch_funding_rate_history (current data) await exchange._fetch_funding_rate_history( pair='BTC/USDT:USDT', From 62ad2cca1abf0d0f31682d6450baae10593a83e3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 8 Aug 2023 20:14:37 +0200 Subject: [PATCH 26/30] Add active test for alternative futures rates (ensures history is loaded correctly). --- tests/exchange/test_ccxt_compat.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 0f8c43876..2c370145d 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -511,7 +511,8 @@ class TestCCXTExchange: now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2)) assert exch.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now) - def ccxt__async_get_candle_history(self, exchange, exchangename, pair, timeframe, candle_type): + def ccxt__async_get_candle_history( + self, exchange, exchangename, pair, timeframe, candle_type, factor=0.9): timeframe_ms = timeframe_to_msecs(timeframe) now = timeframe_to_prev_date( @@ -532,11 +533,11 @@ class TestCCXTExchange: assert res[1] == timeframe assert res[2] == candle_type candles = res[3] - factor = 0.9 candle_count = exchange.ohlcv_candle_limit(timeframe, candle_type, since_ms) * factor candle_count1 = (now.timestamp() * 1000 - since_ms) // timeframe_ms * factor assert len(candles) >= min(candle_count, candle_count1), \ f"{len(candles)} < {candle_count} in {timeframe}, Offset: {offset} {factor}" + # Check if first-timeframe is either the start, or start + 1 assert candles[0][0] == since_ms or (since_ms + timeframe_ms) def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE): @@ -552,15 +553,29 @@ class TestCCXTExchange: self.ccxt__async_get_candle_history( exc, exchangename, pair, timeframe, CandleType.SPOT) - def test_ccxt__async_get_candle_history_futures(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): + @pytest.mark.parametrize('candle_type', [ + CandleType.FUTURES, + CandleType.FUNDING_RATE, + CandleType.MARK, + ]) + def test_ccxt__async_get_candle_history_futures( + self, exchange_futures: EXCHANGE_FIXTURE_TYPE, candle_type): exchange, exchangename = exchange_futures if not exchange: # exchange_futures only returns values for supported exchanges return pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair']) timeframe = EXCHANGES[exchangename]['timeframe'] + if candle_type == CandleType.FUNDING_RATE: + timeframe = exchange._ft_has.get('funding_fee_timeframe', + exchange._ft_has['mark_ohlcv_timeframe']) self.ccxt__async_get_candle_history( - exchange, exchangename, pair, timeframe, CandleType.FUTURES) + exchange, + exchangename, + pair=pair, + timeframe=timeframe, + candle_type=candle_type, + ) def test_ccxt_fetch_funding_rate_history(self, exchange_futures: EXCHANGE_FIXTURE_TYPE): exchange, exchangename = exchange_futures From 565e2699b4618a81aeefaee885bf343b0d4491c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 8 Aug 2023 20:23:01 +0200 Subject: [PATCH 27/30] Re-set funding-fee history limit for bybit to 200 --- freqtrade/exchange/bybit.py | 12 +++++++++++- tests/exchange/test_bybit.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index 84779ea72..5f3762453 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -7,6 +7,7 @@ import ccxt from freqtrade.constants import BuySell from freqtrade.enums import MarginMode, PriceType, TradingMode +from freqtrade.enums.candletype import CandleType from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange from freqtrade.exchange.common import retrier @@ -91,6 +92,14 @@ class Bybit(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e + def ohlcv_candle_limit( + self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None) -> int: + + if candle_type in (CandleType.FUNDING_RATE): + return 200 + + return super().ohlcv_candle_limit(timeframe, candle_type, since_ms) + async def _fetch_funding_rate_history( self, pair: str, @@ -104,7 +113,8 @@ class Bybit(Exchange): """ params = {} if since_ms: - until = since_ms + (timeframe_to_msecs(timeframe) * self._ft_has['ohlcv_candle_limit']) + limit = self.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, since_ms) + until = since_ms + (timeframe_to_msecs(timeframe) * limit) params.update({'until': until}) # Funding rate data = await self._api_async.fetch_funding_rate_history( diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py index a8c902fd6..d0d5114a1 100644 --- a/tests/exchange/test_bybit.py +++ b/tests/exchange/test_bybit.py @@ -26,7 +26,7 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker): api_mock = MagicMock() api_mock.fetch_funding_rate_history = get_mock_coro(return_value=[]) exchange = get_patched_exchange(mocker, default_conf, id='bybit', api_mock=api_mock) - limit = 1000 + limit = 200 # Test fetch_funding_rate_history (current data) await exchange._fetch_funding_rate_history( pair='BTC/USDT:USDT', From 78cf8a1c095123a82313fb14cfb4b2e74fe2e739 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 8 Aug 2023 20:31:10 +0200 Subject: [PATCH 28/30] Fix exchange bybit test --- tests/exchange/test_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 289a67c4a..a8c765553 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2100,7 +2100,7 @@ def test_get_historic_ohlcv(default_conf, mocker, caplog, exchange_name, candle_ exchange._async_get_candle_history = Mock(wraps=mock_candle_hist) # one_call calculation * 1.8 should do 2 calls - since = 5 * 60 * exchange.ohlcv_candle_limit('5m', CandleType.SPOT) * 1.8 + since = 5 * 60 * exchange.ohlcv_candle_limit('5m', candle_type) * 1.8 ret = exchange.get_historic_ohlcv( pair, "5m", From 2069abe31411fa67ba330dd8b650d19eb03380a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 8 Aug 2023 20:54:58 +0200 Subject: [PATCH 29/30] Remove custom fetch_funding_fees from bybit --- freqtrade/exchange/bybit.py | 25 ------------------------- tests/exchange/test_bybit.py | 4 ---- 2 files changed, 29 deletions(-) diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index 5f3762453..626643d06 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -11,7 +11,6 @@ from freqtrade.enums.candletype import CandleType from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange from freqtrade.exchange.common import retrier -from freqtrade.exchange.exchange_utils import timeframe_to_msecs logger = logging.getLogger(__name__) @@ -100,30 +99,6 @@ class Bybit(Exchange): return super().ohlcv_candle_limit(timeframe, candle_type, since_ms) - async def _fetch_funding_rate_history( - self, - pair: str, - timeframe: str, - limit: int, - since_ms: Optional[int] = None, - ) -> List[List]: - """ - Fetch funding rate history - Necessary workaround until https://github.com/ccxt/ccxt/issues/15990 is fixed. - """ - params = {} - if since_ms: - limit = self.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, since_ms) - until = since_ms + (timeframe_to_msecs(timeframe) * limit) - params.update({'until': until}) - # Funding rate - data = await self._api_async.fetch_funding_rate_history( - pair, since=since_ms, - params=params) - # Convert funding rate to candle pattern - data = [[x['timestamp'], x['fundingRate'], 0, 0, 0, 0] for x in data] - return data - def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False): if self.trading_mode != TradingMode.SPOT: params = {'leverage': leverage} diff --git a/tests/exchange/test_bybit.py b/tests/exchange/test_bybit.py index d0d5114a1..7495f543b 100644 --- a/tests/exchange/test_bybit.py +++ b/tests/exchange/test_bybit.py @@ -3,7 +3,6 @@ from unittest.mock import MagicMock from freqtrade.enums.marginmode import MarginMode from freqtrade.enums.tradingmode import TradingMode -from freqtrade.exchange.exchange_utils import timeframe_to_msecs from tests.conftest import get_mock_coro, get_patched_exchange from tests.exchange.test_exchange import ccxt_exceptionhandlers @@ -37,12 +36,10 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker): assert api_mock.fetch_funding_rate_history.call_count == 1 assert api_mock.fetch_funding_rate_history.call_args_list[0][0][0] == 'BTC/USDT:USDT' kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1] - assert kwargs['params'] == {} assert kwargs['since'] is None api_mock.fetch_funding_rate_history.reset_mock() since_ms = 1610000000000 - since_ms_end = since_ms + (timeframe_to_msecs('4h') * limit) # Test fetch_funding_rate_history (current data) await exchange._fetch_funding_rate_history( pair='BTC/USDT:USDT', @@ -54,7 +51,6 @@ async def test_bybit_fetch_funding_rate(default_conf, mocker): assert api_mock.fetch_funding_rate_history.call_count == 1 assert api_mock.fetch_funding_rate_history.call_args_list[0][0][0] == 'BTC/USDT:USDT' kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1] - assert kwargs['params'] == {'until': since_ms_end} assert kwargs['since'] == since_ms From 328a6f791e4057f30011a20e96019b61895c3a46 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 9 Aug 2023 19:55:27 +0200 Subject: [PATCH 30/30] Improve stoploss mock --- tests/test_freqtradebot.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e533acbb8..f595788dd 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1508,15 +1508,15 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, @pytest.mark.parametrize("is_short", [False, True]) def test_create_stoploss_order_invalid_order( - mocker, default_conf_usdt, caplog, fee, is_short, limit_order, limit_order_open + mocker, default_conf_usdt, caplog, fee, is_short, limit_order ): - open_order = limit_order_open[entry_side(is_short)] + open_order = limit_order[entry_side(is_short)] order = limit_order[exit_side(is_short)] rpc_mock = patch_RPCManager(mocker) patch_exchange(mocker) create_order_mock = MagicMock(side_effect=[ open_order, - {'id': order['id']} + order, ]) mocker.patch.multiple( EXMS, @@ -1541,6 +1541,7 @@ def test_create_stoploss_order_invalid_order( trade = Trade.session.scalars(select(Trade)).first() trade.is_short = is_short caplog.clear() + rpc_mock.reset_mock() freqtrade.create_stoploss_order(trade, 200) assert trade.stoploss_order_id is None assert trade.exit_reason == ExitType.EMERGENCY_EXIT.value @@ -1554,9 +1555,11 @@ def test_create_stoploss_order_invalid_order( assert create_order_mock.call_args[1]['amount'] == trade.amount # Rpc is sending first buy, then sell - assert rpc_mock.call_count == 3 - assert rpc_mock.call_args_list[2][0][0]['sell_reason'] == ExitType.EMERGENCY_EXIT.value - assert rpc_mock.call_args_list[2][0][0]['order_type'] == 'market' + assert rpc_mock.call_count == 2 + assert rpc_mock.call_args_list[0][0][0]['sell_reason'] == ExitType.EMERGENCY_EXIT.value + assert rpc_mock.call_args_list[0][0][0]['order_type'] == 'market' + assert rpc_mock.call_args_list[0][0][0]['type'] == 'exit' + assert rpc_mock.call_args_list[1][0][0]['type'] == 'exit_fill' @pytest.mark.parametrize("is_short", [False, True])