Spaces:
Sleeping
Sleeping
import streamlit as st | |
import pandas as pd | |
import numpy as np | |
import plotly.graph_objects as go | |
import plotly.express as px | |
from plotly.subplots import make_subplots | |
import yfinance as yf | |
import pickle | |
from datetime import datetime, timedelta | |
import warnings | |
from curl_cffi import requests | |
session = requests.Session(impersonate="chrome") | |
from indicators.rsi import rsi | |
from indicators.sma import sma | |
from indicators.ema import ema | |
from indicators.macd import macd | |
from strategy.rule_based_strategy import generate_signals_sma, generate_signals_ema | |
from utils.backtester import backtest_signals | |
from utils.logger import setup_logger | |
import logging | |
from indicators.enhanced_features import ( | |
create_volatility_features, create_enhanced_lag_features, | |
create_volume_features, create_momentum_features, create_position_features | |
) | |
# Suppress warnings | |
warnings.filterwarnings('ignore') | |
# Page config | |
st.set_page_config( | |
page_title="Complete Stock Trading & Prediction Platform", | |
page_icon="📈", | |
layout="wide" | |
) | |
# Initialize logging once | |
if 'logging_initialized' not in st.session_state: | |
setup_logger(log_dir="logs", log_level=logging.INFO) | |
st.session_state.logging_initialized = True | |
logging.info("=== Streamlit Trading App Started ===") | |
# Stock symbols | |
STOCK_SYMBOLS = [ | |
'ADANIENT.NS', 'ADANIPORTS.NS', 'APOLLOHOSP.NS', 'ASIANPAINT.NS', | |
'AXISBANK.NS', 'BAJAJ-AUTO.NS', 'BAJFINANCE.NS', 'BAJAJFINSV.NS', | |
'BEL.NS', 'BHARTIARTL.NS', 'CIPLA.NS', 'COALINDIA.NS', 'DRREDDY.NS', | |
'EICHERMOT.NS', 'GRASIM.NS', 'HCLTECH.NS', 'HDFCBANK.NS', 'HDFCLIFE.NS', | |
'HEROMOTOCO.NS', 'HINDALCO.NS', 'HINDUNILVR.NS', 'ICICIBANK.NS', | |
'INDUSINDBK.NS', 'INFY.NS', 'ITC.NS', 'JIOFIN.NS', 'JSWSTEEL.NS', | |
'KOTAKBANK.NS', 'LT.NS', 'M&M.NS', 'MARUTI.NS', 'NESTLEIND.NS', | |
'NTPC.NS', 'ONGC.NS', 'POWERGRID.NS', 'RELIANCE.NS', 'SBILIFE.NS', | |
'SHRIRAMFIN.NS', 'SBIN.NS', 'SUNPHARMA.NS', 'TATACONSUM.NS', 'TCS.NS', | |
'TATAMOTORS.NS', 'TATASTEEL.NS', 'TECHM.NS', 'TITAN.NS', 'TRENT.NS', | |
'ULTRACEMCO.NS', 'WIPRO.NS', 'ETERNAL.NS' | |
] | |
# Feature list for ML model | |
FEATURES = [ | |
'Close', 'Volume', 'SMA20', 'SMA50', 'EMA20', 'EMA50', | |
'RSI14', 'MACD', 'MACD_signal', 'MACD_hist', | |
'SMA_crossover', 'RSI_oversold', | |
'return_1d', 'volatility_5d', 'volatility_10d', 'volatility_20d', | |
'volatility_30d', 'vol_ratio_5_20', 'vol_ratio_10_20', 'vol_rank_20', | |
'vol_rank_50', 'return_lag_1', 'return_lag_2', 'return_lag_3', | |
'return_lag_5', 'return_lag_10', 'rsi_lag_1', 'macd_lag_1', 'rsi_lag_2', | |
'macd_lag_2', 'rsi_lag_3', 'macd_lag_3', 'volume_sma_10', | |
'volume_sma_20', 'volume_sma_50', 'volume_ratio_10', 'volume_ratio_20', | |
'volume_ratio_50', 'price_volume', 'pv_sma_5', 'volume_momentum_5', | |
'momentum_3d', 'momentum_5d', 'momentum_10d', 'momentum_20d', 'roc_5d', | |
'roc_10d', 'high_10d', 'low_10d', 'price_position_10', 'high_20d', | |
'low_20d', 'price_position_20', 'high_50d', 'low_50d', | |
'price_position_50', 'bb_upper', 'bb_lower', 'bb_position', 'target' | |
] | |
# ========================= SHARED FUNCTIONS ========================= | |
def load_stock_data(symbol, start_date, end_date): | |
"""Load stock data from Yahoo Finance""" | |
logging.info(f"Loading stock data for {symbol} from {start_date} to {end_date}") | |
try: | |
data = yf.download(symbol, start=start_date, end=end_date, session=session) | |
# Flatten the MultiIndex columns | |
if data.columns.nlevels > 1: | |
data.columns = [col[0] for col in data.columns] | |
logging.info(f"Successfully loaded {len(data)} records for {symbol}") | |
return data | |
except Exception as e: | |
logging.error(f"Error loading data for {symbol}: {str(e)}") | |
st.error(f"Error loading data: {e}") | |
return None | |
def process_stock_data(df, short_period, long_period, rsi_period): | |
"""Process stock data to create all features""" | |
df = df.copy() | |
# Basic technical indicators | |
df['SMA20'] = sma(df, short_period) | |
df['SMA50'] = sma(df, long_period) | |
df['EMA20'] = ema(df, short_period) | |
df['EMA50'] = ema(df, long_period) | |
df['RSI14'] = rsi(df, rsi_period) | |
df['RSI20'] = rsi(df, rsi_period + 6) | |
df['MACD'], df['MACD_signal'], df['MACD_hist'] = macd(df) | |
# Bollinger Bands | |
df['Upper_Band'] = df['SMA20'] + 2 * df['Close'].rolling(window=20).std() | |
df['Lower_Band'] = df['SMA20'] - 2 * df['Close'].rolling(window=20).std() | |
# Create feature sets | |
df = create_volatility_features(df) | |
df = create_enhanced_lag_features(df) | |
df = create_volume_features(df) | |
df = create_momentum_features(df) | |
df = create_position_features(df) | |
# Additional features | |
df['SMA_crossover'] = (df['SMA20'] > df['SMA50']).astype(int) | |
df['RSI_oversold'] = (df['RSI14'] < 30).astype(int) | |
# Target: next-day up/down | |
df['next_close'] = df['Close'].shift(-1) | |
df['target'] = (df['next_close'] > df['Close']).astype(int) | |
return df | |
# ========================= MAIN APPLICATION ========================= | |
# Main navigation | |
st.title("📈 Stock Trading & Prediction Platform") | |
# Navigation tabs | |
tab1, tab2 = st.tabs(["🔮 Price Prediction", "📊 Trading Dashboard"]) | |
# ========================= SIDEBAR CONFIGURATION ========================= | |
st.sidebar.header("📊 Configuration") | |
# Common inputs | |
selected_stock = st.sidebar.selectbox("Select Stock Symbol", STOCK_SYMBOLS, index=35) | |
start_date = st.sidebar.date_input("Start Date", value=datetime(2023, 1, 1)) | |
end_date = st.sidebar.date_input("End Date", value=datetime.now()) | |
logging.info(f"User selected stock: {selected_stock}, date range: {start_date} to {end_date}") | |
st.sidebar.subheader("📈 Technical Indicators") | |
rsi_period = st.sidebar.slider("RSI Period", min_value=5, max_value=30, value=14, step=1) | |
short_period = st.sidebar.slider("Short-term Period", min_value=5, max_value=50, value=20, step=1) | |
long_period = st.sidebar.slider("Long-term Period", min_value=50, max_value=200, value=50, step=1) | |
logging.info(f"RSI Period: {rsi_period}, Short-term Period: {short_period}, Long-term Period: {long_period}") | |
# Strategy selection (for trading dashboard) | |
strategy_type = st.sidebar.selectbox("Strategy Type", ["SMA-based", "EMA-based", "Both"]) | |
st.sidebar.subheader("💰 Backtesting Parameters") | |
initial_cash = st.sidebar.number_input("Initial Capital (₹)", min_value=10000, value=100000, step=10000) | |
transaction_cost = st.sidebar.slider("Transaction Cost (%)", 0.0, 1.0, 0.1, step=0.05) / 100 | |
stop_loss = st.sidebar.slider("Stop Loss (%)", 0.0, 20.0, 5.0, step=1.0) / 100 | |
take_profit = st.sidebar.slider("Take Profit (%)", 0.0, 50.0, 15.0, step=5.0) / 100 | |
use_risk_mgmt = st.sidebar.checkbox("Enable Risk Management", value=True) | |
logging.info(f"Initial Cash: ₹{initial_cash}, Transaction Cost: {transaction_cost*100}%, " | |
f"Stop Loss: {stop_loss*100}%, Take Profit: {take_profit*100}%, Risk Management: {use_risk_mgmt}") | |
# ========================= PRICE PREDICTION TAB ========================= | |
with tab1: | |
st.header(f"🔮 Price Prediction for {selected_stock}") | |
with st.spinner("Loading stock data..."): | |
stock_data = load_stock_data(selected_stock, start_date, end_date) | |
if stock_data is not None and not stock_data.empty: | |
# Display sample data | |
st.subheader("📊 Latest Stock Data") | |
st.dataframe(stock_data.tail(10), use_container_width=True) | |
# Process the data | |
processed_data = process_stock_data(stock_data, short_period, long_period, rsi_period) | |
processed_data = processed_data.dropna() | |
if len(processed_data) > 0: | |
# Get the latest row for prediction | |
latest_data = processed_data.iloc[-1] | |
# Display current stock info | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.metric("Current Price", f"₹{latest_data['Close']:.2f}") | |
with col2: | |
daily_change = ((latest_data['Close'] - processed_data.iloc[-2]['Close']) / processed_data.iloc[-2]['Close']) * 100 | |
st.metric("Daily Change", f"{daily_change:.2f}%") | |
with col3: | |
st.metric("Volume", f"{latest_data['Volume']:,.0f}") | |
with col4: | |
st.metric("RSI14", f"{latest_data['RSI14']:.2f}") | |
model = pickle.load(open('src/models/logistic_regression_model.pkl', 'rb')) | |
scaler = pickle.load(open('src/models/scaler.pkl', 'rb')) | |
# Create feature vector | |
feature_vector = latest_data[FEATURES].values.reshape(1, -1) | |
feature_vector_scaled = scaler.transform(feature_vector) | |
logging.info(f"Making price prediction for {selected_stock}") | |
# Make prediction | |
prediction = model.predict(feature_vector_scaled)[0] | |
probability = model.predict_proba(feature_vector_scaled)[0].max() | |
logging.info(f"Prediction: {'UP' if prediction == 1 else 'DOWN'} with {probability:.1%} confidence") | |
# Display prediction | |
st.header("🔮 Prediction Results") | |
col1, col2 = st.columns(2) | |
with col1: | |
if prediction == 1: | |
st.success("📈 **PREDICTION: UP**") | |
st.write(f"The model predicts the stock will go **UP** tomorrow with {probability:.1%} confidence.") | |
else: | |
st.error("📉 **PREDICTION: DOWN**") | |
st.write(f"The model predicts the stock will go **DOWN** tomorrow with {probability:.1%} confidence.") | |
with col2: | |
# Confidence gauge | |
fig_gauge = go.Figure(go.Indicator( | |
mode = "gauge+number", | |
value = probability * 100, | |
domain = {'x': [0, 1], 'y': [0, 1]}, | |
title = {'text': "Confidence %"}, | |
gauge = { | |
'axis': {'range': [None, 100]}, | |
'bar': {'color': "darkgreen" if prediction == 1 else "darkred"}, | |
'steps': [ | |
{'range': [0, 50], 'color': "lightgray"}, | |
{'range': [50, 80], 'color': "yellow"}, | |
{'range': [80, 100], 'color': "lightgreen"} | |
], | |
'threshold': { | |
'line': {'color': "red", 'width': 4}, | |
'thickness': 0.75, | |
'value': 90 | |
} | |
} | |
)) | |
fig_gauge.update_layout(height=300) | |
st.plotly_chart(fig_gauge, use_container_width=True) | |
# Technical Analysis Charts | |
st.header("📈 Technical Analysis") | |
# Price charts | |
col1, col2 = st.columns(2) | |
with col1: | |
# SMA Chart | |
fig_sma = go.Figure() | |
fig_sma.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['Close'][-60:], | |
mode='lines', name='Close Price', line=dict(color='blue', width=2))) | |
fig_sma.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['SMA20'][-60:], | |
mode='lines', name='SMA20', line=dict(color='orange', width=1))) | |
fig_sma.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['SMA50'][-60:], | |
mode='lines', name='SMA50', line=dict(color='red', width=1))) | |
fig_sma.update_layout(title=f"{selected_stock} - Simple Moving Averages", height=400) | |
st.plotly_chart(fig_sma, use_container_width=True) | |
with col2: | |
# EMA Chart | |
fig_ema = go.Figure() | |
fig_ema.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['Close'][-60:], | |
mode='lines', name='Close Price', line=dict(color='blue', width=2))) | |
fig_ema.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['EMA20'][-60:], | |
mode='lines', name='EMA20', line=dict(color='orange', width=1))) | |
fig_ema.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['EMA50'][-60:], | |
mode='lines', name='EMA50', line=dict(color='red', width=1))) | |
fig_ema.update_layout(title=f"{selected_stock} - Exponential Moving Averages", height=400) | |
st.plotly_chart(fig_ema, use_container_width=True) | |
# RSI and MACD | |
col1, col2 = st.columns(2) | |
with col1: | |
fig_rsi = go.Figure() | |
fig_rsi.add_trace(go.Scatter(x=processed_data.index[-30:], y=processed_data['RSI14'][-30:], | |
mode='lines', name='RSI14', line=dict(color='purple'))) | |
fig_rsi.add_hline(y=70, line_dash="dash", line_color="red", annotation_text="Overbought") | |
fig_rsi.add_hline(y=30, line_dash="dash", line_color="green", annotation_text="Oversold") | |
fig_rsi.update_layout(title=f"RSI ({rsi_period}-day)", height=300) | |
st.plotly_chart(fig_rsi, use_container_width=True) | |
with col2: | |
fig_macd = go.Figure() | |
fig_macd.add_trace(go.Scatter(x=processed_data.index[-30:], y=processed_data['MACD'][-30:], | |
mode='lines', name='MACD', line=dict(color='blue'))) | |
fig_macd.add_trace(go.Scatter(x=processed_data.index[-30:], y=processed_data['MACD_signal'][-30:], | |
mode='lines', name='Signal', line=dict(color='red'))) | |
fig_macd.update_layout(title="MACD", height=300) | |
st.plotly_chart(fig_macd, use_container_width=True) | |
else: | |
st.error("Not enough data to make a prediction.") | |
else: | |
st.error("Unable to load stock data.") | |
# ========================= TRADING DASHBOARD TAB ========================= | |
with tab2: | |
st.header("📊 Trading Dashboard") | |
with st.spinner(f'Loading data for {selected_stock}...'): | |
df = load_stock_data(selected_stock, start_date, end_date) | |
if df is not None and not df.empty: | |
st.subheader(f"📊 Stock Data for {selected_stock}") | |
st.write(f"**Date Range:** {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}") | |
st.write(f"**Total Records:** {len(df)} days") | |
# Process data for trading | |
df = process_stock_data(df, short_period, long_period, rsi_period) | |
df = df.dropna() | |
# Generate trading signals | |
if strategy_type in ["SMA-based", "Both"]: | |
df = generate_signals_sma(df, rsi_col='RSI14', sma_short_col='SMA20', sma_long_col='SMA50') | |
if strategy_type in ["EMA-based", "Both"]: | |
df = generate_signals_ema(df, rsi_col='RSI14', ema_short_col='EMA20', ema_long_col='EMA50') | |
# Initialize variables to avoid NameError | |
results = None | |
metrics = None | |
signal_col = None | |
strategy_name = None | |
# Backtesting section | |
st.header("🔍 Backtesting Results") | |
if strategy_type == "Both": | |
tab_sma, tab_ema = st.tabs(["SMA Strategy", "EMA Strategy"]) | |
with tab_sma: | |
st.subheader("📊 SMA Strategy Results") | |
logging.info(f"Starting backtest for {selected_stock} with {strategy_type} strategy") | |
sma_results, sma_metrics = backtest_signals( | |
df, signal_col='SMA_Signal', price_col='Close', | |
initial_cash=initial_cash, transaction_cost=transaction_cost if use_risk_mgmt else 0 | |
) | |
logging.info(f"Backtest completed for {selected_stock} with {strategy_type} strategy") | |
# Set variables for common sections | |
results = sma_results | |
metrics = sma_metrics | |
signal_col = 'SMA_Signal' | |
strategy_name = 'SMA' | |
# Display metrics | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.metric("💰 Final Value", sma_metrics['Final Portfolio Value']) | |
st.metric("📈 Total Return", sma_metrics['Total Return']) | |
with col2: | |
st.metric("🎯 Buy & Hold Return", sma_metrics['Buy & Hold Return']) | |
st.metric("📊 Total Trades", sma_metrics['Total Trades']) | |
with col3: | |
st.metric("🏆 Win Rate", sma_metrics['Win Rate']) | |
st.metric("⚡ Sharpe Ratio", sma_metrics['Sharpe Ratio']) | |
with col4: | |
st.metric("📉 Max Drawdown", sma_metrics['Maximum Drawdown']) | |
st.metric("🔥 Volatility", sma_metrics['Volatility (Annual)']) | |
# SMA Price Chart with Signals | |
fig_sma_signals = go.Figure() | |
fig_sma_signals.add_trace(go.Scatter(x=df.index, y=df['Close'], mode='lines', | |
name='Close Price', line=dict(color='purple', width=2))) | |
fig_sma_signals.add_trace(go.Scatter(x=df.index, y=df['SMA20'], mode='lines', | |
name='SMA20', line=dict(color='blue', width=2))) | |
fig_sma_signals.add_trace(go.Scatter(x=df.index, y=df['SMA50'], mode='lines', | |
name='SMA50', line=dict(color='red', width=2))) | |
# Add buy/sell signals | |
buy_signals = df[df['SMA_Signal'] == 1] | |
sell_signals = df[df['SMA_Signal'] == -1] | |
if not buy_signals.empty: | |
fig_sma_signals.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['Close'], | |
mode='markers', name='Buy Signal', | |
marker=dict(symbol='triangle-up', size=12, color='green'))) | |
if not sell_signals.empty: | |
fig_sma_signals.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['Close'], | |
mode='markers', name='Sell Signal', | |
marker=dict(symbol='triangle-down', size=12, color='red'))) | |
fig_sma_signals.update_layout(title=f"{selected_stock} - SMA Strategy Signals", height=500) | |
st.plotly_chart(fig_sma_signals, use_container_width=True) | |
# Portfolio Performance | |
buy_hold_value = initial_cash * (df['Close'] / df['Close'].iloc[0]) | |
fig_perf_sma = go.Figure() | |
fig_perf_sma.add_trace(go.Scatter(x=sma_results.index, y=sma_results['Total'], | |
mode='lines', name='SMA Strategy', line=dict(color='green', width=3))) | |
fig_perf_sma.add_trace(go.Scatter(x=df.index, y=buy_hold_value, | |
mode='lines', name='Buy & Hold', line=dict(color='blue', width=2, dash='dash'))) | |
fig_perf_sma.update_layout(title="SMA Strategy vs Buy & Hold Performance", height=400) | |
st.plotly_chart(fig_perf_sma, use_container_width=True) | |
with tab_ema: | |
st.subheader("📊 EMA Strategy Results") | |
logging.info(f"Starting backtest for {selected_stock} with {strategy_type} strategy") | |
ema_results, ema_metrics = backtest_signals( | |
df, signal_col='EMA_Signal', price_col='Close', | |
initial_cash=initial_cash, transaction_cost=transaction_cost if use_risk_mgmt else 0 | |
) | |
logging.info(f"Backtest completed for {selected_stock} with {strategy_type} strategy") | |
# Set variables for common sections | |
results = ema_results | |
metrics = ema_metrics | |
signal_col = 'EMA_Signal' | |
strategy_name = 'EMA' | |
# Display metrics | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.metric("💰 Final Value", ema_metrics['Final Portfolio Value']) | |
st.metric("📈 Total Return", ema_metrics['Total Return']) | |
with col2: | |
st.metric("🎯 Buy & Hold Return", ema_metrics['Buy & Hold Return']) | |
st.metric("📊 Total Trades", ema_metrics['Total Trades']) | |
with col3: | |
st.metric("🏆 Win Rate", ema_metrics['Win Rate']) | |
st.metric("⚡ Sharpe Ratio", ema_metrics['Sharpe Ratio']) | |
with col4: | |
st.metric("📉 Max Drawdown", ema_metrics['Maximum Drawdown']) | |
st.metric("🔥 Volatility", ema_metrics['Volatility (Annual)']) | |
# EMA Price Chart with Signals | |
fig_ema_signals = go.Figure() | |
fig_ema_signals.add_trace(go.Scatter(x=df.index, y=df['Close'], mode='lines', | |
name='Close Price', line=dict(color='purple', width=2))) | |
fig_ema_signals.add_trace(go.Scatter(x=df.index, y=df['EMA20'], mode='lines', | |
name='EMA20', line=dict(color='blue', width=2))) | |
fig_ema_signals.add_trace(go.Scatter(x=df.index, y=df['EMA50'], mode='lines', | |
name='EMA50', line=dict(color='red', width=2))) | |
# Add buy/sell signals | |
buy_signals = df[df['EMA_Signal'] == 1] | |
sell_signals = df[df['EMA_Signal'] == -1] | |
if not buy_signals.empty: | |
fig_ema_signals.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['Close'], | |
mode='markers', name='Buy Signal', | |
marker=dict(symbol='triangle-up', size=12, color='green'))) | |
if not sell_signals.empty: | |
fig_ema_signals.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['Close'], | |
mode='markers', name='Sell Signal', | |
marker=dict(symbol='triangle-down', size=12, color='red'))) | |
fig_ema_signals.update_layout(title=f"{selected_stock} - EMA Strategy Signals", height=500) | |
st.plotly_chart(fig_ema_signals, use_container_width=True) | |
# Portfolio Performance | |
buy_hold_value = initial_cash * (df['Close'] / df['Close'].iloc[0]) | |
fig_perf_ema = go.Figure() | |
fig_perf_ema.add_trace(go.Scatter(x=ema_results.index, y=ema_results['Total'], | |
mode='lines', name='EMA Strategy', line=dict(color='green', width=3))) | |
fig_perf_ema.add_trace(go.Scatter(x=df.index, y=buy_hold_value, | |
mode='lines', name='Buy & Hold', line=dict(color='blue', width=2, dash='dash'))) | |
fig_perf_ema.update_layout(title="EMA Strategy vs Buy & Hold Performance", height=400) | |
st.plotly_chart(fig_perf_ema, use_container_width=True) | |
else: | |
# Single strategy | |
signal_col = 'SMA_Signal' if strategy_type == "SMA-based" else 'EMA_Signal' | |
strategy_name = strategy_type.split('-')[0] | |
logging.info(f"Starting backtest for {selected_stock} with {strategy_type} strategy") | |
results, metrics = backtest_signals( | |
df, signal_col=signal_col, price_col='Close', | |
initial_cash=initial_cash, transaction_cost=transaction_cost if use_risk_mgmt else 0 | |
) | |
logging.info(f"Backtest completed. Final return: {metrics['Total Return']}") | |
# Display metrics | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.metric("💰 Final Value", metrics['Final Portfolio Value']) | |
st.metric("📈 Total Return", metrics['Total Return']) | |
with col2: | |
st.metric("🎯 Buy & Hold Return", metrics['Buy & Hold Return']) | |
st.metric("📊 Total Trades", metrics['Total Trades']) | |
with col3: | |
st.metric("🏆 Win Rate", metrics['Win Rate']) | |
st.metric("⚡ Sharpe Ratio", metrics['Sharpe Ratio']) | |
with col4: | |
st.metric("📉 Max Drawdown", metrics['Maximum Drawdown']) | |
st.metric("🔥 Volatility", metrics['Volatility (Annual)']) | |
# Price Chart with Signals | |
fig_signals = go.Figure() | |
fig_signals.add_trace(go.Scatter(x=df.index, y=df['Close'], mode='lines', | |
name='Close Price', line=dict(color='purple', width=2))) | |
if strategy_name == 'SMA': | |
fig_signals.add_trace(go.Scatter(x=df.index, y=df['SMA20'], mode='lines', | |
name='SMA20', line=dict(color='blue', width=2))) | |
fig_signals.add_trace(go.Scatter(x=df.index, y=df['SMA50'], mode='lines', | |
name='SMA50', line=dict(color='red', width=2))) | |
else: | |
fig_signals.add_trace(go.Scatter(x=df.index, y=df['EMA20'], mode='lines', | |
name='EMA20', line=dict(color='blue', width=2))) | |
fig_signals.add_trace(go.Scatter(x=df.index, y=df['EMA50'], mode='lines', | |
name='EMA50', line=dict(color='red', width=2))) | |
# Add buy/sell signals | |
buy_signals = df[df[signal_col] == 1] | |
sell_signals = df[df[signal_col] == -1] | |
if not buy_signals.empty: | |
fig_signals.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['Close'], | |
mode='markers', name='Buy Signal', | |
marker=dict(symbol='triangle-up', size=12, color='green'))) | |
if not sell_signals.empty: | |
fig_signals.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['Close'], | |
mode='markers', name='Sell Signal', | |
marker=dict(symbol='triangle-down', size=12, color='red'))) | |
fig_signals.update_layout(title=f"{selected_stock} - {strategy_name} Strategy Signals", height=500) | |
st.plotly_chart(fig_signals, use_container_width=True) | |
# Portfolio Performance | |
buy_hold_value = initial_cash * (df['Close'] / df['Close'].iloc[0]) | |
fig_perf = go.Figure() | |
fig_perf.add_trace(go.Scatter(x=results.index, y=results['Total'], | |
mode='lines', name=f'{strategy_name} Strategy', line=dict(color='green', width=3))) | |
fig_perf.add_trace(go.Scatter(x=df.index, y=buy_hold_value, | |
mode='lines', name='Buy & Hold', line=dict(color='blue', width=2, dash='dash'))) | |
fig_perf.update_layout(title=f"{strategy_name} Strategy vs Buy & Hold Performance", height=400) | |
st.plotly_chart(fig_perf, use_container_width=True) | |
# Additional Technical Analysis Charts (only show if we have results) | |
if results is not None: | |
st.header("📈 Additional Technical Analysis") | |
col1, col2 = st.columns(2) | |
with col1: | |
# RSI Chart | |
fig_rsi = go.Figure() | |
fig_rsi.add_trace(go.Scatter(x=df.index, y=df['RSI14'], mode='lines', | |
name='RSI14', line=dict(color='purple', width=2))) | |
# Add buy/sell signals on RSI if available | |
if not buy_signals.empty: | |
fig_rsi.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['RSI14'], | |
mode='markers', name='Buy Signal', | |
marker=dict(symbol='triangle-up', size=10, color='green'), | |
showlegend=False)) | |
if not sell_signals.empty: | |
fig_rsi.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['RSI14'], | |
mode='markers', name='Sell Signal', | |
marker=dict(symbol='triangle-down', size=10, color='red'), | |
showlegend=False)) | |
fig_rsi.add_hline(y=70, line_dash="dash", line_color="red", annotation_text="Overbought (70)") | |
fig_rsi.add_hline(y=30, line_dash="dash", line_color="green", annotation_text="Oversold (30)") | |
fig_rsi.add_hline(y=50, line_dash="solid", line_color="gray", annotation_text="Midline (50)", opacity=0.5) | |
fig_rsi.update_layout(title="RSI with Trading Signals", yaxis=dict(range=[0, 100]), height=400) | |
st.plotly_chart(fig_rsi, use_container_width=True) | |
with col2: | |
# MACD Chart | |
fig_macd = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, row_heights=[0.7, 0.3]) | |
# MACD line | |
fig_macd.add_trace(go.Scatter(x=df.index, y=df['MACD'], mode='lines', name='MACD', | |
line=dict(color='blue', width=2)), row=1, col=1) | |
# Signal line | |
fig_macd.add_trace(go.Scatter(x=df.index, y=df['MACD_signal'], mode='lines', name='Signal Line', | |
line=dict(color='orange', width=2)), row=1, col=1) | |
# Zero line | |
fig_macd.add_hline(y=0, line_dash="solid", line_color="pink", opacity=0.5, row=1, col=1) | |
# MACD histogram | |
colors = ['green' if val >= 0 else 'red' for val in df['MACD_hist']] | |
fig_macd.add_trace(go.Bar(x=df.index, y=df['MACD_hist'], name='MACD Histogram', | |
marker_color=colors, opacity=0.6), row=2, col=1) | |
fig_macd.update_layout(title="MACD Indicator", height=400, showlegend=True) | |
fig_macd.update_xaxes(title_text="Date", row=2, col=1) | |
fig_macd.update_yaxes(title_text="MACD Value", row=1, col=1) | |
fig_macd.update_yaxes(title_text="Histogram", row=2, col=1) | |
st.plotly_chart(fig_macd, use_container_width=True) | |
# Bollinger Bands | |
st.subheader("📈 Bollinger Bands") | |
fig_bb = go.Figure() | |
fig_bb.add_trace(go.Scatter(x=df.index, y=df['Close'], mode='lines', name='Close Price', | |
line=dict(color='purple', width=2))) | |
fig_bb.add_trace(go.Scatter(x=df.index, y=df['SMA20'], mode='lines', name='20-day SMA', | |
line=dict(color='blue', width=1.5))) | |
fig_bb.add_trace(go.Scatter(x=df.index, y=df['Upper_Band'], mode='lines', name='Upper Band', | |
line=dict(color='red', dash='dash', width=1.5))) | |
fig_bb.add_trace(go.Scatter(x=df.index, y=df['Lower_Band'], mode='lines', name='Lower Band', | |
line=dict(color='green', dash='dash', width=1.5), | |
fill='tonexty', fillcolor='rgba(128,128,128,0.2)')) | |
fig_bb.update_layout(title="Bollinger Bands", height=500) | |
st.plotly_chart(fig_bb, use_container_width=True) | |
# Drawdown Analysis | |
st.subheader("📉 Drawdown Analysis") | |
# Calculate drawdown | |
returns = results['Total'].pct_change().fillna(0) | |
cumulative = (1 + returns).cumprod() | |
running_max = cumulative.expanding().max() | |
drawdown = (cumulative - running_max) / running_max | |
fig_dd = go.Figure() | |
fig_dd.add_trace(go.Scatter( | |
x=df.index, | |
y=drawdown * 100, | |
mode='lines', | |
name='Drawdown', | |
fill='tozeroy', | |
fillcolor='rgba(255,0,0,0.3)', | |
line=dict(color='red', width=1), | |
hovertemplate='<b>Drawdown</b>: %{y:.1f}%<extra></extra>' | |
)) | |
fig_dd.update_layout( | |
title="Portfolio Drawdown Over Time", | |
xaxis_title="Date", | |
yaxis_title="Drawdown (%)", | |
height=400, | |
template='plotly_white' | |
) | |
st.plotly_chart(fig_dd, use_container_width=True) | |
# Trade analysis | |
if metrics is not None and not metrics['Trades DataFrame'].empty: | |
st.subheader("📋 Trade Analysis") | |
trades_df = metrics['Trades DataFrame'] | |
# Trade statistics | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
avg_trade_duration = (pd.to_datetime(trades_df['exit_date']) - | |
pd.to_datetime(trades_df['entry_date'])).dt.days.mean() | |
st.metric("📅 Avg Trade Duration", f"{avg_trade_duration:.1f} days") | |
with col2: | |
best_trade = trades_df['return_pct'].max() | |
st.metric("🚀 Best Trade", f"{best_trade:.2%}") | |
with col3: | |
worst_trade = trades_df['return_pct'].min() | |
st.metric("💥 Worst Trade", f"{worst_trade:.2%}") | |
# Trade returns distribution | |
st.subheader("📊 Trade Returns Distribution") | |
returns_pct = trades_df['return_pct'] * 100 | |
fig_hist = px.histogram( | |
x=returns_pct, | |
nbins=20, | |
title="Distribution of Trade Returns", | |
labels={'x': 'Return (%)', 'y': 'Number of Trades'}, | |
color_discrete_sequence=['steelblue'] | |
) | |
# Add vertical lines for mean and zero | |
fig_hist.add_vline(x=0, line_dash="dash", line_color="red", | |
annotation_text="Break Even") | |
fig_hist.add_vline(x=returns_pct.mean(), line_dash="solid", line_color="green", | |
annotation_text=f"Mean: {returns_pct.mean():.1f}%") | |
fig_hist.update_layout( | |
height=400, | |
template='plotly_white', | |
showlegend=False | |
) | |
st.plotly_chart(fig_hist, use_container_width=True) | |
# Trade timeline | |
st.subheader("📅 Trade Timeline") | |
fig_timeline = go.Figure() | |
for i, trade in trades_df.iterrows(): | |
color = 'green' if trade['return_pct'] > 0 else 'red' | |
fig_timeline.add_trace(go.Scatter( | |
x=[trade['entry_date'], trade['exit_date']], | |
y=[trade['entry_price'], trade['exit_price']], | |
mode='lines+markers', | |
name=f"Trade {i+1}", | |
line=dict(color=color, width=3), | |
marker=dict(size=8), | |
hovertemplate=f'<b>Trade {i+1}</b><br>' + | |
f'Entry: ₹{trade["entry_price"]:.2f}<br>' + | |
f'Exit: ₹{trade["exit_price"]:.2f}<br>' + | |
f'Return: {trade["return_pct"]:.2%}<br>' + | |
f'Duration: {(pd.to_datetime(trade["exit_date"]) - pd.to_datetime(trade["entry_date"])).days} days<extra></extra>', | |
showlegend=False | |
)) | |
fig_timeline.update_layout( | |
title="Individual Trade Performance Timeline", | |
xaxis_title="Date", | |
yaxis_title="Price (₹)", | |
height=500, | |
template='plotly_white' | |
) | |
st.plotly_chart(fig_timeline, use_container_width=True) | |
# Trade history table | |
st.subheader("📊 Detailed Trade History") | |
display_trades = trades_df.copy() | |
display_trades['Entry Date'] = pd.to_datetime(display_trades['entry_date']).dt.strftime('%Y-%m-%d') | |
display_trades['Exit Date'] = pd.to_datetime(display_trades['exit_date']).dt.strftime('%Y-%m-%d') | |
display_trades['Entry Price'] = display_trades['entry_price'].apply(lambda x: f"₹{x:.2f}") | |
display_trades['Exit Price'] = display_trades['exit_price'].apply(lambda x: f"₹{x:.2f}") | |
display_trades['P&L (₹)'] = display_trades['profit_loss'].apply(lambda x: f"₹{x:,.2f}") | |
display_trades['Return %'] = display_trades['return_pct'].apply(lambda x: f"{x:.2%}") | |
display_trades['Duration'] = (pd.to_datetime(trades_df['exit_date']) - | |
pd.to_datetime(trades_df['entry_date'])).dt.days | |
trade_display = display_trades[['Entry Date', 'Exit Date', 'Entry Price', 'Exit Price', | |
'P&L (₹)', 'Return %', 'Duration', 'exit_reason']].copy() | |
trade_display.columns = ['Entry Date', 'Exit Date', 'Entry Price', 'Exit Price', | |
'Profit/Loss', 'Return %', 'Days', 'Exit Reason'] | |
st.dataframe(trade_display, use_container_width=True) | |
else: | |
st.info("📝 No trades were executed during this period with the current parameters.") | |
# Signal summary table | |
if signal_col is not None: | |
st.subheader("📋 Trading Signals Summary") | |
signal_summary = df[df[signal_col] != 0].copy() | |
if not signal_summary.empty: | |
signal_summary['Signal Type'] = signal_summary[signal_col].map({1: '🟢 BUY', -1: '🔴 SELL'}) | |
signal_summary['Price'] = signal_summary['Close'].apply(lambda x: f"₹{x:.2f}") | |
signal_summary['RSI'] = signal_summary['RSI14'].apply(lambda x: f"{x:.1f}") | |
signal_summary[f'{strategy_name}{short_period}'] = signal_summary[f'{strategy_name}{short_period}'].apply(lambda x: f"₹{x:.2f}") | |
signal_summary[f'{strategy_name}{long_period}'] = signal_summary[f'{strategy_name}{long_period}'].apply(lambda x: f"₹{x:.2f}") | |
display_signals = signal_summary[['Signal Type', 'Price', 'RSI', | |
f'{strategy_name}{short_period}', | |
f'{strategy_name}{long_period}']].copy() | |
display_signals.index = display_signals.index.strftime('%Y-%m-%d') | |
st.dataframe(display_signals, use_container_width=True) | |
else: | |
st.info("📝 No trading signals were generated during this period with the current parameters.") | |
# Data Download Section | |
st.subheader("💾 Download Data") | |
col1, col2 = st.columns(2) | |
with col1: | |
csv_data = df.to_csv(index=True) | |
st.download_button( | |
label="📁 Download Full Dataset (CSV)", | |
data=csv_data, | |
file_name=f"{selected_stock}_analysis_{start_date.strftime('%Y%m%d')}.csv", | |
mime="text/csv" | |
) | |
with col2: | |
if results is not None: | |
results_csv = results.to_csv(index=True) | |
st.download_button( | |
label="📊 Download Backtest Results (CSV)", | |
data=results_csv, | |
file_name=f"{selected_stock}_backtest_{start_date.strftime('%Y%m%d')}.csv", | |
mime="text/csv" | |
) | |
else: | |
st.error("❌ No data found for the selected stock and date range.") | |
# ========================= SIDEBAR INFORMATION ========================= | |
st.sidebar.markdown("---") | |
st.sidebar.header("ℹ️ About") | |
st.sidebar.write(""" | |
**Price Prediction Features:** | |
- Logistic Regression model for next-day prediction | |
- 59+ technical features including volatility, momentum, and lag features | |
- Confidence gauge and feature importance analysis | |
**Trading Dashboard Features:** | |
- SMA and EMA-based strategies | |
- Comprehensive backtesting with risk management | |
- Detailed performance metrics and trade analysis | |
- Interactive visualizations with Plotly | |
**Disclaimer**: This is for educational purposes only. Always do your own research before making investment decisions. | |
""") | |
st.sidebar.markdown("---") | |
st.sidebar.write("**Model Performance:**") | |
st.sidebar.write("• Accuracy: 55%") | |
st.sidebar.write("• F1 Score: 0.4839") | |
st.sidebar.write("• AUC: 0.5370") | |
st.sidebar.write("• Average Precision: 0.5300") | |
# Footer | |
st.markdown("---") | |
st.markdown("**⚠️ Disclaimer**: This platform is for research and educational purposes only. Stock market investments are subject to market risks. Please consult with a financial advisor before making investment decisions.") | |
st.markdown("**Developed by**: Zane Vijay Falcao") |