Archive Historical Data
Archive historical market data with efficient columnar compression formats and temporal partitioning for cost-effective long-term storage, enabling multi-year quantitative research on extensive historical datasets without burdening or competing with live production database resources.
Archive Historical Trading Data Easily
This notebook demonstrates how to effectively archive historical trading data. Accessing and storing reliable historical data is crucial for backtesting strategies, conducting quantitative analysis, and training machine learning models in finance.
Table of Concepts
| Concept | Description |
|---|---|
| Data Sourcing | Obtaining historical data from public APIs (e.g., Yahoo Finance). |
| Data Archiving | Storing fetched data locally for persistent access. |
| Retry Mechanisms | Implementing exponential backoff to handle transient API errors. |
| Data Summarization | Basic statistical overview of archived data. |
| Visualization | Plotting time series data to understand trends. |
| State Management | Using dictionaries to manage application state across functions. |
| Logging | Recording events and errors for debugging and monitoring. |
Dependency Installation
This section installs all necessary Python packages required for the notebook. We will use yfinance to fetch historical stock data and backoff for robust API calls.
pip install yfinance pandas backoff matplotlib seabornRequirement already satisfied: yfinance in /usr/local/lib/python3.12/dist-packages (0.2.66) Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2) Requirement already satisfied: backoff in /usr/local/lib/python3.12/dist-packages (2.2.1) Requirement already satisfied: matplotlib in /usr/local/lib/python3.12/dist-packages (3.10.0) Requirement already satisfied: seaborn in /usr/local/lib/python3.12/dist-packages (0.13.2) Requirement already satisfied: numpy>=1.16.5 in /usr/local/lib/python3.12/dist-packages (from yfinance) (2.0.2) Requirement already satisfied: requests>=2.31 in /usr/local/lib/python3.12/dist-packages (from yfinance) (2.32.4) Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.12/dist-packages (from yfinance) (0.0.13) Requirement already satisfied: platformdirs>=2.0.0 in /usr/local/lib/python3.12/dist-packages (from yfinance) (4.10.0) Requirement already satisfied: pytz>=2022.5 in /usr/local/lib/python3.12/dist-packages (from yfinance) (2025.2) Requirement already satisfied: frozendict>=2.3.4 in /usr/local/lib/python3.12/dist-packages (from yfinance) (2.4.7) Requirement already satisfied: peewee>=3.16.2 in /usr/local/lib/python3.12/dist-packages (from yfinance) (4.0.6) Requirement already satisfied: beautifulsoup4>=4.11.1 in /usr/local/lib/python3.12/dist-packages (from yfinance) (4.13.5) Requirement already satisfied: curl_cffi>=0.7 in /usr/local/lib/python3.12/dist-packages (from yfinance) (0.15.0) Requirement already satisfied: protobuf>=3.19.0 in /usr/local/lib/python3.12/dist-packages (from yfinance) (5.29.6) Requirement already satisfied: websockets>=13.0 in /usr/local/lib/python3.12/dist-packages (from yfinance) (15.0.1) Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0) Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2026.2) Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.3.3) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (0.12.1) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (4.63.0) Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.5.0) Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (26.2) Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (11.3.0) Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (3.3.2) Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.12/dist-packages (from beautifulsoup4>=4.11.1->yfinance) (2.8.4) Requirement already satisfied: typing-extensions>=4.0.0 in /usr/local/lib/python3.12/dist-packages (from beautifulsoup4>=4.11.1->yfinance) (4.15.0) Requirement already satisfied: cffi>=2.0.0 in /usr/local/lib/python3.12/dist-packages (from curl_cffi>=0.7->yfinance) (2.0.0) Requirement already satisfied: certifi>=2024.2.2 in /usr/local/lib/python3.12/dist-packages (from curl_cffi>=0.7->yfinance) (2026.5.20) Requirement already satisfied: rich in /usr/local/lib/python3.12/dist-packages (from curl_cffi>=0.7->yfinance) (13.9.4) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0) Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.12/dist-packages (from requests>=2.31->yfinance) (3.4.7) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.12/dist-packages (from requests>=2.31->yfinance) (3.18) Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.12/dist-packages (from requests>=2.31->yfinance) (2.5.0) Requirement already satisfied: pycparser in /usr/local/lib/python3.12/dist-packages (from cffi>=2.0.0->curl_cffi>=0.7->yfinance) (3.0) Requirement already satisfied: markdown-it-py>=2.2.0 in /usr/local/lib/python3.12/dist-packages (from rich->curl_cffi>=0.7->yfinance) (4.2.0) Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /usr/local/lib/python3.12/dist-packages (from rich->curl_cffi>=0.7->yfinance) (2.20.0) Requirement already satisfied: mdurl~=0.1 in /usr/local/lib/python3.12/dist-packages (from markdown-it-py>=2.2.0->rich->curl_cffi>=0.7->yfinance) (0.1.2)
Library Imports
This section imports all required libraries. Standard libraries are imported first, followed by third-party libraries.
import datetime
import time
import logging
import os
from collections import deque
import pandas as pd
import yfinance as yf
import backoff
import matplotlib.pyplot as plt
import seaborn as snsCore Functions
This section defines the core functions for data archiving. Each function is presented with a detailed markdown header, a complete docstring, type hints, and logger statements, as per the requirements.
Function Name: create_state
This function initializes the application's state dictionary. It sets up a basic logger to record important events, warnings, and debug messages, ensuring that all subsequent operations can log their activities consistently.
Parameters: None
Returns:
dict: An initial state dictionary containing a configured logger.
def create_state() -> dict:
"""
Initializes the application state dictionary, including a logger.
Returns
-------
dict
An initial state dictionary containing a configured logger.
"""
state = {}
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
state['logger'] = logging.getLogger(__name__)
state['logger'].info("Application state initialized and logger configured.")
return stateFunction Name: fetch_historical_data
This function fetches historical trading data for a given ticker from Yahoo Finance. It uses the yfinance library for data retrieval and incorporates an exponential backoff mechanism using the backoff library to handle potential API rate limits or transient network errors gracefully. The data includes open, high, low, close prices, adjusted close, and volume.
Parameters:
state (dict): The current state dictionary containing the logger.
ticker (str): The stock ticker symbol (e.g., 'AAPL', 'MSFT').
start_date (str): The start date for data fetching in 'YYYY-MM-DD' format.
end_date (str): The end date for data fetching in 'YYYY-MM-DD' format.
Returns:
dict: The updated state dictionary, including the fetched data as a pandas DataFrame under a key named after the ticker (e.g., state['AAPL_data']). Returns an empty DataFrame if data fetching fails after retries.
@backoff.on_exception(backoff.expo, (Exception, ConnectionError, TimeoutError), max_tries=5, factor=2, jitter=backoff.full_jitter)
def fetch_historical_data(state: dict, ticker: str, start_date: str, end_date: str) -> dict:
"""
Fetches historical trading data for a given ticker using yfinance with exponential backoff.
Parameters
----------
state : dict
Current state dictionary with a logger.
ticker : str
The stock ticker symbol (e.g., 'AAPL').
start_date : str
The start date for data fetching in 'YYYY-MM-DD' format.
end_date : str
The end date for data fetching in 'YYYY-MM-DD' format.
Returns
-------
dict
Updated state with fetched data (pandas DataFrame) or an empty DataFrame if failed.
"""
logger = state['logger']
logger.info(f"Attempting to fetch data for {ticker} from {start_date} to {end_date}")
try:
data = yf.download(ticker, start=start_date, end=end_date)
if not data.empty:
logger.info(f"Successfully fetched {len(data)} rows for {ticker}.")
state[f'{ticker}_data'] = data
else:
logger.warning(f"No data fetched for {ticker} between {start_date} and {end_date}.")
state[f'{ticker}_data'] = pd.DataFrame()
except Exception as e:
logger.error(f"Failed to fetch data for {ticker} after retries: {e}")
state[f'{ticker}_data'] = pd.DataFrame() # Ensure key exists even on failure
return stateFunction Name: store_data_local
This function is responsible for storing a pandas DataFrame to a local CSV file. It ensures that the target directory exists before attempting to write the file. This provides a robust way to persist fetched historical data, preventing re-fetching data unnecessarily and allowing for offline analysis. The data is saved with the index (dates) included, which is typical for time-series financial data.
Parameters:
state (dict): The current state dictionary containing the logger.
data_df (pd.DataFrame): The DataFrame containing the historical data to be stored.
file_path (str): The full path including the filename where the data should be saved (e.g., 'data/AAPL.csv').
Returns:
dict: The updated state dictionary. If the operation is successful, a confirmation message is logged. If an error occurs during saving, an error message is logged.
def store_data_local(state: dict, data_df: pd.DataFrame, file_path: str) -> dict:
"""
Stores a pandas DataFrame to a local CSV file.
Parameters
----------
state : dict
Current state dictionary with a logger.
data_df : pd.DataFrame
The DataFrame containing the historical data to be stored.
file_path : str
The full path including the filename where the data should be saved.
Returns
-------
dict
Updated state dictionary.
"""
logger = state['logger']
try:
os.makedirs(os.path.dirname(file_path), exist_ok=True)
data_df.to_csv(file_path, index=True)
logger.info(f"Data successfully stored to {file_path}")
except Exception as e:
logger.error(f"Failed to store data to {file_path}: {e}")
return stateFunction Name: load_data_local
This function loads historical data from a local CSV file into a pandas DataFrame. It's designed to read data previously stored by store_data_local. The function automatically parses the 'Date' column as datetime objects and sets it as the DataFrame index, which is standard practice for time-series data, ensuring correct chronological order and enabling time-based operations.
Parameters:
state (dict): The current state dictionary containing the logger.
file_path (str): The full path to the CSV file to be loaded (e.g., 'data/AAPL.csv').
ticker (str): The ticker symbol associated with the data (e.g., 'AAPL') to name the key in the state.
Returns:
dict: The updated state dictionary, including the loaded data as a pandas DataFrame under a key like state['AAPL_loaded_data']. Returns an empty DataFrame if the file cannot be found or read.
def load_data_local(state: dict, file_path: str, ticker: str) -> dict:
"""
Loads a pandas DataFrame from a local CSV file.
Parameters
----------
state : dict
Current state dictionary with a logger.
file_path : str
The full path to the CSV file to be loaded.
ticker : str
The ticker symbol to name the key in the state.
Returns
-------
dict
Updated state with loaded data (pandas DataFrame) or an empty DataFrame if failed.
"""
logger = state['logger']
try:
# Read with multi-level header and set the first column (Date) as index.
# This handles the specific format yfinance produces in some environments.
loaded_df = pd.read_csv(file_path, header=[0, 1], index_col=0, parse_dates=True)
# Drop the second level of the MultiIndex (e.g., 'BTC-USD')
loaded_df.columns = loaded_df.columns.droplevel(1)
# The first column is now 'Price' (from the first header row) which is a placeholder.
# We need to drop this column to get only the data columns.
# Ensure we only drop if 'Price' column exists and is not a data column.
if 'Price' in loaded_df.columns:
loaded_df = loaded_df.drop(columns=['Price'])
# Convert relevant columns to numeric, coercing errors to NaN
numeric_cols = ['Close', 'High', 'Low', 'Open', 'Volume', 'Adj Close'] # Include Adj Close if present
for col in numeric_cols:
if col in loaded_df.columns:
loaded_df[col] = pd.to_numeric(loaded_df[col], errors='coerce')
logger.info(f"Data successfully loaded from {file_path}. Shape: {loaded_df.shape}")
state[f'{ticker}_loaded_data'] = loaded_df
except FileNotFoundError:
logger.warning(f"File not found at {file_path}. Returning empty DataFrame.")
state[f'{ticker}_loaded_data'] = pd.DataFrame()
except Exception as e:
logger.error(f"Failed to load data from {file_path}: {e}")
state[f'{ticker}_loaded_data'] = pd.DataFrame()
return stateFunction Name: summarize_historical_data
This function calculates and displays key summary statistics for the historical trading data within a pandas DataFrame. It provides insights into the data's central tendency, dispersion, and shape. Specifically, it computes descriptive statistics for numerical columns and displays the first and last few rows to give a quick overview of the data range. This is essential for understanding the quality and characteristics of the archived data.
Parameters:
state (dict): The current state dictionary containing the logger.
data_df (pd.DataFrame): The DataFrame containing the historical data to be summarized.
ticker (str): The ticker symbol for which the data is being summarized, used for logging and contextual output.
Returns:
dict: The updated state dictionary. It logs the summary statistics and the head/tail of the DataFrame.
def summarize_historical_data(state: dict, data_df: pd.DataFrame, ticker: str) -> dict:
"""
Calculates and displays summary statistics for the historical data.
Parameters
----------
state : dict
Current state dictionary with a logger.
data_df : pd.DataFrame
The DataFrame containing the historical data to be summarized.
ticker : str
The ticker symbol for the data being summarized.
Returns
-------
dict
Updated state dictionary.
"""
logger = state['logger']
if data_df.empty:
logger.warning(f"No data to summarize for {ticker}. DataFrame is empty.")
return state
logger.info(f"Displaying summary for {ticker}:")
print(f"\n--- Summary for {ticker} ---")
print("Shape:", data_df.shape)
print("\nFirst 5 rows:")
display(data_df.head())
print("\nLast 5 rows:")
display(data_df.tail())
print("\nDescriptive Statistics:")
display(data_df.describe())
print("\nMissing values:")
display(data_df.isnull().sum().to_frame(name='Missing Count'))
state[f'{ticker}_summary'] = data_df.describe().to_dict()
return stateDemonstration/Visualization
This section demonstrates the usage of the core functions by fetching, storing, loading, summarizing, and visualizing historical trading data for a few example tickers. It includes plots to illustrate price movements and displays summary statistics in a tabular format.
# Initialize the state
app_state = create_state()
logger = app_state['logger']
# Define parameters for demonstration
tickers = ['BTC-USD', 'ETH-USD']
start_date = '2023-01-01'
end_date = '2025-01-01'
archive_dir = 'archived_data'
# Ensure the archive directory exists
os.makedirs(archive_dir, exist_ok=True)
# --- Step 1: Fetch and store data for each ticker ---
for ticker in tickers:
logger.info(f"Processing ticker: {ticker}")
app_state = fetch_historical_data(app_state, ticker, start_date, end_date)
fetched_data = app_state.get(f'{ticker}_data')
if fetched_data is not None and not fetched_data.empty:
file_path = os.path.join(archive_dir, f'{ticker}.csv')
app_state = store_data_local(app_state, fetched_data, file_path)
else:
logger.warning(f"No data to store for {ticker}.")
# --- Step 2: Load data from local storage and summarize ---
all_loaded_data = {}
for ticker in tickers:
file_path = os.path.join(archive_dir, f'{ticker}.csv')
app_state = load_data_local(app_state, file_path, ticker)
loaded_data_df = app_state.get(f'{ticker}_loaded_data')
if loaded_data_df is not None and not loaded_data_df.empty:
all_loaded_data[ticker] = loaded_data_df
app_state = summarize_historical_data(app_state, loaded_data_df, ticker)
else:
logger.error(f"Could not load data for {ticker} from {file_path}.")/tmp/ipykernel_569/2747763590.py:25: FutureWarning: YF.download() has changed argument auto_adjust default to True data = yf.download(ticker, start=start_date, end=end_date) [*********************100%***********************] 1 of 1 completed /tmp/ipykernel_569/2747763590.py:25: FutureWarning: YF.download() has changed argument auto_adjust default to True data = yf.download(ticker, start=start_date, end=end_date) [*********************100%***********************] 1 of 1 completed
--- Summary for BTC-USD --- Shape: (731, 5) First 5 rows:
| Price | Close | High | Low | Open | Volume |
|---|---|---|---|---|---|
| Date | |||||
| 2023-01-01 | 16625.080078 | 16630.439453 | 16521.234375 | 16547.914062 | 9244361700 |
| 2023-01-02 | 16688.470703 | 16759.343750 | 16572.228516 | 16625.509766 | 12097775227 |
| 2023-01-03 | 16679.857422 | 16760.447266 | 16622.371094 | 16688.847656 | 13903079207 |
| 2023-01-04 | 16863.238281 | 16964.585938 | 16667.763672 | 16680.205078 | 18421743322 |
| 2023-01-05 | 16836.736328 | 16884.021484 | 16790.283203 | 16863.472656 | 13692758566 |
Last 5 rows:
| Price | Close | High | Low | Open | Volume |
|---|---|---|---|---|---|
| Date | |||||
| 2024-12-27 | 94164.859375 | 97294.843750 | 93310.742188 | 95704.976562 | 52419934565 |
| 2024-12-28 | 95163.929688 | 95525.898438 | 94014.289062 | 94160.187500 | 24107436185 |
| 2024-12-29 | 93530.226562 | 95174.875000 | 92881.789062 | 95174.054688 | 29635885267 |
| 2024-12-30 | 92643.210938 | 94903.320312 | 91317.132812 | 93527.195312 | 56188003691 |
| 2024-12-31 | 93429.203125 | 96090.601562 | 91914.031250 | 92643.250000 | 43625106843 |
Descriptive Statistics:
| Price | Close | High | Low | Open | Volume |
|---|---|---|---|---|---|
| count | 731.000000 | 731.000000 | 731.000000 | 731.000000 | 7.310000e+02 |
| mean | 47437.161067 | 48255.729437 | 46474.866471 | 47332.259656 | 2.785814e+10 |
| std | 21675.093627 | 22139.627068 | 21128.637216 | 21639.069063 | 1.921014e+10 |
| min | 16625.080078 | 16630.439453 | 16521.234375 | 16547.914062 | 5.331173e+09 |
| 25% | 27778.728516 | 28185.916992 | 27245.493164 | 27756.757812 | 1.483281e+10 |
| 50% | 42658.667969 | 43354.296875 | 41826.335938 | 42641.511719 | 2.299209e+10 |
| 75% | 64148.892578 | 65558.232422 | 62788.615234 | 64109.300781 | 3.431423e+10 |
| max | 106140.601562 | 108268.445312 | 105291.734375 | 106147.296875 | 1.492189e+11 |
Missing values:
| Missing Count | |
|---|---|
| Price | |
| Close | 0 |
| High | 0 |
| Low | 0 |
| Open | 0 |
| Volume | 0 |
--- Summary for ETH-USD --- Shape: (731, 5) First 5 rows:
| Price | Close | High | Low | Open | Volume |
|---|---|---|---|---|---|
| Date | |||||
| 2023-01-01 | 1200.964844 | 1203.475342 | 1192.885376 | 1196.713623 | 2399674550 |
| 2023-01-02 | 1214.656616 | 1219.860596 | 1195.214966 | 1201.103271 | 3765758498 |
| 2023-01-03 | 1214.778809 | 1219.095337 | 1207.491577 | 1214.744019 | 3392972131 |
| 2023-01-04 | 1256.526611 | 1264.807495 | 1213.168823 | 1214.718628 | 6404416893 |
| 2023-01-05 | 1250.438599 | 1258.571533 | 1245.173096 | 1256.484619 | 4001786456 |
Last 5 rows:
| Price | Close | High | Low | Open | Volume |
|---|---|---|---|---|---|
| Date | |||||
| 2024-12-27 | 3328.916992 | 3436.710693 | 3302.575684 | 3331.053711 | 24091627403 |
| 2024-12-28 | 3397.902344 | 3419.920166 | 3318.033936 | 3328.774658 | 14305648523 |
| 2024-12-29 | 3349.513428 | 3406.648438 | 3321.664795 | 3397.862549 | 13440907792 |
| 2024-12-30 | 3356.392578 | 3428.527344 | 3298.804443 | 3349.585938 | 26981583962 |
| 2024-12-31 | 3332.531738 | 3444.396729 | 3311.412598 | 3356.394775 | 20845452085 |
Descriptive Statistics:
| Price | Close | High | Low | Open | Volume |
|---|---|---|---|---|---|
| count | 731.000000 | 731.000000 | 731.000000 | 731.000000 | 7.310000e+02 |
| mean | 2420.897072 | 2470.367148 | 2365.350447 | 2417.990406 | 1.333297e+10 |
| std | 738.200234 | 761.483960 | 711.295830 | 738.735322 | 1.005866e+10 |
| min | 1200.964844 | 1203.475342 | 1192.885376 | 1196.713623 | 2.081626e+09 |
| 25% | 1807.390259 | 1835.869568 | 1780.568542 | 1805.646423 | 6.804868e+09 |
| 50% | 2281.471191 | 2322.021484 | 2225.081055 | 2274.437744 | 1.055808e+10 |
| 75% | 3089.519409 | 3158.157837 | 3018.964478 | 3080.323242 | 1.613200e+10 |
| max | 4066.445068 | 4106.955566 | 3974.176270 | 4066.690430 | 6.766813e+10 |
Missing values:
| Missing Count | |
|---|---|
| Price | |
| Close | 0 |
| High | 0 |
| Low | 0 |
| Open | 0 |
| Volume | 0 |
Visualization: Historical Closing Prices
This plot displays the historical closing prices for the fetched and archived tickers. A time-series line plot is an effective way to visualize trends, volatility, and general price movements over the specified period. Each ticker's closing price is shown on the same graph for easy comparison of relative performance.
if all_loaded_data:
for ticker, df in all_loaded_data.items():
if not df.empty:
plt.figure(figsize=(14, 7))
plt.plot(df.index, df['Close'], label=f'{ticker} Close Price') # Access 'Close' directly
plt.title(f'Historical Closing Prices for {ticker} ({start_date} to {end_date})') # Dynamic title
plt.xlabel('Date')
plt.ylabel('Close Price (USD)')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
else:
logger.warning("No data available to plot historical closing prices.")Visualization: Daily Returns Distribution
This set of subplots visualizes the daily percentage returns distribution for each ticker. Histograms and Kernel Density Estimates (KDE) help in understanding the frequency and probability density of different return magnitudes, offering insights into the risk characteristics and typical return behavior of the assets. A rolling window of average daily returns is also included to demonstrate deque usage.
if all_loaded_data:
fig, axes = plt.subplots(len(tickers), 2, figsize=(15, 5 * len(tickers)))
fig.suptitle('Daily Returns Distribution and Rolling Average', y=1.02)
for i, (ticker, df_original) in enumerate(all_loaded_data.items()):
if not df_original.empty:
# Make an explicit copy to avoid SettingWithCopyWarning
df = df_original.copy()
# Access 'Close' directly for daily return calculation
df['Daily Return'] = df['Close'].pct_change(fill_method=None) * 100 # Added fill_method=None
df = df.dropna()
# Plotting Daily Returns Distribution
sns.histplot(df['Daily Return'], kde=True, ax=axes[i, 0], bins=50)
axes[i, 0].set_title(f'{ticker} Daily Returns Distribution')
axes[i, 0].set_xlabel('Daily Return (%)')
axes[i, 0].set_ylabel('Frequency')
axes[i, 0].grid(True, linestyle='--', alpha=0.7)
# Demonstrate deque for rolling window of average returns
window_size = 30 # 30-day rolling window
returns_window = deque(maxlen=window_size)
rolling_averages = []
for ret in df['Daily Return']:
returns_window.append(ret)
if len(returns_window) == window_size:
rolling_averages.append(sum(returns_window) / window_size)
else:
rolling_averages.append(None) # Pad with None until window is full
df['Rolling Avg Return'] = rolling_averages
# Update the original dictionary with the modified DataFrame
all_loaded_data[ticker] = df
# Plotting Rolling Average Daily Returns
axes[i, 1].plot(df.index, df['Rolling Avg Return'], label=f'{ticker} {window_size}-Day Rolling Avg Return', color='orange')
axes[i, 1].set_title(f'{ticker} {window_size}-Day Rolling Average Daily Returns')
axes[i, 1].set_xlabel('Date')
axes[i, 1].set_ylabel('Rolling Avg Return (%)')
axes[i, 1].legend()
axes[i, 1].grid(True, linestyle='--', alpha=0.7)
plt.tight_layout(rect=[0, 0.03, 1, 0.98])
plt.show()
else:
logger.warning("No data available to plot daily returns distribution.")Production Considerations
When deploying data archiving solutions in a production environment, several best practices should be followed to ensure reliability, scalability, and maintainability. This table outlines key considerations.
| Best Practice | Description |
|---|---|
| Robust Error Handling | Implement comprehensive try/except blocks around external API calls and file operations. Use retry mechanisms with exponential backoff and jitter to handle transient failures gracefully. |
| Logging and Monitoring | Integrate detailed logging (info, warning, error, debug) to track execution flow, data fetching status, and any anomalies. Use monitoring tools to alert on failures or performance bottlenecks. |
| Configuration Management | Externalize configurable parameters like API keys, file paths, ticker lists, and date ranges. Avoid hardcoding sensitive information or changing code for configuration updates. |
| Incremental Updates | Instead of re-fetching all historical data, implement logic to fetch only new data (e.g., from the last archived date to the present) to save bandwidth and processing time. |
| Data Validation | After fetching and loading, validate data integrity (e.g., check for missing values, correct data types, chronological order, sensible price ranges). Implement alerts for data quality issues. |
| Data Compression & Storage | For large datasets, consider compressed formats (e.g., Parquet, Feather) or database solutions (e.g., PostgreSQL, data warehouses) instead of plain CSVs for better performance and storage efficiency. |
| Concurrency & Parallelism | When archiving data for many assets, use asynchronous fetching or parallel processing to speed up the collection process, respecting API rate limits. |
| Security | Protect sensitive data and API keys. Use secure storage solutions and ensure proper access controls. |
Conclusion
This notebook provides a foundational framework for archiving historical trading data. We've covered:
- Data Acquisition: Using
yfinanceto fetch historical stock data. - Robustness: Implementing
backofffor resilient API calls with retries and jitter. - Local Storage: Persisting data to CSV files for offline access and future use.
- State Management: Utilizing Python dictionaries to maintain application state and a centralized logger.
- Data Summarization & Visualization: Generating basic statistics and time-series plots to understand the data.
This setup forms a solid base for more advanced financial analysis, strategy backtesting, and machine learning applications requiring reliable historical data feeds.