Skip to content

Fund vehicles and pricing

A backtest has five parts: the strategy (what is held), the implementation (screens, caps, costs and execution), the flows (money arriving and leaving), the vehicle (how the money is held) and the modelling assumptions. This page is about the vehicle: the structure a fund is built in, how it prices the units investors buy and sell, and, for an exchange-traded fund, how its shares trade on the exchange.

Keeping the vehicle separate is what makes fair comparisons possible: the same index, limits and flows run through a UK OEIC and a UCITS ETF differ only by what the structure does.

Structures and presets

Fund structures differ by country and by wrapper, but what changes a simulation is behaviour, and the world's wrappers fall into six behavioural archetypes:

Archetype Examples What the model does
Open-ended, dealt at NAV UK OEIC, AUT and ACS; SICAV and FCP; ICAV; US mutual fund; Japanese investment trust; Australian MIS Investors deal with the fund at its price each day; the fund trades for their flows, and its pricing method decides who pays for that trading
Exchange-traded ETFs in any of those wrappers Only authorised participants deal with the fund, in creation units; everyone else trades shares on an exchange at a market price
Closed-ended, listed Investment trust, US closed-end fund, REIT Fixed capital, changed only by buybacks and issuance; the shares trade at a persistent premium or discount
Interval US interval fund, UK LTAF, ELTIF Dealing on set dates, with limits on how much can leave
Unit investment trust US UIT A fixed portfolio, dividends held as cash until paid
Segregated mandate SMA One client's money; effectively the plain backtest

A preset is a named set of vehicle settings for one wrapper: how it deals, how it prices, its diversification limits and how it handles distributions. A preset is a starting point, and any setting can be changed. The first six presets cover the first two archetypes:

Preset Dealing Pricing Diversification limits
UK OEIC Daily, at the next valuation point Swing pricing UCITS
Luxembourg SICAV (UCITS) Daily, at the next valuation point Partial swing pricing UCITS
Irish ICAV (UCITS) Daily, at the next valuation point Anti-dilution levy UCITS
UCITS ETF Creation units, cash by default ETF market price UCITS
US mutual fund Daily, at the next NAV Single pricing, with an optional redemption fee 1940 Act diversified
US ETF Creation units, in kind by default ETF market price 1940 Act diversified

The pricing each preset starts with is a common choice for that wrapper, not a rule of it: most of these wrappers allow several methods.

Dealing. Every preset deals at the price set at the close of the day the flow arrives (forward pricing), so an investor never deals at a price that was already known.

Diversification limits become capacity caps, so a fund near a limit holds less of a name rather than breaking it:

  • UCITS: at most 10% of the fund in one issuer, and the holdings above 5% may add up to at most 40%. A fund replicating an index may hold up to 20% of one issuer, or 35% of a single one where the index is unusually concentrated.
  • 1940 Act diversified: for 75% of the fund, at most 5% in one issuer and at most 10% of an issuer's voting shares. Beacon applies this as: the holdings above 5% may add up to at most 25% of the fund, and no holding exceeds 10% of the company's shares outstanding.

Distributions follow the run's dividend policy: reinvested by default, as an accumulating share class does, or paid out, as a distributing class does.

Using a preset

Pass a preset as the backtest's vehicle, by its function or by name, and change any setting by passing it:

import logging

from beacon.backtest import (
    Backtest,
    DatedFlows,
    DilutionLevy,
    PeriodicFlows,
    preset,
    uk_oeic,
)
from beacon.index.constructor import IndexDefinition
from beacon.index.methodology import MarketCapWeighted
from beacon.testing import dataset

logging.getLogger("beacon").setLevel(logging.ERROR)  # keep the output short

fetcher = dataset.data_fetcher()
definition = IndexDefinition(
    index_id="SAMPLE", index_name="Sample Market-Cap Index",
    base_date="2023-01-03", base_value=1000.0, currency="USD",
    eligibility_rules=[], weighting_scheme=MarketCapWeighted(),
    rebalancing_frequency="QUARTERLY", calendar="XNYS",
    universe_identifiers=["AAA", "BBB", "CCC", "DDD", "EEE"],
)
flows = [PeriodicFlows(fraction=0.02), DatedFlows({"2024-03-01": -2_000_000.0})]

