Spaces:
Sleeping
Sleeping
Completely replace Screener network block with Finology scraping
Browse files- forecaster_cli.py +51 -61
forecaster_cli.py
CHANGED
|
@@ -31,80 +31,70 @@ def get_current_historic_val(table_data, row_name):
|
|
| 31 |
return np.nan
|
| 32 |
|
| 33 |
def fetch_live_features(ticker):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
try:
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
try:
|
| 39 |
-
data = scraper.get_stock_info(ticker)
|
| 40 |
-
if "error" in data and "429" in data["error"]:
|
| 41 |
-
time.sleep(1.5)
|
| 42 |
-
continue
|
| 43 |
-
break
|
| 44 |
-
except Exception:
|
| 45 |
-
time.sleep(1)
|
| 46 |
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
if
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
pass
|
| 67 |
-
|
| 68 |
-
opm = get_current_historic_val(pl, 'OPM %')
|
| 69 |
-
net_profit = get_current_historic_val(pl, 'Net Profit')
|
| 70 |
-
equity = get_current_historic_val(bs, 'Equity Capital')
|
| 71 |
-
reserves = get_current_historic_val(bs, 'Reserves')
|
| 72 |
-
borrowings = get_current_historic_val(bs, 'Borrowings')
|
| 73 |
-
roce = get_current_historic_val(ratios, 'ROCE %')
|
| 74 |
-
|
| 75 |
-
sales_growth = ((sales - prev_sales) / prev_sales * 100) if pd.notnull(prev_sales) and prev_sales > 0 else np.nan
|
| 76 |
-
total_eq = equity + reserves if pd.notnull(equity) and pd.notnull(reserves) else np.nan
|
| 77 |
-
roe = (net_profit / total_eq * 100) if pd.notnull(total_eq) and total_eq > 0 else np.nan
|
| 78 |
-
debt_to_equity = (borrowings / total_eq) if pd.notnull(total_eq) and total_eq > 0 else np.nan
|
| 79 |
-
|
| 80 |
-
pe = np.nan
|
| 81 |
-
for metric in data.get('metrics', []):
|
| 82 |
-
if metric['name'] == 'Stock P/E':
|
| 83 |
-
pe = metric['value']
|
| 84 |
-
break
|
| 85 |
-
|
| 86 |
return {
|
| 87 |
'Ticker': ticker,
|
| 88 |
-
'Sales_Growth':
|
| 89 |
-
'OPM':
|
| 90 |
-
'ROCE':
|
| 91 |
-
'ROE':
|
| 92 |
'Debt_to_Equity': debt_to_equity,
|
| 93 |
-
'PE_Ratio':
|
| 94 |
}
|
| 95 |
except Exception:
|
| 96 |
return None
|
| 97 |
|
| 98 |
def get_market_cap_tier(ticker):
|
| 99 |
-
print(f"[{ticker}] Resolving market cap classification via
|
| 100 |
try:
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
except Exception as e:
|
| 109 |
print(f"Warning: Could not fetch market cap ({e}). Defaulting to Small.")
|
| 110 |
|
|
|
|
| 31 |
return np.nan
|
| 32 |
|
| 33 |
def fetch_live_features(ticker):
|
| 34 |
+
import requests
|
| 35 |
+
from bs4 import BeautifulSoup
|
| 36 |
+
import re
|
| 37 |
+
import numpy as np
|
| 38 |
+
|
| 39 |
try:
|
| 40 |
+
url = f"https://ticker.finology.in/company/{ticker}"
|
| 41 |
+
html = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}).text
|
| 42 |
+
soup = BeautifulSoup(html, 'html.parser')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
+
def clean(val):
|
| 45 |
+
if not val: return np.nan
|
| 46 |
+
try: return float(re.sub(r'[^\d.]', '', val))
|
| 47 |
+
except: return np.nan
|
| 48 |
|
| 49 |
+
data = {}
|
| 50 |
+
for div in soup.find_all('div', class_=re.compile(r'compess')):
|
| 51 |
+
text = div.get_text(" ", strip=True)
|
| 52 |
+
if "P/E" in text: data['PE_Ratio'] = clean(text.replace('P/E', ''))
|
| 53 |
+
if "Sales Growth" in text: data['Sales_Growth'] = clean(text.replace('Sales Growth', ''))
|
| 54 |
+
if "ROE" in text: data['ROE'] = clean(text.replace('ROE', ''))
|
| 55 |
+
if "ROCE" in text: data['ROCE'] = clean(text.replace('ROCE', ''))
|
| 56 |
+
if "CASH" in text: data['CASH'] = clean(text.replace('CASH', ''))
|
| 57 |
+
if "DEBT " in text: data['DEBT'] = clean(text.replace('DEBT', ''))
|
| 58 |
+
if "Book Value" in text: data['BV'] = clean(text.replace('Book Value', ''))
|
| 59 |
+
if "No. of Shares" in text: data['Shares'] = clean(text.replace('No. of Shares', ''))
|
| 60 |
+
|
| 61 |
+
debt_to_equity = np.nan
|
| 62 |
+
if 'DEBT' in data and 'BV' in data and 'Shares' in data and data['BV'] > 0 and data['Shares'] > 0:
|
| 63 |
+
total_equity = data['BV'] * data['Shares']
|
| 64 |
+
debt_to_equity = data['DEBT'] / total_equity if total_equity > 0 else 0
|
| 65 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
return {
|
| 67 |
'Ticker': ticker,
|
| 68 |
+
'Sales_Growth': data.get('Sales_Growth', np.nan),
|
| 69 |
+
'OPM': np.nan, # Imputer will naturally handle this gracefully
|
| 70 |
+
'ROCE': data.get('ROCE', np.nan),
|
| 71 |
+
'ROE': data.get('ROE', np.nan),
|
| 72 |
'Debt_to_Equity': debt_to_equity,
|
| 73 |
+
'PE_Ratio': data.get('PE_Ratio', np.nan)
|
| 74 |
}
|
| 75 |
except Exception:
|
| 76 |
return None
|
| 77 |
|
| 78 |
def get_market_cap_tier(ticker):
|
| 79 |
+
print(f"[{ticker}] Resolving market cap classification via Finology...")
|
| 80 |
try:
|
| 81 |
+
import requests
|
| 82 |
+
from bs4 import BeautifulSoup
|
| 83 |
+
import re
|
| 84 |
+
url = f"https://ticker.finology.in/company/{ticker}"
|
| 85 |
+
html = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}).text
|
| 86 |
+
soup = BeautifulSoup(html, 'html.parser')
|
| 87 |
|
| 88 |
+
cap = 0
|
| 89 |
+
for div in soup.find_all('div', class_=re.compile(r'compess')):
|
| 90 |
+
text = div.get_text(" ", strip=True)
|
| 91 |
+
if "Market Cap" in text:
|
| 92 |
+
try: cap = float(re.sub(r'[^\d.]', '', text.replace('Market Cap', '')))
|
| 93 |
+
except: pass
|
| 94 |
+
|
| 95 |
+
if cap >= 20000: return "Large"
|
| 96 |
+
if cap >= 5000: return "Mid"
|
| 97 |
+
return "Small"
|
| 98 |
except Exception as e:
|
| 99 |
print(f"Warning: Could not fetch market cap ({e}). Defaulting to Small.")
|
| 100 |
|