Dates are not strings
A price series is a time series: what matters is when each price happened. Dates read from a file are plain strings, and a string has no idea that March comes after February or that a Monday follows a Sunday. Convert them with pd.to_datetime and use the result as the index: a DatetimeIndex holds real timestamps that pandas can sort, slice, compare and take apart. Always sort it (sort_index()) so that the oldest observation comes first.
import pandas as pd
raw_dates = ["2026-03-04", "2026-03-02", "2026-03-03"] # strings, in a messy order
closes = [102.0, 100.0, 101.0]
series = pd.Series(closes, index=pd.to_datetime(raw_dates), name="close").sort_index()
print(series)
# 2026-03-02 100.0
# 2026-03-03 101.0
# 2026-03-04 102.0
# Name: close, dtype: float64
print(isinstance(series.index, pd.DatetimeIndex)) # TrueBuilding a calendar
You often need dates that nobody gave you: a grid to compare against, or the days of a backtest. pd.date_range(start, periods=n, freq="D") makes n consecutive calendar days, and pd.bdate_range skips weekends and gives business days. (It knows nothing about holidays.) The freq argument sets the step: "D" is a day, "B" a business day, "h" an hour.
# Four calendar days, then six business days (the weekend is skipped)
print(pd.date_range("2026-03-01", periods=4, freq="D").strftime("%a %d %b").tolist())
# ['Sun 01 Mar', 'Mon 02 Mar', 'Tue 03 Mar', 'Wed 04 Mar']
days = pd.bdate_range("2026-02-26", periods=6)
print(days.strftime("%a %d %b").tolist())
# ['Thu 26 Feb', 'Fri 27 Feb', 'Mon 02 Mar', 'Tue 03 Mar', 'Wed 04 Mar', 'Thu 05 Mar']Selecting by date
With a sorted DatetimeIndex, .loc understands dates written as strings. A partial string such as "2026-03" selects the whole month, "2026" the whole year and "2026-03-04" a single day. A slice .loc["2026-03-04":"2026-03-06"] selects a range, and unlike a slice of positions both ends are included. The ends need not exist in the data: weekend dates simply match nothing, which is what you want when you ask for "everything from the first of the month".
prices = pd.Series(
[float(i) for i in range(20)],
index=pd.bdate_range("2026-02-23", periods=20), # Mon 23 Feb ... Fri 20 Mar
)
print(len(prices.loc["2026-03"])) # 15 the business days of March in the data
print(prices.loc["2026-03-04"]) # 7.0 a single day
print(prices.loc["2026-03-04":"2026-03-06"])
# 2026-03-04 7.0
# 2026-03-05 8.0
# 2026-03-06 9.0
# Freq: B, dtype: float64
print(prices.loc["2026-03-01":"2026-03-03"].tolist()) # [5.0, 6.0] Sunday the 1st matches nothingParts of a date
A DatetimeIndex exposes the parts of every date as attributes: year, month, day, dayofweek (Monday is 0, Sunday is 6). Each one is an array with one entry per row, so it can be used as a mask or as a group key. This is how you ask "how many observations fall on each weekday?" or "what happens on Mondays?" without a loop. Remember to sort the index first: slicing a DatetimeIndex that is out of order can give surprising results or an error.
# The parts of the dates, one entry per row
print(prices.index.dayofweek[:7].tolist()) # [0, 1, 2, 3, 4, 0, 1] Monday is 0
print(prices.index.month[:7].tolist()) # [2, 2, 2, 2, 2, 3, 3]
# How many observations fall on each weekday?
print(prices.groupby(prices.index.dayofweek).size().to_dict()) # {0: 4, 1: 4, 2: 4, 3: 4, 4: 4}
# What do the Mondays look like?
print(prices[prices.index.dayofweek == 0].tolist()) # [0.0, 5.0, 10.0, 15.0].loc["2026-03-04":"2026-03-06"] includes the 6th, but with intraday data it stops at 00:00 on the 6th, losing the whole day. Check the last timestamp of the result when your data is finer than a day.pd.to_datetime(strings)makes a DatetimeIndex;sort_index()puts the oldest first;date_rangeandbdate_rangebuild calendars..loc["2026-03"]selects a month,.loc["2026-03-04":"2026-03-06"]a range with both ends included.index.year,index.month,index.dayofweekgive the parts of every date, to filter or group without loops.
Next up: resampling, turning daily prices into weekly, monthly or even finer bars.
Lab · what's graded
- Build a dated price series with
parse_prices25 pts - Select one month with
prices_in_month25 pts - Select a date range with
prices_between25 pts - Count the observations per weekday with
count_by_weekday25 pts