Stock Trading with Machine Learning - Features
Features intro - what my model considers. One of many blog posts to come related to automatic stock trading leveraging machine learning
6/28/202581 min read
Price-Based Features
These features derive from the stock’s price history. They quantify trends, momentum, and key price levels that traders watch. Price-based metrics help the model sense if a stock is in an uptrend, downtrend, or at an extreme level relative to its recent history.
Returns (Percent & Log Returns): A return is how much the price has changed over a period, typically expressed as a percentage. For example, a 5-day return is (Price_today – Price_5_days_ago) / Price_5_days_ago. A log return is a mathematical variant using logarithms (often treated similarly for small changes). In plain terms, returns measure momentum – positive returns mean the price has been rising, negative returns mean it's been falling. The model uses returns over multiple horizons (1-day, 5-day, 10-day, etc.) to gauge short-term vs. medium-term momentum. Why useful? Big positive returns could indicate bullish momentum (a stock climbing fast), while steep negative returns flag bearish momentum. Consistent positive returns might signal an uptrend, whereas a sudden negative return after a series of positives might warn of a pullback. Essentially, returns tell the model “how fast and in what direction the price is moving” – a key ingredient for timing trades.
Moving Averages (MA) – SMA and EMA: A moving average smooths out price fluctuations by averaging prices over a window (say 20 days). Simple Moving Averages (SMA) weight all days equally, while Exponential Moving Averages (EMA) give more weight to recent days. We compute, for example, a 50-day SMA by summing the last 50 closing prices and dividing by 50. Traders often watch the 50-day or 200-day average as benchmarks of trend. The model includes several MAs (5, 10, 20, 50, 100, 200-day, etc.) and even the slope of these MAs (how much the MA has changed over a recent period) as features. Why useful? A moving average acts like a trend indicator – if the price is consistently above its 50-day SMA, the stock is in an uptrend (price > average). If it’s below, maybe it’s downtrending. The slope of an MA tells how sharply the trend is rising or falling. These features help the model identify trend direction and strength. For instance, if price crosses above its 200-day MA, many traders see that as a bullish signal (price breaking above a long-term average), whereas dropping below could be bearish. The model even looks at price-to-MA ratios (price divided by its moving average) to quantify how far above or below the “normal” trend the price is. Being far above an average might mean the stock is overextended (ripe for a correction), while far below might mean overly sold off – both useful clues for buy/sell decisions.
VWAP (Volume-Weighted Average Price): VWAP is like an average trading price of the day, weighted by volume. While the exact calculation in intraday trading is more complex, here it's approximated using daily data: essentially an average of the day’s typical prices (average of high, low, close) weighted by volume. Think of VWAP as the price level around which most trading happened. We also use Price-to-VWAP (current price divided by VWAP). Why useful? If today’s closing price is above the VWAP, it means the stock closed higher than where most of the day’s volume traded – often considered a sign of strength (buyers were willing to pay a premium by day’s end). Conversely, closing below VWAP could signal weakness. Traders compare price to VWAP as a benchmark – institutions, for example, try to buy below VWAP and sell above it. So for the model, price vs. VWAP indicates whether the day closed on a strong note or not, potentially foreshadowing momentum into the next day.
Position Within Daily Range: Several features tell us where the price closed relative to the day’s range. Close-to-High is Close / High of the day, and Close-to-Low is Close / Low. If Close-to-High is 1 (or 100%), the stock closed at its high of the day; a value like 0.95 means it closed near the high. Similarly, Close-to-Low near 1 means it closed at the day’s low. These indicate the strength of the close. Why useful? A close at the high (close-to-high ~1) suggests buyers were in control till the end – a bullish sign. Closing at the low means sellers dominated into the close – potentially bearish. These can signal short-term momentum: strong closes often lead to strong opens the next day, as sentiment carries over. Another related feature is Close-to-Open, the ratio of the closing price to that day’s opening price. This essentially captures the day’s return in percentage form (above 1 means the stock closed higher than it opened – a green day, below 1 means a red day). The body_size feature (absolute difference between close and open, relative to price) measures how big the day’s candlestick body was. A large body (close far from open) means a decisive move; a tiny body (close ≈ open) means an indecisive day (perhaps a doji candlestick indicating indecision).
Gap Features: Stocks often “gap” up or down at the open compared to the previous day’s close (for instance, due to overnight news or earnings). The feature Gap is defined as Today’s open / Yesterday’s close. A gap > 1.0 means the stock opened higher than it closed yesterday (gap up), and < 1.0 means a gap down. We also quantify gap_size (how large the gap is as a percentage) and even have binary flags: gap_up = 1 if the gap was more than +1% (a significant up gap), gap_down = 1 if gap was more than –1%. Why useful? Gaps can signal a sudden shift in sentiment. A big gap up could indicate new bullish information or enthusiasm – sometimes followed by further gains if momentum continues, or it could overshoot and then retrace (common in fade-the-gap strategies). A gap down might signal bad news or panic. By including gap features, the model is aware of these sudden jumps. For example, if a stock gaps up 5% but the model sees that historically such gaps often reverse, it might be cautious about a buy signal immediately after a large gap. Conversely, a moderate gap up with strong volume might kick off a trend. Essentially, gap features warn the model “something changed significantly overnight.”
Support & Resistance Levels: These features look at recent highs and lows over various periods (10-day high/low, 20-day, 50-day, etc.). For each period, we compute the highest high (resistance) and lowest low (support) within that window. We then measure the distance of today’s price from those levels: e.g., dist_from_resistance_20d = (Resistance_20d – Close) / Close, and similarly distance from support. There’s also sr_range_20d = (Resistance_20d – Support_20d) / Close – basically the size of the recent trading range relative to price. Why useful? Technical traders pay close attention when price approaches a prior high or low. A 20-day high can act as a resistance – a level where the stock struggled before. If the stock nears that level again, two things might happen: it could break out (bullish sign) or hit resistance and fall back. By knowing the distance from resistance, the model can gauge if there’s “headroom” for a move or if it’s near a potential ceiling. For instance, if a stock is just 1% below a major resistance and momentum is waning, the model might be less inclined to predict a further rise (since many sellers might step in at resistance). On the flip side, being near support (a recent low) with oversold conditions might be a ripe buy opportunity, as the support could hold. Essentially, these features give the model a sense of the stock’s trading range and whether it’s near the top or bottom of that range, which are often decision points for trades.
Price Channels: Similar to support/resistance but expressed as a percentile. For example, price_channel_pos_20 is a number between 0 and 1 indicating where today’s close sits within the past 20-day [low, high] range. A value of 0 means the close is at the 20-day low (bottom of the channel), 1 means it’s at the 20-day high, and 0.5 would mean it’s midway. Why useful? This tells us if the stock is “high” or “low” relative to recent history. A value near 1 (close to recent highs) might mean upside is limited unless a breakout occurs. Near 0 (close to recent lows) could indicate pessimism – or an oversold condition if fundamentals haven’t changed. Traders often say “buy low, sell high” – these features literally quantify how low or high the price is in its recent context. They help the model identify potential breakout setups (price_channel_pos near 1 could precede a breakout above the range) or mean-reversion plays (price_channel_pos near 0 might bounce up if the range holds).
Fibonacci Retracements: Traders love Fibonacci levels (derived from the Fibonacci sequence ratios). Common retracement levels are 23.6%, 38.2%, 50%, 61.8%, 78.6% of a significant price range. Here, the model looks at the last 100-day high and low, then calculates those Fib level prices in that range. It then measures how far the current price is from each level (as a percentage of price). For instance, dist_from_fib_382 might represent distance from the 38.2% retracement level. Why useful? Fibonacci retracement levels often act as support or resistance points as many traders watch them. If a stock has pulled back and is hovering around the 61.8% retracement of its last big rally, traders might anticipate a bounce there (it’s considered a deep but common retracement level). Conversely, failing a 38.2% retracement might signal a very weak bounce. By including distances from Fib levels, the model has a finger on these psychologically important points. In short, it knows if price is near a level where many humans might be placing buy or sell orders (simply because of self-fulfilling prophecy around Fibonacci ratios). This can help anticipate stall points or reversal zones even if the model doesn’t “understand” Fibonacci per se – it just sees historically these levels sometimes matter.
In summary, price-based features give the ML model a nuanced view of trends and key price levels. They answer questions like: How much has price moved? Is it trending or choppy? Is the stock cheap or expensive relative to its recent past? Are we near a breakout level or a likely reversal point? Equipped with this, the model can better judge if the current price action looks like a buy opportunity (e.g., price is low in range but showing strength), a sell signal (price is high and losing momentum), or something in between.
Volume-Based Features
Price is only half the story – volume tells us how many shares changed hands and thus the intensity of trading activity. Volume features give insight into the power behind price moves. A price rally on low volume might be suspect (few buyers actually participating), whereas a price rise on huge volume is more convincing. Here are the volume-centric features our strategy uses:
Volume Moving Averages & Ratios: Just like price, volume is tracked with moving averages. For example, a 20-day average volume (volume MA 20) smooths out daily volume to see the typical trading activity. The feature volume_ratio_20 is then the current day’s volume divided by that 20-day average. If volume_ratio_20 = 2, today traded at 2× the typical volume – a volume spike. If it’s 0.5, volume was only half of normal – a quiet day. We also have volume_trend_20, which compares shorter vs longer averages (like 20-day MA vs 40-day MA) to see if volume is generally trending up or down over time. Why useful? Volume spikes or droughts often coincide with key events. A sudden surge in volume could indicate unusual interest or news – possibly the start of a big move (if accompanied by price movement). For example, a breakout above resistance on 3× average volume is more likely to be “real” – strong hands are buying aggressively. Meanwhile, if volume is drying up while price drifts upward, it might be a sign the rally is losing support (few new buyers). By including volume ratios, the model can recognize when a day’s move is supported by broad participation or when it’s occurring in a vacuum. High volume relative to average can also signal capitulation (in selloffs) or exuberance (in rallies), both of which can precede turning points. In short, these features let the model weigh “how significant is this move?” from a participation standpoint.
Volume Rate of Change (Volume ROC): This measures the momentum of volume itself. For example, volume_roc_5 might be the percentage change in volume compared to 5 days ago. It tells us if volume is rapidly increasing or decreasing. Why useful? A sharp increase in volume (positive ROC) could precede or confirm a big price move – like more and more people jumping in (think of volume ROC like excitement building). A big negative ROC means today’s volume cratered compared to last week – perhaps interest is fading. Combining volume ROC with price trends, the model can differentiate between a gradually building rally vs. a sudden explosion of interest. For instance, if price has been flat but volume ROC spikes positive, it might suggest accumulation (smart money quietly buying, volume increasing even if price hasn’t moved much yet). Conversely, if price is still rising but volume ROC turns sharply negative (volume drying up), it may warn that the uptrend is running out of steam.
On-Balance Volume (OBV): OBV is a classic indicator that cumulatively adds volume on up days and subtracts volume on down days. It’s like keeping a running total of buying vs. selling pressure. If a stock closes higher, that day’s volume is considered “up-volume” and gets added to OBV; if it closes lower, the volume is “down-volume” and is subtracted. Over time, OBV goes up if more volume is on up days, down if down days dominate. Our features include the OBV itself and a 20-day average of OBV, and a signal flag if OBV is above its moving average. Why useful? OBV is used to confirm price trends. Ideally, in an uptrend, OBV should also be rising – it means volume is mostly flowing in on days when price rises, indicating accumulation. If price is climbing but OBV is flat or falling, that’s a bearish divergence – volume isn’t supporting the price move (fewer buyers, or stealth distribution by sellers). Similarly, if price is going down but OBV is rising, it could hint that smart money is buying quietly (bullish divergence). Traders often look for OBV to move before price – e.g., OBV making higher lows while price makes lower lows can foreshadow a rally. In our model, obv_divergence is calculated to capture such mismatches between price and OBV. This helps identify potential turning points where price might soon follow volume’s lead. In short, OBV features allow the ML model to weigh “are price moves backed by authentic volume pressure?” A rising OBV with rising price validates the trend, whereas if OBV lags or contradicts, caution is warranted.
Accumulation/Distribution (A/D) Line: The A/D line (developed by Marc Chaikin) is another volume-based indicator, but instead of simply using up or down days, it uses where the price closed within the day’s range to weight the volume. If a stock closes near its high, more of that day’s volume is considered “accumulation” (buying), and if it closes near the low, more volume counts as “distribution” (selling). The A/D line accumulates these daily values. We include A/D and a moving average of A/D, plus a signal flag if A/D is above its average. Why useful? The A/D line is similar in spirit to OBV, but more granular – it can detect buying or selling pressure even on days where the closing price change is small. For example, if a stock closes unchanged but had a big range and closed near the top, the OBV might register nothing (since close was flat), but A/D would capture that intraday buying. This feature helps the model catch subtle shifts in supply-demand. If the A/D line is rising even while price is flat, it suggests accumulation under the surface (bullish). If price is making new highs but the A/D line starts falling, it signals some distribution – perhaps the rally is being sold into by larger players. These nuances improve the model’s ability to sniff out false breakouts or stealth rallies.
Chaikin Money Flow (CMF): This is a related indicator that essentially normalizes the A/D line over a window, often 20 days. It’s typically the sum of A/D values over 20 days divided by the sum of volume over 20 days (or a similar concept). In our features, CMF is derived using a fast/slow volume oscillator (with periods 3 and 10). Why useful? CMF oscillates between positive (net buying pressure) and negative (net selling pressure). A strongly positive CMF means that on average over the last few weeks, the stock has been closing in the upper part of its range on higher volume – a sign of consistent accumulation. Negative CMF means distribution. Including CMF gives the model a short-term volume momentum indicator – essentially, are we in an accumulation phase or distribution phase recently? Traders might, for instance, view a rise in CMF above zero as confirming an uptrend’s strength.
Volume Price Trend (VPT): VPT is another cumulative indicator like OBV, but it scales the volume by the percentage price change. In formula terms, VPT_today = VPT_yesterday + Volume * (Today’s close % change). So if price jumps by 2% on high volume, VPT rises more than if price barely moves. Why useful? VPT basically combines price momentum and volume. It’s useful for confirming trend strength: an increasing VPT means price is rising on volume (good sign for bulls), and a falling VPT means price dropping on volume (bearish). The benefit of VPT is that a big price move carries more weight than a flat day, whereas OBV treats any up day equally. We include VPT and a 20-day average of VPT. If the current VPT is above its moving average, it suggests an upswing in the volume-weighted trend. Like OBV, divergences can be telling – if price hits a new high but VPT doesn’t, it might hint at weakening enthusiasm. By giving the model VPT data, we allow it to factor in the magnitude of price changes along with volume, which is great for spotting how robust a trend is. For example, in a strong rally, you expect not just price up-days, but big price moves on big volume – VPT will surge in that scenario, whereas a limp rally might see price creep up but VPT lagging (since percentage moves are small).
Money Flow Ratio / MFI: Here we calculate a “money flow” by multiplying typical price by volume for each day, and then form a money_flow_ratio = today’s money flow / 20-day average money flow. This is conceptually similar to the Money Flow Index (MFI), often called volume-weighted RSI. The MFI oscillator isn’t explicitly output here, but this ratio serves a similar purpose – telling if dollar volume is high or low relative to the norm. Why useful? Think of money flow as the total traded value (price × volume). High money_flow_ratio means a lot of cash changed hands today compared to usual – could indicate institutions are active (since they move large money). It can also indicate if a price move is supported by heavy trading or if it happened on thin trading. A sudden spike in money flow could precede breakouts or signal climactic action. For instance, extremely high money flow on a sharp price drop might indicate capitulation selling (everyone rushing for the exits). The model can learn patterns like “if money flow is off the charts, expect volatility or a reversal”.
Volume Concentration: This feature is essentially the coefficient of variation of volume (20-day std dev of volume divided by mean volume). It measures how erratic vs. steady the volume has been. A high value means volume swings wildly day to day (some days huge, some days tiny), while a low value means volume is consistent each day. Why useful? Very spiky volume patterns often occur around events or uncertainty – e.g., one day everyone trades like crazy, next few days quiet, then another surge. Steady volume suggests no big shocks, just routine trading. If volume concentration is high, it could indicate that the market is reacting to sporadic news or that big players come in bursts (possible manipulation or episodic interest). This context can help interpret other signals: for example, in a stock with very erratic volume, a single day of high volume might be less meaningful (it always spikes now and then) versus a stock with consistently low volume where a spike is a big deal. Thus, the model can adjust its confidence in volume-based signals by knowing the usual pattern. It’s similar to how you’d treat a loud noise differently if you’re in a quiet library vs. at a rock concert – context matters!
Price-Volume Correlation: We compute the rolling correlation between price and volume over the last ~20 days. This tells us if volume tends to go up when price goes up (positive correlation) or if they move opposite or unrelated. Why useful? In many cases, rising prices on rising volume is bullish confirmation (more traders want in as price increases). If our correlation is strongly positive, it means recently most up-days were accompanied by higher volume and down-days by lower volume – a healthy pattern for an uptrend. A negative correlation might indicate that high-volume days are often down days (could be a bearish sign that big selling happens on heavy volume, and up moves are on lighter volume – not great). Near-zero correlation means price moves and volume surges aren’t particularly in sync (volume might be driven by other factors like news, without a clear directional bias). By giving the model this “price-volume dance” metric, it gains a quick summary of market behavior: Is volume confirming price moves or contradicting them? For instance, if the correlation flipped from positive to negative, something might have changed in market sentiment – perhaps distribution started.
Volume Spike Indicator: We include a binary feature volume_spike that is 1 if today’s volume is more than 2 standard deviations above the 20-day mean (a statistically unusual spike). Why useful? A sudden volume explosion can precede large moves or reversals. For example, a volume spike on a breakout day could kick off a new trend leg upward. Conversely, a volume spike after a long rally – especially if the price candle shows indecision or reversal – could mark a blow-off top (exhaustion). By explicitly flagging “this day had an exceptionally high volume”, the model can treat such days with special attention. In human trading, people often say “volume speaks volumes” – an outsized volume day is rarely meaningless. So this feature is like a neon sign for the model to say “lots of traders showed up today, something might be afoot.”
Volume-Weighted Momentum (VW Momentum) and Change: This is an interesting feature that essentially calculates a volume-weighted average price over the last 10 days (summing price×volume over 10 days and dividing by sum of volume), and then looks at how that has changed over the past 5 days. It’s like a short-term momentum indicator that gives more weight to high-volume days. Why useful? Regular momentum (say 10-day price change) treats each day equally, but VW momentum says “if a big up move happened on a day with huge volume, that should count more in momentum than a similar move on a quiet day.” It’s a way to gauge true momentum by emphasizing moves that had conviction. The vw_momentum_change (percentage change in this VWAP momentum over 5 days) measures if volume-weighted price trend is accelerating or decelerating. For example, imagine a stock slowly drifting up, then in the last few days it jumps higher on big volume – the VW momentum will spike and the momentum change feature will be high, indicating an acceleration in upward pressure. On the flip side, if price is still up but only on low volume lately, the volume-weighted momentum might actually stagnate or drop, warning that the trend could be weakening. This feature is like combining the best of price trend and volume into one: a rising value strongly suggests a robust uptrend (price up + high volume), while a falling or low value suggests the trend lacks participation.
To sum up volume features: They add the dimension of “how many and how enthusiastically traders are trading at each price move.” Volume features help the model distinguish between a fragile rally (price up on weak volume – be skeptical) and a solid rally (price up on heavy volume – more credible). They identify accumulation vs. distribution phases (OBV, A/D, CMF), unusual activity (volume spikes), and gauge liquidity and interest over time. This is crucial because volume often precedes price – spikes in volume or shifts in volume trends can signal a move before price fully reflects it. By leveraging these, the ML model acts a bit like a tape reader of old, watching the “footprints” of big players to infer future price direction.
Volatility Features
Volatility features describe how much prices swing. Some days a stock barely moves; other days it’s all over the place. These features quantify that behavior – both realized volatility and indicators like bands and ranges. Volatility often correlates with risk and opportunity: a very volatile stock can yield big gains or losses in short periods, while a calm stock might require patience for any move. The model uses volatility metrics to adjust for risk and recognize regime changes (e.g., a normally steady stock becoming suddenly volatile could mean a breakout or breakdown).
ATR (Average True Range) and ATR %: ATR is a classic measure of daily volatility developed by Welles Wilder. It considers the true range of each day (accounting for gaps) and then averages it over a period (here 7, 14, 20, 30 days). In plain language, ATR tells us the average daily price move (in absolute terms). We include ATR in dollars, and also ATR% which is ATR / Price, expressing it relative to the stock’s price. Why useful? ATR basically answers “how much does this stock typically move in a day?” If a stock has ATR of $2 and price $100, it usually moves ~2% per day. If another has ATR $10 on $50 stock, that’s 20% swings – clearly much more volatile. The model uses ATR to gauge the risk/reward of moves. For example, if a stock with a $1 ATR suddenly moves $3 intraday, that’s an unusually large move (3× its norm) – possibly significant (breakout or panic). ATR % normalizes across stocks; a penny stock might have low absolute ATR but high ATR% (big percentage swings). Including ATR helps the model calibrate signals: a tight stop or small profit target might be fine for a low-ATR stock, but for a high-ATR stock, small moves are just noise. Additionally, ATR trending up indicates volatility increasing – something changed (perhaps news or a regime shift). Traders often expand their expected trading range in volatile times or tighten it in calm times. The model can do similarly, for instance being more cautious if ATR is spiking (volatility regime shift means potential for larger errors or bigger opportunities).
ATR Bands (Volatility Bands): Using ATR, we also create ATR upper and ATR lower bands around the current price (e.g., upper band = close + 2×ATR, lower = close – 2×ATR). These bands mark a range that price could be expected to stay in if volatility remains typical. Why useful? If the price shoots above the ATR upper band, that’s a more-than-2-ATR move – quite significant (beyond typical daily fluctuation). Similarly, dropping below the ATR lower band suggests an unusually large down move. Traders sometimes use ATR bands like a volatility breakout system – moving outside the band might indicate a strong trend day or a potential exhaustion point. For the model, these bands act as dynamic support/resistance lines based on volatility. For example, if price is near the ATR upper band, perhaps the model should be cautious about predicting further immediate upside (the move might have run its daily course unless it’s a truly exceptional day). Conversely, being near the ATR lower band after a quick drop might signal a short-term oversold condition (time to consider covering shorts or even a quick counter-trend trade). In essence, ATR bands contextualize price moves: is the current move big or small relative to what's normal?
Bollinger Bands (with width, position, squeeze): Bollinger Bands are another popular volatility tool. They consist of a middle line (usually a 20-day SMA) and upper/lower bands typically 2 standard deviations away from that average. We include bands for multiple periods (10, 20, 30 days) and different standard deviation multipliers (1.5, 2, 2.5). The features include the upper, middle, lower band values, the band width (upper – lower, normalized by middle) as a measure of volatility, and the band position which is (price – lower) / (upper – lower) – essentially where the price is between the bands (0 at lower band, 1 at upper band). Additionally, we have a Bollinger Squeeze indicator: the current band width divided by its own 50-day average width. Why useful? Bollinger Bands dynamically adjust to volatility: they widen during volatile periods and contract when the market is quiet. Traders interpret touches of the bands as potential overbought/oversold signals – e.g., when price hits the upper band, it’s relatively high/expensive and might revert, and vice versa for the lower band. The band position feature thus tells the model if the price is riding the upper band (indicative of a strong uptrend or possibly overbought if momentum fades) or hugging the lower band (downtrend or oversold). The band width reflects volatility – a narrow band means very low volatility (range-bound, coiled spring) whereas a wide band means high volatility (big swings). Bollinger Squeeze goes a step further: a value < 1 means the bands are narrower than usual, i.e., volatility is abnormally low – often a precursor to a volatility expansion or big move (the calm before the storm). Many breakout traders look for Bollinger Band squeezes as trade setups, expecting a big move once the stock “breaks out” of that tight range. Conversely, extremely wide bands might occur right after a big shock – often volatility tends to revert (very high volatility might soon settle down). By including these, the model can identify when a stock is trading quietly vs. turbulently and adjust strategy accordingly. For instance, if bands are very tight (squeeze) and suddenly price pokes above the band, that could be the start of a breakout rally. If bands are super wide and price is at an extreme, maybe a mean reversion is likely as volatility eases off. Overall, Bollinger features merge trend and volatility information and are a staple for detecting overbought/oversold conditions and pending volatility shifts.
Keltner Channels: These are similar to Bollinger Bands but use ATR for band distance instead of standard deviation. Typically, a Keltner channel is an EMA (say 20-day) with bands at e.g. 2×ATR above and below. We include Keltner channels for certain periods (10, 20) with multipliers like 1.5, 2, 2.5 ATR, and we calculate a Keltner position similar to Bollinger (where the price lies between the bands). Why useful? Keltner channels often give a smoother channel since ATR (based on high-low range) might respond differently than standard deviation. Traders use them akin to Bollinger – for identifying trend direction and overextended conditions. Some systems use Bollinger and Keltner together (e.g., if Bollinger bands go inside Keltner bands, it’s an extreme squeeze). For the model, Keltner features reinforce the volatility picture: if both Bollinger and Keltner say price is at an extreme, that’s a stronger signal of overbought/oversold. Keltner may also handle outlier days differently. Basically, these channels provide another lens on volatility-adjusted price levels. If price is consistently near the upper Keltner band, it means a strong uptrend with sustained volatility; dropping to lower band might mean a possible bounce zone in a trend. It’s redundant to an extent with Bollinger, but extra confirmation never hurts a cautious model.
Historical Volatility (Standard Deviation): Instead of ATR, which is absolute range, we also compute the statistical volatility: the standard deviation of daily returns, annualized (assuming ~252 trading days per year). We calculate this over different rolling windows (5, 10, 20, 30, 60 days). Why useful? This is basically how a finance textbook defines volatility – it tells what % move (std dev) the stock tends to have. A 20-day volatility of 20% (annualized) implies about a 1.25% daily standard deviation (because daily std ≈ 20%/√252). Historical vol is good for risk management and for regime detection: e.g., if 20-day vol jumps from 10% to 30%, the stock has moved from a low-vol to high-vol state – maybe due to earnings or market turmoil. We also include volatility of volatility (“vol of vol”) – essentially the standard deviation of that volatility measure over the same window. If vol itself is erratic, it means the market’s volatility is unstable (vol comes and goes unpredictably) – often a sign of shaky conditions (like uncertain markets around big news). Why would the model care? If a stock’s volatility is consistently low, certain strategies (like selling options or mean-reversion trades) might work well; if volatility is spiking or unstable, a trend-following or momentum approach might be more appropriate. Also, extremely low volatility often precedes big moves (like a volatility breakout) – the model might learn to be alert when volatility is too low (similar to the Bollinger squeeze concept but in pure stat terms). Conversely, extremely high volatility usually mean-reverts (can't stay crazy forever), so that might signal caution or contrarian opportunities.
Advanced Volatility Estimators (Parkinson, Garman-Klass, Rogers-Satchell, Yang-Zhang): These tongue-twister names are various ways quant analysts try to get a more accurate read on volatility by using more information than just daily closes.
Parkinson’s estimator uses the high-low range of each day in the formula, which generally provides a lower-volatility estimate than close-to-close (because it captures intraday swings more fully) and is more efficient assuming no trend.
Garman-Klass adds in open and close to the high-low info to account for overnight gaps, making it even more accurate if there is no drift.
Rogers-Satchell further allows for non-zero drift (trending markets) in the volatility calc by using both up and down movements within the dayportfoliooptimizer.io.
Yang-Zhang combines overnight volatility and Rogers-Satchell for multi-period estimation, handling both drift and jumps between daysportfoliooptimizer.io. In short, each successive method tries to refine volatility measurement for better accuracyportfoliooptimizer.io.
We include these as features (on a 20-day basis typically). Why useful? These measures may detect changes in volatility faster or more robustly. For instance, if a stock has wild intraday swings but always closes near where it opened, the close-to-close volatility might look low, but Parkinson’s estimator will flag high volatility due to large daily ranges. Conversely, if volatility mostly comes from overnight gaps (say news hits pre-market), Garman-Klass/Yang-Zhang will capture that better than standard deviation. By giving the model these extra eyes on volatility, it’s less likely to be fooled by the quirks of closing prices alone. This is helpful in strategies: e.g., if Garman-Klass volatility is much higher than close-to-close volatility, it implies a lot of overnight risk – the model might then be wary of holding positions overnight or put less weight on patterns that ignore overnight moves. Yang-Zhang being a composite is often considered a very accurate estimator of realized volatility, which could help the model in setting expectations for price range and in identifying volatility regimes more precisely (important for position sizing or knowing when to switch between trend-following vs. mean-reverting approaches).
Parkinson, G-K, R-S, Y-Z – what they imply for trading: Generally, rising volatility (by any measure) means uncertainty or new information in the market – which could break trends or start new ones. Very low volatility means complacency or equilibrium, often preceding a breakout or sharp move as that equilibrium gets disturbed. The model, by tracking these, can infer things like “this stock is currently in a quiet consolidation (low vol) – might be safe to range trade or expect a trend soon” or “this stock is going nuts (high vol) – maybe widen stops or be careful with mean reversion because it can overshoot”. Moreover, some features like volatility regime directly flag if volatility is high relative to a rolling median, effectively saying “we are in a high-vol regime now”. In the code, vol_regime was set to 1 if the current 20-day vol > 1.5× the median of the past 100 days – basically marking exceptionally volatile periods. The model can use that as a context flag (e.g., if vol_regime = 1, perhaps prefer strategies that thrive in chaos, or be cautious with leverage).
Volatility Regime Flags (in Regime features, more below): Additionally, later we define low_vol_regime and high_vol_regime by looking at where current volatility sits in its annual percentile. If in the bottom 30%, it’s a quiet market; if in top 30%, it’s a volatile one. We mention it here because it’s conceptually similar – these binary features help the model flip its mindset depending on volatility environment (like a driver switching between calm street driving vs. stormy weather driving mode).
In summary, volatility features teach the model about the stock’s “mood swings.” They help it know if a stock is playing nice and steady or throwing tantrums. This affects trading decisions greatly: for instance, a strategy might hold longer in low vol trends but cut losses quicker in high vol to avoid whiplash. Volatility indicators like Bollinger Bands also double as overbought/oversold detectors – e.g., price hitting an upper band might be due for a pullback if there’s no strong trend pushing it. Meanwhile, volatility squeeze alerts the model: “hey, things are unusually calm – watch out for a possible explosion.” Overall, these features enable the model to dynamically adapt to how wild or tame the market is at any given time, much like an experienced trader would.
Technical Indicator Features
Technical indicators are formulas that traders have developed over decades to glean insights from price and volume. Many are momentum oscillators or trend-following indicators. They often come with norms like “RSI above 70 is overbought” or “MACD crossover gives a buy signal”. In our strategy, we’ve included a rich set of these indicators. The goal is to give the model the same kind of signals a technical analyst might use to judge momentum, trend strength, and potential reversals. Here’s a breakdown of the major ones:
Relative Strength Index (RSI): RSI is a very popular momentum oscillator that measures the magnitude of recent gains vs losses over a period (commonly 14 days) and scales it to 0-100. We include RSI for multiple periods (7, 14, 21, 28 days). RSI values above 70 typically indicate an overbought condition, meaning the stock has risen quickly and may be due for a pullback, whereas values below 30 indicate oversold (the stock has fallen a lot and might bounce). We also have binary features like rsi_14_oversold (1 if RSI < 30) and rsi_14_overbought (1 if RSI > 70). Additionally, we calculate RSI divergence – comparing the trend of RSI vs price to spot when they diverge (e.g., price makes new highs but RSI makes a lower high is bearish divergence). Why useful? RSI is basically a speedometer of price changes. If a stock’s RSI is very high, it tells the model “this stock’s price has been rising extremely fast relative to its typical pattern” – which often precedes a cooling off or reversal, especially in range-bound markets. Likewise, very low RSI signals the sell-off may be overdone and buyers could step in. Traders often use RSI crossing certain thresholds as buy/sell signals (e.g., cross above 30 from below as a buy). Divergences are valuable too: an RSI divergence (price up but RSI down) can foreshadow a trend reversal – it indicates the rally is losing momentum internally. By giving RSI values and flags to the model, we allow it to incorporate these human-like judgments. For example, if everything looks bullish but RSI is flashing overbought > 80, the model might learn to be cautious going long, as historically that often led to at least a pause or dip.
Moving Average Convergence Divergence (MACD): MACD is another momentum/trend indicator, based on two EMAs (typically 12-day fast and 26-day slow) and their difference. The MACD line = fast EMA – slow EMA, and a signal line is a 9-day EMA of MACD. We included the standard (12,26,9) MACD and a couple of variations (like 5,35,5 and 8,17,9). For each, we have the MACD value, the signal line, the histogram (MACD minus signal), and a MACD cross flag that is 1 when MACD line crosses above signal (bullish crossover) and 0 when below (with a bit of logic to only flag the moment of crossing). We also compute MACD momentum which is basically the change in the histogram (second derivative of price, if you will). Why useful? MACD is a beloved tool for identifying trend changes. When the MACD line crosses above the signal line from below, it’s a classic buy signal; crossing below is a sell signalinvestopedia.com. This crossover indicates a shift in momentum – the short-term trend overtaking the long-term trend or vice versainvestopedia.com. By giving the model a MACD cross indicator, we’re highlighting these traditional signals. The MACD histogram itself shows the distance between MACD and signal – if it’s positive and growing, the uptrend’s momentum is increasing; if it’s shrinking, momentum is waning. Traders watch for the histogram to peak and then recede as an early warning of a crossover coming. The model can similarly learn that, for example, when MACD histogram starts declining while price is still rising, things might soon turn. The multiple MACD settings allow capturing different timeframes of momentum. The standard 12-26-9 is medium-term, 5-35-5 might capture longer swings, etc. In essence, MACD features tell the model about the trend’s momentum and potential turning points via a well-known, proven indicator set.
Stochastic Oscillator (%K, %D): The stochastic oscillator measures where the close is relative to the recent range (often 14 days) and outputs two lines: %K (fast value) and %D (a moving average of %K). It oscillates 0-100 like RSI. We have stochastics with typical settings (14,3), (21,5), and a faster one (5,3). We include the %K, %D, and flags for bullish crossovers (when %K crosses above %D) and overbought/oversold conditions (traditionally stochastic > 80 is overbought, < 20 oversold). Why useful? Stochastics, like RSI, signal momentum extremes. When both %K and %D are above 80, the stock has been closing near the high of its range consistently – strong, but also possibly due for a pullback (can’t close at the top forever). Below 20 is the opposite – consistently closing near lows, possibly ready to bounce. The crossing of %K and %D is akin to a mini MACD – %K is like a fast line, and when it crosses above the slower %D, it indicates momentum swinging up (often a buy signal), and crossing down is a sell signal. By feeding these into the model, it gets precise “trigger” signals similar to what a trader would look for. For instance, a common strategy is to buy when stochastic rises above 20 from below (signaling an end to an oversold condition). The model can integrate that with other context. If, say, stoch_14_3 oversold flag flips from 1 to 0 (meaning it left the oversold zone) and at the same time volume is high and maybe it’s near a support level – all that together can strongly indicate a bottom reversal. So stochastic features are valuable for short-term timing of entries and exits, especially in oscillating markets.
Williams %R: This is essentially the inverse of the stochastic %K (with a negative scale). It ranges from 0 to -100; values above -20 indicate overbought, below -80 oversold. We include Williams %R for periods like 10, 14, 20 days. Why useful? It’s another momentum oscillator giving redundant info to RSI/Stoch but some traders prefer it for certain sensitivity. In practice, %R < -80 means the stock has been in the lower portion of its range (oversold) – might soon reverse upwards; %R > -20 means it’s near range highs (overbought) – might dip soon. The model can use these thresholds similarly to RSI. Redundancy in indicators can also help the model because if multiple oscillators (RSI, Stoch, %R) all scream “overbought” concurrently, that’s a more reliable sign than any one alone.
Commodity Channel Index (CCI): CCI measures how far the price is from its statistical mean relative to mean deviation. It’s an unbounded oscillator (but typically ±100 are notable levels). We include CCI for various periods (14, 20, 30). CCI above +100 can indicate an uptrend is strong (or overbought), and below -100 a downtrend or oversold condition. Why useful? CCI is often used to identify trend strength and reversals. It’s called “Commodity” index but works on stocks too. Readings beyond ±100 suggest price is well above or below average – akin to overbought/oversold. Crossing those thresholds can be entry signals (e.g., CCI crossing below +100 after being above could be a sell). The model having CCI is like giving it another viewpoint on momentum vs. mean. It might catch some cases where RSI doesn’t, due to different smoothing. Overall, it’s extra confirmation for extreme conditions and can highlight strong trending periods (e.g., very high CCI can mean a very strong trend – one might actually ride it rather than counter-trade immediately).
Money Flow Index (MFI): We touched on MFI in volume, but here specifically we have MFI14 and MFI20 features. MFI is often called volume-weighted RSI – it gauges momentum of money flowing in or out, on a 0-100 scale like RSI, with >80 overbought and <20 oversold. Why useful? MFI adds volume context to momentum – e.g., an RSI might go high on small volume moves, but MFI will not go as high unless those moves had significant volume behind them. If MFI and RSI agree (both overbought), it’s a stronger signal that the move might reverse. If RSI is high but MFI isn’t, perhaps the price move lacked volume support. The model can learn to weigh that. For example, an overbought RSI with not-so-overbought MFI might not be as reliable a sell signal as when both align. So, MFI helps refine momentum readings by including buying/selling pressure strength.
ADX and DMI (Directional Movement Index): ADX (Average Directional Index) is an indicator of trend strength (scale 0-100) without regard to directioninvestopedia.com. The DMI part consists of +DI and -DI, which represent the strength of upward vs downward moves. We include ADX and plus_DI, minus_DI for periods 14 and 20, and from them di_diff (the difference) and trend_strength which is ADX times the sign of the DI difference. Why useful? ADX tells if a significant trend exists: typically, ADX above 20 or 25 means a trending market; below that means a sideways or weak-trend market. +DI and -DI tell the trend’s direction – if +DI is way above -DI, the trend is strongly up; if -DI >> +DI, the trend is strongly down. Traders often use ADX rising as confirmation to “trade with the trend”. We encode a combined trend_strength which will be positive for strong uptrends, negative for strong downtrends (because we multiply ADX by the sign of (DI+ – DI–)). This gives the model a single number to gauge both how strong and which way the trend is. ADX being low warns the model that breakouts might fail (no strong trend to carry them) and that mean reversion strategies work better. Conversely, high ADX tells the model to expect trends to persist (buy pullbacks instead of mean reversion). The DI lines also can indicate when a new trend might be starting (e.g., +DI crossing above -DI is a bullish sign, especially if ADX starts rising). We haven’t explicitly put a crossover flag for DI, but the model can infer it from DI values. Essentially, ADX/DMI features give the model a sense of the market structure: trending vs ranging and direction of dominance. For example, if ADX is very low and we get an oscillating signal like RSI oversold, that might be a good mean-reversion play (range-bound assumption). If ADX is high and rising, the model might give more weight to trend-following signals (like breakouts and momentum) and less to oversold/overbought (since in strong trends, oscillators can stay extreme – e.g., RSI can stay overbought for a long time in a rally, and that’s not a sell signal in a runaway trend).
Aroon Indicator: Aroon measures the time since the last high and last low over a period, outputting “Aroon Up” and “Aroon Down” as percentages (0-100). If Aroon Up is 100, the most recent high was 0 days ago (i.e., today made a new high); it slowly declines over time if no new high. Similarly for Aroon Down with lows. We include Aroon Up, Aroon Down for 14-day and 25-day, and an Aroon Oscillator (which is just Aroon Up minus Aroon Down). Why useful? Aroon is great for identifying emerging trends. For instance, if Aroon Up stays high (near 100) and Aroon Down is low, it means the stock is consistently hitting new highs and hasn’t hit a new low in a long time – a strong uptrend. The reverse is true for downtrends. If both are middling, the stock is likely stuck in a range without new extremes. Traders use Aroon Oscillator (difference) crossing above 0 as a bullish signal (trend shift to up) and below 0 as bearish. We haven’t given a binary, but the oscillator feature can serve that purpose (positive values bullish bias, negative bearish). By feeding Aroon data, we inform the model about trend changes in terms of highs and lows. One advantage of Aroon is when a stock transitions from range to trend, Aroon will flip convincingly (one of the lines will shoot to 100). The model could pick up that, for example, when Aroon Up goes from low to 100, it might be break-out time. It’s similar to ADX but more timing-oriented (ADX might take time to rise, but Aroon instantly shows when a new high or low is set).
Ultimate Oscillator: This is an oscillator that combines short, medium, and long-term price momentum (like 7, 14, 28-day weighted) into one value 0-100. We include it as ultimate_osc. Why useful? It was designed to avoid false divergences that single-period oscillators give. It might not be widely used as RSI or MACD, but it’s another read on momentum that could catch things those miss. For example, the Ultimate Oscillator might not drop as much during a quick fake-out dip, thus not giving a false oversold signal. The model can treat it as another confirmation tool – if multiple oscillators align, great; if one is off, maybe hedge bets.
Rate of Change (ROC): We have ROC for 5, 10, 20 days, which is essentially another way to express momentum – as the percentage change over that period. It’s basically the same info as returns, but some strategies use ROC as a momentum indicator (e.g., ROC crossing above 0 or making peaks). Why useful? It directly quantifies momentum in percent terms. The difference from RSI is that ROC can exceed those oscillator bounds – it’s just a raw change. Very high positive ROC indicates explosive short-term momentum (which could be unsustainable, or the start of something big), very negative ROC indicates a rapid drop (could bounce or continue if crash). The model already had returns, so ROC is somewhat duplicative, but it might incorporate it when combined with other things (like maybe comparing ROC to average or something, or using it for divergence detection with price).
Chande Momentum Oscillator (CMO): This is similar to RSI but scaled differently (range -100 to +100). We include CMO 14 and 20. Why useful? It’s basically a variation on RSI that some traders use to get a symmetrical oscillator (RSI is 0-100, CMO is -100 to +100, where 0 is neutral). That symmetry might help the model because changes above/below zero indicate trend of momentum. But largely, consider it another momentum gauge to corroborate signals.
Percentage Price Oscillator (PPO): PPO is like a MACD but in percentage terms (MACD default is absolute differences of EMAs, PPO divides by price or by EMA to normalize). We include a single PPO (likely 12/26 equivalent). Why useful? PPO has the advantage of comparability across prices – a $100 stock and a $10 stock can have similar PPO signals if their trends are proportionally the same, whereas MACD values differ in magnitude. PPO is great when comparing momentum across different stocks or different times. For our single-stock features, it’s not drastically different from MACD, but it ensures the model isn’t biased by price level on momentum. If, say, the stock’s price doubled, MACD values might be larger in absolute terms, but PPO would remain scale-invariant. So it’s a normalized momentum indicator, which might keep consistency of patterns for the model.
TRIX (Triple Exponential Average): TRIX is an indicator that shows the rate of change of a triple-smoothed EMA, effectively an oscillator that filters out short-term noise and tracks trend momentum. We include TRIX 14 and 20. Why useful? TRIX crosses above zero are buy signals, below zero are sell signals, often used like a smoother momentum indicator. It’s less whipsaw-prone. The model can use TRIX to identify sustained momentum shifts. For example, if short-term oscillators are jittery but TRIX has just turned positive from negative, it suggests a more reliable turn toward an uptrend (since it’s triple-smoothed, it likely won’t flip unless there’s a true trend change). TRIX might help reduce false alarms in the model’s decision-making by focusing on more significant momentum changes.
That’s a lot of technical indicators! 😅 Big picture: These indicator features are like giving the model an entire technical analyst’s toolkit. Each has its own way of highlighting where the market might be headed or if it's overextended.
The model doesn’t know the theory behind them, but it can find patterns like:
Confluence: e.g., when RSI < 30 and stochastic < 20 and price hit a support, that’s often a bottom – so the model might learn to predict buys in those cases. We even have specific interaction features for such confluences (like RSI-MACD agreement) which we'll discuss later.
Trend following vs mean reversion context: e.g., if ADX is high and rising, the model might learn that overbought RSI signals aren’t as reliable (because in strong trends, overbought can stay overbought, indicating strength rather than imminent reversal). Conversely, if ADX is low, those oscillator signals work great for mean reversion.
Early warnings: e.g., bullish divergences in RSI or OBV might precede a price uptick – the model could catch that and shift from predicting “sell” to “hold” or “buy” even before price confirms, essentially giving it predictive power.
By combining these indicators, the model has multiple “views” on momentum and trend. In practice, a human trader might eyeball all these and form an opinion; the model instead will statistically weight them. Perhaps it finds MACD crossovers are a strong signal only when confirmed by volume increase and an ADX rising. Or that selling after RSI > 80 works unless OBV is still climbing strongly (meaning the rally has volume support). The beauty is the model can figure out these nuanced rules from data.
Market Microstructure Features
Market microstructure features focus on the fine-grained details of price action within a day and market trading dynamics like liquidity and order flow. These are less about classical chart patterns and more about how prices move during the trading session, where they close relative to the day’s range, and how volume is distributed. They can reveal clues about intraday sentiment, strength of moves, and liquidity (how easy or hard it is to trade a stock without moving its price too much).
Spread Proxies (High-Low, Close-Open, etc.): We calculate a few ratios that represent daily “spreads”:
High-Low Spread (hl_spread): (High – Low) / Close. This is the day’s range as a fraction of the closing price. If this number is large, the stock had a wide range relative to its price (volatile intraday). If small, it was a tight day.
Close-Open Spread (co_spread): |Close – Open| / Close. Essentially the absolute intraday return in percent – how far the price moved from open to close.
Open-Close Spread (oc_spread): (Close – Open) / Open, which is actually the signed intraday return (positive if closed higher than opened).
Why useful? These spreads measure intraday volatility and direction. A big high-low spread (HL range) might indicate a lot of uncertainty or active trading – even if the stock closed flat, if it swung wildly intraday, that’s notable (maybe news came midday). Co_spread tells if the day was a trend day (close far from open) or more of a mean-reversion day (close near open). In candlestick terms, co_spread is like the size of the candle body relative to price, and hl_spread is the size of the full candle (body + wicks). Traders often interpret long wicks (large HL range but small CO difference) as indecision or rejection of prices (like a stock went high but couldn’t hold, leaving a long upper wick). For example, a very high hl_spread but small co_spread means the stock traveled far but ended up near where it started – possibly choppy or reversal day. Conversely, a large co_spread (especially if open-close spread sign is positive) means a strong trend day – open low, close high with little reversal. By giving these to the model, it can infer the character of each trading day beyond just open/close. If it sees consistently large HL spreads day after day, that suggests a volatile environment (even if closes aren’t changing much). If HL spreads suddenly shrink, perhaps volatility is calming. Also, if a day has an unusually large CO_spread (like 5% up from open to close), that’s a bullish marubozu-like day (no significant pullback) – which often shows strong momentum. The model can factor that in as a sign of strength (especially if paired with volume). Essentially, spread features help the model detect intraday momentum and volatility patterns that might influence next day moves or overall trend continuation/reversal.
Intraday Momentum & Volatility: We explicitly include intraday_momentum as (Close - Open) / Open (which is same as oc_spread, the intraday return) and intraday_volatility as (High - Low) / Open. These are similar to above but using open as reference for normalization. Why useful? These put intraday moves in percentage terms. Intraday momentum (close vs open) directly tells if the day was bullish or bearish. Intraday volatility (range % of open) tells how wild the day was relative to where it started. They reinforce the idea: e.g., a day can be volatile but end where it started (intraday momentum ~0 but intraday vol high). Or a day can be steady trending (intraday vol not huge, but intraday momentum large). The model can differentiate a trendy day vs a whipsaw day. This might help in predictions: after a strongly trending day (big intraday momentum) you might get momentum carryover (e.g., follow-through the next day if no reversal signal), whereas after a whipsaw day (big range, no net move), the market might be undecided and waiting for clarity.
Candlestick Shadows (Upper/Lower Shadow ratios): We compute upper_shadow = (High - max(Open, Close)) / Close and lower_shadow = (min(Open, Close) - Low) / Close. These represent the upper and lower wick sizes as fractions of price. Also shadow_ratio = upper_shadow / (lower_shadow + 1e-10) (essentially comparing which shadow is bigger). Why useful? Shadows (wicks) reflect intraday rejection. A long upper shadow means at some point the price was much higher, but by close it dropped – selling pressure pushed it down from the highs (bearish intraday reversal). A long lower shadow means price was much lower but buyers bid it back up (bullish intraday reversal). Traders often interpret a candle with a long upper wick and small body as a potential reversal down (bearish shooting star if after uptrend), and a long lower wick as a potential reversal up (bullish hammer if after downtrend). Our features quantitatively measure those. If upper_shadow is large and lower is tiny, shadow_ratio will be huge ( >>1 ), indicating likely bearish intraday action (price couldn’t sustain highs). If lower_shadow is large (shadow_ratio <<1), that’s bullish intraday action. When both shadows are large and body small (like a long-legged doji), it shows indecision and high volatility. By including these, the model can capture daily candlestick pattern sentiments. For example, if the model sees a long lower shadow day (maybe pin_bar_bull pattern which we have separately too), it might expect a bounce next day since buyers showed up strongly at lows. Or a series of days with long upper shadows might indicate an uptrend losing steam (sellers consistently sell into rallies). These subtle cues add to its understanding of who’s winning intraday – bulls or bears.
Volume at Extreme Prices: We create features that allocate volume to where the price closed relative to extremes:
volume_at_high = volume if close == high, else 0.
volume_at_low = volume if close == low, else 0.
volume_at_close = volume if close was closer to high than to low, else 0 (basically if it closed in the upper half of the day’s range, we allocate volume here).
These are somewhat artificial constructs to highlight end-of-day positioning on volume. Why useful? If a stock closes at its high of the day on huge volume, that’s a very bullish sign – it indicates a lot of shares exchanged right at the top (possibly aggressive buyers into the close). That momentum might carry to the next morning. Conversely, closing at the low on heavy volume is bearish (persistent selling pressure through the close). By isolating volume_at_high/low, the model can pick this up: e.g., if volume_at_high is equal to the day’s volume (meaning it closed at high) and that volume is far above average, that’s a strong bullish signal. volume_at_close approximates whether the close was in the upper half with significant volume – it’s a proxy for “strong close with good trading volume”. Traders often pay attention to closing price behavior, especially on high volume, as it reflects institutionals positioning before end of day (some say “amateurs open the market, pros close the market”). So these features let the model quantify a strong close or weak close in terms of volume, not just price.
Order Flow Imbalance (OFI): We calculate a proxy for order flow: (Close - Open) * Volume for each day. Essentially, this multiplies the day’s price change by volume – so up days with high volume give a big positive number (suggesting net buying aggression), and down days on high volume give a big negative number (net selling aggression). We then compute a 20-day moving average (ofi_ma), standard deviation (ofi_std), and a z-score (standardized value) of this OFI. Why useful? This metric tries to capture the net buying vs selling pressure each day. If a stock goes up on high volume, we assume more buy orders; down on high volume = more sell orders. By averaging, we get a sense of the prevailing order flow trend (positive values mean accumulation over last month, negative mean distribution). The OFI z-score highlights days that are unusually imbalance-heavy compared to recent norm. For example, an OFI_zscore > 2 might indicate an extreme burst of buying or selling pressure beyond what’s typical. Traders at times look at things like accumulation/distribution days (e.g., was there heavy-volume up days vs down days count). OFI quantifies it. For the model, consistent positive OFI values might precede price upticks (smart money accumulating), and consistent negative OFI might precede down moves (distribution). A sharp one-day extreme could mark capitulation or climax buying. Essentially, OFI features give the model a way to detect who’s in control – buyers or sellers – in a way that accounts for both price move and volume. It’s like a simplified way of reading the tape.
Noise Ratio: We define noise_ratio = hl_spread / (volatility_20d + 1e-10). Here hl_spread is daily range % and volatility_20d is the rolling volatility. This compares intraday range to typical volatility. If this ratio is high, it means the stock’s daily swings are big relative to what its overall volatility suggests. Why useful? A high noise ratio could imply a lot of intraday back-and-forth (noise) rather than a clear trend. It might also indicate illiquidity – maybe the stock moves a lot intraday but mean-reverts by close, which often happens in less liquid stocks or ones with conflicting forces. A low noise ratio means daily movement is in line with expectations or smoother. This feature can help the model decide if recent price action is chaotic vs orderly. For instance, if noise_ratio is surging, perhaps technical breakouts might fail (too noisy), so the model might be less confident in trend-following at that time. It’s a bit abstract, but likely it correlates with microstructure conditions (like how much slippage or volatility beyond fundamentals is present).
TWAP (Time-Weighted Average Price) and Price-to-TWAP: We calculate a proxy TWAP as simply (High + Low + Close) / 3 (which is actually the same as typical price used in VWAP). And price_to_twap = Close / TWAP. Why useful? TWAP is the average price across the day (if we assume linear time weight, the average of H,L,C is a rough estimate). If Close is above this average, it means the price tended to increase over the day (closed higher than the day’s midpoint) – bullish intraday bias. If below, the stock sagged later in the day. This is similar to close vs. median price. Big institutional orders often try to execute near the TWAP to not disturb the market too much. If a stock closes far above TWAP, it might mean late-day buying pressure. Price_to_TWAP is another way to quantify how strong the finish was: >1 means strong close, <1 weak close, but it accounts for the whole day’s range, not just open vs close. It’s somewhat redundant with close-to-high or intraday momentum, but adds nuance in case of mid-day swings. For example, a stock that dips then rips could close near high, but TWAP might be lower; price_to_TWAP will be >1 indicating an upward bias overall. Or if it spiked then faded, close might still be above open (so intraday momentum positive), but below TWAP (because it spent more time higher then came down) – indicating maybe distribution in afternoon. The model can glean such patterns.
Amihud Illiquidity: This is an economic measure of liquidity: abs(daily return) / Volume (we add +1 to volume to avoid divide by zero). Essentially, how much does price move per unit of volume. We also have a 20-day average of this (amihud_ma). If a small volume causes a big price move, illiquidity is high. If huge volume barely moves the price, illiquidity is low (very liquid). Why useful? Illiquid stocks behave differently – they have larger slippage, more erratic moves, and can be manipulated easier. If Amihud illiquidity is high and rising, it might mean the stock is getting less liquid (maybe traders left, or a big holder can move it easily). Very illiquid conditions could lead to more noise and false signals, which a model should be wary of. On the other hand, if liquidity suddenly improves (illiquidity drops), perhaps a new catalyst attracted more stable volume. The model might use this to modulate confidence – e.g., avoid aggressive bets in very illiquid conditions since indicators can be whipsawed by random trades. Also, illiquidity spikes often coincide with crises or panics: price moves a lot because not many shares trade (nobody wants to take the other side), which itself is informative of stress.
Kyle’s Lambda (price impact): We approximate Kyle’s lambda as |Close-to-close return| / (|Signed volume| + 1), where signed volume is volume * sign of price change (so positive on up days, negative on down days). This measures price impact per volume – similar spirit to Amihud but focusing on net order flow (if a lot of volume was buyer-initiated, how much did price move up?). Why useful? A high lambda means price moved a lot even when adjusting for volume – which could indicate that large trades have big impact (illiquid market or informed trading). If lambda is low, the market absorbed volume with little price change (very liquid or many participants). In practice, if we see lambda increasing, perhaps fewer participants are providing liquidity (market depth worsened). Alternatively, high lambda could indicate informed trading – e.g., someone is pushing price up with relatively not huge volume (maybe insiders or smart money). It's admittedly advanced, but the model could correlate changes in lambda with subsequent volatility or returns. For example, a sudden spike in lambda could precede a breakout (someone was able to move price a lot with some volume – maybe a signal that once more volume comes, price will really run). Or it could warn “be careful, market is thin.” In either case, it's giving insight into market efficiency – how easily price moves.
Collectively, microstructure features let the model peek under the hood of daily price action and liquidity. They answer questions like:
Was today’s move sustained throughout or did it reverse intraday? (shadows, intraday momentum)
Did the day end with a bang (close at high on volume) or a whimper? (volume_at_high/low, price vs TWAP)
Are big players buying or selling aggressively? (order flow imbalance and its average/z-score)
Is the trading environment noisy or clear, liquid or thin? (noise ratio, Amihud, lambda)
These are things seasoned day traders notice (“Lots of wicks lately, the market’s undecided” or “This stock trades very thin, careful with stops”). By encoding them, the ML model can factor in such context. For example, it might learn that its usual breakout signal is more trustworthy when volume_at_high is high (meaning strong close) and OFI was positive (net buying pressure), but if volume_at_high is zero and there's a long upper shadow (meaning it sold off from the highs), maybe fade that signal. Or it might learn that in highly illiquid conditions (Amihud high, high lambda), it should expect more variance and perhaps demand a bigger expected return to take a trade.
In essence, microstructure features sharpen the model’s insight into short-term price behavior and market quality – key for timing trades and avoiding traps like false breakouts or getting stuck in illiquid positions.
Statistical Features
These features use statistical properties of the price series – like distribution shape, correlations, and randomness. They might sound academic, but they can give practical insights, such as “are returns normal or full of outliers?”, “is there a cycle or autocorrelation we can exploit?”, “is the price path mean-reverting or trending in nature?”, etc. They help the model detect subtle patterns or regimes that aren’t obvious from raw prices.
Higher Moments (Skewness & Kurtosis of Returns): We calculate the skewness and kurtosis of daily returns over rolling windows (5, 10, 20, 50 days).
Skewness measures asymmetry of return distribution. Positive skew means more big up moves than down moves (a long right tail), negative skew means more big drops (long left tail).
Kurtosis measures “tailedness” – high kurtosis means more extreme outliers than a normal distribution (fat tails), low kurtosis means fewer outliers (thin tails).
We also compute the Jarque-Bera statistic which combines skew and kurtosis to test for normality (JB high if distribution is far from normal – heavy tails or skewed). Why useful? These tell us about the nature of recent price movements. For example, a strongly negative skew over 20 days indicates that while the stock might have inched up, it had a nasty downward shock in that period – implying fragility or risk of downside. Positive skew could mean most days are mild but there were a few big up days (maybe news-driven jumps). High kurtosis means price had some very abnormal moves (perhaps a big gap or crash). Traders might avoid strategies that assume normality during those times. For the model, skew/kurtosis could signal when to be cautious: e.g., if returns distribution is showing fat tails and skewness, perhaps it’s a turbulent period (say around earnings or market crashes) where normal trends break down. A high JB stat means “returns are not normal lately” – often the case in crises. The model could learn that certain patterns don’t hold in those times. Conversely, low kurtosis and near-zero skew might indicate a nice stable market with more predictable moves (maybe good for mean reversion strategies, since fewer outliers). In short, these features measure risk of extreme events and bias in direction, helping the model adjust its expectations of likely outcomes (like maybe widen stops or lower position size if kurtosis is high – though it would learn that implicitly by seeing outcomes).
Autocorrelation (Serial Correlation of Returns): We compute rolling autocorrelation of daily returns at various lags (lag 1, 2, 5, 10, 20) over a window (50-day rolling). This basically checks if returns have memory: e.g., lag 1 autocorr positive means if the stock was up yesterday, it's slightly more likely to be up today (momentum); negative means it tends to mean-revert day to day (up yesterday, likely down today). Why useful? If short-term autocorrelation is present, that’s an opportunity: positive autocorr suggests a momentum strategy could work (ride the short-term trend), negative suggests mean reversion strategy (buy after a down day, sell after an up day). Markets often oscillate between these behaviors. By giving these features, the model can detect and adapt to that. For instance, many stocks have slight negative autocorr at lag 1 (maybe due to market making), but during a breakout phase they might develop positive autocorr as each day’s gain begets more gains (trend). If autocorr_1 goes from negative to positive, it might indicate a regime change to trending. Autocorr at longer lags could reveal cyclic patterns (maybe a weekly cycle causing a 5-day lag correlation, etc.). This is quite technical, but essentially it’s formalizing “does this stock have a tendency to continue or reverse its recent moves?” – something traders try to gauge intuitively. The model can use this to weigh recent signals accordingly.
Hurst Exponent: We compute Hurst for 20, 50, 100-day windows. Hurst exponent is a measure of long-term memory of time series:
H ~ 0.5 indicates a random walk (no memory),
H > 0.5 indicates persistence (trends – past direction likely to continue),
H < 0.5 indicates anti-persistence (mean reversion – past direction likely to reverse).
Why useful? It gives a sense if the market is trending or mean-reverting in character over that window. A high H (~0.8) suggests a strong trending behavior (one could have made money following the trend in that period). Low H (~0.3) suggests choppy mean reversion; if you tried to trend follow, you’d get whipsawed, but contrarian trades would work. So feeding Hurst in lets the model gauge “are we in a trending phase or ranging phase?” and choose its signals appropriately. It’s somewhat like ADX but derived differently. Hurst can capture fractal long-term structure in a way ADX might not for shorter windows. If Hurst100 is high, the last 100 days have had a clear directionality bias. If Hurst is ~0.5, maybe nothing systematic – randomness dominates, so don’t rely too heavily on momentum or reversal, maybe lighten up.
Entropy of Returns: We compute an approximate Shannon entropy of the distribution of returns over 20 and 50-day windows. This is done by histogramming returns and computing -Σ p * log(p). High entropy means the returns are broadly spread (no single outcome is much more likely – effectively more randomness/unpredictability), low entropy means returns are clustered (predictable or low variety of movement). Why useful? If entropy is low, it could mean the stock is stuck in a tight range or repeating a pattern often – which might be exploitable (predictable). If entropy is high, returns are all over the place (one day big up, next day modest down, then huge down, etc., with no clear pattern frequency) – harder to predict. In essence, entropy is a measure of uncertainty/chaos in returns. A strategy might hold off when entropy is extremely high, as it implies randomness (maybe news-driven volatility making technical signals less effective). If entropy drops, perhaps the market found a direction or rhythm. For example, a steadily trending market might actually have lower entropy of returns (because most days are modestly up, which is quite predictable) than a volatile seesaw market where anything can happen any day (high entropy). So the model might incorporate that: like if 20-day entropy is in top decile, maybe ignore some weaker signals because randomness is dominating. This is a more esoteric feature but interesting – it tries to quantify how much “information” (uncertainty) is in recent price moves. Low entropy could correspond to more structure (like identifiable pattern or lower volatility), high entropy to structureless moves.
Efficiency Ratio (Kaufman’s): We calculate an efficiency ratio for periods 10 and 20, defined as (|Price_change_over_period|) / (sum of absolute daily changes over period). This ranges 0 to 1. If the price went straight up with no volatility, efficiency = 1 (net change equals total path change). If price zig-zagged a lot but ended near where it started, efficiency is low (net change small, but lots of movement). Why useful? This indicator (by Perry Kaufman) essentially tells if the market’s movement has been trending (efficient) or choppy (inefficient). A high efficiency (say >0.6 or 0.7) means a strong trend with little noise – trend-following strategies shine. A low efficiency (near 0) means mean reversion heaven – price moved a lot but mean-reverted overall. We even use this in regime features to label trending vs ranging market. The model can directly use efficiency: e.g., if efficiency20 is low, expect range-bound behavior – don’t chase breakouts, maybe fade extremes; if high, stick with the trend because it’s been paying off. It’s similar to Hurst exponent but on a shorter scale and simpler. Efficiency ratio basically confirms trend quality. If a stock went up 5% in 20 days but every day was volatile, efficiency might be low – less confidence in trend continuation. If it went up 5% with very steady climbs each day, efficiency high – likely a stable trend.
Z-Score of Price: We compute a Z-score for price relative to a moving average: (Close - MA) / std_dev for 20 and 50-day windows. This tells how many standard deviations the current price is above or below its recent mean. Why useful? It’s a normalized measure of overextension. Price 2+ standard deviations above the mean is quite high – similar to Bollinger band concept (being at upper band is roughly +2 std dev). It can signal overbought (if no new info to justify it) or a breakout. Conversely, a deeply negative Z-score means price is far below trend – perhaps oversold or breaking down. The model can use this raw stat along with Bollinger and others for confluence. One advantage is Z-score is continuous and not bounded to [0,100] like RSI, so extreme outliers are more evident. For example, a crash could give a -4 Z-score, which RSI might just show as, say, 10 (which is already extreme, but -4 std dev indicates a rare event). The model might learn that beyond a certain Z (like >+3), mean reversion almost always happened (unless something fundamental changed), so it might take contrarian stance. Or that a modest Z (like +1) isn’t enough to act alone – could just be an uptrend. Essentially, Z-score feature is a straightforward way to measure how unusual the current price level is relative to recent history.
Percentile Rank of Price: We compute, for 20, 50, 100-day windows, the percentile of current price within that window’s range (0 to 1). This is very much like the price_channel_pos we discussed – it’s another way of saying if price is at multi-week highs (rank ~1) or lows (rank ~0). Why useful? It gives a quick sense of where we are in the distribution of recent prices. If the rank is 1 (100%), the stock is making new highs relative to last N days – bullish breakout sign. If rank is 0, making new lows – bearish breakdown sign. If intermediate, it’s in the range. The model can use this for breakout detection or as an input for mean reversion decisions (like if percentile is extremely high but some indicators show weakness, maybe time to short, expecting it to fall back into range). It’s similar to support/resistance distance, but normalized. It’s also like a simplified momentum indicator (if percentile has been rising from 0 to 1, obviously price has trended up strongly). Overall, it’s an intuitive feature: “how close are we to the highest high in the last X days?” – which is often key info in technical trading.
Mean Reversion Score: We define something like mean_reversion_score_period = -|Close - MA_period| / MA_period. This is essentially negative of the relative distance from the moving average – so it’s highest (least negative) when price is right at the MA, and more negative as price deviates up or down. It’s basically a punishment for being far from the mean. Why useful? It directly encodes the idea that the further price is from its mean, the more likely it is to revert (assuming mean reversion behavior). A very negative value indicates a strong deviation. The model might use this as a contrarian signal – the more negative it is, the more attractive the opposite side of the trade might be (because historically price tends to snap back towards its mean). This feature is inherently assuming mean reversion, so the model will naturally learn when that assumption holds (maybe in low efficiency times or range markets) and when to ignore it (trending times – where staying far from the mean just indicates a strong trend). It’s like a continuously scaled version of “overbought/oversold relative to MA”.
Trend Consistency: For 10 and 20-day spans, we compute the proportion of days that were up (close > previous close). If out of 20 days, 15 were up days, trend_consistency_20 = 0.75 (75%). If it’s 0.5, equal ups and downs, if 0 or 1 then all days were down or up respectively. Why useful? It’s a simple measure of how steady the trend is. Sometimes a stock can gain 0% net over 20 days, but do so with lots of up and down days alternating – consistency would be low. Other times, a stock might grind up a little each day (high consistency) even if net change isn’t huge, and that shows a certain persistent buying interest. Extremely high values (like 1.0 or 0.9) mean almost every day has been in one direction – could indicate either a strong trend that might continue (momentum) or maybe overdone and due for a flip (because that’s unsustainable forever). But generally, a high consistency with positive direction implies a strong bullish environment (even dips intraday get bought, as most days still close up), whereas low consistency (like ~0.5 or oscillating around) implies no clear directional conviction. The model can combine this with ADX: e.g., high ADX and high consistency likely a robust trend (rare combination except in strong rallies), high ADX but lower consistency might mean big trend days interspersed with small pullbacks (volatility but directional). Or low ADX and consistency ~0.5 means a true sideways market (half up, half down). So trend_consistency is a straightforward “how one-sided has trading been lately?”
All these statistical features might be things a discretionary trader doesn’t directly think about, but they capture the flavor of the price series. For example:
If returns have a high negative skew and high kurtosis, the model knows the downside tail risk is high (maybe waiting for confirmation before buying dips).
If autocorrelation is significantly positive at lag 1 and 2, the model senses a short-term momentum that it might ride.
If Hurst ~0.7 and efficiency ~0.8, clearly a trending period – “go with the flow” mindset.
If entropy is low and mean reversion score is not too negative, maybe it’s a calm market where reversion trades work.
If percentile rank is 1 and efficiency is low (meaning it got to high but in a choppy way), perhaps a false breakout risk.
The model doesn’t reason like that explicitly, but it will detect patterns. Essentially, statistical features allow it to detect market regimes (trending vs ranging, normal vs turbulent, predictable vs erratic). They also allow it to measure how unusual current conditions are (like heavy tails or extremes) which might influence risk management.
Pattern Recognition Features
These are features that recognize specific candlestick patterns and price action patterns that traders often use to predict reversals or continuations. Instead of just giving raw data, we pre-encoded certain patterns (like “hammer” or “engulfing”) which have known implications, as well as some custom patterns. The model can use these as hints akin to “pattern signals” that a human trader might look for on a chart.
Candlestick Patterns (TA-Lib): We utilized a library that identifies classic Japanese candlestick patterns based on Open-High-Low-Close of each day. Some patterns included:
Doji (open ~ close): suggests indecision.
Hammer & Hanging Man: Hammer (bullish reversal if found after a down move) has a long lower wick and small body at top; Hanging Man is the same shape but after an uptrend (bearish).
Engulfing: A two-day pattern; bullish engulfing (after downtrend) is a small red candle followed by a larger green candle that engulfs it – indicates a reversal up. Bearish engulfing is opposite.
Harami (& Harami Cross): A small candle contained within previous candle’s body (harami means pregnant in Japanese) – can be a sign of a pause/reversal. Harami Cross is when that small candle is a doji.
Morning Star / Evening Star: Three-day patterns. Morning star = downtrend candle (big red), then a small candle (gap down), then a big green closing above midpoint of first – a bullish reversal pattern. Evening star is the bearish counterpart.
Three White Soldiers / Three Black Crows: 3 consecutive long bullish candles (soldiers) or bearish candles (crows) – indicates strong continuation or exhaustion (depending on context).
Spinning Top: small body, long wicks – indecision.
Shooting Star: small body near low, very long upper wick (looks like an inverted hammer at top of trend) – bearish reversal signal.
Marubozu: long body, no wicks (closed at extreme) – indicates strong momentum day.
Abandoned Baby: a doji gapping isolated between two opposite candles – a rare strong reversal signal.
Breakaway: a pattern of a trend ending and reversing (involves a gap sequence).
3 Inside / 3 Outside: more complex multi-day patterns similar to harami and engulfing confirmations.
We normalized the outputs to -1, 0, 1 (where applicable), though I believe in code they ended up as 0, ±1 for occurrence. We also compute pattern_strength for each pattern, which is basically how many periods since it last occurred (if at all). Actually they did a forward-fill trick to see the last occurrence index difference, so pattern_strength might give something like a count of bars since last pattern or 0 if pattern on that day. Lower is “just happened” (strong), higher means it’s been a while.
Why useful? Candlestick patterns encapsulate common emotional turning points in the market:
A hammer after a decline means sellers pushed it way down but by close buyers brought it back up near the open – potential capitulation and buying interest.
A shooting star after a rally means buyers drove it up but then got overwhelmed by sellers who knocked it down – potential top.
Engulfing patterns show a sudden shift in control (bullish engulfing: from sellers one day to strong buying the next that engulfed prior range – often kicks off a reversal up).
Multiple candlestick patterns like morning star give high confidence reversal signals with a narrative (panic selling, then indecision, then strong buying).
By directly giving these pattern indicators, we make it easier for the model to spot these inflection points that traders have found useful for ages. The model might learn, for example, that a bullish engulfing (cdl_engulfing = +1) leads to positive returns in next few days especially if confirmed by volume increase. Or that three black crows (three big down days in a row) often precede a bounce (exhaustion of sellers) unless macro factors are bad. Pattern_strength could be used if the model wants to gauge “was there a recent signal of this type that’s still relevant?” Although pattern occurrence might be mostly 0s and occasionally a ±1 for those days, pattern_strength counting from last occurrence is a bit peculiar but could tell how long since that signal, maybe to avoid double counting or to see persistence.
We basically gave the model a dictionary of known bullish and bearish patterns (like hammer, morning star as bullish; hanging man, shooting star as bearish; doji as neutral indecision). It can use those as binary flags or continuous signals. It’s like teaching it some of the language of candlesticks rather than expecting it to discover these on its own from raw OHLC, which might be harder.
Custom Patterns:
Pin Bar (Bull/Bear): We defined a bullish pin bar as having a very long lower shadow, small body, and closing higher than open (or at least bullish body). A bearish pin bar vice versa with long upper shadow and closing down. Pin bars are basically hammers or shooting stars with stricter criteria. We flag these as pin_bar_bull or pin_bar_bear = 1 when they occur. These often signal reversals (bullish pin bar suggests a likely upward reversal next day, bearish pin bar suggests down).
Inside Bar: We check if today’s high < yesterday’s high AND today’s low > yesterday’s low. That’s an inside day (range completely inside prior day’s range). It indicates consolidation/tight range, often preceding a breakout as volatility contracts.
Outside Bar: Today’s high > yesterday’s high AND low < yesterday’s low. That’s a wide-range day that engulfed prior range (not necessarily the same as engulfing candlestick which is about open/close). An outside bar signals volatility and possibly a turning point (depending on where it closes).
Consecutive Up/Down: We count sequences: features consecutive_up_3, consecutive_up_5 are true if 3 (or 5) days in a row closes > open (bullish days). Similarly for consecutive_down. These indicate short-term streaks. A 5-day winning streak might mean overextension (some traders fade it expecting a down day next) or just strong momentum. It’s a noteworthy event nonetheless.
Pattern Combinations: We have reversal_pattern which is true if any of a set of bullish reversal patterns triggered (hammer, morning star, or pin_bar_bull), and continuation_pattern if some continuation patterns triggered (three white soldiers or marubozu). So these condense multiple signals into one – basically flagging “some reversal sign happened” or “a strong continuation sign happened”. This is helpful if individually patterns are rare; grouping them gives a slightly more frequent signal.
Why useful? These custom ones cover scenarios not fully captured by TA-Lib or where we want simpler conditions:
Pin bars are one of the favorite price action signals for reversals – a very prominent wick shows rejection of a level. Including them explicitly means the model can quickly react to such bars without needing to weigh upper/lower shadow values manually.
Inside bars often indicate a volatility contraction; some strategies wait for a breakout of the inside bar range to trade. The model might learn that after an inside bar, the next move tends to be a bit larger or may break the previous day’s high/low.
Outside bars can indicate a trend change or stop-run (took out both sides’ stops). For example, a bullish outside bar (higher high and lower low but closing up) can flush out weak hands and then reverse strongly.
Consecutive ups/downs gauge momentum or exhaustion. Five up days could either mean trend is strong or due for a pullback (depending on context). The model will see historically what happened after such streaks. Often, many consecutive up days might lead to a short-term pause because eventually buyers dry up. Or conversely, it might indicate a strong trend that usually continues until a certain news hits.
Reversal_pattern flag simplifies things like “did we see any bullish reversal candlestick recently?” If yes, maybe the model will factor that bullishly for a few days.
Continuation_pattern flag similarly points out strong trend continuation signals (like three white soldiers often means trend will likely continue – lots of buying interest over multiple days).
These features basically inject some trading heuristics into the model: e.g., “if you see a hammer or morning star, treat it as a reversal signal.” The model doesn’t have to follow it blindly, but it’s given that info and can validate it against outcomes. If certain patterns have high predictive value, it will weight them accordingly. If not, it might ignore them. But generally, these patterns encapsulate crowd psychology turning points (fear to greed flips and vice versa), which are valuable to catch.
Normalization of Pattern Signals: We normalized the TA-Lib pattern outputs by dividing by 100, since they come as 100 or -100 typically. So in our data, a pattern feature (like cdl_hammer) would be 1, -1, or 0 indicating bullish, bearish, or no pattern. This makes it straightforward for the model to use in linear combos. It doesn’t need to guess that an unusual combination of OHLC means a hammer; we already label it.
To give an example of how the model might use these: Suppose the stock has been in a downtrend and then one day prints a morning star pattern with heavy volume and a bullish pin bar. Our features would flag cdl_morning_star = 1, pin_bar_bull = 1, reversal_pattern = 1, volume_spike maybe = 1, etc. The model sees this cluster and, if historically such cluster often preceded a multi-day rally, it will likely predict a rise (or at least stop predicting further decline). Human traders would see that as a textbook bottom signal. So effectively, pattern features allow the ML to see the chart somewhat like a human technical analyst does, recognizing familiar shapes and sequences.
In summary, pattern recognition features are the model’s way of saying, “hey, this day’s candlestick arrangement resembles a scenario that often leads to X.” Whether it's a one-day pattern or multi-day, these features are like little signposts in the data. They enrich the model’s input beyond raw numbers, giving it access to higher-level events (patterns) that otherwise it might need many data points to infer. By explicitly coding them, we speed up learning of those relationships. And because we include a broad array of patterns, the model can weigh which ones truly matter for this stock (not all patterns are equally reliable in all markets). Some might be noise, but others gold – the ML will figure that out.
Interaction Features
Markets often move based on a confluence of factors – for instance, a technical indicator signal is more meaningful if volume confirms it, or a support level breach is critical if momentum also flips. Interaction features explicitly capture some of these combinations of basic features that traders might look at together. Instead of hoping the model learns the multiplication/relationship on its own, we directly include these composite signals.
Price-Volume Interaction (Volume Momentum): We create volume_momentum = volume_ratio_20 return_5d. This multiplies a volume indicator (>1 means above-average volume) by a short-term price return. The idea is to highlight instances where price has moved and volume was also high. Why useful? A positive value (high volume ratio * positive return) means the stock had above-normal volume and price gain over 5 days – a strong bullish combo (or vice versa for negative). If volume_ratio is <1 (below average volume) or return is negative, the product will be lower. Essentially, it ups the signal when volume and momentum align. The model could use this as a “confidence” factor: a rally with low volume (volume_ratio<1) yields low volume_momentum, meaning maybe a weaker rally vs. a rally on high volume (gives high volume_momentum). We also have volume_trend_alignment which is a binary that is 1 if volume_ratio_20 > 1 AND return_5d > 0 (meaning volume above average and price has risen). It’s like a flag for “volume confirms this recent upward move.” Similarly, it would be 0 (or could be negative if coded differently) when volume was above average but return negative (which could indicate distribution – heavy volume drop) or when volume was weak on an up move (less convincing trend). Why useful? Traders love to see volume confirming price. This flag directly captures a basic rule: if you’re seeing a rise on rising volume, that’s bullish alignment; if not, caution. The model with this feature might, for instance, be more willing to forecast an uptrend continuation if volume_trend_alignment = 1 (because historically up moves that had good volume follow-through often extended).
RSI-MACD Interaction: We have features combining RSI(14) and MACD(12,26,9) histogram:
rsi_macd_confluence = 1 if RSI > 50 and MACD histogram > 0, or RSI < 50 and MACD hist < 0. Essentially, it’s 1 when RSI and MACD agree on bullish or bearish. If RSI is above midline 50 (which suggests bullish momentum) and MACD hist is positive (MACD above signal, bullish momentum) – both are bullish, so confluence = 1. Or if both indicate bearish, we also set it to 1 (in code they OR the two matching bearish conditions and cast to int). Actually, likely they keep it 1 for both cases meaning "they are in sync," and 0 otherwise.
rsi_macd_divergence = 1 if RSI > 70 and MACD hist < 0 (overbought RSI but MACD momentum down) or RSI < 30 and MACD hist > 0 (oversold but MACD momentum up). That flags a mismatch – an indicator that a reversal might be due (this is a different kind of “divergence” than price vs indicator, it’s indicator vs indicator).
Why useful? RSI and MACD are among the most widely used pair of indicators, and traders often use them together. When they agree (both bullish or both bearish), a signal is stronger and false signals fewer. Our confluence feature =1 means the momentum oscillators are painting the same picture – likely a reliable trend direction at the moment. When they disagree – especially in the extreme sense (divergence feature) – it might spell trouble: e.g., RSI is saying “overbought” but MACD histogram below 0 indicates underlying trend weakness; that contradiction can precede downturns (one of them is lying, usually the truth is the bearish side in that scenario). By explicitly giving these interactions, the model can easily incorporate rules like “only go long if momentum indicators align bullishly” or “be cautious / look for reversal when one says overbought and other shows weakening momentum.” It doesn’t have to infer the combination weight – we already multiplied or logically combined them. In effect, we give it a pre-made signal often used in technical strategy: an RSI/MACD double-cross system (some traders wait for both to confirm a move).
Volatility-Volume Interaction: high_vol_high_volume = 1 if current volatility (20-day) is above its 50-day median AND volume_ratio_20 > 1.5 (volume well above normal). This flags a regime of “high volatility + high volume.” Why useful? Such conditions often occur during big news events or market panics/euphoria – basically, very active and volatile market. It can mean a potential climax or transition. For example, in a selling climax, volatility and volume both spike (huge swings, huge volume) – often marking a capitulation bottom. In a buying frenzy, similarly big volume and volatility. Or it could just be a break from a quiet period due to a major catalyst. The model might learn that during these spikes, normal patterns either break (need to be careful) or that these often coincide with turning points (maybe if combined with oversold signals, it’s a strong buy after a panic). At the very least, it’s a context flag: “we’re in a very energetic market phase.” The model could decide to widen prediction ranges or to focus on mean reversion if it thinks it’s capitulation. Without this feature, it might see pieces of it (like rising vol and rising volume separately), but combining them ensures it notes the special scenario when both are unusually high. That’s typically when big moves happen (the model might become more alert to trend changes or continuation under such circumstances).
Support/Resistance with RSI: We include:
resistance_overbought = 1 if price is within 2% of a 20-day resistance (recent high) and RSI14 > 70. Meaning the stock is right near a known resistance level and is also overbought.
support_oversold = 1 if within 2% of 20-day support and RSI14 < 30. Price near a support level while oversold.
Why useful? This is classic trading logic: Overbought at resistance often leads to a pullback – because not only is the stock overextended (high RSI) but it’s also hitting a ceiling where sellers previously emerged. Combining them increases the chance of a reversal. Similarly, oversold at support is a prime spot for a bounce – price is beaten down (low RSI) but at a floor where buyers historically stepped in. Many traders specifically screen for these combos as high-probability setups. By giving this to the model, we allow it to recognize those scenarios explicitly. It might learn that when resistance_overbought = 1, the next few days’ returns are often negative – so it might predict a drop or at least avoid longs. Conversely, support_oversold = 1 often precedes a bounce, so it might predict upward price action. Without this interaction, the model could try to deduce it from individual features (dist_from_resistance small and RSI high), but doing a logical AND likely helps catch the non-linear effect (the combination might be way more significant than either alone). It encodes a human-like rule directly.
Moving Average Interactions (Alignment & Compression): We create:
ma_alignment_score: We take all simple MA features (like sma_5, sma_10, ..., sma_200), sort them at each time, and see what fraction are in sorted order matching their periods. If all MAs are perfectly ordered (shorter MAs < medium MAs < long MAs in a bull trend, or the reverse in a bear trend), this score might be 1 (depending how it’s computed). If jumbled, lower.
ma_compression: The cross-sectional std dev of all those MAs divided by their mean. This measures how tightly clustered the moving averages are.
Why useful?
Alignment: In a strong bull trend, you get an alignment: price > 5-day MA > 10-day > ... > 200-day (all shorter above longer). That scenario signals a consistent uptrend across timeframes. Score near 1 indicates a very orderly trend structure (either up or down). If they are out of order, the market might be choppy or transitioning (e.g., some short MAs below long ones but others above, etc.). The model can use this as a trend strength/health indicator. 1 in up-order means full bullish trend across scales – likely a good environment to trend-follow. 1 in down-order (all inverted) means a solid downtrend. Mid values mean crossovers happened or conflicting trend lengths – maybe avoid heavy trend bets.
Compression: When all MAs converge, it often indicates the market has been consolidating – price has been going sideways enough that short and long-term averages are roughly equal (think of a coiled spring – after consolidation, often a big move comes). Low compression (i.e., high dispersion of MAs) means the averages are far apart – typically in a trending market where, say, the 50-day is much higher than the 200-day, etc. That might indicate an overextended trend or just an established trend. Traders sometimes look at MA “ribbon” tightness – very tight ribbons precede breakouts. The model could learn that a very small ma_compression (very tight cluster) often happens before a volatility expansion (like a big breakout) – so it might predict one. Or that a large compression (widely spread MAs) could mean the trend is mature or overshoot (maybe due for correction).
By giving these, we summarize the state of multiple MAs into one or two features. Instead of the model having to interpret 6 different MAs, it has a neat “alignment = 0.8, compression = X.” For example, if alignment is high and compression is low (i.e., nicely ordered and well separated), that’s a strong trend in full swing – likely keep going unless something changes. If alignment drops and compression increases, trend is weakening into a range – maybe shift strategy.
Bollinger Band & RSI: bb_rsi_squeeze is a combined indicator we made: it equals +1 if (price is near upper Bollinger band (20-day,2std) AND RSI14 > 70) minus 1 if (price near lower band AND RSI14 < 30). Essentially, it's +1 for overbought at top of band, -1 for oversold at bottom of band. (It's constructed in a way that yields -1, 0, or 1).
Why useful? This is again an overbought/oversold combo but using volatility bands too. If a stock is at the upper Bollinger Band and momentum is overbought, that’s a pretty strong short-term overextension signal. Similarly, at lower band with oversold RSI is strong potential rebound signal. By making it one feature, we highlight those extreme conditions. This is like a specific trading setup: price at Bollinger band and confirming oscillator. Traders often require both for confidence. The model, with this, may find that when bb_rsi_squeeze = +1 (which I'd call actually an “overbought alert”), returns next few days tend to be flat or down as the stock consolidates or mean reverts. If = -1 (oversold alert), often you get a bounce. So it can incorporate that directly. Without it, it might separately consider Bollinger position and RSI, but by combining we explicitly tell it when both conditions hold.Multi-timeframe Momentum Alignment: We gather all return_n_d features (like return_1d, 5d, 10d, 20d, etc.), take their sign (+1,0,-1 for up/down/flat), and sum them then divide by count. This gives momentum_alignment which ranges from -1 to +1 indicating what fraction of those look positive. If all returns from 1-day up to, say, 60-day are positive, alignment = 1 (meaning short, medium, long momentum all up). If mixed, it might be 0 or something. Why useful? This is a quick gauge of whether all timeframes agree on trend direction. If a stock’s 1-day, 5-day, 20-day returns are all positive, it's universally trending up; if short ones are negative but long ones positive, trend might be pulling back in short term (mixed). The model can use this to confirm signals: e.g., a buy signal is more credible if momentum_alignment is high (broad upward pressure), whereas if alignment is negative but some shorter oscillators gave buy, maybe it's just a small counter-trend blip. It basically encodes the concept of "the trend across multiple horizons." A value like +0.5 means maybe out of 6 returns, 4 were positive, 2 negative – moderately bullish leaning. It’s more informative than just one return.
Momentum Acceleration: We also split momentum into “short” (like returns <=5d) and “long” (>=10d) groups and take their average, then do short - long. This gives momentum_acceleration – positive means short-term momentum is stronger than longer-term momentum, implying an acceleration or uptick in trend. Negative means short-term momentum is weaker than longer-term – maybe the trend is stalling. Why useful? If a stock has been in a long uptrend (long momentum high) but recently is struggling (short momentum low or negative), momentum_acceleration will be negative – a warning that the trend is losing steam (potential reversal soon). Conversely, if long-term trend was flat or slight, but suddenly short-term momentum picked up (like a recent breakout), momentum_accel positive suggests the start of a new trend or a speeding up trend. It’s like a second derivative of price. Traders sometimes sense this by seeing MA crossovers (short MA crossing above long MA indicates acceleration). Here we quantify it directly. The model can use it to catch inflection points: a big swing from negative to positive acceleration might mean breakout, from positive to negative might precede a top. It’s somewhat akin to MACD histogram (which is an acceleration indicator in some sense), but done across broad momentum.
These interaction features are powerful because they incorporate compound logic that often underpins trading decisions. We basically asked, "what combinations of our features would a trader manually watch for?" and coded those:
Volume confirming price moves,
Multiple momentum indicators confirming each other or diverging,
Price relative to key levels (support/resistance) with momentum context,
Trend structure summarizing across many MAs,
Combined overbought/oversold signals,
Multi-horizon trend agreement.
For the model, this reduces the burden of learning non-linear interactions – we gave it those interactions explicitly. If they matter, the model’s linear or simpler non-linear layers can easily latch onto these single features instead of having to multiply two features internally. It’s like pre-processing that gives it candidate signals. If some are redundant or not useful, the model can ignore them. But typically, these capture real phenomena, so likely they’ll enhance predictive power.
Regime Features
Market regime features classify broader context: Is the market (or stock) in a bullish trend regime or bearish? Is it a high volatility regime or low? High volume regime? Strong momentum regime? These are more binary or categorical descriptors that help the model modulate its predictions depending on overarching conditions. Think of them like market weather indicators – sunny bull market or stormy bear, etc. A good strategy often changes behavior depending on regime (e.g., trend-follow in trending regime, mean-revert in ranging regime). We make the model regime-aware by these features.
Trend Regime (Bullish/Bearish): We set trend_regime_bullish = 1 if the classic moving average alignment for a bull market holds: price > 20-day SMA, and 20 > 50, and 50 > 200-day SMA (shorter MAs above longer). trend_regime_bearish = 1 if the opposite: price < 20, and 20 < 50, and 50 < 200 (downtrend structure). Otherwise, these are 0. Why useful? This is basically saying: are we in an established uptrend, downtrend, or neither (sideways)? In a bullish regime, one might favor long trades, give more benefit of doubt to bullish signals and be cautious on shorts. In a bearish regime, favor shorts or quick to exit longs. By providing this, we allow the model to condition its outputs on regime easily. For example, it might learn a rule like "if trend_regime_bullish=1, then any dip (oversold) is a buy and overbought signals are less reliable (because it can stay overbought in a bull run). If trend_regime_bearish=1, oversold might not mean buy – it's just in a downtrend, keep selling rallies." Essentially, it splits the strategy by bull/bear market. These regime flags are a bit redundant with features like alignment_score or ADX plus DI, but they make it binary and straightforward. If both are 0, we’re in neither regime strongly – likely choppy or transitional.
Volatility Regime (Low/High): We compute a percentile of 20-day volatility over the past year (252 days). Then:
low_vol_regime = 1 if vol_percentile < 0.3 (vol is in bottom 30% of year).
high_vol_regime = 1 if vol_percentile > 0.7 (vol in top 30%).
Why useful? Trading tactics differ in quiet vs volatile markets. In low vol regimes, breakouts might be scarce and yields small (better to do spreads or just hold positions, risk lower). In high vol, risk is bigger but also opportunity for larger moves (also need to tighten risk or widen stops accordingly). The model might learn to output more conservative predictions (closer to zero maybe) in high vol regime due to uncertainty, or it might use different indicator thresholds. It's basically context. For instance, a high_vol_regime often coincides with bear markets or crises (though not always, could be post-news rally too). Low vol often is in steady bull markets or stagnation. The model will see historically how its signals fared under each condition and can adjust. For example, a momentum signal might be very effective in low vol trending regime but get whipsawed in high vol. So it might implicitly down-weight momentum signals when high_vol_regime=1 (because maybe mean reversion or just noise dominates). Conversely, in low_vol_regime maybe oscillator extremes aren’t hit often and breakouts fail, so it might rely more on breakout signals in high vol but on mean reversion in low vol, etc. Providing a clear flag simplifies this adaptation.
Volume Regime (High/Low): Similarly, we check volume’s 252-day percentile:
high_volume_regime = 1 if volume MA (20-day) is > 70th percentile of past year.
low_volume_regime = 1 if < 30th percentile.
Why useful? High volume regime might indicate increased participation (maybe due to macro factors or bull runs where more people trade), low volume regime might be holidays or just lack of interest. In low volume regimes, technical signals can be less reliable (thin markets can drift oddly). High volume regime might correlate with high volatility often, but not always (sometimes volume goes up in stable bull markets as more people pile in, without huge volatility). The model can consider this: e.g., if volume regime is high, maybe trust volume-based signals more because volume is truly coming in. If volume is low regime, be wary of signals (could be false moves in a desert). Or it might know that certain patterns only work when volume participation is good. It's another context check.
Momentum Regime (Strong/Weak): We take a 20-day Rate of Change (ROC) and find its percentile in past year. If > 80%, we call it strong_momentum_regime (meaning the recent 1-month return is among the highest of the year – so likely in a big uptrend relative to normal). If < 20%, weak_momentum_regime = 1 (price has been very flat or down relative to usual range). Why useful? If strong momentum regime = 1, basically the stock has rallied sharply (top quintile of momentum) – that often corresponds to either overheating (near climactic top short-term) or just a strong trending environment (especially if it aligns with bull trend regime). The model might interpret it as “don’t fight the tape, unless maybe it's too extreme with other overbought signals.” If weak momentum (like nothing much going on or sliding slowly), maybe focus on mean reversion or wait for clear signals. Essentially it quantifies if the stock is in a high momentum phase (like everything is trending up strongly) or a doldrums. For example, during a strong momentum regime, oscillators will be overbought frequently but that might not mean sell yet – the model can learn that those signals have different outcomes in this regime. It's similar to trend regime but measured by returns percentile.
Market Efficiency Regime (Trending/Ranging): We use the efficiency ratio (mentioned earlier) for 20-day. If > 0.7, we flag trending_market = 1, if < 0.3, ranging_market = 1. This is a finer way to say trending vs range-bound. Why separate from trend_regime? Because trend_regime looks at MAs alignment (which is a binary-ish strict condition for a major trend), whereas efficiency could catch shorter ephemeral trends and ranges. You could be not in a fully golden-cross bull, but still trending nicely for a month (maybe all MAs not aligned yet). Efficiency would catch that. Or vice versa, MAs aligned bullishly but price has been oscillating (maybe distribution but still above MAs). Efficiency would be low, marking it as ranging (even though technically in a long-term uptrend). So these features refine the notion of trending vs ranging conditions. Why useful? Strategies differ drastically: trending markets favor trend-following trades; ranging markets favor mean reversion. The model knowing this can weigh evidence differently. E.g., in trending_market=1, a breakout or momentum continuation signal might be given more trust, whereas in ranging_market=1, an overbought oscillator likely means sell (because price swings back and forth). By providing these, we in effect allow the model to have a “switch” for strategy mode. It might implicitly learn to behave differently when trending_market=1 vs ranging_market=1, akin to a regime-switching strategy a human might employ.
All these regime indicators often will correlate with each other (bull trend often has strong momentum and maybe high volume if it’s a lively bull, etc.). But they each add a piece of the puzzle:
Trend_regime is a stricter structural definition (multiple MAs).
Efficiency/Trending vs Ranging is more about price movement pattern.
Vol and Volume regimes tell about market activity level.
Momentum regime specifically highlights if we’re in an outlier run or slump.
By including them, we basically prevent the model from having to deduce the regime purely from raw indicators each time – we hand it a labeled environment. It’s like telling it “we are currently in category X of market conditions.” That simplifies learning because the model can condition on those to learn different behaviors. One could imagine training separate models for bull vs bear regimes; here we give one model a flag to know which world it’s in, so it can internally adapt.
Advanced ML-Enhanced Features
Finally, these are features derived using more advanced or non-standard transformations – things an ML researcher might add to capture non-linear effects or patterns beyond classical technical analysis. They include polynomial transformations of existing features (to allow the model to fit curves), spectral (Fourier) features for cycles, wavelet-like multi-scale changes, entropy-based complexity measures, and fractal dimension. These are a bit “out there” compared to normal trading indicators but can provide edge in a sophisticated model by capturing subtle patterns or ensuring the model can model non-linear dependencies.
Polynomial Features of Key Indicators: We took a few key features – specifically RSI14, MACD histogram (12_26_9), Bollinger Band position (20-day, 2std), and volume_ratio_20 – and generated non-linear transforms: squared, cubed, square root, and log. For example, rsi_14_squared, rsi_14_cubed, sqrt(|rsi_14|), log(|rsi_14|+1), and similarly for MACD hist, etc. Why useful? Many relationships in markets are non-linear. For instance, the impact of RSI might not be linear: going from RSI 50 to 60 is different than 70 to 80 in terms of implications. By squaring or cubing RSI, the model can capture curvature – e.g., maybe extremely high RSI (which yields a high squared value) sharply increases probability of a reversal, whereas middle-range RSI doesn’t. If we only gave RSI itself, a linear model might not capture that threshold effect well; but RSI_cubed will exaggerate high values, allowing a linear combination to mimic a curve. Log and sqrt transformations help with diminishing returns – e.g., volume_ratio effect might level off (log would capture that). In short, polynomials and transforms allow the model to fit more complex shapes. We specifically chose a few features that we suspect have non-linear significance:
RSI: Often overbought/oversold effects kick in after ~70 or ~30. Squared and cubed emphasize those ends. The model might find something like weight on rsi_cubed negative, meaning when RSI is very high (cubed gets really large), that contributes negatively to expected return (i.e., too high RSI -> negative future returns).
MACD hist: Could be similar – moderate positive hist is good, but very high hist might mean overextended. Or squared could help differentiate between slightly positive vs strongly positive momentum.
BB position: Perhaps being at 0.9 (90% of band) isn't that critical, but at 1.0 (100%) it's huge – squaring band position will highlight near-1 values.
Volume ratio: Maybe low volume vs average doesn’t reduce probability of trend success linearly – extremely low volume could drastically mean revert. Sqrt/log can help compress big volume values so one or two big outliers don't overly influence linear fit (log normalizes it).
Essentially, these expansions equip the model to fit curves or highlight extremes in these important indicators. It's like giving it a wider palette to draw relationships with, rather than just straight lines.
Interaction Polynomials: We specifically multiplied RSI14 with volume_ratio_20 to make an rsi_volume_interaction, and also (rsi^2 volume_ratio) as rsi_volume_poly. Why? There might be a combined effect: for instance, an overbought RSI is far more significant if volume was also high during that rise (overbought + high volume = likely exhaustive blow-off), compared to overbought on low volume (maybe squeezes can continue). By having RSI volume, the model can capture such conditional effects. rsi_volume_poly (rsi^2 * volume) exaggerates cases of extremely high RSI and high volume. If those often precede drops, the model will use that feature. Conversely, maybe oversold RSI + high volume often leads to bounces (capitulation), so the feature could correlate with subsequent gains. Interactions basically allow, for example, the slope of RSI's impact to depend on volume. Without it, a linear model would add separate effects of RSI and volume; with it, it can add “when both, do something extra.” This is similar in spirit to the earlier interactions but done for continuous values to allow gradient changes, not just binary logic.
Fourier Transform Features (Cyclical patterns): If we have at least a year of data, we perform an FFT (fast Fourier transform) on the detrended close price (close minus 252-day MA). We find the top 3 frequencies (dominant cycles) excluding the zero-frequency (mean). For each such frequency, we compute a sine and cosine wave for that period length and include those as features (provided the period is between 5 and 100 days to avoid nonsense like extremely long cycles in short data). Why useful? Markets sometimes exhibit cyclical behavior – e.g., a stock might have a ~20-day cycle (perhaps due to monthly investor behaviors or news cycles) or a weekly 5-day cycle (maybe tends to dip mid-week and rise end-week). By extracting top cycles, we attempt to capture any regular oscillatory pattern. If a 20-day cycle is strong, the sine/cosine for that frequency can help predict where we are in that cycle (like a timing indicator). The model can use these to anticipate turning points: if we’re at a cycle phase that historically is a peak, it might temper its bullishness. Or it might find that combining cycle phase with other signals improves timing. Essentially, the Fourier features provide a structured way to detect seasonality or repetitive patterns beyond just trending. If none exist, those features will be small or random and the model will ignore them. But if one does (like a bi-weekly oscillation), they could add significant predictive power.
Wavelet-like Multi-Scale Features: We don’t do actual wavelet transforms, but we simulate something similar:
For scales 2,4,8,16 days, we take the price series and compute a difference over that scale (like price – price 2 days ago for d2, etc.) and a rolling mean over that scale.
We call these wavelet_d2, wavelet_smooth2, wavelet_d4, wavelet_smooth4, ... etc.
Why useful? This gives an idea of price change at different horizons: wavelet_d2 highlights short 2-day momentum (like a very fast change), wavelet_d16 highlights swings over ~3 weeks. The corresponding smooth is like a local baseline at that scale. These are akin to high-pass and low-pass filters of the price at those frequencies (though not exactly, but difference is like a high-pass capturing quick changes, rolling mean is like a low-pass capturing trend at that scale). By including them, we allow the model to consider signals like “the 8-day change” which might capture things a plain 5d or 10d return might not if trending turned recently. It also helps separate short-term noise vs longer moves. A wavelet_smooth16 might be similar to a 16-day MA (which we also have), but wavelet_diff16 is like how much price moved from 16 days ago – which might show medium-term momentum. In short, these features break the price series into components by scale. The model can then recombine them if needed. Perhaps more useful is identifying a big discrepancy: e.g., wavelet_d2 is strongly positive but wavelet_d16 is strongly negative – indicating a short-term counter-trend rally in a bigger down move. The model could learn that that might not sustain (just a bounce). If wavelet_d2 and d16 align in sign, that’s a multi-scale consistent move. This is another way of multi-timeframe analysis, akin to momentum alignment but done on raw price differences rather than percent returns. They might capture subtle aspects like acceleration (difference of smaller scale vs bigger scale).
Entropy-Based Features (Sample Entropy): We calculate sample entropy of returns for windows 20 and 50. Sample entropy basically measures how unpredictable the time series is by checking repeats of short sequences. In code, they attempt to compute it (with some tolerance 0.2 * std). A higher value means more irregular (random-like) sequences, lower means more predictable structure. Why useful? Similar to the Shannon entropy earlier, but this is a time-domain measure focusing on the sequence rather than distribution. If the price path has a lot of repeated patterns (like consistent trend or oscillation), sample entropy would be lower (because sequences of moves repeat often). If every day’s movement seems unrelated to previous patterns, entropy is high. Traders might not directly use this, but it’s an advanced way to see if a market is in a chaotic regime or not. The model, armed with this, could for example discount signals in high entropy states (because when market is chaotic, technical signals may fail since patterns aren’t repeating). Or it might find low sample entropy periods correspond to strongly trending or range-bound predictable moves – so lean into strategies then. It’s a niche measure but can add a layer of understanding of market complexity.
Fractal Dimension: We estimate fractal dimension via a box-counting method on price for windows 30 and 60. Fractal dimension ranges between 1 and 2 for a time series:
Near 1 means very smooth (trending in one direction cleanly, like a line has dimension 1),
Near 2 means very rough (a lot of noise, filling space like a plane, which for a 1D series means it's basically random zigzagging).
If fractal_dim ~ 1.5 it’s basically a random walk borderline.
Why useful? It’s another way to quantify trend vs noise. Many use fractal dimension or the related Hurst exponent (H = fractal_dim - 1 roughly for price series) to decide if market is trending (dimension <1.5, i.e., more line-like) or mean-reverting (dimension >1.5, more space-filling). A famous interpretation: fractal dimension below ~1.33 indicates strong trend; above ~1.66 indicates strong mean reversion (ranging). So our fractal_dim features basically mirror Hurst but maybe more robust in certain cases. The model can use it similarly to efficiency or ADX: low fractal_dim (like 1.2) => exploit trend (because price path is closer to a smooth line); high fractal_dim (like 1.8) => it's very choppy (almost random), better revert to mean or just be cautious.
It might also pick up multi-scale aspects: fractal dimension calculation looks at box sizes, which might catch if volatility clusters or if price structure changes at different scales. Possibly redundant with other vol/trend measures but could provide a unique signal in some cases.
Wrap-up of ML-Enhanced features: These are like giving the model extra horsepower to detect non-linear and hidden patterns:
Polynomials and interactions ensure it can model threshold effects and combined effects without needing a deep network to multiply features (we did it for it).
Fourier/cycle features ensure it doesn’t miss a periodic pattern if one exists (like say the stock oscillates roughly every 15 days – the sine wave feature will help capture that phase).
Wavelet differences let it see simultaneous short- and long-term moves distinctly, rather than them blurring together.
Entropy and fractal dimension give it a sense of complexity/regularity of the series which can refine its strategy usage.
Together with regime flags, it’s like giving the model meta-knowledge: “if fractal_dim is low and trending_market=1, it’s a steady trend – go with it; if fractal_dim is high and vol high, it’s chaos – be careful or shorten horizon.”
Finally, it’s important to note: Not every feature will be used significantly – some might overlap or be less predictive. But by casting a wide net, we allow the model to choose which features matter through training. The blog’s job is to educate on each, which we’ve done. In practice, the model likely finds a subset that are most helpful.
Conclusion / Putting it all together:
The feature engineering script loaded our dataset with a comprehensive set of features spanning price trends, volume dynamics, volatility measures, classical technical indicators, microstructure cues, statistical tests, pattern signals, and higher-level regime flags and transformations. Each category served to answer vital questions:
Price/Volume/Volatility features map out “what has price done and with what participation and variability?”
Technical indicators and patterns capture “what are classic analysis signals saying (momentum, trend, reversal patterns)?”
Microstructure features reveal “what’s happening intraday, are moves genuine or fleeting, and how liquid is trading?”
Statistical features assess “is the current market behaving normally or are there anomalies? Trending or random? Autocorrelated? Fat tails to beware of?”
Interaction features combine multiple aspects to spotlight “especially strong or weak conditions when multiple factors align.”
Regime features define “the backdrop context – bull vs bear, calm vs volatile, active vs quiet, trending vs ranging.”
ML-enhanced features ensure “the model can capture non-linear relationships and any cyclic or fractal patterns that simpler indicators might miss.”
By breaking down each, we provided an intuition of what each feature measures (from simple returns to complex entropy), how it’s computed in plain terms (from averaging prices to counting fractal boxes), and why it could be useful to identify trading opportunities.
When you think in trading terms:
Buy/Sell signals often come from combinations like “oversold + support + low volatility” or “momentum + volume + breakout.” Our engineered features explicitly represent those combos (support_oversold, rsi_macd_confluence, volume_trend_alignment, etc.) so the model can recognize those favorable setups.
Hold or wait signals might come from recognizing noise (high noise_ratio, high entropy) or conflicting signals (rsi_macd_divergence). The model can be fed those to avoid acting in chop or uncertain times.
Risk management adjustments can be informed by volatility regime or volume regime. For example, in a high_vol_regime, maybe the model implicitly learns to expect larger moves (which a trader would adjust stops for).
Positioning can be guided by regime: e.g., trend_regime_bullish environment, it might systematically lean long; in bearish regime, lean short.
Overall, the feature set arms the ML model with nearly everything a technically-inclined trader might look at (and more), but without bias – the model will statistically determine which features truly help predict future returns or price movement. Our focus was on the intuition behind each feature’s usefulness: each one is crafted because in one way or another, it has a story to tell about buy, hold, or sell conditions:
Price making new highs with volume up (bullish continuation),
Price far above average and momentum waning (sell or short-term sell),
Price at support with oversold conditions (buy),
Trend strong and steady (stick with it),
Market chaotic (maybe reduce exposure or use different strategy),
A bullish candlestick reversal pattern forms (maybe time to buy a dip),
Multi-day rally with no pullback (maybe hold but be cautious for reversal soon, depends on context).
By blending all these, the model can form a holistic view. Instead of only seeing, say, RSI or moving average, it sees the whole tapestry: trend structure, momentum, volatility state, confirmation from volume, candlestick signals, etc. In doing so, it can make more informed decisions akin to an experienced trader but with quantitative rigour.
“Relative Strength Index (RSI) Indicator Explained With Formula.” Investopedia. (investopedia.com)
“RSI Indicator: Buy and Sell Signals.” Investopedia. (investopedia.com)
“What Is MACD?” Investopedia. (investopedia.com)
“MACD: A Primer.” Investopedia. (investopedia.com)
“Bollinger Bands: What They Are, and What They Tell Investors.” Investopedia. (investopedia.com)
“The Basics of Bollinger Bands®.” Investopedia. (investopedia.com)
“Average True Range (ATR) Formula, What It Means, and How to Use It.” Investopedia. (investopedia.com)
“Enter Profitable Territory With Average True Range.” Investopedia. (investopedia.com)
“On-Balance Volume (OBV): Definition, Formula, and Uses As Indicator.” Investopedia. (investopedia.com)
“On-Balance Volume Reveals Market Player Strategy.” Investopedia. (investopedia.com)
“ADX: The Trend Strength Indicator.” Investopedia. (investopedia.com)
“How Is the Average Directional Index (ADX) Calculated … ?” Investopedia. (investopedia.com)
Jared Vogler
industrial engineer
Location
Charleston, South Carolina