Holding days

This commit is contained in:
GonzaloHD 2026-07-15 15:51:58 +01:00
parent a18da5cb31
commit ea34b99988
2 changed files with 88 additions and 47 deletions

View file

@ -2,7 +2,11 @@ import os
from alpaca.data.historical import StockHistoricalDataClient from alpaca.data.historical import StockHistoricalDataClient
from alpaca.data.requests import StockBarsRequest from alpaca.data.requests import StockBarsRequest
from alpaca.data.timeframe import TimeFrame from alpaca.data.timeframe import TimeFrame
from datetime import datetime, timedelta, UTC # , timezone (to use instead as timezone.utc in older python) from datetime import (
datetime,
timedelta,
UTC,
) # , timezone (to use instead as timezone.utc in older python)
import pandas as pd import pandas as pd
from dotenv import load_dotenv from dotenv import load_dotenv
@ -19,6 +23,7 @@ data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY)
# 2. Define your small, liquid asset universe # 2. Define your small, liquid asset universe
ASSET_UNIVERSE = ["AAPL", "MSFT", "NVDA", "AMD", "META", "AMZN", "GOOGL", "TSLA"] ASSET_UNIVERSE = ["AAPL", "MSFT", "NVDA", "AMD", "META", "AMZN", "GOOGL", "TSLA"]
def fetch_historical_data(symbols: list, years_back: int = 5) -> pd.DataFrame: def fetch_historical_data(symbols: list, years_back: int = 5) -> pd.DataFrame:
"""Fetches historical daily bars from Alpaca and returns a clean multi-index DataFrame.""" """Fetches historical daily bars from Alpaca and returns a clean multi-index DataFrame."""
end_date = datetime.now(UTC) - timedelta(minutes=15) end_date = datetime.now(UTC) - timedelta(minutes=15)
@ -28,7 +33,7 @@ def fetch_historical_data(symbols: list, years_back: int = 5) -> pd.DataFrame:
symbol_or_symbols=symbols, symbol_or_symbols=symbols,
timeframe=TimeFrame.Day, timeframe=TimeFrame.Day,
start=start_date, start=start_date,
end=end_date end=end_date,
) )
print(f"Fetching {years_back} years of data for {len(symbols)} stocks...") print(f"Fetching {years_back} years of data for {len(symbols)} stocks...")
@ -40,16 +45,16 @@ def fetch_historical_data(symbols: list, years_back: int = 5) -> pd.DataFrame:
# Alpaca returns a multi-index: (symbol, timestamp) # Alpaca returns a multi-index: (symbol, timestamp)
# Reset index to make date-manipulation easier later # Reset index to make date-manipulation easier later
df = df.reset_index() df = df.reset_index()
df['timestamp'] = pd.to_datetime(df['timestamp']).dt.date df["timestamp"] = pd.to_datetime(df["timestamp"]).dt.date
return df return df
if __name__ == '__main__':
df = fetch_historical_data(symbols=ASSET_UNIVERSE)
print(df.head())
print(df.tail()) if __name__ == "__main__":
df = fetch_historical_data(symbols=ASSET_UNIVERSE)
print(df.head(15))
print(df.tail(15))
print(df.describe()) print(df.describe())

View file

