Пример #1
0
def _verify_accept_agreement(organization: QCFullOrganization, open_browser: bool) -> None:
    """ Verifies that the user has accepted the agreement.
    If they haven't, asks the user to accept the CLI API Access and Data Agreement.
    If they have, reminds them of the agreement and moves on.

    The API will enforce signing the agreement at the end of the day but this is how we keep it in the process of the CLI

    :param organization: the organization that the user selected
    :param open_browser: whether the CLI should automatically open the agreement in the browser
    """
    logger = container.logger()
    api_client = container.api_client()

    info = api_client.data.get_info(organization.id)

    # Is signed
    if organization.data.current:
        logger.info(_presigned_terms.format(link=info.agreement, signed_time=datetime.fromtimestamp(organization.data.signedTime)))
        sleep(1)
    else:
        if open_browser:
            webbrowser.open(info.agreement)

        logger.info(f"Go to the following url to accept the CLI API Access and Data Agreement:")
        logger.info(info.agreement)
        logger.info("Waiting until the CLI API Access and Data Agreement has been accepted...")

        container.task_manager().poll(
            make_request=lambda: api_client.organizations.get(organization.id),
            is_done=lambda data: data.data.current != False
        )
Пример #2
0
def login(user_id: Optional[str], api_token: Optional[str]) -> None:
    """Log in with a QuantConnect account.

    If user id or API token is not provided an interactive prompt will show.

    Credentials are stored in ~/.lean/credentials and are removed upon running `lean logout`.
    """
    logger = container.logger()
    credentials_storage = container.credentials_storage()

    if user_id is None or api_token is None:
        logger.info(
            "Your user id and API token are needed to make authenticated requests to the QuantConnect API"
        )
        logger.info(
            "You can request these credentials on https://www.quantconnect.com/account"
        )
        logger.info(f"Both will be saved in {credentials_storage.file}")

    if user_id is None:
        user_id = click.prompt("User id")

    if api_token is None:
        api_token = click.prompt("API token")

    api_client = container.api_client(user_id=user_id, api_token=api_token)
    if not api_client.is_authenticated():
        raise RuntimeError("Credentials are invalid")

    cli_config_manager = container.cli_config_manager()
    cli_config_manager.user_id.set_value(user_id)
    cli_config_manager.api_token.set_value(api_token)

    logger.info("Successfully logged in")
Пример #3
0
def _display_estimate(cloud_project: QCProject,
                      finished_compile: QCCompileWithLogs,
                      organization: QCFullOrganization, name: str,
                      strategy: str, target: OptimizationTarget,
                      parameters: List[OptimizationParameter],
                      constraints: List[OptimizationConstraint],
                      node: NodeType, parallel_nodes: int) -> None:
    """Displays the estimated optimization time and cost."""
    api_client = container.api_client()
    estimate = api_client.optimizations.estimate(cloud_project.projectId,
                                                 finished_compile.compileId,
                                                 name, strategy, target,
                                                 parameters, constraints,
                                                 node.name, parallel_nodes)

    backtest_count = _calculate_backtest_count(parameters)

    hours = _calculate_hours(estimate.time, backtest_count)
    batch_time = ceil((hours * 100) / parallel_nodes) / 100
    batch_cost = max(0.01, ceil(node.price * hours * 100) / 100)

    logger = container.logger()
    logger.info(f"Estimated number of backtests: {backtest_count:,}")
    logger.info(f"Estimated batch time: {_format_hours(batch_time)}")
    logger.info(f"Estimated batch cost: ${batch_cost:,.2f}")
    logger.info(
        f"Organization balance: {organization.credit.balance:,.0f} QCC (${organization.credit.balance / 100:,.2f})"
    )
Пример #4
0
    def _build(cls, lean_config: Dict[str, Any],
               logger: Logger) -> LocalBrokerage:
        api_client = container.api_client()

        organizations = api_client.organizations.get_all()
        options = [
            Option(id=organization.id, label=organization.name)
            for organization in organizations
        ]

        organization_id = logger.prompt_list(
            "Select the organization with the {} module subscription".format(
                cls.get_name()), options)

        logger.info("""
Create an API key by logging in and accessing the Binance API Management page (https://www.binance.com/en/my/settings/api-management).
        """.strip())

        api_key = click.prompt(
            "API key", cls._get_default(lean_config, "binance-api-key"))
        api_secret = logger.prompt_password(
            "API secret", cls._get_default(lean_config, "binance-api-secret"))
        testnet = click.confirm("Use the testnet?")

        return BinanceBrokerage(organization_id, api_key, api_secret, testnet)
Пример #5
0
def _get_available_datasets(organization: QCFullOrganization) -> List[Dataset]:
    """Retrieves the available datasets.

    :param organization: the organization that will be charged
    :return: the datasets which data can be downloaded from
    """
    cloud_datasets = container.api_client().market.list_datasets()
    data_information = _get_data_information(organization)

    available_datasets = []
    for cloud_dataset in cloud_datasets:
        if cloud_dataset.delivery == QCDatasetDelivery.CloudOnly:
            continue

        datasource = data_information.datasources.get(str(cloud_dataset.id), None)
        if datasource is None or isinstance(datasource, list):
            if cloud_dataset.name != "Template Data Source Product":
                name = cloud_dataset.name.strip()
                vendor = cloud_dataset.vendorName.strip()
                container.logger().debug(
                    f"There is no datasources entry for {name} by {vendor} (id {cloud_dataset.id})")
            continue

        available_datasets.append(Dataset(name=cloud_dataset.name.strip(),
                                          vendor=cloud_dataset.vendorName.strip(),
                                          categories=[tag.name.strip() for tag in cloud_dataset.tags],
                                          options=datasource["options"],
                                          paths=datasource["paths"],
                                          requires_security_master=datasource["requiresSecurityMaster"]))

    return available_datasets
Пример #6
0
def pull(project: Optional[str], pull_bootcamp: bool) -> None:
    """Pull projects from QuantConnect to the local drive.

    This command overrides the content of local files with the content of their respective counterparts in the cloud.

    This command will not delete local files for which there is no counterpart in the cloud.
    """
    api_client = container.api_client()
    all_projects = api_client.projects.get_all()

    # Parse which projects need to be pulled
    if project is not None:
        projects_to_pull = [
            p for p in all_projects
            if str(p.projectId) == project or p.name == project
        ]
        if len(projects_to_pull) == 0:
            raise RuntimeError(
                "No project with the given name or id exists in the cloud")
    else:
        projects_to_pull = all_projects
        if not pull_bootcamp:
            projects_to_pull = [
                p for p in projects_to_pull
                if not p.name.startswith("Boot Camp/")
            ]

    pull_manager = container.pull_manager()
    pull_manager.pull_projects(projects_to_pull)
Пример #7
0
    def _build(cls, lean_config: Dict[str, Any], logger: Logger) -> LocalBrokerage:
        api_client = container.api_client()

        organizations = api_client.organizations.get_all()
        options = [Option(id=organization.id, label=organization.name) for organization in organizations]

        organization_id = logger.prompt_list("Select the organization with the Atreyu module subscription", options)

        host = click.prompt("Host", cls._get_default(lean_config, "atreyu-host"))
        req_port = click.prompt("Request port", cls._get_default(lean_config, "atreyu-req-port"), type=int)
        sub_port = click.prompt("Subscribe port", cls._get_default(lean_config, "atreyu-sub-port"), type=int)

        username = click.prompt("Username", cls._get_default(lean_config, "atreyu-username"))
        password = logger.prompt_password("Password", cls._get_default(lean_config, "atreyu-password"))
        client_id = click.prompt("Client id", cls._get_default(lean_config, "atreyu-client-id"))
        broker_mpid = click.prompt("Broker MPID", cls._get_default(lean_config, "atreyu-broker-mpid"))
        locate_rqd = click.prompt("Locate rqd", cls._get_default(lean_config, "atreyu-locate-rqd"))

        return AtreyuBrokerage(organization_id,
                               host,
                               req_port,
                               sub_port,
                               username,
                               password,
                               client_id,
                               broker_mpid,
                               locate_rqd)