for vehicle in (uk_oeic(management_fee_bps=15),
                preset("irish_icav", pricing=DilutionLevy(rate_bps=25))):
    result = Backtest(initial_capital=10_000_000.0, transaction_cost_bps=10.0,
                      flows=flows, vehicle=vehicle, data_provider=fetcher,
                      ).run(definition, start="2023-01-03", end="2024-12-31")
    paid_in = sum(flow.adjustment for flow in result.flows)
    print(vehicle.name, round(result.nav_per_unit.iloc[-1], 4),
          "paid in by dealing investors:", round(paid_in, 2))

PRESETS lists every preset by name.

Dilution, and the four pricing methods

When investors buy into or sell out of an open-ended fund, the fund trades to invest or raise the money. That trading costs money: the bid-ask spread on each holding, commissions, taxes such as stamp duty, and the market impact of the trade. If nothing else happens, the cost comes out of the fund, so the investors who stayed pay for the ones who came or went. That loss is called dilution. A pricing method decides who pays.

In each method the fund's NAV per unit is first calculated at the close as usual. The method then sets the dealing price units are created or cancelled at, or adds a charge. Whatever a dealing investor pays above NAV, or receives below it, stays in the fund and offsets the trading cost.

Single pricing

Everyone deals at NAV per unit, and nothing is adjusted:

dealing price = NAV per unit

The trading cost of the day's flows is borne by the whole fund. This is the baseline the other three are measured against.

Dual pricing

The fund quotes two prices. Buyers pay the offer price: the value of the holdings at the prices they could be bought at, plus the cost of buying them. Sellers receive the bid price: the holdings at the prices they could be sold at, less the cost of selling them.

offer price = NAV per unit x (1 + offer adjustment)
bid price   = NAV per unit x (1 - bid adjustment)

Each investor pays the cost of their own dealing, so the fund is not diluted, but every buyer and seller pays the spread whether or not the fund had to trade much that day. This was the traditional method for UK unit trusts.

Swing pricing

The fund keeps a single price, which moves ("swings") with the day's net flow. On a day of net inflows the price swings up, so buyers pay the cost of investing their money; on a day of net outflows it swings down, so sellers pay the cost of raising theirs:

net inflow:  dealing price = NAV per unit x (1 + swing factor)
net outflow: dealing price = NAV per unit x (1 - swing factor)

Under full swing the price swings every day there is a net flow. Under partial swing it swings only when the net flow is larger than a threshold, such as 2% of the fund, because small flows can be met from cash and cost little. A partial swing leaves small days diluted and protects against the large ones, which do the damage.

The swing factor is meant to be the cost of trading the day's net flow. It can be set as a number of basis points, or estimated from the run's own cost model: the fixed cost plus market impact on the trades the flow actually needs. An estimated factor grows on days the flow is large or the holdings are hard to trade.

Swing pricing is common for Luxembourg SICAVs and Irish ICAVs and allowed for UK OEICs. US rules have permitted it since 2018, though US funds have not adopted it.

Dilution levy

The price stays at NAV, and a deal larger than a threshold pays a separate charge into the fund instead:

levy = levy rate x deal amount        (deals above the threshold)
units bought    = (amount paid - levy) / NAV per unit
amount received = units sold x NAV per unit - levy

Only the investors whose deals cause meaningful trading pay, and the price everyone else sees does not move. UK OEICs and Irish funds use levies, mostly for large deals. A US mutual fund's redemption fee, charged to investors who sell soon after buying, works the same way; Beacon does not track how long each investor held, so it applies the fee to every redemption.

Measuring the protection

The run's flow record shows, for every flow, the dealing price, the units created or cancelled and the amount the dealing investor paid into the fund through the price or levy. Comparing a fund's NAV per unit under each method with the same flows shows how much dilution each one prevented.

Exchange-traded funds

An ETF has two markets.

  • In the primary market, authorised participants (APs) create and redeem shares with the fund, in blocks called creation units (50,000 shares, say), at NAV.
  • In the secondary market, everyone else buys and sells existing shares on an exchange, from each other and from market makers, at a market price.

An investor who buys on the exchange pays the market price, which can sit above NAV (a premium) or below it (a discount), plus half the bid-ask spread. Beacon simulates both.

Creations and redemptions

The run's flows are the APs' net demand for creations (positive) or redemptions (negative). Each day they are rounded down to whole creation units, and what is left over carries to the next day, as an AP waits until a full unit is worth creating.

  • A cash creation delivers money; the fund buys the holdings and pays the trading costs. A variable creation fee, in basis points, charges those costs back to the AP, so the fund is not diluted.
  • An in-kind creation delivers the holdings themselves; the fund does not trade, and the AP bears the cost of assembling the basket.

