manikandan18ramalingam/Agentic-AI-Options-Trading-App
1
1'''2This agent does the following.3 41. Fetch the past 3 years performance of all mega cap stocks from yahoo finance. It uses Close price, RSI, MACD and Bollinger bands.52. Splits up the data set into 80-20 for training and validation. Creates LSTM (Long Short Term Memory) model that uses 3 hidden layers, 1 dense output layer with adam optimizer. The learning rate at 0.001 with 100 epochs.63. Trains and validates the model.74. Plots the loss curve for all tickers.85. Saves the model so that it does not have to train again and again for the same tickers. This improves the Agentic AI performance drastically. It just have to do forecasting in further calls.96. Uses the model to forecast the stock prices of the top option tickers we obtained from options_agent on that expiry date.107. It identifies the best strike price option that is closest to predicted stock price from the model.118. It updates this best_strike_prices in the graph state and returns it.12 13The optimizations done are,14 151. Checking for 100 epochs,162. Dropout of 0.3 was used for generalization and making model not to overfit.173. Used 3 years past performance data instead of 2 years.184. Learning rate modified to 0.001.195. Number of epochs increased to 10020 21The state output is of format,22 23'best_strike_prices': {'AAPL': {'strike': 70.0, 'openInterest': 2526, 'contractSymbol': 'AAPL261218C00070000'}, 'ABBV': {'strike': 250.0, 'openInterest': 806, 'contractSymbol': 'ABBV251121C00250000'}, 'ABT': {'strike': 180.0, 'openInterest': 118, 'contractSymbol': 'ABT251121C00180000'}, 'AVGO': {'strike': 80.0, 'openInterest': 5369, 'contractSymbol': 'AVGO251219C00080000'}}24'''25 26import pandas as pd27import yfinance as yf28import torch29import torch.nn as nn30from sklearn.preprocessing import MinMaxScaler31from langchain_core.runnables import RunnableLambda32import numpy as np33import datetime34import os35import joblib36import matplotlib.pyplot as plt37 38from sklearn.model_selection import train_test_split39 40MODEL_DIR = "models"41LOSS_PLOTS_DIR = "loss_plots"42 43os.makedirs(MODEL_DIR, exist_ok=True)44os.makedirs(LOSS_PLOTS_DIR, exist_ok=True)45 46# ----- Technical Indicators -----47def add_technical_indicators(df):48 df['RSI'] = compute_rsi(df['Close'])49 df['MACD'] = compute_macd(df['Close'])50 df['Bollinger_Upper'], df['Bollinger_Lower'] = compute_bollinger_bands(df['Close'])51 return df.dropna()52 53def compute_rsi(series, period=14):54 delta = series.diff()55 gain = delta.where(delta > 0, 0)56 loss = -delta.where(delta < 0, 0)57 avg_gain = gain.rolling(window=period).mean()58 avg_loss = loss.rolling(window=period).mean()59 rs = avg_gain / avg_loss60 return 100 - (100 / (1 + rs))61 62def compute_macd(series, fast=12, slow=26):63 ema_fast = series.ewm(span=fast, adjust=False).mean()64 ema_slow = series.ewm(span=slow, adjust=False).mean()65 return ema_fast - ema_slow66 67def compute_bollinger_bands(series, window=20, num_std=2):68 sma = series.rolling(window).mean()69 std = series.rolling(window).std()70 return sma + num_std * std, sma - num_std * std71 72# ----- LSTM Model Definition -----73class OptimizedLSTMModel(nn.Module):74 # Add hidden size, hidden layers, dropout optimizations75 def __init__(self, input_size=5, hidden_size=128, num_layers=3, output_size=1, dropout=0.3):76 super(OptimizedLSTMModel, self).__init__()77 self.lstm = nn.LSTM(input_size, hidden_size, num_layers, dropout=dropout, batch_first=True)78 self.fc = nn.Linear(hidden_size, output_size)79 80 def forward(self, x):81 out, _ = self.lstm(x)82 return self.fc(out[:, -1, :])83 84# ----- Predict Future Price -----85def predict_price_n_days(model, scaler, seq, input_size, days):86 for _ in range(days):87 input_seq = torch.tensor(seq[-len(seq):], dtype=torch.float32).unsqueeze(0)88 with torch.no_grad():89 next_pred = model(input_seq).item()90 dummy = np.zeros((1, scaler.n_features_in_))91 dummy[0][0] = next_pred92 next_close = scaler.inverse_transform(dummy)[0][0]93 seq = np.vstack([seq, np.hstack([next_pred] * input_size)])94 return round(next_close, 2)95 96# ----- Plot Losses -----97def plot_loss_per_ticker(loss_dict):98 plt.figure(figsize=(10, 6))99 for ticker, losses in loss_dict.items():100 plt.plot(range(1, len(losses) + 1), losses, label=ticker)101 plt.xlabel("Epoch")102 plt.ylabel("Loss")103 plt.title("Training Loss Per Ticker")104 plt.legend()105 plt.grid(True)106 plt.tight_layout()107 plt.savefig(os.path.join(LOSS_PLOTS_DIR, "loss_plot.png"))108 plt.close()109 110# ----- Model Summary -----111def print_model_summary():112 print("\nModel Summary (Shared Across All Tickers)")113 print("=" * 50)114 print(f"{'Model':<20}: LSTM")115 print(f"{'Hidden Layers':<20}: 3 × LSTM(128 units) + Dropout(0.3)")116 print(f"{'Output Layer':<20}: Dense(1)")117 print(f"{'Optimizer':<20}: Adam")118 print(f"{'Loss Function':<20}: Mean Squared Error")119 print(f"{'Epochs':<20}: 100")120 print("=" * 50 + "\n")121 122# ----- Core Agent Logic -----123def _ml_predictor_agent(state):124 tickers = state["tickers"]125 expiries = state.get("expiries", {})126 top_strikes_all = state.get("top_strikes", {})127 128 print_model_summary()129 loss_history = {}130 results = {}131 132 for ticker in tickers:133 try:134 expiry_str = expiries.get(ticker)135 if not expiry_str:136 results[ticker] = {"error": "No expiry"}137 continue138 139 expiry = datetime.datetime.strptime(expiry_str, "%Y-%m-%d").date()140 today = datetime.date.today()141 days_ahead = (expiry - today).days142 if days_ahead <= 0:143 results[ticker] = {"error": "Invalid expiry"}144 continue145 146 model_path = os.path.join(MODEL_DIR, f"{ticker}_model.pt")147 scaler_path = os.path.join(MODEL_DIR, f"{ticker}_scaler.pkl")148 149 df = yf.download(ticker, period="3y", interval="1d", progress=False)150 df = add_technical_indicators(df)151 features = df[['Close', 'RSI', 'MACD', 'Bollinger_Upper', 'Bollinger_Lower']].values152 153 seq_length = 30154 input_size = features.shape[1]155 156 # Check for model presence per ticker. If present, avoid re-training for performance optimization157 if not os.path.exists(model_path) or not os.path.exists(scaler_path):158 # Scale the data using MinMaxScaler159 scaler = MinMaxScaler()160 data_scaled = scaler.fit_transform(features)161 X, y = [], []162 for i in range(len(data_scaled) - seq_length):163 X.append(data_scaled[i:i+seq_length])164 y.append(data_scaled[i+seq_length][0])165 166 # Train-validation split (80-20)167 X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, shuffle=False)168 169 X_train = torch.tensor(X_train, dtype=torch.float32)170 y_train = torch.tensor(y_train, dtype=torch.float32).view(-1, 1)171 X_val = torch.tensor(X_val, dtype=torch.float32)172 y_val = torch.tensor(y_val, dtype=torch.float32).view(-1, 1)173 174 model = OptimizedLSTMModel(input_size=input_size)175 optimizer = torch.optim.Adam(model.parameters(), lr=0.001)176 loss_fn = nn.MSELoss()177 178 ticker_losses = []179 180 for epoch in range(100):181 model.train()182 optimizer.zero_grad()183 output = model(X_train)184 loss = loss_fn(output, y_train)185 loss.backward()186 optimizer.step()187 188 # Validation189 model.eval()190 with torch.no_grad():191 val_output = model(X_val)192 val_loss = loss_fn(val_output, y_val).item()193 ticker_losses.append(val_loss)194 195 # Save the model for future use and avoid re-training for each call from Web app196 torch.save(model.state_dict(), model_path)197 joblib.dump(scaler, scaler_path)198 loss_history[ticker] = ticker_losses199 else:200 # Store the scaled data appropriate for each ticker201 scaler = joblib.load(scaler_path)202 data_scaled = scaler.transform(features)203 model = OptimizedLSTMModel(input_size=input_size)204 model.load_state_dict(torch.load(model_path))205 model.eval()206 207 recent_seq = data_scaled[-seq_length:]208 209 # Predict the price of expiry date in future210 predicted_price = predict_price_n_days(model, scaler, recent_seq.copy(), input_size, days_ahead)211 212 strikes = top_strikes_all.get(ticker, [])213 if strikes:214 # Get the option with closest strike price to LSTM predicted price215 best_strike = min(strikes, key=lambda s: abs(s["strike"] - predicted_price))216 else:217 best_strike = None218 219 results[ticker] = {220 "predicted_price_on_expiry": predicted_price,221 "best_matching_option": best_strike222 }223 224 except Exception as e:225 results[ticker] = {"error": str(e)}226 227 if loss_history:228 plot_loss_per_ticker(loss_history)229 230 predicted_prices = {231 ticker: data["predicted_price_on_expiry"]232 for ticker, data in results.items()233 if "predicted_price_on_expiry" in data234 }235 236 best_strike_prices = {237 ticker: data["best_matching_option"]238 for ticker, data in results.items()239 if "best_matching_option" in data240 }241 242 return {243 "predicted_prices": predicted_prices,244 "best_strike_prices": best_strike_prices245 }246 247ml_predictor_agent = RunnableLambda(_ml_predictor_agent)248 