# ---
# 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]
# # Engine pricing-router examples
#
# This notebook shows the three main routing paths:
#
# - fixed-rate bullet
# - callable bond with OAS
# - floating-rate note with discount margin

# %%
from __future__ import annotations

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

root = Path.cwd()
if (root / "src").exists():
    sys.path.insert(0, str(root / "src"))
elif (root.parent / "src").exists():
    sys.path.insert(0, str(root.parent / "src"))

# %% [markdown]
# ## Imports

# %%
from fuggers_py.bonds.indices import BondIndex, IndexConventions, IndexFixingStore, OvernightCompounding  # noqa: E402
from fuggers_py.bonds.instruments import CallableBondBuilder, FixedBond, FloatingRateNoteBuilder  # noqa: E402
from fuggers_py.bonds.types import RateIndex, YieldCalculationRules  # noqa: E402
from fuggers_py.core import Currency, Date, Frequency  # noqa: E402
from fuggers_py.engine import CurveBuilder, PricingRouter  # noqa: E402
from fuggers_py.data import (
    AnalyticsCurves,
    CurveId,
    CurvePoint,
    InstrumentId,
    PricingSpec,
)

# %% [markdown]
# ## Build the scenario curves

# %%
settlement = Date.from_ymd(2026, 1, 15)
builder = CurveBuilder()

discount_curve = builder.add_zero_curve(
    CurveId("usd.discount"),
    [
        CurvePoint(Decimal("0.25"), Decimal("0.0450")),
        CurvePoint(Decimal("0.50"), Decimal("0.0445")),
        CurvePoint(Decimal("1.00"), Decimal("0.0435")),
        CurvePoint(Decimal("2.00"), Decimal("0.0415")),
        CurvePoint(Decimal("5.00"), Decimal("0.0395")),
        CurvePoint(Decimal("10.00"), Decimal("0.0400")),
    ],
    settlement,
)
government_curve = builder.add_zero_curve(
    CurveId("usd.government"),
    [
        CurvePoint(Decimal("0.25"), Decimal("0.0420")),
        CurvePoint(Decimal("0.50"), Decimal("0.0410")),
        CurvePoint(Decimal("1.00"), Decimal("0.0400")),
        CurvePoint(Decimal("2.00"), Decimal("0.0385")),
        CurvePoint(Decimal("5.00"), Decimal("0.0370")),
        CurvePoint(Decimal("10.00"), Decimal("0.0380")),
    ],
    settlement,
)
benchmark_curve = builder.add_zero_curve(
    CurveId("usd.benchmark"),
    [
        CurvePoint(Decimal("0.25"), Decimal("0.0435")),
        CurvePoint(Decimal("0.50"), Decimal("0.0428")),
        CurvePoint(Decimal("1.00"), Decimal("0.0418")),
        CurvePoint(Decimal("2.00"), Decimal("0.0402")),
        CurvePoint(Decimal("5.00"), Decimal("0.0388")),
        CurvePoint(Decimal("10.00"), Decimal("0.0392")),
    ],
    settlement,
)
frn_discount_curve = builder.add_zero_curve(
    CurveId("frn.discount"),
    [
        CurvePoint(Decimal("0.25"), Decimal("0.0430")),
        CurvePoint(Decimal("0.50"), Decimal("0.0420")),
        CurvePoint(Decimal("1.00"), Decimal("0.0405")),
        CurvePoint(Decimal("2.00"), Decimal("0.0390")),
        CurvePoint(Decimal("3.00"), Decimal("0.0385")),
    ],
    settlement,
)
forward_curve = builder.add_forward_curve(
    CurveId("usd.forward"),
    [
        CurvePoint(Decimal("0.25"), Decimal("0.0430")),
        CurvePoint(Decimal("0.50"), Decimal("0.0420")),
        CurvePoint(Decimal("1.00"), Decimal("0.0405")),
        CurvePoint(Decimal("2.00"), Decimal("0.0390")),
        CurvePoint(Decimal("3.00"), Decimal("0.0385")),
    ],
    settlement,
)

# %% [markdown]
# ## Build the fixed-rate bullet bond

# %%
fixed_rules = replace(YieldCalculationRules.us_corporate(), frequency=Frequency.SEMI_ANNUAL)
fixed_bond = FixedBond.new(
    issue_date=Date.from_ymd(2024, 1, 15),
    maturity_date=Date.from_ymd(2031, 1, 15),
    coupon_rate=Decimal("0.0450"),
    frequency=Frequency.SEMI_ANNUAL,
    currency=Currency.USD,
    notional=Decimal("100"),
    rules=fixed_rules,
)

# %% [markdown]
# ## Build the callable bond

