Skip to content

Commit

Permalink
Merge pull request #63 from rjra2611/feature-add-support-for-dll-down…
Browse files Browse the repository at this point in the history
…load-zerodha

Add support to download zerodha NuGet
  • Loading branch information
Martin-Molinero authored Jan 31, 2022
2 parents 0e989b3 + 29681ff commit f22a08d
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 12 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ Options:
--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
--zerodha-organization TEXT The name or id of the organization with the zerodha module subscription
--zerodha-api-key TEXT Your Kite Connect API key
--zerodha-access-token TEXT Your Kite Connect access token
--zerodha-product-type [MIS|CNC|NRML]
Expand Down
13 changes: 10 additions & 3 deletions lean/commands/live.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"tt-order-routing-host", "tt-order-routing-port",
"tt-log-fix-messages"],
"KrakenBrokerage": ["kraken-api-key", "kraken-api-secret", "kraken-verification-tier"],
"FTXBrokerage": ["ftx-api-key", "ftx-api-secret", "ftx-account-tier"]
"FTXBrokerage": ["ftx-api-key", "ftx-api-secret", "ftx-account-tier", "ftx-exchange-name"]
}

# Data queue handler -> required configuration properties
Expand Down Expand Up @@ -347,6 +347,10 @@ def _get_default_value(key: str) -> Optional[Any]:
type=bool,
default=lambda: _get_default_value("binance-use-testnet"),
help="Whether the testnet should be used")
@click.option("--zerodha-organization",
type=str,
default=lambda: _get_default_value("job-organization-id"),
help="The name or id of the organization with the zerodha module subscription")
@click.option("--zerodha-api-key",
type=str,
default=lambda: _get_default_value("zerodha-api-key"),
Expand Down Expand Up @@ -632,6 +636,7 @@ def live(project: Path,
binance_api_key: Optional[str],
binance_api_secret: Optional[str],
binance_use_testnet: Optional[bool],
zerodha_organization: Optional[str],
zerodha_api_key: Optional[str],
zerodha_access_token: Optional[str],
zerodha_product_type: Optional[str],
Expand Down Expand Up @@ -777,7 +782,8 @@ def live(project: Path,
"zerodha_access_token",
"zerodha_product_type",
"zerodha_trading_segment"])
brokerage_configurer = ZerodhaBrokerage(zerodha_api_key,
brokerage_configurer = ZerodhaBrokerage(_get_organization_id(zerodha_organization, "Zerodha"),
zerodha_api_key,
zerodha_access_token,
zerodha_product_type,
zerodha_trading_segment)
Expand Down Expand Up @@ -912,7 +918,8 @@ def live(project: Path,
"zerodha_product_type",
"zerodha_trading_segment",
"zerodha_history_subscription"])
data_feed_configurer = ZerodhaDataFeed(ZerodhaBrokerage(zerodha_api_key,
data_feed_configurer = ZerodhaDataFeed(ZerodhaBrokerage(_get_organization_id(zerodha_organization, "Zerodha"),
zerodha_api_key,
zerodha_access_token,
zerodha_product_type,
zerodha_trading_segment),
Expand Down
3 changes: 3 additions & 0 deletions lean/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@
# The product id of the FTX module
FTX_PRODUCT_ID = 138

# The product id of the ZERODHA module
ZERODHA_PRODUCT_ID = 174

# The product id of the SAMCO module
SAMCO_PRODUCT_ID = 173

Expand Down
8 changes: 5 additions & 3 deletions lean/models/brokerages/local/ftx.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,13 @@ class FTXBrokerage(LocalBrokerage):
_exchange: FTXExchange
_is_module_installed = False

def __init__(self, organization_id: str, api_key: str, api_secret: str, account_tier: str, exchange: FTXExchange) -> None:
def __init__(self, organization_id: str, api_key: str, api_secret: str, account_tier: str, exchange_name: str) -> None:
self._api_key = api_key
self._api_secret = api_secret
self._account_tier = account_tier
self._organization_id = organization_id
self._exchange = exchange
self._exchange_name = exchange_name
self._exchange = FTXExchange() if exchange_name.casefold() == "FTX".casefold() else FTXUSExchange()

@classmethod
def get_name(cls) -> str:
Expand Down Expand Up @@ -129,7 +130,7 @@ def _build(cls, lean_config: Dict[str, Any], logger: Logger) -> LocalBrokerage:
cls._get_default(lean_config, f'{prefix}-account-tier')
)

return FTXBrokerage(organization_id, api_key, api_secret, account_tier, exchange)
return FTXBrokerage(organization_id, api_key, api_secret, account_tier, exchange_name)

def _configure_environment(self, lean_config: Dict[str, Any], environment_name: str) -> None:
self.ensure_module_installed()
Expand All @@ -144,6 +145,7 @@ def configure_credentials(self, lean_config: Dict[str, Any]) -> None:
lean_config[f'{prefix}-api-key'] = self._api_key
lean_config[f'{prefix}-api-secret'] = self._api_secret
lean_config[f'{prefix}-account-tier'] = self._account_tier
lean_config["ftx-exchange-name"] = self._exchange_name
lean_config["job-organization-id"] = self._organization_id

self._save_properties(lean_config, ["job-organization-id", f'{prefix}-api-key', f'{prefix}-api-secret', f'{prefix}-account-tier'])
Expand Down
37 changes: 33 additions & 4 deletions lean/models/brokerages/local/zerodha.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
from lean.components.util.logger import Logger
from lean.models.brokerages.local.base import LocalBrokerage
from lean.models.config import LeanConfigConfigurer

from lean.container import container
from lean.constants import ZERODHA_PRODUCT_ID
from lean.models.logger import Option

class ZerodhaBrokerage(LocalBrokerage):
"""A LocalBrokerage implementation for the Zerodha brokerage."""

def __init__(self, api_key: str, access_token: str, product_type: str, trading_segment: str) -> None:
_is_module_installed = False

def __init__(self, organization_id: str, api_key: str, access_token: str, product_type: str, trading_segment: str) -> None:
self._organization_id = organization_id
self._api_key = api_key
self._access_token = access_token
self._product_type = product_type
Expand All @@ -33,8 +38,23 @@ def __init__(self, api_key: str, access_token: str, product_type: str, trading_s
def get_name(cls) -> str:
return "Zerodha"

@classmethod
def get_module_id(cls) -> int:
return ZERODHA_PRODUCT_ID

@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 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"))
Expand All @@ -60,24 +80,32 @@ def _build(cls, lean_config: Dict[str, Any], logger: Logger) -> LocalBrokerage:
type=click.Choice(["EQUITY", "COMMODITY"], case_sensitive=False)
)

return ZerodhaBrokerage(api_key, access_token, product_type, trading_segment)
return ZerodhaBrokerage(organization_id, api_key, access_token, product_type, trading_segment)

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"] = "ZerodhaBrokerage"
lean_config["environments"][environment_name]["transaction-handler"] = \
"QuantConnect.Lean.Engine.TransactionHandlers.BrokerageTransactionHandler"

def configure_credentials(self, lean_config: Dict[str, Any]) -> None:
lean_config["job-organization-id"] = self._organization_id
lean_config["zerodha-api-key"] = self._api_key
lean_config["zerodha-access-token"] = self._access_token
lean_config["zerodha-product-type"] = self._product_type
lean_config["zerodha-trading-segment"] = self._trading_segment

self._save_properties(lean_config, ["zerodha-api-key",
self._save_properties(lean_config, ["job-organization-id",
"zerodha-api-key",
"zerodha-access-token",
"zerodha-product-type",
"zerodha-trading-segment"])

def ensure_module_installed(self) -> None:
if not self._is_module_installed:
container.module_manager().install_module(self.__class__.get_module_id(), self._organization_id)
self._is_module_installed = True


class ZerodhaDataFeed(LeanConfigConfigurer):
"""A LeanConfigConfigurer implementation for the Zerodha data feed."""
Expand All @@ -102,6 +130,7 @@ def build(cls, lean_config: Dict[str, Any], logger: Logger) -> LeanConfigConfigu
return ZerodhaDataFeed(brokerage, history_subscription)

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"] = "ZerodhaBrokerage"
lean_config["environments"][environment_name]["history-provider"] = "BrokerageHistoryProvider"

Expand Down
73 changes: 71 additions & 2 deletions tests/commands/test_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from lean.constants import DEFAULT_ENGINE_IMAGE
from lean.container import container
from lean.models.docker import DockerImage
from lean.models.api import QCMinimalOrganization
from tests.test_helpers import create_fake_lean_cli_directory

ENGINE_IMAGE = DockerImage.parse(DEFAULT_ENGINE_IMAGE)
Expand Down Expand Up @@ -305,7 +306,43 @@ def test_live_aborts_when_lean_config_is_missing_properties(target: str, replace
"zerodha-access-token": "456",
"zerodha-product-type": "MIS",
"zerodha-trading-segment": "EQUITY"
}
},
"Samco": {
"samco-client-id": "123",
"samco-client-password": "456",
"samco-year-of-birth": "2000",
"samco-product-type": "MIS",
"samco-trading-segment": "EQUITY"
},
"Atreyu": {
"atreyu-host": "abc",
"atreyu-req-port": "123",
"atreyu-sub-port": "456",
"atreyu-username": "abc",
"atreyu-password": "abc",
"atreyu-client-id": "abc",
"atreyu-broker-mpid": "abc",
"atreyu-locate-rqd": "abc",
},
"Terminal Link": {
"bloomberg-environment": "Beta",
"bloomberg-server-host": "abc",
"bloomberg-server-port": "123",
"bloomberg-emsx-broker": "abc",
"bloomberg-allow-modification": "no",
},
"Kraken": {
"kraken-api-key": "abc",
"kraken-api-secret": "abc",
"kraken-verification-tier": "abc",
},
"FTX": {
"ftx-api-key": "abc",
"ftx-api-secret": "abc",
"ftx-account-tier": "abc",
"ftx-exchange-name": "FTX"
},

}

data_feed_required_options = {
Expand All @@ -321,7 +358,11 @@ def test_live_aborts_when_lean_config_is_missing_properties(target: str, replace
"Zerodha": {
**brokerage_required_options["Zerodha"],
"zerodha-history-subscription": "yes"
}
},
"Samco": brokerage_required_options["Samco"],
"Terminal Link": brokerage_required_options["Terminal Link"],
"Kraken": brokerage_required_options["Kraken"],
"FTX": brokerage_required_options["FTX"],
}


Expand Down Expand Up @@ -390,6 +431,7 @@ def test_live_non_interactive_aborts_when_missing_data_feed_options(data_feed: s
lean_runner.run_lean.assert_not_called()



@pytest.mark.parametrize("brokerage,data_feed",
itertools.product(brokerage_required_options.keys(), data_feed_required_options.keys()))
def test_live_non_interactive_calls_run_lean_when_all_options_given(brokerage: str, data_feed: str) -> None:
Expand All @@ -401,6 +443,12 @@ def test_live_non_interactive_calls_run_lean_when_all_options_given(brokerage: s
lean_runner = mock.Mock()
container.lean_runner.override(providers.Object(lean_runner))

api_client = mock.MagicMock()
api_client.organizations.get_all.return_value = [
QCMinimalOrganization(id="abc", name="abc", type="type", ownerName="You", members=1, preferred=True)
]
container.api_client.override(providers.Object(api_client))

options = []

for key, value in brokerage_required_options[brokerage].items():
Expand Down Expand Up @@ -439,6 +487,12 @@ def test_live_non_interactive_falls_back_to_lean_config_for_brokerage_settings(b
lean_runner = mock.Mock()
container.lean_runner.override(providers.Object(lean_runner))

api_client = mock.MagicMock()
api_client.organizations.get_all.return_value = [
QCMinimalOrganization(id="abc", name="abc", type="type", ownerName="You", members=1, preferred=True)
]
container.api_client.override(providers.Object(api_client))

options = []

for key, value in current_options:
Expand All @@ -454,6 +508,12 @@ def test_live_non_interactive_falls_back_to_lean_config_for_brokerage_settings(b
if brokerage == "Binance":
data_feed = "Bitfinex"
options.extend(["--bitfinex-api-key", "123", "--bitfinex-api-secret", "456"])
elif brokerage == "FTX":
data_feed = "Binance"
options.extend(["--ftx-exchange-name", "abc",
"--binance-api-key", "123",
"--binance-api-secret", "456",
"--binance-use-testnet", "no"])
else:
data_feed = "Binance"
options.extend(["--binance-api-key", "123",
Expand Down Expand Up @@ -490,6 +550,12 @@ def test_live_non_interactive_falls_back_to_lean_config_for_data_feed_settings(d
lean_runner = mock.Mock()
container.lean_runner.override(providers.Object(lean_runner))

api_client = mock.MagicMock()
api_client.organizations.get_all.return_value = [
QCMinimalOrganization(id="abc", name="abc", type="type", ownerName="You", members=1, preferred=True)
]
container.api_client.override(providers.Object(api_client))

options = []

for key, value in current_options:
Expand All @@ -502,6 +568,9 @@ def test_live_non_interactive_falls_back_to_lean_config_for_data_feed_settings(d
"data-folder": "data"
}))

if data_feed == "FTX":
options.extend(["--ftx-exchange-name", "abc"])

result = CliRunner().invoke(lean, ["live", "Python Project",
"--brokerage", "Paper Trading",
"--data-feed", data_feed,
Expand Down

0 comments on commit f22a08d

Please sign in to comment.