Пример #8
0
def whoami() -> None:
    """Display who is logged in."""
    logger = container.logger()
    api_client = container.api_client()
    cli_config_manager = container.cli_config_manager()

    if cli_config_manager.user_id.get_value(
    ) is not None and cli_config_manager.api_token.get_value() is not None:
        try:
            organizations = api_client.organizations.get_all()
            logged_in = True
        except AuthenticationError:
            logged_in = False
    else:
        logged_in = False

    if not logged_in:
        logger.info("You are not logged in")
        return

    personal_organization_id = next(o.id for o in organizations
                                    if o.ownerName == "You")
    personal_organization = api_client.organizations.get(
        personal_organization_id)
    member = next(m for m in personal_organization.members if m.isAdmin)

    logger.info(f"You are logged in as {member.name} ({member.email})")
Пример #9
0
def status(project: str) -> None:
    """Show the live trading status of a project in the cloud.

    PROJECT must be the name or the id of the project to show the status for.
    """
    logger = container.logger()
    api_client = container.api_client()

    cloud_project_manager = container.cloud_project_manager()
    cloud_project = cloud_project_manager.get_cloud_project(project, False)

    live_algorithm = next((d for d in api_client.live.get_all()
                           if d.projectId == cloud_project.projectId), None)

    logger.info(f"Project id: {cloud_project.projectId}")
    logger.info(f"Project name: {cloud_project.name}")
    logger.info(f"Project url: {cloud_project.get_url()}")

    if live_algorithm is None:
        logger.info("Live status: Not deployed")
        return

    live_status = {
        QCLiveAlgorithmStatus.DeployError: "Deploy error",
        QCLiveAlgorithmStatus.InQueue: "In queue",
        QCLiveAlgorithmStatus.RuntimeError: "Runtime error",
        QCLiveAlgorithmStatus.LoggingIn: "Logging in"
    }.get(live_algorithm.status, live_algorithm.status.value)

    brokerage_name = next(
        (b.name
         for b in all_cloud_brokerages if b.id == live_algorithm.brokerage),
        live_algorithm.brokerage)

    if brokerage_name == "PaperBrokerage":
        brokerage_name = "Paper Trading"

    logger.info(f"Live status: {live_status}")
    logger.info(f"Live id: {live_algorithm.deployId}")
    logger.info(f"Live url: {live_algorithm.get_url()}")
    logger.info(f"Brokerage: {brokerage_name}")
    logger.info(
        f"Launched: {live_algorithm.launched.strftime('%Y-%m-%d %H:%M:%S')} UTC"
    )

    if live_algorithm.stopped is not None:
        logger.info(
            f"Stopped: {live_algorithm.stopped.strftime('%Y-%m-%d %H:%M:%S')} UTC"
        )

    if live_algorithm.error != "":
        logger.info("Error:")
        logger.info(live_algorithm.error)
Пример #10
0
def _get_data_information(organization: QCFullOrganization) -> QCDataInformation:
    """Retrieves the datasources and prices information.

    :param organization: the organization to get the information for
    :return: the datasources and prices information
    """
    global _data_information

    if _data_information is None:
        _data_information = container.api_client().data.get_info(organization.id)

    return _data_information
Пример #11
0
def _select_organization() -> QCFullOrganization:
    """Asks the user for the organization that should be used.

    :return: the selected organization
    """
    api_client = container.api_client()

    organizations = api_client.organizations.get_all()
    options = [Option(id=organization.id, label=organization.name) for organization in organizations]

    logger = container.logger()
    organization_id = logger.prompt_list("Select the organization to purchase and download data with", options)

    return api_client.organizations.get(organization_id)
Пример #12
0
def _select_organization() -> QCMinimalOrganization:
    """Asks the user for the organization that should be charged when downloading data.

    :return: the selected organization
    """
    api_client = container.api_client()

    organizations = api_client.organizations.get_all()
    options = [
        Option(id=organization, label=organization.name)
        for organization in organizations
    ]

    logger = container.logger()
    return logger.prompt_list(
        "Select the organization to purchase and download data with", options)
Пример #13
0
    def build(cls, lean_config: Dict[str, Any],
              logger: Logger) -> LeanConfigConfigurer:
        api_client = container.api_client()

        organizations = api_client.organizations.get_all()
        options = [
            Option(id=organization.id, label=organization.name)
            for organization in organizations
        ]

        logger = container.logger()
        organization_id = logger.prompt_list(
            "Select the organization to purchase and download data with",
            options)

        return QuantConnectDataProvider(organization_id)
Пример #14
0
    def _build(cls, lean_config: Dict[str, Any],
               logger: Logger) -> LocalBrokerage:

        api_client = container.api_client()

        organizations = api_client.organizations.get_all()
        options = [
            Option(id=organization.id, label=organization.name)
            for organization in organizations
        ]

        organization_id = logger.prompt_list(
            "Select the organization with the Samco module subscription",
            options)

        client_id = click.prompt(
            "Client ID", cls._get_default(lean_config, "samco-client-id"))
        client_password = logger.prompt_password(
            "Client Password",
            cls._get_default(lean_config, "samco-client-password"))
        year_of_birth = click.prompt(
            "Year of Birth",
            cls._get_default(lean_config, "samco-year-of-birth"))

        logger.info("""
The product type must be set to MIS if you are targeting intraday products, CNC if you are targeting delivery products or NRML if you are targeting carry forward products.
        """.strip())

        product_type = click.prompt("Product type",
                                    cls._get_default(lean_config,
                                                     "samco-product-type"),
                                    type=click.Choice(["MIS", "CNC", "NRML"],
                                                      case_sensitive=False))

        logger.info("""
The trading segment must be set to EQUITY if you are trading equities on NSE or BSE, or COMMODITY if you are trading commodities on MCX.
        """.strip())

        trading_segment = click.prompt(
            "Trading segment",
            cls._get_default(lean_config, "samco-trading-segment"),
            type=click.Choice(["EQUITY", "COMMODITY"], case_sensitive=False))

        return SamcoBrokerage(organization_id, client_id, client_password,
                              year_of_birth, product_type, trading_segment)
