Пример #1
0
def _select_products(organization: QCFullOrganization) -> List[Product]:
    """Asks the user for the products that should be purchased and downloaded.

    :return: the list of products selected by the user
    """
    products = []

    logger = container.logger()

    security_product_classes = [
        CFDProduct, CryptoProduct, EquityProduct, EquityOptionProduct,
        ForexProduct, FutureProduct
    ]

    alternative_product_classes = [
        CBOEProduct, FREDProduct, SECProduct, USTreasuryProduct,
        USEnergyProduct
    ]

    while True:
        initial_type = logger.prompt_list(
            "Select whether you want to download security data or alternative data",
            [
                Option(id="security", label="Security data"),
                Option(id="alternative", label="Alternative data")
            ])

        if initial_type == "security":
            product_classes = security_product_classes
            product_name_question = "Select the security type"
        else:
            product_classes = alternative_product_classes
            product_name_question = "Select the data type"

        product_class = logger.prompt_list(product_name_question, [
            Option(id=c, label=c.get_product_name()) for c in product_classes
        ])

        new_products = product_class.build(organization)
        current_files = [
            data_file.file
            for data_file in _get_data_files(organization, products)
        ]

        for new_product in new_products:
            new_files = new_product.get_data_files()
            if len(set(new_files) - set(current_files)) > 0:
                products.append(new_product)

        logger.info("Selected data:")
        _display_products(organization, products)

        if not click.confirm("Do you want to download more data?"):
            break

    return products
Пример #2
0
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()
Пример #3
0
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
Пример #4
0
def _select_products_interactive(organization: QCFullOrganization, datasets: List[Dataset]) -> List[Product]:
    """Asks the user for the products that should be purchased and downloaded.

    :param organization: the organization that will be charged
    :param datasets: the available datasets
    :return: the list of products selected by the user
    """
    products = []
    logger = container.logger()

    category_options = {}
    for dataset in datasets:
        for category in dataset.categories:
            if category in category_options:
                continue

            dataset_count = len(list(dataset for dataset in datasets if category in dataset.categories))
            category_options[category] = Option(
                id=category,
                label=f"{category} ({dataset_count} dataset{'s' if dataset_count > 1 else ''})"
            )

    category_options = sorted(category_options.values(), key=lambda opt: opt.label)

    while True:
        category = logger.prompt_list("Select a category", category_options)

        available_datasets = sorted((d for d in datasets if category in d.categories), key=lambda d: d.name)
        dataset: Dataset = logger.prompt_list("Select a dataset",
                                              [Option(id=d, label=d.name) for d in available_datasets])

        if dataset.requires_security_master and not organization.has_security_master_subscription():
            logger.warn("\n".join([
                f"Your organization needs to have an active Security Master subscription to download data from the '{dataset.name}' dataset",
                f"You can add the subscription at https://www.quantconnect.com/datasets/quantconnect-security-master/pricing"
            ]))
            continue

        option_results = OrderedDict()
        for option in dataset.options:
            if option.condition is None or option.condition.check(option_results):
                option_results[option.id] = option.configure_interactive()

        products.append(Product(dataset=dataset, option_results=option_results))

        logger.info("Selected data:")
        _display_products(organization, products)

        if not click.confirm("Do you want to download more data?"):
            break

    return products
