eshan6704 commited on
Commit
2d7c30d
·
verified ·
1 Parent(s): 3972191

Update app/daily.py

Browse files
Files changed (1) hide show
  1. app/daily.py +139 -29
app/daily.py CHANGED
@@ -3,15 +3,17 @@ import yfinance as yf
3
  import pandas as pd
4
  from datetime import datetime as dt
5
  import traceback
6
-
7
  from . import persist
8
- from .common import wrap_html
 
 
 
 
9
 
10
  # ===========================================================
11
  # RAW DAILY FETCHER
12
  # ===========================================================
13
  def daily(symbol, date_end, date_start):
14
- """Fetch daily OHLCV from Yahoo Finance."""
15
  print(f"[{dt.now().strftime('%Y-%m-%d %H:%M:%S')}] yf called for {symbol}")
16
 
17
  start = dt.strptime(date_start, "%d-%m-%Y").strftime("%Y-%m-%d")
@@ -19,60 +21,168 @@ def daily(symbol, date_end, date_start):
19
 
20
  df = yf.download(symbol + ".NS", start=start, end=end)
21
 
22
- # Flatten MultiIndex columns if present
23
  if isinstance(df.columns, pd.MultiIndex):
24
  df.columns = df.columns.get_level_values(0)
25
 
26
- # Remove column names / DataFrame name to avoid "Price" display
27
  df.columns.name = None
28
  df.index.name = None
29
 
30
  return df
31
 
32
  # ===========================================================
33
- # FETCH DAILY HTML TABLE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  # ===========================================================
35
- def fetch_daily(symbol, date_end, date_start):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  key = f"daily_{symbol}"
37
  if persist.exists(key, "html"):
38
  cached = persist.load(key, "html")
39
  if cached:
40
- print(f"[{date_end}] Using cached daily for {symbol}")
41
  return cached
42
 
43
  try:
44
  df = daily(symbol, date_end, date_start)
45
- if df is None or df.empty:
46
- return wrap_html(f'<div id="daily_wrapper"><h1>No daily data for {symbol}</h1></div>')
47
 
48
- # Reset index if not simple RangeIndex
49
  if not isinstance(df.index, pd.RangeIndex):
50
  df.reset_index(inplace=True)
51
-
52
- # Convert numeric columns safely
53
- numeric_cols = ["Open","High","Low","Close","Adj Close","Volume"]
54
- for col in numeric_cols:
55
  if col in df.columns:
56
  df[col] = pd.to_numeric(df[col], errors='coerce')
57
-
58
- # Drop rows with missing essential data
59
  df = df.dropna(subset=["Open","High","Low","Close","Volume"]).reset_index(drop=True)
60
 
61
  # Format date
62
- if "Date" in df.columns:
63
- df["Date"] = pd.to_datetime(df["Date"], errors='coerce')
64
- df = df.dropna(subset=["Date"]).reset_index(drop=True)
65
- df["Date"] = df["Date"].dt.strftime("%d-%b-%Y")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
- # Remove column name again just in case
68
- df.columns.name = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
- # Build HTML table WITHOUT any DataFrame name
71
- html_table = f'<div id="daily_table"><h2>{symbol} Daily Data</h2>{df.to_html(index=False, header=True, border=1, classes="daily-data", escape=False)}</div>'
72
 
73
- # Save to cache
74
- persist.save(key, html_table, "html")
75
- return html_table
 
76
 
77
  except Exception as e:
78
- return wrap_html(f'<div id="daily_wrapper"><h1>Error fetch_daily: {e}</h1><pre>{traceback.format_exc()}</pre></div>')
 
3
  import pandas as pd
4
  from datetime import datetime as dt
5
  import traceback
 
6
  from . import persist
7
+ from .common import wrap_html, format_large_number
8
+
9
+ # Plotly
10
+ import plotly.graph_objs as go
11
+ from plotly.subplots import make_subplots
12
 
13
  # ===========================================================
14
  # RAW DAILY FETCHER
15
  # ===========================================================
16
  def daily(symbol, date_end, date_start):
 
17
  print(f"[{dt.now().strftime('%Y-%m-%d %H:%M:%S')}] yf called for {symbol}")
18
 
19
  start = dt.strptime(date_start, "%d-%m-%Y").strftime("%Y-%m-%d")
 
21
 
22
  df = yf.download(symbol + ".NS", start=start, end=end)
23
 
 
24
  if isinstance(df.columns, pd.MultiIndex):
