Spaces:
Sleeping
Sleeping
Update app/daily.py
Browse files- app/daily.py +55 -14
app/daily.py
CHANGED
|
@@ -5,6 +5,8 @@ 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
|
|
@@ -17,14 +19,53 @@ def daily(symbol, date_end, date_start):
|
|
| 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 = "Date"
|
| 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")
|
|
@@ -37,31 +78,27 @@ def fetch_daily(symbol, date_end, date_start):
|
|
| 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","
|
| 41 |
if col in df.columns:
|
| 42 |
df[col] = pd.to_numeric(df[col], errors='coerce')
|
| 43 |
|
| 44 |
# Drop rows with missing OHLCV
|
| 45 |
df = df.dropna(subset=["Open","High","Low","Close","Volume"]).reset_index()
|
| 46 |
|
| 47 |
-
# Format date
|
| 48 |
df["Date"] = pd.to_datetime(df["Date"], errors='coerce')
|
| 49 |
df = df.dropna(subset=["Date"]).reset_index(drop=True)
|
| 50 |
df["Date"] = df["Date"].dt.strftime("%d-%b-%Y")
|
| 51 |
|
| 52 |
-
# Ensure Adj Close exists
|
| 53 |
-
if "Adj Close" not in df.columns:
|
| 54 |
-
df["Adj Close"] = ""
|
| 55 |
-
|
| 56 |
# Daily change %
|
| 57 |
df["Change %"] = ((df["Close"] - df["Open"]) / df["Open"] * 100).round(2)
|
| 58 |
|
| 59 |
# Build HTML table
|
| 60 |
-
html_table = '<div
|
| 61 |
html_table += '<table border="1" style="border-collapse:collapse; width:100%;">'
|
| 62 |
html_table += '<thead style="position:sticky; top:0; background:#1a4f8a; color:white;">'
|
| 63 |
html_table += '<tr><th>Date</th><th>Open</th><th>High</th><th>Low</th>'
|
| 64 |
-
html_table += '<th>Close</th><th>
|
| 65 |
html_table += '</thead><tbody>'
|
| 66 |
|
| 67 |
for idx, r in df.iterrows():
|
|
@@ -74,15 +111,19 @@ def fetch_daily(symbol, date_end, date_start):
|
|
| 74 |
html_table += f'<td>{r["High"]}</td>'
|
| 75 |
html_table += f'<td>{r["Low"]}</td>'
|
| 76 |
html_table += f'<td>{r["Close"]}</td>'
|
| 77 |
-
html_table += f'<td>{r["Adj Close"]}</td>'
|
| 78 |
html_table += f'<td>{r["Volume"]}</td>'
|
| 79 |
html_table += f'<td style="color:{change_color}; font-weight:600;">{r["Change %"]}%</td>'
|
| 80 |
html_table += '</tr>'
|
| 81 |
-
|
| 82 |
html_table += '</tbody></table></div>'
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
except Exception as e:
|
| 88 |
return wrap_html(f"<h1>Error fetch_daily: {e}</h1><pre>{traceback.format_exc()}</pre>")
|
|
|
|
| 5 |
import traceback
|
| 6 |
from . import persist
|
| 7 |
from .common import wrap_html
|
| 8 |
+
import plotly.graph_objects as go
|
| 9 |
+
import plotly.io as pio
|
| 10 |
|
| 11 |
# ===========================================================
|
| 12 |
# RAW DAILY FETCHER
|
|
|
|
| 19 |
if isinstance(df.columns, pd.MultiIndex):
|
| 20 |
df.columns = df.columns.get_level_values(0)
|
| 21 |
df.columns.name = None
|
| 22 |
+
df.index.name = "Date"
|
| 23 |
return df
|
| 24 |
|
| 25 |
+
# ===========================================================
|
| 26 |
+
# PLOTLY CANDLESTICK
|
| 27 |
+
# ===========================================================
|
| 28 |
+
def plot_candlestick(df, symbol):
|
| 29 |
+
fig = go.Figure()
|
| 30 |
+
|
| 31 |
+
# Candlestick
|
| 32 |
+
fig.add_trace(go.Candlestick(
|
| 33 |
+
x=df['Date'],
|
| 34 |
+
open=df['Open'],
|
| 35 |
+
high=df['High'],
|
| 36 |
+
low=df['Low'],
|
| 37 |
+
close=df['Close'],
|
| 38 |
+
name='Price',
|
| 39 |
+
increasing_line_color='green',
|
| 40 |
+
decreasing_line_color='red'
|
| 41 |
+
))
|
| 42 |
+
|
| 43 |
+
# Volume as bar chart
|
| 44 |
+
fig.add_trace(go.Bar(
|
| 45 |
+
x=df['Date'],
|
| 46 |
+
y=df['Volume'],
|
| 47 |
+
name='Volume',
|
| 48 |
+
marker_color='blue',
|
| 49 |
+
yaxis='y2',
|
| 50 |
+
opacity=0.3
|
| 51 |
+
))
|
| 52 |
+
|
| 53 |
+
# Layout
|
| 54 |
+
fig.update_layout(
|
| 55 |
+
title=f'{symbol} Daily Candlestick',
|
| 56 |
+
xaxis_rangeslider_visible=False,
|
| 57 |
+
yaxis=dict(title='Price'),
|
| 58 |
+
yaxis2=dict(title='Volume', overlaying='y', side='right', showgrid=False),
|
| 59 |
+
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
return pio.to_html(fig, full_html=False, include_plotlyjs='cdn')
|
| 63 |
+
|
| 64 |
# ===========================================================
|
| 65 |
# DAILY TABLE GENERATOR
|
| 66 |
# ===========================================================
|
| 67 |
def fetch_daily(symbol, date_end, date_start):
|
| 68 |
+
"""Return HTML table + candlestick chart of daily stock data."""
|
| 69 |
key = f"daily_{symbol}"
|
| 70 |
if persist.exists(key, "html"):
|
| 71 |
cached = persist.load(key, "html")
|
|
|
|
| 78 |
return wrap_html(f"<h1>No daily data for {symbol}</h1>")
|
| 79 |
|
| 80 |
# Ensure numeric columns
|
| 81 |
+
for col in ["Open","High","Low","Close","Volume"]:
|
| 82 |
if col in df.columns:
|
| 83 |
df[col] = pd.to_numeric(df[col], errors='coerce')
|
| 84 |
|
| 85 |
# Drop rows with missing OHLCV
|
| 86 |
df = df.dropna(subset=["Open","High","Low","Close","Volume"]).reset_index()
|
| 87 |
|
| 88 |
+
# Format date
|
| 89 |
df["Date"] = pd.to_datetime(df["Date"], errors='coerce')
|
| 90 |
df = df.dropna(subset=["Date"]).reset_index(drop=True)
|
| 91 |
df["Date"] = df["Date"].dt.strftime("%d-%b-%Y")
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
# Daily change %
|
| 94 |
df["Change %"] = ((df["Close"] - df["Open"]) / df["Open"] * 100).round(2)
|
| 95 |
|
| 96 |
# Build HTML table
|
| 97 |
+
html_table = '<div style="max-height:300px; overflow:auto; font-family:Arial,sans-serif; margin-bottom:20px;">'
|
| 98 |
html_table += '<table border="1" style="border-collapse:collapse; width:100%;">'
|
| 99 |
html_table += '<thead style="position:sticky; top:0; background:#1a4f8a; color:white;">'
|
| 100 |
html_table += '<tr><th>Date</th><th>Open</th><th>High</th><th>Low</th>'
|
| 101 |
+
html_table += '<th>Close</th><th>Volume</th><th>Change %</th></tr>'
|
| 102 |
html_table += '</thead><tbody>'
|
| 103 |
|
| 104 |
for idx, r in df.iterrows():
|
|
|
|
| 111 |
html_table += f'<td>{r["High"]}</td>'
|
| 112 |
html_table += f'<td>{r["Low"]}</td>'
|
| 113 |
html_table += f'<td>{r["Close"]}</td>'
|
|
|
|
| 114 |
html_table += f'<td>{r["Volume"]}</td>'
|
| 115 |
html_table += f'<td style="color:{change_color}; font-weight:600;">{r["Change %"]}%</td>'
|
| 116 |
html_table += '</tr>'
|
|
|
|
| 117 |
html_table += '</tbody></table></div>'
|
| 118 |
|
| 119 |
+
# Build candlestick chart
|
| 120 |
+
chart_html = plot_candlestick(df, symbol)
|
| 121 |
+
|
| 122 |
+
# Combine
|
| 123 |
+
full_html = f'<div id="daily_dashboard">{html_table}{chart_html}</div>'
|
| 124 |
+
|
| 125 |
+
persist.save(key, full_html, "html")
|
| 126 |
+
return full_html
|
| 127 |
|
| 128 |
except Exception as e:
|
| 129 |
return wrap_html(f"<h1>Error fetch_daily: {e}</h1><pre>{traceback.format_exc()}</pre>")
|