# FX

FX rates with graph-based cross-rate triangulation, currency conversion, and FX forward pricing.

See also: [FX Rates & Forwards Guide](../guides/fx/fx-rates-forwards.md) | [Cross-Currency Swaps Guide](../guides/fx/cross-currency.md) | [Non-Deliverable Instruments Guide](../guides/fx/non-deliverable.md)

All types are available via flat import:

```python notest
from vade import FXPair, FXRates, FXForwards
```

______________________________________________________________________

## FXPair

Currency pair representation. _Rust-backed._

### Constructor

```python notest
FXPair(pair_str)
```

### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `pair_str` | `str` | *required* | Six-character lowercase currency pair (e.g., `"eurusd"`) |

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `.lhs` | `str` | Left-hand (base) currency (e.g., `"eur"`) |
| `.rhs` | `str` | Right-hand (quote) currency (e.g., `"usd"`) |

### Methods

| Method | Returns | Description |
|--------|---------|-------------|
| `.invert()` | `FXPair` | Return pair with currencies swapped |

### Example

```python
from vade import FXPair

pair = FXPair("eurusd")
pair.lhs  # 'eur'
pair.rhs  # 'usd'

inv = pair.invert()
inv.lhs  # 'usd'
inv.rhs  # 'eur'
```

### Advanced Example

```python
from vade import FXPair

# Parse multiple currency pairs
eurusd = FXPair("eurusd")
gbpusd = FXPair("gbpusd")
usdjpy = FXPair("usdjpy")

# Access base (lhs) and quote (rhs) currencies
assert eurusd.lhs == "eur"
assert eurusd.rhs == "usd"
assert gbpusd.lhs == "gbp"
assert gbpusd.rhs == "usd"
assert usdjpy.lhs == "usd"
assert usdjpy.rhs == "jpy"

# Inversion swaps base and quote
inv = eurusd.invert()
assert inv.lhs == "usd"
assert inv.rhs == "eur"

# Double inversion returns to original
inv2 = inv.invert()
assert inv2.lhs == "eur"
assert inv2.rhs == "usd"

# Cross-rate pair
eurgbp = FXPair("eurgbp")
assert eurgbp.lhs == "eur"
assert eurgbp.rhs == "gbp"

# Invert cross-rate
gbpeur = eurgbp.invert()
assert gbpeur.lhs == "gbp"
assert gbpeur.rhs == "eur"
```

______________________________________________________________________

## FXRates

FX rates with graph-based cross-rate triangulation and automatic AD variables. _Rust-backed._

Builds a currency graph from input rates and triangulates any cross-rate not directly provided.

### Constructor

```python notest
FXRates(fx_rates, settlement)
```

### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `fx_rates` | `Dict[str, float]` | *required* | Currency pair rates as `{"eurusd": 1.1, "gbpusd": 1.3}` |
| `settlement` | `datetime.date` | *required* | Settlement date for spot rates |

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `.currencies` | `list[str]` | List of currencies in the rate system |
| `.settlement` | `datetime.date` | Settlement date |

### Methods

| Method | Returns | Description |
|--------|---------|-------------|
| `.rate(pair)` | `float \| Dual` | Rate for pair (triangulates if needed) |
| `.convert(value, domestic, foreign)` | `float \| Dual` | Convert amount from domestic to foreign currency |
| `.convert_positions(positions, base)` | `float \| Dual` | Convert position dict to base currency total |
| `.restate(new_pairs)` | `FXRates` | Restate rates in terms of new pair definitions |