Пример #15
0
    def _build(cls, lean_config: Dict[str, Any], logger: Logger) -> LocalBrokerage:
        api_client = container.api_client()

        organizations = api_client.organizations.get_all()
        options = [Option(id=organization.id, label=organization.name) for organization in organizations]

        organization_id = logger.prompt_list("Select the organization with the Terminal Link module subscription", options)

        environment = click.prompt("Environment",
                                   cls._get_default(lean_config, "bloomberg-environment"),
                                   type=click.Choice(["Production", "Beta"], case_sensitive=False))

        server_host = click.prompt("Server host", cls._get_default(lean_config, "bloomberg-server-host"))
        server_port = click.prompt("Server port", cls._get_default(lean_config, "bloomberg-server-port"), type=int)

        symbol_map_file = click.prompt("Path to symbol map file",
                                       cls._get_default(lean_config, "bloomberg-symbol-map-file") or "",
                                       type=PathParameter(exists=True, file_okay=True, dir_okay=False))

        emsx_broker = click.prompt("EMSX broker", cls._get_default(lean_config, "bloomberg-emsx-broker"))
        emsx_user_time_zone = click.prompt("EMSX user timezone",
                                           cls._get_default(lean_config, "bloomberg-emsx-user-time-zone") or "UTC")
        emsx_account = click.prompt("EMSX account", cls._get_default(lean_config, "bloomberg-emsx-account") or "")
        emsx_strategy = click.prompt("EMSX strategy", cls._get_default(lean_config, "bloomberg-emsx-strategy") or "")
        emsx_notes = click.prompt("EMSX notes", cls._get_default(lean_config, "bloomberg-emsx-notes") or "")
        emsx_handling = click.prompt("EMSX handling", cls._get_default(lean_config, "bloomberg-emsx-handling") or "")

        allow_modification = click.prompt("Allow modification (yes/no)",
                                          cls._get_default(lean_config, "bloomberg-allow-modification"),
                                          type=bool)

        return TerminalLinkBrokerage(organization_id,
                                     environment,
                                     server_host,
                                     server_port,
                                     symbol_map_file,
                                     emsx_broker,
                                     emsx_user_time_zone,
                                     emsx_account,
                                     emsx_strategy,
                                     emsx_notes,
                                     emsx_handling,
                                     allow_modification)
Пример #16
0
def _get_organization_id(given_input: str, shortcut_launch: bool) -> str:
    """Converts the organization name or id given by the user to an organization id.

    Raises an error if the user is not a member of an organization with the given name or id.

    :param given_input: the input given by the user
    :param shortcut_launch: whether the command was invoked using the desktop shortcut
    :return: the id of the organization given by the user
    """
    all_organizations = container.api_client().organizations.get_all()

    organization = next((o for o in all_organizations
                         if o.id == given_input or o.name == given_input),
                        None)
    if organization is None:
        _error(
            f"You are not a member of an organization with name or id '{given_input}'",
            shortcut_launch)

    return organization.id
Пример #17
0
def _map_files_to_vendors(organization: QCFullOrganization,
                          data_files: Iterable[str]) -> List[DataFile]:
    """Maps a list of files to the available data vendors.

    Uses the API to get the latest price information.
    Raises an error if there is no vendor that sells the data of a file in the given list.

    :param organization: the organization to use the price information of
    :param data_files: the data files to map to the available vendors
    :return: the list of data files containing the file and vendor for each file
    """
    global data_information
    if data_information is None:
        data_information = container.api_client().data.get_info(
            organization.id)

    last_vendor: Optional[QCDataVendor] = None
    mapped_files = []

    for file in data_files:
        if last_vendor is not None and last_vendor.regex.search(file):
            mapped_files.append(DataFile(file=file, vendor=last_vendor))
            continue

        last_vendor = None

        for vendor in data_information.prices:
            if vendor.price is None:
                continue

            if vendor.regex.search(file):
                mapped_files.append(DataFile(file=file, vendor=vendor))
                last_vendor = vendor
                break

        if last_vendor is None:
            raise RuntimeError(f"There is no data vendor that sells '{file}'")

    return mapped_files
Пример #18
0
    def _build(cls, lean_config: Dict[str, Any], logger: Logger) -> LocalBrokerage:
        
        api_client = container.api_client()

        organizations = api_client.organizations.get_all()
        options = [Option(id=organization.id, label=organization.name) for organization in organizations]

        organization_id = logger.prompt_list(
            "Select the organization with the Zerodha module subscription",
            options
        )

        logger.info("You need API credentials for Kite Connect (https://kite.trade/) to use the Zerodha brokerage.")

        api_key = click.prompt("API key", cls._get_default(lean_config, "zerodha-api-key"))
        access_token = logger.prompt_password("Access token", cls._get_default(lean_config, "zerodha-access-token"))

        logger.info("""
The product type must be set to MIS if you are targeting intraday products, CNC if you are targeting delivery products or NRML if you are targeting carry forward products.
        """.strip())

        product_type = click.prompt(
            "Product type",
            cls._get_default(lean_config, "zerodha-product-type"),
            type=click.Choice(["MIS", "CNC", "NRML"], case_sensitive=False)
        )

        logger.info("""
The trading segment must be set to EQUITY if you are trading equities on NSE or BSE, or COMMODITY if you are trading commodities on MCX.
        """.strip())

        trading_segment = click.prompt(
            "Trading segment",
            cls._get_default(lean_config, "zerodha-trading-segment"),
            type=click.Choice(["EQUITY", "COMMODITY"], case_sensitive=False)
        )

        return ZerodhaBrokerage(organization_id, api_key, access_token, product_type, trading_segment)
Пример #19
0
def _get_organization_by_name_or_id(user_input: str) -> QCFullOrganization:
    """Finds an organization by name or id.

    Raises an error if no organization with a matching name or id can be found.

    :param user_input: the input given by the user
    :return: the first organization with the given name or id
    """
    api_client = container.api_client()

    if re.match("^[a-f0-9]{32}$", user_input) is not None:
        try:
            return api_client.organizations.get(user_input)
        except:
            pass

    all_organizations = api_client.organizations.get_all()
    selected_organization = next((o for o in all_organizations if o.id == user_input or o.name == user_input), None)

    if selected_organization is None:
        raise RuntimeError(f"You are not a member of an organization with name or id '{user_input}'")

    return api_client.organizations.get(selected_organization.id)
Пример #20
0
    def _build(cls, lean_config: Dict[str, Any],
               logger: Logger) -> LocalBrokerage:
        api_client = container.api_client()

        organizations = api_client.organizations.get_all()
        options = [
            Option(id=organization.id, label=organization.name)
            for organization in organizations
        ]

        organization_id = logger.prompt_list(
            "Select the organization with the {} module subscription".format(
                cls.get_name()), options)

        exchange_name = click.prompt(
            "FTX Exchange [FTX|FTXUS]",
            cls._get_default(lean_config, "ftx-exchange-name"))
        exchange = FTXExchange() if exchange_name.casefold() == "FTX".casefold(
        ) else FTXUSExchange()

        logger.info("""
Create an API key by logging in and accessing the {} Profile page (https://{}/profile).
        """.format(exchange.get_name(), exchange.get_domain()).strip())

        prefix = exchange.prefix()
        api_key = click.prompt(
            "API key", cls._get_default(lean_config, f'{prefix}-api-key'))
        api_secret = logger.prompt_password(
            "API secret", cls._get_default(lean_config,
                                           f'{prefix}-api-secret'))

        account_tier = logger.prompt_list(
            "Select the Account Tier", exchange.account_tier_options(),
            cls._get_default(lean_config, f'{prefix}-account-tier'))

        return FTXBrokerage(organization_id, api_key, api_secret, account_tier,
                            exchange_name)
Пример #21
0
def _accept_agreement(organization: QCFullOrganization) -> None:
    """Asks the user to accept the CLI API Access and Data Agreement.

    :param organization: the organization that the user selected
    """
    logger = container.logger()
    api_client = container.api_client()

    info = api_client.data.get_info(organization.id)

    webbrowser.open(info.agreement)

    logger.info(
        f"Go to the following url to accept the CLI API Access and Data Agreement:"
    )
    logger.info(info.agreement)
    logger.info(
        "Waiting until the CLI API Access and Data Agreement has been accepted..."
    )

    container.task_manager().poll(
        make_request=lambda: api_client.organizations.get(organization.id),
        is_done=lambda data: data.data.signedTime != organization.data.
        signedTime)
