Skip to content

Commit

Permalink
Merge pull request #172 from LouisSzeto/atreyu-existing-portfolio-state
Browse files Browse the repository at this point in the history
Initial cash balance for live paper trading
  • Loading branch information
Martin-Molinero committed Oct 5, 2022
2 parents 922bede + 971ee5b commit bf5e1a3
Show file tree
Hide file tree
Showing 11 changed files with 438 additions and 253 deletions.
23 changes: 22 additions & 1 deletion README.md
Expand Up @@ -241,7 +241,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 @@ -290,6 +290,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 @@ -314,6 +333,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 @@ -987,6 +1007,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
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
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
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
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

0 comments on commit bf5e1a3

Please sign in to comment.