Simulateur boursier 📈

Simulateur de marchĂ© boursier 📈

Les sous-titres sont disponibles en français.

Bienvenue dans ce nouveau projet ! Nous allons récupérer des données historiques du marché boursier et les utiliser pour estimer combien nous aurions gagné ou perdu en investissant dans des entreprises cotées en bourse.

Voici Ă  quoi ressemblera le projet Ă  la fin. La capture d’écran ci-dessous montre le rĂ©sultat d’un investissement de 1 000 $ dans Apple en janvier 2020, jusqu’en fĂ©vrier 2025.

Avant de commencer, assurez-vous d’avoir complĂ©tĂ© les sections Premiers pas 🧑‍🎓 et Aller plus loin 🚀. Ce projet est conçu pour vous aider Ă  pratiquer tout ce que nous avons couvert jusqu’à prĂ©sent dans ce cours.

Une capture d'écran de VS Code montrant un script simulant le marché boursier

Vous voulez ĂȘtre prĂ©venu quand de nouvelles leçons sont publiĂ©es ? Abonnez-vous Ă  l'infolettre ✉ et donnez une ⭐ au cours sur GitHub pour me garder motivé ! Cliquez ici pour me contacter.

Quelle est la question ?

Il est toujours important d’identifier clairement la question Ă  laquelle nous voulons rĂ©pondre avant de plonger trop vite dans un ocĂ©an de donnĂ©es.

Aujourd’hui, notre question est :

  • Combien aurions-nous gagnĂ© ou perdu en investissant dans une entreprise cotĂ©e en bourse Ă  une date spĂ©cifique ?

Pour y répondre, nous devons calculer la différence entre le montant initial et le montant final.

Nous pouvons également identifier trois variables qui influenceront le résultat :

  • L’entreprise choisie
  • Le montant initial investi
  • La date de l’investissement

Avec cela en tĂȘte, commençons !

Configuration

Créez un nouveau dossier contenant :

  • Un fichier main.ts avec console.log("Hello!");
  • Un fichier vide deno.json

Puis exécutez la commande suivante : deno run -A --watch --check main.ts

Une capture d'écran de VS Code montrant un script affichant "Hello!" dans le terminal.

Les données

Yahoo Finance

Pour notre simulateur, nous avons besoin des prix historiques du marchĂ© boursier. L’une des sources les plus courantes pour les donnĂ©es financiĂšres est Yahoo Finance.

L’usage d’une petite quantitĂ© de donnĂ©es est tolĂ©rĂ© pour des fins d’enseignement ou d’intĂ©rĂȘt public, mais si vous voulez rĂ©cupĂ©rer et rĂ©utiliser un grand volume de ces donnĂ©es, avec ou sans objectif commercial, contactez l’équipe derriĂšre le site ou payez un abonnement premium.

Sur la page d’accueil, vous pouvez rechercher une entreprise cotĂ©e en bourse. Par exemple, recherchez Apple et cliquez sur le rĂ©sultat correspondant.

Une capture d'écran montrant le site Yahoo Finance.

Vous arriverez sur la page de l’action Apple. Sur le cĂŽtĂ© gauche, cliquez sur DonnĂ©es historiques.

Une capture d'écran montrant le site Yahoo Finance.

Les donnĂ©es sont maintenant affichĂ©es sous forme de tableau, ce qui vous permet de rĂ©cupĂ©rer toutes les donnĂ©es disponibles depuis que l’entreprise est entrĂ©e en bourse. Pour ce faire, cliquez sur le sĂ©lecteur de dates et choisissez l’option Max.

Les colonnes qui nous intéressent vraiment sont Date et Adj Close (prix ajusté de clÎture).

Une capture d'écran montrant le site Yahoo Finance.

Facile, non ? Mais comment pouvons-nous rĂ©cupĂ©rer ces donnĂ©es dans notre script ? đŸ€”

Trouver les données

Ces donnĂ©es ne viennent pas de nulle part. Elles proviennent probablement d’une API qui les fournit Ă  la page. Jetons un coup d’Ɠil sous le capot pour en dĂ©couvrir la source. 🧐

💡

API signifie Application Programming Interface. Sur le web, les API sont souvent utilisĂ©es pour transfĂ©rer des donnĂ©es. Lorsque vous appelez un point d’accĂšs API (via une URL et parfois des paramĂštres), l’API renvoie les donnĂ©es correspondantes. Les API sont trĂšs utiles pour les sites affichant des donnĂ©es en temps rĂ©el, entre autres. Au lieu de reconstruire et republier le site avec de nouvelles donnĂ©es — ce qui peut ĂȘtre lent et coĂ»teux — il suffit de mettre Ă  jour les points d’accĂšs de l’API. Les rĂ©ponses des API sont souvent en JSON, mais elles peuvent aussi ĂȘtre en CSV, XML et d’autres formats.

