Skip to content

Commit 26cbf34

Browse files
authored
LEAN CLI CharlesSchwab Brokerage support (#517)
* feat: CharlesSchwab in Readme * remove: obsolete TDAmeritrade * feat: add project_id to authorize in auth0_client * feat: use default project id like 0 if configuration is none * Revert "feat: use default project id like 0 if configuration is none" This reverts commit ebaa82b. * Revert "feat: add project_id to authorize in auth0_client" This reverts commit cf0d691. * feat: add project_id to authorize in auth0_client * refactor: set project_id in config_manager * remove: not used imports * refactor: use negative local id if cloud id is not provided * rename: get project id method refactor: return auth url with any project_id * refactor: get project id refactor: Readme * refactor: Readme * feat: set project id always in default configs * feat: validate config value on empty * refactor: description of project-id in Readme * feat: new 'require_project_id' property in AuthConfiguration feat: prompt user to write project id if it required feat: use project_id always -1 refactor: remove extra project-id from CharlesSchwab parameters in Readme * refactor: carry out prompt import into get_project_id * refactor: remove extra project id from Readme * refactor: use int type implicitly * fix: underscore in "require-project-id" param
1 parent bb5fbc9 commit 26cbf34

File tree

7 files changed

+78
-24
lines changed

7 files changed

+78
-24
lines changed

README.md

+21-19
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ Options:
150150
-d, --detach Run the backtest in a detached Docker container and return immediately
151151
--debug [pycharm|ptvsd|debugpy|vsdbg|rider|local-platform]
152152
Enable a certain debugging method (see --help for more information)
153-
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Terminal Link|Bybit|TradeStation|Alpaca]
153+
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|CharlesSchwab|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Terminal Link|Bybit|TradeStation|Alpaca]
154154
Update the Lean configuration file to retrieve data from the given historical provider
155155
--ib-user-name TEXT Your Interactive Brokers username
156156
--ib-account TEXT Your Interactive Brokers account id
@@ -178,6 +178,8 @@ Options:
178178
--kraken-api-secret TEXT Your Kraken API secret
179179
--kraken-verification-tier [Starter|Intermediate|Pro]
180180
Your Kraken Verification Tier
181+
--charles-schwab-account-number TEXT
182+
The CharlesSchwab account number
181183
--iqfeed-iqconnect TEXT The path to the IQConnect binary
182184
--iqfeed-username TEXT Your IQFeed username
183185
--iqfeed-password TEXT Your IQFeed password
@@ -349,9 +351,9 @@ Usage: lean cloud live deploy [OPTIONS] PROJECT
349351
--notify-insights.
350352
351353
Options:
352-
--brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation|Alpaca]
354+
--brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|CharlesSchwab|Bybit|TradeStation|Alpaca]
353355
The brokerage to use
354-
--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]
356+
--data-provider-live [QuantConnect|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|CharlesSchwab|Polygon|IEX|CoinApi|Bybit|TradeStation|Alpaca]
355357
The live data provider to use
356358
--ib-user-name TEXT Your Interactive Brokers username
357359
--ib-account TEXT Your Interactive Brokers account id
@@ -427,11 +429,8 @@ Options:
427429
--kraken-api-secret TEXT Your Kraken API secret
428430
--kraken-verification-tier [Starter|Intermediate|Pro]
429431
Your Kraken Verification Tier
430-
--tdameritrade-api-key TEXT Your TDAmeritrade API key
431-
--tdameritrade-access-token TEXT
432-
Your TDAmeritrade OAuth Access Token
433-
--tdameritrade-account-number TEXT
434-
Your TDAmeritrade account number
432+
--charles-schwab-account-number TEXT
433+
The CharlesSchwab account number
435434
--bybit-api-key TEXT Your Bybit API key
436435
--bybit-api-secret TEXT Your Bybit API secret
437436
--bybit-vip-level [VIP0|VIP1|VIP2|VIP3|VIP4|VIP5|SupremeVIP|Pro1|Pro2|Pro3|Pro4|Pro5]
@@ -852,7 +851,7 @@ Usage: lean data download [OPTIONS]
852851
https://www.quantconnect.com/datasets
853852
854853
Options:
855-
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Terminal Link|Bybit|TradeStation|Alpaca]
854+
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|CharlesSchwab|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Terminal Link|Bybit|TradeStation|Alpaca]
856855
The name of the downloader data provider.
857856
--ib-user-name TEXT Your Interactive Brokers username
858857
--ib-account TEXT Your Interactive Brokers account id
@@ -880,6 +879,8 @@ Options:
880879
--kraken-api-secret TEXT Your Kraken API secret
881880
--kraken-verification-tier [Starter|Intermediate|Pro]
882881
Your Kraken Verification Tier
882+
--charles-schwab-account-number TEXT
883+
The CharlesSchwab account number
883884
--iqfeed-iqconnect TEXT The path to the IQConnect binary
884885
--iqfeed-username TEXT Your IQFeed username
885886
--iqfeed-password TEXT Your IQFeed password
@@ -1289,11 +1290,11 @@ Options:
12891290
--environment TEXT The environment to use
12901291
--output DIRECTORY Directory to store results in (defaults to PROJECT/live/TIMESTAMP)
12911292
-d, --detach Run the live deployment in a detached Docker container and return immediately
1292-
--brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|TDAmeritrade|Bybit|TradeStation|Alpaca]
1293+
--brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|CharlesSchwab|Bybit|TradeStation|Alpaca]
12931294
The brokerage to use
1294-
--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]
1295+
--data-provider-live [Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Zerodha|Samco|Terminal Link|Trading Technologies|Kraken|CharlesSchwab|IQFeed|Polygon|IEX|CoinApi|ThetaData|Custom data only|Bybit|TradeStation|Alpaca]
12951296
The live data provider to use
1296-
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Bybit|TradeStation|Alpaca]
1297+
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|CharlesSchwab|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Bybit|TradeStation|Alpaca]
12971298
Update the Lean configuration file to retrieve data from the given historical provider
12981299
--ib-user-name TEXT Your Interactive Brokers username
12991300
--ib-account TEXT Your Interactive Brokers account id
@@ -1382,11 +1383,8 @@ Options:
13821383
--kraken-api-secret TEXT Your Kraken API secret
13831384
--kraken-verification-tier [Starter|Intermediate|Pro]
13841385
Your Kraken Verification Tier
1385-
--tdameritrade-api-key TEXT Your TDAmeritrade API key
1386-
--tdameritrade-access-token TEXT
1387-
Your TDAmeritrade OAuth Access Token
1388-
--tdameritrade-account-number TEXT
1389-
Your TDAmeritrade account number
1386+
--charles-schwab-account-number TEXT
1387+
The CharlesSchwab account number
13901388
--bybit-api-key TEXT Your Bybit API key
13911389
--bybit-api-secret TEXT Your Bybit API secret
13921390
--bybit-vip-level [VIP0|VIP1|VIP2|VIP3|VIP4|VIP5|SupremeVIP|Pro1|Pro2|Pro3|Pro4|Pro5]
@@ -1730,7 +1728,7 @@ Options:
17301728
--parameter <TEXT FLOAT FLOAT FLOAT>...
17311729
The 'parameter min max step' pairs configuring the parameters to optimize
17321730
--constraint TEXT The 'statistic operator value' pairs configuring the constraints of the optimization
1733-
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Terminal Link|Bybit|TradeStation|Alpaca]
1731+
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|CharlesSchwab|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Terminal Link|Bybit|TradeStation|Alpaca]
17341732
Update the Lean configuration file to retrieve data from the given historical provider
17351733
--download-data Update the Lean configuration file to download data from the QuantConnect API, alias
17361734
for --data-provider-historical QuantConnect
@@ -1771,6 +1769,8 @@ Options:
17711769
--kraken-api-secret TEXT Your Kraken API secret
17721770
--kraken-verification-tier [Starter|Intermediate|Pro]
17731771
Your Kraken Verification Tier
1772+
--charles-schwab-account-number TEXT
1773+
The CharlesSchwab account number
17741774
--iqfeed-iqconnect TEXT The path to the IQConnect binary
17751775
--iqfeed-username TEXT Your IQFeed username
17761776
--iqfeed-password TEXT Your IQFeed password
@@ -1989,7 +1989,7 @@ Usage: lean research [OPTIONS] PROJECT
19891989
19901990
Options:
19911991
--port INTEGER The port to run Jupyter Lab on (defaults to 8888)
1992-
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Terminal Link|Bybit|TradeStation|Alpaca]
1992+
--data-provider-historical [Interactive Brokers|Oanda|Bitfinex|Coinbase Advanced Trade|Binance|Kraken|CharlesSchwab|IQFeed|Polygon|FactSet|IEX|AlphaVantage|CoinApi|ThetaData|QuantConnect|Local|Terminal Link|Bybit|TradeStation|Alpaca]
19931993
Update the Lean configuration file to retrieve data from the given historical provider
19941994
--ib-user-name TEXT Your Interactive Brokers username
19951995
--ib-account TEXT Your Interactive Brokers account id
@@ -2017,6 +2017,8 @@ Options:
20172017
--kraken-api-secret TEXT Your Kraken API secret
20182018
--kraken-verification-tier [Starter|Intermediate|Pro]
20192019
Your Kraken Verification Tier
2020+
--charles-schwab-account-number TEXT
2021+
The CharlesSchwab account number
20202022
--iqfeed-iqconnect TEXT The path to the IQConnect binary
20212023
--iqfeed-username TEXT Your IQFeed username
20222024
--iqfeed-password TEXT Your IQFeed password

