Exchange Rate Forecasting Model

Exchange Rate Forecasting Model

${message}

`; document.body.appendChild(messageBox); } // Ensure all JavaScript code executes after the DOM is fully loaded. document.addEventListener('DOMContentLoaded', function() { // --- DOM Element References --- const currencyPairSelect = document.getElementById('currencyPairSelect'); const historicalPeriodSelect = document.getElementById('historicalPeriodSelect'); const forecastPeriodInput = document.getElementById('forecastPeriodInput'); const maPeriodInput = document.getElementById('maPeriodInput'); const generateForecastBtn = document.getElementById('generateForecastBtn'); const downloadPdfBtn = document.getElementById('downloadPdfBtn'); const resultsSection = document.getElementById('resultsSection'); const displayHistoricalPeriod = document.getElementById('displayHistoricalPeriod'); const displayForecastPeriod = document.getElementById('displayForecastPeriod'); const forecastSummary = document.getElementById('forecastSummary'); const resultCurrencyPair = document.getElementById('resultCurrencyPair'); const buttonText = document.getElementById('buttonText'); const loader = document.getElementById('loader'); const forecastingChartCanvas = document.getElementById('forecastingChart'); let currentChart = null; // To hold the Chart.js instance // --- Mock Data Generation --- // Generates plausible, but random, historical closing prices for a given period. // This is for demonstration purposes as real-time/historical data APIs are not integrated directly. function generateMockHistoricalData(days) { const data = []; let lastClose = Math.random() * (1.2 - 0.8) + 0.8; // Starting price for EUR/USD like range for (let i = days - 1; i >= 0; i--) { const date = new Date(); date.setDate(date.getDate() - i); const formattedDate = formatDate(date); // Use the globally available formatDate const open = lastClose + (Math.random() - 0.5) * 0.005; const close = open + (Math.random() - 0.5) * 0.01; // Ensure prices are positive and somewhat realistic for forex lastClose = Math.max(0.0001, parseFloat(close.toFixed(5))); data.push({ date: formattedDate, close: lastClose }); } return data; } // --- Forecasting Logic (Simplified Trend Extrapolation based on Moving Average) --- /** * Calculates a Simple Moving Average (SMA). * @param {Array} data - Array of objects with a 'close' property. * @param {number} period - The SMA period. * @returns {Array} Array of SMA values. */ function calculateSMA(data, period) { const sma = []; for (let i = 0; i < data.length; i++) { if (i >= period - 1) { const sum = data.slice(i - period + 1, i + 1).reduce((acc, val) => acc + val.close, 0); sma.push(sum / period); } else { sma.push(null); // Or some placeholder until enough data } } return sma; } /** * Generates a simplified forecast based on the last known trend of a moving average. * This is a highly simplified model for demonstration only. * @param {Array} historicalData - Array of historical data with 'date' and 'close'. * @param {number} forecastDays - Number of days to forecast. * @param {number} maPeriod - Moving Average period for trend detection. * @returns {Array} Array of forecasted data points {date, close}. */ function generateForecast(historicalData, forecastDays, maPeriod) { if (historicalData.length < maPeriod) { // Not enough data for MA, simply extrapolate last known close const lastClose = historicalData[historicalData.length - 1].close; const forecast = []; for (let i = 1; i <= forecastDays; i++) { const forecastDate = new Date(historicalData[historicalData.length - 1].date); forecastDate.setDate(forecastDate.getDate() + i); forecast.push({ date: formatDate(forecastDate), // Use the globally available formatDate close: lastClose + (Math.random() - 0.5) * 0.001 // Add minor random drift }); } return forecast; } const sma = calculateSMA(historicalData, maPeriod); const lastValidSmaIndex = sma.lastIndexOf(sma.filter(val => val !== null).pop()); if (lastValidSmaIndex < 1 || sma[lastValidSmaIndex - 1] === null) { // If not enough valid SMA points for a trend, use the last close const lastClose = historicalData[historicalData.length - 1].close; const forecast = []; for (let i = 1; i <= forecastDays; i++) { const forecastDate = new Date(historicalData[historicalData.length - 1].date); forecastDate.setDate(forecastDate.getDate() + i); forecast.push({ date: formatDate(forecastDate), // Use the globally available formatDate close: lastClose + (Math.random() - 0.5) * 0.001 }); } return forecast; } const lastSmaValue = sma[lastValidSmaIndex]; const prevSmaValue = sma[lastValidSmaIndex - 1]; const smaTrendPerDay = (lastSmaValue - prevSmaValue) / maPeriod; // Rough daily trend const forecast = []; let currentForecastPrice = historicalData[historicalData.length - 1].close; // Start from last actual close for (let i = 1; i <= forecastDays; i++) { const forecastDate = new Date(historicalData[historicalData.length - 1].date); forecastDate.setDate(forecastDate.getDate() + i); // Extrapolate based on the calculated trend, with some random noise currentForecastPrice += smaTrendPerDay * 0.5 + (Math.random() - 0.5) * 0.0005; // Reduced impact of trend, added noise forecast.push({ date: formatDate(forecastDate), // Use the globally available formatDate close: parseFloat(currentForecastPrice.toFixed(5)) }); } return forecast; } /** * Generates a textual summary of the forecast. * @param {Array} historicalData - The historical data. * @param {Array} forecastData - The generated forecast data. * @returns {string} A summary text. */ function generateForecastSummary(historicalData, forecastData) { if (!historicalData || historicalData.length === 0 || !forecastData || forecastData.length === 0) { return "No data available for forecasting."; } const lastHistoricalPrice = historicalData[historicalData.length - 1].close; const firstForecastPrice = forecastData[0].close; const lastForecastPrice = forecastData[forecastData.length - 1].close; let trend = 'stable'; let direction = ''; if (lastForecastPrice > lastHistoricalPrice * 1.001) { // 0.1% increase trend = 'upward'; direction = 'appreciate'; } else if (lastForecastPrice < lastHistoricalPrice * 0.999) { // 0.1% decrease trend = 'downward'; direction = 'depreciate'; } if (direction) { return `The model forecasts an overall ${trend} trend, with the currency expected to ${direction} from $${lastHistoricalPrice.toFixed(5)} to approximately $${lastForecastPrice.toFixed(5)} over the forecast period.`; } else { return `The model forecasts a relatively stable trend for the currency, with minimal change from $${lastHistoricalPrice.toFixed(5)} to approximately $${lastForecastPrice.toFixed(5)} over the forecast period.`; } } /** * Renders or updates the Chart.js chart with historical and forecasted prices. * @param {string} currencyPair - The selected currency pair. * @param {Array} historicalData - Array of historical data {date, close}. * @param {Array} forecastData - Array of forecasted data {date, close}. */ function renderChart(currencyPair, historicalData, forecastData) { if (!forecastingChartCanvas) { console.error("Chart canvas not found."); return; } const allLabels = historicalData.map(d => d.date); const historicalPrices = historicalData.map(d => d.close); // Prepare forecast data for chart: fill leading nulls to align with historical data const forecastLabels = forecastData.map(d => d.date); const forecastPrices = Array(historicalData.length - 1).fill(null) .concat(historicalData[historicalData.length - 1].close) // Start forecast from last historical point .concat(forecastData.map(d => d.close)); // Combine labels for continuous chart const combinedLabels = [...allLabels, ...forecastLabels.slice(0)]; // forecastLabels already start after historical if (currentChart) { currentChart.destroy(); // Destroy previous chart instance } currentChart = new Chart(forecastingChartCanvas, { type: 'line', data: { labels: combinedLabels, datasets: [ { label: `${currencyPair} Historical Price`, data: historicalPrices, borderColor: '#3b82f6', // Blue for historical backgroundColor: 'rgba(59, 130, 246, 0.2)', fill: false, tension: 0.1, pointRadius: 0, borderWidth: 2 }, { label: `${currencyPair} Forecast`, data: forecastPrices, borderColor: '#ef4444', // Red for forecast backgroundColor: 'rgba(239, 68, 68, 0.2)', fill: false, tension: 0.1, pointRadius: 0, borderWidth: 2, borderDash: [5, 5] // Dashed line for forecast } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: `${currencyPair} Historical & Forecasted Exchange Rate`, font: { size: 16 } }, legend: { display: true, position: 'top' } }, scales: { x: { title: { display: true, text: 'Date' }, ticks: { autoSkip: true, maxTicksLimit: 15 } }, y: { title: { display: true, text: 'Price ($)' } } } } }); } /** * Handles the generation and display of the forecast. */ async function handleGenerateForecast() { // Input validation if (!currencyPairSelect || !historicalPeriodSelect || !forecastPeriodInput || !maPeriodInput || !resultsSection) { console.error("One or more required DOM elements are missing."); return; } const selectedPair = currencyPairSelect.value; const historicalDays = parseInt(historicalPeriodSelect.value); const forecastDays = parseInt(forecastPeriodInput.value); const maPeriod = parseInt(maPeriodInput.value); if (isNaN(forecastDays) || forecastDays <= 0) { showMessageBox('Please enter a valid positive number for Forecast Period.'); return; } if (isNaN(maPeriod) || maPeriod <= 0 || maPeriod >= historicalDays) { showMessageBox('Please enter a valid Moving Average Period (positive, and less than historical data period).'); return; } // Show loading indicator buttonText.classList.add('hidden'); loader.classList.remove('hidden'); generateForecastBtn.disabled = true; downloadPdfBtn.disabled = true; // Disable PDF button during calculation // Simulate async data fetching and processing await new Promise(resolve => setTimeout(resolve, 1000)); const historicalData = generateMockHistoricalData(historicalDays); const forecastData = generateForecast(historicalData, forecastDays, maPeriod); // Update UI with results resultCurrencyPair.textContent = selectedPair; displayHistoricalPeriod.textContent = `${historicalDays} Days`; displayForecastPeriod.textContent = `${forecastDays} Days`; forecastSummary.textContent = generateForecastSummary(historicalData, forecastData); // Show results section resultsSection.classList.remove('hidden'); // Render the chart renderChart(selectedPair, historicalData, forecastData); // Hide loading indicator buttonText.classList.remove('hidden'); loader.classList.add('hidden'); generateForecastBtn.disabled = false; downloadPdfBtn.disabled = false; // Re-enable PDF button } /** * Handles the PDF download functionality. */ async function handleDownloadPdf() { // Ensure jsPDF and html2canvas are loaded if (typeof window.jspdf === 'undefined' || typeof window.html2canvas === 'undefined') { console.error("jsPDF or html2canvas library not loaded."); showMessageBox("PDF generation libraries are not loaded correctly. Please try again."); return; } if (resultsSection.classList.contains('hidden')) { showMessageBox("Please generate a forecast first to create a PDF report."); return; } downloadPdfBtn.disabled = true; // Disable button during PDF generation const { jsPDF } = window.jspdf; const pdf = new jsPDF('p', 'mm', 'a4'); const margin = 10; let yOffset = margin; const lineHeight = 7; // Add Title pdf.setFontSize(22); pdf.setTextColor(49, 107, 219); // A shade of blue pdf.text("Exchange Rate Forecast Report", 105, yOffset, null, null, "center"); yOffset += 15; // Add Input Parameters pdf.setFontSize(12); pdf.setTextColor(55, 65, 81); // Gray pdf.text(`Currency Pair: ${currencyPairSelect.value}`, margin, yOffset); yOffset += lineHeight; pdf.text(`Historical Period: ${historicalPeriodSelect.value} Days`, margin, yOffset); yOffset += lineHeight; pdf.text(`Forecast Period: ${forecastPeriodInput.value} Days`, margin, yOffset); yOffset += lineHeight; pdf.text(`Moving Average Period: ${maPeriodInput.value} Days`, margin, yOffset); yOffset += lineHeight * 2; // Add Forecast Summary pdf.setFontSize(14); pdf.setTextColor(55, 65, 81); pdf.text("Forecast Summary:", margin, yOffset); yOffset += lineHeight; pdf.setFontSize(12); pdf.setTextColor(31, 41, 55); // Darker gray const summaryLines = pdf.splitTextToSize(forecastSummary.textContent, pdf.internal.pageSize.getWidth() - 2 * margin); pdf.text(summaryLines, margin + 5, yOffset); yOffset += (summaryLines.length * lineHeight) + lineHeight; // Capture the chart as an image const chartContainer = document.querySelector('.chart-container'); if (chartContainer) { try { const canvas = await html2canvas(chartContainer, { scale: 2, logging: false, useCORS: true, backgroundColor: '#ffffff' }); // Increased scale for better resolution const imgData = canvas.toDataURL('image/png'); const imgWidth = 180; // Adjust as needed const imgHeight = canvas.height * imgWidth / canvas.width; // Check if new page is needed if (yOffset + imgHeight + margin > pdf.internal.pageSize.getHeight()) { pdf.addPage(); yOffset = margin; } pdf.text("Historical Data & Forecast Chart:", margin, yOffset); yOffset += lineHeight; pdf.addImage(imgData, 'PNG', margin, yOffset, imgWidth, imgHeight); yOffset += imgHeight + lineHeight; } catch (error) { console.error("Error capturing chart for PDF:", error); showMessageBox("Error generating chart image for PDF. Please try again."); } } // Add disclaimer pdf.setFontSize(8); pdf.setTextColor(107, 114, 128); const disclaimerText = "*Disclaimer: This tool uses a simplified model for demonstration purposes only. Real-world exchange rate forecasting is highly complex and involves advanced statistical methods, fundamental analysis, and is subject to significant market uncertainties. Past performance or simulated forecasts do not guarantee future results."; const disclaimerLines = pdf.splitTextToSize(disclaimerText, pdf.internal.pageSize.getWidth() - 2 * margin); pdf.text(disclaimerLines, margin, yOffset); pdf.save('Exchange_Rate_Forecast_Report.pdf'); downloadPdfBtn.disabled = false; // Re-enable button after generation } // --- Event Listeners --- if (generateForecastBtn) { generateForecastBtn.addEventListener('click', handleGenerateForecast); } else { console.error("Generate Forecast Button not found."); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', handleDownloadPdf); } else { console.error("Download PDF Button not found."); } // Initial state: hide results section resultsSection.classList.add('hidden'); });
Scroll to Top