Notez que j’utiliserai Google Chrome pour les Ă©tapes suivantes, mais vous pouvez faire la mĂȘme chose sur Firefox ou Safari.

Ouvrez les Outils de dĂ©veloppement et cliquez sur l’onglet RĂ©seau.

Une capture d'écran montrant le site Yahoo Finance avec les outils de développement ouverts.

Cet onglet affiche toutes les requĂȘtes effectuĂ©es par la page. Lorsqu’une page se charge, elle a besoin de diverses ressources, telles que des polices, des images, des styles et
 des donnĂ©es ! Toutes ces requĂȘtes sont listĂ©es ici et vous pouvez les explorer.

Dans notre cas, nous nous intĂ©ressons aux donnĂ©es boursiĂšres d’Apple affichĂ©es sous forme de tableau sur la page web.

RafraĂźchissez la page, puis sĂ©lectionnez Ă  nouveau l’option Max pour rĂ©cupĂ©rer toutes les donnĂ©es disponibles. Recherchez une requĂȘte contenant AAPL, qui est le symbole boursier d’Apple. C’est aussi le symbole utilisĂ© dans l’URL de la page, donc c’est un bon indice.

Vous remarquerez une ou plusieurs requĂȘtes fetch commençant par AAPL. Cela semble trĂšs prometteur !

Une capture d'Ă©cran montrant le site Yahoo Finance avec les requĂȘtes rĂ©seau dĂ©taillĂ©es.

Faites un clic droit sur l’une d’elles et ouvrez-la dans un nouvel onglet. Wow ! Vous reconnaissez cette syntaxe ? C’est du JSON ! Et il contient beaucoup de donnĂ©es. 😏

Voici le lien au cas oĂč vous en auriez besoin.

Si vous regardez attentivement l’URL, vous remarquerez des paramĂštres comme symbol, interval, period1 et period2. Il y a aussi les paramĂštres region et lang, qui peuvent ĂȘtre diffĂ©rents pour vous en fonction de votre localisation.

https://query1.finance.yahoo.com/v8/finance/chart/AAPL?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=345479400&period2=1738778777&symbol=AAPL&userYfid=true&lang=en-CA&region=CA

Explorons cela plus en détail avec du code.

Une capture d'écran montrant l'endpoint de l'API Yahoo Finance.

Récupération des données

RĂ©cupĂ©rons les donnĂ©es Ă  partir du point d’accĂšs de l’API que nous venons de dĂ©couvrir. Pour ce faire, nous pouvons Ă©crire un script simple comme celui ci-dessous.

main.ts
const response = await fetch(
  "https://query1.finance.yahoo.com/v8/finance/chart/AAPL?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=345479400&period2=1738778777&symbol=AAPL&userYfid=true&lang=en-CA&region=CA",
);
 
const data = await response.json();
 
console.log(data);

C’est maintenant un peu plus clair.

Les données sont un grand objet nested. Les dates (timestamps) et les prix ajustés de clÎture (adjclose) sont stockés sous forme de listes.

Une capture d'écran montrant les données Yahoo Finance dans le terminal VS Code.

Pour les rĂ©cupĂ©rer, nous devons parcourir l’objet.

Pour accéder aux timestamps, nous devons :

  • Obtenir l’objet chart
  • Puis rĂ©cupĂ©rer la liste result
  • RĂ©cupĂ©rer le premier Ă©lĂ©ment de la liste, qui est un objet
  • À l’intĂ©rieur de cet objet, obtenir la liste timestamp

Cela ressemble Ă  ceci.

const timestamps = data.chart.result[0].timestamp;

Pour accéder aux prix ajustés de clÎture, nous devons :

  • Obtenir l’objet chart
  • Puis rĂ©cupĂ©rer la liste result
  • RĂ©cupĂ©rer le premier Ă©lĂ©ment de la liste, qui est un objet
  • À l’intĂ©rieur de cet objet, obtenir l’objet indicators
  • Ensuite, rĂ©cupĂ©rer la liste adjclose
  • RĂ©cupĂ©rer le premier Ă©lĂ©ment de la liste, qui est un objet
  • À l’intĂ©rieur de cet objet, obtenir les valeurs de adjclose

Cela ressemble Ă  ceci.

const values = data.chart.result[0].indicators.adjclose[0].adjclose;

Ne vous inquiĂ©tez pas. Les structures JSON ne sont pas toujours aussi complexes sur le web. AprĂšs quelques projets, vous serez capable de les lire comme un pro ! đŸ€“

