5 from typing import Any, Dict, List, Optional, Tuple
7 import yfinance as yf # type: ignore
8 import plotly.graph_objects as go
11 import kiosk_constants
15 logger = logging.getLogger(__file__)
18 class stock_quote_renderer(renderer.abstaining_renderer):
19 """Render the stock prices page."""
23 name_to_timeout_dict: Dict[str, int],
25 display_subs: Dict[str, str] = None,
27 super().__init__(name_to_timeout_dict)
28 self.symbols = symbols
29 self.display_subs = display_subs
30 self.info_cache: Dict[yf.ticker.Ticker, Dict] = {}
32 def cache_info(self, ticker: yf.ticker.Ticker) -> Dict:
33 if ticker in self.info_cache:
34 return self.info_cache[ticker]
36 self.info_cache[ticker] = i
39 def get_ticker_name(self, ticker: yf.Ticker) -> str:
40 """Get friendly name of a ticker."""
41 info = self.cache_info(ticker)
42 if "shortName" in info:
43 return info["shortName"]
47 def get_item_from_dict(
48 keys: List[str], dictionary: Dict[str, Any]
52 result = dictionary.get(key, None)
57 def get_price(self, ticker: yf.Ticker) -> Optional[float]:
58 """Get most recent price of a ticker."""
61 price = stock_quote_renderer.get_item_from_dict(
62 ["last_price", "open", "previous_close"], ticker.fast_info
68 price = stock_quote_renderer.get_item_from_dict(
69 ["bid", "ask", "lastMarket"], self.cache_info(ticker)
74 # Finally, fall back on history
75 hist = ticker.history(period="1d").to_dict()["Close"]
78 for k, v in hist.items():
79 if latest is None or k > latest:
84 print(f"Price: fell back on latest close {latest_price} at {latest}")
88 def make_chart(symbol: str, ticker: yf.Ticker, period: str) -> str:
89 base_filename = f"stock_chart_{symbol}.png"
90 output_filename = os.path.join(kiosk_constants.pages_dir, base_filename)
91 transparent = go.Layout(
92 paper_bgcolor="rgba(0,0,0,0)",
93 plot_bgcolor="rgba(0,0,0,0)",
94 xaxis_rangeslider_visible=False,
96 hist = ticker.history(period=period, interval="1wk")
106 chart.update_xaxes(visible=False, showticklabels=False)
107 chart.update_yaxes(side="right")
108 chart.write_image(output_filename, format="png", width=600, height=350)
109 print(f"Write {output_filename}...")
112 def get_last_close(self, ticker: yf.Ticker) -> float:
113 last_close = stock_quote_renderer.get_item_from_dict(
114 ["previous_close", "open"], ticker.fast_info
119 last_close = stock_quote_renderer.get_item_from_dict(
120 ["preMarketPrice"], self.cache_info(ticker)
124 return self.get_price(ticker)
126 def get_change_and_delta(
127 self, ticker: yf.Ticker, price: float
128 ) -> Tuple[float, float]:
129 """Given the current price, look up opening price and compute delta."""
130 last_price = self.get_last_close(ticker)
131 delta = price - last_price
132 return (delta / last_price * 100.0, delta)
134 def periodic_render(self, key: str) -> bool:
135 """Write an up-to-date stock page."""
136 with file_writer.file_writer("stock_3_86400.html") as f:
137 f.write("<H1>Stock Quotes</H1><HR>")
138 f.write("<TABLE WIDTH=99%>")
140 for symbol in self.symbols:
141 ticker = yf.Ticker(symbol)
143 logger.debug(f"Unknown symbol {symbol} -- ignored.")
145 name = self.get_ticker_name(ticker)
147 logger.debug(f"Bad name for {symbol} -- skipped.")
149 price = self.get_price(ticker)
151 logger.debug(f"No price information for {symbol} -- skipped.")
153 (percent_change, delta) = self.get_change_and_delta(ticker, price)
154 chart_filename = stock_quote_renderer.make_chart(symbol, ticker, "1y")
155 print(f"delta: {delta}, change: {percent_change}")
156 if percent_change < 0:
157 cell_color = "#b00000"
158 elif percent_change > 0:
159 cell_color = "#009000"
161 cell_color = "#a0a0a0"
162 if symbols_finished % 4 == 0:
163 if symbols_finished > 0:
166 symbols_finished += 1
167 if self.display_subs is not None and symbol in self.display_subs:
168 symbol = self.display_subs[symbol]
171 <TD WIDTH=20% HEIGHT=150 BGCOLOR="{cell_color}">
173 <DIV style="position:relative;
175 <!-- Symbol {symbol} -->
176 <DIV style="position:absolute;
179 -webkit-transform:rotate(-90deg);
181 font-family: helvetica, arial, sans-serif;
183 -webkit-text-stroke: 2px black;
187 <!-- Current price, Change today and percent change today, name -->
188 <DIV style="position:absolute;
192 font-family:helvetica, arial, sans-serif;
196 <I>({percent_change:.1f}%)</I><BR>
199 <IMG SRC="{chart_filename}" WIDTH=100%>
203 f.write("</TR></TABLE>")
208 # x = stock_quote_renderer({}, ["MSFT", "GOOG", "BTC-USD", "ABHYX", "GC=F", "VNQ"], { "BTC-USD": "BTC", "GC=F": "GOLD" })
209 # x.periodic_render(None)