Files
strategy-lab/to_explore/kama_from_scratch-CB378.ipynb
David Brazda e3da60c647 daily update
2024-10-21 20:57:56 +02:00

2112 lines
62 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Task / Learning "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this exercise, we build the Kaufmann Adaptive Moving Average (**KAMA**) Indicator from scratch using VectorBT Pro's (**VBT**) Indicator Factory.\n",
"\n",
"**Sources**:\n",
"\n",
"- https://school.stockcharts.com/doku.php?id=technical_indicators:kaufman_s_adaptive_moving_average\n",
"- https://www.youtube.com/watch?v=fYlB8mSirJg&t=315s \n",
"- https://vectorbt.pro/documentation/indicators/development/\n",
"- https://vectorbt.pro/tutorials/signal-development/index-locked-81f8af74-7fff-42e5-8b64-bb57c63e3f7c\n",
"\n",
"**Background**:\n",
"\n",
"KAMA is an adaptive moving average developed by _Perry J. Kaufman_ that adapts its window length to \"market noise\". Thereby, the actual window length used to calculate the respective KAMA value depends on the \"efficiency\" of price moves within a lookback period. Generally, KAMA tracks prices more closely where new trends establish. At the same time, it keeps distance to the price action as the trend evolves through phases of corrections. As a result, KAMA generates fewer volatility-caused exit signals compared to SMA or EMA. Such characteristics of a moving average indiciator are especially fovourable for trend-following strategies. \n",
"\n",
"The KAMA indicator comes shipped with TA-LIB (cf. https://www.ta-lib.org/function.html) and is thus ready to be used with a one-liner of VBT's TA Lib parser (https://vectorbt.pro/documentation/indicators/parsers/). However, such implementation has a shortcoming: TA Lib does not allow us to adjust parameters such as the fast and slow smoothing parameters. As VTB users, we _do not like_ such restrictions. Instead, we want to run our own parameter combinations to see if we can do better than Perry, or at least produce a perfectly overfitted equity curve. Hence, we will rebuild the KAMA indicator from scratch, which will enable us to access and backtest all KAMA parameters. Let's do it."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Import and Configure VBT / sample data ##"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import vectorbtpro as vbt\n",
"import numpy as np\n",
"import pandas as pd\n",
"from itertools import product\n",
"from numba import njit\n",
"\n",
"vbt.settings.set_theme('dark')"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "373cf59793cc4e0d867933111d9e4d4f",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"0it [00:00, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"start = '2013-01-01 UTC' # crypto is in UTC\n",
"end = '2022-06-09 UTC'\n",
"timeframe = '1d'\n",
"cols = ['Open', 'High', 'Low', 'Close', 'Volume']\n",
"\n",
"data = vbt.BinanceData.fetch('BTCUSDT', start=start, end=end, timeframe=timeframe, limit=100000)\n",
"\n",
"ohlcv = data.get(cols)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## For comparison: KAMA from TA_Lib ##"
]
},
{
"cell_type": "code",
"execution_count": 157,
"metadata": {},
"outputs": [],
"source": [
"TA_KAMA = vbt.talib('KAMA')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lets see, which parameters the TA-Lib \"standard\" KAMA takes. We can easyily check this using format_func:"
]
},
{
"cell_type": "code",
"execution_count": 169,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"KAMA.run(\n",
" close,\n",
" timeperiod=Default(value=30),\n",
" timeframe=Default(value=None),\n",
" short_name='kama',\n",
" hide_params=None,\n",
" hide_default=True,\n",
" **kwargs\n",
"):\n",
" Run `KAMA` indicator.\n",
" \n",
" * Inputs: `close`\n",
" * Parameters: `timeperiod`, `timeframe`\n",
" * Outputs: `real`\n",
" \n",
" Pass a list of parameter names as `hide_params` to hide their column levels.\n",
" Set `hide_default` to False to show the column levels of the parameters with a default value.\n",
" \n",
" Other keyword arguments are passed to `vectorbtpro.indicators.factory.run_pipeline`.\n"
]
}
],
"source": [
"print(vbt.format_func(TA_KAMA.run))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can see that the only parameters that we may adjust are timeperiod (equals to length for calculating the Efficiency Ratio) and the timeframe. However, there are more paramaters, that we might be interested in (see below)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Building our own KAMA indicator ##"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### KAMA Calculation in general ###"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"... Basic information from school.stockcharts.com:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"KAMA is calculated as follows:\n",
"\n",
"- **KAMA** = Prior KAMA + SC x (Price - Prior KAMA)\n",
"\n",
"Before calculating KAMA, we need to calculate the Efficiency Ratio (**ER**) and the Smoothing Constant (**SC**).\n",
"\n",
"The settings recommended by Perry Kaufman are KAMA(10,2,30):\n",
"\n",
"- 10 is the number of periods for the Efficiency Ratio (ER).\n",
"- 2 is the number of periods for the fastest EMA constant.\n",
"- 30 is the number of periods for the slowest EMA constant."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As noted above, we may not adjust the fastest and the slowest EMA constant with TA_Lib's KAMA. However, even Perry Kaufmann - while warning to overoptimise - suggests at least to adjust the fastest EMA constant (to 3), where appropriate. The reason is that the value of \"2\" results in an EMA length of 4 (see below), which is blazingly reactive and may be \"over-adaptive\" in the end. To better understand this, let's explore the calculation steps in more detail:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Step 1: Efficiency Ratio (ER)**\n",
"\n",
"The ER is basically the price change adjusted for the daily volatility. Formula:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"ER = Change/Volatility\n",
"\n",
"Change = ABS(Close - Close (10 periods ago))\n",
"\n",
"Volatility = Sum10(ABS(Close - Prior Close))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"ABS stands for Absolute Value. Volatility is the sum of the absolute value of the last ten price changes (Close - Prior Close). \n",
"\n",
"- In statistical terms, the Efficiency Ratio tells us the _fractal efficiency of price changes_. \n",
"- ER fluctuates between 1 and 0, but these extremes are the exception, not the norm. ER would be 1 if prices moved up 10 consecutive periods or down 10 consecutive periods. \n",
"- ER would be zero if price is unchanged over the 10 periods."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Step 2: Smoothing Constant (SC)**\n",
"\n",
"The smoothing constant uses the ER and two smoothing parameters based on an exponential moving average. Formula:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"SC = [ER x (fastest SC - slowest SC) + slowest SC]2\n",
"\n",
"SC = [ER x (2/(2+1) - 2/(30+1)) + 2/(30+1)]2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As you may have noticed, the Smoothing Constant is using the smoothing constants for an exponential moving average in its formula: \n",
"- (2/30+1) is the smoothing constant for a 30-period EMA. The Fastest SC is the smoothing constant for shorter EMA (2-periods). \n",
"- The slowest SC is the smoothing constant for the slowest EMA (30-periods). \n",
"Note that the “2” at the end is to square the equation. This means, that KAMA - in its standard application - ranges between EMA 4 (2x2) and 900 (30x30)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As a sidenote: The term \"smoothing constant\" is somewhat an incorrect label, as the SC is anything but constant. As a programmer, you would rather call it a smoothing _variable_."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Example implementation in Pinescript ###"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lets also look at a Pinescript (TradingView) implementation example. Below is HPotter's code (cf. https://www.tradingview.com/script/RsdQHpdq-Kaufman-Moving-Average-Adaptive-KAMA/):"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"study(title=\"Kaufman Moving Average Adaptive (KAMA)\", shorttitle=\"Kaufman Moving Average Adaptive (KAMA)\", overlay = true)\n",
"Length = input(21, minval=1)\n",
"xPrice = close\n",
"xvnoise = abs(xPrice - xPrice[1])\n",
"nAMA = 0.0\n",
"nfastend = 0.666\n",
"nslowend = 0.0645\n",
"nsignal = abs(xPrice - xPrice[Length])\n",
"nnoise = sum(xvnoise, Length)\n",
"nefratio = iff(nnoise != 0, nsignal / nnoise, 0)\n",
"nsmooth = pow(nefratio * (nfastend - nslowend) + nslowend, 2) \n",
"nAMA := nz(nAMA[1]) + nsmooth * (xPrice - nz(nAMA[1]))\n",
"plot(nAMA, color=color.blue, title=\"KAMA\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Implementation in VBT ###"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Having collected all that material, we feel confident to build this indicator under VBT.\n",
"\n",
"Let's first convert our close data to a numpy_array (this step will be done by VBT automatically later on):"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"close_np = ohlcv['Close'].to_numpy()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 1. Efficiency Ratio (ER)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We then start with the ER. The ER requires us to perform the following calculations (see above):\n",
"\n",
"- Change = ABS(Close - Close (10 periods ago))\n",
"- Volatility = Sum10(ABS(Close - Prior Close))\n",
"- **ER = Change/Volatility**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The standard lookback period to calculate the ER is 10. So lets assign it."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"er_lookback = 10"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To calculate the ER, we need to perform two shifting operations:\n",
"\n",
"- The close of 10 bars ago (see above: Change = ABS(Close - Close (**10 periods ago**)))\n",
"- The close of 1 bar ago (see above: Volatility = Sum**10**(ABS(Close - **Prior Close**)))\n",
"\n",
"Lets build our first blocks:\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Code Block**:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"close_er_lb = vbt.nb.fshift_1d_nb(close_np, n=er_lookback) # er_lb stands for efficiency_ratio_lookback\n",
"close_prior = vbt.nb.fshift_1d_nb(close_np, n=1) # Note that the er_lookback is not relevant to this operation, but for the window of the rolling sum (see above)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lets see if we handled things correctly:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>close_prior</th>\n",
" <th>close_er_lb</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>4285.08</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>4108.37</td>\n",
" <td>4285.08</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>4139.98</td>\n",
" <td>4108.37</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4086.29</td>\n",
" <td>4139.98</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>4016.00</td>\n",
" <td>4086.29</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>4040.00</td>\n",
" <td>4016.00</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>4114.01</td>\n",
" <td>4040.00</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>4316.01</td>\n",
" <td>4114.01</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>4280.68</td>\n",
" <td>4316.01</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>4337.44</td>\n",
" <td>4280.68</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>10</th>\n",
" <td>4310.01</td>\n",
" <td>4337.44</td>\n",
" <td>4285.08</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 0 close_prior close_er_lb\n",
"0 4285.08 NaN NaN\n",
"1 4108.37 4285.08 NaN\n",
"2 4139.98 4108.37 NaN\n",
"3 4086.29 4139.98 NaN\n",
"4 4016.00 4086.29 NaN\n",
"5 4040.00 4016.00 NaN\n",
"6 4114.01 4040.00 NaN\n",
"7 4316.01 4114.01 NaN\n",
"8 4280.68 4316.01 NaN\n",
"9 4337.44 4280.68 NaN\n",
"10 4310.01 4337.44 4285.08"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vDF1 = pd.DataFrame(close_np) # Create a Dataframe from our numpy array\n",
"vDF1['close_prior'] = close_prior # add our shifted data as a column (shift: 1 period ago)\n",
"vDF1['close_er_lb'] = close_er_lb # add our shifted data as another column (shift: 10 periods ago = ER lookback parameter)\n",
"\n",
"vDF1[0:11] # Display Dataframe's row 0-10"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We see that our shifts work out properly. Now we can easily calculate \"Change\" and \"Volatility\".\n",
"\n",
"**Code Block**:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"change = np.abs(close_np - close_er_lb) # Get the difference between the current closing price and the closing price 10 periods (ER lookback) ago - only positive numbers allowed.\n",
"\n",
"vola_in = np.abs(close_np - close_prior) # Get the difference between current closing price and prior closing price - only positive numbers allowed.\n",
"\n",
"volatility = vbt.nb.rolling_sum_1d_nb(arr=vola_in, window=er_lookback) # calculate the rolling sum of 1 day differences for a period of 10 (ER lookback)\n",
"\n",
"ER = change / volatility"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lets validate."
]
},
{
"cell_type": "code",
"execution_count": 170,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>close_prior</th>\n",
" <th>change_1_bar</th>\n",
" <th>close_er_lb</th>\n",
" <th>change_10_bars</th>\n",
" <th>Volatility</th>\n",
" <th>Efficieny Ratio (ER)</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>10</th>\n",
" <td>4310.01</td>\n",
" <td>4337.44</td>\n",
" <td>27.43</td>\n",
" <td>4285.08</td>\n",
" <td>24.93</td>\n",
" <td>751.83</td>\n",
" <td>0.033159</td>\n",
" </tr>\n",
" <tr>\n",
" <th>11</th>\n",
" <td>4386.69</td>\n",
" <td>4310.01</td>\n",
" <td>76.68</td>\n",
" <td>4108.37</td>\n",
" <td>278.32</td>\n",
" <td>651.80</td>\n",
" <td>0.427002</td>\n",
" </tr>\n",
" <tr>\n",
" <th>12</th>\n",
" <td>4587.48</td>\n",
" <td>4386.69</td>\n",
" <td>200.79</td>\n",
" <td>4139.98</td>\n",
" <td>447.50</td>\n",
" <td>820.98</td>\n",
" <td>0.545080</td>\n",
" </tr>\n",
" <tr>\n",
" <th>13</th>\n",
" <td>4555.14</td>\n",
" <td>4587.48</td>\n",
" <td>32.34</td>\n",
" <td>4086.29</td>\n",
" <td>468.85</td>\n",
" <td>799.63</td>\n",
" <td>0.586334</td>\n",
" </tr>\n",
" <tr>\n",
" <th>14</th>\n",
" <td>4724.89</td>\n",
" <td>4555.14</td>\n",
" <td>169.75</td>\n",
" <td>4016.00</td>\n",
" <td>708.89</td>\n",
" <td>899.09</td>\n",
" <td>0.788453</td>\n",
" </tr>\n",
" <tr>\n",
" <th>15</th>\n",
" <td>4834.91</td>\n",
" <td>4724.89</td>\n",
" <td>110.02</td>\n",
" <td>4040.00</td>\n",
" <td>794.91</td>\n",
" <td>985.11</td>\n",
" <td>0.806925</td>\n",
" </tr>\n",
" <tr>\n",
" <th>16</th>\n",
" <td>4472.14</td>\n",
" <td>4834.91</td>\n",
" <td>362.77</td>\n",
" <td>4114.01</td>\n",
" <td>358.13</td>\n",
" <td>1273.87</td>\n",
" <td>0.281135</td>\n",
" </tr>\n",
" <tr>\n",
" <th>17</th>\n",
" <td>4509.08</td>\n",
" <td>4472.14</td>\n",
" <td>36.94</td>\n",
" <td>4316.01</td>\n",
" <td>193.07</td>\n",
" <td>1108.81</td>\n",
" <td>0.174124</td>\n",
" </tr>\n",
" <tr>\n",
" <th>18</th>\n",
" <td>4100.11</td>\n",
" <td>4509.08</td>\n",
" <td>408.97</td>\n",
" <td>4280.68</td>\n",
" <td>180.57</td>\n",
" <td>1482.45</td>\n",
" <td>0.121805</td>\n",
" </tr>\n",
" <tr>\n",
" <th>19</th>\n",
" <td>4366.47</td>\n",
" <td>4100.11</td>\n",
" <td>266.36</td>\n",
" <td>4337.44</td>\n",
" <td>29.03</td>\n",
" <td>1692.05</td>\n",
" <td>0.017157</td>\n",
" </tr>\n",
" <tr>\n",
" <th>20</th>\n",
" <td>4619.77</td>\n",
" <td>4366.47</td>\n",
" <td>253.30</td>\n",
" <td>4310.01</td>\n",
" <td>309.76</td>\n",
" <td>1917.92</td>\n",
" <td>0.161508</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 0 close_prior change_1_bar close_er_lb change_10_bars \\\n",
"10 4310.01 4337.44 27.43 4285.08 24.93 \n",
"11 4386.69 4310.01 76.68 4108.37 278.32 \n",
"12 4587.48 4386.69 200.79 4139.98 447.50 \n",
"13 4555.14 4587.48 32.34 4086.29 468.85 \n",
"14 4724.89 4555.14 169.75 4016.00 708.89 \n",
"15 4834.91 4724.89 110.02 4040.00 794.91 \n",
"16 4472.14 4834.91 362.77 4114.01 358.13 \n",
"17 4509.08 4472.14 36.94 4316.01 193.07 \n",
"18 4100.11 4509.08 408.97 4280.68 180.57 \n",
"19 4366.47 4100.11 266.36 4337.44 29.03 \n",
"20 4619.77 4366.47 253.30 4310.01 309.76 \n",
"\n",
" Volatility Efficieny Ratio (ER) \n",
"10 751.83 0.033159 \n",
"11 651.80 0.427002 \n",
"12 820.98 0.545080 \n",
"13 799.63 0.586334 \n",
"14 899.09 0.788453 \n",
"15 985.11 0.806925 \n",
"16 1273.87 0.281135 \n",
"17 1108.81 0.174124 \n",
"18 1482.45 0.121805 \n",
"19 1692.05 0.017157 \n",
"20 1917.92 0.161508 "
]
},
"execution_count": 170,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vDF2 = pd.DataFrame(close_np)\n",
"vDF2['close_prior'] = close_prior\n",
"vDF2['change_1_bar'] = vola_in\n",
"vDF2['close_er_lb'] = close_er_lb\n",
"vDF2['change_10_bars'] = change\n",
"vDF2['Volatility'] = volatility\n",
"vDF2['Efficieny Ratio (ER)'] = ER\n",
"\n",
"vDF2[10:21]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Looks good. But lets further validate: If our code is conceptually correct, ER should have no value less and no value greater than 1 (see above reg. indicator description)."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.0\n",
"0.9995041128046777\n"
]
}
],
"source": [
"print(np.nanmin(ER)) # nanmin and nanmax ignore NaN values\n",
"print(np.nanmax(ER))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Rock solid. Lets sum up the code block so far and wrap it into our first numba_complied function:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Function to calculate Efficiency Ratio (ER) - Final Code Block #####"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"er_lookback = 10\n",
"\n",
"@njit\n",
"def get_ER_nb (close, er_lookback):\n",
" close_np = close\n",
" close_er_lb = vbt.nb.fshift_1d_nb(close_np, n=er_lookback)\n",
" close_prior = vbt.nb.fshift_1d_nb(close_np, n=1)\n",
" change = np.abs(close_np - close_er_lb)\n",
" vola_in = np.abs(close_np - close_prior)\n",
" volatility = vbt.nb.rolling_sum_1d_nb(arr=vola_in, window=er_lookback)\n",
" ER = change / volatility\n",
" return ER\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 2. Smoothing Constant (SC)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lets move on to the SC. To recap what we need to code here:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The formula for the SC is as follows:\n",
"\n",
"- SC = [ER x (fastest SC - slowest SC) + slowest SC]2\n",
"\n",
"With standard settings (2, 30):\n",
"\n",
"- SC = [ER x (2/(2+1) - 2/(30+1)) + 2/(30+1)]2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To make our indicator parametrizable, we will not hardcode these values (2,30), but instead assign them as standard parameters:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"fast_sc_period = 2\n",
"slow_sc_period = 30"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's check our values for fastest and slowest:"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.6666666666666666\n"
]
}
],
"source": [
"fast_sc = 2 / (fast_sc_period +1)\n",
"print(fast_sc)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The result equates to the code of HPotter (\"nfastend\"). However, the excel sheet embedded at school.stockcharts.com gives a value of 0.0645 for \"fastest\". Is something wrong with our calculation?\n",
"- Let's calculate slow_sc to check this."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.06451612903225806\n"
]
}
],
"source": [
"slow_sc = 2 / (slow_sc_period +1)\n",
"print(slow_sc)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ok. The result corresponds HPotter's input value (\"nslowend\") and is (except for rounding) identical to school.stockcharts \"slowest\". \n",
"*Confusion*: Who's right here? \n",
"- Downloading the embedded exceel sheet from school.stockharts.com reveals that stockcharts.com apparently confused fast and slow in the excel sheet. \n",
"- I was able to further verify this by applying some other KAMA scripts in TradingView. Result: We're good."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Function to calculate the Smoothing Constant (ER) - Final Code Block #####"
]
},
{
"cell_type": "code",
"execution_count": 175,
"metadata": {},
"outputs": [],
"source": [
"er_lookback = 10\n",
"fast_sc_period = 2\n",
"slow_sc_period = 30\n",
"\n",
"@njit\n",
"def get_SC_nb (close, er_lookback, fast_sc_period, slow_sc_period):\n",
" close_np = close\n",
" close_er_lb = vbt.nb.fshift_1d_nb(close_np, n=er_lookback)\n",
" close_prior = vbt.nb.fshift_1d_nb(close_np, n=1)\n",
" change = np.abs(close_np - close_er_lb)\n",
" vola_in = np.abs(close_np - close_prior)\n",
" volatility = vbt.nb.rolling_sum_1d_nb(arr=vola_in, window=er_lookback)\n",
" ER = change / volatility\n",
" # New Code starts here\n",
" fast_sc = 2 / (fast_sc_period +1)\n",
" slow_sc = 2 / (slow_sc_period +1)\n",
" SC = np.square(ER * (fast_sc - slow_sc) + slow_sc) # see above. The formula is SC = [ER x (fastest SC - slowest SC) + slowest SC]2\n",
" return SC\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And again: lets validate."
]
},
{
"cell_type": "code",
"execution_count": 173,
"metadata": {},
"outputs": [],
"source": [
"test_SC = get_SC_nb(close=close_np, er_lookback=er_lookback, fast_sc_period=fast_sc_period, slow_sc_period=slow_sc_period)\n",
"\n",
"vDF3 = vDF2\n",
"vDF3['SC'] = test_SC"
]
},
{
"cell_type": "code",
"execution_count": 174,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"384\n",
"1443\n"
]
}
],
"source": [
"print(np.nanargmin(test_SC)) # Lets get the row of the lowest SC - nanargmin ignores the nan values for us.\n",
"print(np.nanargmax(test_SC)) # And the row of the max SC"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>close_prior</th>\n",
" <th>change_1_bar</th>\n",
" <th>close_er_lb</th>\n",
" <th>change_10_bars</th>\n",
" <th>Volatility</th>\n",
" <th>Efficieny Ratio (ER)</th>\n",
" <th>SC</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>384</th>\n",
" <td>6700.0</td>\n",
" <td>7359.06</td>\n",
" <td>659.06</td>\n",
" <td>6700.0</td>\n",
" <td>0.0</td>\n",
" <td>1578.64</td>\n",
" <td>0.0</td>\n",
" <td>0.004162</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 0 close_prior change_1_bar close_er_lb change_10_bars \\\n",
"384 6700.0 7359.06 659.06 6700.0 0.0 \n",
"\n",
" Volatility Efficieny Ratio (ER) SC \n",
"384 1578.64 0.0 0.004162 "
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vDF3[384:385] # min ER value"
]
},
{
"cell_type": "code",
"execution_count": 176,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>close_prior</th>\n",
" <th>change_1_bar</th>\n",
" <th>close_er_lb</th>\n",
" <th>change_10_bars</th>\n",
" <th>Volatility</th>\n",
" <th>Efficieny Ratio (ER)</th>\n",
" <th>SC</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>1443</th>\n",
" <td>42206.37</td>\n",
" <td>40016.48</td>\n",
" <td>2189.89</td>\n",
" <td>29790.35</td>\n",
" <td>12416.02</td>\n",
" <td>12422.18</td>\n",
" <td>0.999504</td>\n",
" <td>0.444046</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 0 close_prior change_1_bar close_er_lb change_10_bars \\\n",
"1443 42206.37 40016.48 2189.89 29790.35 12416.02 \n",
"\n",
" Volatility Efficieny Ratio (ER) SC \n",
"1443 12422.18 0.999504 0.444046 "
]
},
"execution_count": 176,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vDF3[1443:1444] # min ER value"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Well - looks good. Apparently, the SC (unlike the ER) does not range between 0 and 1. \n",
"- This seems to be confirmed by the excel spreadsheet obtained from school.stockcharts.com. \n",
"- Yes, we have lost some faith in this source - but we can cross-validate our indicator later, vs. the TA-Lib implementation."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 3. KAMA Calculation ####"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So lets code the last bit and puzzle our pieces together."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Current KAMA = Prior KAMA + SC x (Price - Prior KAMA)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We have two challenges here:\n",
"- _Generally_, we need to access the prior value for calculating the current value of the very same parameter that we are calculating. The issue with this type of calculation is that we won't be able to do this using shifting (i tried to implement that in various ways). Instead we will have to use a for loop, that numba will speed up for us later on.\n",
"- _Specifically_, we need to deal with of our first KAMA value seperately: We cannot perform any KAMA calculation without having a \"prior\" KAMA. But _what_ is our \"prior\" KAMA in case of our first calculation?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### a) Input for our first KAMA calculation #####"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's start with our initial value issue. This is were the online documentation got rather silent. school.stockcharts mentions:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\"Since we need an initial value to start the calculation, **the first KAMA is just a simple moving average**. The following calculations are based on the formula below.\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**First Approach: SMA for initial value**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I couldn't figure out any such step from the provided formulas or HPotter's Code - but it does not sound too weird to use an SMA for the initial value (but wouldn't be an EMA more close to the nature of KAMA?). So, lets do it:"
]
},
{
"cell_type": "code",
"execution_count": 182,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"4172.386"
]
},
"execution_count": 182,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"kama = np.empty(close_np.shape, close_np.dtype) # create an empty array for kama values that has the same length as the close array\n",
"kama[:] = np.nan # assign nan to all values\n",
"first_value_range = close_np[0:er_lookback] # get the values of our pre-calculation range for SMA.\n",
"first_value = np.sum(first_value_range) / er_lookback # The SMA is the sum of all closing prices within this range, divided by the size of the range (=lookback period)\n",
"\n",
"kama[er_lookback-1] = first_value\n",
"kama[er_lookback-1]\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Second Approach: 0.0 als initial value**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It seems like in HPotter's code, the initial value of the KAMA is 0.0. However, if we recap the formula (Prior KAMA + SC x (Price - Prior KAMA)) this does not seem to make sense:"
]
},
{
"cell_type": "code",
"execution_count": 185,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"30.76209029303158"
]
},
"execution_count": 185,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"zero_approach = 0.0 + SC[10] * (close_np[10] - 0.0)\n",
"zero_approach"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This calculation apparently disqualifies. Remember that all further values will build upon this initial value - a value of 30 is rather useless."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Third approach: Use last close as initial value**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A further approach might be to set the last close price as initial value:"
]
},
{
"cell_type": "code",
"execution_count": 187,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"4337.44"
]
},
"execution_count": 187,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"kama[er_lookback-1] = close_np[er_lookback-1]\n",
"kama[er_lookback-1] "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This might have the benefit, to start the KAMA calculation at the recent price level; however, as there is no averaging at all, a lot of weight would be given to the most recent candle before KAMA calculation."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Conclusion:**\n",
"\n",
"Approach 1 and 3 seem acceptable, approach 2 disqualifies. I tend to favour approach one, since using a SMA for the initial calculation is also a necessary step within EMA calculation:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\"Suppose that you want to use 20 days as the number of observations for the EMA. Then, you must wait until the 20th day to obtain the SMA. On the 21st day, you can then use the SMA from the previous day as the first EMA for yesterday.\"\n",
"\n",
"(Source: https://www.investopedia.com/terms/e/ema.asp)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Way forward**:\n",
"\n",
"But hey, we do not have to make this decision. Let's leave it to the user, by providing our indicator with another parameter **useSMA** that accepts true or false."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### b) The KAMA Loop #####"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The loop itself is rather simple. It starts, once the first SC is available, i.e. with SC[er_lookback]. To verify:"
]
},
{
"cell_type": "code",
"execution_count": 188,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.007137359378059814"
]
},
"execution_count": 188,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"SC[er_lookback]"
]
},
{
"cell_type": "code",
"execution_count": 189,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"nan"
]
},
"execution_count": 189,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"SC[er_lookback-1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Code Block:**"
]
},
{
"cell_type": "code",
"execution_count": 190,
"metadata": {},
"outputs": [],
"source": [
"for i in range (er_lookback, close_np.shape[0]):\n",
" kama[i] = kama[i-1] + SC[i] * (close_np[i] - kama[i-1])"
]
},
{
"cell_type": "code",
"execution_count": 192,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([ nan, nan, nan, nan,\n",
" nan, nan, nan, nan,\n",
" nan, 4337.44 , 4337.24422223])"
]
},
"execution_count": 192,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"kama[0:11]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Looks good. Remember that our first SC is based on a very low ER value, so it makes sense for the price to remain nearly unchanged."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### c) KAMA Function - Final Code Block #####"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So let's put those pieces together."
]
},
{
"cell_type": "code",
"execution_count": 193,
"metadata": {},
"outputs": [],
"source": [
"@njit\n",
"def KAMA_nb (close, er_lookback=10, fast_sc_period=2, slow_sc_period=30, useSMA=True):\n",
" close_np = close\n",
" close_er_lb = vbt.nb.fshift_1d_nb(close_np, n=er_lookback)\n",
" close_prior = vbt.nb.fshift_1d_nb(close_np, n=1)\n",
" change = np.abs(close_np - close_er_lb)\n",
" vola_in = np.abs(close_np - close_prior)\n",
" volatility = vbt.nb.rolling_sum_1d_nb(arr=vola_in, window=er_lookback)\n",
" ER = change / volatility\n",
" fast_sc = 2 / (fast_sc_period +1)\n",
" slow_sc = 2 / (slow_sc_period +1)\n",
" SC = np.square(ER * (fast_sc - slow_sc) + slow_sc)\n",
" # New Code starts here\n",
" KAMA = np.empty(close_np.shape, close_np.dtype)\n",
" KAMA[:] = np.nan\n",
" if useSMA==True:\n",
" first_value_range = close_np[0:er_lookback]\n",
" KAMA[er_lookback-1] = np.sum(first_value_range) / er_lookback\n",
" else: \n",
" KAMA[er_lookback-1] = close_np[er_lookback-1]\n",
" for i in range (er_lookback, close_np.shape[0]):\n",
" KAMA[i] = KAMA[i-1] + SC[i] * (close_np[i] - KAMA[i-1])\n",
" return KAMA"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 4. TA Lib Cross-Validation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lets run both of our options and cross-validate our results vs. the KAMA standard implementation of TA-Lib."
]
},
{
"cell_type": "code",
"execution_count": 196,
"metadata": {},
"outputs": [],
"source": [
"KAMA_SMA = KAMA_nb (close=close_np, er_lookback=10, fast_sc_period=2, slow_sc_period=30, useSMA=True)\n",
"KAMA_CLOSE = KAMA_nb (close=close_np, er_lookback=10, fast_sc_period=2, slow_sc_period=30, useSMA=False)\n",
"TALIB_KAMA = vbt.talib('KAMA').run(close=close_np, timeperiod=10).real # Note that the standard setting for time period is 30 so we need to adjust it to 10 to get comparable results."
]
},
{
"cell_type": "code",
"execution_count": 198,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>close_prior</th>\n",
" <th>change_1_bar</th>\n",
" <th>close_er_lb</th>\n",
" <th>change_10_bars</th>\n",
" <th>Volatility</th>\n",
" <th>Efficieny Ratio (ER)</th>\n",
" <th>SC</th>\n",
" <th>K_SMA</th>\n",
" <th>K_CLOSE</th>\n",
" <th>K_TALIB</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>4337.44</td>\n",
" <td>4280.68</td>\n",
" <td>56.76</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>NaN</td>\n",
" <td>4172.386000</td>\n",
" <td>4337.440000</td>\n",
" <td>NaN</td>\n",
" </tr>\n",
" <tr>\n",
" <th>10</th>\n",
" <td>4310.01</td>\n",
" <td>4337.44</td>\n",
" <td>27.43</td>\n",
" <td>4285.08</td>\n",
" <td>24.93</td>\n",
" <td>751.83</td>\n",
" <td>0.033159</td>\n",
" <td>0.007137</td>\n",
" <td>4173.368272</td>\n",
" <td>4337.244222</td>\n",
" <td>4337.244222</td>\n",
" </tr>\n",
" <tr>\n",
" <th>11</th>\n",
" <td>4386.69</td>\n",
" <td>4310.01</td>\n",
" <td>76.68</td>\n",
" <td>4108.37</td>\n",
" <td>278.32</td>\n",
" <td>651.80</td>\n",
" <td>0.427002</td>\n",
" <td>0.103450</td>\n",
" <td>4195.436303</td>\n",
" <td>4342.359364</td>\n",
" <td>4342.359364</td>\n",
" </tr>\n",
" <tr>\n",
" <th>12</th>\n",
" <td>4587.48</td>\n",
" <td>4386.69</td>\n",
" <td>200.79</td>\n",
" <td>4139.98</td>\n",
" <td>447.50</td>\n",
" <td>820.98</td>\n",
" <td>0.545080</td>\n",
" <td>0.154242</td>\n",
" <td>4255.905893</td>\n",
" <td>4380.167253</td>\n",
" <td>4380.167253</td>\n",
" </tr>\n",
" <tr>\n",
" <th>13</th>\n",
" <td>4555.14</td>\n",
" <td>4587.48</td>\n",
" <td>32.34</td>\n",
" <td>4086.29</td>\n",
" <td>468.85</td>\n",
" <td>799.63</td>\n",
" <td>0.586334</td>\n",
" <td>0.174371</td>\n",
" <td>4308.083576</td>\n",
" <td>4410.677386</td>\n",
" <td>4410.677386</td>\n",
" </tr>\n",
" <tr>\n",
" <th>14</th>\n",
" <td>4724.89</td>\n",
" <td>4555.14</td>\n",
" <td>169.75</td>\n",
" <td>4016.00</td>\n",
" <td>708.89</td>\n",
" <td>899.09</td>\n",
" <td>0.788453</td>\n",
" <td>0.290827</td>\n",
" <td>4429.301960</td>\n",
" <td>4502.058764</td>\n",
" <td>4502.058764</td>\n",
" </tr>\n",
" <tr>\n",
" <th>15</th>\n",
" <td>4834.91</td>\n",
" <td>4724.89</td>\n",
" <td>110.02</td>\n",
" <td>4040.00</td>\n",
" <td>794.91</td>\n",
" <td>985.11</td>\n",
" <td>0.806925</td>\n",
" <td>0.302947</td>\n",
" <td>4552.179836</td>\n",
" <td>4602.895160</td>\n",
" <td>4602.895160</td>\n",
" </tr>\n",
" <tr>\n",
" <th>16</th>\n",
" <td>4472.14</td>\n",
" <td>4834.91</td>\n",
" <td>362.77</td>\n",
" <td>4114.01</td>\n",
" <td>358.13</td>\n",
" <td>1273.87</td>\n",
" <td>0.281135</td>\n",
" <td>0.054663</td>\n",
" <td>4547.804589</td>\n",
" <td>4595.747642</td>\n",
" <td>4595.747642</td>\n",
" </tr>\n",
" <tr>\n",
" <th>17</th>\n",
" <td>4509.08</td>\n",
" <td>4472.14</td>\n",
" <td>36.94</td>\n",
" <td>4316.01</td>\n",
" <td>193.07</td>\n",
" <td>1108.81</td>\n",
" <td>0.174124</td>\n",
" <td>0.028684</td>\n",
" <td>4546.693797</td>\n",
" <td>4593.261631</td>\n",
" <td>4593.261631</td>\n",
" </tr>\n",
" <tr>\n",
" <th>18</th>\n",
" <td>4100.11</td>\n",
" <td>4509.08</td>\n",
" <td>408.97</td>\n",
" <td>4280.68</td>\n",
" <td>180.57</td>\n",
" <td>1482.45</td>\n",
" <td>0.121805</td>\n",
" <td>0.019006</td>\n",
" <td>4538.206161</td>\n",
" <td>4583.888941</td>\n",
" <td>4583.888941</td>\n",
" </tr>\n",
" <tr>\n",
" <th>19</th>\n",
" <td>4366.47</td>\n",
" <td>4100.11</td>\n",
" <td>266.36</td>\n",
" <td>4337.44</td>\n",
" <td>29.03</td>\n",
" <td>1692.05</td>\n",
" <td>0.017157</td>\n",
" <td>0.005602</td>\n",
" <td>4537.244081</td>\n",
" <td>4582.670943</td>\n",
" <td>4582.670943</td>\n",
" </tr>\n",
" <tr>\n",
" <th>20</th>\n",
" <td>4619.77</td>\n",
" <td>4366.47</td>\n",
" <td>253.30</td>\n",
" <td>4310.01</td>\n",
" <td>309.76</td>\n",
" <td>1917.92</td>\n",
" <td>0.161508</td>\n",
" <td>0.026169</td>\n",
" <td>4539.403704</td>\n",
" <td>4583.641789</td>\n",
" <td>4583.641789</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 0 close_prior change_1_bar close_er_lb change_10_bars \\\n",
"9 4337.44 4280.68 56.76 NaN NaN \n",
"10 4310.01 4337.44 27.43 4285.08 24.93 \n",
"11 4386.69 4310.01 76.68 4108.37 278.32 \n",
"12 4587.48 4386.69 200.79 4139.98 447.50 \n",
"13 4555.14 4587.48 32.34 4086.29 468.85 \n",
"14 4724.89 4555.14 169.75 4016.00 708.89 \n",
"15 4834.91 4724.89 110.02 4040.00 794.91 \n",
"16 4472.14 4834.91 362.77 4114.01 358.13 \n",
"17 4509.08 4472.14 36.94 4316.01 193.07 \n",
"18 4100.11 4509.08 408.97 4280.68 180.57 \n",
"19 4366.47 4100.11 266.36 4337.44 29.03 \n",
"20 4619.77 4366.47 253.30 4310.01 309.76 \n",
"\n",
" Volatility Efficieny Ratio (ER) SC K_SMA K_CLOSE \\\n",
"9 NaN NaN NaN 4172.386000 4337.440000 \n",
"10 751.83 0.033159 0.007137 4173.368272 4337.244222 \n",
"11 651.80 0.427002 0.103450 4195.436303 4342.359364 \n",
"12 820.98 0.545080 0.154242 4255.905893 4380.167253 \n",
"13 799.63 0.586334 0.174371 4308.083576 4410.677386 \n",
"14 899.09 0.788453 0.290827 4429.301960 4502.058764 \n",
"15 985.11 0.806925 0.302947 4552.179836 4602.895160 \n",
"16 1273.87 0.281135 0.054663 4547.804589 4595.747642 \n",
"17 1108.81 0.174124 0.028684 4546.693797 4593.261631 \n",
"18 1482.45 0.121805 0.019006 4538.206161 4583.888941 \n",
"19 1692.05 0.017157 0.005602 4537.244081 4582.670943 \n",
"20 1917.92 0.161508 0.026169 4539.403704 4583.641789 \n",
"\n",
" K_TALIB \n",
"9 NaN \n",
"10 4337.244222 \n",
"11 4342.359364 \n",
"12 4380.167253 \n",
"13 4410.677386 \n",
"14 4502.058764 \n",
"15 4602.895160 \n",
"16 4595.747642 \n",
"17 4593.261631 \n",
"18 4583.888941 \n",
"19 4582.670943 \n",
"20 4583.641789 "
]
},
"execution_count": 198,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vDF4 = vDF2\n",
"vDF4['K_SMA'] = KAMA_SMA\n",
"vDF4['K_CLOSE'] = KAMA_CLOSE\n",
"vDF4['K_TALIB'] = TALIB_KAMA\n",
"vDF4[9:21]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Rock solid.** We can see that\n",
"- our results perfectly macth TA Lib when _the last close is used as an initial value_ (see row 9 and row 10 in K_Close and K_TALIB)\n",
"- using an SMA as initial value has some serious impact on the KAMA calculation when going forward - and we wouldn't be able to use this alternative, if we sticked to the TA Lib standard integration.\n",
"\n",
"Maybe you tell me, what Perry would prefer? If you guys leave me hanging, I might need to get myself a copy of his 1995 book."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 5. Feeding the Indicator Factory"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We are almost done. The logic and function is fully complete. All that is left to us is to feed VBT's indicator factory to perpetuate our work in a VBT class."
]
},
{
"cell_type": "code",
"execution_count": 199,
"metadata": {},
"outputs": [],
"source": [
"KAMA = vbt.IF(\n",
" class_name='KAMA',\n",
" short_name='kma',\n",
" input_names=['close'],\n",
" param_names=['er_lookback', 'fast_sc_period', 'slow_sc_period', 'useSMA'],\n",
" output_names=['KAMA']\n",
").with_apply_func(\n",
" KAMA_nb, \n",
" takes_1d=True, \n",
" er_lookback=10, \n",
" fast_sc_period=2,\n",
" slow_sc_period=30,\n",
" useSMA=False\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 203,
"metadata": {},
"outputs": [],
"source": [
"KAMA_results = KAMA.run(close=ohlcv['Close']).KAMA"
]
},
{
"cell_type": "code",
"execution_count": 204,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Open time\n",
"2017-08-26 00:00:00+00:00 4337.440000\n",
"2017-08-27 00:00:00+00:00 4337.244222\n",
"2017-08-28 00:00:00+00:00 4342.359364\n",
"2017-08-29 00:00:00+00:00 4380.167253\n",
"2017-08-30 00:00:00+00:00 4410.677386\n",
"2017-08-31 00:00:00+00:00 4502.058764\n",
"2017-09-01 00:00:00+00:00 4602.895160\n",
"2017-09-02 00:00:00+00:00 4595.747642\n",
"2017-09-03 00:00:00+00:00 4593.261631\n",
"2017-09-04 00:00:00+00:00 4583.888941\n",
"2017-09-05 00:00:00+00:00 4582.670943\n",
"2017-09-06 00:00:00+00:00 4583.641789\n",
"Freq: D, Name: Close, dtype: float64"
]
},
"execution_count": 204,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"KAMA_results[9:21]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Wow - that was the easiest part, right? Nice and smooth**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Speed Test ##"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Last not least, we want to evaluate how fast our indicator got. For this purpose, we'll create a simply crossover strategy and backtest 200 lookback periods versus the TA Lib implementation (thats the only thing comparable for the TA Lib implementation, remember?)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Basic Function to test parameters with our KAMA indicator:"
]
},
{
"cell_type": "code",
"execution_count": 210,
"metadata": {},
"outputs": [],
"source": [
"def test_our_KAMA(close=ohlcv['Close'], window=2):\n",
" kama = KAMA.run(close=close, er_lookback=window).KAMA\n",
" entries = close.vbt.crossed_above(kama)\n",
" exits = close.vbt.crossed_below(kama)\n",
" pf = vbt.Portfolio.from_signals(\n",
" close=close, \n",
" entries=entries, \n",
" exits=exits,\n",
" size=100,\n",
" size_type='value',\n",
" init_cash='auto')\n",
" return pf.stats([\n",
" 'total_return', \n",
" 'win_rate', \n",
" 'profit_factor',\n",
" 'max_dd',\n",
" 'total_trades'\n",
" ])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Test Run:"
]
},
{
"cell_type": "code",
"execution_count": 211,
"metadata": {},
"outputs": [],
"source": [
"test_run = test_our_KAMA()"
]
},
{
"cell_type": "code",
"execution_count": 212,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Total Return [%] 360.743458\n",
"Win Rate [%] 31.279621\n",
"Profit Factor 1.924447\n",
"Max Drawdown [%] 23.960212\n",
"Total Trades 211\n",
"dtype: object"
]
},
"execution_count": 212,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"test_run"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Basic Function to test parameters with TA Lib KAMA indicator:"
]
},
{
"cell_type": "code",
"execution_count": 213,
"metadata": {},
"outputs": [],
"source": [
"def test_TALIB(close=ohlcv['Close'], window=2):\n",
" kama = vbt.talib('KAMA').run(close=close, timeperiod=window).real\n",
" entries = close.vbt.crossed_above(kama)\n",
" exits = close.vbt.crossed_below(kama)\n",
" pf = vbt.Portfolio.from_signals(\n",
" close=close, \n",
" entries=entries, \n",
" exits=exits,\n",
" size=100,\n",
" size_type='value',\n",
" init_cash='auto')\n",
" return pf.stats([\n",
" 'total_return', \n",
" 'win_rate', \n",
" 'profit_factor',\n",
" 'max_dd',\n",
" 'total_trades'\n",
" ])"
]
},
{
"cell_type": "code",
"execution_count": 214,
"metadata": {},
"outputs": [],
"source": [
"test_run2 = test_TALIB()"
]
},
{
"cell_type": "code",
"execution_count": 215,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Total Return [%] 360.743458\n",
"Win Rate [%] 31.279621\n",
"Profit Factor 1.924447\n",
"Max Drawdown [%] 23.960212\n",
"Total Trades 211\n",
"dtype: object"
]
},
"execution_count": 215,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"test_run2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Prepare backtest setup"
]
},
{
"cell_type": "code",
"execution_count": 219,
"metadata": {},
"outputs": [],
"source": [
"windows = range(2,203)\n",
"th_combs = list(product(windows))"
]
},
{
"cell_type": "code",
"execution_count": 220,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"10.3 s ± 62.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
]
}
],
"source": [
"%%timeit\n",
"comb_stats = [\n",
" test_our_KAMA(window=window)\n",
" for window in th_combs\n",
" ] "
]
},
{
"cell_type": "code",
"execution_count": 221,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"9.81 s ± 713 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
]
}
],
"source": [
"%%timeit\n",
"comb_stats = [\n",
" test_TALIB(window=window)\n",
" for window in th_combs\n",
" ] "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Great. We are almost as fast as a hardcoded C++! And there sure is some room for optimisation."
]
}
],
"metadata": {
"interpreter": {
"hash": "153de004bc7c67f2fe26ef68536905b78d93ac2b900cf841dfced1c052cbd86b"
},
"kernelspec": {
"display_name": "Python 3.9.12 ('base')",
"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.9.12"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}