From bddf25b451465d5211686f5afcbaccf5effee67f Mon Sep 17 00:00:00 2001 From: Rudy Osuna Date: Thu, 18 Jun 2026 14:27:06 -0700 Subject: [PATCH 1/2] Add custom indicator research template --- .../python/custom-indicator/main.py | 2 +- .../python/custom-indicator/research.ipynb | 155 ++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 project-templates/python/custom-indicator/research.ipynb diff --git a/project-templates/python/custom-indicator/main.py b/project-templates/python/custom-indicator/main.py index 140da03788..fac808ba2f 100644 --- a/project-templates/python/custom-indicator/main.py +++ b/project-templates/python/custom-indicator/main.py @@ -9,7 +9,7 @@ def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) # Request daily SPY data to feed the indicators to generate trade signals and trade. - self._spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW) + self._spy = self.add_equity("SPY") # Create a custom money flow index to generate a trade signal. self._custom_mfi = CustomMoneyFlowIndex(20) # Warm up for immediate usage of indicators. diff --git a/project-templates/python/custom-indicator/research.ipynb b/project-templates/python/custom-indicator/research.ipynb new file mode 100644 index 0000000000..db64427e26 --- /dev/null +++ b/project-templates/python/custom-indicator/research.ipynb @@ -0,0 +1,155 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ci-logo", + "metadata": {}, + "source": [ + "![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\n", + "
\n", + "\n", + "## Custom Indicator Research\n", + "\n", + "This notebook implements a custom Money Flow Index and builds value and signal series from daily SPY history." + ] + }, + { + "cell_type": "markdown", + "id": "ci-setup-md", + "metadata": {}, + "source": [ + "### Set Up QuantBook\n", + "\n", + "Create a daily SPY subscription for the custom indicator updates." + ] + }, + { + "cell_type": "code", + "id": "ci-setup", + "metadata": {}, + "source": [ + "qb = QuantBook()\n", + "qb.set_start_date(2024, 12, 31)\n", + "qb.settings.seed_initial_prices = True\n", + "equity = qb.add_equity(\"SPY\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "ci-class-md", + "metadata": {}, + "source": [ + "### Define Indicator\n", + "\n", + "Implement a 20-period [custom Money Flow Index](https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/custom-indicators)." + ] + }, + { + "cell_type": "code", + "id": "ci-class", + "metadata": {}, + "source": [ + "class CustomMoneyFlowIndex(PythonIndicator):\n", + "\n", + " def __init__(self, period: int) -> None:\n", + " super().__init__()\n", + " self.value = 0\n", + " self._previous_typical_price = 0\n", + " self._negative_money_flow: RollingWindow[float] = RollingWindow(period)\n", + " self._positive_money_flow: RollingWindow[float] = RollingWindow(period)\n", + "\n", + " def update(self, input: TradeBar) -> bool:\n", + " # Estimate the money flow by averaging the price multiplied by volume.\n", + " typical_price = (input.high + input.low + input.close) / 3\n", + " money_flow = typical_price * input.volume\n", + " # Classify the flow as positive or negative relative to the previous bar.\n", + " self._negative_money_flow.add(money_flow if typical_price < self._previous_typical_price else 0)\n", + " self._positive_money_flow.add(money_flow if typical_price > self._previous_typical_price else 0)\n", + " self._previous_typical_price = typical_price\n", + " positive_money_flow_sum = sum(self._positive_money_flow)\n", + " total_money_flow = positive_money_flow_sum + sum(self._negative_money_flow)\n", + " # Set the value to the positive money flow ratio.\n", + " self.value = 100\n", + " if total_money_flow != 0:\n", + " self.value *= positive_money_flow_sum / total_money_flow\n", + " return self._positive_money_flow.is_ready" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "ci-build-md", + "metadata": {}, + "source": [ + "### Build Time Series\n", + "\n", + "Feed daily TradeBar history through the indicator and store its value for each bar." + ] + }, + { + "cell_type": "code", + "id": "ci-build", + "metadata": {}, + "source": [ + "mfi = CustomMoneyFlowIndex(20)\n", + "mfi_by_date = {\n", + " bar.end_time: mfi.value\n", + " for bar in qb.history[TradeBar](equity, 250, Resolution.DAILY)\n", + " if mfi.update(bar)\n", + "}\n", + "\n", + "indicator_values = pd.Series(mfi_by_date, name=\"mfi\")\n", + "indicator_values" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "ci-signal-md", + "metadata": {}, + "source": [ + "### Signal Series\n", + "\n", + "Derive long and short signals based on the money flow indicator." + ] + }, + { + "cell_type": "code", + "id": "ci-signal", + "metadata": {}, + "source": [ + "# Go long when demand outweighs supply, otherwise go short.\n", + "signal = pd.Series(-1, index=indicator_values.index, name=\"signal\")\n", + "signal[indicator_values > 50] = 1\n", + "signal" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Foundation-Py-Default", + "language": "python", + "name": "Foundation-Py-Default" + }, + "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 +} From 787c95a9df95ab01949d5a3f37165a80908e89da Mon Sep 17 00:00:00 2001 From: Rudy Osuna Date: Thu, 18 Jun 2026 14:31:39 -0700 Subject: [PATCH 2/2] Update custom indicator research notebook --- .../python/custom-indicator/research.ipynb | 74 +++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/project-templates/python/custom-indicator/research.ipynb b/project-templates/python/custom-indicator/research.ipynb index db64427e26..df5a7f959e 100644 --- a/project-templates/python/custom-indicator/research.ipynb +++ b/project-templates/python/custom-indicator/research.ipynb @@ -25,16 +25,16 @@ }, { "cell_type": "code", + "execution_count": 5, "id": "ci-setup", "metadata": {}, + "outputs": [], "source": [ "qb = QuantBook()\n", "qb.set_start_date(2024, 12, 31)\n", "qb.settings.seed_initial_prices = True\n", "equity = qb.add_equity(\"SPY\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -43,13 +43,15 @@ "source": [ "### Define Indicator\n", "\n", - "Implement a 20-period [custom Money Flow Index](https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/custom-indicators)." + "Implement a [custom Money Flow Index Indicator](https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/custom-indicators)." ] }, { "cell_type": "code", + "execution_count": 6, "id": "ci-class", "metadata": {}, + "outputs": [], "source": [ "class CustomMoneyFlowIndex(PythonIndicator):\n", "\n", @@ -75,9 +77,7 @@ " if total_money_flow != 0:\n", " self.value *= positive_money_flow_sum / total_money_flow\n", " return self._positive_money_flow.is_ready" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -91,8 +91,32 @@ }, { "cell_type": "code", + "execution_count": 7, "id": "ci-build", "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2024-01-31 16:00:00 67.098719\n", + "2024-02-01 16:00:00 66.933356\n", + "2024-02-02 16:00:00 72.315045\n", + "2024-02-05 16:00:00 72.500965\n", + "2024-02-06 16:00:00 72.214645\n", + " ... \n", + "2024-12-23 16:00:00 56.324222\n", + "2024-12-24 13:00:00 55.565956\n", + "2024-12-26 16:00:00 55.006566\n", + "2024-12-27 16:00:00 53.274876\n", + "2024-12-30 16:00:00 48.933252\n", + "Name: mfi, Length: 231, dtype: float64" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "mfi = CustomMoneyFlowIndex(20)\n", "mfi_by_date = {\n", @@ -103,9 +127,7 @@ "\n", "indicator_values = pd.Series(mfi_by_date, name=\"mfi\")\n", "indicator_values" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -119,23 +141,45 @@ }, { "cell_type": "code", + "execution_count": 8, "id": "ci-signal", "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2024-01-31 16:00:00 1\n", + "2024-02-01 16:00:00 1\n", + "2024-02-02 16:00:00 1\n", + "2024-02-05 16:00:00 1\n", + "2024-02-06 16:00:00 1\n", + " ..\n", + "2024-12-23 16:00:00 1\n", + "2024-12-24 13:00:00 1\n", + "2024-12-26 16:00:00 1\n", + "2024-12-27 16:00:00 1\n", + "2024-12-30 16:00:00 -1\n", + "Name: signal, Length: 231, dtype: int64" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Go long when demand outweighs supply, otherwise go short.\n", "signal = pd.Series(-1, index=indicator_values.index, name=\"signal\")\n", "signal[indicator_values > 50] = 1\n", "signal" - ], - "execution_count": null, - "outputs": [] + ] } ], "metadata": { "kernelspec": { "display_name": "Foundation-Py-Default", "language": "python", - "name": "Foundation-Py-Default" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -147,7 +191,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11" + "version": "3.11.14" } }, "nbformat": 4,