Пример #22
0
    def _build(cls, lean_config: Dict[str, Any], logger: Logger) -> LocalBrokerage:
        api_client = container.api_client()

        organizations = api_client.organizations.get_all()
        options = [Option(id=organization.id, label=organization.name) for organization in organizations]

        organization_id = logger.prompt_list(
            "Select the organization with the Kraken module subscription",
            options
        )

        logger.info("""
Create an API key by logging in and accessing the Kraken API Management page (https://www.kraken.com/u/security/api).
        """.strip())

        api_key = click.prompt("API key", cls._get_default(lean_config, "kraken-api-key"))
        api_secret = logger.prompt_password("API secret", cls._get_default(lean_config, "kraken-api-secret"))

        verification_tier = logger.prompt_list("Select the Verification Tier",
            [Option(id="Starter", label="Starter"), Option(id="Intermediate", label="Intermediate"), Option(id="Pro", label="Pro")],
            cls._get_default(lean_config, "kraken-verification-tier")
        )

        return KrakenBrokerage(organization_id, api_key, api_secret, verification_tier)
Пример #23
0
def init() -> None:
    """Scaffold a Lean configuration file and data directory."""
    current_dir = Path.cwd()
    data_dir = current_dir / DEFAULT_DATA_DIRECTORY_NAME
    lean_config_path = current_dir / DEFAULT_LEAN_CONFIG_FILE_NAME

    # Abort if one of the files we are going to create already exists to prevent us from overriding existing files
    for path in [data_dir, lean_config_path]:
        if path.exists():
            relative_path = path.relative_to(current_dir)
            raise MoreInfoError(f"{relative_path} already exists, please run this command in an empty directory",
                                "https://www.lean.io/docs/lean-cli/initialization/directory-structure#02-lean-init")

    logger = container.logger()

    # Warn the user if the current directory is not empty
    if next(current_dir.iterdir(), None) is not None:
        logger.info("This command will create a Lean configuration file and data directory in the current directory")
        click.confirm("The current directory is not empty, continue?", default=False, abort=True)

    # Download the Lean repository
    tmp_directory = container.temp_manager().create_temporary_directory()
    _download_repository(tmp_directory / "master.zip")

    # Extract the downloaded repository
    with zipfile.ZipFile(tmp_directory / "master.zip") as zip_file:
        zip_file.extractall(tmp_directory / "master")

    # Copy the data directory
    shutil.copytree(tmp_directory / "master" / "Lean-master" / "Data", data_dir)

    # Create the config file
    lean_config_manager = container.lean_config_manager()
    config = (tmp_directory / "master" / "Lean-master" / "Launcher" / "config.json").read_text(encoding="utf-8")
    config = lean_config_manager.clean_lean_config(config)
    lean_config_manager.store_known_lean_config_path(lean_config_path)

    # Update the data-folder configuration
    config = config.replace('"data-folder": "../../../Data/"', f'"data-folder": "{DEFAULT_DATA_DIRECTORY_NAME}"')

    with lean_config_path.open("w+", encoding="utf-8") as file:
        file.write(config)

    # Prompt for some general configuration if not set yet
    cli_config_manager = container.cli_config_manager()
    if cli_config_manager.default_language.get_value() is None:
        default_language = click.prompt("What should the default language for new projects be?",
                                        type=click.Choice(cli_config_manager.default_language.allowed_values))
        cli_config_manager.default_language.set_value(default_language)

    logger.info(f"""
The following objects have been created:
- {DEFAULT_LEAN_CONFIG_FILE_NAME} contains the configuration used when running the LEAN engine locally
- {DEFAULT_DATA_DIRECTORY_NAME}/ contains the data that is used when running the LEAN engine locally

The following documentation pages may be useful:
- Setting up local autocomplete: https://www.lean.io/docs/lean-cli/projects/autocomplete
- Synchronizing projects with the cloud: https://www.lean.io/docs/lean-cli/projects/cloud-synchronization

Here are some commands to get you going:
- Run `lean create-project "My Project"` to create a new project with starter code
- Run `lean cloud pull` to download all your QuantConnect projects to your local drive
- Run `lean backtest "My Project"` to backtest a project locally with the data in {DEFAULT_DATA_DIRECTORY_NAME}/
""".strip())

    # Prompt to create a desktop shortcut for the local GUI if the user is in an organization with a subscription
    api_client = container.api_client()
    if api_client.is_authenticated():
        for simple_organization in api_client.organizations.get_all():
            organization = api_client.organizations.get(simple_organization.id)
            modules_product = next((p for p in organization.products if p.name == "Modules"), None)
            if modules_product is None:
                continue

            if any(i for i in modules_product.items if i.productId in GUI_PRODUCT_SUBSCRIPTION_IDS):
                container.shortcut_manager().prompt_if_necessary(simple_organization.id)
                break
Пример #24
0
    def _build(cls, lean_config: Dict[str, Any],
               logger: Logger) -> LocalBrokerage:
        api_client = container.api_client()

        organizations = api_client.organizations.get_all()
        options = [
            Option(id=organization.id, label=organization.name)
            for organization in organizations
        ]

        organization_id = logger.prompt_list(
            "Select the organization with the Trading Technologies module subscription",
            options)

        user_name = click.prompt("User name",
                                 cls._get_default(lean_config, "tt-user-name"))
        session_password = logger.prompt_password(
            "Session password",
            cls._get_default(lean_config, "tt-session-password"))
        account_name = click.prompt(
            "Account name", cls._get_default(lean_config, "tt-account-name"))

        rest_app_key = click.prompt(
            "REST app key", cls._get_default(lean_config, "tt-rest-app-key"))
        rest_app_secret = logger.prompt_password(
            "REST app secret",
            cls._get_default(lean_config, "tt-rest-app-secret"))
        rest_environment = click.prompt(
            "REST environment",
            cls._get_default(lean_config, "tt-rest-environment"))

        market_data_sender_comp_id = click.prompt(
            "Market data sender comp id",
            cls._get_default(lean_config, "tt-market-data-sender-comp-id"))
        market_data_target_comp_id = click.prompt(
            "Market data target comp id",
            cls._get_default(lean_config, "tt-market-data-target-comp-id"))
        market_data_host = click.prompt(
            "Market data host",
            cls._get_default(lean_config, "tt-market-data-host"))
        market_data_port = click.prompt(
            "Market data port",
            cls._get_default(lean_config, "tt-market-data-port"))

        order_routing_sender_comp_id = click.prompt(
            "Order routing sender comp id",
            cls._get_default(lean_config, "tt-order-routing-sender-comp-id"))
        order_routing_target_comp_id = click.prompt(
            "Order routing target comp id",
            cls._get_default(lean_config, "tt-order-routing-target-comp-id"))
        order_routing_host = click.prompt(
            "Order routing host",
            cls._get_default(lean_config, "tt-order-routing-host"))
        order_routing_port = click.prompt(
            "Order routing port",
            cls._get_default(lean_config, "tt-order-routing-port"))

        log_fix_messages = click.prompt("Log FIX messages (yes/no)",
                                        cls._get_default(
                                            lean_config,
                                            "tt-log-fix-messages"),
                                        type=bool)

        return TradingTechnologiesBrokerage(
            organization_id, user_name, session_password, account_name,
            rest_app_key, rest_app_secret, rest_environment,
            market_data_sender_comp_id, market_data_target_comp_id,
            market_data_host, market_data_port, order_routing_sender_comp_id,
            order_routing_target_comp_id, order_routing_host,
            order_routing_port, log_fix_messages)
Пример #25
0
 def _list_files(self, prefix: str) -> Tuple[str, Optional[List[str]]]:
     if len(prefix.split("/")) < 3:
         # Cannot get cloud directory listing less than 3 levels deep
         return prefix, None
     else:
         return prefix, container.api_client().data.list_files(prefix)
