├── .gitattributes ├── .gitignore ├── private ├── README.md └── data │ └── README.md ├── public ├── sounds │ ├── loading.mp3 │ └── completion.mp3 ├── img │ ├── creator-jofpin.png │ ├── favicon-active.png │ ├── favicon-inactive.png │ └── favicon-processing.png ├── js │ └── main.js └── index.html ├── images ├── gh-cover-synthBTC.png └── gh-screenshot-synthBTC.png ├── .editorconfig ├── config.json ├── package.json ├── LICENSE ├── modules ├── priceFetcher.js ├── csvHandler.js ├── apiCore.js ├── serverCore.js ├── monteCarloEngine.js └── utils.js ├── research-script ├── README.md └── bitcoinAnalysis.js ├── README.md └── synthBTC.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules 4 | npm-debug.log 5 | package-lock.json -------------------------------------------------------------------------------- /private/README.md: -------------------------------------------------------------------------------- 1 | # Private directory 2 | 3 | All simulation processing data is stored here. -------------------------------------------------------------------------------- /public/sounds/loading.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jofpin/synthBTC/HEAD/public/sounds/loading.mp3 -------------------------------------------------------------------------------- /images/gh-cover-synthBTC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jofpin/synthBTC/HEAD/images/gh-cover-synthBTC.png -------------------------------------------------------------------------------- /public/sounds/completion.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jofpin/synthBTC/HEAD/public/sounds/completion.mp3 -------------------------------------------------------------------------------- /public/img/creator-jofpin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jofpin/synthBTC/HEAD/public/img/creator-jofpin.png -------------------------------------------------------------------------------- /public/img/favicon-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jofpin/synthBTC/HEAD/public/img/favicon-active.png -------------------------------------------------------------------------------- /public/img/favicon-inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jofpin/synthBTC/HEAD/public/img/favicon-inactive.png -------------------------------------------------------------------------------- /images/gh-screenshot-synthBTC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jofpin/synthBTC/HEAD/images/gh-screenshot-synthBTC.png -------------------------------------------------------------------------------- /public/img/favicon-processing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jofpin/synthBTC/HEAD/public/img/favicon-processing.png -------------------------------------------------------------------------------- /private/data/README.md: -------------------------------------------------------------------------------- 1 | # Private directory of simulation data 2 | All the simulations are stored here, listed, if you are a researcher or analyst you could use this data for different types of processes. -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = false -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "simulationConfig": { 3 | "turbitPower": 100, 4 | "totalSimulations": 1000000, 5 | "volatilityPercentage": 20, 6 | "simulationDays": 365, 7 | "simulationInterval": 1 8 | }, 9 | "webConfig": { 10 | "serverPort": 1337, 11 | "homeEndpoint": "/", 12 | "htmlFilePath": "index.html" 13 | } 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "synthbtc", 3 | "version": "1.0.0", 4 | "description": "A tool that uses advanced Monte Carlo simulations and Turbit parallel processing to create possible Bitcoin prediction scenarios.", 5 | "main": "synthBTC.js", 6 | "scripts": { 7 | "test": "node synthBTC.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jofpin/turbit/tools/synthBTC" 12 | }, 13 | "keywords": [ 14 | "turbit", 15 | "data-processing", 16 | "monte-carlo-simulation", 17 | "synthetic-data", 18 | "prediction", 19 | "bitcoin" 20 | ], 21 | "author": "Jose Pino", 22 | "license": "MIT", 23 | "dependencies": { 24 | "turbit": "^1.0.0", 25 | "axios": "^1.7.2", 26 | "cheerio": "^1.0.0-rc.12", 27 | "csv-parser": "^3.0.0", 28 | "express": "^4.19.2", 29 | "mathjs": "^13.0.1", 30 | "moment": "^2.30.1", 31 | "path": "^0.12.7", 32 | "sentiment": "^5.0.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jose Pino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /modules/priceFetcher.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const cheerio = require("cheerio"); 3 | 4 | const PriceFetcher = { 5 | /** 6 | * Fetches the current price of Bitcoin (BTC) from the CoinGecko API. 7 | */ 8 | async fetchCoinGeckoPrice() { 9 | try { 10 | // Make a GET request to the CoinGecko API to fetch the current price of Bitcoin in USD 11 | const response = await axios.get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd"); 12 | // Return the price of Bitcoin in USD 13 | return response.data.bitcoin.usd; 14 | } catch (error) { 15 | // Log a warning message if the fetch fails 16 | console.warn(`\x1b[0m- \x1b[31mWARNING\x1b[0m | \x1b[37mFailed to fetch BTC price from CoinGecko.\x1b[0m`); 17 | return null; 18 | } 19 | }, 20 | 21 | /** 22 | * Fetches the current price of Bitcoin (BTC) from the Etherscan website. 23 | */ 24 | async fetchEtherscanPrice() { 25 | try { 26 | // Make a GET request to the Etherscan website to fetch the page content 27 | const response = await axios.get("https://etherscan.io/address/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"); 28 | // Load the HTML content using Cheerio 29 | const $ = cheerio.load(response.data); 30 | // Extract the price value from the specific HTML element 31 | const value = $("#ContentPlaceHolder1_tr_tokeninfo > div > span").text().trim(); 32 | // Extract the numeric value from the text and remove any commas or dollar signs 33 | const matches = value.match(/[\d,]+\.?\d*/)[0].replace(/[\$,]/g, ""); 34 | // Return the price as an integer 35 | return parseInt(matches, 10); 36 | } catch (error) { 37 | // Log a warning message if the fetch fails 38 | console.warn(`\x1b[0m- \x1b[31mWARNING\x1b[0m | \x1b[37mFailed to fetch BTC price from Etherscan.\x1b[0m`); 39 | return null; 40 | } 41 | }, 42 | 43 | /** 44 | * Fetches the current price of Bitcoin (BTC) from both CoinGecko and Etherscan, 45 | * and returns the average price if both fetches are successful. 46 | */ 47 | async getCurrentPrice() { 48 | // Fetch the price from CoinGecko 49 | const priceCoinGecko = await this.fetchCoinGeckoPrice(); 50 | // Fetch the price from Etherscan 51 | const priceEtherscan = await this.fetchEtherscanPrice(); 52 | 53 | // If both fetches are successful, return the average price 54 | if (priceCoinGecko !== null && priceEtherscan !== null) { 55 | return (priceCoinGecko + priceEtherscan) / 2; 56 | } 57 | // If only the CoinGecko fetch is successful, return the CoinGecko price 58 | else if (priceCoinGecko !== null) { 59 | return priceCoinGecko; 60 | } 61 | // If only the Etherscan fetch is successful, return the Etherscan price 62 | else if (priceEtherscan !== null) { 63 | return priceEtherscan; 64 | } 65 | // If both fetches fail, throw an error 66 | else { 67 | throw new Error(`\x1b[0m- \x1b[31mWARNING\x1b[0m | \x1b[37mFailed to fetch BTC price from both sources.\x1b[0m`); 68 | } 69 | } 70 | }; 71 | 72 | module.exports = PriceFetcher; -------------------------------------------------------------------------------- /research-script/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Analysis Script 2 | 3 | This script is part of the research conducted for the development of synthBTC. It analyzes Bitcoin price data to calculate volatility metrics, predict future prices, and provide insights into Bitcoin's historical price movements. 4 | 5 | ## Functionality 6 | 7 | The script performs the following key functions: 8 | 9 | 1. Automatically downloads the most up-to-date BTC-USD historical data from Yahoo Finance. 10 | 2. Calculates daily returns and various volatility metrics (daily, weekly, monthly, and annual). 11 | 3. Identifies important dates such as maximum price increases and decreases. 12 | 4. Predicts volatility for the current date based on historical data. 13 | 5. Estimates probabilities of price increases and decreases for the current date. 14 | 6. Predicts future prices for the next 365 days using a Monte Carlo simulation approach. 15 | 7. Projects long-term price predictions for 3, 5, and 10 years with optimistic, pessimistic, and average scenarios. 16 | 8. Analyzes market sentiment using news data from a news API. 17 | 9. Calculates RSI (Relative Strength Index) for technical analysis. 18 | 10. Generates buy/sell recommendations based on multiple market indicators. 19 | 11. Provides a comprehensive market analysis with price and volume comparisons. 20 | 12. Outputs a detailed JSON object with all analysis results. 21 | 13. Saves predicted future prices to a separate CSV file. 22 | 23 | ## Data Source 24 | 25 | The script automatically downloads the most up-to-date BTC-USD historical data from Yahoo Finance using the following link: 26 | [https://finance.yahoo.com/quote/BTC-USD/history/](https://finance.yahoo.com/quote/BTC-USD/history/) 27 | 28 | > This ensures that the analysis is always performed on the latest available data. 29 | 30 | ## Objective 31 | 32 | The main objectives of this script are: 33 | 34 | - To provide a deep understanding of Bitcoin's historical price volatility. 35 | - To offer insights into potential future price movements. 36 | - To support the development of synthBTC by providing data-driven analysis of Bitcoin's behavior. 37 | 38 | ## Usage 39 | 40 | To use the script, ensure you have a CSV file with Bitcoin price data in the specified format. Then run: 41 | 42 | ```shell 43 | node bitcoinAnalysis.js 44 | ``` 45 | 46 | The script expects the CSV file to be located at `YahooFinance/BTC-USD.csv`. Adjust the file path in the script if necessary. 47 | 48 | ## Output 49 | 50 | The script produces one main output, a JSON object logged to the console, containing all analysis results, including: 51 | - Historical volatility metrics 52 | - Important dates and price points 53 | - Predicted volatility for the current date 54 | - Probability estimates for price movements 55 | - Short-term future price predictions (365 days) 56 | - Long-term price projections (3, 5, and 10 years) with optimistic, pessimistic, and average scenarios 57 | - Market analysis including current price, volume, RSI, and market mood 58 | - Buy/sell recommendation based on current market conditions 59 | 60 | ## Dependencies 61 | 62 | This script requires the following Node.js packages: 63 | 64 | - fs 65 | - axios 66 | - csv-parser 67 | - mathjs 68 | - moment 69 | - json2csv 70 | - sentiment 71 | 72 | Ensure these are installed before running the script. 73 | 74 | ## Note 75 | 76 | This script is part of the research phase for **synthBTC** development. The predictions and analysis should be used for informational purposes only and not as financial advice. The accuracy of predictions depends on the quality and representativeness of the historical data used. Long-term projections, in particular, are speculative and should be interpreted with caution. -------------------------------------------------------------------------------- /modules/csvHandler.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | const CSVHandler = { 5 | /** 6 | * Writes data to a CSV file. If the file already exists, it will be overwritten. 7 | * 8 | * @param {string} filePath - The path to the CSV file. 9 | * @param {string} header - The header row for the CSV file. 10 | * @param {string} data - The data to be written to the CSV file. 11 | */ 12 | writeCSV(filePath, header, data) { 13 | fs.writeFileSync(filePath, header); // Write the header to the file 14 | fs.writeFileSync(filePath, data, { flag: "a" }); // Append the data to the file 15 | }, 16 | 17 | /** 18 | * Appends data to an existing CSV file. 19 | * 20 | * @param {string} filePath - The path to the CSV file. 21 | * @param {string} data - The data to be appended to the CSV file. 22 | */ 23 | appendCSV(filePath, data) { 24 | fs.writeFileSync(filePath, data, { flag: "a" }); // Append the data to the file 25 | }, 26 | 27 | /** 28 | * Reads data from a CSV file. 29 | * 30 | * @param {string} filePath - The path to the CSV file. 31 | * @returns {string|null} The content of the CSV file as a string, or null if the file does not exist. 32 | */ 33 | readCSV(filePath) { 34 | if (fs.existsSync(filePath)) { 35 | return fs.readFileSync(filePath, "utf8"); // Read and return the file content 36 | } 37 | return null; // Return null if the file does not exist 38 | }, 39 | 40 | /** 41 | * Retrieves a list of CSV files in a directory that match a given prefix. 42 | * 43 | * @param {string} directory - The directory to search for CSV files. 44 | * @param {string} prefix - The prefix that the CSV files should start with. 45 | * @returns {string[]} An array of matching CSV file names. 46 | */ 47 | getCSVFiles(directory, prefix) { 48 | return fs.readdirSync(directory).filter(file => file.startsWith(prefix) && file.endsWith(".csv")); 49 | }, 50 | 51 | /** 52 | * Reads and parses the core simulation data from a CSV file. 53 | * 54 | * @param {string} coreFilePath - The path to the directory containing the core CSV file. 55 | * @param {string} coreFileName - The name of the core CSV file. 56 | * @returns {Object} An object containing the parsed simulation records. 57 | * @throws Will throw an error if the core CSV file does not exist. 58 | */ 59 | readCoreSimulations(coreFilePath, coreFileName) { 60 | const coreLogFile = path.join(coreFilePath, coreFileName); 61 | if (!fs.existsSync(coreLogFile)) { 62 | throw new Error("Core log file does not exist."); 63 | } 64 | 65 | const data = fs.readFileSync(coreLogFile, "utf8"); 66 | const lines = data.trim().split("\n"); 67 | const headers = lines[0].split(","); 68 | const records = lines.slice(1).map(line => { 69 | const values = line.split(","); 70 | return headers.reduce((obj, header, index) => { 71 | obj[header] = values[index]; 72 | return obj; 73 | }, {}); 74 | }); 75 | 76 | return { simulations: records }; 77 | }, 78 | 79 | /** 80 | * Calculates the total number of simulated data entries across multiple CSV files. 81 | * 82 | * @param {string} directory - The directory containing the CSV files. 83 | * @param {string} prefix - The prefix that the CSV files should start with. 84 | * @returns {number} The total number of simulated data entries. 85 | */ 86 | calculateSimulatedData(directory, prefix) { 87 | const csvFiles = this.getCSVFiles(directory, prefix); 88 | return csvFiles.reduce((acc, file) => { 89 | const filePath = path.join(directory, file); 90 | const data = this.readCSV(filePath); 91 | const lines = data.split("\n").slice(1); // Exclude the header line 92 | return acc + lines.length - 1; // Subtract 1 to account for the header line 93 | }, 0); 94 | } 95 | }; 96 | 97 | module.exports = CSVHandler; -------------------------------------------------------------------------------- /modules/apiCore.js: -------------------------------------------------------------------------------- 1 | const CSVHandler = require("./csvHandler"); 2 | 3 | const APICore = (synthBTC) => ({ 4 | /** 5 | * Retrieves the most recent simulation data, including key statistics and execution details. 6 | * If the latest simulation data is not available, it triggers a new simulation. 7 | */ 8 | getOverview: async (req, res) => { 9 | try { 10 | if (!synthBTC.latestOutput) { 11 | synthBTC.latestOutput = await synthBTC.getSimulationData(synthBTC.simulationConfig); 12 | } 13 | synthBTC.latestOutput.details.executionTime = synthBTC.Utils.defineExecutionTime(synthBTC.serverStartTime); 14 | synthBTC.latestOutput.status = synthBTC.simulationStatus; 15 | res.json(synthBTC.latestOutput); 16 | } catch (error) { 17 | res.status(500).json({ error: error.message }); 18 | } 19 | }, 20 | 21 | /** 22 | * Returns a comprehensive list of all historical simulation records stored in core.csv. 23 | */ 24 | getSimulations: async (req, res) => { 25 | try { 26 | const simulations = await CSVHandler.readCoreSimulations(synthBTC.coreFilePath, synthBTC.coreFileName); 27 | res.json(simulations); 28 | } catch (error) { 29 | res.status(500).json({ error: error.message }); 30 | } 31 | }, 32 | 33 | /** 34 | * Fetches specific simulation records by their unique identifiers from core.csv. 35 | * The IDs are provided as a comma-separated string in the request parameters. 36 | */ 37 | getSimulationsByIds: async (req, res) => { 38 | try { 39 | const simulations = await CSVHandler.readCoreSimulations(synthBTC.coreFilePath, synthBTC.coreFileName); 40 | const ids = req.params.ids.split(",").map(id => parseInt(id, 10)); 41 | const results = ids.map(id => { 42 | if (id > 0 && id <= simulations.simulations.length) { 43 | return simulations.simulations[id - 1]; 44 | } else { 45 | return { error: `Simulation ${id} not available` }; 46 | } 47 | }); 48 | res.json(results); 49 | } catch (error) { 50 | res.status(500).json({ error: error.message }); 51 | } 52 | }, 53 | 54 | /** 55 | * Provides an index of available API endpoints with descriptions. 56 | */ 57 | getApiIndex: (req, res) => { 58 | res.json({ 59 | message: "Welcome to the synthBTC API", 60 | endpoints: { 61 | "/api/overview": "Retrieves the most recent simulation data, including key statistics and execution details.", 62 | "/api/simulations": "Returns a comprehensive list of all historical simulation records stored in core.csv.", 63 | "/api/simulations/:id": "Fetches a specific simulation record by its unique identifier from core.csv.", 64 | "/api/simulations/:ids": "Retrieves multiple simulation records by their IDs (comma-separated) from core.csv." 65 | } 66 | }); 67 | }, 68 | 69 | /** 70 | * Handles requests to routes that are not available. 71 | */ 72 | handleNotFound: (req, res) => { 73 | res.status(404).json({ error: "Route not available" }); 74 | }, 75 | 76 | /** 77 | * Handles internal server errors. 78 | */ 79 | handleServerError: (err, req, res, next) => { 80 | res.status(500).json({ error: "Internal server error" }); 81 | }, 82 | 83 | /** 84 | * Sets up the API routes and error handling for the application. 85 | */ 86 | setupRoutes: function(app) { 87 | app.get("/api/overview", (req, res) => this.getOverview(req, res)); 88 | app.get("/api/simulations", (req, res) => this.getSimulations(req, res)); 89 | app.get("/api/simulations/:ids", (req, res) => this.getSimulationsByIds(req, res)); 90 | app.get("/api/prediction-performance", (req, res) => this.getPredictionPerformance(req, res)); 91 | app.get("/api", (req, res) => this.getApiIndex(req, res)); 92 | 93 | // Error handling 94 | app.use(this.handleNotFound); 95 | app.use(this.handleServerError); 96 | } 97 | }); 98 | 99 | module.exports = APICore; -------------------------------------------------------------------------------- /modules/serverCore.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const path = require("path"); 3 | const APICore = require("./apiCore"); 4 | const Utils = require("./utils"); 5 | 6 | const ServerCore = { 7 | latestOutput: null, 8 | 9 | /** 10 | * Initializes the server with the given configuration. 11 | * Sets up the Express application, serves static files, sets up API routes, 12 | * schedules simulation generation, and starts the server. 13 | * 14 | * @param {Object} synthBTC - The main synthBTC object containing core functionalities. 15 | * @param {Object} config - The configuration object. 16 | * @param {Object} config.simulationConfig - The simulation configuration. 17 | * @param {Object} config.webConfig - The web server configuration. 18 | */ 19 | init: function(synthBTC, { simulationConfig, webConfig }) { 20 | const app = express(); 21 | let port = webConfig.serverPort; 22 | 23 | // Serve static files from the clientPublicDir directory 24 | app.use("/assets", express.static(path.join(__dirname, "..", synthBTC.clientPublicDir))); 25 | 26 | // Serve the home page 27 | app.get(webConfig.homeEndpoint, async (req, res) => { 28 | try { 29 | res.sendFile(path.join(__dirname, "..", synthBTC.clientPublicDir, webConfig.htmlFilePath)); 30 | } catch (error) { 31 | res.status(500).json({ error: error.message }); 32 | } 33 | }); 34 | 35 | // Setup API routes 36 | const api = APICore(synthBTC); 37 | api.setupRoutes(app); 38 | 39 | // Schedule simulation generation based on the interval in minutes 40 | const defineIntervalConfig = simulationConfig.simulationInterval; 41 | 42 | setInterval(async () => { 43 | this.latestOutput = await synthBTC.getSimulationData(simulationConfig); 44 | }, defineIntervalConfig * 60 * 1000); 45 | 46 | /** 47 | * Starts the server on the given port. 48 | * If the port is already in use, it increments the port number and tries again. 49 | * 50 | * @param {number} port - The port number to start the server on. 51 | */ 52 | function runServer(port) { 53 | const server = app.listen(port); 54 | 55 | server.on("error", (error) => { 56 | if (error.code === "EADDRINUSE") { 57 | runServer(port + 1); 58 | } else { 59 | console.error(`\x1b[0m- \x1b[31mERROR\x1b[0m | \x1b[37mServer error: ${error.message}\x1b[0m`); 60 | process.exit(1); 61 | } 62 | }); 63 | 64 | server.on("listening", async () => { 65 | console.clear(); 66 | const steps = [ 67 | { message: "Initializing synthBTC", duration: 250 }, 68 | { message: "Loading configuration", duration: 1000 }, 69 | { message: "Setting up API routes", duration: 1200 }, 70 | { message: "Preparing Simulation Engine", duration: 1800 }, 71 | { message: "Starting Server", duration: 4000 } 72 | ]; 73 | 74 | // Display loading animation for each step 75 | for (const step of steps) { 76 | await Utils.animateLoading(step.message, step.duration); 77 | } 78 | 79 | setTimeout(async () => { 80 | console.clear(); 81 | console.log("\x1b[32m%s\x1b[0m", "🟢 synthBTC Server is now running!"); 82 | console.log("\x1b[37m%s\x1b[0m", "----------------------------------------"); 83 | console.log("\x1b[36m%s\x1b[0m", `🏠 Home (Endpoint): \x1b[4mhttp://localhost:${port}${webConfig.homeEndpoint}\x1b[0m`); 84 | console.log("\x1b[36m%s\x1b[0m", `🌐 API (Endpoint): \x1b[4mhttp://localhost:${port}/api\x1b[0m`); 85 | console.log("\x1b[37m%s\x1b[0m", "----------------------------------------"); 86 | console.log("\x1b[32m%s\x1b[0m", "Welcome to synthBTC!"); 87 | console.log("\x1b[37m%s\x1b[0m", "Enjoy your simulation experience."); 88 | console.log("\x1b[37m%s\x1b[0m", "---------------------------"); 89 | console.log("\x1b[37m%s\x1b[0m", "2024 Created by Jose Pino ©"); 90 | console.log("\x1b[37m%s\x1b[0m", "---------------------------"); 91 | 92 | // Generate initial simulations if not already generated 93 | if (!this.latestOutput) { 94 | this.latestOutput = await synthBTC.getSimulationData(simulationConfig); 95 | } 96 | }, 6000); 97 | }); 98 | } 99 | 100 | // Start the server with the initial port 101 | runServer(port); 102 | } 103 | }; 104 | 105 | module.exports = ServerCore; -------------------------------------------------------------------------------- /modules/monteCarloEngine.js: -------------------------------------------------------------------------------- 1 | // Try to import the installed version of Turbit 2 | const Turbit = require("turbit"); 3 | // Create a Turbit instance for parallel processing 4 | const turbit = Turbit(); 5 | 6 | const MonteCarloEngine = { 7 | /** 8 | * Validates the input parameters for the simulation. 9 | */ 10 | validateInputs({ currentPrice, totalSimulations, decimalVolatility, simulationDays }) { 11 | if (currentPrice <= 0) throw new Error("Current price must be greater than 0"); 12 | if (totalSimulations <= 0) throw new Error("Simulations must be greater than 0"); 13 | if (decimalVolatility <= 0) throw new Error("Volatility must be greater than 0"); 14 | if (simulationDays <= 0) throw new Error("Days must be greater than 0"); 15 | }, 16 | 17 | /** 18 | * Simulates the prices using Monte Carlo simulation. 19 | * This function performs the core logic of the Monte Carlo simulation. It uses the Turbit library 20 | * to run the simulation in parallel, which significantly speeds up the computation. 21 | * 22 | * The simulation generates random price changes for a given number of days, based on the provided 23 | * volatility and current price. It uses a Gaussian distribution to model the random changes. 24 | * 25 | * @param {number} currentPrice - The current price of the asset. 26 | * @param {number} totalSimulations - The total number of simulations to run. 27 | * @param {number} decimalVolatility - The volatility of the asset as a decimal. 28 | * @param {number} simulationDays - The number of days to simulate. 29 | * @param {number} turbitPower - The power setting for Turbit parallel processing. 30 | * @returns {Promise} A promise that resolves to an array of simulated prices. 31 | */ 32 | async simulatePrices(currentPrice, totalSimulations, decimalVolatility, simulationDays, turbitPower) { 33 | // Validate the input parameters to ensure they are within acceptable ranges 34 | this.validateInputs({ currentPrice, totalSimulations, decimalVolatility, simulationDays }); 35 | 36 | // Using Turbit for parallel processing 37 | // Turbit allows us to run the simulation in parallel, distributing the workload across multiple processes 38 | const result = await turbit.run(function ({ data, args }) { 39 | /** 40 | * Generates a random number following a Gaussian distribution. 41 | * This function uses the Box-Muller transform to generate a normally distributed random number. 42 | */ 43 | const gaussianRandom = function () { 44 | let u = 0, v = 0; 45 | while (u === 0) u = Math.random(); 46 | while (v === 0) v = Math.random(); 47 | return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); 48 | } 49 | 50 | const { currentPrice, decimalVolatility, simulationDays } = args; 51 | 52 | // Simulate the price changes over the given number of days 53 | // For each simulation, we start with the current price and apply daily changes 54 | // The daily change is calculated using the exponential of the product of volatility and a Gaussian random number 55 | return Array.from({ length: data.length }, () => { 56 | let price = currentPrice; 57 | for (let i = 0; i < simulationDays; i++) { 58 | const dailyChange = Math.exp(decimalVolatility * gaussianRandom() / Math.sqrt(simulationDays)); 59 | price *= dailyChange; 60 | } 61 | return price; 62 | }); 63 | }, { 64 | type: "extended", // Specifies the type of processing to be used by Turbit 65 | data: Array(totalSimulations), // An array representing the number of simulations to run 66 | args: { currentPrice, decimalVolatility, simulationDays }, // Arguments to be passed to the simulation function 67 | power: turbitPower // The power setting for Turbit, controlling the level of parallelism 68 | }); 69 | 70 | // Return the array of simulated prices 71 | return result.data; 72 | }, 73 | 74 | /** 75 | * Executes the full simulation in batches. 76 | * This function divides the total number of simulations into smaller batches to manage memory usage 77 | * and improve performance. It calls the simulatePrices function for each batch and combines the results. 78 | * 79 | * @param {Object} params - The input parameters. 80 | * @param {number} params.currentPrice - The current price of the asset. 81 | * @param {number} params.totalSimulations - The total number of simulations to run. 82 | * @param {number} params.decimalVolatility - The volatility of the asset as a decimal. 83 | * @param {number} params.simulationDays - The number of days to simulate. 84 | * @param {number} params.turbitPower - The power setting for Turbit parallel processing. 85 | * @returns {Promise} A promise that resolves to an array of all simulated prices. 86 | */ 87 | async executeFullSimulation({ currentPrice, totalSimulations, decimalVolatility, simulationDays, turbitPower }) { 88 | const desiredBatchSize = 5000; 89 | const batchCount = Math.ceil(totalSimulations / desiredBatchSize); 90 | 91 | let allPrices = []; 92 | for (let i = 0; i < batchCount; i++) { 93 | // Determine the size of the current batch 94 | const batchSize = i === batchCount - 1 ? totalSimulations - (i * desiredBatchSize) : desiredBatchSize; 95 | // Run the simulation for the current batch 96 | const batchPrices = await this.simulatePrices(currentPrice, batchSize, decimalVolatility, simulationDays, turbitPower); 97 | // Combine the results of the current batch with the previous results 98 | allPrices = allPrices.concat(batchPrices); 99 | } 100 | // Return the combined results of all batches 101 | return allPrices; 102 | } 103 | }; 104 | 105 | module.exports = MonteCarloEngine; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # synthBTC 2 | 3 | ![synthBTC](images/gh-cover-synthBTC.png) 4 | 5 | ## Bitcoin Price Prediction Simulator Using Synthetic Data 6 | 7 | An advanced Bitcoin price prediction tool that utilizes Monte Carlo simulations and [Turbit](https://github.com/jofpin/turbit) parallel processing to create millions of potential price scenarios. synthBTC offers real-time market insights and synthetic data generation for comprehensive Bitcoin trend analysis and risk assessment. 8 | 9 | > This is a tool for synthesizing Bitcoin data and generating synthetic data for testing and training purposes. 10 | 11 | **Design Philosophy:** My primary goal with synthBTC has always been to make complex data easy to understand. I'm passionate about pure CSS and have focused my expertise on creating an intuitive, user-friendly interface. The UI includes subtle micro-interactions that I believe will delight fellow interface design enthusiasts. I've poured my love for clean, efficient design into every aspect of synthBTC to ensure that interpreting Bitcoin price predictions and simulation data is as straightforward as possible. 12 | 13 | ![synthBTC](images/gh-screenshot-synthBTC.png) 14 | 15 | ## Key Features 16 | 17 | synthBTC excels in providing comprehensive Bitcoin price analysis and prediction: 18 | 19 | | Feature | Description | 20 | |---------|-------------| 21 | | Monte Carlo Simulations | Generate thousands or millions of potential price scenarios | 22 | | Real-time Data Integration | Fetch and incorporate live Bitcoin market data | 23 | | Parallel Processing | Utilize Turbit for high-speed multicore computations | 24 | | Customizable Parameters | Adjust volatility, time frames, and simulation counts | 25 | | UI & Data Visualization | Intuitive web-based dashboard for visualizing predictions | 26 | | API Integration | API for programmatic access to simulation results | 27 | | Synthetic Data Generation | Create realistic Bitcoin price datasets for testing and training | 28 | 29 | ## Installation 30 | 31 | To use synthBTC, ensure you have **[Node.js](https://nodejs.org/)** installed. Then, clone the repository and install dependencies: 32 | 33 | ```shell 34 | git clone https://github.com/jofpin/synthBTC.git 35 | cd synthBTC 36 | npm install 37 | ``` 38 | 39 | ## Usage 40 | 41 | 1. Run synthBTC with the following command: 42 | 43 | ```shell 44 | node synthBTC.js 45 | ``` 46 | 47 | This command initializes the simulation engine, starts the web server, and makes the API available. 48 | 49 | 2. Access the intuitive dashboard by opening a web browser and navigating to: 50 | 51 | ```shell 52 | http://localhost:1337 53 | ``` 54 | 55 | The dashboard offers: 56 | - Real-time visualization of Bitcoin price predictions 57 | - Historical data charts 58 | - Customizable simulation parameters 59 | 60 | 3. Use the API endpoints: 61 | - **GET** `/api/overview`: Retrieve the most recent simulation data and key statistics 62 | - **GET** `/api/simulations`: Fetch a list of all historical simulation records 63 | - **GET** `/api/simulations/:id`: Get a specific simulation record by its unique identifier 64 | - **GET** `/api/simulations/:ids`: Retrieve multiple simulation records by their IDs (comma-separated) 65 | 66 | ## Configuration 67 | 68 | The `config.json` file contains the configuration for the simulation and web server setup. 69 | 70 | #### simulationConfig 71 | - **turbitPower**: The number of cores to be used for simulations. 72 | - **totalSimulations**: The number of simulations to be performed. 73 | - **volatilityPercentage**: 20% volatility based on an average obtained from the `bitcoinAnalysis.js` file located in the `research-script` directory. 74 | - **simulationDays**: The number of days to simulate. 75 | - **simulationInterval**: How often (in minutes) a simulation is generated. 76 | 77 | #### webConfig 78 | - **serverPort**: The port to be used for the server. 79 | - **homeEndpoint**: The route to be used for the main page. 80 | - **htmlFilePath**: The name of the file to be used for the main page. 81 | 82 | ## Architecture 83 | 84 | synthBTC utilizes a modular architecture leveraging Turbit for parallel processing: 85 | 86 | 1. **Data Fetching:** Real-time Bitcoin price data is retrieved using the [PriceFetcher](modules/priceFetcher.js) module. 87 | 2. **Monte Carlo Engine:** The [MonteCarloEngine](modules/monteCarloEngine.js) generates price scenarios using parallel processing. 88 | 3. **CSV Handling:** The [CSVHandler](modules/csvHandler.js) manages data input/output in CSV format. 89 | 4. **Server Core:** The [ServerCore](modules/serverCore.js) module orchestrates the entire simulation process. 90 | 91 | ## Synthetic Data Generation 92 | 93 | synthBTC can generate synthetic Bitcoin price data for various purposes: 94 | 95 | - Training machine learning models 96 | - Testing trading algorithms 97 | - Simulating market conditions 98 | 99 | The generated data is saved in the `private/data` path, where the `core.csv` file contains the simulation overviews for each generated csv file. 100 | 101 | ## Research Script 102 | 103 | The [`research-script`](research-script/) directory contains the Bitcoin Analysis Script ([`bitcoinAnalysis.js`](research-script/bitcoinAnalysis.js)). This script, developed prior to synthBTC, is the first of its kind to perform a comprehensive analysis of multiple Bitcoin factors in a single tool. It serves as a crucial component for synthBTC development and ongoing refinement, offering valuable insights into Bitcoin behavior and market trends. 104 | 105 | > The script comprehensive analysis and modular structure provide a solid foundation for developers to create derivative tools and further innovate in Bitcoin price and market analysis. 106 | 107 | Explore the script and its documentation in the [`research-script`](research-script/) directory. 108 | 109 | ## Powered by Turbit 110 | 111 | synthBTC is powered by Turbit, an advanced high-speed multicore computing library designed for the multi-core era. Turbit optimizes performance for computationally intensive operations by leveraging parallel processing across multiple CPU cores. 112 | 113 | --- 114 | 115 | For more information on Turbit, the parallel processing library powering synthBTC, visit the [repository](https://github.com/jofpin/turbit). 116 | 117 | ## License 118 | 119 | The content of this project itself is licensed under the [Creative Commons Attribution 3.0 license](https://creativecommons.org/licenses/by/3.0/us/deed.en), and the underlying source code used to format and display that content is licensed under the [MIT license](LICENSE). 120 | 121 | Copyright (c) 2024 by [**Jose Pino**](https://x.com/jofpin) 122 | -------------------------------------------------------------------------------- /modules/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | const Utils = { 5 | /** 6 | * Converts a size in bytes to a human-readable string with appropriate units. 7 | */ 8 | defineDataSize(bytes) { 9 | const units = ["Bytes", "KB", "MB", "GB", "TB"]; 10 | const exponent = bytes ? Math.floor(Math.log2(bytes) / 10) : 0; 11 | const size = bytes ? (bytes / (1 << (exponent * 10))).toFixed(2) : "0"; 12 | return `${size} ${units[exponent]}`; 13 | }, 14 | 15 | /** 16 | * Calculates the elapsed time since a given start time and returns it as a formatted string. 17 | */ 18 | defineExecutionTime(startTime) { 19 | const elapsed = Date.now() - startTime; 20 | const seconds = Math.floor((elapsed / 1000) % 60); 21 | const minutes = Math.floor((elapsed / (1000 * 60)) % 60); 22 | const hours = Math.floor((elapsed / (1000 * 60 * 60)) % 24); 23 | const days = Math.floor(elapsed / (1000 * 60 * 60 * 24)); 24 | const pad = (num) => String(num).padStart(2, "0"); 25 | return `${pad(days)}:${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; 26 | }, 27 | 28 | /** 29 | * Converts a time duration in milliseconds to a human-readable string. 30 | */ 31 | defineProcessingTime(timeInMs) { 32 | if (timeInMs < 1000) { 33 | return `${timeInMs} ms`; 34 | } else if (timeInMs < 60000) { 35 | return `${(timeInMs / 1000).toFixed(2)} sec`; 36 | } else { 37 | return `${(timeInMs / 60000).toFixed(2)} min`; 38 | } 39 | }, 40 | 41 | /** 42 | * Ensures that a directory exists. If it does not exist, it is created. 43 | */ 44 | ensureDirectoryExists(dir) { 45 | if (!fs.existsSync(dir)) { 46 | fs.mkdirSync(dir); 47 | } 48 | }, 49 | 50 | /** 51 | * Calculates the total size of all files in a directory. 52 | * 53 | * @param {string} dir - The path to the directory. 54 | * @returns {number} The total size of all files in the directory in bytes. 55 | */ 56 | calculateDataSize(dir) { 57 | const files = fs.readdirSync(dir); 58 | let totalSize = 0; 59 | 60 | files.forEach(file => { 61 | const filePath = path.join(dir, file); 62 | const stats = fs.statSync(filePath); 63 | if (stats.isFile()) { 64 | totalSize += stats.size; 65 | } 66 | }); 67 | 68 | return totalSize; 69 | }, 70 | 71 | /** 72 | * Calculates the percentage change between a simulated price and the current price. 73 | * 74 | * @param {number} simulatedPrice - The simulated price. 75 | * @param {number} currentPrice - The current price. 76 | * @returns {string} The percentage change formatted as "+X.XX%" or "-X.XX%". 77 | */ 78 | calculateChangePercentage(simulatedPrice, currentPrice) { 79 | const change = ((simulatedPrice - currentPrice) / currentPrice) * 100; 80 | return `${change >= 0 ? "+" : ""}${change.toFixed(2)}%`; 81 | }, 82 | 83 | /** 84 | * Calculates the target price as the mean plus one standard deviation of a list of prices. 85 | */ 86 | calculateTargetPrice(prices) { 87 | const mean = prices.reduce((acc, p) => acc + p, 0) / prices.length; 88 | const variance = prices.reduce((acc, p) => acc + Math.pow(p - mean, 2), 0) / prices.length; 89 | const stdDev = Math.sqrt(variance); 90 | return mean + stdDev; // Target price as mean + 1 standard deviation 91 | }, 92 | 93 | /** 94 | * Gets the index of the last file in a directory that matches a given prefix. 95 | * 96 | * @param {string} dir - The path to the directory. 97 | * @param {string} prefix - The prefix that the files should start with. 98 | * @returns {number} The index of the last file, or 0 if no files match. 99 | */ 100 | getLastFileIndex(dir, prefix) { 101 | const files = fs.readdirSync(dir); 102 | const csvFiles = files.filter(file => file.startsWith(prefix + "_") && file.endsWith(".csv")); 103 | if (csvFiles.length === 0) return 0; 104 | 105 | const indices = csvFiles.map(file => parseInt(file.match(/_(\d+)\.csv$/)[1], 10)); 106 | return Math.max(...indices) + 1; 107 | }, 108 | 109 | /** 110 | * Determines the next file index in a directory that matches a given prefix. 111 | * 112 | * @param {string} dir - The path to the directory. 113 | * @param {string} prefix - The prefix that the files should start with. 114 | * @returns {number} The next file index. 115 | */ 116 | determineNextFileIndex(dir, prefix) { 117 | const existingFiles = fs.readdirSync(dir).filter(file => file.startsWith(prefix + "_") && file.endsWith(".csv")); 118 | const existingIndices = existingFiles.map(file => parseInt(file.match(/_(\d+)\.csv$/)[1], 10)); 119 | return existingIndices.length > 0 ? Math.max(...existingIndices) + 1 : 1; 120 | }, 121 | 122 | /** 123 | * Displays a loading animation in the console for a given duration. 124 | */ 125 | animateLoading(message, duration) { 126 | return new Promise((resolve) => { 127 | const frames = ["⠋", "⠙", "⠚", "⠞", "⠖", "⠦", "⠴", "⠲", "⠳", "⠓", "⠒", "⠉"]; 128 | const glitchChars = ["@", "!", "#", "$", "%", "&", "*", "?", "=", "+"]; 129 | let i = 0; 130 | const startTime = Date.now(); 131 | 132 | const interval = setInterval(() => { 133 | const elapsedTime = Date.now() - startTime; 134 | const progress = Math.min(elapsedTime / duration, 1); 135 | const frameIndex = i % frames.length; 136 | 137 | const opacity = Math.floor(128 + 127 * Math.sin(i * 0.2)); 138 | const frame = `\x1b[38;2;0;${opacity};255m${frames[frameIndex]}\x1b[0m`; 139 | 140 | const wavePosition = (i % 20) / 20; 141 | const coloredMessage = message.split("").map((char, index) => { 142 | const charPosition = index / message.length; 143 | const distance = Math.abs(charPosition - wavePosition); 144 | const intensity = Math.floor(255 - (distance * 255 * 2)); 145 | if (Math.random() < 0.02) { 146 | char = glitchChars[Math.floor(Math.random() * glitchChars.length)]; 147 | } 148 | return `\x1b[38;2;${intensity};${intensity};${intensity}m${char}\x1b[0m`; 149 | }).join(""); 150 | 151 | process.stdout.clearLine(); 152 | process.stdout.cursorTo(0); 153 | process.stdout.write(`${frame} ${coloredMessage} \x1b[32m${(progress * 100).toFixed(0)}%\x1b[0m`); 154 | 155 | if (progress === 1) { 156 | clearInterval(interval); 157 | process.stdout.clearLine(); 158 | process.stdout.cursorTo(0); 159 | process.stdout.write(`\x1b[32m✓\x1b[0m ${message} \x1b[32m100%\x1b[0m\n`); 160 | resolve(); 161 | } 162 | i++; 163 | }, 80); 164 | }); 165 | } 166 | }; 167 | 168 | module.exports = Utils; -------------------------------------------------------------------------------- /public/js/main.js: -------------------------------------------------------------------------------- 1 | const synthBTCApp = { 2 | /** 3 | * Fetches data from the server's overview API endpoint. 4 | * On success, it updates the overview and details sections of the UI. 5 | */ 6 | async fetchData() { 7 | try { 8 | // Fetch data from the /api/overview endpoint 9 | const response = await fetch("/api/overview"); 10 | const data = await response.json(); 11 | // Update the UI with the fetched data 12 | this.displayOverview(data.overview); 13 | this.displayDetails(data.details); 14 | 15 | // Check and update the processing status 16 | this.updateProcessingStatus(data.status); 17 | } catch (error) { 18 | // Log an error message if the fetch fails 19 | console.error("Error fetching data:", error); 20 | } 21 | }, 22 | 23 | /** 24 | * Formats a number as a localized string with commas as thousand separators. 25 | */ 26 | formatCounter(price) { 27 | return `${Number(price).toLocaleString()}`; 28 | }, 29 | 30 | /** 31 | * Updates the overview section of the UI with the provided data. 32 | */ 33 | displayOverview(overview) { 34 | // Update the price elements with the corresponding data 35 | this.displayPrice("highest-price", "highest-percentage", overview.highest); 36 | this.displayPrice("target-price", "target-percentage", overview.target); 37 | this.displayPrice("average-price", "average-percentage", overview.average); 38 | this.displayPrice("lowest-price", "lowest-percentage", overview.lowest); 39 | 40 | // Update the current price element 41 | const currentPriceElement = document.getElementById("current-price"); 42 | currentPriceElement.textContent = "$" + this.formatCounter(overview.current.price); 43 | }, 44 | 45 | /** 46 | * Updates the details section of the UI with the provided data. 47 | */ 48 | displayDetails(details) { 49 | // Update the details elements with the corresponding data 50 | document.getElementById("total-simulations").textContent = details.totalSimulations; 51 | document.getElementById("total-simulation-days").textContent = details.totalSimulationDays; 52 | document.getElementById("total-processing-time").textContent = details.processingTime; 53 | document.getElementById("total-execution-time").textContent = details.executionTime; 54 | document.getElementById("current-year").textContent = details.currentYear; 55 | document.getElementById("simulated-data").textContent = this.formatCounter(details.simulatedData); 56 | }, 57 | 58 | /** 59 | * Updates the price and percentage elements with the provided data. 60 | * 61 | * @param {string} priceElementId - The ID of the price element. 62 | * @param {string} percentageElementId - The ID of the percentage element. 63 | * @param {Object} priceData - The price data. 64 | */ 65 | displayPrice(priceElementId, percentageElementId, priceData) { 66 | // Get the price and percentage elements by their IDs 67 | const priceElement = document.getElementById(priceElementId); 68 | const percentageElement = document.getElementById(percentageElementId); 69 | 70 | // Update the text content of the elements with the formatted data 71 | priceElement.textContent = "$" + this.formatCounter(priceData.price); 72 | percentageElement.textContent = `${priceData.changePercentage}`; 73 | }, 74 | 75 | /** 76 | * Handles the loader animation and removal. 77 | */ 78 | outLoader(className) { 79 | var element = document.querySelector("[class^='"+className+"']"); 80 | 81 | element.style.transition = "opacity 3s ease"; 82 | element.style.opacity = 1; 83 | 84 | setTimeout(() => { 85 | element.parentNode.removeChild(element); 86 | }, 3300); 87 | }, 88 | 89 | /** 90 | * Plays the loading sound. 91 | */ 92 | playLoadingSound() { 93 | const audio = new Audio("/assets/sounds/loading.mp3"); 94 | audio.loop = true; 95 | audio.play().catch(error => console.log('Error playing loading sound:', error)); 96 | return audio; 97 | }, 98 | 99 | /** 100 | * Plays the completion sound. 101 | */ 102 | playCompletionSound() { 103 | const audio = new Audio("/assets/sounds/completion.mp3"); 104 | audio.play().catch(error => console.log('Error playing completion sound:', error)); 105 | }, 106 | 107 | /** 108 | * Sets up the API button event listener. 109 | */ 110 | setupAPIButton() { 111 | var apiButton = document.querySelector(".synthBTC-App--btnAPI"); 112 | 113 | apiButton.addEventListener("click", (event) => { 114 | event.preventDefault(); 115 | 116 | // Add loading class to start the spin animation 117 | apiButton.classList.add("loading"); 118 | 119 | // Start playing the loading sound 120 | const loadingSound = this.playLoadingSound(); 121 | 122 | setTimeout(() => { 123 | // Stop the loading sound 124 | loadingSound.pause(); 125 | loadingSound.currentTime = 0; 126 | 127 | // Remove loading class and add completed class 128 | apiButton.classList.remove("loading"); 129 | apiButton.classList.add("completed"); 130 | 131 | // Play the completion sound 132 | this.playCompletionSound(); 133 | 134 | // Change the title 135 | document.title = "API HAS BEEN OPENED"; 136 | 137 | // Remove completed class and open link after a short delay 138 | setTimeout(() => { 139 | apiButton.classList.remove("completed"); 140 | window.open(apiButton.href, "_blank"); 141 | 142 | // Reset the title after a short delay 143 | setTimeout(() => { 144 | this.updateState(document.hasFocus()); 145 | }, 5000); 146 | }, 700); 147 | }, 2300); 148 | }); 149 | }, 150 | 151 | /** 152 | * Displays a notification message with animation. 153 | * 154 | * This function shows a notification message at the top of the screen with different 155 | * animations based on the message content. It handles two specific message types: 156 | * "PROCESSING DATA" and "SIMULATION SUCCESSFUL". 157 | */ 158 | showNotification(message) { 159 | const notification = document.querySelector(".synthBTC-App--OverHeader"); 160 | const messageElement = notification.querySelector(".synthBTC-App--OverHeader---message"); 161 | 162 | messageElement.classList.remove("processing-animation", "success-animation"); 163 | 164 | if (message === "PROCESSING DATA") { 165 | messageElement.classList.add("processing-animation"); 166 | } else if (message === "SIMULATION SUCCESSFUL") { 167 | messageElement.classList.add("success-animation"); 168 | } 169 | 170 | messageElement.textContent = message; 171 | notification.style.display = "flex"; 172 | notification.style.opacity = "1"; 173 | 174 | setTimeout(() => { 175 | notification.style.opacity = "0"; 176 | setTimeout(() => { 177 | notification.style.display = "none"; 178 | notification.style.opacity = "1"; 179 | }, 800); 180 | }, 3000); 181 | }, 182 | 183 | /** 184 | * Updates the processing status and changes the title and favicon accordingly. 185 | */ 186 | updateProcessingStatus(status) { 187 | const wasProcessing = this.isProcessing; 188 | this.isProcessing = status === "PROCESSING"; 189 | 190 | if (this.isProcessing && !wasProcessing) { 191 | this.showNotification("PROCESSING DATA"); 192 | } else if (!this.isProcessing && wasProcessing) { 193 | this.showNotification("SIMULATION SUCCESSFUL"); 194 | } 195 | 196 | this.updateState(document.hasFocus()); 197 | }, 198 | 199 | /** 200 | * Sets up dynamic favicon and title changes based on window focus. 201 | */ 202 | setupPageState() { 203 | const defaultTitle = "synthBTC: Bitcoin Price Prediction Using Synthetic Data"; 204 | const inactiveTitle = "Return to synthBTC"; 205 | const processingTitle = "PROCESSING DATA"; 206 | const activeFaviconPath = "/assets/img/favicon-active.png"; 207 | const inactiveFaviconPath = "/assets/img/favicon-inactive.png"; 208 | const processingFaviconPath = "/assets/img/favicon-processing.png"; 209 | 210 | this.updateState = (isActive) => { 211 | if (this.isProcessing) { 212 | document.title = processingTitle; 213 | document.querySelector("link[rel='icon']").href = `${processingFaviconPath}?v=${new Date().getTime()}`; 214 | } else { 215 | document.title = isActive ? defaultTitle : inactiveTitle; 216 | const faviconPath = isActive ? activeFaviconPath : inactiveFaviconPath; 217 | document.querySelector("link[rel='icon']").href = `${faviconPath}?v=${new Date().getTime()}`; 218 | } 219 | }; 220 | 221 | window.addEventListener("blur", () => this.updateState(false)); 222 | window.addEventListener("focus", () => this.updateState(true)); 223 | }, 224 | 225 | /** 226 | * Initializes the application by fetching data, setting up a periodic fetch, 227 | * handling the loader, and setting up the API button. 228 | */ 229 | init() { 230 | this.isProcessing = false; 231 | // Fetch data immediately 232 | this.fetchData(); 233 | // Set up a periodic fetch every second 234 | setInterval(() => this.fetchData(), 1000); 235 | // Handle the loader 236 | this.outLoader("synthBTC-App--processingLoader"); 237 | // Set up the API button 238 | this.setupAPIButton(); 239 | // Set up dynamic favicon and title changes 240 | this.setupPageState(); 241 | } 242 | }; 243 | 244 | // Initialize the application when the window loads 245 | window.onload = () => { 246 | synthBTCApp.init(); 247 | }; -------------------------------------------------------------------------------- /synthBTC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file synthBTC.js 3 | * @name synthBTC 4 | * @description A tool that uses advanced Monte Carlo simulations and Turbit parallel processing to create possible Bitcoin prediction scenarios. 5 | * 6 | * @version 1.0.0 7 | * @license MIT 8 | * 9 | * @author Jose Pino 10 | * @contact jose@pino.sh (https://x.com/jofpin) 11 | * 12 | * Find the project on GitHub: 13 | * https://github.com/jofpin/synthBTC 14 | * 15 | * =============================== 16 | * Copyright (c) 2024 by Jose Pino 17 | * =============================== 18 | * 19 | * Released on: August 2, 2024 20 | * Last update: August 2, 2024 21 | * 22 | */ 23 | 24 | // External dependencies 25 | const fs = require("fs"); 26 | const path = require("path"); 27 | const { exec } = require("child_process"); 28 | 29 | // Internal modules 30 | const Utils = require("./modules/utils"); 31 | const CSVHandler = require("./modules/csvHandler"); 32 | const PriceFetcher = require("./modules/priceFetcher"); 33 | const MonteCarloEngine = require("./modules/monteCarloEngine"); 34 | const ServerCore = require("./modules/serverCore"); 35 | 36 | // Configuration main (json) 37 | const Config = require("./config.json"); 38 | 39 | /** 40 | * synthBTC class 41 | * Handles Bitcoin price simulations and server initialization 42 | */ 43 | class synthBTC { 44 | // Static properties 45 | static lastKnownPrice = null; 46 | static connectionLost = false; 47 | static coreFilePath = "private"; 48 | static coreFileName = "core.csv"; 49 | static dataPrivateDir = path.join(__dirname, "private/data"); 50 | static clientPublicDir = "public"; 51 | static outputFileSources = "source_simulation"; 52 | static fileIndex = 1; 53 | static simulatedData = 0; 54 | static simulationCounter = 0; 55 | static simulationStatus = "OK"; 56 | static serverStartTime = Date.now(); 57 | static latestOutput = null; 58 | 59 | /** 60 | * Fetch the current price of BTC 61 | */ 62 | static async currentPriceBTC() { 63 | try { 64 | this.lastKnownPrice = await PriceFetcher.getCurrentPrice(); 65 | this.connectionLost = false; 66 | } catch (error) { 67 | console.warn(error.message); 68 | this.connectionLost = true; 69 | } 70 | 71 | return this.lastKnownPrice; 72 | } 73 | 74 | /** 75 | * Get updated overview data. 76 | */ 77 | static async getUpdatedOverview(currentPrice, highestPrice, targetPrice, averagePrice, lowestPrice) { 78 | return { 79 | current: { 80 | price: Math.round(currentPrice) 81 | }, 82 | highest: { 83 | price: Math.round(highestPrice), 84 | changePercentage: Utils.calculateChangePercentage(highestPrice, currentPrice) 85 | }, 86 | target: { 87 | price: Math.round(targetPrice), 88 | changePercentage: Utils.calculateChangePercentage(targetPrice, currentPrice) 89 | }, 90 | average: { 91 | price: Math.round(averagePrice), 92 | changePercentage: Utils.calculateChangePercentage(averagePrice, currentPrice) 93 | }, 94 | lowest: { 95 | price: Math.round(lowestPrice), 96 | changePercentage: Utils.calculateChangePercentage(lowestPrice, currentPrice) 97 | } 98 | }; 99 | } 100 | 101 | /** 102 | * Generate simulations and save to file 103 | * @param {Object} params - Simulation parameters 104 | * @param {number} params.totalSimulations - Total number of simulations to run 105 | * @param {number} params.volatilityPercentage - Volatility percentage for simulations 106 | * @param {number} params.simulationDays - Number of days to simulate 107 | * @param {number} params.turbitPower - Turbit power for simulations 108 | * @returns {Promise} Simulation results 109 | * @description This method generates Bitcoin price simulations using the MonteCarloEngine module. 110 | * It calculates various statistics such as the lowest, highest, average, and target prices, and logs the simulation details. 111 | * The results are saved to a CSV file, and an overview of the simulation data is returned. 112 | */ 113 | static async generateSimulations({ totalSimulations, volatilityPercentage, simulationDays, turbitPower }) { 114 | this.simulationStatus = "PROCESSING"; 115 | 116 | const currentPrice = await this.currentPriceBTC(); 117 | const decimalVolatility = volatilityPercentage / 100; 118 | 119 | const startTime = Date.now(); 120 | const allPrices = await MonteCarloEngine.executeFullSimulation({ currentPrice, totalSimulations, decimalVolatility, simulationDays, turbitPower }); 121 | const processingTime = Date.now() - startTime; 122 | 123 | const lowestPrice = allPrices.reduce((min, p) => p < min ? p : min, allPrices[0]); 124 | const highestPrice = allPrices.reduce((max, p) => p > max ? p : max, allPrices[0]); 125 | const averagePrice = allPrices.reduce((acc, p) => acc + p, 0) / allPrices.length; 126 | 127 | const targetPrice = Utils.calculateTargetPrice(allPrices); 128 | 129 | this.simulatedData += totalSimulations; 130 | this.simulationCounter++; 131 | 132 | // Log the simulation details 133 | console.log(`\x1b[0m- SIMULATION \x1b[32m#${this.simulationCounter}\x1b[0m | \x1b[37mTotal Simulations:\x1b[33m ${totalSimulations.toLocaleString()}\x1b[0m | \x1b[37mBTC Price:\x1b[33m $${Math.round(currentPrice)}\x1b[0m | \x1b[37mProcessing Time:\x1b[33m ${Utils.defineProcessingTime(processingTime)}\x1b[0m`); 134 | 135 | // Determine the next file index based on existing files 136 | this.fileIndex = Utils.determineNextFileIndex(this.dataPrivateDir, this.outputFileSources); 137 | 138 | const file = path.join(this.dataPrivateDir, `${this.outputFileSources}_${this.fileIndex}.csv`); 139 | const csvHeader = "simulation_id,price,percentage_change\n"; 140 | CSVHandler.writeCSV(file, csvHeader, ""); 141 | 142 | for (let i = 0; i < Math.ceil(totalSimulations / 5000); i++) { 143 | let csvContent = ""; 144 | const slicedPrices = allPrices.slice(i * 5000, (i + 1) * 5000); 145 | slicedPrices.forEach((price, index) => { 146 | csvContent += `${index + 1},${Math.round(price)},${Utils.calculateChangePercentage(price, currentPrice)}\n`; 147 | }); 148 | CSVHandler.appendCSV(file, csvContent); 149 | } 150 | 151 | const coreLogFile = path.join(this.coreFilePath, this.coreFileName); 152 | const logHeader = "simulation_id,timestamp,current_price,highest_price,target_price,average_price,lowest_price,simulated_data,total_simulated,processing_time,data_source\n"; 153 | const logEntry = `${this.simulationCounter},${Date.now()},${Math.round(currentPrice)},${Math.round(highestPrice)},${Math.round(targetPrice)},${Math.round(averagePrice)},${Math.round(lowestPrice)},${totalSimulations},${this.simulatedData},${Utils.defineProcessingTime(processingTime)},${this.outputFileSources}_${this.fileIndex}.csv\n`; 154 | 155 | if (!fs.existsSync(coreLogFile)) { 156 | CSVHandler.writeCSV(coreLogFile, logHeader, logEntry); 157 | } else { 158 | CSVHandler.appendCSV(coreLogFile, logEntry); 159 | } 160 | 161 | this.simulationStatus = "OK"; 162 | 163 | return { 164 | status: this.simulationStatus, 165 | overview: await this.getUpdatedOverview(currentPrice, highestPrice, targetPrice, averagePrice, lowestPrice), 166 | details: { 167 | simulatedData: this.simulatedData, 168 | processingTime: Utils.defineProcessingTime(processingTime), 169 | executionTime: Utils.defineExecutionTime(this.serverStartTime), 170 | currentYear: new Date().getFullYear(), 171 | totalSimulations: this.simulationCounter, 172 | totalSimulationDays: simulationDays, 173 | dataSource: `${this.outputFileSources}_${this.fileIndex}.csv`, 174 | dataSize: Utils.defineDataSize(Utils.calculateDataSize(this.dataPrivateDir)) 175 | } 176 | }; 177 | } 178 | 179 | /** 180 | * Get simulation data 181 | * @description This method generates new simulation data based on the provided configuration. 182 | * It updates the latestOutput property with the new simulation results and returns the results. 183 | */ 184 | static async getSimulationData(simulationConfig) { 185 | const output = await this.generateSimulations(simulationConfig); 186 | this.latestOutput = output; 187 | return output; 188 | } 189 | 190 | /** 191 | * Check for missing dependencies 192 | * @description This method checks for the required dependencies (express, cheerio, axios and turbit :) ) and returns a list of any missing dependencies. 193 | */ 194 | static async checkDependencies() { 195 | const dependencies = [ 196 | "express", 197 | "cheerio", 198 | "axios", 199 | "turbit" 200 | ]; 201 | 202 | const missingDependencies = []; 203 | 204 | for (const dep of dependencies) { 205 | try { 206 | require.resolve(dep); 207 | } catch (e) { 208 | missingDependencies.push(dep); 209 | } 210 | } 211 | 212 | return missingDependencies; 213 | } 214 | 215 | /** 216 | * Install missing dependencies 217 | * @description This method installs any missing dependencies using npm. 218 | */ 219 | static async installDependencies(missingDependencies) { 220 | for (let i = 0; i < missingDependencies.length; i++) { 221 | const dep = missingDependencies[i]; 222 | console.log(`\x1b[0m- \x1b[34mINFO\x1b[0m | \x1b[37mInstalling dependency (${i + 1}/${missingDependencies.length}): ${dep}\x1b[0m`); 223 | 224 | await Utils.animateLoading(`Installing dependency: ${dep}`, 3000, async () => { 225 | return new Promise((resolve, reject) => { 226 | // Check if the package exists in npm 227 | exec(`npm view ${dep} version`, (viewError, viewStdout, viewStderr) => { 228 | if (viewError) { 229 | console.error(`\x1b[0m- \x1b[31mERROR\x1b[0m | \x1b[37mPackage ${dep} does not exist in npm.\x1b[0m`); 230 | console.error(`\x1b[0m- \x1b[31mDETAILS\x1b[0m | \x1b[37m${viewStderr}\x1b[0m`); 231 | reject(`\x1b[0m- \x1b[31mERROR\x1b[0m | \x1b[37mPackage ${dep} does not exist in npm.\x1b[0m`); 232 | } else { 233 | console.log(`\x1b[0m- \x1b[34mINFO\x1b[0m | \x1b[37mPackage ${dep} found in npm: ${viewStdout.trim()}\x1b[0m`); 234 | // Install the package 235 | exec(`npm install ${dep}`, (installError, installStdout, installStderr) => { 236 | if (installError) { 237 | console.error(`\x1b[0m- \x1b[31mERROR\x1b[0m | \x1b[37mError installing ${dep}: ${installStderr}\x1b[0m`); 238 | reject(`\x1b[0m- \x1b[31mERROR\x1b[0m | \x1b[37mError installing ${dep}: ${installError.message}\x1b[0m`); 239 | } else { 240 | console.log(`\x1b[0m- \x1b[32mSUCCESS\x1b[0m | \x1b[37m${dep} installed successfully.\x1b[0m`); 241 | console.log(`\x1b[0m- \x1b[34mDETAILS\x1b[0m | \x1b[37m${installStdout}\x1b[0m`); 242 | resolve(`\x1b[0m- \x1b[32mSUCCESS\x1b[0m | \x1b[37m${dep} installed successfully.\x1b[0m`); 243 | } 244 | }); 245 | } 246 | }); 247 | }); 248 | }); 249 | } 250 | } 251 | 252 | /** 253 | * Initialize the server 254 | * @description This method initializes the server by checking for and installing any missing dependencies, 255 | * setting up the necessary directories, and starting the server using the ServerCore module. 256 | */ 257 | static async init({ simulationConfig, webConfig }) { 258 | console.clear(); 259 | const missingDependencies = await this.checkDependencies(); 260 | 261 | if (missingDependencies.length > 0) { 262 | await this.installDependencies(missingDependencies); 263 | } else { 264 | console.log("🟢 All dependencies are already installed."); 265 | } 266 | 267 | this.simulationConfig = simulationConfig; 268 | this.serverStartTime = Date.now(); 269 | this.Utils = Utils; 270 | 271 | Utils.ensureDirectoryExists(this.dataPrivateDir); 272 | this.fileIndex = Utils.getLastFileIndex(this.dataPrivateDir, this.outputFileSources); 273 | 274 | // Calculate existing statistics 275 | this.simulatedData = CSVHandler.calculateSimulatedData(this.dataPrivateDir, this.outputFileSources); 276 | this.simulationCounter = CSVHandler.getCSVFiles(this.dataPrivateDir, this.outputFileSources).length; 277 | 278 | // Initialize the server using ServerCore 279 | ServerCore.init(this, { simulationConfig, webConfig }); 280 | } 281 | } 282 | 283 | // Initialize the server with configuration from config.json 284 | synthBTC.init(Config); 285 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | synthBTC: Bitcoin Price Prediction Using Synthetic Data 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 |
35 | 36 | OPEN API 37 | 38 | 39 |
40 | 43 | 44 |
45 |
46 |
Total simulations
47 |
0
48 |
49 |
50 |
51 |
Simulated data
52 |
0
53 |
54 |
55 |
56 |
Processing time
57 |
0
58 |
59 |
60 | 61 |
62 | 63 | 64 | 65 |
66 | 67 |
68 |

