Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Aligning Daily Market Price Series in Python

Welcome to the first lesson of the module on building your Python and Pine Script research workflow. In the previous modules, we've established the theoretical groundwork for understanding commodity markets and the companies that operate within them. Now, we will transition to the practical application of these concepts by developing the tools for quantitative analysis.

This lesson addresses a fundamental first step in any data-driven trading strategy: acquiring and preparing market data. You will learn how to retrieve daily price series for a variety of assets—commodities, equities, market benchmarks, and foreign exchange rates—and, critically, how to align them into a clean, unified dataset ready for analysis. This process of data retrieval and alignment is the bedrock upon which all subsequent quantitative research is built. Given your extensive background in development, you'll find the programmatic approach using Python both powerful and familiar.

1. Retrieving Market Data with yfinance

To analyze markets, we first need data. There are many commercial data providers, but for building and testing initial ideas, free sources can be highly effective. We will use yfinance, a popular Python library that allows you to download historical market data from Yahoo! Finance.

Let's begin by setting up your environment and downloading data for a few relevant instruments. The following video provides a concise guide to getting started.

yfinance Python Tutorial: Get Free Market Data

Watch this tutorial from the channel Analyzing Alpha to see how to install and use yfinance.

First, watch the section on installing and importing the necessary libraries, yfinance and pandas. Next, see how to download data for a single ticker and inspect the resulting DataFrame. Finally, observe the process for downloading data for multiple tickers at once, which is the method we'll be using. Pay attention to how the data is structured in a multi-level column format.

As the video demonstrates, we can download data for multiple tickers with a single command. For your goal of trading industrial metals and related companies, a typical analysis might involve comparing the price of a copper producer's stock against the price of copper itself, a broad market index, and a relevant currency pair.

Let's retrieve data for:

  • FCX: Freeport-McMoRan, a major copper producer on the NYSE.
  • HG=F: The ticker for Copper Futures, our commodity.
  • ^GSPC: The S&P 500 Index, a market benchmark.
  • GBPUSD=X: The exchange rate between the British Pound and the US Dollar.

Here is the Python code to do this. You can run this in a Jupyter notebook or your preferred environment.

import yfinance as yf
import pandas as pd

# Define the tickers and the date range for our analysis
tickers = ["FCX", "HG=F", "^GSPC", "GBPUSD=X"]
start_date = "2021-01-01"
end_date = "2023-12-31"

# Download the historical data
data = yf.download(tickers, start=start_date, end=end_date, interval="1d")

# Display the first few rows
print(data.head())

When you run this code, yfinance returns a pandas DataFrame. Notice the structure: the columns have two levels. The top level indicates the data type (e.g., 'Adj Close', 'Open', 'High', 'Low', 'Close', 'Volume'), and the second level contains the ticker symbol. This is the default group_by='column' behavior.

               Adj Close                                Close          ...
                    FCX        HG=F  GBPUSD=X      ^GSPC      FCX  ...
Date                                                                ...
2021-01-04  25.688826  3.547500  1.357194  3700.649902  26.790001  ...
2021-01-05  26.973402  3.633500  1.362201  3726.860107  28.139999  ...
2021-01-06  28.941999  3.693500  1.362991  3748.139893  30.190001  ...
...

For most of our analysis, we will focus on the adjusted closing price, which accounts for dividends and stock splits. Let's select just the 'Adj Close' data.

adj_close_data = data['Adj Close']
print(adj_close_data.tail())

Look closely at the last few rows of the output. You will likely see NaN (Not a Number) values. For example, GBPUSD=X might have a value for December 29, 2023, while FCX and ^GSPC show NaN because the NYSE was open, but it was a holiday in other markets.

This brings us to the core challenge of this lesson: aligning these disparate time series.

2. Aligning Time Series Data

Different assets trade on different exchanges with different calendars. Copper futures on COMEX, stocks on the NYSE, and FX markets all have unique holiday schedules. This results in misaligned data, represented by the NaN values we observed. To perform meaningful comparisons and calculations (like correlation), we must align the data onto a common timeline. This process is a form of data conditioning, essential for any rigorous time series analysis.

The definitive guide for time series manipulation in Python is Wes McKinney's book, Python for Data Analysis, as he is the creator of the pandas library.

Python for Data Analysis, 3E - 11 Time Series

First, read this section to understand the fundamentals of time series data in pandas. This will provide the conceptual foundation for the alignment techniques we will apply.

Focus on these key concepts: Date and Time Data Types: Read the introductory part of section 11.1, Date and Time Data Types, to understand how Python and pandas represent dates and times. The pandas.to_datetime function is particularly important. Time Series Basics: In section 11.2, review the material on how a pandas Series can be indexed by timestamps. Read from the beginning of the section up to the discussion on arithmetic operations, Time Series Basics. This demonstrates a crucial feature: operations between time series automatically align by date, which is the very behavior causing the NaNs we need to fix.

Strategies for Handling Missing Data

Now that we understand how pandas handles time series, let's address the NaN values. The video below provides a practical overview of the most common techniques.

Python Pandas Tutorial 5: Handle Missing Data: fillna, dropna, interpolate

This tutorial from codebasics offers a clear, step-by-step guide to handling missing data in pandas.