Пример #5
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)
Пример #6
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)
Пример #7
0
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
Пример #8
0
def _configure_lean_config_interactively(lean_config: Dict[str, Any],
                                         environment_name: str) -> None:
    """Interactively configures the Lean config to use.

    Asks the user all questions required to set up the Lean config for local live trading.

    :param lean_config: the base lean config to use
    :param environment_name: the name of the environment to configure
    """
    logger = container.logger()

    lean_config["environments"] = {
        environment_name: {
            "live-mode":
            True,
            "setup-handler":
            "QuantConnect.Lean.Engine.Setup.BrokerageSetupHandler",
            "result-handler":
            "QuantConnect.Lean.Engine.Results.LiveTradingResultHandler",
            "data-feed-handler":
            "QuantConnect.Lean.Engine.DataFeeds.LiveTradingDataFeed",
            "real-time-handler":
            "QuantConnect.Lean.Engine.RealTime.LiveTradingRealTimeHandler"
        }
    }

    brokerage = logger.prompt_list("Select a brokerage", [
        Option(id=brokerage, label=brokerage.get_name())
        for brokerage in all_local_brokerages
    ])

    brokerage.configure(lean_config, environment_name, logger)

    data_feeds = local_brokerage_data_feeds[brokerage]
    if platform.system() == "Windows":
        data_feeds.append(IQFeedDataFeed)

    data_feed = logger.prompt_list("Select a data feed", [
        Option(id=data_feed, label=data_feed.get_name())
        for data_feed in data_feeds
    ])

    data_feed.configure(lean_config, environment_name, logger)
    def configure_constraints(self) -> List[OptimizationConstraint]:
        """Asks the user for the optimization constraints.

        :return: the chosen optimization constraints
        """
        self._logger.info(
            "Constraints can be used to filter out backtests from the results")
        self._logger.info(
            "When a backtest doesn't comply with the constraints it is dropped from the results"
        )
        self._logger.info(
            "Example constraint: Drawdown < 0.25 (Drawdown less than 25%)")

        results: List[OptimizationConstraint] = []

        while True:
            results_str = ", ".join([str(result) for result in results])
            results_str = results_str or "None"
            self._logger.info(f"Current constraints: {results_str}")

            if not click.confirm("Do you want to add a constraint?",
                                 default=False):
                return results

            target_options = [
                Option(id=target[0], label=target[1])
                for target in self.available_targets
            ]
            target = self._logger.prompt_list("Select a constraint target",
                                              target_options)

            operator = self._logger.prompt_list(
                "Select a constraint operator (<value> will be asked after this)",
                [
                    Option(id=OptimizationConstraintOperator.Less,
                           label="Less than <value>"),
                    Option(id=OptimizationConstraintOperator.LessOrEqual,
                           label="Less than or equal to <value>"),
                    Option(id=OptimizationConstraintOperator.Greater,
                           label="Greater than <value>"),
                    Option(id=OptimizationConstraintOperator.GreaterOrEqual,
                           label="Greater than or equal to <value>"),
                    Option(id=OptimizationConstraintOperator.Equals,
                           label="Equal to <value>"),
                    Option(id=OptimizationConstraintOperator.NotEqual,
                           label="Not equal to <value>")
                ])

            value = click.prompt("Set the <value> for the selected operator",
                                 type=click.FLOAT)

            results.append(
                OptimizationConstraint(**{
                    "target": target,
                    "operator": operator,
                    "target-value": value
                }))
Пример #10
0
def _configure_brokerage(logger: Logger) -> CloudBrokerage:
    """Interactively configures the brokerage to use.

    :param logger: the logger to use
    :return: the cloud brokerage the user configured
    """
    brokerage_options = [
        Option(id=b, label=b.get_name()) for b in all_cloud_brokerages
    ]
    return logger.prompt_list("Select a brokerage",
                              brokerage_options).build(logger)
    def configure_strategy(self, cloud: bool) -> str:
        """Asks the user for the optimization strategy to use.

        :param cloud: True if the optimization will be ran in the cloud, False if not
        :return: the class name of the optimization strategy to use
        """
        options = [
            Option(
                id=
                "QuantConnect.Optimizer.Strategies.GridSearchOptimizationStrategy",
                label="Grid Search")
        ]

        if not cloud:
            options.append(
                Option(
                    id=
                    "QuantConnect.Optimizer.Strategies.EulerSearchOptimizationStrategy",
                    label="Euler Search"))

        return self._logger.prompt_list(
            "Select the optimization strategy to use", options)
