diff --git a/README.md b/README.md index d92ecee2..07f0d437 100644 --- a/README.md +++ b/README.md @@ -773,6 +773,7 @@ Options: --gdax-api-secret TEXT Your Coinbase Pro API secret --gdax-passphrase TEXT Your Coinbase Pro API passphrase --gdax-use-sandbox BOOLEAN Whether the sandbox should be used + --binance-organization TEXT The name or id of the organization with the Binance module subscription --binance-api-key TEXT Your Binance API key --binance-api-secret TEXT Your Binance API secret --binance-use-testnet BOOLEAN Whether the testnet should be used diff --git a/lean/commands/live.py b/lean/commands/live.py index 90ab2a5f..18d19cb3 100644 --- a/lean/commands/live.py +++ b/lean/commands/live.py @@ -335,6 +335,10 @@ def _get_default_value(key: str) -> Optional[Any]: type=bool, default=lambda: _get_default_value("gdax-use-sandbox"), help="Whether the sandbox should be used") +@click.option("--binance-organization", + type=str, + default=lambda: _get_default_value("job-organization-id"), + help="The name or id of the organization with the Binance module subscription") @click.option("--binance-api-key", type=str, default=lambda: _get_default_value("binance-api-key"), @@ -633,6 +637,7 @@ def live(project: Path, gdax_api_secret: Optional[str], gdax_passphrase: Optional[str], gdax_use_sandbox: Optional[bool], + binance_organization: Optional[str], binance_api_key: Optional[str], binance_api_secret: Optional[str], binance_use_testnet: Optional[bool], @@ -776,7 +781,10 @@ def live(project: Path, gdax_use_sandbox) elif brokerage == BinanceBrokerage.get_name(): ensure_options(["binance_api_key", "binance_api_secret", "binance_use_testnet"]) - brokerage_configurer = BinanceBrokerage(binance_api_key, binance_api_secret, binance_use_testnet) + brokerage_configurer = BinanceBrokerage(_get_organization_id(binance_organization, "Binance"), + binance_api_key, + binance_api_secret, + binance_use_testnet) elif brokerage == ZerodhaBrokerage.get_name(): ensure_options(["zerodha_api_key", "zerodha_access_token", @@ -909,7 +917,8 @@ def live(project: Path, gdax_use_sandbox)) elif data_feed == BinanceDataFeed.get_name(): ensure_options(["binance_api_key", "binance_api_secret", "binance_use_testnet"]) - data_feed_configurer = BinanceDataFeed(BinanceBrokerage(binance_api_key, + data_feed_configurer = BinanceDataFeed(BinanceBrokerage(_get_organization_id(binance_organization, "Binance"), + binance_api_key, binance_api_secret, binance_use_testnet)) elif data_feed == ZerodhaDataFeed.get_name(): diff --git a/lean/constants.py b/lean/constants.py index 7a1a6f7a..64e591c4 100644 --- a/lean/constants.py +++ b/lean/constants.py @@ -90,6 +90,9 @@ # The product id of the ZERODHA module ZERODHA_PRODUCT_ID = 174 +# The product id of the Binance module +BINANCE_PRODUCT_ID = 176 + # The product id of the SAMCO module SAMCO_PRODUCT_ID = 173 diff --git a/lean/models/brokerages/local/binance.py b/lean/models/brokerages/local/binance.py index 0335319d..0b14e14a 100644 --- a/lean/models/brokerages/local/binance.py +++ b/lean/models/brokerages/local/binance.py @@ -16,14 +16,19 @@ import click from lean.components.util.logger import Logger +from lean.constants import BINANCE_PRODUCT_ID +from lean.container import container from lean.models.brokerages.local.base import LocalBrokerage from lean.models.config import LeanConfigConfigurer +from lean.models.logger import Option class BinanceBrokerage(LocalBrokerage): """A LocalBrokerage implementation for the Binance brokerage.""" + _is_module_installed = False - def __init__(self, api_key: str, api_secret: str, testnet: bool) -> None: + def __init__(self, organization_id: str, api_key: str, api_secret: str, testnet: bool) -> None: + self._organization_id = organization_id self._api_key = api_key self._api_secret = api_secret self._testnet = testnet @@ -34,6 +39,16 @@ def get_name(cls) -> str: @classmethod 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()) @@ -42,9 +57,11 @@ def _build(cls, lean_config: Dict[str, Any], logger: Logger) -> LocalBrokerage: api_secret = logger.prompt_password("API secret", cls._get_default(lean_config, "binance-api-secret")) testnet = click.confirm("Use the testnet?") - return BinanceBrokerage(api_key, api_secret, testnet) + return BinanceBrokerage(organization_id, api_key, api_secret, testnet) def _configure_environment(self, lean_config: Dict[str, Any], environment_name: str) -> None: + self.ensure_module_installed() + lean_config["environments"][environment_name]["live-mode-brokerage"] = "BinanceBrokerage" lean_config["environments"][environment_name]["transaction-handler"] = \ "QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler" @@ -61,8 +78,13 @@ def configure_credentials(self, lean_config: Dict[str, Any]) -> None: lean_config["binance-api-url"] = "https://api.binance.com" lean_config["binance-websocket-url"] = "wss://stream.binance.com:9443/ws" - self._save_properties(lean_config, ["binance-api-key", "binance-api-secret", "binance-use-testnet"]) + lean_config["job-organization-id"] = self._organization_id + self._save_properties(lean_config, ["job-organization-id", "binance-api-key", "binance-api-secret", "binance-use-testnet"]) + def ensure_module_installed(self) -> None: + if not self._is_module_installed: + container.module_manager().install_module(BINANCE_PRODUCT_ID, self._organization_id) + self._is_module_installed = True class BinanceDataFeed(LeanConfigConfigurer): """A LeanConfigConfigurer implementation for the Binance data feed.""" @@ -79,6 +101,8 @@ def build(cls, lean_config: Dict[str, Any], logger: Logger) -> LeanConfigConfigu return BinanceDataFeed(BinanceBrokerage.build(lean_config, logger)) def configure(self, lean_config: Dict[str, Any], environment_name: str) -> None: + self._brokerage.ensure_module_installed() + lean_config["environments"][environment_name]["data-queue-handler"] = "BinanceBrokerage" lean_config["environments"][environment_name]["history-provider"] = "BrokerageHistoryProvider"