index.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Financial Data Charts</title>
  7. <script src="https://unpkg.com/lightweight-charts@4.1.0/dist/lightweight-charts.standalone.production.mjs"></script>
  8. <style>
  9. body {
  10. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
  11. margin: 0;
  12. padding: 20px;
  13. background-color: #f5f5f5;
  14. }
  15. .container {
  16. max-width: 1200px;
  17. margin: 0 auto;
  18. }
  19. .controls {
  20. background: white;
  21. padding: 20px;
  22. border-radius: 8px;
  23. margin-bottom: 20px;
  24. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  25. }
  26. .chart-container {
  27. background: white;
  28. border-radius: 8px;
  29. padding: 20px;
  30. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  31. height: 600px;
  32. }
  33. .control-group {
  34. margin-bottom: 15px;
  35. }
  36. label {
  37. display: inline-block;
  38. width: 100px;
  39. font-weight: 500;
  40. }
  41. select, input, button {
  42. padding: 8px 12px;
  43. border: 1px solid #ddd;
  44. border-radius: 4px;
  45. margin-right: 10px;
  46. }
  47. button {
  48. background: #007bff;
  49. color: white;
  50. cursor: pointer;
  51. border: none;
  52. }
  53. button:hover {
  54. background: #0056b3;
  55. }
  56. .stats {
  57. background: #e9ecef;
  58. padding: 10px;
  59. border-radius: 4px;
  60. margin-top: 10px;
  61. font-family: monospace;
  62. }
  63. </style>
  64. </head>
  65. <body>
  66. <div class="container">
  67. <h1>Financial Data Charts & API Testing</h1>
  68. <div class="controls">
  69. <div class="control-group">
  70. <label for="symbol">Symbol:</label>
  71. <select id="symbol">
  72. <option value="EURUSD">EURUSD</option>
  73. <option value="GBPUSD">GBPUSD</option>
  74. <option value="USDJPY">USDJPY</option>
  75. </select>
  76. <label for="timeframe">Timeframe:</label>
  77. <select id="timeframe">
  78. <option value="M1">M1</option>
  79. <option value="M5">M5</option>
  80. <option value="M15">M15</option>
  81. <option value="H1">H1</option>
  82. <option value="H4">H4</option>
  83. <option value="D1">D1</option>
  84. </select>
  85. <label for="limit">Candles:</label>
  86. <input type="number" id="limit" value="100" min="10" max="1000">
  87. <button onclick="loadChart()">Load Chart</button>
  88. <button onclick="testCandles()">Test Candles</button>
  89. </div>
  90. <div class="control-group">
  91. <button onclick="startRealTime()">Start Real-time</button>
  92. <button onclick="stopRealTime()">Stop Real-time</button>
  93. <button onclick="validateCandles()">Validate Candles</button>
  94. </div>
  95. <div class="stats" id="stats">
  96. Ready to load chart data...
  97. </div>
  98. </div>
  99. <div class="chart-container">
  100. <div id="chart"></div>
  101. </div>
  102. <div style="margin-top: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
  103. <h3>API Testing Results</h3>
  104. <div id="test-results" style="font-family: monospace; white-space: pre-wrap;"></div>
  105. </div>
  106. </div>
  107. <script>
  108. let chart = null;
  109. let candlestickSeries = null;
  110. let realTimeInterval = null;
  111. // Initialize chart
  112. function initChart() {
  113. const chartContainer = document.getElementById('chart');
  114. chart = LightweightCharts.createChart(chartContainer, {
  115. width: chartContainer.clientWidth,
  116. height: 500,
  117. layout: {
  118. background: { color: '#ffffff' },
  119. textColor: '#333',
  120. },
  121. grid: {
  122. vertLines: { color: '#e1e1e1' },
  123. horzLines: { color: '#e1e1e1' },
  124. },
  125. crosshair: {
  126. mode: LightweightCharts.CrosshairMode.Normal,
  127. },
  128. rightPriceScale: {
  129. borderColor: '#485158',
  130. },
  131. timeScale: {
  132. borderColor: '#485158',
  133. timeVisible: true,
  134. secondsVisible: false,
  135. },
  136. });
  137. candlestickSeries = chart.addCandlestickSeries({
  138. upColor: '#26a69a',
  139. downColor: '#ef5350',
  140. borderVisible: false,
  141. wickUpColor: '#26a69a',
  142. wickDownColor: '#ef5350',
  143. });
  144. }
  145. // Load chart data
  146. async function loadChart() {
  147. const symbol = document.getElementById('symbol').value;
  148. const timeframe = document.getElementById('timeframe').value;
  149. const limit = document.getElementById('limit').value;
  150. updateStats(`Loading ${symbol} ${timeframe} candles...`);
  151. try {
  152. const response = await fetch(`/api/prices/historical/${symbol}?timeframe=${timeframe}&limit=${limit}`);
  153. const result = await response.json();
  154. if (result.status === 'success') {
  155. const candles = result.data.map(candle => ({
  156. time: Math.floor(new Date(candle.timestamp).getTime() / 1000),
  157. open: parseFloat(candle.open),
  158. high: parseFloat(candle.high),
  159. low: parseFloat(candle.low),
  160. close: parseFloat(candle.close),
  161. }));
  162. if (!chart) {
  163. initChart();
  164. }
  165. candlestickSeries.setData(candles);
  166. chart.timeScale().fitContent();
  167. updateStats(`Loaded ${candles.length} candles for ${symbol} ${timeframe}`);
  168. } else {
  169. updateStats(`Error: ${result.message}`);
  170. }
  171. } catch (error) {
  172. updateStats(`Error loading chart: ${error.message}`);
  173. }
  174. }
  175. // Test candles for correctness
  176. async function testCandles() {
  177. const symbol = document.getElementById('symbol').value;
  178. const timeframe = document.getElementById('timeframe').value;
  179. const limit = document.getElementById('limit').value;
  180. updateStats(`Testing ${symbol} ${timeframe} candles...`);
  181. try {
  182. const response = await fetch(`/api/prices/historical/${symbol}?timeframe=${timeframe}&limit=${limit}`);
  183. const result = await response.json();
  184. if (result.status === 'success') {
  185. const tests = validateCandleData(result.data);
  186. displayTestResults(tests);
  187. } else {
  188. updateStats(`Error: ${result.message}`);
  189. }
  190. } catch (error) {
  191. updateStats(`Error testing candles: ${error.message}`);
  192. }
  193. }
  194. // Validate candle data
  195. function validateCandleData(candles) {
  196. const tests = {
  197. total: candles.length,
  198. validOHLC: 0,
  199. validPriceLogic: 0,
  200. validTimestamps: 0,
  201. issues: []
  202. };
  203. candles.forEach((candle, index) => {
  204. const open = parseFloat(candle.open);
  205. const high = parseFloat(candle.high);
  206. const low = parseFloat(candle.low);
  207. const close = parseFloat(candle.close);
  208. // Test 1: OHLC values are valid numbers
  209. if (!isNaN(open) && !isNaN(high) && !isNaN(low) && !isNaN(close)) {
  210. tests.validOHLC++;
  211. } else {
  212. tests.issues.push(`Candle ${index}: Invalid OHLC values`);
  213. }
  214. // Test 2: Price logic (high >= open, high >= close, low <= open, low <= close)
  215. if (high >= Math.max(open, close) && low <= Math.min(open, close)) {
  216. tests.validPriceLogic++;
  217. } else {
  218. tests.issues.push(`Candle ${index}: Invalid price logic (H: ${high}, L: ${low}, O: ${open}, C: ${close})`);
  219. }
  220. // Test 3: Valid timestamp
  221. const timestamp = new Date(candle.timestamp);
  222. if (!isNaN(timestamp.getTime())) {
  223. tests.validTimestamps++;
  224. } else {
  225. tests.issues.push(`Candle ${index}: Invalid timestamp`);
  226. }
  227. });
  228. return tests;
  229. }
  230. // Display test results
  231. function displayTestResults(tests) {
  232. const resultsDiv = document.getElementById('test-results');
  233. let output = `Candle Validation Results:\n`;
  234. output += `Total Candles: ${tests.total}\n`;
  235. output += `Valid OHLC Values: ${tests.validOHLC}/${tests.total}\n`;
  236. output += `Valid Price Logic: ${tests.validPriceLogic}/${tests.total}\n`;
  237. output += `Valid Timestamps: ${tests.validTimestamps}/${tests.total}\n\n`;
  238. if (tests.issues.length > 0) {
  239. output += `Issues Found:\n`;
  240. tests.issues.slice(0, 10).forEach(issue => {
  241. output += ` - ${issue}\n`;
  242. });
  243. if (tests.issues.length > 10) {
  244. output += ` ... and ${tests.issues.length - 10} more issues\n`;
  245. }
  246. } else {
  247. output += `✅ All candles passed validation!\n`;
  248. }
  249. resultsDiv.textContent = output;
  250. }
  251. // Start real-time updates
  252. function startRealTime() {
  253. if (realTimeInterval) {
  254. clearInterval(realTimeInterval);
  255. }
  256. realTimeInterval = setInterval(async () => {
  257. const symbol = document.getElementById('symbol').value;
  258. try {
  259. const response = await fetch(`/api/prices/current/${symbol}`);
  260. const result = await response.json();
  261. if (result.status === 'success') {
  262. const data = result.data;
  263. const newCandle = {
  264. time: Math.floor(new Date(data.timestamp).getTime() / 1000),
  265. open: parseFloat(data.price),
  266. high: parseFloat(data.high),
  267. low: parseFloat(data.low),
  268. close: parseFloat(data.price),
  269. };
  270. if (candlestickSeries) {
  271. candlestickSeries.update(newCandle);
  272. }
  273. }
  274. } catch (error) {
  275. console.error('Real-time update error:', error);
  276. }
  277. }, 1000);
  278. updateStats('Real-time updates started');
  279. }
  280. // Stop real-time updates
  281. function stopRealTime() {
  282. if (realTimeInterval) {
  283. clearInterval(realTimeInterval);
  284. realTimeInterval = null;
  285. }
  286. updateStats('Real-time updates stopped');
  287. }
  288. // Validate candles via API
  289. async function validateCandles() {
  290. const symbol = document.getElementById('symbol').value;
  291. const timeframe = document.getElementById('timeframe').value;
  292. updateStats(`Validating ${symbol} ${timeframe} candles via API...`);
  293. try {
  294. const response = await fetch(`/api/prices/validate-candles/${symbol}?timeframe=${timeframe}`);
  295. const result = await response.json();
  296. if (result.status === 'success') {
  297. displayTestResults(result.data);
  298. } else {
  299. updateStats(`Validation error: ${result.message}`);
  300. }
  301. } catch (error) {
  302. updateStats(`Error validating candles: ${error.message}`);
  303. }
  304. }
  305. // Update stats display
  306. function updateStats(message) {
  307. document.getElementById('stats').textContent = message;
  308. }
  309. // Initialize on page load
  310. window.addEventListener('load', () => {
  311. // Load initial chart
  312. loadChart();
  313. });
  314. // Handle window resize
  315. window.addEventListener('resize', () => {
  316. if (chart) {
  317. chart.applyOptions({ width: document.getElementById('chart').clientWidth });
  318. }
  319. });
  320. </script>
  321. </body>
  322. </html>