# ---
# 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]
# # Curve Scenarios, Key Rates, and Multi-Curve
#
# This notebook builds a small discount/projection setup, applies standard bumps, and shows how those shocks feed downstream floating-rate risk interpretation.

# %%
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.bonds import Tenor
from fuggers_py.core import Currency, Date
from fuggers_py.curves import KeyRateBump, MultiCurveEnvironmentBuilder, ParallelBump, RateIndex, ZeroCurveBuilder, key_rate_profile

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, 2)
discount_curve = (
    ZeroCurveBuilder(reference_date=reference_date)
    .add_rate(reference_date.add_years(1), Decimal('0.0405'))
    .add_rate(reference_date.add_years(5), Decimal('0.0385'))
    .add_rate(reference_date.add_years(10), Decimal('0.0390'))
    .build()
)
projection_curve = (
    ZeroCurveBuilder(reference_date=reference_date)
    .add_rate(reference_date.add_years(1), Decimal('0.0420'))
    .add_rate(reference_date.add_years(5), Decimal('0.0402'))
    .add_rate(reference_date.add_years(10), Decimal('0.0408'))
    .build()
)
sofr = RateIndex.new('SOFR', Tenor.parse('3M'), Currency.USD)
environment = (
    MultiCurveEnvironmentBuilder()
    .add_discount_curve(Currency.USD, discount_curve)
    .add_projection_curve(sofr, projection_curve)
    .build()
)

parallel_up = ParallelBump(0.0010).apply(discount_curve)
key_rate_5y = KeyRateBump([Tenor.parse('2Y'), Tenor.parse('5Y'), Tenor.parse('10Y')], Tenor.parse('5Y'), 0.0010).apply(discount_curve)

scenario_grid = [1.0, 2.0, 5.0, 7.0, 10.0]
scenario_table = pd.DataFrame(
    {
        'tenor_years': scenario_grid,
        'discount_base': [float(discount_curve.zero_rate(reference_date.add_days(int(365 * tenor))).value()) for tenor in scenario_grid],
        'discount_parallel_up': [float(parallel_up.zero_rate(reference_date.add_days(int(365 * tenor))).value()) for tenor in scenario_grid],
        'discount_key_rate_5y': [float(key_rate_5y.zero_rate(reference_date.add_days(int(365 * tenor))).value()) for tenor in scenario_grid],
        'projection_curve': [float(environment.projection_curve(sofr).zero_rate(reference_date.add_days(int(365 * tenor))).value()) for tenor in scenario_grid],
    }
)

display_table(
    scenario_table,
    percent_columns=['discount_base', 'discount_parallel_up', 'discount_key_rate_5y', 'projection_curve'],
    float_columns=['tenor_years'],
)

# %% [markdown]
# Discounting and projection are separated because floating-rate instruments discount cashflows off the funding curve but project coupons off the index-specific curve. That separation is what makes multi-curve scenario analysis useful in practice.

# %%
figure, axes = plt.subplots(1, 2, figsize=(12, 4.6))
axes[0].plot(scenario_table['tenor_years'], 100 * scenario_table['discount_base'], label='Base discount', linewidth=2)
axes[0].plot(scenario_table['tenor_years'], 100 * scenario_table['discount_parallel_up'], label='Parallel +10bp', linewidth=2)
axes[0].plot(scenario_table['tenor_years'], 100 * scenario_table['discount_key_rate_5y'], label='5Y key-rate +10bp', linewidth=2)
axes[0].plot(scenario_table['tenor_years'], 100 * scenario_table['projection_curve'], label='Projection curve', linewidth=2)
axes[0].set_title('Base And Shocked Curves')
axes[0].set_xlabel('Tenor (years)')
axes[0].set_ylabel('Zero rate')
percent_axis(axes[0])
axes[0].legend(title='')

profile = key_rate_profile(Tenor.parse('5Y'))
profile_table = pd.DataFrame({'tenor': list(profile.keys()), 'shock': list(profile.values())})
axes[1].bar([str(tenor) for tenor in profile_table['tenor']], [10000 * float(value) for value in profile_table['shock']])
axes[1].set_title('Standard 5Y Key-Rate Bump Profile')
axes[1].set_xlabel('Key tenor')
axes[1].set_ylabel('Shock (bps)')
plt.tight_layout()
figure