Watch the section on fillna to see how to replace NaNs with a specific value, like 0. Next, focus on the more appropriate method for time series: "forward fill" and "backward fill". Pay close attention to the explanation of ffill. The video also introduces a more sophisticated method, interpolate. Watch the segment on linear interpolation, and note how it can provide a more nuanced estimate than simply carrying a value forward. Finally, understand the dropna method, which allows you to remove rows that contain missing values.

Let's apply these strategies to our adj_close_data DataFrame.

  1. Dropping Missing Data (dropna): The simplest approach is to remove any row containing at least one NaN.

    # Drop rows with any NaN values
    aligned_data_dropna = adj_close_data.dropna()
    print(f"Original length: {len(adj_close_data)}, After dropna: {len(aligned_data_dropna)}")
    

    This ensures perfect alignment but can cause significant data loss. You discard valid data from one asset just because another wasn't trading.

  2. Forward-Filling Missing Data (fillna): A much more common and effective method for financial time series is forward-filling. This propagates the last known value forward. The logic is that if a market is closed, its price is unchanged from its previous close.

    # Forward-fill NaN values
    aligned_data_ffill = adj_close_data.fillna(method='ffill')
    
    # Check the last few rows again, NaNs should be gone
    print(aligned_data_ffill.tail())
    

    You might still see NaN values at the very beginning of the DataFrame if some series start later than others. We can chain a bfill (backward fill) or a dropna to clean these up.

    # A robust fill strategy
    aligned_data_ffill = adj_close_data.fillna(method='ffill').dropna()
    print(aligned_data_ffill.head())
    

This ffill().dropna() combination is a robust and widely-used technique for aligning daily financial price series.

3. A Complete Data Retrieval and Alignment Workflow

Let's consolidate everything into a single, reusable Python script. This workflow retrieves data for our chosen assets and produces a clean, aligned DataFrame of adjusted close prices.

import yfinance as yf
import pandas as pd

def get_aligned_prices(tickers, start_date, end_date, interval="1d"):
    """
    Retrieves and aligns daily adjusted close prices for a list of tickers.

    Args:
        tickers (list): A list of ticker symbols.
        start_date (str): The start date for the data (YYYY-MM-DD).
        end_date (str): The end date for the data (YYYY-MM-DD).
        interval (str): The data interval (e.g., '1d' for daily).

    Returns:
        pandas.DataFrame: A DataFrame with aligned daily adjusted close prices.
    """
    # Download the data
    data = yf.download(tickers, start=start_date, end=end_date, interval=interval)
    
    # Check if data was downloaded
    if data.empty:
        print("No data downloaded. Check ticker symbols and date range.")
        return None

    # Select the Adjusted Close prices
    adj_close = data['Adj Close']
    
    # In case of a single ticker, yfinance doesn't create a multilevel column.
    # We handle this to make the function robust.
    if isinstance(adj_close, pd.Series):
        adj_close = adj_close.to_frame(name=tickers[0])

    print(f"Original data points: {len(adj_close)}")

    # Forward-fill handles missing values from non-trading days
    aligned_prices = adj_close.fillna(method='ffill')
    
    # Drop any remaining NaN rows (usually at the beginning of the series)
    aligned_prices = aligned_prices.dropna()
    
    print(f"Aligned data points: {len(aligned_prices)}")

    return aligned_prices

# --- Main execution ---
# Define our assets of interest
target_tickers = ["FCX", "HG=F", "^GSPC", "GBPUSD=X"]
start_period = "2021-01-01"
end_period = "2023-12-31"

# Get the aligned data using our function
final_prices = get_aligned_prices(target_tickers, start_period, end_period)

# Display the result
if final_prices is not None:
    print("\n--- Aligned Prices (Head) ---")
    print(final_prices.head())
    print("\n--- Aligned Prices (Tail) ---")
    print(final_prices.tail())

This function encapsulates our entire process. It fetches the raw data, isolates the adjusted close prices, and then applies the forward-fill and drop-na strategy to produce a clean, analysis-ready dataset. You now have a foundational tool for your trading research workflow.

Conclusion

In this lesson, you have learned how to construct the first critical component of a quantitative analysis pipeline. We've moved from theory to practice, writing Python code to gather and prepare the raw data needed to test trading ideas.

Here are the key takeaways:

  • Data Retrieval: The yfinance library offers a straightforward way to download daily market data for equities, commodities, indices, and currencies directly into a pandas DataFrame.
  • The Alignment Problem: Raw price data from different sources is often misaligned due to different trading calendars, resulting in missing (NaN) values.
  • Data Alignment with pandas: We can solve this problem using pandas time series functions. The fillna(method='ffill') method is a standard and effective technique for creating a continuous price series by carrying forward the last valid observation.
  • Workflow Encapsulation: By wrapping the retrieval and alignment logic in a Python function, you've created a reusable and robust tool for all your future analyses.

In the next lesson, we will put this aligned dataset to immediate use. You will learn how to calculate fundamental metrics of price behavior: returns, volatility, drawdown, and correlation. These calculations are the next step in transforming raw price data into actionable market insights.

Can't find a good explanation? Sign up and we'll make it for you

Sign up