Skip to content

Commit

Permalink
add as of date
Browse files Browse the repository at this point in the history
  • Loading branch information
MarekOzana committed Mar 30, 2024
1 parent a9be258 commit 53fe2f9
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 31 deletions.
Binary file modified data/t_exp.parquet
Binary file not shown.
Binary file added data/t_keyfigures.parquet
Binary file not shown.
5 changes: 3 additions & 2 deletions src/charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,10 @@ def create_exp_chart(df: pl.DataFrame) -> alt.Chart:
----------
df: pl.DataFrame(name, m_rating, ticker, m_pct)
"""
as_of: date = df["date"].item(0)
brush = alt.selection_point(fields=["m_rating"])
rtgs = df["m_rating"].unique().to_list()
base = alt.Chart(df).encode(
base = alt.Chart(df.drop(columns="date")).encode(
color=alt.Color("m_rating:O").sort(rtgs).scale(scheme="blueorange")
)
f_rtg = (
Expand Down Expand Up @@ -254,7 +255,7 @@ def create_exp_chart(df: pl.DataFrame) -> alt.Chart:
rank="rank(mv_pct)", sort=[alt.SortField("mv_pct", order="descending")]
)
.transform_filter((alt.datum.rank <= 18))
.properties(title="Largest Ticker Exposures")
.properties(title=f"Largest Ticker Exposures (as of {as_of:%F})")
)
fig = f_rtg | f_ticker
return fig
49 changes: 43 additions & 6 deletions src/data_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
.. date:: 2024-03
"""

import argparse
import logging
import os
from pathlib import Path
Expand Down Expand Up @@ -284,7 +285,7 @@ def get_fund_exposures(self, name: str) -> pl.DataFrame:
df = (
self.t_exp.join(self.t_fund, on="portf")
.filter(pl.col("name") == name)
.select(["name", "m_rating", "ticker", "mv_pct"])
.select(["name", "date", "m_rating", "ticker", "mv_pct"])
.with_columns(pl.col("m_rating").fill_null("NR"))
)
return df
Expand Down Expand Up @@ -414,11 +415,12 @@ def __init__(self, f_name: Path) -> None:
Usage
-----
>>> u = Updater(f_name='tests/data/M_Fund.csv')
>>> u = Updater(f_name='tests/data/M_Funds.csv')
>>> u.save_t_exp_table(o_name='data/t_exp.parquet')
"""
self.tbl: pl.LazyFrame = self._import_fund_info(f_name)
self.as_of: date = self._extract_report_date(f_name)
logger.info(f"Parsing {f_name} as of {self.as_of:%F}")
self.tbl: pl.LazyFrame = self._import_fund_info(f_name)
# TODO: insert key figures and date

def _extract_report_date(self, f_name: Path) -> Optional[date]:
Expand All @@ -432,7 +434,7 @@ def _extract_report_date(self, f_name: Path) -> Optional[date]:
f_name (Path): The path to the text file from which the date is to be extracted.
Returns:
Optional[date]: The extracted date as a datetime.date object or None if no date is found.
Optional[date]: The extracted date as a datetime.date object or None
"""

# Define the regex pattern to find the date in the format M/D/YYYY
Expand Down Expand Up @@ -521,7 +523,7 @@ def save_t_exp_table(self, o_name: Path = Path("data/t_exp.parquet")) -> None:
df_exp: pl.DataFrame = (
self.tbl.group_by(["portf", "m_rating", "rating", "ticker"])
.agg(pl.col("mv_pct").sum().mul(0.01))
.filter(pl.col("ticker").is_not_null())
.filter(pl.col("ticker").is_not_null()) # Remove totals
.sort(
"portf",
"m_rating",
Expand All @@ -530,7 +532,42 @@ def save_t_exp_table(self, o_name: Path = Path("data/t_exp.parquet")) -> None:
descending=[True, False, False, True],
nulls_last=True,
)
.with_columns(pl.lit(self.as_of).alias("date")) # add as of date
.collect()
)
logger.info(f"Saving exposures to {o_name}")
logger.info(f"Saving {df_exp.shape} exposures to {o_name}")
df_exp.write_parquet(o_name)

def save_t_keyfigures_table(
self, o_name: Path = Path("data/t_keyfigures.parquet")
) -> None:
"""Create and save Key Figures to o_name"""
df_kf: pl.DataFrame = (
self.tbl.filter(pl.col("ticker").is_null())
.select(["portf", "ytc", "ytw", "oas", "zspread"])
.with_columns(pl.lit(self.as_of).alias("date"))
.melt(id_vars=["date", "portf"], variable_name="key")
.with_columns(pl.col("value").cast(pl.Float64))
.sort(by=["date", "portf", "key"])
.collect()
)
logger.info(f"Saving {df_kf.shape} key figures to {o_name}")
df_kf.write_parquet(o_name)


def main():
parser = argparse.ArgumentParser(description="Update Exposures and KeyFigures")
parser.add_argument("-f", "--file", required=True, help="M_Funds.csv file")

args = parser.parse_args()

logging.basicConfig(level=logging.INFO)
logger.setLevel(logging.INFO)
u = Updater(args.file)
u.save_t_exp_table(o_name=Path("data/t_exp.parquet"))
u.save_t_keyfigures_table(o_name=Path("data/t_keyfigures.parquet"))
logger.info("DONE")


if __name__ == "__main__":
main()
Binary file modified tests/data/df_chart_exp.parquet
Binary file not shown.
Binary file modified tests/data/t_exp.parquet
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/test_charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,6 @@ def test_create_exp_chart():
assert f_rtg.mark.type == "bar"
assert isinstance(f_ticker, alt.Chart)
assert f_ticker.mark == "bar"
assert f_ticker.title == "Largest Ticker Exposures"
assert f_ticker.title == "Largest Ticker Exposures (as of 2024-03-27)"
assert f_ticker.encoding.x.shorthand == "mv_pct:Q"
assert f_ticker.encoding.y.shorthand == "ticker:N"
129 changes: 107 additions & 22 deletions tests/test_data_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from src.data_manager import DataManager
from src.data_manager import Updater
import src.data_manager