Пример #26
0
def optimize(project: str, name: Optional[str], push: bool) -> None:
    """Optimize a project in the cloud.

    An interactive prompt will be shown to configure the optimizer.

    PROJECT must be the name or id of the project to optimize.

    If the project that has to be optimized has been pulled to the local drive
    with `lean cloud pull` it is possible to use the --push option to push local
    modifications to the cloud before running the optimization.
    """
    logger = container.logger()
    api_client = container.api_client()

    cloud_project_manager = container.cloud_project_manager()
    cloud_project = cloud_project_manager.get_cloud_project(project, push)

    if name is None:
        name = container.name_generator().generate_name()

    cloud_runner = container.cloud_runner()
    finished_compile = cloud_runner.compile_project(cloud_project)

    optimizer_config_manager = container.optimizer_config_manager()
    optimization_strategy = optimizer_config_manager.configure_strategy(
        cloud=True)
    optimization_target = optimizer_config_manager.configure_target()
    optimization_parameters = optimizer_config_manager.configure_parameters(
        cloud_project.parameters, cloud=True)
    optimization_constraints = optimizer_config_manager.configure_constraints()

    backtest_count = _calculate_backtest_count(optimization_parameters)

    organization = api_client.organizations.get(cloud_project.organizationId)

    while True:
        node, parallel_nodes = optimizer_config_manager.configure_node()

        estimate = api_client.optimizations.estimate(
            cloud_project.projectId, finished_compile.compileId, name,
            optimization_strategy, optimization_target,
            optimization_parameters, optimization_constraints, node.name,
            parallel_nodes)

        hours = _calculate_hours(estimate.time, backtest_count)
        batch_time = ceil((hours * 100) / parallel_nodes) / 100
        batch_cost = max(0.01, ceil(node.price * hours * 100) / 100)

        logger.info(f"Estimated number of backtests: {backtest_count:,}")
        logger.info(f"Estimated batch time: {_format_hours(batch_time)}")
        logger.info(f"Estimated batch cost: ${batch_cost:,.2f}")
        logger.info(
            f"Organization balance: {organization.credit.balance:,.0f} QCC (${organization.credit.balance / 100:,.2f})"
        )

        if click.confirm(
                "Do you want to start the optimization on the selected node type?",
                default=True):
            break

    optimization = cloud_runner.run_optimization(
        cloud_project, finished_compile, name, optimization_strategy,
        optimization_target, optimization_parameters, optimization_constraints,
        node.name, parallel_nodes)

    backtests = optimization.backtests.values()
    backtests = [b for b in backtests if b.exitCode == 0]
    backtests = [
        b for b in backtests
        if _backtest_meets_constraints(b, optimization_constraints)
    ]

    if len(backtests) == 0:
        logger.info(
            "No optimal parameter combination found, no successful backtests meet all constraints"
        )
        return

    optimal_backtest = sorted(backtests,
                              key=lambda backtest: _get_backtest_statistic(
                                  backtest, optimization_target.target),
                              reverse=optimization_target.extremum ==
                              OptimizationExtremum.Maximum)[0]

    parameters = ", ".join(f"{key}: {optimal_backtest.parameterSet[key]}"
                           for key in optimal_backtest.parameterSet)
    logger.info(f"Optimal parameters: {parameters}")

    optimal_backtest = api_client.backtests.get(cloud_project.projectId,
                                                optimal_backtest.id)

    logger.info(f"Optimal backtest id: {optimal_backtest.backtestId}")
    logger.info(f"Optimal backtest name: {optimal_backtest.name}")
    logger.info(f"Optimal backtest results:")
    logger.info(optimal_backtest.get_statistics_table())
