Course Name: Unsupervised Learning in Trading, Section No: 11, Unit No: 3, Unit type: Notebook
Hi,
I have a question about how the strategy return is computed in the codebook. The return is defined as close.pct_change() * direction_skewness, but the two factors seem to be on different time horizons:
-
The Apple price data consists of 15-minute time bars. Early in the codebook, time_prd = 15 and fut_ret is computed with pct_change(time_prd), so fut_ret is a 15-bar forward return, i.e. 15 × 15 min = 3.75 hours.
-
In the skewness function of the data module, the trading signal (direction_skewness) for each cluster is generated from fut_ret.skew(), so the signal is based on the distribution of these 3.75-hour returns.
-
The strategy return, however, multiplies that signal by close.pct_change(), which is a single 15-minute return.
So the direction is chosen from the skewness of 3.75-hour forward returns, but the PnL is realised one 15-minute bar at a time. Is this mismatch intended? If so, what is the reasoning — or should the position be held for the full 15 bars after a signal? When I set time_prd = 1 so that both are on the same horizon, the results differ noticeably from those shown in the codebook, so I want to make sure I understand which configuration is intended.
Thanks in advance for any help.
Hi,
Good question, and you’ve read the code correctly. But there’s one thing the code does that resolves most of the puzzle: the position is not held for just one 15-minute bar.
Every bar in the test set gets a direction from its cluster. As long as the market stays in a cluster with the same direction, the position stays open. The line close.pct_change() * direction_skewness.shift(1) doesn’t mean “trade for one bar and exit.” It just records the profit or loss of an open position bar by bar, the way a broker statement would. The position only closes when the market moves into a cluster with a different or neutral direction.
You can see this in the trade log from get_trades: the hit-ratio version has only 119 trades across 6,885 test bars. That’s an average holding period of dozens of bars, not one. And if a position happens to last exactly 15 bars, those fifteen 15-minute returns compound into exactly the 3.75-hour return the skewness was measured on. So the two horizons line up whenever the market state persists.
The idea behind the design: the cluster describes a market state, built from slow-moving features like 1-day RSI and 14-day volatility. The training data tells us “when the market is in this state, returns over the next 3.75 hours tend to be skewed in a particular direction.” The trading rule is then simply “hold the position while the market stays in that state.” Both entry and exit depend on the state, not on a timer.
Your point is still partly valid, though. Since the exit isn’t fixed at 15 bars, the actual holding period won’t always match the 3.75-hour horizon the analysis was done on. Holding for exactly 15 bars after each signal would be a more literal version of the strategy. The notebook avoids it because a new signal arrives every bar, so you’d need extra rules for overlapping positions. Trying that version yourself and comparing results would be a good exercise.
On time_prd = 1: the results change because you’ve changed the question being asked. Skewness of single 15-minute returns is much noisier, and one outlier bar can flip a cluster’s label from long to neutral or short. The intended setting is time_prd = 15.
Hope that helps.
1 Like