@ -1,34 +1,46 @@
import os
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from dotenv import load_dotenv
load_dotenv()
MIN_RETURN = float(os.getenv("MIN_RETURN", 0.010))
HOLDING_DAYS = int(os.getenv("HOLDING_DAYS", 5))
def engineer_features(df: pd.DataFrame) -> pd.DataFrame: def engineer_features(df: pd.DataFrame) -> pd.DataFrame:
"""Calculates stationary features and binary targets per stock.""" """Calculates stationary features and binary targets per stock."""
processed_stocks = [] processed_stocks = []
# Group by symbol so indicators don't bleed across different stocks for symbol, group in df.groupby("symbol"):
for symbol, group in df.groupby('symbol'):
group = group.sort_values('timestamp').copy()
# 1. Calculate Stationary Log Returns (Instead of absolute price) group = group.sort_values("timestamp").copy()
group['log_return'] = np.log(group['close'] / group['close'].shift(1))
# 2. Calculate Rolling Volatility (Standard deviation of returns) # Feature 1: today's log return
group['volatility_5d'] = group['log_return'].rolling(window=5).std() group["log_return"] = np.log(group["close"] / group["close"].shift(1))
# 3. Simple RSI implementation (Normalized between 0 and 1) # Feature 2: recent volatility from past returns
delta = group['close'].diff() group["volatility_5d"] = group["log_return"].rolling(window=5).std()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
# Feature 3: RSI
delta = group["close"].diff()
gain = delta.where(delta > 0, 0).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean() loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / (loss + 1e-9) # Avoid division by zero
rs = gain / (loss + 1e-9)
rsi = 100 - (100 / (1 + rs)) rsi = 100 - (100 / (1 + rs))
group['rsi_scaled'] = rsi / 100.0 # Scale to 0-1 range for Neural Network group["rsi_scaled"] = rsi / 100.0
# 4. Create Binary Target: Will the stock go UP tomorrow? # Target: buy tomorrow open, sell after HOLDING_DAYS at close
# Shift tomorrow's return backward to align with today's features group["future_trade_return"] = np.log(
group['tomorrow_return'] = group['log_return'].shift(-1) group["close"].shift(-HOLDING_DAYS) / group["open"].shift(-1)
group['target'] = (group['tomorrow_return'] > 0).astype(int) )
log_min_return = np.log(MIN_RETURN + 1)
group["target"] = (group["future_trade_return"] > log_min_return).astype(int)
# Drop rows with NaN values resulting from rolling windows and shifts
group = group.dropna() group = group.dropna()
processed_stocks.append(group) processed_stocks.append(group)
@ -41,18 +53,20 @@ def create_lstm_sequences(df: pd.DataFrame, lookback: int = 30):
y_list = [] y_list = []
# Feature columns to feed into the model # Feature columns to feed into the model
feature_cols = ['log_return', 'volatility_5d', 'rsi_scaled'] feature_cols = ["log_return", "volatility_5d", "rsi_scaled"]
for symbol, group in df.groupby('symbol'): for symbol, group in df.groupby("symbol"):
group = group.sort_values('timestamp') group = group.sort_values("timestamp")
feature_data = group[feature_cols].values feature_data = group[feature_cols].values
target_data = group['target'].values target_data = group["target"].values
# Create rolling window sequences # Create rolling window sequences
for i in range(len(group) - lookback): for i in range(len(group) - lookback):
# Extract sequence from i to i + lookback # Extract sequence from i to i + lookback
X_seq = feature_data[i : i + lookback] X_seq = feature_data[i : i + lookback]
# Target is the outcome immediately following the lookback window # Target for the day immediately after the lookback window.
# The target indicates whether buying on that day and
# holding for HOLDING_DAYS would achieve MIN_RETURN.
y_label = target_data[i + lookback] y_label = target_data[i + lookback]
X_list.append(X_seq) X_list.append(X_seq)
@ -60,6 +74,28 @@ def create_lstm_sequences(df: pd.DataFrame, lookback: int = 30):
return np.array(X_list), np.array(y_list) return np.array(X_list), np.array(y_list)
if __name__ == "__main__":
from lstm_alpha_strategy.data.alpaca_client import fetch_historical_data
ASSET_UNIVERSE = ["AAPL", "MSFT", "NVDA", "AMD", "META", "AMZN", "GOOGL", "TSLA"]
raw_df = fetch_historical_data(ASSET_UNIVERSE)
feature_df = engineer_features(raw_df)
print(feature_df.head(60))
row = feature_df.iloc[0]
buy_open = feature_df.iloc[1]["open"]
sell_close = feature_df.iloc[5]["close"]
manual = np.log(sell_close / buy_open)
print(manual)
print(row["future_trade_return"])
# --- EXECUTION PIPELINE --- # --- EXECUTION PIPELINE ---
# 1. Pull raw_df # 1. Pull raw_df
# feature_df = engineer_features(raw_df) # feature_df = engineer_features(raw_df)