Skip to content

Commit

Permalink
Fixes for:
Browse files Browse the repository at this point in the history
- ownership report
- ratios
- financial statements
  • Loading branch information
gnzsnz committed Sep 8, 2024
1 parent 848ca0f commit 28a86ad
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 104 deletions.
128 changes: 44 additions & 84 deletions ib_fundamental/fundamental.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from datetime import datetime
from typing import Optional

import pandas as pd
from ib_async import IB, Dividends, FundamentalRatios, Stock, Ticker
from pandas import DataFrame

Expand All @@ -45,11 +44,8 @@
OwnershipReport,
RatioSnapshot,
Revenue,
StatementCode,
StatementData,
statement_type,
)
from ib_fundamental.utils import to_dataframe
from ib_fundamental.utils import build_statement, to_dataframe

from .ib_client import IBClient
from .xml_parser import XMLParser
Expand Down Expand Up @@ -94,7 +90,7 @@ def income_annual(self) -> IncomeSet:
try:
return self.__income_annual
except AttributeError:
self.__income_annual = self.parser.get_fin_statement(
self.__income_annual: IncomeSet = self.parser.get_fin_statement(
statement="INC", period="annual"
)
return self.__income_annual
Expand All @@ -105,7 +101,7 @@ def income_quarter(self) -> IncomeSet:
try:
return self.__income_quarter
except AttributeError:
self.__income_quarter = self.parser.get_fin_statement(
self.__income_quarter: IncomeSet = self.parser.get_fin_statement(
statement="INC", period="quarter"
)
return self.__income_quarter
Expand All @@ -115,7 +111,7 @@ def balance_annual(self) -> BalanceSheetSet:
try:
return self.__balance_annual
except AttributeError:
self.__balance_annual = self.parser.get_fin_statement(
self.__balance_annual: BalanceSheetSet = self.parser.get_fin_statement(
statement="BAL", period="annual"
)
return self.__balance_annual
Expand All @@ -125,7 +121,7 @@ def balance_quarter(self) -> BalanceSheetSet:
try:
return self.__balance_quarter
except AttributeError:
self.__balance_quarter = self.parser.get_fin_statement(
self.__balance_quarter: BalanceSheetSet = self.parser.get_fin_statement(
statement="BAL", period="quarter"
)
return self.__balance_quarter
Expand All @@ -135,7 +131,7 @@ def cashflow_annual(self) -> CashFlowSet:
try:
return self.__cashflow_annual
except AttributeError:
self.__cashflow_annual = self.parser.get_fin_statement(
self.__cashflow_annual: CashFlowSet = self.parser.get_fin_statement(
statement="CAS", period="annual"
)
return self.__cashflow_annual
Expand All @@ -145,7 +141,7 @@ def cashflow_quarter(self) -> CashFlowSet:
try:
return self.__cashflow_quarter
except AttributeError:
self.__cashflow_quarter = self.parser.get_fin_statement(
self.__cashflow_quarter: CashFlowSet = self.parser.get_fin_statement(
statement="CAS", period="quarter"
)
return self.__cashflow_quarter
Expand All @@ -156,7 +152,9 @@ def ownership_report(self) -> OwnershipReport:
try:
return self.__ownership_report
except AttributeError:
self.__ownership_report = self.parser.get_ownership_report()
self.__ownership_report: OwnershipReport = (
self.parser.get_ownership_report()
)
return self.__ownership_report

@property
Expand Down Expand Up @@ -192,55 +190,65 @@ def revenue_ttm(self) -> list[Revenue]:
try:
return self.__revenue_ttm
except AttributeError:
self.__revenue_ttm = self.parser.get_revenue(report_type="TTM")
self.__revenue_ttm: list[Revenue] = self.parser.get_revenue(
report_type="TTM"
)
return self.__revenue_ttm

@property
def revenue_q(self) -> list[Revenue]:
try:
return self.__revenue_q
except AttributeError:
self.__revenue_q = self.parser.get_revenue(report_type="R", period="3M")
self.__revenue_q: list[Revenue] = self.parser.get_revenue(
report_type="R", period="3M"
)
return self.__revenue_q

@property
def eps_ttm(self) -> list[EarningsPerShare]:
try:
return self.__eps_ttm
except AttributeError:
self.__eps_ttm = self.parser.get_eps(report_type="TTM")
self.__eps_ttm: list[EarningsPerShare] = self.parser.get_eps(
report_type="TTM"
)
return self.__eps_ttm

@property
def eps_q(self) -> list[EarningsPerShare]:
try:
return self.__eps_q
except AttributeError:
self.__eps_q = self.parser.get_eps(report_type="R", period="3M")
self.__eps_q: list[EarningsPerShare] = self.parser.get_eps(
report_type="R", period="3M"
)
return self.__eps_q

@property
def analyst_forecast(self) -> AnalystForecast:
try:
return self.__analyst_forecast
except AttributeError:
self.__analyst_forecast = self.parser.get_analyst_forecast()
self.__analyst_forecast: AnalystForecast = (
self.parser.get_analyst_forecast()
)
return self.__analyst_forecast

@property
def ratios(self) -> RatioSnapshot:
try:
return self.__ratios
except AttributeError:
self.__ratios = self.parser.get_ratios()
self.__ratios: RatioSnapshot = self.parser.get_ratios()
return self.__ratios

@property
def fundamental_ratios(self) -> FundamentalRatios:
try:
return self.__fundamental_ratios
except AttributeError:
self.__fundamental_ratios = self.client.get_ratios()
self.__fundamental_ratios: FundamentalRatios = self.client.get_ratios()
self.ticker = self.client.ib.ticker(self.contract)
return self.__fundamental_ratios

Expand All @@ -249,7 +257,7 @@ def dividend_summary(self) -> Dividends:
try:
return self.__dividend_summary
except AttributeError:
self.__dividend_summary = self.client.get_dividends()
self.__dividend_summary: Dividends = self.client.get_dividends()
self.ticker = self.client.ib.ticker(self.contract)
return self.__dividend_summary

Expand All @@ -258,23 +266,23 @@ def fy_estimates(self) -> list[ForwardYear]:
try:
return self.__fy_estimates
except AttributeError:
self.__fy_estimates = self.parser.get_fy_estimates()
self.__fy_estimates: list[ForwardYear] = self.parser.get_fy_estimates()
return self.__fy_estimates

@property
def fy_actuals(self) -> list[ForwardYear]:
try:
return self.__fy_actuals
except AttributeError:
self.__fy_actuals = self.parser.get_fy_actuals()
self.__fy_actuals: list[ForwardYear] = self.parser.get_fy_actuals()
return self.__fy_actuals

@property
def company_info(self) -> CompanyInfo:
try:
return self.__company_info
except AttributeError:
self.__company_info: CompanyFinancials = self.parser.get_company_info()
self.__company_info: CompanyInfo = self.parser.get_company_info()
return self.__company_info


Expand All @@ -299,95 +307,47 @@ def __repr__(self):
cls_name = self.__class__.__qualname__
return f"{cls_name}(symbol={self.data.symbol!r},IB={self.data.client.ib!r})"

def _get_data_frame(
self,
statement: StatementData,
) -> DataFrame:
"""Build dataframe for pp"""
_df = to_dataframe(statement)
return _df.T.sort_index(axis=1, ascending=False) # sort columns

def _get_map_items(self, stat_code: StatementCode) -> DataFrame:
"""build map items for pp"""
_df = to_dataframe(self.data.parser.get_map_items(statement=stat_code))
_df.coa_item = _df.coa_item.str.lower()
return _df

def _get_header(
self, data: DataFrame, statement_code: StatementCode, idx: int = 6
) -> DataFrame:
"""build header for pp"""
_header = data.iloc[:idx]
_header = (
_header.assign(line_id=range(idx))
.assign(statement_type=statement_code)
.reset_index()
.rename(columns={"index": "map_item"})
)
return _header.assign(coa_item=_header["map_item"])

def _join(
self, data: DataFrame, header: DataFrame, mapping: DataFrame, idx: int
) -> DataFrame:
"""join data to present"""
_pp = mapping.join(data, on="coa_item")
_df = pd.concat([header, _pp]).set_index("line_id")

(_names,) = _df.loc[
_df["coa_item"] == "end_date", _df.columns[1:idx]
].values.tolist()
_l = _df.columns.to_list()
_l[1:idx] = _names
_df.columns = _l
_df.statement_type = _df.statement_type.map(lambda x: statement_type[x])
_df = _df.drop(columns="coa_item").dropna()
return _df

def _build_statement(
self, data: StatementData, statement_code: StatementCode, idx: int
) -> DataFrame:
"""build statement pp"""
_map = self._get_map_items(stat_code=statement_code)
_data = self._get_data_frame(statement=data)
_header = self._get_header(data=_data, statement_code=statement_code)
# pp
return self._join(data=_data, header=_header, mapping=_map, idx=idx)

@property
def balance_quarter(self) -> DataFrame | None:
"""Quarterly balance statement"""
if self.data.balance_quarter:
return self._build_statement(self.data.balance_quarter, "BAL", 6)
mapping = self.data.parser.get_map_items("BAL")
return build_statement(self.data.balance_quarter, "BAL", mapping)
return None

@property
def balance_annual(self) -> DataFrame | None:
if self.data.balance_annual:
return self._build_statement(self.data.balance_annual, "BAL", 7)
mapping = self.data.parser.get_map_items("BAL")
return build_statement(self.data.balance_annual, "BAL", mapping)
return None

@property
def income_quarter(self) -> DataFrame | None:
if self.data.income_quarter:
return self._build_statement(self.data.income_quarter, "INC", 6)
mapping = self.data.parser.get_map_items("INC")
return build_statement(self.data.income_quarter, "INC", mapping)
return None

@property
def income_annual(self) -> DataFrame | None:
if self.data.income_annual:
return self._build_statement(self.data.income_annual, "INC", 7)
mapping = self.data.parser.get_map_items("INC")
return build_statement(self.data.income_annual, "INC", mapping)
return None

@property
def cashflow_quarter(self) -> DataFrame | None:
if self.data.cashflow_annual:
return self._build_statement(self.data.cashflow_quarter, "CAS", 6)
mapping = self.data.parser.get_map_items("CAS")
return build_statement(self.data.cashflow_quarter, "CAS", mapping)
return None

@property
def cashflow_annual(self) -> DataFrame | None:
if self.data.cashflow_annual:
return self._build_statement(self.data.cashflow_annual, "CAS", 7)
mapping = self.data.parser.get_map_items("CAS")
return build_statement(self.data.cashflow_annual, "CAS", mapping)
return None

@property
Expand Down Expand Up @@ -465,7 +425,7 @@ def company_information(self) -> DataFrame | None:
@property
def ratios(self) -> DataFrame | None:
if self.data.ratios:
return to_dataframe([self.data.ratios]).T
return to_dataframe([self.data.ratios]).T.dropna()
return None

@property
Expand Down
50 changes: 32 additions & 18 deletions ib_fundamental/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,21 +299,35 @@ class RatioSnapshot:
vol10davg: float
ev: float
mktcap: float
ttmrev: float
ttmebitd: float
ttmniac: float
ttmepsxclx: float
ttmrevps: float
qbvps: float
qcshps: float
ttmcfshr: float
ttmdivshr: float
ttmgrosmgn: float
ttmroepct: float
ttmpr2rev: float
peexclxor: float
price2bk: float
employees: float
ttmrev: Optional[float] = None
ttmebitd: Optional[float] = None
ttmniac: Optional[float] = None
ttmepsxclx: Optional[float] = None
ttmrevps: Optional[float] = None
qbvps: Optional[float] = None
qcshps: Optional[float] = None
ttmcfshr: Optional[float] = None
ttmdivshr: Optional[float] = None
ttmgrosmgn: Optional[float] = None
ttmroepct: Optional[float] = None
ttmpr2rev: Optional[float] = None
peexclxor: Optional[float] = None
price2bk: Optional[float] = None
arev: Optional[float] = None
aebitd: Optional[float] = None
aniac: Optional[float] = None
aepsxclxor: Optional[float] = None
arevps: Optional[float] = None
abvps: Optional[float] = None
acshps: Optional[float] = None
acfshr: Optional[float] = None
adivshr: Optional[float] = None
agrosmgn: Optional[float] = None
aroepct: Optional[float] = None
apr2rev: Optional[float] = None
apeexclxor: Optional[float] = None
aprice2bk: Optional[float] = None
employees: Optional[float] = None


@dataclass(slots=True)
Expand Down Expand Up @@ -352,7 +366,7 @@ class OwnershipCompany:

ISIN: str # pylint: disable=invalid-name
float_shares: int
as_of_date: datetime
as_of_date: datetime | None


@dataclass(slots=True)
Expand All @@ -371,5 +385,5 @@ class OwnershipDetails:
class OwnershipReport:
"""Ownership Report"""

company: OwnershipCompany
ownership_details: list[OwnershipDetails]
company: OwnershipCompany | None = None
ownership_details: list[OwnershipDetails] | None = None
Loading

0 comments on commit 28a86ad

Please sign in to comment.