eshan6704 commited on
Commit
a6d803d
·
verified ·
1 Parent(s): 1b7b6e5

Update daily.py

Browse files
Files changed (1) hide show
  1. daily.py +374 -110
daily.py CHANGED
@@ -1,131 +1,395 @@
1
  # daily.py
2
  import yfinance as yf
3
  import pandas as pd
4
- import talib
5
  import numpy as np
6
  import plotly.graph_objs as go
 
7
 
8
- STYLE_BLOCK = """
9
- <style>
10
- .styled-table { border-collapse: collapse; margin: 10px 0; font-size: 0.9em; font-family: sans-serif; width: 100%; box-shadow: 0 0 10px rgba(0,0,0,0.1);}
11
- .styled-table th, .styled-table td { padding: 8px 10px; border: 1px solid #ddd;}
12
- .styled-table tbody tr:nth-child(even) { background-color: #f9f9f9;}
13
- .button { margin:5px; padding:5px 10px; border-radius:5px; border:1px solid #0077cc; background:#0077cc; color:#fff; cursor:pointer;}
14
- .button:hover { background:#005fa3; }
15
- </style>
16
- """
17
-
18
- # --- Custom functions ---
19
- def supertrend(df, period=10, multiplier=3):
20
- hl2 = (df['High'] + df['Low']) / 2
21
- tr = pd.concat([df['High'] - df['Low'],
22
- abs(df['High'] - df['Close'].shift()),
23
- abs(df['Low'] - df['Close'].shift())], axis=1).max(axis=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  atr = tr.rolling(period).mean()
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  upperband = hl2 + multiplier * atr
26
  lowerband = hl2 - multiplier * atr
27
- st = pd.Series(index=df.index)
28
- trend_up = True
29
- for i in range(1, len(df)):
30
- if df['Close'][i] > upperband[i-1]:
31
- trend_up = True
32
- elif df['Close'][i] < lowerband[i-1]:
33
- trend_up = False
34
- st[i] = lowerband[i] if trend_up else upperband[i]
35
- return st
36
-
37
- def zigzag(df, pct=5):
38
- zz = pd.Series(index=df.index)
39
- last_pivot = df['Close'][0]
40
- trend = 0
41
  for i in range(1, len(df)):
42
- change = (df['Close'][i] - last_pivot) / last_pivot * 100
43
- if trend >= 0 and change <= -pct:
44
- zz[i] = df['Close'][i]
45
- last_pivot = df['Close'][i]
46
- trend = -1
47
- elif trend <= 0 and change >= pct:
48
- zz[i] = df['Close'][i]
49
- last_pivot = df['Close'][i]
50
- trend = 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  return zz
52
 
 
53
  def swing_high_low(df, window=5):
 
54
  df['SwingHigh'] = df['High'].rolling(window, center=True).max()
55
  df['SwingLow'] = df['Low'].rolling(window, center=True).min()
56
- return df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- def keltner_channel(df, period=20, atr_mult=2):
59
- ema = talib.EMA(df['Close'], period)
60
- tr = pd.concat([df['High'] - df['Low'],
61
- abs(df['High'] - df['Close'].shift()),
62
- abs(df['Low'] - df['Close'].shift())], axis=1).max(axis=1)
63
- atr = tr.rolling(period).mean()
64
- df['KC_Upper'] = ema + atr_mult * atr
65
- df['KC_Lower'] = ema - atr_mult * atr
66
- return df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- # --- Main function ---
69
- def fetch_daily(symbol):
70
- yfsymbol = symbol + ".NS"
71
- content_html = f"<h1>No daily data for {symbol}</h1>"
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  try:
74
- df = yf.download(yfsymbol, period="1y", interval="1d").round(2)
75
- if not df.empty:
76
- if isinstance(df.columns, pd.MultiIndex):
77
- df.columns = df.columns.get_level_values(0)
78
-
79
- # --- TA-Lib indicators ---
80
- df["SMA20"] = talib.SMA(df["Close"], timeperiod=20)
81
- df["SMA50"] = talib.SMA(df["Close"], timeperiod=50)
82
- df["EMA20"] = talib.EMA(df["Close"], timeperiod=20)
83
- df["RSI14"] = talib.RSI(df["Close"], timeperiod=14)
84
- df["MACD"], df["MACD_Signal"], df["MACD_Hist"] = talib.MACD(df["Close"], fastperiod=12, slowperiod=26, signalperiod=9)
85
-
86
- # --- Custom indicators ---
87
- df["SuperTrend"] = supertrend(df)
88
- df = keltner_channel(df)
89
- df["ZigZag"] = zigzag(df)
90
- df = swing_high_low(df)
91
-
92
- # --- Plotly chart ---
93
- fig = go.Figure()
94
- fig.add_trace(go.Candlestick(
95
- x=df.index, open=df["Open"], high=df["High"],
96
- low=df["Low"], close=df["Close"], name="Price"
97
- ))
98
-
99
- # Indicators toggle
100
- indicators = ["SMA20","SMA50","EMA20","RSI14","MACD","MACD_Signal",
101
- "SuperTrend","KC_Upper","KC_Lower","ZigZag","SwingHigh","SwingLow"]
102
-
103
- for ind in indicators:
104
- yaxis = 'y2' if ind in ["RSI14","MACD","MACD_Signal"] else 'y'
105
- fig.add_trace(go.Scatter(x=df.index, y=df[ind], mode='lines+markers' if 'Swing' in ind or 'ZigZag' in ind else 'lines',
106
- name=ind, visible=False, yaxis=yaxis))
107
-
108
- buttons=[]
109
- for i, ind in enumerate(indicators):
110
- visible=[True]+[False]*len(indicators)
111
- visible[i+1]=True
112
- buttons.append(dict(label=ind, method="restyle", args=[{"visible":visible}]))
113
- buttons.append(dict(label="All Off", method="restyle", args=[{"visible":[True]+[False]*len(indicators)}]))
114
-
115
- fig.update_layout(
116
- xaxis_title="Date",
117
- yaxis_title="Price",
118
- yaxis2=dict(title="Indicator", overlaying="y", side="right"),
119
- xaxis_rangeslider_visible=False,
120
- height=700,
121
- updatemenus=[dict(type="buttons", x=1.05, y=0.8, buttons=buttons)]
122
- )
123
-
124
- chart_html = fig.to_html(full_html=False)
125
- table_html = df.tail(30).to_html(classes="styled-table", border=0)
126
- content_html = f"{chart_html}<h2>Recent Daily Data (last 30 rows)</h2>{table_html}"
127
 
128
- except Exception as e:
129
- content_html = f"<h1>Error</h1><p>{e}</p>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
- return f"<!DOCTYPE html><html><head>{STYLE_BLOCK}</head><body>{content_html}</body></html>"
 
1
  # daily.py
2
  import yfinance as yf
3
  import pandas as pd
 
4
  import numpy as np
5
  import plotly.graph_objs as go
6
+ from plotly.subplots import make_subplots
7
 
8
+ # Try to import TA-Lib; if unavailable, will use fallback implementations
9
+ try:
10
+ import talib
11
+ TALIB_AVAILABLE = True
12
+ except Exception:
13
+ TALIB_AVAILABLE = False
14
+
15
+ from common import wrap_html, STYLE_BLOCK
16
+
17
+ # --- Helper fallback implementations (used if TA-Lib isn't available) ---
18
+ def sma_fallback(series, period):
19
+ return series.rolling(period).mean()
20
+
21
+ def ema_fallback(series, period):
22
+ return series.ewm(span=period, adjust=False).mean()
23
+
24
+ def macd_fallback(close, fast=12, slow=26, signal=9):
25
+ fast_ema = ema_fallback(close, fast)
26
+ slow_ema = ema_fallback(close, slow)
27
+ macd = fast_ema - slow_ema
28
+ macd_signal = ema_fallback(macd, signal)
29
+ macd_hist = macd - macd_signal
30
+ return macd, macd_signal, macd_hist
31
+
32
+ def atr_fallback(high, low, close, period=14):
33
+ tr1 = high - low
34
+ tr2 = (high - close.shift()).abs()
35
+ tr3 = (low - close.shift()).abs()
36
+ tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
37
  atr = tr.rolling(period).mean()
38
+ return atr
39
+
40
+ def stoch_fallback(high, low, close, k_period=14, d_period=3):
41
+ low_min = low.rolling(k_period).min()
42
+ high_max = high.rolling(k_period).max()
43
+ k = 100 * (close - low_min) / (high_max - low_min)
44
+ d = k.rolling(d_period).mean()
45
+ return k, d
46
+
47
+ # SuperTrend (custom)
48
+ def supertrend_fallback(df, period=10, multiplier=3):
49
+ hl2 = (df['High'] + df['Low']) / 2
50
+ atr = atr_fallback(df['High'], df['Low'], df['Close'], period=period)
51
  upperband = hl2 + multiplier * atr
52
  lowerband = hl2 - multiplier * atr
53
+
54
+ st = pd.Series(index=df.index, dtype=float)
55
+ trend = pd.Series(index=df.index, dtype=bool) # True = uptrend, False = downtrend
56
+
57
+ # initialize
58
+ st.iloc[0] = np.nan
59
+ trend.iloc[0] = True
60
+
 
 
 
 
 
 
61
  for i in range(1, len(df)):
62
+ if df['Close'].iat[i] > upperband.iat[i-1]:
63
+ trend.iat[i] = True
64
+ elif df['Close'].iat[i] < lowerband.iat[i-1]:
65
+ trend.iat[i] = False
66
+ else:
67
+ trend.iat[i] = trend.iat[i-1]
68
+ if trend.iat[i] and lowerband.iat[i] < st.iat[i-1]:
69
+ lowerband.iat[i] = st.iat[i-1]
70
+ if not trend.iat[i] and upperband.iat[i] > st.iat[i-1]:
71
+ upperband.iat[i] = st.iat[i-1]
72
+
73
+ st.iat[i] = lowerband.iat[i] if trend.iat[i] else upperband.iat[i]
74
+
75
+ return st, trend
76
+
77
+ # ZigZag (simple percent threshold)
78
+ def zigzag_fallback(close_series, pct=5.0):
79
+ zz = pd.Series(index=close_series.index, dtype=float)
80
+ last_extreme_idx = 0
81
+ last_extreme_price = close_series.iat[0]
82
+ last_trend = 0 # 1 = up, -1 = down, 0 = unknown
83
+ for i in range(1, len(close_series)):
84
+ change = (close_series.iat[i] - last_extreme_price) / last_extreme_price * 100.0
85
+ if last_trend >= 0 and change <= -pct:
86
+ zz.iat[i] = close_series.iat[i]
87
+ last_extreme_idx = i
88
+ last_extreme_price = close_series.iat[i]
89
+ last_trend = -1
90
+ elif last_trend <= 0 and change >= pct:
91
+ zz.iat[i] = close_series.iat[i]
92
+ last_extreme_idx = i
93
+ last_extreme_price = close_series.iat[i]
94
+ last_trend = 1
95
  return zz
96
 
97
+ # Swing High/Low via rolling window
98
  def swing_high_low(df, window=5):
99
+ df = df.copy()
100
  df['SwingHigh'] = df['High'].rolling(window, center=True).max()
101
  df['SwingLow'] = df['Low'].rolling(window, center=True).min()
102
+ # Only keep values where the center equals the extreme (to mark pivot)
103
+ center = window // 2
104
+ swing_high = [np.nan]*len(df)
105
+ swing_low = [np.nan]*len(df)
106
+ highs = df['High'].values
107
+ lows = df['Low'].values
108
+ for i in range(center, len(df)-center):
109
+ window_high = highs[i-center:i+center+1]
110
+ window_low = lows[i-center:i+center+1]
111
+ if highs[i] == np.max(window_high):
112
+ swing_high[i] = highs[i]
113
+ if lows[i] == np.min(window_low):
114
+ swing_low[i] = lows[i]
115
+ df['SwingHigh'] = swing_high
116
+ df['SwingLow'] = swing_low
117
+ return df['SwingHigh'], df['SwingLow']
118
 
119
+ # Keltner Channel (EMA ± ATR * mult)
120
+ def keltner_channel(df, ema_period=20, atr_period=10, atr_mult=2):
121
+ if TALIB_AVAILABLE:
122
+ ema = talib.EMA(df['Close'], timeperiod=ema_period)
123
+ atr = talib.ATR(df['High'], df['Low'], df['Close'], timeperiod=atr_period)
124
+ else:
125
+ ema = ema_fallback(df['Close'], ema_period)
126
+ atr = atr_fallback(df['High'], df['Low'], df['Close'], period=atr_period)
127
+ upper = ema + atr_mult * atr
128
+ lower = ema - atr_mult * atr
129
+ return upper, lower
130
+
131
+ # --- Main fetch function ---
132
+ def fetch_daily(symbol,
133
+ period="1y",
134
+ interval="1d",
135
+ supertrend_period=10,
136
+ supertrend_mult=3,
137
+ keltner_ema=20,
138
+ keltner_atr=10,
139
+ keltner_mult=2,
140
+ zigzag_pct=5.0):
141
+ """
142
+ Returns full HTML with multi-panel Plotly chart (price, volume, MACD, Stochastic)
143
+ and controls (buttons) to toggle indicators.
144
+ """
145
+ yfsymbol = f"{symbol}.NS"
146
+ try:
147
+ df = yf.download(yfsymbol, period=period, interval=interval).round(6)
148
+ except Exception as e:
149
+ return wrap_html("Error", f"<h1>Error fetching data for {symbol}</h1><p>{str(e)}</p>")
150
+
151
+ if df is None or df.empty:
152
+ return wrap_html("No Data", f"<h1>No {interval} data for {symbol} (period={period})</h1>")
153
 
154
+ # Ensure datetime index is proper
155
+ df = df.copy()
156
+ if isinstance(df.columns, pd.MultiIndex):
157
+ df.columns = df.columns.get_level_values(0)
158
 
159
+ # Compute indicators (TA-Lib if available, else fallback)
160
+ if TALIB_AVAILABLE:
161
+ # moving averages
162
+ df['SMA20'] = talib.SMA(df['Close'], timeperiod=20)
163
+ df['SMA50'] = talib.SMA(df['Close'], timeperiod=50)
164
+ df['EMA20'] = talib.EMA(df['Close'], timeperiod=20)
165
+ # MACD
166
+ macd, macd_signal, macd_hist = talib.MACD(df['Close'], fastperiod=12, slowperiod=26, signalperiod=9)
167
+ df['MACD'], df['MACD_Signal'], df['MACD_Hist'] = macd, macd_signal, macd_hist
168
+ # Stochastic
169
+ slowk, slowd = talib.STOCH(df['High'], df['Low'], df['Close'], fastk_period=14, slowk_period=3, slowk_matype=0, slowd_period=3, slowd_matype=0)
170
+ df['StochK'], df['StochD'] = slowk, slowd
171
+ # ATR for SuperTrend/Keltner
172
+ df['ATR14'] = talib.ATR(df['High'], df['Low'], df['Close'], timeperiod=14)
173
+ else:
174
+ # SMA/EMA fallback
175
+ df['SMA20'] = sma_fallback(df['Close'], 20)
176
+ df['SMA50'] = sma_fallback(df['Close'], 50)
177
+ df['EMA20'] = ema_fallback(df['Close'], 20)
178
+ # MACD fallback
179
+ macd, macd_signal, macd_hist = macd_fallback(df['Close'], 12, 26, 9)
180
+ df['MACD'], df['MACD_Signal'], df['MACD_Hist'] = macd, macd_signal, macd_hist
181
+ # Stoch fallback
182
+ stoch_k, stoch_d = stoch_fallback(df['High'], df['Low'], df['Close'], 14, 3)
183
+ df['StochK'], df['StochD'] = stoch_k, stoch_d
184
+ df['ATR14'] = atr_fallback(df['High'], df['Low'], df['Close'], period=14)
185
+
186
+ # SuperTrend (custom using ATR)
187
  try:
188
+ st_values, st_trend = supertrend_fallback(df, period=supertrend_period, multiplier=supertrend_mult)
189
+ df['SuperTrend'] = st_values
190
+ df['SuperTrendDir'] = st_trend
191
+ except Exception:
192
+ # if fails, fill NaN
193
+ df['SuperTrend'] = np.nan
194
+ df['SuperTrendDir'] = np.nan
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
+ # Keltner Channel
197
+ try:
198
+ kc_upper, kc_lower = keltner_channel(df, ema_period=keltner_ema, atr_period=keltner_atr, atr_mult=keltner_mult)
199
+ df['KC_Upper'] = kc_upper
200
+ df['KC_Lower'] = kc_lower
201
+ except Exception:
202
+ df['KC_Upper'] = np.nan
203
+ df['KC_Lower'] = np.nan
204
+
205
+ # ZigZag
206
+ try:
207
+ df['ZigZag'] = zigzag_fallback(df['Close'], pct=zigzag_pct)
208
+ except Exception:
209
+ df['ZigZag'] = np.nan
210
+
211
+ # Swing High/Low
212
+ try:
213
+ sh, sl = swing_high_low(df, window=5)
214
+ df['SwingHigh'] = sh
215
+ df['SwingLow'] = sl
216
+ except Exception:
217
+ df['SwingHigh'] = np.nan
218
+ df['SwingLow'] = np.nan
219
+
220
+ # OBV or Volume smoothing if needed (not required but left as data)
221
+ df['VolumeSmoothed'] = df['Volume'].rolling(3).mean()
222
+
223
+ # Build Plotly subplots: 4 rows (price, volume, MACD, Stochastic)
224
+ fig = make_subplots(rows=4, cols=1, shared_xaxes=True,
225
+ row_heights=[0.5, 0.12, 0.18, 0.18],
226
+ specs=[[{"secondary_y": False}],
227
+ [{"secondary_y": False}],
228
+ [{"secondary_y": False}],
229
+ [{"secondary_y": False}]])
230
+
231
+ x = df.index
232
+
233
+ # Row 1: Candlestick
234
+ fig.add_trace(go.Candlestick(x=x, open=df['Open'], high=df['High'], low=df['Low'], close=df['Close'],
235
+ name='Price', increasing_line_color='green', decreasing_line_color='red'), row=1, col=1)
236
+
237
+ # SuperTrend (line, colored by trend)
238
+ fig.add_trace(go.Scatter(x=x, y=df['SuperTrend'], mode='lines', name='SuperTrend', visible=True,
239
+ line=dict(width=2, dash='dash')), row=1, col=1)
240
+
241
+ # SMA & EMA
242
+ fig.add_trace(go.Scatter(x=x, y=df['SMA20'], mode='lines', name='SMA20', visible=True, line=dict(width=1)), row=1, col=1)
243
+ fig.add_trace(go.Scatter(x=x, y=df['SMA50'], mode='lines', name='SMA50', visible=False, line=dict(width=1)), row=1, col=1)
244
+ fig.add_trace(go.Scatter(x=x, y=df['EMA20'], mode='lines', name='EMA20', visible=False, line=dict(width=1, dash='dot')), row=1, col=1)
245
+
246
+ # Keltner Channels
247
+ fig.add_trace(go.Scatter(x=x, y=df['KC_Upper'], mode='lines', name='KC Upper', visible=False, line=dict(width=1)), row=1, col=1)
248
+ fig.add_trace(go.Scatter(x=x, y=df['KC_Lower'], mode='lines', name='KC Lower', visible=False, line=dict(width=1)), row=1, col=1)
249
+
250
+ # ZigZag and Swing markers as markers on price panel
251
+ fig.add_trace(go.Scatter(x=x, y=df['ZigZag'], mode='markers', name='ZigZag', visible=False,
252
+ marker=dict(symbol='diamond', size=8)), row=1, col=1)
253
+ fig.add_trace(go.Scatter(x=x, y=df['SwingHigh'], mode='markers', name='Swing High', visible=False,
254
+ marker=dict(symbol='triangle-up', size=8, color='purple')), row=1, col=1)
255
+ fig.add_trace(go.Scatter(x=x, y=df['SwingLow'], mode='markers', name='Swing Low', visible=False,
256
+ marker=dict(symbol='triangle-down', size=8, color='brown')), row=1, col=1)
257
+
258
+ # Row 2: Volume
259
+ fig.add_trace(go.Bar(x=x, y=df['Volume'], name='Volume', marker_color='lightblue', visible=True), row=2, col=1)
260
+
261
+ # Row 3: MACD (line + signal + histogram as bar)
262
+ fig.add_trace(go.Scatter(x=x, y=df['MACD'], mode='lines', name='MACD', visible=False), row=3, col=1)
263
+ fig.add_trace(go.Scatter(x=x, y=df['MACD_Signal'], mode='lines', name='MACD Signal', visible=False), row=3, col=1)
264
+ fig.add_trace(go.Bar(x=x, y=df['MACD_Hist'], name='MACD Hist', visible=False), row=3, col=1)
265
+
266
+ # Row 4: Stochastic (%K & %D)
267
+ fig.add_trace(go.Scatter(x=x, y=df['StochK'], mode='lines', name='Stoch %K', visible=False), row=4, col=1)
268
+ fig.add_trace(go.Scatter(x=x, y=df['StochD'], mode='lines', name='Stoch %D', visible=False), row=4, col=1)
269
+
270
+ # Layout defaults
271
+ fig.update_layout(
272
+ template="plotly_white",
273
+ xaxis_rangeslider_visible=False,
274
+ height=1000,
275
+ legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)
276
+ )
277
+
278
+ # Prepare updatemenu buttons for toggling indicators
279
+ # Determine trace indices in the order they were added:
280
+ # 0 price candlestick
281
+ # 1 supertrend
282
+ # 2 SMA20
283
+ # 3 SMA50
284
+ # 4 EMA20
285
+ # 5 KC Upper
286
+ # 6 KC Lower
287
+ # 7 ZigZag
288
+ # 8 SwingHigh
289
+ # 9 SwingLow
290
+ # 10 Volume
291
+ # 11 MACD
292
+ # 12 MACD Signal
293
+ # 13 MACD Hist
294
+ # 14 StochK
295
+ # 15 StochD
296
+
297
+ total_traces = len(fig.data) # should be 16
298
+
299
+ # Helper to create visible array with selective traces visible
300
+ def visible_array(show_indices):
301
+ arr = [False] * total_traces
302
+ # keep price and volume visible by default in any selection for context
303
+ arr[0] = True # candle
304
+ arr[10] = True # volume (index may vary—check length; we've added bar at position 10)
305
+ for i in show_indices:
306
+ if 0 <= i < total_traces:
307
+ arr[i] = True
308
+ return arr
309
+
310
+ # Map indicator buttons to the trace indices they affect
311
+ buttons = []
312
+
313
+ # SuperTrend
314
+ buttons.append(dict(label="SuperTrend",
315
+ method="restyle",
316
+ args=[{"visible": visible_array([1])}]))
317
+
318
+ # SMA20
319
+ buttons.append(dict(label="SMA20",
320
+ method="restyle",
321
+ args=[{"visible": visible_array([2])}]))
322
+
323
+ # SMA50
324
+ buttons.append(dict(label="SMA50",
325
+ method="restyle",
326
+ args=[{"visible": visible_array([3])}]))
327
+
328
+ # EMA20
329
+ buttons.append(dict(label="EMA20",
330
+ method="restyle",
331
+ args=[{"visible": visible_array([4])}]))
332
+
333
+ # Keltner Channel
334
+ buttons.append(dict(label="Keltner",
335
+ method="restyle",
336
+ args=[{"visible": visible_array([5,6])}]))
337
+
338
+ # ZigZag
339
+ buttons.append(dict(label="ZigZag",
340
+ method="restyle",
341
+ args=[{"visible": visible_array([7])}]))
342
+
343
+ # Swing HI/LO
344
+ buttons.append(dict(label="Swing H/L",
345
+ method="restyle",
346
+ args=[{"visible": visible_array([8,9])}]))
347
+
348
+ # MACD
349
+ buttons.append(dict(label="MACD",
350
+ method="restyle",
351
+ args=[{"visible": visible_array([11,12,13])}]))
352
+
353
+ # Stochastic
354
+ buttons.append(dict(label="Stochastic",
355
+ method="restyle",
356
+ args=[{"visible": visible_array([14,15])}]))
357
+
358
+ # Show All (turn on all traces)
359
+ buttons.append(dict(label="All On",
360
+ method="restyle",
361
+ args=[{"visible": [True]*total_traces}]))
362
+
363
+ # All Off (only show price and volume)
364
+ buttons.append(dict(label="All Off",
365
+ method="restyle",
366
+ args=[{"visible": visible_array([])}]))
367
+
368
+ # Reset (default view: price, volume, supertrend, SMA20)
369
+ buttons.append(dict(label="Reset",
370
+ method="restyle",
371
+ args=[{"visible": visible_array([1,2])}]))
372
+
373
+ fig.update_layout(
374
+ updatemenus=[dict(type="buttons", direction="down", buttons=buttons, x=1.02, xanchor="left", y=0.95, yanchor="top")]
375
+ )
376
+
377
+ # Finalize: add a table (recent 30 rows) below chart area as HTML
378
+ table_html = df.tail(30)[['Open', 'High', 'Low', 'Close', 'Volume']].round(2).to_html(classes="styled-table", border=0)
379
+
380
+ chart_div = fig.to_html(full_html=False, include_plotlyjs='cdn')
381
+
382
+ content_html = f"""
383
+ <div class='card'>
384
+ <h2>Daily Chart - {symbol.upper()}</h2>
385
+ <div class='card-content-grid'>
386
+ <div style="width:100%">{chart_div}</div>
387
+ </div>
388
+ </div>
389
+ <div class='big-box'>
390
+ <h2>Recent Daily Data (last 30 rows)</h2>
391
+ {table_html}
392
+ </div>
393
+ """
394
 
395
+ return wrap_html(f"Daily Chart for {symbol}", content_html, style_block=STYLE_BLOCK)