main.ts
const response = await fetch(
  "https://query1.finance.yahoo.com/v8/finance/chart/AAPL?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=345479400&period2=1738778777&symbol=AAPL&userYfid=true&lang=en-CA&region=CA",
);
 
const data = await response.json();
 
const timestamps = data.chart.result[0].timestamp;
console.log(timestamps);
 
const values = data.chart.result[0].indicators.adjclose[0].adjclose;
console.log(values);

Une capture d'écran montrant les timestamps et les valeurs dans le terminal.

Pour Ă©viter d’encombrer le terminal, Deno affiche seulement les 100 premiers Ă©lĂ©ments des listes. Mais avez-vous remarquĂ© que les listes de timestamps et de valeurs ont le mĂȘme nombre d’élĂ©ments ?

C’est un trùs bon signe.

Cela signifie probablement que le premier élément de la liste des timestamps (une date) correspond au premier élément de la liste des valeurs (un prix ajusté de clÎture).

VĂ©rifions les premiers et derniers Ă©lĂ©ments pour en ĂȘtre sĂ»rs.

Les timestamps reprĂ©sentent la durĂ©e Ă©coulĂ©e depuis le 1á”‰Êł janvier 1970. Ici, ils semblent ĂȘtre en secondes. Cependant, en JavaScript, les timestamps sont en millisecondes. Nous pouvons facilement ajuster le tout.

main.ts
const response = await fetch(
  "https://query1.finance.yahoo.com/v8/finance/chart/AAPL?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=345479400&period2=1738778777&symbol=AAPL&userYfid=true&lang=en-CA&region=CA",
);
 
const data = await response.json();
 
const timestamps = data.chart.result[0].timestamp;
const firstTimestamp = new Date(timestamps[0] * 1000);
const lastTimestamp = new Date(timestamps[timestamps.length - 1] * 1000);
 
const values = data.chart.result[0].indicators.adjclose[0].adjclose;
const firstValue = values[0];
const lastValue = values[values.length - 1];
 
console.log({ firstTimestamp, lastTimestamp, firstValue, lastValue });

Ce code affiche ces valeurs dans le terminal.

{
  firstTimestamp: 1980-12-12T14:30:00.000Z,
  lastTimestamp: 2025-02-05T14:30:00.000Z,
  firstValue: 0.09883447736501694,
  lastValue: 232.47000122070312
}

Apple est bien entrĂ©e en bourse le 12 dĂ©cembre 1980 avec un prix par action de 0,10 $, selon son site web, et la derniĂšre valeur correspond Ă  ce que je vois sur le site de Yahoo au 5 fĂ©vrier 2025 ! Nous avons nos donnĂ©es ! đŸ„ł

Une capture d'écran montrant la valeur de l'action Apple sur le site de Yahoo.

L’URL actuelle est spĂ©cifique Ă  Apple, mais nous voulons que notre code fonctionne avec n’importe quelle entreprise. Extrayons donc certaines valeurs de paramĂštres sous forme de variables.

En utilisant des backticks, nous pouvons insĂ©rer des variables dans l’URL. Au lieu de ceci :

https://query1.finance.yahoo.com/v8/finance/chart/AAPL?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=345479400&period2=1738778777&symbol=AAPL&userYfid=true&lang=en-CA&region=CA

Nous pouvons gĂ©nĂ©raliser l’appel Ă  l’API en utilisant ${symbol}, ${period1} et ${period2} :

https://query1.finance.yahoo.com/v8/finance/chart/${symbol}?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=${period1}&period2=${period2}&symbol=${symbol}&userYfid=true&lang=en-CA&region=CA

Pour period1, nous pouvons la dĂ©finir Ă  0, ce qui correspond au 1á”‰Êł janvier 1970, puisque les timestamps reprĂ©sentent la durĂ©e Ă©coulĂ©e depuis cette date. Pour period2, nous pouvons utiliser Date.now(), qui retourne le nombre de millisecondes depuis le 1á”‰Êł janvier 1970, garantissant ainsi que nous rĂ©cupĂ©rons les donnĂ©es les plus rĂ©centes disponibles.

main.ts
const symbol = "AAPL";
const period1 = 0;
const period2 = Date.now();
 
const response = await fetch(
  `https://query1.finance.yahoo.com/v8/finance/chart/${symbol}?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=${period1}&period2=${period2}&symbol=${symbol}&userYfid=true&lang=en-CA&region=CA`,
);
 
const data = await response.json();
 
const timestamps = data.chart.result[0].timestamp;
const firstTimestamp = new Date(timestamps[0] * 1000);
const lastTimestamp = new Date(timestamps[timestamps.length - 1] * 1000);
 
const values = data.chart.result[0].indicators.adjclose[0].adjclose;
const firstValue = values[0];
const lastValue = values[values.length - 1];
 
