From 828440a5f3886c56ed0e9e18dfeafd19b88c3099 Mon Sep 17 00:00:00 2001 From: Alexandre Catarino Date: Wed, 13 May 2026 18:35:41 +0100 Subject: [PATCH 1/2] Add common research notebook templates for charting --- .../csharp/common/research.ipynb | 343 ++++++++++++++++++ .../python/common/research.ipynb | 286 +++++++++++++++ 2 files changed, 629 insertions(+) create mode 100644 project-templates/csharp/common/research.ipynb create mode 100644 project-templates/python/common/research.ipynb diff --git a/project-templates/csharp/common/research.ipynb b/project-templates/csharp/common/research.ipynb new file mode 100644 index 0000000000..c311163c4e --- /dev/null +++ b/project-templates/csharp/common/research.ipynb @@ -0,0 +1,343 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f3b00e08", + "source": "This template shows how to use Plotly.NET to create charts, since charting is a common feature in research.", + "metadata": {} + }, + { + "cell_type": "markdown", + "id": "17fb20aa", + "metadata": {}, + "source": [ + "## Preparation\n", + "\n", + "Load assemblies, import the QuantConnect, Plotly.NET, and Accord packages, then get some historical data to plot." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75a10299", + "metadata": { + "vscode": { + "languageId": "csharp" + } + }, + "outputs": [], + "source": [ + "// Load the assembly files and data types in their own cell.\n", + "#load \"../Initialize.csx\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "079dc2af", + "metadata": { + "vscode": { + "languageId": "csharp" + } + }, + "outputs": [], + "source": [ + "// Load the necessary assembly files.\n", + "#load \"../QuantConnect.csx\"\n", + "#r \"nuget: Plotly.NET\"\n", + "#r \"nuget: Plotly.NET.Interactive\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ea581c8", + "metadata": { + "vscode": { + "languageId": "csharp" + } + }, + "outputs": [], + "source": [ + "// Import the QuantConnect, Plotly.NET, and Accord packages for calculation and plotting.\n", + "using QuantConnect;\n", + "using QuantConnect.Research;\n", + "\n", + "using Plotly.NET;\n", + "using Plotly.NET.Interactive;\n", + "using Plotly.NET.LayoutObjects;\n", + "\n", + "using Accord.Math;\n", + "using Accord.Statistics;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa6c58a4", + "metadata": { + "vscode": { + "languageId": "csharp" + } + }, + "outputs": [], + "source": [ + "// Get historical data for a bank sector ETF and some banking companies over 2021.\n", + "var qb = new QuantBook();\n", + "var tickers = new[]\n", + "{\n", + " \"XLF\", // Financial Select Sector SPDR Fund\n", + " \"COF\", // Capital One Financial Corporation\n", + " \"GS\", // Goldman Sachs Group, Inc.\n", + " \"JPM\", // J P Morgan Chase & Co\n", + " \"WFC\" // Wells Fargo & Company\n", + "};\n", + "var symbols = tickers.Select(ticker => qb.AddEquity(ticker, Resolution.Daily).Symbol).ToList();\n", + "var history = qb.History(symbols, new DateTime(2021, 1, 1), new DateTime(2022, 1, 1)).ToList();" + ] + }, + { + "cell_type": "markdown", + "id": "d9ce2705", + "metadata": {}, + "source": [ + "## Candlestick Chart" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ca44325", + "metadata": { + "vscode": { + "languageId": "csharp" + } + }, + "outputs": [], + "source": [ + "// Select a symbol to plot its candlestick plot.\n", + "var symbol = symbols.First();\n", + "\n", + "// Call the Chart2D.Chart.Candlestick constructor with the time and OHLC price IEnumerable to create the candlestick plot.\n", + "var bars = history.Select(slice => slice.Bars[symbol]);\n", + "var candlestick = Chart2D.Chart.Candlestick(\n", + " bars.Select(x => x.Open),\n", + " bars.Select(x => x.High),\n", + " bars.Select(x => x.Low),\n", + " bars.Select(x => x.Close),\n", + " bars.Select(x => x.EndTime)\n", + ");\n", + "\n", + "// Create the layout.\n", + "LinearAxis candleX = new LinearAxis();\n", + "candleX.SetValue(\"title\", \"Time\");\n", + "LinearAxis candleY = new LinearAxis();\n", + "candleY.SetValue(\"title\", \"Price ($)\");\n", + "Layout candleLayout = new Layout();\n", + "candleLayout.SetValue(\"xaxis\", candleX);\n", + "candleLayout.SetValue(\"yaxis\", candleY);\n", + "candleLayout.SetValue(\"title\", Title.init($\"{symbol} OHLC\"));\n", + "\n", + "// Assign the Layout and show the plot.\n", + "candlestick.WithLayout(candleLayout);\n", + "display(candlestick);" + ] + }, + { + "cell_type": "markdown", + "id": "93db8598", + "metadata": {}, + "source": [ + "## Line Chart" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7be5f8a7", + "metadata": { + "vscode": { + "languageId": "csharp" + } + }, + "outputs": [], + "source": [ + "// Plot the volume of the first symbol as a line chart.\n", + "var lineBars = history.Select(slice => slice.Bars[symbol]);\n", + "var line = Chart2D.Chart.Line(\n", + " lineBars.Select(x => x.EndTime),\n", + " lineBars.Select(x => x.Volume)\n", + ");\n", + "\n", + "LinearAxis lineX = new LinearAxis();\n", + "lineX.SetValue(\"title\", \"Time\");\n", + "LinearAxis lineY = new LinearAxis();\n", + "lineY.SetValue(\"title\", \"Volume\");\n", + "Layout lineLayout = new Layout();\n", + "lineLayout.SetValue(\"xaxis\", lineX);\n", + "lineLayout.SetValue(\"yaxis\", lineY);\n", + "lineLayout.SetValue(\"title\", Title.init($\"{symbol} Volume\"));\n", + "\n", + "line.WithLayout(lineLayout);\n", + "display(line);" + ] + }, + { + "cell_type": "markdown", + "id": "9c0ff10e", + "metadata": {}, + "source": [ + "## Scatter Plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18a22de4", + "metadata": { + "vscode": { + "languageId": "csharp" + } + }, + "outputs": [], + "source": [ + "// Plot the closing-price relationship between two symbols as a scatter plot.\n", + "var symbol1 = symbols.First();\n", + "var symbol2 = symbols.Last();\n", + "\n", + "var scatter = Chart2D.Chart.Point(\n", + " history.Select(slice => slice.Bars[symbol1].Close),\n", + " history.Select(slice => slice.Bars[symbol2].Close)\n", + ");\n", + "\n", + "LinearAxis scatterX = new LinearAxis();\n", + "scatterX.SetValue(\"title\", $\"{symbol1} Price ($)\");\n", + "LinearAxis scatterY = new LinearAxis();\n", + "scatterY.SetValue(\"title\", $\"{symbol2} Price ($)\");\n", + "Layout scatterLayout = new Layout();\n", + "scatterLayout.SetValue(\"xaxis\", scatterX);\n", + "scatterLayout.SetValue(\"yaxis\", scatterY);\n", + "scatterLayout.SetValue(\"title\", Title.init($\"{symbol1} vs {symbol2}\"));\n", + "\n", + "scatter.WithLayout(scatterLayout);\n", + "display(scatter);" + ] + }, + { + "cell_type": "markdown", + "id": "fc076c7b", + "metadata": {}, + "source": [ + "## Heat Map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a7c40f9", + "metadata": { + "vscode": { + "languageId": "csharp" + } + }, + "outputs": [], + "source": [ + "// Compute the daily returns of each stock.\n", + "var data = history.SelectMany(x => x.Bars.Values)\n", + " .GroupBy(x => x.Symbol)\n", + " .Select(x =>\n", + " {\n", + " var prices = x.Select(b => (double)b.Close).ToArray();\n", + " return Enumerable.Range(0, prices.Length - 1)\n", + " .Select(i => prices[i + 1] / prices[i] - 1).ToArray();\n", + " }).ToArray().Transpose();\n", + "\n", + "// Build the correlation matrix.\n", + "var corrMatrix = Measures.Correlation(data).Select(x => x.ToList()).ToList();\n", + "\n", + "// Plot the heat map.\n", + "var X = Enumerable.Range(0, tickers.Length).ToList();\n", + "var heatmap = Plotly.NET.Chart2D.Chart.Heatmap, double, int, int, string>(\n", + " zData: corrMatrix,\n", + " X: X,\n", + " Y: X,\n", + " ShowScale: true,\n", + " ReverseYAxis: true\n", + ");\n", + "\n", + "var heatAxis = new LinearAxis();\n", + "heatAxis.SetValue(\"tickvals\", X);\n", + "heatAxis.SetValue(\"ticktext\", tickers);\n", + "var heatLayout = new Layout();\n", + "heatLayout.SetValue(\"xaxis\", heatAxis);\n", + "heatLayout.SetValue(\"yaxis\", heatAxis);\n", + "heatLayout.SetValue(\"title\", Title.init(\"Banking Stocks and bank sector ETF Correlation Heat Map\"));\n", + "\n", + "heatmap.WithLayout(heatLayout);\n", + "display(heatmap);" + ] + }, + { + "cell_type": "markdown", + "id": "b403a42e", + "metadata": {}, + "source": [ + "## 3D Chart" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9e1459b", + "metadata": { + "vscode": { + "languageId": "csharp" + } + }, + "outputs": [], + "source": [ + "// Plot the closing-price correlation between three symbols in 3D.\n", + "var s1 = symbols[0];\n", + "var s2 = symbols[1];\n", + "var s3 = symbols[2];\n", + "\n", + "var chart3d = Chart3D.Chart.Point3D(\n", + " history.Select(slice => slice.Bars[s1].Close),\n", + " history.Select(slice => slice.Bars[s2].Close),\n", + " history.Select(slice => slice.Bars[s3].Close)\n", + ");\n", + "\n", + "LinearAxis xAxis3d = new LinearAxis();\n", + "xAxis3d.SetValue(\"title\", $\"{s1} Price ($)\");\n", + "LinearAxis yAxis3d = new LinearAxis();\n", + "yAxis3d.SetValue(\"title\", $\"{s2} Price ($)\");\n", + "LinearAxis zAxis3d = new LinearAxis();\n", + "zAxis3d.SetValue(\"title\", $\"{s3} Price ($)\");\n", + "Layout layout3d = new Layout();\n", + "layout3d.SetValue(\"xaxis\", xAxis3d);\n", + "layout3d.SetValue(\"yaxis\", yAxis3d);\n", + "layout3d.SetValue(\"zaxis\", zAxis3d);\n", + "layout3d.SetValue(\"title\", Title.init($\"{s1} vs {s2} vs {s3}\"));\n", + "\n", + "chart3d.WithLayout(layout3d);\n", + "display(chart3d);" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".NET (C#)", + "language": "C#", + "name": ".net-csharp" + }, + "language_info": { + "file_extension": ".cs", + "mimetype": "text/x-csharp", + "name": "C#", + "pygments_lexer": "csharp", + "version": "11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/project-templates/python/common/research.ipynb b/project-templates/python/common/research.ipynb new file mode 100644 index 0000000000..50986810f5 --- /dev/null +++ b/project-templates/python/common/research.ipynb @@ -0,0 +1,286 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7e07d002", + "source": "This template shows how to use Matplotlib to create charts, since charting is a common feature in research.", + "metadata": {} + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Preparation\n", + "\n", + "Import matplotlib and get some historical data to plot." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Import the matplotlib library for plots and settings.\n", + "import matplotlib.pyplot as plt\n", + "import mplfinance # for candlestick\n", + "import numpy as np\n", + "from pandas.plotting import register_matplotlib_converters\n", + "register_matplotlib_converters()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get historical data for a bank sector ETF and some banking companies over 2021.\n", + "qb = QuantBook()\n", + "tickers = [\"XLF\", # Financial Select Sector SPDR Fund\n", + " \"COF\", # Capital One Financial Corporation\n", + " \"GS\", # Goldman Sachs Group, Inc.\n", + " \"JPM\", # J P Morgan Chase & Co\n", + " \"WFC\"] # Wells Fargo & Company\n", + "symbols = [qb.add_equity(ticker, Resolution.DAILY).symbol for ticker in tickers]\n", + "history = qb.history(symbols, datetime(2021, 1, 1), datetime(2022, 1, 1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Candlestick Chart" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Select a symbol and grab its OHLCV data.\n", + "symbol = symbols[0]\n", + "data = history.loc[symbol]\n", + "data.columns = ['Close', 'High', 'Low', 'Open', 'Volume']\n", + "\n", + "# Plot the candlesticks.\n", + "mplfinance.plot(data,\n", + " type='candle',\n", + " style='charles',\n", + " title=f'{symbol.value} OHLC',\n", + " ylabel='Price ($)',\n", + " figratio=(15, 10))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Line Plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot the close price of the first symbol.\n", + "data = history.loc[symbol]['close']\n", + "data.plot(title=f\"{symbol} Close Price\", figsize=(15, 10))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Scatter Plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Select 2 stocks and compute their daily returns.\n", + "symbol1 = symbols[1]\n", + "symbol2 = symbols[2]\n", + "close_price1 = history.loc[symbol1]['close']\n", + "close_price2 = history.loc[symbol2]['close']\n", + "daily_returns1 = close_price1.pct_change().dropna()\n", + "daily_returns2 = close_price2.pct_change().dropna()\n", + "\n", + "# Fit an OLS line to the returns.\n", + "m, b = np.polyfit(daily_returns1, daily_returns2, deg=1)\n", + "x = np.linspace(daily_returns1.min(), daily_returns1.max())\n", + "y = m*x + b\n", + "\n", + "# Plot regression line and scatter dots.\n", + "plt.plot(x, y, color='red')\n", + "plt.scatter(daily_returns1, daily_returns2)\n", + "plt.title(f'{symbol1} vs {symbol2} daily returns Scatter Plot')\n", + "plt.xlabel(symbol1.value)\n", + "plt.ylabel(symbol2.value)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Histogram" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Daily returns of the first symbol.\n", + "close_prices = history.loc[symbol]['close']\n", + "daily_returns = close_prices.pct_change().dropna()\n", + "\n", + "# Normal distribution PDF using the empirical mean and std.\n", + "mean = daily_returns.mean()\n", + "std = daily_returns.std()\n", + "x = np.linspace(-3*std, 3*std, 1000)\n", + "pdf = 1/(std * np.sqrt(2*np.pi)) * np.exp(-(x-mean)**2 / (2*std**2))\n", + "\n", + "# Plot the PDF over the histogram.\n", + "plt.plot(x, pdf, label=\"Normal Distribution\")\n", + "plt.hist(daily_returns, bins=20)\n", + "plt.title(f'{symbol} Return Distribution')\n", + "plt.xlabel('Daily Return')\n", + "plt.ylabel('Count')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Bar Chart" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Mean daily % return per ticker.\n", + "close_prices = history['close'].unstack(level=0)\n", + "daily_returns = close_prices.pct_change() * 100\n", + "avg_daily_returns = daily_returns.mean()\n", + "\n", + "plt.figure(figsize=(15, 10))\n", + "plt.bar(avg_daily_returns.index, avg_daily_returns)\n", + "plt.title('Banking Stocks Average Daily % Returns')\n", + "plt.xlabel('Tickers')\n", + "plt.ylabel('%')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Heat Map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Correlation matrix of daily returns.\n", + "close_prices = history['close'].unstack(level=0)\n", + "daily_returns = close_prices.pct_change()\n", + "corr_matrix = daily_returns.corr()\n", + "\n", + "# Plot the heat map with ticker labels.\n", + "plt.imshow(corr_matrix, cmap='hot', interpolation='nearest')\n", + "plt.title('Banking Stocks and Bank Sector ETF Correlation Heat Map')\n", + "plt.xticks(np.arange(len(tickers)), labels=tickers)\n", + "plt.yticks(np.arange(len(tickers)), labels=tickers)\n", + "plt.colorbar()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pie Chart" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Inverse-variance portfolio weights.\n", + "close_prices = history['close'].unstack(level=0)\n", + "daily_returns = close_prices.pct_change()\n", + "inverse_variance = 1 / daily_returns.var()\n", + "\n", + "plt.pie(inverse_variance, labels=inverse_variance.index, autopct='%1.1f%%')\n", + "plt.title('Banking Stocks and Bank Sector ETF Allocation')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3D Chart" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Pick three symbols and grab their close-price series.\n", + "x, y, z = symbols[:3]\n", + "x_hist = history.loc[x].close\n", + "y_hist = history.loc[y].close\n", + "z_hist = history.loc[z].close\n", + "\n", + "# Build the 3D scatter.\n", + "fig = plt.figure(figsize=(8, 8))\n", + "ax = fig.add_subplot(projection='3d')\n", + "ax.scatter(x_hist, y_hist, z_hist)\n", + "ax.set_xlabel(f\"{x} Price\")\n", + "ax.set_ylabel(f\"{y} Price\")\n", + "ax.set_zlabel(f\"{z} Price\")\n", + "\n", + "# Zoom out a bit so the z-axis label isn't clipped.\n", + "ax.set_box_aspect(None, zoom=0.85)\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file From 8e17e3b41c318b65272d7fc72d7dc0178dec5174 Mon Sep 17 00:00:00 2001 From: Alexandre Catarino Date: Wed, 13 May 2026 18:36:53 +0100 Subject: [PATCH 2/2] Fix bar chart x-axis to use ticker strings instead of Symbol objects --- project-templates/python/common/research.ipynb | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/project-templates/python/common/research.ipynb b/project-templates/python/common/research.ipynb index 50986810f5..763f0942f3 100644 --- a/project-templates/python/common/research.ipynb +++ b/project-templates/python/common/research.ipynb @@ -168,18 +168,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "# Mean daily % return per ticker.\n", - "close_prices = history['close'].unstack(level=0)\n", - "daily_returns = close_prices.pct_change() * 100\n", - "avg_daily_returns = daily_returns.mean()\n", - "\n", - "plt.figure(figsize=(15, 10))\n", - "plt.bar(avg_daily_returns.index, avg_daily_returns)\n", - "plt.title('Banking Stocks Average Daily % Returns')\n", - "plt.xlabel('Tickers')\n", - "plt.ylabel('%')" - ] + "source": "# Mean daily % return per ticker.\nclose_prices = history['close'].unstack(level=0)\ndaily_returns = close_prices.pct_change() * 100\navg_daily_returns = daily_returns.mean()\n\nplt.figure(figsize=(15, 10))\nplt.bar([s.value for s in avg_daily_returns.index], avg_daily_returns)\nplt.title('Banking Stocks Average Daily % Returns')\nplt.xlabel('Tickers')\nplt.ylabel('%')" }, { "cell_type": "markdown",