Meta Analysis
Live Analysis
Read Live Results
To get the results of a live algorithm, call the ReadLiveAlgorithmread_live_algorithm method with the project Id and deployment ID.
#load "../Initialize.csx"
#load "../QuantConnect.csx"
using QuantConnect; using QuantConnect.Api; var liveAlgorithm = api.ReadLiveAlgorithm(projectId, deployId);
live_algorithm = api.read_live_algorithm(project_id, deploy_id)
The following table provides links to documentation that explains how to get the project Id and deployment Id, depending on the platform you use:
| Platform | Project Id | Deployment Id |
|---|---|---|
| Cloud Platform | Get Project Id | Get Deployment Id |
| Local Platform | Get Project Id | Get Deployment Id |
| CLI | Get Project Id |
The ReadLiveAlgorithmread_live_algorithm method returns a LiveAlgorithmResults object, which have the following attributes:
Plot Order Fills
Follow these steps to plot the daily order fills of a live algorithm:
- Get the live trading orders.
-
Organize the trade times and prices for each security into a dictionary.
class OrderData: def __init__(self): self.buy_fill_times = [] self.buy_fill_prices = [] self.sell_fill_times = [] self.sell_fill_prices = [] order_data_by_symbol = {} for order in [x.order for x in orders]: if order.symbol not in order_data_by_symbol: order_data_by_symbol[order.symbol] = OrderData() order_data = order_data_by_symbol[order.symbol] is_buy = order.quantity > 0 (order_data.buy_fill_times if is_buy else order_data.sell_fill_times).append(order.last_fill_time.date()) (order_data.buy_fill_prices if is_buy else order_data.sell_fill_prices).append(order.price) -
Get the price history of each security you traded.
qb = QuantBook() start_date = datetime.max.date() end_date = datetime.min.date() for symbol, order_data in order_data_by_symbol.items(): if order_data.buy_fill_times: start_date = min(start_date, min(order_data.buy_fill_times)) end_date = max(end_date, max(order_data.buy_fill_times)) if order_data.sell_fill_times: start_date = min(start_date, min(order_data.sell_fill_times)) end_date = max(end_date, max(order_data.sell_fill_times)) start_date -= timedelta(days=3) all_history = qb.history(list(order_data_by_symbol.keys()), start_date, end_date, Resolution.DAILY) -
Create a candlestick plot for each security and annotate each plot with buy and sell markers.
import plotly.express as px import plotly.graph_objects as go for symbol, order_data in order_data_by_symbol.items(): history = all_history.loc[symbol] # Plot security price candlesticks candlestick = go.Candlestick(x=history.index, open=history['open'], high=history['high'], low=history['low'], close=history['close'], name='Price') layout = go.Layout(title=go.layout.Title(text=f'{symbol.value} Trades'), xaxis_title='Date', yaxis_title='Price', xaxis_rangeslider_visible=False, height=600) fig = go.Figure(data=[candlestick], layout=layout) # Plot buys fig.add_trace(go.Scatter( x=order_data.buy_fill_times, y=order_data.buy_fill_prices, marker=go.scatter.Marker(color='aqua', symbol='triangle-up', size=10), mode='markers', name='Buys', )) # Plot sells fig.add_trace(go.Scatter( x=order_data.sell_fill_times, y=order_data.sell_fill_prices, marker=go.scatter.Marker(color='indigo', symbol='triangle-down', size=10), mode='markers', name='Sells', )) fig.show()
orders = api.read_live_orders(project_id)
The following table provides links to documentation that explains how to get the project Id, depending on the platform you use:
| Platform | Project Id |
|---|---|
| Cloud Platform | Get Project Id |
| Local Platform | Get Project Id |
| CLI | Get Project Id |
By default, the orders with an ID between 0 and 100. To get orders with an ID greater than 100, pass start and end arguments to the ReadLiveOrdersread_live_orders method. Note that end - start must be less than 100.
orders = api.read_live_orders(project_id, 100, 150)
The ReadLiveOrdersread_live_orders method returns a list of Order objects, which have the following properties:
Note: The preceding plots only show the last fill of each trade. If your trade has partial fills, the plots only display the last fill.
Plot Charts
Follow these steps to plot the equity curve, benchmark, and drawdown of a live algorithm:
- Define the project Id and read the "Strategy Equity", "Drawdown", and "Benchmark" charts.
- Extract the series and create a
pandas.DataFrame. - Plot the performance chart.
from time import sleep, time
project_id = 23034953
def read_chart(project_id, chart_name, start=0, end=int(time()), count=500):
# Retry up to 10 times if the chart data is still loading
for attempt in range(10):
result = api.read_live_chart(project_id, chart_name, start, end, count)
if result.status == 'loading':
print(f"Chart data is loading... (attempt {attempt + 1}/10)")
sleep(10)
continue
break
return result.chart
strategy_equity = read_chart(project_id, 'Strategy Equity')
drawdown_chart = read_chart(project_id, 'Drawdown')
benchmark_chart = read_chart(project_id, 'Benchmark')
The process to get your project Id depends on if you use the Cloud Platform, Local Platform, or CLI.
def to_series(chart, series_name, selector=lambda x: x.y):
return pd.Series({v.time: selector(v) for v in chart.series[series_name].values})
df = pd.DataFrame({
"Equity": to_series(strategy_equity, 'Equity', selector=lambda x: x.close),
"Return": to_series(strategy_equity, 'Return'),
"Drawdown": to_series(drawdown_chart, 'Equity Drawdown'),
"Benchmark": to_series(benchmark_chart, 'Benchmark')
}).ffill()
df.index = df.index.tz_localize('UTC').tz_convert('US/Eastern').tz_localize(None)
# Create subplots to plot series on same/different plots
fig, ax = plt.subplots(3, 1, figsize=(12, 16), sharex=True, gridspec_kw={'height_ratios': [2, 1, 1]})
# Plot the equity curve
ax[0].plot(df.index, df["Equity"])
ax[0].set_title("Strategy Equity Curve")
ax[0].set_ylabel("Portfolio Value ($)")
# Plot the benchmark on the same plot, scale by using another y-axis
ax2 = ax[0].twinx()
ax2.plot(df.index, df["Benchmark"], color="grey")
ax2.set_ylabel("Benchmark Price ($)", color="grey")
# Plot the daily returns
ax[1].plot(df.index, df["Return"], color="blue")
ax[1].set_title("Daily Return")
ax[1].set_ylabel("%")
# Plot the drawdown on another plot
ax[2].plot(df.index, df["Drawdown"], color="red")
ax[2].set_title("Drawdown")
ax[2].set_xlabel("Time")
ax[2].set_ylabel("%");
The following table shows all the chart series you can plot:
| Chart | Series | Description |
|---|---|---|
| Strategy Equity | Equity | Time series of the equity curve. This series may not update daily. |
| Return | Time series of daily returns. Use this series for daily updates. | |
| Daily Performance | Time series of daily percentage change. This series has as many data points as "Equity". | |
| Capacity | Strategy Capacity | Time series of strategy capacity snapshots |
| Drawdown | Equity Drawdown | Time series of equity peak-to-trough value |
| Benchmark | Benchmark | Time series of the benchmark closing price (SPY, by default) |
| Exposure | SecurityType - Long Ratio | Time series of the overall ratio of SecurityType long positions of the whole portfolio if any SecurityType is ever in the universe |
| SecurityType - Short Ratio | Time series of the overall ratio of SecurityType short position of the whole portfolio if any SecurityType is ever in the universe | |
| Assets Sales Volume | Each ticker is one series | A chart showing the proportion of total volume for each traded security. |
| Portfolio Turnover | Portfolio Turnover | A time series of the portfolio turnover rate. |
| Portfolio Margin | Each ticker is one series | A stacked area chart of the portfolio margin usage. For more information about this chart, see Portfolio Margin Plots. |
| Asset Plot | Price and Annotations | A time series of an asset's price with order event annotations. For more information about these charts, see Asset Plots. |
| Custom Chart | Custom Series | Time series of a Series in a custom chart |
Reconstruct Trades
When you stop a live algorithm, it loses its in-memory state. After you redeploy the project, the trade builder starts empty, so its ClosedTradesclosed_trades property only contains the trades of the current deployment. Follow these steps to reconstruct the complete trade list across all the live deployments of a project in the Research Environment:
#load "../Initialize.csx"
#load "../QuantConnect.csx"
- Define the project Id and get the deployments of the project.
- Get all the orders of the project.
- Replay the fill events through a new trade builder.
- Get the closed trades of the trade builder.
using QuantConnect;
using QuantConnect.Api;
using QuantConnect.Orders;
using QuantConnect.Statistics;
var projectId = 12345678;
// Request each status separately. Requests without a status filter can time out on accounts with many deployments.
var statuses = new[]
{
AlgorithmStatus.Running, AlgorithmStatus.Stopped, AlgorithmStatus.Liquidated, AlgorithmStatus.RuntimeError
};
var deployments = statuses
.SelectMany(status => api.ListLiveAlgorithms(status).Algorithms)
.Where(x => x.ProjectId == projectId)
.OrderBy(x => x.Launched)
.ToList();
foreach (var deployment in deployments)
{
Console.WriteLine($"{deployment.DeployId}: {deployment.Launched} - {deployment.Stopped}");
} project_id = 12345678
# Request each status separately. Requests without a status filter can time out on accounts with many deployments.
statuses = [
AlgorithmStatus.RUNNING, AlgorithmStatus.STOPPED, AlgorithmStatus.LIQUIDATED, AlgorithmStatus.RUNTIME_ERROR
]
deployments = sorted(
[x for status in statuses for x in api.list_live_algorithms(status).algorithms if x.project_id == project_id],
key=lambda x: x.launched
)
for deployment in deployments:
print(f'{deployment.deploy_id}: {deployment.launched} - {deployment.stopped}')
The following table provides links to documentation that explains how to get the project Id, depending on the platform you use:
| Platform | Project Id |
|---|---|
| Cloud Platform | Get Project Id |
| Local Platform | Get Project Id |
| CLI | Get Project Id |
The ListLiveAlgorithmslist_live_algorithms method returns a list of deployment summaries with the DeployIddeploy_id, Launchedlaunched, and Stoppedstopped times of each deployment. The reconstruction in the following steps doesn't need them, but each fill event carries its deployment Id, so you can use these summaries to relate the reconstructed trades to a specific deployment.
List<ApiOrderResponse> ReadAllOrders(Func<int, int, List<ApiOrderResponse>> fetchWindow)
{
var all = new List<ApiOrderResponse>();
// Retry the first window while the response is empty (may be loading).
List<ApiOrderResponse> first = null;
for (var attempt = 0; attempt < 10; attempt++)
{
first = fetchWindow(0, 100);
if (first.Any()) break;
Console.WriteLine($"Orders loading... (attempt {attempt + 1}/10)");
Thread.Sleep(10000);
}
if (first == null || !first.Any()) return all;
all.AddRange(first);
// Paginate in 100-index windows until the endpoint returns an empty window.
var start = 100;
while (true)
{
var window = fetchWindow(start, start + 100);
if (!window.Any()) break;
all.AddRange(window);
start += 100;
}
return all;
}
var orders = ReadAllOrders((s, e) => api.ReadLiveOrders(projectId, s, e));
Console.WriteLine($"Orders: {orders.Count}"); from time import sleep
def read_all_orders(fetch_window):
orders = []
# Retry the first window while the response is empty (may be loading).
first = []
for attempt in range(10):
first = fetch_window(0, 100)
if first:
break
print(f"Orders loading... (attempt {attempt + 1}/10)")
sleep(10)
if not first:
return orders
orders.extend(first)
# Paginate in 100-index windows until the endpoint returns an empty window.
start = 100
while True:
window = fetch_window(start, start + 100)
if not window:
break
orders.extend(window)
start += 100
return orders
orders = read_all_orders(lambda s, e: api.read_live_orders(project_id, s, e))
print(f'Orders: {len(orders)}')
The ReadLiveOrdersread_live_orders method returns the orders of every deployment of the project, concatenated in the order the deployments launched, so a single project-level loop retrieves the complete order history. Each call returns at most 100 orders, so paginate in 100-index windows until the endpoint returns an empty window, like the Live Reconciliation tutorial does. Each element of the result is an ApiOrderResponse object that contains the order and its fill events.
// Use the same grouping and matching methods as the trade builder in your algorithm.
var tradeBuilder = new TradeBuilder(FillGroupingMethod.FillToFill, FillMatchingMethod.FIFO);
// The orders are already in deployment-launch order, so replay them in list order.
foreach (var response in orders)
{
foreach (var serializedEvent in response.Events.Where(x => x.FillQuantity != 0))
{
var fill = OrderEvent.FromSerialized(serializedEvent);
tradeBuilder.ProcessFill(fill, 1m, serializedEvent.OrderFeeAmount ?? 0m);
}
} # Use the same grouping and matching methods as the trade builder in your algorithm.
trade_builder = TradeBuilder(FillGroupingMethod.FILL_TO_FILL, FillMatchingMethod.FIFO)
# The orders are already in deployment-launch order, so replay them in list order.
for response in orders:
for event in [x for x in response.events if x.fill_quantity != 0]:
fill = OrderEvent.from_serialized(event)
trade_builder.process_fill(fill, 1, event.order_fee_amount if event.order_fee_amount else 0)
Each fill event carries the Id of the deployment that produced it in its AlgorithmIdalgorithm_id property, so you can group the fills by deployment.
foreach (var trade in tradeBuilder.ClosedTrades)
{
Console.WriteLine($"{trade.Symbols[0].Value} {trade.Direction} quantity {trade.Quantity} " +
$"entry {trade.EntryTime:u} @ {trade.EntryPrice} exit {trade.ExitTime:u} @ {trade.ExitPrice} " +
$"profit {trade.ProfitLoss} fees {trade.TotalFees}");
} trades = trade_builder.closed_trades
data = pd.DataFrame([{
'symbol': trade.symbols[0].value,
'direction': str(trade.direction),
'quantity': trade.quantity,
'entry_time': trade.entry_time,
'entry_price': trade.entry_price,
'exit_time': trade.exit_time,
'exit_price': trade.exit_price,
'profit_loss': trade.profit_loss,
'total_fees': trade.total_fees
} for trade in trades])
data
The ClosedTradesclosed_trades property returns a list of Trade objects, which have the following attributes:
The preceding replay passes a conversion rate of 1 and the raw fee amount of each event, so it assumes the security and its fees are denominated in your account currency and the contract multiplier is 1. Adjust these arguments if these assumptions don't hold for the products you trade. Order Ids restart at 1 for each deployment and the trade builder assigns an order fee only to the first fill of each order Id, so the total fees exclude the fees of reused order Ids in later deployments. Positions that stay open across a redeployment form complete trades only because the replay is continuous across all the deployments.
Example
Example 1: Read Live Algorithm Statistics
The following example reads the current running live algorithm's statistics in a jupyter notebook.
// Load the necessary assemblies. #load "../Initialize.csx"
// Load the necessary assembly files. #load "../QuantConnect.csx"
using QuantConnect;
using QuantConnect.Api;
using QuantConnect.Research;
// Instantiate QuantBook instance for researching.
var qb = new QuantBook();
// Get current running live algorithm list in the current project.
var liveAlgorithms = api.ListLiveAlgorithms(AlgorithmStatus.Running)
var deployId = liveAlgorithms.Algorithms
.Single(x => x.ProjectId == qb.ProjectId)
.DeployId;
var liveAlgorithm = api.ReadLiveAlgorithm(qb.ProjectId, deployId);
// Obtain the live algorithm statistics.
Console.WriteLine(backtest.RuntimeStatistics.ToString()); # Instantiate QuantBook instance for researching.
qb = QuantBook()
# Get current running live algorithm list in the current project.
live_algorithms = api.list_live_algorithms(AlgorithmStatus.RUNNING)
deploy_id = next(
[x for x in live_algorithms.algorithms if x.project_id == qb.project_id]
).deploy_id
live_algorithm = api.read_live_algorithm(project_id, deploy_id)
# Obtain the live algorithm statistics.
print(live_algorithm.runtime_statistics)
Example 2: Reconstruct Trades Across Deployments
The following example reconstructs the complete trade list across all the live deployments of the current project in a jupyter notebook.
// Load the necessary assemblies. #load "../Initialize.csx"
// Load the necessary assembly files. #load "../QuantConnect.csx"
using QuantConnect;
using QuantConnect.Api;
using QuantConnect.Orders;
using QuantConnect.Research;
using QuantConnect.Statistics;
// Instantiate a QuantBook instance to get the project Id.
var qb = new QuantBook();
var projectId = qb.ProjectId;
// Get the deployments of the project, sorted by launch time.
// Request each status separately, since unfiltered requests can time out on large accounts.
var statuses = new[]
{
AlgorithmStatus.Running, AlgorithmStatus.Stopped, AlgorithmStatus.Liquidated, AlgorithmStatus.RuntimeError
};
var deployments = statuses
.SelectMany(status => api.ListLiveAlgorithms(status).Algorithms)
.Where(x => x.ProjectId == projectId)
.OrderBy(x => x.Launched)
.ToList();
Console.WriteLine($"Deployments: {deployments.Count}");
// Get the first window of orders. Retry while it is empty, since the orders may still be loading.
var orders = new List<ApiOrderResponse>();
for (var attempt = 0; attempt < 10; attempt++)
{
orders = api.ReadLiveOrders(projectId, 0, 100);
if (orders.Any()) break;
Thread.Sleep(10000);
}
// Get the remaining orders. A single project-level loop spans every deployment.
var start = 100;
while (true)
{
var window = api.ReadLiveOrders(projectId, start, start + 100);
if (!window.Any()) break;
orders.AddRange(window);
start += 100;
}
// Replay the fill events through a trade builder to reconstruct the trades.
var tradeBuilder = new TradeBuilder(FillGroupingMethod.FillToFill, FillMatchingMethod.FIFO);
foreach (var response in orders)
{
foreach (var serializedEvent in response.Events.Where(x => x.FillQuantity != 0))
{
tradeBuilder.ProcessFill(OrderEvent.FromSerialized(serializedEvent), 1m, serializedEvent.OrderFeeAmount ?? 0m);
}
}
// Print the closed trades of every deployment.
foreach (var trade in tradeBuilder.ClosedTrades)
{
Console.WriteLine($"{trade.Symbols[0].Value} {trade.Direction} quantity {trade.Quantity} " +
$"entry {trade.EntryTime:u} @ {trade.EntryPrice} exit {trade.ExitTime:u} @ {trade.ExitPrice} " +
$"profit {trade.ProfitLoss} fees {trade.TotalFees}");
} from time import sleep
# Instantiate a QuantBook instance to get the project Id.
qb = QuantBook()
project_id = qb.project_id
# Get the deployments of the project, sorted by launch time.
# Request each status separately, since unfiltered requests can time out on large accounts.
statuses = [
AlgorithmStatus.RUNNING, AlgorithmStatus.STOPPED, AlgorithmStatus.LIQUIDATED, AlgorithmStatus.RUNTIME_ERROR
]
deployments = sorted(
[x for status in statuses for x in api.list_live_algorithms(status).algorithms if x.project_id == project_id],
key=lambda x: x.launched
)
print(f'Deployments: {len(deployments)}')
# Get the first window of orders. Retry while it is empty, since the orders may still be loading.
orders = []
for attempt in range(10):
orders = api.read_live_orders(project_id, 0, 100)
if orders:
break
sleep(10)
# Get the remaining orders. A single project-level loop spans every deployment.
start = 100
while True:
window = api.read_live_orders(project_id, start, start + 100)
if not window:
break
orders.extend(window)
start += 100
# Replay the fill events through a trade builder to reconstruct the trades.
trade_builder = TradeBuilder(FillGroupingMethod.FILL_TO_FILL, FillMatchingMethod.FIFO)
for response in orders:
for event in [x for x in response.events if x.fill_quantity != 0]:
fee = event.order_fee_amount if event.order_fee_amount else 0
trade_builder.process_fill(OrderEvent.from_serialized(event), 1, fee)
# Organize the closed trades of every deployment into a DataFrame.
trades = trade_builder.closed_trades
pd.DataFrame([{
'symbol': trade.symbols[0].value,
'direction': str(trade.direction),
'quantity': trade.quantity,
'entry_time': trade.entry_time,
'entry_price': trade.entry_price,
'exit_time': trade.exit_time,
'exit_price': trade.exit_price,
'profit_loss': trade.profit_loss,
'total_fees': trade.total_fees
} for trade in trades])