console.log({ firstTimestamp, lastTimestamp, firstValue, lastValue });

Ce code affiche toujours les bonnes valeurs pour le premier et le dernier Ă©lĂ©ment, ce qui signifie que l’API accepte nos paramĂštres plus gĂ©nĂ©raux !

{
  firstTimestamp: 1980-12-12T14:30:00.000Z,
  lastTimestamp: 2025-02-05T14:30:00.000Z,
  firstValue: 0.09883449226617813,
  lastValue: 232.47000122070312
}

En passant, cette technique de collecte de donnĂ©es s’appelle le web scraping. Le web est une source incroyable de donnĂ©es, et nous aurons une leçon complĂšte Ă  ce sujet plus tard.

💡

Comme j’utilise de temps en temps les donnĂ©es de Yahoo Finance dans mes projets, j’ai publiĂ© une fonction qui fait tout cela dans la librairie journalism. Nous aurions pu l’utiliser directement, mais je pense qu’il est trĂšs utile de comprendre comment tout fonctionne avant d’utiliser des fonctions qui ressemblent Ă  de la magie ! 🧙

Mise en cache

Actuellement, chaque fois que nous mettons Ă  jour main.ts et que nous l’enregistrons, le code est relancĂ© et les donnĂ©es sont rĂ©cupĂ©rĂ©es Ă  nouveau.

Nous ne voulons pas surcharger les serveurs de Yahoo avec nos requĂȘtes. Nous devons respecter leur infrastructure. De plus, nous ne voulons pas ĂȘtre mis sur une liste noire et nous faire bloquer l’accĂšs
 đŸ«Ł Alors, mettons les donnĂ©es en cache.

La mise en cache peut signifier différentes choses dans différents contextes, mais ici, elle consiste simplement à écrire les données dans un fichier local et à utiliser ce fichier au lieu de récupérer les données à chaque fois.

En gros, si le fichier local existe, nous devons l’utiliser. Sinon, nous devons rĂ©cupĂ©rer les donnĂ©es.

Pour vérifier si le fichier existe déjà sur notre machine, nous pouvons utiliser la fonction exists de la librairie standard de Deno @std/fs (fs signifie systÚme de fichiers).

ArrĂȘtez votre terminal et installez-la en exĂ©cutant : deno add jsr:@std/fs

Ensuite, redémarrez la surveillance de main.ts en exécutant : deno -A --watch --check main.ts

CrĂ©ez un nouveau dossier appelĂ© data, oĂč nous stockerons les donnĂ©es mises en cache.

Le code ci-dessous gĂšre la mise en cache. Voici ce qui se passe lorsque vous l’exĂ©cutez :

  • Ligne 1 : Nous importons la fonction exists de la librairie standard.
  • Ligne 6 : Nous utilisons le symbol pour crĂ©er un chemin d’accĂšs pour les donnĂ©es mises en cache.
  • Ligne 8 : Nous dĂ©clarons une variable let pour les donnĂ©es.
  • Ligne 9 : Nous vĂ©rifions si un fichier pour cette entreprise existe dĂ©jĂ .
    • Si c’est le cas, nous lisons les donnĂ©es depuis ce fichier et les assignons Ă  la variable data (ligne 11).
  • Si le fichier n’existe pas, les donnĂ©es ne sont pas mises en cache :
    • Nous les rĂ©cupĂ©rons (lignes 14–16), les convertissons en JSON, et les stockons dans la variable data.
    • Ensuite, nous les mettons en cache en Ă©crivant un fichier JSON Ă  l’aide du path de l’entreprise (ligne 18).
  • Lignes 21–22 : À ce moment-lĂ , la variable data contient les informations qui ont Ă©tĂ© soit lues Ă  partir d’un fichier local, soit rĂ©cupĂ©rĂ©es de l’API, nous pouvons donc rĂ©cupĂ©rer les timestamps et les valeurs !
main.ts
import { exists } from "@std/fs";
 
const symbol = "AAPL";
const period1 = 0;
const period2 = Date.now();
const path = `data/${symbol}.json`;
 
let data;
if (await exists(path)) {
  console.log("=> Retrieving data from cache...");
  data = JSON.parse(await Deno.readTextFile(path));
} else {
  console.log("=> Fetching data...");
  const response = await fetch(
    `https://query1.finance.yahoo.com/v8/finance/chart/${symbol}?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=${period1}&period2=${period2}&symbol=${symbol}&userYfid=true&lang=en-CA&region=CA`,
  );
  data = await response.json();
  await Deno.writeTextFile(path, JSON.stringify(data));
}
 
const timestamps = data.chart.result[0].timestamp;
const values = data.chart.result[0].indicators.adjclose[0].adjclose;

