pandas Stock Data: Clean Time Series Before Computing Signals
TECH INSIGHT / EXPLAINED
Before calculating an indicator, make the time axis and missing-data policy explicit. A sorted CSV is a useful starting point, but a strategy also needs a clear distinction between observations, signals and executable orders.
Local execution of the actual project
These captures show the existing project UI or functions executed locally with non-private sample inputs. Each caption states the scope of the execution.
Use a small reproducible dataset
The three rows below are synthetic and intentionally unsorted. Reading them produces an index starting at 1.0; the sequence 100→110→99 ends at 0.99 with a 10% drawdown from 110. Including the initial value prevents the first loss from disappearing from the drawdown baseline.
import pandas as pd
from io import StringIO
csv = """date,close,volume
2026-01-07,99,300
2026-01-05,100,100
2026-01-06,110,200
"""
df = pd.read_csv(StringIO(csv)) # replace with a CSV file path
df['date'] = pd.to_datetime(df['date'], errors='raise')
df = df.set_index('date').sort_index()
if not df.index.is_unique:
raise ValueError('duplicate observation dates')
for col in ['close', 'volume']:
df[col] = pd.to_numeric(df[col], errors='raise')
if df[['close', 'volume']].isna().any().any() or not (df.close > 0).all():
raise ValueError('missing or invalid observations')
df['ma2'] = df.close.rolling(2, min_periods=2).mean()
df['ret'] = df.close.pct_change(fill_method=None)
df['index_value'] = df.close / df.close.iloc[0]
df['drawdown'] = df.index_value / df.index_value.cummax() - 1
assert abs(df.index_value.iloc[-1] - .99) < 1e-12
assert abs(df.drawdown.min() + .10) < 1e-12Validate more than dtypes
Check duplicates, finite numeric values, non-negative volume and, when OHLC is supplied, high/low consistency. A symbol with a leading zero must remain text. For real instruments, document adjustments for splits and distributions. The price index above is not automatically a total-return series.
rolling(20) means twenty observations, not twenty calendar days. Market holidays are not missing trading data. Compare the expected session calendar with the actual index before deciding a gap is an error or forward-filling it.
Avoid future information in filters
For a volume-spike decision at bar t, use a historical baseline such as volume.rolling(20).mean().shift(1). Using the entire file’s mean leaks future observations into earlier decisions. Retrospective descriptive analysis may use a full-period mean, but label that different purpose.
Compute a crossover from both current and previous averages. Keep warm-up values as unavailable and use dropna(subset=[the required indicators]) only where needed; dropping every missing row can silently alter unrelated calculations.
Align the signal with the fill model
A signal based on a completed close is available only after that close is known. Model a later executable price and its costs. shift(1) is an alignment tool, not proof that an assumed next-bar fill is achievable. Also state whether a row label represents bar start, bar end or exchange session date.
Save the cleaned data and the transformation settings together. UTF-8 CSV is suitable for programmatic use; UTF-8 with BOM can help some spreadsheet applications. Keep a pristine source copy so a cleaning choice can be audited.
Sources and further reading
pandas percentage change · pandas time series
Content and examples reviewed: 2026-09-29
댓글
댓글 쓰기