Hi team — following up on #1189 where I noted that passing a Series to leg2_rate_fixings no longer worked. I've since traced the root cause and believe this is a bug in the Rust layer rather than intended behavior
Description:
When passing a pd.Series of daily fixings to leg2_rate_fixings on a STIRFuture with an RFR spec, the Series is silently dropped by the Rust layer and the instrument behaves as if no fixings were provided.
Reproduction:
import rateslib as rl
import pandas as pd
from pandas import Series
# Register a dummy fixings series in the global fixings object
fixings_series = Series(
index=rl.get_calendar('nyc').bus_date_range(rl.dt(2025, 12, 17), rl.dt(2026, 2, 9)),
data=4.15,
)
rl.fixings.add("SOFR_1B", fixings_series)
# Build a curve starting at 2026-02-09
curve = rl.Curve(
nodes={rl.dt(2026, 2, 9): 1.0, rl.dt(2026, 6, 17): 0.99},
convention="Act360",
)
# Case 1 - string identifier: WORKS
fut_str = rl.STIRFuture(
effective=rl.dt(2025, 12, 17),
termination=rl.dt(2026, 3, 18),
spec="usd_stir",
curves=curve,
leg2_rate_fixings="sofr",
)
print(fut_str.rate(curves=curve)) # works
# Case 2 - Series: FAILS (silently dropped by Rust layer)
fut_srs = rl.STIRFuture(
effective=rl.dt(2025, 12, 17),
termination=rl.dt(2026, 3, 18),
spec="usd_stir",
curves=curve,
leg2_rate_fixings=fixings_series,
)
print(fut_srs.rate(curves=curve)) # ValueError
The second case fails with:
ValueError: `effective` date for rate period is before the initial node date of the Curve.
If you are trying to calculate a rate for an historical FloatPeriod have you neglected to supply appropriate `fixings`?
Root cause:
The Rust function _init_FloatRateParams creates an RFRFixing object with .identifier (string) and .value (scalar) slots. A pd.Series doesn't fit either slot, so it's silently converted to NoInput. This causes _is_rfr_efficient (fixings.py:3332) to return True -> pure DF path -> fails for dates before the curve's initial node.
The Python code downstream is ready to handle this case. In _push_rate_fixings_as_series_to_fixing_rates (fixings.py:3179-3182):
if isinstance(rate_fixings, str):
fixing_series = fixings[rate_fixings][1] # global lookup
else:
fixing_series = rate_fixings # Series used directly
Both str and Series converge into the same fixing_rates.update(fixing_series) call, but the Series never reaches this code because the Rust intermediary drops it.
Use case:
Historical curve fitting where global fixings extend past the curve date. Passing a truncated Series would allow the Solver to use fixings only up to the curve date, keeping forward rates curve-implied.
Versions: rateslib 2.5.x, Python 3.14, Windows 11
Hi team — following up on #1189 where I noted that passing a Series to leg2_rate_fixings no longer worked. I've since traced the root cause and believe this is a bug in the Rust layer rather than intended behavior
Description:
When passing a pd.Series of daily fixings to leg2_rate_fixings on a STIRFuture with an RFR spec, the Series is silently dropped by the Rust layer and the instrument behaves as if no fixings were provided.
Reproduction:
The second case fails with:
Root cause:
The Rust function _init_FloatRateParams creates an RFRFixing object with .identifier (string) and .value (scalar) slots. A pd.Series doesn't fit either slot, so it's silently converted to NoInput. This causes _is_rfr_efficient (fixings.py:3332) to return True -> pure DF path -> fails for dates before the curve's initial node.
The Python code downstream is ready to handle this case. In _push_rate_fixings_as_series_to_fixing_rates (fixings.py:3179-3182):
Both str and Series converge into the same fixing_rates.update(fixing_series) call, but the Series never reaches this code because the Rust intermediary drops it.
Use case:
Historical curve fitting where global fixings extend past the curve date. Passing a truncated Series would allow the Solver to use fixings only up to the curve date, keeping forward rates curve-implied.
Versions: rateslib 2.5.x, Python 3.14, Windows 11