La premiĂšre fois que vous exĂ©cutez ce code, vous verrez le message de la ligne 13 s’afficher dans le terminal, et vous remarquerez la crĂ©ation d’un nouveau fichier AAPL.json dans data.

Les donnĂ©es ont Ă©tĂ© rĂ©cupĂ©rĂ©es et Ă©crites dans un fichier pour une utilisation future. Si vous ĂȘtes curieux, vous pouvez jeter un Ɠil Ă  ce qui se trouve dans AAPL.json !

Une capture d'écran montrant les données boursiÚres d'Apple récupérées.

Si vous relancez main.ts en l’enregistrant Ă  nouveau, vous verrez que le message affichĂ© provient dĂ©sormais de la ligne 10. Vous utilisez maintenant les donnĂ©es mises en cache ! Les donnĂ©es ne sont plus rĂ©cupĂ©rĂ©es depuis l’API, elles sont lues depuis le fichier local.

Une capture d'écran montrant les données boursiÚres d'Apple mises en cache.

Vous pouvez conserver des donnĂ©es en cache pour plusieurs entreprises. Par exemple, remplacez AAPL dans la variable symbol par GOOG pour obtenir les prix historiques des actions d’Alphabet (anciennement Google) et exĂ©cutez le code.

Vous verrez qu’un autre fichier, GOOG.json, est créé dans data.

Si vous revenez à AAPL, les données sont toujours là. Plus besoin de les récupérer à nouveau !

Et si vous voulez des prix plus récents, il vous suffit de supprimer les fichiers dans data et de relancer votre code. Facile !

💡

Ici, nous utilisons la mise en cache pour éviter de solliciter trop souvent les serveurs de Yahoo. Mais la mise en cache est aussi trÚs couramment utilisée pour améliorer les performances. Lire un fichier local est beaucoup plus rapide que de récupérer des données sur Internet.

Nettoyage

Les valeurs sont des nombres et ne nécessitent aucun nettoyage. Cependant, les timestamps ne sont pas trÚs pratiques à utiliser. Il serait préférable de les convertir en dates.

Avant de faire cela, nettoyons notre code en encapsulant tout ce que nous avons fait jusqu’à prĂ©sent dans une fonction getData. Cela nous aidera Ă  garder main.ts organisĂ© et rendra notre code plus facile Ă  comprendre et Ă  dĂ©boguer.

CrĂ©ez un nouveau dossier appelĂ© helpers, et Ă  l’intĂ©rieur, crĂ©ez un nouveau fichier nommĂ© getData.ts avec le code ci-dessous.

Comme nous utilisons await dans cette fonction, nous devons la déclarer comme une fonction async.

Nous garderons symbol dans main.ts, donc ici, il est passé en paramÚtre de la fonction.

La fonction retourne les timestamps et les values sous forme d’un objet.

getData.ts
import { exists } from "@std/fs";
 
export default async function getData(symbol: string) {
  const period1 = 0;
  const period2 = Date.now();
  const path = `data/${symbol}.json`;
 
  let data;
  if (await exists(path)) {
    console.log("=> Retrieving data from cache...");
    data = JSON.parse(await Deno.readTextFile(path));
  } else {
    console.log("=> Fetching data...");
    const response = await fetch(
      `https://query1.finance.yahoo.com/v8/finance/chart/${symbol}?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=${period1}&period2=${period2}&symbol=${symbol}&userYfid=true&lang=en-CA&region=CA`,
    );
    data = await response.json();
    await Deno.writeTextFile(path, JSON.stringify(data));
  }
 
  const timestamps = data.chart.result[0].timestamp;
  const values = data.chart.result[0].indicators.adjclose[0].adjclose;
 
  return { timestamps, values };
}

Dans main.ts, nous pouvons maintenant importer et utiliser getData avec notre symbol. Comme il s’agit d’une fonction async, nous devons nous assurer de l’appeler avec await.

main.ts
import getData from "./helpers/getData.ts";
 
const symbol = "AAPL";
 
const { timestamps, values } = await getData(symbol);
 
console.log(timestamps, values);
💡

Vous vous demandez peut-ĂȘtre ce qui se passe avec const { timestamps, values }. Lorsque vous avez un objet, vous pouvez le dĂ©structurer en extrayant ses clĂ©s et en les plaçant directement dans des variables portant le mĂȘme nom. Ici, comme getData retourne un objet avec les clĂ©s timestamps et values, nous pouvons le dĂ©structurer pour crĂ©er directement les variables timestamps et values contenant les donnĂ©es correspondantes.

Voici à quoi tout devrait ressembler maintenant. Vous pouvez cliquer sur l’image pour l’agrandir.

Une capture d'écran montrant main.ts et getData.ts.

Maintenant, pour nettoyer nos timestamps, créons une autre fonction, cleanTimestamps, dans helpers.

