Skip to content

Commit 757dbac

Browse files
authored
Fix Local Live Deploy Using Environment Option (#399)
* update essential properties when loading from envrionment * review * handle null case * auto fill essential properties * add test
1 parent 4dfc62a commit 757dbac

File tree

4 files changed

+112
-6
lines changed

4 files changed

+112
-6
lines changed

lean/commands/live/deploy.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@
2121
from lean.models.errors import MoreInfoError
2222
from lean.models.lean_config_configurer import LeanConfigConfigurer
2323
from lean.models.logger import Option
24-
from lean.models.configuration import InternalInputUserInput
24+
from lean.models.configuration import ConfigurationsEnvConfiguration, InternalInputUserInput
2525
from lean.models.click_options import options_from_json, get_configs_for_options
2626
from lean.models.json_module import LiveInitialStateInput
2727
from lean.commands.live.live import live
2828
from lean.components.util.live_utils import get_last_portfolio_cash_holdings, configure_initial_cash_balance, configure_initial_holdings,\
2929
_configure_initial_cash_interactively, _configure_initial_holdings_interactively
3030
from lean.models.data_providers import all_data_providers
31-
from lean.components.util.json_modules_handler import build_and_configure_modules, get_and_build_module
31+
from lean.components.util.json_modules_handler import build_and_configure_modules, get_and_build_module, update_essential_properties_available
3232

3333
_environment_skeleton = {
3434
"live-mode": True,
@@ -54,11 +54,20 @@ def _get_configurable_modules_from_environment(lean_config: Dict[str, Any], envi
5454

5555
brokerage = environment["live-mode-brokerage"]
5656
data_queue_handlers = environment["data-queue-handler"]
57-
[brokerage_configurer] = [local_brokerage for local_brokerage in all_local_brokerages if local_brokerage.get_live_name(environment_name) == brokerage]
58-
data_feed_configurers = [local_data_feed for local_data_feed in all_local_data_feeds if local_data_feed.get_live_name(environment_name) in data_queue_handlers]
57+
[brokerage_configurer] = [local_brokerage for local_brokerage in all_local_brokerages if _get_brokerage_base_name(local_brokerage.get_live_name(environment_name)) == _get_brokerage_base_name(brokerage)]
58+
data_queue_handlers_base_names = [_get_brokerage_base_name(data_queue_handler) for data_queue_handler in data_queue_handlers]
59+
data_feed_configurers = [local_data_feed for local_data_feed in all_local_data_feeds if _get_brokerage_base_name(local_data_feed.get_live_name(environment_name)) in data_queue_handlers_base_names]
5960
return brokerage_configurer, data_feed_configurers
6061

6162

63+
def _get_brokerage_base_name(brokerage: str) -> str:
64+
"""Returns the base name of the brokerage.
65+
66+
:param brokerage: the name of the brokerage
67+
:return: the base name of the brokerage
68+
"""
69+
return brokerage.split('.')[-1]
70+
6271
def _install_modules(modules: List[LeanConfigConfigurer], user_kwargs: Dict[str, Any]) -> None:
6372
"""Raises an error if any of the given modules are not installed.
6473
@@ -326,6 +335,52 @@ def deploy(project: Path,
326335
if environment is not None:
327336
environment_name = environment
328337
lean_config = lean_config_manager.get_complete_lean_config(environment_name, algorithm_file, None)
338+
339+
lean_environment = lean_config["environments"][environment_name]
340+
for key in ["live-mode-brokerage", "data-queue-handler"]:
341+
if key not in lean_environment:
342+
raise MoreInfoError(f"The '{environment_name}' environment does not specify a {key}",
343+
"https://www.lean.io/docs/v2/lean-cli/live-trading/algorithm-control")
344+
345+
brokerage = lean_environment["live-mode-brokerage"]
346+
data_queue_handlers = lean_environment["data-queue-handler"]
347+
data_queue_handlers_base_names = [_get_brokerage_base_name(data_queue_handler) for data_queue_handler in data_queue_handlers]
348+
data_feed_configurers = []
349+
350+
for local_brokerage in all_local_brokerages:
351+
configuration_environments: List[ConfigurationsEnvConfiguration] = [config for config in local_brokerage._lean_configs if config._is_type_configurations_env]
352+
for configuration_environment in configuration_environments:
353+
configuration_environment_values = list(configuration_environment._env_and_values.values())[0]
354+
if any(True for x in configuration_environment_values if x["name"] == "live-mode-brokerage" and _get_brokerage_base_name(x["value"]) == _get_brokerage_base_name(brokerage)):
355+
brokerage_configurer = local_brokerage
356+
# fill essential properties
357+
for condition in configuration_environment._filter._conditions:
358+
if condition._type != "exact-match":
359+
continue
360+
property_name_to_fill = local_brokerage.convert_lean_key_to_variable(condition._dependent_config_id)
361+
property_value_to_fill = condition._pattern
362+
kwargs[property_name_to_fill] = property_value_to_fill
363+
lean_config[condition._dependent_config_id] = property_value_to_fill
364+
break
365+
366+
for local_data_feed in all_local_data_feeds:
367+
configuration_environments: List[ConfigurationsEnvConfiguration] = [config for config in local_data_feed._lean_configs if config._is_type_configurations_env]
368+
for configuration_environment in configuration_environments:
369+
configuration_environment_values = list(configuration_environment._env_and_values.values())[0]
370+
if any(True for x in configuration_environment_values if x["name"] == "data-queue-handler" and _get_brokerage_base_name(x["value"]) in data_queue_handlers_base_names):
371+
data_feed_configurers.append(local_data_feed)
372+
# fill essential properties
373+
for condition in configuration_environment._filter._conditions:
374+
if condition._type != "exact-match":
375+
continue
376+
property_name_to_fill = local_data_feed.convert_lean_key_to_variable(condition._dependent_config_id)
377+
property_value_to_fill = condition._pattern
378+
kwargs[property_name_to_fill] = property_value_to_fill
379+
lean_config[condition._dependent_config_id] = property_value_to_fill
380+
381+
[update_essential_properties_available([brokerage_configurer], kwargs)]
382+
[update_essential_properties_available(data_feed_configurers, kwargs)]
383+
329384
elif brokerage is not None or len(data_feed) > 0:
330385
ensure_options(["brokerage", "data_feed"])
331386

lean/components/util/json_modules_handler.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,12 @@ def get_and_build_module(target_module_name: str, module_list: List[JsonModule],
5656
target_module.update_configs(required_properties_value)
5757
logger.debug(f"json_module_handler.get_and_build_module(): non-interactive: required_properties_value with module {target_module_name}: {required_properties_value}")
5858
return target_module
59+
60+
61+
def update_essential_properties_available(module_list: List[JsonModule], properties: Dict[str, Any]) -> JsonModule:
62+
for target_module in module_list:
63+
# update essential properties from brokerage to datafeed
64+
# needs to be updated before fetching required properties
65+
essential_properties = [target_module.convert_lean_key_to_variable(prop) for prop in target_module.get_essential_properties()]
66+
essential_properties_value = {target_module.convert_variable_to_lean_key(prop) : properties[prop] for prop in essential_properties if properties[prop] is not None}
67+
target_module.update_configs(essential_properties_value)

lean/models/json_module.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ def get_configurations_env_values_from_name(self, target_env: str) -> List[Dict[
9191
config._is_type_configurations_env and self.check_if_config_passes_filters(
9292
config)
9393
] or [None]
94-
if env_config is not None and target_env in env_config._env_and_values.keys():
95-
env_config_values = env_config._env_and_values[target_env]
94+
if env_config is not None:
95+
env_config_values = list(env_config._env_and_values.values())[0]
9696
return env_config_values
9797

9898
def get_config_from_type(self, config_type: Configuration) -> str:

tests/commands/test_live.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,34 @@ def create_fake_environment(name: str, live_mode: bool) -> None:
6565
path.write_text(config, encoding="utf-8")
6666

6767

68+
def create_fake_binance_environment(name: str, live_mode: bool) -> None:
69+
path = Path.cwd() / "lean.json"
70+
config = path.read_text(encoding="utf-8")
71+
config = config.replace("{", f"""
72+
{{
73+
"binance-use-testnet": "live",
74+
"binance-exchange-name": "binance",
75+
"binance-api-secret": "abc",
76+
"binance-api-key": "abc",
77+
"organization-id": "abc",
78+
79+
"environments": {{
80+
"{name}": {{
81+
"live-mode": {str(live_mode).lower()},
82+
83+
"live-mode-brokerage": "QuantConnect.BinanceBrokerage.BinanceCoinFuturesBrokerage",
84+
"data-queue-handler": [ "QuantConnect.BinanceBrokerage.BinanceCoinFuturesBrokerage" ],
85+
"setup-handler": "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
86+
"result-handler": "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
87+
"data-feed-handler": "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
88+
"real-time-handler": "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler",
89+
"transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler",
90+
"history-provider": [ "BrokerageHistoryProvider", "SubscriptionDataReaderHistoryProvider" ]
91+
}}
92+
}},
93+
""")
94+
95+
path.write_text(config, encoding="utf-8")
6896

6997
def test_live_calls_lean_runner_with_correct_algorithm_file() -> None:
7098
# TODO: currently it is not using the live-paper envrionment
@@ -258,6 +286,20 @@ def test_live_aborts_when_lean_config_is_missing_properties(target: str, replace
258286

259287
lean_runner.run_lean.assert_not_called()
260288

289+
def test_live_sets_dependent_configurations_from_modules_json_based_on_environment() -> None:
290+
create_fake_lean_cli_directory()
291+
create_fake_binance_environment("live-binance", True)
292+
lean_runner = container.lean_runner
293+
294+
config_path = Path.cwd() / "lean.json"
295+
config = config_path.read_text(encoding="utf-8")
296+
config_path.write_text(config.replace("binance-exchange-name", "different-config"), encoding="utf-8")
297+
298+
result = CliRunner().invoke(lean, ["live", "Python Project", "--environment", "live-binance"])
299+
300+
assert result.exit_code == 0
301+
302+
lean_runner.run_lean.assert_called()
261303

262304
terminal_link_required_options = {
263305
"terminal-link-connection-type": "SAPI",

0 commit comments

Comments
 (0)