Пример #27
0
def live(project: str, brokerage: str, ib_user_name: Optional[str],
         ib_account: Optional[str], ib_password: Optional[str],
         ib_data_feed: Optional[bool], tradier_account_id: Optional[str],
         tradier_access_token: Optional[str],
         tradier_environment: Optional[str], oanda_account_id: Optional[str],
         oanda_access_token: Optional[str], oanda_environment: Optional[str],
         bitfinex_api_key: Optional[str], bitfinex_api_secret: Optional[str],
         gdax_api_key: Optional[str], gdax_api_secret: Optional[str],
         gdax_passphrase: Optional[str], gdax_environment: Optional[str],
         binance_api_key: Optional[str], binance_api_secret: Optional[str],
         binance_environment: Optional[str], kraken_api_key: Optional[str],
         kraken_api_secret: Optional[str],
         kraken_verification_tier: Optional[str], ftx_api_key: Optional[str],
         ftx_api_secret: Optional[str], ftx_account_tier: Optional[str],
         ftx_exchange_name: Optional[str], zerodha_api_key: Optional[str],
         zerodha_access_token: Optional[str],
         zerodha_product_type: Optional[str],
         zerodha_trading_segment: Optional[str],
         samco_client_id: Optional[str], samco_client_password: Optional[str],
         samco_year_of_birth: Optional[str], samco_product_type: Optional[str],
         samco_trading_segment: Optional[str], node: str, auto_restart: bool,
         notify_order_events: Optional[bool], notify_insights: Optional[bool],
         notify_emails: Optional[str], notify_webhooks: Optional[str],
         notify_sms: Optional[str], push: bool, open_browser: bool) -> None:
    """Start live trading for a project in the cloud.

    PROJECT must be the name or the id of the project to start live trading for.

    By default an interactive wizard is shown letting you configure the deployment.
    If --brokerage is given the command runs in non-interactive mode.
    In this mode the CLI does not prompt for input or confirmation.
    In non-interactive mode the options specific to the given brokerage are required,
    as well as --node, --auto-restart, --notify-order-events and --notify-insights.
    """
    logger = container.logger()
    api_client = container.api_client()

    cloud_project_manager = container.cloud_project_manager()
    cloud_project = cloud_project_manager.get_cloud_project(project, push)

    cloud_runner = container.cloud_runner()
    finished_compile = cloud_runner.compile_project(cloud_project)

    if brokerage is not None:
        ensure_options([
            "brokerage", "node", "auto_restart", "notify_order_events",
            "notify_insights"
        ])

        brokerage_instance = None

        if brokerage == PaperTradingBrokerage.get_name():
            brokerage_instance = PaperTradingBrokerage()
        elif brokerage == InteractiveBrokersBrokerage.get_name():
            ensure_options(
                ["ib_user_name", "ib_account", "ib_password", "ib_data_feed"])
            brokerage_instance = InteractiveBrokersBrokerage(
                ib_user_name, ib_account, ib_password, ib_data_feed)
        elif brokerage == TradierBrokerage.get_name():
            ensure_options([
                "tradier_account_id", "tradier_access_token",
                "tradier_environment"
            ])
            brokerage_instance = TradierBrokerage(tradier_account_id,
                                                  tradier_access_token,
                                                  tradier_environment)
        elif brokerage == OANDABrokerage.get_name():
            ensure_options([
                "oanda_account_id", "oanda_access_token", "oanda_environment"
            ])
            brokerage_instance = OANDABrokerage(oanda_account_id,
                                                oanda_access_token,
                                                oanda_environment)
        elif brokerage == BitfinexBrokerage.get_name():
            ensure_options(["bitfinex_api_key", "bitfinex_api_secret"])
            brokerage_instance = BitfinexBrokerage(bitfinex_api_key,
                                                   bitfinex_api_secret)
        elif brokerage == CoinbaseProBrokerage.get_name():
            ensure_options([
                "gdax_api_key", "gdax_api_secret", "gdax_passphrase",
                "gdax_environment"
            ])
            brokerage_instance = CoinbaseProBrokerage(gdax_api_key,
                                                      gdax_api_secret,
                                                      gdax_passphrase,
                                                      gdax_environment)
        elif brokerage == BinanceBrokerage.get_name():
            ensure_options([
                "binance_api_key", "binance_api_secret", "binance_environment"
            ])
            brokerage_instance = BinanceBrokerage(binance_api_key,
                                                  binance_api_secret,
                                                  binance_environment)
        elif brokerage == KrakenBrokerage.get_name():
            ensure_options([
                "kraken_api_key", "kraken_api_secret",
                "kraken_verification_tier"
            ])
            brokerage_instance = KrakenBrokerage(kraken_api_key,
                                                 kraken_api_secret,
                                                 kraken_verification_tier)
        elif brokerage == FTXBrokerage.get_name():
            ensure_options([
                "ftx_api_key", "ftx_api_secret", "ftx_account_tier",
                "ftx_exchange_name"
            ])
            brokerage_instance = FTXBrokerage(ftx_api_key, ftx_api_secret,
                                              ftx_account_tier,
                                              ftx_exchange_name)
        elif brokerage == ZerodhaBrokerage.get_name():
            ensure_options([
                "zerodha_api_key", "zerodha_access_token",
                "zerodha_product_type", "zerodha_trading_segment"
            ])
            brokerage_instance = ZerodhaBrokerage(zerodha_api_key,
                                                  zerodha_access_token,
                                                  zerodha_product_type,
                                                  zerodha_trading_segment)
        elif brokerage == SamcoBrokerage.get_name():
            ensure_options([
                "samco_client_id", "samco_client_password",
                "samco_year_of_birth", "samco_product_type",
                "samco_trading_segment"
            ])
            brokerage_instance = SamcoBrokerage(samco_client_id,
                                                samco_client_password,
                                                samco_year_of_birth,
                                                samco_product_type,
                                                samco_trading_segment)

        all_nodes = api_client.nodes.get_all(cloud_project.organizationId)
        live_node = next(
            (n for n in all_nodes.live if n.id == node or n.name == node),
            None)

        if live_node is None:
            raise RuntimeError(
                f"You have no live node with name or id '{node}'")

        if live_node.busy:
            raise RuntimeError(
                f"The live node named '{live_node.name}' is already in use by '{live_node.usedBy}'"
            )

        notify_methods = []

        if notify_emails is not None:
            for config in notify_emails.split(","):
                address, subject = config.split(":")
                notify_methods.append(
                    QCEmailNotificationMethod(address=address,
                                              subject=subject))

        if notify_webhooks is not None:
            for config in notify_webhooks.split(","):
                address, *headers = config.split(":")
                headers = {
                    header.split("=")[0]: header.split("=")[1]
                    for header in headers
                }
                notify_methods.append(
                    QCWebhookNotificationMethod(address=address,
                                                headers=headers))

        if notify_sms is not None:
            for phoneNumber in notify_sms.split(","):
                notify_methods.append(
                    QCSMSNotificationMethod(phoneNumber=phoneNumber))
    else:
        brokerage_instance = _configure_brokerage(logger)
        live_node = _configure_live_node(logger, api_client, cloud_project)
        notify_order_events, notify_insights, notify_methods = _configure_notifications(
            logger)
        auto_restart = _configure_auto_restart(logger)

    brokerage_settings = brokerage_instance.get_settings()
    price_data_handler = brokerage_instance.get_price_data_handler()

    logger.info(f"Brokerage: {brokerage_instance.get_name()}")
    logger.info(f"Project id: {cloud_project.projectId}")
    logger.info(f"Environment: {brokerage_settings['environment'].title()}")
    logger.info(f"Server name: {live_node.name}")
    logger.info(f"Server type: {live_node.sku}")
    logger.info(f"Data provider: {price_data_handler.replace('Handler', '')}")
    logger.info(f"LEAN version: {cloud_project.leanVersionId}")
    logger.info(
        f"Order event notifications: {'Yes' if notify_order_events else 'No'}")
    logger.info(f"Insight notifications: {'Yes' if notify_insights else 'No'}")
    if notify_order_events or notify_insights:
        _log_notification_methods(notify_methods)
    logger.info(
        f"Automatic algorithm restarting: {'Yes' if auto_restart else 'No'}")

    if brokerage is None:
        click.confirm(
            f"Are you sure you want to start live trading for project '{cloud_project.name}'?",
            default=False,
            abort=True)

    live_algorithm = api_client.live.start(
        cloud_project.projectId, finished_compile.compileId, live_node.id,
        brokerage_settings, price_data_handler, auto_restart,
        cloud_project.leanVersionId, notify_order_events, notify_insights,
        notify_methods)

    logger.info(f"Live url: {live_algorithm.get_url()}")

    if open_browser:
        webbrowser.open(live_algorithm.get_url())
Пример #28
0
def optimize(project: str, target: Optional[str],
             target_direction: Optional[str],
             parameter: List[Tuple[str, float, float,
                                   float]], constraint: List[str],
             node: Optional[str], parallel_nodes: Optional[int],
             name: Optional[str], push: bool) -> None:
    """Optimize a project in the cloud.

    PROJECT must be the name or id of the project to optimize.

    An interactive prompt will be shown to configure the optimizer.
    If --target is given the command runs in non-interactive mode.
    In this mode the CLI does not prompt for input and the following options become required:
    --target, --target-direction, --parameter, --node and --parallel-nodes.

    \b
    In non-interactive mode the --parameter option can be provided multiple times to configure multiple parameters:
    - --parameter <name> <min value> <max value> <step size>
    - --parameter my-first-parameter 1 10 0.5 --parameter my-second-parameter 20 30 5

    \b
    In non-interactive mode the --constraint option can be provided multiple times to configure multiple constraints:
    - --constraint "<statistic> <operator> <value>"
    - --constraint "Sharpe Ratio >= 0.5" --constraint "Drawdown < 0.25"

    If the project that has to be optimized has been pulled to the local drive
    with `lean cloud pull` it is possible to use the --push option to push local
    modifications to the cloud before running the optimization.
    """
    logger = container.logger()
    api_client = container.api_client()

    cloud_project_manager = container.cloud_project_manager()
    cloud_project = cloud_project_manager.get_cloud_project(project, push)

    if name is None:
        name = container.name_generator().generate_name()

    cloud_runner = container.cloud_runner()
    finished_compile = cloud_runner.compile_project(cloud_project)

    optimizer_config_manager = container.optimizer_config_manager()
    organization = api_client.organizations.get(cloud_project.organizationId)

    if target is not None:
        ensure_options([
            "target", "target_direction", "parameter", "node", "parallel_nodes"
        ])

        optimization_strategy = "QuantConnect.Optimizer.Strategies.GridSearchOptimizationStrategy"
        optimization_target = OptimizationTarget(
            target=optimizer_config_manager.parse_target(target),
            extremum=target_direction)
        optimization_parameters = optimizer_config_manager.parse_parameters(
            parameter)
        optimization_constraints = optimizer_config_manager.parse_constraints(
            constraint)

        node = next(n for n in available_nodes if n.name == node)
        if parallel_nodes < node.min_nodes:
            raise RuntimeError(
                f"The minimum number of parallel nodes for {node.name} is {node.min_nodes}"
            )
        if parallel_nodes > node.max_nodes:
            raise RuntimeError(
                f"The maximum number of parallel nodes for {node.name} is {node.max_nodes}"
            )

        _display_estimate(cloud_project, finished_compile, organization, name,
                          optimization_strategy, optimization_target,
                          optimization_parameters, optimization_constraints,
                          node, parallel_nodes)
    else:
        optimization_strategy = optimizer_config_manager.configure_strategy(
            cloud=True)
        optimization_target = optimizer_config_manager.configure_target()
        optimization_parameters = optimizer_config_manager.configure_parameters(
            cloud_project.parameters, cloud=True)
        optimization_constraints = optimizer_config_manager.configure_constraints(
        )

        while True:
            node, parallel_nodes = optimizer_config_manager.configure_node()

            _display_estimate(cloud_project, finished_compile, organization,
                              name, optimization_strategy, optimization_target,
                              optimization_parameters,
                              optimization_constraints, node, parallel_nodes)

            if click.confirm(
                    "Do you want to start the optimization on the selected node type?",
                    default=True):
                break

    optimization = cloud_runner.run_optimization(
        cloud_project, finished_compile, name, optimization_strategy,
        optimization_target, optimization_parameters, optimization_constraints,
        node.name, parallel_nodes)

    backtests = optimization.backtests.values()
    backtests = [b for b in backtests if b.exitCode == 0]
    backtests = [
        b for b in backtests
        if _backtest_meets_constraints(b, optimization_constraints)
    ]

    if len(backtests) == 0:
        logger.info(
            "No optimal parameter combination found, no successful backtests meet all constraints"
        )
        return

    optimal_backtest = sorted(backtests,
                              key=lambda backtest: _get_backtest_statistic(
                                  backtest, optimization_target.target),
                              reverse=optimization_target.extremum ==
                              OptimizationExtremum.Maximum)[0]

    parameters = ", ".join(f"{key}: {optimal_backtest.parameterSet[key]}"
                           for key in optimal_backtest.parameterSet)
    logger.info(f"Optimal parameters: {parameters}")

    optimal_backtest = api_client.backtests.get(cloud_project.projectId,
                                                optimal_backtest.id)

    logger.info(f"Optimal backtest id: {optimal_backtest.backtestId}")
    logger.info(f"Optimal backtest name: {optimal_backtest.name}")
    logger.info(f"Optimal backtest results:")
    logger.info(optimal_backtest.get_statistics_table())