Cette fonction attend un paramĂštre timestamps, qui doit ĂȘtre une liste de nombres, comme indiquĂ© par le type number[].

Ces timestamps sont en secondes, mais ils devraient ĂȘtre en millisecondes. Nous les multiplions donc par 1000 avant de crĂ©er un new Date. Nous utilisons la mĂ©thode .map() sur la liste pour convertir facilement tous les Ă©lĂ©ments.

cleanTimestamps.ts
export default function cleanTimestamps(
  timestamps: number[],
) {
  console.log("=> Cleaning timestamps...");
 
  const cleanedTimestamps = timestamps.map((timestamp) =>
    new Date(timestamp * 1000)
  );
 
  return cleanedTimestamps;
}

Nous pouvons importer cette fonction dans getData.ts et lui passer les timestamps bruts.

getData.ts
import { exists } from "@std/fs";
import cleanTimestamps from "./cleanTimestamps.ts";
 
export default async function getData(symbol: string) {
  const period1 = 0;
  const period2 = Date.now();
  const path = `data/${symbol}.json`;
 
  let data;
  if (await exists(path)) {
    console.log("=> Retrieving data from cache...");
    data = JSON.parse(await Deno.readTextFile(path));
  } else {
    console.log("=> Fetching data...");
    const response = await fetch(
      `https://query1.finance.yahoo.com/v8/finance/chart/${symbol}?events=capitalGain%7Cdiv%7Csplit&formatted=true&includeAdjustedClose=true&interval=1d&period1=${period1}&period2=${period2}&symbol=${symbol}&userYfid=true&lang=en-CA&region=CA`,
    );
    data = await response.json();
    await Deno.writeTextFile(path, JSON.stringify(data));
  }
 
  const timestamps = cleanTimestamps(data.chart.result[0].timestamp);
  const values = data.chart.result[0].indicators.adjclose[0].adjclose;
 
  return { timestamps, values };
}

Vous pouvez maintenant voir les timestamps convertis en dates. Le premier est le 12 dĂ©cembre 1980, date Ă  laquelle Apple a commencĂ© Ă  ĂȘtre Ă©changĂ© en bourse.

Une capture d'écran montrant les timestamps convertis en dates.

Restructuration des données

Actuellement, nous avons deux listes distinctes. Il serait plus pratique d’avoir une seule liste contenant des objets. De plus, nous devons calculer la variation en pourcentage entre chaque jour pour dĂ©terminer nos rendements d’investissement.

Créons une nouvelle fonction appelée restructureData.ts dans le dossier helpers. Cette fonction attend deux paramÚtres : timestamps sous forme de liste de dates et values sous forme de liste de nombres.

Nous savons que timestamps et values ont le mĂȘme nombre d’élĂ©ments. Le premier timestamp correspond au premier prix ajustĂ©, le deuxiĂšme timestamp au deuxiĂšme prix, et ainsi de suite.

Nous pouvons donc utiliser une boucle pour rĂ©cupĂ©rer les valeurs aux mĂȘmes index dans les deux listes, crĂ©er des objets, et les ajouter Ă  la liste dailyStockData. Enfin, nous retournons cette liste.

restructureData.ts
export default function restructureData(
    timestamps: Date[],
    values: number[]
  ) {
  console.log("=> Restructuring data...");
 
  const dailyStockData = [];
  for (let i = 0; i < timestamps.length; i++) {
    const date = timestamps[i];
    const value = values[i];
 
    dailyStockData.push({
      date,
      value,
    });
  }
 
  return dailyStockData;
}

Nous pouvons maintenant importer et appeler cette fonction dans main.ts. Comme dailyStockData est une liste d’objets, nous pouvons l’afficher avec console.table.

Pour Ă©viter d’afficher des milliers de lignes dans le terminal, nous utilisons la mĂ©thode .slice() sur la liste pour ne montrer que les 10 premiers Ă©lĂ©ments.

main.ts
import getData from "./helpers/getData.ts";
import restructureData from "./helpers/restructureData.ts";
 
const symbol = "AAPL";
 
const { timestamps, values } = await getData(symbol);
 
const dailyStockData = restructureData(timestamps, values);
 
console.table(dailyStockData.slice(0, 10));

Une capture d'écran montrant les dates et les valeurs affichées sous forme de tableau.

Ça commence à bien prendre forme ! Maintenant, calculons la variation quotidienne en pourcentage.

Pour ce faire, nous avons besoin de la valeur du jour précédent. Comme nous utilisons des index, nous pouvons simplement la récupérer en utilisant i - 1, puis calculer la variation.

