Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial cash balance for live paper trading #172

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ Usage: lean cloud live deploy [OPTIONS] PROJECT
--notify-insights.

Options:
--brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Pro|Binance|Zerodha|Samco|Kraken|FTX]
--brokerage [Paper Trading|Interactive Brokers|Tradier|Oanda|Bitfinex|Coinbase Pro|Binance|Zerodha|Samco|Trading Technologies|Kraken|FTX]
The brokerage to use
--ib-user-name TEXT Your Interactive Brokers username
--ib-account TEXT Your Interactive Brokers account id
Expand Down Expand Up @@ -288,6 +288,25 @@ Options:
--samco-trading-segment [equity|commodity]
EQUITY if you are trading equities on NSE or BSE, COMMODITY if you are trading
commodities on MCX
--tt-user-name TEXT Your Trading Technologies username
--tt-session-password TEXT Your Trading Technologies session password
--tt-account-name TEXT Your Trading Technologies account name
--tt-rest-app-key TEXT Your Trading Technologies REST app key
--tt-rest-app-secret TEXT Your Trading Technologies REST app secret
--tt-rest-environment TEXT The REST environment to run in
--tt-market-data-sender-comp-id TEXT
The market data sender comp id to use
--tt-market-data-target-comp-id TEXT
The market data target comp id to use
--tt-market-data-host TEXT The host of the market data server
--tt-market-data-port TEXT The port of the market data server
--tt-order-routing-sender-comp-id TEXT
The order routing sender comp id to use
--tt-order-routing-target-comp-id TEXT
The order routing target comp id to use
--tt-order-routing-host TEXT The host of the order routing server
--tt-order-routing-port TEXT The port of the order routing server
--tt-log-fix-messages BOOLEAN Whether FIX messages should be logged
--kraken-api-key TEXT Your Kraken API key
--kraken-api-secret TEXT Your Kraken API secret
--kraken-verification-tier [Starter|Intermediate|Pro]
Expand All @@ -312,6 +331,7 @@ Options:
--notify-sms TEXT A comma-separated list of phone numbers configuring SMS-notifications
--notify-telegram TEXT A comma-separated list of 'user/group Id:token(optional)' pairs configuring telegram-
notifications
--live-cash-balance TEXT A comma-separated list of currency:amount pairs of initial cash balance
--push Push local modifications to the cloud before starting live trading
--open Automatically open the live results in the browser once the deployment starts
--verbose Enable debug logging
Expand Down Expand Up @@ -979,6 +999,7 @@ Options:
--release Compile C# projects in release configuration instead of debug
--image TEXT The LEAN engine image to use (defaults to quantconnect/lean:latest)
--python-venv TEXT The path of the python virtual environment to be used
--live-cash-balance TEXT A comma-separated list of currency:amount pairs of initial cash balance
--update Pull the LEAN engine image before starting live trading
--lean-config FILE The Lean configuration file that should be used (defaults to the nearest lean.json)
--verbose Enable debug logging
Expand Down
33 changes: 20 additions & 13 deletions lean/commands/cloud/live/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@
# limitations under the License.

import webbrowser
from typing import Dict, List, Tuple, Optional
from typing import List, Tuple, Optional
import click
from lean.click import LeanCommand, ensure_options
from lean.components.api.api_client import APIClient
from lean.components.util.logger import Logger
from lean.container import container
from lean.models.api import (QCEmailNotificationMethod, QCNode, QCNotificationMethod, QCSMSNotificationMethod,
QCWebhookNotificationMethod, QCTelegramNotificationMethod, QCProject)
from lean.models.json_module import LiveCashBalanceInput
from lean.models.logger import Option
from lean.models.brokerages.cloud.cloud_brokerage import CloudBrokerage
from lean.models.configuration import Configuration, InfoConfiguration, InternalInputUserInput, OrganzationIdConfiguration
from lean.models.configuration import InternalInputUserInput, OrganzationIdConfiguration
from lean.models.click_options import options_from_json
from lean.models.brokerages.cloud import all_cloud_brokerages
from lean.commands.cloud.live.live import live
from lean.components.util.live_utils import _get_configs_for_options, get_latest_cash_state, configure_initial_cash_balance

def _log_notification_methods(methods: List[QCNotificationMethod]) -> None:
"""Logs a list of notification methods."""
Expand Down Expand Up @@ -160,22 +162,13 @@ def _configure_auto_restart(logger: Logger) -> bool:
logger.info("This can help improve its resilience to temporary errors such as a brokerage API disconnection")
return click.confirm("Do you want to enable automatic algorithm restarting?", default=True)

#TODO: same duplication present in commands\live.py
def _get_configs_for_options() -> Dict[Configuration, str]:
run_options: Dict[str, Configuration] = {}
for module in all_cloud_brokerages:
for config in module.get_all_input_configs([InternalInputUserInput, InfoConfiguration]):
if config._id in run_options:
raise ValueError(f'Options names should be unique. Duplicate key present: {config._id}')
run_options[config._id] = config
return list(run_options.values())

