Recommended resources
The freely available resources this lesson will direct you to.
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.
-
Dropping Missing Data (
dropna): The simplest approach is to remove any row containing at least oneNaN.# 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.
-
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
NaNvalues at the very beginning of the DataFrame if some series start later than others. We can chain abfill(backward fill) or adropnato 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
yfinancelibrary 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.