Falcao Zane Vijay commited on
Commit
8e0b458
·
1 Parent(s): 0475bbf

deploy #1

Browse files
requirements.txt CHANGED
@@ -1,3 +1,5 @@
1
- altair
2
  pandas
 
 
 
3
  streamlit
 
 
1
  pandas
2
+ numpy
3
+ matplotlib
4
+ yfinance
5
  streamlit
src/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ __pycache__/
2
+ .streamlit/
3
+ config.toml
src/README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Placeholder for README.md
src/config.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # config.py
2
+ STOCKS = ['RELIANCE.NS', 'TCS.NS', 'INFY.NS']
3
+ START_DATE = '2014-02-01'
4
+ RSI_PERIOD = 14
5
+ SMA_SHORT = 20
6
+ SMA_LONG = 50
src/indicators/bb.py ADDED
File without changes
src/indicators/ema.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # indicators/sma.py
2
+
3
+ import pandas as pd
4
+
5
+ def ema(df, period=20, column="Close"):
6
+ """
7
+ Calculates Exponential Moving Average (EMA)
8
+ """
9
+ return df[column].ewm(span=period, adjust=False).mean()
src/indicators/enhanced_features.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def create_volatility_features(df):
2
+ if 'return_1d' not in df.columns:
3
+ df['return_1d'] = df['Close'].pct_change()
4
+
5
+ for period in [5, 10, 20, 30]:
6
+ df[f'volatility_{period}d'] = df['return_1d'].rolling(period).std()
7
+
8
+ df['vol_ratio_5_20'] = df['volatility_5d'] / df['volatility_20d']
9
+ df['vol_ratio_10_20'] = df['volatility_10d'] / df['volatility_20d']
10
+ df['vol_rank_20'] = df['volatility_5d'].rolling(20).rank(pct=True)
11
+ df['vol_rank_50'] = df['volatility_5d'].rolling(50).rank(pct=True)
12
+
13
+ return df
14
+
15
+ def create_enhanced_lag_features(df):
16
+ for lag in [1, 2, 3, 5, 10]:
17
+ df[f'return_lag_{lag}'] = df['return_1d'].shift(lag)
18
+
19
+ for lag in [1, 2, 3]:
20
+ if 'RSI14' in df.columns:
21
+ df[f'rsi_lag_{lag}'] = df['RSI14'].shift(lag)
22
+ if 'MACD' in df.columns:
23
+ df[f'macd_lag_{lag}'] = df['MACD'].shift(lag)
24
+
25
+ if 'volume_ratio_20' in df.columns:
26
+ for lag in [1, 2]:
27
+ df[f'volume_ratio_lag_{lag}'] = df['volume_ratio_20'].shift(lag)
28
+
29
+ return df
30
+
31
+ def create_volume_features(df):
32
+ df['volume_sma_10'] = df['Volume'].rolling(10).mean()
33
+ df['volume_sma_20'] = df['Volume'].rolling(20).mean()
34
+ df['volume_sma_50'] = df['Volume'].rolling(50).mean()
35
+
36
+ df['volume_ratio_10'] = df['Volume'] / df['volume_sma_10']
37
+ df['volume_ratio_20'] = df['Volume'] / df['volume_sma_20']
38
+ df['volume_ratio_50'] = df['Volume'] / df['volume_sma_50']
39
+
40
+ df['price_volume'] = df['Close'] * df['Volume']
41
+ df['pv_sma_5'] = df['price_volume'].rolling(5).mean()
42
+ df['volume_momentum_5'] = df['Volume'] / df['Volume'].shift(5)
43
+
44
+ return df
45
+
46
+ def create_momentum_features(df):
47
+ for period in [3, 5, 10, 20]:
48
+ df[f'momentum_{period}d'] = df['Close'] / df['Close'].shift(period) - 1
49
+
50
+ for period in [5, 10]:
51
+ df[f'roc_{period}d'] = (df['Close'] - df['Close'].shift(period)) / df['Close'].shift(period)
52
+
53
+ return df
54
+
55
+ def create_position_features(df):
56
+ for period in [10, 20, 50]:
57
+ df[f'high_{period}d'] = df['High'].rolling(period).max()
58
+ df[f'low_{period}d'] = df['Low'].rolling(period).min()
59
+ df[f'price_position_{period}'] = (df['Close'] - df[f'low_{period}d']) / (df[f'high_{period}d'] - df[f'low_{period}d'])
60
+
61
+ if 'SMA20' in df.columns:
62
+ bb_std = df['Close'].rolling(20).std()
63
+ df['bb_upper'] = df['SMA20'] + (bb_std * 2)
64
+ df['bb_lower'] = df['SMA20'] - (bb_std * 2)
65
+ df['bb_position'] = (df['Close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])
66
+
67
+ return df
src/indicators/macd.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # indicators/macd.py
3
+
4
+ import pandas as pd
5
+
6
+ def macd(df, fast_period=12, slow_period=26, signal_period=9, column="Close"):
7
+ """
8
+ Calculates MACD Line, Signal Line, and Histogram
9
+ """
10
+ ema_fast = df[column].ewm(span=fast_period, adjust=False).mean()
11
+ ema_slow = df[column].ewm(span=slow_period, adjust=False).mean()
12
+
13
+ macd_line = ema_fast - ema_slow
14
+ signal_line = macd_line.ewm(span=signal_period, adjust=False).mean()
15
+ histogram = macd_line - signal_line
16
+
17
+ return macd_line, signal_line, histogram
src/indicators/rsi.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # indicators/rsi.py
2
+
3
+ import pandas as pd
4
+
5
+ def rsi(df, period=14, column="Close"):
6
+ """
7
+ Calculates Relative Strength Index (RSI)
8
+ """
9
+ delta = df[column].diff()
10
+ gain = delta.clip(lower=0)
11
+ loss = -1 * delta.clip(upper=0)
12
+
13
+ avg_gain = gain.ewm(com=period-1, min_periods=period).mean()
14
+ avg_loss = loss.ewm(com=period-1, min_periods=period).mean()
15
+
16
+ rs = avg_gain / avg_loss
17
+ rsi = 100 - (100 / (1 + rs))
18
+
19
+ return rsi
src/indicators/sma.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # indicators/sma.py
2
+
3
+ import pandas as pd
4
+
5
+ def sma(df, period=20, column="Close"):
6
+ """
7
+ Calculates Simple Moving Average (SMA)
8
+ """
9
+ return df[column].rolling(window=period).mean()
10
+
11
+
12
+
13
+ def ema(df, period=20, column="Close"):
14
+ """
15
+ Calculates Exponential Moving Average (EMA)
16
+ """
17
+ return df[column].ewm(span=period, adjust=False).mean()
18
+
src/logs/log_2025-08-03_22-03-43.log ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ 2025-08-03 22:03:43,463 [INFO] - Logger initialized.
2
+ 2025-08-03 22:03:43,463 [INFO] - Fetching data for RELIANCE.NS from 2024-02-01 to 2025-08-03 22:03:39.386764
3
+ 2025-08-03 22:03:49,344 [INFO] - Downloaded 372 rows for RELIANCE.NS
4
+ 2025-08-03 22:04:04,219 [INFO] - Fetching data for TCS.NS from 2024-02-01 to 2025-08-03 22:03:39.386764
5
+ 2025-08-03 22:04:04,496 [INFO] - Downloaded 372 rows for TCS.NS
6
+ 2025-08-03 22:04:08,637 [INFO] - Fetching data for INFY.NS from 2024-02-01 to 2025-08-03 22:03:39.386764
7
+ 2025-08-03 22:04:08,888 [INFO] - Downloaded 372 rows for INFY.NS
src/main.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # main.py
2
+
3
+ from utils.logger import setup_logger
4
+ from utils.data_loader import fetch_stock_data
5
+ from indicators.rsi import rsi
6
+ from indicators.sma import sma
7
+ from indicators.ema import ema
8
+ from indicators.macd import macd
9
+ from strategy.rule_based_strategy import generate_signals
10
+ from utils.backtester import backtest_signals
11
+
12
+ import pandas as pd
13
+ import matplotlib.pyplot as plt
14
+
15
+ # 1. Setup logging
16
+ setup_logger()
17
+
18
+ # 2. Define configuration
19
+ stocks = ['RELIANCE.NS', 'TCS.NS', 'INFY.NS']
20
+ start_date = '2024-02-01'
21
+ rsi_period = 14
22
+ sma_short = 20
23
+ sma_long = 50
24
+ ema_short = 20
25
+ ema_long = 50
26
+
27
+ for symbol in stocks:
28
+ print(f"\n--- Running for: {symbol} ---")
29
+
30
+ # 3. Fetch stock data
31
+ df = fetch_stock_data(symbol, start_date=start_date)
32
+
33
+ if df.empty:
34
+ print(f"No data for {symbol}, skipping...")
35
+ continue
36
+
37
+ # 4. Add indicators
38
+ df['RSI'] = rsi(df, period=rsi_period)
39
+ df['SMA20'] = sma(df, period=sma_short)
40
+ df['SMA50'] = sma(df, period=sma_long)
41
+ df['EMA20'] = ema(df, period=ema_short)
42
+ df['EMA50'] = ema(df, period=ema_long)
43
+ df['MACD'], df['MACD_signal'], df['MACD_hist'] = macd(df)
44
+
45
+ # 5. Generate buy/sell signals
46
+ df = generate_signals(df, rsi_col='RSI', sma_short_col='SMA20', sma_long_col='SMA50')
47
+
48
+ # 6. Backtest strategy
49
+ results = backtest_signals(df, signal_col='Signal')
50
+
51
+ # 7. Plot equity curve
52
+ plt.figure(figsize=(10, 5))
53
+ plt.plot(results['Total'], label='Equity Curve')
54
+ plt.title(f"{symbol} - Backtest Equity Curve")
55
+ plt.xlabel("Date")
56
+ plt.ylabel("Portfolio Value (₹)")
57
+ plt.legend()
58
+ plt.grid(True)
59
+ plt.tight_layout()
60
+ plt.show()
61
+
62
+ # 8. Print final portfolio value
63
+ final_value = results['Total'].iloc[-1]
64
+ print(f"Final portfolio value for {symbol}: ₹{final_value:,.2f}")
src/main.txt ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # main.py
2
+
3
+ from utils.logger import setup_logger
4
+ from utils.data_loader import fetch_stock_data
5
+ from indicators.rsi import rsi
6
+ from indicators.sma import sma
7
+ from indicators.macd import macd
8
+ from strategy.rule_based_strategy import generate_signals
9
+ from utils.backtester import backtest_signals
10
+
11
+ import pandas as pd
12
+ import matplotlib.pyplot as plt
13
+
14
+ # ---------------------- Configuration ----------------------
15
+ STOCK = 'RELIANCE.NS'
16
+ START_DATE = '2024-02-01'
17
+
18
+ # Indicator Parameters
19
+ RSI_PERIOD = 14
20
+ SMA_SHORT = 20
21
+ SMA_LONG = 50
22
+
23
+ # Backtest Settings
24
+ INITIAL_CASH = 100000
25
+ # -----------------------------------------------------------
26
+
27
+
28
+ def main():
29
+ # Initialize Logger
30
+ setup_logger()
31
+
32
+ print(f"\nFetching data for {STOCK}...")
33
+ df = fetch_stock_data(STOCK, start_date=START_DATE)
34
+
35
+ if df.empty:
36
+ print("No data available. Exiting.")
37
+ return
38
+
39
+ print("Calculating indicators...")
40
+ df['RSI'] = rsi(df, period=RSI_PERIOD)
41
+ df['SMA20'] = sma(df, period=SMA_SHORT)
42
+ df['SMA50'] = sma(df, period=SMA_LONG)
43
+ df['MACD'], df['MACD_signal'], df['MACD_hist'] = macd(df)
44
+
45
+ print("Generating signals...")
46
+ df = generate_signals(df, rsi_col='RSI', sma_short_col='SMA20', sma_long_col='SMA50')
47
+
48
+ print("Backtesting strategy...")
49
+ results = backtest_signals(df, signal_col='Signal', price_col='Close', initial_cash=INITIAL_CASH)
50
+
51
+ final_equity = results['Total'].iloc[-1]
52
+ print(f"\n✅ Final Portfolio Value: ₹{final_equity:,.2f}")
53
+
54
+ # Plotting equity curve
55
+ plt.figure(figsize=(12, 6))
56
+ results['Total'].plot(label='Equity Curve', color='green')
57
+ results['Close'].plot(label='Close Price', secondary_y=True, alpha=0.3)
58
+ plt.title(f"Backtest Results for {STOCK}")
59
+ plt.legend()
60
+ plt.tight_layout()
61
+ plt.show()
62
+
63
+
64
+ if __name__ == "__main__":
65
+ main()
src/main_app.py ADDED
@@ -0,0 +1,663 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # main_app.py
2
+
3
+ import streamlit as st
4
+ import pandas as pd
5
+ import numpy as np
6
+ import plotly.express as px
7
+ import plotly.graph_objects as go
8
+ from plotly.subplots import make_subplots
9
+
10
+ from utils.data_loader import fetch_stock_data
11
+ from indicators.rsi import rsi
12
+ from indicators.sma import sma
13
+ from indicators.ema import ema
14
+ from indicators.macd import macd
15
+ from strategy.rule_based_strategy import generate_signals_sma, generate_signals_ema
16
+ from utils.backtester import backtest_signals
17
+
18
+ # Function to display strategy results with Plotly
19
+ def display_strategy_results(df, results, metrics, strategy_name, period_short, period_long, initial_cash, selected_stock):
20
+ """
21
+ Display comprehensive strategy results in Streamlit interface using Plotly
22
+ """
23
+
24
+ # Performance metrics in columns
25
+ st.subheader("📊 Performance Overview")
26
+ col1, col2, col3, col4 = st.columns(4)
27
+
28
+ with col1:
29
+ st.metric("💰 Final Value", metrics['Final Portfolio Value'])
30
+ st.metric("📈 Total Return", metrics['Total Return'])
31
+
32
+ with col2:
33
+ st.metric("🎯 Buy & Hold Return", metrics['Buy & Hold Return'])
34
+ st.metric("📊 Total Trades", metrics['Total Trades'])
35
+
36
+ with col3:
37
+ st.metric("🏆 Win Rate", metrics['Win Rate'])
38
+ st.metric("⚡ Sharpe Ratio", metrics['Sharpe Ratio'])
39
+
40
+ with col4:
41
+ st.metric("📉 Max Drawdown", metrics['Maximum Drawdown'])
42
+ st.metric("🔥 Volatility", metrics['Volatility (Annual)'])
43
+
44
+ # Get signals for plotting
45
+ signal_col = f'{strategy_name}_Signal'
46
+ buy_signals = df[df[signal_col] == 1]
47
+ sell_signals = df[df[signal_col] == -1]
48
+
49
+ # 1. Main price chart with signals and moving averages
50
+ st.subheader(f"📉 {selected_stock} Price Chart with {strategy_name} Strategy")
51
+
52
+ fig_price = go.Figure()
53
+
54
+ # Add price line
55
+ fig_price.add_trace(go.Scatter(
56
+ x=df.index,
57
+ y=df['Close'],
58
+ mode='lines',
59
+ name='Close Price',
60
+ line=dict(color='purple', width=2),
61
+ hovertemplate='<b>Price</b>: ₹%{y:.2f}<br><b>Date</b>: %{x}<extra></extra>'
62
+ ))
63
+
64
+ # Add moving averages
65
+ fig_price.add_trace(go.Scatter(
66
+ x=df.index,
67
+ y=df[f'{strategy_name}{period_short}'],
68
+ mode='lines',
69
+ name=f'{strategy_name}{period_short}',
70
+ line=dict(color='blue', width=2),
71
+ hovertemplate=f'<b>{strategy_name}{period_short}</b>: ₹%{{y:.2f}}<br><b>Date</b>: %{{x}}<extra></extra>'
72
+ ))
73
+
74
+ fig_price.add_trace(go.Scatter(
75
+ x=df.index,
76
+ y=df[f'{strategy_name}{period_long}'],
77
+ mode='lines',
78
+ name=f'{strategy_name}{period_long}',
79
+ line=dict(color='red', width=2),
80
+ hovertemplate=f'<b>{strategy_name}{period_long}</b>: ₹%{{y:.2f}}<br><b>Date</b>: %{{x}}<extra></extra>'
81
+ ))
82
+
83
+ # Add buy signals
84
+ if not buy_signals.empty:
85
+ fig_price.add_trace(go.Scatter(
86
+ x=buy_signals.index,
87
+ y=buy_signals['Close'],
88
+ mode='markers',
89
+ name='Buy Signal',
90
+ marker=dict(
91
+ symbol='triangle-up',
92
+ size=12,
93
+ color='green',
94
+ line=dict(color='darkgreen', width=1)
95
+ ),
96
+ hovertemplate='<b>BUY</b><br><b>Price</b>: ₹%{y:.2f}<br><b>Date</b>: %{x}<extra></extra>'
97
+ ))
98
+
99
+ # Add sell signals
100
+ if not sell_signals.empty:
101
+ fig_price.add_trace(go.Scatter(
102
+ x=sell_signals.index,
103
+ y=sell_signals['Close'],
104
+ mode='markers',
105
+ name='Sell Signal',
106
+ marker=dict(
107
+ symbol='triangle-down',
108
+ size=12,
109
+ color='red',
110
+ line=dict(color='darkred', width=1)
111
+ ),
112
+ hovertemplate='<b>SELL</b><br><b>Price</b>: ₹%{y:.2f}<br><b>Date</b>: %{x}<extra></extra>'
113
+ ))
114
+
115
+ # Add trend zones
116
+ fig_price.add_trace(go.Scatter(
117
+ x=df.index,
118
+ y=df[f'{strategy_name}{period_short}'],
119
+ fill=None,
120
+ mode='lines',
121
+ line_color='rgba(0,0,0,0)',
122
+ showlegend=False
123
+ ))
124
+
125
+ fig_price.add_trace(go.Scatter(
126
+ x=df.index,
127
+ y=df[f'{strategy_name}{period_long}'],
128
+ fill='tonexty',
129
+ mode='lines',
130
+ line_color='rgba(0,0,0,0)',
131
+ fillcolor='rgba(0,255,0,0.1)',
132
+ name='Bullish Zone',
133
+ showlegend=True
134
+ ))
135
+
136
+ fig_price.update_layout(
137
+ title=f"{selected_stock} - {strategy_name} Strategy Signals",
138
+ xaxis_title="Date",
139
+ yaxis_title="Price (₹)",
140
+ height=600,
141
+ hovermode='x unified',
142
+ template='plotly_white'
143
+ )
144
+
145
+ st.plotly_chart(fig_price, use_container_width=True)
146
+
147
+ # 2. Portfolio performance comparison
148
+ st.subheader("📈 Portfolio Performance vs Buy & Hold")
149
+
150
+ # Calculate buy & hold
151
+ buy_hold_value = initial_cash * (df['Close'] / df['Close'].iloc[0])
152
+
153
+ fig_perf = go.Figure()
154
+
155
+ fig_perf.add_trace(go.Scatter(
156
+ x=results.index,
157
+ y=results['Total'],
158
+ mode='lines',
159
+ name='Strategy Portfolio',
160
+ line=dict(color='green', width=3),
161
+ hovertemplate='<b>Strategy</b>: ₹%{y:,.0f}<br><b>Date</b>: %{x}<extra></extra>'
162
+ ))
163
+
164
+ fig_perf.add_trace(go.Scatter(
165
+ x=df.index,
166
+ y=buy_hold_value,
167
+ mode='lines',
168
+ name='Buy & Hold',
169
+ line=dict(color='blue', width=2, dash='dash'),
170
+ hovertemplate='<b>Buy & Hold</b>: ₹%{y:,.0f}<br><b>Date</b>: %{x}<extra></extra>'
171
+ ))
172
+
173
+ fig_perf.update_layout(
174
+ title="Strategy vs Buy & Hold Performance",
175
+ xaxis_title="Date",
176
+ yaxis_title="Portfolio Value (₹)",
177
+ height=500,
178
+ hovermode='x unified',
179
+ template='plotly_white'
180
+ )
181
+
182
+ st.plotly_chart(fig_perf, use_container_width=True)
183
+
184
+ # 3. Technical indicators in columns
185
+ col1, col2 = st.columns(2)
186
+
187
+ with col1:
188
+ st.subheader("💹 RSI Indicator")
189
+
190
+ fig_rsi = go.Figure()
191
+
192
+ # RSI line
193
+ fig_rsi.add_trace(go.Scatter(
194
+ x=df.index,
195
+ y=df['RSI'],
196
+ mode='lines',
197
+ name='RSI',
198
+ line=dict(color='purple', width=2),
199
+ hovertemplate='<b>RSI</b>: %{y:.1f}<br><b>Date</b>: %{x}<extra></extra>'
200
+ ))
201
+
202
+ # Overbought/Oversold lines
203
+ fig_rsi.add_hline(y=70, line_dash="dash", line_color="red",
204
+ annotation_text="Overbought (70)")
205
+ fig_rsi.add_hline(y=30, line_dash="dash", line_color="green",
206
+ annotation_text="Oversold (30)")
207
+ fig_rsi.add_hline(y=50, line_dash="solid", line_color="gray",
208
+ annotation_text="Midline (50)", opacity=0.5)
209
+
210
+ # Fill zones
211
+ fig_rsi.add_hrect(y0=0, y1=30, fillcolor="red", opacity=0.1,
212
+ line_width=0, annotation_text="Oversold Zone")
213
+ fig_rsi.add_hrect(y0=70, y1=100, fillcolor="green", opacity=0.1,
214
+ line_width=0, annotation_text="Overbought Zone")
215
+
216
+ # Add buy/sell signals on RSI
217
+ if not buy_signals.empty:
218
+ fig_rsi.add_trace(go.Scatter(
219
+ x=buy_signals.index,
220
+ y=buy_signals['RSI'],
221
+ mode='markers',
222
+ name='Buy Signal',
223
+ marker=dict(symbol='triangle-up', size=10, color='green'),
224
+ showlegend=False
225
+ ))
226
+
227
+ if not sell_signals.empty:
228
+ fig_rsi.add_trace(go.Scatter(
229
+ x=sell_signals.index,
230
+ y=sell_signals['RSI'],
231
+ mode='markers',
232
+ name='Sell Signal',
233
+ marker=dict(symbol='triangle-down', size=10, color='red'),
234
+ showlegend=False
235
+ ))
236
+
237
+ fig_rsi.update_layout(
238
+ title="RSI with Trading Signals",
239
+ xaxis_title="Date",
240
+ yaxis_title="RSI Value",
241
+ height=400,
242
+ yaxis=dict(range=[0, 100]),
243
+ template='plotly_white'
244
+ )
245
+
246
+ st.plotly_chart(fig_rsi, use_container_width=True)
247
+
248
+ with col2:
249
+ st.subheader("📊 MACD Indicator")
250
+
251
+ fig_macd = make_subplots(rows=2, cols=1,
252
+ shared_xaxes=True,
253
+ vertical_spacing=0.05,
254
+ row_width=[0.7, 0.3])
255
+
256
+ # MACD line
257
+ fig_macd.add_trace(go.Scatter(
258
+ x=df.index,
259
+ y=df['MACD'],
260
+ mode='lines',
261
+ name='MACD',
262
+ line=dict(color='blue', width=2),
263
+ hovertemplate='<b>MACD</b>: %{y:.3f}<extra></extra>'
264
+ ), row=1, col=1)
265
+
266
+ # Signal line
267
+ fig_macd.add_trace(go.Scatter(
268
+ x=df.index,
269
+ y=df['MACD_signal'],
270
+ mode='lines',
271
+ name='Signal Line',
272
+ line=dict(color='orange', width=2),
273
+ hovertemplate='<b>Signal</b>: %{y:.3f}<extra></extra>'
274
+ ), row=1, col=1)
275
+
276
+ # Zero line
277
+ fig_macd.add_hline(y=0, line_dash="solid", line_color="pink",
278
+ opacity=0.5, row=1, col=1)
279
+
280
+ # MACD histogram
281
+ colors = ['green' if val >= 0 else 'red' for val in df['MACD_hist']]
282
+ fig_macd.add_trace(go.Bar(
283
+ x=df.index,
284
+ y=df['MACD_hist'],
285
+ name='MACD Histogram',
286
+ marker_color=colors,
287
+ opacity=0.6,
288
+ hovertemplate='<b>Histogram</b>: %{y:.3f}<extra></extra>'
289
+ ), row=2, col=1)
290
+
291
+ fig_macd.update_layout(
292
+ title="MACD Indicator",
293
+ height=500,
294
+ template='plotly_white',
295
+ showlegend=True
296
+ )
297
+
298
+ fig_macd.update_xaxes(title_text="Date", row=2, col=1)
299
+ fig_macd.update_yaxes(title_text="MACD Value", row=1, col=1)
300
+ fig_macd.update_yaxes(title_text="Histogram", row=2, col=1)
301
+
302
+ st.plotly_chart(fig_macd, use_container_width=True)
303
+
304
+ # 4. Bollinger Bands
305
+ st.subheader("📈 Bollinger Bands")
306
+
307
+ fig_bb = go.Figure()
308
+
309
+ # Price line
310
+ fig_bb.add_trace(go.Scatter(
311
+ x=df.index,
312
+ y=df['Close'],
313
+ mode='lines',
314
+ name='Close Price',
315
+ line=dict(color='purple', width=2),
316
+ hovertemplate='<b>Price</b>: ₹%{y:.2f}<extra></extra>'
317
+ ))
318
+
319
+ # 20-day SMA
320
+ fig_bb.add_trace(go.Scatter(
321
+ x=df.index,
322
+ y=df['SMA20'],
323
+ mode='lines',
324
+ name='20-day SMA',
325
+ line=dict(color='blue', width=1.5),
326
+ hovertemplate='<b>SMA20</b>: ₹%{y:.2f}<extra></extra>'
327
+ ))
328
+
329
+ # Upper Band
330
+ fig_bb.add_trace(go.Scatter(
331
+ x=df.index,
332
+ y=df['Upper_Band'],
333
+ mode='lines',
334
+ name='Upper Band',
335
+ line=dict(color='red', dash='dash', width=1.5),
336
+ hovertemplate='<b>Upper Band</b>: ₹%{y:.2f}<extra></extra>'
337
+ ))
338
+
339
+ # Lower Band with fill
340
+ fig_bb.add_trace(go.Scatter(
341
+ x=df.index,
342
+ y=df['Lower_Band'],
343
+ mode='lines',
344
+ name='Lower Band',
345
+ line=dict(color='green', dash='dash', width=1.5),
346
+ fill='tonexty',
347
+ fillcolor='rgba(128,128,128,0.2)',
348
+ hovertemplate='<b>Lower Band</b>: ₹%{y:.2f}<extra></extra>'
349
+ ))
350
+
351
+ fig_bb.update_layout(
352
+ title="Bollinger Bands",
353
+ xaxis_title="Date",
354
+ yaxis_title="Price (₹)",
355
+ height=500,
356
+ template='plotly_white'
357
+ )
358
+
359
+ st.plotly_chart(fig_bb, use_container_width=True)
360
+
361
+ # 5. Drawdown Analysis
362
+ st.subheader("📉 Drawdown Analysis")
363
+
364
+ # Calculate drawdown
365
+ returns = results['Total'].pct_change().fillna(0)
366
+ cumulative = (1 + returns).cumprod()
367
+ running_max = cumulative.expanding().max()
368
+ drawdown = (cumulative - running_max) / running_max
369
+
370
+ fig_dd = go.Figure()
371
+
372
+ fig_dd.add_trace(go.Scatter(
373
+ x=df.index,
374
+ y=drawdown * 100,
375
+ mode='lines',
376
+ name='Drawdown',
377
+ fill='tozeroy',
378
+ fillcolor='rgba(255,0,0,0.3)',
379
+ line=dict(color='red', width=1),
380
+ hovertemplate='<b>Drawdown</b>: %{y:.1f}%<extra></extra>'
381
+ ))
382
+
383
+ fig_dd.update_layout(
384
+ title="Portfolio Drawdown Over Time",
385
+ xaxis_title="Date",
386
+ yaxis_title="Drawdown (%)",
387
+ height=400,
388
+ template='plotly_white'
389
+ )
390
+
391
+ st.plotly_chart(fig_dd, use_container_width=True)
392
+
393
+ # 6. Trade analysis
394
+ if not metrics['Trades DataFrame'].empty:
395
+ st.subheader("📋 Trade Analysis")
396
+
397
+ trades_df = metrics['Trades DataFrame']
398
+
399
+ # Trade statistics
400
+ col1, col2, col3 = st.columns(3)
401
+ with col1:
402
+ avg_trade_duration = (pd.to_datetime(trades_df['exit_date']) -
403
+ pd.to_datetime(trades_df['entry_date'])).dt.days.mean()
404
+ st.metric("📅 Avg Trade Duration", f"{avg_trade_duration:.1f} days")
405
+
406
+ with col2:
407
+ best_trade = trades_df['return_pct'].max()
408
+ st.metric("🚀 Best Trade", f"{best_trade:.2%}")
409
+
410
+ with col3:
411
+ worst_trade = trades_df['return_pct'].min()
412
+ st.metric("💥 Worst Trade", f"{worst_trade:.2%}")
413
+
414
+ # Trade returns distribution
415
+ st.subheader("📊 Trade Returns Distribution")
416
+
417
+ returns_pct = trades_df['return_pct'] * 100
418
+
419
+ fig_hist = px.histogram(
420
+ x=returns_pct,
421
+ nbins=20,
422
+ title="Distribution of Trade Returns",
423
+ labels={'x': 'Return (%)', 'y': 'Number of Trades'},
424
+ color_discrete_sequence=['steelblue']
425
+ )
426
+
427
+ # Add vertical lines for mean and zero
428
+ fig_hist.add_vline(x=0, line_dash="dash", line_color="red",
429
+ annotation_text="Break Even")
430
+ fig_hist.add_vline(x=returns_pct.mean(), line_dash="solid", line_color="green",
431
+ annotation_text=f"Mean: {returns_pct.mean():.1f}%")
432
+
433
+ fig_hist.update_layout(
434
+ height=400,
435
+ template='plotly_white',
436
+ showlegend=False
437
+ )
438
+
439
+ st.plotly_chart(fig_hist, use_container_width=True)
440
+
441
+ # Trade timeline
442
+ st.subheader("📅 Trade Timeline")
443
+
444
+ fig_timeline = go.Figure()
445
+
446
+ for i, trade in trades_df.iterrows():
447
+ color = 'green' if trade['return_pct'] > 0 else 'red'
448
+ fig_timeline.add_trace(go.Scatter(
449
+ x=[trade['entry_date'], trade['exit_date']],
450
+ y=[trade['entry_price'], trade['exit_price']],
451
+ mode='lines+markers',
452
+ name=f"Trade {i+1}",
453
+ line=dict(color=color, width=3),
454
+ marker=dict(size=8),
455
+ hovertemplate=f'<b>Trade {i+1}</b><br>' +
456
+ f'Entry: ₹{trade["entry_price"]:.2f}<br>' +
457
+ f'Exit: ₹{trade["exit_price"]:.2f}<br>' +
458
+ f'Return: {trade["return_pct"]:.2%}<br>' +
459
+ f'Duration: {(pd.to_datetime(trade["exit_date"]) - pd.to_datetime(trade["entry_date"])).days} days<extra></extra>',
460
+ showlegend=False
461
+ ))
462
+
463
+ fig_timeline.update_layout(
464
+ title="Individual Trade Performance Timeline",
465
+ xaxis_title="Date",
466
+ yaxis_title="Price (₹)",
467
+ height=500,
468
+ template='plotly_white'
469
+ )
470
+
471
+ st.plotly_chart(fig_timeline, use_container_width=True)
472
+
473
+ # Trade history table
474
+ st.subheader("📊 Detailed Trade History")
475
+ display_trades = trades_df.copy()
476
+ display_trades['Entry Date'] = pd.to_datetime(display_trades['entry_date']).dt.strftime('%Y-%m-%d')
477
+ display_trades['Exit Date'] = pd.to_datetime(display_trades['exit_date']).dt.strftime('%Y-%m-%d')
478
+ display_trades['Entry Price'] = display_trades['entry_price'].apply(lambda x: f"₹{x:.2f}")
479
+ display_trades['Exit Price'] = display_trades['exit_price'].apply(lambda x: f"₹{x:.2f}")
480
+ display_trades['P&L (₹)'] = display_trades['profit_loss'].apply(lambda x: f"₹{x:,.2f}")
481
+ display_trades['Return %'] = display_trades['return_pct'].apply(lambda x: f"{x:.2%}")
482
+ display_trades['Duration'] = (pd.to_datetime(trades_df['exit_date']) -
483
+ pd.to_datetime(trades_df['entry_date'])).dt.days
484
+
485
+ trade_display = display_trades[['Entry Date', 'Exit Date', 'Entry Price', 'Exit Price',
486
+ 'P&L (₹)', 'Return %', 'Duration', 'exit_reason']].copy()
487
+ trade_display.columns = ['Entry Date', 'Exit Date', 'Entry Price', 'Exit Price',
488
+ 'Profit/Loss', 'Return %', 'Days', 'Exit Reason']
489
+
490
+ st.dataframe(trade_display, use_container_width=True)
491
+
492
+ else:
493
+ st.info("📝 No trades were executed during this period with the current parameters.")
494
+
495
+ # 7. Signal summary table
496
+ st.subheader("📋 Trading Signals Summary")
497
+ signal_summary = df[df[signal_col] != 0].copy()
498
+
499
+ if not signal_summary.empty:
500
+ signal_summary['Signal Type'] = signal_summary[signal_col].map({1: '🟢 BUY', -1: '🔴 SELL'})
501
+ signal_summary['Price'] = signal_summary['Close'].apply(lambda x: f"₹{x:.2f}")
502
+ signal_summary['RSI'] = signal_summary['RSI'].apply(lambda x: f"{x:.1f}")
503
+ signal_summary[f'{strategy_name}{period_short}'] = signal_summary[f'{strategy_name}{period_short}'].apply(lambda x: f"₹{x:.2f}")
504
+ signal_summary[f'{strategy_name}{period_long}'] = signal_summary[f'{strategy_name}{period_long}'].apply(lambda x: f"₹{x:.2f}")
505
+
506
+ display_signals = signal_summary[['Signal Type', 'Price', 'RSI',
507
+ f'{strategy_name}{period_short}',
508
+ f'{strategy_name}{period_long}']].copy()
509
+ display_signals.index = display_signals.index.strftime('%Y-%m-%d')
510
+
511
+ st.dataframe(display_signals, use_container_width=True)
512
+ else:
513
+ st.info("📝 No trading signals were generated during this period with the current parameters.")
514
+
515
+ # ---------------------------------------
516
+ st.set_page_config(layout="wide", page_title="Algo Trading Dashboard", page_icon="📈")
517
+ st.title("📈 Algo-Trading Dashboard: Technical Analysis & Backtesting")
518
+
519
+ # Sidebar config
520
+ st.sidebar.header("📊 Configuration")
521
+
522
+ # Stock selection
523
+ stocks = ['ADANIENT.NS', 'ADANIPORTS.NS', 'APOLLOHOSP.NS', 'ASIANPAINT.NS', 'AXISBANK.NS',
524
+ 'BAJAJ-AUTO.NS', 'BAJFINANCE.NS', 'BAJAJFINSV.NS', 'BEL.NS', 'BHARTIARTL.NS',
525
+ 'CIPLA.NS', 'COALINDIA.NS', 'DRREDDY.NS', 'EICHERMOT.NS', 'GRASIM.NS',
526
+ 'HCLTECH.NS', 'HDFCBANK.NS', 'HDFCLIFE.NS', 'HEROMOTOCO.NS', 'HINDALCO.NS',
527
+ 'HINDUNILVR.NS', 'ICICIBANK.NS', 'INDUSINDBK.NS', 'INFY.NS', 'ITC.NS',
528
+ 'JIOFIN.NS', 'JSWSTEEL.NS', 'KOTAKBANK.NS', 'LT.NS', 'M&M.NS', 'MARUTI.NS',
529
+ 'NESTLEIND.NS', 'NTPC.NS', 'ONGC.NS', 'POWERGRID.NS', 'RELIANCE.NS',
530
+ 'SBILIFE.NS', 'SHRIRAMFIN.NS', 'SBIN.NS', 'SUNPHARMA.NS', 'TATACONSUM.NS',
531
+ 'TCS.NS', 'TATAMOTORS.NS', 'TATASTEEL.NS', 'TECHM.NS', 'TITAN.NS',
532
+ 'TRENT.NS', 'ULTRACEMCO.NS', 'WIPRO.NS', 'ETERNAL.NS']
533
+
534
+ selected_stock = st.sidebar.selectbox("Select Stock", stocks)
535
+ start_date = st.sidebar.date_input("Start Date", pd.to_datetime("2024-01-01"))
536
+
537
+ # Strategy selection
538
+ strategy_type = st.sidebar.selectbox("Strategy Type", ["SMA-based", "EMA-based", "Both"])
539
+
540
+ st.sidebar.subheader("📈 Technical Indicators")
541
+ rsi_period = st.sidebar.slider("RSI Period", 5, 30, 14)
542
+ sma_short = st.sidebar.slider("Short-term SMA", 5, 30, 20)
543
+ sma_long = st.sidebar.slider("Long-term SMA", 30, 100, 50)
544
+ ema_short = st.sidebar.slider("Short-term EMA", 5, 30, 20)
545
+ ema_long = st.sidebar.slider("Long-term EMA", 30, 100, 50)
546
+
547
+ st.sidebar.subheader("💰 Backtesting Parameters")
548
+ initial_cash = st.sidebar.number_input("Initial Capital (₹)", min_value=10000, value=100000, step=10000)
549
+ transaction_cost = st.sidebar.slider("Transaction Cost (%)", 0.0, 1.0, 0.1, step=0.05) / 100
550
+ stop_loss = st.sidebar.slider("Stop Loss (%)", 0.0, 20.0, 5.0, step=1.0) / 100
551
+ take_profit = st.sidebar.slider("Take Profit (%)", 0.0, 50.0, 15.0, step=5.0) / 100
552
+
553
+ # Enable/disable risk management
554
+ use_risk_mgmt = st.sidebar.checkbox("Enable Risk Management", value=True)
555
+
556
+ # Load data with progress bar
557
+ with st.spinner(f'Loading data for {selected_stock}...'):
558
+ df = fetch_stock_data(selected_stock, start_date=start_date.strftime("%Y-%m-%d"))
559
+
560
+ st.subheader(f"📊 Stock Data for {selected_stock}")
561
+ st.write(f"**Date Range:** {start_date.strftime('%Y-%m-%d')} to Present")
562
+ st.write(f"**Total Records:** {len(df)} days")
563
+
564
+ if df.empty:
565
+ st.error("❌ No data found for the selected stock and date range.")
566
+ st.stop()
567
+
568
+ # Apply indicators
569
+ with st.spinner('Calculating technical indicators...'):
570
+ df['RSI'] = rsi(df, period=rsi_period)
571
+ df['SMA20'] = sma(df, period=sma_short)
572
+ df['SMA50'] = sma(df, period=sma_long)
573
+ df['EMA20'] = ema(df, period=ema_short)
574
+ df['EMA50'] = ema(df, period=ema_long)
575
+ df['MACD'], df['MACD_signal'], df['MACD_hist'] = macd(df)
576
+ df['Upper_Band'] = df['SMA20'] + 2 * df['Close'].rolling(window=20).std()
577
+ df['Lower_Band'] = df['SMA20'] - 2 * df['Close'].rolling(window=20).std()
578
+
579
+ # Apply strategies based on selection
580
+ if strategy_type in ["SMA-based", "Both"]:
581
+ df = generate_signals_sma(df, rsi_col='RSI', sma_short_col='SMA20', sma_long_col='SMA50')
582
+
583
+ if strategy_type in ["EMA-based", "Both"]:
584
+ df = generate_signals_ema(df, rsi_col='RSI', ema_short_col='EMA20', ema_long_col='EMA50')
585
+
586
+ # Backtesting section
587
+ st.header("🔍 Backtesting Results")
588
+
589
+ # Create tabs for different strategies
590
+ if strategy_type == "Both":
591
+ tab1, tab2 = st.tabs(["SMA Strategy", "EMA Strategy"])
592
+
593
+ with tab1:
594
+ st.subheader("📊 SMA Strategy Results")
595
+ sma_results, sma_metrics = backtest_signals(
596
+ df,
597
+ signal_col='SMA_Signal',
598
+ price_col='Close',
599
+ initial_cash=initial_cash,
600
+ transaction_cost=transaction_cost if use_risk_mgmt else 0,
601
+ stop_loss=stop_loss if use_risk_mgmt else None,
602
+ take_profit=take_profit if use_risk_mgmt else None
603
+ )
604
+ display_strategy_results(df, sma_results, sma_metrics, "SMA", sma_short, sma_long, initial_cash, selected_stock)
605
+
606
+ with tab2:
607
+ st.subheader("📊 EMA Strategy Results")
608
+ ema_results, ema_metrics = backtest_signals(
609
+ df,
610
+ signal_col='EMA_Signal',
611
+ price_col='Close',
612
+ initial_cash=initial_cash,
613
+ transaction_cost=transaction_cost if use_risk_mgmt else 0,
614
+ stop_loss=stop_loss if use_risk_mgmt else None,
615
+ take_profit=take_profit if use_risk_mgmt else None
616
+ )
617
+ display_strategy_results(df, ema_results, ema_metrics, "EMA", ema_short, ema_long, initial_cash, selected_stock)
618
+
619
+ else:
620
+ # Single strategy
621
+ signal_col = 'SMA_Signal' if strategy_type == "SMA-based" else 'EMA_Signal'
622
+ strategy_name = strategy_type.split('-')[0]
623
+
624
+ results, metrics = backtest_signals(
625
+ df,
626
+ signal_col=signal_col,
627
+ price_col='Close',
628
+ initial_cash=initial_cash,
629
+ transaction_cost=transaction_cost if use_risk_mgmt else 0,
630
+ stop_loss=stop_loss if use_risk_mgmt else None,
631
+ take_profit=take_profit if use_risk_mgmt else None
632
+ )
633
+
634
+ period_short = sma_short if strategy_type == "SMA-based" else ema_short
635
+ period_long = sma_long if strategy_type == "SMA-based" else ema_long
636
+ display_strategy_results(df, results, metrics, strategy_name, period_short, period_long, initial_cash, selected_stock)
637
+
638
+ # Data download section
639
+ st.subheader("💾 Download Data")
640
+ col1, col2 = st.columns(2)
641
+
642
+ with col1:
643
+ csv_data = df.to_csv(index=True)
644
+ st.download_button(
645
+ label="📁 Download Full Dataset (CSV)",
646
+ data=csv_data,
647
+ file_name=f"{selected_stock}_analysis_{start_date.strftime('%Y%m%d')}.csv",
648
+ mime="text/csv"
649
+ )
650
+
651
+ with col2:
652
+ if 'results' in locals():
653
+ results_csv = results.to_csv(index=True)
654
+ st.download_button(
655
+ label="📊 Download Backtest Results (CSV)",
656
+ data=results_csv,
657
+ file_name=f"{selected_stock}_backtest_{start_date.strftime('%Y%m%d')}.csv",
658
+ mime="text/csv"
659
+ )
660
+
661
+ # Footer
662
+ st.markdown("---")
663
+ st.markdown("Developed by Zane Vijay Falcao")
src/models/H2H_model.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
src/models/app.py ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import yfinance as yf
5
+ import pickle
6
+ import plotly.graph_objects as go
7
+ import plotly.express as px
8
+ from datetime import datetime, timedelta
9
+ import warnings
10
+ from curl_cffi import requests
11
+ session = requests.Session(impersonate="chrome")
12
+ warnings.filterwarnings('ignore')
13
+
14
+ # Page config
15
+ st.set_page_config(
16
+ page_title="Stock Price Prediction App",
17
+ page_icon="📈",
18
+ layout="wide"
19
+ )
20
+
21
+ # Title and description
22
+ st.title("📈 Stock Price Prediction App")
23
+ st.markdown("This app uses a trained Logistic Regression model to predict whether a stock will go **UP** ⬆️ or **DOWN** ⬇️ the next day.")
24
+
25
+ # Sidebar for user inputs
26
+ st.sidebar.header("🔧 Configuration")
27
+
28
+ # Stock symbols from your model
29
+ STOCK_SYMBOLS = [
30
+ 'ADANIENT.NS', 'ADANIPORTS.NS', 'APOLLOHOSP.NS', 'ASIANPAINT.NS',
31
+ 'AXISBANK.NS', 'BAJAJ-AUTO.NS', 'BAJFINANCE.NS', 'BAJAJFINSV.NS',
32
+ 'BEL.NS', 'BHARTIARTL.NS', 'CIPLA.NS', 'COALINDIA.NS', 'DRREDDY.NS',
33
+ 'EICHERMOT.NS', 'GRASIM.NS', 'HCLTECH.NS', 'HDFCBANK.NS', 'HDFCLIFE.NS',
34
+ 'HEROMOTOCO.NS', 'HINDALCO.NS', 'HINDUNILVR.NS', 'ICICIBANK.NS',
35
+ 'INDUSINDBK.NS', 'INFY.NS', 'ITC.NS', 'JIOFIN.NS', 'JSWSTEEL.NS',
36
+ 'KOTAKBANK.NS', 'LT.NS', 'M&M.NS', 'MARUTI.NS', 'NESTLEIND.NS',
37
+ 'NTPC.NS', 'ONGC.NS', 'POWERGRID.NS', 'RELIANCE.NS', 'SBILIFE.NS',
38
+ 'SHRIRAMFIN.NS', 'SBIN.NS', 'SUNPHARMA.NS', 'TATACONSUM.NS', 'TCS.NS',
39
+ 'TATAMOTORS.NS', 'TATASTEEL.NS', 'TECHM.NS', 'TITAN.NS', 'TRENT.NS',
40
+ 'ULTRACEMCO.NS', 'WIPRO.NS', 'ETERNAL.NS'
41
+ ]
42
+
43
+ # User inputs
44
+ selected_stock = st.sidebar.selectbox("Select Stock Symbol", STOCK_SYMBOLS, index=35) # Default to RELIANCE.NS
45
+ start_date = st.sidebar.date_input("Start Date", value=datetime(2020, 1, 1))
46
+ end_date = st.sidebar.date_input("End Date", value=datetime.now())
47
+ prediction_mode = st.sidebar.button("Start Analysis")
48
+ rsi_period = st.sidebar.slider("RSI Period", min_value=5, max_value=30, value=14, step=1)
49
+ short_period = st.sidebar.slider("Short-term", min_value=5, max_value=50, value=20, step=1)
50
+ long_period = st.sidebar.slider("Long-term", min_value=50, max_value=200, value=50, step=1)
51
+
52
+ # Helper functions (same as in your original code)
53
+ def SMA(series, period):
54
+ return series.rolling(window=period).mean()
55
+
56
+ def EMA(series, period):
57
+ return series.ewm(span=period, adjust=False).mean()
58
+
59
+ def MACD(series, fast=12, slow=26, signal=9):
60
+ ema_fast = EMA(series, fast)
61
+ ema_slow = EMA(series, slow)
62
+ macd = ema_fast - ema_slow
63
+ macd_signal = EMA(macd, signal)
64
+ macd_hist = macd - macd_signal
65
+ return macd, macd_signal, macd_hist
66
+
67
+ def RSI(series, period=14):
68
+ delta = series.diff()
69
+ gain = (delta.where(delta > 0, 0)).ewm(alpha=1/period, min_periods=period).mean()
70
+ loss = (-delta.where(delta < 0, 0)).ewm(alpha=1/period, min_periods=period).mean()
71
+ RS = gain / loss
72
+ return 100 - (100 / (1 + RS))
73
+
74
+ def create_volatility_features(df):
75
+ if 'return_1d' not in df.columns:
76
+ df['return_1d'] = df['Close'].pct_change()
77
+
78
+ for period in [5, 10, 20, 30]:
79
+ df[f'volatility_{period}d'] = df['return_1d'].rolling(period).std()
80
+
81
+ df['vol_ratio_5_20'] = df['volatility_5d'] / df['volatility_20d']
82
+ df['vol_ratio_10_20'] = df['volatility_10d'] / df['volatility_20d']
83
+ df['vol_rank_20'] = df['volatility_5d'].rolling(20).rank(pct=True)
84
+ df['vol_rank_50'] = df['volatility_5d'].rolling(50).rank(pct=True)
85
+
86
+ return df
87
+
88
+ def create_enhanced_lag_features(df):
89
+ for lag in [1, 2, 3, 5, 10]:
90
+ df[f'return_lag_{lag}'] = df['return_1d'].shift(lag)
91
+
92
+ for lag in [1, 2, 3]:
93
+ if 'RSI14' in df.columns:
94
+ df[f'rsi_lag_{lag}'] = df['RSI14'].shift(lag)
95
+ if 'MACD' in df.columns:
96
+ df[f'macd_lag_{lag}'] = df['MACD'].shift(lag)
97
+
98
+ if 'volume_ratio_20' in df.columns:
99
+ for lag in [1, 2]:
100
+ df[f'volume_ratio_lag_{lag}'] = df['volume_ratio_20'].shift(lag)
101
+
102
+ return df
103
+
104
+ def create_volume_features(df):
105
+ df['volume_sma_10'] = df['Volume'].rolling(10).mean()
106
+ df['volume_sma_20'] = df['Volume'].rolling(20).mean()
107
+ df['volume_sma_50'] = df['Volume'].rolling(50).mean()
108
+
109
+ df['volume_ratio_10'] = df['Volume'] / df['volume_sma_10']
110
+ df['volume_ratio_20'] = df['Volume'] / df['volume_sma_20']
111
+ df['volume_ratio_50'] = df['Volume'] / df['volume_sma_50']
112
+
113
+ df['price_volume'] = df['Close'] * df['Volume']
114
+ df['pv_sma_5'] = df['price_volume'].rolling(5).mean()
115
+ df['volume_momentum_5'] = df['Volume'] / df['Volume'].shift(5)
116
+
117
+ return df
118
+
119
+ def create_momentum_features(df):
120
+ for period in [3, 5, 10, 20]:
121
+ df[f'momentum_{period}d'] = df['Close'] / df['Close'].shift(period) - 1
122
+
123
+ for period in [5, 10]:
124
+ df[f'roc_{period}d'] = (df['Close'] - df['Close'].shift(period)) / df['Close'].shift(period)
125
+
126
+ return df
127
+
128
+ def create_position_features(df):
129
+ for period in [10, 20, 50]:
130
+ df[f'high_{period}d'] = df['High'].rolling(period).max()
131
+ df[f'low_{period}d'] = df['Low'].rolling(period).min()
132
+ df[f'price_position_{period}'] = (df['Close'] - df[f'low_{period}d']) / (df[f'high_{period}d'] - df[f'low_{period}d'])
133
+
134
+ if 'SMA20' in df.columns:
135
+ bb_std = df['Close'].rolling(20).std()
136
+ df['bb_upper'] = df['SMA20'] + (bb_std * 2)
137
+ df['bb_lower'] = df['SMA20'] - (bb_std * 2)
138
+ df['bb_position'] = (df['Close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])
139
+
140
+ return df
141
+
142
+ def process_stock_data(df):
143
+ """Process stock data to create all features"""
144
+ df = df.copy()
145
+
146
+ # Basic technical indicators
147
+ df['SMA20'] = SMA(df['Close'], short_period)
148
+ df['SMA50'] = SMA(df['Close'], long_period)
149
+ df['EMA20'] = EMA(df['Close'], short_period)
150
+ df['EMA50'] = EMA(df['Close'], long_period)
151
+ df['RSI14'] = RSI(df['Close'], rsi_period)
152
+ df['RSI20'] = RSI(df['Close'], rsi_period + 6) # Example for another RSI period
153
+ df['MACD'], df['MACD_signal'], df['MACD_hist'] = MACD(df['Close'])
154
+
155
+ # Create feature sets
156
+ df = create_volatility_features(df)
157
+ df = create_enhanced_lag_features(df)
158
+ df = create_volume_features(df)
159
+ df = create_momentum_features(df)
160
+ df = create_position_features(df)
161
+
162
+ # Additional features
163
+ df['SMA_crossover'] = (df['SMA20'] > df['SMA50']).astype(int)
164
+ df['RSI_oversold'] = (df['RSI14'] < 30).astype(int)
165
+ # Target: next-day up/down
166
+ df['next_close'] = df['Close'].shift(-1)
167
+ df['target'] = (df['next_close'] > df['Close']).astype(int)
168
+
169
+ return df
170
+
171
+ @st.cache_data
172
+ def load_stock_data(symbol, start_date, end_date):
173
+ """Load stock data from Yahoo Finance"""
174
+ try:
175
+ data = yf.download(symbol, start=start_date, end=end_date,session=session)
176
+ # Flatten the MultiIndex columns
177
+ data.columns = [col[0] for col in data.columns]
178
+ return data
179
+ except Exception as e:
180
+ st.error(f"Error loading data: {e}")
181
+ return None
182
+
183
+ # Feature list (same as in your model)
184
+ FEATURES = [
185
+ 'Close', 'Volume', 'SMA20', 'SMA50', 'EMA20', 'EMA50',
186
+ 'RSI14', 'MACD', 'MACD_signal', 'MACD_hist',
187
+ 'SMA_crossover', 'RSI_oversold',
188
+ 'return_1d', 'volatility_5d', 'volatility_10d', 'volatility_20d',
189
+ 'volatility_30d', 'vol_ratio_5_20', 'vol_ratio_10_20', 'vol_rank_20',
190
+ 'vol_rank_50', 'return_lag_1', 'return_lag_2', 'return_lag_3',
191
+ 'return_lag_5', 'return_lag_10', 'rsi_lag_1', 'macd_lag_1', 'rsi_lag_2',
192
+ 'macd_lag_2', 'rsi_lag_3', 'macd_lag_3', 'volume_sma_10',
193
+ 'volume_sma_20', 'volume_sma_50', 'volume_ratio_10', 'volume_ratio_20',
194
+ 'volume_ratio_50', 'price_volume', 'pv_sma_5', 'volume_momentum_5',
195
+ 'momentum_3d', 'momentum_5d', 'momentum_10d', 'momentum_20d', 'roc_5d',
196
+ 'roc_10d', 'high_10d', 'low_10d', 'price_position_10', 'high_20d',
197
+ 'low_20d', 'price_position_20', 'high_50d', 'low_50d',
198
+ 'price_position_50', 'bb_upper', 'bb_lower', 'bb_position','target'
199
+ ]
200
+
201
+ # Main app logic
202
+ st.header(f"📊 Latest Data Prediction for {selected_stock}")
203
+
204
+
205
+ with st.spinner("Loading stock data..."):
206
+ stock_data = load_stock_data(selected_stock, start_date, end_date)
207
+
208
+ if stock_data is not None and not stock_data.empty:
209
+ # Process the data
210
+ processed_data = process_stock_data(stock_data)
211
+ processed_data = processed_data.dropna()
212
+
213
+
214
+ if len(processed_data) > 0:
215
+ # Get the latest row for prediction
216
+ latest_data = processed_data.iloc[-1]
217
+
218
+ # Display current stock info
219
+ col1, col2, col3, col4 = st.columns(4)
220
+ with col1:
221
+ st.metric("Current Price", f"₹{latest_data['Close']:.2f}")
222
+ with col2:
223
+ daily_change = ((latest_data['Close'] - processed_data.iloc[-2]['Close']) / processed_data.iloc[-2]['Close']) * 100
224
+ st.metric("Daily Change", f"{daily_change:.2f}%")
225
+ with col3:
226
+ st.metric("Volume", f"{latest_data['Volume']:,.0f}")
227
+ with col4:
228
+ st.metric("RSI14", f"{latest_data['RSI14']:.2f}")
229
+
230
+ # Create feature vector
231
+ feature_vector = latest_data[FEATURES].values.reshape(1, -1)
232
+
233
+ # For demo purposes, create a mock prediction (since we don't have the actual model file)
234
+ # In real implementation, you would load your saved model:
235
+ model = pickle.load(open('logistic_regression_model.pkl', 'rb'))
236
+ scaler = pickle.load(open('scaler.pkl', 'rb')) # You'd need to save this too
237
+
238
+
239
+ # Scale the features
240
+ feature_vector_scaled = scaler.transform(feature_vector)
241
+
242
+ # Make prediction
243
+ prediction = model.predict(feature_vector_scaled)[0]
244
+ probability = model.predict_proba(feature_vector_scaled)[0].max()
245
+
246
+ # Display prediction
247
+ st.header("🔮 Prediction")
248
+ col1, col2 = st.columns(2)
249
+
250
+ with col1:
251
+ if prediction == 1:
252
+ st.success("📈 **PREDICTION: UP**")
253
+ st.write(f"The model predicts the stock will go **UP** tomorrow with {probability:.1%} confidence.")
254
+ else:
255
+ st.error("📉 **PREDICTION: DOWN**")
256
+ st.write(f"The model predicts the stock will go **DOWN** tomorrow with {probability:.1%} confidence.")
257
+
258
+ with col2:
259
+ # Confidence gauge
260
+ fig_gauge = go.Figure(go.Indicator(
261
+ mode = "gauge+number",
262
+ value = probability * 100,
263
+ domain = {'x': [0, 1], 'y': [0, 1]},
264
+ title = {'text': "Confidence %"},
265
+ gauge = {
266
+ 'axis': {'range': [None, 100]},
267
+ 'bar': {'color': "darkgreen" if prediction == 1 else "darkred"},
268
+ 'steps': [
269
+ {'range': [0, 50], 'color': "lightgray"},
270
+ {'range': [50, 80], 'color': "yellow"},
271
+ {'range': [80, 100], 'color': "lightgreen"}
272
+ ],
273
+ 'threshold': {
274
+ 'line': {'color': "red", 'width': 4},
275
+ 'thickness': 0.75,
276
+ 'value': 90
277
+ }
278
+ }
279
+ ))
280
+ fig_gauge.update_layout(height=300)
281
+ st.plotly_chart(fig_gauge, use_container_width=True)
282
+
283
+ # Technical indicators chart
284
+ st.header("📈 Technical Analysis")
285
+
286
+ # Price and Simple moving averages
287
+ fig_price = go.Figure()
288
+ fig_price.add_trace(go.Scatter(
289
+ x=processed_data.index[-60:],
290
+ y=processed_data['Close'][-60:],
291
+ mode='lines',
292
+ name='Close Price',
293
+ line=dict(color='blue', width=2)
294
+ ))
295
+ fig_price.add_trace(go.Scatter(
296
+ x=processed_data.index[-60:],
297
+ y=processed_data['SMA20'][-60:],
298
+ mode='lines',
299
+ name='SMA20',
300
+ line=dict(color='orange', width=1)
301
+ ))
302
+ fig_price.add_trace(go.Scatter(
303
+ x=processed_data.index[-60:],
304
+ y=processed_data['SMA50'][-60:],
305
+ mode='lines',
306
+ name='SMA50',
307
+ line=dict(color='red', width=1)
308
+ ))
309
+
310
+ fig_price.update_layout(
311
+ title=f"{selected_stock} - Price and Simple Moving Averages (Last 60 Days)",
312
+ xaxis_title="Date",
313
+ yaxis_title="Price (₹)",
314
+ height=400
315
+ )
316
+ st.plotly_chart(fig_price, use_container_width=True)
317
+
318
+ # Price and Exponential moving averages
319
+ fig_price = go.Figure()
320
+ fig_price.add_trace(go.Scatter(
321
+ x=processed_data.index[-30:],
322
+ y=processed_data['Close'][-30:],
323
+ mode='lines',
324
+ name='Close Price',
325
+ line=dict(color='blue', width=2)
326
+ ))
327
+ fig_price.add_trace(go.Scatter(
328
+ x=processed_data.index[-30:],
329
+ y=processed_data['EMA20'][-30:],
330
+ mode='lines',
331
+ name='EMA20',
332
+ line=dict(color='orange', width=1)
333
+ ))
334
+ fig_price.add_trace(go.Scatter(
335
+ x=processed_data.index[-30:],
336
+ y=processed_data['EMA50'][-30:],
337
+ mode='lines',
338
+ name='EMA50',
339
+ line=dict(color='red', width=1)
340
+ ))
341
+
342
+ fig_price.update_layout(
343
+ title=f"{selected_stock} - Price and Exponential Moving Averages (Last 60 Days)",
344
+ xaxis_title="Date",
345
+ yaxis_title="Price (₹)",
346
+ height=400
347
+ )
348
+ st.plotly_chart(fig_price, use_container_width=True)
349
+
350
+ # RSI chart
351
+ col1, col2 = st.columns(2)
352
+ with col1:
353
+ fig_rsi = go.Figure()
354
+ fig_rsi.add_trace(go.Scatter(
355
+ x=processed_data.index[-30:],
356
+ y=processed_data['RSI14'][-30:],
357
+ mode='lines',
358
+ name='RSI14',
359
+ line=dict(color='purple')
360
+ ))
361
+ fig_rsi.add_hline(y=70, line_dash="dash", line_color="red", annotation_text="Overbought")
362
+ fig_rsi.add_hline(y=30, line_dash="dash", line_color="green", annotation_text="Oversold")
363
+ fig_rsi.update_layout(
364
+ title="RSI (14-day)",
365
+ xaxis_title="Date",
366
+ yaxis_title="RSI",
367
+ height=300
368
+ )
369
+ st.plotly_chart(fig_rsi, use_container_width=True)
370
+
371
+ with col2:
372
+ # MACD chart
373
+ fig_macd = go.Figure()
374
+ fig_macd.add_trace(go.Scatter(
375
+ x=processed_data.index[-30:],
376
+ y=processed_data['MACD'][-30:],
377
+ mode='lines',
378
+ name='MACD',
379
+ line=dict(color='blue')
380
+ ))
381
+ fig_macd.add_trace(go.Scatter(
382
+ x=processed_data.index[-30:],
383
+ y=processed_data['MACD_signal'][-30:],
384
+ mode='lines',
385
+ name='Signal',
386
+ line=dict(color='red')
387
+ ))
388
+ fig_macd.update_layout(
389
+ title="MACD",
390
+ xaxis_title="Date",
391
+ yaxis_title="MACD",
392
+ height=300
393
+ )
394
+ st.plotly_chart(fig_macd, use_container_width=True)
395
+
396
+ # Feature importance (mock data for demo)
397
+ st.header("🎯 Key Factors")
398
+ st.write("Most important features affecting the prediction:")
399
+
400
+ mock_features = ['RSI14', 'return_lag_1', 'volatility_5d', 'MACD', 'volume_ratio_20']
401
+ mock_importance = [0.15, 0.12, 0.10, 0.08, 0.07]
402
+
403
+ fig_importance = px.bar(
404
+ x=mock_importance,
405
+ y=mock_features,
406
+ orientation='h',
407
+ title="Feature Importance"
408
+ )
409
+ fig_importance.update_layout(height=300)
410
+ st.plotly_chart(fig_importance, use_container_width=True)
411
+
412
+ else:
413
+ st.error("Not enough data to make a prediction. Please try a different stock or date range.")
414
+ else:
415
+ st.error("Unable to load stock data. Please check the symbol and try again.")
416
+
417
+
418
+
419
+ # Sidebar information
420
+ st.sidebar.markdown("---")
421
+ st.sidebar.header("ℹ️ About")
422
+ st.sidebar.write("""
423
+ This app uses a Logistic Regression model trained on:
424
+ - **50 Indian stocks** from NSE
425
+ - **59 technical features** including RSI, MACD, moving averages, volatility measures, and lag features
426
+ - **Historical data** for pattern recognition
427
+
428
+ **Disclaimer**: This is for educational purposes only. Always do your own research before making investment decisions.
429
+ """)
430
+
431
+ st.sidebar.markdown("---")
432
+ st.sidebar.write("**Model Performance:**")
433
+ st.sidebar.write("• Accuracy: 55%")
434
+ st.sidebar.write("• F1 Score: 0.4839")
435
+ st.sidebar.write("• AUC: 0.5370")
436
+ st.sidebar.write("Average Precision (AP): 0.5300")
437
+
438
+ # Footer
439
+ st.markdown("---")
440
+ st.markdown("**⚠️ Disclaimer**: This prediction model is for research purposes only. Stock market investments are subject to market risks. Please consult with a financial advisor before making investment decisions.")
src/models/h2h_model.py ADDED
@@ -0,0 +1,491 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """H2H model.ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1uxbLGJ4l9i0bdWy43Oz4rgsyTdA5FTSd
8
+ """
9
+
10
+ !pip install yfinance
11
+
12
+ # Data and computation
13
+ import pandas as pd
14
+ import numpy as np
15
+
16
+ # Plotting
17
+ import matplotlib.pyplot as plt
18
+ import seaborn as sns
19
+ sns.set_style('whitegrid')
20
+ plt.style.use("fivethirtyeight")
21
+
22
+ # Yahoo Finance data import
23
+ import yfinance as yf
24
+
25
+ # Machine Learning
26
+ from sklearn.model_selection import train_test_split
27
+ from sklearn.tree import DecisionTreeClassifier
28
+ from sklearn.linear_model import LogisticRegression
29
+ from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, roc_curve, auc
30
+ from sklearn.preprocessing import StandardScaler, RobustScaler
31
+ from sklearn.pipeline import Pipeline
32
+ import scipy.stats as stats
33
+ from sklearn.model_selection import GridSearchCV
34
+
35
+ # Misc
36
+ import warnings
37
+ warnings.filterwarnings('ignore')
38
+
39
+ # Select stocks and date range
40
+ symbols = ['ADANIENT.NS',
41
+ 'ADANIPORTS.NS', 'APOLLOHOSP.NS', 'ASIANPAINT.NS',
42
+ 'AXISBANK.NS', 'BAJAJ-AUTO.NS', 'BAJFINANCE.NS', 'BAJAJFINSV.NS', 'BEL.NS', 'BHARTIARTL.NS', 'CIPLA.NS',
43
+ 'COALINDIA.NS', 'DRREDDY.NS', 'EICHERMOT.NS', 'GRASIM.NS', 'HCLTECH.NS', 'HDFCBANK.NS',
44
+ 'HDFCLIFE.NS', 'HEROMOTOCO.NS', 'HINDALCO.NS', 'HINDUNILVR.NS', 'ICICIBANK.NS', 'INDUSINDBK.NS',
45
+ 'INFY.NS', 'ITC.NS', 'JIOFIN.NS', 'JSWSTEEL.NS', 'KOTAKBANK.NS', 'LT.NS', 'M&M.NS', 'MARUTI.NS',
46
+ 'NESTLEIND.NS', 'NTPC.NS', 'ONGC.NS', 'POWERGRID.NS', 'RELIANCE.NS', 'SBILIFE.NS', 'SHRIRAMFIN.NS',
47
+ 'SBIN.NS', 'SUNPHARMA.NS', 'TATACONSUM.NS', 'TCS.NS',
48
+ 'TATAMOTORS.NS', 'TATASTEEL.NS', 'TECHM.NS',
49
+ 'TITAN.NS', 'TRENT.NS', 'ULTRACEMCO.NS',
50
+ 'WIPRO.NS',
51
+ 'ETERNAL.NS']
52
+
53
+ start_date = '2024-07-31'
54
+ end_date = '2025-07-31'
55
+
56
+ # Download daily close data for both stocks
57
+ raw_data = yf.download(symbols, start=start_date, end=end_date)
58
+
59
+ # Flatten MultiIndex columns
60
+ raw_data.columns = ['_'.join(col).strip() for col in raw_data.columns.values]
61
+
62
+ # For simplicity, stack to long format and process each stock similarly
63
+ data = raw_data.copy()
64
+
65
+ data
66
+
67
+ # Helper functions
68
+
69
+ def SMA(series, period):
70
+ return series.rolling(window=period).mean()
71
+
72
+ def EMA(series, period):
73
+ return series.ewm(span=period, adjust=False).mean()
74
+
75
+ def MACD(series, fast=12, slow=26, signal=9):
76
+ ema_fast = EMA(series, fast)
77
+ ema_slow = EMA(series, slow)
78
+ macd = ema_fast - ema_slow
79
+ macd_signal = EMA(macd, signal)
80
+ macd_hist = macd - macd_signal
81
+ return macd, macd_signal, macd_hist
82
+
83
+ def RSI(series, period=14):
84
+ delta = series.diff()
85
+ gain = (delta.where(delta > 0, 0)).ewm(alpha=1/period, min_periods=period).mean()
86
+ loss = (-delta.where(delta < 0, 0)).ewm(alpha=1/period, min_periods=period).mean()
87
+ RS = gain / loss
88
+ return 100 - (100 / (1 + RS))
89
+
90
+ def create_volatility_features(df):
91
+
92
+ # Calculate returns if not exists
93
+ if 'return_1d' not in df.columns:
94
+ df['return_1d'] = df['Close'].pct_change()
95
+
96
+ # Volatility features (crucial for logistic regression)
97
+ for period in [5, 10, 20, 30]:
98
+ df[f'volatility_{period}d'] = df['return_1d'].rolling(period).std()
99
+
100
+ # Volatility ratios
101
+ df['vol_ratio_5_20'] = df['volatility_5d'] / df['volatility_20d']
102
+ df['vol_ratio_10_20'] = df['volatility_10d'] / df['volatility_20d']
103
+
104
+ # Volatility rank (where current vol sits in historical range)
105
+ df['vol_rank_20'] = df['volatility_5d'].rolling(20).rank(pct=True)
106
+ df['vol_rank_50'] = df['volatility_5d'].rolling(50).rank(pct=True)
107
+
108
+ return df
109
+
110
+ def create_enhanced_lag_features(df):
111
+ """Add comprehensive lag features - Critical for time series"""
112
+ #print("Adding enhanced lag features...")
113
+
114
+ # Price momentum lags
115
+ for lag in [1, 2, 3, 5, 10]:
116
+ df[f'return_lag_{lag}'] = df['return_1d'].shift(lag)
117
+
118
+ # Technical indicator lags
119
+ for lag in [1, 2, 3]:
120
+ if 'RSI14' in df.columns:
121
+ df[f'rsi_lag_{lag}'] = df['RSI14'].shift(lag)
122
+ if 'MACD' in df.columns:
123
+ df[f'macd_lag_{lag}'] = df['MACD'].shift(lag)
124
+
125
+ # Volume lags
126
+ if 'volume_ratio_20' in df.columns:
127
+ for lag in [1, 2]:
128
+ df[f'volume_ratio_lag_{lag}'] = df['volume_ratio_20'].shift(lag)
129
+
130
+ return df
131
+
132
+ def create_volume_features(df):
133
+ """Enhanced volume features"""
134
+ #print("Adding volume features...")
135
+
136
+ # Volume moving averages
137
+ df['volume_sma_10'] = df['Volume'].rolling(10).mean()
138
+ df['volume_sma_20'] = df['Volume'].rolling(20).mean()
139
+ df['volume_sma_50'] = df['Volume'].rolling(50).mean()
140
+
141
+ # Volume ratios
142
+ df['volume_ratio_10'] = df['Volume'] / df['volume_sma_10']
143
+ df['volume_ratio_20'] = df['Volume'] / df['volume_sma_20']
144
+ df['volume_ratio_50'] = df['Volume'] / df['volume_sma_50']
145
+
146
+ # Price-volume features
147
+ df['price_volume'] = df['Close'] * df['Volume']
148
+ df['pv_sma_5'] = df['price_volume'].rolling(5).mean()
149
+
150
+ # Volume momentum
151
+ df['volume_momentum_5'] = df['Volume'] / df['Volume'].shift(5)
152
+
153
+ return df
154
+
155
+ def create_momentum_features(df):
156
+ """Add momentum features"""
157
+ #print("Adding momentum features...")
158
+
159
+ # Price momentum
160
+ for period in [3, 5, 10, 20]:
161
+ df[f'momentum_{period}d'] = df['Close'] / df['Close'].shift(period) - 1
162
+
163
+ # Rate of change
164
+ for period in [5, 10]:
165
+ df[f'roc_{period}d'] = (df['Close'] - df['Close'].shift(period)) / df['Close'].shift(period)
166
+
167
+ return df
168
+
169
+ def create_position_features(df):
170
+ """Add price position features"""
171
+ #print("Adding position features...")
172
+
173
+ # Price position in recent range
174
+ for period in [10, 20, 50]:
175
+ df[f'high_{period}d'] = df['High'].rolling(period).max()
176
+ df[f'low_{period}d'] = df['Low'].rolling(period).min()
177
+ df[f'price_position_{period}'] = (df['Close'] - df[f'low_{period}d']) / (df[f'high_{period}d'] - df[f'low_{period}d'])
178
+
179
+ # Bollinger Band position (if BB exists)
180
+ if 'SMA20' in df.columns:
181
+ bb_std = df['Close'].rolling(20).std()
182
+ df['bb_upper'] = df['SMA20'] + (bb_std * 2)
183
+ df['bb_lower'] = df['SMA20'] - (bb_std * 2)
184
+ df['bb_position'] = (df['Close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])
185
+
186
+ return df
187
+
188
+ def create_rolling_stats(df):
189
+ """Add rolling statistical features"""
190
+ #print("Adding rolling statistics...")
191
+
192
+ # Rolling statistics of returns
193
+ for period in [5, 10]:
194
+ df[f'return_mean_{period}d'] = df['return_1d'].rolling(period).mean()
195
+ df[f'return_std_{period}d'] = df['return_1d'].rolling(period).std()
196
+ df[f'return_skew_{period}d'] = df['return_1d'].rolling(period).skew()
197
+ df[f'return_kurt_{period}d'] = df['return_1d'].rolling(period).kurt()
198
+
199
+ # Rolling statistics of RSI
200
+ if 'RSI14' in df.columns:
201
+ df['rsi_mean_5d'] = df['RSI14'].rolling(5).mean()
202
+ df['rsi_std_5d'] = df['RSI14'].rolling(5).std()
203
+
204
+ return df
205
+
206
+ # Process each stock separately and then concatenate for ML
207
+ all_ml_data = []
208
+
209
+ for ticker in symbols:
210
+ df = pd.DataFrame({
211
+ 'Open': data[f'Open_{ticker}'],
212
+ 'High': data[f'High_{ticker}'],
213
+ 'Low': data[f'Low_{ticker}'],
214
+ 'Close': data[f'Close_{ticker}'],
215
+ 'Volume': data[f'Volume_{ticker}']
216
+ })
217
+
218
+ df['SMA20'] = SMA(df['Close'], 20)
219
+ df['SMA50'] = SMA(df['Close'], 50)
220
+ df['EMA20'] = EMA(df['Close'], 20)
221
+ df['EMA50'] = EMA(df['Close'], 50)
222
+ df['RSI14'] = RSI(df['Close'], 14)
223
+ df['RSI20'] = RSI(df['Close'], 20)
224
+ df['MACD'], df['MACD_signal'], df['MACD_hist'] = MACD(df['Close'])
225
+ df = create_volatility_features(df)
226
+ df = create_enhanced_lag_features(df)
227
+ df = create_volume_features(df)
228
+ df = create_momentum_features(df)
229
+ df = create_position_features(df)
230
+
231
+ # Feature: SMA 20 above SMA 50 (bullish crossover)
232
+ df['SMA_crossover'] = (df['SMA20'] > df['SMA50']).astype(int)
233
+ # Feature: RSI oversold signal
234
+ df['RSI_oversold'] = (df['RSI14'] < 30).astype(int)
235
+ # Target: next-day up/down
236
+ df['next_close'] = df['Close'].shift(-1)
237
+ df['target'] = (df['next_close'] > df['Close']).astype(int)
238
+
239
+ df['ticker'] = ticker
240
+
241
+ # Drop rows with NaN (from indicator calculations)
242
+ df = df.dropna().copy()
243
+ all_ml_data.append(df)
244
+
245
+ # Concatenate all stocks
246
+ ml_data = pd.concat(all_ml_data)
247
+ ml_data.reset_index(inplace=True)
248
+
249
+ ml_data
250
+
251
+ ml_data.columns
252
+
253
+ for ticker in ml_data['ticker'].unique():
254
+ plt.figure(figsize=(20,12))
255
+ plt.plot(
256
+ ml_data[ml_data['ticker'] == ticker]['Date'],
257
+ ml_data[ml_data['ticker'] == ticker]['Close'],
258
+ label=f"{ticker}"
259
+ )
260
+ plt.title("Closing Price Over Time")
261
+ plt.xlabel("Date")
262
+ plt.ylabel("Close Price (USD)")
263
+ plt.legend(loc='upper left')
264
+
265
+ plt.show()
266
+
267
+ sample = ml_data[ml_data['ticker'] == 'RELIANCE.NS'].copy()
268
+ plt.figure(figsize=(14,7))
269
+ plt.plot(sample['Date'], sample['Close'], label='Close')
270
+ plt.plot(sample['Date'], sample['SMA20'], label='SMA20')
271
+ plt.plot(sample['Date'], sample['SMA50'], label='SMA50')
272
+ plt.title('RELIANCE: Close with SMA20 & SMA50')
273
+ plt.legend()
274
+ plt.show()
275
+
276
+ plt.figure(figsize=(14,4))
277
+ plt.plot(sample['Date'], sample['RSI14'], label='RSI14', color='green')
278
+ plt.axhline(70, linestyle='--', color='r')
279
+ plt.axhline(30, linestyle='--', color='b')
280
+ plt.title('RELIANCE: RSI14 Time Series')
281
+ plt.legend()
282
+ plt.show()
283
+
284
+ plt.figure(figsize=(14,4))
285
+ plt.plot(sample['Date'], sample['MACD'], label='MACD')
286
+ plt.plot(sample['Date'], sample['MACD_signal'], label='MACD Signal')
287
+ plt.title('RELIANCE: MACD & Signal')
288
+ plt.legend()
289
+ plt.show()
290
+
291
+ # Select features
292
+ features = [
293
+ 'Close', 'Volume', 'SMA20', 'SMA50', 'EMA20', 'EMA50',
294
+ 'RSI14', 'MACD', 'MACD_signal', 'MACD_hist',
295
+ 'SMA_crossover', 'RSI_oversold',
296
+ 'return_1d', 'volatility_5d', 'volatility_10d', 'volatility_20d',
297
+ 'volatility_30d', 'vol_ratio_5_20', 'vol_ratio_10_20', 'vol_rank_20',
298
+ 'vol_rank_50', 'return_lag_1', 'return_lag_2', 'return_lag_3',
299
+ 'return_lag_5', 'return_lag_10', 'rsi_lag_1', 'macd_lag_1', 'rsi_lag_2',
300
+ 'macd_lag_2', 'rsi_lag_3', 'macd_lag_3', 'volume_sma_10',
301
+ 'volume_sma_20', 'volume_sma_50', 'volume_ratio_10', 'volume_ratio_20',
302
+ 'volume_ratio_50', 'price_volume', 'pv_sma_5', 'volume_momentum_5',
303
+ 'momentum_3d', 'momentum_5d', 'momentum_10d', 'momentum_20d', 'roc_5d',
304
+ 'roc_10d', 'high_10d', 'low_10d', 'price_position_10', 'high_20d',
305
+ 'low_20d', 'price_position_20', 'high_50d', 'low_50d',
306
+ 'price_position_50', 'bb_upper', 'bb_lower', 'bb_position',
307
+ 'SMA_crossover', 'RSI_oversold', 'next_close'
308
+ ]
309
+ target = 'target'
310
+
311
+ # Standardize features (recommended for LR)
312
+ X = ml_data[features]
313
+ y = ml_data[target]
314
+
315
+ scaler = RobustScaler()
316
+ X_scaled = scaler.fit_transform(X)
317
+
318
+ sns.countplot(x='target', data=ml_data)
319
+ plt.title("Class Balance: Next-Day Up/Down Distribution")
320
+ plt.xlabel("0 = Down, 1 = Up")
321
+ plt.ylabel("Count")
322
+ plt.show()
323
+
324
+ # # Sort by date (if multi-stock, by ticker as well)
325
+ # ml_data = ml_data.sort_values(['ticker', 'Date'])
326
+
327
+ # # Chronological split: 80% train, 20% test
328
+ # split_idx = int(0.8 * len(ml_data))
329
+ # X_train, X_test = X_scaled[:split_idx], X_scaled[split_idx:]
330
+ # y_train, y_test = y[:split_idx], y[split_idx:]
331
+
332
+ from sklearn.model_selection import TimeSeriesSplit
333
+
334
+ tscv = TimeSeriesSplit(n_splits=5)
335
+
336
+ # Use only the last fold for final testing
337
+ for train_index, test_index in tscv.split(X_scaled):
338
+ pass # this will give you the last split
339
+
340
+ X_train, X_test = X_scaled[train_index], X_scaled[test_index]
341
+ y_train, y_test = y.iloc[train_index], y.iloc[test_index]
342
+
343
+ # Hyperparameter tuning
344
+ from sklearn.model_selection import GridSearchCV
345
+
346
+ param_grid = {
347
+ 'max_depth': [5, 7, 10, 15],
348
+ 'min_samples_split': [2, 5, 10],
349
+ 'min_samples_leaf': [1, 2, 4],
350
+ 'criterion': ['gini', 'entropy']
351
+ }
352
+
353
+ # Decision Tree Classifier
354
+ dt_model = GridSearchCV(DecisionTreeClassifier(random_state=42), param_grid, cv=tscv, n_jobs=-1)
355
+ dt_model.fit(X_train, y_train)
356
+ dt_preds = dt_model.predict(X_test)
357
+
358
+ # Logistic Regression
359
+ lr_model = LogisticRegression(random_state=42, max_iter=1000)
360
+ lr_model.fit(X_train, y_train)
361
+ lr_preds = lr_model.predict(X_test)
362
+
363
+ # Performance
364
+ def print_metrics(model_name, y_true, y_pred):
365
+ print(f"\n=== {model_name} ===")
366
+ print("Accuracy:", accuracy_score(y_true, y_pred))
367
+ print(classification_report(y_true, y_pred, target_names=['Down','Up']))
368
+
369
+ print_metrics("Decision Tree", y_test, dt_preds)
370
+ print_metrics("Logistic Regression", y_test, lr_preds)
371
+
372
+ # Decision Tree feature importances
373
+ importances = pd.Series(dt_model.best_index_, index=features)
374
+ importances = importances.sort_values(ascending=False)
375
+ print("\nTop Feature Importances (Decision Tree):")
376
+ print(importances)
377
+
378
+ plt.figure(figsize=(35,20))
379
+ corr = ml_data[[
380
+ 'Close', 'Volume', 'SMA20', 'SMA50', 'EMA20', 'EMA50',
381
+ 'RSI14', 'MACD', 'MACD_signal', 'MACD_hist',
382
+ 'SMA_crossover', 'RSI_oversold',
383
+ 'return_1d', 'volatility_5d', 'volatility_10d', 'volatility_20d',
384
+ 'volatility_30d', 'vol_ratio_5_20', 'vol_ratio_10_20', 'vol_rank_20',
385
+ 'vol_rank_50', 'return_lag_1', 'return_lag_2', 'return_lag_3',
386
+ 'return_lag_5', 'return_lag_10', 'rsi_lag_1', 'macd_lag_1', 'rsi_lag_2',
387
+ ]].corr()
388
+ sns.heatmap(corr, annot=True, cmap='coolwarm', center=0)
389
+ plt.title("Correlation Heatmap of Features")
390
+ plt.show()
391
+
392
+ from sklearn.metrics import confusion_matrix
393
+ import seaborn as sns
394
+
395
+ # Calculate the confusion matrix
396
+ cm = confusion_matrix(y_test, lr_preds)
397
+
398
+ # Display the confusion matrix using a heatmap
399
+ plt.figure(figsize=(8, 6))
400
+ sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['Down', 'Up'], yticklabels=['Down', 'Up'])
401
+ plt.xlabel('Predicted')
402
+ plt.ylabel('Actual')
403
+ plt.title('Confusion Matrix for LR model')
404
+ plt.show()
405
+
406
+ print("\n=======Confusion Matrix========\n")
407
+ print(cm)
408
+
409
+ from sklearn.metrics import precision_recall_curve, average_precision_score
410
+
411
+ # Get predicted probabilities for the positive class
412
+ y_scores = lr_model.predict_proba(X_test)[:, 1]
413
+
414
+ # Calculate precision and recall for different thresholds
415
+ precision, recall, _ = precision_recall_curve(y_test, y_scores)
416
+
417
+ # Calculate the Average Precision (AP) score
418
+ average_precision = average_precision_score(y_test, y_scores)
419
+
420
+ # Plot the Precision-Recall curve
421
+ plt.figure(figsize=(8, 6))
422
+ plt.plot(recall, precision, color='red', lw=2, label='Precision-Recall curve (AP = %0.2f)' % average_precision)
423
+ plt.xlabel('Recall')
424
+ plt.ylabel('Precision')
425
+ plt.title('Precision-Recall Curve')
426
+ plt.ylim([0.0, 1.05])
427
+ plt.xlim([0.0, 1.0])
428
+ plt.legend(loc="lower left")
429
+ plt.tight_layout()
430
+ plt.show()
431
+
432
+ print(f"\nAverage Precision (AP) for Logistic Regression: {average_precision:.4f}")
433
+
434
+ from sklearn.metrics import roc_curve, auc
435
+
436
+ # Calculate ROC curve
437
+ fpr, tpr, thresholds = roc_curve(y_test, lr_model.predict_proba(X_test)[:,1])
438
+ roc_auc = auc(fpr, tpr)
439
+
440
+ # Plot ROC curve
441
+ plt.figure(figsize=(8, 6))
442
+ plt.plot(fpr, tpr, color='red', lw=2, label='ROC curve (area = %0.2f)' % roc_auc)
443
+ plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
444
+ plt.xlim([0.0, 1.0])
445
+ plt.ylim([0.0, 1.05])
446
+ plt.xlabel('False Positive Rate')
447
+ plt.ylabel('True Positive Rate')
448
+ plt.title('Receiver Operating Characteristic (ROC) Curve')
449
+ plt.legend(loc="lower right")
450
+ plt.show()
451
+
452
+ print(f"\nAUC for Logistic Regression: {roc_auc:.4f}")
453
+
454
+ from sklearn.metrics import f1_score, classification_report
455
+
456
+ # Calculate F1 score
457
+ f1 = f1_score(y_test, lr_preds)
458
+ print(f"\nF1 Score for Logistic Regression: {f1:.4f}")
459
+
460
+ # Print classification report
461
+ print("\nClassification Report for Logistic Regression:")
462
+ print(classification_report(y_test, lr_preds, target_names=['Down', 'Up']))
463
+
464
+ # Calculate training accuracy for Logistic Regression
465
+ lr_train_accuracy = lr_model.score(X_train, y_train)
466
+ print(f"\nLogistic Regression Training Accuracy: {lr_train_accuracy:.4f}")
467
+ print(f"Logistic Regression Test Accuracy: {accuracy_score(y_test, lr_preds):.4f}")
468
+
469
+ import pickle
470
+
471
+ # Save the Logistic Regression model
472
+ filename = 'logistic_regression_model.pkl'
473
+ pickle.dump(lr_model, open(filename, 'wb'))
474
+
475
+ print(f"Logistic Regression model saved to {filename}")
476
+
477
+ import pickle
478
+
479
+ # Load the saved Logistic Regression model
480
+ filename = 'logistic_regression_model.pkl'
481
+ loaded_model = pickle.load(open(filename, 'rb'))
482
+
483
+ print(f"Logistic Regression model loaded from {filename}")
484
+
485
+ # Test the loaded model
486
+ loaded_preds = loaded_model.predict(X_test)
487
+
488
+ # Evaluate the loaded model's performance
489
+ print("\n=== Loaded Logistic Regression Model Performance ===")
490
+ print("\n Accuracy:", accuracy_score(y_test, loaded_preds)," \n")
491
+ print(classification_report(y_test, loaded_preds, target_names=['Down','Up']))
src/models/logistic_regression_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:919c661816bdbe075897c7f6497a39463fc75a28d24fd9d61698578c0489f1a5
3
+ size 1200
src/models/ml_model.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Placeholder for ml_model.py
src/models/scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:04a408395df58f2d61dabfe3131509fe00170012806d34248a095b1446910404
3
+ size 2254
src/requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Placeholder for requirements.txt
2
+
3
+ pandas
4
+ numpy
5
+ matplotlib
6
+ yfinance
7
+ streamlit
8
+
9
+ gspread
10
+ google-auth
11
+ google-auth-oauthlib
12
+ google-auth-httplib2
src/script.ps1 ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # create_project_structure.ps1
2
+
3
+ # Root directory
4
+ $root = "src"
5
+
6
+ # Define folders
7
+ $folders = @(
8
+ "$root\data\historical",
9
+ "$root\indicators",
10
+ "$root\models",
11
+ "$root\strategy",
12
+ "$root\utils"
13
+ )
14
+
15
+ # Define files
16
+ $files = @(
17
+ "$root\main.py",
18
+ "$root\config.py",
19
+ "$root\requirements.txt",
20
+ "$root\README.md",
21
+ "$root\indicators\sma.py",
22
+ "$root\indicators\ema.py",
23
+ "$root\indicators\rsi.py",
24
+ "$root\indicators\enhanced_features.py",
25
+ "$root\indicators\macd.py",
26
+ "$root\models\ml_model.py",
27
+ "$root\strategy\rule_based_strategy.py",
28
+ "$root\utils\data_loader.py",
29
+ "$root\utils\logger.py",
30
+ "$root\utils\backtester.py"
31
+ )
32
+
33
+ # Create folders
34
+ foreach ($folder in $folders) {
35
+ if (-Not (Test-Path -Path $folder)) {
36
+ New-Item -ItemType Directory -Path $folder | Out-Null
37
+ }
38
+ }
39
+
40
+ # Create files with placeholder content
41
+ foreach ($file in $files) {
42
+ if (-Not (Test-Path -Path $file)) {
43
+ New-Item -ItemType File -Path $file | Out-Null
44
+ Add-Content -Path $file -Value "# Placeholder for $([System.IO.Path]::GetFileName($file))"
45
+ }
46
+ }
47
+
48
+ Write-Host "Project structure created successfully under $root"
49
+ # End of script
50
+ # Save this script as create_project_structure.ps1 and run it in PowerShell to create the project structure.
51
+ # Ensure you have the necessary permissions to create directories and files in the specified location.
src/strategy/rule_based_strategy.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # strategy/rule_based_strategy.py
2
+
3
+ """
4
+ Generates buy/sell signals based on:
5
+ - Buy when RSI < 30 and SMA20 crosses above SMA50
6
+ - Sell when RSI > 70 and SMA20 crosses below SMA50
7
+
8
+ Returns:
9
+ pd.DataFrame: DataFrame with new 'Signal' column: 1 = Buy, -1 = Sell, 0 = Hold
10
+ """
11
+
12
+ import pandas as pd
13
+
14
+ def generate_signals_sma(df, rsi_col='RSI', sma_short_col='SMA20', sma_long_col='SMA50'):
15
+ df = df.copy()
16
+ df['SMA_Signal'] = 0
17
+
18
+ # Method 1: Use OR condition (either RSI or SMA crossover)
19
+ # Buy condition: RSI < 30 OR SMA20 crosses above SMA50
20
+ rsi_oversold = df[rsi_col] < 30
21
+ sma_bullish_cross = (df[sma_short_col].shift(1) < df[sma_long_col].shift(1)) & (df[sma_short_col] > df[sma_long_col])
22
+ buy_signal = rsi_oversold | sma_bullish_cross
23
+ df.loc[buy_signal, 'SMA_Signal'] = 1
24
+
25
+ # Sell condition: RSI > 70 OR SMA20 crosses below SMA50
26
+ rsi_overbought = df[rsi_col] > 70
27
+ sma_bearish_cross = (df[sma_short_col].shift(1) > df[sma_long_col].shift(1)) & (df[sma_short_col] < df[sma_long_col])
28
+ sell_signal = rsi_overbought | sma_bearish_cross
29
+ df.loc[sell_signal, 'SMA_Signal'] = -1
30
+
31
+ return df
32
+
33
+
34
+ def generate_signals_ema(df, rsi_col='RSI', ema_short_col='EMA20', ema_long_col='EMA50'):
35
+ df = df.copy()
36
+ df['EMA_Signal'] = 0
37
+
38
+ # Method 1: Use OR condition (either RSI or EMA crossover)
39
+ # Buy condition: RSI < 30 OR EMA20 crosses above EMA50
40
+ rsi_oversold = df[rsi_col] < 30
41
+ ema_bullish_cross = (df[ema_short_col].shift(1) < df[ema_long_col].shift(1)) & (df[ema_short_col] > df[ema_long_col])
42
+ buy_signal = rsi_oversold | ema_bullish_cross
43
+ df.loc[buy_signal, 'EMA_Signal'] = 1
44
+
45
+ # Sell condition: RSI > 70 OR EMA20 crosses below EMA50
46
+ rsi_overbought = df[rsi_col] > 70
47
+ ema_bearish_cross = (df[ema_short_col].shift(1) > df[ema_long_col].shift(1)) & (df[ema_short_col] < df[ema_long_col])
48
+ sell_signal = rsi_overbought | ema_bearish_cross
49
+ df.loc[sell_signal, 'EMA_Signal'] = -1
50
+
51
+ return df
src/streamlit_app.py CHANGED
@@ -1,40 +1,808 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.graph_objects as go
5
+ import plotly.express as px
6
+ from plotly.subplots import make_subplots
7
+ import yfinance as yf
8
+ import pickle
9
+ from datetime import datetime, timedelta
10
+ import warnings
11
+ from curl_cffi import requests
12
+ session = requests.Session(impersonate="chrome")
13
+
14
+ from indicators.rsi import rsi
15
+ from indicators.sma import sma
16
+ from indicators.ema import ema
17
+ from indicators.macd import macd
18
+
19
+ from strategy.rule_based_strategy import generate_signals_sma, generate_signals_ema
20
+ from utils.backtester import backtest_signals
21
+
22
+ from indicators.enhanced_features import (
23
+ create_volatility_features, create_enhanced_lag_features,
24
+ create_volume_features, create_momentum_features, create_position_features
25
+ )
26
+
27
+ # Suppress warnings
28
+ warnings.filterwarnings('ignore')
29
+
30
+ # Page config
31
+ st.set_page_config(
32
+ page_title="Complete Stock Trading & Prediction Platform",
33
+ page_icon="📈",
34
+ layout="wide"
35
+ )
36
+
37
+ # Stock symbols
38
+ STOCK_SYMBOLS = [
39
+ 'ADANIENT.NS', 'ADANIPORTS.NS', 'APOLLOHOSP.NS', 'ASIANPAINT.NS',
40
+ 'AXISBANK.NS', 'BAJAJ-AUTO.NS', 'BAJFINANCE.NS', 'BAJAJFINSV.NS',
41
+ 'BEL.NS', 'BHARTIARTL.NS', 'CIPLA.NS', 'COALINDIA.NS', 'DRREDDY.NS',
42
+ 'EICHERMOT.NS', 'GRASIM.NS', 'HCLTECH.NS', 'HDFCBANK.NS', 'HDFCLIFE.NS',
43
+ 'HEROMOTOCO.NS', 'HINDALCO.NS', 'HINDUNILVR.NS', 'ICICIBANK.NS',
44
+ 'INDUSINDBK.NS', 'INFY.NS', 'ITC.NS', 'JIOFIN.NS', 'JSWSTEEL.NS',
45
+ 'KOTAKBANK.NS', 'LT.NS', 'M&M.NS', 'MARUTI.NS', 'NESTLEIND.NS',
46
+ 'NTPC.NS', 'ONGC.NS', 'POWERGRID.NS', 'RELIANCE.NS', 'SBILIFE.NS',
47
+ 'SHRIRAMFIN.NS', 'SBIN.NS', 'SUNPHARMA.NS', 'TATACONSUM.NS', 'TCS.NS',
48
+ 'TATAMOTORS.NS', 'TATASTEEL.NS', 'TECHM.NS', 'TITAN.NS', 'TRENT.NS',
49
+ 'ULTRACEMCO.NS', 'WIPRO.NS', 'ETERNAL.NS'
50
+ ]
51
+
52
+ # Feature list for ML model
53
+ FEATURES = [
54
+ 'Close', 'Volume', 'SMA20', 'SMA50', 'EMA20', 'EMA50',
55
+ 'RSI14', 'MACD', 'MACD_signal', 'MACD_hist',
56
+ 'SMA_crossover', 'RSI_oversold',
57
+ 'return_1d', 'volatility_5d', 'volatility_10d', 'volatility_20d',
58
+ 'volatility_30d', 'vol_ratio_5_20', 'vol_ratio_10_20', 'vol_rank_20',
59
+ 'vol_rank_50', 'return_lag_1', 'return_lag_2', 'return_lag_3',
60
+ 'return_lag_5', 'return_lag_10', 'rsi_lag_1', 'macd_lag_1', 'rsi_lag_2',
61
+ 'macd_lag_2', 'rsi_lag_3', 'macd_lag_3', 'volume_sma_10',
62
+ 'volume_sma_20', 'volume_sma_50', 'volume_ratio_10', 'volume_ratio_20',
63
+ 'volume_ratio_50', 'price_volume', 'pv_sma_5', 'volume_momentum_5',
64
+ 'momentum_3d', 'momentum_5d', 'momentum_10d', 'momentum_20d', 'roc_5d',
65
+ 'roc_10d', 'high_10d', 'low_10d', 'price_position_10', 'high_20d',
66
+ 'low_20d', 'price_position_20', 'high_50d', 'low_50d',
67
+ 'price_position_50', 'bb_upper', 'bb_lower', 'bb_position', 'target'
68
+ ]
69
+
70
+ # ========================= SHARED FUNCTIONS =========================
71
+
72
+ @st.cache_data
73
+ def load_stock_data(symbol, start_date, end_date):
74
+ """Load stock data from Yahoo Finance"""
75
+ try:
76
+ data = yf.download(symbol, start=start_date, end=end_date, session=session)
77
+ # Flatten the MultiIndex columns
78
+ if data.columns.nlevels > 1:
79
+ data.columns = [col[0] for col in data.columns]
80
+ return data
81
+ except Exception as e:
82
+ st.error(f"Error loading data: {e}")
83
+ return None
84
+
85
+ def process_stock_data(df, short_period, long_period, rsi_period):
86
+ """Process stock data to create all features"""
87
+ df = df.copy()
88
+
89
+ # Basic technical indicators
90
+ df['SMA20'] = sma(df, short_period)
91
+ df['SMA50'] = sma(df, long_period)
92
+ df['EMA20'] = ema(df, short_period)
93
+ df['EMA50'] = ema(df, long_period)
94
+ df['RSI14'] = rsi(df, rsi_period)
95
+ df['RSI20'] = rsi(df, rsi_period + 6)
96
+ df['MACD'], df['MACD_signal'], df['MACD_hist'] = macd(df)
97
+
98
+ # Bollinger Bands
99
+ df['Upper_Band'] = df['SMA20'] + 2 * df['Close'].rolling(window=20).std()
100
+ df['Lower_Band'] = df['SMA20'] - 2 * df['Close'].rolling(window=20).std()
101
+
102
+ # Create feature sets
103
+ df = create_volatility_features(df)
104
+ df = create_enhanced_lag_features(df)
105
+ df = create_volume_features(df)
106
+ df = create_momentum_features(df)
107
+ df = create_position_features(df)
108
+
109
+ # Additional features
110
+ df['SMA_crossover'] = (df['SMA20'] > df['SMA50']).astype(int)
111
+ df['RSI_oversold'] = (df['RSI14'] < 30).astype(int)
112
+
113
+ # Target: next-day up/down
114
+ df['next_close'] = df['Close'].shift(-1)
115
+ df['target'] = (df['next_close'] > df['Close']).astype(int)
116
+
117
+ return df
118
+
119
+ # ========================= MAIN APPLICATION =========================
120
+
121
+ # Main navigation
122
+ st.title("📈 Stock Trading & Prediction Platform")
123
+
124
+ # Navigation tabs
125
+ tab1, tab2 = st.tabs(["🔮 Price Prediction", "📊 Trading Dashboard"])
126
+
127
+ # ========================= SIDEBAR CONFIGURATION =========================
128
+
129
+ st.sidebar.header("📊 Configuration")
130
+
131
+ # Common inputs
132
+ selected_stock = st.sidebar.selectbox("Select Stock Symbol", STOCK_SYMBOLS, index=35)
133
+ start_date = st.sidebar.date_input("Start Date", value=datetime(2020, 1, 1))
134
+ end_date = st.sidebar.date_input("End Date", value=datetime.now())
135
+
136
+ st.sidebar.subheader("📈 Technical Indicators")
137
+ rsi_period = st.sidebar.slider("RSI Period", min_value=5, max_value=30, value=14, step=1)
138
+ short_period = st.sidebar.slider("Short-term Period", min_value=5, max_value=50, value=20, step=1)
139
+ long_period = st.sidebar.slider("Long-term Period", min_value=50, max_value=200, value=50, step=1)
140
+
141
+ # Strategy selection (for trading dashboard)
142
+ strategy_type = st.sidebar.selectbox("Strategy Type", ["SMA-based", "EMA-based", "Both"])
143
+
144
+ st.sidebar.subheader("💰 Backtesting Parameters")
145
+ initial_cash = st.sidebar.number_input("Initial Capital (₹)", min_value=10000, value=100000, step=10000)
146
+ transaction_cost = st.sidebar.slider("Transaction Cost (%)", 0.0, 1.0, 0.1, step=0.05) / 100
147
+ stop_loss = st.sidebar.slider("Stop Loss (%)", 0.0, 20.0, 5.0, step=1.0) / 100
148
+ take_profit = st.sidebar.slider("Take Profit (%)", 0.0, 50.0, 15.0, step=5.0) / 100
149
+ use_risk_mgmt = st.sidebar.checkbox("Enable Risk Management", value=True)
150
+
151
+ # ========================= PRICE PREDICTION TAB =========================
152
+
153
+ with tab1:
154
+ st.header(f"🔮 Price Prediction for {selected_stock}")
155
+
156
+ with st.spinner("Loading stock data..."):
157
+ stock_data = load_stock_data(selected_stock, start_date, end_date)
158
+
159
+ if stock_data is not None and not stock_data.empty:
160
+ # Display sample data
161
+ st.subheader("📊 Latest Stock Data")
162
+ st.dataframe(stock_data.tail(10), use_container_width=True)
163
+
164
+ # Process the data
165
+ processed_data = process_stock_data(stock_data, short_period, long_period, rsi_period)
166
+ processed_data = processed_data.dropna()
167
+
168
+ if len(processed_data) > 0:
169
+ # Get the latest row for prediction
170
+ latest_data = processed_data.iloc[-1]
171
+
172
+ # Display current stock info
173
+ col1, col2, col3, col4 = st.columns(4)
174
+ with col1:
175
+ st.metric("Current Price", f"₹{latest_data['Close']:.2f}")
176
+ with col2:
177
+ daily_change = ((latest_data['Close'] - processed_data.iloc[-2]['Close']) / processed_data.iloc[-2]['Close']) * 100
178
+ st.metric("Daily Change", f"{daily_change:.2f}%")
179
+ with col3:
180
+ st.metric("Volume", f"{latest_data['Volume']:,.0f}")
181
+ with col4:
182
+ st.metric("RSI14", f"{latest_data['RSI14']:.2f}")
183
+
184
+
185
+ model = pickle.load(open('models/logistic_regression_model.pkl', 'rb'))
186
+ scaler = pickle.load(open('models/scaler.pkl', 'rb'))
187
+
188
+ # Create feature vector
189
+ feature_vector = latest_data[FEATURES].values.reshape(1, -1)
190
+ feature_vector_scaled = scaler.transform(feature_vector)
191
+
192
+ # Make prediction
193
+ prediction = model.predict(feature_vector_scaled)[0]
194
+ probability = model.predict_proba(feature_vector_scaled)[0].max()
195
+
196
+
197
+ # Display prediction
198
+ st.header("🔮 Prediction Results")
199
+ col1, col2 = st.columns(2)
200
+
201
+ with col1:
202
+ if prediction == 1:
203
+ st.success("📈 **PREDICTION: UP**")
204
+ st.write(f"The model predicts the stock will go **UP** tomorrow with {probability:.1%} confidence.")
205
+ else:
206
+ st.error("📉 **PREDICTION: DOWN**")
207
+ st.write(f"The model predicts the stock will go **DOWN** tomorrow with {probability:.1%} confidence.")
208
+
209
+ with col2:
210
+ # Confidence gauge
211
+ fig_gauge = go.Figure(go.Indicator(
212
+ mode = "gauge+number",
213
+ value = probability * 100,
214
+ domain = {'x': [0, 1], 'y': [0, 1]},
215
+ title = {'text': "Confidence %"},
216
+ gauge = {
217
+ 'axis': {'range': [None, 100]},
218
+ 'bar': {'color': "darkgreen" if prediction == 1 else "darkred"},
219
+ 'steps': [
220
+ {'range': [0, 50], 'color': "lightgray"},
221
+ {'range': [50, 80], 'color': "yellow"},
222
+ {'range': [80, 100], 'color': "lightgreen"}
223
+ ],
224
+ 'threshold': {
225
+ 'line': {'color': "red", 'width': 4},
226
+ 'thickness': 0.75,
227
+ 'value': 90
228
+ }
229
+ }
230
+ ))
231
+ fig_gauge.update_layout(height=300)
232
+ st.plotly_chart(fig_gauge, use_container_width=True)
233
+
234
+ # Technical Analysis Charts
235
+ st.header("📈 Technical Analysis")
236
+
237
+ # Price charts
238
+ col1, col2 = st.columns(2)
239
+
240
+ with col1:
241
+ # SMA Chart
242
+ fig_sma = go.Figure()
243
+ fig_sma.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['Close'][-60:],
244
+ mode='lines', name='Close Price', line=dict(color='blue', width=2)))
245
+ fig_sma.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['SMA20'][-60:],
246
+ mode='lines', name='SMA20', line=dict(color='orange', width=1)))
247
+ fig_sma.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['SMA50'][-60:],
248
+ mode='lines', name='SMA50', line=dict(color='red', width=1)))
249
+ fig_sma.update_layout(title=f"{selected_stock} - Simple Moving Averages", height=400)
250
+ st.plotly_chart(fig_sma, use_container_width=True)
251
+
252
+ with col2:
253
+ # EMA Chart
254
+ fig_ema = go.Figure()
255
+ fig_ema.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['Close'][-60:],
256
+ mode='lines', name='Close Price', line=dict(color='blue', width=2)))
257
+ fig_ema.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['EMA20'][-60:],
258
+ mode='lines', name='EMA20', line=dict(color='orange', width=1)))
259
+ fig_ema.add_trace(go.Scatter(x=processed_data.index[-60:], y=processed_data['EMA50'][-60:],
260
+ mode='lines', name='EMA50', line=dict(color='red', width=1)))
261
+ fig_ema.update_layout(title=f"{selected_stock} - Exponential Moving Averages", height=400)
262
+ st.plotly_chart(fig_ema, use_container_width=True)
263
+
264
+ # RSI and MACD
265
+ col1, col2 = st.columns(2)
266
+
267
+ with col1:
268
+ fig_rsi = go.Figure()
269
+ fig_rsi.add_trace(go.Scatter(x=processed_data.index[-30:], y=processed_data['RSI14'][-30:],
270
+ mode='lines', name='RSI14', line=dict(color='purple')))
271
+ fig_rsi.add_hline(y=70, line_dash="dash", line_color="red", annotation_text="Overbought")
272
+ fig_rsi.add_hline(y=30, line_dash="dash", line_color="green", annotation_text="Oversold")
273
+ fig_rsi.update_layout(title=f"RSI ({rsi_period}-day)", height=300)
274
+ st.plotly_chart(fig_rsi, use_container_width=True)
275
+
276
+ with col2:
277
+ fig_macd = go.Figure()
278
+ fig_macd.add_trace(go.Scatter(x=processed_data.index[-30:], y=processed_data['MACD'][-30:],
279
+ mode='lines', name='MACD', line=dict(color='blue')))
280
+ fig_macd.add_trace(go.Scatter(x=processed_data.index[-30:], y=processed_data['MACD_signal'][-30:],
281
+ mode='lines', name='Signal', line=dict(color='red')))
282
+ fig_macd.update_layout(title="MACD", height=300)
283
+ st.plotly_chart(fig_macd, use_container_width=True)
284
+
285
+ else:
286
+ st.error("Not enough data to make a prediction.")
287
+ else:
288
+ st.error("Unable to load stock data.")
289
+
290
+ # ========================= TRADING DASHBOARD TAB =========================
291
+
292
+ with tab2:
293
+ st.header("📊 Trading Dashboard")
294
+
295
+ with st.spinner(f'Loading data for {selected_stock}...'):
296
+ df = load_stock_data(selected_stock, start_date, end_date)
297
+
298
+ if df is not None and not df.empty:
299
+ st.subheader(f"📊 Stock Data for {selected_stock}")
300
+ st.write(f"**Date Range:** {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}")
301
+ st.write(f"**Total Records:** {len(df)} days")
302
+
303
+ # Process data for trading
304
+ df = process_stock_data(df, short_period, long_period, rsi_period)
305
+ df = df.dropna()
306
+
307
+ # Generate trading signals
308
+ if strategy_type in ["SMA-based", "Both"]:
309
+ df = generate_signals_sma(df, rsi_col='RSI14', sma_short_col='SMA20', sma_long_col='SMA50')
310
+
311
+ if strategy_type in ["EMA-based", "Both"]:
312
+ df = generate_signals_ema(df, rsi_col='RSI14', ema_short_col='EMA20', ema_long_col='EMA50')
313
+
314
+ # Initialize variables to avoid NameError
315
+ results = None
316
+ metrics = None
317
+ signal_col = None
318
+ strategy_name = None
319
+
320
+ # Backtesting section
321
+ st.header("🔍 Backtesting Results")
322
+
323
+ if strategy_type == "Both":
324
+ tab_sma, tab_ema = st.tabs(["SMA Strategy", "EMA Strategy"])
325
+
326
+ with tab_sma:
327
+ st.subheader("📊 SMA Strategy Results")
328
+ sma_results, sma_metrics = backtest_signals(
329
+ df, signal_col='SMA_Signal', price_col='Close',
330
+ initial_cash=initial_cash, transaction_cost=transaction_cost if use_risk_mgmt else 0
331
+ )
332
+
333
+ # Set variables for common sections
334
+ results = sma_results
335
+ metrics = sma_metrics
336
+ signal_col = 'SMA_Signal'
337
+ strategy_name = 'SMA'
338
+
339
+ # Display metrics
340
+ col1, col2, col3, col4 = st.columns(4)
341
+ with col1:
342
+ st.metric("💰 Final Value", sma_metrics['Final Portfolio Value'])
343
+ st.metric("📈 Total Return", sma_metrics['Total Return'])
344
+ with col2:
345
+ st.metric("🎯 Buy & Hold Return", sma_metrics['Buy & Hold Return'])
346
+ st.metric("📊 Total Trades", sma_metrics['Total Trades'])
347
+ with col3:
348
+ st.metric("🏆 Win Rate", sma_metrics['Win Rate'])
349
+ st.metric("⚡ Sharpe Ratio", sma_metrics['Sharpe Ratio'])
350
+ with col4:
351
+ st.metric("📉 Max Drawdown", sma_metrics['Maximum Drawdown'])
352
+ st.metric("🔥 Volatility", sma_metrics['Volatility (Annual)'])
353
+
354
+ # SMA Price Chart with Signals
355
+ fig_sma_signals = go.Figure()
356
+ fig_sma_signals.add_trace(go.Scatter(x=df.index, y=df['Close'], mode='lines',
357
+ name='Close Price', line=dict(color='purple', width=2)))
358
+ fig_sma_signals.add_trace(go.Scatter(x=df.index, y=df['SMA20'], mode='lines',
359
+ name='SMA20', line=dict(color='blue', width=2)))
360
+ fig_sma_signals.add_trace(go.Scatter(x=df.index, y=df['SMA50'], mode='lines',
361
+ name='SMA50', line=dict(color='red', width=2)))
362
+
363
+ # Add buy/sell signals
364
+ buy_signals = df[df['SMA_Signal'] == 1]
365
+ sell_signals = df[df['SMA_Signal'] == -1]
366
+
367
+ if not buy_signals.empty:
368
+ fig_sma_signals.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['Close'],
369
+ mode='markers', name='Buy Signal',
370
+ marker=dict(symbol='triangle-up', size=12, color='green')))
371
+
372
+ if not sell_signals.empty:
373
+ fig_sma_signals.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['Close'],
374
+ mode='markers', name='Sell Signal',
375
+ marker=dict(symbol='triangle-down', size=12, color='red')))
376
+
377
+ fig_sma_signals.update_layout(title=f"{selected_stock} - SMA Strategy Signals", height=500)
378
+ st.plotly_chart(fig_sma_signals, use_container_width=True)
379
+
380
+ # Portfolio Performance
381
+ buy_hold_value = initial_cash * (df['Close'] / df['Close'].iloc[0])
382
+ fig_perf_sma = go.Figure()
383
+ fig_perf_sma.add_trace(go.Scatter(x=sma_results.index, y=sma_results['Total'],
384
+ mode='lines', name='SMA Strategy', line=dict(color='green', width=3)))
385
+ fig_perf_sma.add_trace(go.Scatter(x=df.index, y=buy_hold_value,
386
+ mode='lines', name='Buy & Hold', line=dict(color='blue', width=2, dash='dash')))
387
+ fig_perf_sma.update_layout(title="SMA Strategy vs Buy & Hold Performance", height=400)
388
+ st.plotly_chart(fig_perf_sma, use_container_width=True)
389
+
390
+ with tab_ema:
391
+ st.subheader("📊 EMA Strategy Results")
392
+ ema_results, ema_metrics = backtest_signals(
393
+ df, signal_col='EMA_Signal', price_col='Close',
394
+ initial_cash=initial_cash, transaction_cost=transaction_cost if use_risk_mgmt else 0
395
+ )
396
+
397
+ # Set variables for common sections
398
+ results = ema_results
399
+ metrics = ema_metrics
400
+ signal_col = 'EMA_Signal'
401
+ strategy_name = 'EMA'
402
+
403
+ # Display metrics
404
+ col1, col2, col3, col4 = st.columns(4)
405
+ with col1:
406
+ st.metric("💰 Final Value", ema_metrics['Final Portfolio Value'])
407
+ st.metric("📈 Total Return", ema_metrics['Total Return'])
408
+ with col2:
409
+ st.metric("🎯 Buy & Hold Return", ema_metrics['Buy & Hold Return'])
410
+ st.metric("📊 Total Trades", ema_metrics['Total Trades'])
411
+ with col3:
412
+ st.metric("🏆 Win Rate", ema_metrics['Win Rate'])
413
+ st.metric("⚡ Sharpe Ratio", ema_metrics['Sharpe Ratio'])
414
+ with col4:
415
+ st.metric("📉 Max Drawdown", ema_metrics['Maximum Drawdown'])
416
+ st.metric("🔥 Volatility", ema_metrics['Volatility (Annual)'])
417
+
418
+ # EMA Price Chart with Signals
419
+ fig_ema_signals = go.Figure()
420
+ fig_ema_signals.add_trace(go.Scatter(x=df.index, y=df['Close'], mode='lines',
421
+ name='Close Price', line=dict(color='purple', width=2)))
422
+ fig_ema_signals.add_trace(go.Scatter(x=df.index, y=df['EMA20'], mode='lines',
423
+ name='EMA20', line=dict(color='blue', width=2)))
424
+ fig_ema_signals.add_trace(go.Scatter(x=df.index, y=df['EMA50'], mode='lines',
425
+ name='EMA50', line=dict(color='red', width=2)))
426
+
427
+ # Add buy/sell signals
428
+ buy_signals = df[df['EMA_Signal'] == 1]
429
+ sell_signals = df[df['EMA_Signal'] == -1]
430
+
431
+ if not buy_signals.empty:
432
+ fig_ema_signals.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['Close'],
433
+ mode='markers', name='Buy Signal',
434
+ marker=dict(symbol='triangle-up', size=12, color='green')))
435
+
436
+ if not sell_signals.empty:
437
+ fig_ema_signals.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['Close'],
438
+ mode='markers', name='Sell Signal',
439
+ marker=dict(symbol='triangle-down', size=12, color='red')))
440
+
441
+ fig_ema_signals.update_layout(title=f"{selected_stock} - EMA Strategy Signals", height=500)
442
+ st.plotly_chart(fig_ema_signals, use_container_width=True)
443
+
444
+ # Portfolio Performance
445
+ buy_hold_value = initial_cash * (df['Close'] / df['Close'].iloc[0])
446
+ fig_perf_ema = go.Figure()
447
+ fig_perf_ema.add_trace(go.Scatter(x=ema_results.index, y=ema_results['Total'],
448
+ mode='lines', name='EMA Strategy', line=dict(color='green', width=3)))
449
+ fig_perf_ema.add_trace(go.Scatter(x=df.index, y=buy_hold_value,
450
+ mode='lines', name='Buy & Hold', line=dict(color='blue', width=2, dash='dash')))
451
+ fig_perf_ema.update_layout(title="EMA Strategy vs Buy & Hold Performance", height=400)
452
+ st.plotly_chart(fig_perf_ema, use_container_width=True)
453
+
454
+ else:
455
+ # Single strategy
456
+ signal_col = 'SMA_Signal' if strategy_type == "SMA-based" else 'EMA_Signal'
457
+ strategy_name = strategy_type.split('-')[0]
458
+
459
+ results, metrics = backtest_signals(
460
+ df, signal_col=signal_col, price_col='Close',
461
+ initial_cash=initial_cash, transaction_cost=transaction_cost if use_risk_mgmt else 0
462
+ )
463
+
464
+ # Display metrics
465
+ col1, col2, col3, col4 = st.columns(4)
466
+ with col1:
467
+ st.metric("💰 Final Value", metrics['Final Portfolio Value'])
468
+ st.metric("📈 Total Return", metrics['Total Return'])
469
+ with col2:
470
+ st.metric("🎯 Buy & Hold Return", metrics['Buy & Hold Return'])
471
+ st.metric("📊 Total Trades", metrics['Total Trades'])
472
+ with col3:
473
+ st.metric("🏆 Win Rate", metrics['Win Rate'])
474
+ st.metric("⚡ Sharpe Ratio", metrics['Sharpe Ratio'])
475
+ with col4:
476
+ st.metric("📉 Max Drawdown", metrics['Maximum Drawdown'])
477
+ st.metric("🔥 Volatility", metrics['Volatility (Annual)'])
478
+
479
+ # Price Chart with Signals
480
+ fig_signals = go.Figure()
481
+ fig_signals.add_trace(go.Scatter(x=df.index, y=df['Close'], mode='lines',
482
+ name='Close Price', line=dict(color='purple', width=2)))
483
+
484
+ if strategy_name == 'SMA':
485
+ fig_signals.add_trace(go.Scatter(x=df.index, y=df['SMA20'], mode='lines',
486
+ name='SMA20', line=dict(color='blue', width=2)))
487
+ fig_signals.add_trace(go.Scatter(x=df.index, y=df['SMA50'], mode='lines',
488
+ name='SMA50', line=dict(color='red', width=2)))
489
+ else:
490
+ fig_signals.add_trace(go.Scatter(x=df.index, y=df['EMA20'], mode='lines',
491
+ name='EMA20', line=dict(color='blue', width=2)))
492
+ fig_signals.add_trace(go.Scatter(x=df.index, y=df['EMA50'], mode='lines',
493
+ name='EMA50', line=dict(color='red', width=2)))
494
+
495
+ # Add buy/sell signals
496
+ buy_signals = df[df[signal_col] == 1]
497
+ sell_signals = df[df[signal_col] == -1]
498
+
499
+ if not buy_signals.empty:
500
+ fig_signals.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['Close'],
501
+ mode='markers', name='Buy Signal',
502
+ marker=dict(symbol='triangle-up', size=12, color='green')))
503
+
504
+ if not sell_signals.empty:
505
+ fig_signals.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['Close'],
506
+ mode='markers', name='Sell Signal',
507
+ marker=dict(symbol='triangle-down', size=12, color='red')))
508
+
509
+ fig_signals.update_layout(title=f"{selected_stock} - {strategy_name} Strategy Signals", height=500)
510
+ st.plotly_chart(fig_signals, use_container_width=True)
511
+
512
+ # Portfolio Performance
513
+ buy_hold_value = initial_cash * (df['Close'] / df['Close'].iloc[0])
514
+ fig_perf = go.Figure()
515
+ fig_perf.add_trace(go.Scatter(x=results.index, y=results['Total'],
516
+ mode='lines', name=f'{strategy_name} Strategy', line=dict(color='green', width=3)))
517
+ fig_perf.add_trace(go.Scatter(x=df.index, y=buy_hold_value,
518
+ mode='lines', name='Buy & Hold', line=dict(color='blue', width=2, dash='dash')))
519
+ fig_perf.update_layout(title=f"{strategy_name} Strategy vs Buy & Hold Performance", height=400)
520
+ st.plotly_chart(fig_perf, use_container_width=True)
521
+
522
+ # Additional Technical Analysis Charts (only show if we have results)
523
+ if results is not None:
524
+ st.header("📈 Additional Technical Analysis")
525
+
526
+ col1, col2 = st.columns(2)
527
+
528
+ with col1:
529
+ # RSI Chart
530
+ fig_rsi = go.Figure()
531
+ fig_rsi.add_trace(go.Scatter(x=df.index, y=df['RSI14'], mode='lines',
532
+ name='RSI14', line=dict(color='purple', width=2)))
533
+
534
+ # Add buy/sell signals on RSI if available
535
+ if not buy_signals.empty:
536
+ fig_rsi.add_trace(go.Scatter(x=buy_signals.index, y=buy_signals['RSI14'],
537
+ mode='markers', name='Buy Signal',
538
+ marker=dict(symbol='triangle-up', size=10, color='green'),
539
+ showlegend=False))
540
+
541
+ if not sell_signals.empty:
542
+ fig_rsi.add_trace(go.Scatter(x=sell_signals.index, y=sell_signals['RSI14'],
543
+ mode='markers', name='Sell Signal',
544
+ marker=dict(symbol='triangle-down', size=10, color='red'),
545
+ showlegend=False))
546
+
547
+ fig_rsi.add_hline(y=70, line_dash="dash", line_color="red", annotation_text="Overbought (70)")
548
+ fig_rsi.add_hline(y=30, line_dash="dash", line_color="green", annotation_text="Oversold (30)")
549
+ fig_rsi.add_hline(y=50, line_dash="solid", line_color="gray", annotation_text="Midline (50)", opacity=0.5)
550
+
551
+ fig_rsi.update_layout(title="RSI with Trading Signals", yaxis=dict(range=[0, 100]), height=400)
552
+ st.plotly_chart(fig_rsi, use_container_width=True)
553
+
554
+ with col2:
555
+ # MACD Chart
556
+ fig_macd = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, row_heights=[0.7, 0.3])
557
+
558
+ # MACD line
559
+ fig_macd.add_trace(go.Scatter(x=df.index, y=df['MACD'], mode='lines', name='MACD',
560
+ line=dict(color='blue', width=2)), row=1, col=1)
561
+
562
+ # Signal line
563
+ fig_macd.add_trace(go.Scatter(x=df.index, y=df['MACD_signal'], mode='lines', name='Signal Line',
564
+ line=dict(color='orange', width=2)), row=1, col=1)
565
+
566
+ # Zero line
567
+ fig_macd.add_hline(y=0, line_dash="solid", line_color="pink", opacity=0.5, row=1, col=1)
568
+
569
+ # MACD histogram
570
+ colors = ['green' if val >= 0 else 'red' for val in df['MACD_hist']]
571
+ fig_macd.add_trace(go.Bar(x=df.index, y=df['MACD_hist'], name='MACD Histogram',
572
+ marker_color=colors, opacity=0.6), row=2, col=1)
573
+
574
+ fig_macd.update_layout(title="MACD Indicator", height=400, showlegend=True)
575
+ fig_macd.update_xaxes(title_text="Date", row=2, col=1)
576
+ fig_macd.update_yaxes(title_text="MACD Value", row=1, col=1)
577
+ fig_macd.update_yaxes(title_text="Histogram", row=2, col=1)
578
+
579
+ st.plotly_chart(fig_macd, use_container_width=True)
580
+
581
+ # Bollinger Bands
582
+ st.subheader("📈 Bollinger Bands")
583
+ fig_bb = go.Figure()
584
+
585
+ fig_bb.add_trace(go.Scatter(x=df.index, y=df['Close'], mode='lines', name='Close Price',
586
+ line=dict(color='purple', width=2)))
587
+ fig_bb.add_trace(go.Scatter(x=df.index, y=df['SMA20'], mode='lines', name='20-day SMA',
588
+ line=dict(color='blue', width=1.5)))
589
+ fig_bb.add_trace(go.Scatter(x=df.index, y=df['Upper_Band'], mode='lines', name='Upper Band',
590
+ line=dict(color='red', dash='dash', width=1.5)))
591
+ fig_bb.add_trace(go.Scatter(x=df.index, y=df['Lower_Band'], mode='lines', name='Lower Band',
592
+ line=dict(color='green', dash='dash', width=1.5),
593
+ fill='tonexty', fillcolor='rgba(128,128,128,0.2)'))
594
+
595
+ fig_bb.update_layout(title="Bollinger Bands", height=500)
596
+ st.plotly_chart(fig_bb, use_container_width=True)
597
+
598
+ # Drawdown Analysis
599
+ st.subheader("📉 Drawdown Analysis")
600
+
601
+ # Calculate drawdown
602
+ returns = results['Total'].pct_change().fillna(0)
603
+ cumulative = (1 + returns).cumprod()
604
+ running_max = cumulative.expanding().max()
605
+ drawdown = (cumulative - running_max) / running_max
606
+
607
+ fig_dd = go.Figure()
608
+
609
+ fig_dd.add_trace(go.Scatter(
610
+ x=df.index,
611
+ y=drawdown * 100,
612
+ mode='lines',
613
+ name='Drawdown',
614
+ fill='tozeroy',
615
+ fillcolor='rgba(255,0,0,0.3)',
616
+ line=dict(color='red', width=1),
617
+ hovertemplate='<b>Drawdown</b>: %{y:.1f}%<extra></extra>'
618
+ ))
619
+
620
+ fig_dd.update_layout(
621
+ title="Portfolio Drawdown Over Time",
622
+ xaxis_title="Date",
623
+ yaxis_title="Drawdown (%)",
624
+ height=400,
625
+ template='plotly_white'
626
+ )
627
+
628
+ st.plotly_chart(fig_dd, use_container_width=True)
629
+
630
+ # Trade analysis
631
+ if metrics is not None and not metrics['Trades DataFrame'].empty:
632
+ st.subheader("📋 Trade Analysis")
633
+
634
+ trades_df = metrics['Trades DataFrame']
635
+
636
+ # Trade statistics
637
+ col1, col2, col3 = st.columns(3)
638
+ with col1:
639
+ avg_trade_duration = (pd.to_datetime(trades_df['exit_date']) -
640
+ pd.to_datetime(trades_df['entry_date'])).dt.days.mean()
641
+ st.metric("📅 Avg Trade Duration", f"{avg_trade_duration:.1f} days")
642
+
643
+ with col2:
644
+ best_trade = trades_df['return_pct'].max()
645
+ st.metric("🚀 Best Trade", f"{best_trade:.2%}")
646
+
647
+ with col3:
648
+ worst_trade = trades_df['return_pct'].min()
649
+ st.metric("💥 Worst Trade", f"{worst_trade:.2%}")
650
+
651
+ # Trade returns distribution
652
+ st.subheader("📊 Trade Returns Distribution")
653
+
654
+ returns_pct = trades_df['return_pct'] * 100
655
+
656
+ fig_hist = px.histogram(
657
+ x=returns_pct,
658
+ nbins=20,
659
+ title="Distribution of Trade Returns",
660
+ labels={'x': 'Return (%)', 'y': 'Number of Trades'},
661
+ color_discrete_sequence=['steelblue']
662
+ )
663
+
664
+ # Add vertical lines for mean and zero
665
+ fig_hist.add_vline(x=0, line_dash="dash", line_color="red",
666
+ annotation_text="Break Even")
667
+ fig_hist.add_vline(x=returns_pct.mean(), line_dash="solid", line_color="green",
668
+ annotation_text=f"Mean: {returns_pct.mean():.1f}%")
669
+
670
+ fig_hist.update_layout(
671
+ height=400,
672
+ template='plotly_white',
673
+ showlegend=False
674
+ )
675
+
676
+ st.plotly_chart(fig_hist, use_container_width=True)
677
+
678
+ # Trade timeline
679
+ st.subheader("📅 Trade Timeline")
680
+
681
+ fig_timeline = go.Figure()
682
+
683
+ for i, trade in trades_df.iterrows():
684
+ color = 'green' if trade['return_pct'] > 0 else 'red'
685
+ fig_timeline.add_trace(go.Scatter(
686
+ x=[trade['entry_date'], trade['exit_date']],
687
+ y=[trade['entry_price'], trade['exit_price']],
688
+ mode='lines+markers',
689
+ name=f"Trade {i+1}",
690
+ line=dict(color=color, width=3),
691
+ marker=dict(size=8),
692
+ hovertemplate=f'<b>Trade {i+1}</b><br>' +
693
+ f'Entry: ₹{trade["entry_price"]:.2f}<br>' +
694
+ f'Exit: ₹{trade["exit_price"]:.2f}<br>' +
695
+ f'Return: {trade["return_pct"]:.2%}<br>' +
696
+ f'Duration: {(pd.to_datetime(trade["exit_date"]) - pd.to_datetime(trade["entry_date"])).days} days<extra></extra>',
697
+ showlegend=False
698
+ ))
699
+
700
+ fig_timeline.update_layout(
701
+ title="Individual Trade Performance Timeline",
702
+ xaxis_title="Date",
703
+ yaxis_title="Price (₹)",
704
+ height=500,
705
+ template='plotly_white'
706
+ )
707
+
708
+ st.plotly_chart(fig_timeline, use_container_width=True)
709
+
710
+ # Trade history table
711
+ st.subheader("📊 Detailed Trade History")
712
+ display_trades = trades_df.copy()
713
+ display_trades['Entry Date'] = pd.to_datetime(display_trades['entry_date']).dt.strftime('%Y-%m-%d')
714
+ display_trades['Exit Date'] = pd.to_datetime(display_trades['exit_date']).dt.strftime('%Y-%m-%d')
715
+ display_trades['Entry Price'] = display_trades['entry_price'].apply(lambda x: f"₹{x:.2f}")
716
+ display_trades['Exit Price'] = display_trades['exit_price'].apply(lambda x: f"₹{x:.2f}")
717
+ display_trades['P&L (₹)'] = display_trades['profit_loss'].apply(lambda x: f"₹{x:,.2f}")
718
+ display_trades['Return %'] = display_trades['return_pct'].apply(lambda x: f"{x:.2%}")
719
+ display_trades['Duration'] = (pd.to_datetime(trades_df['exit_date']) -
720
+ pd.to_datetime(trades_df['entry_date'])).dt.days
721
+
722
+ trade_display = display_trades[['Entry Date', 'Exit Date', 'Entry Price', 'Exit Price',
723
+ 'P&L (₹)', 'Return %', 'Duration', 'exit_reason']].copy()
724
+ trade_display.columns = ['Entry Date', 'Exit Date', 'Entry Price', 'Exit Price',
725
+ 'Profit/Loss', 'Return %', 'Days', 'Exit Reason']
726
+
727
+ st.dataframe(trade_display, use_container_width=True)
728
+
729
+ else:
730
+ st.info("📝 No trades were executed during this period with the current parameters.")
731
+
732
+ # Signal summary table
733
+ if signal_col is not None:
734
+ st.subheader("📋 Trading Signals Summary")
735
+ signal_summary = df[df[signal_col] != 0].copy()
736
+
737
+ if not signal_summary.empty:
738
+ signal_summary['Signal Type'] = signal_summary[signal_col].map({1: '🟢 BUY', -1: '🔴 SELL'})
739
+ signal_summary['Price'] = signal_summary['Close'].apply(lambda x: f"₹{x:.2f}")
740
+ signal_summary['RSI'] = signal_summary['RSI14'].apply(lambda x: f"{x:.1f}")
741
+ signal_summary[f'{strategy_name}{short_period}'] = signal_summary[f'{strategy_name}{short_period}'].apply(lambda x: f"₹{x:.2f}")
742
+ signal_summary[f'{strategy_name}{long_period}'] = signal_summary[f'{strategy_name}{long_period}'].apply(lambda x: f"₹{x:.2f}")
743
+
744
+ display_signals = signal_summary[['Signal Type', 'Price', 'RSI',
745
+ f'{strategy_name}{short_period}',
746
+ f'{strategy_name}{long_period}']].copy()
747
+ display_signals.index = display_signals.index.strftime('%Y-%m-%d')
748
+
749
+ st.dataframe(display_signals, use_container_width=True)
750
+ else:
751
+ st.info("📝 No trading signals were generated during this period with the current parameters.")
752
+
753
+ # Data Download Section
754
+ st.subheader("💾 Download Data")
755
+ col1, col2 = st.columns(2)
756
+
757
+ with col1:
758
+ csv_data = df.to_csv(index=True)
759
+ st.download_button(
760
+ label="📁 Download Full Dataset (CSV)",
761
+ data=csv_data,
762
+ file_name=f"{selected_stock}_analysis_{start_date.strftime('%Y%m%d')}.csv",
763
+ mime="text/csv"
764
+ )
765
+
766
+ with col2:
767
+ if results is not None:
768
+ results_csv = results.to_csv(index=True)
769
+ st.download_button(
770
+ label="📊 Download Backtest Results (CSV)",
771
+ data=results_csv,
772
+ file_name=f"{selected_stock}_backtest_{start_date.strftime('%Y%m%d')}.csv",
773
+ mime="text/csv"
774
+ )
775
+
776
+ else:
777
+ st.error("❌ No data found for the selected stock and date range.")
778
+
779
+ # ========================= SIDEBAR INFORMATION =========================
780
+
781
+ st.sidebar.markdown("---")
782
+ st.sidebar.header("ℹ️ About")
783
+ st.sidebar.write("""
784
+ **Price Prediction Features:**
785
+ - Logistic Regression model for next-day prediction
786
+ - 59+ technical features including volatility, momentum, and lag features
787
+ - Confidence gauge and feature importance analysis
788
+
789
+ **Trading Dashboard Features:**
790
+ - SMA and EMA-based strategies
791
+ - Comprehensive backtesting with risk management
792
+ - Detailed performance metrics and trade analysis
793
+ - Interactive visualizations with Plotly
794
+
795
+ **Disclaimer**: This is for educational purposes only. Always do your own research before making investment decisions.
796
+ """)
797
+
798
+ st.sidebar.markdown("---")
799
+ st.sidebar.write("**Model Performance:**")
800
+ st.sidebar.write("• Accuracy: 55%")
801
+ st.sidebar.write("• F1 Score: 0.4839")
802
+ st.sidebar.write("• AUC: 0.5370")
803
+ st.sidebar.write("• Average Precision: 0.5300")
804
 
