Daily bars are noisy. A monthly view answers "how did each month do?", the workhorse of performance reports and factor research.
Do NOT slice by position (every 21st row): month lengths vary (18-23 trading days), and a fixed step drifts off the true month-ends. pandas has the right tool:resample , the time-series version ofgroupby .
Pythonmonthly_close = close.resample('ME').last() # ME = calendar Month-End
-resample('ME')groups a DatetimeIndex into calendar month-end buckets (use'ME'on modern pandas;'M'is the old, deprecated spelling).
-.last()takes the final close in each month. Other aggregations:.first() ,.max() ,.mean() ,.sum()(for volume), or.ohlc()for a full monthly candle.
.pct_change()turns the month-end levels into month-over-month returns.This is downsampling (daily to monthly). The reverse, upsampling, uses.resample('D').ffill()to carry a lower-frequency series forward.
The starter samples every 21st row, a naive stand-in that lands on the wrong dates. Swap that one line forclose.resample('ME').last()so the buckets are true month-ends, then read off the monthly returns.