• GuruFinance Insights
  • Posts
  • The Minimalist’s Guide to Testing a Profitable Trading Strategy in Python

The Minimalist’s Guide to Testing a Profitable Trading Strategy in Python

How to validate a trading edge in less than 50 lines of code

In partnership with

`

This Stock is Up 220% and Primed for the Next Breakout

Bank of America analysts predict gold will hit $3,000 by 2025 — and this hidden gold stock is set to benefit.

With gold's post-election dip, now could be a good opportunity to consider adding to your portfolio. Savvy investors understand the value of holding gold and gold stocks.

This stock has made impressive gains in recent years, and with insiders continuing to buy, it's one to keep on your watchlist.

P.S. The last gold stock we highlighted in this newsletter saw a strong rally, climbing over 60% just days after our feature. Be sure to keep this one on your watchlist!

This is a sponsored advertisement on behalf of Four Nines Gold. Past performance does not guarantee future results. Investing involves risk. View the full disclaimer here: https://shorturl.at/73AF8

Exciting News: Paid Subscriptions Have Launched! 🚀

On September 1, we officially rolled out our new paid subscription plans at GuruFinance Insights, offering you the chance to take your investing journey to the next level! Whether you're just starting or are a seasoned trader, these plans are packed with exclusive trading strategies, in-depth research paper analysis, ad-free content, monthly AMAsessions, coding tutorials for automating trading strategies, and much more.

Our three tailored plans—Starter Investor, Pro Trader, and Elite Investor—provide a range of valuable tools and personalized support to suit different needs and goals. Don’t miss this opportunity to get real-time trade alerts, access to masterclasses, one-on-one strategy consultations, and be part of our private community group. Click here to explore the plans and see how becoming a premium member can elevate your investment strategy!

Most traders overcomplicate strategy testing. They get lost in complex indicators, machine learning models, and endless optimization. But what if I told you that testing a profitable trading strategy could be remarkably simple?

I’ll show you how to investigate a real market edge using basic Python code. We’ll explore a fascinating phenomenon that occurs due to institutional fund managers’ monthly reporting requirements.

The Edge: Understanding Window Dressing

Fund managers have a secret they don’t want you to know: they engage in “window dressing” at month-end. They sell their embarrassing positions (like meme stocks) and buy high-quality assets (like bonds) to make their monthly reports look better.

This creates a predictable pattern we can exploit:

  • Buy bonds at month-end when fund managers are buying

  • Sell at the start of the next month when they revert to their regular positions

Why hasn’t this edge been arbitraged away? Simple: it’s too small for the big players to care about, making it perfect for individual traders.

The Test: Simple Python Implementation

Let’s validate this edge with minimal code. Here’s how:

import pandas as pd
import numpy as np
import yfinance as yf

# Download TLT (Long-term Treasury Bond ETF) data
tlt = yf.download("TLT", start="2002-01-01", end="2024-06-30")
tlt["log_return"] = np.log(tlt["Adj Close"] / tlt["Adj Close"].shift(1))

This fetches historical data for TLT, a liquid bond ETF, and calculates log returns for clean analysis.

Analyzing the Pattern

To check if our hypothesis holds, we’ll group returns by calendar day:

tlt["day_of_month"] = tlt.index.day
tlt["year"] = tlt.index.year
grouped_by_day = tlt.groupby("day_of_month").log_return.mean()
grouped_by_day.plot.bar(title="Mean Log Returns by Calendar Day of Month")

The results confirm our hypothesis: positive returns cluster at month-end, while negative returns appear at the start of months.

Free Daily Trade Alerts: Expert Insights at Your Fingertips

  • Master the market in 5 minutes per day

  • Hot stock alerts sent directly to your phone

  • 150,000+ active subscribers and growing fast!

Measuring Strategy Performance

Let’s compare the first and last week of each month:

tlt["first_week_returns"] = 0.0
tlt.loc[tlt.day_of_month <= 7, "first_week_returns"] = tlt[tlt.day_of_month <= 7].log_return

tlt["last_week_returns"] = 0.0
tlt.loc[tlt.day_of_month >= 23, "last_week_returns"] = tlt[tlt.day_of_month >= 23].log_return

tlt["last_week_less_first_week"] = tlt.last_week_returns - tlt.first_week_returns

The Results

The strategy’s performance is compelling:

(
    tlt.groupby("year")
    .last_week_less_first_week.sum()
    .cumsum()
    .plot(title="Cumulative Sum of Returns By Year")
)tlt["first_week_returns"] = 0.0
tlt.loc[tlt.day_of_month <= 7, "first_week_returns"] = tlt[tlt.day_of_month <= 7].log_return

tlt["last_week_returns"] = 0.0
tlt.loc[tlt.day_of_month >= 23, "last_week_returns"] = tlt[tlt.day_of_month >= 23].log_return

tlt["last_week_less_first_week"] = tlt.last_week_returns - tlt.first_week_returns
The Results
The strategys performance is compelling:

(
    tlt.groupby("year")
    .last_week_less_first_week.sum()
    .cumsum()
    .plot(title="Cumulative Sum of Returns By Year")
)

Why This Matters

This exercise demonstrates several crucial points:

  1. Profitable strategies don’t need to be complex

  2. Market edges often exist in plain sight

  3. Simple Python code can validate trading ideas quickly

Finding the Best ETFs for Month-End Trading

Let’s expand our analysis to a broader universe of ETFs. We’ll test the strategy across different asset classes to see where this effect is strongest.

# List of ETFs to test
etfs = [
    "TLT",  # Long-term Treasury bonds
    "IEF",  # Intermediate Treasury bonds
    "SHY",  # Short-term Treasury bonds
    "LQD",  # Corporate bonds
    "HYG",  # High-yield bonds
    "AGG",  # Aggregate bonds
    "GLD",  # Gold
    "SPY",  # S&P 500
    "QQQ",  # Nasdaq 100
    "IWM"   # Russell 2000
]

# Function to calculate strategy returns for an ETF
def calculate_strategy_returns(ticker):
    try:
        # Download data
        df = yf.download(ticker, start="2002-01-01", end="2024-06-30")
        
        # Calculate log returns
        df["log_return"] = np.log(df["Adj Close"] / df["Adj Close"].shift(1))
        
        # Add calendar features
        df["day_of_month"] = df.index.day
        
        # Calculate strategy returns
        df["first_week_returns"] = 0.0
        df.loc[df.day_of_month <= 7, "first_week_returns"] = df[df.day_of_month <= 7].log_return
        
        df["last_week_returns"] = 0.0
        df.loc[df.day_of_month >= 23, "last_week_returns"] = df[df.day_of_month >= 23].log_return
        
        # Calculate strategy metrics
        total_return = (df.last_week_returns - df.first_week_returns).sum()
        annual_return = total_return / (len(df) / 252)  # Annualized
        sharpe = np.sqrt(252) * (df.last_week_returns - df.first_week_returns).mean() / (df.last_week_returns - df.first_week_returns).std()
        
        return {
            "ticker": ticker,
            "total_return": total_return,
            "annual_return": annual_return,
            "sharpe": sharpe
        }
    except Exception as e:
        print(f"Error processing {ticker}: {e}")
        return None

# Run analysis for all ETFs
results = []
for etf in etfs:
    result = calculate_strategy_returns(etf)
    if result:
        results.append(result)

# Create results DataFrame and sort by Sharpe ratio
results_df = pd.DataFrame(results)
results_df = results_df.sort_values("sharpe", ascending=False)

# Format results for display
results_df["total_return"] = results_df["total_return"].map("{:.2%}".format)
results_df["annual_return"] = results_df["annual_return"].map("{:.2%}".format)
results_df["sharpe"] = results_df["sharpe"].map("{:.2f}".format)

# Display results
print("\nStrategy Performance Across ETFs:")
print(results_df.to_string(index=False))

When we run this analysis, we get some interesting insights about where the month-end effect is strongest:Why This Matters

This exercise demonstrates several crucial points:

  1. Profitable strategies don’t need to be complex

  2. Market edges often exist in plain sight

  3. Simple Python code can validate trading ideas quickly

Finding the Best ETFs for Month-End Trading

Let’s expand our analysis to a broader universe of ETFs. We’ll test the strategy across different asset classes to see where this effect is strongest.

# List of ETFs to test
etfs = [
    "TLT",  # Long-term Treasury bonds
    "IEF",  # Intermediate Treasury bonds
    "SHY",  # Short-term Treasury bonds
    "LQD",  # Corporate bonds
    "HYG",  # High-yield bonds
    "AGG",  # Aggregate bonds
    "GLD",  # Gold
    "SPY",  # S&P 500
    "QQQ",  # Nasdaq 100
    "IWM"   # Russell 2000
]

# Function to calculate strategy returns for an ETF
def calculate_strategy_returns(ticker):
    try:
        # Download data
        df = yf.download(ticker, start="2002-01-01", end="2024-06-30")
        
        # Calculate log returns
        df["log_return"] = np.log(df["Adj Close"] / df["Adj Close"].shift(1))
        
        # Add calendar features
        df["day_of_month"] = df.index.day
        
        # Calculate strategy returns
        df["first_week_returns"] = 0.0
        df.loc[df.day_of_month <= 7, "first_week_returns"] = df[df.day_of_month <= 7].log_return
        
        df["last_week_returns"] = 0.0
        df.loc[df.day_of_month >= 23, "last_week_returns"] = df[df.day_of_month >= 23].log_return
        
        # Calculate strategy metrics
        total_return = (df.last_week_returns - df.first_week_returns).sum()
        annual_return = total_return / (len(df) / 252)  # Annualized
        sharpe = np.sqrt(252) * (df.last_week_returns - df.first_week_returns).mean() / (df.last_week_returns - df.first_week_returns).std()
        
        return {
            "ticker": ticker,
            "total_return": total_return,
            "annual_return": annual_return,
            "sharpe": sharpe
        }
    except Exception as e:
        print(f"Error processing {ticker}: {e}")
        return None

# Run analysis for all ETFs
results = []
for etf in etfs:
    result = calculate_strategy_returns(etf)
    if result:
        results.append(result)

# Create results DataFrame and sort by Sharpe ratio
results_df = pd.DataFrame(results)
results_df = results_df.sort_values("sharpe", ascending=False)

# Format results for display
results_df["total_return"] = results_df["total_return"].map("{:.2%}".format)
results_df["annual_return"] = results_df["annual_return"].map("{:.2%}".format)
results_df["sharpe"] = results_df["sharpe"].map("{:.2f}".format)

# Display results
print("\nStrategy Performance Across ETFs:")
print(results_df.to_string(index=False))

When we run this analysis, we get some interesting insights about where the month-end effect is strongest:

The results are revealing. The month-end effect is strongest in bond ETFs, particularly aggregate bonds (AGG) and long-term Treasuries (TLT). This makes intuitive sense — these are exactly the high-quality assets that fund managers would prefer to show in their monthly reports.

Key observations:

  1. Bond ETFs dominate the top of the list

  2. Aggregate bonds (AGG) show the strongest effect

  3. The effect weakens as we move to commodities (GLD)

  4. Equity ETFs show much weaker effects

  5. Gold (GLD) shows minimal month-end patterns

This analysis suggests we should focus our strategy on AGG or TLT for the best risk-adjusted returns. The higher Sharpe ratios in bond ETFs indicate more consistent returns per unit of risk taken.

Strategy Refinement

Based on these results, we could potentially enhance our returns by:

  1. Focusing exclusively on bond ETFs

  2. Using leverage on the highest Sharpe ratio instruments

  3. Creating a portfolio weighted by Sharpe ratio

  4. Adjusting position sizing based on volatility

Next Steps

You can adapt this framework to test your own ideas:

  • Change the ticker symbol to analyze different assets

  • Adjust the time periods

  • Explore seasonal patterns

Remember: the best trading strategies are often the simplest ones that nobody else is looking at.