Lesson 7.4.2 · 60 min

ESMA and CFDs: leverage, close-out and negative balance

Since 2018 an EU retail client cannot open a CFD with more leverage than ESMA sets, and in Spain a CNMV resolution with the same rules has applied since 2019. This lesson turns those rules into a margin calculator and an account simulator, and tests them on the series from levels 2 and 3.

Fig. — 20-session windows in which the 50 % rule closes a CFD at the maximum retail leverage, by underlying (level 2 and 3 data)
Level 7 lessons
  1. 7.1 · Software engineering for trading

    1. 7.1.1 Project structure, configuration and quality: from notebook to repository
    2. 7.1.2 Performance: measure, profile and compile only what is needed
    3. 7.1.3 Patterns: events, message queues and hexagonal architecture
  2. 7.2 · Infrastructure and deployment

    1. 7.2.1 Docker and docker-compose: reproducible images with uv
    2. 7.2.2 VPS, cloud and latency; CI/CD with GitHub Actions
    3. 7.2.3 Orchestration and monitoring: task graphs, structured logs, metrics, alerts and heartbeats
    4. 7.2.4 Security and continuity: 2FA, IP allowlists, backups and failure procedures
  3. 7.3 · MLOps for trading models

    1. 7.3.1 Tracking experiments, models and data: MLflow, DVC and a run reproduced end to end
    2. 7.3.2 Scheduled retraining, automatic validation and champion/challenger
    3. 7.3.3 Live model monitoring and automatic shutdown
    4. 7.3.4 Model serving: ONNX, CPU or GPU inference and the cost of LLMs in production
  4. 7.4 · Regulation, ethics and taxation

    1. 7.4.1 MiFID II: clients, suitability and algorithmic trading
    2. 7.4.2 ESMA and CFDs: leverage, close-out and negative balance
    3. 7.4.3 PRIIPs, the KID and US ETFs
    4. 7.4.4 MiCA after the transitional period: checking a provider in the registers
    5. 7.4.5 Market abuse: spoofing, layering and the bot that imitates them by accident
    6. 7.4.6 Trading taxes in Spain
    7. 7.4.7 Ethics: personal data, artificial intelligence and selling signals
  5. 7.5 · Quant career

    1. 7.5.1 Quant roles: six jobs around the same system
    2. 7.5.2 Where to work: what each firm does and what the registers say
    3. 7.5.3 Public portfolio and interviews: show judgment, not returns
    4. 7.5.4 Certifications, training and prop firms: what each one certifies and what a challenge measures
  6. 7.6 · Final capstone

    1. 7.6.1 Capstone: common requirements, the panel's rubric and preparing the defense
    2. 7.6.2 Capstone C1: a multi-asset futures system, from forecast to paper account
    3. 7.6.3 Capstone C2: systematic options book, volatility surface and real-time Greeks
    4. 7.6.4 Capstone C3: stock and ETF portfolio with ML, HRP and Spanish taxation
    5. 7.6.5 Capstone C4: a research platform with LLMs and alternative data
    6. 7.6.6 Capstone C5: crypto market making or arbitrage with counterparty control

From the ESMA decision to the CNMV resolution

Lesson 1.2.4 presented the limits on CFDs for retail clients and computed how much movement closes a position in EURUSD. This lesson goes to the sources and to the detail that decides the outcome of an account: which underlying falls in which band, how the 50% close-out is measured, what happens with a price gap and where the figure in the warning comes from. The framework is the EU one as applied in Spain.

