Skip to content

Instantly share code, notes, and snippets.

@qubitrenegade
Created October 27, 2024 18:36
Show Gist options
  • Select an option

  • Save qubitrenegade/769b133ce4a8b23824b9668ee3501ba3 to your computer and use it in GitHub Desktop.

Select an option

Save qubitrenegade/769b133ce4a8b23824b9668ee3501ba3 to your computer and use it in GitHub Desktop.
stock-weekend-gaps.py - Analyze stock price gaps between last trading day of week and first trading day of next week
#!/usr/bin/env python3
"""
stock-weekend-gaps.py - Analyze stock price gaps between last trading day of week and first trading day of next week
This script fetches historical stock data and analyzes the price gaps that occur over weekends
and holidays. It captures the opening and closing prices of the last trading day of each week
and the first trading day of the following week, accounting for holidays and irregular trading days.
Installation:
pip install yfinance pandas
Usage:
python3 stock-weekend-gaps.py AAPL # Analyze Apple stock for last 5 years
python3 stock-weekend-gaps.py AAPL --years=10 # Analyze Apple stock for last 10 years
python3 stock-weekend-gaps.py AAPL --since=2019-10-10 # Analyze from specific date
python3 stock-weekend-gaps.py AAPL -o custom_filename.csv # Specify output file
The script will create a CSV with columns:
Week_End_Date: Date of last trading day of week (typically Friday)
Week_End_Day: Day of week for last trading day
Week_End_Open: Opening price of last trading day
Week_End_Close: Closing price of last trading day
Next_Open_Date: Date of first trading day of next week (typically Monday)
Next_Open_Day: Day of week for first trading day
Next_Open: Opening price of first trading day
Next_Close: Closing price of first trading day
Days_Between: Number of calendar days between trading days
Price_Gap: Difference between Week_End_Close and Next_Open
Price_Gap_Percent: Percentage change in price over the gap
Data is fetched using Yahoo Finance API via yfinance library.
"""
import yfinance as yf
import pandas as pd
from datetime import datetime, timedelta
import argparse
import sys
import os
def parse_date(date_str):
"""Parse date string in YYYY-MM-DD format"""
try:
return datetime.strptime(date_str, '%Y-%m-%d')
except ValueError:
raise argparse.ArgumentTypeError(f'Invalid date format: {date_str}. Use YYYY-MM-DD')
def get_weekend_prices(ticker_symbol, start_date=None, years=None, output_file=None):
"""
Fetch weekend (Friday close, Monday open/close) stock prices.
Args:
ticker_symbol (str): Stock ticker symbol
start_date (datetime, optional): Start date for data collection
years (int, optional): Number of years of historical data to fetch
output_file (str, optional): Custom output file path
"""
if not ticker_symbol:
raise ValueError("Ticker symbol is required")
ticker = yf.Ticker(ticker_symbol)
try:
ticker.info
except:
raise ValueError(f"Could not find ticker: {ticker_symbol}")
end_date = datetime.now()
if start_date:
if start_date > end_date:
raise ValueError("Start date cannot be in the future")
elif years:
start_date = end_date - timedelta(days=years*365)
else:
start_date = end_date - timedelta(days=5*365)
print(f"Fetching data for {ticker_symbol} from {start_date.date()} to {end_date.date()}...")
# Get the historical data with some buffer days to handle holidays
df = ticker.history(start=start_date, end=end_date)
if df.empty:
raise ValueError(f"No data found for {ticker_symbol} in the specified date range")
df = df.reset_index()
df['DayOfWeek'] = df['Date'].dt.dayofweek
# Create a new dataframe with weekend transitions
results = []
# Iterate through the dataframe
i = 0
while i < len(df) - 1:
current_row = df.iloc[i]
# Look for trading days near weekend (Thursday/Friday for end of week)
if current_row['DayOfWeek'] <= 4: # Monday to Friday
# Find the last trading day of the week
week_end_idx = i
while (week_end_idx < len(df) - 1 and
df.iloc[week_end_idx]['Date'].week == current_row['Date'].week):
week_end_idx += 1
week_end_idx -= 1
# Find the first trading day of next week
next_week_idx = week_end_idx + 1
while (next_week_idx < len(df) and
df.iloc[next_week_idx]['Date'].week == df.iloc[week_end_idx]['Date'].week):
next_week_idx += 1
if next_week_idx < len(df):
week_end_row = df.iloc[week_end_idx]
next_week_row = df.iloc[next_week_idx]
# Calculate the number of calendar days between close and next open
days_between = (next_week_row['Date'] - week_end_row['Date']).days
results.append({
'Week_End_Date': week_end_row['Date'].strftime('%Y-%m-%d'),
'Week_End_Day': week_end_row['Date'].strftime('%A'),
'Week_End_Open': week_end_row['Open'],
'Week_End_Close': week_end_row['Close'],
'Next_Open_Date': next_week_row['Date'].strftime('%Y-%m-%d'),
'Next_Open_Day': next_week_row['Date'].strftime('%A'),
'Next_Open': next_week_row['Open'],
'Next_Close': next_week_row['Close'],
'Days_Between': days_between,
'Price_Gap': next_week_row['Open'] - week_end_row['Close'],
'Price_Gap_Percent': ((next_week_row['Open'] - week_end_row['Close']) / week_end_row['Close'] * 100)
})
i = next_week_idx
else:
i += 1
results_df = pd.DataFrame(results)
if results_df.empty:
print("No weekend transitions found in the specified date range")
return None
if not output_file:
output_file = f"{ticker_symbol}_weekend_prices_{start_date.strftime('%Y%m%d')}-{end_date.strftime('%Y%m%d')}.csv"
output_dir = os.path.dirname(output_file)
if output_dir:
os.makedirs(output_dir, exist_ok=True)
# Save to CSV with original precision
results_df.to_csv(output_file, index=False, float_format='%g')
print(f"\nData saved to {output_file}")
# Print summary statistics
print("\nSummary Statistics:")
print(f"Total weekend transitions: {len(results_df)}")
print(f"Regular Friday-Monday transitions: {len(results_df[results_df['Days_Between'] == 3])}")
print(f"Extended weekend transitions: {len(results_df[results_df['Days_Between'] > 3])}")
print(f"Average price gap: {results_df['Price_Gap'].mean()}")
print(f"Average price gap %: {results_df['Price_Gap_Percent'].mean()}%")
print(f"Largest positive gap: {results_df['Price_Gap'].max()}")
print(f"Largest negative gap: {results_df['Price_Gap'].min()}")
# Print holiday weekend summary
holiday_weekends = results_df[results_df['Days_Between'] > 3]
if not holiday_weekends.empty:
print("\nHoliday/Extended Weekends Found:")
for _, row in holiday_weekends.iterrows():
print(f"{row['Week_End_Day']} {row['Week_End_Date']} to {row['Next_Open_Day']} {row['Next_Open_Date']} "
f"({row['Days_Between']} days)")
return results_df
def main():
parser = argparse.ArgumentParser(
description='Fetch weekend (Friday close, Monday open/close) stock prices.',
formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument('ticker', nargs='?', help='Stock ticker symbol (e.g., AAPL)')
parser.add_argument('--since', type=parse_date, help='Start date (YYYY-MM-DD)')
parser.add_argument('--years', type=int, help='Number of years of historical data')
parser.add_argument('--output', '-o', help='Output file path (default: TICKER_weekend_prices_STARTDATE-ENDDATE.csv)')
args = parser.parse_args()
if not args.ticker:
args.ticker = input("Enter ticker symbol (e.g., AAPL): ").strip().upper()
try:
get_weekend_prices(args.ticker, args.since, args.years, args.output)
except Exception as e:
print(f"Error: {str(e)}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment