The Quest Begins (The “Why”)
Honestly, I stared at my first crypto chart and felt like I was trying to read ancient runes. Prices zig‑zagged, my gut screamed “buy!” and then the market laughed and dumped 10% in an hour. I kept hearing folks toss around “moving average” and “RSI” like they were magic spells, but I had no clue how to actually wield them. My goal? Slay the dragon of false signals and stop chasing every flicker on the screen. I wanted a simple, repeatable way to spot when momentum was really building—not just noise.
The Revelation (The Insight)
Here’s the thing: moving averages aren’t about predicting the future; they’re about smoothing out the noise so you can see the underlying trend. Think of a simple moving average (SMA) as a rolling window that averages the last n prices. When the price crosses above that line, it often hints at upward momentum; when it dips below, the opposite.
The RSI (Relative Strength Index) is a different beast. It measures how fast and hard the price is moving, scaling it between 0 and 100. Values above 70 scream “overbought” (maybe time to pause or sell), while below 30 whisper “oversold” (maybe a buying opportunity). The magic happens when you combine them: a price crossing its SMA and an RSI climbing out of the oversold zone can be a stronger buy signal than either alone.
I spent three nights wrestling with spreadsheet formulas, then finally cracked it with a few lines of Python. When the plot showed clean buy/sell markers lining up with actual reversals, I felt like I’d just found the secret level in a classic game—pure, unfiltered excitement.
Wielding the Power (Code & Examples)
The Struggle: Loopy, Error‑Prone Code
At first I tried to compute everything with explicit loops. It worked, but it was slow and easy to mess up.
# 🚫 Naive loop‑based SMA & RSI (don’t do this)
def sma_loop(prices, window):
sma = []
for i in range(len(prices)):
if i < window - 1:
sma.append(None)
else:
window_slice = prices[i - window + 1:i + 1]
sma.append(sum(window_slice) / window)
return sma
def rsi_loop(prices, period=14):
gains = []
losses = []
rsi = [None] * len(prices)
for i in range(1, len(prices)):
change = prices[i] - prices[i - 1]
gains.append(max(change, 0))
losses.append(max(-change, 0))
if i >= period:
avg_gain = sum(gains[-period:]) / period
avg_loss = sum(losses[-period:]) / period
if avg_loss == 0:
rsi[i] = 100
else:
rs = avg_gain / avg_loss
rsi[i] = 100 - (100 / (1 + rs))
return rsi
Enter fullscreen mode Exit fullscreen mode
Trap #1: Forgetting to pad the start with None leads to misaligned indices.
Trap #2: Using sum(gains[-period:]) when the list is shorter than period gives wrong averages early on.
The Victory: Vectorized, Pandas‑Powered Spells
Switching to pandas’ rolling functions turned the whole thing into a one‑liner for each indicator. Plus, it’s fast enough for live‑tick data.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Load your data – assume a DataFrame `df` with a 'close' column
df = pd.read_csv('btc_usd_daily.csv', parse_dates=['date'])
df.set_index('date', inplace=True)
# ---- Simple Moving Average (SMA) ----
window = 20
df['SMA_20'] = df['close'].rolling(window=window).mean()
# ---- RSI (Relative Strength Index) ----
def compute_rsi(series, period=14):
delta = series.diff()
up = delta.clip(lower=0)
down = -delta.clip(upper=0)
ma_up = up.ewm(com=period - 1, adjust=False).mean()
ma_down = down.ewm(com=period - 1, adjust=False).mean()
rs = ma_up / ma_down
rsi = 100 - (100 / (1 + rs))
return rsi
df['RSI_14'] = compute_rsi(df['close'], period=14)
# ---- Signal Generation ----
# Buy when price crosses above SMA and RSI rises out of oversold (<30)
df['signal'] = 0
df.loc[(df['close'] > df['SMA_20']) & (df['RSI_14'] > 30) &
(df['close'].shift(1) <= df['SMA_20'].shift(1)) &
(df['RSI_14'].shift(1) <= 30), 'signal'] = 1
# Sell when price crosses below SMA and RSI falls from overbought (>70)
df.loc[(df['close'] < df['SMA_20']) & (df['RSI_14'] < 70) &
(df['close'].shift(1) >= df['SMA_20'].shift(1)) &
(df['RSI_14'].shift(1) >= 70), 'signal'] = -1
# Plot
plt.figure(figsize=(12,6))
plt.plot(df['close'], label='Close Price', alpha=0.7)
plt.plot(df['SMA_20'], label='20‑day SMA', alpha=0.9)
plt.scatter(df.index[df['signal']==1], df['close'][df['signal']==1],
marker='^', color='g', label='Buy', s=100)
plt.scatter(df.index[df['signal']==-1], df['close'][df['signal']==-1],
marker='v', color='r', label='Sell', s=100)
plt.title('BTC/USD with SMA & RSI Signals')
plt.legend()
plt.show()
Enter fullscreen mode Exit fullscreen mode
Why this feels like a power‑up:
- The
rollingandewm(exponential weighted mean) functions handle the windowing and alignment automatically—no more off‑by‑one bugs. - The signal logic is explicit, readable, and easy to tweak (change the window, RSI thresholds, or add EMA instead of SMA).
- Plotting the signals right on the price chart lets you instantly see if your strategy is catching real turns or just chasing noise.
Why This New Power Matters
Now you’ve got a repeatable, code‑driven way to filter out the market’s chatter. Instead of second‑guessing every tick, you can:
- Backtest any asset (stocks, forex, crypto) with a few minutes of script.
- Automate entries/exits in a trading bot, knowing exactly what conditions trigger them.
- Combine with other tools (volume, MACD, etc.) without rewriting the core logic—just plug in another column.
The best part? You’re not relying on black‑box indicators from a paid service; you built the intuition yourself, line by line. That feeling when the green buy arrows line up with a genuine upward move? It’s like finally beating that tough level in Contra after memorizing the pattern—pure, earned satisfaction.
Your Turn
Grab a dataset (Yahoo Finance, Binance, or even a CSV you’ve got lying around), drop the snippet above into a Jupyter notebook, and play with the window size or RSI thresholds. What happens if you switch to an exponential moving average? Does tightening the RSI band give you fewer but cleaner signals?
Drop your findings in the comments—I’d love to see what tweaks you discover on your own quest! 🚀
답글 남기기