The product intervention power came with Regulation (EU) 600/2014 (MiFIR), applicable since January 3, 2018: its Article 40 allows ESMA to temporarily restrict a product throughout the EU and Article 42 allows each national authority to do so in its own country. On May 22, 2018 ESMA adopted two decisions: banning binary options for retail clients from July 2 and restricting CFDs from August 1 (Decision (EU) 2018/796), for three renewable months. According to the CNMV (Comisión Nacional del Mercado de Valores, Spain's securities market regulator), ESMA renewed them three times. To give them stability, each authority adopted equivalent national measures; in Spain, the CNMV Resolution of June 27, 2019, whose CFD chapter came into force on August 1, 2019 and still applies “as long as the CNMV does not consider that there have been changes in market conditions that allow its repeal” (unofficial translation).

MiFIR art. 40 and 42ESMA 2018/796CNMV June 2019CNMV July 2023
Mermaid source
flowchart TB
  A[MiFIR art. 40 and 42] --> B[ESMA 2018/796]
  B --> C[CNMV June 2019]
  C --> D[CNMV July 2023]

The 2019 resolution applies to all marketing, distribution and sale of CFDs to retail clients resident in Spain, whatever the country of the firm and whether or not it has a branch there; if another authority imposes something different, the stricter rule applies. It treats as CFDs, among others, rolling spot forex and spread bets, and it leaves out warrants and turbos. It does not apply to professional clients: that is why lesson 7.4.1 insists on what is lost by asking for that treatment.

The five conditions of Article 4

Article 4 of the resolution allows a CFD to be sold to a retail client only if all of these conditions are met:

ConditionWhat it requiresDefinition
a) Initial marginCharge the client at least the margin in Annex IArt. 3.c and Annex I
b) Margin close-outLiquidate CFDs when funds plus unrealized net profits fall below half of the total initial marginArt. 3.e
c) Negative balanceThe client's liability is limited to the funds in the CFD accountArt. 3.f
d) No incentivesNo payments or monetary or non-monetary benefits for trading, except information and research toolsArt. 3.b
e) WarningEvery communication carries the Annex II warning with the percentage of losing accountsAnnex II

Annex I sets the margin by type of underlying. The detail is in the lists: there are six “major currencies” (US dollar, euro, yen, pound, Canadian dollar and Swiss franc), and the 5% indices are ten named ones, with their 2019 names (“DAX30”). The IBEX 35 is not among them. A calculator that applies it as written:

IN [01]python
import numpy as np
import pandas as pd

PRINCIPALES = {"USD", "EUR", "JPY", "GBP", "CAD", "CHF"}
INDICES_5 = {"FTSE 100", "CAC 40", "DAX30", "DJIA", "S&P 500",
             "NASDAQ", "NASDAQ 100", "Nikkei 225", "ASX 200",
             "EURO STOXX 50"}

def garantia_inicial(tipo, nombre=""):
    """Fraction of the notional (Annex I of the 2019 Resolution)."""
    if tipo == "divisa":
        a, b = nombre[:3], nombre[3:]
        return 0.0333 if {a, b} <= PRINCIPALES else 0.05
    if tipo == "indice":
        return 0.05 if nombre in INDICES_5 else 0.10
    if tipo == "materia_prima":
        return 0.05 if nombre == "oro" else 0.10
    if tipo == "cripto":
        return 0.50
    return 0.20                 # stocks and any other underlying

cartera = pd.DataFrame([
    ("divisa", "EURUSD", 100_000), ("divisa", "EURTRY", 20_000),
    ("indice", "DAX30", 40_000), ("indice", "IBEX 35", 40_000),
    ("materia_prima", "oro", 20_000),
    ("materia_prima", "brent", 20_000),
    ("accion", "una acción", 10_000), ("cripto", "BTC", 4_000),
], columns=["tipo", "subyacente", "nominal_eur"])
pares = zip(cartera["tipo"], cartera["subyacente"])
cartera["garantia_pct"] = [100 * garantia_inicial(t, s)
                           for t, s in pares]
cartera["garantia_eur"] = (cartera["nominal_eur"]
                           * cartera["garantia_pct"] / 100)
print(cartera.to_string(index=False))
total = cartera["garantia_eur"].sum()
nominal = cartera["nominal_eur"].sum()
print(f"total margin {total:,.0f} EUR for {nominal:,.0f} EUR of "
      f"notional (average leverage {nominal / total:.1f}:1)")
OUT [01]Minimum initial margin under Annex I
         tipo subyacente  nominal_eur  garantia_pct  garantia_eur
       divisa     EURUSD       100000          3.33        3330.0
       divisa     EURTRY        20000          5.00        1000.0
       indice      DAX30        40000          5.00        2000.0
       indice    IBEX 35        40000         10.00        4000.0
materia_prima        oro        20000          5.00        1000.0
materia_prima      brent        20000         10.00        2000.0
       accion una acción        10000         20.00        2000.0
       cripto        BTC         4000         50.00        2000.0
total margin 17,330 EUR for 254,000 EUR of notional (average leverage 14.7:1)

The data values of the table stay in Spanish, as in the rest of the course: divisa (currency), indice (index), materia_prima (commodity), accion (stock), oro (gold). The same notional in the DAX30 and in the IBEX 35 costs twice the margin in the second, and the EURTRY pair, with a currency that is not on the list, goes to 5%. The average leverage of the portfolio, which mixes bands, is none of those in the annex: that is what happens in a real account.

