@@ -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