Пример #29
0
def live(project: str, push: bool, open_browser: bool) -> None:
    """Start live trading for a project in the cloud.

    An interactive prompt will be shown to configure the deployment.

    PROJECT must be the name or the id of the project to start live trading for.

    If the project that has to be live traded has been pulled to the local drive
    with `lean cloud pull` it is possible to use the --push option to push local
    modifications to the cloud before starting live trading.
    """
    logger = container.logger()
    api_client = container.api_client()

    cloud_project_manager = container.cloud_project_manager()
    cloud_project = cloud_project_manager.get_cloud_project(project, push)

    cloud_runner = container.cloud_runner()
    finished_compile = cloud_runner.compile_project(cloud_project)

    brokerages = [
        PaperTradingBrokerage(),
        InteractiveBrokersBrokerage(),
        TradierBrokerage(),
        FXCMBrokerage(),
        OANDABrokerage(),
        BitfinexBrokerage(),
        CoinbaseProBrokerage()
    ]

    brokerage_options = [
        Option(id=brokerage, label=brokerage.name) for brokerage in brokerages
    ]

    brokerage: CloudBrokerage = logger.prompt_list("Select a brokerage",
                                                   brokerage_options)
    brokerage_settings = brokerage.get_settings(logger)
    price_data_handler = brokerage.get_price_data_handler()

    organization = api_client.accounts.get_organization()
    nodes = api_client.nodes.get_all(organization.organizationId)

    live_nodes = [node for node in nodes.live if not node.busy]
    if len(live_nodes) == 0:
        raise RuntimeError(
            "You don't have any live nodes available, you can manage your nodes on https://www.quantconnect.com/terminal/#organization/resources"
        )

    node_options = [
        Option(id=node, label=f"{node.name} - {node.description}")
        for node in nodes.live
    ]
    node: QCNode = logger.prompt_list("Select a node", node_options)

    logger.info(
        "You can optionally request for your strategy to send notifications when it generates an order or emits an insight"
    )
    logger.info(
        "You can use any combination of email notifications, webhook notifications and SMS notifications"
    )
    notify_order_events = click.confirm(
        "Do you want to send notifications on order events?", default=False)
    notify_insights = click.confirm(
        "Do you want to send notifications on insights?", default=False)
    notify_methods = []

    if notify_order_events or notify_insights:
        _log_notification_methods(notify_methods)
        notify_methods.append(_prompt_notification_method())

        while True:
            _log_notification_methods(notify_methods)
            if not click.confirm(
                    "Do you want to add another notification method?",
                    default=False):
                break
            notify_methods.append(_prompt_notification_method())

    logger.info(
        "Automatic restarting uses best efforts to restart the algorithm if it fails due to a runtime error"
    )
    logger.info(
        "This can help improve its resilience to temporary errors such as a brokerage API disconnection"
    )
    automatic_redeploy = click.confirm(
        "Do you want to enable automatic algorithm restarting?", default=True)

    logger.info(f"Brokerage: {brokerage.name}")
    logger.info(f"Project id: {cloud_project.projectId}")
    logger.info(f"Environment: {brokerage_settings['environment'].title()}")
    logger.info(f"Server name: {node.name}")
    logger.info(f"Server type: {node.sku}")
    logger.info(f"Data provider: {price_data_handler.replace('Handler', '')}")
    logger.info(f"LEAN version: {cloud_project.leanVersionId}")
    logger.info(
        f"Order event notifications: {'Yes' if notify_order_events else 'No'}")
    logger.info(f"Insight notifications: {'Yes' if notify_insights else 'No'}")
    if notify_order_events or notify_insights:
        _log_notification_methods(notify_methods)
    logger.info(
        f"Automatic algorithm restarting: {'Yes' if automatic_redeploy else 'No'}"
    )

    click.confirm(
        f"Are you sure you want to start live trading for project '{cloud_project.name}'?",
        default=False,
        abort=True)

    api_client.live.start(cloud_project.projectId, finished_compile.compileId,
                          node.id, brokerage_settings, price_data_handler,
                          automatic_redeploy, cloud_project.leanVersionId,
                          notify_order_events, notify_insights, notify_methods)

    live_url = cloud_project.get_url().replace("#open", "#openLive")
    logger.info(f"Live url: {live_url}")

    if open_browser:
        webbrowser.open(live_url)
