Skip to content

Commit

Permalink
TST: add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
attack68 committed Jan 31, 2025
1 parent cc3fade commit c8132d5
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 47 deletions.
5 changes: 3 additions & 2 deletions python/rateslib/fx/fx_forwards.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ def __init__(
self._validate_fx_curves(fx_curves)
self.fx_rates: FXRates | list[FXRates] = fx_rates
self._calculate_immediate_rates(base, init=True)
assert self.currencies_list == self.fx_rates_immediate.currencies_list
self._set_new_state()

def _get_composited_state(self) -> int:
Expand Down Expand Up @@ -216,7 +217,7 @@ def _calculate_immediate_rates(self, base: str | NoInput, init: bool) -> None:
if init:
self.currencies = self.fx_rates.currencies
self.q = len(self.currencies.keys())
self.currencies_list: list[str] = list(self.currencies.keys())
self.currencies_list: list[str] = self.fx_rates.currencies_list
self.transform = self._get_forwards_transformation_matrix(
self.q,
self.currencies,
Expand All @@ -241,7 +242,7 @@ def _calculate_immediate_rates(self, base: str | NoInput, init: bool) -> None:
pair: self.fx_rates[0].settlement for pair in self.fx_rates[0].pairs
}

# Now itertate through the remaining FXRates objects and patch them into the fxf
# Now iterate through the remaining FXRates objects and patch them into the fxf
for fx_rates_obj in self.fx_rates[1:]:
# create sub FXForwards for each FXRates instance and re-combine.
# This reuses the arg validation of a single FXRates object and
Expand Down
2 changes: 1 addition & 1 deletion python/rateslib/fx/fx_rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def restate(self, pairs: list[str], keep_ad: bool = False) -> FXRates:
fxr2 = fxr.restate({"eurusd", "gbpusd"})
fxr2.rates_table()
"""
if set(pairs) == set(self.pairs) and keep_ad:
if pairs == self.pairs and keep_ad:
return self.__copy__() # no restate needed but return new instance

restated_fx_rates = FXRates(
Expand Down
102 changes: 58 additions & 44 deletions python/tests/test_fx.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,11 +278,13 @@ def usdeur():
nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.996}
return Curve(nodes=nodes, interpolation="log_linear")


@pytest.fixture
def cadcad():
nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.987}
return Curve(nodes=nodes, interpolation="log_linear")


@pytest.fixture
def cadcol():
nodes = {dt(2022, 1, 1): 1.00, dt(2022, 4, 1): 0.984}
Expand Down Expand Up @@ -703,17 +705,26 @@ def test_single_system(self, usdusd, eureur):
res2 = fxf2.rate("eurusd", dt(2022, 3, 1))
assert res1 == res2

def test_separable_system(self, usdusd, eureur, usdeur, cadcad, cadcol):
pair = choice(["usdcad", "cadusd"])
pair2 = choice(["eurusd", "usdeur"])

fxr1 = FXRates({pair: 1.25}, settlement=dt(2022, 1, 3))
fxr2 = FXRates({pair2: 2.0}, settlement=dt(2022, 1, 2))
@pytest.mark.parametrize("base1", [NoInput(0), "usd", "cad"])
@pytest.mark.parametrize("base2", [NoInput(0), "eur", "usd"])
@pytest.mark.parametrize("pair1", ["cadusd", "usdcad"])
@pytest.mark.parametrize("pair2", ["usdeur", "eurusd"])
def test_separable_system(
self, usdusd, eureur, usdeur, cadcad, cadcol, base1, base2, pair1, pair2
):
fxr1 = FXRates({pair1: 1.25}, settlement=dt(2022, 1, 3), base=base1)
fxr2 = FXRates({pair2: 2.0}, settlement=dt(2022, 1, 2), base=base2)

curves = {"usdusd": usdusd, "eureur": eureur, "cadcad": cadcad, "cadusd": cadcol, "usdeur": usdeur}
fxf1 = FXForwards([fxr1, fxr2], curves, base="usd")
fxf2 = FXForwards([fxr1, fxr2], curves, base="eur")
fxf3 = FXForwards([fxr1, fxr2], curves, base="cad")
curves = {
"usdusd": usdusd,
"eureur": eureur,
"cadcad": cadcad,
"cadusd": cadcol,
"usdeur": usdeur,
}
fxf1 = FXForwards([fxr2, fxr1], curves, base="usd")
fxf2 = FXForwards([fxr2, fxr1], curves, base="eur")
fxf3 = FXForwards([fxr2, fxr1], curves, base="cad")

for pair in ["usdcad", "cadeur", "eurusd"]:
assert fxf1.rate(pair, dt(2022, 3, 20)) == fxf2.rate(pair, dt(2022, 3, 20))
Expand All @@ -726,7 +737,13 @@ def test_dependent_acyclic_system(self, usdusd, eureur, usdeur, cadcad, cadcol):
fxr1 = FXRates({pair2: 1.25}, settlement=dt(2022, 1, 3))
fxr2 = FXRates({pair: 2.0}, settlement=dt(2022, 1, 2))

curves = {"usdusd": usdusd, "eureur": eureur, "cadcad": cadcad, "cadeur": cadcol, "usdeur": usdeur}
curves = {
"usdusd": usdusd,
"eureur": eureur,
"cadcad": cadcad,
"cadeur": cadcol,
"usdeur": usdeur,
}
fxf1 = FXForwards([fxr1, fxr2], curves, base="usd")
fxf2 = FXForwards([fxr1, fxr2], curves, base="eur")
fxf3 = FXForwards([fxr1, fxr2], curves, base="cad")
Expand All @@ -735,43 +752,40 @@ def test_dependent_acyclic_system(self, usdusd, eureur, usdeur, cadcad, cadcol):
assert fxf1.rate(pair, dt(2022, 3, 20)) == fxf2.rate(pair, dt(2022, 3, 20))
assert fxf1.rate(pair, dt(2022, 3, 20)) == fxf3.rate(pair, dt(2022, 3, 20))

def test_fx_forwards_base_always_results_in_correct_currency_order(usdusd):
from itertools import combinations
from random import choice

currencies = ["eur", "gbp", "sek", "nok"]
pairs = [f"{c[0]}{c[1]}" for c in combinations(currencies, 2)]

for i in range(20):
# Build a random set of curves
selected_indices = choice(list(combinations([0, 1, 2, 3], 3)))
curve_pairs = [pairs[i] for i in selected_indices]
curve_pairs.extend(["eureur", "gbpgbp", "seksek", "noknok"])

# Use two different settlement frames
selected_indices = choice(list(combinations([0, 1, 2, 3], 3)))
ccy_pairs = [pairs[i] for i in selected_indices]
fxr1 = FXRates({ccy_pairs[0]: 10.0, ccy_pairs[1]: 25.0}, settlement=dt(2022, 1, 3))
fxr2 = FXRates({ccy_pairs[2]: 100.0}, settlement=dt(2022, 1, 4))

# Combine if possible
base = choice(currencies)
try:
fxf = FXForwards([fxr1, fxr2], {k: usdusd for k in curve_pairs}, base=base)
except ValueError:
try:
fxf = FXForwards([fxr2, fxr1], {k: usdusd for k in curve_pairs}, base=base)
except ValueError as e:
print(e)
print("fxr1 currencies: ", fxr1.currencies_list)
print("fxr2 currencies: ", fxr2.currencies_list)
print("base: ", base)
print("curve_pairs: ", curve_pairs)
def test_fx_forwards_base_always_results_in_correct_currency_order(self, usdusd):
from itertools import combinations
from random import choice

currencies = ["eur", "gbp", "sek", "nok"]
pairs = [f"{c[0]}{c[1]}" for c in combinations(currencies, 2)]

pass
for i in range(20):
# Build a random set of curves
selected_indices = choice(list(combinations([0, 1, 2, 3], 3)))
curve_pairs = [pairs[i] for i in selected_indices]
curve_pairs.extend(["eureur", "gbpgbp", "seksek", "noknok"])

# Use two different settlement frames
selected_indices = choice(list(combinations([0, 1, 2, 3], 3)))
ccy_pairs = [pairs[i] for i in selected_indices]
fxr1 = FXRates({ccy_pairs[0]: 10.0, ccy_pairs[1]: 25.0}, settlement=dt(2022, 1, 3))
fxr2 = FXRates({ccy_pairs[2]: 100.0}, settlement=dt(2022, 1, 4))

# Combine if possible
base = choice(currencies)
try:
fxf = FXForwards([fxr1, fxr2], {k: usdusd for k in curve_pairs}, base=base)
except ValueError:
try:
fxf = FXForwards([fxr2, fxr1], {k: usdusd for k in curve_pairs}, base=base)
except ValueError as e:
print(e)
print("fxr1 currencies: ", fxr1.currencies_list)
print("fxr2 currencies: ", fxr2.currencies_list)
print("base: ", base)
print("curve_pairs: ", curve_pairs)

pass


def test_multiple_settlement_forwards() -> None:
Expand Down

0 comments on commit c8132d5

Please sign in to comment.