| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Financial Data Charts</title>
- <script src="https://unpkg.com/lightweight-charts@4.1.0/dist/lightweight-charts.standalone.production.mjs"></script>
- <style>
- body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
- margin: 0;
- padding: 20px;
- background-color: #f5f5f5;
- }
- .container {
- max-width: 1200px;
- margin: 0 auto;
- }
- .controls {
- background: white;
- padding: 20px;
- border-radius: 8px;
- margin-bottom: 20px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- }
- .chart-container {
- background: white;
- border-radius: 8px;
- padding: 20px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- height: 600px;
- }
- .control-group {
- margin-bottom: 15px;
- }
- label {
- display: inline-block;
- width: 100px;
- font-weight: 500;
- }
- select, input, button {
- padding: 8px 12px;
- border: 1px solid #ddd;
- border-radius: 4px;
- margin-right: 10px;
- }
- button {
- background: #007bff;
- color: white;
- cursor: pointer;
- border: none;
- }
- button:hover {
- background: #0056b3;
- }
- .stats {
- background: #e9ecef;
- padding: 10px;
- border-radius: 4px;
- margin-top: 10px;
- font-family: monospace;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>Financial Data Charts & API Testing</h1>
- <div class="controls">
- <div class="control-group">
- <label for="symbol">Symbol:</label>
- <select id="symbol">
- <option value="EURUSD">EURUSD</option>
- <option value="GBPUSD">GBPUSD</option>
- <option value="USDJPY">USDJPY</option>
- </select>
- <label for="timeframe">Timeframe:</label>
- <select id="timeframe">
- <option value="M1">M1</option>
- <option value="M5">M5</option>
- <option value="M15">M15</option>
- <option value="H1">H1</option>
- <option value="H4">H4</option>
- <option value="D1">D1</option>
- </select>
- <label for="limit">Candles:</label>
- <input type="number" id="limit" value="100" min="10" max="1000">
- <button onclick="loadChart()">Load Chart</button>
- <button onclick="testCandles()">Test Candles</button>
- </div>
- <div class="control-group">
- <button onclick="startRealTime()">Start Real-time</button>
- <button onclick="stopRealTime()">Stop Real-time</button>
- <button onclick="validateCandles()">Validate Candles</button>
- </div>
- <div class="stats" id="stats">
- Ready to load chart data...
- </div>
- </div>
- <div class="chart-container">
- <div id="chart"></div>
- </div>
- <div style="margin-top: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
- <h3>API Testing Results</h3>
- <div id="test-results" style="font-family: monospace; white-space: pre-wrap;"></div>
- </div>
- </div>
- <script>
- let chart = null;
- let candlestickSeries = null;
- let realTimeInterval = null;
- // Initialize chart
- function initChart() {
- const chartContainer = document.getElementById('chart');
- chart = LightweightCharts.createChart(chartContainer, {
- width: chartContainer.clientWidth,
- height: 500,
- layout: {
- background: { color: '#ffffff' },
- textColor: '#333',
- },
- grid: {
- vertLines: { color: '#e1e1e1' },
- horzLines: { color: '#e1e1e1' },
- },
- crosshair: {
- mode: LightweightCharts.CrosshairMode.Normal,
- },
- rightPriceScale: {
- borderColor: '#485158',
- },
- timeScale: {
- borderColor: '#485158',
- timeVisible: true,
- secondsVisible: false,
- },
- });
- candlestickSeries = chart.addCandlestickSeries({
- upColor: '#26a69a',
- downColor: '#ef5350',
- borderVisible: false,
- wickUpColor: '#26a69a',
- wickDownColor: '#ef5350',
- });
- }
- // Load chart data
- async function loadChart() {
- const symbol = document.getElementById('symbol').value;
- const timeframe = document.getElementById('timeframe').value;
- const limit = document.getElementById('limit').value;
- updateStats(`Loading ${symbol} ${timeframe} candles...`);
- try {
- const response = await fetch(`/api/prices/historical/${symbol}?timeframe=${timeframe}&limit=${limit}`);
- const result = await response.json();
- if (result.status === 'success') {
- const candles = result.data.map(candle => ({
- time: Math.floor(new Date(candle.timestamp).getTime() / 1000),
- open: parseFloat(candle.open),
- high: parseFloat(candle.high),
- low: parseFloat(candle.low),
- close: parseFloat(candle.close),
- }));
- if (!chart) {
- initChart();
- }
- candlestickSeries.setData(candles);
- chart.timeScale().fitContent();
- updateStats(`Loaded ${candles.length} candles for ${symbol} ${timeframe}`);
- } else {
- updateStats(`Error: ${result.message}`);
- }
- } catch (error) {
- updateStats(`Error loading chart: ${error.message}`);
- }
- }
- // Test candles for correctness
- async function testCandles() {
- const symbol = document.getElementById('symbol').value;
- const timeframe = document.getElementById('timeframe').value;
- const limit = document.getElementById('limit').value;
- updateStats(`Testing ${symbol} ${timeframe} candles...`);
- try {
- const response = await fetch(`/api/prices/historical/${symbol}?timeframe=${timeframe}&limit=${limit}`);
- const result = await response.json();
- if (result.status === 'success') {
- const tests = validateCandleData(result.data);
- displayTestResults(tests);
- } else {
- updateStats(`Error: ${result.message}`);
- }
- } catch (error) {
- updateStats(`Error testing candles: ${error.message}`);
- }
- }
- // Validate candle data
- function validateCandleData(candles) {
- const tests = {
- total: candles.length,
- validOHLC: 0,
- validPriceLogic: 0,
- validTimestamps: 0,
- issues: []
- };
- candles.forEach((candle, index) => {
- const open = parseFloat(candle.open);
- const high = parseFloat(candle.high);
- const low = parseFloat(candle.low);
- const close = parseFloat(candle.close);
- // Test 1: OHLC values are valid numbers
- if (!isNaN(open) && !isNaN(high) && !isNaN(low) && !isNaN(close)) {
- tests.validOHLC++;
- } else {
- tests.issues.push(`Candle ${index}: Invalid OHLC values`);
- }
- // Test 2: Price logic (high >= open, high >= close, low <= open, low <= close)
- if (high >= Math.max(open, close) && low <= Math.min(open, close)) {
- tests.validPriceLogic++;
- } else {
- tests.issues.push(`Candle ${index}: Invalid price logic (H: ${high}, L: ${low}, O: ${open}, C: ${close})`);
- }
- // Test 3: Valid timestamp
- const timestamp = new Date(candle.timestamp);
- if (!isNaN(timestamp.getTime())) {
- tests.validTimestamps++;
- } else {
- tests.issues.push(`Candle ${index}: Invalid timestamp`);
- }
- });
- return tests;
- }
- // Display test results
- function displayTestResults(tests) {
- const resultsDiv = document.getElementById('test-results');
- let output = `Candle Validation Results:\n`;
- output += `Total Candles: ${tests.total}\n`;
- output += `Valid OHLC Values: ${tests.validOHLC}/${tests.total}\n`;
- output += `Valid Price Logic: ${tests.validPriceLogic}/${tests.total}\n`;
- output += `Valid Timestamps: ${tests.validTimestamps}/${tests.total}\n\n`;
- if (tests.issues.length > 0) {
- output += `Issues Found:\n`;
- tests.issues.slice(0, 10).forEach(issue => {
- output += ` - ${issue}\n`;
- });
- if (tests.issues.length > 10) {
- output += ` ... and ${tests.issues.length - 10} more issues\n`;
- }
- } else {
- output += `✅ All candles passed validation!\n`;
- }
- resultsDiv.textContent = output;
- }
- // Start real-time updates
- function startRealTime() {
- if (realTimeInterval) {
- clearInterval(realTimeInterval);
- }
- realTimeInterval = setInterval(async () => {
- const symbol = document.getElementById('symbol').value;
- try {
- const response = await fetch(`/api/prices/current/${symbol}`);
- const result = await response.json();
- if (result.status === 'success') {
- const data = result.data;
- const newCandle = {
- time: Math.floor(new Date(data.timestamp).getTime() / 1000),
- open: parseFloat(data.price),
- high: parseFloat(data.high),
- low: parseFloat(data.low),
- close: parseFloat(data.price),
- };
- if (candlestickSeries) {
- candlestickSeries.update(newCandle);
- }
- }
- } catch (error) {
- console.error('Real-time update error:', error);
- }
- }, 1000);
- updateStats('Real-time updates started');
- }
- // Stop real-time updates
- function stopRealTime() {
- if (realTimeInterval) {
- clearInterval(realTimeInterval);
- realTimeInterval = null;
- }
- updateStats('Real-time updates stopped');
- }
- // Validate candles via API
- async function validateCandles() {
- const symbol = document.getElementById('symbol').value;
- const timeframe = document.getElementById('timeframe').value;
- updateStats(`Validating ${symbol} ${timeframe} candles via API...`);
- try {
- const response = await fetch(`/api/prices/validate-candles/${symbol}?timeframe=${timeframe}`);
- const result = await response.json();
- if (result.status === 'success') {
- displayTestResults(result.data);
- } else {
- updateStats(`Validation error: ${result.message}`);
- }
- } catch (error) {
- updateStats(`Error validating candles: ${error.message}`);
- }
- }
- // Update stats display
- function updateStats(message) {
- document.getElementById('stats').textContent = message;
- }
- // Initialize on page load
- window.addEventListener('load', () => {
- // Load initial chart
- loadChart();
- });
- // Handle window resize
- window.addEventListener('resize', () => {
- if (chart) {
- chart.applyOptions({ width: document.getElementById('chart').clientWidth });
- }
- });
- </script>
- </body>
- </html>
|