Пример #12
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)
Пример #13
0
def _prompt_notification_method() -> QCNotificationMethod:
    """Prompts the user to add a notification method.

    :return: the notification method configured by the user
    """
    logger = container.logger()
    selected_method = logger.prompt_list("Select a notification method", [
        Option(id="email", label="Email"),
        Option(id="webhook", label="Webhook"),
        Option(id="sms", label="SMS")
    ])

    if selected_method == "email":
        address = click.prompt("Email address")
        subject = click.prompt("Subject")
        return QCEmailNotificationMethod(address=address, subject=subject)
    elif selected_method == "webhook":
        address = click.prompt("URL")
        headers = {}

        while True:
            headers_str = "None" if headers == {} else ", ".join(
                f"{key}={headers[key]}" for key in headers)
            logger.info(f"Headers: {headers_str}")

            if not click.confirm("Do you want to add a header?",
                                 default=False):
                break

            key = click.prompt("Header key")
            value = click.prompt("Header value")
            headers[key] = value

        return QCWebhookNotificationMethod(address=address, headers=headers)
    else:
        phone_number = click.prompt("Phone number")
        return QCSMSNotificationMethod(phoneNumber=phone_number)
Пример #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 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)
Пример #15
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)
Пример #16
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)
Пример #17
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)
    def configure_target(self) -> OptimizationTarget:
        """Asks the user for the optimization target.

        :return: the chosen optimization target
        """
        # Create a list of options containing a "<target> (min)" and "<target> (max)" option for every target
        options = list(
            itertools.product(
                self.available_targets,
                [OptimizationExtremum.Minimum, OptimizationExtremum.Maximum]))
        options = [
            Option(id=OptimizationTarget(target=option[0][0],
                                         extremum=option[1]),
                   label=f"{option[0][1]} ({option[1]})") for option in options
        ]

        return self._logger.prompt_list("Select an optimization target",
                                        options)
Пример #19
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)
    def configure_node(self) -> Tuple[NodeType, int]:
        """Asks the user for the node type and number of parallel nodes to run on.

        :return: the type of the node and the amount of parallel nodes to run
        """
        node_options = [
            Option(
                id=node,
                label=
                f"{node.name} ({node.cores} cores, {node.ram} GB RAM) @ ${node.price:.2f} per hour"
            ) for node in available_nodes
        ]

        node = self._logger.prompt_list("Select the optimization node type",
                                        node_options)
        parallel_nodes = click.prompt(
            f"How many nodes should run in parallel ({node.min_nodes}-{node.max_nodes})",
            type=click.IntRange(min=node.min_nodes, max=node.max_nodes),
            default=node.default_nodes)

        return node, parallel_nodes
Пример #21
0
def _configure_live_node(logger: Logger, api_client: APIClient,
                         cloud_project: QCProject) -> QCNode:
    """Interactively configures the live node to use.

    :param logger: the logger to use
    :param api_client: the API client to make API requests with
    :param cloud_project: the cloud project the user wants to start live trading for
    :return: the live node the user wants to start live trading on
    """
    nodes = api_client.nodes.get_all(cloud_project.organizationId)

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

    node_options = [
        Option(id=node, label=f"{node.name} - {node.description}")
        for node in live_nodes
    ]
    return logger.prompt_list("Select a node", node_options)
Пример #22
0
    def configure_interactive(self) -> OptionResult:
        logger = container.logger()

        keys = list(self.choices.keys())

        if len(keys) <= 5:
            key = logger.prompt_list(
                self.label, [Option(id=key, label=key) for key in keys])
        else:
            while True:
                user_input = click.prompt(
                    f"{self.label} (example: {min(keys, key=len)})")

                key = next(
                    (key for key in keys if key.lower() == user_input.lower()),
                    None)
                if key is not None:
                    break

                logger.info(f"Error: '{user_input}' is not a valid option")

        return OptionResult(value=self.choices[key], label=key)
