From 30dd63fcb91cbfa6281a2456cf4a0330a4ac7038 Mon Sep 17 00:00:00 2001 From: Robert Caulk Date: Thu, 1 Jun 2023 15:54:05 +0200 Subject: [PATCH 01/13] Update freqai.md --- docs/freqai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/freqai.md b/docs/freqai.md index 3c4f47212..a1b20ae1e 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -76,7 +76,7 @@ pip install -r requirements-freqai.txt ### Usage with docker -If you are using docker, a dedicated tag with FreqAI dependencies is available as `:freqai`. As such - you can replace the image line in your docker compose file with `image: freqtradeorg/freqtrade:develop_freqai`. This image contains the regular FreqAI dependencies. Similar to native installs, Catboost will not be available on ARM based devices. +If you are using docker, a dedicated tag with FreqAI dependencies is available as `:freqai`. As such - you can replace the image line in your docker compose file with `image: freqtradeorg/freqtrade:develop_freqai`. This image contains the regular FreqAI dependencies. Similar to native installs, Catboost will not be available on ARM based devices. If you would like to use PyTorch or Reinforcement learning, you should use the torch or RL tags, `image: freqtradeorg/freqtrade:develop_freqaitorch`, `image: freqtradeorg/freqtrade:develop_freqairl`. !!! note "docker-compose-freqai.yml" We do provide an explicit docker-compose file for this in `docker/docker-compose-freqai.yml` - which can be used via `docker compose -f docker/docker-compose-freqai.yml run ...` - or can be copied to replace the original docker file. This docker-compose file also contains a (disabled) section to enable GPU resources within docker containers. This obviously assumes the system has GPU resources available. From 49c0fdf367de05f1f38c5e07d7ced0b5d9a17cdb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 20:23:40 +0000 Subject: [PATCH 02/13] Bump cryptography from 40.0.1 to 41.0.0 Bumps [cryptography](https://github.com/pyca/cryptography) from 40.0.1 to 41.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/40.0.1...41.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d04774361..8f5ee3b3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pandas-ta==0.3.14b ccxt==3.1.13 cryptography==40.0.2; platform_machine != 'armv7l' -cryptography==40.0.1; platform_machine == 'armv7l' +cryptography==41.0.0; platform_machine == 'armv7l' aiohttp==3.8.4 SQLAlchemy==2.0.15 python-telegram-bot==20.3 From e890bc0718b8cd73e247247c79ea1dbeb9b42d09 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 08:30:38 +0200 Subject: [PATCH 03/13] Don't bump pi version, but bump regular version --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8f5ee3b3a..b25e28046 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,8 @@ pandas==2.0.1 pandas-ta==0.3.14b ccxt==3.1.13 -cryptography==40.0.2; platform_machine != 'armv7l' -cryptography==41.0.0; platform_machine == 'armv7l' +cryptography==41.0.1; platform_machine != 'armv7l' +cryptography==40.0.1; platform_machine == 'armv7l' aiohttp==3.8.4 SQLAlchemy==2.0.15 python-telegram-bot==20.3 From b5d10177794ac97882bcd6186bc6dc1ec005f092 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 07:15:05 +0200 Subject: [PATCH 04/13] Update list_exchanges to use a dict internally --- freqtrade/commands/list_commands.py | 11 ++++++++--- freqtrade/exchange/exchange_utils.py | 10 +++++++--- freqtrade/exchange/types.py | 8 ++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 3358f8cc8..d81800896 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -28,15 +28,20 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: exchanges = validate_exchanges(args['list_exchanges_all']) if args['print_one_column']: - print('\n'.join([e[0] for e in exchanges])) + print('\n'.join([e['name'] for e in exchanges])) else: if args['list_exchanges_all']: print("All exchanges supported by the ccxt library:") else: print("Exchanges available for Freqtrade:") - exchanges = [e for e in exchanges if e[1] is not False] + exchanges = [e for e in exchanges if e['valid'] is not False] - print(tabulate(exchanges, headers=['Exchange name', 'Valid', 'reason'])) + headers = { + 'name': 'Exchange name', + 'valid': 'Valid', + 'comment': 'reason', + } + print(tabulate(exchanges, headers=headers)) def _print_objs_tabular(objs: List, print_colorized: bool) -> None: diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index c6c2d5a24..32a68a959 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -10,6 +10,7 @@ from ccxt import (DECIMAL_PLACES, ROUND, ROUND_DOWN, ROUND_UP, SIGNIFICANT_DIGIT TRUNCATE, decimal_to_precision) from freqtrade.exchange.common import BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED +from freqtrade.exchange.types import ValidExchangesType from freqtrade.util import FtPrecise from freqtrade.util.datetime_helpers import dt_from_ts, dt_ts @@ -55,14 +56,17 @@ def validate_exchange(exchange: str) -> Tuple[bool, str]: return True, '' -def validate_exchanges(all_exchanges: bool) -> List[Tuple[str, bool, str]]: +def validate_exchanges(all_exchanges: bool) -> List[ValidExchangesType]: """ :return: List of tuples with exchangename, valid, reason. """ exchanges = ccxt_exchanges() if all_exchanges else available_exchanges() - exchanges_valid = [ - (e, *validate_exchange(e)) for e in exchanges + + exchanges_valid: List[ValidExchangesType] = [ + {'name': e, 'valid': valid, 'comment': comment} + for e, valid, comment in ((e, *validate_exchange(e)) for e in exchanges) ] + return exchanges_valid diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py index 5568e4336..b20c51201 100644 --- a/freqtrade/exchange/types.py +++ b/freqtrade/exchange/types.py @@ -26,5 +26,13 @@ class OrderBook(TypedDict): Tickers = Dict[str, Ticker] + +# Used for list-exchanges +class ValidExchangesType(TypedDict): + name: str + valid: bool + comment: str + + # pair, timeframe, candleType, OHLCV, drop last?, OHLCVResponse = Tuple[str, str, CandleType, List, bool] From 250ae2d0061cd60bd121a4658a5013b3930feeb0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 08:28:44 +0200 Subject: [PATCH 05/13] Enhance list-exchanges with more information --- freqtrade/commands/list_commands.py | 34 +++++++++++++++++------- freqtrade/exchange/exchange_utils.py | 31 ++++++++++++++++++--- freqtrade/resolvers/exchange_resolver.py | 26 +++++++++++++++++- freqtrade/resolvers/iresolver.py | 2 +- 4 files changed, 79 insertions(+), 14 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index d81800896..dadab7b9b 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -30,18 +30,34 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: if args['print_one_column']: print('\n'.join([e['name'] for e in exchanges])) else: - if args['list_exchanges_all']: - print("All exchanges supported by the ccxt library:") - else: - print("Exchanges available for Freqtrade:") - exchanges = [e for e in exchanges if e['valid'] is not False] - headers = { 'name': 'Exchange name', 'valid': 'Valid', - 'comment': 'reason', - } - print(tabulate(exchanges, headers=headers)) + 'supported': 'Supported', + 'trade_modes': 'Markets', + 'comment': 'Reason', + } + + def build_entry(exchange, valid): + valid_entry = {'valid': exchange['valid']} if valid else {} + result = { + 'name': exchange['name'], + **valid_entry, + 'supported': 'Official' if exchange['supported'] else '', + 'trade_modes': ', '.join(exchange['trade_modes']), + 'comment': exchange['comment'], + } + + return result + + if args['list_exchanges_all']: + print("All exchanges supported by the ccxt library:") + exchanges = [build_entry(e, True) for e in exchanges] + else: + print("Exchanges available for Freqtrade:") + exchanges = [build_entry(e, False) for e in exchanges if e['valid'] is not False] + + print(tabulate(exchanges, headers=headers, )) def _print_objs_tabular(objs: List, print_colorized: bool) -> None: diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 32a68a959..05f701136 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -9,7 +9,8 @@ import ccxt from ccxt import (DECIMAL_PLACES, ROUND, ROUND_DOWN, ROUND_UP, SIGNIFICANT_DIGITS, TICK_SIZE, TRUNCATE, decimal_to_precision) -from freqtrade.exchange.common import BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED +from freqtrade.exchange.common import (BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED, + SUPPORTED_EXCHANGES) from freqtrade.exchange.types import ValidExchangesType from freqtrade.util import FtPrecise from freqtrade.util.datetime_helpers import dt_from_ts, dt_ts @@ -56,15 +57,39 @@ def validate_exchange(exchange: str) -> Tuple[bool, str]: return True, '' +def build_exchange_list_entry( + exchange_name: str, exchangeClasses: Dict[str, Any]) -> ValidExchangesType: + valid, comment = validate_exchange(exchange_name) + result = { + 'name': exchange_name, + 'valid': valid, + 'supported': exchange_name.lower() in SUPPORTED_EXCHANGES, + 'comment': comment, + 'trade_modes': ['spot'], + } + if resolved := exchangeClasses.get(exchange_name.lower()): + supported_modes = ['spot'] + [ + f"{mm.value} {tm.value}" + for tm, mm in resolved['class']._supported_trading_mode_margin_pairs + ] + result.update({ + 'trade_modes': supported_modes, + }) + + return result + + def validate_exchanges(all_exchanges: bool) -> List[ValidExchangesType]: """ :return: List of tuples with exchangename, valid, reason. """ exchanges = ccxt_exchanges() if all_exchanges else available_exchanges() + from freqtrade.resolvers.exchange_resolver import ExchangeResolver + + subclassed = {e['name'].lower(): e for e in ExchangeResolver.search_all_objects({}, False)} exchanges_valid: List[ValidExchangesType] = [ - {'name': e, 'valid': valid, 'comment': comment} - for e, valid, comment in ((e, *validate_exchange(e)) for e in exchanges) + build_exchange_list_entry(e, subclassed) for e in exchanges ] return exchanges_valid diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py index c5c4e1a68..2f912c4ab 100644 --- a/freqtrade/resolvers/exchange_resolver.py +++ b/freqtrade/resolvers/exchange_resolver.py @@ -2,7 +2,8 @@ This module loads custom exchanges """ import logging -from typing import Optional +from inspect import isclass +from typing import Any, Dict, List, Optional import freqtrade.exchange as exchanges from freqtrade.constants import Config, ExchangeConfig @@ -72,3 +73,26 @@ class ExchangeResolver(IResolver): f"Impossible to load Exchange '{exchange_name}'. This class does not exist " "or contains Python code errors." ) + + @classmethod + def search_all_objects(cls, config: Config, enum_failed: bool, + recursive: bool = False) -> List[Dict[str, Any]]: + """ + Searches for valid objects + :param config: Config object + :param enum_failed: If True, will return None for modules which fail. + Otherwise, failing modules are skipped. + :param recursive: Recursively walk directory tree searching for strategies + :return: List of dicts containing 'name', 'class' and 'location' entries + """ + result = [] + for exchange_name in dir(exchanges): + exchange = getattr(exchanges, exchange_name) + if isclass(exchange) and issubclass(exchange, Exchange): + result.append({ + 'name': exchange_name, + 'class': exchange, + 'location': exchange.__module__, + 'location_rel: ': exchange.__module__.replace('freqtrade.', ''), + }) + return result diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 2b20560e2..1557f0f35 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -41,7 +41,7 @@ class IResolver: object_type: Type[Any] object_type_str: str user_subdir: Optional[str] = None - initial_search_path: Optional[Path] + initial_search_path: Optional[Path] = None # Optional config setting containing a path (strategy_path, freqaimodel_path) extra_path: Optional[str] = None From fcb960185eb350daa25178b1d390cb9992378898 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 08:36:14 +0200 Subject: [PATCH 06/13] Clarify function naming --- freqtrade/commands/list_commands.py | 4 ++-- freqtrade/exchange/__init__.py | 10 +++++----- freqtrade/exchange/exchange_utils.py | 2 +- tests/test_main.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index dadab7b9b..2970c1fc3 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -11,7 +11,7 @@ from tabulate import tabulate from freqtrade.configuration import setup_utils_configuration from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException -from freqtrade.exchange import market_is_active, validate_exchanges +from freqtrade.exchange import list_available_exchanges, market_is_active from freqtrade.misc import parse_db_uri_for_logging, plural from freqtrade.resolvers import ExchangeResolver, StrategyResolver @@ -25,7 +25,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: :param args: Cli args from Arguments() :return: None """ - exchanges = validate_exchanges(args['list_exchanges_all']) + exchanges = list_available_exchanges(args['list_exchanges_all']) if args['print_one_column']: print('\n'.join([e['name'] for e in exchanges])) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 12fb0c55e..9ac31a0d8 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -13,11 +13,11 @@ from freqtrade.exchange.exchange_utils import (ROUND_DOWN, ROUND_UP, amount_to_c amount_to_contracts, amount_to_precision, available_exchanges, ccxt_exchanges, contracts_to_amount, date_minus_candles, - is_exchange_known_ccxt, market_is_active, - price_to_precision, timeframe_to_minutes, - timeframe_to_msecs, timeframe_to_next_date, - timeframe_to_prev_date, timeframe_to_seconds, - validate_exchange, validate_exchanges) + is_exchange_known_ccxt, list_available_exchanges, + market_is_active, price_to_precision, + timeframe_to_minutes, timeframe_to_msecs, + timeframe_to_next_date, timeframe_to_prev_date, + timeframe_to_seconds, validate_exchange) from freqtrade.exchange.gate import Gate from freqtrade.exchange.hitbtc import Hitbtc from freqtrade.exchange.huobi import Huobi diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 05f701136..cbb7f7733 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -79,7 +79,7 @@ def build_exchange_list_entry( return result -def validate_exchanges(all_exchanges: bool) -> List[ValidExchangesType]: +def list_available_exchanges(all_exchanges: bool) -> List[ValidExchangesType]: """ :return: List of tuples with exchangename, valid, reason. """ diff --git a/tests/test_main.py b/tests/test_main.py index 59a5bb0f7..bdb3c2bba 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -118,7 +118,7 @@ def test_main_operational_exception(mocker, default_conf, caplog) -> None: def test_main_operational_exception1(mocker, default_conf, caplog) -> None: patch_exchange(mocker) mocker.patch( - 'freqtrade.commands.list_commands.validate_exchanges', + 'freqtrade.commands.list_commands.list_available_exchanges', MagicMock(side_effect=ValueError('Oh snap!')) ) patched_configuration_load_config_file(mocker, default_conf) @@ -132,7 +132,7 @@ def test_main_operational_exception1(mocker, default_conf, caplog) -> None: assert log_has('Fatal exception!', caplog) assert not log_has_re(r'SIGINT.*', caplog) mocker.patch( - 'freqtrade.commands.list_commands.validate_exchanges', + 'freqtrade.commands.list_commands.list_available_exchanges', MagicMock(side_effect=KeyboardInterrupt) ) with pytest.raises(SystemExit): From cc04f3279ae31339c1177d8143fd7d41d60540ce Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 08:47:46 +0200 Subject: [PATCH 07/13] bump pre-commit mypy version --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eb50a3a3f..67e7ece19 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: # stages: [push] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.0.1" + rev: "v1.3.0" hooks: - id: mypy exclude: build_helpers From 6f928b826f8f9a75072c86ca1cb9a3d027882611 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 08:49:16 +0200 Subject: [PATCH 08/13] Update types for build_exchange_list_entry --- freqtrade/exchange/exchange_utils.py | 6 +++--- freqtrade/exchange/types.py | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index cbb7f7733..a57b2406c 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -57,10 +57,10 @@ def validate_exchange(exchange: str) -> Tuple[bool, str]: return True, '' -def build_exchange_list_entry( +def _build_exchange_list_entry( exchange_name: str, exchangeClasses: Dict[str, Any]) -> ValidExchangesType: valid, comment = validate_exchange(exchange_name) - result = { + result: ValidExchangesType = { 'name': exchange_name, 'valid': valid, 'supported': exchange_name.lower() in SUPPORTED_EXCHANGES, @@ -89,7 +89,7 @@ def list_available_exchanges(all_exchanges: bool) -> List[ValidExchangesType]: subclassed = {e['name'].lower(): e for e in ExchangeResolver.search_all_objects({}, False)} exchanges_valid: List[ValidExchangesType] = [ - build_exchange_list_entry(e, subclassed) for e in exchanges + _build_exchange_list_entry(e, subclassed) for e in exchanges ] return exchanges_valid diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py index b20c51201..13030986e 100644 --- a/freqtrade/exchange/types.py +++ b/freqtrade/exchange/types.py @@ -31,7 +31,9 @@ Tickers = Dict[str, Ticker] class ValidExchangesType(TypedDict): name: str valid: bool + supported: bool comment: str + trade_modes: List[str] # pair, timeframe, candleType, OHLCV, drop last?, From 54bf1634c7c51acdb35134056336056a152d19bb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 09:04:40 +0200 Subject: [PATCH 09/13] Refactor validExchangesType to separate types package --- freqtrade/exchange/exchange_utils.py | 2 +- freqtrade/exchange/types.py | 9 --------- freqtrade/types/__init__.py | 1 + freqtrade/types/valid_exchanges_type.py | 10 ++++++++++ 4 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 freqtrade/types/__init__.py create mode 100644 freqtrade/types/valid_exchanges_type.py diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index a57b2406c..1f1e926ee 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -11,7 +11,7 @@ from ccxt import (DECIMAL_PLACES, ROUND, ROUND_DOWN, ROUND_UP, SIGNIFICANT_DIGIT from freqtrade.exchange.common import (BAD_EXCHANGES, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED, SUPPORTED_EXCHANGES) -from freqtrade.exchange.types import ValidExchangesType +from freqtrade.types import ValidExchangesType from freqtrade.util import FtPrecise from freqtrade.util.datetime_helpers import dt_from_ts, dt_ts diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py index 13030986e..1247e5754 100644 --- a/freqtrade/exchange/types.py +++ b/freqtrade/exchange/types.py @@ -27,14 +27,5 @@ class OrderBook(TypedDict): Tickers = Dict[str, Ticker] -# Used for list-exchanges -class ValidExchangesType(TypedDict): - name: str - valid: bool - supported: bool - comment: str - trade_modes: List[str] - - # pair, timeframe, candleType, OHLCV, drop last?, OHLCVResponse = Tuple[str, str, CandleType, List, bool] diff --git a/freqtrade/types/__init__.py b/freqtrade/types/__init__.py new file mode 100644 index 000000000..11fe6354b --- /dev/null +++ b/freqtrade/types/__init__.py @@ -0,0 +1 @@ +from freqtrade.types.valid_exchanges_type import ValidExchangesType # noqa: F401 diff --git a/freqtrade/types/valid_exchanges_type.py b/freqtrade/types/valid_exchanges_type.py new file mode 100644 index 000000000..f40c3fb30 --- /dev/null +++ b/freqtrade/types/valid_exchanges_type.py @@ -0,0 +1,10 @@ +# Used for list-exchanges +from typing import List, TypedDict + + +class ValidExchangesType(TypedDict): + name: str + valid: bool + supported: bool + comment: str + trade_modes: List[str] From 74254bb8936f9da1ee0f90f89b02d77eae3664f3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 09:20:01 +0200 Subject: [PATCH 10/13] Add /exchanges endpoint to list available exchanges --- freqtrade/rpc/api_server/api_schemas.py | 5 ++++ freqtrade/rpc/api_server/api_v1.py | 12 ++++++++- tests/rpc/test_rpc_apiserver.py | 33 +++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index a081f9fe9..e218465fc 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -5,6 +5,7 @@ from pydantic import BaseModel from freqtrade.constants import DATETIME_PRINT_FORMAT, IntOrInf from freqtrade.enums import OrderTypeValues, SignalDirection, TradingMode +from freqtrade.types import ValidExchangesType class Ping(BaseModel): @@ -396,6 +397,10 @@ class StrategyListResponse(BaseModel): strategies: List[str] +class ExchangeListResponse(BaseModel): + exchanges: List[ValidExchangesType] + + class FreqAIModelListResponse(BaseModel): freqaimodels: List[str] diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 2354c4bf8..6af6d7709 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -12,7 +12,8 @@ from freqtrade.exceptions import OperationalException from freqtrade.rpc import RPC from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, BlacklistResponse, Count, Daily, - DeleteLockRequest, DeleteTrade, ForceEnterPayload, + DeleteLockRequest, DeleteTrade, + ExchangeListResponse, ForceEnterPayload, ForceEnterResponse, ForceExitPayload, FreqAIModelListResponse, Health, Locks, Logs, OpenTradeSchema, PairHistory, PerformanceEntry, @@ -312,6 +313,15 @@ def get_strategy(strategy: str, config=Depends(get_config)): } +@router.get('/exchanges', response_model=ExchangeListResponse, tags=[]) +def list_exchanges(config=Depends(get_config)): + from freqtrade.exchange import list_available_exchanges + exchanges = list_available_exchanges(config) + return { + 'exchanges': exchanges, + } + + @router.get('/freqaimodels', response_model=FreqAIModelListResponse, tags=['freqai']) def list_freqaimodels(config=Depends(get_config)): from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cdf620b90..8377de547 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1578,6 +1578,38 @@ def test_api_strategy(botclient): assert_response(rc, 500) +def test_api_exchanges(botclient): + ftbot, client = botclient + + rc = client_get(client, f"{BASE_URI}/exchanges") + assert_response(rc) + response = rc.json() + assert isinstance(response['exchanges'], list) + assert len(response['exchanges']) > 20 + okx = [x for x in response['exchanges'] if x['name'] == 'okx'][0] + assert okx == { + "name": "okx", + "valid": True, + "supported": True, + "comment": "", + "trade_modes": [ + "spot", + "isolated futures", + ] + } + + mexc = [x for x in response['exchanges'] if x['name'] == 'mexc'][0] + assert mexc == { + "name": "mexc", + "valid": True, + "supported": False, + "comment": "", + "trade_modes": [ + "spot", + ] + } + + def test_api_freqaimodels(botclient, tmpdir, mocker): ftbot, client = botclient ftbot.config['user_data_dir'] = Path(tmpdir) @@ -1933,3 +1965,4 @@ def test_api_ws_send_msg(default_conf, mocker, caplog): finally: ApiServer.shutdown() + ApiServer.shutdown() From 72f4e1475c968923a9b476cbee4dd89c69808048 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 09:20:36 +0200 Subject: [PATCH 11/13] Bump api version --- 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 6af6d7709..6c0ea04aa 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -47,7 +47,8 @@ logger = logging.getLogger(__name__) # 2.26: increase /balance output # 2.27: Add /trades//reload endpoint # 2.28: Switch reload endpoint to Post -API_VERSION = 2.28 +# 2.29: Add /exchanges endpoint +API_VERSION = 2.29 # Public API, requires no auth. router_public = APIRouter() From ac7419e9754e97678b93ac5644f175dcd92edbca Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 11:46:01 +0200 Subject: [PATCH 12/13] Split trademode response value into trade_mode and margin-mode --- freqtrade/commands/list_commands.py | 12 ++++++++---- freqtrade/exchange/exchange_utils.py | 6 +++--- freqtrade/exchange/types.py | 1 - freqtrade/types/valid_exchanges_type.py | 7 ++++++- tests/rpc/test_rpc_apiserver.py | 17 +++++++++++++---- 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 2970c1fc3..dcb102ce5 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -1,7 +1,7 @@ import csv import logging import sys -from typing import Any, Dict, List +from typing import Any, Dict, List, Union import rapidjson from colorama import Fore, Style @@ -14,6 +14,7 @@ from freqtrade.exceptions import OperationalException from freqtrade.exchange import list_available_exchanges, market_is_active from freqtrade.misc import parse_db_uri_for_logging, plural from freqtrade.resolvers import ExchangeResolver, StrategyResolver +from freqtrade.types import ValidExchangesType logger = logging.getLogger(__name__) @@ -38,13 +39,16 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: 'comment': 'Reason', } - def build_entry(exchange, valid): + def build_entry(exchange: ValidExchangesType, valid: bool): valid_entry = {'valid': exchange['valid']} if valid else {} - result = { + result: Dict[str, Union[str, bool]] = { 'name': exchange['name'], **valid_entry, 'supported': 'Official' if exchange['supported'] else '', - 'trade_modes': ', '.join(exchange['trade_modes']), + 'trade_modes': ', '.join( + (f"{a['margin_mode']} " if a['margin_mode'] else '') + a['trading_mode'] + for a in exchange['trade_modes'] + ), 'comment': exchange['comment'], } diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py index 1f1e926ee..fe7264dd9 100644 --- a/freqtrade/exchange/exchange_utils.py +++ b/freqtrade/exchange/exchange_utils.py @@ -65,11 +65,11 @@ def _build_exchange_list_entry( 'valid': valid, 'supported': exchange_name.lower() in SUPPORTED_EXCHANGES, 'comment': comment, - 'trade_modes': ['spot'], + 'trade_modes': [{'trading_mode': 'spot', 'margin_mode': ''}], } if resolved := exchangeClasses.get(exchange_name.lower()): - supported_modes = ['spot'] + [ - f"{mm.value} {tm.value}" + supported_modes = [{'trading_mode': 'spot', 'margin_mode': ''}] + [ + {'trading_mode': tm.value, 'margin_mode': mm.value} for tm, mm in resolved['class']._supported_trading_mode_margin_pairs ] result.update({ diff --git a/freqtrade/exchange/types.py b/freqtrade/exchange/types.py index 1247e5754..5568e4336 100644 --- a/freqtrade/exchange/types.py +++ b/freqtrade/exchange/types.py @@ -26,6 +26,5 @@ class OrderBook(TypedDict): Tickers = Dict[str, Ticker] - # pair, timeframe, candleType, OHLCV, drop last?, OHLCVResponse = Tuple[str, str, CandleType, List, bool] diff --git a/freqtrade/types/valid_exchanges_type.py b/freqtrade/types/valid_exchanges_type.py index f40c3fb30..0f02b1f5d 100644 --- a/freqtrade/types/valid_exchanges_type.py +++ b/freqtrade/types/valid_exchanges_type.py @@ -2,9 +2,14 @@ from typing import List, TypedDict +class TradeModeType(TypedDict): + trading_mode: str + margin_mode: str + + class ValidExchangesType(TypedDict): name: str valid: bool supported: bool comment: str - trade_modes: List[str] + trade_modes: List[TradeModeType] diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 8377de547..ac7904515 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1593,8 +1593,14 @@ def test_api_exchanges(botclient): "supported": True, "comment": "", "trade_modes": [ - "spot", - "isolated futures", + { + "trading_mode": "spot", + "margin_mode": "" + }, + { + "trading_mode": "futures", + "margin_mode": "isolated" + } ] } @@ -1605,8 +1611,11 @@ def test_api_exchanges(botclient): "supported": False, "comment": "", "trade_modes": [ - "spot", - ] + { + "trading_mode": "spot", + "margin_mode": "" + } + ] } From 12e31208e126ba5ecde2e0f561a57954935a81a9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 3 Jun 2023 12:33:44 +0200 Subject: [PATCH 13/13] Update typedDict type used with pydantic --- freqtrade/types/valid_exchanges_type.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/types/valid_exchanges_type.py b/freqtrade/types/valid_exchanges_type.py index 0f02b1f5d..c01149455 100644 --- a/freqtrade/types/valid_exchanges_type.py +++ b/freqtrade/types/valid_exchanges_type.py @@ -1,5 +1,7 @@ # Used for list-exchanges -from typing import List, TypedDict +from typing import List + +from typing_extensions import TypedDict class TradeModeType(TypedDict):