Skip to content

Commit eadc538

Browse files
authored
fix: running IQFeed client locally (#584)
* fix: running IQFeed client locally * refactor: run IQFeed app based on packages everywhere remove: IQFeed run in other part of source code base * refactor: use log warning instead of runtime error * fix: missed 'return'
1 parent 12ddac1 commit eadc538

2 files changed

Lines changed: 60 additions & 39 deletions

File tree

lean/commands/live/deploy.py

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -39,38 +39,6 @@
3939
"transaction-handler": "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler"
4040
}
4141

42-
43-
def _start_iqconnect_if_necessary(lean_config: Dict[str, Any], environment_name: str) -> None:
44-
"""Starts IQConnect if the given environment uses IQFeed as data queue handler.
45-
46-
:param lean_config: the LEAN configuration that should be used
47-
:param environment_name: the name of the environment
48-
"""
49-
from subprocess import Popen
50-
51-
environment = lean_config["environments"][environment_name]
52-
if environment["data-queue-handler"] != "QuantConnect.ToolBox.IQFeed.IQFeedDataQueueHandler":
53-
return
54-
55-
args = [lean_config["iqfeed-iqconnect"],
56-
"-product", lean_config["iqfeed-productName"],
57-
"-version", lean_config["iqfeed-version"]]
58-
59-
username = lean_config.get("iqfeed-username", "")
60-
if username != "":
61-
args.extend(["-login", username])
62-
63-
password = lean_config.get("iqfeed-password", "")
64-
if password != "":
65-
args.extend(["-password", password])
66-
67-
Popen(args)
68-
69-
container.logger.info("Waiting 10 seconds for IQFeed to start")
70-
from time import sleep
71-
sleep(10)
72-
73-
7442
def _get_history_provider_name(data_provider_live_names: [str]) -> [str]:
7543
""" Get name for history providers based on the live data providers
7644
@@ -285,8 +253,6 @@ def deploy(project: Path,
285253
raise MoreInfoError(f"The '{environment_name}' is not a live trading environment (live-mode is set to false)",
286254
"https://www.lean.io/docs/v2/lean-cli/live-trading/brokerages/quantconnect-paper-trading")
287255

288-
_start_iqconnect_if_necessary(lean_config, environment_name)
289-
290256
if python_venv is not None and python_venv != "":
291257
lean_config["python-venv"] = f'{"/" if python_venv[0] != "/" else ""}{python_venv}'
292258

lean/components/docker/lean_runner.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ def get_basic_docker_config(self,
233233
lean_config[key] = "host.docker.internal"
234234

235235
# Set up modules
236-
set_up_common_csharp_options_called = self._setup_installed_packages(run_options, image)
236+
set_up_common_csharp_options_called = self._setup_installed_packages(run_options, image, lean_config)
237237

238238
# Set up language-specific run options
239239
self.setup_language_specific_run_options(run_options, project_dir, algorithm_file,
@@ -295,7 +295,7 @@ def get_basic_docker_config_without_algo(self,
295295
lean_config[key] = "host.docker.internal"
296296

297297
# Set up modules
298-
self._setup_installed_packages(run_options, image, target_path)
298+
self._setup_installed_packages(run_options, image, lean_config, target_path)
299299

300300
self._mount_lean_config_and_finalize(run_options, lean_config, None, config_local_path)
301301

@@ -340,7 +340,8 @@ def _mount_lean_config_and_finalize(self, run_options: Dict[str, Any], lean_conf
340340
if "live-mode-brokerage" in environment:
341341
output_config.set("brokerage", environment["live-mode-brokerage"].split(".")[-1])
342342

343-
def _setup_installed_packages(self, run_options: Dict[str, Any], image: DockerImage, target_path: str = "/Lean/Launcher/bin/Debug"):
343+
def _setup_installed_packages(self, run_options: Dict[str, Any], image: DockerImage,
344+
lean_config: Dict[str, Any], target_path: str = "/Lean/Launcher/bin/Debug"):
344345
"""Sets up installed packages."""
345346
installed_packages = self._module_manager.get_installed_packages()
346347
if installed_packages:
@@ -364,6 +365,7 @@ def _setup_installed_packages(self, run_options: Dict[str, Any], image: DockerIm
364365
for package in installed_packages:
365366
self._logger.debug(f"LeanRunner._setup_installed_packages(): Adding module {package} to the project")
366367
run_options["commands"].append(f"rm -rf /root/.nuget/packages/{package.name.lower()}")
368+
self._ensure_iqconnect_running(lean_config, package.name)
367369
run_options["commands"].append(f"dotnet add /ModulesProject package {package.name} --version {package.version}")
368370

369371
# Copy all module files to /Lean/Launcher/bin/Debug, but don't overwrite anything that already exists
@@ -938,7 +940,7 @@ def parse_extra_docker_config(run_options: Dict[str, Any], extra_docker_config:
938940

939941
if "name" in extra_docker_config:
940942
run_options["name"] = extra_docker_config["name"]
941-
943+
942944
if "environment" in extra_docker_config:
943945
target = run_options.get("environment")
944946
if not target:
@@ -947,7 +949,7 @@ def parse_extra_docker_config(run_options: Dict[str, Any], extra_docker_config:
947949
target.update({item[0]: item[1] for item in [
948950
item if not isinstance(item, str) else (item.split("=")[0], item.split("=")[1]) for item in extra_docker_config["environment"]
949951
]})
950-
elif isinstance(extra_docker_config["environment"], dict):
952+
elif isinstance(extra_docker_config["environment"], dict):
951953
target.update(extra_docker_config["environment"])
952954
else:
953955
raise ValueError("Additional environment variables can be passed to the container in a dictionary, list of '{key}={value}' strings, or list of '(key, value)' tuples.")
@@ -975,3 +977,56 @@ def parse_extra_docker_config(run_options: Dict[str, Any], extra_docker_config:
975977
if "read_only" in mount:
976978
read_only = mount["read_only"]
977979
target.append(Mount(target=mount["target"], source=mount["source"], type="bind", read_only=read_only))
980+
981+
def _ensure_iqconnect_running(self, lean_config: Dict[str, Any], data_provider_package_name: str) -> None:
982+
"""
983+
Starts the IQConnect client if the given data provider is IQFeed.
984+
985+
:param lean_config: The LEAN configuration dictionary to use.
986+
:param data_provider_package_name: The fully qualified name of the data provider or data downloader.
987+
"""
988+
989+
if data_provider_package_name != "QuantConnect.Lean.DataSource.IQFeed":
990+
self._logger.debug(
991+
f"Skipped starting IQConnect: data provider '{data_provider_package_name}' is not IQFeed."
992+
)
993+
return
994+
995+
args = [
996+
lean_config["iqfeed-iqconnect"],
997+
"-product", lean_config["iqfeed-productName"],
998+
"-version", lean_config["iqfeed-version"],
999+
"-autoconnect"
1000+
]
1001+
1002+
username = lean_config.get("iqfeed-username", "")
1003+
if username != "":
1004+
args.extend(["-login", username])
1005+
1006+
password = lean_config.get("iqfeed-password", "")
1007+
if password != "":
1008+
args.extend(["-password", password])
1009+
1010+
from subprocess import Popen
1011+
1012+
try:
1013+
process = Popen(args)
1014+
except FileNotFoundError:
1015+
self._logger.warn(
1016+
"IQFeed executable not found. Please check:\n"
1017+
" - The path in 'args' is correct.\n"
1018+
" - IQFeed is installed.\n"
1019+
" - You have permission to access the file."
1020+
)
1021+
return
1022+
1023+
self._logger.info("Waiting 10 seconds for IQFeed to start")
1024+
from time import sleep
1025+
sleep(10)
1026+
1027+
if process.poll() is not None:
1028+
self._logger.warn(
1029+
f"IQFeed failed to start (exit code {process.returncode}). "
1030+
"It might already be running, or there was an error starting it. "
1031+
"Check if IQFeed is installed, the path is correct, and no issues with permissions."
1032+
)

0 commit comments

Comments
 (0)