def test_debug_does_not_log_until_debug_logging_is_enabled(capsys: CaptureFixture) -> None: logger = Logger() logger.debug("Message 1") logger.debug_logging_enabled = True logger.debug("Message 2") assert_stdout_stderr(capsys, "Message 2\n", "")
def test_prompt_list_returns_id_of_selected_option(prompt: mock.Mock, capsys: CaptureFixture) -> None: logger = Logger() options = [Option(id=1, label="Option 1"), Option(id=2, label="Option 2"), Option(id=3, label="Option 3")] prompt.return_value = 3 selected_option = logger.prompt_list("Select an option", options) assert selected_option == 3 capsys.readouterr()
def test_progress_creates_started_progress_instance(capsys: CaptureFixture) -> None: logger = Logger() progress = logger.progress() result = progress._started progress.stop() assert result capsys.readouterr()
def test_prompt_returns_single_option_without_prompting_with_display_of_value(capsys: CaptureFixture) -> None: logger = Logger() options = [Option(id=1, label="Option 1")] selected_option = logger.prompt_list("Select an option", options) assert selected_option == 1 stdout, stderr = capsys.readouterr() assert "Select an option: Option 1" in stdout
def test_prompt_list_displays_all_options(prompt: mock.Mock, capsys: CaptureFixture) -> None: logger = Logger() options = [Option(id=1, label="Option 1"), Option(id=2, label="Option 2"), Option(id=3, label="Option 3")] prompt.return_value = 3 logger.prompt_list("Select an option", options) stdout, stderr = capsys.readouterr() assert "Option 1" in stdout assert "Option 2" in stdout assert "Option 3" in stdout
def test_error_logs_message(capsys: CaptureFixture) -> None: logger = Logger() logger.error("Message") assert_stdout_stderr(capsys, "Message\n", "")
def test_debug_logs_message(capsys: CaptureFixture) -> None: logger = Logger() logger.debug_logging_enabled = True logger.debug("Message") assert_stdout_stderr(capsys, "Message\n", "")
def test_cli() -> None: """Tests the CLI by actually calling it like a real user would do. Unlike "normal" tests, this file only contains a single test method which steps through all commands. This is done on purpose to make the test as close to what real users do as possible. """ user_id = USER_ID or os.getenv("QC_USER_ID", "") api_token = API_TOKEN or os.getenv("QC_API_TOKEN", "") if user_id == "" or api_token == "": pytest.skip("API credentials not specified") global_config_path = Path("~/.lean").expanduser() credentials_path = global_config_path / "credentials" # Create an empty directory to perform tests in test_dir = Path(tempfile.mkdtemp()) # We use project names suffixed by a timestamp to prevent conflicts when we synchronize with the cloud timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") python_project_name = f"Python Project {timestamp}" csharp_project_name = f"CSharp Project {timestamp}" # Unset all global configuration shutil.rmtree(global_config_path, ignore_errors=True) # Log in run_command(["lean", "login"], input=[user_id, api_token]) assert credentials_path.exists() assert json.loads(credentials_path.read_text(encoding="utf-8")) == { "user-id": user_id, "api-token": api_token } # Download sample data and LEAN configuration file run_command(["lean", "init"], cwd=test_dir, input=["python"]) assert (test_dir / "data").is_dir() assert (test_dir / "lean.json").is_file() # Generate random data # This is the first command that uses the LEAN Docker image, so we increase the timeout to have time to pull it generate_output = run_command([ "lean", "data", "generate", "--start", "20150101", "--symbol-count", "1", "--resolution", "Daily" ], cwd=test_dir, timeout=600) matches = re.findall( r"Begin data generation of 1 randomly generated Equity assets\.\.\.\nSymbol\[1]: ([A-Z]+)", generate_output) assert len(matches) == 1 assert (test_dir / "data" / "equity" / "usa" / "daily" / f"{matches[0].lower()}.zip").is_file() # Configure global settings run_command(["lean", "config", "set", "default-language", "csharp"]) run_command(["lean", "config", "get", "default-language"], expected_output="csharp") run_command(["lean", "config", "unset", "default-language"]) run_command(["lean", "config", "get", "default-language"], expected_return_code=1) run_command(["lean", "config", "set", "default-language", "python"]) run_command(["lean", "config", "get", "default-language"], expected_output="python") list_output = run_command(["lean", "config", "list"]) assert len(re.findall(r"default-language[ ]+[^ ] python", list_output)) == 1 # Create Python project run_command([ "lean", "create-project", "--language", "python", python_project_name ], cwd=test_dir) python_project_dir = test_dir / python_project_name assert (python_project_dir / "main.py").is_file() assert (python_project_dir / "research.ipynb").is_file() assert (python_project_dir / "config.json").is_file() assert (python_project_dir / ".vscode" / "launch.json").is_file() assert (python_project_dir / ".vscode" / "settings.json").is_file() assert (python_project_dir / ".idea" / f"{python_project_name}.iml").is_file() assert (python_project_dir / ".idea" / "misc.xml").is_file() assert (python_project_dir / ".idea" / "modules.xml").is_file() assert (python_project_dir / ".idea" / "workspace.xml").is_file() # Create C# project run_command([ "lean", "create-project", "--language", "csharp", csharp_project_name ], cwd=test_dir) csharp_project_dir = test_dir / csharp_project_name assert (csharp_project_dir / "Main.cs").is_file() assert (csharp_project_dir / "research.ipynb").is_file() assert (csharp_project_dir / "config.json").is_file() assert (csharp_project_dir / f"{csharp_project_name}.csproj").is_file() assert (csharp_project_dir / ".vscode" / "launch.json").is_file() # Copy over algorithms containing a SPY buy-and-hold strategy fixtures_dir = Path(__file__).parent / "fixtures" shutil.copy(fixtures_dir / "main.py", python_project_dir / "main.py") shutil.copy(fixtures_dir / "Main.cs", csharp_project_dir / "Main.cs") # Backtest Python project locally run_command(["lean", "backtest", python_project_name], cwd=test_dir, expected_output="Total Trades 1") python_backtest_dirs = list((python_project_dir / "backtests").iterdir()) assert len(python_backtest_dirs) == 1 # Backtest C# project locally run_command(["lean", "backtest", csharp_project_name], cwd=test_dir, expected_output="Total Trades 1") csharp_backtest_dirs = list((csharp_project_dir / "backtests").iterdir()) assert len(csharp_backtest_dirs) == 1 # Generate report run_command([ "lean", "report", "--backtest-data-source-file", f"{python_project_name}/backtests/{python_backtest_dirs[0].name}/main.json" ], cwd=test_dir) assert (test_dir / "report.html").is_file() # Push projects to the cloud run_command(["lean", "cloud", "push", "--project", python_project_name], cwd=test_dir) run_command(["lean", "cloud", "push", "--project", csharp_project_name], cwd=test_dir) # Remove some files and see if we can successfully pull them from the cloud (python_project_dir / "main.py").unlink() (csharp_project_dir / "Main.cs").unlink() # Pull projects from the cloud run_command(["lean", "cloud", "pull", "--project", python_project_name], cwd=test_dir) run_command(["lean", "cloud", "pull", "--project", csharp_project_name], cwd=test_dir) # Ensure deleted files have been pulled (python_project_dir / "main.py").is_file() (csharp_project_dir / "Main.cs").is_file() # Run Python backtest in the cloud run_command(["lean", "cloud", "backtest", python_project_name], cwd=test_dir) # Run C# backtest in the cloud run_command(["lean", "cloud", "backtest", csharp_project_name], cwd=test_dir) # Log out run_command(["lean", "logout"]) assert not credentials_path.exists() # Delete the test directory that we used shutil.rmtree(test_dir, ignore_errors=True) # Delete the cloud projects that we used api_client = APIClient(Logger(), user_id, api_token) cloud_projects = api_client.projects.get_all() api_client.projects.delete( next(p.projectId for p in cloud_projects if p.name == python_project_name)) api_client.projects.delete( next(p.projectId for p in cloud_projects if p.name == csharp_project_name))
def test_cli() -> None: """Tests the CLI by actually calling it like a real user would do. Unlike "normal" tests, this file only contains a single test method which steps through all commands. This is done on purpose to make the test as close to what real users do as possible. """ user_id = USER_ID or os.environ.get("QC_USER_ID", "") api_token = API_TOKEN or os.environ.get("QC_API_TOKEN", "") if user_id == "" or api_token == "": pytest.skip("API credentials not specified") credentials_path = Path("~/.lean").expanduser() / "credentials" # Create an empty directory to perform tests in test_dir = Path(tempfile.mkdtemp()) # We use project names suffixed by a timestamp to prevent conflicts when we synchronize with the cloud timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") python_project_name = f"Python Project {timestamp}" csharp_project_name = f"CSharp Project {timestamp}" # Log in run_command(["lean", "login"], input=[user_id, api_token]) assert credentials_path.exists() assert json.loads(credentials_path.read_text(encoding="utf-8")) == { "user-id": user_id, "api-token": api_token } # Check that we are logged in run_command(["lean", "whoami"]) # Download sample data and LEAN configuration file run_command(["lean", "init"], cwd=test_dir, input=["python"]) assert (test_dir / "data").is_dir() assert (test_dir / "lean.json").is_file() # Generate random data # This is the first command that uses the LEAN Docker image, so we increase the timeout to have time to pull it generate_output = run_command([ "lean", "data", "generate", "--start", "20150101", "--symbol-count", "1", "--resolution", "Daily" ], cwd=test_dir, timeout=600) matches = re.findall( r"Begin data generation of 1 randomly generated Equity assets\.\.\.\r?\n\s*Symbol\[1]: ([A-Z]+)", generate_output) assert len(matches) == 1 assert (test_dir / "data" / "equity" / "usa" / "daily" / f"{matches[0].lower()}.zip").is_file() # Configure global settings run_command(["lean", "config", "set", "default-language", "csharp"]) run_command(["lean", "config", "get", "default-language"], expected_output="csharp") run_command(["lean", "config", "unset", "default-language"]) run_command(["lean", "config", "get", "default-language"], expected_return_code=1) run_command(["lean", "config", "set", "default-language", "python"]) run_command(["lean", "config", "get", "default-language"], expected_output="python") list_output = run_command(["lean", "config", "list"]) assert len(re.findall(r"default-language[ ]+[^ ] python", list_output)) == 1 # Create Python project run_command([ "lean", "create-project", "--language", "python", python_project_name ], cwd=test_dir) python_project_dir = test_dir / python_project_name assert (python_project_dir / "main.py").is_file() assert (python_project_dir / "research.ipynb").is_file() assert (python_project_dir / "config.json").is_file() assert (python_project_dir / ".vscode" / "launch.json").is_file() assert (python_project_dir / ".vscode" / "settings.json").is_file() assert (python_project_dir / ".idea" / f"{python_project_name}.iml").is_file() assert (python_project_dir / ".idea" / "misc.xml").is_file() assert (python_project_dir / ".idea" / "modules.xml").is_file() assert (python_project_dir / ".idea" / "workspace.xml").is_file() # Create C# project run_command([ "lean", "create-project", "--language", "csharp", csharp_project_name ], cwd=test_dir) csharp_project_dir = test_dir / csharp_project_name assert (csharp_project_dir / "Main.cs").is_file() assert (csharp_project_dir / "research.ipynb").is_file() assert (csharp_project_dir / "config.json").is_file() assert (csharp_project_dir / f"{csharp_project_name}.csproj").is_file() assert (csharp_project_dir / ".vscode" / "launch.json").is_file() # Add custom Python library run_command(["lean", "library", "add", python_project_name, "altair"], cwd=test_dir) assert (python_project_dir / "requirements.txt").is_file() assert f"altair==" in (python_project_dir / "requirements.txt").read_text(encoding="utf-8") # Cannot add custom Python library incompatible with Python 3.6 run_command(["lean", "library", "add", python_project_name, "PyS3DE"], cwd=test_dir, expected_return_code=1) # Cannot add custom Python library without version when it's not on PyPI run_command( ["lean", "library", "add", python_project_name, str(uuid.uuid4())], cwd=test_dir, expected_return_code=1) # Cannot add custom Python library with version when version is invalid run_command([ "lean", "library", "add", python_project_name, "matplotlib", "--version", "0.0.0.0.0.1" ], cwd=test_dir, expected_return_code=1) # Cannot add custom Python library with version when version is incompatible with Python 3.6 run_command([ "lean", "library", "add", python_project_name, "matplotlib", "--version", "3.4.2" ], cwd=test_dir, expected_return_code=1) # Add custom C# library run_command( ["lean", "library", "add", csharp_project_name, "Microsoft.ML"], cwd=test_dir) csproj_file = csharp_project_dir / f"{csharp_project_name}.csproj" assert 'Include="Microsoft.ML"' in csproj_file.read_text(encoding="utf-8") # Cannot add custom C# library without version when it's not on NuGet run_command( ["lean", "library", "add", csharp_project_name, str(uuid.uuid4())], cwd=test_dir, expected_return_code=1) # Copy over algorithms containing a SPY buy-and-hold strategy with custom libraries fixtures_dir = Path(__file__).parent / "fixtures" shutil.copy(fixtures_dir / "local" / "main.py", python_project_dir / "main.py") shutil.copy(fixtures_dir / "local" / "Main.cs", csharp_project_dir / "Main.cs") # Backtest Python project locally run_command(["lean", "backtest", python_project_name], cwd=test_dir, expected_output="Total Trades 1") python_backtest_dirs = list((python_project_dir / "backtests").iterdir()) assert len(python_backtest_dirs) == 1 # Backtest C# project locally run_command(["lean", "backtest", csharp_project_name], cwd=test_dir, expected_output="Total Trades 1") csharp_backtest_dirs = list((csharp_project_dir / "backtests").iterdir()) assert len(csharp_backtest_dirs) == 1 # Remove custom Python library run_command(["lean", "library", "remove", python_project_name, "altair"], cwd=test_dir) assert f"altair==" not in (python_project_dir / "requirements.txt").read_text(encoding="utf-8") # Remove custom C# library run_command( ["lean", "library", "remove", csharp_project_name, "Microsoft.ML"], cwd=test_dir) assert 'Include="Microsoft.ML"' not in csproj_file.read_text( encoding="utf-8") # Custom Python library is removed, so Python backtest should now fail run_command(["lean", "backtest", python_project_name], cwd=test_dir, expected_return_code=1) # Custom C# library is removed, so C# backtest should now fail run_command(["lean", "backtest", csharp_project_name], cwd=test_dir, expected_return_code=1) # Generate reports python_results_file = next(f for f in python_backtest_dirs[0].iterdir() if f.name.endswith(".json") and not f.name.endswith("-order-events.json")) run_command([ "lean", "report", "--backtest-results", str(python_results_file), "--report-destination", "python.html" ], cwd=test_dir) csharp_results_file = next(f for f in csharp_backtest_dirs[0].iterdir() if f.name.endswith(".json") and not f.name.endswith("-order-events.json")) run_command([ "lean", "report", "--backtest-results", str(csharp_results_file), "--report-destination", "csharp.html" ], cwd=test_dir) assert (test_dir / "python.html").is_file() assert (test_dir / "csharp.html").is_file() # Copy over algorithms containing a SPY buy-and-hold strategy without custom libraries shutil.copy(fixtures_dir / "cloud" / "main.py", python_project_dir / "main.py") shutil.copy(fixtures_dir / "cloud" / "Main.cs", csharp_project_dir / "Main.cs") # Push projects to the cloud run_command(["lean", "cloud", "push", "--project", python_project_name], cwd=test_dir) run_command(["lean", "cloud", "push", "--project", csharp_project_name], cwd=test_dir) # Remove some files and see if we can successfully pull them from the cloud (python_project_dir / "main.py").unlink() (csharp_project_dir / "Main.cs").unlink() # Pull projects from the cloud run_command(["lean", "cloud", "pull", "--project", python_project_name], cwd=test_dir) run_command(["lean", "cloud", "pull", "--project", csharp_project_name], cwd=test_dir) # Ensure deleted files have been pulled (python_project_dir / "main.py").is_file() (csharp_project_dir / "Main.cs").is_file() # Run Python backtest in the cloud run_command(["lean", "cloud", "backtest", python_project_name], cwd=test_dir) # Run C# backtest in the cloud run_command(["lean", "cloud", "backtest", csharp_project_name], cwd=test_dir) # Get cloud project status run_command(["lean", "cloud", "status", python_project_name], cwd=test_dir) run_command(["lean", "cloud", "status", csharp_project_name], cwd=test_dir) # Log out run_command(["lean", "logout"]) assert not credentials_path.exists() # Delete the test directory that we used shutil.rmtree(test_dir, ignore_errors=True) # Delete the cloud projects that we used api_client = APIClient(Logger(), HTTPClient(Logger()), user_id, api_token) cloud_projects = api_client.projects.get_all() api_client.projects.delete( next(p.projectId for p in cloud_projects if p.name == python_project_name)) api_client.projects.delete( next(p.projectId for p in cloud_projects if p.name == csharp_project_name))