What maximum leverage means with data

ESMA did not pick the figures by eye. In its decision it explains that it calibrated the margin of each underlying according to its volatility, simulating with about ten years of daily prices the probability that a client loses 50% of their initial investment over an appropriate holding period, with the aim of making that probability consistent across underlyings. The same exercise can be done with the course data. The series imitate real markets; the band assigned to each is that of the market it imitates, and this is an assumption of the lesson:

Series (data)ImitatesAnnex I bandMargin
QEVUSD (fx3_diario)EURUSDTwo major currencies3.33%
USDQMP (fx3_diario)USDMXNOther currency5%
RVE (futuros3_indices_contado)EURO STOXX 50Major index5%
MEA (futuros2_continuos)GoldGold5%
RVH (futuros3_indices_contado)IBEX 35Other index10%
ENA (futuros2_continuos)Light crudeOther commodity10%
120 stocks (universo2_ajustados)StocksStocks20%
XBQ on boreal (cripto3_diario)BitcoinCryptocurrency50%

The experiment: the whole account balance as margin (the maximum leverage allowed), one position opened every session, long or short, held for 20 sessions. It is closed out if at any daily close the loss reaches half of the margin, that is, an adverse move of half of the annex percentage:

IN [02]python
from numpy.lib.stride_tricks import sliding_window_view

fx = pd.read_parquet("fx3_diario.parquet")
ind = pd.read_csv("futuros3_indices_contado.csv", parse_dates=["fecha"])
fut = pd.read_parquet("futuros2_continuos.parquet")
acc = pd.read_parquet("universo2_ajustados.parquet")
cri = pd.read_parquet("cripto3_diario.parquet")

def serie(df, col_id, valor, col_precio):
    s = df[df[col_id] == valor].set_index("fecha")[col_precio]
    return s.sort_index().dropna()

series = {
    "QEVUSD": (serie(fx, "par", "QEVUSD", "spot"), 0.0333),
    "USDQMP": (serie(fx, "par", "USDQMP", "spot"), 0.05),
    "RVE": (serie(ind, "indice", "RVE", "contado"), 0.05),
    "MEA": (serie(fut, "mercado", "MEA", "cierre_ajustado"), 0.05),
    "RVH": (serie(ind, "indice", "RVH", "contado"), 0.10),
    "ENA": (serie(fut, "mercado", "ENA", "cierre_ajustado"), 0.10),
    "XBQ": (serie(cri[cri["exchange"] == "boreal"], "activo", "XBQ",
                  "cierre_spot"), 0.50),
}

def prob_cierre(precios, garantia, h=20):
    """Fraction of h-session windows with a close-out (long/short mean)."""
    v = sliding_window_view(np.asarray(precios, float), h + 1)
    r = v[:, 1:] / v[:, :1] - 1
    largo = (r <= -0.5 * garantia).any(axis=1).mean()
    corto = (r >= 0.5 * garantia).any(axis=1).mean()
    return (largo + corto) / 2

res = {k: prob_cierre(s, g) for k, (s, g) in series.items()}
por_accion = [prob_cierre(g["cierre_ajustado"], 0.20)
              for _, g in acc.sort_values("fecha").groupby("ticker")
              if len(g) > 21]
res["stocks"] = float(np.mean(por_accion))
prob = pd.Series(res).sort_values(ascending=False)
(100 * prob).round(1)
OUT [02]Eight underlyings at maximum leverage
RVE       52.7
ENA       51.6
MEA       49.8
QEVUSD    39.8
USDQMP    36.7
RVH       30.0
stocks    19.3
XBQ        7.4
dtype: float64

The figure at the top sorts the result. At maximum leverage with the whole balance committed, a CFD is closed out by the 50% rule in a large share of the four-week windows, and the share is not the same across underlyings. The single 5% band for major indices, gold and secondary currencies treats series with different volatilities alike, and the same happens between the IBEX 35 and crude oil at 10%. The cryptocurrency, with the 50% margin, is the one closed out least often: the band is conservative for its volatility. The figures depend on the synthetic data; what can be generalized is the method and the order of magnitude.

The way to reduce that probability is decided not by the rule but by the client: not committing the whole balance. The same account with less effective leverage on the major pair:

