# ---
# 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]
# # ETF NAV, Basket, And SEC Yield
#
# This notebook treats the synthetic portfolio as the holdings of a small bond ETF. We examine the creation basket, per-share NAV and risk, premium/discount behavior, SEC-style yield, and a lightweight compliance report.

# %%
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 (
    SecYieldInput,
    arbitrage_opportunity,
    build_creation_basket,
    calculate_etf_nav_metrics,
    calculate_sec_yield,
    estimate_yield_from_holdings,
    etf_compliance_checks,
)

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()

# %%
reference_date = research_reference_date()
curve = build_usd_discount_curve(reference_date)
portfolio = build_research_portfolio(reference_date)
shares_outstanding = Decimal('1000')
liabilities = Decimal('20')
market_prices = [Decimal('29.70'), Decimal('29.90'), Decimal('30.00'), Decimal('30.10')]

creation_basket = build_creation_basket(
    portfolio,
    curve=curve,
    settlement_date=reference_date,
    shares_outstanding=shares_outstanding,
    creation_unit_shares=Decimal('100'),
    liabilities=liabilities,
)
expense_metrics = estimate_yield_from_holdings(
    portfolio,
    curve=curve,
    settlement_date=reference_date,
    gross_expense_ratio=Decimal('0.0020'),
    fee_waiver_ratio=Decimal('0.0005'),
)
nav_metrics = calculate_etf_nav_metrics(
    portfolio,
    curve=curve,
    settlement_date=reference_date,
    shares_outstanding=shares_outstanding,
    liabilities=liabilities,
    market_price=Decimal('30.00'),
)
sec_yield = calculate_sec_yield(
    SecYieldInput(
        net_investment_income=expense_metrics.annual_income_estimate - expense_metrics.annual_expense_amount,
        average_shares_outstanding=shares_outstanding,
        max_offering_price=Decimal('30.00'),
        gross_expenses=expense_metrics.annual_expense_amount,
        fee_waivers=expense_metrics.annual_expense_amount * Decimal('0.3333333333'),
    )
)
compliance = etf_compliance_checks(
    holdings_weight_sum=Decimal('1.0'),
    max_issuer_weight=max(component.weight for component in creation_basket.components),
)

basket_table = pd.DataFrame(
    [
        {
            'name': component.name,
            'quantity': component.quantity,
            'market_value': component.market_value,
            'weight': component.weight,
            'sector': component.sector,
        }
        for component in creation_basket.components
    ]
)
summary_table = pd.DataFrame(
    [
        {'metric': 'NAV per share', 'value': nav_metrics.nav_per_share},
        {'metric': 'iNAV', 'value': nav_metrics.inav},
        {'metric': 'DV01 per share', 'value': nav_metrics.dv01_per_share},
        {'metric': 'SEC 30-day yield', 'value': sec_yield.sec_30_day_yield},
        {'metric': 'Unsubsidized yield', 'value': sec_yield.unsubsidized_yield},
        {'metric': 'Net yield from holdings', 'value': expense_metrics.net_yield},
    ]
)
premium_discount = pd.DataFrame(
    [
        {
            'market_price': price,
            'premium_discount_pct': arbitrage_opportunity(
                portfolio,
                curve=curve,
                settlement_date=reference_date,
                shares_outstanding=shares_outstanding,
                liabilities=liabilities,
                market_price=price,
            ).premium_discount.premium_discount_pct,
        }
        for price in market_prices
    ]
)
compliance_table = pd.DataFrame(
    [
        {
            'check': check.name,
            'passed': check.passed,
            'value': check.value,
            'limit': check.limit,
            'severity': check.severity.value,
        }
        for check in compliance.checks
    ]
)

# %%
display_table(basket_table, percent_columns=['weight'], float_columns=['quantity', 'market_value'])
display_table(summary_table, percent_columns=['value'])
display_table(compliance_table, float_columns=['value', 'limit'])

# %% [markdown]
# The ETF lens makes the portfolio operational: the same holdings now need to clear creation/redemption mechanics, per-share risk reporting, and yield conventions that are standardized for distribution.

# %%
figure, axes = plt.subplots(1, 2, figsize=(12, 4.8))
axes[0].pie(100 * basket_table['weight'], labels=basket_table['name'], autopct='%1.1f%%')
axes[0].set_title('Creation Basket Composition')

axes[1].plot(premium_discount['market_price'], 100 * premium_discount['premium_discount_pct'], marker='o', linewidth=2)
axes[1].axhline(0.0, color='black', linewidth=1)
axes[1].set_title('Premium/Discount Versus Market Price')
axes[1].set_xlabel('Market price')
axes[1].set_ylabel('Premium / discount (%)')
plt.tight_layout()
figure

# %%
figure, axes = plt.subplots(1, 2, figsize=(12, 4.8))
axes[0].bar(summary_table['metric'], summary_table['value'])
axes[0].set_title('ETF Summary Metrics')
axes[0].set_xlabel('Metric')
axes[0].set_ylabel('Value')
axes[0].tick_params(axis='x', rotation=25)

axes[1].bar(compliance_table['check'], compliance_table['value'])
axes[1].set_title('Compliance Check Inputs')
axes[1].set_xlabel('Check')
axes[1].set_ylabel('Observed value')
axes[1].tick_params(axis='x', rotation=20)
plt.tight_layout()
figure

# %% [markdown]
# ## Conclusion
#
# This is the sort of notebook an ETF desk can keep open intraday: basket composition, premium/discount drift, per-share risk, and yield disclosures all come from the same underlying holdings view.
