Skip to content

Commit a7236c1

Browse files
authored
Feature: support new config type account-id (#500)
* feat: support `account-id` config * refactor: reuse interactive logic of `input-account-id` * refactor: update account_ids in AuthConfiguration feat: skip account ids if it is not provided * test:feat: Auth0Client * feat: support Prompt in AccountIdsConfiguration * feat: handle accountIds and provide different option to user * refactor: handle of accountIds in json_module * refactor: validate len api_accounts_ids refactor: use elif instead of if * feat: handle when api return None of api_account_ids * feat: skip validation of choices (not provided) * feat: filter dict config by regex condition * refactor: update of api_account_ids * remove: not used class AccountIdsConfiguration * remove: missed import class * remove: not used import * test:refactor: test_auth0_client * feat: new config of TredeStation brokerage in README * refactor: change spacing * feat: class Accounts in auth0_client * feat: skip choice type without choices * feat: new object account in Auth0 model refactor: parsing of new object Auth0 test:refactor: use new mock object of Auth0 * refactor: QCAuth0Authorization model * test:feat: add alpaca configuration test * remove: trade-station-account-type parameter (deprecated) * feat: add filter_dependency flag * refactor: remove optional in trade-station-account-id
1 parent 90984b5 commit a7236c1

File tree

6 files changed

+166
-30
lines changed

6 files changed

+166
-30
lines changed

README.md

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,6 @@ Options:
198198
Your ThetaData subscription price plan
199199
--terminal-link-connection-type [DAPI|SAPI]
200200
Terminal Link Connection Type [DAPI, SAPI]
201-
--terminal-link-server-auth-id TEXT
202-
The Auth ID of the TerminalLink server
203201
--terminal-link-environment [Production|Beta]
204202
The environment to run in
205203
--terminal-link-server-host TEXT
@@ -208,12 +206,14 @@ Options:
208206
The port of the TerminalLink server
209207
--terminal-link-openfigi-api-key TEXT
210208
The Open FIGI API key to use for mapping options
209+
--terminal-link-server-auth-id TEXT
210+
The Auth ID of the TerminalLink server
211211
--bybit-api-key TEXT Your Bybit API key
212212
--bybit-api-secret TEXT Your Bybit API secret
213213
--trade-station-environment [live|paper]
214214
Whether Live or Paper environment should be used
215-
--trade-station-account-type [Cash|Margin|Futures|DVP]
216-
Specifies the type of account on TradeStation
215+
--trade-station-account-id TEXT
216+
The TradeStation account Id
217217
--alpaca-environment [live|paper]
218218
Whether Live or Paper environment should be used
219219
--download-data Update the Lean configuration file to download data from the QuantConnect API, alias
@@ -435,8 +435,8 @@ Options:
435435
Your Bybit VIP Level
436436
--trade-station-environment [live|paper]
437437
Whether Live or Paper environment should be used
438-
--trade-station-account-type [Cash|Margin|Futures|DVP]
439-
Specifies the type of account on TradeStation
438+
--trade-station-account-id TEXT
439+
The TradeStation account Id
440440
--alpaca-environment [live|paper]
441441
Whether Live or Paper environment should be used
442442
--polygon-api-key TEXT Your Polygon.io API Key
@@ -900,8 +900,6 @@ Options:
900900
Your ThetaData subscription price plan
901901
--terminal-link-connection-type [DAPI|SAPI]
902902
Terminal Link Connection Type [DAPI, SAPI]
903-
--terminal-link-server-auth-id TEXT
904-
The Auth ID of the TerminalLink server
905903
--terminal-link-environment [Production|Beta]
906904
The environment to run in
907905
--terminal-link-server-host TEXT
@@ -910,12 +908,14 @@ Options:
910908
The port of the TerminalLink server
911909
--terminal-link-openfigi-api-key TEXT
912910
The Open FIGI API key to use for mapping options
911+
--terminal-link-server-auth-id TEXT
912+
The Auth ID of the TerminalLink server
913913
--bybit-api-key TEXT Your Bybit API key
914914
--bybit-api-secret TEXT Your Bybit API secret
915915
--trade-station-environment [live|paper]
916916
Whether Live or Paper environment should be used
917-
--trade-station-account-type [Cash|Margin|Futures|DVP]
918-
Specifies the type of account on TradeStation
917+
--trade-station-account-id TEXT
918+
The TradeStation account Id
919919
--alpaca-environment [live|paper]
920920
Whether Live or Paper environment should be used
921921
--dataset TEXT The name of the dataset to download non-interactively
@@ -1341,8 +1341,6 @@ Options:
13411341
commodities on MCX
13421342
--terminal-link-connection-type [DAPI|SAPI]
13431343
Terminal Link Connection Type [DAPI, SAPI]
1344-
--terminal-link-server-auth-id TEXT
1345-
The Auth ID of the TerminalLink server
13461344
--terminal-link-environment [Production|Beta]
13471345
The environment to run in
13481346
--terminal-link-server-host TEXT
@@ -1356,20 +1354,22 @@ Options:
13561354
--terminal-link-emsx-team TEXT The EMSX team to receive order events from (Optional).
13571355
--terminal-link-openfigi-api-key TEXT
13581356
The Open FIGI API key to use for mapping options
1357+
--terminal-link-server-auth-id TEXT
1358+
The Auth ID of the TerminalLink server
13591359
--tt-user-name TEXT Your Trading Technologies username
13601360
--tt-session-password TEXT Your Trading Technologies session password
13611361
--tt-account-name TEXT Your Trading Technologies account name
13621362
--tt-rest-app-key TEXT Your Trading Technologies REST app key
13631363
--tt-rest-app-secret TEXT Your Trading Technologies REST app secret
13641364
--tt-rest-environment TEXT The REST environment to run in
1365+
--tt-order-routing-sender-comp-id TEXT
1366+
The order routing sender comp id to use
13651367
--tt-market-data-sender-comp-id TEXT
13661368
The market data sender comp id to use
13671369
--tt-market-data-target-comp-id TEXT
13681370
The market data target comp id to use
13691371
--tt-market-data-host TEXT The host of the market data server
13701372
--tt-market-data-port TEXT The port of the market data server
1371-
--tt-order-routing-sender-comp-id TEXT
1372-
The order routing sender comp id to use
13731373
--tt-order-routing-target-comp-id TEXT
13741374
The order routing target comp id to use
13751375
--tt-order-routing-host TEXT The host of the order routing server
@@ -1392,8 +1392,8 @@ Options:
13921392
Whether the testnet should be used
13931393
--trade-station-environment [live|paper]
13941394
Whether Live or Paper environment should be used
1395-
--trade-station-account-type [Cash|Margin|Futures|DVP]
1396-
Specifies the type of account on TradeStation
1395+
--trade-station-account-id TEXT
1396+
The TradeStation account Id
13971397
--alpaca-environment [live|paper]
13981398
Whether Live or Paper environment should be used
13991399
--ib-enable-delayed-streaming-data BOOLEAN
@@ -1791,8 +1791,6 @@ Options:
17911791
Your ThetaData subscription price plan
17921792
--terminal-link-connection-type [DAPI|SAPI]
17931793
Terminal Link Connection Type [DAPI, SAPI]
1794-
--terminal-link-server-auth-id TEXT
1795-
The Auth ID of the TerminalLink server
17961794
--terminal-link-environment [Production|Beta]
17971795
The environment to run in
17981796
--terminal-link-server-host TEXT
@@ -1801,12 +1799,14 @@ Options:
18011799
The port of the TerminalLink server
18021800
--terminal-link-openfigi-api-key TEXT
18031801
The Open FIGI API key to use for mapping options
1802+
--terminal-link-server-auth-id TEXT
1803+
The Auth ID of the TerminalLink server
18041804
--bybit-api-key TEXT Your Bybit API key
18051805
--bybit-api-secret TEXT Your Bybit API secret
18061806
--trade-station-environment [live|paper]
18071807
Whether Live or Paper environment should be used
1808-
--trade-station-account-type [Cash|Margin|Futures|DVP]
1809-
Specifies the type of account on TradeStation
1808+
--trade-station-account-id TEXT
1809+
The TradeStation account Id
18101810
--alpaca-environment [live|paper]
18111811
Whether Live or Paper environment should be used
18121812
--lean-config FILE The Lean configuration file that should be used (defaults to the nearest lean.json)
@@ -1963,8 +1963,6 @@ Options:
19631963
Your ThetaData subscription price plan
19641964
--terminal-link-connection-type [DAPI|SAPI]
19651965
Terminal Link Connection Type [DAPI, SAPI]
1966-
--terminal-link-server-auth-id TEXT
1967-
The Auth ID of the TerminalLink server
19681966
--terminal-link-environment [Production|Beta]
19691967
The environment to run in
19701968
--terminal-link-server-host TEXT
@@ -1973,12 +1971,14 @@ Options:
19731971
The port of the TerminalLink server
19741972
--terminal-link-openfigi-api-key TEXT
19751973
The Open FIGI API key to use for mapping options
1974+
--terminal-link-server-auth-id TEXT
1975+
The Auth ID of the TerminalLink server
19761976
--bybit-api-key TEXT Your Bybit API key
19771977
--bybit-api-secret TEXT Your Bybit API secret
19781978
--trade-station-environment [live|paper]
19791979
Whether Live or Paper environment should be used
1980-
--trade-station-account-type [Cash|Margin|Futures|DVP]
1981-
Specifies the type of account on TradeStation
1980+
--trade-station-account-id TEXT
1981+
The TradeStation account Id
19821982
--alpaca-environment [live|paper]
19831983
Whether Live or Paper environment should be used
19841984
--download-data Update the Lean configuration file to download data from the QuantConnect API, alias

lean/models/api.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,34 @@
2121
# The models in this module are all parts of responses from the QuantConnect API
2222
# The keys of properties are not changed, so they don't obey the rest of the project's naming conventions
2323

24+
2425
class QCAuth0Authorization(WrappedBaseModel):
25-
authorization: Optional[Dict[str, str]]
26+
authorization: Optional[Dict[str, Any]]
27+
28+
def get_account_ids(self) -> List[str]:
29+
"""
30+
Retrieves a list of account IDs from the list of Account objects.
31+
32+
This method returns only the 'id' values from each account in the 'accounts' list.
33+
If there are no accounts, it returns an empty list.
34+
35+
Returns:
36+
List[str]: A list of account IDs.
37+
"""
38+
accounts = self.authorization.get('accounts', [])
39+
return [account["id"] for account in accounts] if accounts else []
40+
41+
def get_authorization_config_without_account(self) -> Dict[str, str]:
42+
"""
43+
Returns the authorization data without the 'accounts' key.
44+
45+
Iterates through the 'authorization' dictionary and excludes the 'accounts' entry.
46+
47+
Returns:
48+
Dict[str, str]: Authorization details excluding 'accounts'.
49+
"""
50+
return {key: value for key, value in self.authorization.items() if key != 'accounts'}
51+
2652

2753
class ProjectEncryptionKey(WrappedBaseModel):
2854
id: str

lean/models/click_options.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ def get_click_option_type(configuration: Configuration):
5656
if configuration._input_method == "confirm":
5757
return bool
5858
elif configuration._input_method == "choice":
59+
# Skip validation if no predefined choices in config and user provided input manually
60+
if not configuration._choices:
61+
return str
5962
return Choice(configuration._choices, case_sensitive=False)
6063
elif configuration._input_method == "prompt":
6164
return configuration.get_input_type()

lean/models/configuration.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@ def __init__(self, config_json_object):
9696
self._is_required_from_user = False
9797
self._save_persistently_in_lean = False
9898
self._log_message: str = ""
99+
self.has_filter_dependency: bool = False
99100
if "log-message" in config_json_object.keys():
100101
self._log_message = config_json_object["log-message"]
101102
if "filters" in config_json_object.keys():
102103
self._filter = Filter(config_json_object["filters"])
104+
self.has_filter_dependency = Filter.has_conditions
103105
else:
104106
self._filter = Filter([])
105107
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):
137139
self._conditions: List[BaseCondition] = [BaseCondition.factory(
138140
condition["condition"]) for condition in filter_conditions]
139141

