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.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())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue