Comment créer un analyste financier privé en local ?

Vous pouvez bâtir un analyste financier privé en Python qui prétraite des CSV bancaires, exécute des modèles ML locaux et utilise un LLM hébergé localement (ex. Ollama) pour générer des insights—sans envoyer vos données dans le cloud. Lisez la suite pour le plan technique et les exemples concrets.

Pourquoi créer un analyste financier privé ?

Créer un analyste financier privé permet de reprendre le contrôle de vos données tout en obtenant des insights exploitables sans dépendre d’un fournisseur cloud tiers.

Le problème principal reste la confiance et la confidentialité des solutions cloud : Les données financières transitent et sont stockées sur des serveurs externes, augmentant la surface d’attaque et les risques de fuite. Les obligations légales comme le RGPD (Article 5 sur la minimisation et Article 32 sur la sécurité) imposent des exigences que l’on maîtrise mieux en local.

Objectifs concrets d’un analyste local :

  • Analyse locale immédiate : Réponses quasi-instantanées sans aller-retour réseau.
  • Détection d’anomalies : Identification de transactions suspectes en temps réel pour réduire le délai de réponse.
  • Classification des dépenses : Catégorisation automatique pour budgétisation et reporting.
  • Explications en langage naturel : Génération de comptes-rendus compréhensibles sans exporter de données.

Bénéfices chiffrés et concrets :

  • Réduction du risque d’exposition des comptes : Les données qui ne quittent jamais la machine ne peuvent pas être compromises via le cloud (zéro vecteur cloud pour ces éléments).
  • Conformité RGPD améliorée : Respect facilité des principes de minimisation et de sécurité (RGPD Art.5 et Art.32).
  • Latence réduite : Réponses généralement perceptibles en millisecondes localement, versus centaines de ms à plusieurs secondes en cloud selon la connexion.
  • Coût opérationnel maîtrisé : Économie sur les frais continus d’API / stockage cloud pour volumes sensibles.

Scénarios d’usage :

  • Contrôle personnel des dépenses et alertes budgétaires.
  • Détection rapide de fraude avant contact avec la banque.
  • Préparation automatisée des pièces pour l’expert-comptable.

Contraintes techniques et réglementaires à considérer :

  • Stockage local chiffré et gestion des clés.
  • Politique de backups sécurisés et hors-site chiffrés.
  • Limitation des modèles sur machine (taille, mémoire, GPU).
  • Mises à jour, patching et responsabilité en cas d’incident.
  • Respect des obligations de conservation et de transfert transfrontalier si applicable.
Critère Cloud Local
Confidentialité Partagée avec le fournisseur, dépend des contrats Maîtrisée si chiffrage et clés locales
Coût Frais récurrents d’API et stockage Investissement initial + coûts d’infra internes
Latence Variable selon réseau Quasi-instantanée localement
Complexité Moins technique pour l’utilisateur final Plus de gestion (sécurité, backup, modèle)

Quelle architecture pour l’application ?

Je propose une architecture modulaire pour un analyste financier local : interface Streamlit, pipeline de preprocessing, modules ML, visualisations Plotly et intégration d’un LLM local (ex. Ollama) orchestrés depuis app.py.

Je décris ici le rôle de chaque composant et leurs échanges.

  • app.py : Point d’entrée Streamlit, orchestration des appels aux modules, gestion UI et secrets.
  • config.py : Paramètres centralisés (chemins, hyperparamètres, endpoints locaux).
  • preprocessing.py : Nettoyage, feature engineering, export en parquet pour persistance.
  • ml_models.py : Entraînement, inférence, sauvegarde/chargement de modèles avec joblib.
  • visualizations.py : Graphs Plotly retournant objets JSON pour Streamlit.
  • llm_integration.py : Intégration d’un LLM local (LLM = Large Language Model) via requêtes HTTP vers Ollama ou équivalent.
  • requirements.txt : Dépendances listées pour reproduction (pandas, scikit-learn, plotly, streamlit, joblib, requests, pyarrow).
  • sample_data/ : Jeux de données exemples en parquet.

Je fais communiquer les composants par fonctions et fichiers locaux : preprocessing.write_parquet(), ml_models.load_model() utilisant joblib.load(), visualizations.plot_*() renvoyant figures, llm_integration.query(prompt) appelant l’API locale via requests.

Je recommande Python 3.10+, environnement isolé venv ou conda, et les dépendances principales citées plus haut.

# app.py (squelette)
import streamlit as st
from config import CFG
from preprocessing import load_and_prep
from ml_models import load_model, predict
from visualizations import plot_timeseries
from llm_integration import query_llm

st.sidebar.title("Contrôles")
data = load_and_prep(CFG.DATA_PATH)
model = load_model(CFG.MODEL_PATH)
if st.button("Prédire"):
    preds = predict(model, data)
    st.plotly_chart(plot_timeseries(data, preds))
    st.write(query_llm("Explique ces prédictions", context=preds))
# config.py (squelette)
import os
class CFG:
    DATA_PATH = os.getenv("DATA_PATH", "sample_data/data.parquet")
    MODEL_PATH = os.getenv("MODEL_PATH", "models/model.joblib")
    LLM_ENDPOINT = os.getenv("LLM_ENDPOINT", "http://localhost:11434")

Je recommande trois options de déploiement local : sur PC (développement), serveur interne pour usage d’entreprise, ou container Docker pour reproductibilité.

Je conseille sécurité : chiffrement disque (LUKS/BitLocker), accès restreint (VPN, pare-feu), secrets en variables d’environnement ou fichiers chiffrés (age/gpg), sauvegardes chiffrées hors-site.

Fichier Responsabilité
app.py Orchestration UI et appels modules
config.py Paramètres et secrets
preprocessing.py Nettoyage et export parquet
ml_models.py Entraînement/Inférence, joblib
visualizations.py Plots Plotly
llm_integration.py API locale LLM (Ollama)
  • Créer l’environnement (venv/conda) et installer requirements.
  • Configurer variables d’environnement et chiffrer les secrets.
  • Valider accès réseau restreint au port LLM local.
  • Activer chiffrement disque et sauvegardes chiffrées régulières.
  • Tester restauration depuis backup et rotation des clés/secrets.

Comment prétraiter des relevés bancaires hétérogènes ?

Traiter des relevés bancaires hétérogènes commence par résoudre des problèmes concrets : colonnes et séparateurs variables, formats de date multiples, montants répartis en débit/crédit, séparateurs décimaux différents et symboles de devise. J’utilise pandas et des regex pour détecter et normaliser automatiquement à l’import afin d’obtenir le schéma standard (date, description, amount).

  • Exemples de patterns regex pour détecter les colonnes (insensibles à la casse) :
  • Date : date|transaction_date|posted|valeur|booking_date
  • Montant : amount|montant|credit|debit|valeur|solde
  • Description : desc|libellé|libelle|description|narration

Principes de traitement automatisé :

  • Détection automatique des colonnes via regex pour mapper à date/description/amount.
  • Conversion des dates avec une liste de formats et fallback à pd.to_datetime (errors=’coerce’).
  • Fusion Debit/Credit : calculer amount = credit.fillna(0) – debit.fillna(0) pour que les dépenses soient négatives.
  • Normalisation des décimales et devises : remplacer les virgules par des points, retirer les symboles monétaires, conserver une colonne currency si détectée.
  • Suppression des duplicatas et gestion des valeurs manquantes (supprimer lignes sans date/amount, remplir description par ‘N/A’).

Règle d’or : Normaliser tôt pour simplifier l’ingénierie des features et éviter la dette technique en aval.

import re
import pandas as pd

DATE_PATTERNS = re.compile(r"date|transaction_date|posted|valeur|booking_date", re.I)
AMOUNT_PATTERNS = re.compile(r"amount|montant|credit|debit|valeur|solde", re.I)
DESC_PATTERNS = re.compile(r"desc|libellé|libelle|description|narration", re.I)
CURRENCY_RE = re.compile(r"[\u20AC$£]")

def detect_columns(cols):
    mapping = {}
    for c in cols:
        if DATE_PATTERNS.search(c):
            mapping['date'] = c
        elif DESC_PATTERNS.search(c):
            mapping['description'] = c
        elif AMOUNT_PATTERNS.search(c):
            mapping.setdefault('amount_candidates', []).append(c)
    return mapping

def parse_amount_series(s):
    s = s.astype(str).str.replace(r"[^\d,.\-()]", "", regex=True)
    s = s.str.replace(r"\(([\d.,]+)\)", r"-\1", regex=True)
    s = s.str.replace(",", ".", regex=False)
    return pd.to_numeric(s, errors='coerce')

def preprocess_df(df):
    cols = detect_columns(df.columns)
    # Date
    date_col = cols.get('date')
    df['date'] = pd.to_datetime(df[date_col], dayfirst=True, errors='coerce') if date_col else pd.NaT
    # Description
    desc_col = cols.get('description')
    df['description'] = df[desc_col].fillna('N/A') if desc_col else 'N/A'
    # Amount
    ac = cols.get('amount_candidates', [])
    if 'debit' in [c.lower() for c in ac] and 'credit' in [c.lower() for c in ac]:
        credit = parse_amount_series(df[[c for c in ac if 'credit' in c.lower()][0]])
        debit = parse_amount_series(df[[c for c in ac if 'debit' in c.lower()][0]])
        df['amount'] = (credit.fillna(0) - debit.fillna(0))
    elif ac:
        df['amount'] = parse_amount_series(df[ac[0]])
    else:
        df['amount'] = pd.NA
    # Currency detection
    df['currency'] = df.apply(lambda row: 'EUR' if row.astype(str).str.contains('€').any() else None, axis=1)
    # Clean
    df = df.drop_duplicates()
    df = df.dropna(subset=['date','amount'])
    return df[['date','description','amount','currency']]
def test_preprocess_examples():
    import pandas as pd
    from preprocessing import preprocess_df
    # Exemple 1
    df1 = pd.DataFrame({'Date':['01/02/2021'],'Libellé':['Café'],'Débit':['5,00'],'Crédit':[None]})
    out1 = preprocess_df(df1); assert out1['amount'].iloc[0] == -5.0
    # Exemple 2
    df2 = pd.DataFrame({'transaction_date':['2021-03-01'],'description':['Salaire'],'amount':['1500.00']})
    out2 = preprocess_df(df2); assert out2['amount'].iloc[0] == 1500.0
    # Exemple 3
    df3 = pd.DataFrame({'posted':['01.04.2021'],'desc':['Remboursement'],'montant':['(20,50) €']})
    out3 = preprocess_df(df3); assert out3['amount'].iloc[0] == -20.5
Avant (exemples) Après (schéma standard)
Date, Libellé, Débit, Crédit date, description, amount (crédit – débit), currency
transaction_date, description, amount (virgule) date, description, amount (float, point décimal)
posted, desc, montant (€ entre parenthèses) date, description, amount (négatif), currency=EUR

Comment intégrer ML, visualisations et LLM locaux ?

Pour construire une pipeline locale combinant ML, visualisations et LLM, commencez par extraire des features robustes: fréquence de paiement, montant moyen, écart-type, encodage des libellés (one‑hot ou embeddings), jours depuis dernière transaction, et ratio crédit/débit.

Pour la détection d’anomalies, j’identifie IsolationForest et LocalOutlierFactor comme solides points de départ. Pour la classification, RandomForest puis LightGBM si vous pouvez l’exécuter localement (plus rapide, meilleure gestion des features catégoriques).

Exemple d’entraînement IsolationForest, sauvegarde et prédiction (scikit-learn + joblib):

from sklearn.ensemble import IsolationForest
import pandas as pd
from joblib import dump, load

# Charger données pré-traitées avec features
X = pd.read_csv('features.csv').drop(columns=['id'])

# Entraîner
clf = IsolationForest(n_estimators=100, contamination=0.01, random_state=42)
clf.fit(X)

# Sauvegarder
dump(clf, 'isoforest.joblib')

# Charger et prédire (exemple)
clf = load('isoforest.joblib')
scores = clf.decision_function(X)  # plus bas = anomalie
labels = clf.predict(X)  # -1 anomalie, 1 normal

Intégration minimale Streamlit + Plotly: afficher série temporelle, bar par catégorie et liste d’anomalies.

import streamlit as st
import plotly.express as px
from joblib import load
import pandas as pd

clf = load('isoforest.joblib')
df = pd.read_csv('transactions.csv', parse_dates=['date'])
X = pd.read_csv('features.csv')

df['anomaly'] = clf.predict(X) == -1

st.title("Tableau financier local")
fig_ts = px.line(df.groupby('date').sum().reset_index(), x='date', y='amount', title='Timeseries')
st.plotly_chart(fig_ts)

fig_cat = px.bar(df.groupby('category').sum().reset_index(), x='category', y='amount', title='Par catégorie')
st.plotly_chart(fig_cat)

st.dataframe(df[df['anomaly']])

Pour combiner avec un LLM local (ex. Ollama), structurez un prompt clair: résumé agrégé + top N anomalies + questions spécifiques. Toujours anonymiser et agréger avant d’envoyer.

  • Règles d’anonymisation: Masquer les PII (noms, IBAN), remplacer par tokens génériques, agrégation par catégorie/dates.
  • Règles d’usage: Limiter texte envoyé, logger les prompts localement, revues humaines périodiques.

Exemple d’appel HTTP local vers Ollama (Python requests) et template de prompt:

import requests, json

prompt = """
Résumé: Total mois=12000€, Montant moyen=45€, Ecarts significatifs sur catégorie 'Télécoms'.
Top anomalies: 1) 2026-03-12 - 1200€ - 'Virement X' 2) 2026-03-20 - 950€ - 'Paiement Y'
Consignes: Expliquer en langage clair, proposer 3 hypothèses et prochaines actions.
"""

resp = requests.post('http://localhost:11434/api/generate', json={
    "model":"your-model",
    "prompt": prompt,
    "max_tokens": 400
})
print(resp.json())

Tableau comparatif des rôles et métriques à suivre:

Composant Rôle Métriques
Modèles ML Détecter anomalies et classer transactions Precision/Recall, FPR, taux de détection (recall), AUC
LLM local Transformer résultats en langage clair, expliquer et prioriser Latence (ms), qualité d’explication (revue humaine), taux d’humain-in-the-loop
Visualisations Explorer, valider et prioriser alertes Temps de rafraîchissement, interactivité, taux d’usage

Surveillez en continu précision/recall pour la classification, taux de détection et faux positifs pour l’anomalie, ainsi que la latence locale du pipeline ML+LLM.

Prêt à analyser vos finances localement et en toute confidentialité ?

Un analyste financier privé local combine preprocessing robuste, modèles ML adaptés et un LLM local pour produire des insights compréhensibles—le tout sans exposer vos données au cloud. La clé : normaliser tôt, modulariser l’architecture, et appliquer des mesures de sécurité simples (chiffrement, backups). En suivant cette approche vous gagnez en confidentialité, en contrôle opérationnel et en réactivité : bénéfice direct pour vos finances et votre tranquillité d’esprit.

FAQ

Quelle garantie de confidentialité offre une solution locale ?
En exécutant tout localement (préprocessing, ML, LLM) vous évitez la transmission vers des serveurs tiers. Complétez par chiffrement disque, contrôle d’accès et sauvegardes chiffrées pour minimiser tout risque.
Puis-je utiliser n’importe quel LLM local ?
Vous pouvez, à condition qu’il dispose d’une API locale (ex. Ollama ou runtime équivalent). Vérifiez les ressources CPU/RAM requises et la compatibilité des prompts pour générer des résumés financiers pertinents.
Quels modèles ML conviennent pour la détection d’anomalies ?
IsolationForest et LocalOutlierFactor sont des choix robustes pour anomalies transactionnelles. Pour classification de catégories, RandomForest ou LightGBM (si support local) offrent un bon compromis précision/ressources.
Faut-il anonymiser les données avant d’utiliser le LLM ?
Oui : agrège ou anonymise les transactions sensibles (masquage d’IBAN, noms complets). Préférez transmettre des résumés agrégés au LLM pour limiter tout risque de fuite d’information.
Quelles compétences sont nécessaires pour déployer ce projet ?
Compétences Python (pandas, scikit-learn), bases en déploiement local (venv/Docker), et notions de sécurité (chiffrement, gestion des secrets). Pour la partie LLM, connaissance des appels HTTP locaux et du formatage de prompts est utile.

 

 

A propos de l’auteur

Franck Scandolera — expert & formateur en Tracking avancé server-side, Analytics Engineering, Automatisation No/Low Code (n8n) et intégration d’IA en entreprise. Responsable de l’agence webAnalyste et de l’organisme de formation Formations Analytics. J’accompagne des clients tels que Logis Hôtel, Yelloh Village, BazarChic, Fédération Française de Football et Texdecor. Disponible pour aider les entreprises — contactez moi.

Retour en haut
MetricsMag