PREPARING INTERFACE

69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Current price 84 | 85 | 86 | 87 | 88 | 89 | $0 90 | 91 | 92 | $0 93 | 94 | 95 | $0 96 | 97 | 98 | $0 99 | 100 | 101 | 102 | 103 | $0 104 | 105 | 106 | 00.0% 107 | 108 | 109 | 110 | 00.0% 111 | 112 | 113 | 114 | 00.0% 115 | 116 | 117 | 118 | 00.0% 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | EXECUTION TIME 143 | 144 | 145 | 00:00:00:00 146 | 147 | 148 | 149 | Highest 150 | 151 | 152 | Target 153 | 154 | 155 | Average 156 | 157 | 158 | Lowest 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 0 168 | 169 | 170 | DAYS 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | synthBTC 179 | 180 | 181 | INTERFACE 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 |
234 | 235 | 236 | 237 | 246 | 247 |
248 | 249 | -------------------------------------------------------------------------------- /research-script/bitcoinAnalysis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bitcoin Analysis for synthBTC Research 3 | * 4 | * This script is part of the synthBTC research project, focusing on comprehensive analysis 5 | * of Bitcoin's price volatility and market trends. It automates the process of data 6 | * collection, analysis, and prediction generation to provide valuable insights for 7 | * synthBTC development and strategy. 8 | * 9 | * Key functionalities: 10 | * - Fetches historical Bitcoin price data from Yahoo Finance 11 | * - Processes raw data to extract meaningful metrics 12 | * - Generates various predictive models and market indicators, including: 13 | * • Volatility calculations across multiple time scales 14 | * • Price movement probabilities 15 | * • Future price projections 16 | * • Market sentiment analysis based on news 17 | * • Technical indicators like RSI 18 | * • Data-driven buy/sell recommendations 19 | * 20 | * The insights derived from this analysis play a crucial role in refining synthBTC 21 | * algorithms, risk management strategies, and overall market approach. 22 | * 23 | * 24 | * @author Jose Pino 25 | * @contact jose@pino.sh (https://x.com/jofpin) 26 | * @version 1.0.0 27 | * @date 2024-08-02 28 | * 29 | */ 30 | const fs = require("fs"); 31 | const axios = require("axios"); 32 | const csv = require("csv-parser"); 33 | const math = require("mathjs"); 34 | const moment = require("moment"); 35 | const Sentiment = require("sentiment"); 36 | const sentiment = new Sentiment(); 37 | 38 | const bitcoinAnalysis = { 39 | /** 40 | * Analyzes Bitcoin volatility from a CSV file 41 | * @param {string} filePath - Path to the CSV file 42 | */ 43 | analyze: async function(config) { 44 | const filePath = config.PATH; 45 | const NEWS_API_KEY = config.NEWS_API_KEY || ""; 46 | const results = []; 47 | 48 | /** 49 | * Reads the CSV file and returns a Promise with the results 50 | */ 51 | const readCSV = (csvFilePath) => { 52 | return new Promise((resolve, reject) => { 53 | fs.createReadStream(csvFilePath) 54 | .pipe(csv()) 55 | .on("data", (data) => { 56 | if (Object.values(data).some(value => value === null || value === 'null')) { 57 | return; 58 | } 59 | results.push(data); 60 | }) 61 | .on("end", () => resolve(results)) 62 | .on("error", (error) => reject(error)); 63 | }); 64 | }; 65 | 66 | /** 67 | * Calculates volatility metrics 68 | */ 69 | const calculateVolatility = (returns, scale, stdDev) => { 70 | const scaledStdDev = stdDev * Math.sqrt(scale); 71 | return { 72 | worst: (math.quantileSeq(returns, 0.01) * Math.sqrt(scale) * 100).toFixed(2) + "%", 73 | low: (math.quantileSeq(returns, 0.25) * Math.sqrt(scale) * 100).toFixed(2) + "%", 74 | average: (scaledStdDev * 100).toFixed(2) + "%", 75 | high: (math.quantileSeq(returns, 0.75) * Math.sqrt(scale) * 100).toFixed(2) + "%", 76 | extreme: (math.quantileSeq(returns, 0.99) * Math.sqrt(scale) * 100).toFixed(2) + "%" 77 | }; 78 | }; 79 | 80 | /** 81 | * Predicts future prices based on historical returns 82 | */ 83 | const predictFuturePrices = (initialPrice, returns, numDays, startDate) => { 84 | const futurePrices = []; 85 | let currentPrice = initialPrice; 86 | for (let i = 0; i < numDays; i++) { 87 | const randomReturn = returns[Math.floor(Math.random() * returns.length)]; 88 | const nextPrice = currentPrice * (1 + randomReturn); 89 | const nextDate = moment(startDate).add(i + 1, "days").format("MMM D, YYYY"); 90 | futurePrices.push({ date: nextDate, price: Math.round(nextPrice) }); 91 | currentPrice = nextPrice; 92 | } 93 | return futurePrices; 94 | }; 95 | 96 | /** 97 | * Projects Bitcoin price for a given number of years 98 | */ 99 | const projectPriceForYears = (initialPrice, avgReturn, stdDev, years) => { 100 | const annualReturn = Math.pow(1 + avgReturn, 365) - 1; 101 | const annualStdDev = stdDev * Math.sqrt(365); 102 | 103 | const optimisticReturn = annualReturn + annualStdDev; 104 | const pessimisticReturn = Math.max(annualReturn - annualStdDev, -0.99); 105 | 106 | let optimisticPrice = initialPrice * Math.pow(1 + optimisticReturn, years); 107 | let pessimisticPrice = initialPrice * Math.pow(1 + pessimisticReturn, years); 108 | let averagePrice = initialPrice * Math.pow(1 + annualReturn, years); 109 | 110 | const currentDate = moment(); 111 | const projectionYear = currentDate.clone().add(years, 'years').year(); 112 | 113 | return { 114 | optimistic: Math.round(optimisticPrice), 115 | pessimistic: Math.round(pessimisticPrice), 116 | average: Math.round(averagePrice), 117 | year: projectionYear 118 | }; 119 | }; 120 | 121 | /** 122 | * Fetches the Bitcoin Fear and Greed Index 123 | */ 124 | const fetchFearAndGreedIndex = async () => { 125 | try { 126 | const response = await axios.get("https://api.alternative.me/fng/"); 127 | const data = response.data.data[0]; 128 | return { 129 | value: parseInt(data.value), 130 | valueClassification: data.value_classification 131 | }; 132 | } catch (error) { 133 | console.error("Error fetching Fear and Greed Index:", error); 134 | return null; 135 | } 136 | }; 137 | 138 | /** 139 | * Fetches and analyzes sentiment from recent Bitcoin-related news articles 140 | */ 141 | const fetchNewsSentiment = async () => { 142 | if (!NEWS_API_KEY) { 143 | console.log("News API key not provided. Market sentiment analysis will be skipped."); 144 | return null; 145 | } 146 | 147 | try { 148 | const url = `https://newsapi.org/v2/everything?q=bitcoin&apiKey=${NEWS_API_KEY}&language=en`; 149 | const response = await axios.get(url); 150 | const articles = response.data.articles; 151 | 152 | let totalScore = 0; 153 | let articleCount = 0; 154 | 155 | articles.forEach(article => { 156 | const content = article.title + " " + article.description; 157 | const result = sentiment.analyze(content); 158 | totalScore += result.score; 159 | articleCount++; 160 | }); 161 | 162 | const averageScore = totalScore / articleCount; 163 | // Normalize the score to a 0-100 scale, similar to the Fear and Greed Index 164 | const normalizedScore = Math.round(((averageScore + 5) / 10) * 100); 165 | 166 | return Math.max(0, Math.min(100, normalizedScore)); // Ensure the score is between 0 and 100 167 | } catch (error) { 168 | console.error("Error fetching news sentiment:", error); 169 | return null; 170 | } 171 | }; 172 | 173 | /** 174 | * Determines a descriptive sentiment category based on a numerical score 175 | */ 176 | const getSentimentType = (score) => { 177 | if (score === null) return "Not Available"; 178 | if (score >= 75) return "Extreme Greed"; 179 | if (score >= 60) return "Greed"; 180 | if (score > 40) return "Neutral"; 181 | if (score > 25) return "Fear"; 182 | return "Extreme Fear"; 183 | }; 184 | 185 | /** 186 | * Interprets the market sentiment based on a numerical score. 187 | */ 188 | const getMarketSentiment = (score) => { 189 | if (score === null) return "Not Available"; 190 | if (score <= 25) return "Extreme Fear"; 191 | if (score <= 40) return "Fear"; 192 | if (score <= 60) return "Neutral"; 193 | if (score <= 75) return "Greed"; 194 | return "Extreme Greed"; 195 | }; 196 | 197 | /** 198 | * Combines sentiment analysis with Fear and Greed Index 199 | * @param {number} sentimentScore - The sentiment score from news analysis 200 | * @param {Object} fearAndGreedData - The Fear and Greed Index data 201 | * @returns {Object} Combined market mood data 202 | */ 203 | const getCombinedMarketMood = (sentimentScore, fearAndGreedData) => { 204 | if (fearAndGreedData === null) { 205 | return { 206 | mood: "Not Available", 207 | explanation: "Fear & Greed Index data is not available." 208 | }; 209 | } 210 | 211 | const fngScore = fearAndGreedData.value; 212 | const combinedScore = sentimentScore !== null ? (fngScore + sentimentScore) / 2 : fngScore; 213 | const mood = getMarketSentiment(combinedScore); 214 | 215 | let explanation = `Based on the Fear & Greed Index (${fearAndGreedData.valueClassification})`; 216 | if (sentimentScore !== null) { 217 | explanation += ` and news sentiment analysis (${getMarketSentiment(sentimentScore)})`; 218 | } 219 | explanation += `, the overall market mood appears to be ${mood}.`; 220 | 221 | if (Math.abs(fngScore - sentimentScore) > 20) { 222 | explanation += " There's a significant discrepancy between market indicators and news sentiment, which might indicate a shifting market mood or potential opportunities."; 223 | } 224 | 225 | return { mood, explanation }; 226 | }; 227 | 228 | /** 229 | * Calculates the Relative Strength Index (RSI) for a given set of close prices 230 | */ 231 | const calculateRSI = (closePrices, period = 14) => { 232 | if (closePrices.length < period + 1) { 233 | return null; 234 | } 235 | 236 | const changes = []; 237 | for (let i = 1; i < closePrices.length; i++) { 238 | changes.push(closePrices[i] - closePrices[i - 1]); 239 | } 240 | 241 | let gains = changes.map(change => change > 0 ? change : 0); 242 | let losses = changes.map(change => change < 0 ? -change : 0); 243 | 244 | let avgGain = gains.slice(0, period).reduce((sum, gain) => sum + gain, 0) / period; 245 | let avgLoss = losses.slice(0, period).reduce((sum, loss) => sum + loss, 0) / period; 246 | 247 | const rsiValues = []; 248 | 249 | for (let i = period; i < changes.length; i++) { 250 | avgGain = ((avgGain * (period - 1)) + gains[i]) / period; 251 | avgLoss = ((avgLoss * (period - 1)) + losses[i]) / period; 252 | 253 | const rs = avgGain / avgLoss; 254 | const rsi = 100 - (100 / (1 + rs)); 255 | 256 | rsiValues.push(rsi); 257 | } 258 | 259 | return rsiValues[rsiValues.length - 1]; 260 | }; 261 | 262 | /** 263 | * Generates a detailed buy/sell/hold recommendation based on various market indicators 264 | * @param {number} currentPrice - The current price of Bitcoin 265 | * @param {number} avgRecentPrice - The average price over recent period 266 | * @param {number} rsi - The current RSI value 267 | * @param {string} marketMood - The current market mood 268 | * @param {number} volumeRatio - The ratio of current volume to average volume 269 | * @returns {Object} An object containing the recommendation, explanation, and reasons 270 | */ 271 | const generateDetailedRecommendation = (currentPrice, avgRecentPrice, rsi, newsSentiment, fearAndGreedData, volumeRatio) => { 272 | let buyScore = 0; 273 | let reasons = []; 274 | 275 | // Price analysis 276 | if (currentPrice < avgRecentPrice * 0.9) { 277 | buyScore += 2; 278 | reasons.push("The current price is significantly below the recent average (potential buying opportunity)."); 279 | } else if (currentPrice < avgRecentPrice) { 280 | buyScore += 1; 281 | reasons.push("The current price is slightly below the recent average."); 282 | } else if (currentPrice > avgRecentPrice * 1.1) { 283 | buyScore -= 2; 284 | reasons.push("The current price is significantly above the recent average (potential selling opportunity)."); 285 | } else { 286 | buyScore -= 1; 287 | reasons.push("The current price is slightly above the recent average."); 288 | } 289 | 290 | // RSI analysis 291 | if (rsi < 30) { 292 | buyScore += 2; 293 | reasons.push("The RSI indicates that the asset might be oversold."); 294 | } else if (rsi > 70) { 295 | buyScore -= 2; 296 | reasons.push("The RSI indicates that the asset might be overbought."); 297 | } else if (rsi < 45) { 298 | buyScore += 1; 299 | reasons.push("The RSI is in a slightly oversold range."); 300 | } else if (rsi > 55) { 301 | buyScore -= 1; 302 | reasons.push("The RSI is in a slightly overbought range."); 303 | } else { 304 | reasons.push("The RSI is in a neutral range."); 305 | } 306 | 307 | // News sentiment and Fear and Greed Index analysis 308 | if (newsSentiment !== null && fearAndGreedData !== null) { 309 | reasons.push(`The news sentiment is ${getSentimentType(newsSentiment)}.`); 310 | 311 | if (newsSentiment <= -5) { 312 | buyScore += 2; 313 | reasons.push("Extremely negative news sentiment often presents good buying opportunities."); 314 | } else if (newsSentiment <= -2) { 315 | buyScore += 1; 316 | reasons.push("Negative news sentiment might present buying opportunities."); 317 | } else if (newsSentiment >= 5) { 318 | buyScore -= 2; 319 | reasons.push("Extremely positive news sentiment might signal a market top."); 320 | } else if (newsSentiment >= 2) { 321 | buyScore -= 1; 322 | reasons.push("Positive news sentiment might precede a market correction."); 323 | } 324 | 325 | reasons.push(`The Fear and Greed Index is at ${fearAndGreedData.value} (${fearAndGreedData.valueClassification}).`); 326 | 327 | if (fearAndGreedData.value <= 20) { 328 | buyScore += 2; 329 | reasons.push("This extreme fear often presents good buying opportunities."); 330 | } else if (fearAndGreedData.value <= 40) { 331 | buyScore += 1; 332 | reasons.push("This fear in the market might present buying opportunities."); 333 | } else if (fearAndGreedData.value >= 80) { 334 | buyScore -= 2; 335 | reasons.push("This extreme greed might signal a market top."); 336 | } else if (fearAndGreedData.value >= 60) { 337 | buyScore -= 1; 338 | reasons.push("This greed in the market might precede a correction."); 339 | } 340 | } else { 341 | reasons.push("Market sentiment data is not available."); 342 | } 343 | 344 | // Volume analysis 345 | if (volumeRatio > 2) { 346 | buyScore += 1; 347 | reasons.push("Trading volume is exceptionally high, indicating strong market interest."); 348 | } else if (volumeRatio > 1.5) { 349 | buyScore += 0.5; 350 | reasons.push("Trading volume is higher than average, showing increased market activity."); 351 | } else if (volumeRatio < 0.5) { 352 | buyScore -= 1; 353 | reasons.push("Trading volume is significantly lower than average, indicating weak market interest."); 354 | } else { 355 | reasons.push("Trading volume is around average levels."); 356 | } 357 | 358 | let recommendation, explanation; 359 | if (buyScore >= 4) { 360 | recommendation = "Strong Buy"; 361 | explanation = "Multiple indicators suggest very favorable buying conditions."; 362 | } else if (buyScore >= 2) { 363 | recommendation = "Buy"; 364 | explanation = "Several indicators suggest it might be a good time to buy, but exercise some caution."; 365 | } else if (buyScore > 0) { 366 | recommendation = "Weak Buy"; 367 | explanation = "Some indicators are positive, but the overall signal is not strong. Consider buying small amounts."; 368 | } else if (buyScore === 0) { 369 | recommendation = "Hold"; 370 | explanation = "Current indicators are mixed. Consider holding your position and monitoring the market closely."; 371 | } else if (buyScore > -2) { 372 | recommendation = "Weak Sell"; 373 | explanation = "Some indicators are negative, but the overall signal is not strong. Consider selling small amounts."; 374 | } else if (buyScore > -4) { 375 | recommendation = "Sell"; 376 | explanation = "Several indicators suggest it might be a good time to sell, but exercise some caution."; 377 | } else { 378 | recommendation = "Strong Sell"; 379 | explanation = "Multiple indicators suggest very unfavorable market conditions for holding."; 380 | } 381 | 382 | return { recommendation, explanation, reasons: reasons.join(" ") }; 383 | }; 384 | 385 | /** 386 | * Performs the main analysis on the CSV data 387 | */ 388 | const performAnalysis = async (results) => { 389 | // Extract close prices and dates from results 390 | const closePrices = results.map(row => parseFloat(row["Close"])); 391 | const dates = results.map(row => row["Date"]); 392 | const rsi = calculateRSI(closePrices); 393 | 394 | // Calculate daily returns 395 | const dailyReturns = closePrices.slice(1).map((price, i) => (price - closePrices[i]) / closePrices[i]); 396 | 397 | // Calculate standard deviation of daily returns 398 | const stdDev = math.std(dailyReturns); 399 | 400 | // Calculate average daily return 401 | const avgReturn = math.mean(dailyReturns); 402 | 403 | // Calculate volatility for different time scales 404 | const dailyVolatility = calculateVolatility(dailyReturns, 1, stdDev); 405 | const weeklyVolatility = calculateVolatility(dailyReturns, 7, stdDev); 406 | const monthlyVolatility = calculateVolatility(dailyReturns, 30, stdDev); 407 | const annualVolatility = calculateVolatility(dailyReturns, 365, stdDev); 408 | 409 | // Calculate number of days, months, and years 410 | const numDays = dates.length; 411 | const numYears = (numDays / 365).toFixed(2); 412 | const numMonths = (numDays / 30).toFixed(2); 413 | 414 | // Format start and end dates 415 | const startDate = moment(dates[0]).format("MMM D, YYYY"); 416 | const endDate = moment(dates[dates.length - 1]).format("MMM D, YYYY"); 417 | 418 | // Find maximum increase and decrease 419 | const maxIncrease = dailyReturns.reduce((max, ret, i) => ret > max.value ? { date: dates[i + 1], value: ret } : max, { date: null, value: -Infinity }); 420 | const maxDecrease = dailyReturns.reduce((min, ret, i) => ret < min.value ? { date: dates[i + 1], value: ret } : min, { date: null, value: Infinity }); 421 | 422 | // Find highest and lowest prices 423 | const maxPrice = Math.max(...closePrices); 424 | const minPrice = Math.min(...closePrices); 425 | const maxPriceDate = dates[closePrices.indexOf(maxPrice)]; 426 | const minPriceDate = dates[closePrices.indexOf(minPrice)]; 427 | 428 | // Predict today's volatility 429 | const today = moment().format("MMM D"); 430 | const historicalReturnsToday = dailyReturns.filter((_, i) => moment(dates[i]).format("MMM D") === today); 431 | const predictedVolatilityToday = calculateVolatility(historicalReturnsToday, 1, stdDev); 432 | 433 | // Calculate probability of increase and decrease 434 | const numIncreases = dailyReturns.filter(ret => ret > 0).length; 435 | const numDecreases = dailyReturns.filter(ret => ret < 0).length; 436 | const numNoChange = dailyReturns.filter(ret => ret === 0).length; 437 | const probabilityIncrease = ((numIncreases / dailyReturns.length) * 100).toFixed(2) + "%"; 438 | const probabilityDecrease = ((numDecreases / dailyReturns.length) * 100).toFixed(2) + "%"; 439 | const probabilityNoChange = ((numNoChange / dailyReturns.length) * 100).toFixed(2) + "%"; 440 | 441 | // Calculate today's price movements 442 | const todayIncreases = historicalReturnsToday.filter(ret => ret > 0).length; 443 | const todayDecreases = historicalReturnsToday.filter(ret => ret < 0).length; 444 | 445 | // Predict future prices for the next 365 days 446 | const initialPrice = closePrices[closePrices.length - 1]; 447 | const futurePrices = predictFuturePrices(initialPrice, dailyReturns, 365, dates[dates.length - 1]); 448 | 449 | // Project prices for 3, 5, and 10 years 450 | const threeYearsPrediction = projectPriceForYears(initialPrice, avgReturn, stdDev, 3); 451 | const fiveYearsPrediction = projectPriceForYears(initialPrice, avgReturn, stdDev, 5); 452 | const tenYearsPrediction = projectPriceForYears(initialPrice, avgReturn, stdDev, 10); 453 | 454 | // Segment future prices into daily, weekly, and monthly without dates 455 | const segmentedFuturePrices = { 456 | daily: futurePrices.map(futurePrice => futurePrice.price), 457 | weekly: futurePrices.filter((_, index) => index % 7 === 0).map(futurePrice => futurePrice.price), 458 | monthly: futurePrices.filter((_, index) => index % 30 === 0).map(futurePrice => futurePrice.price) 459 | }; 460 | 461 | // Extract volume data from the results 462 | const volumes = results.map(row => parseFloat(row["Volume"])); 463 | 464 | // Get the last 30 closing prices for recent price analysis 465 | const recentPrices = closePrices.slice(-30); 466 | // Calculate the average of recent prices 467 | const avgRecentPrice = math.mean(recentPrices); 468 | // Get the most recent (current) price 469 | const currentPrice = closePrices[closePrices.length - 1]; 470 | // Check if the current price is higher than the recent average 471 | const isPriceHigherThanAverage = currentPrice > avgRecentPrice; 472 | // Get the last 30 volume data points for recent volume analysis 473 | const recentVolumes = volumes.slice(-30); 474 | // Calculate the average of recent volumes 475 | const avgRecentVolume = math.mean(recentVolumes); 476 | // Get the most recent (current) volume 477 | const currentVolume = volumes[volumes.length - 1]; 478 | // Check if the current volume is higher than the recent average 479 | const isVolumeHigherThanAverage = currentVolume > avgRecentVolume; 480 | // Calculate the ratio of current volume to average volume: This helps in understanding if the current trading activity is higher or lower than usual 481 | const volumeRatio = currentVolume / avgRecentVolume; 482 | 483 | let newsSentiment = null; 484 | let fearAndGreedData = null; 485 | 486 | if (NEWS_API_KEY) { 487 | newsSentiment = await fetchNewsSentiment(); 488 | fearAndGreedData = await fetchFearAndGreedIndex(); 489 | } 490 | 491 | const { recommendation, explanation, reasons } = generateDetailedRecommendation( 492 | currentPrice, 493 | avgRecentPrice, 494 | rsi, 495 | newsSentiment, 496 | fearAndGreedData, 497 | volumeRatio 498 | ); 499 | 500 | const combinedMood = getCombinedMarketMood(newsSentiment, fearAndGreedData); 501 | 502 | const formatPrice = (price) => `$${price.toLocaleString()}`; 503 | const formatVolume = (volume) => `$${(volume / 1e9).toFixed(2)} billion`; 504 | 505 | // Structure the results 506 | const volatilityData = { 507 | // Information about the analysis period 508 | analysisPeriod: { 509 | startDate: startDate, 510 | endDate: endDate, 511 | totalDays: numDays, 512 | totalMonths: numMonths, 513 | totalYears: numYears 514 | }, 515 | // Current market metrics 516 | currentMetrics: { 517 | // Price information 518 | price: { 519 | current: formatPrice(currentPrice), 520 | averageRecent: formatPrice(avgRecentPrice), 521 | comparison: isPriceHigherThanAverage 522 | ? "The current price is higher than the recent average." 523 | : "The current price is lower than the recent average." 524 | }, 525 | // Volume information 526 | volume: { 527 | current: formatVolume(currentVolume), 528 | averageRecent: formatVolume(avgRecentVolume), 529 | comparison: isVolumeHigherThanAverage 530 | ? "Trading volume is higher than usual." 531 | : "Trading volume is lower than usual." 532 | }, 533 | // Relative Strength Index (RSI) 534 | rsi: { 535 | value: rsi.toFixed(2), 536 | interpretation: rsi > 70 ? "Overbought" : rsi < 30 ? "Oversold" : "Neutral" 537 | }, 538 | // Predicted volatility for today 539 | predictedVolatilityToday: predictedVolatilityToday, 540 | // Probabilities of price changes for today 541 | priceChangeProbabilitiesToday: { 542 | increase: probabilityIncrease, 543 | decrease: probabilityDecrease, 544 | noChange: probabilityNoChange 545 | }, 546 | // Actual price movements for today 547 | todayPriceMovements: { 548 | increases: todayIncreases, 549 | decreases: todayDecreases 550 | } 551 | }, 552 | // Sentiment analysis (only included if NEWS_API_KEY is provided) 553 | sentimentAnalysis: NEWS_API_KEY ? { 554 | // News sentiment analysis 555 | newsSentiment: newsSentiment !== null ? { 556 | score: newsSentiment, 557 | interpretation: getMarketSentiment(newsSentiment) 558 | } : "News sentiment data not available.", 559 | // Fear and Greed Index 560 | fearAndGreedIndex: fearAndGreedData ? { 561 | value: fearAndGreedData.value, 562 | classification: fearAndGreedData.valueClassification 563 | } : "Fear and Greed Index data not available.", 564 | // Combined market mood based on news sentiment and Fear and Greed Index 565 | combinedMarketMood: combinedMood 566 | } : {}, 567 | // Volatility metrics for different time periods 568 | volatilityMetrics: { 569 | daily: dailyVolatility, 570 | weekly: weeklyVolatility, 571 | monthly: monthlyVolatility, 572 | annual: annualVolatility 573 | }, 574 | // Significant dates in the analysis period 575 | significantDates: { 576 | maxIncrease: { 577 | date: moment(maxIncrease.date).format("MMM D, YYYY"), 578 | percentage: (maxIncrease.value * 100).toFixed(2) + "%" 579 | }, 580 | maxDecrease: { 581 | date: moment(maxDecrease.date).format("MMM D, YYYY"), 582 | percentage: (maxDecrease.value * -100).toFixed(2) + "%" 583 | }, 584 | highestPrice: { 585 | date: moment(maxPriceDate).format("MMM D, YYYY"), 586 | price: maxPrice.toFixed(2) 587 | }, 588 | lowestPrice: { 589 | date: moment(minPriceDate).format("MMM D, YYYY"), 590 | price: minPrice.toFixed(2) 591 | } 592 | }, 593 | // Future price projections 594 | futurePriceProjections: { 595 | daily: segmentedFuturePrices.daily, 596 | weekly: segmentedFuturePrices.weekly, 597 | monthly: segmentedFuturePrices.monthly, 598 | // Long-term price predictions 599 | longTerm: { 600 | in3Years: { 601 | optimistic: formatPrice(threeYearsPrediction.optimistic), 602 | pessimistic: formatPrice(threeYearsPrediction.pessimistic), 603 | average: formatPrice(threeYearsPrediction.average), 604 | year: threeYearsPrediction.year 605 | }, 606 | in5Years: { 607 | optimistic: formatPrice(fiveYearsPrediction.optimistic), 608 | pessimistic: formatPrice(fiveYearsPrediction.pessimistic), 609 | average: formatPrice(fiveYearsPrediction.average), 610 | year: fiveYearsPrediction.year 611 | }, 612 | in10Years: { 613 | optimistic: formatPrice(tenYearsPrediction.optimistic), 614 | pessimistic: formatPrice(tenYearsPrediction.pessimistic), 615 | average: formatPrice(tenYearsPrediction.average), 616 | year: tenYearsPrediction.year 617 | } 618 | } 619 | }, 620 | // Overall recommendation based on the analysis 621 | recommendation: { 622 | action: recommendation, 623 | explanation: explanation, 624 | factors: reasons 625 | } 626 | }; 627 | 628 | // Log the structured data as JSON 629 | console.log(JSON.stringify(volatilityData, null, 2)); 630 | }; 631 | 632 | /** 633 | * Downloads the Bitcoin price dataset CSV from Yahoo Finance 634 | */ 635 | const downloadDatasetCSV = async () => { 636 | try { 637 | const endDateTimestamp = Math.floor(Date.now() / 1000); 638 | const url = `https://query1.finance.yahoo.com/v7/finance/download/BTC-USD?period1=1410912000&period2=${endDateTimestamp}&frequency=1d`; 639 | const response = await axios.get(url, { responseType: "stream" }); 640 | return new Promise((resolve, reject) => { 641 | response.data.pipe(fs.createWriteStream(filePath)) 642 | .on("finish", () => resolve()) 643 | .on("error", (error) => reject(error)); 644 | }); 645 | } catch (error) { 646 | console.error("Error downloading the CSV file:", error); 647 | throw error; 648 | } 649 | }; 650 | 651 | // Execute the download and analysis 652 | try { 653 | await downloadDatasetCSV(); 654 | const csvResults = await readCSV(filePath); 655 | performAnalysis(csvResults); 656 | } catch (error) { 657 | console.error("Error in download or analysis process:", error); 658 | } 659 | } 660 | }; 661 | 662 | // Call the analysis function with the CSV file path 663 | bitcoinAnalysis.analyze({ 664 | PATH: "YahooFinance/BTC-USD.csv", // This is the path CSV of the Bitcoin price dataset 665 | NEWS_API_KEY: "b0e29658504e4178bdd5ae90e075f7ef" // News API key: This Api is functional, but in case it doesn't work for you, I recommend creating one, just by entering the https://newsapi.org/ site, it is totally free. 666 | }); --------------------------------------------------------------------------------