restructureData.ts
export default function restructureData(timestamps: Date[], values: number[]) {
  console.log("=> Restructuring data...");
 
  const dailyStockData = [];
  for (let i = 0; i < timestamps.length; i++) {
    const date = timestamps[i];
    const value = values[i];
    const previousValue = values[i - 1];
    const percChange = (value - previousValue) / previousValue;
 
    dailyStockData.push({
      date,
      value,
      previousValue,
      percChange,
    });
  }
 
  return dailyStockData;
}

Oh ! Mais quelque chose ne va pas
 La premiĂšre valeur de previousValue est undefined, et le premier percChange est NaN ! đŸ˜±

Une capture d'écran montrant les dates et valeurs affichées sous forme de tableau. La premiÚre ligne contient des valeurs undefined et NaN.

En fait, c’est logique. Le premier jour est
 le premier jour ! Il n’y a pas de valeur prĂ©cĂ©dente. Lorsque la boucle commence, i est 0, donc lorsque l’ordinateur cherche i - 1, il essaie d’accĂ©der Ă  l’index -1, qui n’existe pas !

Corrigeons cela en remplaçant la premiÚre valeur de percChange par 0.

restructureData.ts
export default function restructureData(timestamps: Date[], values: number[]) {
  console.log("=> Restructuring data...");
 
  const dailyStockData = [];
  for (let i = 0; i < timestamps.length; i++) {
    const date = timestamps[i];
    const value = values[i];
    const previousValue = values[i - 1];
    const percChange = (value - previousValue) / previousValue;
 
    dailyStockData.push({
      date,
      value,
      previousValue,
      percChange,
    });
  }
 
  dailyStockData[0].percChange = 0;
 
  return dailyStockData;
}

VoilĂ  qui est mieux !

Une capture d'écran montrant les dates et valeurs affichées sous forme de tableau. La valeur NaN a disparu.

Calcul du rendement

Nous avons maintenant tout ce qu’il nous faut pour calculer les rendements !

Créons une autre fonction, getFinalAmount, dans helpers. Cette fonction nécessitera trois paramÚtres :

  • amount : le montant investi, qui doit ĂȘtre un number
  • startDate : la Date de l’investissement
  • dailyStockData : une liste d’objets contenant :
    • Une clĂ© percChange avec une valeur de type number
    • Une clĂ© date avec une valeur de type Date

Notez qu’il y a d’autres clĂ©s dans les objets de dailyStockData, mais nous n’en avons pas besoin ici, donc inutile de les spĂ©cifier.

D’abord, nous devons filtrer dailyStockData pour ne garder que les donnĂ©es Ă  partir de la date de l’investissement. Nous utilisons la mĂ©thode .filter() sur la liste pour cela, aux lignes 8–10.

Ensuite, nous crĂ©ons une variable let appelĂ©e adjustedAmount (ligne 12), qui suivra l’évolution de la valeur de notre investissement au fil du temps. Au dĂ©part, elle est Ă©gale au amount investi.

Dans la boucle, nous calculons les gains ou pertes journaliÚres et ajustons adjustedAmount en conséquence (ligne 15).

Enfin, nous retournons adjustedAmount !

getFinalAmount.ts
export default function getFinalAmount(
  amount: number,
  startDate: Date,
  dailyStockData: { percChange: number; date: Date }[],
) {
  console.log("=> Calculating final amount...");
 
  const filteredDailyStockData = dailyStockData.filter((dailyData) =>
    dailyData.date >= startDate
  );
 
  let adjustedAmount = amount;
 
  for (const dailyData of filteredDailyStockData) {
    adjustedAmount += adjustedAmount * dailyData.percChange;
  }
 
  return adjustedAmount;
}

Dans main.ts, nous pouvons crĂ©er les variables amount et startDate pour stocker le montant initial investi et la date de l’investissement, puis les passer Ă  notre nouvelle fonction.

Nous stockons le résultat de la fonction getFinalAmount dans une nouvelle variable appelée finalAmount.

main.ts
import getData from "./helpers/getData.ts";
import getFinalAmount from "./helpers/getFinalAmount.ts";
import restructureData from "./helpers/restructureData.ts";
 
const symbol = "AAPL";
const amount = 1000;
const startDate = new Date("2020-01-01");
 
const { timestamps, values } = await getData(symbol);
 
const dailyStockData = restructureData(timestamps, values);
 
const finalAmount = getFinalAmount(amount, startDate, dailyStockData);
 
console.log(finalAmount);

Et voici le rĂ©sultat ! Ça fonctionne ! Vous auriez aujourd’hui 3 266 $ (au 5 fĂ©vrier 2025) si vous aviez investi 1 000 $ dans Apple en janvier 2020.

Une capture d'écran montrant le montant final investi.

Les résultats

Créons une fonction pour mieux afficher les résultats.

Dans helpers, créez la fonction logResult.