Redemptions work the same way in reverse. UCITS ETFs create in cash by default and US ETFs in kind, as is usual for each.

The arbitrage band

APs keep the market price close to NAV. When the ETF trades at a premium larger than the cost of creating, an AP can buy the holdings, create shares at NAV and sell them at the market price for a profit; the new shares push the price down. At a discount larger than the cost of redeeming, an AP buys shares cheaply, redeems them for the holdings and sells those. So the premium is bounded by what creating and redeeming cost:

create cost = AP fee per unit + the cost of buying one creation unit's basket
redeem cost = AP fee per unit + the cost of selling one creation unit's basket

- redeem cost / unit value  <=  premium  <=  create cost / unit value

The basket's cost comes from the run's cost model: the fixed cost plus the market impact of trading one creation unit's worth of every holding. The band is narrow for an ETF of large, liquid shares and wide for one holding small or illiquid ones, and it widens on days the basket becomes expensive to trade.

The premium or discount

Inside the band the premium is not zero. It persists from day to day, it is pushed by buying and selling pressure, and in a sharp fall the market price tends to fall faster than the holdings' closing prices reflect, opening a discount (as bond ETFs showed in March 2020). Beacon models the premium, as a fraction of NAV, as:

premium(t) = persistence x premium(t-1)
           + flow sensitivity x net flow(t) / AUM
           + return sensitivity x NAV return(t)
           + noise x a standard normal draw
             then held inside the arbitrage band
  • Persistence (between 0 and 1) sets how long a premium lasts: 0.5 halves it each day.
  • Flow sensitivity turns buying pressure into a premium and selling pressure into a discount, before APs close it.
  • Return sensitivity lets the price lead NAV on large moves, so a sharp fall opens a discount and a sharp rise a premium.
  • Noise is the day-to-day wander, seeded so a run repeats exactly.

The bid-ask spread

Market makers quote a spread that pays them for the cost of hedging their inventory in the holdings and for the risk of holding it. ETF spreads are known to be wider when the holdings are less liquid, wider when markets are volatile, much wider in stress, and never narrower than one price tick. Beacon models the full spread, as a fraction of the market price, as:

spread(t) = max(tick / price,
                floor
                + basket sensitivity x the cost of trading one creation unit's basket(t)
                + volatility sensitivity x the NAV's recent daily volatility(t))

The basket term makes the spread follow the holdings' liquidity, and the volatility term widens it in turbulent markets. Both rise together in a crisis, which is what produces the wide spreads seen then.

What the run reports

market price = NAV per unit x (1 + premium)
ask          = market price x (1 + spread / 2)
bid          = market price x (1 - spread / 2)

An ETF run reports, for each day, the market price, the premium or discount, the spread, the bid and the ask, and the creation units issued or redeemed. Beside the NAV return it reports the return an exchange investor made: buying at the ask on the first day and selling at the bid on the last, with the market price in between. The difference between the two is what the ETF's trading cost its secondary-market investors.

In code

ucits_etf() and us_etf() are the two ETF presets, and EtfVehicle builds any other. EtfMarket holds the quote model's settings, all of which have defaults:

from beacon.backtest import EtfMarket, MarketImpact, Implementation, ucits_etf

etf = ucits_etf(creation_unit=50_000, ap_fee=500.0,
                market=EtfMarket(spread_floor_bps=2.0, persistence=0.5,
                                 flow_sensitivity=0.1, return_sensitivity=0.05,
                                 noise_bps=2.0, seed=7))

listed = Backtest(initial_capital=50_000_000.0, transaction_cost_bps=5.0,
                  implementation=Implementation(impact=MarketImpact()),
                  flows=[PeriodicFlows(fraction=0.02)], vehicle=etf,
                  data_provider=fetcher,
                  ).run(definition, start="2023-01-03", end="2024-12-31")

print(listed.market[["premium", "spread", "bid", "ask"]].tail(3))
print({name: round(value, 6) for name, value in listed.market_summary().items()})

result.market holds each day's quote, and market_summary() (also part of summary()) gives the exchange investor's return and the average premium and spread. Each flow's creation_units says how many units it was.

What the model leaves out

  • Stale holdings prices. When an ETF's holdings trade in a market that is closed while the ETF trades (a US-listed ETF of Japanese shares, say), the market price moves on news the NAV has not seen yet, so part of its apparent premium is the NAV being out of date. The model works on one daily close and does not separate this out.
  • Intraday trading. Prices are daily closes; the spread is the typical closing spread, not the range seen through a day.