Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion project-templates/python/custom-indicator/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
199 changes: 199 additions & 0 deletions project-templates/python/custom-indicator/research.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "ci-logo",
"metadata": {},
"source": [
"![QuantConnect Logo](https://cdn.quantconnect.com/web/i/icon.png)\n",
"<hr>\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",
"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\")"
]
},
{
"cell_type": "markdown",
"id": "ci-class-md",
"metadata": {},
"source": [
"### Define Indicator\n",
"\n",
"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",
" 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"
]
},
{
"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",
"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",
" 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"
]
},
{
"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",
"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"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Foundation-Py-Default",
"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.14"
}
},
"nbformat": 4,
"nbformat_minor": 5
}