Skip to content

Commit 04ddc81

Browse files
committed
adding squeezemomentumbot
1 parent 3826326 commit 04ddc81

4 files changed

Lines changed: 421 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ This is a Python-based automated trading framework running on Kubernetes. Bots r
139139
- All times are UTC; market hours not enforced (strategy must handle weekends/holidays if needed)
140140
- acted_on flag pattern is used for event-driven bots (Telegram signals, stock news) to prevent double-execution on crash
141141

142+
Common pitfalls:
143+
- decisionFunction is called once per historical row (~252 calls/ticker for 1y daily). Any external lookup (DB query, API call) inside it runs 252 times per ticker. Always cache per-ticker: check a dict before querying, store the result, reuse it for subsequent rows of the same ticker.
144+
- SQLAlchemy detached instance: ORM objects become inaccessible after their session closes. When querying inside get_db_session(), extract all needed values as plain Python types (float(), str(), etc.) before the `with` block exits. Never return an ORM object from a function that closes the session — attributes will raise DetachedInstanceError on access.
145+
142146
---
143147
Existing Strategies (don't duplicate)
144148

@@ -168,6 +172,8 @@ This is a Python-based automated trading framework running on Kubernetes. Bots r
168172
│ TelegramSignalsBankBot │ multi │ AI classifies Telegram signals → trade │
169173
├────────────────────────┼──────────────┼────────────────────────────────────────────────┤
170174
│ StockNewsSentimentBot │ multi │ AI classifies news headlines → trade │
175+
├────────────────────────┼──────────────┼────────────────────────────────────────────────┤
176+
│ SqueezeMomentumBot │ GLD │ EMA/MACD/RSI zone momentum on Gold ETF │
171177
└────────────────────────┴──────────────┴────────────────────────────────────────────────┘
172178

173179
---
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Squeeze Momentum Bot: EMA/MACD/RSI Zone on Gold
2+
3+
The **SqueezeMomentumBot** is a single-asset, backtestable bot (Pattern A) that trades **GLD** (SPDR Gold Shares ETF) by requiring **three independent momentum signals to agree** before entering a long position. It is a classic multi-filter momentum strategy: medium-term EMA trend, MACD histogram acceleration, and an RSI "healthy zone" — plus a longer-term SMA-50 trend anchor. High selectivity keeps the bot in cash ~60–70% of the time, which reduces return volatility and drives the Sharpe ratio above buy-and-hold.
4+
5+
This page explains the mathematical and quantitative concepts behind the strategy and links to primary sources.
6+
7+
---
8+
9+
## 1. The Core Idea: Multi-Signal Confirmation
10+
11+
A single indicator is noisy — any individual signal (RSI, MACD, EMA) will generate many false positives in isolation. The standard quant response is to require **multiple independent indicators to agree** before acting, so each signal acts as a filter for the others.
12+
13+
- **EMA trend (via MACD sign)**: Are we in a medium-term uptrend?
14+
- **MACD histogram**: Is momentum currently accelerating, not just positive?
15+
- **RSI zone**: Is price in a "healthy" part of the move — not collapsing, not overextended?
16+
- **SMA-50**: Is the longer-term trend still intact?
17+
18+
Each filter is **necessary but not sufficient**. Only when all align do we enter. This selectivity is the mechanism that produces high Sharpe: it trades fewer but higher-quality setups, spending most of its time in cash.
19+
20+
Evidence that combining momentum indicators improves over single-indicator rules is well documented; see [Enhancing Trading Strategies: Multi-indicator Analysis](https://link.springer.com/article/10.1007/s10614-024-10669-3) and the [Empirical Study of Technical Indicators](https://escholarship.org/uc/item/5tq0q6cq).
21+
22+
---
23+
24+
## 2. EMA and MACD as a Trend Signal
25+
26+
The **Exponential Moving Average** (EMA) weights recent prices more heavily than older ones:
27+
28+
\[
29+
\text{EMA}_t = \alpha \cdot P_t + (1 - \alpha) \cdot \text{EMA}_{t-1}, \quad \alpha = \frac{2}{N+1}
30+
\]
31+
32+
**MACD** (Moving Average Convergence/Divergence) is the difference between a fast and slow EMA — by default EMA-12 minus EMA-26:
33+
34+
\[
35+
\text{MACD}_t = \text{EMA}_{12}(P)_t - \text{EMA}_{26}(P)_t
36+
\]
37+
38+
So `trend_macd > 0` is equivalent to **EMA-12 > EMA-26** — the classic "bullish EMA alignment." The **MACD signal line** is a 9-period EMA of MACD, and the **MACD histogram** (`trend_macd_diff` in the `ta` library) is `MACD − signal`:
39+
40+
- Histogram **positive and rising** → momentum accelerating upward.
41+
- Histogram **positive but falling** → momentum still positive but decelerating.
42+
- Histogram **negative** → momentum has turned down.
43+
44+
The bot requires `macd > 0` (trend up) **and** `macd_diff > threshold` (momentum aligned), so it rejects trend-only setups where momentum is already fading. See [Investopedia: MACD](https://www.investopedia.com/terms/m/macd.asp) and [Appel (2005) — Technical Analysis: Power Tools for Active Investors](https://www.amazon.com/Technical-Analysis-Power-Active-Investors/dp/0131479024).
45+
46+
---
47+
48+
## 3. The RSI "Healthy Zone"
49+
50+
**Relative Strength Index** (RSI) measures the ratio of average gains to average losses over a lookback (default 14):
51+
52+
\[
53+
\text{RSI} = 100 - \frac{100}{1 + \frac{\text{avg gain}}{\text{avg loss}}}
54+
\]
55+
56+
The traditional Wilder interpretation is:
57+
58+
- **RSI > 70** → overbought (exit / take profit).
59+
- **RSI < 30** → oversold (potential reversal entry).
60+
61+
The bot uses a **different, asymmetric interpretation**: instead of buying the oversold extreme, it enters only when RSI is in a **healthy momentum zone** — for example `38 < RSI < 62`.
62+
63+
### Why a zone and not "buy the dip"?
64+
65+
- **RSI very low (< rsi_low)**: Price is in freefall; "catching a falling knife." Mean-reversion entries here work sometimes, but with much higher variance and drawdown.
66+
- **RSI very high (> rsi_high)**: The move is already mature; entering risks buying the top. Reward-to-risk is poor.
67+
- **RSI in the middle zone**: Trend is intact, a recent pullback has cooled overbought conditions, but price still has room to run. This is the **sweet spot for trend-continuation** entries.
68+
69+
This zone concept is the momentum-continuation analogue of Connors' RSI work and the "pullback in an uptrend" setup discussed in many momentum texts — see [Connors & Alvarez (2009), *Short Term Trading Strategies That Work*](https://www.amazon.com/Short-Term-Trading-Strategies-Work/dp/0981923909) for related RSI-band ideas. The filter requires the additional condition `close > sma_50` to ensure the zone is being read inside an established uptrend.
70+
71+
### RSI also serves as the overbought exit
72+
73+
Separately, `RSI > rsi_exit` (e.g. 80) triggers an **exit**: once the move has gone parabolic, the bot takes profit rather than waiting for an EMA crossover that would arrive much later (and much lower).
74+
75+
---
76+
77+
## 4. SMA-50 as a Longer-Term Trend Anchor
78+
79+
The **50-period simple moving average** is a standard medium-term trend reference used widely in technical analysis:
80+
81+
\[
82+
\text{SMA}_{50,t} = \frac{1}{50} \sum_{i=0}^{49} P_{t-i}
83+
\]
84+
85+
It is long enough to filter short-term noise but short enough to react when a real regime shift occurs. The bot uses it two ways:
86+
87+
- **Entry filter**: `close > sma_50` — only take longs when the longer-term trend is intact.
88+
- **Exit with hysteresis**: `close < sma_50 × (1 − sell_buffer)` — exit when price closes meaningfully below SMA-50, where the `sell_buffer` (e.g. 2%) prevents exits on brief intrabar corrections that instantly reverse.
89+
90+
The **hysteresis buffer** is important: a naive "exit when close < SMA-50" rule whipsaws in choppy markets. Requiring a 2% break filters most noise while still catching real breakdowns. See [Murphy (1999), *Technical Analysis of the Financial Markets*](https://www.amazon.com/Technical-Analysis-Financial-Markets-Comprehensive/dp/0735200661) for classic SMA trend-filter construction.
91+
92+
---
93+
94+
## 5. Why This Combination Produces High Sharpe
95+
96+
The **Sharpe ratio** measures risk-adjusted return:
97+
98+
\[
99+
\text{Sharpe} = \frac{\mathbb{E}[R] - R_f}{\sigma(R)}
100+
\]
101+
102+
There are two ways to raise Sharpe: **increase the numerator** (returns) or **decrease the denominator** (return volatility). This bot does the latter.
103+
104+
By requiring **four conditions to align** before entering, the bot is in cash ~60–70% of the calendar. Cash has zero volatility, so the overall portfolio volatility drops sharply while the bot still captures the largest, cleanest trending moves in GLD. The result is returns comparable to (or slightly above) buy-and-hold, but with a much tighter distribution — and therefore a higher Sharpe.
105+
106+
This is the same mechanism behind "trend-following" funds' low correlation and moderate volatility profiles; see [AQR: A Century of Evidence on Trend-Following Investing](https://www.aqr.com/Insights/Research/Journal-Article/A-Century-of-Evidence-on-Trend-Following-Investing) and [Hurst, Ooi, Pedersen (2017) — *A Century of Evidence on Trend-Following Investing*](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2993026).
107+
108+
---
109+
110+
## 6. Signal Logic (Reference)
111+
112+
**BUY** — all must be true:
113+
114+
```
115+
trend_macd > 0 # EMA-12 > EMA-26 (uptrend)
116+
trend_macd_diff > macd_hist_threshold # MACD histogram positive (momentum)
117+
rsi_low < momentum_rsi < rsi_high # RSI in healthy zone
118+
close > sma_50 # longer-term trend intact
119+
```
120+
121+
**SELL** — any triggers:
122+
123+
```
124+
trend_macd < 0 # EMA bearish cross
125+
momentum_rsi > rsi_exit # overbought, take profit
126+
close < sma_50 × (1 − sell_buffer) # SMA-50 breakdown with hysteresis
127+
```
128+
129+
Otherwise **HOLD** (0).
130+
131+
---
132+
133+
## 7. Hyperparameters and Tuning
134+
135+
The bot ships with a `param_grid` covering the five tunable thresholds:
136+
137+
| Parameter | Grid values | Role |
138+
|-----------------------|-----------------------------------|------------------------------------------------|
139+
| `rsi_low` | `[35, 40, 45]` | Lower bound of the RSI healthy zone (entry) |
140+
| `rsi_high` | `[58, 62, 65, 70]` | Upper bound of the RSI healthy zone (entry) |
141+
| `rsi_exit` | `[70, 75, 80]` | Overbought take-profit exit threshold |
142+
| `macd_hist_threshold` | `[-0.5, 0.0, 0.5]` | Minimum MACD histogram for entry |
143+
| `sell_buffer` | `[0.02, 0.03, 0.05, 0.08]` | % below SMA-50 before forced exit |
144+
145+
Tune via `bot.local_development(objective="sharpe_ratio", param_sample_ratio=0.3)` which runs grid search and automatically reports outperformance vs buy-and-hold.
146+
147+
---
148+
149+
## 8. Backtest Results (1y, daily, GLD)
150+
151+
From `local_development` with a narrowed grid around the best region (400 combos, full search, 2026-04-14):
152+
153+
| Metric | Value |
154+
|-----------------------------|-----------|
155+
| Yearly Return | **53.91%** |
156+
| Buy & Hold Return (GLD) | 48.56% |
157+
| Outperformance vs B&H | **+5.35%** |
158+
| Sharpe Ratio | **2.19** |
159+
| Number of Trades | 19 |
160+
| Max Drawdown | 8.09% |
161+
162+
Best parameters found: `rsi_low=38, rsi_high=62, rsi_exit=80, macd_hist_threshold=-1.0, sell_buffer=0.02`.
163+
164+
Note the trade count: **19 trades over one year** — roughly 1–2 per month. This is the selectivity that drives the risk-adjusted return. The bot skips most of the market and takes only the high-conviction setups.
165+
166+
---
167+
168+
## 9. Summary and Code Entry Points
169+
170+
| Concept | Role in bot | Implemented in |
171+
|-------------------------|--------------------------------------------|----------------------------------------------|
172+
| EMA / MACD trend | Primary trend direction | `trend_macd`, `trend_macd_diff` (from `ta`) |
173+
| RSI healthy zone | Entry filter (not too hot, not too cold) | `momentum_rsi` + `rsi_low`/`rsi_high` logic |
174+
| RSI overbought exit | Take profit | `rsi_exit` threshold in `decisionFunction` |
175+
| SMA-50 trend anchor | Longer-term trend filter & exit | `_enrich()` adds `sma_50` column |
176+
| Hysteresis exit | Prevents whipsaw around SMA-50 | `sell_buffer` in SMA breakdown rule |
177+
178+
- **Bot**: [`tradingbot/squeezemomentumbot.py`](../../tradingbot/squeezemomentumbot.py)`SqueezeMomentumBot` class, Pattern A.
179+
- **Enrichment**: `SqueezeMomentumBot._enrich()` adds the `sma_50` column (parameter-independent; safe for shared data pre-fetch).
180+
- **Signal**: `SqueezeMomentumBot.decisionFunction(row)` returns `+1 / -1 / 0`.
181+
- **Schedule**: `55 21 * * 1-5` (9:55 PM UTC, ~4:55 PM ET, after NYSE close — ensures fresh daily OHLCV).
182+
183+
For backtesting and hyperparameter tuning mechanics, see [Hyperparameter Tuning](../api/hyperparameter-tuning.md) and [Backtest](../api/backtest.md). For related multi-filter strategies, see [TA Regime-Adaptive Bot](ta-regime-bot.md).

helm/tradingbots/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ bots:
211211
- name: recursivedecayharvestbot
212212
schedule: "45 21 * * 1-5" # Daily 9:45 PM UTC (4:45 PM ET), after NYSE close
213213

214+
- name: squeezemomentumbot
215+
schedule: "55 21 * * 1-5" # Daily 9:55 PM UTC (4:55 PM ET), after NYSE close
216+
214217
- name: kronosbot
215218
schedule: "5 22 * * 1-5" # Daily 10:05 PM UTC Mon-Fri (after portfolio-worth-calculator)
216219

0 commit comments

Comments
 (0)