|
|
<!DOCTYPE html><html lang = "zh-CN"><head> <meta charset = "UTF-8"> <meta name = "viewport" content = "width = device-width, initial-scale = 1.0"> <title>Kronos Financial Prediction Web UI</title> <!--? <script src = "https://cdn.plot.ly/plotly-latest.min.js"></script>--> <script src = "https://cdn.plot.ly/plotly-2.27.0.min.js"></script> <script src = "https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<style> * { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; color: #333; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.header { text-align: center; margin-bottom: 30px; color: white; }
.header h1 { font-size: 2.5rem; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); }
.header p { font-size: 1.1rem; opacity: 0.9; }
.main-content { display: grid; grid-template-columns: 1fr 2fr; gap: 30px; margin-bottom: 30px; }
.control-panel { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); height: fit-content; }
.control-panel h2 { color: #4a5568; margin-bottom: 20px; font-size: 1.5rem; border-bottom: 2px solid #e2e8f0; padding-bottom: 10px; }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 8px; font-weight: 600; color: #4a5568; }
.form-group select, .form-group input { width: 100%; padding: 12px; border: 2px solid #e2e8f0; border-radius: 8px; font-size: 14px; transition: border-color 0.3s ease; }
.form-group select:focus, .form-group input:focus { outline: none; border-color: #667eea; }
/* Prediction quality parameter styles */ .form-group input[type="range"] { width: 70%; margin-right: 10px; }
.form-group input[type="number"] { width: 100%; }
.form-group span { display: inline-block; min-width: 40px; font-weight: 600; color: #667eea; }
.form-text { font-size: 12px; color: #718096; margin-top: 5px; }
.btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 12px 24px; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: transform 0.2s ease, box-shadow 0.2s ease; width: 100%; margin-bottom: 10px; }
.btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.2); }
.btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
.btn-secondary { background: linear-gradient(135deg, #718096 0%, #4a5568 100%); }
.btn-success { background: linear-gradient(135deg, #48bb78 0%, #38a169 100%); }
.btn-warning { background: linear-gradient(135deg, #ffc19d 0%, #ffc19d 100%); }
/*表格弹窗样式*/ .search { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.search-container{ white-space: nowrap; padding: 6px 12px; font-size: 14px; background-color: #8a94e0; color: white; border: none; border-radius: 6px; cursor: pointer; }
.search-modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 1000; justify-content: center; align-items: center; }
.search-modal-content { background: white; padding: 20px; border-radius: 10px; width: 90%; max-width: 1000px; max-height: 80vh; display: flex; flex-direction: column; text-align: center; }
.search-modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
.search-modal-title { margin: 0; font-size: 1.5rem; }
.close-modal-btn { background: #764ba2; color: white; border: none; padding: 5px 10px; border-radius: 5px; cursor: pointer; font-size: 1rem; }
.search-results-container { overflow-y: auto; flex-grow: 1; }
.rep-code { cursor: pointer; padding: 2px 8px; background: #f5f7fa; border-radius: 4px; font-size: 12px; color: #1989fa; transition: background 0.2s; display: inline-block; text-align: center; }
.rep-code:hover { background: #e8f3ff; }
.representative-codes { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
.chart-grid { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; }
.status { padding: 15px; border-radius: 8px; margin-bottom: 20px; font-weight: 500; }
.indicator-status { padding: 15px; border-radius: 8px; margin-bottom: 20px; font-weight: 500; }
.status.success { background: #c6f6d5; color: #22543d; border: 1px solid #9ae6b4; }
.status.error { background: #fed7d7; color: #742a2a; border: 1px solid #feb2b2; }
.status.info { background: #bee3f8; color: #2a4365; border: 1px solid #90cdf4; }
.status.warning { background: #fef5e7; color: #744210; border: 1px solid #fbd38d; }
.chart-container { background: white; border-radius: 15px; padding: 25px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }
.chart-container h2 { color: #4a5568; margin-bottom: 20px; font-size: 1.5rem; border-bottom: 2px solid #e2e8f0; padding-bottom: 10px; }
#chart { width: 100%; height: 600px; }
.data-info { background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px; margin-bottom: 20px; }
.data-info h3 { color: #4a5568; margin-bottom: 10px; font-size: 1.1rem; }
.data-info p { margin-bottom: 5px; color: #4a5568; }
.data-info strong { color: #2d3748; }
/* Time window selector styles */ .time-window-container { background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; margin-bottom: 20px; }
.time-window-container h3 { color: #4a5568; margin-bottom: 15px; font-size: 1.1rem; }
.time-window-info { display: flex; justify-content: space-between; margin-bottom: 15px; font-size: 12px; color: #666; }
.time-window-slider { position: relative; margin-bottom: 10px; }
.slider-track { position: relative; height: 6px; background: #e2e8f0; border-radius: 3px; cursor: pointer; }
.slider-handle { position: absolute; top: -7px; width: 20px; height: 20px; background: #667eea; border-radius: 50%; cursor: grab; border: 2px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.2); z-index: 10; }
.slider-handle:hover { background: #5a67d8; transform: scale(1.1); }
.slider-handle:active { cursor: grabbing; }
.slider-selection { position: absolute; height: 6px; background: #48bb78; border-radius: 3px; top: 0; }
.slider-labels { display: flex; justify-content: space-between; font-size: 11px; color: #999; margin-top: 5px; }
/* Comparison analysis styles */ .comparison-section { background: #f7fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; margin-top: 20px; }
.comparison-section h3 { color: #4a5568; margin-bottom: 15px; font-size: 1.1rem; }
.comparison-info { background: white; border: 1px solid #e2e8f0; border-radius: 6px; padding: 15px; margin-bottom: 15px; }
.comparison-table { width: 100%; border-collapse: collapse; margin-top: 15px; }
.comparison-table th, .comparison-table td { border: 1px solid #e2e8f0; padding: 8px; text-align: center; font-size: 12px; }
.comparison-table th { background: #f7fafc; font-weight: 600; }
.error-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-top: 15px; }
.error-stat { background: white; border: 1px solid #e2e8f0; border-radius: 6px; padding: 15px; text-align: center; }
.error-stat h4 { color: #4a5568; margin-bottom: 5px; font-size: 0.9rem; }
.error-stat .value { font-size: 1.5rem; font-weight: 600; color: #667eea; }
.error-stat .unit { font-size: 0.8rem; color: #718096; }
.loading { display: none; text-align: center; padding: 20px; }
.loading.show { display: block; }
.spinner { border: 4px solid #f3f3f3; border-top: 4px solid #667eea; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 10px; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.indicator-loading { display: none; text-align: center; padding: 20px; }
.indicator-loading.show { display: block; }
.indicator-loading .spinner { border: 4px solid #f3f3f3; border-top: 4px solid #6dea66; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 10px; }
.model-info { background: #e6fffa; border: 1px solid #81e6d9; border-radius: 8px; padding: 15px; margin-bottom: 20px; }
.model-info h3 { color: #234e52; margin-bottom: 10px; font-size: 1.1rem; }
.model-info p { margin-bottom: 5px; color: #234e52; }
.model-info strong { color: #0f2027; }
@media (max-width: 768px) { .main-content { grid-template-columns: 1fr; } .container { padding: 10px; } .header h1 { font-size: 2rem; } } </style> </head><body> <div class = "container"> <div class = "header"> <h1>🚀 Kronos Financial Prediction Web UI</h1> <p>AI-based financial K-line data prediction analysis platform</p> </div>
<div class = "main-content"> <div class = "control-panel"> <h2>🎯 Control Panel</h2>
<!-- Model Selection --> <div class = "form-group"> <label for = "model-select">Select Model:</label> <select id = "model-select"> <option value = "">Please load available models first</option> </select> <small class = "form-text">Select the Kronos model to use</small> </div>
<!-- Device Selection --> <div class = "form-group"> <label for = "device-select">Select Device:</label> <select id = "device-select"> <option value = "cpu">CPU</option> <option value = "cuda">CUDA (NVIDIA GPU)</option> <option value = "mps">MPS (Apple Silicon)</option> </select> <small class = "form-text">Select the device to run the model on</small> </div>
<!-- Model Status --> <div id = "model-status" class = "status info" style = "display: none;"> Model status information </div>
<!-- Load Model Button --> <button id = "load-model-btn" class = "btn btn-secondary"> 🔄 Load Model </button>
<hr style = "margin: 20px 0; border: 1px solid #e2e8f0;">
<!-- 股票数据采集 --> <div class = "form-group"> <div class="search"> <label class="sr-only" for="stock_code">Ticker Symbol Input:</label>
<button id="search-btn" class="search-container"> 🔍 Search </button> </div>
<input type = "text" class = "form-control" id = "stock_code" name = 'stock_code' value = "{{ stock_code }}" placeholder = "例如:sh.600000" >
<small class="form-text">Enter the ticker symbol you want to analyze</small> </div>
<button id="stock-data-btn" class="btn btn-secondary"> 📄 Stock Data </button>
<!--股票数据提示弹窗--> <div id="search-modal" class="search-modal"> <div class="search-modal-content"> <div class="search-modal-header"> <h3 class="search-modal-title">📃 Stock Code Prompt:</h3> <button id="close-modal" class="close-modal-btn">×</button> </div> <div class="search-results-container"> <table class="comparison-table"> <thead> <tr> <th>Exchange</th> <th>Prefix</th> <th>Code_Range</th> <th>Representative_Code</th> </tr> </thead>
<tbody id="search-results-body">
<!-- 上海证券交易所 (SSE)--> <tr> <td rowspan="5">上海证券交易所 (SSE)</td> <td rowspan="5">sh.</td> <td>600</td> <td class="representative-codes"> <span class="rep-code">sh.600000(浦发银行)</span> <span class="rep-code">sh.600036(招商银行)</span> <span class="rep-code">sh.600519(贵州茅台)</span> <span class="rep-code">sh.600887(伊利股份)</span> <span class="rep-code">sh.600900(长江电力)</span> <span class="rep-code">sh.600030(中信证券)</span> <span class="rep-code">sh.600050(中国联通)</span> <span class="rep-code">sh.600690(海尔智家)</span> <span class="rep-code">sh.600570(恒生电子)</span> <span class="rep-code">sh.600588(用友网络)</span>
</td> </tr> <tr> <td>601</td> <td class="representative-codes"> <span class="rep-code">sh.601318(中国平安)</span> <span class="rep-code">sh.601398(工商银行)</span> <span class="rep-code">sh.601888(中国中免)</span> <span class="rep-code">sh.601857(中国石油)</span> <span class="rep-code">sh.601012(隆基绿能)</span> <span class="rep-code">sh.601988(中国国航)</span> <span class="rep-code">sh.601088(中国神华)</span> <span class="rep-code">sh.601766(中国中车)</span> <span class="rep-code">sh.601390(中国中铁)</span> <span class="rep-code">sh.601186(中国铁建)</span>
</td> </tr> <tr> <td>603</td> <td class="representative-codes"> <span class="rep-code">sh.603288(海天味业)</span> <span class="rep-code">sh.603259(药明康德)</span> <span class="rep-code">sh.603986(兆易创新)</span> <span class="rep-code">sh.603501(韦尔股份)</span> <span class="rep-code">sh.603993(中科曙光)</span> <span class="rep-code">sh.603899(晨光股份)</span> <span class="rep-code">sh.603605(珀莱雅)</span> <span class="rep-code">sh.603707(健友股份)</span> <span class="rep-code">sh.603833(欧派家居)</span> <span class="rep-code">sh.603806(福斯特)</span> </td> </tr> <tr> <td>605</td> <td class="representative-codes"> <span class="rep-code">sh.605499(东鹏饮料)</span> <span class="rep-code">sh.605338(巴比食品)</span> <span class="rep-code">sh.605111(新洁能)</span> <span class="rep-code">sh.605589(圣泉集团)</span> <span class="rep-code">sh.605168(三人行)</span> <span class="rep-code">sh.605066(天正电气)</span> <span class="rep-code">sh.605155(西大门)</span> <span class="rep-code">sh.605136(丽人丽妆)</span> <span class="rep-code">sh.605333(沪光股份)</span> <span class="rep-code">sh.605358(立昂微)</span> </td> </tr> <tr> <td>688</td> <td class="representative-codes"> <span class="rep-code">sh.688012(中微公司)</span> <span class="rep-code">sh.688981(中芯国际)</span> <span class="rep-code">sh.688111(金山办公)</span> <span class="rep-code">sh.688036(传音控股)</span> <span class="rep-code">sh.688008(澜起科技)</span> <span class="rep-code">sh.688235(百济神州)</span> <span class="rep-code">sh.688185(康希诺)</span> <span class="rep-code">sh.688036(传音控股)</span> <span class="rep-code">sh.688187(时代电气)</span> <span class="rep-code">sh.688390(固德威)</span> </td> </tr>
<!-- 深圳证券交易所 (SZSE)--> <tr> <td rowspan="5">深圳证券交易所</td> <td rowspan="5">sz.</td> <td>000</td> <td class="representative-codes"> <span class="rep-code">sz.000001(平安银行)</span> <span class="rep-code">sz.000002(万科A)</span> <span class="rep-code">sz.000858(五粮液)</span> <span class="rep-code">sz.000333(美的集团)</span> <span class="rep-code">sz.000651(格力电器)</span> <span class="rep-code">sz.000538(云南白药)</span> <span class="rep-code">sz.000063(中兴通讯)</span> <span class="rep-code">sz.000100(TCL科技)</span> <span class="rep-code">sz.000157(中联重科)</span> <span class="rep-code">sz.000895(双汇发展)</span> </td> </tr> <tr> <td>001</td> <td class="representative-codes"> <span class="rep-code">sz.001203(大中矿业)</span> <span class="rep-code">sz.001208(华菱线缆)</span> <span class="rep-code">sz.001212(中旗新材)</span> <span class="rep-code">sz.001213(中铁特货)</span> <span class="rep-code">sz.001215(千味央厨)</span> <span class="rep-code">sz.001217(华尔泰)</span> <span class="rep-code">sz.001218(丽臣实业)</span> <span class="rep-code">sz.001222(源飞宠物)</span> <span class="rep-code">sz.001225(和泰机电)</span> <span class="rep-code">sz.001309(德明利)</span> </td> </tr> <tr> <td>002</td> <td class="representative-codes"> <span class="rep-code">sz.002024(苏宁易购)</span> <span class="rep-code">sz.002027(分众传媒)</span> <span class="rep-code">sz.002555(三七互娱)</span> <span class="rep-code">sz.002624(完美世界)</span> <span class="rep-code">sz.002049(紫光国微)</span> <span class="rep-code">sz.002179(中航光电)</span> <span class="rep-code">sz.002415(海康威视)</span> <span class="rep-code">sz.002475(立讯精密)</span> <span class="rep-code">sz.002594(比亚迪)</span> <span class="rep-code">sz.002142(宁波银行)</span> </td> </tr> <tr> <td>003</td> <td class="representative-codes"> <span class="rep-code">sz.003816(中国广核)</span> <span class="rep-code">sz.003022(联泓新科)</span> <span class="rep-code">sz.003031(中瓷电子)</span> <span class="rep-code">sz.003032(传智教育)</span> <span class="rep-code">sz.003035(南网能源)</span> <span class="rep-code">sz.003036(泰坦股份)</span> <span class="rep-code">sz.003000(劲仔食品)</span> <span class="rep-code">sz.003001(中晶科技)</span> <span class="rep-code">sz.003002(传智教育)</span> <span class="rep-code">sz.003011(海象新材)</span> </td> </tr> <tr> <td>300</td> <td class="representative-codes"> <span class="rep-code">sz.300750(宁德时代)</span> <span class="rep-code">sz.300059(东方财富)</span> <span class="rep-code">sz.300124(汇川技术)</span> <span class="rep-code">sz.300760(迈瑞医疗)</span> <span class="rep-code">sz.300014(亿纬锂能)</span> <span class="rep-code">sz.300122(智飞生物)</span> <span class="rep-code">sz.300015(爱尔眼科)</span> <span class="rep-code">sz.300274(阳光电源)</span> <span class="rep-code">sz.300496(中科创达)</span> <span class="rep-code">sz.300782(卓胜微)</span> </td> </tr>
<!--? 北京证券交易所 (BSE) --><!--? <tr>--><!--? <td rowspan="5">北京证券交易所 (BSE)</td>--><!--? <td rowspan="5">bj.</td>--><!--? <td>43</td>--><!--? <td class="representative-codes">--><!--? <span class="rep-code">bj.430047(诺思兰德)</span>--><!--? <span class="rep-code">bj.430090(同辉信息)</span>--><!--? <span class="rep-code">bj.430198(微创光电)</span>--><!--? <span class="rep-code">bj.430300(辰光医疗)</span>--><!--? <span class="rep-code">bj.430425(乐创技术)</span>--><!--? <span class="rep-code">bj.430476(海能技术)</span>--><!--? <span class="rep-code">bj.430510(丰光精密)</span>--><!--? <span class="rep-code">bj.430556(雅葆轩)</span>--><!--? <span class="rep-code">bj.430564(天润科技)</span>--><!--? <span class="rep-code">bj.430685(新芝生物)</span>--><!--? </td>--><!--? </tr>--><!--? <tr>--><!--? <td>83</td>--><!--? <td class="representative-codes">--><!--? <span class="rep-code">bj.830799(艾融软件)</span>--><!--? <span class="rep-code">bj.830809(安达科技)</span>--><!--? <span class="rep-code">bj.830839(万通液压)</span>--><!--? <span class="rep-code">bj.830866(齐鲁华信)</span>--><!--? <span class="rep-code">bj.830879(基康仪器)</span>--><!--? <span class="rep-code">bj.830896(旺成科技)</span>--><!--? <span class="rep-code">bj.830946(森萱医药)</span>--><!--? <span class="rep-code">bj.830964(润农节水)</span>--><!--? <span class="rep-code">bj.831010(凯添燃气)</span>--><!--? <span class="rep-code">bj.831039(国义招标)</span>--><!--? </td>--><!--? </tr>--><!--? <tr>--><!--? <td>87</td>--><!--? <td class="representative-codes">--><!--? <span class="rep-code">bj.870204(沪江材料)</span>--><!--? <span class="rep-code">bj.870357(雅葆轩)</span>--><!--? <span class="rep-code">bj.870436(大地电气)</span>--><!--? <span class="rep-code">bj.870640(曙光数创)</span>--><!--? <span class="rep-code">bj.870726(鸿智科技)</span>--><!--? <span class="rep-code">bj.870866(视声智能)</span>--><!--? <span class="rep-code">bj.870976(视声智能)</span>--><!--? <span class="rep-code">bj.871263(一致魔芋)</span>--><!--? <span class="rep-code">bj.871396(佳合科技)</span>--><!--? <span class="rep-code">bj.871478(海泰新能)</span>--><!--? </td>--><!--? </tr>--><!--? <tr>--><!--? <td>89</td>--><!--? <td class="representative-codes">--><!--? <span class="rep-code">bj.892089(科强股份)</span>--><!--? <span class="rep-code">bj.892282(美心翼申)</span>--><!--? <span class="rep-code">bj.892358(派诺科技)</span>--><!--? <span class="rep-code">bj.892519(捷众科技)</span>--><!--? <span class="rep-code">bj.892541(康农种业)</span>--><!--? <span class="rep-code">bj.892622(许昌智能)</span>--><!--? <span class="rep-code">bj.892679(云星宇)</span>--><!--? <span class="rep-code">bj.892748(欣捷高新)</span>--><!--? <span class="rep-code">bj.892810(捷安高科)</span>--><!--? <span class="rep-code">bj.892925(海昇药业)</span>--><!--? </td>--><!--? </tr>--><!--? <tr>--><!--? <td>920</td>--><!--? <td class="representative-codes">--><!--? <span class="rep-code">bj.920099(万达轴承)</span>--><!--? <span class="rep-code">bj.920175(欧福蛋业)</span>--><!--? <span class="rep-code">bj.920177(瑞华技术)</span>--><!--? <span class="rep-code">bj.920179(和特能源)</span>--><!--? <span class="rep-code">bj.920180(豪钢重工)</span>--><!--? <span class="rep-code">bj.920179(和特能源)</span>--><!--? </td>--><!--? </tr>--> </tbody> </table> </div> </div> </div>
<hr style="margin: 20px 0; border: 1px solid #e2e8f0;">
<!-- Data File Selection --> <div class = "form-group"> <label for = "data-file-select">Select Data File:</label> <select id = "data-file-select"> <option value = "">Please load data file list first</option> </select> <small class = "form-text">Select K-line data file from data directory</small> </div>
<button id = "load-data-btn" class = "btn btn-secondary"> 📁 Load Data </button>
<!-- Data Information Display --> <div id = "data-info" class = "data-info" style = "display: none;"> <h3>📊 Data Information</h3> <p><strong>Rows:</strong> <span id = "data-rows">-</span></p> <p><strong>Columns:</strong> <span id = "data-cols">-</span></p> <p><strong>Time Range:</strong> <span id = "data-time-range">-</span></p> <p><strong>Price Range:</strong> <span id = "data-price-range">-</span></p> <p><strong>Time Frequency:</strong> <span id = "data-timeframe">-</span></p> <p><strong>Prediction Columns:</strong> <span id = "data-prediction-cols">-</span></p> </div>
<hr style = "margin: 20px 0; border: 1px solid #e2e8f0;">
<!-- Time Window Selector --> <div class = "time-window-container"> <h3>⏰ Time Window Selection</h3> <div class = "time-window-info"> <span id = "window-start">Start: --</span> <span id = "window-end">End: --</span> <span id = "window-size">Window Size: 400 + 120 = 520 data points</span> </div> <div class = "time-window-slider"> <div class = "slider-track"> <div class = "slider-handle start-handle" id = "start-handle"></div> <div class = "slider-selection" id = "slider-selection"></div> <div class = "slider-handle end-handle" id = "end-handle"></div> </div> <div class = "slider-labels"> <span id = "min-label">Earliest</span> <span id = "max-label">Latest</span> </div> </div> <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> </div>
<!-- Prediction Parameters --> <div class="form-group"> <label for="lookback">Lookback Window Size:</label> <input type="number" id="lookback" value="400" readonly> <small class="form-text">Fixed at 400 data points</small> </div>
<div class="form-group"> <label for="pred-len">Prediction Length:</label> <input type="number" id="pred-len" value="120" readonly> <small class="form-text">Fixed at 120 data points</small> </div>
<!-- Prediction Quality Parameters --> <div class="form-group"> <label for="temperature">Prediction Temperature (T):</label> <input type="range" id="temperature" value="1.0" min="0.1" max="2.0" step="0.1"> <span id="temperature-value">1.0</span> <small class="form-text">Controls prediction randomness, higher values make predictions more diverse, lower values make predictions more conservative</small> </div>
<div class="form-group"> <label for="top-p">Nucleus Sampling Parameter (top_p):</label> <input type="range" id="top-p" value="0.9" min="0.1" max="1.0" step="0.1"> <span id="top-p-value">0.9</span> <small class="form-text">Controls prediction diversity, higher values consider broader probability distributions</small> </div>
<div class="form-group"> <label for="sample-count">Sample Count:</label> <input type="number" id="sample-count" value="1" min="1" max="5" step="1"> <small class="form-text">Generate multiple prediction samples to improve quality (recommended 1-3)</small> </div>
<button id="predict-btn" class="btn btn-success" disabled> 🔮 Start Prediction </button>
<!-- Loading Status --> <div id="loading" class="loading"> <div class="spinner"></div> <p>Processing, please wait...</p> </div> </div>
<div class="chart-container"> <h2>📈 Prediction Results Chart</h2> <div id="chart"></div>
<!-- Comparison Analysis --> <div id="comparison-section" class="comparison-section" style="display: none;"> <h3>📊 Prediction vs Actual Data Comparison</h3> <div id="comparison-info" class="comparison-info"> <p><strong>Prediction Type:</strong> <span id="prediction-type">-</span></p> <p><strong>Comparison Data:</strong> <span id="comparison-data">-</span></p> </div>
<div class="error-stats"> <div class="error-stat"> <h4>Mean Absolute Error</h4> <div class="value" id="mae">-</div> <div class="unit">Price Units</div> </div> <div class="error-stat"> <h4>Root Mean Square Error</h4> <div class="value" id="rmse">-</div> <div class="unit">Price Units</div> </div> <div class="error-stat"> <h4>Mean Absolute Percentage Error</h4> <div class="value" id="mape">-</div> <div class="unit">%</div> </div> </div>
<div class="error-details"> <h4>Detailed Comparison Data:</h4> <div style="max-height: 300px; overflow-y: auto;"> <table class="comparison-table"> <thead> <tr> <th>Time</th> <th>Actual Open</th> <th>Predicted Open</th> <th>Actual High</th> <th>Predicted High</th> <th>Actual Low</th> <th>Predicted Low</th> <th>Actual Close</th> <th>Predicted Close</th> </tr> </thead> <tbody id="comparison-tbody"> </tbody> </table> </div> </div> </div>
<br>
<!--技术指标图表--> <h2>📶 Technical Indicator Chart</h2>
<div id="indicator-status" class="indicator-status" style="display: none;"></div>
<div class="form-group"> <label for="diagram_type">Select Diagram Type:</label> <div class="chart-grid"> <select id="diagram_type" class="form-control"> <option value="Volume Chart (VOL)">Volume Chart (VOL)</option> <option value="Moving Average (MA)">Moving Average (MA)</option> <option value="MACD Indicator (MACD)">MACD Indicator (MACD)</option> <option value="RSI Indicator (RSI)">RSI Indicator (RSI)</option> <option value="Bollinger Bands (BB)">Bollinger Bands (BB)</option> <option value="Stochastic Oscillator (STOCH)">Stochastic Oscillator (STOCH)</option> <option value="Rolling Window Mean Strategy">Rolling Window Mean Strategy</option> <option value="TRIX Indicator (TRIX)">TRIX Indicator (TRIX)</option> </select>
<button id="generate-chart-btn" class="btn btn-warning"> ✏️ Generate chart </button> </div>
<small class="form-text">Select the type to draw the relevant chart</small> </div>
<div id="indicator-loading" class="indicator-loading"> <div class="spinner"></div> <p>Generating chart, please wait...</p> </div>
<div id="indicator-chart"></div>
<!-- 数据表格 --> <div id="data-presentation" class="comparison-section" style="display: none;"> <h3>💹 Financial Data Visualization</h3>
<div class="error-details"> <h4>Detailed Financial Data:</h4> <div style="max-height: 300px; overflow-y: auto;"> <table class="comparison-table"> <thead> <tr> <th>Timestamps</th> <th>Open</th> <th>High</th> <th>Low</th> <th>Close</th> <th>Volume</th> <th>Amount</th> </tr> </thead> <tbody id="data-tbody"> </tbody> </table> </div> </div> </div> </div> </div> </div>
<script> // Global variables let currentDataFile = null; let currentDataInfo = null; let availableModels = []; let modelLoaded = false;
// Initialize after page loads document.addEventListener('DOMContentLoaded', function() { initializeApp(); });
// Initialize application async function initializeApp() { console.log('🚀 Initializing Kronos Web UI...'); // Load available models await loadAvailableModels(); // Load data file list await loadDataFiles(); // Set up event listeners setupEventListeners(); // Initialize time slider initializeTimeSlider(); console.log('✅ Application initialization completed'); }
// Load available models async function loadAvailableModels() { try { console.log('开始加载模型列表...'); const response = await fetch('/api/available-models'); const data = await response.json(); console.log('API返回数据:', data);
const modelSelect = document.getElementById('model-select'); console.log('找到的下拉菜单:', modelSelect);
if (!modelSelect) { console.error('错误: 找不到 modelSelect 元素'); return; }
// 清空现有选项 modelSelect.innerHTML = '';
// 添加模型选项 for (const [key, model] of Object.entries(data.models)) { const option = document.createElement('option'); option.value = key; option.textContent = `${model.name} (${model.params}) - ${model.description}`; modelSelect.appendChild(option); console.log('添加选项:', option.textContent); }
console.log('最终选项数量:', modelSelect.options.length);
} catch (error) { console.error('加载模型列表失败:', error); } }
// Populate model selection dropdown function populateModelSelect() { const modelSelect = document.getElementById('model-select'); modelSelect.innerHTML = '<option value="">Please select model</option>'; Object.entries(availableModels).forEach(([key, model]) => { const option = document.createElement('option'); option.value = key; option.textContent = `${model.name} (${model.params}) - ${model.description}`; modelSelect.appendChild(option); }); }
// Load model async function loadModel() { const modelKey = document.getElementById('model-select').value; const device = document.getElementById('device-select').value; if (!modelKey) { showStatus('error', 'Please select a model to load'); return; } try { showLoading(true); document.getElementById('load-model-btn').disabled = true; const response = await axios.post('/api/load-model', { model_key: modelKey, device: device }); if (response.data.success) { modelLoaded = true; showStatus('success', response.data.message); updateModelStatus(); document.getElementById('predict-btn').disabled = false; console.log('✅ Model loaded successfully:', response.data.model_info); } else { showStatus('error', response.data.error); } } catch (error) { console.error('❌ Model loading failed:', error); showStatus('error', `Model loading failed: ${error.response?.data?.error || error.message}`); } finally { showLoading(false); document.getElementById('load-model-btn').disabled = false; } }
// Update model status async function updateModelStatus() { try { const response = await axios.get('/api/model-status'); const status = response.data; if (status.loaded) { showStatus('success', `Model loaded: ${status.current_model.name} on ${status.current_model.device}`); } else if (status.available) { showStatus('info', 'Model available but not loaded'); } else { showStatus('warning', 'Model library not available'); } } catch (error) { console.error('❌ Failed to get model status:', error); } }
//Stock Data按钮 document.addEventListener('DOMContentLoaded', function() { const generateChartBtn = document.getElementById('stock-data-btn'); if (generateChartBtn) { generateChartBtn.addEventListener('click', StockData); console.log('Stock Data button event listener bound'); } else { console.error('stock-data-btn element not found'); } });
async function StockData() { console.log('Get stock data...'); const stockCodeInput = document.getElementById('stock_code'); const generateBtn = document.getElementById('stock-data-btn'); const stockCode = stockCodeInput.value.trim(); generateBtn.disabled = true;
try { if (!stockCode) { showStatus('error', 'Stock code cannot be empty'); return; }
const stockCodeRegex = /^[a-z]+\.\d+$/; if (!stockCodeRegex.test(stockCode)) { showStatus('error', 'The ticker symbol is in the wrong format'); return; }
showLoading(true);
const response = await axios.post('/api/stock-data', {stock_code: stockCode});
if (response.data.success) { showStatus('success', `Successfully fetched data for ${stockCode}`); loadDataFiles(); stockCodeInput.value = ''; } else { showStatus('error', response.data.error || 'Failed to fetch stock data'); } } catch (error) { console.error('❌ Failed to fetch stock data:', error); showStatus('error', `Failed to fetch data`); } finally { showLoading(false); if (generateBtn) generateBtn.disabled = false; } }
// Search按钮表格弹窗 document.addEventListener('DOMContentLoaded', function() { const searchBtn = document.getElementById('search-btn'); const searchModal = document.getElementById('search-modal'); const closeModal = document.getElementById('close-modal'); const stockCodeInput = document.getElementById('stock_code');
searchBtn.addEventListener('click', () => { searchModal.style.display = 'flex'; });
closeModal.addEventListener('click', () => { searchModal.style.display = 'none'; });
document.addEventListener('click', (e) => { if (e.target.classList.contains('rep-code')) { const code = e.target.textContent.match(/[a-z]+\.\d+/)[0]; stockCodeInput.value = code; closeModalFunc(); } }); });
// Load data file list async function loadDataFiles() { try { const response = await axios.get('/api/data-files'); const dataFiles = response.data; const dataFileSelect = document.getElementById('data-file-select'); dataFileSelect.innerHTML = '<option value="">Please select data file</option>'; dataFiles.forEach(file => { const option = document.createElement('option'); option.value = file.path; option.textContent = `${file.name} (${file.size})`; dataFileSelect.appendChild(option); }); console.log('✅ Data file list loaded successfully:', dataFiles); } catch (error) { console.error('❌ Failed to load data file list:', error); showStatus('error', 'Failed to load data file list'); } }
// Load data file async function loadData() { const filePath = document.getElementById('data-file-select').value; if (!filePath) { showStatus('error', 'Please select a data file to load'); return; } try { showLoading(true); document.getElementById('load-data-btn').disabled = true; const response = await axios.post('/api/load-data', { file_path: filePath }); if (response.data.success) { currentDataFile = filePath; currentDataInfo = response.data.data_info; showDataInfo(response.data.data_info); showStatus('success', response.data.message); // Update prediction button status if (modelLoaded) { document.getElementById('predict-btn').disabled = false; } console.log('✅ Data loaded successfully:', response.data.data_info); } else { showStatus('error', response.data.error); } } catch (error) { console.error('❌ Data loading failed:', error); showStatus('error', `Data loading failed: ${error.response?.data?.error || error.message}`); } finally { showLoading(false); document.getElementById('load-data-btn').disabled = false; } }
// Display data information function showDataInfo(dataInfo) { document.getElementById('data-info').style.display = 'block'; document.getElementById('data-rows').textContent = dataInfo.rows; document.getElementById('data-cols').textContent = dataInfo.columns.length; document.getElementById('data-time-range').textContent = `${dataInfo.start_date} to ${dataInfo.end_date}`; document.getElementById('data-price-range').textContent = `${dataInfo.price_range.min.toFixed(4)} - ${dataInfo.price_range.max.toFixed(4)}`; document.getElementById('data-timeframe').textContent = dataInfo.timeframe; document.getElementById('data-prediction-cols').textContent = dataInfo.prediction_columns.join(', '); // Initialize time window slider initializeTimeWindowSlider(dataInfo); }
// Time window slider related variables let sliderData = null; let isDragging = false; let currentHandle = null;
// Initialize time window slider function initializeTimeSlider() { // Set up slider event listeners setupSliderEventListeners(); }
// Set up slider event listeners function setupSliderEventListeners() { const startHandle = document.getElementById('start-handle'); const endHandle = document.getElementById('end-handle'); const track = document.querySelector('.slider-track'); // Start dragging startHandle.addEventListener('mousedown', (e) => { isDragging = true; currentHandle = 'start'; e.preventDefault(); }); endHandle.addEventListener('mousedown', (e) => { isDragging = true; currentHandle = 'end'; e.preventDefault(); }); // Dragging document.addEventListener('mousemove', (e) => { if (!isDragging) return; const rect = track.getBoundingClientRect(); const x = e.clientX - rect.left; const percentage = Math.max(0, Math.min(1, x / rect.width)); if (currentHandle === 'start') { updateStartHandle(percentage); } else if (currentHandle === 'end') { updateEndHandle(percentage); } updateSliderFromHandles(); });
// End dragging document.addEventListener('mouseup', () => { isDragging = false; currentHandle = null; });
// Click track to set position directly track.addEventListener('click', (e) => { const rect = track.getBoundingClientRect(); const x = e.clientX - rect.left; const percentage = Math.max(0, Math.min(1, x / rect.width)); // Determine which handle is closer to the click position const startHandle = document.getElementById('start-handle'); const endHandle = document.getElementById('end-handle'); const startRect = startHandle.getBoundingClientRect(); const endRect = endHandle.getBoundingClientRect(); if (Math.abs(x - (startRect.left - rect.left)) < Math.abs(x - (endRect.left - rect.left))) { updateStartHandle(percentage); } else { updateEndHandle(percentage); } updateSliderFromHandles(); }); }
// Update start handle position function updateStartHandle(percentage) { const startHandle = document.getElementById('start-handle'); const selection = document.getElementById('slider-selection'); // Fixed window size of 520 data points const windowSize = 520; const totalRows = sliderData ? sliderData.totalRows : 1000; const windowPercentage = windowSize / totalRows; // Ensure start handle doesn't cause window to exceed data range if (percentage + windowPercentage > 1) { percentage = 1 - windowPercentage; } startHandle.style.left = (percentage * 100) + '%'; selection.style.left = (percentage * 100) + '%'; selection.style.width = (windowPercentage * 100) + '%'; // Automatically adjust end handle position to maintain fixed window size const endHandle = document.getElementById('end-handle'); endHandle.style.left = ((percentage + windowPercentage) * 100) + '%'; }
// Update end handle position function updateEndHandle(percentage) { const endHandle = document.getElementById('end-handle'); const selection = document.getElementById('slider-selection'); // Fixed window size of 520 data points const windowSize = 520; const totalRows = sliderData ? sliderData.totalRows : 1000; const windowPercentage = windowSize / totalRows; // Ensure end handle doesn't cause window to exceed data range if (percentage - windowPercentage < 0) { percentage = windowPercentage; } endHandle.style.left = (percentage * 100) + '%'; selection.style.left = ((percentage - windowPercentage) * 100) + '%'; selection.style.width = (windowPercentage * 100) + '%'; // Automatically adjust start handle position to maintain fixed window size const startHandle = document.getElementById('start-handle'); startHandle.style.left = ((percentage - windowPercentage) * 100) + '%'; }
// Update slider display based on handle positions function updateSliderFromHandles() { const startHandle = document.getElementById('start-handle'); const endHandle = document.getElementById('end-handle'); const startPercentage = parseFloat(startHandle.style.left) / 100; const endPercentage = parseFloat(endHandle.style.left) / 100; if (!sliderData) return; // Calculate selected time range const totalTime = sliderData.endDate.getTime() - sliderData.startDate.getTime(); const startTime = sliderData.startDate.getTime() + (totalTime * startPercentage); const endTime = sliderData.startDate.getTime() + (totalTime * endPercentage); const startDate = new Date(startTime); const endDate = new Date(endTime); // Update display information document.getElementById('window-start').textContent = `Start: ${startDate.toLocaleDateString()}`; document.getElementById('window-end').textContent = `End: ${endDate.toLocaleDateString()}`; // Display fixed window size document.getElementById('window-size').textContent = `Window Size: 400 + 120 = 520 data points (fixed)`; // Input field values remain fixed document.getElementById('lookback').value = 400; document.getElementById('pred-len').value = 120; }
// Update slider based on input fields function updateSliderFromInputs() { if (!sliderData) return; // Fixed window size: 400 + 120 = 520 data points const lookback = 400; const predLen = 120; const windowSize = lookback + predLen; // Fixed at 520 // Calculate slider position const totalRows = sliderData.totalRows; if (windowSize > totalRows) { // If window size exceeds total data amount, show error showStatus('error', `Insufficient data, need at least ${windowSize} data points, currently only ${totalRows} available`); return; } // Calculate slider position (default select first half of data) const startPercentage = 0.1; // Start from 10% const endPercentage = startPercentage + (windowSize / totalRows); // Update handle positions updateStartHandle(startPercentage); updateEndHandle(endPercentage); // Update display information updateSliderFromHandles(); }
// Initialize time window slider function initializeTimeWindowSlider(dataInfo) { sliderData = { startDate: new Date(dataInfo.start_date), endDate: new Date(dataInfo.end_date), totalRows: dataInfo.rows, timeframe: dataInfo.timeframe }; // Set slider labels document.getElementById('min-label').textContent = dataInfo.start_date.split('T')[0]; document.getElementById('max-label').textContent = dataInfo.end_date.split('T')[0]; // Initialize slider position updateSliderFromInputs(); }
// Binding Generate chart document.addEventListener('DOMContentLoaded', function() { const generateChartBtn = document.getElementById('generate-chart-btn');
if (generateChartBtn) { generateChartBtn.addEventListener('click', generateTechnicalChart); console.log('Generate chart button event listener bound'); } else { console.error('generate-chart-btn element not found'); } });
// 技术指标图表 async function generateTechnicalChart() { console.log('Generating technical indicator chart...');
const indicatorLoading = document.getElementById('indicator-loading'); indicatorLoading.classList.add('show');
const generateBtn = document.getElementById('generate-chart-btn'); generateBtn.disabled = true;
try { await new Promise(resolve => setTimeout(resolve, 2000));
const startHandle = document.getElementById('start-handle'); const endHandle = document.getElementById('end-handle');
const startPercentage = parseFloat(startHandle.style.left) / 100; const endPercentage = parseFloat(endHandle.style.left) / 100;
const totalRows = sliderData ? sliderData.totalRows : 0; const historicalStartIdx = Math.floor(startPercentage * totalRows);
const lookback = Math.floor((endPercentage - startPercentage) * totalRows);
const predLen = parseInt(document.getElementById('pred-len').value);
const filePath = document.getElementById('data-file-select').value; const diagramType = document.getElementById('diagram_type').value;
if (!filePath) throw new Error('Please select a data file first'); if (isNaN(lookback) || isNaN(predLen)) throw new Error('Invalid parameters');
// 获取接口 const response = await fetch('/api/generate-chart', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file_path: filePath, lookback: lookback, pred_len: predLen, diagram_type: diagramType, historical_start_idx: historicalStartIdx }) });
const result = await response.json(); if (!result.success) throw new Error(result.error || 'Failed to generate chart');
// 渲染图标 const chartContainer = document.getElementById('indicator-chart'); if (chartContainer) { if (chartContainer.data) Plotly.purge(chartContainer); Plotly.newPlot( chartContainer, result.chart.data, result.chart.layout, { responsive: true } ); } else { throw new Error('Indicator chart container not found'); }
document.getElementById('data-presentation').style.display = 'block';
if (result.table_data) { fillDataTable(result.table_data); } else { console.warn('No table data returned from server'); fillDataTable([]); }
showIndicatorStatus('success', `chart (${diagramType}) generated successfully`);
} catch (error) { console.error('Chart generation error:', error); showIndicatorStatus('error', `Chart generation failed: ${error.message}`); } finally { indicatorLoading.classList.remove('show'); generateBtn.disabled = false; } }
// 数据表格 function fillDataTable(data) { const tbody = document.getElementById('data-tbody');
tbody.innerHTML = '';
if (!data || data.length === 0) { const emptyRow = document.createElement('tr'); emptyRow.innerHTML = '<td colspan = "7" style = "text-align:center">暂无数据</td>'; tbody.appendChild(emptyRow); return; }
data.forEach(item => { const row = document.createElement('tr'); row.innerHTML = ` <td>${new Date(item.timestamps).toLocaleString()}</td> <td>${item.open.toFixed(4)}</td> <td>${item.high.toFixed(4)}</td> <td>${item.low.toFixed(4)}</td> <td>${item.close.toFixed(4)}</td> <td>${item.volume ? item.volume.toLocaleString() : '-'}</td> <td>${item.amount ? item.amount.toFixed(2) : '-'}</td> `; tbody.appendChild(row); }); }
// Start prediction async function startPrediction() { if (!currentDataFile) { showStatus('error', 'Please load data file first'); return; }
if (!modelLoaded) { showStatus('error', 'Please load model first'); return; }
try { showLoading(true); document.getElementById('predict-btn').disabled = true;
const lookback = parseInt(document.getElementById('lookback').value); const predLen = parseInt(document.getElementById('pred-len').value);
// Get selected time range from time window slider const startHandle = document.getElementById('start-handle'); const startPercentage = parseFloat(startHandle.style.left) / 100;
if (!sliderData) { showStatus('error', 'Time window slider not initialized'); return; }
// Calculate selected time range const totalTime = sliderData.endDate.getTime() - sliderData.startDate.getTime(); const startTime = sliderData.startDate.getTime() + (totalTime * startPercentage); const startDate = new Date(startTime);
// Get prediction quality parameters const temperature = parseFloat(document.getElementById('temperature').value); const topP = parseFloat(document.getElementById('top-p').value); const sampleCount = parseInt(document.getElementById('sample-count').value);
let predictionParams = { file_path: currentDataFile, lookback: lookback, pred_len: predLen, start_date: startDate.toISOString().slice(0, 16), // Format as YYYY-MM-DDTHH:MM temperature: temperature, top_p: topP, sample_count: sampleCount };
console.log('🚀 Starting prediction, parameters:', predictionParams);
const response = await axios.post('/api/predict', predictionParams);
console.log('📊 Prediction response received:', response.data);
// 添加更详细的响应数据检查 console.log('🔍 Response data check:'); console.log('- success:', response.data.success); console.log('- has chart:', !!response.data.chart); console.log('- chart length:', response.data.chart ? response.data.chart.length : 0); console.log('- prediction results count:', response.data.prediction_results ? response.data.prediction_results.length : 0); console.log('- actual data count:', response.data.actual_data ? response.data.actual_data.length : 0);
if (response.data.success) { // Display prediction results displayPredictionResult(response.data); showStatus('success', response.data.message); } else { showStatus('error', response.data.error); } } catch (error) { console.error('❌ Prediction failed:', error); showStatus('error', `Prediction failed: ${error.response?.data?.error || error.message}`); } finally { showLoading(false); document.getElementById('predict-btn').disabled = false; } }
// // Display prediction results // {#function displayPredictionResult(result) {#} // {# // Display chart#} // {# const chartData = JSON.parse(result.chart);#} // {# Plotly.newPlot('chart', chartData.data, chartData.layout);#} // {##} // {# // Display comparison analysis (if actual data exists)#} // {# if (result.has_comparison) {#} // {# displayComparisonAnalysis(result);#} // {# } else {#} // {# document.getElementById('comparison-section').style.display = 'none';#} // {# }#} // {#}#}
function displayPredictionResult(result) { console.log('🔍 DEBUG - displayPredictionResult called'); console.log('Result keys:', Object.keys(result)); console.log('Has chart:', !!result.chart); console.log('Chart type:', typeof result.chart); console.log('Chart length:', result.chart ? result.chart.length : 0); console.log('Has comparison:', result.has_comparison); console.log('Actual data length:', result.actual_data ? result.actual_data.length : 0);
try { // Parse and display chart if (result.chart) { console.log('📊 Parsing chart data...'); const chartData = JSON.parse(result.chart); console.log('📈 Chart data parsed successfully:', chartData);
// Clear previous chart const chartDiv = document.getElementById('chart'); chartDiv.innerHTML = '';
// Create new chart with error handling Plotly.newPlot('chart', chartData.data, chartData.layout, { responsive: true }).then(function () { console.log('✅ Chart rendered successfully'); // 确保图表容器可见 chartDiv.style.display = 'block'; }).catch(function (error) { console.error('❌ Chart rendering failed:', error); showStatus('error', `图表渲染失败: ${error.message}`);
// 显示错误信息 chartDiv.innerHTML = ` <div style="text-align: center; padding: 50px; color: #666;"> <h3>图表加载失败</h3> <p>错误信息: ${error.message}</p> <p>请检查控制台获取详细信息</p> </div> `; }); } else { console.error('❌ No chart data in response'); showStatus('error', '服务器返回的图表数据为空'); }
// Display comparison analysis (if actual data exists) if (result.has_comparison && result.actual_data && result.actual_data.length > 0) { console.log('📊 Displaying comparison analysis'); displayComparisonAnalysis(result); } else { console.log('ℹ️ No comparison data available'); document.getElementById('comparison-section').style.display = 'none'; }
} catch (error) { console.error('❌ Error displaying prediction result:', error); showStatus('error', `结果显示失败: ${error.message}`);
// 显示错误信息在图表区域 const chartDiv = document.getElementById('chart'); chartDiv.innerHTML = ` <div style="text-align: center; padding: 50px; color: #666;"> <h3>数据处理失败</h3> <p>错误信息: ${error.message}</p> <p>请检查数据格式是否正确</p> </div> `; } }
// Display comparison analysis function displayComparisonAnalysis(result) { document.getElementById('comparison-section').style.display = 'block'; // Update comparison information document.getElementById('prediction-type').textContent = result.prediction_type; document.getElementById('comparison-data').textContent = `${result.actual_data.length} actual data points`; // Calculate error statistics const errorStats = getPredictionQuality(result.prediction_results, result.actual_data); // Display error statistics document.getElementById('mae').textContent = errorStats.mae.toFixed(4); document.getElementById('rmse').textContent = errorStats.rmse.toFixed(4); document.getElementById('mape').textContent = errorStats.mape.toFixed(2); // Fill comparison table fillComparisonTable(result.prediction_results, result.actual_data); }
// Calculate prediction quality metrics function getPredictionQuality(predictions, actuals) { if (!predictions || !actuals || predictions.length === 0 || actuals.length === 0) { return { mae: 0, rmse: 0, mape: 0 }; } const minLen = Math.min(predictions.length, actuals.length); let mae = 0, rmse = 0, mape = 0; for (let i = 0; i < minLen; i++) { const pred = predictions[i]; const act = actuals[i]; // Use closing price to calculate errors const error = Math.abs(pred.close - act.close); const percentError = (error / act.close) * 100; mae += error; rmse += error * error; mape += percentError; } mae /= minLen; rmse = Math.sqrt(rmse / minLen); mape /= minLen; return { mae, rmse, mape }; }
// Fill comparison table function fillComparisonTable(predictions, actuals) { const tbody = document.getElementById('comparison-tbody'); tbody.innerHTML = ''; const minLen = Math.min(predictions.length, actuals.length); for (let i = 0; i < minLen; i++) { const pred = predictions[i]; const act = actuals[i]; const row = document.createElement('tr'); row.innerHTML = ` <td>${new Date(pred.timestamp).toLocaleString()}</td> <td>${act.open.toFixed(4)}</td> <td>${pred.open.toFixed(4)}</td> <td>${act.high.toFixed(4)}</td> <td>${pred.high.toFixed(4)}</td> <td>${act.low.toFixed(4)}</td> <td>${pred.low.toFixed(4)}</td> <td>${act.close.toFixed(4)}</td> <td>${pred.close.toFixed(4)}</td> `; tbody.appendChild(row); } }
// Set up event listeners function setupEventListeners() { // Load model button document.getElementById('load-model-btn').addEventListener('click', loadModel); // Load data button document.getElementById('load-data-btn').addEventListener('click', loadData); // Prediction button document.getElementById('predict-btn').addEventListener('click', startPrediction);
// Generate chart button document.addEventListener('DOMContentLoaded', function(){ // Prediction quality parameter sliders document.getElementById('temperature').addEventListener('input', function() { document.getElementById('temperature-value').textContent = this.value; }); document.getElementById('top-p').addEventListener('input', function() { document.getElementById('top-p-value').textContent = this.value; }); // Update slider when lookback window size changes document.getElementById('lookback').addEventListener('input', updateSliderFromInputs); document.getElementById('pred-len').addEventListener('input', updateSliderFromInputs);
const chartButton = document.getElementById('load-chart-btn'); if (chartButton) { chartButton.addEventListener('click', generatechart); console.log('Chart button event listener bound'); } else { console.error('load-chart-btn element not found'); } }); }
// Display status information function showStatus(type, message) { const statusDiv = document.getElementById('model-status'); statusDiv.className = `status ${type}`; statusDiv.textContent = message; statusDiv.style.display = 'block'; // Auto-hide setTimeout(() => { statusDiv.style.display = 'none'; }, 5000); }
function showIndicatorStatus(type, message) { const statusDiv = document.getElementById('indicator-status'); statusDiv.className = `indicator-status status ${type}`; statusDiv.textContent = message; statusDiv.style.display = 'block';
// Auto-hide after 5 seconds setTimeout(() => { statusDiv.style.display = 'none'; }, 5000); }
// Show/hide loading status function showLoading(show) { const loadingDiv = document.getElementById('loading'); if (show) { loadingDiv.classList.add('show'); } else { loadingDiv.classList.remove('show'); } }
</script></body></html>
|