@live.command(cls=LeanCommand, default_command=True, name="deploy")
@click.argument("project", type=str)
@click.option("--brokerage",
type=click.Choice([b.get_name() for b in all_cloud_brokerages], case_sensitive=False),
help="The brokerage to use")
@options_from_json(_get_configs_for_options())
@options_from_json(_get_configs_for_options("cloud"))
@click.option("--node", type=str, help="The name or id of the live node to run on")
@click.option("--auto-restart", type=bool, help="Whether automatic algorithm restarting must be enabled")
@click.option("--notify-order-events", type=bool, help="Whether notifications must be sent for order events")
Expand All @@ -188,6 +181,9 @@ def _get_configs_for_options() -> Dict[Configuration, str]:
help="A comma-separated list of 'url:HEADER_1=VALUE_1:HEADER_2=VALUE_2:etc' pairs configuring webhook-notifications")
@click.option("--notify-sms", type=str, help="A comma-separated list of phone numbers configuring SMS-notifications")
@click.option("--notify-telegram", type=str, help="A comma-separated list of 'user/group Id:token(optional)' pairs configuring telegram-notifications")
@click.option("--live-cash-balance",
type=str,
help=f"A comma-separated list of currency:amount pairs of initial cash balance")
@click.option("--push",
is_flag=True,
default=False,
Expand All @@ -206,6 +202,7 @@ def deploy(project: str,
notify_webhooks: Optional[str],
notify_sms: Optional[str],
notify_telegram: Optional[str],
live_cash_balance: Optional[str],
push: bool,
open_browser: bool,
**kwargs) -> None:
Expand Down Expand Up @@ -289,6 +286,13 @@ def deploy(project: str,
brokerage_settings = brokerage_instance.get_settings()
price_data_handler = brokerage_instance.get_price_data_handler()

cash_balance_option = brokerage_instance._initial_cash_balance
if cash_balance_option != LiveCashBalanceInput.NotSupported:
previous_cash_state = get_latest_cash_state(api_client, cloud_project.projectId, project)
live_cash_balance = configure_initial_cash_balance(logger, cash_balance_option, live_cash_balance, previous_cash_state)
elif live_cash_balance is not None and live_cash_balance != "":
raise RuntimeError(f"Custom cash balance setting is not available for {brokerage_instance.get_name()}")

logger.info(f"Brokerage: {brokerage_instance.get_name()}")
logger.info(f"Project id: {cloud_project.projectId}")
logger.info(f"Environment: {brokerage_settings['environment'].title()}")
Expand All @@ -300,6 +304,8 @@ def deploy(project: str,
logger.info(f"Insight notifications: {'Yes' if notify_insights else 'No'}")
if notify_order_events or notify_insights:
_log_notification_methods(notify_methods)
if live_cash_balance:
logger.info(f"Initial live cash balance: {live_cash_balance}")
logger.info(f"Automatic algorithm restarting: {'Yes' if auto_restart else 'No'}")

if brokerage is None:
Expand All @@ -316,7 +322,8 @@ def deploy(project: str,
cloud_project.leanVersionId,
notify_order_events,
notify_insights,
notify_methods)
notify_methods,
live_cash_balance)

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

Expand Down
40 changes: 20 additions & 20 deletions lean/commands/live/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import subprocess
import time
from datetime import datetime
import json
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
import click
Expand All @@ -25,10 +26,11 @@
from lean.models.errors import MoreInfoError
from lean.models.lean_config_configurer import LeanConfigConfigurer
from lean.models.logger import Option
from lean.models.configuration import Configuration, InfoConfiguration, InternalInputUserInput, OrganzationIdConfiguration
from lean.models.configuration import InternalInputUserInput, OrganzationIdConfiguration
from lean.models.click_options import options_from_json
from lean.models.json_module import JsonModule
from lean.models.json_module import JsonModule, LiveCashBalanceInput
from lean.commands.live.live import live
from lean.components.util.live_utils import _get_configs_for_options, get_latest_cash_state, configure_initial_cash_balance
from lean.models.data_providers import all_data_providers

_environment_skeleton = {
Expand Down Expand Up @@ -172,7 +174,6 @@ def _configure_lean_config_interactively(lean_config: Dict[str, Any], environmen
setattr(data_feed, '_is_installed_and_build', True)
data_feed.build(lean_config, logger).configure(lean_config, environment_name)


_cached_organizations = None


Expand Down Expand Up @@ -262,21 +263,6 @@ def _get_default_value(key: str) -> Optional[Any]:

return value

def _get_configs_for_options() -> List[Configuration]:
run_options: Dict[str, Configuration] = {}
config_with_module_id: Dict[str, str] = {}
for module in all_local_brokerages + all_local_data_feeds + all_data_providers:
for config in module.get_all_input_configs([InternalInputUserInput, InfoConfiguration]):
if config._id in run_options:
if (config._id in config_with_module_id
and config_with_module_id[config._id] == module._id):
# config of same module
continue
else:
raise ValueError(f'Options names should be unique. Duplicate key present: {config._id}')
run_options[config._id] = config
config_with_module_id[config._id] = module._id
return list(run_options.values())

@live.command(cls=LeanCommand, requires_lean_config=True, requires_docker=True, default_command=True, name="deploy")
@click.argument("project", type=PathParameter(exists=True, file_okay=True, dir_okay=True))
Expand All @@ -300,7 +286,7 @@ def _get_configs_for_options() -> List[Configuration]:
@click.option("--data-provider",
type=click.Choice([dp.get_name() for dp in all_data_providers], case_sensitive=False),
help="Update the Lean configuration file to retrieve data from the given provider")
@options_from_json(_get_configs_for_options())
@options_from_json(_get_configs_for_options("local"))
@click.option("--release",
is_flag=True,
default=False,
Expand All @@ -311,6 +297,9 @@ def _get_configs_for_options() -> List[Configuration]:
@click.option("--python-venv",
type=str,
help=f"The path of the python virtual environment to be used")
@click.option("--live-cash-balance",
type=str,
help=f"A comma-separated list of currency:amount pairs of initial cash balance")
@click.option("--update",
is_flag=True,
default=False,
Expand All @@ -325,6 +314,7 @@ def deploy(project: Path,
release: bool,
image: Optional[str],
python_venv: Optional[str],
live_cash_balance: Optional[str],
update: bool,
**kwargs) -> None:
"""Start live trading a project locally using Docker.
Expand Down Expand Up @@ -422,9 +412,19 @@ def deploy(project: Path,

output_config_manager = container.output_config_manager()
lean_config["algorithm-id"] = f"L-{output_config_manager.get_live_deployment_id(output)}"

if python_venv is not None and python_venv != "":
lean_config["python-venv"] = f'{"/" if python_venv[0] != "/" else ""}{python_venv}'

cash_balance_option = env_brokerage._initial_cash_balance
logger = container.logger()
if cash_balance_option != LiveCashBalanceInput.NotSupported:
previous_cash_state = get_latest_cash_state(container.api_client(), project_config.get("cloud-id", None), project)
live_cash_balance = configure_initial_cash_balance(logger, cash_balance_option, live_cash_balance, previous_cash_state)
if live_cash_balance:
lean_config["live-cash-balance"] = live_cash_balance
elif live_cash_balance is not None and live_cash_balance != "":
raise RuntimeError(f"Custom cash balance setting is not available for {brokerage}")

lean_runner = container.lean_runner()
lean_runner.run_lean(lean_config, environment_name, algorithm_file, output, engine_image, None, release, detach)
14 changes: 5 additions & 9 deletions lean/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from lean.constants import DEFAULT_ENGINE_IMAGE, PROJECT_CONFIG_FILE_NAME
from lean.container import container
from lean.models.errors import MoreInfoError
from lean.components.util.live_utils import get_state_json


def _find_project_directory(backtest_file: Path) -> Optional[Path]:
Expand Down Expand Up @@ -107,18 +108,13 @@ def report(backtest_results: Optional[Path],
raise RuntimeError(f"{report_destination} already exists, use --overwrite to overwrite it")

if backtest_results is None:
backtest_json_files = list(Path.cwd().rglob("backtests/*/*.json"))
result_json_files = [f for f in backtest_json_files if
not f.name.endswith("-order-events.json") and not f.name.endswith("alpha-results.json")]

if len(result_json_files) == 0:
backtest_results = get_state_json("backtests")
if not backtest_results:
raise MoreInfoError(
"Could not find a recent backtest result file, please use the --backtest-results option",
"https://www.lean.io/docs/v2/lean-cli/reports#02-Generate-Reports"
"Could not find a recent backtest result file, please use the --backtest-results option",
"https://www.lean.io/docs/v2/lean-cli/reports#02-Generate-Reports"
)

backtest_results = sorted(result_json_files, key=lambda f: f.stat().st_mtime, reverse=True)[0]

logger = container.logger()

if live_results is None:
Expand Down
9 changes: 7 additions & 2 deletions lean/components/api/live_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,14 @@ def start(self,
project_id: int,
compile_id: str,
node_id: str,
brokerage_settings: Dict[str, str],
brokerage_settings: Dict[str, Any],
price_data_handler: str,
automatic_redeploy: bool,
version_id: int,
notify_order_events: bool,
notify_insights: bool,
notify_methods: List[QCNotificationMethod]) -> QCMinimalLiveAlgorithm:
notify_methods: List[QCNotificationMethod],
live_cash_balance: Optional[List[Dict[str, float]]] = None) -> QCMinimalLiveAlgorithm:
"""Starts live trading for a project.

:param project_id: the id of the project to start live trading for
Expand All @@ -75,9 +76,13 @@ def start(self,
:param notify_order_events: whether notifications should be sent on order events
:param notify_insights: whether notifications should be sent on insights
:param notify_methods: the places to send notifications to
:param live_cash_balance: the list of initial cash balance
:return: the created live algorithm
"""

if live_cash_balance:
brokerage_settings["cash"] = live_cash_balance

parameters = {
"projectId": project_id,
"compileId": compile_id,
Expand Down
Loading