Pour startDate, nous pouvons la convertir en texte en utilisant .toISOString(), qui retourne la date d’investissement sous ce format : "2020-01-01T00:00:00.000Z". Pour ne conserver que la date, nous sĂ©parons le texte avec "T". La mĂ©thode .split() retourne une liste : ["2020-01-01", "00:00:00.000Z"]. Comme nous avons seulement besoin de la date, nous utilisons l’index [0].

logResult.ts
export default function logResult(
  amount: number,
  symbol: string,
  startDate: Date,
  finalAmount: number,
) {
  console.log(
    `\nIf you had invested $${amount} in ${symbol} on ${
      startDate.toISOString().split("T")[0]
    }, you would have $${Math.round(finalAmount)} today.\n`,
  );
}

Nous pouvons maintenant utiliser cette fonction dans main.ts.

main.ts
import getData from "./helpers/getData.ts";
import getFinalAmount from "./helpers/getFinalAmount.ts";
import restructureData from "./helpers/restructureData.ts";
import logResult from "./helpers/logResult.ts";
 
const symbol = "AAPL";
const amount = 1000;
const startDate = new Date("2020-01-01");
 
const { timestamps, values } = await getData(symbol);
 
const dailyStockData = restructureData(timestamps, values);
 
const finalAmount = getFinalAmount(amount, startDate, dailyStockData);
 
logResult(amount, symbol, startDate, finalAmount);

Magnifique, n’est-ce pas ? 😍

Une capture d'écran de VS Code montrant un script simulant le marché boursier.

Attendez une minute


Tout fonctionne bien avec une startDate en janvier 2020
 Mais que se passerait-il si la date de dĂ©part Ă©tait en 1970, avant qu’Apple n’entre en bourse ?

C’est impossible ! Notre code devrait avertir l’utilisateur.

Mettez à jour startDate avec "1950-01-01" pour tester
 Il retourne un montant, alors qu’il devrait produire une erreur.

Nous pouvons mettre à jour main.ts pour comparer startDate avec la premiÚre date dans timestamps. Si startDate est antérieure au premier timestamp, nous créons erreur avec un message explicite.

main.ts
import getData from "./helpers/getData.ts";
import getFinalAmount from "./helpers/getFinalAmount.ts";
import restructureData from "./helpers/restructureData.ts";
import logResult from "./helpers/logResult.ts";
 
const symbol = "AAPL";
const amount = 1000;
const startDate = new Date("1950-01-01");
 
const { timestamps, values } = await getData(symbol);
 
if (startDate < timestamps[0]) {
  throw new Error(
    "The company was not public at that time. Please choose a later date.",
  );
}
 
const dailyStockData = restructureData(timestamps, values);
 
const finalAmount = getFinalAmount(amount, startDate, dailyStockData);
 
logResult(amount, symbol, startDate, finalAmount);
💡

Les erreurs sont trĂšs utiles pour arrĂȘter l’exĂ©cution de votre script et fournir un message Ă  l’utilisateur (ou Ă  vous-mĂȘme). Essayez toujours de rĂ©diger des messages clairs qui vous aideront Ă  dĂ©boguer facilement ou qui guideront l’utilisateur.

C’est bien mieux. Nous n’obtiendrons plus de rĂ©sultats impossibles ! Vous pouvez maintenant tester diffĂ©rentes valeurs pour startDate en toute sĂ©curitĂ©.

Une capture d'écran montrant une erreur.

Conclusion

FĂ©licitations ! Vous avez appris beaucoup de choses dans ce projet. Nous avons explorĂ© le site de Yahoo pour trouver leur API et rĂ©cupĂ©rer des donnĂ©es. Ensuite, nous avons traitĂ© ces donnĂ©es pour calculer des rendements en fonction d’un montant investi Ă  une date donnĂ©e. C’est un Ă©norme !

Maintenant, vous pouvez expĂ©rimenter davantage avec ce script. Recherchez d’autres entreprises sur le site de Yahoo et copiez-collez leurs symboles. Testez diffĂ©rents montants et dates d’investissement !

Ou modifiez le code pour ajouter d’autres fonctionnalitĂ©s. Voici quelques idĂ©es :

  • Comparer les rendements de plusieurs entreprises
  • Investir dans plusieurs entreprises en mĂȘme temps
  • Acheter et vendre des actions Ă  diffĂ©rentes dates

Amusez-vous bien ! 💃đŸ•ș

Vous avez aimé ? Vous voulez ĂȘtre prĂ©venu quand de nouvelles leçons sont publiĂ©es ? Abonnez-vous Ă  l'infolettre ✉ et donnez une ⭐ au cours sur GitHub pour me garder motivé ! Contactez-moi si vous avez des questions.