Features Engineering - Trend¶
Identifying trends is at the heart of most quantitative trading strategies. Trend-related features help traders and quants capture the market's directional bias, momentum strength, and phase transitions. In this notebook, we will dive into key trend-based features extracted with the quantreo package.
Tips: Trend indicators are more effective when combined with volatility or regime filters to reduce false signals and improve model robustness. You can also enhance trend signals by smoothing them (e.g., using an EMA or KAMA on the raw trend output).
# Import the Features Engineering Package from Quantreo
import quantreo.features_engineering as fe
# To display the graphics
import matplotlib.pyplot as plt
plt.style.use("seaborn-v0_8")
/Users/lucasinglese/Desktop/Trading-laboratory/Projects/quantreo/quantreo/__init__.py:5: RuntimeWarning: Quantreo has only been tested on Python 3.11. You are using Python 3.10. Use at your own risk. warnings.warn(
# Import a dataset to test the functions and create new ones easily
from quantreo.datasets import load_generated_ohlcv
df = load_generated_ohlcv()
df = df.loc["2016"]
# Show the data
df
| open | high | low | close | volume | |
|---|---|---|---|---|---|
| time | |||||
| 2016-01-04 00:00:00 | 104.944241 | 105.312073 | 104.929735 | 105.232289 | 576.805768 |
| 2016-01-04 04:00:00 | 105.233361 | 105.252139 | 105.047564 | 105.149357 | 485.696723 |
| 2016-01-04 08:00:00 | 105.159851 | 105.384745 | 105.141110 | 105.330306 | 403.969745 |
| 2016-01-04 12:00:00 | 105.330306 | 105.505799 | 104.894155 | 104.923404 | 1436.917324 |
| 2016-01-04 16:00:00 | 104.914147 | 105.023293 | 104.913252 | 105.014347 | 1177.672605 |
| ... | ... | ... | ... | ... | ... |
| 2016-12-30 04:00:00 | 103.632257 | 103.711884 | 103.495896 | 103.564574 | 563.932484 |
| 2016-12-30 08:00:00 | 103.564574 | 103.629321 | 103.555581 | 103.616731 | 697.707475 |
| 2016-12-30 12:00:00 | 103.615791 | 103.628165 | 103.496810 | 103.515847 | 1768.926665 |
| 2016-12-30 16:00:00 | 103.515847 | 103.692243 | 103.400370 | 103.422193 | 921.437054 |
| 2016-12-30 20:00:00 | 103.420285 | 103.429955 | 103.195921 | 103.226868 | 764.291608 |
1548 rows × 5 columns
Simple Moving Average¶
The sma function computes a Simple Moving Average (SMA) on any numerical column of your DataFrame. The SMA is one of the most widely used technical indicators to smooth out price data and detect trends over a given period.
Warning: The first (window_size - 1) values will return NaN due to insufficient data to compute the average on these points.
df["sma"] = fe.trend.sma(df=df, col="close", window_size=30)
df["sma"]
time
2016-01-04 00:00:00 NaN
2016-01-04 04:00:00 NaN
2016-01-04 08:00:00 NaN
2016-01-04 12:00:00 NaN
2016-01-04 16:00:00 NaN
...
2016-12-30 04:00:00 103.594041
2016-12-30 08:00:00 103.616726
2016-12-30 12:00:00 103.642880
2016-12-30 16:00:00 103.650611
2016-12-30 20:00:00 103.660890
Name: sma, Length: 1548, dtype: float64
plt.figure(figsize=(15,6))
plt.plot(df["sma"], label="SMA")
plt.plot(df["close"], label="Close Price")
plt.title("SMA Feature", size=15)
plt.legend()
plt.show()
Kaufman's Adaptive Moving Average (KAMA)¶
The kama function calculates the Kaufman's Adaptive Moving Average, which adjusts dynamically to market noise by adapting its smoothing factor based on the price efficiency ratio.
- KAMA belongs to the family of moving averages and was developed by Perry J. Kaufman. It was first introduced in his book "Smarter Trading: Improving Performance in Changing Markets" (1995).
- This indicator is designed to smooth prices when markets are ranging and to be more responsive when markets trend.
The calculation is based on:
$$ ER_t = \frac{|\text{Price}_t - \text{Price}_{t - l_1}|}{\sum_{i = t - l_1 + 1}^{t} |\text{Price}_i - \text{Price}_{i - 1}|} $$
Then, the smoothing constant:
$$ SC_t = \left( ER_t \cdot \left( \frac{2}{l_2 + 1} - \frac{2}{l_3 + 1} \right) + \frac{2}{l_3 + 1} \right)^2 $$
Finally, the recursive calculation:
$$ KAMA_t = KAMA_{t-1} + SC_t \cdot \left( \text{Price}_t - KAMA_{t-1} \right) $$
Warning: The first (l1 - 1) values will be NaN due to insufficient data for the efficiency ratio calculation.
df["kama"] = fe.trend.kama(df=df, col="close", l1=10, l2=2, l3=30)
df["kama"]
time
2016-01-04 00:00:00 105.232289
2016-01-04 04:00:00 105.231944
2016-01-04 08:00:00 105.232354
2016-01-04 12:00:00 105.231068
2016-01-04 16:00:00 105.230166
...
2016-12-30 04:00:00 103.759441
2016-12-30 08:00:00 103.741749
2016-12-30 12:00:00 103.713567
2016-12-30 16:00:00 103.639221
2016-12-30 20:00:00 103.533520
Name: kama, Length: 1548, dtype: float64
plt.figure(figsize=(15,6))
plt.plot(df["kama"], label="KAMA")
plt.plot(df["close"], label="Close Price")
plt.title("kama Feature", size=15)
plt.legend()
plt.show()
Compare Moving Averages¶
plt.figure(figsize=(15,6))
plt.plot(df["kama"], label="KAMA")
plt.plot(df["sma"], label="SMA")
plt.plot(df["close"], label="Close Price")
plt.title("Moving averages Feature", size=15)
plt.legend()
plt.show()
Linear Slope¶
The linear_slope function computes the slope of a linear regression line over a rolling window on a given column.
This slope is often used to detect momentum shifts, trend strength, or simply directional bias over a moving window.
It works by fitting a simple linear regression of the form $y = ax + b$ on the rolling window and returning the value of the slope $a$ at each point in time.
A positive slope implies upward momentum, a negative slope implies downward momentum.
df["linear_slope_1M"] = fe.trend.linear_slope(df, col='close', window_size=30*6) # x6 because we have 4-hour data
df["linear_slope_1M"]
time
2016-01-04 00:00:00 NaN
2016-01-04 04:00:00 NaN
2016-01-04 08:00:00 NaN
2016-01-04 12:00:00 NaN
2016-01-04 16:00:00 NaN
...
2016-12-30 04:00:00 0.027650
2016-12-30 08:00:00 0.027722
2016-12-30 12:00:00 0.027700
2016-12-30 16:00:00 0.027682
2016-12-30 20:00:00 0.027576
Name: linear_slope_1M, Length: 1548, dtype: float64
plt.figure(figsize=(15,6))
plt.plot(df["linear_slope_1M"], label="1-month Linear Slope")
plt.title("1-Month Linear Slope Feature", size=15)
plt.legend()
plt.show()