# ---
# 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]
# # Tutorial: ETF basket surface examples
#
# This notebook demonstrates:
#
# - building a small ETF-style portfolio
# - deriving a creation basket
# - estimating holdings-based net yield after expenses
# - checking a premium/discount arbitrage point

# %%
from __future__ import annotations

import sys
from dataclasses import asdict, replace
from decimal import Decimal
from pathlib import Path

cwd = Path.cwd().resolve()
candidates = [cwd] + list(cwd.parents)
repo_root = None
for path in candidates:
    if (path / "src" / "fuggers_py").exists():
        repo_root = path
        break

assert repo_root is not None, "Could not locate repo root containing src/fuggers_py"
sys.path.insert(0, str(repo_root / "src"))

from fuggers_py.bonds.instruments import FixedBond
from fuggers_py.bonds.types import CreditRating, RatingInfo, Sector, SectorInfo, YieldCalculationRules
from fuggers_py.core import Currency, Date, Frequency
from fuggers_py.curves import DiscountCurveBuilder
from fuggers_py.portfolio import (
    CashPosition,
    Classification,
    Holding,
    Portfolio,
    arbitrage_opportunity,
    build_creation_basket,
    estimate_yield_from_holdings,
)

# %% [markdown]
# ## Build a small deterministic portfolio

# %%
def make_curve(reference_date: Date) -> object:
    return (
        DiscountCurveBuilder(reference_date=reference_date)
        .add_zero_rate(0.5, Decimal("0.03"))
        .add_zero_rate(2.0, Decimal("0.0325"))
        .add_zero_rate(5.0, Decimal("0.0350"))
        .add_zero_rate(10.0, Decimal("0.04"))
        .build()
    )


def annual_rules() -> YieldCalculationRules:
    return replace(YieldCalculationRules.us_corporate(), frequency=Frequency.ANNUAL)


def make_holding(
    reference_date: Date,
    *,
    years: int,
    coupon: str,
    label: str,
    sector: Sector,
    rating: CreditRating,
    quantity: str,
    price: str,
) -> Holding:
    bond = FixedBond.new(
        issue_date=reference_date,
        maturity_date=reference_date.add_years(years),
        coupon_rate=Decimal(coupon),
        frequency=Frequency.ANNUAL,
        currency=Currency.USD,
        rules=annual_rules(),
    )
    return Holding(
        id=label,
        instrument=bond,
        quantity=Decimal(quantity),
        clean_price=Decimal(price),
        label=label,
        classification=Classification(sector=sector, rating=rating, currency=Currency.USD, issuer=f"{label}_issuer"),
        rating_info=RatingInfo(rating=rating),
        sector_info=SectorInfo(sector=sector, issuer=f"{label}_issuer", country="US", region="NA"),
        liquidity_score=Decimal("0.85"),
    )


def build_demo_portfolio(reference_date: Date) -> Portfolio:
    return Portfolio.new(
        [
            make_holding(
                reference_date,
                years=2,
                coupon="0.035",
                label="gov_short",
                sector=Sector.GOVERNMENT,
                rating=CreditRating.AA,
                quantity="100",
                price="99.5",
            ),
            make_holding(
                reference_date,
                years=5,
                coupon="0.05",
                label="corp_bbb",
                sector=Sector.CORPORATE,
                rating=CreditRating.BBB,
                quantity="100",
                price="101.0",
            ),
            make_holding(
                reference_date,
                years=8,
                coupon="0.0625",
                label="corp_bb",
                sector=Sector.INDUSTRIALS,
                rating=CreditRating.BB,
                quantity="100",
                price="98.0",
            ),
            CashPosition(amount=Decimal("50"), currency=Currency.USD),
        ],
        currency=Currency.USD,
    )


reference_date = Date.from_ymd(2024, 1, 1)
curve = make_curve(reference_date)
portfolio = build_demo_portfolio(reference_date)

print("Portfolio positions:", len(portfolio.positions))
print("Reference date:", reference_date)

# %% [markdown]
# ## Run the ETF helpers

# %%
creation_basket = build_creation_basket(
    portfolio,
    curve=curve,
    settlement_date=reference_date,
    shares_outstanding=Decimal("1000"),
    creation_unit_shares=Decimal("100"),
    liabilities=Decimal("20"),
)
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"),
)
opportunity = arbitrage_opportunity(
    portfolio,
    curve=curve,
    settlement_date=reference_date,
    shares_outstanding=Decimal("1000"),
    liabilities=Decimal("20"),
    market_price=Decimal("30.00"),
)

print("Creation basket summary:")
print(asdict(creation_basket.flow_summary))
print()
print("Creation basket components:")
for component in creation_basket:
    print(asdict(component))
print()
print("Holdings-based expense metrics:")
print(asdict(expense_metrics))
print()
print("Premium/discount opportunity point:")
print(asdict(opportunity))
