Spaces:
Running
Running
Update app/yahooinfo.py
Browse files- app/yahooinfo.py +57 -19
app/yahooinfo.py
CHANGED
|
@@ -27,7 +27,6 @@ def yfinfo(symbol):
|
|
| 27 |
MAIN_ICONS = {
|
| 28 |
"Price / Volume": "π",
|
| 29 |
"Fundamentals": "π",
|
| 30 |
-
"Valuation": "π°",
|
| 31 |
"Trend": "π",
|
| 32 |
"Signals": "π§ ",
|
| 33 |
"Company Profile": "π’",
|
|
@@ -90,7 +89,7 @@ def html_card(title, body, mini=False, shade=0):
|
|
| 90 |
|
| 91 |
|
| 92 |
# ==============================
|
| 93 |
-
# Formatting
|
| 94 |
# ==============================
|
| 95 |
def human_number(n):
|
| 96 |
try:
|
|
@@ -103,8 +102,29 @@ def human_number(n):
|
|
| 103 |
return str(n)
|
| 104 |
|
| 105 |
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
try:
|
|
|
|
|
|
|
|
|
|
| 108 |
return datetime.fromtimestamp(v, tz=timezone.utc).strftime("%d %b %Y")
|
| 109 |
except:
|
| 110 |
return v
|
|
@@ -112,18 +132,24 @@ def format_date(v):
|
|
| 112 |
|
| 113 |
def format_value(k, v):
|
| 114 |
lk = k.lower()
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
if isinstance(v, (int, float)):
|
| 117 |
cls = "pos" if v > 0 else "neg" if v < 0 else ""
|
| 118 |
if "percent" in lk:
|
| 119 |
return f'<span class="{cls}">{v:.2f}%</span>'
|
| 120 |
-
if any(x in lk for x in [
|
|
|
|
|
|
|
|
|
|
| 121 |
return f'<span class="{cls}">βΉ{human_number(v)}</span>'
|
| 122 |
return f'<span class="{cls}">{human_number(v)}</span>'
|
| 123 |
|
| 124 |
-
if "date" in lk or "time" in lk:
|
| 125 |
-
return format_date(v)
|
| 126 |
-
|
| 127 |
return v
|
| 128 |
|
| 129 |
|
|
@@ -166,14 +192,17 @@ SHORT_NAMES = {
|
|
| 166 |
"returnOnEquity":"ROE",
|
| 167 |
"returnOnAssets":"ROA",
|
| 168 |
"profitMargins":"Margin",
|
| 169 |
-
"debtToEquity":"D/E"
|
|
|
|
|
|
|
|
|
|
| 170 |
}
|
| 171 |
|
| 172 |
PIN_PRICE = ["Price","Chg","Chg%","Open","High","Low","Vol"]
|
| 173 |
PIN_FUND = ["MCap","PE","PB","EPS","ROE","ROA","Margin","D/E"]
|
| 174 |
|
| 175 |
def pretty_key(k):
|
| 176 |
-
return SHORT_NAMES.get(k, k[:
|
| 177 |
|
| 178 |
|
| 179 |
# ==============================
|
|
@@ -181,8 +210,12 @@ def pretty_key(k):
|
|
| 181 |
# ==============================
|
| 182 |
def classify(k, v):
|
| 183 |
lk = k.lower()
|
| 184 |
-
if k == "companyOfficers":
|
| 185 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
return "fundamental"
|
| 187 |
if isinstance(v, (int, float)):
|
| 188 |
return "price_volume"
|
|
@@ -192,8 +225,13 @@ def classify(k, v):
|
|
| 192 |
|
| 193 |
|
| 194 |
def group_info(info):
|
| 195 |
-
g = {
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
for k,v in info.items():
|
| 198 |
if k in NOISE_KEYS or v in [None,"",[],{}]:
|
| 199 |
continue
|
|
@@ -205,12 +243,12 @@ def group_info(info):
|
|
| 205 |
# Builders
|
| 206 |
# ==============================
|
| 207 |
def build_df(data, pinned=None):
|
| 208 |
-
rows = []
|
| 209 |
-
for k,v in data.items():
|
| 210 |
-
rows.append((pretty_key(k), format_value(k,v)))
|
| 211 |
pinned = pinned or []
|
| 212 |
-
rows.sort(key=lambda x: (
|
| 213 |
-
|
|
|
|
|
|
|
| 214 |
return pd.DataFrame(rows, columns=["Field","Value"])
|
| 215 |
|
| 216 |
|
|
|
|
| 27 |
MAIN_ICONS = {
|
| 28 |
"Price / Volume": "π",
|
| 29 |
"Fundamentals": "π",
|
|
|
|
| 30 |
"Trend": "π",
|
| 31 |
"Signals": "π§ ",
|
| 32 |
"Company Profile": "π’",
|
|
|
|
| 89 |
|
| 90 |
|
| 91 |
# ==============================
|
| 92 |
+
# Formatting helpers
|
| 93 |
# ==============================
|
| 94 |
def human_number(n):
|
| 95 |
try:
|
|
|
|
| 102 |
return str(n)
|
| 103 |
|
| 104 |
|
| 105 |
+
# ---------- DATE FIX (ROBUST) ----------
|
| 106 |
+
DATE_KEYWORDS = (
|
| 107 |
+
"date", "time", "timestamp",
|
| 108 |
+
"fiscal", "quarter",
|
| 109 |
+
"earnings", "dividend"
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
def looks_like_unix_ts(v):
|
| 113 |
+
try:
|
| 114 |
+
v = int(v)
|
| 115 |
+
return (
|
| 116 |
+
946684800 <= v <= 4102444800 or # seconds
|
| 117 |
+
946684800000 <= v <= 4102444800000 # milliseconds
|
| 118 |
+
)
|
| 119 |
+
except:
|
| 120 |
+
return False
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def unix_to_date(v):
|
| 124 |
try:
|
| 125 |
+
v = int(v)
|
| 126 |
+
if v > 10**12:
|
| 127 |
+
v //= 1000
|
| 128 |
return datetime.fromtimestamp(v, tz=timezone.utc).strftime("%d %b %Y")
|
| 129 |
except:
|
| 130 |
return v
|
|
|
|
| 132 |
|
| 133 |
def format_value(k, v):
|
| 134 |
lk = k.lower()
|
| 135 |
+
|
| 136 |
+
# --- DATE HANDLING ---
|
| 137 |
+
if isinstance(v, (int, float)) and looks_like_unix_ts(v):
|
| 138 |
+
if any(x in lk for x in DATE_KEYWORDS):
|
| 139 |
+
return unix_to_date(v)
|
| 140 |
+
|
| 141 |
+
# --- NUMBERS ---
|
| 142 |
if isinstance(v, (int, float)):
|
| 143 |
cls = "pos" if v > 0 else "neg" if v < 0 else ""
|
| 144 |
if "percent" in lk:
|
| 145 |
return f'<span class="{cls}">{v:.2f}%</span>'
|
| 146 |
+
if any(x in lk for x in [
|
| 147 |
+
"marketcap","revenue","income",
|
| 148 |
+
"value","cap","enterprise"
|
| 149 |
+
]):
|
| 150 |
return f'<span class="{cls}">βΉ{human_number(v)}</span>'
|
| 151 |
return f'<span class="{cls}">{human_number(v)}</span>'
|
| 152 |
|
|
|
|
|
|
|
|
|
|
| 153 |
return v
|
| 154 |
|
| 155 |
|
|
|
|
| 192 |
"returnOnEquity":"ROE",
|
| 193 |
"returnOnAssets":"ROA",
|
| 194 |
"profitMargins":"Margin",
|
| 195 |
+
"debtToEquity":"D/E",
|
| 196 |
+
"mostRecentQuarter":"Recent Q",
|
| 197 |
+
"lastFiscalYearEnd":"FY End",
|
| 198 |
+
"nextFiscalYearEnd":"Next FY"
|
| 199 |
}
|
| 200 |
|
| 201 |
PIN_PRICE = ["Price","Chg","Chg%","Open","High","Low","Vol"]
|
| 202 |
PIN_FUND = ["MCap","PE","PB","EPS","ROE","ROA","Margin","D/E"]
|
| 203 |
|
| 204 |
def pretty_key(k):
|
| 205 |
+
return SHORT_NAMES.get(k, k[:16])
|
| 206 |
|
| 207 |
|
| 208 |
# ==============================
|
|
|
|
| 210 |
# ==============================
|
| 211 |
def classify(k, v):
|
| 212 |
lk = k.lower()
|
| 213 |
+
if k == "companyOfficers":
|
| 214 |
+
return "management"
|
| 215 |
+
if any(x in lk for x in [
|
| 216 |
+
"pe","pb","roe","roa","margin",
|
| 217 |
+
"debt","revenue","profit","eps","cap"
|
| 218 |
+
]):
|
| 219 |
return "fundamental"
|
| 220 |
if isinstance(v, (int, float)):
|
| 221 |
return "price_volume"
|
|
|
|
| 225 |
|
| 226 |
|
| 227 |
def group_info(info):
|
| 228 |
+
g = {
|
| 229 |
+
"price_volume": {},
|
| 230 |
+
"fundamental": {},
|
| 231 |
+
"profile": {},
|
| 232 |
+
"management": {},
|
| 233 |
+
"long_text": {}
|
| 234 |
+
}
|
| 235 |
for k,v in info.items():
|
| 236 |
if k in NOISE_KEYS or v in [None,"",[],{}]:
|
| 237 |
continue
|
|
|
|
| 243 |
# Builders
|
| 244 |
# ==============================
|
| 245 |
def build_df(data, pinned=None):
|
| 246 |
+
rows = [(pretty_key(k), format_value(k,v)) for k,v in data.items()]
|
|
|
|
|
|
|
| 247 |
pinned = pinned or []
|
| 248 |
+
rows.sort(key=lambda x: (
|
| 249 |
+
0 if x[0] in pinned else 1,
|
| 250 |
+
pinned.index(x[0]) if x[0] in pinned else x[0]
|
| 251 |
+
))
|
| 252 |
return pd.DataFrame(rows, columns=["Field","Value"])
|
| 253 |
|
| 254 |
|