142+
@property
143+
def has_conditions(self) -> bool:
144+
"""Returns True if there are any conditions, False otherwise."""
145+
return bool(self._conditions)
140146

141147
class InfoConfiguration(Configuration):
142148
"""Configuration class used for informational configurations.

lean/models/json_module.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from lean.constants import MODULE_TYPE, MODULE_PLATFORM, MODULE_CLI_PLATFORM
2323
from lean.container import container
2424
from lean.models.configuration import BrokerageEnvConfiguration, Configuration, InternalInputUserInput, \
25-
PathParameterUserInput, AuthConfiguration
25+
PathParameterUserInput, AuthConfiguration, ChoiceUserInput
2626
from copy import copy
2727
from abc import ABC
2828

@@ -60,13 +60,17 @@ def get_id(self):
6060

6161
def sort_configs(self) -> List[Configuration]:
6262
sorted_configs = []
63+
filter_configs = []
6364
brokerage_configs = []
6465
for config in self._lean_configs:
6566
if isinstance(config, BrokerageEnvConfiguration):
6667
brokerage_configs.append(config)
6768
else:
68-
sorted_configs.append(config)
69-
return brokerage_configs + sorted_configs
69+
if config.has_filter_dependency:
70+
filter_configs.append(config)
71+
else:
72+
sorted_configs.append(config)
73+
return brokerage_configs + sorted_configs + filter_configs
7074

7175
def get_name(self) -> str:
7276
"""Returns the user-friendly name which users can identify this object by.
@@ -86,7 +90,11 @@ def _check_if_config_passes_filters(self, config: Configuration, all_for_platfor
8690
# skip, we want all configurations that match type and platform, for help
8791
continue
8892
target_value = self.get_config_value_from_name(condition._dependent_config_id)
89-
if not target_value or not condition.check(target_value):
93+
if not target_value:
94+
return False
95+
elif isinstance(target_value, dict):
96+
return all(condition.check(value) for value in target_value.values())
97+
elif not condition.check(target_value):
9098
return False
9199
return True
92100

@@ -207,10 +215,27 @@ def config_build(self,
207215
_logged_messages.add(log_message)
208216
if type(configuration) is InternalInputUserInput:
209217
continue
218+
if isinstance(configuration, ChoiceUserInput) and len(configuration._choices) == 0:
219+
logger.debug(f"skipping configuration '{configuration._id}': no choices available.")
220+
continue
210221
elif isinstance(configuration, AuthConfiguration):
211222
auth_authorizations = get_authorization(container.api_client.auth0, self._display_name.lower(), logger)
212223
logger.debug(f'auth: {auth_authorizations}')
213-
configuration._value = auth_authorizations.authorization
224+
configuration._value = auth_authorizations.get_authorization_config_without_account()
225+
for inner_config in self._lean_configs:
226+
if any(condition._dependent_config_id == configuration._id for condition in
227+
inner_config._filter._conditions):
228+
api_account_ids = auth_authorizations.get_account_ids()
229+
config_dash = inner_config._id.replace('-', '_')
230+
inner_config._choices = api_account_ids
231+
if user_provided_options and config_dash in user_provided_options:
232+
user_provide_account_id = user_provided_options[config_dash]
233+
if (api_account_ids and len(api_account_ids) > 0 and
234+
not any(account_id.lower() == user_provide_account_id.lower()
235+
for account_id in api_account_ids)):
236+
raise ValueError(f"The provided account id '{user_provide_account_id}' is not valid, "
237+
f"available: {api_account_ids}")
238+
break
214239
continue
215240

216241
property_name = self.convert_lean_key_to_variable(configuration._id)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
2+
# Lean CLI v1.0. Copyright 2021 QuantConnect Corporation.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
import responses
15+
from unittest import mock
16+
from lean.constants import API_BASE_URL
17+
from lean.components.api.api_client import APIClient
18+
from lean.components.util.http_client import HTTPClient
19+
20+
21+
@responses.activate
22+
def test_auth0client_trade_station() -> None:
23+
api_clint = APIClient(mock.Mock(), HTTPClient(mock.Mock()), user_id="123", api_token="abc")
24+
25+
responses.add(
26+
responses.POST,
27+
f"{API_BASE_URL}live/auth0/read",
28+
json={
29+
"authorization": {
30+
"trade-station-client-id": "123",
31+
"trade-station-refresh-token": "456",
32+
"accounts": [
33+
{"id": "11223344", "name": "11223344 | Margin | USD"},
34+
{"id": "55667788", "name": "55667788 | Futures | USD"}
35+
]
36+
},
37+
"success": "true"},
38+
status=200
39+
)
40+
41+
brokerage_id = "TestBrokerage"
42+
43+
result = api_clint.auth0.read(brokerage_id)
44+
45+
assert result
46+
assert result.authorization
47+
assert len(result.authorization) > 0
48+
assert len(result.get_authorization_config_without_account()) > 0
49+
assert len(result.get_account_ids()) > 0
50+
51+
52+
@responses.activate
53+
def test_auth0client_alpaca() -> None:
54+
api_clint = APIClient(mock.Mock(), HTTPClient(mock.Mock()), user_id="123", api_token="abc")
55+
56+
responses.add(
57+
responses.POST,
58+
f"{API_BASE_URL}live/auth0/read",
59+
json={
60+
"authorization": {
61+
"alpaca-access-token": "XXXX-XXX-XXX-XXX-XXXXX-XX",
62+
"accounts": [{"id": "XXXX-XXX-XXX-XXX-XXXXX-XX", "name": " |USD"}]
63+
},
64+
"success": "true"},
65+
status=200
66+
)
67+
68+
brokerage_id = "TestBrokerage"
69+
70+
result = api_clint.auth0.read(brokerage_id)
71+
72+
assert result
73+
assert result.authorization
74+
assert len(result.authorization) > 0
75+
assert len(result.get_authorization_config_without_account()) > 0
76+
assert len(result.get_account_ids()) > 0

0 commit comments

Comments
 (0)