From 7d5c88393b8c82045a49da16ebac5cd1393fdf20 Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 18 Sep 2024 23:42:44 +0300 Subject: [PATCH 01/26] feat: support `account-id` config --- lean/models/api.py | 1 + lean/models/configuration.py | 31 +++++++++++++++++++++++++++++++ lean/models/json_module.py | 12 +++++++++++- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/lean/models/api.py b/lean/models/api.py index 14851de3..66c3cb30 100644 --- a/lean/models/api.py +++ b/lean/models/api.py @@ -23,6 +23,7 @@ class QCAuth0Authorization(WrappedBaseModel): authorization: Optional[Dict[str, str]] + accountIds: Optional[List[str]] class ProjectEncryptionKey(WrappedBaseModel): id: str diff --git a/lean/models/configuration.py b/lean/models/configuration.py index 867abb29..949fd0c3 100644 --- a/lean/models/configuration.py +++ b/lean/models/configuration.py @@ -121,6 +121,8 @@ def factory(config_json_object) -> 'Configuration': return BrokerageEnvConfiguration.factory(config_json_object) elif config_json_object["type"] == "oauth-token": return AuthConfiguration.factory(config_json_object) + elif config_json_object["type"] == "input-account-id": + return AccountIdsConfiguration.factory(config_json_object) else: raise ValueError( f'Undefined input method type {config_json_object["type"]}') @@ -419,6 +421,35 @@ def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = F raise ValueError(f'user input not allowed with {self.__class__.__name__}') +class AccountIdsConfiguration(InternalInputUserInput): + + def __init__(self, config_json_object): + super().__init__(config_json_object) + + def factory(config_json_object) -> 'AccountIdsConfiguration': + """Creates an instance of the child classes. + + :param config_json_object: the json object dict with configuration info + :return: An instance of AuthConfiguration. + """ + if config_json_object["type"] == "input-account-id": + return AccountIdsConfiguration(config_json_object) + else: + raise ValueError( + f'Undefined input method type {config_json_object["type"]}') + + def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = False): + """Prompts user to provide input while validating the type of input + against the expected type + + :param default_value: The default to prompt to the user. + :param logger: The instance of logger class. + :param hide_input: Whether to hide the input (not used for this type of input, which is never hidden). + :return: The value provided by the user. + """ + raise ValueError(f'user input not allowed with {self.__class__.__name__}') + + class FilterEnvConfiguration(BrokerageEnvConfiguration): """This class adds extra filters to user filters.""" diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 8b18f4ef..ec1d8b9c 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -21,8 +21,9 @@ from lean.components.util.logger import Logger from lean.constants import MODULE_TYPE, MODULE_PLATFORM, MODULE_CLI_PLATFORM from lean.container import container +from lean.models.logger import Option from lean.models.configuration import BrokerageEnvConfiguration, Configuration, InternalInputUserInput, \ - PathParameterUserInput, AuthConfiguration + PathParameterUserInput, AuthConfiguration, AccountIdsConfiguration from copy import copy from abc import ABC @@ -212,6 +213,15 @@ def config_build(self, logger.debug(f'auth: {auth_authorizations}') configuration._value = auth_authorizations.authorization continue + elif isinstance(configuration, AccountIdsConfiguration): + account_ids = get_authorization(container.api_client.auth0, self._display_name.lower(), logger).accountIds + if account_ids and len(account_ids) > 0: + logger.debug(f'accountIds: {account_ids}') + options = [Option(id=data_type, label=data_type) for data_type in account_ids] + account_id = container.logger.prompt_list(f"Chosen {self._display_name.lower()} account ID", options) + logger.debug(f'user choose account ID: {account_id}') + configuration._value = account_id + continue property_name = self.convert_lean_key_to_variable(configuration._id) # Only ask for user input if the config wasn't given as an option From 0b4ef9826eebceea02cde2b749854ebc8aa9cfd2 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 19 Sep 2024 01:44:12 +0300 Subject: [PATCH 02/26] refactor: reuse interactive logic of `input-account-id` --- lean/models/configuration.py | 7 +++++-- lean/models/json_module.py | 9 +++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lean/models/configuration.py b/lean/models/configuration.py index 949fd0c3..7faf5334 100644 --- a/lean/models/configuration.py +++ b/lean/models/configuration.py @@ -421,7 +421,7 @@ def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = F raise ValueError(f'user input not allowed with {self.__class__.__name__}') -class AccountIdsConfiguration(InternalInputUserInput): +class AccountIdsConfiguration(ChoiceUserInput): def __init__(self, config_json_object): super().__init__(config_json_object) @@ -447,7 +447,10 @@ def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = F :param hide_input: Whether to hide the input (not used for this type of input, which is never hidden). :return: The value provided by the user. """ - raise ValueError(f'user input not allowed with {self.__class__.__name__}') + if self._input_method == "choice": + return ChoiceUserInput.ask_user_for_input(self, default_value, logger) + else: + raise ValueError(f"Undefined input method type {self._input_method}") class FilterEnvConfiguration(BrokerageEnvConfiguration): diff --git a/lean/models/json_module.py b/lean/models/json_module.py index ec1d8b9c..46b4fc7a 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -216,12 +216,9 @@ def config_build(self, elif isinstance(configuration, AccountIdsConfiguration): account_ids = get_authorization(container.api_client.auth0, self._display_name.lower(), logger).accountIds if account_ids and len(account_ids) > 0: - logger.debug(f'accountIds: {account_ids}') - options = [Option(id=data_type, label=data_type) for data_type in account_ids] - account_id = container.logger.prompt_list(f"Chosen {self._display_name.lower()} account ID", options) - logger.debug(f'user choose account ID: {account_id}') - configuration._value = account_id - continue + configuration._choices = account_ids + elif configuration._optional: + continue property_name = self.convert_lean_key_to_variable(configuration._id) # Only ask for user input if the config wasn't given as an option From 0b05cfe4035cf210a9ea19b11f3e3ddfad7f8bb4 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 19 Sep 2024 02:28:09 +0300 Subject: [PATCH 03/26] refactor: update account_ids in AuthConfiguration feat: skip account ids if it is not provided --- lean/models/json_module.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 46b4fc7a..001d6ae0 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -212,13 +212,16 @@ def config_build(self, auth_authorizations = get_authorization(container.api_client.auth0, self._display_name.lower(), logger) logger.debug(f'auth: {auth_authorizations}') configuration._value = auth_authorizations.authorization + for config in self._lean_configs: + if isinstance(config, AccountIdsConfiguration): + account_ids = auth_authorizations.accountIds + if account_ids and len(account_ids) > 0: + config._choices = account_ids + break + continue + elif (isinstance(configuration, AccountIdsConfiguration) and configuration._optional + and not configuration._choices): continue - elif isinstance(configuration, AccountIdsConfiguration): - account_ids = get_authorization(container.api_client.auth0, self._display_name.lower(), logger).accountIds - if account_ids and len(account_ids) > 0: - configuration._choices = account_ids - elif configuration._optional: - continue property_name = self.convert_lean_key_to_variable(configuration._id) # Only ask for user input if the config wasn't given as an option From ec72de207c20c5d2e26168d47bbae2f202afbea8 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 19 Sep 2024 18:22:01 +0300 Subject: [PATCH 04/26] test:feat: Auth0Client --- tests/components/api/test_auth0_client.py | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/components/api/test_auth0_client.py diff --git a/tests/components/api/test_auth0_client.py b/tests/components/api/test_auth0_client.py new file mode 100644 index 00000000..aabd82af --- /dev/null +++ b/tests/components/api/test_auth0_client.py @@ -0,0 +1,47 @@ +# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. +# Lean CLI v1.0. Copyright 2021 QuantConnect Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import responses +from lean.constants import API_BASE_URL +from components.api.test_clients import create_api_client + + +@responses.activate +def test_auth0client() -> None: + os.environ.setdefault("QC_API", "local") + api_clint = create_api_client() + + responses.add( + responses.POST, + f"{API_BASE_URL}live/auth0/read", + json={ + "authorization": { + "test-brokerage-client-id": "123", + "test-brokerage-refresh-token": "123" + }, + "accountIds": ["123", "321"], + "success": "true" + }, + status=200 + ) + + brokerage_id = "TestBrokerage" + + result = api_clint.auth0.read(brokerage_id) + + assert result + assert result.authorization + assert len(result.authorization) > 0 + assert result.accountIds + assert len(result.accountIds) > 0 From 31428be9e06810a4548250c5fa323bf0bbae642c Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 19 Sep 2024 23:27:07 +0300 Subject: [PATCH 05/26] feat: support Prompt in AccountIdsConfiguration --- lean/models/configuration.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lean/models/configuration.py b/lean/models/configuration.py index 7faf5334..23d25cf2 100644 --- a/lean/models/configuration.py +++ b/lean/models/configuration.py @@ -421,7 +421,7 @@ def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = F raise ValueError(f'user input not allowed with {self.__class__.__name__}') -class AccountIdsConfiguration(ChoiceUserInput): +class AccountIdsConfiguration(PromptUserInput, ChoiceUserInput): def __init__(self, config_json_object): super().__init__(config_json_object) @@ -447,7 +447,9 @@ def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = F :param hide_input: Whether to hide the input (not used for this type of input, which is never hidden). :return: The value provided by the user. """ - if self._input_method == "choice": + if self._input_method == "prompt": + return PromptUserInput.ask_user_for_input(self, default_value, logger) + elif self._input_method == "choice": return ChoiceUserInput.ask_user_for_input(self, default_value, logger) else: raise ValueError(f"Undefined input method type {self._input_method}") From 977b7af93e245efa7eb8f1cc0eb579074faebf92 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 19 Sep 2024 23:28:05 +0300 Subject: [PATCH 06/26] feat: handle accountIds and provide different option to user --- lean/models/json_module.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 001d6ae0..196f696f 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -212,15 +212,30 @@ def config_build(self, auth_authorizations = get_authorization(container.api_client.auth0, self._display_name.lower(), logger) logger.debug(f'auth: {auth_authorizations}') configuration._value = auth_authorizations.authorization - for config in self._lean_configs: - if isinstance(config, AccountIdsConfiguration): - account_ids = auth_authorizations.accountIds - if account_ids and len(account_ids) > 0: - config._choices = account_ids + for inner_config in self._lean_configs: + if isinstance(inner_config, AccountIdsConfiguration): + api_account_ids = auth_authorizations.accountIds + config_dash = inner_config._id.replace('-', '_') + if user_provided_options and config_dash in user_provided_options: + user_provide_account_id = user_provided_options[config_dash] + if any(account_id.lower() == user_provide_account_id.lower() for account_id in + api_account_ids): + logger.info(f'The account ID: {user_provide_account_id}') + inner_config._value = user_provide_account_id + else: + raise ValueError(f"The account ID '{user_provide_account_id}' " + f"you provided is not valid in the configuration '{inner_config._id}'." + f"Please double-check the account ID and try again.") + if api_account_ids and len(api_account_ids) > 0: + if len(api_account_ids) == 1: + logger.info(f'The account ID: {api_account_ids[0]}') + inner_config._value = api_account_ids[0] + else: + inner_config._input_method = "choice" + inner_config._choices = api_account_ids break continue - elif (isinstance(configuration, AccountIdsConfiguration) and configuration._optional - and not configuration._choices): + elif isinstance(configuration, AccountIdsConfiguration) and inner_config._input_method != "choice": continue property_name = self.convert_lean_key_to_variable(configuration._id) From cc9c5fb6f565f5c5bf7c2be20dd204beb4274c8a Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 19 Sep 2024 23:55:28 +0300 Subject: [PATCH 07/26] refactor: handle of accountIds in json_module --- lean/models/json_module.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 196f696f..6b937457 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -223,9 +223,8 @@ def config_build(self, logger.info(f'The account ID: {user_provide_account_id}') inner_config._value = user_provide_account_id else: - raise ValueError(f"The account ID '{user_provide_account_id}' " - f"you provided is not valid in the configuration '{inner_config._id}'." - f"Please double-check the account ID and try again.") + raise ValueError(f"The provided account id '{user_provide_account_id} is not valid, " + f"available: {api_account_ids}") if api_account_ids and len(api_account_ids) > 0: if len(api_account_ids) == 1: logger.info(f'The account ID: {api_account_ids[0]}') @@ -233,9 +232,12 @@ def config_build(self, else: inner_config._input_method = "choice" inner_config._choices = api_account_ids + inner_config._value = inner_config.ask_user_for_input(inner_config._input_default, + logger, + hide_input=hide_input) break continue - elif isinstance(configuration, AccountIdsConfiguration) and inner_config._input_method != "choice": + elif isinstance(configuration, AccountIdsConfiguration): continue property_name = self.convert_lean_key_to_variable(configuration._id) From 345e53916691082c665776630db1d5738e801dad Mon Sep 17 00:00:00 2001 From: Romazes Date: Fri, 20 Sep 2024 01:50:30 +0300 Subject: [PATCH 08/26] refactor: validate len api_accounts_ids refactor: use elif instead of if --- lean/models/json_module.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 6b937457..1460ff59 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -218,14 +218,15 @@ def config_build(self, config_dash = inner_config._id.replace('-', '_') if user_provided_options and config_dash in user_provided_options: user_provide_account_id = user_provided_options[config_dash] - if any(account_id.lower() == user_provide_account_id.lower() for account_id in - api_account_ids): + if (api_account_ids and len(api_account_ids) > 0 and + any(account_id.lower() == user_provide_account_id.lower() + for account_id in api_account_ids)): logger.info(f'The account ID: {user_provide_account_id}') inner_config._value = user_provide_account_id else: raise ValueError(f"The provided account id '{user_provide_account_id} is not valid, " f"available: {api_account_ids}") - if api_account_ids and len(api_account_ids) > 0: + elif api_account_ids and len(api_account_ids) > 0: if len(api_account_ids) == 1: logger.info(f'The account ID: {api_account_ids[0]}') inner_config._value = api_account_ids[0] From 19bac8a298864a3b2a39adb471bfb687400e9a49 Mon Sep 17 00:00:00 2001 From: Romazes Date: Fri, 20 Sep 2024 02:27:41 +0300 Subject: [PATCH 09/26] feat: handle when api return None of api_account_ids --- lean/models/json_module.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 1460ff59..24aadf73 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -223,6 +223,10 @@ def config_build(self, for account_id in api_account_ids)): logger.info(f'The account ID: {user_provide_account_id}') inner_config._value = user_provide_account_id + elif not api_account_ids: + logger.warn(f"The '{self._display_name}' oauth authentication returned no account id, " + f"using provided account id: '{user_provide_account_id}'") + inner_config._value = user_provide_account_id else: raise ValueError(f"The provided account id '{user_provide_account_id} is not valid, " f"available: {api_account_ids}") From 70c2b961ebbeb5f6d138d5bdcf9a24125c791cae Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 21 Sep 2024 00:35:34 +0300 Subject: [PATCH 10/26] feat: skip validation of choices (not provided) --- lean/models/click_options.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lean/models/click_options.py b/lean/models/click_options.py index 15d95349..65526334 100644 --- a/lean/models/click_options.py +++ b/lean/models/click_options.py @@ -56,6 +56,9 @@ def get_click_option_type(configuration: Configuration): if configuration._input_method == "confirm": return bool elif configuration._input_method == "choice": + # Skip validation if no predefined choices in config and user provided input manually + if not configuration._choices: + return str return Choice(configuration._choices, case_sensitive=False) elif configuration._input_method == "prompt": return configuration.get_input_type() From eaec7f6905c4c079bede497202cb376dabd23867 Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 21 Sep 2024 00:38:53 +0300 Subject: [PATCH 11/26] feat: filter dict config by regex condition --- lean/models/json_module.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 24aadf73..3833bb34 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -87,7 +87,11 @@ def _check_if_config_passes_filters(self, config: Configuration, all_for_platfor # skip, we want all configurations that match type and platform, for help continue target_value = self.get_config_value_from_name(condition._dependent_config_id) - if not target_value or not condition.check(target_value): + if not target_value: + return False + elif isinstance(target_value, dict): + return all(condition.check(value) for value in target_value.values()) + elif not condition.check(target_value): return False return True From b0b0195b80331b644d84bc687859dfe93ef0ea5c Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 21 Sep 2024 00:41:53 +0300 Subject: [PATCH 12/26] refactor: update of api_account_ids --- lean/models/json_module.py | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 3833bb34..f45cbee7 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -217,37 +217,20 @@ def config_build(self, logger.debug(f'auth: {auth_authorizations}') configuration._value = auth_authorizations.authorization for inner_config in self._lean_configs: - if isinstance(inner_config, AccountIdsConfiguration): + if any(condition._dependent_config_id == configuration._id for condition in + inner_config._filter._conditions): api_account_ids = auth_authorizations.accountIds config_dash = inner_config._id.replace('-', '_') + inner_config._choices = api_account_ids if user_provided_options and config_dash in user_provided_options: user_provide_account_id = user_provided_options[config_dash] if (api_account_ids and len(api_account_ids) > 0 and - any(account_id.lower() == user_provide_account_id.lower() - for account_id in api_account_ids)): - logger.info(f'The account ID: {user_provide_account_id}') - inner_config._value = user_provide_account_id - elif not api_account_ids: - logger.warn(f"The '{self._display_name}' oauth authentication returned no account id, " - f"using provided account id: '{user_provide_account_id}'") - inner_config._value = user_provide_account_id - else: - raise ValueError(f"The provided account id '{user_provide_account_id} is not valid, " + not any(account_id.lower() == user_provide_account_id.lower() + for account_id in api_account_ids)): + raise ValueError(f"The provided account id '{user_provide_account_id}' is not valid, " f"available: {api_account_ids}") - elif api_account_ids and len(api_account_ids) > 0: - if len(api_account_ids) == 1: - logger.info(f'The account ID: {api_account_ids[0]}') - inner_config._value = api_account_ids[0] - else: - inner_config._input_method = "choice" - inner_config._choices = api_account_ids - inner_config._value = inner_config.ask_user_for_input(inner_config._input_default, - logger, - hide_input=hide_input) break continue - elif isinstance(configuration, AccountIdsConfiguration): - continue property_name = self.convert_lean_key_to_variable(configuration._id) # Only ask for user input if the config wasn't given as an option From d5288defddc2c01aeac2b50654120c1b23f48f40 Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 21 Sep 2024 00:43:34 +0300 Subject: [PATCH 13/26] remove: not used class AccountIdsConfiguration --- lean/models/configuration.py | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/lean/models/configuration.py b/lean/models/configuration.py index 23d25cf2..867abb29 100644 --- a/lean/models/configuration.py +++ b/lean/models/configuration.py @@ -121,8 +121,6 @@ def factory(config_json_object) -> 'Configuration': return BrokerageEnvConfiguration.factory(config_json_object) elif config_json_object["type"] == "oauth-token": return AuthConfiguration.factory(config_json_object) - elif config_json_object["type"] == "input-account-id": - return AccountIdsConfiguration.factory(config_json_object) else: raise ValueError( f'Undefined input method type {config_json_object["type"]}') @@ -421,40 +419,6 @@ def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = F raise ValueError(f'user input not allowed with {self.__class__.__name__}') -class AccountIdsConfiguration(PromptUserInput, ChoiceUserInput): - - def __init__(self, config_json_object): - super().__init__(config_json_object) - - def factory(config_json_object) -> 'AccountIdsConfiguration': - """Creates an instance of the child classes. - - :param config_json_object: the json object dict with configuration info - :return: An instance of AuthConfiguration. - """ - if config_json_object["type"] == "input-account-id": - return AccountIdsConfiguration(config_json_object) - else: - raise ValueError( - f'Undefined input method type {config_json_object["type"]}') - - def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = False): - """Prompts user to provide input while validating the type of input - against the expected type - - :param default_value: The default to prompt to the user. - :param logger: The instance of logger class. - :param hide_input: Whether to hide the input (not used for this type of input, which is never hidden). - :return: The value provided by the user. - """ - if self._input_method == "prompt": - return PromptUserInput.ask_user_for_input(self, default_value, logger) - elif self._input_method == "choice": - return ChoiceUserInput.ask_user_for_input(self, default_value, logger) - else: - raise ValueError(f"Undefined input method type {self._input_method}") - - class FilterEnvConfiguration(BrokerageEnvConfiguration): """This class adds extra filters to user filters.""" From 886868a2a09be1e97ccba51fdecddc4882210784 Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 21 Sep 2024 00:44:22 +0300 Subject: [PATCH 14/26] remove: missed import class --- lean/models/json_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index f45cbee7..600bbe1e 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -23,7 +23,7 @@ from lean.container import container from lean.models.logger import Option from lean.models.configuration import BrokerageEnvConfiguration, Configuration, InternalInputUserInput, \ - PathParameterUserInput, AuthConfiguration, AccountIdsConfiguration + PathParameterUserInput, AuthConfiguration from copy import copy from abc import ABC From f9560fb83c76aa83d6bc192f2f2608b835d1590d Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 21 Sep 2024 00:45:38 +0300 Subject: [PATCH 15/26] remove: not used import --- lean/models/json_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 600bbe1e..a9160b29 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -21,7 +21,6 @@ from lean.components.util.logger import Logger from lean.constants import MODULE_TYPE, MODULE_PLATFORM, MODULE_CLI_PLATFORM from lean.container import container -from lean.models.logger import Option from lean.models.configuration import BrokerageEnvConfiguration, Configuration, InternalInputUserInput, \ PathParameterUserInput, AuthConfiguration from copy import copy From fd68fc0ca4fba171bbf828b4604e4b1d8481c8c8 Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 21 Sep 2024 01:36:47 +0300 Subject: [PATCH 16/26] test:refactor: test_auth0_client --- tests/components/api/test_auth0_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/components/api/test_auth0_client.py b/tests/components/api/test_auth0_client.py index aabd82af..eb5f58d1 100644 --- a/tests/components/api/test_auth0_client.py +++ b/tests/components/api/test_auth0_client.py @@ -11,16 +11,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import responses +from unittest import mock from lean.constants import API_BASE_URL -from components.api.test_clients import create_api_client +from lean.components.api.api_client import APIClient +from lean.components.util.http_client import HTTPClient @responses.activate def test_auth0client() -> None: - os.environ.setdefault("QC_API", "local") - api_clint = create_api_client() + api_clint = APIClient(mock.Mock(), HTTPClient(mock.Mock()), user_id="123", api_token="abc") responses.add( responses.POST, From a6b40373e84bde725bba8e937ee40aefe28eff1f Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 21 Sep 2024 02:05:18 +0300 Subject: [PATCH 17/26] feat: new config of TredeStation brokerage in README --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 5629fa17..2ef73011 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,8 @@ Options: The Open FIGI API key to use for mapping options --bybit-api-key TEXT Your Bybit API key --bybit-api-secret TEXT Your Bybit API secret + --trade-station-account-id TEXT + The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used --trade-station-account-type [Cash|Margin|Futures|DVP] @@ -433,6 +435,8 @@ Options: --bybit-api-secret TEXT Your Bybit API secret --bybit-vip-level [VIP0|VIP1|VIP2|VIP3|VIP4|VIP5|SupremeVIP|Pro1|Pro2|Pro3|Pro4|Pro5] Your Bybit VIP Level + --trade-station-account-id TEXT + The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used --trade-station-account-type [Cash|Margin|Futures|DVP] @@ -912,6 +916,8 @@ Options: The Open FIGI API key to use for mapping options --bybit-api-key TEXT Your Bybit API key --bybit-api-secret TEXT Your Bybit API secret + --trade-station-account-id TEXT + The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used --trade-station-account-type [Cash|Margin|Futures|DVP] @@ -1390,6 +1396,8 @@ Options: Your Bybit VIP Level --bybit-use-testnet [live|paper] Whether the testnet should be used + --trade-station-account-id TEXT + The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used --trade-station-account-type [Cash|Margin|Futures|DVP] @@ -1803,6 +1811,8 @@ Options: The Open FIGI API key to use for mapping options --bybit-api-key TEXT Your Bybit API key --bybit-api-secret TEXT Your Bybit API secret + --trade-station-account-id TEXT + The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used --trade-station-account-type [Cash|Margin|Futures|DVP] @@ -1975,6 +1985,8 @@ Options: The Open FIGI API key to use for mapping options --bybit-api-key TEXT Your Bybit API key --bybit-api-secret TEXT Your Bybit API secret + --trade-station-account-id TEXT + The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used --trade-station-account-type [Cash|Margin|Futures|DVP] From fe931440acf5543671ff23e75a830a58bab84e09 Mon Sep 17 00:00:00 2001 From: Romazes Date: Sat, 21 Sep 2024 02:10:53 +0300 Subject: [PATCH 18/26] refactor: change spacing --- lean/models/json_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index a9160b29..e8517774 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -89,7 +89,7 @@ def _check_if_config_passes_filters(self, config: Configuration, all_for_platfor if not target_value: return False elif isinstance(target_value, dict): - return all(condition.check(value) for value in target_value.values()) + return all(condition.check(value) for value in target_value.values()) elif not condition.check(target_value): return False return True From ae82cfce9f34aa91f68f5a18504c0137f014f565 Mon Sep 17 00:00:00 2001 From: Romazes Date: Mon, 23 Sep 2024 17:30:56 +0300 Subject: [PATCH 19/26] feat: class Accounts in auth0_client --- lean/models/api.py | 21 ++++++++++++++++++++- lean/models/json_module.py | 2 +- tests/components/api/test_auth0_client.py | 10 +++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/lean/models/api.py b/lean/models/api.py index 66c3cb30..6374a062 100644 --- a/lean/models/api.py +++ b/lean/models/api.py @@ -21,9 +21,28 @@ # The models in this module are all parts of responses from the QuantConnect API # The keys of properties are not changed, so they don't obey the rest of the project's naming conventions + +class Account(WrappedBaseModel): + id: str + name: str + + class QCAuth0Authorization(WrappedBaseModel): authorization: Optional[Dict[str, str]] - accountIds: Optional[List[str]] + accounts: Optional[List[Account]] + + def get_account_ids(self) -> List[str]: + """ + Retrieves a list of account IDs from the list of Account objects. + + This method returns only the 'id' values from each account in the 'accounts' list. + If there are no accounts, it returns an empty list. + + Returns: + List[str]: A list of account IDs. + """ + return [account.id for account in self.accounts] if self.accounts else [] + class ProjectEncryptionKey(WrappedBaseModel): id: str diff --git a/lean/models/json_module.py b/lean/models/json_module.py index e8517774..0f0fd7ec 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -218,7 +218,7 @@ def config_build(self, for inner_config in self._lean_configs: if any(condition._dependent_config_id == configuration._id for condition in inner_config._filter._conditions): - api_account_ids = auth_authorizations.accountIds + api_account_ids = auth_authorizations.get_account_ids() config_dash = inner_config._id.replace('-', '_') inner_config._choices = api_account_ids if user_provided_options and config_dash in user_provided_options: diff --git a/tests/components/api/test_auth0_client.py b/tests/components/api/test_auth0_client.py index eb5f58d1..64d66d63 100644 --- a/tests/components/api/test_auth0_client.py +++ b/tests/components/api/test_auth0_client.py @@ -30,7 +30,10 @@ def test_auth0client() -> None: "test-brokerage-client-id": "123", "test-brokerage-refresh-token": "123" }, - "accountIds": ["123", "321"], + "accounts": [ + {"name": "account_1", "id": "123"}, + {"name": "account_2", "id": "456"} + ], "success": "true" }, status=200 @@ -43,5 +46,6 @@ def test_auth0client() -> None: assert result assert result.authorization assert len(result.authorization) > 0 - assert result.accountIds - assert len(result.accountIds) > 0 + assert result.accounts + assert len(result.accounts) > 0 + assert len(result.get_account_ids()) > 0 From e7d212f5bb090763223c7b253400e694b1e2c94b Mon Sep 17 00:00:00 2001 From: Romazes Date: Mon, 23 Sep 2024 17:50:44 +0300 Subject: [PATCH 20/26] feat: skip choice type without choices --- lean/models/json_module.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 0f0fd7ec..4a87d1ac 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -22,7 +22,7 @@ from lean.constants import MODULE_TYPE, MODULE_PLATFORM, MODULE_CLI_PLATFORM from lean.container import container from lean.models.configuration import BrokerageEnvConfiguration, Configuration, InternalInputUserInput, \ - PathParameterUserInput, AuthConfiguration + PathParameterUserInput, AuthConfiguration, ChoiceUserInput from copy import copy from abc import ABC @@ -211,6 +211,9 @@ def config_build(self, _logged_messages.add(log_message) if type(configuration) is InternalInputUserInput: continue + if isinstance(configuration, ChoiceUserInput) and len(configuration._choices) == 0: + logger.debug(f"skipping configuration '{configuration._id}': no choices available.") + continue elif isinstance(configuration, AuthConfiguration): auth_authorizations = get_authorization(container.api_client.auth0, self._display_name.lower(), logger) logger.debug(f'auth: {auth_authorizations}') From 0bec06f1a76675a13dcf2ed643aa2c820fc98988 Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 1 Oct 2024 15:34:47 +0300 Subject: [PATCH 21/26] feat: new object account in Auth0 model refactor: parsing of new object Auth0 test:refactor: use new mock object of Auth0 --- lean/components/api/auth0_client.py | 2 +- lean/models/api.py | 30 ++++++++++++++++++++--- lean/models/json_module.py | 4 +-- tests/components/api/test_auth0_client.py | 22 ++++++++--------- 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/lean/components/api/auth0_client.py b/lean/components/api/auth0_client.py index f1350c1f..1f87eb98 100644 --- a/lean/components/api/auth0_client.py +++ b/lean/components/api/auth0_client.py @@ -45,7 +45,7 @@ def read(self, brokerage_id: str) -> QCAuth0Authorization: data = self._api.post("live/auth0/read", payload) # Store in cache - result = QCAuth0Authorization(**data) + result = QCAuth0Authorization.from_raw(data) self._cache[brokerage_id] = result return result except RequestFailedError as e: diff --git a/lean/models/api.py b/lean/models/api.py index 6374a062..3572d31b 100644 --- a/lean/models/api.py +++ b/lean/models/api.py @@ -27,9 +27,20 @@ class Account(WrappedBaseModel): name: str -class QCAuth0Authorization(WrappedBaseModel): - authorization: Optional[Dict[str, str]] - accounts: Optional[List[Account]] +class Authorization(WrappedBaseModel): + client_info: Dict[str, str] # Holds flexible client data like client ID and tokens + accounts: List[Account] # Holds account information + + @classmethod + def from_raw(cls, raw_data: Dict[str, any]) -> 'Authorization': + """ + Custom constructor to preprocess raw authorization data + Separates client info from accounts. + """ + # Separate the client info (non-list values) from accounts (list of accounts) + client_info = {k: v for k, v in raw_data.items() if not isinstance(v, list)} + accounts = raw_data.get('accounts', []) + return cls(client_info=client_info, accounts=accounts) def get_account_ids(self) -> List[str]: """ @@ -44,6 +55,19 @@ def get_account_ids(self) -> List[str]: return [account.id for account in self.accounts] if self.accounts else [] +class QCAuth0Authorization(WrappedBaseModel): + authorization: Optional[Authorization] + + @classmethod + def from_raw(cls, raw_data: Dict[str, any]) -> 'QCAuth0Authorization': + """ + Custom constructor to preprocess raw QCAuth0Authorization data + """ + authorization_data = raw_data.get('authorization', {}) + authorization = Authorization.from_raw(authorization_data) if authorization_data else None + return cls(authorization=authorization) + + class ProjectEncryptionKey(WrappedBaseModel): id: str name: str diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 4a87d1ac..95f5463a 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -217,11 +217,11 @@ def config_build(self, elif isinstance(configuration, AuthConfiguration): auth_authorizations = get_authorization(container.api_client.auth0, self._display_name.lower(), logger) logger.debug(f'auth: {auth_authorizations}') - configuration._value = auth_authorizations.authorization + configuration._value = auth_authorizations.authorization.client_info for inner_config in self._lean_configs: if any(condition._dependent_config_id == configuration._id for condition in inner_config._filter._conditions): - api_account_ids = auth_authorizations.get_account_ids() + api_account_ids = auth_authorizations.authorization.get_account_ids() config_dash = inner_config._id.replace('-', '_') inner_config._choices = api_account_ids if user_provided_options and config_dash in user_provided_options: diff --git a/tests/components/api/test_auth0_client.py b/tests/components/api/test_auth0_client.py index 64d66d63..9e9838be 100644 --- a/tests/components/api/test_auth0_client.py +++ b/tests/components/api/test_auth0_client.py @@ -27,15 +27,14 @@ def test_auth0client() -> None: f"{API_BASE_URL}live/auth0/read", json={ "authorization": { - "test-brokerage-client-id": "123", - "test-brokerage-refresh-token": "123" + "trade-station-client-id": "123", + "trade-station-refresh-token": "456", + "accounts": [ + {"id": "11223344", "name": "11223344 | Margin | USD"}, + {"id": "55667788", "name": "55667788 | Futures | USD"} + ] }, - "accounts": [ - {"name": "account_1", "id": "123"}, - {"name": "account_2", "id": "456"} - ], - "success": "true" - }, + "success": "true"}, status=200 ) @@ -45,7 +44,6 @@ def test_auth0client() -> None: assert result assert result.authorization - assert len(result.authorization) > 0 - assert result.accounts - assert len(result.accounts) > 0 - assert len(result.get_account_ids()) > 0 + assert len(result.authorization.client_info) > 0 + assert len(result.authorization.accounts) > 0 + assert len(result.authorization.get_account_ids()) > 0 From 2390fa09c3960aa45e302e4c718684f95cc6c95b Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 1 Oct 2024 17:29:23 +0300 Subject: [PATCH 22/26] refactor: QCAuth0Authorization model --- lean/components/api/auth0_client.py | 2 +- lean/models/api.py | 40 +++++++---------------- lean/models/json_module.py | 4 +-- tests/components/api/test_auth0_client.py | 5 ++- 4 files changed, 16 insertions(+), 35 deletions(-) diff --git a/lean/components/api/auth0_client.py b/lean/components/api/auth0_client.py index 1f87eb98..f1350c1f 100644 --- a/lean/components/api/auth0_client.py +++ b/lean/components/api/auth0_client.py @@ -45,7 +45,7 @@ def read(self, brokerage_id: str) -> QCAuth0Authorization: data = self._api.post("live/auth0/read", payload) # Store in cache - result = QCAuth0Authorization.from_raw(data) + result = QCAuth0Authorization(**data) self._cache[brokerage_id] = result return result except RequestFailedError as e: diff --git a/lean/models/api.py b/lean/models/api.py index 3572d31b..99a69509 100644 --- a/lean/models/api.py +++ b/lean/models/api.py @@ -22,25 +22,8 @@ # The keys of properties are not changed, so they don't obey the rest of the project's naming conventions -class Account(WrappedBaseModel): - id: str - name: str - - -class Authorization(WrappedBaseModel): - client_info: Dict[str, str] # Holds flexible client data like client ID and tokens - accounts: List[Account] # Holds account information - - @classmethod - def from_raw(cls, raw_data: Dict[str, any]) -> 'Authorization': - """ - Custom constructor to preprocess raw authorization data - Separates client info from accounts. - """ - # Separate the client info (non-list values) from accounts (list of accounts) - client_info = {k: v for k, v in raw_data.items() if not isinstance(v, list)} - accounts = raw_data.get('accounts', []) - return cls(client_info=client_info, accounts=accounts) +class QCAuth0Authorization(WrappedBaseModel): + authorization: Optional[Dict[str, Any]] def get_account_ids(self) -> List[str]: """ @@ -52,20 +35,19 @@ def get_account_ids(self) -> List[str]: Returns: List[str]: A list of account IDs. """ - return [account.id for account in self.accounts] if self.accounts else [] + accounts = self.authorization.get('accounts', []) + return [account["id"] for account in accounts] if accounts else [] + def get_authorization_config_without_account(self) -> Dict[str, str]: + """ + Returns the authorization data without the 'accounts' key. -class QCAuth0Authorization(WrappedBaseModel): - authorization: Optional[Authorization] + Iterates through the 'authorization' dictionary and excludes the 'accounts' entry. - @classmethod - def from_raw(cls, raw_data: Dict[str, any]) -> 'QCAuth0Authorization': - """ - Custom constructor to preprocess raw QCAuth0Authorization data + Returns: + Dict[str, str]: Authorization details excluding 'accounts'. """ - authorization_data = raw_data.get('authorization', {}) - authorization = Authorization.from_raw(authorization_data) if authorization_data else None - return cls(authorization=authorization) + return {key: value for key, value in self.authorization.items() if key != 'accounts'} class ProjectEncryptionKey(WrappedBaseModel): diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 95f5463a..fa93c1bf 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -217,11 +217,11 @@ def config_build(self, elif isinstance(configuration, AuthConfiguration): auth_authorizations = get_authorization(container.api_client.auth0, self._display_name.lower(), logger) logger.debug(f'auth: {auth_authorizations}') - configuration._value = auth_authorizations.authorization.client_info + configuration._value = auth_authorizations.get_authorization_config_without_account() for inner_config in self._lean_configs: if any(condition._dependent_config_id == configuration._id for condition in inner_config._filter._conditions): - api_account_ids = auth_authorizations.authorization.get_account_ids() + api_account_ids = auth_authorizations.get_account_ids() config_dash = inner_config._id.replace('-', '_') inner_config._choices = api_account_ids if user_provided_options and config_dash in user_provided_options: diff --git a/tests/components/api/test_auth0_client.py b/tests/components/api/test_auth0_client.py index 9e9838be..dcf240bf 100644 --- a/tests/components/api/test_auth0_client.py +++ b/tests/components/api/test_auth0_client.py @@ -44,6 +44,5 @@ def test_auth0client() -> None: assert result assert result.authorization - assert len(result.authorization.client_info) > 0 - assert len(result.authorization.accounts) > 0 - assert len(result.authorization.get_account_ids()) > 0 + assert len(result.authorization) > 0 + assert len(result.get_account_ids()) > 0 From be9414ad8ce913b6da2e98049a628aedd3cf6164 Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 1 Oct 2024 23:02:18 +0300 Subject: [PATCH 23/26] test:feat: add alpaca configuration test --- tests/components/api/test_auth0_client.py | 30 ++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/components/api/test_auth0_client.py b/tests/components/api/test_auth0_client.py index dcf240bf..5495747a 100644 --- a/tests/components/api/test_auth0_client.py +++ b/tests/components/api/test_auth0_client.py @@ -19,7 +19,7 @@ @responses.activate -def test_auth0client() -> None: +def test_auth0client_trade_station() -> None: api_clint = APIClient(mock.Mock(), HTTPClient(mock.Mock()), user_id="123", api_token="abc") responses.add( @@ -45,4 +45,32 @@ def test_auth0client() -> None: assert result assert result.authorization assert len(result.authorization) > 0 + assert len(result.get_authorization_config_without_account()) > 0 + assert len(result.get_account_ids()) > 0 + + +@responses.activate +def test_auth0client_alpaca() -> None: + api_clint = APIClient(mock.Mock(), HTTPClient(mock.Mock()), user_id="123", api_token="abc") + + responses.add( + responses.POST, + f"{API_BASE_URL}live/auth0/read", + json={ + "authorization": { + "alpaca-access-token": "XXXX-XXX-XXX-XXX-XXXXX-XX", + "accounts": [{"id": "XXXX-XXX-XXX-XXX-XXXXX-XX", "name": " |USD"}] + }, + "success": "true"}, + status=200 + ) + + brokerage_id = "TestBrokerage" + + result = api_clint.auth0.read(brokerage_id) + + assert result + assert result.authorization + assert len(result.authorization) > 0 + assert len(result.get_authorization_config_without_account()) > 0 assert len(result.get_account_ids()) > 0 From 326e616122b42f0686bdeb3ad44023c0f16519e3 Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 1 Oct 2024 23:07:47 +0300 Subject: [PATCH 24/26] remove: trade-station-account-type parameter (deprecated) --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index 2ef73011..470e2199 100644 --- a/README.md +++ b/README.md @@ -214,8 +214,6 @@ Options: The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used - --trade-station-account-type [Cash|Margin|Futures|DVP] - Specifies the type of account on TradeStation --alpaca-environment [live|paper] Whether Live or Paper environment should be used --download-data Update the Lean configuration file to download data from the QuantConnect API, alias @@ -439,8 +437,6 @@ Options: The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used - --trade-station-account-type [Cash|Margin|Futures|DVP] - Specifies the type of account on TradeStation --alpaca-environment [live|paper] Whether Live or Paper environment should be used --polygon-api-key TEXT Your Polygon.io API Key @@ -920,8 +916,6 @@ Options: The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used - --trade-station-account-type [Cash|Margin|Futures|DVP] - Specifies the type of account on TradeStation --alpaca-environment [live|paper] Whether Live or Paper environment should be used --dataset TEXT The name of the dataset to download non-interactively @@ -1400,8 +1394,6 @@ Options: The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used - --trade-station-account-type [Cash|Margin|Futures|DVP] - Specifies the type of account on TradeStation --alpaca-environment [live|paper] Whether Live or Paper environment should be used --ib-enable-delayed-streaming-data BOOLEAN @@ -1815,8 +1807,6 @@ Options: The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used - --trade-station-account-type [Cash|Margin|Futures|DVP] - Specifies the type of account on TradeStation --alpaca-environment [live|paper] Whether Live or Paper environment should be used --lean-config FILE The Lean configuration file that should be used (defaults to the nearest lean.json) @@ -1989,8 +1979,6 @@ Options: The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used - --trade-station-account-type [Cash|Margin|Futures|DVP] - Specifies the type of account on TradeStation --alpaca-environment [live|paper] Whether Live or Paper environment should be used --download-data Update the Lean configuration file to download data from the QuantConnect API, alias From 8fcd385d661884215614046cab717f0801228125 Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 1 Oct 2024 23:37:43 +0300 Subject: [PATCH 25/26] feat: add filter_dependency flag --- lean/models/configuration.py | 6 ++++++ lean/models/json_module.py | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lean/models/configuration.py b/lean/models/configuration.py index 867abb29..b3cd23d7 100644 --- a/lean/models/configuration.py +++ b/lean/models/configuration.py @@ -96,10 +96,12 @@ def __init__(self, config_json_object): self._is_required_from_user = False self._save_persistently_in_lean = False self._log_message: str = "" + self.has_filter_dependency: bool = False if "log-message" in config_json_object.keys(): self._log_message = config_json_object["log-message"] if "filters" in config_json_object.keys(): self._filter = Filter(config_json_object["filters"]) + self.has_filter_dependency = Filter.has_conditions else: self._filter = Filter([]) self._input_default = config_json_object["input-default"] if "input-default" in config_json_object else None @@ -137,6 +139,10 @@ def __init__(self, filter_conditions): self._conditions: List[BaseCondition] = [BaseCondition.factory( condition["condition"]) for condition in filter_conditions] + @property + def has_conditions(self) -> bool: + """Returns True if there are any conditions, False otherwise.""" + return bool(self._conditions) class InfoConfiguration(Configuration): """Configuration class used for informational configurations. diff --git a/lean/models/json_module.py b/lean/models/json_module.py index fa93c1bf..c07d1777 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -60,13 +60,17 @@ def get_id(self): def sort_configs(self) -> List[Configuration]: sorted_configs = [] + filter_configs = [] brokerage_configs = [] for config in self._lean_configs: if isinstance(config, BrokerageEnvConfiguration): brokerage_configs.append(config) else: - sorted_configs.append(config) - return brokerage_configs + sorted_configs + if config.has_filter_dependency: + filter_configs.append(config) + else: + sorted_configs.append(config) + return brokerage_configs + sorted_configs + filter_configs def get_name(self) -> str: """Returns the user-friendly name which users can identify this object by. From e1294ccde518ec79a990bbfa751689d4e0eb8aba Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 2 Oct 2024 00:11:26 +0300 Subject: [PATCH 26/26] refactor: remove optional in trade-station-account-id --- README.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 470e2199..86ecd8d5 100644 --- a/README.md +++ b/README.md @@ -198,8 +198,6 @@ Options: Your ThetaData subscription price plan --terminal-link-connection-type [DAPI|SAPI] Terminal Link Connection Type [DAPI, SAPI] - --terminal-link-server-auth-id TEXT - The Auth ID of the TerminalLink server --terminal-link-environment [Production|Beta] The environment to run in --terminal-link-server-host TEXT @@ -208,12 +206,14 @@ Options: The port of the TerminalLink server --terminal-link-openfigi-api-key TEXT The Open FIGI API key to use for mapping options + --terminal-link-server-auth-id TEXT + The Auth ID of the TerminalLink server --bybit-api-key TEXT Your Bybit API key --bybit-api-secret TEXT Your Bybit API secret - --trade-station-account-id TEXT - The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used + --trade-station-account-id TEXT + The TradeStation account Id --alpaca-environment [live|paper] Whether Live or Paper environment should be used --download-data Update the Lean configuration file to download data from the QuantConnect API, alias @@ -433,10 +433,10 @@ Options: --bybit-api-secret TEXT Your Bybit API secret --bybit-vip-level [VIP0|VIP1|VIP2|VIP3|VIP4|VIP5|SupremeVIP|Pro1|Pro2|Pro3|Pro4|Pro5] Your Bybit VIP Level - --trade-station-account-id TEXT - The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used + --trade-station-account-id TEXT + The TradeStation account Id --alpaca-environment [live|paper] Whether Live or Paper environment should be used --polygon-api-key TEXT Your Polygon.io API Key @@ -900,8 +900,6 @@ Options: Your ThetaData subscription price plan --terminal-link-connection-type [DAPI|SAPI] Terminal Link Connection Type [DAPI, SAPI] - --terminal-link-server-auth-id TEXT - The Auth ID of the TerminalLink server --terminal-link-environment [Production|Beta] The environment to run in --terminal-link-server-host TEXT @@ -910,12 +908,14 @@ Options: The port of the TerminalLink server --terminal-link-openfigi-api-key TEXT The Open FIGI API key to use for mapping options + --terminal-link-server-auth-id TEXT + The Auth ID of the TerminalLink server --bybit-api-key TEXT Your Bybit API key --bybit-api-secret TEXT Your Bybit API secret - --trade-station-account-id TEXT - The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used + --trade-station-account-id TEXT + The TradeStation account Id --alpaca-environment [live|paper] Whether Live or Paper environment should be used --dataset TEXT The name of the dataset to download non-interactively @@ -1341,8 +1341,6 @@ Options: commodities on MCX --terminal-link-connection-type [DAPI|SAPI] Terminal Link Connection Type [DAPI, SAPI] - --terminal-link-server-auth-id TEXT - The Auth ID of the TerminalLink server --terminal-link-environment [Production|Beta] The environment to run in --terminal-link-server-host TEXT @@ -1356,20 +1354,22 @@ Options: --terminal-link-emsx-team TEXT The EMSX team to receive order events from (Optional). --terminal-link-openfigi-api-key TEXT The Open FIGI API key to use for mapping options + --terminal-link-server-auth-id TEXT + The Auth ID of the TerminalLink server --tt-user-name TEXT Your Trading Technologies username --tt-session-password TEXT Your Trading Technologies session password --tt-account-name TEXT Your Trading Technologies account name --tt-rest-app-key TEXT Your Trading Technologies REST app key --tt-rest-app-secret TEXT Your Trading Technologies REST app secret --tt-rest-environment TEXT The REST environment to run in + --tt-order-routing-sender-comp-id TEXT + The order routing sender comp id to use --tt-market-data-sender-comp-id TEXT The market data sender comp id to use --tt-market-data-target-comp-id TEXT The market data target comp id to use --tt-market-data-host TEXT The host of the market data server --tt-market-data-port TEXT The port of the market data server - --tt-order-routing-sender-comp-id TEXT - The order routing sender comp id to use --tt-order-routing-target-comp-id TEXT The order routing target comp id to use --tt-order-routing-host TEXT The host of the order routing server @@ -1390,10 +1390,10 @@ Options: Your Bybit VIP Level --bybit-use-testnet [live|paper] Whether the testnet should be used - --trade-station-account-id TEXT - The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used + --trade-station-account-id TEXT + The TradeStation account Id --alpaca-environment [live|paper] Whether Live or Paper environment should be used --ib-enable-delayed-streaming-data BOOLEAN @@ -1791,8 +1791,6 @@ Options: Your ThetaData subscription price plan --terminal-link-connection-type [DAPI|SAPI] Terminal Link Connection Type [DAPI, SAPI] - --terminal-link-server-auth-id TEXT - The Auth ID of the TerminalLink server --terminal-link-environment [Production|Beta] The environment to run in --terminal-link-server-host TEXT @@ -1801,12 +1799,14 @@ Options: The port of the TerminalLink server --terminal-link-openfigi-api-key TEXT The Open FIGI API key to use for mapping options + --terminal-link-server-auth-id TEXT + The Auth ID of the TerminalLink server --bybit-api-key TEXT Your Bybit API key --bybit-api-secret TEXT Your Bybit API secret - --trade-station-account-id TEXT - The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used + --trade-station-account-id TEXT + The TradeStation account Id --alpaca-environment [live|paper] Whether Live or Paper environment should be used --lean-config FILE The Lean configuration file that should be used (defaults to the nearest lean.json) @@ -1963,8 +1963,6 @@ Options: Your ThetaData subscription price plan --terminal-link-connection-type [DAPI|SAPI] Terminal Link Connection Type [DAPI, SAPI] - --terminal-link-server-auth-id TEXT - The Auth ID of the TerminalLink server --terminal-link-environment [Production|Beta] The environment to run in --terminal-link-server-host TEXT @@ -1973,12 +1971,14 @@ Options: The port of the TerminalLink server --terminal-link-openfigi-api-key TEXT The Open FIGI API key to use for mapping options + --terminal-link-server-auth-id TEXT + The Auth ID of the TerminalLink server --bybit-api-key TEXT Your Bybit API key --bybit-api-secret TEXT Your Bybit API secret - --trade-station-account-id TEXT - The TradeStation account Id (Optional). --trade-station-environment [live|paper] Whether Live or Paper environment should be used + --trade-station-account-id TEXT + The TradeStation account Id --alpaca-environment [live|paper] Whether Live or Paper environment should be used --download-data Update the Lean configuration file to download data from the QuantConnect API, alias