IN [03]python
qev = series["QEVUSD"][0]
for apal in (30, 20, 10, 5, 2):
    # with effective leverage L, the close-out comes with a fall
    # of 50% of the balance: a move of 0.5 / L
    p = prob_cierre(qev, 1 / apal)
    print(f"{apal:>2}:1  move that closes out {50 / apal:5.2f} %  "
          f"prob. in 20 sessions {100 * p:5.1f} %")
OUT [03]Close-out probability by effective leverage
30:1  move that closes out  1.67 %  prob. in 20 sessions  39.8 %
20:1  move that closes out  2.50 %  prob. in 20 sessions  23.0 %
10:1  move that closes out  5.00 %  prob. in 20 sessions   4.3 %
 5:1  move that closes out 10.00 %  prob. in 20 sessions   0.4 %
 2:1  move that closes out 25.00 %  prob. in 20 sessions   0.0 %
Fig. — Probability that the 50 % rule closes a CFD within 20 sessions by effective leverage; each line ends at the maximum for its class

The simplification is honest in one sense: with the minimum margin and the whole balance committed, margin and balance coincide, and the close-out comes with the loss of half of the balance. With less leverage, the provider would not close out under the 50% rule (the balance is more than enough), but losing 50% of the balance is still the threshold that matters to the client.

The 50% close-out: per account, not per position

Article 3.e defines the protection as the liquidation of one or more CFDs “when the sum of funds in the CFD trading account and the unrealized net profits of all open CFDs connected to that account falls to less than half of the total initial margin provided for all those open CFDs” (unofficial translation of the CNMV resolution). It is a rule per account. ESMA explains in its decision that the public consultation proposed a rule per position and that it changed it: a rule per account allows the gains of one position to offset the losses of another, and a provider that closes out per position also complies, because it is stricter.

The difference matters as soon as there are two positions that hedge each other. A long-short pair: a long CFD on RVE (major index, 5%) and a short one on RVH (10%), with the same notional, and the account holding exactly the margin of both. The two series have a daily correlation of 0.73:

IN [04]python
p = ind.pivot(index="fecha", columns="indice", values="contado")
par = p[["RVE", "RVH"]].to_numpy()

def ventana(i, regla, h=20):
    nominal, signo = 1.0, np.array([1, -1])     # long RVE, short RVH
    garantia = nominal * np.array([0.05, 0.10])
    fondos = garantia.sum()
    abierta = np.array([True, True])
    cerrada_pnl = np.zeros(2)
    cerro = False
    for t in range(1, h + 1):
        vivo = signo * nominal * (par[i + t] / par[i] - 1)
        pnl = np.where(abierta, vivo, cerrada_pnl)
        if regla == "position":
            corta = abierta & (pnl < -0.5 * garantia)
            cerrada_pnl[corta], abierta[corta] = pnl[corta], False
            cerro |= corta.any()
        if abierta.any() and (fondos + pnl.sum()
                              < 0.5 * garantia[abierta].sum()):
            cerrada_pnl[abierta], abierta[:] = pnl[abierta], False
            cerro = True
    return cerro, pnl.sum() / fondos

for regla in ("account", "position"):
    r = [ventana(i, regla) for i in range(len(par) - 20)]
    cerradas = np.mean([c for c, _ in r])
    final = np.array([x for _, x in r])
    print(f"{regla:9} close-outs {100 * cerradas:5.1f} %   mean "
          f"result {100 * final.mean():5.1f} %   5th percentile "
          f"{100 * np.percentile(final, 5):6.1f} %")
OUT [04]Account simulator: close-out per account or per position
account   close-outs   6.6 %   mean result   0.9 %   5th percentile  -51.2 %
position  close-outs  71.8 %   mean result   2.0 %   5th percentile  -58.7 %

With the close-out per position, one leg is liquidated in most windows: the 5% leg withstands a 2.5% adverse move before being closed, and in a hedged pair each leg moves much more than the whole. The worst part is not the frequency, it is what is left: one leg open without its hedge, an exposure the client had not chosen. That is the argument ESMA takes from the public consultation. The rule per account looks at what the client can lose, the net, and barely acts. In an account with a single position, as ESMA says, the two rules are the same.

The CNMV adds a good practice in the preamble of the 2019 resolution: warn the client before the 50%, so they can post more margin or close on their own. And the 2023 resolution specifies that the close-out “must not take place before that threshold is reached and the client has been informed” (unofficial translation).

funds below thresholdadds funds or closesbelow 50%balance leftnegative balanceNormalWarningCloseOutProtected
Mermaid source
stateDiagram-v2
  [*] --> Normal
  Normal --> Warning: funds below threshold
  Warning --> Normal: adds funds or closes
  Warning --> CloseOut: below 50%
  CloseOut --> Normal: balance left
  CloseOut --> Protected: negative balance
  Protected --> [*]

Negative balance protection

The 50% close-out leaves the other half of the margin as a cushion to execute the close-out. That cushion is useless if the price gaps between two observations: a weekend, a central bank decision, a gap at the open. The currency data contain one like that. On Friday, October 2, 2015 QEVUSD closed at 1.58226 and on Monday the 5th, at 1.45688:

IN [05]python
q = series["QEVUSD"][0]
salto = q.pct_change().abs().idxmax()
antes, despues = q.shift(1)[salto], q[salto]
mov = despues / antes - 1
fondos = 10_000                       # EUR in the CFD account
nominal = fondos / 0.0333             # the whole balance as margin
saldo = fondos + nominal * mov        # long CFD
print(f"{salto.date()}: {antes:.5f} -> {despues:.5f} "
      f"({100 * mov:.2f} %)")
print(f"notional {nominal:,.0f}; balance after the gap {saldo:,.0f} EUR")
print(f"client's loss with protection: {fondos:,.0f} EUR; "
      f"borne by the provider: {-min(saldo, 0):,.0f} EUR")
OUT [05]A gap in the major pair and the negative balance
2015-10-05: 1.58226 -> 1.45688 (-7.92 %)
notional 300,300; balance after the gap -13,796 EUR
client's loss with protection: 10,000 EUR; borne by the provider: 13,796 EUR

For simplicity the account is in euros and the notional is expressed in euros. A 7.9% fall at 30:1 leverage is a loss of 238% of the margin: by the time the provider can close out, there is no balance left. Negative balance protection (Article 3.f) limits the client's liability, for all the CFDs in that account, to the funds deposited in it. How often this happens in the experiment of the previous section can be counted: the first close-out signal arrives with the loss already above 100% of the margin.

IN [06]python
def saldo_negativo(precios, garantia, h=20):
    v = sliding_window_view(np.asarray(precios, float), h + 1)
    r = v[:, 1:] / v[:, :1] - 1
    out = 0
    for x in (r, -r):                          # long and short
        toca = x <= -0.5 * garantia
        hay = toca.any(axis=1)
        primero = toca.argmax(axis=1)
        perdida = -x[np.arange(len(x)), primero] / garantia
        out += int((hay & (perdida > 1)).sum())
    return out

pd.Series({k: saldo_negativo(s, g) for k, (s, g) in series.items()})
OUT [06]Close-outs that come too late: negative balance
QEVUSD     88
USDQMP      4
RVE       167
MEA       116
RVH        16
ENA       232
XBQ         0
dtype: int64

These are windows (long and short) whose first close-out signal arrives with the loss already above the margin: 232 in crude oil, 167 in the major index, 88 in the major pair and none in the cryptocurrency, whose 50% margin leaves a cushion of a 25% move. Gaps are concentrated on a few dates and affect every window that spans them, which is why the counts are large in some series and small in others. With intraday data and continuous monitoring there would be fewer cases, not none: the gap between two prices exists at any frequency.

The risk warning and its percentage

Every communication from a CFD provider to retail clients carries a warning in a fixed format (Annex II). On a website or a durable medium (unofficial translation of the Spanish text):

