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

Update app/daily.py

Browse files
Files changed (1) hide show
  1. app/daily.py +37 -140
app/daily.py CHANGED
@@ -4,93 +4,27 @@ 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")
20
  end = dt.strptime(date_end, "%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")
@@ -102,87 +36,50 @@ def fetch_daily(symbol, date_end, date_start, spark_days=10):
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>")
 
4
  from datetime import datetime as dt
5
  import traceback
6
  from . import persist
7
+ from .common import wrap_html
 
 
 
 
8
 
9
  # ===========================================================
10
  # RAW DAILY FETCHER
11
  # ===========================================================
12
  def daily(symbol, date_end, date_start):
13
+ """Fetch daily OHLCV from Yahoo Finance."""
 
14
  start = dt.strptime(date_start, "%d-%m-%Y").strftime("%Y-%m-%d")
15
  end = dt.strptime(date_end, "%d-%m-%Y").strftime("%Y-%m-%d")
 
16
  df = yf.download(symbol + ".NS", start=start, end=end)
 
17
  if isinstance(df.columns, pd.MultiIndex):
18
  df.columns = df.columns.get_level_values(0)
 
19
  df.columns.name = None
20
  df.index.name = None
 
21
  return df
22
 
23
  # ===========================================================
24
+ # DAILY TABLE GENERATOR
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  # ===========================================================
26
+ def fetch_daily(symbol, date_end, date_start):
27
+ """Return HTML table of daily stock data."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  key = f"daily_{symbol}"
29
  if persist.exists(key, "html"):
30
  cached = persist.load(key, "html")
 
36
  if df.empty:
37
  return wrap_html(f"<h1>No daily data for {symbol}</h1>")
38
 
39
+ # Ensure numeric columns
 
 
 
40
  for col in ["Open","High","Low","Close","Adj Close","Volume"]:
41
  if col in df.columns:
42
  df[col] = pd.to_numeric(df[col], errors='coerce')
43
  df = df.dropna(subset=["Open","High","Low","Close","Volume"]).reset_index(drop=True)
44
 
45
+ # Ensure Date column
46
  df["Date"] = pd.to_datetime(df.index if "Date" not in df.columns else df["Date"], errors='coerce')
47
  df = df.dropna(subset=["Date"]).reset_index(drop=True)
48
  df["Date"] = df["Date"].dt.strftime("%d-%b-%Y")
 
 
 
49
 
50
+ # Safe Adj Close
51
+ if "Adj Close" not in df.columns:
52
+ df["Adj Close"] = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ # Daily change %
55
+ df["Change %"] = ((df["Close"] - df["Open"]) / df["Open"] * 100).round(2)
 
 
 
 
 
 
 
 
 
 
56
 
57
+ # Build HTML table
58
+ html_table = f'<div id="daily_dashboard" style="max-height:600px; overflow:auto; font-family:Arial,sans-serif;">'
59
+ html_table += '<table border="1" style="border-collapse:collapse; width:100%;">'
60
+ html_table += '<thead style="position:sticky; top:0; background:#1a4f8a; color:white;">'
61
+ html_table += '<tr><th>Date</th><th>Open</th><th>High</th><th>Low</th><th>Close</th><th>Adj Close</th><th>Volume</th><th>Change %</th></tr>'
62
+ html_table += '</thead><tbody>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ for idx, r in df.iterrows():
65
+ row_color = "#e8f5e9" if idx % 2 == 0 else "#f5f5f5"
66
+ change_color = "green" if r["Change %"] > 0 else "red" if r["Change %"] < 0 else "black"
67
+
68
+ html_table += f'<tr style="background:{row_color};">'
69
+ html_table += f'<td>{r["Date"]}</td>'
70
+ html_table += f'<td>{r["Open"]}</td>'
71
+ html_table += f'<td>{r["High"]}</td>'
72
+ html_table += f'<td>{r["Low"]}</td>'
73
+ html_table += f'<td>{r["Close"]}</td>'
74
+ html_table += f'<td>{r["Adj Close"]}</td>'
75
+ html_table += f'<td>{r["Volume"]}</td>'
76
+ html_table += f'<td style="color:{change_color}; font-weight:600;">{r["Change %"]}%</td>'
77
+ html_table += '</tr>'
78
+
79
+ html_table += '</tbody></table></div>'
80
+
81
+ persist.save(key, html_table, "html")
82
+ return html_table
83
 
84
  except Exception as e:
85
  return wrap_html(f"<h1>Error fetch_daily: {e}</h1><pre>{traceback.format_exc()}</pre>")