805
+ # Footer
806
+ st.markdown("---")
807
+ 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.")
808
+ st.markdown("**Developed by**: Zane Vijay Falcao")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/utils/backtester.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/backtester.py
2
+
3
+ import pandas as pd
4
+ import numpy as np
5
+
6
+ def backtest_signals(df, signal_col='Signal', price_col='Close', initial_cash=100000,
7
+ transaction_cost=0.001, stop_loss=None, take_profit=None):
8
+ """
9
+ Enhanced backtest strategy using buy/sell signals.
10
+
11
+ Parameters:
12
+ df (pd.DataFrame): DataFrame with signal and price columns
13
+ signal_col (str): Name of the signal column (1 = Buy, -1 = Sell)
14
+ price_col (str): Name of the price column to use for trading
15
+ initial_cash (float): Starting cash for the backtest
16
+ transaction_cost (float): Transaction cost as percentage (0.001 = 0.1%)
17
+ stop_loss (float): Stop loss percentage (0.05 = 5%)
18
+ take_profit (float): Take profit percentage (0.10 = 10%)
19
+
20
+ Returns:
21
+ tuple: (results_df, performance_metrics)
22
+ """
23
+ df = df.copy()
24
+ df['Position'] = 0 # 1 if holding, 0 otherwise
25
+ df['Cash'] = initial_cash
26
+ df['Holdings_Value'] = 0
27
+ df['Total'] = initial_cash
28
+ df['Returns'] = 0
29
+ df['Trade_Action'] = ''
30
+
31
+ position = 0 # Whether we hold a stock
32
+ cash = initial_cash
33
+ shares = 0
34
+ entry_price = 0
35
+ trades = []
36
+
37
+ for i in range(len(df)):
38
+ current_price = df[price_col].iloc[i]
39
+ signal = df[signal_col].iloc[i]
40
+
41
+ # Check stop loss and take profit if holding position
42
+ if position == 1 and shares > 0:
43
+ price_change = (current_price - entry_price) / entry_price
44
+
45
+ # Stop loss check
46
+ if stop_loss and price_change <= -stop_loss:
47
+ # Force sell due to stop loss
48
+ cash = shares * current_price * (1 - transaction_cost)
49
+ trades.append({
50
+ 'entry_date': entry_date,
51
+ 'exit_date': df.index[i],
52
+ 'entry_price': entry_price,
53
+ 'exit_price': current_price,
54
+ 'shares': shares,
55
+ 'profit_loss': cash - (shares * entry_price),
56
+ 'return_pct': price_change,
57
+ 'exit_reason': 'Stop Loss'
58
+ })
59
+ shares = 0
60
+ position = 0
61
+ df.at[df.index[i], 'Trade_Action'] = 'STOP_LOSS'
62
+
63
+ # Take profit check
64
+ elif take_profit and price_change >= take_profit:
65
+ # Force sell due to take profit
66
+ cash = shares * current_price * (1 - transaction_cost)
67
+ trades.append({
68
+ 'entry_date': entry_date,
69
+ 'exit_date': df.index[i],
70
+ 'entry_price': entry_price,
71
+ 'exit_price': current_price,
72
+ 'shares': shares,
73
+ 'profit_loss': cash - (shares * entry_price),
74
+ 'return_pct': price_change,
75
+ 'exit_reason': 'Take Profit'
76
+ })
77
+ shares = 0
78
+ position = 0
79
+ df.at[df.index[i], 'Trade_Action'] = 'TAKE_PROFIT'
80
+
81
+ # Process regular buy/sell signals
82
+ if signal == 1 and position == 0 and cash > 0:
83
+ # Buy signal
84
+ cost_with_fees = cash * (1 + transaction_cost)
85
+ if cost_with_fees <= cash:
86
+ shares = cash / (current_price * (1 + transaction_cost))
87
+ cash = 0
88
+ position = 1
89
+ entry_price = current_price
90
+ entry_date = df.index[i]
91
+ df.at[df.index[i], 'Trade_Action'] = 'BUY'
92
+
93
+ elif signal == -1 and position == 1 and shares > 0:
94
+ # Sell signal
95
+ cash = shares * current_price * (1 - transaction_cost)
96
+
97
+ # Record trade
98
+ price_change = (current_price - entry_price) / entry_price
99
+ trades.append({
100
+ 'entry_date': entry_date,
101
+ 'exit_date': df.index[i],
102
+ 'entry_price': entry_price,
103
+ 'exit_price': current_price,
104
+ 'shares': shares,
105
+ 'profit_loss': cash - (shares * entry_price),
106
+ 'return_pct': price_change,
107
+ 'exit_reason': 'Signal'
108
+ })
109
+
110
+ shares = 0
111
+ position = 0
112
+ df.at[df.index[i], 'Trade_Action'] = 'SELL'
113
+
114
+ # Update portfolio values
115
+ holdings_value = shares * current_price if shares > 0 else 0
116
+ total_value = cash + holdings_value
117
+
118
+ df.at[df.index[i], 'Position'] = position
119
+ df.at[df.index[i], 'Cash'] = cash
120
+ df.at[df.index[i], 'Holdings_Value'] = holdings_value
121
+ df.at[df.index[i], 'Total'] = total_value
122
+
123
+ # Calculate daily returns
124
+ if i > 0:
125
+ prev_total = df['Total'].iloc[i-1]
126
+ df.at[df.index[i], 'Returns'] = (total_value - prev_total) / prev_total
127
+
128
+ # Calculate performance metrics
129
+ performance_metrics = calculate_performance_metrics(df, trades, initial_cash)
130
+
131
+ return df[['Close', signal_col, 'Position', 'Cash', 'Holdings_Value', 'Total',
132
+ 'Returns', 'Trade_Action']], performance_metrics
133
+
134
+
135
+ def calculate_performance_metrics(df, trades, initial_cash):
136
+ """Calculate comprehensive performance metrics"""
137
+ final_value = df['Total'].iloc[-1]
138
+ total_return = (final_value - initial_cash) / initial_cash
139
+
140
+ # Calculate buy and hold return for comparison
141
+ buy_hold_return = (df['Close'].iloc[-1] - df['Close'].iloc[0]) / df['Close'].iloc[0]
142
+
143
+ # Risk metrics
144
+ returns = df['Returns'].dropna()
145
+ if len(returns) > 0:
146
+ volatility = returns.std() * np.sqrt(252) # Annualized volatility
147
+ sharpe_ratio = (returns.mean() * 252) / volatility if volatility > 0 else 0
148
+
149
+ # Maximum drawdown
150
+ cumulative = (1 + returns).cumprod()
151
+ running_max = cumulative.expanding().max()
152
+ drawdown = (cumulative - running_max) / running_max
153
+ max_drawdown = drawdown.min()
154
+ else:
155
+ volatility = 0
156
+ sharpe_ratio = 0
157
+ max_drawdown = 0
158
+
159
+ # Trade statistics
160
+ if trades:
161
+ trades_df = pd.DataFrame(trades)
162
+ win_rate = len(trades_df[trades_df['return_pct'] > 0]) / len(trades_df)
163
+ avg_win = trades_df[trades_df['return_pct'] > 0]['return_pct'].mean() if len(trades_df[trades_df['return_pct'] > 0]) > 0 else 0
164
+ avg_loss = trades_df[trades_df['return_pct'] < 0]['return_pct'].mean() if len(trades_df[trades_df['return_pct'] < 0]) > 0 else 0
165
+ profit_factor = abs(avg_win / avg_loss) if avg_loss != 0 else float('inf')
166
+ else:
167
+ win_rate = 0
168
+ avg_win = 0
169
+ avg_loss = 0
170
+ profit_factor = 0
171
+
172
+ return {
173
+ 'Total Return': f"{total_return:.2%}",
174
+ 'Buy & Hold Return': f"{buy_hold_return:.2%}",
175
+ 'Final Portfolio Value': f"₹{final_value:,.2f}",
176
+ 'Total Trades': len(trades),
177
+ 'Win Rate': f"{win_rate:.2%}",
178
+ 'Average Win': f"{avg_win:.2%}",
179
+ 'Average Loss': f"{avg_loss:.2%}",
180
+ 'Profit Factor': f"{profit_factor:.2f}",
181
+ 'Volatility (Annual)': f"{volatility:.2%}",
182
+ 'Sharpe Ratio': f"{sharpe_ratio:.2f}",
183
+ 'Maximum Drawdown': f"{max_drawdown:.2%}",
184
+ 'Trades DataFrame': pd.DataFrame(trades) if trades else pd.DataFrame()
185
+ }
186
+
src/utils/data_loader.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/data_loader.py
2
+
3
+ import yfinance as yf
4
+ import pandas as pd
5
+ from datetime import datetime
6
+ import logging
7
+
8
+ from curl_cffi import requests
9
+ session = requests.Session(impersonate="chrome")
10
+
11
+ today = datetime.today()
12
+
13
+ def fetch_stock_data(symbol, start_date="2023-01-01", end_date=today, interval="1d"):
14
+ """
15
+ Fetch historical stock data from Yahoo Finance.
16
+
17
+ Parameters:
18
+ symbol (str): Ticker symbol (e.g., "RELIANCE.NS")
19
+ start_date (str): Start date in "YYYY-MM-DD"
20
+ end_date (str): End date (default is today)
21
+ interval (str): Data interval ("1d", "1h", etc.)
22
+
23
+ Returns:
24
+ pd.DataFrame: Historical OHLCV stock data
25
+ """
26
+ try:
27
+ logging.info(f"Fetching data for {symbol} from {start_date} to {end_date or 'today'}")
28
+ df = yf.download(symbol, start=start_date, end=end_date, interval=interval, progress=False, session=session, auto_adjust=True, threads=True)
29
+ # Flatten the MultiIndex columns
30
+ df.columns = [col[0] for col in df.columns]
31
+
32
+ if df.empty:
33
+ logging.warning(f"No data found for {symbol}")
34
+ else:
35
+ logging.info(f"Downloaded {len(df)} rows for {symbol}")
36
+ return df
37
+
38
+ except Exception as e:
39
+ logging.error(f"Failed to fetch data for {symbol}: {e}")
40
+ return pd.DataFrame()
41
+
src/utils/google_sheets.py ADDED
@@ -0,0 +1,426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/google_sheets.py
2
+
3
+ import gspread
4
+ from google.oauth2.service_account import Credentials
5
+ import pandas as pd
6
+ from datetime import datetime
7
+ import streamlit as st
8
+ import json
9
+
10
+ class TradingGoogleSheets:
11
+ def __init__(self, credentials_json_path=None, credentials_dict=None, sheet_name="Trading_Log"):
12
+ """
13
+ Initialize Google Sheets connection
14
+
15
+ Parameters:
16
+ credentials_json_path (str): Path to service account JSON file
17
+ credentials_dict (dict): Service account credentials as dictionary (for Streamlit secrets)
18
+ sheet_name (str): Name of the Google Sheet to create/use
19
+ """
20
+ self.sheet_name = sheet_name
21
+ self.scope = [
22
+ "https://spreadsheets.google.com/feeds",
23
+ "https://www.googleapis.com/auth/drive"
24
+ ]
25
+
26
+ # Initialize credentials
27
+ if credentials_dict:
28
+ # For Streamlit deployment with secrets
29
+ self.creds = Credentials.from_service_account_info(credentials_dict, scopes=self.scope)
30
+ elif credentials_json_path:
31
+ # For local development with JSON file
32
+ self.creds = Credentials.from_service_account_file(credentials_json_path, scopes=self.scope)
33
+ else:
34
+ raise ValueError("Either credentials_json_path or credentials_dict must be provided")
35
+
36
+ self.client = gspread.authorize(self.creds)
37
+ self.spreadsheet = None
38
+
39
+ def create_or_get_spreadsheet(self):
40
+ """Create a new spreadsheet or get existing one"""
41
+ try:
42
+ # Try to open existing spreadsheet
43
+ self.spreadsheet = self.client.open(self.sheet_name)
44
+ print(f"Opened existing spreadsheet: {self.sheet_name}")
45
+ except gspread.SpreadsheetNotFound:
46
+ # Create new spreadsheet
47
+ self.spreadsheet = self.client.create(self.sheet_name)
48
+ print(f"Created new spreadsheet: {self.sheet_name}")
49
+
50
+ # Share with your email (replace with your email)
51
+ # self.spreadsheet.share('your-email@gmail.com', perm_type='user', role='writer')
52
+
53
+ # Create the three required worksheets
54
+ self.setup_worksheets()
55
+ return self.spreadsheet
56
+
57
+ def setup_worksheets(self):
58
+ """Setup the required worksheets with headers"""
59
+ worksheets_config = {
60
+ "Trade_Log": [
61
+ "Timestamp", "Stock", "Strategy", "Signal_Type", "Price", "RSI",
62
+ "MA_Short", "MA_Long", "Entry_Date", "Exit_Date", "Entry_Price",
63
+ "Exit_Price", "Shares", "Profit_Loss", "Return_Pct", "Exit_Reason", "Duration_Days"
64
+ ],
65
+ "Summary_PL": [
66
+ "Date", "Stock", "Strategy", "Total_Trades", "Winning_Trades",
67
+ "Losing_Trades", "Win_Rate", "Total_PL", "Best_Trade", "Worst_Trade",
68
+ "Avg_Win", "Avg_Loss", "Profit_Factor", "Max_Drawdown", "Sharpe_Ratio",
69
+ "Final_Portfolio_Value", "Total_Return"
70
+ ],
71
+ "Performance_Metrics": [
72
+ "Date", "Stock", "Strategy", "Initial_Capital", "Final_Value",
73
+ "Total_Return", "Buy_Hold_Return", "Alpha", "Volatility",
74
+ "Sharpe_Ratio", "Max_Drawdown", "Total_Trades", "Win_Rate",
75
+ "Avg_Trade_Duration", "Transaction_Cost", "Notes"
76
+ ]
77
+ }
78
+
79
+ existing_sheets = [ws.title for ws in self.spreadsheet.worksheets()]
80
+
81
+ for sheet_name, headers in worksheets_config.items():
82
+ if sheet_name not in existing_sheets:
83
+ # Create new worksheet
84
+ worksheet = self.spreadsheet.add_worksheet(title=sheet_name, rows=1000, cols=len(headers))
85
+ worksheet.append_row(headers)
86
+ print(f"Created worksheet: {sheet_name}")
87
+ else:
88
+ print(f"Worksheet already exists: {sheet_name}")
89
+
90
+ def log_trade_signals(self, df, strategy_name, stock_symbol):
91
+ """Log all trade signals to Trade_Log worksheet"""
92
+ try:
93
+ worksheet = self.spreadsheet.worksheet("Trade_Log")
94
+
95
+ # Get signals from dataframe
96
+ signal_col = f'{strategy_name}_Signal'
97
+ signals_df = df[df[signal_col] != 0].copy()
98
+
99
+ if signals_df.empty:
100
+ print("No signals to log")
101
+ return
102
+
103
+ # Prepare data for logging
104
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
105
+
106
+ rows_to_add = []
107
+ for idx, row in signals_df.iterrows():
108
+ signal_type = "BUY" if row[signal_col] == 1 else "SELL"
109
+
110
+ row_data = [
111
+ current_time, # Timestamp
112
+ stock_symbol, # Stock
113
+ strategy_name, # Strategy
114
+ signal_type, # Signal_Type
115
+ round(row['Close'], 2), # Price
116
+ round(row['RSI'], 2), # RSI
117
+ round(row[f'{strategy_name}20'], 2), # MA_Short
118
+ round(row[f'{strategy_name}50'], 2), # MA_Long
119
+ "", # Entry_Date (filled when trade completes)
120
+ "", # Exit_Date
121
+ "", # Entry_Price
122
+ "", # Exit_Price
123
+ "", # Shares
124
+ "", # Profit_Loss
125
+ "", # Return_Pct
126
+ "", # Exit_Reason
127
+ "" # Duration_Days
128
+ ]
129
+ rows_to_add.append(row_data)
130
+
131
+ # Add all rows at once
132
+ if rows_to_add:
133
+ worksheet.append_rows(rows_to_add)
134
+ print(f"Logged {len(rows_to_add)} signals to Trade_Log")
135
+
136
+ except Exception as e:
137
+ print(f"Error logging trade signals: {e}")
138
+
139
+ def log_completed_trades(self, trades_df, strategy_name, stock_symbol):
140
+ """Log completed trades to Trade_Log worksheet"""
141
+ try:
142
+ worksheet = self.spreadsheet.worksheet("Trade_Log")
143
+
144
+ if trades_df.empty:
145
+ print("No completed trades to log")
146
+ return
147
+
148
+ current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
149
+
150
+ rows_to_add = []
151
+ for _, trade in trades_df.iterrows():
152
+ row_data = [
153
+ current_time, # Timestamp
154
+ stock_symbol, # Stock
155
+ strategy_name, # Strategy
156
+ "COMPLETED_TRADE", # Signal_Type
157
+ round(trade['exit_price'], 2), # Price (exit price)
158
+ "", # RSI
159
+ "", # MA_Short
160
+ "", # MA_Long
161
+ trade['entry_date'], # Entry_Date
162
+ trade['exit_date'], # Exit_Date
163
+ round(trade['entry_price'], 2), # Entry_Price
164
+ round(trade['exit_price'], 2), # Exit_Price
165
+ round(trade['shares'], 4), # Shares
166
+ round(trade['profit_loss'], 2), # Profit_Loss
167
+ round(trade['return_pct'] * 100, 2), # Return_Pct
168
+ trade['exit_reason'], # Exit_Reason
169
+ (pd.to_datetime(trade['exit_date']) - pd.to_datetime(trade['entry_date'])).days # Duration_Days
170
+ ]
171
+ rows_to_add.append(row_data)
172
+
173
+ if rows_to_add:
174
+ worksheet.append_rows(rows_to_add)
175
+ print(f"Logged {len(rows_to_add)} completed trades to Trade_Log")
176
+
177
+ except Exception as e:
178
+ print(f"Error logging completed trades: {e}")
179
+
180
+ def log_summary_pl(self, metrics, strategy_name, stock_symbol, trades_df):
181
+ """Log summary P&L to Summary_PL worksheet"""
182
+ try:
183
+ worksheet = self.spreadsheet.worksheet("Summary_PL")
184
+
185
+ current_date = datetime.now().strftime("%Y-%m-%d")
186
+
187
+ # Calculate additional metrics
188
+ total_trades = len(trades_df) if not trades_df.empty else 0
189
+ winning_trades = len(trades_df[trades_df['return_pct'] > 0]) if not trades_df.empty else 0
190
+ losing_trades = len(trades_df[trades_df['return_pct'] < 0]) if not trades_df.empty else 0
191
+
192
+ # Extract numeric values from metrics
193
+ win_rate = float(metrics['Win Rate'].strip('%')) if metrics['Win Rate'] != '0.00%' else 0
194
+ total_pl = float(metrics['Final Portfolio Value'].replace('₹', '').replace(',', '')) - 100000 # Assuming 100k initial
195
+ best_trade = trades_df['return_pct'].max() * 100 if not trades_df.empty else 0
196
+ worst_trade = trades_df['return_pct'].min() * 100 if not trades_df.empty else 0
197
+ avg_win = float(metrics['Average Win'].strip('%')) if metrics['Average Win'] != '0.00%' else 0
198
+ avg_loss = float(metrics['Average Loss'].strip('%')) if metrics['Average Loss'] != '0.00%' else 0
199
+ profit_factor = float(metrics['Profit Factor']) if metrics['Profit Factor'] != '0.00' else 0
200
+ max_drawdown = float(metrics['Maximum Drawdown'].strip('%'))
201
+ sharpe_ratio = float(metrics['Sharpe Ratio'])
202
+ final_value = float(metrics['Final Portfolio Value'].replace('₹', '').replace(',', ''))
203
+ total_return = float(metrics['Total Return'].strip('%'))
204
+
205
+ row_data = [
206
+ current_date, # Date
207
+ stock_symbol, # Stock
208
+ strategy_name, # Strategy
209
+ total_trades, # Total_Trades
210
+ winning_trades, # Winning_Trades
211
+ losing_trades, # Losing_Trades
212
+ round(win_rate, 2), # Win_Rate
213
+ round(total_pl, 2), # Total_PL
214
+ round(best_trade, 2), # Best_Trade
215
+ round(worst_trade, 2), # Worst_Trade
216
+ round(avg_win, 2), # Avg_Win
217
+ round(avg_loss, 2), # Avg_Loss
218
+ round(profit_factor, 2), # Profit_Factor
219
+ round(max_drawdown, 2), # Max_Drawdown
220
+ round(sharpe_ratio, 2), # Sharpe_Ratio
221
+ round(final_value, 2), # Final_Portfolio_Value
222
+ round(total_return, 2) # Total_Return
223
+ ]
224
+
225
+ worksheet.append_row(row_data)
226
+ print("Logged summary P&L to Summary_PL")
227
+
228
+ except Exception as e:
229
+ print(f"Error logging summary P&L: {e}")
230
+
231
+ def log_performance_metrics(self, metrics, strategy_name, stock_symbol, initial_capital,
232
+ transaction_cost, notes=""):
233
+ """Log performance metrics to Performance_Metrics worksheet"""
234
+ try:
235
+ worksheet = self.spreadsheet.worksheet("Performance_Metrics")
236
+
237
+ current_date = datetime.now().strftime("%Y-%m-%d")
238
+
239
+ # Extract and clean numeric values
240
+ final_value = float(metrics['Final Portfolio Value'].replace('₹', '').replace(',', ''))
241
+ total_return = float(metrics['Total Return'].strip('%'))
242
+ buy_hold_return = float(metrics['Buy & Hold Return'].strip('%'))
243
+ alpha = total_return - buy_hold_return
244
+ volatility = float(metrics['Volatility (Annual)'].strip('%'))
245
+ sharpe_ratio = float(metrics['Sharpe Ratio'])
246
+ max_drawdown = float(metrics['Maximum Drawdown'].strip('%'))
247
+ total_trades = metrics['Total Trades']
248
+ win_rate = float(metrics['Win Rate'].strip('%'))
249
+
250
+ # Calculate average trade duration (you might need to pass this from trades_df)
251
+ avg_trade_duration = 0 # You can calculate this from trades_df if needed
252
+
253
+ row_data = [
254
+ current_date, # Date
255
+ stock_symbol, # Stock
256
+ strategy_name, # Strategy
257
+ initial_capital, # Initial_Capital
258
+ round(final_value, 2), # Final_Value
259
+ round(total_return, 2), # Total_Return
260
+ round(buy_hold_return, 2), # Buy_Hold_Return
261
+ round(alpha, 2), # Alpha
262
+ round(volatility, 2), # Volatility
263
+ round(sharpe_ratio, 2), # Sharpe_Ratio
264
+ round(max_drawdown, 2), # Max_Drawdown
265
+ total_trades, # Total_Trades
266
+ round(win_rate, 2), # Win_Rate
267
+ avg_trade_duration, # Avg_Trade_Duration
268
+ transaction_cost * 100, # Transaction_Cost (as percentage)
269
+ notes # Notes
270
+ ]
271
+
272
+ worksheet.append_row(row_data)
273
+ print("Logged performance metrics to Performance_Metrics")
274
+
275
+ except Exception as e:
276
+ print(f"Error logging performance metrics: {e}")
277
+
278
+ def get_sheet_url(self):
279
+ """Get the URL of the Google Sheet"""
280
+ if self.spreadsheet:
281
+ return self.spreadsheet.url
282
+ return None
283
+
284
+ def clear_worksheet(self, worksheet_name):
285
+ """Clear all data from a worksheet (except headers)"""
286
+ try:
287
+ worksheet = self.spreadsheet.worksheet(worksheet_name)
288
+ worksheet.clear()
289
+ # Re-add headers based on the worksheet
290
+ if worksheet_name == "Trade_Log":
291
+ headers = ["Timestamp", "Stock", "Strategy", "Signal_Type", "Price", "RSI",
292
+ "MA_Short", "MA_Long", "Entry_Date", "Exit_Date", "Entry_Price",
293
+ "Exit_Price", "Shares", "Profit_Loss", "Return_Pct", "Exit_Reason", "Duration_Days"]
294
+ elif worksheet_name == "Summary_PL":
295
+ headers = ["Date", "Stock", "Strategy", "Total_Trades", "Winning_Trades",
296
+ "Losing_Trades", "Win_Rate", "Total_PL", "Best_Trade", "Worst_Trade",
297
+ "Avg_Win", "Avg_Loss", "Profit_Factor", "Max_Drawdown", "Sharpe_Ratio",
298
+ "Final_Portfolio_Value", "Total_Return"]
299
+ elif worksheet_name == "Performance_Metrics":
300
+ headers = ["Date", "Stock", "Strategy", "Initial_Capital", "Final_Value",
301
+ "Total_Return", "Buy_Hold_Return", "Alpha", "Volatility",
302
+ "Sharpe_Ratio", "Max_Drawdown", "Total_Trades", "Win_Rate",
303
+ "Avg_Trade_Duration", "Transaction_Cost", "Notes"]
304
+
305
+ worksheet.append_row(headers)
306
+ print(f"Cleared and reset worksheet: {worksheet_name}")
307
+
308
+ except Exception as e:
309
+ print(f"Error clearing worksheet {worksheet_name}: {e}")
310
+
311
+
312
+ # Integration function for your Streamlit app
313
+ def log_to_google_sheets(df, results, metrics, strategy_name, stock_symbol,
314
+ initial_cash, transaction_cost, credentials_dict=None,
315
+ credentials_json_path=None):
316
+ """
317
+ Main function to log all data to Google Sheets
318
+
319
+ Parameters:
320
+ df: DataFrame with signals and indicators
321
+ results: Backtest results DataFrame
322
+ metrics: Performance metrics dictionary
323
+ strategy_name: Name of the strategy (SMA/EMA)
324
+ stock_symbol: Stock symbol
325
+ initial_cash: Initial capital
326
+ transaction_cost: Transaction cost percentage
327
+ credentials_dict: Google service account credentials (for Streamlit)
328
+ credentials_json_path: Path to JSON credentials file (for local)
329
+ """
330
+ try:
331
+ # Initialize Google Sheets connection
332
+ sheets_logger = TradingGoogleSheets(
333
+ credentials_dict=credentials_dict,
334
+ credentials_json_path=credentials_json_path,
335
+ sheet_name=f"Trading_Log_{stock_symbol}"
336
+ )
337
+
338
+ # Create or get spreadsheet
339
+ spreadsheet = sheets_logger.create_or_get_spreadsheet()
340
+
341
+ # Log trade signals
342
+ sheets_logger.log_trade_signals(df, strategy_name, stock_symbol)
343
+
344
+ # Log completed trades if available
345
+ if not metrics['Trades DataFrame'].empty:
346
+ sheets_logger.log_completed_trades(metrics['Trades DataFrame'], strategy_name, stock_symbol)
347
+
348
+ # Log summary P&L
349
+ trades_df = metrics['Trades DataFrame'] if not metrics['Trades DataFrame'].empty else pd.DataFrame()
350
+ sheets_logger.log_summary_pl(metrics, strategy_name, stock_symbol, trades_df)
351
+
352
+ # Log performance metrics
353
+ sheets_logger.log_performance_metrics(
354
+ metrics, strategy_name, stock_symbol, initial_cash, transaction_cost
355
+ )
356
+
357
+ return sheets_logger.get_sheet_url()
358
+
359
+ except Exception as e:
360
+ print(f"Error in log_to_google_sheets: {e}")
361
+ return None
362
+
363
+
364
+ # # Streamlit integration function
365
+ # def add_google_sheets_to_streamlit(df, results, metrics, strategy_name, stock_symbol,
366
+ # initial_cash, transaction_cost):
367
+ # """Add Google Sheets logging functionality to your Streamlit app"""
368
+
369
+ # st.subheader("📊 Google Sheets Integration")
370
+
371
+ # # Check if credentials are configured
372
+ # if 'google_sheets_credentials' in st.secrets:
373
+ # col1, col2 = st.columns([2, 1])
374
+
375
+ # with col1:
376
+ # if st.button("📤 Log to Google Sheets", type="primary"):
377
+ # with st.spinner("Logging data to Google Sheets..."):
378
+ # sheet_url = log_to_google_sheets(
379
+ # df=df,
380
+ # results=results,
381
+ # metrics=metrics,
382
+ # strategy_name=strategy_name,
383
+ # stock_symbol=stock_symbol,
384
+ # initial_cash=initial_cash,
385
+ # transaction_cost=transaction_cost,
386
+ # credentials_dict=dict(st.secrets.google_sheets_credentials)
387
+ # )
388
+
389
+ # if sheet_url:
390
+ # st.success("✅ Data logged successfully!")
391
+ # st.markdown(f"🔗 [View Google Sheet]({sheet_url})")
392
+ # else:
393
+ # st.error("❌ Failed to log data to Google Sheets")
394
+
395
+ # with col2:
396
+ # st.info("💡 **Auto-logging enabled**\n\nData will be saved to:\n- Trade signals\n- P&L summary\n- Performance metrics")
397
+
398
+ # else:
399
+ # st.warning("⚠️ Google Sheets credentials not configured. Add your service account credentials to Streamlit secrets to enable logging.")
400
+
401
+ # with st.expander("📋 Setup Instructions"):
402
+ # st.markdown("""
403
+ # **To enable Google Sheets integration:**
404
+
405
+ # 1. **Create a Google Cloud Project**
406
+ # 2. **Enable Google Sheets & Drive APIs**
407
+ # 3. **Create a Service Account**
408
+ # 4. **Download the JSON credentials**
409
+ # 5. **Add credentials to Streamlit secrets**
410
+
411
+ # **In your `.streamlit/secrets.toml` file:**
412
+ # ```toml
413
+ # [google_sheets_credentials]
414
+ # type = "service_account"
415
+ # project_id = "your-project-id"
416
+ # private_key_id = "your-private-key-id"
417
+ # private_key = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
418
+ # client_email = "your-service-account@your-project.iam.gserviceaccount.com"
419
+ # client_id = "your-client-id"
420
+ # auth_uri = "https://accounts.google.com/o/oauth2/auth"
421
+ # token_uri = "https://oauth2.googleapis.com/token"
422
+ # auth_provider_x509_cert_url = "https://www.googleapis.com/oauth2/v1/certs"
423
+ # client_x509_cert_url = "https://www.googleapis.com/robot/v1/metadata/x509/your-service-account%40your-project.iam.gserviceaccount.com"
424
+ # universe_domain = "googleapis.com"
425
+ # ```
426
+ # """)
src/utils/logger.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/logger.py
2
+
3
+ import logging
4
+ import os
5
+ from datetime import datetime
6
+
7
+ def setup_logger(log_dir="logs", log_level=logging.INFO):
8
+ """
9
+ Sets up a logger that writes to both console and a timestamped log file.
10
+ """
11
+ os.makedirs(log_dir, exist_ok=True)
12
+ timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
13
+ log_file = os.path.join(log_dir, f"log_{timestamp}.log")
14
+
15
+ logging.basicConfig(
16
+ level=log_level,
17
+ format="%(asctime)s [%(levelname)s] - %(message)s",
18
+ handlers=[
19
+ logging.FileHandler(log_file),
20
+ logging.StreamHandler()
21
+ ]
22
+ )
23
+ logging.info("Logger initialized.")
24
+