Пример #30
0
def start(organization: Optional[str], port: int, no_open: bool,
          shortcut: bool, gui: Optional[Path], shortcut_launch: bool) -> None:
    """Start the local GUI."""
    logger = container.logger()
    docker_manager = container.docker_manager()
    temp_manager = container.temp_manager()
    module_manager = container.module_manager()
    api_client = container.api_client()

    gui_container = docker_manager.get_container_by_name(
        LOCAL_GUI_CONTAINER_NAME)
    if gui_container is not None:
        if gui_container.status == "running":
            if shortcut_launch:
                port = gui_container.ports["5612/tcp"][0]["HostPort"]
                url = f"http://localhost:{port}/"
                webbrowser.open(url)
                return
            else:
                _error(
                    "The local GUI is already running, run `lean gui restart` to restart it or `lean gui stop` to stop it",
                    shortcut_launch)

        gui_container.remove()

    if organization is not None:
        organization_id = _get_organization_id(organization, shortcut_launch)
    else:
        organizations = api_client.organizations.get_all()
        options = [
            Option(id=organization.id, label=organization.name)
            for organization in organizations
        ]
        organization_id = logger.prompt_list(
            "Select the organization with the local GUI module subscription",
            options)

    module_manager.install_module(GUI_PRODUCT_INSTALL_ID, organization_id)

    shortcut_manager = container.shortcut_manager()
    if shortcut:
        shortcut_manager.create_shortcut(organization_id)
    else:
        shortcut_manager.prompt_if_necessary(organization_id)

    # The dict containing all options passed to `docker run`
    # See all available options at https://docker-py.readthedocs.io/en/stable/containers.html
    run_options: Dict[str, Any] = {
        "name": LOCAL_GUI_CONTAINER_NAME,
        "detach": True,
        "remove": False,
        "commands": [],
        "environment": {
            "PYTHONUNBUFFERED": "1",
            "QC_LOCAL_GUI": "true",
            "QC_DOCKER_HOST_SYSTEM": platform.system(),
            "QC_DOCKER_HOST_MACHINE": platform.machine(),
            "QC_ORGANIZATION_ID": organization_id,
            "QC_API": os.environ.get("QC_API", "")
        },
        "mounts": [],
        "volumes": {},
        "ports": {
            "5612": str(port)
        }
    }

    # Cache the site-packages so we don't re-install everything when the container is restarted
    docker_manager.create_volume("lean_cli_gui_python")
    run_options["volumes"]["lean_cli_gui_python"] = {
        "bind": "/root/.local/lib/python3.9/site-packages",
        "mode": "rw"
    }

    # Update PATH in the GUI container to add executables installed with pip
    run_options["commands"].append('export PATH="$PATH:/root/.local/bin"')

    package_file_name = module_manager.get_installed_packages_by_module(
        GUI_PRODUCT_INSTALL_ID)[0].get_file_name()
    with zipfile.ZipFile(Path.home() / ".lean" / "modules" /
                         package_file_name) as package_file:
        content_file_names = [
            f.replace("content/", "") for f in package_file.namelist()
            if f.startswith("content/")
        ]
        wheel_file_name = next(f for f in content_file_names
                               if f.endswith(".whl"))
        terminal_file_name = next(f for f in content_file_names
                                  if f.endswith(".zip"))

    # Install the CLI in the GUI container
    run_options["commands"].append("pip uninstall -y lean")
    if lean.__version__ == "dev":
        lean_cli_dir = str(
            Path(__file__).absolute().parent.parent.parent.parent)
        logger.info(
            f"Detected lean dev version. Will mount local folder '{lean_cli_dir}' as /lean-cli"
        )
        run_options["volumes"][str(lean_cli_dir)] = {
            "bind": "/lean-cli",
            "mode": "rw"
        }

        run_options["commands"].append("cd /lean-cli")
        run_options["commands"].append(
            "pip install --user --progress-bar off -r requirements.txt")
    else:
        run_options["commands"].append(
            "pip install --user --progress-bar off --upgrade lean")

    # Install the GUI in the GUI container
    run_options["commands"].append("pip uninstall -y leangui")
    if gui is None:
        run_options["commands"].append(
            f"unzip -p /root/.lean/modules/{package_file_name} content/{wheel_file_name} > /{wheel_file_name}"
        )
        run_options["commands"].append(
            f"pip install --user --progress-bar off /{wheel_file_name}")
    elif gui.is_file():
        run_options["mounts"].append(
            Mount(target=f"/{gui.name}",
                  source=str(gui),
                  type="bind",
                  read_only=True))
        run_options["commands"].append(
            f"pip install --user --progress-bar off /{gui.name}")
    else:
        run_options["volumes"][str(gui)] = {
            "bind": "/lean-cli-gui",
            "mode": "rw"
        }

        run_options["commands"].append("cd /lean-cli-gui")
        run_options["commands"].append(
            "pip install --user --progress-bar off -r requirements.txt")

    # Extract the terminal in the GUI container
    run_options["commands"].append(
        f"unzip -p /root/.lean/modules/{package_file_name} content/{terminal_file_name} > /{terminal_file_name}"
    )
    run_options["commands"].append(
        f"unzip -o /{terminal_file_name} -d /terminal")

    # Write correct streaming url to /terminal/local.socket.host.conf
    run_options["commands"].append(
        f'echo "ws://localhost:{port}/streaming" > /terminal/local.socket.host.conf'
    )

    # Mount the `lean init` directory in the GUI container
    cli_root_dir = container.lean_config_manager().get_cli_root_directory()
    run_options["volumes"][str(cli_root_dir)] = {
        "bind": "/LeanCLI",
        "mode": "rw"
    }

    # Mount the global config directory in the GUI container
    run_options["volumes"][str(Path("~/.lean").expanduser())] = {
        "bind": "/root/.lean",
        "mode": "rw"
    }

    # Mount a directory to the tmp directory in the GUI container
    gui_tmp_directory = temp_manager.create_temporary_directory()
    run_options["volumes"][str(gui_tmp_directory)] = {
        "bind": "/tmp",
        "mode": "rw"
    }

    # Set up the path mappings between paths in the host system and paths in the GUI container
    run_options["environment"]["DOCKER_PATH_MAPPINGS"] = json.dumps({
        "/LeanCLI":
        cli_root_dir.as_posix(),
        "/root/.lean":
        Path("~/.lean").expanduser().as_posix(),
        "/tmp":
        gui_tmp_directory.as_posix()
    })

    # Mount the Docker socket in the GUI container
    run_options["mounts"].append(
        Mount(target="/var/run/docker.sock",
              source="/var/run/docker.sock",
              type="bind",
              read_only=False))

    # Run the GUI in the GUI container
    run_options["commands"].append("cd /LeanCLI")
    run_options["commands"].append(f"leangui")

    # Don't delete temporary directories when the command exits, the container will still need them
    temp_manager.delete_temporary_directories_when_done = False

    logger.info("Starting the local GUI, this may take some time...")

    # Pull the Docker images used by the local GUI
    # If this is done while the local GUI is running there is a big delay between pressing Backtest and seeing it run
    update_manager = container.update_manager()
    cli_config_manager = container.cli_config_manager()
    update_manager.pull_docker_image_if_necessary(
        cli_config_manager.get_engine_image(), False)
    update_manager.pull_docker_image_if_necessary(
        cli_config_manager.get_research_image(), False)

    try:
        docker_manager.run_image(
            DockerImage(name="python", tag="3.9.6-buster"), **run_options)
    except APIError as error:
        msg = error.explanation
        if isinstance(msg, str) and any(m in msg.lower() for m in [
                "port is already allocated", "ports are not available"
                "an attempt was made to access a socket in a way forbidden by its access permissions"
        ]):
            _error(
                f"Port {port} is already in use, please specify a different port using --port <number>",
                shortcut_launch)
        raise error

    url = f"http://localhost:{port}/"

    # Wait until the GUI is running
    while True:
        gui_container = docker_manager.get_container_by_name(
            LOCAL_GUI_CONTAINER_NAME)
        if gui_container is None or gui_container.status != "running":
            docker_manager.show_logs(LOCAL_GUI_CONTAINER_NAME)
            if shortcut_launch:
                _error(
                    "Something went wrong while starting the local GUI, run `lean gui logs` for more information",
                    shortcut_launch)
            else:
                _error(
                    "Something went wrong while starting the local GUI, see the logs above for more information",
                    shortcut_launch)

        try:
            requests.get(url)
            break
        except requests.exceptions.ConnectionError:
            time.sleep(0.25)

    logger.info(f"The local GUI has started and is running on {url}")

    if not no_open:
        webbrowser.open(url)