SOFR Futures Fair Value & Implied Fed Path
A fair-value calculator for SOFR futures (SR3 contracts like SFRH6, SFRM6, SFRZ6) that produces the implied Fed funds path from the market price.
A SOFR future settles to the compounded daily SOFR over its 3-month reference quarter:
\[ \text{settle} = \left(\prod_{i \in \text{ref-quarter}} \left(1 + \frac{\text{SOFR}_i}{360} \cdot d_i\right) - 1\right) \cdot \frac{360}{D} \cdot 100 \]
So pricing the contract is really a question of forecasting daily SOFR through the quarter, which decomposes into (1) the daily SOFR–FF spread and (2) the path of fed funds itself.
How it works:
- SOFR–FF spread: a per-month spread table (the
SER_FFcalendar) giving the empirical month-by-month SOFR-vs-FF basis. Daily SOFR is reconstructed asFF − SER_FF. - Fed path: starting from the current FF level, apply rate changes on every FOMC meeting date through the reference quarter.
- In-reference handling: if the contract’s reference quarter has already begun, the elapsed portion is filled in with realized SOFR from
newsofr.csvand only the forward portion is modeled. - Scenario tree: with
permutations=True, the calculator enumerates every combination of FF moves (−25/0/+25 bp, or any specified set) across each FOMC date in the reference window and prices the contract under each path. Given a market price target, it then finds the scenarios that bracket the target, i.e. the closest implied Fed paths above and below.
import pandas as pd
import pandas_market_calendars as mcal
from datetime import date
df = pd.read_csv("newsofr.csv").drop("PX_BID", axis=1)
sofr_df = df
sofr_df["Date"] = pd.to_datetime(df["Date"], errors="coerce").dt.date
begin_date = '2026-02-19'
ref_start_date = '2026-12-16'
ref_end_date= '2027-03-16'
def minus_one_day(day):
return "-".join(day.split("-")[:-1] + [str(int(day.split("-")[-1]) - 1).zfill(2)])
if (int("".join(begin_date.split("-"))) - int("".join(ref_start_date.split("-"))) > 0):
in_reference = True
prev_days = mcal.get_calendar('NYSE').valid_days(start_date=ref_start_date, end_date=minus_one_day(begin_date))
days = mcal.get_calendar('NYSE').valid_days(start_date=begin_date, end_date=ref_end_date)
else:
in_reference = False
prev_days = mcal.get_calendar('NYSE').valid_days(start_date=begin_date, end_date=minus_one_day(ref_start_date))
days = mcal.get_calendar('NYSE').valid_days(start_date=ref_start_date, end_date=ref_end_date)
params = {
"initialSFR": 3.73,
"SER_FF": {
"G": -2.875, "H": -3.25, "J": -2.75, "K": -2.75,
"M": -3.25, "N": -3.75, "Q": -3.75, "U": -4.75,
"V": -4.25, "X": -4.25, "Z": -6.25, "F": -6.25,
},
"starting_ff": 3.64,
"ff_changes_prob": [
(1, {
date(2027, 3, 17): 0,
date(2027, 4, 28): 0,
date(2027, 6, 9): 0,
date(2027, 1, 27): 0,
date(2026, 6, 17): 0,
date(2026, 7, 29): 0,
date(2026, 3, 18): 0,
date(2026, 4, 29): 0,
}),
],
"expected_value": False,
"permutations": True,
"values": [-0.25, 0, 0.25],
"target": 3.12,
}
def get_contract_code(month):
mapping = {1:'F', 2:'G', 3:'H', 4:'J', 5:'K', 6:'M',
7:'N', 8:'Q', 9:'U', 10:'V', 11:'X', 12:'Z'}
return mapping.get(month, "Invalid Month")
def get_day_rate(month, ff):
ser_ff = params["SER_FF"][get_contract_code(month)]
return (ff - (ser_ff/100))/100
def get_known_day_rate(day):
return sofr_df.loc[sofr_df["Date"] == day.date()]["PX_LAST"].item()/100
def run_sim(ff_changes):
current_ff = params["starting_ff"]
running_total = 1
if in_reference:
# elapsed portion: use realized SOFR
for j in range(len(prev_days)):
prev_day = prev_days[j]
if j != len(prev_days) - 1:
effective_days = (prev_days[j + 1] - prev_day).days
else:
effective_days = (days[0] - prev_day).days
day_rate = get_known_day_rate(prev_days[j])
running_total *= 1 + (day_rate * effective_days)/360
current_ff += ff_changes.get(prev_day.date(), 0)
else:
# pre-reference: walk forward applying FOMC changes
for j in range(len(prev_days)):
prev_day = prev_days[j]
current_ff += ff_changes.get(prev_day.date(), 0)
# forward portion: simulate using current FF + per-month SOFR-FF spread
for i in range(len(days)):
day = days[i]
effective_days = (days[i + 1] - day).days if i != len(days) - 1 else 1
day_rate = get_day_rate(day.month, current_ff)
running_total *= 1 + (day_rate * effective_days)/360
current_ff += ff_changes.get(day.date(), 0)
total_days = ((days[-1] - prev_days[0]).days + 1) if in_reference else ((days[-1] - days[0]).days + 1)
return (running_total - 1) * (360/total_days * 100)
def create_sim(contingencies, values):
new_contingencies = []
key_list = list(contingencies[0][1].keys())
create_more(key_list, 0, dict(), values, new_contingencies)
return new_contingencies
def create_more(key_list, idx, current_dict, values, new_contingencies):
if idx == len(key_list):
chance = 1/(len(values) ** len(key_list))
new_contingencies.append((chance, current_dict))
return
for value in values:
new_dict = current_dict.copy()
new_dict[key_list[idx]] = value
create_more(key_list, idx+1, new_dict, values, new_contingencies)
if __name__ == "__main__":
for contingency in params["ff_changes_prob"]:
for key in list(contingency[1].keys()):
if (key - date.fromisoformat(ref_end_date)).days > 0:
del contingency[1][key]
contingencies = create_sim(params["ff_changes_prob"], params["values"]) \
if params["permutations"] else params["ff_changes_prob"]
expected_value = 0 if params["expected_value"] else []
for contingency in contingencies:
if params["expected_value"]:
expected_value += run_sim(contingency[1]) * contingency[0]
else:
expected_value.append(run_sim(contingency[1]))
# find scenarios that bracket the market price
if not params["expected_value"]:
closest_below = closest_above = float('inf')
closest_below_idx = closest_above_idx = -1
for i in range(len(expected_value)):
diff = params["target"] - expected_value[i]
if diff <= 0 and abs(diff) < closest_above:
closest_above, closest_above_idx = abs(diff), i
elif diff > 0 and abs(diff) < closest_below:
closest_below, closest_below_idx = abs(diff), i
print(f"Target: {params['target']}")
if closest_above_idx != -1:
print(f"Closest Above: {expected_value[closest_above_idx]}")
print(f" scenario: {contingencies[closest_above_idx][1]}")
if closest_below_idx != -1:
print(f"Closest Below: {expected_value[closest_below_idx]}")
print(f" scenario: {contingencies[closest_below_idx][1]}")