Пример #23
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)
Пример #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 {} 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)
Пример #25
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)
Пример #26
0
    def invoke(self, ctx: click.Context):
        if self._requires_lean_config:
            lean_config_manager = container.lean_config_manager()
            try:
                # This method will raise an error if the directory cannot be found
                lean_config_manager.get_cli_root_directory()
            except Exception:
                # Use one of the cached Lean config locations to avoid having to abort the command
                lean_config_paths = lean_config_manager.get_known_lean_config_paths(
                )
                if len(lean_config_paths) > 0:
                    lean_config_path = container.logger().prompt_list(
                        "Select the Lean configuration file to use", [
                            Option(id=p, label=str(p))
                            for p in lean_config_paths
                        ])
                    lean_config_manager.set_default_lean_config_path(
                        lean_config_path)
                else:
                    # Abort with a display-friendly error message if the command requires a Lean config and none found
                    raise MoreInfoError(
                        "This command requires a Lean configuration file, run `lean init` in an empty directory to create one, or specify the file to use with --lean-config",
                        "https://www.lean.io/docs/lean-cli/key-concepts/troubleshooting#02-Common-Errors"
                    )

        if self._requires_docker and "pytest" not in sys.modules:
            is_system_linux = container.platform_manager().is_system_linux()

            # The CLI uses temporary directories in /tmp because sometimes it may leave behind files owned by root
            # These files cannot be deleted by the CLI itself, so we rely on the OS to empty /tmp on reboot
            # The Snap version of Docker does not provide access to files outside $HOME, so we can't support it
            if is_system_linux:
                docker_path = shutil.which("docker")
                if docker_path is not None and docker_path.startswith("/snap"):
                    raise MoreInfoError(
                        "The Lean CLI does not work with the Snap version of Docker, please re-install Docker via the official installation instructions",
                        "https://docs.docker.com/engine/install/")

            # A usual Docker installation on Linux requires the user to use sudo to run Docker
            # If we detect that this is the case and the CLI was started without sudo we elevate automatically
            if is_system_linux and os.getuid(
            ) != 0 and container.docker_manager().is_missing_permission():
                container.logger().info(
                    "This command requires access to Docker, you may be asked to enter your password"
                )

                args = [
                    "sudo", "--preserve-env=HOME", sys.executable, *sys.argv
                ]
                os.execlp(args[0], *args)

        if self._allow_unknown_options:
            # Unknown options are passed to ctx.args and need to be parsed manually
            # We parse them to ctx.params so they're available like normal options
            # Because of this all commands with allow_unknown_options=True must have a **kwargs argument
            arguments = list(
                itertools.chain(*[arg.split("=") for arg in ctx.args]))

            skip_next = False
            for index in range(len(arguments) - 1):
                if skip_next:
                    skip_next = False
                    continue

                if arguments[index].startswith("--"):
                    option = arguments[index].replace("--", "")
                    value = arguments[index + 1]
                    ctx.params[option] = value
                    skip_next = True

        update_manager = container.update_manager()
        update_manager.show_announcements()

        result = super().invoke(ctx)

        update_manager.warn_if_cli_outdated()

        return result
Пример #27
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)
Пример #28
0
 def account_tier_options(self) -> List[Option]:
     return [
         Option(id="Tier1", label="Tier1"),
         Option(id="Tier2", label="Tier2"),
         Option(id="Tier3", label="Tier3"),
         Option(id="Tier4", label="Tier4"),
         Option(id="Tier5", label="Tier5"),
         Option(id="Tier6", label="Tier6"),
         Option(id="Tier7", label="Tier7"),
         Option(id="Tier8", label="Tier8"),
         Option(id="Tier9", label="Tier9"),
         Option(id="VIP1", label="VIP1"),
         Option(id="VIP2", label="VIP2"),
         Option(id="MM1", label="MM1"),
         Option(id="MM2", label="MM2"),
         Option(id="MM3", label="MM3")
     ]
Пример #29
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)