|
|
|
|
|
import streamlit as st |
|
import pandas as pd |
|
import yfinance as yf |
|
import plotly.graph_objects as go |
|
from plotly.subplots import make_subplots |
|
import ta |
|
from datetime import datetime, timedelta |
|
import pytz |
|
|
|
|
|
@st.cache_data(ttl=600) |
|
def fetch_stock_data(symbol, start_date, end_date, interval): |
|
"""Fetch stock data with error handling and data validation.""" |
|
try: |
|
|
|
ist = pytz.timezone('Asia/Kolkata') |
|
end_date_ist = datetime.now(ist).date() |
|
|
|
ticker = yf.Ticker(symbol) |
|
data = ticker.history(start=start_date.strftime('%Y-%m-%d'), |
|
end=(end_date + timedelta(days=1)).strftime('%Y-%m-%d'), |
|
interval=interval) |
|
|
|
if data.empty: |
|
st.error(f"No data available for {symbol} with interval '{interval}'. Please verify the symbol, interval, or try a different date range.") |
|
return None |
|
|
|
|
|
data = calculate_technical_indicators(data) |
|
|
|
return data |
|
|
|
except Exception as e: |
|
st.error(f"Error fetching data for {symbol}: {e}") |
|
return None |
|
|
|
def calculate_technical_indicators(data): |
|
"""Calculate comprehensive technical indicators.""" |
|
|
|
data['SMA_20'] = ta.trend.sma_indicator(data['Close'], window=20) |
|
data['SMA_50'] = ta.trend.sma_indicator(data['Close'], window=50) |
|
data['SMA_200'] = ta.trend.sma_indicator(data['Close'], window=200) |
|
data['EMA_20'] = ta.trend.ema_indicator(data['Close'], window=20) |
|
|
|
|
|
data['RSI'] = ta.momentum.rsi(data['Close'], window=14) |
|
data['MACD'] = ta.trend.macd_diff(data['Close']) |
|
data['ROC'] = ta.momentum.roc(data['Close'], window=12) |
|
|
|
|
|
data['OBV'] = ta.volume.on_balance_volume(data['Close'], data['Volume']) |
|
data['MFI'] = ta.volume.money_flow_index(data['High'], data['Low'], data['Close'], data['Volume']) |
|
|
|
|
|
bb = ta.volatility.BollingerBands(close=data['Close']) |
|
data['BB_upper'] = bb.bollinger_hband() |
|
data['BB_middle'] = bb.bollinger_mavg() |
|
data['BB_lower'] = bb.bollinger_lband() |
|
data['ATR'] = ta.volatility.average_true_range(data['High'], data['Low'], data['Close']) |
|
|
|
|
|
data['Daily_Return'] = data['Close'].pct_change() * 100 |
|
data['52W_High'] = data['Close'].rolling(window=252).max() |
|
data['52W_Low'] = data['Close'].rolling(window=252).min() |
|
data['Distance_From_52W_High'] = ((data['Close'] - data['52W_High']) / data['52W_High']) * 100 |
|
|
|
return data |
|
|
|
def plot_charts(data, symbol, interval): |
|
"""Create interactive charts using Plotly.""" |
|
|
|
minute_intervals = ["1m", "2m", "5m", "15m", "30m", "60m", "90m", "1h"] |
|
|
|
|
|
if interval in minute_intervals: |
|
rows = 3 |
|
subplot_titles = ('Price & Volume', 'RSI', 'MACD') |
|
row_heights = [0.5, 0.25, 0.25] |
|
else: |
|
rows = 4 |
|
subplot_titles = ('Price & Volume', 'RSI', 'MACD', 'Technical Indicators') |
|
row_heights = [0.4, 0.2, 0.2, 0.2] |
|
|
|
fig = make_subplots(rows=rows, cols=1, |
|
shared_xaxes=True, |
|
vertical_spacing=0.05, |
|
subplot_titles=subplot_titles, |
|
row_heights=row_heights) |
|
|
|
|
|
fig.add_trace(go.Candlestick(x=data.index, |
|
open=data['Open'], |
|
high=data['High'], |
|
low=data['Low'], |
|
close=data['Close'], |
|
name='OHLC'), |
|
row=1, col=1) |
|
|
|
|
|
if 'SMA_20' in data.columns: |
|
fig.add_trace(go.Scatter(x=data.index, y=data['SMA_20'], name='SMA 20', line=dict(color='orange')), row=1, col=1) |
|
if 'SMA_50' in data.columns: |
|
fig.add_trace(go.Scatter(x=data.index, y=data['SMA_50'], name='SMA 50', line=dict(color='blue')), row=1, col=1) |
|
if 'SMA_200' in data.columns: |
|
fig.add_trace(go.Scatter(x=data.index, y=data['SMA_200'], name='SMA 200', line=dict(color='red')), row=1, col=1) |
|
|
|
if interval not in minute_intervals: |
|
|
|
colors = ['red' if row['Open'] - row['Close'] >= 0 else 'green' for index, row in data.iterrows()] |
|
fig.add_trace(go.Bar(x=data.index, y=data['Volume'], name='Volume', marker_color=colors), row=1, col=1) |
|
|
|
|
|
if 'RSI' in data.columns: |
|
fig.add_trace(go.Scatter(x=data.index, y=data['RSI'], name='RSI', line=dict(color='purple')), row=2, col=1) |
|
fig.add_hline(y=70, line_dash="dash", line_color="red", row=2, col=1) |
|
fig.add_hline(y=30, line_dash="dash", line_color="green", row=2, col=1) |
|
|
|
|
|
if 'MACD' in data.columns: |
|
fig.add_trace(go.Scatter(x=data.index, y=data['MACD'], name='MACD', line=dict(color='blue')), row=3, col=1) |
|
|
|
if rows == 4: |
|
|
|
if 'BB_upper' in data.columns and 'BB_middle' in data.columns and 'BB_lower' in data.columns: |
|
fig.add_trace(go.Scatter(x=data.index, y=data['BB_upper'], name='BB Upper', |
|
line=dict(color='gray', dash='dash')), row=4, col=1) |
|
fig.add_trace(go.Scatter(x=data.index, y=data['BB_middle'], name='BB Middle', |
|
line=dict(color='gray')), row=4, col=1) |
|
fig.add_trace(go.Scatter(x=data.index, y=data['BB_lower'], name='BB Lower', |
|
line=dict(color='gray', dash='dash')), row=4, col=1) |
|
|
|
|
|
fig.update_layout( |
|
title=f'{symbol} Technical Analysis Dashboard', |
|
yaxis_title='Price', |
|
height=800 if rows == 4 else 600, |
|
showlegend=True, |
|
xaxis_rangeslider_visible=False |
|
) |
|
|
|
return fig |
|
|
|
def main(): |
|
st.set_page_config(layout="wide") |
|
st.title("Indian Stock Market Technical Analysis Dashboard") |
|
|
|
|
|
minute_intervals = ["1m", "2m", "5m", "15m", "30m", "60m", "90m", "1h"] |
|
daily_or_higher_intervals = ["1d", "5d", "1wk", "1mo", "3mo"] |
|
all_intervals = minute_intervals + daily_or_higher_intervals |
|
|
|
|
|
st.sidebar.header("Configuration") |
|
symbol = st.sidebar.text_input("Enter Stock Symbol (e.g., RELIANCE.NS, TCS.NS):", "RELIANCE.NS").strip().upper() |
|
|
|
interval = st.sidebar.selectbox( |
|
"Select Data Interval", |
|
all_intervals, |
|
index=8 |
|
) |
|
|
|
|
|
ist = pytz.timezone('Asia/Kolkata') |
|
end_date_ist = datetime.now(ist).date() |
|
|
|
|
|
if interval in minute_intervals: |
|
|
|
start_date = end_date_ist - timedelta(days=7) |
|
st.sidebar.write(f"**Note:** For interval '{interval}', only the last 7 days of data are available.") |
|
st.sidebar.write(f"**Start Date:** {start_date.strftime('%Y-%m-%d')}") |
|
else: |
|
|
|
|
|
max_history = 5 * 365 |
|
default_start = end_date_ist - timedelta(days=365) |
|
start_date = st.sidebar.date_input( |
|
"Start Date", |
|
value=default_start, |
|
min_value=end_date_ist - timedelta(days=max_history), |
|
max_value=end_date_ist - timedelta(days=1) |
|
) |
|
|
|
|
|
end_date = end_date_ist |
|
|
|
|
|
if st.sidebar.button("Fetch Data and Update Charts"): |
|
with st.spinner('Fetching data...'): |
|
data = fetch_stock_data(symbol, start_date, end_date, interval) |
|
|
|
if data is not None: |
|
|
|
st.subheader(f"Analysis for {symbol}") |
|
current_price = data['Close'][-1] |
|
if len(data) >= 2: |
|
daily_change = ((data['Close'][-1] - data['Close'][-2]) / data['Close'][-2]) * 100 |
|
else: |
|
daily_change = 0.0 |
|
|
|
col1, col2, col3, col4 = st.columns(4) |
|
col1.metric("Current Price", f"₹{current_price:.2f}", f"{daily_change:.2f}%") |
|
|
|
if '52W_High' in data.columns and '52W_Low' in data.columns: |
|
col2.metric("52 Week High", f"₹{data['52W_High'][-1]:.2f}") |
|
col3.metric("52 Week Low", f"₹{data['52W_Low'][-1]:.2f}") |
|
|
|
if 'RSI' in data.columns: |
|
col4.metric("RSI", f"{data['RSI'][-1]:.2f}") |
|
|
|
|
|
fig = plot_charts(data, symbol, interval) |
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.subheader("Technical Analysis Summary") |
|
|
|
|
|
signals = [] |
|
if 'RSI' in data.columns: |
|
rsi = data['RSI'][-1] |
|
if rsi < 30: |
|
signals.append("RSI indicates oversold conditions") |
|
elif rsi > 70: |
|
signals.append("RSI indicates overbought conditions") |
|
|
|
if 'MACD' in data.columns: |
|
macd = data['MACD'][-1] |
|
if macd > 0: |
|
signals.append("MACD is positive - Bullish momentum") |
|
else: |
|
signals.append("MACD is negative - Bearish momentum") |
|
|
|
if 'SMA_20' in data.columns and 'SMA_50' in data.columns: |
|
current_price = data['Close'][-1] |
|
sma_20 = data['SMA_20'][-1] |
|
sma_50 = data['SMA_50'][-1] |
|
|
|
if current_price > sma_20 and current_price > sma_50: |
|
signals.append("Price is above major moving averages - Bullish") |
|
elif current_price < sma_20 and current_price < sma_50: |
|
signals.append("Price is below major moving averages - Bearish") |
|
|
|
for signal in signals: |
|
st.write(f"• {signal}") |
|
|
|
if not signals: |
|
st.write("No clear technical signals detected.") |
|
|
|
|
|
with st.expander("View Raw Data"): |
|
st.dataframe(data) |
|
|
|
|
|
csv = data.to_csv(index=True) |
|
st.download_button( |
|
label="Download data as CSV", |
|
data=csv, |
|
file_name=f'{symbol}_{interval}.csv', |
|
mime='text/csv', |
|
) |
|
|
|
if __name__ == "__main__": |
|
main() |
|
|