premier article, je l'ai implémentée en utilisant GridGain dans mon second article. J'ai conclu dans ce dernier que les performances relativement bonnes obtenues étaient liées aux optimisations réalisées. L'une d'elles était basée sur l'hypothèse que les résultats intermédiaires - les prix issus de chaque tirage - pouvaient être oubliés. Cependant, ce n'est pas toujours le cas. Conserver les paramètres de génération et les prix des calls pour chaque tirage peut être très utile pour le métier afin de pouvoir analyser l'influence des différents paramètres. De telles données sont souvent traitées par des outils de Business Intelligence. Le calcul de la VAR n'est peut être pas le meilleur exemple pour illustrer ce besoin métier mais je vais le réutiliser car il a déjà été introduit dans un précédent artcile. L'objectif de cette série d'articles sera de calculer la Value At Risk et de conserver tous les résultats, de façon à pouvoir les analyser.
Dans ces articles, certaines portions de code ont été supprimées lorsqu'aucune évolution n'avait eu lieu depuis le dernier article. Ces portions ont été remplacées par des commentaires (//Unchanged...
).
La façon la plus simple de répondre au besoin est de modifier l'implémentation courante. J'ai créé pour cela une classe Result
qui porte les paramètres et le prix pour chaque tirage. Elle implémente Serializable
de façon à pouvoir être stockée sur le disque et Comparable
de façon à pouvoir être triée de la même façon que les prix
.
public class Result implements Serializable, Comparable<result> {
private final Parameters parameters;
private final double price;
//Constructor, getters, compareTo(), equals(), hashCode(), toString() implementations...
}
computeVar()
signature is modified accordingly.
public SortedSet<result> computeVar(...) throws MathException {
//Unchanged...
for (int i = 0; i < drawsNb; i++) {
//Unchanged....
final Result result = new Result(new Parameters(optionPricer.getParameters()),price);
// For each draw, put the price in the sorted set
smallerPrices.add(result);
if(configuration.isCombineAllowed()) {
//Unchanged...
}
}
return smallerPrices;
}
Enfin, les résultats sont écrits sur le disque en appelant FilePersistenceManager.writeToDisk()
qui délègue simplement à un ObjectOutputStream branché sur le disque.
Quelques mesures, réalisées sur le même portable que pour le précédent article, montre comme nous pouvions nous y attendre, des impacts de performance :
Result
est plus grosse qu'une collection de simples double
. En fait, sur mon architecture 32 bits, je n'ai pas été capable de générer plus de 1000000 de résultats.Le graphique suivant montre la perte liée au stockage des résultats intermédiaires comme définie ci-dessous, à travers les différents scénarios.
, Ces scénarios sont définis comme indiqués ci-dessous.
Ecrire tous les résultats intermédiaires sur le disque pour 1000000 de tirages est environ 40 fois plus lent que le scénario le plus optimisé. Le challenge est désormais de trouver des solutions. Cette approche est très naïve et certaines optimisations telles que l'utilisation d'un cache distribué pour stocker les données auraient probablement aidé. La troisième version de GridGain apporte par exemple une grille de données intégrée. Cependant, de façon à évaluer de nouvelles architectures, j'ai choisi de l'implémenter avec Hadoop.
Hadoop, est un projet Apache qui se définit lui-même comme un projet open-source pour du calcul distribué, scalable et fiable (an open-source software for reliable, scalable, distributed computing). Selon Wikipedia il permet de travailler avec des milliers de noeuds et des petabytes de données. En bref, il s'agit d'un outil employé par Yahoo pour traiter de très gros volumes de données. Son architecture a été inspirée par Google MapReduce and Distributed File System. Je ne le décrirai pas plus avant. Je vous renverrai vers cet article pour de plus amples informations.
J'ai décidé d'implémenter ce calcul de VAR avec Hadoop pour 3 raisons :
Plus spécifiquement, les architectures "NoSQL" sont bien adaptées aux traitements décisionnels. Aussi, j'ai voulu voir s'il était possible de combiner les deux fonctions dans un seul outil. Un des avantages serait de limiter la quantité de données transportées d'un outil à un autre. Pour ce faire, j'ai utilisé Hadoop pour le map/reduce et pour l'analyse avec Hive. Hive est une sorte de petit system de datawarehouse conçu au dessus de Hadoop. En pratique, il fournit un petit DSL très proche du SQL, qui est transcrit en actions de map/reduce. Je vous renvoie à cet article pour plus de détails sur l'installation de Hive.
Hadoop et Hive sont des systèmes basés sur la manipulation de fichier (en conservant en tête qu'ils sont basés sur un système de fichiers distribué). Hadoop consiste donc en un système de fichier distribué (Distributed File System) - qui permet de partager le code et les données entre noeuds (les machines) -, et d'un ensemble de travaux (jobs), divisés en tâches map et reduce coordonnées par le framework Hadoop. Ainsi, Hadoop fournit nativement :
Hadoop traite des fichiers. Pour mon besoin je lui ai fourni en entrée le fichier suivant.
1;252;120.0;120.0;0.05;0.2;0.15;1000;0.99;250
1;252;120.0;120.0;0.05;0.2;0.15;1000;0.99;250
1;252;120.0;120.0;0.05;0.2;0.15;1000;0.99;250
1;252;120.0;120.0;0.05;0.2;0.15;1000;0.99;250
Chaque ligne signifie: Calcule 250 tirages du prix du call avec les paramètres suivants (t=252 days, s0=120€, k=120€, r=0.05%, sigma=0.2, historicalVolatility=0.15) pour ce scenario 1. L'objectif est de calculer la VAR à 1% (0.99) sur 1000 tirages. Je vous renvoie à mon précédent article pour plus d'explications sur ces paramètres. L'implémentation sur Hadoop est décrite ci-dessous. La numérotation correspond aux étiquettes sur le schéma et fait référence à l'ordre chronologique:
NLineInputFormat
, décrite dans un prochain article de cette série), ce qui signifie que chaque ligne sera envoyée à un créneau de tâche map
map
et les prix sont calculés. Les résultats sont stockés temporairement dans le système de fichier local. Le format de sortie sera clé-valeur comme décrit ci-aprèsreduce
. Pour les besoins de la première implémentation, je n'ai pas utilisé de code dans la partie reduce
comme nous le verrons dans le prochain article 5. Les résultats sont partagés sur le système de fichiers distribuéAinsi, l'implémentation avec GridGain a montré que stocker les résultats sur le disque a un impact sur les performances. Hadoop est un outil différent. Il est moins optimisé pour les tâches de calcul intensif mais fournit un framework efficace pour distribuer le traitement de gros volumes de données. Son implémentation du pattern map/reduce est assez différente de celle de GridGain et nécessite un certain nombre d'adaptations pour calculer la VAR. Nous discuterons en détail, dans le prochain article de cette série, l'implémentation du calcul de la VAR. Les trois parties qui suivront se concentreront sur du code et des détails d'implémentation. Les personnes les moins intéressées par la technique pourront reprendre la série au niveau de la partie 4 où j'introduirai l'analyse des données intermédiaires.