diff --git a/JournalDeBord.md b/JournalDeBord.md index b1ebd5310b2eda25cc98d0f3ca6eb2f34b13461f..b19d63a01e70ee48f93e0e96a0477fed0e3c4247 100644 --- a/JournalDeBord.md +++ b/JournalDeBord.md @@ -4,5 +4,8 @@ | 28.10.2024-04.11.2024 | Lister les fonctionnalités nécessaires au projet + Lire la documentation de Mr Souzaluz | Lecture des parties "Lexique", "Liste des acronymes", "1.2 TRADING AUTOMATISÉ", "CHAPITRE 2 : LES STRATÉGIES" et "CHAPITRE 3 : MÉTHODOLOGIE" + Début du listage des fonctionnalités | | | | 04.11.2024-11.11.2024 | Rechercher une API qui permet de récupérer les données de chaque seconde depuis 4-5 ans + Justifier le choix de la technologie | Listage des différentes API avec leurs avantages et désavantages + Justification de la technologie + Trouvé une librairie pour les graphiques | La plupart des API ont des limites de données par requêtes et de fréquences des données (1 min) | | | 11.11.2024-18.11.2024 | Récupérer les données (200000) et tester la librairie pour faire des graphes | J'ai créer un projet spring boot qui permet de récupérer les données en faisant plusieurs appels à l'api Binance | Problème avec la librairie des graphiques : le graphique s'affiche mais pas les candles | | +| 18.11.2024-26.11.2024 | Afficher les données avec la librairie + Voir le temps que prends le programme pour récupérer 1 an de données + Mise en cache des données si récupération trop longue | Affichage des données avec la librairie. Il faut environ 1 minute pour récupérer 1 an de données. L'utilisateur peut choisir les dates qu'il veut. | Affichage des données pour plus de 10 jours : problème de chevauchement des timestamp de la 1ère requête et de la 2ème | | +| 26.11.2024-02.12.2024 | Commencer à implémenter le simulateur et commencer l'introduction | Début d'une introduction et implémentation d'un ticker (déclencher un évènement toutes les $x$ secondes) | | | + <!-- | | | | | | --> \ No newline at end of file diff --git a/documentation.md b/documentation.md new file mode 100644 index 0000000000000000000000000000000000000000..593485af2c3b3db65a860ceac7385c09abec5060 --- /dev/null +++ b/documentation.md @@ -0,0 +1,15 @@ +# Projet de semestre +## Introduction +Le trading de cryptomonnaies se démocratise de plus en plus notamment grâce à la multiplication des plateformes de trading comme Binance, Coinbase ou encore Crypto\.com. Ces plateformes permettent d'acheter et de vendre des cryptomonnaies assez facilement notamment grâce à leurs applictions sur smartphone. De plus, beaucoup de personnes remarquent la grande volatilité des prix des cryptomonnaies et font du profit grâce à ces variations de prix. Cependant, bien que certains fassent effectivement des bénéfices, d'autres font de grosses pertes. Et c'est pourquoi les simulateurs de trading de cryptomonnaies sont utiles. Ces simulateurs permettent de s'entrainer avec les stratégies existantes sans pour autant avoir à réellement investir dans une cryptomonnaie et risquer de tout perdre. + +Les simulateurs de trading de cryptomonnaies sont très utiles pour tester et améliorer les différents outils et stratégies de trading de cryptomonnaies. Ce projet a pour but de refactoriser un simulateur de trading de cryptomonnaies existant, développé en Python, dans un langage orienté objet. + +Ce projet est la suite de projets déjà réalisés par Mr. Pighini, Mr. Toniutt et Mr. SouzaLuz. Il est réalisé dans le cadre des projets de semestre et de Bachelor à HEPIA et a pour objectif d'améliorer le simulateur de trading de cryptomonnaies existant, notamment grâce à des notions de développement logicielle comme la programmation orientée objet. + +## Cahier des charges + +- Choisir une API permettant de récupérer les données de cryptomonnaies depuis au moins 5ans. +- Afficher un graphique des bougies d'une cryptomonnaie sur une certaine période +- Créer le simulateur permettant de simuler l'évolution du prix d'une cryptomonnaie toutes les $x$ secondes +- Implémenter les différents indicateurs +- Créer et gérer un système de cache pour les données (moins important pour le moment) diff --git a/testApi/pom.xml b/testApi/pom.xml index b19bd1f317e0071c7059f5dcfef67ba667e4fcea..72738ede180516384c6f9c590404e3e193c29ff5 100644 --- a/testApi/pom.xml +++ b/testApi/pom.xml @@ -60,10 +60,6 @@ <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-webflux</artifactId> - </dependency> </dependencies> <dependencyManagement> <dependencies> diff --git a/testApi/src/main/java/com/example/testapi/controller/ApiController.java b/testApi/src/main/java/com/example/testapi/controller/ApiController.java index dab8318151d7e1659ab075c2630f70a1b1df4576..b3cc2fa310658d46c8cdb589a16b37b74af0dd27 100644 --- a/testApi/src/main/java/com/example/testapi/controller/ApiController.java +++ b/testApi/src/main/java/com/example/testapi/controller/ApiController.java @@ -1,11 +1,12 @@ package com.example.testapi.controller; +import com.example.testapi.model.DateRange; import com.example.testapi.service.ApiService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.Collections; +import java.util.Map; @RestController @RequestMapping("/api") @@ -13,6 +14,8 @@ public class ApiController { private final ApiService apiService; + private DateRange dateRange; + @Autowired public ApiController(ApiService apiService) { this.apiService = apiService; @@ -22,8 +25,16 @@ public class ApiController { public String getGraphPage() throws InterruptedException, com.fasterxml.jackson.core.JsonProcessingException { System.out.println("btc to usdt graphique !"); // System.out.println(apiService.getCandlesJson()); - return apiService.getCandlesJson(); -// return "ok"; -// return "ca marche"; + return apiService.getCandlesJson(dateRange.getTimestampStartDate(), dateRange.getTimestampEndDate()); + } + + @PostMapping("/sendDates") + public Map<String, Object> sendDates(@RequestBody DateRange json) throws InterruptedException, com.fasterxml.jackson.core.JsonProcessingException { + System.out.println("Dates received: " + json.getStartDate() + " - " + json.getEndDate()); + this.dateRange = new DateRange(json.getStartDate(), json.getEndDate()); + return Collections.emptyMap(); } + + } + diff --git a/testApi/src/main/java/com/example/testapi/controller/PageApiController.java b/testApi/src/main/java/com/example/testapi/controller/PageApiController.java index a329a88ba249e35f9a2a00de4ba768e510c9abff..06674827c4a2965f49797128829bf522545ca5f3 100644 --- a/testApi/src/main/java/com/example/testapi/controller/PageApiController.java +++ b/testApi/src/main/java/com/example/testapi/controller/PageApiController.java @@ -19,4 +19,10 @@ public class PageApiController { System.out.println("home page requested !"); return "index.html"; } + + @GetMapping("/candlesGraph") + public String getGraphPage() { + System.out.println("graph page requested !"); + return "affichageDataGraph.html"; + } } diff --git a/testApi/src/main/java/com/example/testapi/model/DateRange.java b/testApi/src/main/java/com/example/testapi/model/DateRange.java new file mode 100644 index 0000000000000000000000000000000000000000..b7c9181546ec71471f6dbdecafc805ef0d562d66 --- /dev/null +++ b/testApi/src/main/java/com/example/testapi/model/DateRange.java @@ -0,0 +1,43 @@ +package com.example.testapi.model; + +import java.time.LocalDate; +import java.time.ZoneId; + +public class DateRange { + private String startDate; + private String endDate; + + public DateRange(String startDate, String endDate) { + this.startDate = startDate; + this.endDate = endDate; + } + + public String getStartDate() { + return startDate; + } + + public void setStartDate(String startDate) { + this.startDate = startDate; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public long getTimestampStartDate() { + return this.dateToTimestamp(this.startDate); + } + + public long getTimestampEndDate() { + return this.dateToTimestamp(this.endDate); + } + + public long dateToTimestamp(String date) { + LocalDate localDate = LocalDate.parse(date); + return localDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } +} diff --git a/testApi/src/main/java/com/example/testapi/service/ApiService.java b/testApi/src/main/java/com/example/testapi/service/ApiService.java index 89c52224389638148bdc4c07bb5d82fca0b3f817..7d9b6692500eee1bd3c2587801c8ea9dbdc2529c 100644 --- a/testApi/src/main/java/com/example/testapi/service/ApiService.java +++ b/testApi/src/main/java/com/example/testapi/service/ApiService.java @@ -25,18 +25,28 @@ public class ApiService { private String candlesJson; - public void getCandles() throws InterruptedException, com.fasterxml.jackson.core.JsonProcessingException { - String baseUrl = "https://api4.binance.com/api/v3/klines"; - long startTime = 1572331210000L; - long endTime = startTime + (15 * 1000 * 60 * 1000); + private static final String baseUrl = "https://api4.binance.com/api/v3/klines"; + + private static final long TIME_10_JOURS = 10 * 24 * 60 * 60 * 1000; + + public void getCandles(long startTime, long endTime) throws InterruptedException, com.fasterxml.jackson.core.JsonProcessingException { + if (endTime < startTime) { + System.out.println("La date de fin ne peut pas être avant la date de début"); + return; + } List<Candle> candles = new ArrayList<Candle>(); - for (int i = 0; i < 200; i++) + int nbreq = 0; + while (startTime < endTime) { +// System.out.println("Nb req API binance : " + nbreq); +// nbreq++; + long currEndTime = Math.min(startTime + TIME_10_JOURS + 1, endTime); String url = UriComponentsBuilder.fromHttpUrl(baseUrl) .queryParam("symbol", "BTCUSDT") .queryParam("interval", "15m") .queryParam("startTime", startTime) - .queryParam("limit", 1000) // limite à 1 pour tester plus vite mais changer par 1000 après + .queryParam("endTime", currEndTime) + .queryParam("limit", 1000) .toUriString(); String response = restTemplate.getForObject(url, String.class); @@ -56,20 +66,25 @@ public class ApiService { // System.out.println("Response n°" + i + ": " + response); // Thread.sleep(10); // avancer de 1000 * 15 minutes * 60 pour convertir en secondes et * 1000 pour millisecondes - startTime += (15 * 1000 * 60 * 1000); +// System.out.println(startTime + " " + currEndTime); + startTime += TIME_10_JOURS + 1; } + this.candles = candles; } - public String getCandlesJson() throws InterruptedException, com.fasterxml.jackson.core.JsonProcessingException { + public String getCandlesJson(long startDate, long endDate) throws InterruptedException, com.fasterxml.jackson.core.JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); - if (this.candles == null || this.candles.isEmpty()) { - this.getCandles(); - } - if (this.candlesJson == null) { - this.candlesJson = objectMapper.writeValueAsString(this.candles); - } +// if (this.candles == null || this.candles.isEmpty()) { +// this.getCandles(startDate, endDate); +// } +// if (this.candlesJson == null) { +// this.candlesJson = objectMapper.writeValueAsString(this.candles); +// } + this.getCandles(startDate, endDate); + this.candlesJson = objectMapper.writeValueAsString(this.candles); return this.candlesJson; + } // private final WebClient webClient; diff --git a/testApi/src/main/java/com/example/testapi/service/SimulateurService.java b/testApi/src/main/java/com/example/testapi/service/SimulateurService.java new file mode 100644 index 0000000000000000000000000000000000000000..fe9fe6624be1fd2488eb997fdfee147ee7f5140d --- /dev/null +++ b/testApi/src/main/java/com/example/testapi/service/SimulateurService.java @@ -0,0 +1,17 @@ +package com.example.testapi.service; + +public class SimulateurService { + private Ticker ticker; + private int periodApproximationSec; + + public SimulateurService(int periodApproximationSec) { + this.periodApproximationSec = periodApproximationSec; +// this.ticker = new Ticker(this.); + } + + public void pricesApproximations() { + ticker.start(this.periodApproximationSec); + + ticker.stop(); + } +} diff --git a/testApi/src/main/java/com/example/testapi/service/Ticker.java b/testApi/src/main/java/com/example/testapi/service/Ticker.java new file mode 100644 index 0000000000000000000000000000000000000000..0ec077fb17eca75147635fa193cbbcbc746e2ac2 --- /dev/null +++ b/testApi/src/main/java/com/example/testapi/service/Ticker.java @@ -0,0 +1,24 @@ +package com.example.testapi.service; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class Ticker { + private final Runnable approximationTask; + private final ScheduledExecutorService executorService; + + public Ticker(Runnable approximationTask) { + this.approximationTask = approximationTask; + this.executorService = Executors.newScheduledThreadPool(1); + } + + public void start(int periodInSec) { + executorService.scheduleAtFixedRate(this.approximationTask, 0, periodInSec, TimeUnit.SECONDS); + } + + public void stop() { + executorService.shutdown(); + } + +} diff --git a/testApi/src/main/resources/static/affichageDataGraph.html b/testApi/src/main/resources/static/affichageDataGraph.html new file mode 100644 index 0000000000000000000000000000000000000000..5f028bc5dc2f4e4a9c92689b07e6130775cf8c65 --- /dev/null +++ b/testApi/src/main/resources/static/affichageDataGraph.html @@ -0,0 +1,17 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Graphe des données</title> + <link rel="stylesheet" href="/css/style.css"> +</head> +<body> + <h1>Test</h1> + <div id="container"></div> +</body> +<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.development.js"></script> +<!-- <script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script> --> +<script src="/js/graph.js"></script> +<script src="/js/api.js"></script> +</html> \ No newline at end of file diff --git a/testApi/src/main/resources/static/index.html b/testApi/src/main/resources/static/index.html index 5c0f2ed257bfaf2bc52148b4c58c3cd9e40ae458..df8c1f42ab7db43da22af788e971717eccc4b23d 100644 --- a/testApi/src/main/resources/static/index.html +++ b/testApi/src/main/resources/static/index.html @@ -7,9 +7,15 @@ <link rel="stylesheet" href="/css/style.css"> </head> <body> - <h1>Test</h1> - <div id="container"></div></div> + <h1>Sélectionner les dates de débuts et de fin</h1> + <form> + <label for="dateDebut">Date de début :</label> + <input type="date" id="dateDebut" name="dateDebut"><br> + <label for="dateFin">Date de fin :</label> + <input type="date" id="dateFin" name="dateFin"><br> + <input type="button" value="Afficher le graphique" id="formBtn"> + </form> </body> -<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script> -<script type="module" src="/js/script.js"></script> +<script src="./js/index.script.js"></script> +<script src="./js/api.js"></script> </html> \ No newline at end of file diff --git a/testApi/src/main/resources/static/js/api.js b/testApi/src/main/resources/static/js/api.js new file mode 100644 index 0000000000000000000000000000000000000000..cb968902dec2f94cedb51e576b008bf51f867b21 --- /dev/null +++ b/testApi/src/main/resources/static/js/api.js @@ -0,0 +1,44 @@ + +const BASE_URL = "http://localhost:8080/"; +const API_URL = BASE_URL + "api"; + +function callApi(route, method = "GET", data = null) +{ + let options = { + method: method, + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + } + }; + if (method === "POST" && data) { + options.body = JSON.stringify(data); + } + console.log("fetching data ..."); + fetch(API_URL + route, options) + .then(result => result.json()) + .then(data => { + switch (route) { + case "/btcusdt": + // console.table(data); + // data.forEach((item, index) => { + // console.log(`Élément ${index}:`, item); + // }); + console.log("ok"); + displayChart(data); + break; + case "/sendDates": + console.log("Data sent successfully"); + window.location.href = BASE_URL + "candlesGraph"; + break; + default: + console.log("Unknown route: " + API_URL + route); + break; + } + }) + .catch(error => { + console.error("Une erreur est survenue :", error); + }); +} + + diff --git a/testApi/src/main/resources/static/js/graph.js b/testApi/src/main/resources/static/js/graph.js new file mode 100644 index 0000000000000000000000000000000000000000..c0b6b5c2289c51efd96cc5fea8d3386886094812 --- /dev/null +++ b/testApi/src/main/resources/static/js/graph.js @@ -0,0 +1,21 @@ +// import { createChart } from './node_modules/lightweight-charts/dist/lightweight-charts.esm.production.js'; + +window.onload = function() { + callApi("/btcusdt"); +}; + +function displayChart(data) +{ + const chartOptions = { layout: { textColor: 'black', background: { type: 'solid', color: 'white' } } }; + const chart = LightweightCharts.createChart(document.getElementById('container'), chartOptions); + + const candlestickSeries = chart.addCandlestickSeries({ + upColor: '#26a69a', downColor: '#ef5350', borderVisible: false, + wickUpColor: '#26a69a', wickDownColor: '#ef5350', + }); + console.log(data); + candlestickSeries.setData(data); + + chart.timeScale().fitContent(); +} + diff --git a/testApi/src/main/resources/static/js/index.script.js b/testApi/src/main/resources/static/js/index.script.js new file mode 100644 index 0000000000000000000000000000000000000000..d51164b66d53a435d9448fb9c5a1fd016e0e6ac8 --- /dev/null +++ b/testApi/src/main/resources/static/js/index.script.js @@ -0,0 +1,25 @@ +window.onload = function() { + const dateMax = new Date().toISOString().split("T")[0]; + console.log("date max : " + dateMax); + document.getElementById("dateDebut").max = dateMax; + document.getElementById("dateFin").max = dateMax; + const btn = document.getElementById("formBtn"); + btn.addEventListener("click", sendDates); +}; + +function sendDates() { + const dateDebut = document.getElementById("dateDebut").value; + const dateFin = document.getElementById("dateFin").value; + + if (!dateDebut || !dateFin) { + alert("Veuillez remplir tous les champs."); + return; + } + + const data = { + startDate: dateDebut, + endDate: dateFin + }; + callApi("/sendDates", "POST", data); + +} \ No newline at end of file diff --git a/testApi/src/main/resources/static/js/script.js b/testApi/src/main/resources/static/js/script.js deleted file mode 100644 index 1a295da447b9747a9568ed8dd4f5bdd9aad2f8b5..0000000000000000000000000000000000000000 --- a/testApi/src/main/resources/static/js/script.js +++ /dev/null @@ -1,55 +0,0 @@ -// import { createChart } from './node_modules/lightweight-charts/dist/lightweight-charts.esm.production.js'; - -const API_URL = "http://localhost:8080/api"; - -function callApi(route) -{ - console.log("okdscas"); - fetch(API_URL + route) - .then(result => result.json()) - .then(data => { - switch (route) { - case "/btcusdt": - // console.table(data); - // data.forEach((item, index) => { - // console.log(`Élément ${index}:`, item); - // }); - displayChart(data); - break; - default: - console.log("Unknown route: " + API_URL + tmp); - break; - } - }) - .catch(error => { - console.error("Une erreur est survenue :", error); - }); -} - -function displayChart(data) -{ - const chartOptions = { layout: { textColor: 'black', background: { type: 'solid', color: 'white' } } }; - const chart = LightweightCharts.createChart(document.getElementById('container'), chartOptions); - - const candlestickSeries = chart.addCandlestickSeries({ - upColor: '#26a69a', downColor: '#ef5350', borderVisible: false, - wickUpColor: '#26a69a', wickDownColor: '#ef5350', - }); - console.log(data); - candlestickSeries.setData(data); - - chart.timeScale().fitContent(); -} - -function transformData(apiData, mapping) { - return apiData.map(item => ({ - time: item[mapping.time], - open: item[mapping.open], - high: item[mapping.high], - low: item[mapping.low], - close: item[mapping.close] - })); -} - -callApi("/btcusdt"); -