# ---
# 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]
# # Floating-Rate Notes, Fixings, and Discount Margin
#
# This notebook combines historical SOFR-style fixings with forward projection and discount-margin analysis for one deterministic FRN.

# %%
from __future__ import annotations

from dataclasses import replace
from decimal import Decimal

import matplotlib

matplotlib.use("Agg")

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from fuggers_py.analytics.spreads import DiscountMarginCalculator
from fuggers_py.bonds import BondIndex, IndexConventions, IndexFixingStore, FloatingRateNoteBuilder, OvernightCompounding, RateIndex, YieldCalculationRules
from fuggers_py.core import Currency, Date, Frequency
from fuggers_py.curves import DiscountCurveBuilder, ForwardCurve

from examples._shared.plotting import apply_research_style, percent_axis
from examples._shared.tables import display_table

apply_research_style()

# %%
reference_date = Date.from_ymd(2026, 1, 15)
fixing_store = IndexFixingStore()
for offset, rate in enumerate(['0.0400', '0.0405', '0.0410', '0.0415', '0.0420', '0.0420', '0.0425']):
    fixing_store.add_fixing('SOFR', reference_date.add_days(-10 + offset), Decimal(rate))

index_definition = BondIndex(
    name='SOFR',
    rate_index=RateIndex.SOFR,
    currency=Currency.USD,
    fixing_store=fixing_store,
    conventions=IndexConventions(overnight_compounding=OvernightCompounding.COMPOUNDED),
)
rules = replace(YieldCalculationRules.us_treasury(), frequency=Frequency.QUARTERLY)
frn = (
    FloatingRateNoteBuilder.new()
    .with_issue_date(reference_date.add_months(-3))
    .with_maturity_date(reference_date.add_years(2))
    .with_index(RateIndex.SOFR)
    .with_index_definition(index_definition)
    .with_frequency(Frequency.QUARTERLY)
    .with_rules(rules)
    .with_current_reference_rate(Decimal('0.0425'))
    .with_quoted_spread(Decimal('0.0018'))
    .build()
)

discount_curve = (
    DiscountCurveBuilder(reference_date=reference_date)
    .add_zero_rate(0.25, Decimal('0.0410'))
    .add_zero_rate(1.0, Decimal('0.0405'))
    .add_zero_rate(2.0, Decimal('0.0398'))
    .build()
)
forward_curve = ForwardCurve.from_months(discount_curve, 3)
calculator = DiscountMarginCalculator(forward_curve=forward_curve, discount_curve=discount_curve)

# %%
fixing_table = pd.DataFrame(
    {
        'fixing_date': [reference_date.add_days(-10 + offset) for offset in range(7)],
        'rate': [Decimal(rate) for rate in ['0.0400', '0.0405', '0.0410', '0.0415', '0.0420', '0.0420', '0.0425']],
    }
)
display_table(fixing_table, date_columns=['fixing_date'], percent_columns=['rate'])

projected_cashflows = frn.projected_cash_flows(forward_curve, settlement_date=reference_date, fixing_store=fixing_store)
projection_table = pd.DataFrame(
    [
        {
            'payment_date': cashflow.date,
            'coupon_amount': cashflow.amount,
            'reference_rate': cashflow.reference_rate,
        }
        for cashflow in projected_cashflows[:6]
    ]
)
display_table(projection_table, date_columns=['payment_date'], percent_columns=['reference_rate'], float_columns=['coupon_amount'])

# %% [markdown]
# Historical fixings anchor the front of the coupon path. Beyond that window the FRN behaves like a projection-curve instrument with relatively low duration and more spread sensitivity than a fixed-rate bond.

# %%
dm_grid = np.array([-0.0025, 0.0, 0.0025, 0.0050, 0.0075, 0.0100])
price_dm = pd.DataFrame(
    {
        'discount_margin': dm_grid,
        'dirty_price': [calculator.price_with_dm(frn, Decimal(str(dm)), reference_date) for dm in dm_grid],
    }
)
base_dm = calculator.calculate(frn, price_dm.loc[3, 'dirty_price'], reference_date)

figure, axes = plt.subplots(1, 2, figsize=(12, 4.6))
axes[0].plot(fixing_table['fixing_date'].astype(str), 100 * fixing_table['rate'].astype(float), marker='o', linewidth=2, label='Observed fixing')
axes[0].plot(projection_table['payment_date'].astype(str), 100 * projection_table['reference_rate'].astype(float), marker='o', linewidth=2, label='Projected coupon rate')
axes[0].set_title('Fixings And Projected Coupon Path')
axes[0].set_xlabel('Date')
axes[0].set_ylabel('Rate')
percent_axis(axes[0])
axes[0].legend(title='')
for tick in axes[0].get_xticklabels():
    tick.set_rotation(25)
    tick.set_ha('right')

axes[1].plot(10000 * price_dm['discount_margin'], price_dm['dirty_price'], marker='o', linewidth=2)
axes[1].axvline(10000 * float(base_dm), color='#b7410e', linestyle='--', linewidth=1.5)
axes[1].set_title('Price Versus Discount Margin')
axes[1].set_xlabel('Discount margin (bps)')
axes[1].set_ylabel('Dirty price')
plt.tight_layout()
figure
