|
|
|
@ -1,4 +1,5 @@
|
|
|
|
|
import logging
|
|
|
|
|
import asyncio
|
|
|
|
|
from typing import Union, List, Dict
|
|
|
|
|
|
|
|
|
|
from aiohttp.client import ClientSession
|
|
|
|
@ -6,7 +7,8 @@ from bs4 import BeautifulSoup
|
|
|
|
|
from bs4.element import Tag
|
|
|
|
|
from webutils import in_async_session, gather_in_batches
|
|
|
|
|
|
|
|
|
|
from . import constants
|
|
|
|
|
from .constants import (HTML_PARSER, BASE_URL, END_DATE, BS, IS, CF, FIN_STMT_URL_SUFFIX, FIN_STMT_ITEMS,
|
|
|
|
|
DEFAULT_CONCURRENT_BATCH_SIZE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
@ -25,7 +27,7 @@ async def soup_from_url(url: str, session: ClientSession = None) -> BeautifulSou
|
|
|
|
|
"""
|
|
|
|
|
async with session.get(url) as response:
|
|
|
|
|
html = await response.text()
|
|
|
|
|
return BeautifulSoup(html, constants.HTML_PARSER)
|
|
|
|
|
return BeautifulSoup(html, HTML_PARSER)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def extract_end_dates(soup: BeautifulSoup) -> tuple[str]:
|
|
|
|
@ -43,7 +45,7 @@ def is_relevant_table_row(tr: Tag) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
item_name = str(tr.td.div.string).strip()
|
|
|
|
|
try:
|
|
|
|
|
return constants.FINANCIAL_STATEMENT_ITEMS[item_name]
|
|
|
|
|
return FIN_STMT_ITEMS[item_name]
|
|
|
|
|
except KeyError:
|
|
|
|
|
log.warning(f"Unknown item name '{item_name}' found in financial statement.")
|
|
|
|
|
return False
|
|
|
|
@ -73,7 +75,7 @@ def extract_all_data(soup: BeautifulSoup) -> ResultDict:
|
|
|
|
|
"""
|
|
|
|
|
Extracts financials from the page.
|
|
|
|
|
"""
|
|
|
|
|
output = {constants.END_DATE: extract_end_dates(soup)}
|
|
|
|
|
output = {END_DATE: extract_end_dates(soup)}
|
|
|
|
|
for row in find_relevant_table_rows(soup):
|
|
|
|
|
row_data = extract_row_data(row)
|
|
|
|
|
output[row_data[0]] = row_data[1]
|
|
|
|
@ -86,7 +88,7 @@ async def _get_single_company_fin_stmt(statement: str, ticker_symbol: str, quart
|
|
|
|
|
"""
|
|
|
|
|
Returns data from the specified financial statement of the specified company.
|
|
|
|
|
"""
|
|
|
|
|
url = f'{constants.BASE_URL}/{ticker_symbol}/financials{constants.FIN_STMT_URL_SUFFIX[statement]}'
|
|
|
|
|
url = f'{BASE_URL}/{ticker_symbol}/financials{FIN_STMT_URL_SUFFIX[statement]}'
|
|
|
|
|
if quarterly:
|
|
|
|
|
url += '/quarter'
|
|
|
|
|
soup = await soup_from_url(url, session)
|
|
|
|
@ -95,70 +97,69 @@ async def _get_single_company_fin_stmt(statement: str, ticker_symbol: str, quart
|
|
|
|
|
|
|
|
|
|
@in_async_session
|
|
|
|
|
async def _get_multi_companies_fin_stmt(statement: str, *ticker_symbols: str, quarterly: bool = False,
|
|
|
|
|
concurrent_batch_size: int = constants.DEFAULT_CONCURRENT_BATCH_SIZE,
|
|
|
|
|
concurrent_batch_size: int = DEFAULT_CONCURRENT_BATCH_SIZE,
|
|
|
|
|
session: ClientSession = None) -> Union[ResultDict, Dict[str, ResultDict]]:
|
|
|
|
|
if len(ticker_symbols) == 1:
|
|
|
|
|
return await _get_single_company_fin_stmt(statement, ticker_symbols[0], quarterly, session)
|
|
|
|
|
result_list = await gather_in_batches(
|
|
|
|
|
concurrent_batch_size,
|
|
|
|
|
*(_get_single_company_fin_stmt(statement, symbol, quarterly, session) for symbol in ticker_symbols)
|
|
|
|
|
)
|
|
|
|
|
coroutines = (_get_single_company_fin_stmt(statement, symbol, quarterly, session) for symbol in ticker_symbols)
|
|
|
|
|
result_list = await gather_in_batches(concurrent_batch_size, *coroutines)
|
|
|
|
|
return {symbol: data for symbol, data in zip(ticker_symbols, result_list)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@in_async_session
|
|
|
|
|
async def get_balance_sheet(*ticker_symbols: str, quarterly: bool = False,
|
|
|
|
|
concurrent_batch_size: int = constants.DEFAULT_CONCURRENT_BATCH_SIZE,
|
|
|
|
|
concurrent_batch_size: int = DEFAULT_CONCURRENT_BATCH_SIZE,
|
|
|
|
|
session: ClientSession = None) -> Union[ResultDict, Dict[str, ResultDict]]:
|
|
|
|
|
"""
|
|
|
|
|
Returns data from the balance sheet of the specified company.
|
|
|
|
|
"""
|
|
|
|
|
return await _get_multi_companies_fin_stmt(constants.BS, *ticker_symbols,
|
|
|
|
|
return await _get_multi_companies_fin_stmt(BS, *ticker_symbols,
|
|
|
|
|
quarterly=quarterly, concurrent_batch_size=concurrent_batch_size,
|
|
|
|
|
session=session)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@in_async_session
|
|
|
|
|
async def get_income_statement(*ticker_symbols: str, quarterly: bool = False,
|
|
|
|
|
concurrent_batch_size: int = constants.DEFAULT_CONCURRENT_BATCH_SIZE,
|
|
|
|
|
concurrent_batch_size: int = DEFAULT_CONCURRENT_BATCH_SIZE,
|
|
|
|
|
session: ClientSession = None) -> Union[ResultDict, Dict[str, ResultDict]]:
|
|
|
|
|
"""
|
|
|
|
|
Returns data from the income statement of the specified company.
|
|
|
|
|
"""
|
|
|
|
|
return await _get_multi_companies_fin_stmt(constants.IS, *ticker_symbols,
|
|
|
|
|
return await _get_multi_companies_fin_stmt(IS, *ticker_symbols,
|
|
|
|
|
quarterly=quarterly, concurrent_batch_size=concurrent_batch_size,
|
|
|
|
|
session=session)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@in_async_session
|
|
|
|
|
async def get_cash_flow_statement(*ticker_symbols: str, quarterly: bool = False,
|
|
|
|
|
concurrent_batch_size: int = constants.DEFAULT_CONCURRENT_BATCH_SIZE,
|
|
|
|
|
concurrent_batch_size: int = DEFAULT_CONCURRENT_BATCH_SIZE,
|
|
|
|
|
session: ClientSession = None) -> Union[ResultDict, Dict[str, ResultDict]]:
|
|
|
|
|
"""
|
|
|
|
|
Returns data from the cash flow statement of the specified company.
|
|
|
|
|
"""
|
|
|
|
|
return await _get_multi_companies_fin_stmt(constants.CF, *ticker_symbols,
|
|
|
|
|
return await _get_multi_companies_fin_stmt(CF, *ticker_symbols,
|
|
|
|
|
quarterly=quarterly, concurrent_batch_size=concurrent_batch_size,
|
|
|
|
|
session=session)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@in_async_session
|
|
|
|
|
async def _get_single_company_financials(ticker_symbol: str, quarterly: bool = False,
|
|
|
|
|
session: ClientSession = None) -> Dict[str, ResultDict]:
|
|
|
|
|
return {
|
|
|
|
|
constants.BS: await _get_single_company_fin_stmt(constants.BS, ticker_symbol, quarterly, session),
|
|
|
|
|
constants.IS: await _get_single_company_fin_stmt(constants.IS, ticker_symbol, quarterly, session),
|
|
|
|
|
constants.CF: await _get_single_company_fin_stmt(constants.CF, ticker_symbol, quarterly, session)
|
|
|
|
|
}
|
|
|
|
|
async def _get_single_company_all_financials(ticker_symbol: str, quarterly: bool = False,
|
|
|
|
|
session: ClientSession = None) -> Dict[str, ResultDict]:
|
|
|
|
|
coroutines = (_get_single_company_fin_stmt(stmt, ticker_symbol, quarterly, session) for stmt in (BS, IS, CF))
|
|
|
|
|
results = await asyncio.gather(*coroutines)
|
|
|
|
|
return {stmt: data for stmt, data in zip((BS, IS, CF), results)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@in_async_session
|
|
|
|
|
async def get_company_financials(*ticker_symbols: str, quarterly: bool = False,
|
|
|
|
|
session: ClientSession = None) -> Union[Dict[str, ResultDict],
|
|
|
|
|
Dict[str, Dict[str, ResultDict]]]:
|
|
|
|
|
async def get_all_financials(*ticker_symbols: str, quarterly: bool = False,
|
|
|
|
|
concurrent_batch_size: int = DEFAULT_CONCURRENT_BATCH_SIZE,
|
|
|
|
|
session: ClientSession = None) -> Union[Dict[str, ResultDict],
|
|
|
|
|
Dict[str, Dict[str, ResultDict]]]:
|
|
|
|
|
"""
|
|
|
|
|
Returns all fundamentals (balance sheet, income statement and cash flow statement) of the specified company.
|
|
|
|
|
"""
|
|
|
|
|
if len(ticker_symbols) == 1:
|
|
|
|
|
return await _get_single_company_financials(ticker_symbols[0], quarterly, session)
|
|
|
|
|
return {symbol: await _get_single_company_financials(symbol, quarterly, session) for symbol in ticker_symbols}
|
|
|
|
|
return await _get_single_company_all_financials(ticker_symbols[0], quarterly, session)
|
|
|
|
|
coroutines = (_get_single_company_all_financials(symbol, quarterly, session) for symbol in ticker_symbols)
|
|
|
|
|
result_list = await gather_in_batches(concurrent_batch_size, *coroutines)
|
|
|
|
|
return {symbol: data for symbol, data in zip(ticker_symbols, result_list)}
|
|
|
|
|