Holding days
This commit is contained in:
parent
a18da5cb31
commit
ea34b99988
2 changed files with 88 additions and 47 deletions
|
|
@ -2,7 +2,11 @@ import os
|
|||
from alpaca.data.historical import StockHistoricalDataClient
|
||||
from alpaca.data.requests import StockBarsRequest
|
||||
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
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
|
@ -19,37 +23,38 @@ data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY)
|
|||
# 2. Define your small, liquid asset universe
|
||||
ASSET_UNIVERSE = ["AAPL", "MSFT", "NVDA", "AMD", "META", "AMZN", "GOOGL", "TSLA"]
|
||||
|
||||
|
||||
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."""
|
||||
end_date = datetime.now(UTC) - timedelta(minutes = 15)
|
||||
end_date = datetime.now(UTC) - timedelta(minutes=15)
|
||||
start_date = end_date - timedelta(days=years_back * 365)
|
||||
|
||||
|
||||
request_params = StockBarsRequest(
|
||||
symbol_or_symbols=symbols,
|
||||
timeframe=TimeFrame.Day,
|
||||
start=start_date,
|
||||
end=end_date
|
||||
end=end_date,
|
||||
)
|
||||
|
||||
|
||||
print(f"Fetching {years_back} years of data for {len(symbols)} stocks...")
|
||||
bars = data_client.get_stock_bars(request_params)
|
||||
|
||||
|
||||
# Convert directly to a pandas DataFrame
|
||||
df = bars.df
|
||||
|
||||
|
||||
# Alpaca returns a multi-index: (symbol, timestamp)
|
||||
# Reset index to make date-manipulation easier later
|
||||
df = df.reset_index()
|
||||
df['timestamp'] = pd.to_datetime(df['timestamp']).dt.date
|
||||
df["timestamp"] = pd.to_datetime(df["timestamp"]).dt.date
|
||||
|
||||
|
||||
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())
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +1,49 @@
|
|||
import os
|
||||
import numpy as np
|
||||
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:
|
||||
"""Calculates stationary features and binary targets per stock."""
|
||||
processed_stocks = []
|
||||
|
||||
# Group by symbol so indicators don't bleed across different stocks
|
||||
for symbol, group in df.groupby('symbol'):
|
||||
group = group.sort_values('timestamp').copy()
|
||||
|
||||
# 1. Calculate Stationary Log Returns (Instead of absolute price)
|
||||
group['log_return'] = np.log(group['close'] / group['close'].shift(1))
|
||||
|
||||
# 2. Calculate Rolling Volatility (Standard deviation of returns)
|
||||
group['volatility_5d'] = group['log_return'].rolling(window=5).std()
|
||||
|
||||
# 3. Simple RSI implementation (Normalized between 0 and 1)
|
||||
delta = group['close'].diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
|
||||
|
||||
for symbol, group in df.groupby("symbol"):
|
||||
|
||||
group = group.sort_values("timestamp").copy()
|
||||
|
||||
# Feature 1: today's log return
|
||||
group["log_return"] = np.log(group["close"] / group["close"].shift(1))
|
||||
|
||||
# Feature 2: recent volatility from past returns
|
||||
group["volatility_5d"] = group["log_return"].rolling(window=5).std()
|
||||
|
||||
# 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()
|
||||
rs = gain / (loss + 1e-9) # Avoid division by zero
|
||||
|
||||
rs = gain / (loss + 1e-9)
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
group['rsi_scaled'] = rsi / 100.0 # Scale to 0-1 range for Neural Network
|
||||
|
||||
# 4. Create Binary Target: Will the stock go UP tomorrow?
|
||||
# Shift tomorrow's return backward to align with today's features
|
||||
group['tomorrow_return'] = group['log_return'].shift(-1)
|
||||
group['target'] = (group['tomorrow_return'] > 0).astype(int)
|
||||
|
||||
# Drop rows with NaN values resulting from rolling windows and shifts
|
||||
group["rsi_scaled"] = rsi / 100.0
|
||||
|
||||
# Target: buy tomorrow open, sell after HOLDING_DAYS at close
|
||||
group["future_trade_return"] = np.log(
|
||||
group["close"].shift(-HOLDING_DAYS) / group["open"].shift(-1)
|
||||
)
|
||||
|
||||
log_min_return = np.log(MIN_RETURN + 1)
|
||||
|
||||
group["target"] = (group["future_trade_return"] > log_min_return).astype(int)
|
||||
|
||||
group = group.dropna()
|
||||
processed_stocks.append(group)
|
||||
|
||||
|
||||
return pd.concat(processed_stocks, ignore_index=True)
|
||||
|
||||
|
||||
|
|
@ -39,27 +51,51 @@ def create_lstm_sequences(df: pd.DataFrame, lookback: int = 30):
|
|||
"""Reshapes the DataFrame into 3D Numpy Arrays for TensorFlow."""
|
||||
X_list = []
|
||||
y_list = []
|
||||
|
||||
|
||||
# Feature columns to feed into the model
|
||||
feature_cols = ['log_return', 'volatility_5d', 'rsi_scaled']
|
||||
|
||||
for symbol, group in df.groupby('symbol'):
|
||||
group = group.sort_values('timestamp')
|
||||
feature_cols = ["log_return", "volatility_5d", "rsi_scaled"]
|
||||
|
||||
for symbol, group in df.groupby("symbol"):
|
||||
group = group.sort_values("timestamp")
|
||||
feature_data = group[feature_cols].values
|
||||
target_data = group['target'].values
|
||||
|
||||
target_data = group["target"].values
|
||||
|
||||
# Create rolling window sequences
|
||||
for i in range(len(group) - lookback):
|
||||
# Extract sequence from i to 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]
|
||||
|
||||
|
||||
X_list.append(X_seq)
|
||||
y_list.append(y_label)
|
||||
|
||||
|
||||
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 ---
|
||||
# 1. Pull raw_df
|
||||
# feature_df = engineer_features(raw_df)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue