Lesson 7.4.1 · 55 min
MiFID II: clients, suitability and algorithmic trading
MiFID II does not regulate the individual who trades their own money, but it decides what their broker can sell them and how. This lesson turns into code the rules that categorize and assess that client under the EU and Spanish framework, and reviews, as background culture, what RTS 6 requires of a firm that trades with algorithms.
- Module
- 7.4 · Regulation, ethics and taxation
- Duration
- 55 min
- Prerequisites
- 4.2.5 · Drawdown control and kill switch, 4.4.1 · Choosing a broker from Spain or the EU: regulation, assets, costs and API, 6.4.5 · Guardrails for agents: the agent proposes, the engine decides
- Author
- Jesús Cuesta
- Updated
Level 7 lessons
7.1 · Software engineering for trading
7.2 · Infrastructure and deployment
7.3 · MLOps for trading models
7.4 · Regulation, ethics and taxation
- 7.4.1 MiFID II: clients, suitability and algorithmic trading
- 7.4.2 ESMA and CFDs: leverage, close-out and negative balance
- 7.4.3 PRIIPs, the KID and US ETFs
- 7.4.4 MiCA after the transitional period: checking a provider in the registers
- 7.4.5 Market abuse: spoofing, layering and the bot that imitates them by accident
- 7.4.6 Trading taxes in Spain
- 7.4.7 Ethics: personal data, artificial intelligence and selling signals
7.5 · Quant career
7.6 · Final capstone
- 7.6.1 Capstone: common requirements, the panel's rubric and preparing the defense
- 7.6.2 Capstone C1: a multi-asset futures system, from forecast to paper account
- 7.6.3 Capstone C2: systematic options book, volatility surface and real-time Greeks
- 7.6.4 Capstone C3: stock and ETF portfolio with ML, HRP and Spanish taxation
- 7.6.5 Capstone C4: a research platform with LLMs and alternative data
- 7.6.6 Capstone C5: crypto market making or arbitrage with counterparty control
A rule for firms that affects you as a client
Directive 2014/65/EU (MiFID II) regulates investment firms and markets. Someone who only trades their own money is outside it: Article 2(1)(d) excludes those who deal on own account without providing investment services, unless they are a market maker, a member of a regulated market or of an MTF, have direct electronic access, apply a high-frequency algorithmic trading technique or execute client orders by dealing on own account. An individual with a bot that sends orders through their broker's API is not in any of those cases.
But that individual is a client of a firm that is subject to it, and what the rules require of the firm decides what the client can buy: whether it asks for a test before letting them trade derivatives, whether it applies the CFD leverage limits (lesson 7.4.2), whether it can sell them an ETF without a key information document (lesson 7.4.3). Everything depends on two decisions the firm makes with written rules: which category the client is in and which assessment applies.
The second half of the lesson looks at the other side: what Delegated Regulation (EU) 2017/589, RTS 6, requires of a firm engaged in algorithmic trading. It does not bind an individual, but it is the best list of controls there is for a system that sends orders on its own, and it is what anyone who works on a trading desk will find (lesson 7.5.1).
Three client categories
MiFID II defines a professional client as one who meets the criteria of Annex II, and a retail client as “any client who is not a professional client” (Article 4(1), points 10 and 11; unofficial translation of the Spanish text). The eligible counterparty is in Article 30. In Spain, Law 6/2023 on Securities Markets and Investment Services (Ley 6/2023 de los Mercados de Valores y de los Servicios de Inversión), the Spanish act that transposes MiFID II, requires firms to categorize all their clients into those three classes (Article 192), and Royal Decree 813/2023 (Real Decreto 813/2023) specifies who is a professional (Articles 112 to 114).
| Category | Who | Protection |
|---|---|---|
| Retail | Everyone who is not a professional: almost all individuals | Maximum: appropriateness, information, KID, product limits |
| Per se professional | Regulated financial institutions; large undertakings meeting two of: balance sheet total of 20 M EUR, net turnover of 40 M EUR, own funds of 2 M EUR; governments, central banks, supranational bodies; institutional investors | Lower; can request retail treatment |
| Professional on request | A retail client who requests it, meets two of three criteria and follows the procedure | The protection lost is notified in writing |
| Eligible counterparty | Investment firms, credit institutions, insurance companies, UCITS, pension funds, other regulated financial institutions, governments, central banks | Minimum; only in reception and transmission of orders, execution and dealing on own account |
Two details that are often confused. The eligible counterparty is not a fourth client class for everything: Article 192 of Law 6/2023 limits it to reception and transmission of orders, execution and dealing on own account, and makes clear that it does not apply to portfolio management or advice. And the categorization can be changed in both directions: a professional can request more protection (Annex II, Section I) and the firm can offer it on its own initiative (Article 45(3) of Delegated Regulation (EU) 2017/565).
Mermaid source
flowchart TB
C[New client] --> F{Regulated entity?}
F -- yes --> S{Service?}
S -- orders --> CE[Eligible counterp.]
S -- management or advice --> P[Professional]
F -- no --> G{2 of 3 size tests?}
G -- yes --> P
G -- no --> M[Retail]
M --> O{Requests prof. treatment?}
O -- yes, and meets criteria --> PP[Prof. on request]Opting up to professional: two of three criteria
A retail client can waive part of their protection. Section II.1 of Annex II, which Article 113 of Royal Decree 813/2023 reproduces, requires the firm to verify at least two of these criteria:
- Frequency. Transactions of significant size on the relevant market for the instrument, or for similar instruments, at an average frequency of 10 per quarter over the previous four quarters.
- Portfolio. Financial instruments and cash deposits worth more than 500,000 EUR.
- Professional experience. A position of at least one year in the financial sector that requires knowledge of the transactions or services envisaged.
And a procedure (Section II.2 of the Annex; Article 114 of the Royal Decree): the client requests the change in writing, in general or for a particular service or type of product; the firm warns them in writing of the protections and investor compensation rights they may lose; and the client states in writing, in a document separate from the contract, that they are aware of the consequences. If they later stop meeting the requirements, Article 114.3 of the Royal Decree requires the firm to treat them as retail immediately.
The rules do not define “significant size”. Each firm's policy sets it, and that is why it is a parameter in the code. Six synthetic profiles with their transactions over the last four quarters:
import numpy as np
import pandas as pd
rng = np.random.default_rng(741)
perfiles = pd.DataFrame([
# trades/quarter, median amount (EUR), portfolio, years in the sector
("A", 4, 3_000, 120_000, 0),
("B", 30, 1_500, 650_000, 0),
("C", 14, 9_000, 300_000, 2),
("D", 45, 12_000, 2_400_000, 0),
("E", 12, 6_000, 480_000, 0),
("F", 2, 50_000, 900_000, 6),
], columns=["cliente", "ops_trim", "importe_med", "cartera_eur",
"anios_sector"]).set_index("cliente")
filas = []
for c in perfiles.itertuples():
for trim in range(1, 5):
n = rng.poisson(c.ops_trim)
importes = rng.lognormal(np.log(c.importe_med), 0.6, n)
filas += [(c.Index, trim, round(x, 2)) for x in importes]
ops = pd.DataFrame(filas, columns=["cliente", "trimestre", "importe"])
print(len(ops), "transactions")
ops.groupby("cliente").size().to_dict()413 transactions
{'A': 12, 'B': 113, 'C': 67, 'D': 162, 'E': 54, 'F': 5}The assessor separates the criteria, which are measurable facts, from the procedure, which is documents. A request that meets the criteria but lacks the separate declaration is not accepted:
UMBRAL_SIGNIFICATIVO = 5_000 # EUR per transaction: policy (assumption)
def criterios(cliente, ops, perfiles, umbral=UMBRAL_SIGNIFICATIVO):
o = ops[(ops["cliente"] == cliente) & (ops["importe"] >= umbral)]
por_trim = o.groupby("trimestre").size().reindex(
range(1, 5), fill_value=0)
p = perfiles.loc[cliente]
return {
"media_trim": por_trim.mean(),
"frecuencia": bool(por_trim.mean() >= 10),
"cartera": bool(p["cartera_eur"] > 500_000),
"sector": bool(p["anios_sector"] >= 1),
}
def evaluar(cliente, documentos):
c = criterios(cliente, ops, perfiles)
n = c["frecuencia"] + c["cartera"] + c["sector"]
falta = [d for d in ("solicitud_escrita", "advertencia_entregada",
"declaracion_separada")
if not documentos.get(d)]
if n < 2:
decision = "rejected: criteria"
elif falta:
decision = "pending: " + ", ".join(falta)
else:
decision = "accepted"
return {**c, "cumple": int(n), "decision": decision}
completos = dict(solicitud_escrita=True, advertencia_entregada=True,
declaracion_separada=True)
docs = {k: completos for k in perfiles.index}
docs["D"] = {**completos, "declaracion_separada": False}
tabla = pd.DataFrame({k: evaluar(k, v) for k, v in docs.items()}).T
tabla["media_trim"] = tabla["media_trim"].astype(float).round(2)
tabla media_trim frecuencia cartera sector cumple decision
A 0.25 False False False 0 rejected: criteria
B 1.50 False True False 1 rejected: criteria
C 14.25 True False True 2 accepted
D 37.50 True True False 2 pending: declaracion_separada
E 7.00 False False False 0 rejected: criteria
F 1.25 False True True 2 acceptedThe figure at the top summarizes the cumple column (criteria met). The most instructive case is B: about 30
transactions per quarter, which at first sight are well over ten, but of about 1,500 EUR each; above the firm's
threshold only 1.5 per quarter remain. The 650,000 EUR portfolio is their only criterion. E falls short on two
(seven significant transactions per quarter and a portfolio of 480,000 EUR). C, D and F meet two criteria
by different routes, and D stays pending because the separate declaration (declaracion_separada) is missing:
without it the waiver is not valid, however much wealth they have.
Suitability, appropriateness and execution-only
Once the client is categorized, Article 25 of MiFID II decides what has to be assessed, and it depends on the service, not on the product:
| Assessment | When | What is asked | Rule |
|---|---|---|---|
| Suitability | Investment advice and portfolio management | Knowledge and experience, financial situation and ability to bear losses, objectives and risk tolerance | Art. 25(2); Arts. 54-55 of Delegated Reg. 2017/565; Art. 204 of Law 6/2023 |
| Appropriateness | Other services (reception, transmission and execution of orders) | Only knowledge and experience with that type of product or service | Art. 25(3); Arts. 55-56 of Delegated Reg. 2017/565; Art. 205 of Law 6/2023 |
| Execution-only | Non-complex instruments, at the client's initiative, with a warning | Nothing | Art. 25(4); Art. 207 of Law 6/2023 |
Suitability ends in a recommendation and a written statement of why it fits the client (Article 25(6)). ESMA sets out how to carry it out in its guidelines ESMA35-43-3172 (2023), which also apply when the advice is given by an algorithm: Article 54(1) of the Delegated Regulation says that responsibility for the assessment lies with the firm and is not reduced by the use of an automated system.
For a trader who uses an execution broker, what matters is appropriateness. If the product is not appropriate, the firm must warn the client; if the client does not provide information, it must warn them that it cannot assess. In both cases the client can go ahead, and the firm has to record the result, the warnings and whether the client went ahead (Article 56(2)). The execution-only route, without an assessment, is limited to the instruments listed in Article 25(4): shares admitted to trading on a regulated market or equivalent (without embedded derivatives), bonds without derivatives or structures that are hard to understand, money market instruments, units in non-structured UCITS, simple structured deposits and other non-complex instruments.
An appropriateness assessor that keeps its record. The experience criterion (five transactions in three years or a related profession) is an example of a policy, not a figure from the rules:
NO_COMPLEJOS = {"accion_cotizada", "etf_ucits", "bono_simple"}
PRODUCTOS = NO_COMPLEJOS | {"cfd", "opcion_cotizada", "futuro"}
def conveniencia(cliente, producto, test, profesional=False,
iniciativa_cliente=True):
if producto not in PRODUCTOS:
raise ValueError(f"unknown product: {producto}")
if profesional:
return "appropriate (presumption Art. 56.1)"
if producto in NO_COMPLEJOS and iniciativa_cliente:
return "execution-only (warning Art. 25.4.c)"
if test is None:
return "no information: warn (Art. 25.3)"
conoce = producto in test["conoce"]
experiencia = (test["ops_3a"].get(producto, 0) >= 5
or test["profesion_relacionada"])
if conoce and experiencia:
return "appropriate"
return "not appropriate: warn (Art. 25.3)"
tests = {
"A": dict(conoce={"accion_cotizada"}, ops_3a={},
profesion_relacionada=False),
"C": dict(conoce={"accion_cotizada", "futuro", "cfd"},
ops_3a={"futuro": 12, "cfd": 2},
profesion_relacionada=True),
"E": dict(conoce={"cfd"}, ops_3a={"cfd": 3},
profesion_relacionada=False),
}
registro = []
for cli, prod in [("A", "etf_ucits"), ("A", "cfd"), ("C", "cfd"),
("E", "cfd"), ("E", "opcion_cotizada"),
("B", "futuro"), ("F", "futuro")]:
res = conveniencia(cli, prod, tests.get(cli),
profesional=(cli == "F"))
registro.append({"cliente": cli, "producto": prod,
"resultado": res})
pd.DataFrame(registro) cliente producto resultado
0 A etf_ucits execution-only (warning Art. 25.4.c)
1 A cfd not appropriate: warn (Art. 25.3)
2 C cfd appropriate
3 E cfd not appropriate: warn (Art. 25.3)
4 E opcion_cotizada not appropriate: warn (Art. 25.3)
5 B futuro no information: warn (Art. 25.3)
6 F futuro appropriate (presumption Art. 56.1)The record tells the story that Article 56(2) asks for. A has no experience with CFDs and receives a warning; E knows the product but has only made three transactions, fewer than the policy requires; B has not answered the test; C qualifies through experience and through profession. F appears as appropriate without a test because in the example they are professional for futures: the presumption of Article 56(1) is exactly what stops protecting the client who asked for that treatment.
RTS 6: what is required of a firm with algorithms
MiFID II defines algorithmic trading as trading in which an algorithm automatically determines the parameters of orders (whether to send them, when, at what price, in what quantity, how they are managed) with limited or no human intervention; routing orders or confirming trades is not enough (Article 4(1)(39)). A firm that engages in it must have resilient systems, with thresholds and limits, that prevent erroneous orders, full testing and continuity (Article 17(1)), and must notify its competent authority, which can ask it for a description of its strategies, limits and testing (Article 17(2)).
RTS 6 details those obligations. Grouped:
| Block | Articles | What it requires |
|---|---|---|
| Governance | 1-4 | Lines of responsibility, separation between the trading desk and control, a compliance function that understands the algorithms, qualified staff; outsourcing does not transfer responsibility |
| Testing and deployment | 5-8 | Development and testing methodologies; each deployment or substantial change authorized by a designated person; a change record (who, when, who approves, what); conformance testing with the venue; a separate testing environment; limits set before deployment |
| After deployment | 9-11 | Annual self-assessment and validation with the criteria of Annex I; stress testing; analysis of each material change |
| Resilience | 12-18 | Kill functionality; automated surveillance for market abuse; business continuity; pre-trade controls; real-time monitoring with alerts within five seconds or less; post-trade controls and reconciliation; security and annual penetration tests |
The kill functionality (Article 12) and the disabling after repeated executions (Article 15(3)) were already coded in lesson 4.2.5, and lesson 6.4.5 applied rate limits to an agent's proposals. Here the whole of Article 15 is completed, with two details that tend to be forgotten: the limits count all orders sent, including modifications and cancellations, at the moment they go out (Article 15(2)), and there must be a procedure to let a blocked order through, exceptionally and with authorization (Article 15(6)).
Mermaid source
flowchart TB
O[Message from the algo] --> D{Algorithm active?}
D -- no --> R[Reject and log]
D -- yes --> M{Messages/sec?}
M -- excess --> R
M -- ok --> B{Band and size?}
B -- outside --> R
B -- inside --> E[To the venue]
E --> X{Repeated exec.?}
X -- yes --> P[Disable]The order log is synthetic: an hour of normal messages from an algorithm alfa on two fictitious stocks, plus three
planted incidents: a price 10% away from the market, an order for 15,000 NOVA shares (797,250 EUR in value) and
another for 250,000 ORBE shares. An algorithm beta gets into a modification loop (250 messages in one second) and
another one, gamma, buys 100 shares every two seconds without stopping. In the log, lado is the side (C buy,
V sell) and tipo the message type (nueva new, modifica modify, cancela cancel):
rng = np.random.default_rng(15)
REF = {"NOVA": 53.15, "ORBE": 12.10} # reference price
n = 1200
base = pd.DataFrame({
"ts": np.sort(rng.integers(0, 3_600_000, n)), # ms within one hour
"algo": "alfa",
"instr": rng.choice(list(REF), n),
"lado": rng.choice(["C", "V"], n),
"cant": rng.integers(1, 20, n) * 100,
"tipo": rng.choice(["nueva", "modifica", "cancela"], n,
p=[0.6, 0.25, 0.15]),
})
base["precio"] = (base["instr"].map(REF)
* (1 + rng.normal(0, 0.002, n))).round(2)
def msg(ts, algo, instr, lado, cant, tipo, precio):
return dict(ts=ts, algo=algo, instr=instr, lado=lado, cant=cant,
tipo=tipo, precio=precio)
incidentes = [
msg(600_000, "alfa", "NOVA", "C", 300, "nueva", 58.47),
msg(900_000, "alfa", "NOVA", "V", 15_000, "nueva", 53.15),
msg(1_200_000, "alfa", "ORBE", "V", 250_000, "nueva", 12.08),
]
incidentes += [msg(2_000_000 + 4 * k, "beta", "NOVA", "C", 100,
"modifica", 53.10) for k in range(250)]
incidentes += [msg(3_000_000 + 2_000 * k, "gamma", "ORBE", "C", 100,
"nueva", 12.11) for k in range(40)]
ordenes = (pd.concat([base, pd.DataFrame(incidentes)])
.sort_values("ts", kind="stable").reset_index(drop=True))
ordenes.groupby("algo").size().to_dict(){'alfa': 1203, 'beta': 250, 'gamma': 40}The controls, with the limits as data and not as constants hidden in the code (Article 8 requires them to be set before the algorithm is deployed). The simplification of the simulator is that every accepted new order is executed:
from collections import Counter, deque
from dataclasses import dataclass
@dataclass(frozen=True)
class Limites:
banda: float = 0.05 # maximum deviation from the reference
valor_max: float = 250_000 # EUR per order
cant_max: int = 20_000 # shares per order
msg_seg: int = 50 # messages per second and algorithm
repeticiones: int = 20 # identical executions within 60 s
class Controles:
def __init__(self, lim, ref):
self.lim, self.ref = lim, ref
self.ventanas = {} # algo -> ts of its messages
self.ejecuciones = {} # (algo, instr, lado) -> ts
self.desactivados = set()
self.registro = []
def evaluar(self, m, excepcion=None):
w = self.ventanas.setdefault(m.algo, deque())
w.append(m.ts) # Art. 15.2: everything counts now
while w[0] <= m.ts - 1_000:
w.popleft()
motivo = self._motivo(m, len(w))
if motivo and excepcion and motivo != "disabled":
motivo = None # Art. 15.6, authorized
if motivo is None and m.tipo == "nueva":
self._ejecutada(m)
self.registro.append(dict(ts=m.ts, algo=m.algo,
resultado=motivo or "accepted",
excepcion=excepcion))
return motivo
def _motivo(self, m, en_ventana):
if m.algo in self.desactivados:
return "disabled"
if en_ventana > self.lim.msg_seg:
return "messages"
if m.tipo == "cancela":
return None # cancelling is always allowed
if abs(m.precio / self.ref[m.instr] - 1) > self.lim.banda:
return "band"
if m.cant > self.lim.cant_max:
return "volume"
if m.precio * m.cant > self.lim.valor_max:
return "value"
return None
def _ejecutada(self, m):
e = self.ejecuciones.setdefault((m.algo, m.instr, m.lado),
deque())
e.append(m.ts)
while e[0] <= m.ts - 60_000:
e.popleft()
if len(e) >= self.lim.repeticiones:
self.desactivados.add(m.algo) # Art. 15.3
ctl = Controles(Limites(), REF)
for m in ordenes.itertuples():
ctl.evaluar(m)
reg = pd.DataFrame(ctl.registro)
print(reg.groupby(["algo", "resultado"]).size().to_string())
print("disabled:", sorted(ctl.desactivados))algo resultado
alfa accepted 1200
band 1
value 1
volume 1
beta accepted 50
messages 200
gamma accepted 20
disabled 20
disabled: ['gamma']Each control triggers where it should. The band stops the price that is 10% too high; the maximum volume, the order
for 250,000 ORBE, and the maximum value, the one for 15,000 NOVA, which is not large in shares but is in euros. The
message limit cuts off beta's loop at message 51 of that second, even though they are modifications that would
never execute anything: if the limit only looked at executions, the whole loop would have reached the venue. gamma
never exceeds the message rate, one purchase every two seconds is normal traffic, but at the twentieth identical
purchase within a minute it is disabled and its next 20 orders are rejected. No normal alfa message has been
blocked: limits that reject legitimate traffic end up being switched off by the desk itself.
Disabling does not undo itself. A re-arm, like an exception, is an act of a designated person that is recorded:
bloqueada = ordenes.iloc[reg.index[reg["resultado"] == "value"][0]]
motivo = ctl.evaluar(bloqueada, excepcion="risk: R. Gil, block "
"agreed with the client")
print("retry with exception:", motivo or "accepted")
def rearmar(ctl, algo, persona, causa):
ctl.desactivados.discard(algo)
ctl.registro.append(dict(ts=None, algo=algo, resultado="re-arm",
excepcion=f"{persona}: {causa}"))
rearmar(ctl, "gamma", "L. Mar", "loop fixed (v1.4.2)")
pd.DataFrame(ctl.registro).tail(2)[["algo", "resultado", "excepcion"]]retry with exception: accepted
algo resultado excepcion
1493 alfa accepted risk: R. Gil, block agreed with the client
1494 gamma re-arm L. Mar: loop fixed (v1.4.2)Stress tests and annual self-assessment
Article 10 sets the minimum load of the stress tests: the highest number of messages sent and received over the previous six months, multiplied by two, and the highest trading volume over the same period, also times two, without touching the production environment. With the daily peaks of messages per second over six months (synthetic):
rng = np.random.default_rng(10)
sesiones = pd.bdate_range("2026-04-01", periods=126)
pico = pd.Series(rng.lognormal(np.log(22), 0.25, 126).round(),
index=sesiones, name="msg_seg")
pico.iloc[87] = 61 # earnings day and volatility
prueba = 2 * pico.max()
print(f"six-month peak: {pico.max():.0f} msg/s on "
f"{pico.idxmax().date()}; test: {prueba:.0f} msg/s")
print(f"days above the 50 msg/s limit: "
f"{(pico > Limites().msg_seg).sum()}")six-month peak: 61 msg/s on 2026-07-31; test: 122 msg/s
days above the 50 msg/s limit: 1The test teaches two things at once. The system has to withstand 122 messages per second without degrading, and the pre-trade limit of 50 per second, which seemed comfortable in the previous section, would have rejected legitimate traffic on the day of the peak. Limits are calibrated with the data of the six months, not once at deployment.
The annual self-assessment (Article 9) reviews the algorithms, governance, business continuity and compliance with Article 17, using the criteria of Annex I: nature of the business (role in the market, automation, strategies, latency sensitivity), scale (algorithms running in parallel, instruments, messages, positions) and complexity. The risk management function draws it up, internal audit audits it and senior management approves it. As an executable checklist:
REQUISITOS = {
"Art. 5.2 deployment authorized by a designated person": "ok",
"Art. 5.7 change record (who, when, approves, what)": "ok",
"Art. 7 testing environment separate from production": "ok",
"Art. 10 test at twice the six-month maximum": "fail",
"Art. 12 kill functionality tested": "ok",
"Art. 14.4 continuity reviewed and tested this year": None,
"Art. 15.1 bands, value, volume and messages": "ok",
"Art. 16.5 alerts within 5 s or less": "ok",
"Art. 18.4 annual penetration tests": None,
}
for req, estado in REQUISITOS.items():
if estado != "ok":
print(f"{'NO EVIDENCE' if estado is None else 'FAIL':13}"
f" {req}")FAIL Art. 10 test at twice the six-month maximum
NO EVIDENCE Art. 14.4 continuity reviewed and tested this year
NO EVIDENCE Art. 18.4 annual penetration testsAn individual does not have to hand this report to anyone, but the questions are the same for a system that trades only its owner's money: who approved the latest version, where the tests are, what happens if the bot gets into a loop at three in the morning and who disables it. Lessons 7.2.4 (business continuity) and 7.3.3 (automatic model shutdown) develop those answers for your own infrastructure.
What this changes in your system
- Your category decides your product. Before designing a strategy with CFDs, US ETFs or options, check what your broker will let you do as a retail client; opting up to professional has requirements and a price in protection.
- The appropriateness test is not a formality. Answering whatever “unlocks” a product makes a warning that the firm has already given you, and recorded, your own responsibility.
- Copy Article 15 even if nobody makes you. Band, value, volume, messages and repeated executions cost a few lines and stop the failures that cost the most money.
- Limits are calibrated with data. Twice the six-month maximum is a good rule for your own infrastructure.
Exercises
- Change the significant-size threshold to 1,000 EUR and assess the requests again. Which clients change decision? What does that tell you about the importance of the firm's written policy?
Solution
def evaluar_con(umbral):
out = {}
for k in perfiles.index:
c = criterios(k, ops, perfiles, umbral=umbral)
out[k] = int(c["frecuencia"] + c["cartera"] + c["sector"])
return out
print("5,000 EUR:", evaluar_con(5_000))
print("1,000 EUR:", evaluar_con(1_000))5,000 EUR: {'A': 0, 'B': 1, 'C': 2, 'D': 2, 'E': 0, 'F': 2}
1,000 EUR: {'A': 0, 'B': 2, 'C': 2, 'D': 2, 'E': 1, 'F': 2}With a threshold of 1,000 EUR, most of B's transactions count and B now meets two criteria; E gains the frequency criterion, but stays at one. With the same activity, one firm would accept B as a professional and another would not. That is why Annex II requires written categorization policies, and why it is worth reading your broker's before requesting the change.
- Add to the controls engine a position limit per algorithm and instrument (for example, 5,000 net shares) that uses the accepted new orders. Which article of RTS 6 requires it and for which instruments does it require it explicitly?
Solution
class ControlesConPosicion(Controles):
def __init__(self, lim, ref, pos_max=5_000):
super().__init__(lim, ref)
self.pos_max, self.pos = pos_max, Counter()
def _motivo(self, m, en_ventana):
motivo = super()._motivo(m, en_ventana)
if motivo or m.tipo != "nueva":
return motivo
signo = 1 if m.lado == "C" else -1
if abs(self.pos[(m.algo, m.instr)] + signo * m.cant) \
> self.pos_max:
return "position"
return None
def _ejecutada(self, m):
signo = 1 if m.lado == "C" else -1
self.pos[(m.algo, m.instr)] += signo * m.cant
super()._ejecutada(m)
ctl2 = ControlesConPosicion(Limites(), REF)
for m in ordenes.itertuples():
ctl2.evaluar(m)
r2 = pd.DataFrame(ctl2.registro)
r2.groupby(["algo", "resultado"]).size().to_dict(){('alfa', 'accepted'): 1132, ('alfa', 'band'): 1, ('alfa', 'position'): 68, ('alfa', 'value'): 1, ('alfa', 'volume'): 1, ('beta', 'accepted'): 50, ('beta', 'messages'): 200, ('gamma', 'accepted'): 20, ('gamma', 'disabled'): 20}The position limit trims the alfa orders that would accumulate more than 5,000 net shares: with an algorithm that
buys and sells at random, the position drifts and the limit ends up stopping part of the normal traffic, so in
practice it is set from the position history. Article 15(4) requires credit and market risk limits based on capital
and strategy, and Article 17(4) requires, for derivatives, controls on maximum long, short and overall positions.
- A friend shows you an ad: “Become a professional client and trade with 1:500 leverage”. Explain, with the articles in this lesson, what they would have to prove, what documents they would sign and what protections they would lose.
Solution
They would have to meet two of the three criteria of Annex II (Article 113 of Spain's Royal Decree 813/2023): ten transactions of significant size per quarter on average over the last year, a portfolio of more than 500,000 EUR, or one year in a related position in the financial sector. They would sign a written request, receive a written warning of the protections they lose and sign a declaration in a document separate from the contract (Article 114). They would lose, among other things, an effective appropriateness assessment (the firm can presume their knowledge), the leverage limits, the close-out at 50% of margin and the negative balance protection of CFDs, which only apply to retail clients (lesson 7.4.2). The firm, moreover, must take reasonable steps to check that they meet the requirements before accepting: an ad that offers it with no further ado is a red flag.
Each answer is folded under its question
Self-assessment
A client makes about 30 transactions per quarter averaging 1,500 EUR and has a portfolio of 650,000 EUR. They have never worked in the financial sector. Can their broker treat them as a professional if they ask?
- AYes: they trade more than 10 times per quarter
- BYes: the portfolio exceeds 500,000 EUR and that is enough
- CNo: they only meet one criterion (the portfolio); transactions count if they are of significant size, and theirs are not according to the firm's policy
- DNo: individuals can never be professionals
Show answer
Answer C. No: they only meet one criterion (the portfolio); transactions count if they are of significant size, and theirs are not according to the firm's policy
Annex II requires at least two of three criteria. Frequency is measured in transactions “of significant size”, not in transactions alone: in the lesson, client B only has 1.5 per quarter above the firm's threshold.
What assessment does a broker that only receives and executes orders carry out when a retail client wants to buy a CFD?
- AAppropriateness: whether the client has the knowledge and experience to understand the product (Art. 25(3))
- BSuitability: whether the product fits their objectives and their ability to bear losses
- CNone: it is an order at the client's initiative
- DIt only asks for the KID
Show answer
Answer A. Appropriateness: whether the client has the knowledge and experience to understand the product (Art. 25(3))
Suitability (Art. 25(2)) is for investment advice and portfolio management. In the other services, appropriateness is assessed (Art. 25(3)). The execution-only route (Art. 25(4)) is only available for non-complex instruments, and a CFD is not one.
Under Article 15 of RTS 6, what counts towards the pre-trade limits?
- AOnly executed orders
- BAll orders sent to the trading venue, included immediately in the calculation
- COnly new orders, not modifications
- DThe previous day's orders
Show answer
Answer B. All orders sent to the trading venue, included immediately in the calculation
Article 15(2) requires all orders sent to be included immediately. A control that only looked at executions would not stop a loop of modifications, which is exactly what the message limit cuts off in the lesson.
With what load must the stress tests of Article 10 of RTS 6 be run?
- AWith the average of the last six months
- BWith the volume of a normal day
- CWith twice the highest number of messages and twice the highest trading volume of the previous six months
- DWith whatever load each firm freely sets
Show answer
Answer C. With twice the highest number of messages and twice the highest trading volume of the previous six months
Article 10 sets the two minimum tests: messages and trading volume, each at twice the maximum of the previous six months, and without affecting the production environment.
When is an entity an “eligible counterparty” under Spain's Law 6/2023?
- AWhenever it is a financial institution, for any service
- BWhen an individual with more than 500,000 EUR requests it
- CWhen it trades with algorithms
- DOnly in reception and transmission of orders, execution and dealing on own account; not in portfolio management or advice
Show answer
Answer D. Only in reception and transmission of orders, execution and dealing on own account; not in portfolio management or advice
Article 192 of Law 6/2023 limits the category to those services and their ancillary services. For portfolio management or advice, the same entity is a professional client.
Key points
- Every investment firm categorizes its clients as retail, professional or eligible counterparties; an individual is retail unless they request and obtain professional treatment.
- Opting up to professional requires two of three criteria (an average of 10 transactions of significant size per quarter over the previous four, a portfolio of more than 500,000 EUR, one year in a position in the financial sector) and a written procedure with a separate declaration.
- Suitability for advice and portfolio management; appropriateness for the rest; execution-only only with non-complex instruments, at the client's initiative and with a warning.
- Being professional removes protections: the firm can presume knowledge and experience, and the measures designed for retail clients (CFD limits, KID) stop applying.
- RTS 6 requires firms engaged in algorithmic trading to have governance, testing, an annual self-assessment, a kill functionality, pre-trade controls on all orders and stress tests at twice their six-month maximum.
Resources
Documentation
- Directive 2014/65/EU (MiFID II), in the BOE (Spain's official gazette, in Spanish): Articles 4, 17, 25 and 30 and Annex II
- Delegated Regulation (EU) 2017/589 (RTS 6), in the BOE (in Spanish)
- Delegated Regulation (EU) 2017/565: categorization (Art. 45), suitability and appropriateness (Arts. 54-56), in the BOE (in Spanish)
- Spain's Law 6/2023 on Securities Markets and Investment Services (Arts. 192-208), consolidated text (in Spanish)
- Spain's Royal Decree 813/2023 (Arts. 112-114: professional clients), consolidated text (in Spanish)
- ESMA (2023): Guidelines on certain aspects of the MiFID II suitability requirements (ESMA35-43-3172)