From ed895a37d31d09a08712b12bd28727ddb3218449 Mon Sep 17 00:00:00 2001 From: Romazes Date: Tue, 2 Jul 2024 19:10:45 +0300 Subject: [PATCH 01/12] feat: TradeStation in Readme --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 000469e2..278e0c7f 100644 --- a/README.md +++ b/README.md @@ -320,7 +320,7 @@ Usage: lean cloud live deploy [OPTIONS] PROJECT --notify-insights. Options: - --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit] + --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation] The brokerage to use --data-provider-live [QuantConnect|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Polygon|IEX|CoinApi|Bybit] The live data provider to use @@ -406,6 +406,15 @@ 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-api-key TEXT Your Trade Station api key + --trade-station-api-secret TEXT + Your Trade Station api secret + --trade-station-redirect-url TEXT + Your Trade Station redirect url for authorization + --trade-station-refresh-token TEXT + Your Trade Station OAuth Refresh Token + --trade-station-account-type [Cash|Margin|Futures|DVP] + Specifies the type of account on TradeStation --polygon-api-key TEXT Your Polygon.io API Key --iex-cloud-api-key TEXT Your iexcloud.io API token publishable key --iex-price-plan [Launch|Grow|Enterprise] @@ -1225,7 +1234,7 @@ Options: --environment TEXT The environment to use --output DIRECTORY Directory to store results in (defaults to PROJECT/live/TIMESTAMP) -d, --detach Run the live deployment in a detached Docker container and return immediately - --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit] + --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation] The brokerage to use --data-provider-live [Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|IQFeed|Polygon|IEX|CoinApi|ThetaData|Custom data only|Bybit] The live data provider to use @@ -1328,6 +1337,17 @@ Options: Your Bybit VIP Level --bybit-use-testnet [live|paper] Whether the testnet should be used + --trade-station-api-key TEXT Your Trade Station api key + --trade-station-api-secret TEXT + Your Trade Station api secret + --trade-station-redirect-url TEXT + Your Trade Station redirect url for authorization + --trade-station-refresh-token TEXT + Your Trade Station OAuth Refresh Token + --trade-station-use-simulator [live|paper] + Whether the testnet should be used + --trade-station-account-type [Cash|Margin|Futures|DVP] + Specifies the type of account on TradeStation --ib-enable-delayed-streaming-data BOOLEAN Whether delayed data may be used when your algorithm subscribes to a security you don't have a market data subscription for (Optional). From 728ba698cfcfb21632f1fb5cb2bed571c92be141 Mon Sep 17 00:00:00 2001 From: Romazes Date: Wed, 17 Jul 2024 19:35:24 +0300 Subject: [PATCH 02/12] feat: DQH of TradeStation --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 278e0c7f..c1728d53 100644 --- a/README.md +++ b/README.md @@ -322,7 +322,7 @@ Usage: lean cloud live deploy [OPTIONS] PROJECT Options: --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation] The brokerage to use - --data-provider-live [QuantConnect|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Polygon|IEX|CoinApi|Bybit] + --data-provider-live [QuantConnect|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Polygon|IEX|CoinApi|Bybit|TradeStation] The live data provider to use --ib-user-name TEXT Your Interactive Brokers username --ib-account TEXT Your Interactive Brokers account id @@ -410,7 +410,7 @@ Options: --trade-station-api-secret TEXT Your Trade Station api secret --trade-station-redirect-url TEXT - Your Trade Station redirect url for authorization + Your Trade Station redirect url for authorization (Optional). --trade-station-refresh-token TEXT Your Trade Station OAuth Refresh Token --trade-station-account-type [Cash|Margin|Futures|DVP] @@ -1236,7 +1236,7 @@ Options: -d, --detach Run the live deployment in a detached Docker container and return immediately --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation] The brokerage to use - --data-provider-live [Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|IQFeed|Polygon|IEX|CoinApi|ThetaData|Custom data only|Bybit] + --data-provider-live [Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|IQFeed|Polygon|IEX|CoinApi|ThetaData|Custom data only|Bybit|TradeStation] The live data provider to use --data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Bybit] Update the Lean configuration file to retrieve data from the given historical provider @@ -1341,7 +1341,7 @@ Options: --trade-station-api-secret TEXT Your Trade Station api secret --trade-station-redirect-url TEXT - Your Trade Station redirect url for authorization + Your Trade Station redirect url for authorization (Optional). --trade-station-refresh-token TEXT Your Trade Station OAuth Refresh Token --trade-station-use-simulator [live|paper] From fb8c33fa33727ce72fcb39a30575837871250e38 Mon Sep 17 00:00:00 2001 From: Ronit Jain Date: Wed, 10 Jul 2024 18:25:46 +0530 Subject: [PATCH 03/12] Implement auth0 api client --- lean/components/api/api_client.py | 2 + lean/components/api/auth0_client.py | 56 ++++++++++++++++++++++++++++ lean/components/util/auth0_helper.py | 41 ++++++++++++++++++++ lean/models/api.py | 2 + 4 files changed, 101 insertions(+) create mode 100644 lean/components/api/auth0_client.py create mode 100644 lean/components/util/auth0_helper.py diff --git a/lean/components/api/api_client.py b/lean/components/api/api_client.py index c6787c75..731a6fed 100644 --- a/lean/components/api/api_client.py +++ b/lean/components/api/api_client.py @@ -13,6 +13,7 @@ from typing import Any, Dict +from lean.components.api.auth0_client import Auth0Client from lean.components.api.account_client import AccountClient from lean.components.api.backtest_client import BacktestClient from lean.components.api.compile_client import CompileClient @@ -52,6 +53,7 @@ def __init__(self, logger: Logger, http_client: HTTPClient, user_id: str, api_to self.set_user_token(user_id, api_token) # Create the clients containing the methods to send requests to the various API endpoints + self.auth0 = Auth0Client(self) self.accounts = AccountClient(self) self.backtests = BacktestClient(self) self.compiles = CompileClient(self) diff --git a/lean/components/api/auth0_client.py b/lean/components/api/auth0_client.py new file mode 100644 index 00000000..12c178cc --- /dev/null +++ b/lean/components/api/auth0_client.py @@ -0,0 +1,56 @@ +# 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. + +from lean.components.api.api_client import * +from lean.components.util.logger import Logger +from lean.constants import API_BASE_URL +from lean.models.api import QCAuth0Authorization +from lean.models.errors import RequestFailedError + +class Auth0Client: + """The Auth0Client class contains methods to interact with live/auth0/* API endpoints.""" + + def __init__(self, api_client: 'APIClient') -> None: + """Creates a new Auth0Client instance. + + :param api_client: the APIClient instance to use when making requests + """ + self._api = api_client + + + def read(self, brokerage_id: str) -> QCAuth0Authorization: + """Reads the authorization data for a brokerage. + + :param brokerage_id: the id of the brokerage to read the authorization data for + :return: the authorization data for the specified brokerage + """ + try: + payload = { + "brokerage": brokerage_id + } + + data = self._api.post("live/auth0/read", payload) + return QCAuth0Authorization(**data["authorization"]) + except RequestFailedError as e: + return QCAuth0Authorization(authorization=None) + + def authorize(self, brokerage_id: str, logger: Logger) -> None: + """Starts the authorization process for a brokerage. + + :param brokerage_id: the id of the brokerage to start the authorization process for + :param logger: the logger instance to use + """ + + full_url = f"{API_BASE_URL}ive/auth0/authorize?brokerage={brokerage_id}" + logger.info(f"Please open the following URL in your browser to authorize the LEAN CLI.") + logger.info(full_url) diff --git a/lean/components/util/auth0_helper.py b/lean/components/util/auth0_helper.py new file mode 100644 index 00000000..33fb90ed --- /dev/null +++ b/lean/components/util/auth0_helper.py @@ -0,0 +1,41 @@ +# 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. + +from lean.models.api import QCAuth0Authorization +from lean.components.api.api_client import Auth0Client +from lean.components.util.logger import Logger + +def get_authorization(auth0_client: Auth0Client, brokerage_id: str, logger: Logger) -> QCAuth0Authorization: + """Gets the authorization data for a brokerage, authorizing if necessary. + + :param brokerage_id: the id of the brokerage to get the authorization data for + :return: the authorization data for the specified brokerage + """ + from time import time, sleep + + data = auth0_client.read(brokerage_id) + if data.authorization is not None: + return data + + start_time = time() + auth0_client.authorize(brokerage_id, logger) + + # keep checking for new data every 5 seconds for 7 minutes + while time() - start_time < 420: + sleep(5) + data = auth0_client.read(brokerage_id) + if data.authorization is None: + continue + return data + + raise Exception("Authorization failed") \ No newline at end of file diff --git a/lean/models/api.py b/lean/models/api.py index 4df959c4..14851de3 100644 --- a/lean/models/api.py +++ b/lean/models/api.py @@ -21,6 +21,8 @@ # 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 QCAuth0Authorization(WrappedBaseModel): + authorization: Optional[Dict[str, str]] class ProjectEncryptionKey(WrappedBaseModel): id: str From b1998638d5ed65ed172bde605c9d45da1d9f9b67 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 18 Jul 2024 15:40:10 +0300 Subject: [PATCH 04/12] feat: OAth implementation --- lean/components/api/auth0_client.py | 7 ++--- lean/components/util/auth0_helper.py | 39 +++++++++++++++------------- lean/models/configuration.py | 38 +++++++++++++++++++++++++++ lean/models/json_module.py | 6 ++++- 4 files changed, 68 insertions(+), 22 deletions(-) diff --git a/lean/components/api/auth0_client.py b/lean/components/api/auth0_client.py index 12c178cc..ab51b9e9 100644 --- a/lean/components/api/auth0_client.py +++ b/lean/components/api/auth0_client.py @@ -17,6 +17,7 @@ from lean.models.api import QCAuth0Authorization from lean.models.errors import RequestFailedError + class Auth0Client: """The Auth0Client class contains methods to interact with live/auth0/* API endpoints.""" @@ -27,7 +28,6 @@ def __init__(self, api_client: 'APIClient') -> None: """ self._api = api_client - def read(self, brokerage_id: str) -> QCAuth0Authorization: """Reads the authorization data for a brokerage. @@ -44,13 +44,14 @@ def read(self, brokerage_id: str) -> QCAuth0Authorization: except RequestFailedError as e: return QCAuth0Authorization(authorization=None) - def authorize(self, brokerage_id: str, logger: Logger) -> None: + @staticmethod + def authorize(brokerage_id: str, logger: Logger) -> None: """Starts the authorization process for a brokerage. :param brokerage_id: the id of the brokerage to start the authorization process for :param logger: the logger instance to use """ - full_url = f"{API_BASE_URL}ive/auth0/authorize?brokerage={brokerage_id}" + full_url = f"{API_BASE_URL}live/auth0/authorize?brokerage={brokerage_id}" logger.info(f"Please open the following URL in your browser to authorize the LEAN CLI.") logger.info(full_url) diff --git a/lean/components/util/auth0_helper.py b/lean/components/util/auth0_helper.py index 33fb90ed..d6c35eb0 100644 --- a/lean/components/util/auth0_helper.py +++ b/lean/components/util/auth0_helper.py @@ -15,27 +15,30 @@ from lean.components.api.api_client import Auth0Client from lean.components.util.logger import Logger + def get_authorization(auth0_client: Auth0Client, brokerage_id: str, logger: Logger) -> QCAuth0Authorization: - """Gets the authorization data for a brokerage, authorizing if necessary. + """Gets the authorization data for a brokerage, authorizing if necessary. - :param brokerage_id: the id of the brokerage to get the authorization data for - :return: the authorization data for the specified brokerage - """ - from time import time, sleep + :param auth0_client: An instance of Auth0Client, containing methods to interact with live/auth0/* API endpoints. + :param brokerage_id: The ID of the brokerage to get the authorization data for. + :param logger: An instance of Logger, handling all output printing. + :return: The authorization data for the specified brokerage. + """ + from time import time, sleep - data = auth0_client.read(brokerage_id) - if data.authorization is not None: - return data + data = auth0_client.read(brokerage_id) + if data.authorization is not None: + return data - start_time = time() - auth0_client.authorize(brokerage_id, logger) + start_time = time() + auth0_client.authorize(brokerage_id, logger) - # keep checking for new data every 5 seconds for 7 minutes - while time() - start_time < 420: - sleep(5) - data = auth0_client.read(brokerage_id) - if data.authorization is None: - continue - return data + # keep checking for new data every 5 seconds for 7 minutes + while time() - start_time < 420: + sleep(5) + data = auth0_client.read(brokerage_id) + if data.authorization is None: + continue + return data - raise Exception("Authorization failed") \ No newline at end of file + raise Exception("Authorization failed") diff --git a/lean/models/configuration.py b/lean/models/configuration.py index c1e1601e..12590e01 100644 --- a/lean/models/configuration.py +++ b/lean/models/configuration.py @@ -119,6 +119,8 @@ def factory(config_json_object) -> 'Configuration': return UserInputConfiguration.factory(config_json_object) elif config_json_object["type"] == "filter-env": return BrokerageEnvConfiguration.factory(config_json_object) + elif config_json_object["type"] == "oauth-token": + return AuthConfiguration.factory(config_json_object) else: raise ValueError( f'Undefined input method type {config_json_object["type"]}') @@ -388,6 +390,42 @@ def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = F raise ValueError(f"Undefined input method type {self._input_method}") +class AuthConfiguration(PromptUserInput, ChoiceUserInput, ConfirmUserInput): + + def __init__(self, config_json_object): + super().__init__(config_json_object) + + def factory(config_json_object) -> 'AuthConfiguration': + """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"] == "oauth-token": + return AuthConfiguration(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 == "confirm": + return ConfirmUserInput.ask_user_for_input(self, default_value, logger) + elif self._input_method == "choice": + return ChoiceUserInput.ask_user_for_input(self, default_value, logger) + elif self._input_method == "prompt": + return PromptUserInput.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.""" diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 14bde708..c73afd51 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -17,11 +17,12 @@ from click import get_current_context from click.core import ParameterSource +from lean.components.util.auth0_helper import get_authorization 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.configuration import BrokerageEnvConfiguration, Configuration, InternalInputUserInput, \ - PathParameterUserInput + PathParameterUserInput, AuthConfiguration from copy import copy from abc import ABC @@ -223,6 +224,9 @@ def config_build(self, else: missing_options.append(f"--{configuration._id}") + if isinstance(configuration, AuthConfiguration) and user_choice.lower() == "yes": + auth_authorization = get_authorization(container.api_client.auth0, self._display_name, logger) + configuration._value = user_choice if len(missing_options) > 0: From cdeb9175f514eba350378605beb95e238ba192e1 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 18 Jul 2024 15:54:54 +0300 Subject: [PATCH 05/12] feat: rename configuration of TS in Readme --- README.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c1728d53..bb90fb08 100644 --- a/README.md +++ b/README.md @@ -406,13 +406,19 @@ 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-api-key TEXT Your Trade Station api key - --trade-station-api-secret TEXT - Your Trade Station api secret + --trade-station-oath-token [Yes|No] + Select 'Yes' if you want to use OAuth authentication to connect to QuantConnect + securely. This will allow you to access TradeStation's brokerage services through + QuantConnect. Select 'No' if you do not want to use OAuth authentication. + --trade-station-client-id TEXT Your TradeStation Client ID is a unique identifier provided by TradeStation when you + register for an account. This ID is required to authenticate your access to + TradeStation's brokerage services + --trade-station-client-secret TEXT + Your Trade Station api secret (Optional). --trade-station-redirect-url TEXT Your Trade Station redirect url for authorization (Optional). --trade-station-refresh-token TEXT - Your Trade Station OAuth Refresh Token + Your Trade Station OAuth Refresh Token (Optional). --trade-station-account-type [Cash|Margin|Futures|DVP] Specifies the type of account on TradeStation --polygon-api-key TEXT Your Polygon.io API Key @@ -1337,13 +1343,19 @@ Options: Your Bybit VIP Level --bybit-use-testnet [live|paper] Whether the testnet should be used - --trade-station-api-key TEXT Your Trade Station api key - --trade-station-api-secret TEXT - Your Trade Station api secret + --trade-station-oath-token [Yes|No] + Select 'Yes' if you want to use OAuth authentication to connect to QuantConnect + securely. This will allow you to access TradeStation's brokerage services through + QuantConnect. Select 'No' if you do not want to use OAuth authentication. + --trade-station-client-id TEXT Your TradeStation Client ID is a unique identifier provided by TradeStation when you + register for an account. This ID is required to authenticate your access to + TradeStation's brokerage services + --trade-station-client-secret TEXT + Your Trade Station api secret (Optional). --trade-station-redirect-url TEXT Your Trade Station redirect url for authorization (Optional). --trade-station-refresh-token TEXT - Your Trade Station OAuth Refresh Token + Your Trade Station OAuth Refresh Token (Optional). --trade-station-use-simulator [live|paper] Whether the testnet should be used --trade-station-account-type [Cash|Margin|Futures|DVP] From 8855ad0785b8c606c44c911d1e7dd2777c51a5c5 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 18 Jul 2024 20:18:33 +0300 Subject: [PATCH 06/12] feat: get auth0 configurations --- lean/components/api/auth0_client.py | 2 +- lean/models/configuration.py | 11 ++--------- lean/models/json_module.py | 14 ++++++++++---- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lean/components/api/auth0_client.py b/lean/components/api/auth0_client.py index ab51b9e9..a7be6916 100644 --- a/lean/components/api/auth0_client.py +++ b/lean/components/api/auth0_client.py @@ -40,7 +40,7 @@ def read(self, brokerage_id: str) -> QCAuth0Authorization: } data = self._api.post("live/auth0/read", payload) - return QCAuth0Authorization(**data["authorization"]) + return QCAuth0Authorization(**data) except RequestFailedError as e: return QCAuth0Authorization(authorization=None) diff --git a/lean/models/configuration.py b/lean/models/configuration.py index 12590e01..867abb29 100644 --- a/lean/models/configuration.py +++ b/lean/models/configuration.py @@ -390,7 +390,7 @@ def ask_user_for_input(self, default_value, logger: Logger, hide_input: bool = F raise ValueError(f"Undefined input method type {self._input_method}") -class AuthConfiguration(PromptUserInput, ChoiceUserInput, ConfirmUserInput): +class AuthConfiguration(InternalInputUserInput): def __init__(self, config_json_object): super().__init__(config_json_object) @@ -416,14 +416,7 @@ 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 == "confirm": - return ConfirmUserInput.ask_user_for_input(self, default_value, logger) - elif self._input_method == "choice": - return ChoiceUserInput.ask_user_for_input(self, default_value, logger) - elif self._input_method == "prompt": - return PromptUserInput.ask_user_for_input(self, default_value, logger) - else: - raise ValueError(f"Undefined input method type {self._input_method}") + raise ValueError(f'user input not allowed with {self.__class__.__name__}') class FilterEnvConfiguration(BrokerageEnvConfiguration): diff --git a/lean/models/json_module.py b/lean/models/json_module.py index c73afd51..0fd21509 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -133,7 +133,11 @@ def get_settings(self) -> Dict[str, str]: for configuration in self._lean_configs: if not self._check_if_config_passes_filters(configuration, all_for_platform_type=False): continue - settings[configuration._id] = str(configuration._value).replace("\\", "/") + if isinstance(configuration, AuthConfiguration) and isinstance(configuration._value, dict): + for key, value in configuration._value.items(): + settings[key] = str(value) + else: + settings[configuration._id] = str(configuration._value).replace("\\", "/") return settings @@ -196,6 +200,11 @@ def config_build(self, _logged_messages.add(log_message) if type(configuration) is InternalInputUserInput: continue + 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 + 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 @@ -224,9 +233,6 @@ def config_build(self, else: missing_options.append(f"--{configuration._id}") - if isinstance(configuration, AuthConfiguration) and user_choice.lower() == "yes": - auth_authorization = get_authorization(container.api_client.auth0, self._display_name, logger) - configuration._value = user_choice if len(missing_options) > 0: From e32057a1bf287407c61ee8e604b67789a5ce8dea Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 18 Jul 2024 20:19:24 +0300 Subject: [PATCH 07/12] feat: increase number of modules json file --- lean/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lean/models/__init__.py b/lean/models/__init__.py index 952d30e5..4e0e8bca 100644 --- a/lean/models/__init__.py +++ b/lean/models/__init__.py @@ -17,7 +17,7 @@ from time import time json_modules = {} -file_name = "modules-1.13.json" +file_name = "modules-1.14.json" directory = Path(__file__).parent file_path = directory.parent / file_name From 2579d5f0d0cf366aac7f8c8bc08315f61400cf95 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 18 Jul 2024 20:32:00 +0300 Subject: [PATCH 08/12] refactor: README --- README.md | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index bb90fb08..08a29bbb 100644 --- a/README.md +++ b/README.md @@ -406,19 +406,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-oath-token [Yes|No] - Select 'Yes' if you want to use OAuth authentication to connect to QuantConnect - securely. This will allow you to access TradeStation's brokerage services through - QuantConnect. Select 'No' if you do not want to use OAuth authentication. - --trade-station-client-id TEXT Your TradeStation Client ID is a unique identifier provided by TradeStation when you - register for an account. This ID is required to authenticate your access to - TradeStation's brokerage services - --trade-station-client-secret TEXT - Your Trade Station api secret (Optional). - --trade-station-redirect-url TEXT - Your Trade Station redirect url for authorization (Optional). - --trade-station-refresh-token TEXT - Your Trade Station OAuth Refresh Token (Optional). + --trade-station-oath-token TEXT + Using OAuth authentication with QuantConnect. + --trade-station-environment [live|paper] + Whether the testnet should be used --trade-station-account-type [Cash|Margin|Futures|DVP] Specifies the type of account on TradeStation --polygon-api-key TEXT Your Polygon.io API Key @@ -1343,20 +1334,9 @@ Options: Your Bybit VIP Level --bybit-use-testnet [live|paper] Whether the testnet should be used - --trade-station-oath-token [Yes|No] - Select 'Yes' if you want to use OAuth authentication to connect to QuantConnect - securely. This will allow you to access TradeStation's brokerage services through - QuantConnect. Select 'No' if you do not want to use OAuth authentication. - --trade-station-client-id TEXT Your TradeStation Client ID is a unique identifier provided by TradeStation when you - register for an account. This ID is required to authenticate your access to - TradeStation's brokerage services - --trade-station-client-secret TEXT - Your Trade Station api secret (Optional). - --trade-station-redirect-url TEXT - Your Trade Station redirect url for authorization (Optional). - --trade-station-refresh-token TEXT - Your Trade Station OAuth Refresh Token (Optional). - --trade-station-use-simulator [live|paper] + --trade-station-oath-token TEXT + Using OAuth authentication with QuantConnect. + --trade-station-environment [live|paper] Whether the testnet should be used --trade-station-account-type [Cash|Margin|Futures|DVP] Specifies the type of account on TradeStation From eb21466d5ddac2b5d6cffee478add020666316af Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 18 Jul 2024 20:45:02 +0300 Subject: [PATCH 09/12] fix: use isinstance instead of type in condition fix: Readme skip oauth type --- README.md | 4 ---- lean/models/json_module.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 08a29bbb..c383d123 100644 --- a/README.md +++ b/README.md @@ -406,8 +406,6 @@ 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-oath-token TEXT - Using OAuth authentication with QuantConnect. --trade-station-environment [live|paper] Whether the testnet should be used --trade-station-account-type [Cash|Margin|Futures|DVP] @@ -1334,8 +1332,6 @@ Options: Your Bybit VIP Level --bybit-use-testnet [live|paper] Whether the testnet should be used - --trade-station-oath-token TEXT - Using OAuth authentication with QuantConnect. --trade-station-environment [live|paper] Whether the testnet should be used --trade-station-account-type [Cash|Margin|Futures|DVP] diff --git a/lean/models/json_module.py b/lean/models/json_module.py index 0fd21509..b95173fe 100644 --- a/lean/models/json_module.py +++ b/lean/models/json_module.py @@ -143,7 +143,7 @@ def get_settings(self) -> Dict[str, str]: def get_all_input_configs(self, filters: List[Type[Configuration]] = []) -> List[Configuration]: return [copy(config) for config in self._lean_configs if config._is_required_from_user - if type(config) not in filters + if not isinstance(config, tuple(filters)) and self._check_if_config_passes_filters(config, all_for_platform_type=True)] def convert_lean_key_to_variable(self, lean_key: str) -> str: From 7268ea028a472ee058bf2b123f0503bc68537356 Mon Sep 17 00:00:00 2001 From: Romazes Date: Thu, 18 Jul 2024 20:50:39 +0300 Subject: [PATCH 10/12] feat: auto open browser when call authorize --- lean/components/api/auth0_client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lean/components/api/auth0_client.py b/lean/components/api/auth0_client.py index a7be6916..ec9d0046 100644 --- a/lean/components/api/auth0_client.py +++ b/lean/components/api/auth0_client.py @@ -51,7 +51,11 @@ def authorize(brokerage_id: str, logger: Logger) -> None: :param brokerage_id: the id of the brokerage to start the authorization process for :param logger: the logger instance to use """ + from webbrowser import open full_url = f"{API_BASE_URL}live/auth0/authorize?brokerage={brokerage_id}" logger.info(f"Please open the following URL in your browser to authorize the LEAN CLI.") logger.info(full_url) + open(full_url) + + From 07b64f975ec6f8129d1ef7c41fe8529b145407f5 Mon Sep 17 00:00:00 2001 From: Martin Molinero Date: Thu, 18 Jul 2024 17:06:59 -0300 Subject: [PATCH 11/12] Add Alpaca to readme --- README.md | 20 ++++++++++++++------ lean/components/util/auth0_helper.py | 1 + 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c383d123..2e4ca255 100644 --- a/README.md +++ b/README.md @@ -320,9 +320,9 @@ Usage: lean cloud live deploy [OPTIONS] PROJECT --notify-insights. Options: - --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation] + --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation|Alpaca] The brokerage to use - --data-provider-live [QuantConnect|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Polygon|IEX|CoinApi|Bybit|TradeStation] + --data-provider-live [QuantConnect|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Polygon|IEX|CoinApi|Bybit|TradeStation|Alpaca] The live data provider to use --ib-user-name TEXT Your Interactive Brokers username --ib-account TEXT Your Interactive Brokers account id @@ -407,9 +407,11 @@ Options: --bybit-vip-level [VIP0|VIP1|VIP2|VIP3|VIP4|VIP5|SupremeVIP|Pro1|Pro2|Pro3|Pro4|Pro5] Your Bybit VIP Level --trade-station-environment [live|paper] - Whether the testnet should be used + 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 --iex-cloud-api-key TEXT Your iexcloud.io API token publishable key --iex-price-plan [Launch|Grow|Enterprise] @@ -417,6 +419,8 @@ Options: --coinapi-api-key TEXT Your coinapi.io Api Key --coinapi-product [Free|Startup|Streamer|Professional|Enterprise] CoinApi pricing plan (https://www.coinapi.io/market-data-api/pricing) + --alpaca-api-key TEXT Your Alpaca Api Key + --alpaca-api-secret TEXT Your Alpaca Api Secret --node TEXT The name or id of the live node to run on --auto-restart BOOLEAN Whether automatic algorithm restarting must be enabled --notify-order-events BOOLEAN Whether notifications must be sent for order events @@ -1229,9 +1233,9 @@ Options: --environment TEXT The environment to use --output DIRECTORY Directory to store results in (defaults to PROJECT/live/TIMESTAMP) -d, --detach Run the live deployment in a detached Docker container and return immediately - --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation] + --brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation|Alpaca] The brokerage to use - --data-provider-live [Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|IQFeed|Polygon|IEX|CoinApi|ThetaData|Custom data only|Bybit|TradeStation] + --data-provider-live [Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|IQFeed|Polygon|IEX|CoinApi|ThetaData|Custom data only|Bybit|TradeStation|Alpaca] The live data provider to use --data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Bybit] Update the Lean configuration file to retrieve data from the given historical provider @@ -1333,9 +1337,11 @@ Options: --bybit-use-testnet [live|paper] Whether the testnet should be used --trade-station-environment [live|paper] - Whether the testnet should be used + 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 Whether delayed data may be used when your algorithm subscribes to a security you don't have a market data subscription for (Optional). @@ -1355,6 +1361,8 @@ Options: --thetadata-rest-url TEXT The ThetaData host address (Optional). --thetadata-subscription-plan [Free|Value|Standard|Pro] Your ThetaData subscription price plan + --alpaca-api-key TEXT Your Alpaca Api Key + --alpaca-api-secret TEXT Your Alpaca Api Secret --factset-auth-config-file FILE The path to the FactSet authentication configuration file --alpha-vantage-api-key TEXT Your Alpha Vantage Api Key diff --git a/lean/components/util/auth0_helper.py b/lean/components/util/auth0_helper.py index d6c35eb0..b5c453d4 100644 --- a/lean/components/util/auth0_helper.py +++ b/lean/components/util/auth0_helper.py @@ -35,6 +35,7 @@ def get_authorization(auth0_client: Auth0Client, brokerage_id: str, logger: Logg # keep checking for new data every 5 seconds for 7 minutes while time() - start_time < 420: + logger.info("Will sleep 5 seconds and retry fetching authorization...") sleep(5) data = auth0_client.read(brokerage_id) if data.authorization is None: From bd4599c9745402958aa14ff35832fc8e720ccbd8 Mon Sep 17 00:00:00 2001 From: Martin Molinero Date: Fri, 19 Jul 2024 19:57:55 -0300 Subject: [PATCH 12/12] Pin setuptools --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 18dbad1c..248edc3b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Install dependencies run: | - pip install wheel + pip install setuptools==69.5.1 wheel pip install -r requirements.txt - name: Run tests