lean/components/api/auth0_client.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,17 @@ def read(self, brokerage_id: str) -> QCAuth0Authorization:
5252
return QCAuth0Authorization(authorization=None)
5353

5454
@staticmethod
55-
def authorize(brokerage_id: str, logger: Logger) -> None:
55+
def authorize(brokerage_id: str, logger: Logger, project_id: int) -> None:
5656
"""Starts the authorization process for a brokerage.
5757
5858
:param brokerage_id: the id of the brokerage to start the authorization process for
5959
:param logger: the logger instance to use
60+
:param project_id: The local or cloud project_id
6061
"""
6162
from webbrowser import open
6263

63-
full_url = f"{API_BASE_URL}live/auth0/authorize?brokerage={brokerage_id}"
64+
full_url = f"{API_BASE_URL}live/auth0/authorize?brokerage={brokerage_id}&projectId={project_id}"
65+
6466
logger.info(f"Please open the following URL in your browser to authorize the LEAN CLI.")
6567
logger.info(full_url)
6668
open(full_url)

lean/components/config/lean_config_manager.py

+2
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ def get_complete_lean_config(self,
245245
"job-user-id": self._cli_config_manager.user_id.get_value(default="0"),
246246
"api-access-token": self._cli_config_manager.api_token.get_value(default=""),
247247
"job-organization-id": get_organization(config),
248+
"project-id": self._project_config_manager.get_project_id_from_project_config(
249+
algorithm_file.parent if algorithm_file else None),
248250

249251
"ib-host": "127.0.0.1",
250252
"ib-port": "4002",

lean/components/config/project_config_manager.py

+28
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,34 @@ def get_local_id(self, project_directory: Path) -> int:
6868

6969
return project_id
7070

71+
def get_project_id_from_project_config(self, project_directory: Path) -> int:
72+
"""
73+
Resolves the project ID from the configuration.
74+
75+
Args:
76+
project_directory (Path): The directory of the project. If None,
77+
it indicates the directory is unavailable.
78+
79+
Returns:
80+
int: Returns the 'cloud-id' if available.
81+
If 'cloud-id' is missing, returns the negative of 'local-id'.
82+
If neither is found nor if project_directory is None, returns -1.
83+
"""
84+
if project_directory is None:
85+
return -1
86+
87+
project_config = self.get_project_config(project_directory)
88+
89+
cloud_id = project_config.get("cloud-id")
90+
if cloud_id is not None:
91+
return cloud_id
92+
93+
local_id = project_config.get("local-id")
94+
if local_id is not None:
95+
return -local_id # Local ID must be negative.
96+
97+
return -1 # Return -1 if no valid IDs are found
98+
7199
def get_latest_live_directory(self, project_directory: Path) -> Path:
72100
"""Returns the path of the latest live directory.
73101

lean/components/util/auth0_helper.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
from lean.components.util.logger import Logger
1717

1818

19-
def get_authorization(auth0_client: Auth0Client, brokerage_id: str, logger: Logger) -> QCAuth0Authorization:
19+
def get_authorization(auth0_client: Auth0Client, brokerage_id: str, logger: Logger, project_id: int) -> QCAuth0Authorization:
2020
"""Gets the authorization data for a brokerage, authorizing if necessary.
2121
2222
:param auth0_client: An instance of Auth0Client, containing methods to interact with live/auth0/* API endpoints.
2323
:param brokerage_id: The ID of the brokerage to get the authorization data for.
2424
:param logger: An instance of Logger, handling all output printing.
25+
:param project_id: The local or cloud project_id.
2526
:return: The authorization data for the specified brokerage.
2627
"""
2728
from time import time, sleep
@@ -31,7 +32,7 @@ def get_authorization(auth0_client: Auth0Client, brokerage_id: str, logger: Logg
3132
return data
3233

3334
start_time = time()
34-
auth0_client.authorize(brokerage_id, logger)
35+
auth0_client.authorize(brokerage_id, logger, project_id)
3536

3637
# keep checking for new data every 5 seconds for 7 minutes
3738
while time() - start_time < 420:

lean/models/configuration.py

+1
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ class AuthConfiguration(InternalInputUserInput):
400400

401401
def __init__(self, config_json_object):
402402
super().__init__(config_json_object)
403+
self.require_project_id = config_json_object.get("require-project-id", False)
403404

404405
def factory(config_json_object) -> 'AuthConfiguration':
405406
"""Creates an instance of the child classes.

lean/models/json_module.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,20 @@ def convert_variable_to_lean_key(self, variable_key: str) -> str:
175175
"""
176176
return variable_key.replace('_', '-')
177177

178+
def get_project_id(self, default_project_id: int, require_project_id: bool) -> int:
179+
"""Retrieve the project ID, prompting the user if required and default is invalid.
180+
181+
:param default_project_id: The default project ID to use.
182+
:param require_project_id: Flag to determine if prompting is necessary.
183+
:return: A valid project ID.
184+
"""
185+
from click import prompt
186+
project_id: int = default_project_id
187+
if require_project_id and project_id <= 0:
188+
project_id = prompt("Please enter any cloud project ID to proceed with Auth0 authentication",
189+
-1, show_default=False)
190+
return project_id
191+
178192
def config_build(self,
179193
lean_config: Dict[str, Any],
180194
logger: Logger,
@@ -219,7 +233,11 @@ def config_build(self,
219233
logger.debug(f"skipping configuration '{configuration._id}': no choices available.")
220234
continue
221235
elif isinstance(configuration, AuthConfiguration):
222-
auth_authorizations = get_authorization(container.api_client.auth0, self._display_name.lower(), logger)
236+
lean_config["project-id"] = self.get_project_id(lean_config["project-id"],
237+
configuration.require_project_id)
238+
logger.debug(f'project_id: {lean_config["project-id"]}')
239+
auth_authorizations = get_authorization(container.api_client.auth0, self._display_name.lower(),
240+
logger, lean_config["project-id"])
223241
logger.debug(f'auth: {auth_authorizations}')
224242
configuration._value = auth_authorizations.get_authorization_config_without_account()
225243
for inner_config in self._lean_configs:

0 commit comments

Comments
 (0)