# %%
callable_base = FixedBond.new(
    issue_date=Date.from_ymd(2023, 1, 15),
    maturity_date=Date.from_ymd(2033, 1, 15),
    coupon_rate=Decimal("0.0500"),
    frequency=Frequency.SEMI_ANNUAL,
    currency=Currency.USD,
    notional=Decimal("100"),
    rules=fixed_rules,
)
callable_builder = CallableBondBuilder.new().with_base_bond(callable_base)
for year in range(2028, 2033):
    callable_builder = callable_builder.add_call(call_date=Date.from_ymd(year, 1, 15), call_price=Decimal("100.00"))
callable_bond = callable_builder.build()

# %% [markdown]
# ## Build the FRN and its fixing store

# %%
fixing_store = IndexFixingStore()
for fixing_date in [
    Date.from_ymd(2025, 12, 29),
    Date.from_ymd(2025, 12, 30),
    Date.from_ymd(2025, 12, 31),
    Date.from_ymd(2026, 1, 2),
    Date.from_ymd(2026, 1, 5),
    Date.from_ymd(2026, 1, 6),
    Date.from_ymd(2026, 1, 7),
    Date.from_ymd(2026, 1, 8),
    Date.from_ymd(2026, 1, 9),
    Date.from_ymd(2026, 1, 12),
    Date.from_ymd(2026, 1, 13),
    Date.from_ymd(2026, 1, 14),
]:
    fixing_store.add_fixing("SOFR", fixing_date, Decimal("0.0410"))

index = BondIndex(
    name="SOFR",
    rate_index=RateIndex.SOFR,
    currency=Currency.USD,
    fixing_store=fixing_store,
    conventions=IndexConventions(overnight_compounding=OvernightCompounding.COMPOUNDED),
)
frn_rules = replace(YieldCalculationRules.us_treasury(), frequency=Frequency.QUARTERLY)
frn = (
    FloatingRateNoteBuilder.new()
    .with_issue_date(Date.from_ymd(2025, 1, 15))
    .with_maturity_date(Date.from_ymd(2028, 1, 15))
    .with_index(RateIndex.SOFR)
    .with_index_definition(index)
    .with_quoted_spread(Decimal("0.0125"))
    .with_frequency(Frequency.QUARTERLY)
    .with_currency(Currency.USD)
    .with_notional(Decimal("100"))
    .with_rules(frn_rules)
    .with_current_reference_rate(Decimal("0.0410"))
    .build()
)

# %% [markdown]
# ## Route the three pricing paths

# %%
router = PricingRouter()

fixed_output = router.price(
    fixed_bond,
    settlement,
    instrument_id=InstrumentId("SCENARIO-A-FIXED"),
    market_price=Decimal("101.25"),
    pricing_spec=PricingSpec(compute_spreads=True, compute_key_rates=True, include_asset_swap=True),
    curves=AnalyticsCurves(
        discount_curve=discount_curve,
        government_curve=government_curve,
        benchmark_curve=benchmark_curve,
    ),
)

callable_output = router.price(
    callable_bond,
    settlement,
    instrument_id=InstrumentId("SCENARIO-B-CALLABLE"),
    market_price=Decimal("102.50"),
    pricing_spec=PricingSpec(callable_mean_reversion=Decimal("0.03"), callable_volatility=Decimal("0.01")),
    curves=AnalyticsCurves(
        discount_curve=discount_curve,
        government_curve=government_curve,
        benchmark_curve=benchmark_curve,
    ),
)

frn_output = router.price(
    frn,
    settlement,
    instrument_id=InstrumentId("SCENARIO-C-FRN"),
    market_price=Decimal("100.15"),
    pricing_spec=PricingSpec(market_price_is_dirty=True),
    curves=AnalyticsCurves(discount_curve=frn_discount_curve, forward_curve=forward_curve),
)

# %% [markdown]
# ## Review the routed outputs

# %%
print("Fixed-rate bullet")
print("  clean price:", fixed_output.clean_price)
print("  dirty price:", fixed_output.dirty_price)
print("  ytm:", fixed_output.yield_to_maturity)
print("  duration / dv01 / convexity:", fixed_output.modified_duration, fixed_output.dv01, fixed_output.convexity)
print("  z / g / i spread:", fixed_output.z_spread, fixed_output.g_spread, fixed_output.i_spread)

print("\nCallable bond")
print("  clean price:", callable_output.clean_price)
print("  dirty price:", callable_output.dirty_price)
print("  ytm:", callable_output.yield_to_maturity)
print("  OAS:", callable_output.oas)
print("  option value:", callable_output.option_value)
print("  effective duration / convexity:", callable_output.effective_duration, callable_output.effective_convexity)

print("\nFloating-rate note")
print("  clean price:", frn_output.clean_price)
print("  dirty price:", frn_output.dirty_price)
print("  current yield:", frn_output.current_yield)
print("  discount margin:", frn_output.discount_margin)
print("  projected next coupon:", frn_output.projected_next_coupon)
print("  spread duration:", frn_output.spread_duration)
