Infrastructure·System Monitoring·Intermediate

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.

data-storageinfrastructure

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

ConceptDescription
Data SourcingObtaining historical data from public APIs (e.g., Yahoo Finance).
Data ArchivingStoring fetched data locally for persistent access.
Retry MechanismsImplementing exponential backoff to handle transient API errors.
Data SummarizationBasic statistical overview of archived data.
VisualizationPlotting time series data to understand trends.
State ManagementUsing dictionaries to manage application state across functions.
LoggingRecording 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.

[34]
pip install yfinance pandas backoff matplotlib seaborn
Requirement 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.

[35]
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 sns

Core 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.

[36]
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 state

Function 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.

[37]
@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 state

Function 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.

[38]
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 state

Function 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.

[39]
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 state

Function 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.

[40]
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 state

Demonstration/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.

[41]
# 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.

[42]
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.")
cell output
cell output

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.

[43]
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.")
cell output

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 PracticeDescription
Robust Error HandlingImplement 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 MonitoringIntegrate 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 ManagementExternalize configurable parameters like API keys, file paths, ticker lists, and date ranges. Avoid hardcoding sensitive information or changing code for configuration updates.
Incremental UpdatesInstead 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 ValidationAfter 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 & StorageFor 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 & ParallelismWhen archiving data for many assets, use asynchronous fetching or parallel processing to speed up the collection process, respecting API rate limits.
SecurityProtect 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 yfinance to fetch historical stock data.
  • Robustness: Implementing backoff for 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.