Sanyog Chavhan
commited on
Commit
Β·
e74d865
1
Parent(s):
cb9f190
Pushing only required files, removed sensitive files
Browse files- ai_insights.py +34 -0
- app.py +89 -0
- data_loader.py +31 -0
- indicators.py +111 -0
- portfolio_optimiser.py +67 -0
- requirements.txt +8 -0
ai_insights.py
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import openai
|
2 |
+
import streamlit as st
|
3 |
+
import os
|
4 |
+
|
5 |
+
def get_ai_insight(indicator_name, indicator_value, api_key):
|
6 |
+
"""
|
7 |
+
Generates an AI-driven insight for a given indicator and its value using LLM.
|
8 |
+
|
9 |
+
Args:
|
10 |
+
indicator_name (str): Name of the technical indicator.
|
11 |
+
indicator_value (float): Value of the technical indicator.
|
12 |
+
api_key(str) : API key for accessing the LLM
|
13 |
+
|
14 |
+
Returns:
|
15 |
+
str: AI-generated insight.
|
16 |
+
"""
|
17 |
+
client = openai.OpenAI(api_key = api_key)
|
18 |
+
try:
|
19 |
+
prompt = f"Explain the significance of a {indicator_name} value of {indicator_value} in stock trading in one sentence."
|
20 |
+
response = client.chat.completions.create(
|
21 |
+
model="gpt-4-turbo", # Use "gpt-3.5-turbo" for faster and cheaper results
|
22 |
+
messages=[{"role": "system", "content": "You are a stock trading expert."},
|
23 |
+
{"role": "user", "content": prompt}],
|
24 |
+
temperature=0.5 # Moderate creativity
|
25 |
+
)
|
26 |
+
|
27 |
+
return response.choices[0].message.content.strip()
|
28 |
+
|
29 |
+
except openai.AuthenticationError:
|
30 |
+
return "Invalid OpenAI API key. Please check your API key in Streamlit secrets."
|
31 |
+
except openai.RateLimitError:
|
32 |
+
return "OpenAI API rate limit exceeded. Try again later."
|
33 |
+
except Exception as e:
|
34 |
+
return f"Error generating AI insight: {e}"
|
app.py
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import plotly.express as px
|
4 |
+
from data_loader import load_data
|
5 |
+
from indicators import calculate_indicators
|
6 |
+
from ai_insights import get_ai_insight
|
7 |
+
from portfolio_optimiser import optimize_portfolio
|
8 |
+
import os
|
9 |
+
|
10 |
+
# Streamlit UI
|
11 |
+
st.title("AI-Based Stock Analysis and Portfolio Optimisation")
|
12 |
+
|
13 |
+
# User Inputs
|
14 |
+
tickers = st.text_input("Enter stock tickers (comma-separated, e.g., TSLA, AAPL, MSFT):", "TSLA, AAPL")
|
15 |
+
tickers = [ticker.strip().upper() for ticker in tickers.split(",")]
|
16 |
+
|
17 |
+
start_date = st.date_input("Start Date:", value=pd.to_datetime("2024-01-01"))
|
18 |
+
end_date = st.date_input("End Date:", value=pd.to_datetime("2025-01-01"))
|
19 |
+
|
20 |
+
ma_window = st.slider("Moving Average Window:", min_value=5, max_value=50, value=20)
|
21 |
+
rsi_window = st.slider("RSI Window:", min_value=5, max_value=30, value=14)
|
22 |
+
macd_fast = st.slider("MACD Fast Period:", min_value=5, max_value=30, value=12)
|
23 |
+
macd_slow = st.slider("MACD Slow Period:", min_value=20, max_value=50, value=26)
|
24 |
+
macd_signal = st.slider("MACD Signal Period:", min_value=5, max_value=20, value=9)
|
25 |
+
|
26 |
+
risk_tolerance = st.slider("Risk Tolerance (0-1):", min_value=0.0, max_value=1.0, value=0.5, step=0.01)
|
27 |
+
|
28 |
+
|
29 |
+
def calculate_and_return_everything(data):
|
30 |
+
# Calculate Indicators
|
31 |
+
indicator_data = calculate_indicators(data, ma_window, rsi_window, macd_fast, macd_slow, macd_signal)
|
32 |
+
if indicator_data is None:
|
33 |
+
st.stop()
|
34 |
+
|
35 |
+
# **AI Insights**
|
36 |
+
st.subheader("π‘ AI Insights")
|
37 |
+
last_row = indicator_data.iloc[-1]
|
38 |
+
|
39 |
+
api_key = st.secrets["OPENAI_API_KEY"] # Ensure API key is set
|
40 |
+
|
41 |
+
# Get AI insights for each indicator
|
42 |
+
ma_insight = get_ai_insight("Moving Average", last_row['MA'], api_key)
|
43 |
+
bb_insight = get_ai_insight("Bollinger Bands", f"Upper: {last_row['Upper_BB']}, Lower: {last_row['Lower_BB']}", api_key)
|
44 |
+
rsi_insight = get_ai_insight("RSI", last_row['RSI'], api_key)
|
45 |
+
macd_insight = get_ai_insight("MACD", last_row['MACD'], api_key)
|
46 |
+
|
47 |
+
st.write(f"Moving Average: {ma_insight}")
|
48 |
+
st.write(f"Bollinger Bands: {bb_insight}")
|
49 |
+
st.write(f"RSI: {rsi_insight}")
|
50 |
+
st.write(f"MACD: {macd_insight}")
|
51 |
+
|
52 |
+
return indicator_data
|
53 |
+
|
54 |
+
|
55 |
+
|
56 |
+
if st.button("Analyse"):
|
57 |
+
all_data = {}
|
58 |
+
for i in tickers:
|
59 |
+
# Load Data
|
60 |
+
data = load_data(i, start_date, end_date) # Passing ticker instead of tickers
|
61 |
+
if data is None:
|
62 |
+
st.warning(f"No data found for {i}. Skipping...")
|
63 |
+
continue
|
64 |
+
st.subheader(f'Ticker: {i}')
|
65 |
+
id = calculate_and_return_everything(data)
|
66 |
+
if id is not None:
|
67 |
+
all_data[i] = id
|
68 |
+
|
69 |
+
if not all_data:
|
70 |
+
st.warning("No valid data available for any of the tickers. Exiting analysis.")
|
71 |
+
st.stop()
|
72 |
+
|
73 |
+
# π **Portfolio Optimisation**
|
74 |
+
st.subheader("Portfolio Optimisation")
|
75 |
+
|
76 |
+
# Extract closing prices for portfolio optimization
|
77 |
+
close_prices = pd.DataFrame({ticker: all_data[ticker]['Close'] for ticker in all_data})
|
78 |
+
|
79 |
+
print(close_prices.columns)
|
80 |
+
|
81 |
+
asset_weights = optimize_portfolio(close_prices, risk_tolerance)
|
82 |
+
if asset_weights:
|
83 |
+
st.write("### Optimised Asset Allocation:")
|
84 |
+
for ticker, weight in asset_weights.items():
|
85 |
+
st.write(f"**{ticker}:** {weight:.4f}")
|
86 |
+
else:
|
87 |
+
st.write("Could not perform portfolio optimisation.")
|
88 |
+
|
89 |
+
print("Ensure that you have your OpenAI API set as environmental variable in Hugging Face Spaces for this script to work")
|
data_loader.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import yfinance as yf
|
2 |
+
import pandas as pd
|
3 |
+
import streamlit as st
|
4 |
+
|
5 |
+
def load_data(ticker, start_date, end_date):
|
6 |
+
"""
|
7 |
+
Loads stock data from Yahoo Finance for a single ticker and date range.
|
8 |
+
|
9 |
+
Args:
|
10 |
+
ticker (str): Stock ticker symbol.
|
11 |
+
start_date (str): Start date in YYYY-MM-DD format.
|
12 |
+
end_date (str): End date in YYYY-MM-DD format.
|
13 |
+
|
14 |
+
Returns:
|
15 |
+
pandas.DataFrame: DataFrame containing stock data for the ticker.
|
16 |
+
"""
|
17 |
+
try:
|
18 |
+
data = yf.download(ticker, start=start_date, end=end_date)
|
19 |
+
# Flatten the MultiIndex columns
|
20 |
+
data.columns = data.columns.droplevel(1) # Drop the second level (ticker name)
|
21 |
+
data.columns.name = None # Remove the column name
|
22 |
+
|
23 |
+
if data is None or data.empty:
|
24 |
+
st.error(f"No data found for ticker: {ticker}")
|
25 |
+
return None
|
26 |
+
|
27 |
+
return data
|
28 |
+
|
29 |
+
except Exception as e:
|
30 |
+
st.error(f"Error loading data: {e}")
|
31 |
+
return None
|
indicators.py
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import pandas_ta as ta
|
3 |
+
import plotly.graph_objects as go
|
4 |
+
import streamlit as st
|
5 |
+
from ai_insights import get_ai_insight # Import AI insight function
|
6 |
+
|
7 |
+
def calculate_indicators(data, ma_window, rsi_window, macd_fast, macd_slow, macd_signal):
|
8 |
+
"""
|
9 |
+
Calculates Moving Average (MA), Bollinger Bands, RSI, MACD, and displays them in a 2x2 grid in Streamlit.
|
10 |
+
Also includes AI-generated insights next to each chart.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
data (pandas.DataFrame): Stock data.
|
14 |
+
ma_window (int): Moving Average window.
|
15 |
+
rsi_window (int): RSI window.
|
16 |
+
macd_fast (int): MACD Fast period.
|
17 |
+
macd_slow (int): MACD Slow period.
|
18 |
+
macd_signal (int): MACD Signal period.
|
19 |
+
|
20 |
+
Returns:
|
21 |
+
pandas.DataFrame: Data with calculated indicators.
|
22 |
+
"""
|
23 |
+
|
24 |
+
# π **Calculate Moving Average (MA)**
|
25 |
+
data['MA'] = data['Close'].rolling(window=ma_window).mean()
|
26 |
+
|
27 |
+
# π **Calculate Bollinger Bands**
|
28 |
+
data['stddev'] = data['Close'].rolling(window=ma_window).std()
|
29 |
+
data['Upper_BB'] = data['MA'] + (2 * data['stddev'])
|
30 |
+
data['Lower_BB'] = data['MA'] - (2 * data['stddev'])
|
31 |
+
|
32 |
+
# π **Calculate RSI**
|
33 |
+
delta = data['Close'].diff(1)
|
34 |
+
gain = (delta.where(delta > 0, 0)).rolling(window=rsi_window).mean()
|
35 |
+
loss = (-delta.where(delta < 0, 0)).rolling(window=rsi_window).mean()
|
36 |
+
rs = gain / loss
|
37 |
+
data['RSI'] = 100 - (100 / (1 + rs))
|
38 |
+
|
39 |
+
# π **Calculate MACD**
|
40 |
+
data['MACD'] = data['Close'].ewm(span=macd_fast, adjust=False).mean() - data['Close'].ewm(span=macd_slow, adjust=False).mean()
|
41 |
+
data['MACD_Signal'] = data['MACD'].ewm(span=macd_signal, adjust=False).mean()
|
42 |
+
data['MACD_Hist'] = data['MACD'] - data['MACD_Signal']
|
43 |
+
|
44 |
+
# π **Create Candlestick Chart with Bollinger Bands & MA**
|
45 |
+
fig_candlestick = go.Figure()
|
46 |
+
fig_candlestick.add_trace(go.Candlestick(
|
47 |
+
x=data.index,
|
48 |
+
open=data['Open'], high=data['High'],
|
49 |
+
low=data['Low'], close=data['Close'],
|
50 |
+
name="Candlestick",
|
51 |
+
increasing_line_color="green",
|
52 |
+
decreasing_line_color="red"
|
53 |
+
))
|
54 |
+
fig_candlestick.add_trace(go.Scatter(x=data.index, y=data['MA'], mode='lines', name=f"{ma_window}-day MA", line=dict(color="blue")))
|
55 |
+
fig_candlestick.add_trace(go.Scatter(x=data.index, y=data['Upper_BB'], mode='lines', name="Upper BB", line=dict(color="purple", dash='dot')))
|
56 |
+
fig_candlestick.add_trace(go.Scatter(x=data.index, y=data['Lower_BB'], mode='lines', name="Lower BB", line=dict(color="purple", dash='dot')))
|
57 |
+
fig_candlestick.update_layout(title="π Stock Price with Bollinger Bands & MA", xaxis_rangeslider_visible=True, template="plotly_dark", height=500, width=900)
|
58 |
+
|
59 |
+
# π **Create RSI Chart**
|
60 |
+
fig_rsi = go.Figure()
|
61 |
+
fig_rsi.add_trace(go.Scatter(x=data.index, y=data['RSI'], mode='lines', name="RSI", line=dict(color="orange")))
|
62 |
+
fig_rsi.add_trace(go.Scatter(x=data.index, y=[70]*len(data), mode='lines', name="Overbought (70)", line=dict(color="red", dash='dash')))
|
63 |
+
fig_rsi.add_trace(go.Scatter(x=data.index, y=[30]*len(data), mode='lines', name="Oversold (30)", line=dict(color="green", dash='dash')))
|
64 |
+
fig_rsi.update_layout(title="π Relative Strength Index (RSI)", xaxis_rangeslider_visible=False, template="plotly_dark", height=500, width=900)
|
65 |
+
|
66 |
+
# π **Create MACD Chart**
|
67 |
+
fig_macd = go.Figure()
|
68 |
+
fig_macd.add_trace(go.Scatter(x=data.index, y=data['MACD'], mode='lines', name="MACD", line=dict(color="blue")))
|
69 |
+
fig_macd.add_trace(go.Scatter(x=data.index, y=data['MACD_Signal'], mode='lines', name="MACD Signal", line=dict(color="red")))
|
70 |
+
fig_macd.add_trace(go.Bar(x=data.index, y=data['MACD_Hist'], name="MACD Histogram", marker_color="purple"))
|
71 |
+
fig_macd.update_layout(title="π MACD Indicator", xaxis_rangeslider_visible=False, template="plotly_dark", height=500, width=900)
|
72 |
+
|
73 |
+
# π **Display Charts One Below Another**
|
74 |
+
st.subheader("π Stock Price with Bollinger Bands & Moving Averages")
|
75 |
+
st.plotly_chart(fig_candlestick, use_container_width=True) # π Stock Chart
|
76 |
+
|
77 |
+
st.subheader("π Relative Strength Index (RSI)")
|
78 |
+
st.plotly_chart(fig_rsi, use_container_width=True) # π RSI Chart
|
79 |
+
|
80 |
+
st.subheader("π MACD Indicator")
|
81 |
+
st.plotly_chart(fig_macd, use_container_width=True) # π MACD Chart
|
82 |
+
|
83 |
+
|
84 |
+
print(data.columns)
|
85 |
+
|
86 |
+
'''
|
87 |
+
# π **Display Charts and AI Insights in a 2x2 Grid**
|
88 |
+
col1, col2 = st.columns((7, 1.5)) # 2/3 chart, 1/3 AI insight
|
89 |
+
with col1:
|
90 |
+
st.plotly_chart(fig_candlestick, use_container_width=True) # π Stock Price
|
91 |
+
with col2:
|
92 |
+
st.subheader("π‘ AI Insight for Moving Average")
|
93 |
+
st.write(ma_insight)
|
94 |
+
st.subheader("π‘ AI Insight for Bollinger Bands")
|
95 |
+
st.write(bb_insight)
|
96 |
+
|
97 |
+
col3, col4 = st.columns((7, 1.5))
|
98 |
+
with col3:
|
99 |
+
st.plotly_chart(fig_rsi, use_container_width=True) # π RSI
|
100 |
+
with col4:
|
101 |
+
st.subheader("π‘ AI Insight for RSI")
|
102 |
+
st.write(rsi_insight)
|
103 |
+
|
104 |
+
col5, col6 = st.columns((7, 1.5))
|
105 |
+
with col5:
|
106 |
+
st.plotly_chart(fig_macd, use_container_width=True) # π MACD
|
107 |
+
with col6:
|
108 |
+
st.subheader("π‘ AI Insight for MACD")
|
109 |
+
st.write(macd_insight)
|
110 |
+
'''
|
111 |
+
return data # β
Return only data, all charts & AI insights are displayed in Streamlit
|
portfolio_optimiser.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import numpy as np
|
3 |
+
from scipy.optimize import minimize
|
4 |
+
|
5 |
+
def optimize_portfolio(data, risk_tolerance):
|
6 |
+
"""
|
7 |
+
Performs Modern Portfolio Theory (MPT) optimization with diversification constraints.
|
8 |
+
|
9 |
+
Args:
|
10 |
+
data (pandas.DataFrame): DataFrame containing historical stock prices.
|
11 |
+
risk_tolerance (float): Risk tolerance level (higher value means higher risk).
|
12 |
+
|
13 |
+
Returns:
|
14 |
+
dict: Dictionary containing optimized asset weights for a diversified portfolio.
|
15 |
+
"""
|
16 |
+
try:
|
17 |
+
# β
Calculate daily returns & covariance matrix
|
18 |
+
returns = data.pct_change().dropna()
|
19 |
+
mean_returns = returns.mean()
|
20 |
+
cov_matrix = returns.cov()
|
21 |
+
|
22 |
+
num_assets = len(mean_returns)
|
23 |
+
risk_free_rate = 0.01 # Example: Assume a 1% risk-free rate
|
24 |
+
|
25 |
+
# π **Objective Function: Maximize Sharpe Ratio**
|
26 |
+
def negative_sharpe_ratio(weights):
|
27 |
+
portfolio_return = np.sum(mean_returns * weights)
|
28 |
+
portfolio_std_dev = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
|
29 |
+
sharpe_ratio = (portfolio_return - risk_free_rate) / portfolio_std_dev
|
30 |
+
return -sharpe_ratio # Negative because we minimize in SciPy
|
31 |
+
|
32 |
+
# π **Constraints:**
|
33 |
+
constraints = [
|
34 |
+
{"type": "eq", "fun": lambda x: np.sum(x) - 1}, # Sum of weights must be 1
|
35 |
+
]
|
36 |
+
|
37 |
+
# π **Set Diversification Constraints**
|
38 |
+
max_allocation = 0.50 # Max 50% allocation per asset
|
39 |
+
min_assets_allocated = 2 # At least 2 assets must have nonzero allocation
|
40 |
+
|
41 |
+
def diversification_constraint(weights):
|
42 |
+
return np.count_nonzero(weights) - min_assets_allocated # Enforce minimum assets
|
43 |
+
|
44 |
+
constraints.append({"type": "ineq", "fun": diversification_constraint})
|
45 |
+
|
46 |
+
# π **Set Bounds (Per-Asset Allocation Limits)**
|
47 |
+
bounds = tuple((0.05, max_allocation) for _ in range(num_assets)) # Min 5%, Max 50%
|
48 |
+
|
49 |
+
# π **Initialize Equal Weights**
|
50 |
+
initial_weights = np.array([1 / num_assets] * num_assets)
|
51 |
+
|
52 |
+
# π **Optimize Portfolio Allocation**
|
53 |
+
optimized_results = minimize(
|
54 |
+
negative_sharpe_ratio,
|
55 |
+
initial_weights,
|
56 |
+
method="SLSQP",
|
57 |
+
bounds=bounds,
|
58 |
+
constraints=constraints,
|
59 |
+
)
|
60 |
+
|
61 |
+
# β
Extract Optimal Weights
|
62 |
+
asset_weights = dict(zip(data.columns, optimized_results.x))
|
63 |
+
return asset_weights
|
64 |
+
|
65 |
+
except Exception as e:
|
66 |
+
print(f"Error optimizing portfolio: {e}")
|
67 |
+
return None
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
yfinance
|
3 |
+
pandas
|
4 |
+
numpy
|
5 |
+
scipy
|
6 |
+
plotly
|
7 |
+
openai
|
8 |
+
pandas-ta
|