Les entités minimales
Notre schéma logique distingue au moins cinq objets principaux :
Issuer
L'émetteur, avec un identifiant interne stable, ses noms connus, le ticker si disponible, la place de cotation, la juridiction, et des métadonnées de résolution d'entité.
Insider
La personne déclarante ou liée à la transaction, avec son nom normalisé, son rôle si disponible, et un identifiant interne. Il faut accepter qu'un même individu apparaisse sous plusieurs variantes orthographiques. Le système n'a pas le droit d'être surpris.
Filing
Le document source ou l'événement déclaré. Il porte un identifiant de filing, une date de dépôt, une date d'événement si distincte, la juridiction, la source, et un lien éventuel vers le document d'origine.
Transaction
La ligne économique utile, achat, vente, exercice, attribution, cession automatique, etc. C'est ici que vivent les quantités, prix, devise, nature du titre, sens de la transaction, et éventuellement la part détenue après opération.
Instrument
L'objet négocié, action ordinaire, option, RSU, dérivé, autre. Sans cette couche, les agrégations deviennent vite absurdes.
Cette séparation paraît académique jusqu'au moment où un utilisateur demande, "combien d'achats au marché par des dirigeants exécutifs, hors exercices d'options, sur les six derniers mois". Si tout est compacté dans une table unique et mal typée, la réponse devient un exercice de poésie.
Les champs qui évitent les contresens
Quelques champs sont plus importants qu'ils n'en ont l'air :
transaction_date et filed_at doivent être distincts ;
transaction_code source et transaction_type_normalized doivent coexister ;
security_type doit être normalisé ;
ownership_nature, direct ou indirect, doit être conservé si la source le permet ;
currency et price_per_unit doivent être séparés de toute valeur notionnelle calculée ;
source_url ou source_reference doit être renvoyé quand disponible.
Le point n'est pas le perfectionnisme. C'est la prévention. Un agent qui ne distingue pas date d'exécution et date de publication peut raconter une histoire de "signal récent" sur une transaction vieille de plusieurs jours. Dans le monde des filings, quelques jours suffisent à transformer une observation en anecdote.
Identifiants stables, ou la fin des joins approximatifs
Les identifiants stables sont la pièce maîtresse. Un agent doit pouvoir demander d'abord "trouve l'émetteur", puis "liste les filings", puis "donne le détail du filing X". Cela suppose :
- un
issuer_id unique ;
- un
insider_id unique ;
- un
filing_id unique ;
- des alias et clés de résolution pour les noms ambigus.
Sans cela, les appels d'outils deviennent fragiles. Le modèle réessaie avec des chaînes de caractères, multiplie les approximations, et l'utilisateur obtient un résultat qui "a l'air juste". C'est souvent la phrase qui précède une erreur de production.
Comment les agents interrogent le serveur
Le plus utile n'est pas une longue liste d'outils. C'est un petit ensemble cohérent, documenté, avec des paramètres explicites et des réponses prévisibles.
Les outils de base
Un serveur MCP pour données d'initiés peut exposer, par exemple :
search_issuers
Recherche d'émetteurs par nom, ticker, ISIN si disponible, ou alias.
Entrée typique
{
"query": "LVMH",
"jurisdiction": "FR",
"limit": 5
}
Sortie typique
{
"items": [
{
"issuer_id": "iss_123",
"name": "LVMH Moët Hennessy Louis Vuitton SE",
"ticker": "MC",
"jurisdiction": "FR",
"match_score": 0.98
}
]
}
L'intérêt est évident. L'agent n'invente pas quel "LVMH" il a trouvé. Il récupère un identifiant et peut poursuivre proprement.
list_filings
Liste des filings selon des filtres bornés.
Entrée typique
{
"issuer_id": "iss_123",
"start_date": "2025-01-01",
"end_date": "2025-12-31",
"transaction_direction": "buy",
"normalized_types": ["open_market"],
"page": 1,
"page_size": 50
}
Sortie typique
{
"items": [
{
"filing_id": "fil_987",
"filed_at": "2025-03-14T08:12:00Z",
"transaction_date": "2025-03-12",
"issuer_id": "iss_123",
"insider_id": "ins_456",
"summary": "Achat au marché",
"source_reference": "n/a"
}
],
"next_page": 2
}
Le détail important est la pagination. Les agents ont tendance à demander "tout". Les serveurs sérieux répondent "par pages".
get_filing_detail
Récupère le détail d'un filing ou d'une transaction.
Entrée typique
{
"filing_id": "fil_987"
}
Sortie typique
{
"filing_id": "fil_987",
"issuer": {
"issuer_id": "iss_123",
"name": "LVMH Moët Hennessy Louis Vuitton SE",
"jurisdiction": "FR"
},
"insider": {
"insider_id": "ins_456",
"name": "Nom de l'initié",
"role": "Dirigeant"
},
"transactions": [
{
"transaction_type_normalized": "open_market_buy",
"security_type": "common_stock",
"quantity": "1000",
"price_per_unit": "712.50",
"currency": "EUR",
"transaction_date": "2025-03-12"
}
],
"source_url": "n/a"
}
L'agent peut alors résumer, comparer, ou signaler les limites, sans broder.
Une séquence de requêtes réaliste
Prenons une demande utilisateur dans Claude :
"Montre-moi les achats d'initiés les plus récents sur un émetteur français, et résume les trois derniers filings."
Le chemin raisonnable est :
search_issuers pour résoudre l'émetteur ;
list_filings avec filtres jurisdiction=FR, transaction_direction=buy, tri décroissant par filed_at ;
get_filing_detail sur les trois premiers résultats ;
- rédaction d'une synthèse, avec mention des dates de transaction et de dépôt, et sans extrapolation de performance boursière.
C'est presque décevant de simplicité. C'est bon signe.
Ce que l'agent ne devrait pas faire
Un agent ne devrait pas :
- déduire le sens économique d'une transaction à partir d'un code source non normalisé ;
- agréger achats et exercices d'options comme si c'était la même chose ;
- comparer des valeurs en devise sans conversion explicite ;
- présenter une absence de filings comme une absence d'activité économique ;
- ignorer la juridiction et ses règles de publication.
Ces interdictions sont moins glamour qu'une "analyse augmentée", mais elles évitent les erreurs qui coûtent la réputation d'un produit.