End-to-end time-series analysis and feature engineering on Apple Inc. (NASDAQ: AAPL) historical price data covering 5 years of market activity (2021–2026), built entirely in Python using pandas, numpy, and matplotlib.
| Ticker | AAPL (Apple Inc.) |
| Data Period | July 2021 – July 2026 |
| Trading Days | 1,255 |
| Starting Price | $141.96 |
| Latest Price | $316.15 |
| Cumulative Return | +122.7% |
| Max Drawdown | -33.4% (2022 bear market) |
| Sharpe Ratio | 0.72 |
| Tools | Python · pandas · numpy · matplotlib |
AAPL-Stock-Analysis/
├── AAPL.csv # Raw price data (OHLCV)
├── AAPL_transformed.csv # Cleaned dataset with all features
├── AAPL_monthly_summary.csv # Monthly aggregation table
├── AAPL_quarterly_summary.csv # Quarterly aggregation table
├── AAPL_yearly_summary.csv # Yearly aggregation table
├── AAPL_pivot_close.csv # Avg close pivot (Year x Quarter)
├── AAPL_pivot_volume.csv # Total volume pivot (Year x Quarter)
├── AAPL_long_format.csv # OHLC in long/melted format
├── AAPL_Report.pdf # Full analysis report (4 pages)
└── README.md # This file
Profiled the raw CSV structure, identified 2 junk metadata header rows, confirmed all columns stored as object/str type.
df = df_raw.iloc[2:].copy() # skip junk rows
df.columns = ["Date","Close","High","Low","Open","Volume"]
df["Date"] = pd.to_datetime(df["Date"]) # fix types
df["Close"] = df["Close"].astype(float)
df["Volume"] = df["Volume"].astype(int)
df = df.sort_values("Date").reset_index(drop=True) # sort oldest → newestDropped nulls on critical price columns — 0 rows removed after removing junk header rows.
df["Year"] = df["Date"].dt.year
df["Month_Name"] = df["Date"].dt.strftime("%b")
df["Quarter"] = df["Date"].dt.quarter
df["Weekday"] = df["Date"].dt.day_name()
df["Is_Month_End"] = df["Date"].dt.is_month_end
df["Is_Quarter_End"] = df["Date"].dt.is_quarter_end18+ new analytical columns across 6 categories:
| Category | Features |
|---|---|
| Price | Daily_Range, Daily_Price_Change, Open_to_Close_Change, Overnight_Gap |
| Returns | Daily_Return_%, Open_to_Close_%, Rolling_Monthly_Return_% |
| Moving Averages | SMA_7/21/50/200, EMA_7/21/50/200, EMA_SMA_Diff, Dist_from_SMA200_% |
| Volatility | Volatility_7/21/30d, Ann_Volatility_30d, ATR_14/30, BB_Upper/Mid/Lower/Width |
| Momentum | RSI_14/7, MACD, MACD_Signal, MACD_Hist, ROC_5/10/20 |
| Volume | Vol_SMA_20, Volume_Ratio, OBV, Volume_Spike |
# Top 10 highest close days
df.sort_values("Close", ascending=False).head(10)
# High volume bullish sessions
df[(df["High_Volume"] == True) & (df["Direction"] == "UP")]
# Days above 30-day moving average
df[df["Close"] > df["MA_30"]]# Yearly summary
df.groupby("Year").agg(
Avg_Close = ("Close", "mean"),
Max_Close = ("Close", "max"),
UP_Days = ("Direction", lambda x: (x == "UP").sum()),
Volatility = ("Daily_Return_%", "std")
)
# Monthly summary
df.groupby(["Year","Month","Month_Name"]).agg(
Open_Price = ("Open", "first"),
Close_Price = ("Close", "last"),
Total_Volume = ("Volume","sum"),
Trading_Days = ("Date", "count")
)sharpe = (returns.mean() / returns.std()) * np.sqrt(252) # 0.72
sortino = (returns.mean() / downside_std) * np.sqrt(252)
var_95 = np.percentile(returns, 5) # -2.72%
max_dd = df["Drawdown_%"].min() # -33.4%# Pivot table — avg close by year and quarter
df.pivot_table(values="Close", index="Year",
columns="Quarter", aggfunc="mean")
# Melt OHLC to long format
df[["Date","Open","High","Low","Close"]].melt(
id_vars="Date", value_vars=["Open","High","Low","Close"],
var_name="Price_Type", value_name="Price"
)Trends
- AAPL delivered +122.7% cumulative return from Jul 2021 to Jul 2026
- 2022 bear market caused a -33.4% max drawdown with volatility spiking above 40% annualized
- Strong 2023–2024 recovery — best annual return year was 2023; MACD turned bullish in early 2023 and stayed positive
- Q4 seasonality is consistent — November is the strongest calendar month every year; September is the weakest
- Volume trended down year-over-year, suggesting institutional accumulation over retail speculation
Key Insights
- Sharpe Ratio of 0.72 — decent but not exceptional risk-adjusted return; the 2022 drawdown weighs on this
- VaR (95%) of -2.72% — on 1 in 20 trading days, losses can exceed this level
- RSI below 30 in 2022 closely coincided with major price bottoms and subsequent rallies
- OBV trended upward throughout — buying pressure outweighed selling even during dips
- Bollinger Band Width compressed significantly in 2025–2026, preceding the breakout to ATH of $316.22
Recommendations
- Use SMA 200 as a directional filter — hold when above, reduce exposure when below
- Buy September dips — historically the weakest month, consistently followed by Q4 strength
- Use RSI < 35 + MACD crossover as a combined entry signal
- Size positions using 1.5–2× ATR_14 as stop-loss distance (~$4.50–$7.50)
- Increase allocation heading into October–November each year; reduce after December peak
| Chart | Description |
|---|---|
| Price + SMA 50/200 | Full 5-year price history with moving averages and bull/bear shading |
| Volume Trend | Daily volume with 20-day moving average (green=UP, red=DOWN days) |
| Monthly Returns | Bar chart of every month's return + seasonality average by calendar month |
| MACD + Volatility | MACD indicator with signal line and 30-day annualized volatility |
| Bollinger Bands | Dynamic support/resistance bands around 20-day SMA |
| RSI | 14-day RSI with overbought (70) and oversold (30) zones |
| Cumulative vs Drawdown | Total return vs peak-to-trough drawdown on dual axis |
| Annual Returns | Year-by-year return bar chart |
pip install pandas numpy matplotlibimport pandas as pd
import numpy as np
import matplotlib.pyplot as pltThis project is for educational purposes only and does not constitute financial advice. Past performance is not indicative of future results.
Analysis by Jonas Nwachukwu · AAPL Stock Analysis 2021–2026