# Fixture to create a DataManager instance
Expand Down Expand Up @@ -56,7 +57,15 @@ def test__init__(dm: DataManager):
]
assert dm.ret_vol.shape == (7, 4)
assert isinstance(dm.t_exp, pl.DataFrame)
assert dm.t_exp.shape == (139, 5)
assert dm.t_exp.shape == (139, 6)
assert dm.t_exp.columns == [
"portf",
"m_rating",
"rating",
"ticker",
"mv_pct",
"date",
]


def test_setup_session_with_proxy(dm: DataManager):
Expand Down Expand Up @@ -268,9 +277,10 @@ def test_get_cumulative_rets_with_OPT(dm: DataManager):

def test_get_fund_exposures(dm: DataManager):
df = dm.get_fund_exposures(name="SEB Hybrid")
assert df.shape == (58, 4)
assert df.shape == (58, 5)
assert df[-2].to_dict(as_series=False) == {
"name": ["SEB Hybrid"],
"date": [datetime.date(2024, 3, 27)],
"m_rating": ["BB"],
"ticker": ["LLOYDS"],
"mv_pct": [0.0021],
Expand All @@ -283,23 +293,53 @@ def updater(self):
f_name = Path("tests/data/M_Funds.csv")
return Updater(f_name)

def test_import_fund_info(self, updater: Updater):
assert isinstance(updater.tbl, pl.LazyFrame)
tbl = updater.tbl.collect()
assert tbl.shape == (10, 23)
assert tbl["rating"].to_list() == [
"BBB-",
"BBB",
"BBB-",
"BBB-",
"AA+",
"BB+",
"BB+",
"BB+",
"B+",
"AA+",
]

def test__extract_report_date(self, updater: Updater):
assert updater.as_of == datetime.date(2024, 3, 27)

def test_save_t_exp_table(
self, updater: Updater, tmp_path: Path, caplog: pytest.LogCaptureFixture
):
o_path: Path = tmp_path / "test_t_exp.parquet"
with caplog.at_level(level="DEBUG"):
updater.save_t_exp_table(o_name=o_path)

assert "Saving exposures to" in caplog.text
assert "Saving (8, 6) exposures to" in caplog.text
# Verify the file exists
assert o_path.exists()

# Load the parquet file and verify its contents (optional)
df = pl.read_parquet(o_path)
assert df.shape == (8, 5)
assert df.shape == (8, 6)
assert df.with_columns(cs.by_dtype(pl.Float64).round(2)).to_dict(
as_series=False
) == {
"date": [
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
],
"portf": [
"HYBRID",
"HYBRID",
Expand All @@ -325,22 +365,67 @@ def test_save_t_exp_table(
"mv_pct": [0.03, 0.02, 0.01, 0.04, 0.01, 0.0, 0.01, 0.03],
}

def test_import_fund_info(self, updater: Updater):
assert isinstance(updater.tbl, pl.LazyFrame)
tbl = updater.tbl.collect()
assert tbl.shape == (10, 23)
assert tbl["rating"].to_list() == [
"BBB-",
"BBB",
"BBB-",
"BBB-",
"AA+",
"BB+",
"BB+",
"BB+",
"B+",
"AA+",
]
def test_save_t_keyfigures_table(
self, updater: Updater, tmp_path: Path, caplog: pytest.LogCaptureFixture
):
o_path: Path = tmp_path / "test_t_keyfigures.parquet"
with caplog.at_level(level="DEBUG"):
updater.save_t_keyfigures_table(o_name=o_path)

def test__extract_report_date(self, updater: Updater):
assert updater.as_of == datetime.date(2024, 3, 27)
assert "Saving (8, 4) key figures to" in caplog.text
# Verify the file exists
assert o_path.exists()

# Load the parquet file and verify its contents (optional)
df = pl.read_parquet(o_path)
assert df.shape == (8, 4)
assert df.columns == ["date", "portf", "key", "value"]
assert df.to_dict(as_series=False) == {
"date": [
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
datetime.date(2024, 3, 27),
],
"portf": [
"EURHYL HOLD",
"EURHYL HOLD",
"EURHYL HOLD",
"EURHYL HOLD",
"HYBRID",
"HYBRID",
"HYBRID",
"HYBRID",
],
"key": ["oas", "ytc", "ytw", "zspread", "oas", "ytc", "ytw", "zspread"],
"value": [314.27, 218.91, 6.88, 369.9, 329.62, 7.34, 7.18, 346.97],
}


class TestCLI:
"""Unit tests on command line interface for Updater"""

@patch("src.data_manager.Updater")
def test_main(self, m_updater, caplog: pytest.LogCaptureFixture):
m_instance = MagicMock()
m_updater.return_value = m_instance
test_args = ["data_manager.py", "-f", "M_Funds.csv"]
with patch("sys.argv", test_args):
src.data_manager.main()
# Check if Updater was called correctly
m_updater.assert_called_once_with("M_Funds.csv")

# Verify that save_t_exp_table and save_t_keyfigures_table were called
m_instance.save_t_exp_table.assert_called_once_with(
o_name=Path("data/t_exp.parquet")
)
m_instance.save_t_keyfigures_table.assert_called_once_with(
o_name=Path("data/t_keyfigures.parquet")
)

# Check logging message
assert "DONE" in caplog.text

0 comments on commit 53fe2f9

Please sign in to comment.