You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1700 lines
62 KiB

1 month ago
  1. <!DOCTYPE html>
  2. <html lang = "zh-CN">
  3. <head>
  4. <meta charset = "UTF-8">
  5. <meta name = "viewport" content = "width = device-width, initial-scale = 1.0">
  6. <title>Kronos Financial Prediction Web UI</title>
  7. {# <script src = "https://cdn.plot.ly/plotly-latest.min.js"></script>#}
  8. <script src = "https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
  9. <script src = "https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  10. <style>
  11. * {
  12. margin: 0;
  13. padding: 0;
  14. box-sizing: border-box;
  15. }
  16. body {
  17. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  18. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  19. min-height: 100vh;
  20. color: #333;
  21. }
  22. .container {
  23. max-width: 1400px;
  24. margin: 0 auto;
  25. padding: 20px;
  26. }
  27. .header {
  28. text-align: center;
  29. margin-bottom: 30px;
  30. color: white;
  31. }
  32. .header h1 {
  33. font-size: 2.5rem;
  34. margin-bottom: 10px;
  35. text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
  36. }
  37. .header p {
  38. font-size: 1.1rem;
  39. opacity: 0.9;
  40. }
  41. .main-content {
  42. display: grid;
  43. grid-template-columns: 1fr 2fr;
  44. gap: 30px;
  45. margin-bottom: 30px;
  46. }
  47. .control-panel {
  48. background: white;
  49. border-radius: 15px;
  50. padding: 25px;
  51. box-shadow: 0 10px 30px rgba(0,0,0,0.2);
  52. height: fit-content;
  53. }
  54. .control-panel h2 {
  55. color: #4a5568;
  56. margin-bottom: 20px;
  57. font-size: 1.5rem;
  58. border-bottom: 2px solid #e2e8f0;
  59. padding-bottom: 10px;
  60. }
  61. .form-group {
  62. margin-bottom: 20px;
  63. }
  64. .form-group label {
  65. display: block;
  66. margin-bottom: 8px;
  67. font-weight: 600;
  68. color: #4a5568;
  69. }
  70. .form-group select,
  71. .form-group input {
  72. width: 100%;
  73. padding: 12px;
  74. border: 2px solid #e2e8f0;
  75. border-radius: 8px;
  76. font-size: 14px;
  77. transition: border-color 0.3s ease;
  78. }
  79. .form-group select:focus,
  80. .form-group input:focus {
  81. outline: none;
  82. border-color: #667eea;
  83. }
  84. /* Prediction quality parameter styles */
  85. .form-group input[type="range"] {
  86. width: 70%;
  87. margin-right: 10px;
  88. }
  89. .form-group input[type="number"] {
  90. width: 100%;
  91. }
  92. .form-group span {
  93. display: inline-block;
  94. min-width: 40px;
  95. font-weight: 600;
  96. color: #667eea;
  97. }
  98. .form-text {
  99. font-size: 12px;
  100. color: #718096;
  101. margin-top: 5px;
  102. }
  103. .btn {
  104. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  105. color: white;
  106. border: none;
  107. padding: 12px 24px;
  108. border-radius: 8px;
  109. font-size: 16px;
  110. font-weight: 600;
  111. cursor: pointer;
  112. transition: transform 0.2s ease, box-shadow 0.2s ease;
  113. width: 100%;
  114. margin-bottom: 10px;
  115. }
  116. .btn:hover {
  117. transform: translateY(-2px);
  118. box-shadow: 0 5px 15px rgba(0,0,0,0.2);
  119. }
  120. .btn:disabled {
  121. opacity: 0.6;
  122. cursor: not-allowed;
  123. transform: none;
  124. }
  125. .btn-secondary {
  126. background: linear-gradient(135deg, #718096 0%, #4a5568 100%);
  127. }
  128. .btn-success {
  129. background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
  130. }
  131. .btn-warning {
  132. background: linear-gradient(135deg, #ffc19d 0%, #ffc19d 100%);
  133. }
  134. .chart-grid {
  135. display: grid;
  136. grid-template-columns: 1fr auto;
  137. gap: 10px;
  138. align-items: center;
  139. }
  140. .status {
  141. padding: 15px;
  142. border-radius: 8px;
  143. margin-bottom: 20px;
  144. font-weight: 500;
  145. }
  146. .indicator-status {
  147. padding: 15px;
  148. border-radius: 8px;
  149. margin-bottom: 20px;
  150. font-weight: 500;
  151. }
  152. .status.success {
  153. background: #c6f6d5;
  154. color: #22543d;
  155. border: 1px solid #9ae6b4;
  156. }
  157. .status.error {
  158. background: #fed7d7;
  159. color: #742a2a;
  160. border: 1px solid #feb2b2;
  161. }
  162. .status.info {
  163. background: #bee3f8;
  164. color: #2a4365;
  165. border: 1px solid #90cdf4;
  166. }
  167. .status.warning {
  168. background: #fef5e7;
  169. color: #744210;
  170. border: 1px solid #fbd38d;
  171. }
  172. .chart-container {
  173. background: white;
  174. border-radius: 15px;
  175. padding: 25px;
  176. box-shadow: 0 10px 30px rgba(0,0,0,0.2);
  177. }
  178. .chart-container h2 {
  179. color: #4a5568;
  180. margin-bottom: 20px;
  181. font-size: 1.5rem;
  182. border-bottom: 2px solid #e2e8f0;
  183. padding-bottom: 10px;
  184. }
  185. #chart {
  186. width: 100%;
  187. height: 600px;
  188. }
  189. .data-info {
  190. background: #f7fafc;
  191. border: 1px solid #e2e8f0;
  192. border-radius: 8px;
  193. padding: 15px;
  194. margin-bottom: 20px;
  195. }
  196. .data-info h3 {
  197. color: #4a5568;
  198. margin-bottom: 10px;
  199. font-size: 1.1rem;
  200. }
  201. .data-info p {
  202. margin-bottom: 5px;
  203. color: #4a5568;
  204. }
  205. .data-info strong {
  206. color: #2d3748;
  207. }
  208. /* Time window selector styles */
  209. .time-window-container {
  210. background: #f7fafc;
  211. border: 1px solid #e2e8f0;
  212. border-radius: 8px;
  213. padding: 20px;
  214. margin-bottom: 20px;
  215. }
  216. .time-window-container h3 {
  217. color: #4a5568;
  218. margin-bottom: 15px;
  219. font-size: 1.1rem;
  220. }
  221. .time-window-info {
  222. display: flex;
  223. justify-content: space-between;
  224. margin-bottom: 15px;
  225. font-size: 12px;
  226. color: #666;
  227. }
  228. .time-window-slider {
  229. position: relative;
  230. margin-bottom: 10px;
  231. }
  232. .slider-track {
  233. position: relative;
  234. height: 6px;
  235. background: #e2e8f0;
  236. border-radius: 3px;
  237. cursor: pointer;
  238. }
  239. .slider-handle {
  240. position: absolute;
  241. top: -7px;
  242. width: 20px;
  243. height: 20px;
  244. background: #667eea;
  245. border-radius: 50%;
  246. cursor: grab;
  247. border: 2px solid white;
  248. box-shadow: 0 2px 4px rgba(0,0,0,0.2);
  249. z-index: 10;
  250. }
  251. .slider-handle:hover {
  252. background: #5a67d8;
  253. transform: scale(1.1);
  254. }
  255. .slider-handle:active {
  256. cursor: grabbing;
  257. }
  258. .slider-selection {
  259. position: absolute;
  260. height: 6px;
  261. background: #48bb78;
  262. border-radius: 3px;
  263. top: 0;
  264. }
  265. .slider-labels {
  266. display: flex;
  267. justify-content: space-between;
  268. font-size: 11px;
  269. color: #999;
  270. margin-top: 5px;
  271. }
  272. /* Comparison analysis styles */
  273. .comparison-section {
  274. background: #f7fafc;
  275. border: 1px solid #e2e8f0;
  276. border-radius: 8px;
  277. padding: 20px;
  278. margin-top: 20px;
  279. }
  280. .comparison-section h3 {
  281. color: #4a5568;
  282. margin-bottom: 15px;
  283. font-size: 1.1rem;
  284. }
  285. .comparison-info {
  286. background: white;
  287. border: 1px solid #e2e8f0;
  288. border-radius: 6px;
  289. padding: 15px;
  290. margin-bottom: 15px;
  291. }
  292. .comparison-table {
  293. width: 100%;
  294. border-collapse: collapse;
  295. margin-top: 15px;
  296. }
  297. .comparison-table th,
  298. .comparison-table td {
  299. border: 1px solid #e2e8f0;
  300. padding: 8px;
  301. text-align: center;
  302. font-size: 12px;
  303. }
  304. .comparison-table th {
  305. background: #f7fafc;
  306. font-weight: 600;
  307. }
  308. .error-stats {
  309. display: grid;
  310. grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  311. gap: 15px;
  312. margin-top: 15px;
  313. }
  314. .error-stat {
  315. background: white;
  316. border: 1px solid #e2e8f0;
  317. border-radius: 6px;
  318. padding: 15px;
  319. text-align: center;
  320. }
  321. .error-stat h4 {
  322. color: #4a5568;
  323. margin-bottom: 5px;
  324. font-size: 0.9rem;
  325. }
  326. .error-stat .value {
  327. font-size: 1.5rem;
  328. font-weight: 600;
  329. color: #667eea;
  330. }
  331. .error-stat .unit {
  332. font-size: 0.8rem;
  333. color: #718096;
  334. }
  335. .loading {
  336. display: none;
  337. text-align: center;
  338. padding: 20px;
  339. }
  340. .loading.show {
  341. display: block;
  342. }
  343. .spinner {
  344. border: 4px solid #f3f3f3;
  345. border-top: 4px solid #667eea;
  346. border-radius: 50%;
  347. width: 40px;
  348. height: 40px;
  349. animation: spin 1s linear infinite;
  350. margin: 0 auto 10px;
  351. }
  352. @keyframes spin {
  353. 0% { transform: rotate(0deg); }
  354. 100% { transform: rotate(360deg); }
  355. }
  356. .indicator-loading {
  357. display: none;
  358. text-align: center;
  359. padding: 20px;
  360. }
  361. .indicator-loading.show {
  362. display: block;
  363. }
  364. .indicator-loading .spinner {
  365. border: 4px solid #f3f3f3;
  366. border-top: 4px solid #6dea66;
  367. border-radius: 50%;
  368. width: 40px;
  369. height: 40px;
  370. animation: spin 1s linear infinite;
  371. margin: 0 auto 10px;
  372. }
  373. .model-info {
  374. background: #e6fffa;
  375. border: 1px solid #81e6d9;
  376. border-radius: 8px;
  377. padding: 15px;
  378. margin-bottom: 20px;
  379. }
  380. .model-info h3 {
  381. color: #234e52;
  382. margin-bottom: 10px;
  383. font-size: 1.1rem;
  384. }
  385. .model-info p {
  386. margin-bottom: 5px;
  387. color: #234e52;
  388. }
  389. .model-info strong {
  390. color: #0f2027;
  391. }
  392. @media (max-width: 768px) {
  393. .main-content {
  394. grid-template-columns: 1fr;
  395. }
  396. .container {
  397. padding: 10px;
  398. }
  399. .header h1 {
  400. font-size: 2rem;
  401. }
  402. }
  403. </style>
  404. </head>
  405. <body>
  406. <div class = "container">
  407. <div class = "header">
  408. <h1>🚀 Kronos Financial Prediction Web UI</h1>
  409. <p>AI-based financial K-line data prediction analysis platform</p>
  410. </div>
  411. <div class = "main-content">
  412. <div class = "control-panel">
  413. <h2>🎯 Control Panel</h2>
  414. <!-- Model Selection -->
  415. <div class = "form-group">
  416. <label for = "model-select">Select Model:</label>
  417. <select id = "model-select">
  418. <option value = "">Please load available models first</option>
  419. </select>
  420. <small class = "form-text">Select the Kronos model to use</small>
  421. </div>
  422. <!-- Device Selection -->
  423. <div class = "form-group">
  424. <label for = "device-select">Select Device:</label>
  425. <select id = "device-select">
  426. <option value = "cpu">CPU</option>
  427. <option value = "cuda">CUDA (NVIDIA GPU)</option>
  428. <option value = "mps">MPS (Apple Silicon)</option>
  429. </select>
  430. <small class = "form-text">Select the device to run the model on</small>
  431. </div>
  432. <!-- Model Status -->
  433. <div id = "model-status" class = "status info" style = "display: none;">
  434. Model status information
  435. </div>
  436. <!-- Load Model Button -->
  437. <button id = "load-model-btn" class = "btn btn-secondary">
  438. 🔄 Load Model
  439. </button>
  440. <hr style = "margin: 20px 0; border: 1px solid #e2e8f0;">
  441. <!-- Stock data acquisition -->
  442. <div class = "form-group">
  443. <label class = "sr-only" for = "stock_code">Ticker Symbol Input:</label>
  444. <input type = "text" class = "form-control" id = "stock_code" name = 'stock_code'
  445. value = "{{ stock_code }}" placeholder = "例如:sh.600000" >
  446. <small class="form-text">Enter the ticker symbol you want to analyze</small>
  447. </div>
  448. <button id="stock-data-btn" class="btn btn-secondary">
  449. 📄 Stock Data
  450. </button>
  451. <hr style="margin: 20px 0; border: 1px solid #e2e8f0;">
  452. <!-- Data File Selection -->
  453. <div class = "form-group">
  454. <label for = "data-file-select">Select Data File:</label>
  455. <select id = "data-file-select">
  456. <option value = "">Please load data file list first</option>
  457. </select>
  458. <small class = "form-text">Select K-line data file from data directory</small>
  459. </div>
  460. <button id = "load-data-btn" class = "btn btn-secondary">
  461. 📁 Load Data
  462. </button>
  463. <!-- Data Information Display -->
  464. <div id = "data-info" class = "data-info" style = "display: none;">
  465. <h3>📊 Data Information</h3>
  466. <p><strong>Rows:</strong> <span id = "data-rows">-</span></p>
  467. <p><strong>Columns:</strong> <span id = "data-cols">-</span></p>
  468. <p><strong>Time Range:</strong> <span id = "data-time-range">-</span></p>
  469. <p><strong>Price Range:</strong> <span id = "data-price-range">-</span></p>
  470. <p><strong>Time Frequency:</strong> <span id = "data-timeframe">-</span></p>
  471. <p><strong>Prediction Columns:</strong> <span id = "data-prediction-cols">-</span></p>
  472. </div>
  473. <hr style = "margin: 20px 0; border: 1px solid #e2e8f0;">
  474. <!-- Time Window Selector -->
  475. <div class = "time-window-container">
  476. <h3>⏰ Time Window Selection</h3>
  477. <div class = "time-window-info">
  478. <span id = "window-start">Start: --</span>
  479. <span id = "window-end">End: --</span>
  480. <span id = "window-size">Window Size: 400 + 120 = 520 data points</span>
  481. </div>
  482. <div class = "time-window-slider">
  483. <div class = "slider-track">
  484. <div class = "slider-handle start-handle" id = "start-handle"></div>
  485. <div class = "slider-selection" id = "slider-selection"></div>
  486. <div class = "slider-handle end-handle" id = "end-handle"></div>
  487. </div>
  488. <div class = "slider-labels">
  489. <span id = "min-label">Earliest</span>
  490. <span id = "max-label">Latest</span>
  491. </div>
  492. </div>
  493. <small class = "form-text">Drag slider to select time window position for 520 data points, green area represents fixed 400+120 data point range</small>
  494. </div>
  495. <!-- Prediction Parameters -->
  496. <div class="form-group">
  497. <label for="lookback">Lookback Window Size:</label>
  498. <input type="number" id="lookback" value="400" readonly>
  499. <small class="form-text">Fixed at 400 data points</small>
  500. </div>
  501. <div class="form-group">
  502. <label for="pred-len">Prediction Length:</label>
  503. <input type="number" id="pred-len" value="120" readonly>
  504. <small class="form-text">Fixed at 120 data points</small>
  505. </div>
  506. <!-- Prediction Quality Parameters -->
  507. <div class="form-group">
  508. <label for="temperature">Prediction Temperature (T):</label>
  509. <input type="range" id="temperature" value="1.0" min="0.1" max="2.0" step="0.1">
  510. <span id="temperature-value">1.0</span>
  511. <small class="form-text">Controls prediction randomness, higher values make predictions more
  512. diverse, lower values make predictions more conservative</small>
  513. </div>
  514. <div class="form-group">
  515. <label for="top-p">Nucleus Sampling Parameter (top_p):</label>
  516. <input type="range" id="top-p" value="0.9" min="0.1" max="1.0" step="0.1">
  517. <span id="top-p-value">0.9</span>
  518. <small class="form-text">Controls prediction diversity, higher values consider broader probability
  519. distributions</small>
  520. </div>
  521. <div class="form-group">
  522. <label for="sample-count">Sample Count:</label>
  523. <input type="number" id="sample-count" value="1" min="1" max="5" step="1">
  524. <small class="form-text">Generate multiple prediction samples to improve quality (recommended
  525. 1-3)</small>
  526. </div>
  527. <button id="predict-btn" class="btn btn-success" disabled>
  528. 🔮 Start Prediction
  529. </button>
  530. <!-- Loading Status -->
  531. <div id="loading" class="loading">
  532. <div class="spinner"></div>
  533. <p>Processing, please wait...</p>
  534. </div>
  535. </div>
  536. <div class="chart-container">
  537. <h2>📈 Prediction Results Chart</h2>
  538. <div id="chart"></div>
  539. <!-- Comparison Analysis -->
  540. <div id="comparison-section" class="comparison-section" style="display: none;">
  541. <h3>📊 Prediction vs Actual Data Comparison</h3>
  542. <div id="comparison-info" class="comparison-info">
  543. <p><strong>Prediction Type:</strong> <span id="prediction-type">-</span></p>
  544. <p><strong>Comparison Data:</strong> <span id="comparison-data">-</span></p>
  545. </div>
  546. <div class="error-stats">
  547. <div class="error-stat">
  548. <h4>Mean Absolute Error</h4>
  549. <div class="value" id="mae">-</div>
  550. <div class="unit">Price Units</div>
  551. </div>
  552. <div class="error-stat">
  553. <h4>Root Mean Square Error</h4>
  554. <div class="value" id="rmse">-</div>
  555. <div class="unit">Price Units</div>
  556. </div>
  557. <div class="error-stat">
  558. <h4>Mean Absolute Percentage Error</h4>
  559. <div class="value" id="mape">-</div>
  560. <div class="unit">%</div>
  561. </div>
  562. </div>
  563. <div class="error-details">
  564. <h4>Detailed Comparison Data:</h4>
  565. <div style="max-height: 300px; overflow-y: auto;">
  566. <table class="comparison-table">
  567. <thead>
  568. <tr>
  569. <th>Time</th>
  570. <th>Actual Open</th>
  571. <th>Predicted Open</th>
  572. <th>Actual High</th>
  573. <th>Predicted High</th>
  574. <th>Actual Low</th>
  575. <th>Predicted Low</th>
  576. <th>Actual Close</th>
  577. <th>Predicted Close</th>
  578. </tr>
  579. </thead>
  580. <tbody id="comparison-tbody">
  581. </tbody>
  582. </table>
  583. </div>
  584. </div>
  585. </div>
  586. <br>
  587. <h2>📶 Technical Indicator Chart</h2>
  588. <div id="indicator-status" class="indicator-status" style="display: none;"></div>
  589. <div class="form-group">
  590. <label for="diagram_type">Select Diagram Type:</label>
  591. <div class="chart-grid">
  592. <select id="diagram_type" class="form-control">
  593. <option value="Volume Chart (VOL)">Volume Chart (VOL)</option>
  594. <option value="Moving Average (MA)">Moving Average (MA)</option>
  595. <option value="MACD Indicator (MACD)">MACD Indicator (MACD)</option>
  596. <option value="RSI Indicator (RSI)">RSI Indicator (RSI)</option>
  597. <option value="Bollinger Bands (BB)">Bollinger Bands (BB)</option>
  598. <option value="Stochastic Oscillator (STOCH)">Stochastic Oscillator (STOCH)</option>
  599. <option value="Rolling Window Mean Strategy">Rolling Window Mean Strategy</option>
  600. <option value="TRIX Indicator (TRIX)">Triple Exponential Average (TRIX) Strategy</option>
  601. </select>
  602. <button id="generate-chart-btn" class="btn btn-warning">
  603. ✏️ Generate chart
  604. </button>
  605. </div>
  606. <small class="form-text">Select the type to draw the relevant chart</small>
  607. </div>
  608. <div id="indicator-loading" class="indicator-loading">
  609. <div class="spinner"></div>
  610. <p>Generating chart, please wait...</p>
  611. </div>
  612. <div id="indicator-chart"></div>
  613. <!-- Data Presentation -->
  614. <div id="data-presentation" class="comparison-section" style="display: none;">
  615. <h3>💹 Financial Data Visualization</h3>
  616. <div class="error-details">
  617. <h4>Detailed Financial Data:</h4>
  618. <div style="max-height: 300px; overflow-y: auto;">
  619. <table class="comparison-table">
  620. <thead>
  621. <tr>
  622. <th>Timestamps</th>
  623. <th>Open</th>
  624. <th>High</th>
  625. <th>Low</th>
  626. <th>Close</th>
  627. <th>Volume</th>
  628. <th>Amount</th>
  629. </tr>
  630. </thead>
  631. <tbody id="data-tbody">
  632. </tbody>
  633. </table>
  634. </div>
  635. </div>
  636. </div>
  637. </div>
  638. </div>
  639. </div>
  640. <script>
  641. // Global variables
  642. let currentDataFile = null;
  643. let currentDataInfo = null;
  644. let availableModels = [];
  645. let modelLoaded = false;
  646. // Initialize after page loads
  647. document.addEventListener('DOMContentLoaded', function() {
  648. initializeApp();
  649. });
  650. // Initialize application
  651. async function initializeApp() {
  652. console.log('🚀 Initializing Kronos Web UI...');
  653. // Load available models
  654. await loadAvailableModels();
  655. // Load data file list
  656. await loadDataFiles();
  657. // Set up event listeners
  658. setupEventListeners();
  659. // Initialize time slider
  660. initializeTimeSlider();
  661. console.log('✅ Application initialization completed');
  662. }
  663. // Load available models
  664. async function loadAvailableModels() {
  665. try {
  666. console.log('开始加载模型列表...');
  667. const response = await fetch('/api/available-models');
  668. const data = await response.json();
  669. console.log('API返回数据:', data);
  670. const modelSelect = document.getElementById('model-select');
  671. console.log('找到的下拉菜单:', modelSelect);
  672. if (!modelSelect) {
  673. console.error('错误: 找不到 modelSelect 元素');
  674. return;
  675. }
  676. // 清空现有选项
  677. modelSelect.innerHTML = '';
  678. // 添加模型选项
  679. for (const [key, model] of Object.entries(data.models)) {
  680. const option = document.createElement('option');
  681. option.value = key;
  682. option.textContent = `${model.name} (${model.params}) - ${model.description}`;
  683. modelSelect.appendChild(option);
  684. console.log('添加选项:', option.textContent);
  685. }
  686. console.log('最终选项数量:', modelSelect.options.length);
  687. } catch (error) {
  688. console.error('加载模型列表失败:', error);
  689. }
  690. }
  691. // Populate model selection dropdown
  692. function populateModelSelect() {
  693. const modelSelect = document.getElementById('model-select');
  694. modelSelect.innerHTML = '<option value="">Please select model</option>';
  695. Object.entries(availableModels).forEach(([key, model]) => {
  696. const option = document.createElement('option');
  697. option.value = key;
  698. option.textContent = `${model.name} (${model.params}) - ${model.description}`;
  699. modelSelect.appendChild(option);
  700. });
  701. }
  702. // Load model
  703. async function loadModel() {
  704. const modelKey = document.getElementById('model-select').value;
  705. const device = document.getElementById('device-select').value;
  706. if (!modelKey) {
  707. showStatus('error', 'Please select a model to load');
  708. return;
  709. }
  710. try {
  711. showLoading(true);
  712. document.getElementById('load-model-btn').disabled = true;
  713. const response = await axios.post('/api/load-model', {
  714. model_key: modelKey,
  715. device: device
  716. });
  717. if (response.data.success) {
  718. modelLoaded = true;
  719. showStatus('success', response.data.message);
  720. updateModelStatus();
  721. document.getElementById('predict-btn').disabled = false;
  722. console.log('✅ Model loaded successfully:', response.data.model_info);
  723. } else {
  724. showStatus('error', response.data.error);
  725. }
  726. } catch (error) {
  727. console.error('❌ Model loading failed:', error);
  728. showStatus('error', `Model loading failed: ${error.response?.data?.error || error.message}`);
  729. } finally {
  730. showLoading(false);
  731. document.getElementById('load-model-btn').disabled = false;
  732. }
  733. }
  734. // Update model status
  735. async function updateModelStatus() {
  736. try {
  737. const response = await axios.get('/api/model-status');
  738. const status = response.data;
  739. if (status.loaded) {
  740. showStatus('success', `Model loaded: ${status.current_model.name} on ${status.current_model.device}`);
  741. } else if (status.available) {
  742. showStatus('info', 'Model available but not loaded');
  743. } else {
  744. showStatus('warning', 'Model library not available');
  745. }
  746. } catch (error) {
  747. console.error('❌ Failed to get model status:', error);
  748. }
  749. }
  750. //Stock Data按钮
  751. document.addEventListener('DOMContentLoaded', function() {
  752. const generateChartBtn = document.getElementById('stock-data-btn');
  753. if (generateChartBtn) {
  754. generateChartBtn.addEventListener('click', StockData);
  755. console.log('Stock Data button event listener bound');
  756. } else {
  757. console.error('stock-data-btn element not found');
  758. }
  759. });
  760. async function StockData() {
  761. console.log('Get stock data...');
  762. const stockCodeInput = document.getElementById('stock_code');
  763. const generateBtn = document.getElementById('stock-data-btn');
  764. const stockCode = stockCodeInput.value.trim();
  765. generateBtn.disabled = true;
  766. try {
  767. if (!stockCode) {
  768. showStatus('error', 'Stock code cannot be empty');
  769. return;
  770. }
  771. const stockCodeRegex = /^[a-z]+\.\d+$/;
  772. if (!stockCodeRegex.test(stockCode)) {
  773. showStatus('error', 'The ticker symbol is in the wrong format');
  774. return;
  775. }
  776. showLoading(true);
  777. const response = await axios.post('/api/stock-data', {stock_code: stockCode});
  778. if (response.data.success) {
  779. showStatus('success', `Successfully fetched data for ${stockCode}`);
  780. loadDataFiles();
  781. stockCodeInput.value = '';
  782. } else {
  783. showStatus('error', response.data.error || 'Failed to fetch stock data');
  784. }
  785. } catch (error) {
  786. console.error('❌ Failed to fetch stock data:', error);
  787. showStatus('error', `Failed to fetch data`);
  788. } finally {
  789. showLoading(false);
  790. if (generateBtn) generateBtn.disabled = false;
  791. }
  792. }
  793. // Load data file list
  794. async function loadDataFiles() {
  795. try {
  796. const response = await axios.get('/api/data-files');
  797. const dataFiles = response.data;
  798. const dataFileSelect = document.getElementById('data-file-select');
  799. dataFileSelect.innerHTML = '<option value="">Please select data file</option>';
  800. dataFiles.forEach(file => {
  801. const option = document.createElement('option');
  802. option.value = file.path;
  803. option.textContent = `${file.name} (${file.size})`;
  804. dataFileSelect.appendChild(option);
  805. });
  806. console.log('✅ Data file list loaded successfully:', dataFiles);
  807. } catch (error) {
  808. console.error('❌ Failed to load data file list:', error);
  809. showStatus('error', 'Failed to load data file list');
  810. }
  811. }
  812. // Load data file
  813. async function loadData() {
  814. const filePath = document.getElementById('data-file-select').value;
  815. if (!filePath) {
  816. showStatus('error', 'Please select a data file to load');
  817. return;
  818. }
  819. try {
  820. showLoading(true);
  821. document.getElementById('load-data-btn').disabled = true;
  822. const response = await axios.post('/api/load-data', {
  823. file_path: filePath
  824. });
  825. if (response.data.success) {
  826. currentDataFile = filePath;
  827. currentDataInfo = response.data.data_info;
  828. showDataInfo(response.data.data_info);
  829. showStatus('success', response.data.message);
  830. // Update prediction button status
  831. if (modelLoaded) {
  832. document.getElementById('predict-btn').disabled = false;
  833. }
  834. console.log('✅ Data loaded successfully:', response.data.data_info);
  835. } else {
  836. showStatus('error', response.data.error);
  837. }
  838. } catch (error) {
  839. console.error('❌ Data loading failed:', error);
  840. showStatus('error', `Data loading failed: ${error.response?.data?.error || error.message}`);
  841. } finally {
  842. showLoading(false);
  843. document.getElementById('load-data-btn').disabled = false;
  844. }
  845. }
  846. // Display data information
  847. function showDataInfo(dataInfo) {
  848. document.getElementById('data-info').style.display = 'block';
  849. document.getElementById('data-rows').textContent = dataInfo.rows;
  850. document.getElementById('data-cols').textContent = dataInfo.columns.length;
  851. document.getElementById('data-time-range').textContent = `${dataInfo.start_date} to ${dataInfo.end_date}`;
  852. document.getElementById('data-price-range').textContent = `${dataInfo.price_range.min.toFixed(4)} - ${dataInfo.price_range.max.toFixed(4)}`;
  853. document.getElementById('data-timeframe').textContent = dataInfo.timeframe;
  854. document.getElementById('data-prediction-cols').textContent = dataInfo.prediction_columns.join(', ');
  855. // Initialize time window slider
  856. initializeTimeWindowSlider(dataInfo);
  857. }
  858. // Time window slider related variables
  859. let sliderData = null;
  860. let isDragging = false;
  861. let currentHandle = null;
  862. // Initialize time window slider
  863. function initializeTimeSlider() {
  864. // Set up slider event listeners
  865. setupSliderEventListeners();
  866. }
  867. // Set up slider event listeners
  868. function setupSliderEventListeners() {
  869. const startHandle = document.getElementById('start-handle');
  870. const endHandle = document.getElementById('end-handle');
  871. const track = document.querySelector('.slider-track');
  872. // Start dragging
  873. startHandle.addEventListener('mousedown', (e) => {
  874. isDragging = true;
  875. currentHandle = 'start';
  876. e.preventDefault();
  877. });
  878. endHandle.addEventListener('mousedown', (e) => {
  879. isDragging = true;
  880. currentHandle = 'end';
  881. e.preventDefault();
  882. });
  883. // Dragging
  884. document.addEventListener('mousemove', (e) => {
  885. if (!isDragging) return;
  886. const rect = track.getBoundingClientRect();
  887. const x = e.clientX - rect.left;
  888. const percentage = Math.max(0, Math.min(1, x / rect.width));
  889. if (currentHandle === 'start') {
  890. updateStartHandle(percentage);
  891. } else if (currentHandle === 'end') {
  892. updateEndHandle(percentage);
  893. }
  894. updateSliderFromHandles();
  895. });
  896. // End dragging
  897. document.addEventListener('mouseup', () => {
  898. isDragging = false;
  899. currentHandle = null;
  900. });
  901. // Click track to set position directly
  902. track.addEventListener('click', (e) => {
  903. const rect = track.getBoundingClientRect();
  904. const x = e.clientX - rect.left;
  905. const percentage = Math.max(0, Math.min(1, x / rect.width));
  906. // Determine which handle is closer to the click position
  907. const startHandle = document.getElementById('start-handle');
  908. const endHandle = document.getElementById('end-handle');
  909. const startRect = startHandle.getBoundingClientRect();
  910. const endRect = endHandle.getBoundingClientRect();
  911. if (Math.abs(x - (startRect.left - rect.left)) < Math.abs(x - (endRect.left - rect.left))) {
  912. updateStartHandle(percentage);
  913. } else {
  914. updateEndHandle(percentage);
  915. }
  916. updateSliderFromHandles();
  917. });
  918. }
  919. // Update start handle position
  920. function updateStartHandle(percentage) {
  921. const startHandle = document.getElementById('start-handle');
  922. const selection = document.getElementById('slider-selection');
  923. // Fixed window size of 520 data points
  924. const windowSize = 520;
  925. const totalRows = sliderData ? sliderData.totalRows : 1000;
  926. const windowPercentage = windowSize / totalRows;
  927. // Ensure start handle doesn't cause window to exceed data range
  928. if (percentage + windowPercentage > 1) {
  929. percentage = 1 - windowPercentage;
  930. }
  931. startHandle.style.left = (percentage * 100) + '%';
  932. selection.style.left = (percentage * 100) + '%';
  933. selection.style.width = (windowPercentage * 100) + '%';
  934. // Automatically adjust end handle position to maintain fixed window size
  935. const endHandle = document.getElementById('end-handle');
  936. endHandle.style.left = ((percentage + windowPercentage) * 100) + '%';
  937. }
  938. // Update end handle position
  939. function updateEndHandle(percentage) {
  940. const endHandle = document.getElementById('end-handle');
  941. const selection = document.getElementById('slider-selection');
  942. // Fixed window size of 520 data points
  943. const windowSize = 520;
  944. const totalRows = sliderData ? sliderData.totalRows : 1000;
  945. const windowPercentage = windowSize / totalRows;
  946. // Ensure end handle doesn't cause window to exceed data range
  947. if (percentage - windowPercentage < 0) {
  948. percentage = windowPercentage;
  949. }
  950. endHandle.style.left = (percentage * 100) + '%';
  951. selection.style.left = ((percentage - windowPercentage) * 100) + '%';
  952. selection.style.width = (windowPercentage * 100) + '%';
  953. // Automatically adjust start handle position to maintain fixed window size
  954. const startHandle = document.getElementById('start-handle');
  955. startHandle.style.left = ((percentage - windowPercentage) * 100) + '%';
  956. }
  957. // Update slider display based on handle positions
  958. function updateSliderFromHandles() {
  959. const startHandle = document.getElementById('start-handle');
  960. const endHandle = document.getElementById('end-handle');
  961. const startPercentage = parseFloat(startHandle.style.left) / 100;
  962. const endPercentage = parseFloat(endHandle.style.left) / 100;
  963. if (!sliderData) return;
  964. // Calculate selected time range
  965. const totalTime = sliderData.endDate.getTime() - sliderData.startDate.getTime();
  966. const startTime = sliderData.startDate.getTime() + (totalTime * startPercentage);
  967. const endTime = sliderData.startDate.getTime() + (totalTime * endPercentage);
  968. const startDate = new Date(startTime);
  969. const endDate = new Date(endTime);
  970. // Update display information
  971. document.getElementById('window-start').textContent = `Start: ${startDate.toLocaleDateString()}`;
  972. document.getElementById('window-end').textContent = `End: ${endDate.toLocaleDateString()}`;
  973. // Display fixed window size
  974. document.getElementById('window-size').textContent = `Window Size: 400 + 120 = 520 data points (fixed)`;
  975. // Input field values remain fixed
  976. document.getElementById('lookback').value = 400;
  977. document.getElementById('pred-len').value = 120;
  978. }
  979. // Update slider based on input fields
  980. function updateSliderFromInputs() {
  981. if (!sliderData) return;
  982. // Fixed window size: 400 + 120 = 520 data points
  983. const lookback = 400;
  984. const predLen = 120;
  985. const windowSize = lookback + predLen; // Fixed at 520
  986. // Calculate slider position
  987. const totalRows = sliderData.totalRows;
  988. if (windowSize > totalRows) {
  989. // If window size exceeds total data amount, show error
  990. showStatus('error', `Insufficient data, need at least ${windowSize} data points, currently only ${totalRows} available`);
  991. return;
  992. }
  993. // Calculate slider position (default select first half of data)
  994. const startPercentage = 0.1; // Start from 10%
  995. const endPercentage = startPercentage + (windowSize / totalRows);
  996. // Update handle positions
  997. updateStartHandle(startPercentage);
  998. updateEndHandle(endPercentage);
  999. // Update display information
  1000. updateSliderFromHandles();
  1001. }
  1002. // Initialize time window slider
  1003. function initializeTimeWindowSlider(dataInfo) {
  1004. sliderData = {
  1005. startDate: new Date(dataInfo.start_date),
  1006. endDate: new Date(dataInfo.end_date),
  1007. totalRows: dataInfo.rows,
  1008. timeframe: dataInfo.timeframe
  1009. };
  1010. // Set slider labels
  1011. document.getElementById('min-label').textContent = dataInfo.start_date.split('T')[0];
  1012. document.getElementById('max-label').textContent = dataInfo.end_date.split('T')[0];
  1013. // Initialize slider position
  1014. updateSliderFromInputs();
  1015. }
  1016. // Binding Generate chart
  1017. document.addEventListener('DOMContentLoaded', function() {
  1018. const generateChartBtn = document.getElementById('generate-chart-btn');
  1019. if (generateChartBtn) {
  1020. generateChartBtn.addEventListener('click', generateTechnicalChart);
  1021. console.log('Generate chart button event listener bound');
  1022. } else {
  1023. console.error('generate-chart-btn element not found');
  1024. }
  1025. });
  1026. // Generate technical chart
  1027. async function generateTechnicalChart() {
  1028. console.log('Generating technical indicator chart...');
  1029. const indicatorLoading = document.getElementById('indicator-loading');
  1030. indicatorLoading.classList.add('show');
  1031. const generateBtn = document.getElementById('generate-chart-btn');
  1032. generateBtn.disabled = true;
  1033. try {
  1034. await new Promise(resolve => setTimeout(resolve, 2000));
  1035. // get arguments
  1036. const startHandle = document.getElementById('start-handle');
  1037. const endHandle = document.getElementById('end-handle');
  1038. const startPercentage = parseFloat(startHandle.style.left) / 100;
  1039. const endPercentage = parseFloat(endHandle.style.left) / 100;
  1040. const totalRows = sliderData ? sliderData.totalRows : 0;
  1041. const historicalStartIdx = Math.floor(startPercentage * totalRows);
  1042. const lookback = Math.floor((endPercentage - startPercentage) * totalRows);
  1043. const predLen = parseInt(document.getElementById('pred-len').value);
  1044. const filePath = document.getElementById('data-file-select').value;
  1045. const diagramType = document.getElementById('diagram_type').value;
  1046. // validate arguments
  1047. if (!filePath) throw new Error('Please select a data file first');
  1048. if (isNaN(lookback) || isNaN(predLen)) throw new Error('Invalid parameters');
  1049. // fetch chart data
  1050. const response = await fetch('/api/generate-chart', {
  1051. method: 'POST',
  1052. headers: { 'Content-Type': 'application/json' },
  1053. body: JSON.stringify({
  1054. file_path: filePath,
  1055. lookback: lookback,
  1056. pred_len: predLen,
  1057. diagram_type: diagramType,
  1058. historical_start_idx: historicalStartIdx
  1059. })
  1060. });
  1061. const result = await response.json();
  1062. if (!result.success) throw new Error(result.error || 'Failed to generate chart');
  1063. // render the chart
  1064. const chartContainer = document.getElementById('indicator-chart');
  1065. if (chartContainer) {
  1066. if (chartContainer.data) Plotly.purge(chartContainer);
  1067. Plotly.newPlot(
  1068. chartContainer,
  1069. result.chart.data,
  1070. result.chart.layout,
  1071. { responsive: true }
  1072. );
  1073. } else {
  1074. throw new Error('Indicator chart container not found');
  1075. }
  1076. document.getElementById('data-presentation').style.display = 'block';
  1077. if (result.table_data) {
  1078. fillDataTable(result.table_data);
  1079. } else {
  1080. console.warn('No table data returned from server');
  1081. fillDataTable([]);
  1082. }
  1083. showIndicatorStatus('success', `chart (${diagramType}) generated successfully`);
  1084. } catch (error) {
  1085. console.error('Chart generation error:', error);
  1086. showIndicatorStatus('error', `Chart generation failed: ${error.message}`);
  1087. } finally {
  1088. indicatorLoading.classList.remove('show');
  1089. generateBtn.disabled = false;
  1090. }
  1091. }
  1092. // Fill data table
  1093. function fillDataTable(data) {
  1094. const tbody = document.getElementById('data-tbody');
  1095. tbody.innerHTML = '';
  1096. if (!data || data.length === 0) {
  1097. const emptyRow = document.createElement('tr');
  1098. emptyRow.innerHTML = '<td colspan = "7" style = "text-align:center">暂无数据</td>';
  1099. tbody.appendChild(emptyRow);
  1100. return;
  1101. }
  1102. data.forEach(item => {
  1103. const row = document.createElement('tr');
  1104. row.innerHTML = `
  1105. <td>${new Date(item.timestamps).toLocaleString()}</td>
  1106. <td>${item.open.toFixed(4)}</td>
  1107. <td>${item.high.toFixed(4)}</td>
  1108. <td>${item.low.toFixed(4)}</td>
  1109. <td>${item.close.toFixed(4)}</td>
  1110. <td>${item.volume ? item.volume.toLocaleString() : '-'}</td>
  1111. <td>${item.amount ? item.amount.toFixed(2) : '-'}</td>
  1112. `;
  1113. tbody.appendChild(row);
  1114. });
  1115. }
  1116. // Start prediction
  1117. async function startPrediction() {
  1118. if (!currentDataFile) {
  1119. showStatus('error', 'Please load data file first');
  1120. return;
  1121. }
  1122. if (!modelLoaded) {
  1123. showStatus('error', 'Please load model first');
  1124. return;
  1125. }
  1126. try {
  1127. showLoading(true);
  1128. document.getElementById('predict-btn').disabled = true;
  1129. const lookback = parseInt(document.getElementById('lookback').value);
  1130. const predLen = parseInt(document.getElementById('pred-len').value);
  1131. // Get selected time range from time window slider
  1132. const startHandle = document.getElementById('start-handle');
  1133. const startPercentage = parseFloat(startHandle.style.left) / 100;
  1134. if (!sliderData) {
  1135. showStatus('error', 'Time window slider not initialized');
  1136. return;
  1137. }
  1138. // Calculate selected time range
  1139. const totalTime = sliderData.endDate.getTime() - sliderData.startDate.getTime();
  1140. const startTime = sliderData.startDate.getTime() + (totalTime * startPercentage);
  1141. const startDate = new Date(startTime);
  1142. // Get prediction quality parameters
  1143. const temperature = parseFloat(document.getElementById('temperature').value);
  1144. const topP = parseFloat(document.getElementById('top-p').value);
  1145. const sampleCount = parseInt(document.getElementById('sample-count').value);
  1146. let predictionParams = {
  1147. file_path: currentDataFile,
  1148. lookback: lookback,
  1149. pred_len: predLen,
  1150. start_date: startDate.toISOString().slice(0, 16), // Format as YYYY-MM-DDTHH:MM
  1151. temperature: temperature,
  1152. top_p: topP,
  1153. sample_count: sampleCount
  1154. };
  1155. console.log('🚀 Starting prediction, parameters:', predictionParams);
  1156. const response = await axios.post('/api/predict', predictionParams);
  1157. console.log('📊 Prediction response received:', response.data);
  1158. // 添加更详细的响应数据检查
  1159. console.log('🔍 Response data check:');
  1160. console.log('- success:', response.data.success);
  1161. console.log('- has chart:', !!response.data.chart);
  1162. console.log('- chart length:', response.data.chart ? response.data.chart.length : 0);
  1163. console.log('- prediction results count:', response.data.prediction_results ? response.data.prediction_results.length : 0);
  1164. console.log('- actual data count:', response.data.actual_data ? response.data.actual_data.length : 0);
  1165. if (response.data.success) {
  1166. // Display prediction results
  1167. displayPredictionResult(response.data);
  1168. showStatus('success', response.data.message);
  1169. } else {
  1170. showStatus('error', response.data.error);
  1171. }
  1172. } catch (error) {
  1173. console.error('❌ Prediction failed:', error);
  1174. showStatus('error', `Prediction failed: ${error.response?.data?.error || error.message}`);
  1175. } finally {
  1176. showLoading(false);
  1177. document.getElementById('predict-btn').disabled = false;
  1178. }
  1179. }
  1180. // // Display prediction results
  1181. // {#function displayPredictionResult(result) {#}
  1182. // {# // Display chart#}
  1183. // {# const chartData = JSON.parse(result.chart);#}
  1184. // {# Plotly.newPlot('chart', chartData.data, chartData.layout);#}
  1185. // {##}
  1186. // {# // Display comparison analysis (if actual data exists)#}
  1187. // {# if (result.has_comparison) {#}
  1188. // {# displayComparisonAnalysis(result);#}
  1189. // {# } else {#}
  1190. // {# document.getElementById('comparison-section').style.display = 'none';#}
  1191. // {# }#}
  1192. // {#}#}
  1193. function displayPredictionResult(result) {
  1194. console.log('🔍 DEBUG - displayPredictionResult called');
  1195. console.log('Result keys:', Object.keys(result));
  1196. console.log('Has chart:', !!result.chart);
  1197. console.log('Chart type:', typeof result.chart);
  1198. console.log('Chart length:', result.chart ? result.chart.length : 0);
  1199. console.log('Has comparison:', result.has_comparison);
  1200. console.log('Actual data length:', result.actual_data ? result.actual_data.length : 0);
  1201. try {
  1202. // Parse and display chart
  1203. if (result.chart) {
  1204. console.log('📊 Parsing chart data...');
  1205. const chartData = JSON.parse(result.chart);
  1206. console.log('📈 Chart data parsed successfully:', chartData);
  1207. // Clear previous chart
  1208. const chartDiv = document.getElementById('chart');
  1209. chartDiv.innerHTML = '';
  1210. // Create new chart with error handling
  1211. Plotly.newPlot('chart', chartData.data, chartData.layout, {
  1212. responsive: true
  1213. }).then(function () {
  1214. console.log('✅ Chart rendered successfully');
  1215. // 确保图表容器可见
  1216. chartDiv.style.display = 'block';
  1217. }).catch(function (error) {
  1218. console.error('❌ Chart rendering failed:', error);
  1219. showStatus('error', `图表渲染失败: ${error.message}`);
  1220. // 显示错误信息
  1221. chartDiv.innerHTML = `
  1222. <div style="text-align: center; padding: 50px; color: #666;">
  1223. <h3>图表加载失败</h3>
  1224. <p>错误信息: ${error.message}</p>
  1225. <p>请检查控制台获取详细信息</p>
  1226. </div>
  1227. `;
  1228. });
  1229. } else {
  1230. console.error('❌ No chart data in response');
  1231. showStatus('error', '服务器返回的图表数据为空');
  1232. }
  1233. // Display comparison analysis (if actual data exists)
  1234. if (result.has_comparison && result.actual_data && result.actual_data.length > 0) {
  1235. console.log('📊 Displaying comparison analysis');
  1236. displayComparisonAnalysis(result);
  1237. } else {
  1238. console.log('ℹ️ No comparison data available');
  1239. document.getElementById('comparison-section').style.display = 'none';
  1240. }
  1241. } catch (error) {
  1242. console.error('❌ Error displaying prediction result:', error);
  1243. showStatus('error', `结果显示失败: ${error.message}`);
  1244. // 显示错误信息在图表区域
  1245. const chartDiv = document.getElementById('chart');
  1246. chartDiv.innerHTML = `
  1247. <div style="text-align: center; padding: 50px; color: #666;">
  1248. <h3>数据处理失败</h3>
  1249. <p>错误信息: ${error.message}</p>
  1250. <p>请检查数据格式是否正确</p>
  1251. </div>
  1252. `;
  1253. }
  1254. }
  1255. // Display comparison analysis
  1256. function displayComparisonAnalysis(result) {
  1257. document.getElementById('comparison-section').style.display = 'block';
  1258. // Update comparison information
  1259. document.getElementById('prediction-type').textContent = result.prediction_type;
  1260. document.getElementById('comparison-data').textContent = `${result.actual_data.length} actual data points`;
  1261. // Calculate error statistics
  1262. const errorStats = getPredictionQuality(result.prediction_results, result.actual_data);
  1263. // Display error statistics
  1264. document.getElementById('mae').textContent = errorStats.mae.toFixed(4);
  1265. document.getElementById('rmse').textContent = errorStats.rmse.toFixed(4);
  1266. document.getElementById('mape').textContent = errorStats.mape.toFixed(2);
  1267. // Fill comparison table
  1268. fillComparisonTable(result.prediction_results, result.actual_data);
  1269. }
  1270. // Calculate prediction quality metrics
  1271. function getPredictionQuality(predictions, actuals) {
  1272. if (!predictions || !actuals || predictions.length === 0 || actuals.length === 0) {
  1273. return { mae: 0, rmse: 0, mape: 0 };
  1274. }
  1275. const minLen = Math.min(predictions.length, actuals.length);
  1276. let mae = 0, rmse = 0, mape = 0;
  1277. for (let i = 0; i < minLen; i++) {
  1278. const pred = predictions[i];
  1279. const act = actuals[i];
  1280. // Use closing price to calculate errors
  1281. const error = Math.abs(pred.close - act.close);
  1282. const percentError = (error / act.close) * 100;
  1283. mae += error;
  1284. rmse += error * error;
  1285. mape += percentError;
  1286. }
  1287. mae /= minLen;
  1288. rmse = Math.sqrt(rmse / minLen);
  1289. mape /= minLen;
  1290. return { mae, rmse, mape };
  1291. }
  1292. // Fill comparison table
  1293. function fillComparisonTable(predictions, actuals) {
  1294. const tbody = document.getElementById('comparison-tbody');
  1295. tbody.innerHTML = '';
  1296. const minLen = Math.min(predictions.length, actuals.length);
  1297. for (let i = 0; i < minLen; i++) {
  1298. const pred = predictions[i];
  1299. const act = actuals[i];
  1300. const row = document.createElement('tr');
  1301. row.innerHTML = `
  1302. <td>${new Date(pred.timestamp).toLocaleString()}</td>
  1303. <td>${act.open.toFixed(4)}</td>
  1304. <td>${pred.open.toFixed(4)}</td>
  1305. <td>${act.high.toFixed(4)}</td>
  1306. <td>${pred.high.toFixed(4)}</td>
  1307. <td>${act.low.toFixed(4)}</td>
  1308. <td>${pred.low.toFixed(4)}</td>
  1309. <td>${act.close.toFixed(4)}</td>
  1310. <td>${pred.close.toFixed(4)}</td>
  1311. `;
  1312. tbody.appendChild(row);
  1313. }
  1314. }
  1315. // Set up event listeners
  1316. function setupEventListeners() {
  1317. // Load model button
  1318. document.getElementById('load-model-btn').addEventListener('click', loadModel);
  1319. // Load data button
  1320. document.getElementById('load-data-btn').addEventListener('click', loadData);
  1321. // Prediction button
  1322. document.getElementById('predict-btn').addEventListener('click', startPrediction);
  1323. // Generate chart button
  1324. document.addEventListener('DOMContentLoaded', function(){
  1325. // Prediction quality parameter sliders
  1326. document.getElementById('temperature').addEventListener('input', function() {
  1327. document.getElementById('temperature-value').textContent = this.value;
  1328. });
  1329. document.getElementById('top-p').addEventListener('input', function() {
  1330. document.getElementById('top-p-value').textContent = this.value;
  1331. });
  1332. // Update slider when lookback window size changes
  1333. document.getElementById('lookback').addEventListener('input', updateSliderFromInputs);
  1334. document.getElementById('pred-len').addEventListener('input', updateSliderFromInputs);
  1335. const chartButton = document.getElementById('load-chart-btn');
  1336. if (chartButton) {
  1337. chartButton.addEventListener('click', generatechart);
  1338. console.log('Chart button event listener bound');
  1339. } else {
  1340. console.error('load-chart-btn element not found');
  1341. }
  1342. });
  1343. }
  1344. // Display status information
  1345. function showStatus(type, message) {
  1346. const statusDiv = document.getElementById('model-status');
  1347. statusDiv.className = `status ${type}`;
  1348. statusDiv.textContent = message;
  1349. statusDiv.style.display = 'block';
  1350. // Auto-hide
  1351. setTimeout(() => {
  1352. statusDiv.style.display = 'none';
  1353. }, 5000);
  1354. }
  1355. function showIndicatorStatus(type, message) {
  1356. const statusDiv = document.getElementById('indicator-status');
  1357. statusDiv.className = `indicator-status status ${type}`;
  1358. statusDiv.textContent = message;
  1359. statusDiv.style.display = 'block';
  1360. // Auto-hide after 5 seconds
  1361. setTimeout(() => {
  1362. statusDiv.style.display = 'none';
  1363. }, 5000);
  1364. }
  1365. // Show/hide loading status
  1366. function showLoading(show) {
  1367. const loadingDiv = document.getElementById('loading');
  1368. if (show) {
  1369. loadingDiv.classList.add('show');
  1370. } else {
  1371. loadingDiv.classList.remove('show');
  1372. }
  1373. }
  1374. </script>
  1375. </body>
  1376. </html>