# ---
# jupyter:
#   jupytext:
#     formats: ipynb,py:percent
#     text_representation:
#       extension: .py
#       format_name: percent
#       format_version: '1.3'
#       jupytext_version: 1.19.1
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# %% [markdown]
# # Portfolio Summary, Bucketing, And Liquidity
#
# This notebook turns a small deterministic bond book into a PM-style dashboard: holdings, top-line analytics, maturity and sector buckets, and a liquidity overlay that estimates how quickly the book can be monetized.

# %%
from __future__ import annotations

from decimal import Decimal

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt
import pandas as pd

from fuggers_py.portfolio import calculate_liquidity_metrics, calculate_portfolio_analytics, estimate_days_to_liquidate

from examples._shared.datasets import build_research_portfolio, build_usd_discount_curve, research_reference_date
from examples._shared.plotting import apply_research_style
from examples._shared.tables import display_table

apply_research_style()

# %% [markdown]
# ## Load the synthetic portfolio
#
# The holdings mix one liquid government bond with two credit positions. That is enough to show the interactions between carry, duration, rating, and liquidity without depending on external data.

# %%
reference_date = research_reference_date()
curve = build_usd_discount_curve(reference_date)
portfolio = build_research_portfolio(reference_date)
summary = calculate_portfolio_analytics(portfolio, curve=curve, settlement_date=reference_date)
liquidity = calculate_liquidity_metrics(portfolio, curve=curve, settlement_date=reference_date)
liquidation = estimate_days_to_liquidate(portfolio, curve=curve, settlement_date=reference_date)

holdings_rows = []
for holding in portfolio.positions:
    years_to_maturity = reference_date.days_between(holding.instrument.maturity_date()) / Decimal('365')
    holdings_rows.append(
        {
            'name': holding.label,
            'sector': holding.classification.sector.value,
            'rating': holding.classification.rating.value,
            'years_to_maturity': years_to_maturity,
            'coupon_rate': holding.instrument.coupon_rate(),
            'clean_price': holding.clean_price,
            'market_value': holding.clean_price * holding.quantity,
            'liquidity_score': holding.liquidity_score,
        }
    )
holdings = pd.DataFrame(holdings_rows)
holdings['maturity_bucket'] = pd.cut(
    holdings['years_to_maturity'].astype(float),
    bins=[0.0, 2.0, 5.0, 10.0, 100.0],
    labels=['0-2Y', '2-5Y', '5-10Y', '10Y+'],
    right=False,
)
holdings['weight'] = holdings['market_value'].astype(float) / float(holdings['market_value'].sum())
holdings['bucket_dv01'] = holdings['weight'] * float(summary.dv01)

summary_table = pd.DataFrame(
    [
        {'metric': 'Dirty PV', 'value': summary.dirty_pv},
        {'metric': 'Portfolio YTM', 'value': summary.ytm},
        {'metric': 'Modified duration', 'value': summary.modified_duration},
        {'metric': 'DV01', 'value': summary.dv01},
        {'metric': 'Weighted z-spread', 'value': summary.z_spread},
        {'metric': 'Liquidity score', 'value': liquidity.liquidity_score},
        {'metric': 'Days to liquidate', 'value': liquidation.days},
    ]
)

maturity_buckets = holdings.groupby('maturity_bucket', observed=False).agg(weight=('weight', 'sum'), dv01=('bucket_dv01', 'sum')).reset_index()
sector_buckets = holdings.groupby('sector', observed=False).agg(weight=('weight', 'sum'), dv01=('bucket_dv01', 'sum')).reset_index()
rating_buckets = holdings.groupby('rating', observed=False).agg(weight=('weight', 'sum'), market_value=('market_value', 'sum')).reset_index()
liquidity_buckets = pd.DataFrame(
    [
        {
            'bucket': entry.label,
            'weight': entry.weight,
            'dirty_pv': entry.dirty_pv,
            'holding_count': entry.holding_count,
        }
        for entry in liquidity.distribution.entries
    ]
)

# %%
display_table(holdings, percent_columns=['coupon_rate', 'weight'], float_columns=['years_to_maturity', 'clean_price', 'market_value', 'liquidity_score', 'bucket_dv01'])
display_table(summary_table, percent_columns=['value'])
display_table(maturity_buckets, percent_columns=['weight'], float_columns=['dv01'])
display_table(rating_buckets, percent_columns=['weight'], float_columns=['market_value'])

# %% [markdown]
# The dashboard is doing two jobs at once: it shows how the book earns carry and spread, and it shows how much of that book can realistically be rebalanced without paying a large liquidity penalty.

# %%
figure, axes = plt.subplots(1, 2, figsize=(12, 4.8))
axes[0].bar(maturity_buckets['maturity_bucket'].astype(str), maturity_buckets['dv01'])
axes[0].set_title('DV01 By Maturity Bucket')
axes[0].set_xlabel('Bucket')
axes[0].set_ylabel('DV01')

axes[1].bar(liquidity_buckets['bucket'], 100 * liquidity_buckets['weight'], color='#2c7da0')
axes[1].set_title('Liquidity Distribution')
axes[1].set_xlabel('Liquidity bucket')
axes[1].set_ylabel('Weight (%)')
plt.tight_layout()
figure

# %%
figure, axes = plt.subplots(1, 2, figsize=(12, 4.8))
axes[0].pie(100 * sector_buckets['weight'], labels=sector_buckets['sector'], autopct='%1.1f%%')
axes[0].set_title('Sector Weights')

axes[1].bar(rating_buckets['rating'], rating_buckets['market_value'])
axes[1].set_title('Market Value By Rating Bucket')
axes[1].set_xlabel('Rating')
axes[1].set_ylabel('Market value')
plt.tight_layout()
figure

# %% [markdown]
# ## Desk takeaways
#
# - The book's duration sits mainly in the `5-10Y` bucket because the longer credit names dominate risk.
# - Liquidity is still acceptable overall, but the lower-scoring high-yield sleeve slows down full liquidation.
# - Rating and sector concentrations are visible even in a small portfolio; they should be monitored alongside total DV01 and carry.
