Mid-Month Payday S&P500 Strategy

Log in to collect

Onsite backtest IDE

Quant Buffet native backtest IDE

Edit and run Quant Buffet Python for Mid-Month Payday S&P500 Strategy in the browser. Results update live with equity, drawdown, and metrics charts. Allowed: backtest.data, backtest.engine, backtest.metrics, numpy, pandas. Define ASSETS and make_on_day(prices). Shortcut: Ctrl+Enter. API docs →

Ready — edit code, then Run backtest.
IDE · 43 lines
Quant Buffet syntax cheat sheet (copy / insert)

Paste these fragments into the editor. The sandbox rejects QuantConnect, os, and network libraries.

Required imports
Only these libraries are allowed in the sandbox.
from __future__ import annotations

import numpy as np
import pandas as pd

from backtest.data import load_daily_prices
from backtest.engine import EngineConfig, PortfolioEngine
from backtest.metrics import compute_metrics
ASSETS list (whitelisted ETFs)
Module-level list. Tickers must be in the Quant Buffet whitelist.
ASSETS = ["SPY", "QQQ", "TLT", "GLD", "BIL"]
make_on_day contract
Must return (on_day, ready). on_day calls engine.set_target_weights.
def make_on_day(prices: pd.DataFrame):
    cols = [c for c in ASSETS if c in prices.columns]
    sma = prices[cols].rolling(200, min_periods=200).mean()
    state = {"last": None}

    def on_day(engine: PortfolioEngine, dt: pd.Timestamp) -> None:
        if sma.loc[dt].isna().all():
            return
        key = (dt.year, dt.month)
        if state["last"] == key:
            return
        state["last"] = key
        long = [
            s for s in cols
            if pd.notna(prices.at[dt, s]) and pd.notna(sma.at[dt, s])
            and prices.at[dt, s] > sma.at[dt, s]
        ]
        weights = {} if not long else {s: 1.0 / len(long) for s in long}
        engine.set_target_weights(dt, weights)

    ready = sma.dropna(how="all").index.min() if sma.notna().any().any() else None
    return on_day, ready
Set target weights
Weights should sum to about 1.0. Empty dict = 100% cash.
engine.set_target_weights(dt, {"SPY": 0.60, "BIL": 0.40})

Live backtest performance

CAGR
8.24%
Sharpe
0.51
Max DD
-55.19%
Vol
19.27%
Sortino
0.79
Beta
1.00
Up days
57%

Run the backtest to populate charts.

Export to your platform

Transform Quant Buffet lab code (ASSETS + make_on_day / PortfolioEngine) into native classes for a third-party IDE — then copy and paste.

Run in: QuantConnect Cloud or LEAN CLI · QCAlgorithm with Equity securities and monthly rebalance.

Detected pattern: Custom / hybridAssets: SPY, TLT, GLD, BIL
# Generated from Quant Buffet → QuantConnect LEAN
# Strategy: Mid-Month Payday S&P500 Strategy
# Detected pattern: Custom / hybrid
# Source uses Quant Buffet lab APIs (ASSETS + make_on_day / PortfolioEngine).
# Review fees, data, and risk before live trading — educational export only.

from AlgorithmImports import *


class QuantBuffetExport(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2010, 1, 1)
        self.SetCash(100000)
        tickers = ["SPY", "TLT", "GLD", "BIL"]
        self.symbols = []
        for t in tickers:
            if "-" in t:  # crypto proxy e.g. BTC-USD
                self.symbols.append(self.AddCrypto(t.replace("-USD", ""), Resolution.Daily).Symbol)
            else:
                self.symbols.append(self.AddEquity(t, Resolution.Daily).Symbol)
        self.Schedule.On(
            self.DateRules.MonthStart(self.symbols[0]),
            self.TimeRules.AfterMarketOpen(self.symbols[0], 30),
            self.Rebalance,
        )
        # Logic: Custom Quant Buffet logic — adapt the signal block to match your lab on_day().

    def Rebalance(self):
        # Pattern: custom — Custom Quant Buffet logic — adapt the signal block to match your lab on_day().
        # Default: equal-weight. Port your make_on_day weights here via SetHoldings.
        w = 1.0 / len(self.symbols) if self.symbols else 0.0
        for symbol in self.symbols:
            self.SetHoldings(symbol, w)

Exported code uses the platform’s native classes and libraries. Install dependencies in your third-party IDE, then run. Validate before live trading.

Academic paper

Payday Anomaly

AuthorsAixin Ma; William R. Pratt

Institute
  • Oklahoma City University
  • ?Oklahoma City University - Meinders School of Business

Screenshot from the original paper

Screenshot from the original paper
Screenshot from the original paper

Strategy in a nutshell

The investment universe consists of the S&P500 index. Simply, buy and hold the index during the 16th day in the month during each month of the year.

Economic rationale

The reason for the functionality is probably deeply connected with paychecks. Employees get paid at the end of the month, and many of them either automatically invest a portion of their paycheck in the market through retirement contributions or are encouraged to do so by having a surplus of funds with the new paycheck. However, many companies pay their employees twice a month, on the 15th and at the end of the month. Therefore, building on that the pay-day effect holds true for the turn-of-the-month days, there should be a clear pattern in the middle of the month as well as at the end of the month since employees do make retirement contributions with every paycheck. Results confirm that abnormal returns indeed exist in the middle of the month. According to the research, the 16th of the month is not only the 3rd best day in the month overall, but has moved up in the ranks monotonically every decade since the 1950s, until the most recent decade, the 2010s. Although more and more firms are paying wages on a bi-weekly basis, the highest average hourly earnings are distributed semi-monthly followed by monthly distribution, which favours this strategy, and therefore, this effect should not diminish. Another possible reason for the functionality is that, because the other pay-days have been extensively discussed in the literature, practitioners have been trying to take advantage of the anomaly and market efficiency has caught up, reducing the magnitude of the anomaly. Therefore, this novel anomaly has a strong performance if we compare it with the other days, and mainly, it is not traded-off, at least not yet.

Backtest performance

Annualised return8.24%
Volatility19.27%
Beta1.00
Sharpe ratio0.51
Sortino ratio0.79
Maximum drawdown-55.19%
Win rate57%