25
  df.columns = df.columns.get_level_values(0)
26
 
 
27
  df.columns.name = None
28
  df.index.name = None
29
 
30
  return df
31
 
32
  # ===========================================================
33
+ # TECHNICAL INDICATORS
34
+ # ===========================================================
35
+ def add_indicators(df):
36
+ df["SMA10"] = df["Close"].rolling(10).mean()
37
+ df["SMA50"] = df["Close"].rolling(50).mean()
38
+ df["SMA200"] = df["Close"].rolling(200).mean()
39
+ df["EMA20"] = df["Close"].ewm(span=20, adjust=False).mean()
40
+ delta = df["Close"].diff()
41
+ gain = (delta.where(delta>0,0)).rolling(14).mean()
42
+ loss = (-delta.where(delta<0,0)).rolling(14).mean()
43
+ rs = gain/loss
44
+ df["RSI14"] = 100 - (100/(1+rs))
45
+ df["Change %"] = ((df["Close"] - df["Open"])/df["Open"]*100).round(2)
46
+ return df
47
+
48
+ # ===========================================================
49
+ # SPARKLINE & MINI CANDLE
50
  # ===========================================================
51
+ def sparkline(values, height=20, color="#1a4f8a"):
52
+ if len(values) == 0: return ""
53
+ max_val = max(values) if max(values)!=0 else 1
54
+ bars = "".join([f'<div style="display:inline-block;width:3px;height:{int(v/max_val*height)}px;margin-right:1px;background:{color};"></div>' for v in values])
55
+ return f'<div style="display:flex; align-items:flex-end;">{bars}</div>'
56
+
57
+ def volume_bar(values, height=20, color="#5584d6"):
58
+ if len(values) == 0: return ""
59
+ max_val = max(values) if max(values)!=0 else 1
60
+ bars = "".join([f'<div style="display:inline-block;width:3px;height:{int(v/max_val*height)}px;margin-right:1px;background:{color};"></div>' for v in values])
61
+ return f'<div style="display:flex; align-items:flex-end;">{bars}</div>'
62
+
63
+ # ===========================================================
64
+ # PLOTLY CHART GENERATOR
65
+ # ===========================================================
66
+ def plotly_dashboard(df, symbol):
67
+ fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
68
+ vertical_spacing=0.05, row_heights=[0.7,0.3],
69
+ subplot_titles=[f"{symbol} OHLC + SMA/EMA", "Volume"])
70
+
71
+ # Candlestick
72
+ fig.add_trace(go.Candlestick(
73
+ x=df.index, open=df["Open"], high=df["High"], low=df["Low"], close=df["Close"],
74
+ name="OHLC"), row=1, col=1)
75
+
76
+ # Moving averages
77
+ for col, name in [("SMA10","SMA10"),("SMA50","SMA50"),("SMA200","SMA200"),("EMA20","EMA20")]:
78
+ fig.add_trace(go.Scatter(
79
+ x=df.index, y=df[col], mode="lines", line=dict(width=1.5), name=name), row=1, col=1)
80
+
81
+ # Volume bars
82
+ fig.add_trace(go.Bar(
83
+ x=df.index, y=df["Volume"], marker_color="#5584d6", name="Volume"), row=2, col=1)
84
+
85
+ fig.update_layout(height=700, showlegend=True, margin=dict(t=50,b=50), template="plotly_white")
86
+
87
+ # Return div as HTML
88
+ return fig.to_html(full_html=False, include_plotlyjs='cdn')
89
+
90
+ # ===========================================================
91
+ # DASHBOARD GENERATOR
92
+ # ===========================================================
93
+ def fetch_daily(symbol, date_end, date_start, spark_days=10):
94
  key = f"daily_{symbol}"
95
  if persist.exists(key, "html"):
96
  cached = persist.load(key, "html")
97
  if cached:
 
98
  return cached
99
 
100
  try:
101
  df = daily(symbol, date_end, date_start)
102
+ if df.empty:
103
+ return wrap_html(f"<h1>No daily data for {symbol}</h1>")
104
 
 
105
  if not isinstance(df.index, pd.RangeIndex):
106
  df.reset_index(inplace=True)
107
+
108
+ # Numeric conversion
109
+ for col in ["Open","High","Low","Close","Adj Close","Volume"]:
 
110
  if col in df.columns:
111
  df[col] = pd.to_numeric(df[col], errors='coerce')
 
 
