Esc

    The API is under testing and may change before release.

    Equity total return swap

    This page lists what you need to value an equity total return swap on one name or a basket: the constructors' arguments, the market they read, how the swap is valued, and an example you can run. A total return swap passes a share's return, its price change and its dividends, from one side to the other in exchange for a funding leg.

    Quick example

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

    This values a swap on a thousand ACME shares from 110 against 4.5% fixed funding, three months into a semi-annual period, with ACME at 120. It takes the payer's side, adds a dividend of 0.80 a share gone ex during the period, and extends the swap to a basket of ACME and BETA. Each value is printed beside the formula below, worked out from the trade's terms.

    Rust
    use chrono::NaiveDate;
    use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
    use helixrs::{
        AnyInstrument, CollateralSpec, Currency, EquityTotalReturnSwap, FixingStore, MarketContext,
        QuoteSheet,
    };
    
    type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
    
    const FUNDING: f64 = 0.045;
    
    fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
        NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
    }
    
    /// Fixed funding at `FUNDING`: zero gearing on the index, the rate as margin.
    fn funded(mut swap: EquityTotalReturnSwap, paying: bool) -> AnyInstrument {
        swap.funding_margin = FUNDING;
        swap.funding_gearing = 0.0;
        swap.pay_total_return = paying;
        AnyInstrument::EquityTotalReturnSwap(Box::new(swap))
    }
    
    fn main() -> Outcome<()> {
        let close = date(SOFR_CLOSE_DATE);
        let usd: Currency = "USD".parse()?;
        let market = MarketContext::as_of(close)
            .with_recipe_quotes(
                &CollateralSpec::Currency(usd),
                &QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?,
            )?
            .with_equity_spot("ACME", 120.0)?
            .with_equity_spot("BETA", 45.0)?;
        // Semi-annual resets; the first period started in July, before the close.
        let resets = vec![date((2026, 7, 1)), date((2027, 1, 4)), date((2027, 7, 1))];
        #[allow(clippy::cast_precision_loss)] // days into the period
        let elapsed = (close - resets[0]).num_days() as f64 / 360.0;
        // The total return on 1,000 ACME shares from 110, against 4.5% fixed funding.
        let acme = |paying: bool| -> Outcome<AnyInstrument> {
            Ok(funded(
                EquityTotalReturnSwap::new("ACME", usd, 1000.0, 110.0, resets.clone())?,
                paying,
            ))
        };
    
        // Receiving the total return: the shares' gain since the period began, less
        // the funding accrued on the notional they started at, on ACT/360.
        let receiver = acme(false)?.npv(&market)?;
        let by_formula = 1000.0 * (120.0 - 110.0) - 1000.0 * 110.0 * FUNDING * elapsed;
        println!("receiver    {receiver:.2}");
        println!("by formula  {by_formula:.2}");
    
        // Paying the total return is the same swap from the other side.
        let payer = acme(true)?.npv(&market)?;
        println!("payer       {payer:.2}");
    
        // A dividend gone ex during the period is part of the return: 0.80 a share.
        let with_dividend = market.clone().with_fixings(FixingStore::from_observations(&[(
            "DIV-ACME",
            date((2026, 8, 14)),
            0.80,
        )])?);
        let paid_out = acme(false)?.npv(&with_dividend)?;
        println!("with div    {paid_out:.2}");
    
        // On a basket, each name returns on its own spot, and the funding accrues on
        // the sum of quantity times initial price.
        let basket = funded(
            EquityTotalReturnSwap::basket(
                vec![
                    ("ACME".to_owned(), 1000.0, 110.0),
                    ("BETA".to_owned(), 2000.0, 50.0),
                ],
                usd,
                resets.clone(),
            )?,
            false,
        );
        let gains = 1000.0 * (120.0 - 110.0) + 2000.0 * (45.0 - 50.0);
        let funding = (1000.0 * 110.0 + 2000.0 * 50.0) * FUNDING * elapsed;
        println!("basket      {:.2}", basket.npv(&market)?);
        println!("by formula  {:.2}", gains - funding);
    
        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_equity_spot("ACME", 120.0)
        .with_equity_spot("BETA", 45.0)
    )
    # Semi-annual resets; the first period started in July, before the close.
    RESETS = [dt.date(2026, 7, 1), dt.date(2027, 1, 4), dt.date(2027, 7, 1)]
    FUNDING = 0.045
    ELAPSED = (SOFR_CLOSE_DATE - RESETS[0]).days / 360.0
    
    def acme(**kw):
        """The total return on 1,000 ACME shares from 110, against 4.5% fixed funding."""
        return hlx.products.equity_total_return_swap(
            "ACME", "USD", quantity=1000.0, initial_price=110.0, reset_schedule=RESETS, funding_rate=FUNDING, **kw
        )
    
    # Receiving the total return: the shares' gain since the period began, less
    # the funding accrued on the notional they started at, on ACT/360.
    receiver = acme().npv(market)
    by_formula = 1000.0 * (120.0 - 110.0) - 1000.0 * 110.0 * FUNDING * ELAPSED
    print(f"receiver    {receiver:.2f}")
    print(f"by formula  {by_formula:.2f}")
    
    # Paying the total return is the same swap from the other side.
    payer = acme(paying_total_return=True).npv(market)
    print(f"payer       {payer:.2f}")
    
    # A dividend gone ex during the period is part of the return: 0.80 a share.
    with_dividend = market.with_fixings([("DIV-ACME", dt.date(2026, 8, 14), 0.80)])
    paid_out = acme().npv(with_dividend)
    print(f"with div    {paid_out:.2f}")
    
    # On a basket, each name returns on its own spot, and the funding accrues on
    # the sum of quantity times initial price.
    basket = hlx.products.equity_basket_total_return_swap(
        [("ACME", 1000.0, 110.0), ("BETA", 2000.0, 50.0)], "USD", reset_schedule=RESETS, funding_rate=FUNDING
    )
    gains = 1000.0 * (120.0 - 110.0) + 2000.0 * (45.0 - 50.0)
    funding = (1000.0 * 110.0 + 2000.0 * 50.0) * FUNDING * ELAPSED
    print(f"basket      {basket.npv(market):.2f}")
    print(f"by formula  {gains - funding:.2f}")
    receiver    8735.00
    by formula  8735.00
    payer       -8735.00
    with div    9535.00
    basket      -2415.00
    by formula  -2415.00

    The receiver gets ten dollars a share of gain less the funding accrued since July. The payer holds the same value with the sign turned. The dividend adds 0.80 on each of the thousand shares. In the basket BETA has fallen from 50 to 45, so the basket's value is below zero.

    How it's priced

    In the period under way, starting on tst_s, a swap on QjQ_j shares of each constituent jj is worth to the receiver

    V=∑jQj (Sj−Sj(ts)+Dj)−r N τ(ts,t),V = \sum_j Q_j\,(S_j - S_j(t_s) + D_j) - r\,N\,\tau(t_s, t),

    with SjS_j today's spot, Sj(ts)S_j(t_s) the period's start price, DjD_j the dividends gone ex since then, rr the funding rate, τ\tau the ACT/360 fraction to today and NN the funding notional. The value is undiscounted, and periods not yet started are worth nothing; see how equity products work.

    API

    Argument Type Default Meaning
    products.equity_total_return_swap(name, currency, ...) str, str required a swap on one share; Rust EquityTotalReturnSwap::new(name, currency, quantity, initial_price, resets)
    quantity float required the number of shares
    initial_price float required the price the first period starts from
    reset_schedule list of date required the period boundaries; a later period starts from the EQ-<name> fixing at its reset
    paying_total_return bool False pay the return and receive the funding; Rust pay_total_return
    funding_rate float None a fixed funding rate; Rust sets funding_margin to the rate and funding_gearing to 0.0
    funding_index, funding_margin, funding_gearing str, float, float None, 0.0, 1.0 floating funding: the index, the margin over it, and the gearing on it
    funding_notional float quantity times initial price, summed the notional the funding accrues on
    products.equity_basket_total_return_swap(constituents, currency, ...) list of (name, quantity, initial_price), str required the same swap on a basket; the other arguments as above; Rust EquityTotalReturnSwap::basket(constituents, currency, resets)
    Symbol Meaning Rust Python
    rr fixed funding funding_margin with funding_gearing = 0.0 funding_rate=
    the side payer or receiver pay_total_return paying_total_return=
    SjS_j the spot MarketContext::with_equity_spot MarketContext.with_equity_spot
    DjD_j dividends DIV-<name> in a FixingStore MarketContext.with_fixings([("DIV-<name>", date, amount)])
    VV its value AnyInstrument::npv Instrument.npv

    Defaults and limits

    • Funding: funding_rate and the floating-funding arguments don't combine; giving both raises ValueError. With no funding_index, valuing inside a period that has already begun is refused.
    • The market: each constituent's spot from with_equity_spot, the trade currency's curve, and, where they apply, DIV-<name> dividend fixings and EQ-<name> reset fixings.
    • What's valued: the period under way only, undiscounted. Periods not yet started add nothing.
    • Dividends: a dividend counts when its ex-date falls in the period under way.
    • Funding: accrues on ACT/360 from the period's start to the valuation date. The notional defaults to the sum of quantity times initial price.
    • Side: the builder books the receiver of the total return unless you set paying_total_return.

    See also

    • Source: crates/helixrs/src/instruments/financing.rs, with the valuation in crates/helixrs/src/market/pricers.rs
    • Related pages:
      • How equity products work, the ideas behind the valuation.
      • Bond total return swap, for the same swap on a bond.
      • Equity spots and vols, for the spots it reads.
      • The dated market, for the fixings a later period starts from.

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