Remove references to utils in lieu of just using pylib.
[kiosk.git] / stock_renderer.py
1 #!/usr/bin/env python3
2
3 import logging
4 from typing import Dict, List, Optional, Tuple
5
6 import yfinance as yf  # type: ignore
7
8 import file_writer
9 import renderer
10
11
12 logger = logging.getLogger(__file__)
13
14
15 class stock_quote_renderer(renderer.abstaining_renderer):
16     """Render the stock prices page."""
17
18     def __init__(
19             self,
20             name_to_timeout_dict: Dict[str, int],
21             symbols: List[str],
22             display_subs: Dict[str, str] = None,
23     ) -> None:
24         super().__init__(name_to_timeout_dict)
25         self.symbols = symbols
26         self.display_subs = display_subs
27
28     @staticmethod
29     def get_ticker_name(ticker: yf.ticker.Ticker) -> str:
30         """Get friendly name of a ticker."""
31         info = ticker.get_info()
32         if "shortName" in info:
33             return info["shortName"]
34         return ticker
35
36     @staticmethod
37     def get_price(ticker: yf.ticker.Ticker) -> Optional[float]:
38         """Get most recent price of a ticker."""
39         keys = [
40             "bid",
41             "ask",
42             "regularMarketPrice",
43             "lastMarket",
44             "open",
45             "previousClose",
46         ]
47         info = ticker.get_info()
48         for key in keys:
49             if key in info and info[key] is not None and info[key] != 0.0:
50                 print(f"Price: picked {key}, ${info[key]}.")
51                 return float(info[key])
52         return None
53
54     @staticmethod
55     def get_change_and_delta(
56         ticker: yf.ticker.Ticker, price: float
57     ) -> Tuple[float, float]:
58         """Given the current price, look up opening price and compute delta."""
59         keys = [
60             "previousClose",
61             "open",
62         ]
63         info = ticker.get_info()
64         for key in keys:
65             if key in info and info[key] is not None:
66                 print(f"Change: picked {key}, ${info[key]}.")
67                 old_price = float(info[key])
68                 delta = price - old_price
69                 return (delta / old_price * 100.0, delta)
70         return (0.0, 0.0)
71
72     def periodic_render(self, key: str) -> bool:
73         """Write an up-to-date stock page."""
74         with file_writer.file_writer("stock_3_86400.html") as f:
75             f.write("<H1>Stock Quotes</H1><HR>")
76             f.write("<TABLE WIDTH=99%>")
77             symbols_finished = 0
78             for symbol in self.symbols:
79                 ticker = yf.Ticker(symbol)
80                 # print(ticker.get_info())
81                 if ticker is None:
82                     logger.debug(f"Unknown symbol {symbol} -- ignored.")
83                     continue
84                 name = stock_quote_renderer.get_ticker_name(ticker)
85                 price = stock_quote_renderer.get_price(ticker)
86                 if price is None:
87                     logger.debug(f"No price information for {symbol} -- skipped.")
88                     continue
89                 (percent_change, delta) = stock_quote_renderer.get_change_and_delta(
90                     ticker, price
91                 )
92                 # print(f"delta: {delta}, change: {percent_change}")
93                 cell_color = "#b00000" if percent_change < 0 else "#009000"
94                 if symbols_finished % 4 == 0:
95                     if symbols_finished > 0:
96                         f.write("</TR>")
97                         f.write("<TR>")
98                 symbols_finished += 1
99                 if self.display_subs is not None and symbol in self.display_subs:
100                     symbol = self.display_subs[symbol]
101                 f.write(
102                     f"""
103 <TD WIDTH=20% HEIGHT=150 BGCOLOR="{cell_color}">
104   <!-- Container -->
105   <DIV style="position:relative;
106               height:150px;">
107     <!-- Symbol {symbol} -->
108     <DIV style="position:absolute;
109                 bottom:50;
110                 right:-20;
111                 -webkit-transform:rotate(-90deg);
112                 font-size:28pt;
113                 font-family: helvetica, arial, sans-serif;
114                 font-weight:900;
115                 -webkit-text-stroke: 2px black;
116                 color: #ddd">
117       {symbol}
118     </DIV>
119     <!-- Current price, Change today and percent change today, name -->
120     <DIV style="position:absolute;
121                 left:10;
122                 top:20;
123                 font-size:23pt;
124                 font-family: helvetica, arial, sans-serif;
125                 width:70%">
126             ${price:.2f}<BR>
127             <I>({percent_change:.1f}%)</I><BR>
128             <B>${delta:.2f}</B>
129     </DIV>
130   </DIV>
131 </TD>"""
132                 )
133             f.write("</TR></TABLE>")
134         return True
135
136 # Test
137 #x = stock_quote_renderer({}, ["MSFT", "GOOG", "BTC-USD", "OPTAX", "GC=F", "VNQ"], { "BTC-USD": "BTC", "GC=F": "GOLD" })
138 #x.periodic_render(None)