112
  df = df.dropna(subset=["Open","High","Low","Close","Volume"]).reset_index(drop=True)
113
 
114
  # Format date
115
+ df["Date"] = pd.to_datetime(df.index if "Date" not in df.columns else df["Date"], errors='coerce')
116
+ df = df.dropna(subset=["Date"]).reset_index(drop=True)
117
+ df["Date"] = df["Date"].dt.strftime("%d-%b-%Y")
118
+
119
+ # Add indicators
120
+ df = add_indicators(df)
121
+
122
+ # Summary
123
+ summary_html = f"""
124
+ <div style="margin-bottom:10px; font-family:Arial,sans-serif;">
125
+ <h3>{symbol} Summary</h3>
126
+ <table border="1" style="border-collapse:collapse; width:400px;">
127
+ <tr><th>Metric</th><th>Value</th></tr>
128
+ <tr><td>Start Date</td><td>{df['Date'].iloc[0]}</td></tr>
129
+ <tr><td>End Date</td><td>{df['Date'].iloc[-1]}</td></tr>
130
+ <tr><td>Min Close</td><td>{format_large_number(df['Close'].min())}</td></tr>
131
+ <tr><td>Max Close</td><td>{format_large_number(df['Close'].max())}</td></tr>
132
+ <tr><td>Mean Close</td><td>{format_large_number(df['Close'].mean())}</td></tr>
133
+ <tr><td>Total Volume</td><td>{format_large_number(df['Volume'].sum())}</td></tr>
134
+ <tr><td>Avg Daily Change %</td><td>{df['Change %'].mean():.2f}%</td></tr>
135
+ <tr><td>Latest Close</td><td>{df['Close'].iloc[-1]}</td></tr>
136
+ <tr><td>Prev Close</td><td>{df['Close'].iloc[-2] if len(df)>1 else df['Close'].iloc[-1]}</td></tr>
137
+ </table>
138
+ </div>
139
+ """
140
+
141
+ # Table with sparkline, volume, mini candle
142
+ html_table = f"""
143
+ <div style="max-height:400px; overflow:auto; font-family:Arial,sans-serif;">
144
+ <table border="1" style="border-collapse:collapse; width:100%;">
145
+ <thead style="position:sticky; top:0; background:#1a4f8a; color:white;">
146
+ <tr>
147
+ <th>Date</th><th>Open</th><th>High</th><th>Low</th><th>Close</th><th>Adj Close</th>
148
+ <th>Volume</th><th>Change %</th><th>Close Trend</th><th>Vol Trend</th><th>Mini Candle</th>
149
+ </tr>
150
+ </thead>
151
+ <tbody>
152
+ """
153
+
154
+ for idx, r in df.iterrows():
155
+ row_color = "#e8f5e9" if idx%2==0 else "#f5f5f5"
156
+ change_color = "green" if r["Change %"]>0 else "red" if r["Change %"]<0 else "black"
157
+ start_idx = max(0, idx-spark_days+1)
158
+ close_trend = sparkline(df["Close"].iloc[start_idx:idx+1].tolist())
159
+ vol_trend = volume_bar(df["Volume"].iloc[start_idx:idx+1].tolist())
160
+ mini_c = sparkline([r["Open"], r["High"], r["Low"], r["Close"]], height=20, color="#1a4f8a")
161
 
162
+ html_table += f"""
163
+ <tr style='background:{row_color};'>
164
+ <td>{r['Date']}</td>
165
+ <td>{r['Open']}</td>
166
+ <td>{r['High']}</td>
167
+ <td>{r['Low']}</td>
168
+ <td>{r['Close']}</td>
169
+ <td>{r.get('Adj Close','')}</td>
170
+ <td>{r['Volume']}</td>
171
+ <td style='color:{change_color}; font-weight:600;'>{r['Change %']}%</td>
172
+ <td>{close_trend}</td>
173
+ <td>{vol_trend}</td>
174
+ <td>{mini_c}</td>
175
+ </tr>
176
+ """
177
+ html_table += "</tbody></table></div>"
178
 
179
+ # Plotly chart
180
+ chart_html = plotly_dashboard(df, symbol)
181
 
182
+ # Combine full HTML
183
+ full_html = summary_html + html_table + chart_html
184
+ persist.save(key, full_html, "html")
185
+ return full_html
186
 
187
  except Exception as e:
188
+ return wrap_html(f"<h1>Error fetch_daily: {e}</h1><pre>{traceback.format_exc()}</pre>")