Update daily.py
Browse files
daily.py
CHANGED
|
@@ -1,395 +1,35 @@
|
|
| 1 |
# daily.py
|
| 2 |
import yfinance as yf
|
| 3 |
import pandas as pd
|
| 4 |
-
|
| 5 |
-
import plotly.graph_objs as go
|
| 6 |
-
from plotly.subplots import make_subplots
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
TALIB_AVAILABLE = True
|
| 12 |
-
except Exception:
|
| 13 |
-
TALIB_AVAILABLE = False
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
|
| 18 |
-
def sma_fallback(series, period):
|
| 19 |
-
return series.rolling(period).mean()
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 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 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# daily.py
|
| 2 |
import yfinance as yf
|
| 3 |
import pandas as pd
|
| 4 |
+
from chart_builder import build_chart
|
|
|
|
|
|
|
| 5 |
|
| 6 |
+
def fetch_daily(symbol):
|
| 7 |
+
yfs = f"{symbol}.NS"
|
| 8 |
+
df = yf.download(yfs, period="1y", interval="1d").round(2)
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
+
if df.empty:
|
| 11 |
+
return {
|
| 12 |
+
"html": f"<h1>No daily data for {symbol}</h1>",
|
| 13 |
+
"data": {}
|
| 14 |
+
}
|
| 15 |
|
| 16 |
+
chart_html = build_chart(df)
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
table_html = df.tail(30).to_html(
|
| 19 |
+
classes="styled-table",
|
| 20 |
+
border=0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
)
|
| 22 |
|
| 23 |
+
final = f"""
|
| 24 |
+
<div class="group">
|
| 25 |
+
<h2>Daily Chart — {symbol}</h2>
|
| 26 |
+
{chart_html}
|
| 27 |
+
<h3>Last 30 Days Data</h3>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
{table_html}
|
| 29 |
</div>
|
| 30 |
"""
|
| 31 |
|
| 32 |
+
return {
|
| 33 |
+
"html": final,
|
| 34 |
+
"data": df.tail(30).to_dict()
|
| 35 |
+
}
|