Esc

    The API is under testing and may change before release.

    NDS and NDCCS

    This page lists what you need to draft and value non-deliverable swaps: their templates, the arguments they take, the curves they read, and examples you can run. A non-deliverable swap (NDS) is a local-currency interest rate swap whose every payment settles in dollars. A non-deliverable cross-currency swap (NDCCS) exchanges a local fixed leg against a dollar SOFR leg, with the local leg settling in dollars the same way.

    Quick example

    The example reads its dollar quotes from the desk sheets; load them once and it runs as shown.

    This marks the dollar curve and the won and rupee offshore curves from NDF outrights. It adds a won sheet carrying KOFR swaps and KRW-CD-NDS par rates, which marks the 3M CD projection, and checks that each quoted NDS reprices. Then it values a four-year won NDS between the quotes, and a two-year rupee NDCCS of 885 million rupees fixed against ten million dollars on SOFR.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, Sheet, USD_SOFR_CLOSE};
    use helixrs::{
        AnyInstrument, AnyTemplate, CollateralSpec, CrossCurrencySwap, Currency, CurrencyPair, FxRates,
        MarketContext, Period, QuoteSheet, market_data_needs, template,
    };
    
    type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
    
    fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let (usd, krw, inr): (Currency, Currency, Currency) =
            ("USD".parse()?, "KRW".parse()?, "INR".parse()?);
        let won: Sheet = &[
            ("KRW-USD-NDF", "1M", 1388.5),
            ("KRW-USD-NDF", "3M", 1385.0),
            ("KRW-USD-NDF", "6M", 1380.0),
            ("KRW-USD-NDF", "1Y", 1371.0),
            ("KRW-USD-NDF", "2Y", 1355.0),
        ];
        let rupee: Sheet = &[
            ("INR-USD-NDF", "1M", 88.70),
            ("INR-USD-NDF", "3M", 89.10),
            ("INR-USD-NDF", "6M", 89.75),
            ("INR-USD-NDF", "1Y", 91.00),
            ("INR-USD-NDF", "2Y", 93.50),
            ("INR-USD-NDF", "3Y", 96.00),
        ];
    
        // Dollars from the SOFR close, the spots, and each offshore curve from its
        // recipe: won and rupees under dollar collateral, from NDF outrights.
        let market = MarketContext::as_of(close)
            .with_recipe_quotes(&CollateralSpec::Currency(usd), &QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?)?
            .with_fx_rates(FxRates::from_quotes(&[
                (CurrencyPair::new(usd, krw), 1390.0),
                (CurrencyPair::new(usd, inr), 88.50),
            ])?)
            .with_recipe_quotes(&"KRW-CSA-USD".parse()?, &QuoteSheet::from_rows(won.iter().copied())?)?
            .with_recipe_quotes(&"INR-CSA-USD".parse()?, &QuoteSheet::from_rows(rupee.iter().copied())?)?;
    
        // Won swaps on the 3M CD rate. The KRW recipe marks KOFR from its OIS swaps and,
        // when the sheet carries NDS par rates, the CD projection from them: each NDS
        // rung is solved against the dollar curve and the offshore won curve already
        // held, so the base market comes first.
        let cd_nds: Sheet = &[
            ("KRW-CD-NDS", "2Y", 0.0290),
            ("KRW-CD-NDS", "3Y", 0.0292),
            ("KRW-CD-NDS", "5Y", 0.0295),
            ("KRW-CD-NDS", "10Y", 0.0300),
        ];
        let kofr: Sheet = &[("KRW-KOFR-OIS", "1Y", 0.0255), ("KRW-KOFR-OIS", "2Y", 0.0262), ("KRW-KOFR-OIS", "5Y", 0.0275)];
        let with_cd = market.clone().with_recipe_quotes(
            &CollateralSpec::Currency(krw),
            &QuoteSheet::from_rows(kofr.iter().chain(cd_nds).copied())?,
        )?;
        for (role, key) in with_cd.contents() {
            if role == "projection" || key.ends_with("CSA-USD") {
                println!("{role:<10} {key}");
            }
        }
        let nds_template = template("KRW-CD-NDS")?;
        let mut worst: f64 = 0.0;
        for (_, tenor, quote) in cd_nds {
            let rung = nds_template.nds(close, tenor.parse()?, 10_000_000_000.0, *quote, false)?;
            worst = worst.max((rung.par_rate(&with_cd)? - quote).abs());
        }
        println!("rungs reprice within 1e-9: {}", if worst < 1e-9 { "yes" } else { "no" });
    
        // A four-year NDS, between the quoted tenors: what it reads, and its par rate.
        let four_years: Period = "4Y".parse()?;
        let nds = nds_template.nds(close, four_years, 10_000_000_000.0, 0.0293, false)?;
        let needs = market_data_needs(&nds)?;
        println!("needs      discount({})", needs.discount);
        if let Some(projection) = needs.projection {
            println!("needs      projection({projection})");
        }
        if let Some(foreign) = needs.foreign_discount {
            println!("needs      foreign_discount({foreign})");
        }
        let par = nds.par_rate(&with_cd)?;
        let at_par = nds_template.nds(close, four_years, 10_000_000_000.0, par, false)?;
        println!("4Y par     {par:.6}");
        println!("at par     {:.2}", at_par.npv(&with_cd)?.abs());
    
        // A two-year rupee NDCCS: 885 million rupees fixed against ten million dollars
        // on SOFR. The rupee leg's coupons and redemption settle in dollars.
        let AnyTemplate::Xccy(ndccs_terms) = template("INR-USD-NDCCS")? else {
            unreachable!("INR-USD-NDCCS is a cross-currency template")
        };
        let draft = |fixed: f64| -> Outcome<AnyInstrument> {
            Ok(AnyInstrument::CrossCurrency(Box::new(CrossCurrencySwap::from_template(
                &ndccs_terms,
                date((2026, 10, 5)),
                date((2028, 10, 5)),
                885_000_000.0,
                1.0 / 88.5,
                fixed,
            )?)))
        };
        let ndccs = draft(0.065)?;
        // Receiving 6.5% in rupees is worth less than nothing on this market, so the
        // rate that makes the swap fair is higher.
        let ndccs_npv = ndccs.npv(&market)?;
        let fair_rate = ndccs.par_rate(&market)?;
        println!("npv        {ndccs_npv:.2}");
        println!("par rate   {fair_rate:.6}");
        let records = ndccs.cashflow_records(&market)?;
        let column = |name: &str| records.column(name).expect("a cashflow column");
        let (kind, currency, settles) = (
            column("kind").text().expect("text"),
            column("currency").text().expect("text"),
            column("settlement_currency").text().expect("text"),
        );
        let (fixing, paid) = (
            column("fx_fixing_date").dates().expect("dates"),
            column("payment_date").dates().expect("dates"),
        );
        let mut rupee_flows = Vec::new();
        for i in 0..records.len() {
            if currency[i] == "INR" {
                let (fixing, paid) = (fixing[i].expect("a fixing"), paid[i].expect("a payment"));
                println!("{:<10} INR settles {} fixing {fixing} paid {paid}", kind[i], settles[i]);
                rupee_flows.push((settles[i].clone(), fixing, paid));
            }
        }
        let fair = draft(fair_rate)?;
        println!("at par     {:.2}", fair.npv(&market)?.abs());
    
        // Every payment settles in dollars, so both swaps value in dollars, whatever
        // currency their coupons are struck in.
        println!("NDS        {}", nds.currency());
        println!("NDCCS      {}", ndccs.currency());
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    # Dollars from the SOFR close, the spots, and each offshore curve from its
    # recipe: won and rupees under dollar collateral, from NDF outrights.
    market = (
        hlx.MarketContext(SOFR_CLOSE_DATE)
        .with_recipe_quotes("USD", USD_SOFR_CLOSE)
        .with_fx_rates([("USD", "KRW", 1390.0), ("USD", "INR", 88.50)])
        .with_recipe_quotes("KRW-CSA-USD", {"KRW-USD-NDF": {"1M": 1388.5, "3M": 1385.0, "6M": 1380.0, "1Y": 1371.0, "2Y": 1355.0}})
        .with_recipe_quotes("INR-CSA-USD", {"INR-USD-NDF": {"1M": 88.70, "3M": 89.10, "6M": 89.75, "1Y": 91.00, "2Y": 93.50, "3Y": 96.00}})
    )
    
    # Won swaps on the 3M CD rate. The KRW recipe marks KOFR from its OIS swaps and,
    # when the sheet carries NDS par rates, the CD projection from them: each NDS
    # rung is solved against the dollar curve and the offshore won curve already
    # held, so the base market comes first.
    CD_NDS = {"2Y": 0.0290, "3Y": 0.0292, "5Y": 0.0295, "10Y": 0.0300}
    with_cd = market.with_recipe_quotes(
        "KRW", {"KRW-KOFR-OIS": {"1Y": 0.0255, "2Y": 0.0262, "5Y": 0.0275}, "KRW-CD-NDS": CD_NDS}
    )
    for role, key in with_cd.contents():
        if role == "projection" or key.endswith("CSA-USD"):
            print(f"{role:<10} {key}")
    nds_template = hlx.templates.get("KRW-CD-NDS")
    worst = max(
        abs(nds_template.nds(SOFR_CLOSE_DATE, tenor, notional=10_000_000_000.0, fixed_rate=quote).par_rate(with_cd) - quote)
        for tenor, quote in CD_NDS.items()
    )
    print(f"rungs reprice within 1e-9: {'yes' if worst < 1e-9 else 'no'}")
    
    # A four-year NDS, between the quoted tenors: what it reads, and its par rate.
    nds = nds_template.nds(SOFR_CLOSE_DATE, "4Y", notional=10_000_000_000.0, fixed_rate=0.0293)
    for role, key in nds.market_data_needs():
        print(f"needs      {role}({key})")
    par = nds.par_rate(with_cd)
    at_par = nds_template.nds(SOFR_CLOSE_DATE, "4Y", notional=10_000_000_000.0, fixed_rate=par)
    print(f"4Y par     {par:.6f}")
    print(f"at par     {abs(at_par.npv(with_cd)):.2f}")
    
    # A two-year rupee NDCCS: 885 million rupees fixed against ten million dollars
    # on SOFR. The rupee leg's coupons and redemption settle in dollars.
    ndccs_template = hlx.templates.get("INR-USD-NDCCS")
    ndccs = ndccs_template.cross_currency_swap(
        dt.date(2026, 10, 5), dt.date(2028, 10, 5), notional=885_000_000.0, fx_rate=1 / 88.5, fixed_rate=0.065
    )
    # Receiving 6.5% in rupees is worth less than nothing on this market, so the
    # rate that makes the swap fair is higher.
    ndccs_npv = ndccs.npv(market)
    fair_rate = ndccs.par_rate(market)
    print(f"npv        {ndccs_npv:.2f}")
    print(f"par rate   {fair_rate:.6f}")
    records = ndccs.cashflow_records(market).to_dict()
    rupee_flows = [
        (kind, settles, fixing, paid)
        for kind, currency, settles, fixing, paid in zip(
            records["kind"], records["currency"], records["settlement_currency"], records["fx_fixing_date"], records["payment_date"]
        )
        if currency == "INR"
    ]
    for kind, settles, fixing, paid in rupee_flows:
        print(f"{kind:<10} INR settles {settles} fixing {fixing} paid {paid}")
    fair = ndccs_template.cross_currency_swap(
        dt.date(2026, 10, 5), dt.date(2028, 10, 5), notional=885_000_000.0, fx_rate=1 / 88.5, fixed_rate=fair_rate
    )
    print(f"at par     {abs(fair.npv(market)):.2f}")
    
    # Every payment settles in dollars, so both swaps value in dollars, whatever
    # currency their coupons are struck in.
    print(f"NDS        {nds.npv_ccy()}")
    print(f"NDCCS      {ndccs.npv_ccy()}")
    discount   INR-CSA-USD
    discount   KRW-CSA-USD
    projection KRW-CD-3M
    projection KRW-KOFR
    projection USD-SOFR
    rungs reprice within 1e-9: yes
    needs      discount(USD)
    needs      projection(KRW-CD-3M)
    needs      foreign_discount(KRW-CSA-USD)
    4Y par     0.029387
    at par     0.00
    npv        -168804.87
    par rate   0.074226
    Fixed      INR settles USD fixing 2027-04-01 paid 2027-04-07
    Fixed      INR settles USD fixing 2027-10-01 paid 2027-10-07
    Fixed      INR settles USD fixing 2028-03-31 paid 2028-04-10
    Fixed      INR settles USD fixing 2028-10-03 paid 2028-10-10
    Redemption INR settles USD fixing 2028-10-03 paid 2028-10-05
    at par     0.00
    NDS        USD
    NDCCS      USD

    The four-year NDS reads the dollar discount, the CD projection and the offshore won curve, and its par rate sits between the three- and five-year quotes. Receiving 6.5% in rupees is worth less than nothing on this market, so the NDCCS's par rate is higher. Every rupee flow names the fixing it converts on, two business days or more before it's paid in dollars, and both swaps value in dollars.

    How it's priced

    An NDS's fixed and floating legs are projected and discounted as a local swap's would be, each payment converted at its fixing's forward and discounted in dollars. The local discount is the offshore curve, which NDF outrights fix by covered interest parity:

    PKRW|USD(T)=SF(T) PUSD(T).P_{\text{KRW|USD}}(T) = \frac{S}{F(T)}\,P_{\text{USD}}(T).

    An NDS's par rate is the fixed rate at which it's worth nothing. An NDCCS's is the local fixed rate at which its converted local leg equals its SOFR leg. Why NDS quotes mark the CD projection rather than the offshore curve is covered in how FX products work.

    API

    Argument Type Default Meaning
    templates.get(code) str required the swap's terms: KRW-CD-NDS, INR-USD-NDCCS and the rest; Rust template(code)?
    Template.nds(trade_date, tenor, ...) date, str required drafts an NDS; Rust AnyTemplate::nds(trade_date, tenor, notional, fixed_rate, pays_fixed)
    notional float required the local-currency notional
    fixed_rate float required the fixed rate on the local leg
    Template.cross_currency_swap(start, end, ...) date, date required an NDCCS from the template's terms; Rust CrossCurrencySwap::from_template(terms, start, end, notional, fx_rate, fixed_rate)
    fx_rate float required units of the dollar leg per one unit of the local leg, so 885 million rupees at 1/88.5 is ten million dollars
    calibration.helpers.swap(code, trade_date, tenor, quote) str, date, str, float — one NDS rung for with_curve_helpers; Rust nds_rung(..).solving_projection()
    Symbol Meaning Rust Python
    PKRW\|USDP_{\text{KRW\|USD}} the offshore curve MarketContext::with_recipe_quotes under KRW-CSA-USD MarketContext.with_recipe_quotes("KRW-CSA-USD", …)
    PCDP_{\text{CD}} the 3M CD projection, from NDS par rates with_recipe_quotes(&CollateralSpec::Currency(krw), &sheet) with KRW-CD-NDS rows with_recipe_quotes("KRW", {"KRW-KOFR-OIS": …, "KRW-CD-NDS": …})
    what each reads discount, projection, offshore curve market_data_needs Instrument.market_data_needs
    par rate the fair fixed rate AnyInstrument::par_rate Instrument.par_rate
    settlement per flow currency, settlement currency, fixing date AnyInstrument::cashflow_records Instrument.cashflow_records
    the npv currency the settlement currency, USD AnyInstrument::currency Instrument.npv_ccy

    Defaults and limits

    • The market: the dollar curve, the pair's spot, and the offshore curve under KRW-CSA-USD or INR-CSA-USD, which the offshore recipe marks from NDF outrights. A won NDS also reads the 3M CD projection.
    • The CD projection: the KRW recipe marks it from KRW-CD-NDS par rates when the sheet quotes them. Each rung is solved against the dollar curve and the offshore won curve, so add those before the KRW sheet. A sheet without NDS quotes marks KOFR alone. No other non-deliverable currency's recipe carries an NDS strip.
    • Settlement: every payment settles in dollars, so both swaps value in dollars, whatever currency their coupons are struck in.
    • The agreed rate: an NDCCS fx_rate that isn't positive and finite is refused when the swap is built. One that reads as the market's rate inverted, more than ten times off it while its reciprocal is within ten times, is refused when the swap is valued, with a PricingError naming both orientations.
    • Terms: each market's swap terms, fixing source and lags are in the non-deliverable conventions catalog.

    More examples

    Two more cases on the same market. The CD projection is built a second way, from one curve helper per NDS par rate: these are the rungs the recipe builds, so the four-year par rate agrees with the recipe's. Then an NDCCS whose fx_rate is the market quote, 88.5 rupees per dollar, the wrong way round: it builds, and valuing it is refused with a message that names both orientations.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, Sheet, USD_SOFR_CLOSE};
    use helixrs::{
        AnyInstrument, AnyTemplate, CollateralSpec, CrossCurrencySwap, Currency, CurrencyPair, FxRates,
        MarketContext, Period, PricingError, QuoteSheet, nds_rung, template,
    };
    
    type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
    
    fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let (usd, krw, inr): (Currency, Currency, Currency) =
            ("USD".parse()?, "KRW".parse()?, "INR".parse()?);
        let won: Sheet = &[
            ("KRW-USD-NDF", "1M", 1388.5),
            ("KRW-USD-NDF", "3M", 1385.0),
            ("KRW-USD-NDF", "6M", 1380.0),
            ("KRW-USD-NDF", "1Y", 1371.0),
            ("KRW-USD-NDF", "2Y", 1355.0),
        ];
        let rupee: Sheet = &[
            ("INR-USD-NDF", "1M", 88.70),
            ("INR-USD-NDF", "3M", 89.10),
            ("INR-USD-NDF", "6M", 89.75),
            ("INR-USD-NDF", "1Y", 91.00),
            ("INR-USD-NDF", "2Y", 93.50),
            ("INR-USD-NDF", "3Y", 96.00),
        ];
        let market = MarketContext::as_of(close)
            .with_recipe_quotes(&CollateralSpec::Currency(usd), &QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?)?
            .with_fx_rates(FxRates::from_quotes(&[
                (CurrencyPair::new(usd, krw), 1390.0),
                (CurrencyPair::new(usd, inr), 88.50),
            ])?)
            .with_recipe_quotes(&"KRW-CSA-USD".parse()?, &QuoteSheet::from_rows(won.iter().copied())?)?
            .with_recipe_quotes(&"INR-CSA-USD".parse()?, &QuoteSheet::from_rows(rupee.iter().copied())?)?;
        let cd_nds: Sheet = &[
            ("KRW-CD-NDS", "2Y", 0.0290),
            ("KRW-CD-NDS", "3Y", 0.0292),
            ("KRW-CD-NDS", "5Y", 0.0295),
            ("KRW-CD-NDS", "10Y", 0.0300),
        ];
        let nds_template = template("KRW-CD-NDS")?;
        let four_years: Period = "4Y".parse()?;
        let nds = nds_template.nds(close, four_years, 10_000_000_000.0, 0.0293, false)?;
    
        // The CD projection from curve helpers: one NDS helper per par rate, solving it
        // against the dollar and offshore won curves the market holds. The recipe
        // builds these same rungs, so the 4Y par rate agrees with the recipe's.
        let kofr: Sheet = &[("KRW-KOFR-OIS", "1Y", 0.0255), ("KRW-KOFR-OIS", "2Y", 0.0262), ("KRW-KOFR-OIS", "5Y", 0.0275)];
        let with_cd = market.clone().with_recipe_quotes(
            &CollateralSpec::Currency(krw),
            &QuoteSheet::from_rows(kofr.iter().chain(cd_nds).copied())?,
        )?;
        let mut rungs = Vec::new();
        for (code, tenor, quote) in cd_nds {
            rungs.push(nds_rung(code, &nds_template, close, tenor.parse()?, *quote, 1.0)?.solving_projection());
        }
        let by_helpers = market.clone().with_curve_helpers(&rungs)?;
        let (recipe_par, helper_par) = (nds.par_rate(&with_cd)?, nds.par_rate(&by_helpers)?);
        let mut worst: f64 = 0.0;
        for (_, tenor, quote) in cd_nds {
            let rung = nds_template.nds(close, tenor.parse()?, 10_000_000_000.0, *quote, false)?;
            worst = worst.max((rung.par_rate(&by_helpers)? - quote).abs());
        }
        println!("4Y par     {recipe_par:.6} from the recipe");
        println!("4Y par     {helper_par:.6} from helpers");
        println!("rungs reprice within 1e-9: {}", if worst < 1e-9 { "yes" } else { "no" });
    
        // The NDCCS's fx_rate is dollars per rupee, 1/88.5. The market quote, 88.5
        // rupees per dollar, builds, but the valuation refuses it: it is more than ten
        // times off the market's rate while its reciprocal is within ten times.
        let AnyTemplate::Xccy(ndccs_terms) = template("INR-USD-NDCCS")? else {
            unreachable!("INR-USD-NDCCS is a cross-currency template")
        };
        let inverted = AnyInstrument::CrossCurrency(Box::new(CrossCurrencySwap::from_template(
            &ndccs_terms,
            date((2026, 10, 5)),
            date((2028, 10, 5)),
            885_000_000.0,
            88.5,
            0.065,
        )?));
        let refusal = inverted.npv(&market).err();
        let message = refusal.as_ref().map_or_else(|| "valued".to_string(), ToString::to_string);
        println!("{message}");
    
        Ok(())
    }
    Python
    import datetime as dt
    
    import helixio as hlx
    from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
    
    market = (
        hlx.MarketContext(SOFR_CLOSE_DATE)
        .with_recipe_quotes("USD", USD_SOFR_CLOSE)
        .with_fx_rates([("USD", "KRW", 1390.0), ("USD", "INR", 88.50)])
        .with_recipe_quotes("KRW-CSA-USD", {"KRW-USD-NDF": {"1M": 1388.5, "3M": 1385.0, "6M": 1380.0, "1Y": 1371.0, "2Y": 1355.0}})
        .with_recipe_quotes("INR-CSA-USD", {"INR-USD-NDF": {"1M": 88.70, "3M": 89.10, "6M": 89.75, "1Y": 91.00, "2Y": 93.50, "3Y": 96.00}})
    )
    CD_NDS = {"2Y": 0.0290, "3Y": 0.0292, "5Y": 0.0295, "10Y": 0.0300}
    nds_template = hlx.templates.get("KRW-CD-NDS")
    nds = nds_template.nds(SOFR_CLOSE_DATE, "4Y", notional=10_000_000_000.0, fixed_rate=0.0293)
    
    # The CD projection from curve helpers: one NDS helper per par rate, solving it
    # against the dollar and offshore won curves the market holds. The recipe
    # builds these same rungs, so the 4Y par rate agrees with the recipe's.
    with_cd = market.with_recipe_quotes(
        "KRW", {"KRW-KOFR-OIS": {"1Y": 0.0255, "2Y": 0.0262, "5Y": 0.0275}, "KRW-CD-NDS": CD_NDS}
    )
    helpers = hlx.calibration.helpers
    by_helpers = market.with_curve_helpers(
        [helpers.swap("KRW-CD-NDS", SOFR_CLOSE_DATE, tenor, quote) for tenor, quote in CD_NDS.items()]
    )
    recipe_par, helper_par = nds.par_rate(with_cd), nds.par_rate(by_helpers)
    worst = max(
        abs(nds_template.nds(SOFR_CLOSE_DATE, tenor, notional=10_000_000_000.0, fixed_rate=quote).par_rate(by_helpers) - quote)
        for tenor, quote in CD_NDS.items()
    )
    print(f"4Y par     {recipe_par:.6f} from the recipe")
    print(f"4Y par     {helper_par:.6f} from helpers")
    print(f"rungs reprice within 1e-9: {'yes' if worst < 1e-9 else 'no'}")
    
    # The NDCCS's fx_rate is dollars per rupee, 1/88.5. The market quote, 88.5
    # rupees per dollar, builds, but the valuation refuses it: it is more than ten
    # times off the market's rate while its reciprocal is within ten times.
    inverted = hlx.templates.get("INR-USD-NDCCS").cross_currency_swap(
        dt.date(2026, 10, 5), dt.date(2028, 10, 5), notional=885_000_000.0, fx_rate=88.5, fixed_rate=0.065
    )
    try:
        inverted.npv(market)
        message = "valued"
    except hlx.PricingError as refusal:
        message = str(refusal)
    print(message)
    4Y par     0.029387 from the recipe
    4Y par     0.029387 from helpers
    rungs reprice within 1e-9: yes
    the agreed fx_rate 88.5 is USD per one INR, and the market's is 1.129944e-2; it reads as the rate inverted — did you mean 1/88.5 = 1.129944e-2?

    See also

    • Source: crates/helixrs/src/statics/templates/nds.rs, crates/helixrs/src/statics/templates/xccy.rs, crates/helixrs/src/statics/recipes/offshore.rs, crates/helixrs/src/statics/recipes/ois.rs
    • Related pages:
      • How FX products work, the ideas behind the swaps.
      • NDF, for the forward whose outrights mark the offshore curve.
      • Cross-currency swap, for the deliverable swap.
      • Curve helpers, for the rungs a recipe binds.

    From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.