CFDs are complex instruments and come with a high risk of losing money rapidly due to leverage. [provider's percentage] % of retail investor accounts lose money when trading CFDs with this provider. You should consider whether you understand how CFDs work and whether you can afford to take the high risk of losing your money.

The percentage is not free. Section A.6 defines it: every three months, over the previous 12 months, the proportion of retail CFD accounts whose net realized and unrealized result on CFDs was negative, with all costs (charges, fees, commissions) and without the accounts that had no open CFD, without the result of other products and without deposits or withdrawals. A provider with no open CFDs in the period uses the standard warning: “Between 74% and 89% of retail investors lose money when trading CFDs” (unofficial translation), the range from the supervisors' studies that the resolution itself cites.

A validator on 5,000 synthetic accounts, compared with two tempting and wrong calculations:

IN [07]python
rng = np.random.default_rng(2019)
n = 5_000
cuentas = pd.DataFrame({
    "con_cfd_abierto": rng.random(n) < 0.85,
    "realizado": rng.normal(-150, 900, n).round(2),
    "no_realizado": rng.normal(0, 200, n).round(2),
    "costes": rng.gamma(2.0, 180, n).round(2),
    "otros_productos": rng.normal(300, 1500, n).round(2),
    "ingresos": rng.gamma(1.5, 2000, n).round(2),
    "retiradas": rng.gamma(1.2, 900, n).round(2),
})
cuentas.loc[~cuentas["con_cfd_abierto"],
            ["realizado", "no_realizado", "costes"]] = 0.0

def pct_pierden(c):
    """Annex II, section A.6 of the 2019 Resolution."""
    c = c[c["con_cfd_abierto"]]
    neto = c["realizado"] + c["no_realizado"] - c["costes"]
    return 100 * (neto < 0).mean()

c = cuentas
sin_costes = c[c["con_cfd_abierto"]]
mal_1 = 100 * ((sin_costes["realizado"] + sin_costes["no_realizado"])
               < 0).mean()
saldo = (c["realizado"] + c["no_realizado"] - c["costes"]
         + c["otros_productos"] + c["ingresos"] - c["retiradas"])
mal_2 = 100 * (saldo < 0).mean()
print(f"Annex II A.6:                       {pct_pierden(c):5.1f} %")
print(f"without costs:                      {mal_1:5.1f} %")
print(f"change in balance, all accounts:    {mal_2:5.1f} %")
OUT [07]The warning percentage, computed right and wrong
Annex II A.6:                        70.3 %
without costs:                       56.0 %
change in balance, all accounts:     29.7 %

Both errors lower the figure. Forgetting costs flips the sign of accounts that make a small profit before commissions and financing. Using the change in balance of all accounts mixes in deposits, other products and accounts without CFDs, and turns into “winners” accounts that only received a transfer. The annex's method exists so that the figure is comparable across providers.

What the CNMV added in 2023 and what none of this covers

The CNMV Resolution of July 11, 2023, applicable since August 3, 2023, does two things. For CFDs it bans marketing communications aimed at retail clients in Spain, including those that redirect to a provider's website or form and public offers of free or token-price courses, seminars or demo accounts; sponsorship and brand advertising with that effect; and practices such as paying clients for bringing in clients, rewarding the sales network for client acquisitions or client losses, call centers that promote these products and deposits by credit card. And for other leveraged instruments (those whose maximum risk is not known at subscription or can exceed the initial outlay, such as futures or written options) it requires an initial margin and the 50% close-out for retail clients. The margin is the lower of two amounts: the one in the 2019 Annex I according to the underlying, or the one required by the trading venue; if the underlying is a crypto-asset that is not a MiFID financial instrument, the annex amount with no reduction.

IN [08]python
esp = pd.read_csv("futuros2_especificaciones.csv")
ultimo = fut[fut["fecha"] == fut["fecha"].max()].set_index("mercado")
esp = esp.set_index("mercado").loc[["IDA", "MEA", "ENA", "DIB", "BOC"]]
esp["nominal"] = ultimo["cierre"] * esp["multiplicador"]
esp["centro_pct"] = 100 * esp["margen_inicial"] / esp["nominal"]
# assumptions: IDA as a major index; DIB, a non-major currency;
# a bond future is an "other underlying"
esp["anexo_pct"] = [5.0, 5.0, 10.0, 5.0, 20.0]
esp["exigible_pct"] = esp[["centro_pct", "anexo_pct"]].min(axis=1)
esp[["nombre", "nominal", "centro_pct", "anexo_pct",
     "exigible_pct"]].round(2)
OUT [08]Margin on a future for a retail client in Spain (2023)
                       nombre   nominal  centro_pct  anexo_pct  exigible_pct
mercado
IDA      Índice Aldebarán 500  389500.0        7.01        5.0          5.00
MEA                       Oro   41300.0        5.81        5.0          5.00
ENA              Crudo ligero   42670.0       12.89       10.0         10.00
DIB         Divisa Kestra/USD   60390.0        3.31        5.0          3.31
BOC            Bono a 10 años   95937.5        3.86       20.0          3.86

In three of the five level 2 futures (index, gold and crude oil) the annex figure is lower than the market margin and is the one required; in the currency and the bond the market margin applies, lower than the annex's 5% and 20%. The columns are centro_pct (trading venue margin), anexo_pct (Annex I) and exigible_pct (the one required). The resolution sets the minimum the provider must ask of the retail client. Its clearest practical effect is the 50% close-out, which a market's margin does not impose on the end client.

What none of these measures covers: professional clients; retail clients who trade with unauthorized firms (lesson 4.4.1); and market risk itself. They limit how much can be lost through leverage, not whether the strategy makes money.

Exercises

  1. Repeat the experiment at the top with windows of 5 and 60 sessions. How does the ranking of the underlyings change? What does it tell you about the “appropriate holding period” ESMA used?
Solution
IN [09]python
tabla = pd.DataFrame({
    h: {k: 100 * prob_cierre(s, g, h=h)
        for k, (s, g) in series.items()}
    for h in (5, 20, 60)
}).round(1)
tabla.columns = [f"{h} sessions" for h in tabla.columns]
tabla.sort_values("20 sessions", ascending=False)
OUT [09]Close-out probability by horizon
        5 sessions  20 sessions  60 sessions
RVE           22.3         52.7         70.0
ENA           21.0         51.6         70.7
MEA           18.7         49.8         69.3
QEVUSD        10.8         39.8         62.5
USDQMP         8.1         36.7         59.8
RVH            6.1         30.0         54.9
XBQ            0.3          7.4         36.2

All probabilities rise with the horizon, because a longer path has more chances to touch the threshold, but not in the same proportion: those already closed out often in 5 sessions (RVE, ENA, MEA, around 20%) approach a ceiling of 70% at 60 sessions, and the cryptocurrency goes from almost never (0.3%) to just over a third (36.2%). The ranking barely changes, but the distances do. That is why a calibration “consistent across underlyings” depends on the chosen horizon, and a margin that looks ample for a week is not for a quarter.

  1. Modify ventana so that the account holds twice the funds of the required margin. How many close-outs are there now under each rule?
Solution
IN [10]python
def ventana_holgada(i, regla, h=20, colchon=2.0):
    nominal, signo = 1.0, np.array([1, -1])
    garantia = nominal * np.array([0.05, 0.10])
    fondos = colchon * garantia.sum()
    abierta = np.array([True, True])
    cerrada_pnl, cerro = np.zeros(2), False
    for t in range(1, h + 1):
        vivo = signo * nominal * (par[i + t] / par[i] - 1)
        pnl = np.where(abierta, vivo, cerrada_pnl)
        if regla == "position":
            corta = abierta & (pnl < -0.5 * garantia)
            cerrada_pnl[corta], abierta[corta] = pnl[corta], False
            cerro |= corta.any()
        if abierta.any() and (fondos + pnl.sum()
                              < 0.5 * garantia[abierta].sum()):
            cerrada_pnl[abierta], abierta[:] = pnl[abierta], False
            cerro = True
    return cerro

for regla in ("account", "position"):
    c = np.mean([ventana_holgada(i, regla)
                 for i in range(len(par) - 20)])
    print(f"{regla:9} close-outs {100 * c:5.1f} %")
OUT [10]More funds than the margin
account   close-outs   0.0 %
position  close-outs  71.8 %

With twice the funds, the close-out per account disappears (0.0%): the account has to lose one and a half times the margin before reaching the threshold. The close-out per position does not change at all, because it looks at each position against its own margin and does not see the account's funds. Adding more balance protects against the close-out only if the rule is per account, which is the minimum the rule requires; it is worth knowing which one your provider applies.

  1. A provider advertises on a social network: “Trade the IBEX with 20:1 leverage and get a 100 EUR bonus”. Point out which parts of the 2019 and 2023 resolutions that ad breaches if it is aimed at retail clients in Spain.
Solution

Three. The IBEX 35 is not on the list of 5% indices: its minimum margin is 10%, 10:1 leverage (Annex I), so 20:1 breaches Article 4.a. The bonus is a monetary benefit tied to trading CFDs, banned by Article 4.d. And since the 2023 resolution any CFD marketing communication aimed at retail clients in Spain is banned, including those on social networks; even if it were not, the ad would need the Annex II risk warning with the provider's percentage.

Each answer is folded under its question

Self-assessment

  1. A retail client resident in Spain has 10,000 EUR in their CFD account and opens a CFD on the IBEX 35. What is the maximum notional?

    • A200,000 EUR: indices have 20:1 leverage
    • B100,000 EUR: the IBEX 35 is not on the list of major indices, so the margin is 10% (10:1)
    • C300,000 EUR: 30:1
    • D50,000 EUR: 5:1, like stocks
    Show answer

    Answer B. 100,000 EUR: the IBEX 35 is not on the list of major indices, so the margin is 10% (10:1)

    Annex I gives 5% only to ten named indices (FTSE 100, CAC 40, DAX30, DJIA, S&P 500, NASDAQ, NASDAQ 100, Nikkei 225, ASX 200 and EURO STOXX 50). All other equity indices go to 10%.

  2. An account holds a long CFD on one index and a short one on another, correlated index. When must the provider close positions under the CNMV resolution?

    • AWhen the sum of the account's funds and the unrealized net profits of all its CFDs falls below half of the total initial margin
    • BWhen either of the two loses half of its margin
    • CAt the end of every session, if there are losses
    • DNever: the client decides
    Show answer

    Answer A. When the sum of the account's funds and the unrealized net profits of all its CFDs falls below half of the total initial margin

    The close-out protection is measured per account. ESMA explains in its decision that a close-out per position also complies (it is stricter), but the required minimum is per account. In the lesson, with the long-short pair, the per-position close-out triggers in 72% of the 20-session windows and the per-account one in 7%.

  3. The example account has 10,000 EUR and a long CFD of about 300,000 EUR notional on the major pair (the whole balance as margin) when the price falls 7.9% between two closes. What does negative balance protection cover?

    • ANothing: the client owes the provider the difference
    • BIt returns half of the loss to the client
    • CIt only applies if the client requests it in writing
    • DIt limits the client's loss to the funds in their CFD account: the balance is left at zero and the provider bears the rest
    Show answer

    Answer D. It limits the client's loss to the funds in their CFD account: the balance is left at zero and the provider bears the rest

    Negative balance protection limits the retail client's aggregate liability for all CFDs in the account to the funds in that account (Article 3.f). In the lesson, the gap leaves the account at −13,796 EUR; with the protection, the client loses 10,000 and the provider bears the rest.

  4. To compute the risk warning's percentage of losing accounts, which accounts and which amounts are used?

    • AAll open accounts, with the change in balance, including deposits and withdrawals
    • BOnly the accounts closed during the year
    • CAccounts with at least one CFD open in the 12 months, with the net realized and unrealized result of their CFDs, costs included and without deposits, withdrawals or other products; it is recalculated every three months
    • DA fixed figure of 74% to 89%
    Show answer

    Answer C. Accounts with at least one CFD open in the 12 months, with the net realized and unrealized result of their CFDs, costs included and without deposits, withdrawals or other products; it is recalculated every three months

    It is the method of section A.6 of Annex II. The 74% to 89% range is the standard warning, for a provider that has had no open CFDs in the last calculation period.

  5. What did the CNMV resolution of July 11, 2023 add?

    • AIt banned CFD advertising aimed at retail clients in Spain and extended the initial margin and the 50% close-out to other leveraged instruments, such as futures and options
    • BIt raised the maximum leverage of currency CFDs
    • CIt removed negative balance protection
    • DIt repealed the 2019 resolution
    Show answer

    Answer A. It banned CFD advertising aimed at retail clients in Spain and extended the initial margin and the 50% close-out to other leveraged instruments, such as futures and options

    The first part bans marketing communications, sponsorship and practices such as paying to acquire clients or accepting deposits by credit card; the second applies the minimum margin and the 50% close-out to other instruments whose maximum risk is not known or can exceed the initial outlay.

Key points

  • ESMA restricted CFDs for retail clients with Decision (EU) 2018/796 (from August 1, 2018, renewed three times); in Spain the CNMV Resolution of June 27, 2019 has applied the same rules since August 1, 2019, with no end date.
  • Minimum initial margin: 3.33% on pairs of two major currencies; 5% on ten major indices, gold and other pairs; 10% on other commodities and indices; 20% on stocks and everything else; 50% on cryptocurrencies.
  • The 50% close-out is measured per account: funds plus unrealized net profits against half of the total initial margin. At maximum leverage with the whole balance committed, in the course data it triggers in 7% (cryptocurrency) to 53% (major index) of the 20-session windows.
  • Negative balance protection limits the loss to the funds in the CFD account; a gap between two closes can take the balance below zero before the close-out acts.
  • The warning carries the percentage of the provider's retail accounts that lose money, calculated every three months over 12 months with costs and without deposits or withdrawals; since 2023 the CNMV also bans CFD advertising to retail clients.

Resources

Data