FX methods may return [Dual](../getting-started/type-system.md#dual) values when curves have AD-enabled nodes.

### Example

```python
import datetime
from vade import FXRates

fx = FXRates({"eurusd": 1.10, "gbpusd": 1.30}, datetime.date(2024, 1, 3))

# Direct rate lookup
eurusd = fx.rate("eurusd")
assert 1.09 < float(eurusd) < 1.11

# Cross-rate triangulation: EUR/GBP derived from EUR/USD and GBP/USD
eurgbp = fx.rate("eurgbp")
assert 0.8 < float(eurgbp) < 0.9  # ~1.10/1.30 = 0.846

# Currency conversion
usd_amount = fx.convert(1_000_000.0, "eur", "usd")
assert float(usd_amount) > 1_000_000  # EUR to USD at 1.10

fx.currencies  # ['eur', 'gbp', 'usd']
fx.settlement  # datetime.date(2024, 1, 3)
```

______________________________________________________________________

## FXForwards

FX forwards via interest rate parity. _Rust-backed._

Computes forward FX rates and forward points using spot rates and discount curves for each currency.

### Constructor

```python notest
FXForwards(fx_rates, settlements)
```

### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `fx_rates` | `FXRates` | *required* | Spot FX rates |
| `settlements` | `Dict[str, datetime.date]` | *required* | Settlement dates per currency code |

### Methods

| Method | Returns | Description |
|--------|---------|-------------|
| `.rate(pair, delivery, curves)` | `float \| Dual` | Forward FX rate at delivery date. `curves` is `Dict[str, DiscountCurve]` mapping currency codes to [discount curves](curves.md#discountcurve). |
| `.points(pair, delivery, curves)` | `float \| Dual` | Forward points (forward rate minus spot rate) |

Return types are [Dual](autodiff.md#dual) when AD variables are tracked.

### Example

```python
import datetime
from vade import DiscountCurve, FXRates, FXForwards

# Spot rates
fx = FXRates({"eurusd": 1.10}, datetime.date(2024, 1, 3))

# Discount curves per currency
usd_nodes = {
    datetime.date(2024, 1, 1): 1.0,
    datetime.date(2025, 1, 1): 0.96,
}
eur_nodes = {
    datetime.date(2024, 1, 1): 1.0,
    datetime.date(2025, 1, 1): 0.97,
}
usd_curve = DiscountCurve(usd_nodes, convention="act365f", id="usd")
eur_curve = DiscountCurve(eur_nodes, convention="act365f", id="eur")

# FX forwards
settlements = {"usd": datetime.date(2024, 1, 3), "eur": datetime.date(2024, 1, 3)}
fwd = FXForwards(fx, settlements)

curves = {"usd": usd_curve, "eur": eur_curve}
delivery = datetime.date(2025, 1, 3)

fwd_rate = fwd.rate("eurusd", delivery, curves)
assert float(fwd_rate) > 1.0  # forward rate reflects interest rate differential

fwd_pts = fwd.points("eurusd", delivery, curves)
assert isinstance(float(fwd_pts), float)  # forward points (forward - spot)
```

### Advanced Example

```python
import datetime
from vade import DiscountCurve, FXRates, FXForwards

# Spot rates for two major pairs
spot_date = datetime.date(2024, 6, 3)
fx = FXRates({"eurusd": 1.08, "gbpusd": 1.27}, spot_date)

# Discount curves per currency (USD, EUR, GBP)
# Higher discount factor = lower interest rate
usd_nodes = {datetime.date(2024, 1, 1): 1.0, datetime.date(2025, 7, 1): 0.955}
eur_nodes = {datetime.date(2024, 1, 1): 1.0, datetime.date(2025, 7, 1): 0.965}
gbp_nodes = {datetime.date(2024, 1, 1): 1.0, datetime.date(2025, 7, 1): 0.960}

usd_curve = DiscountCurve(usd_nodes, convention="act365f", interpolation="log_linear", id="usd")
eur_curve = DiscountCurve(eur_nodes, convention="act365f", interpolation="log_linear", id="eur")
gbp_curve = DiscountCurve(gbp_nodes, convention="act365f", interpolation="log_linear", id="gbp")

# Settlement dates per currency
settlements = {
    "usd": spot_date,
    "eur": spot_date,
    "gbp": spot_date,
}
fwd = FXForwards(fx, settlements)
curves = {"usd": usd_curve, "eur": eur_curve, "gbp": gbp_curve}

# Forward rates at multiple delivery dates show term structure
delivery_1m = datetime.date(2024, 7, 3)
delivery_3m = datetime.date(2024, 9, 3)
delivery_6m = datetime.date(2024, 12, 3)
delivery_1y = datetime.date(2025, 6, 3)

rate_1m = float(fwd.rate("eurusd", delivery_1m, curves))
rate_3m = float(fwd.rate("eurusd", delivery_3m, curves))
rate_6m = float(fwd.rate("eurusd", delivery_6m, curves))
rate_1y = float(fwd.rate("eurusd", delivery_1y, curves))

# Forward rates increase with tenor (EUR rates < USD rates => EUR appreciates)
assert 1.08 < rate_1m < rate_3m < rate_6m < rate_1y

# Forward points = forward rate - spot rate
pts_1m = float(fwd.points("eurusd", delivery_1m, curves))
pts_3m = float(fwd.points("eurusd", delivery_3m, curves))
pts_6m = float(fwd.points("eurusd", delivery_6m, curves))
pts_1y = float(fwd.points("eurusd", delivery_1y, curves))

# Points are positive and increase with tenor
assert 0 < pts_1m < pts_3m < pts_6m < pts_1y
assert 0.001 < pts_1m < 0.01   # small for 1M
assert 0.005 < pts_1y < 0.02   # larger for 1Y

# GBP/USD forward rates via the same curve set
gbp_rate_1y = float(fwd.rate("gbpusd", delivery_1y, curves))
assert 1.27 < gbp_rate_1y < 1.30  # GBP also appreciates vs USD

# Cross-rate: EUR/GBP derived from triangulation
eurgbp_fwd = float(fwd.rate("eurgbp", delivery_1y, curves))
assert 0.84 < eurgbp_fwd < 0.87  # ~1.09/1.28
```
