| <!DOCTYPE html> |
| <html lang="ko"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>๐ AI ์ฃผ์ ๋ฆฌ์์น ์ผํฐ v5.5</title> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
| |
| <script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script> |
| <style> |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #0a0a1a 0%, #1a1a2e 100%); padding: 20px; color: #e8e8e8; min-height: 100vh; } |
| .container { max-width: 1600px; margin: 0 auto; background: rgba(26, 26, 46, 0.95); border-radius: 20px; padding: 40px; box-shadow: 0 20px 60px rgba(0,0,0,0.5); border: 1px solid rgba(0,217,255,0.2); } |
| h1 { text-align: center; background: linear-gradient(135deg, #00d9ff, #00ff88); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 10px; font-size: 2.5em; } |
| .subtitle { text-align: center; color: #888; margin-bottom: 30px; } |
| |
| .indices-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; margin-bottom: 30px; } |
| .index-card { background: linear-gradient(135deg, rgba(0,217,255,0.1), rgba(0,255,136,0.05)); border: 1px solid rgba(0,217,255,0.3); border-radius: 15px; padding: 15px; text-align: center; } |
| .index-name { font-size: 0.85em; color: #888; margin-bottom: 5px; } |
| .index-value { font-size: 1.4em; font-weight: 700; color: #fff; } |
| .index-change { font-size: 0.9em; font-weight: 600; margin-top: 5px; } |
| .positive { color: #00ff88; } |
| .negative { color: #ff4757; } |
| |
| .tabs { display: flex; gap: 10px; margin-bottom: 30px; border-bottom: 2px solid rgba(255,255,255,0.1); overflow-x: auto; } |
| .tab { padding: 15px 30px; background: transparent; border: none; cursor: pointer; font-size: 1em; font-weight: 600; color: #888; border-radius: 10px 10px 0 0; white-space: nowrap; } |
| .tab.active { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; } |
| .tab:hover:not(.active) { background: rgba(255,255,255,0.1); color: #fff; } |
| .tab-content { display: none; animation: fadeIn 0.5s; } |
| .tab-content.active { display: block; } |
| @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } |
| |
| |
| .sentiment-section { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 25px; margin-bottom: 30px; border: 1px solid rgba(255,255,255,0.1); } |
| .sentiment-title { color: #00d9ff; font-size: 1.2em; font-weight: 700; margin-bottom: 20px; text-align: center; } |
| .gauge-container { display: flex; justify-content: center; align-items: center; flex-direction: column; } |
| .gauge-wrapper { position: relative; width: 300px; height: 180px; } |
| .gauge-labels { display: flex; justify-content: space-between; width: 100%; margin-top: 10px; font-size: 0.7em; color: #888; } |
| .gauge-value { text-align: center; margin-top: 15px; } |
| .gauge-value .number { font-size: 2em; font-weight: 700; } |
| .gauge-value .label { font-size: 1.1em; margin-top: 5px; } |
| |
| |
| .heatmap-section { margin-bottom: 30px; } |
| .heatmap-container { background: rgba(0,0,0,0.3); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); } |
| .heatmap-title { color: #00d9ff; font-size: 1.1em; font-weight: 700; margin-bottom: 15px; } |
| .stock-heatmap { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 4px; } |
| .heatmap-cell { padding: 12px 8px; border-radius: 4px; text-align: center; cursor: pointer; transition: all 0.3s; position: relative; min-height: 70px; display: flex; flex-direction: column; justify-content: center; } |
| .heatmap-cell:hover { transform: scale(1.05); z-index: 10; box-shadow: 0 5px 20px rgba(0,0,0,0.5); } |
| .heatmap-cell .ticker { font-weight: 700; font-size: 0.9em; color: #fff; } |
| .heatmap-cell .change { font-size: 0.8em; font-weight: 600; margin-top: 4px; } |
| .heatmap-cell .price { font-size: 0.7em; color: rgba(255,255,255,0.7); margin-top: 2px; } |
| .heatmap-cell.strong-up { background: linear-gradient(135deg, #00c853, #00e676); } |
| .heatmap-cell.up { background: linear-gradient(135deg, #4caf50, #66bb6a); } |
| .heatmap-cell.slight-up { background: linear-gradient(135deg, #8bc34a, #9ccc65); } |
| .heatmap-cell.neutral { background: linear-gradient(135deg, #607d8b, #78909c); } |
| .heatmap-cell.slight-down { background: linear-gradient(135deg, #ff9800, #ffa726); } |
| .heatmap-cell.down { background: linear-gradient(135deg, #f44336, #ef5350); } |
| .heatmap-cell.strong-down { background: linear-gradient(135deg, #c62828, #d32f2f); } |
| |
| |
| .keyword-cloud { display: flex; flex-wrap: wrap; gap: 10px; padding: 20px; background: rgba(255,255,255,0.03); border-radius: 15px; margin-bottom: 30px; } |
| .keyword-tag { padding: 8px 16px; border-radius: 20px; font-weight: 600; cursor: pointer; transition: all 0.3s; } |
| .keyword-tag:hover { transform: scale(1.1); } |
| .keyword-tag.size-xl { font-size: 1.3em; padding: 12px 24px; } |
| .keyword-tag.size-lg { font-size: 1.1em; padding: 10px 20px; } |
| .keyword-tag.size-md { font-size: 0.95em; } |
| .keyword-tag.size-sm { font-size: 0.85em; padding: 6px 12px; } |
| |
| |
| .ticker-tape-container { margin: -40px -40px 0 -40px; border-radius: 20px 20px 0 0; overflow: hidden; } |
| |
| |
| .market-overview-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 30px; } |
| @media (max-width: 1200px) { .market-overview-grid { grid-template-columns: repeat(2, 1fr); } } |
| @media (max-width: 768px) { .market-overview-grid { grid-template-columns: 1fr; } } |
| |
| .market-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); display: flex; flex-direction: column; align-items: center; } |
| .market-card-title { color: #00d9ff; font-size: 1em; font-weight: 700; margin-bottom: 15px; text-align: center; } |
| .chart-container { width: 100%; height: 180px; position: relative; } |
| .chart-legend { display: flex; justify-content: center; gap: 15px; margin-top: 10px; font-size: 0.85em; } |
| .legend-item { display: flex; align-items: center; gap: 5px; } |
| .legend-dot { width: 12px; height: 12px; border-radius: 50%; } |
| |
| |
| .widget-section { margin-bottom: 30px; } |
| .widget-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; } |
| @media (max-width: 992px) { .widget-grid { grid-template-columns: 1fr; } } |
| .widget-card { background: rgba(0,0,0,0.3); border-radius: 15px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); min-height: 400px; } |
| .widget-card-full { background: rgba(0,0,0,0.3); border-radius: 15px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); width: 100%; } |
| |
| |
| .heatmap-tabs { display: flex; gap: 8px; } |
| .heatmap-tab { padding: 8px 16px; background: rgba(255,255,255,0.1); border: none; border-radius: 20px; color: #888; cursor: pointer; font-size: 0.9em; font-weight: 600; transition: all 0.3s; } |
| .heatmap-tab:hover { background: rgba(255,255,255,0.2); color: #fff; } |
| .heatmap-tab.active { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; } |
| |
| |
| .pagination-tabs { display: flex; gap: 8px; } |
| .page-tab { padding: 8px 16px; background: rgba(255,255,255,0.1); border: none; border-radius: 20px; color: #888; cursor: pointer; font-size: 0.85em; font-weight: 600; transition: all 0.3s; } |
| .page-tab:hover { background: rgba(255,255,255,0.2); color: #fff; } |
| .page-tab.active { background: linear-gradient(135deg, #9b59b6, #8e44ad); color: #fff; } |
| |
| |
| .sub-tabs { display: flex; gap: 10px; margin-bottom: 10px; border-bottom: 2px solid rgba(255,255,255,0.1); padding-bottom: 15px; } |
| .sub-tab { padding: 12px 30px; background: rgba(255,255,255,0.05); border: 2px solid rgba(255,255,255,0.1); border-radius: 12px; color: #888; cursor: pointer; font-size: 1em; font-weight: 700; transition: all 0.3s; } |
| .sub-tab:hover { background: rgba(255,255,255,0.1); color: #fff; border-color: rgba(255,255,255,0.3); } |
| .sub-tab.active { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; border-color: transparent; } |
| |
| |
| .screening-sub-content { display: none; } |
| .screening-sub-content.active { display: block; } |
| |
| |
| .screening-section { background: rgba(255,255,255,0.02); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); } |
| |
| |
| .news-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 25px; margin-bottom: 20px; border-left: 4px solid #00d9ff; } |
| .news-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 15px; flex-wrap: wrap; gap: 10px; } |
| .news-title { font-size: 1.15em; font-weight: 700; color: #fff; flex: 1; line-height: 1.5; } |
| .news-meta { display: flex; gap: 10px; flex-wrap: wrap; } |
| .badge { padding: 5px 12px; border-radius: 20px; font-size: 0.8em; font-weight: 600; } |
| .ticker-badge { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; } |
| .market-badge { background: linear-gradient(135deg, #9b59b6, #8e44ad); color: #fff; } |
| .impact-high { background: #ff4757; color: white; } |
| .impact-medium { background: #ffa502; color: #1a1a2e; } |
| .impact-low { background: #2ed573; color: #1a1a2e; } |
| .sentiment-positive { background: rgba(0,255,136,0.2); color: #00ff88; border: 1px solid #00ff88; } |
| .sentiment-negative { background: rgba(255,71,87,0.2); color: #ff4757; border: 1px solid #ff4757; } |
| .sentiment-neutral { background: rgba(255,165,2,0.2); color: #ffa502; border: 1px solid #ffa502; } |
| |
| .news-summary { color: #bbb; line-height: 1.8; margin-bottom: 15px; font-size: 0.95em; } |
| .news-content { color: #999; line-height: 1.7; margin-bottom: 15px; font-size: 0.9em; padding: 15px; background: rgba(0,0,0,0.2); border-radius: 10px; } |
| .analysis-box { background: rgba(0,217,255,0.08); border-radius: 10px; padding: 15px; margin-top: 15px; } |
| .analysis-label { display: inline-block; background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; padding: 5px 12px; border-radius: 15px; font-size: 0.85em; font-weight: 600; margin-bottom: 10px; } |
| .news-link { display: inline-block; background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; padding: 10px 20px; border-radius: 8px; text-decoration: none; font-size: 0.9em; font-weight: 600; margin-top: 10px; transition: all 0.3s; border: none; cursor: pointer; } |
| .news-link:hover { transform: scale(1.05); box-shadow: 0 5px 15px rgba(0,217,255,0.3); } |
| |
| .research-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 25px; margin-bottom: 20px; border: 1px solid rgba(255,255,255,0.1); border-left: 4px solid #00ff88; } |
| .research-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 10px; } |
| .research-ticker { font-size: 1.8em; font-weight: 800; background: linear-gradient(135deg, #00d9ff, #00ff88); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } |
| .rating-badge { padding: 10px 20px; border-radius: 25px; font-size: 1em; font-weight: 700; } |
| .rating-strong-buy { background: linear-gradient(135deg, #00ff88, #00d9ff); color: #1a1a2e; } |
| .rating-buy { background: #2ed573; color: #1a1a2e; } |
| .rating-hold { background: #ffa502; color: #1a1a2e; } |
| .rating-sell { background: #ff4757; color: white; } |
| |
| .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; margin-bottom: 20px; } |
| .metric-card { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 10px; text-align: center; } |
| .metric-label { color: #888; font-size: 0.85em; margin-bottom: 5px; } |
| .metric-value { font-size: 1.2em; font-weight: 700; color: #fff; } |
| |
| |
| .scheduler-status { |
| display: flex; align-items: center; justify-content: center; gap: 10px; |
| background: rgba(155, 89, 182, 0.1); border: 1px solid rgba(155, 89, 182, 0.3); |
| border-radius: 25px; padding: 10px 20px; margin: 15px auto; max-width: 500px; |
| } |
| .scheduler-indicator { font-size: 1.2em; animation: pulse 2s infinite; } |
| .scheduler-indicator.active { color: #00ff88; } |
| .scheduler-indicator.inactive { color: #ff4757; } |
| @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } |
| #schedulerText { color: #bbb; font-size: 0.9em; } |
| |
| .soma-agent { background: rgba(255,255,255,0.02); border-radius: 12px; padding: 20px; margin-bottom: 15px; border-left: 3px solid; } |
| .soma-supervisor { border-color: #9b59b6; } |
| .soma-researcher { border-color: #3498db; } |
| .soma-critic { border-color: #e74c3c; } |
| .soma-analyst { border-color: #00ff88; } |
| .soma-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; } |
| .soma-icon { width: 35px; height: 35px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.1em; } |
| .soma-name { font-weight: 700; font-size: 0.95em; } |
| .soma-content { color: #bbb; line-height: 1.8; font-size: 0.95em; } |
| |
| .deep-analysis-btn { background: linear-gradient(135deg, #9b59b6, #8e44ad); color: white; border: none; padding: 12px 24px; border-radius: 25px; font-size: 0.9em; font-weight: 700; cursor: pointer; transition: all 0.3s; } |
| .deep-analysis-btn:hover { transform: scale(1.05); box-shadow: 0 10px 30px rgba(155, 89, 182, 0.4); } |
| |
| .download-btn { background: linear-gradient(135deg, #27ae60, #2ecc71); color: white; border: none; padding: 12px 24px; border-radius: 25px; font-size: 0.9em; font-weight: 700; cursor: pointer; transition: all 0.3s; text-decoration: none; margin: 5px; display: inline-flex; align-items: center; gap: 8px; } |
| .download-btn:hover { transform: scale(1.05); box-shadow: 0 10px 30px rgba(39, 174, 96, 0.4); } |
| .download-btn.pdf { background: linear-gradient(135deg, #e74c3c, #c0392b); } |
| |
| |
| .screening-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; } |
| .stock-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); transition: all 0.3s; } |
| .stock-card:hover { transform: translateY(-5px); box-shadow: 0 10px 30px rgba(0,217,255,0.2); border-color: #00d9ff; } |
| .stock-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } |
| .stock-card-ticker { font-size: 1.4em; font-weight: 800; color: #00d9ff; } |
| .stock-card-rank { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; padding: 5px 12px; border-radius: 15px; font-weight: 700; font-size: 0.85em; } |
| .stock-card-name { color: #888; font-size: 0.9em; margin-bottom: 15px; } |
| .stock-card-price { font-size: 1.5em; font-weight: 700; margin-bottom: 5px; } |
| .stock-card-change { font-size: 1.1em; font-weight: 600; margin-bottom: 15px; } |
| .mini-chart { width: 100%; height: 60px; background: rgba(0,0,0,0.2); border-radius: 8px; margin-bottom: 15px; overflow: hidden; } |
| .stock-card-metrics { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; font-size: 0.85em; } |
| .stock-card-metric { display: flex; justify-content: space-between; padding: 8px; background: rgba(255,255,255,0.03); border-radius: 5px; } |
| .stock-card-metric-label { color: #888; } |
| .stock-card-metric-value { font-weight: 600; } |
| .view-chart-btn { width: 100%; margin-top: 15px; padding: 10px; background: rgba(0,217,255,0.2); border: 1px solid #00d9ff; color: #00d9ff; border-radius: 8px; cursor: pointer; font-weight: 600; transition: all 0.3s; } |
| .view-chart-btn:hover { background: #00d9ff; color: #1a1a2e; } |
| |
| |
| .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); overflow: auto; } |
| .modal.active { display: flex; justify-content: center; align-items: flex-start; padding: 30px 20px; } |
| .modal-content { background: #1a1a2e; border-radius: 20px; padding: 30px; max-width: 1200px; width: 100%; border: 1px solid rgba(0,217,255,0.3); max-height: 95vh; overflow-y: auto; } |
| .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; position: sticky; top: -30px; background: #1a1a2e; padding: 10px 0; z-index: 10; } |
| .modal-close { background: none; border: none; color: #888; font-size: 2em; cursor: pointer; } |
| .modal-close:hover { color: #ff4757; } |
| |
| |
| .deep-section { margin-bottom: 20px; padding: 20px; background: rgba(255,255,255,0.03); border-radius: 12px; } |
| .deep-section h3 { color: #00d9ff; margin-bottom: 15px; font-size: 1.05em; } |
| .deep-section .content { color: #bbb; line-height: 1.8; font-size: 0.95em; } |
| .deep-section .content table { width: 100%; border-collapse: collapse; margin: 15px 0; } |
| .deep-section .content th { background: rgba(0,217,255,0.2); padding: 10px; text-align: left; color: #00d9ff; } |
| .deep-section .content td { padding: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); } |
| .deep-section .content ul, .deep-section .content ol { margin: 10px 0 10px 20px; } |
| .deep-section .content li { margin-bottom: 8px; } |
| .deep-section .content strong { color: #fff; } |
| .deep-section .content p { margin-bottom: 10px; } |
| |
| .chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin: 20px 0; } |
| .chart-box { background: rgba(255,255,255,0.03); border-radius: 12px; padding: 15px; } |
| .chart-box h4 { color: #00d9ff; margin-bottom: 10px; font-size: 0.9em; text-align: center; } |
| .chart-box canvas { max-height: 200px; } |
| |
| .loading { display: none; text-align: center; padding: 40px; } |
| .loading.active { display: block; } |
| .spinner { border: 4px solid rgba(255,255,255,0.1); border-top: 4px solid #00d9ff; border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; margin: 0 auto 20px; } |
| @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } |
| |
| |
| .chart-modal-content { max-width: 1000px; } |
| #tradingview-chart { width: 100%; height: 500px; border-radius: 10px; overflow: hidden; } |
| |
| .button-group { text-align: center; margin: 40px 0; display: flex; justify-content: center; gap: 15px; flex-wrap: wrap; } |
| .refresh-btn { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; border: none; padding: 15px 35px; font-size: 1em; font-weight: 700; border-radius: 50px; cursor: pointer; transition: all 0.3s; } |
| .refresh-btn:hover { transform: scale(1.05); box-shadow: 0 10px 30px rgba(0,217,255,0.4); } |
| .refresh-btn.secondary { background: transparent; border: 2px solid #00d9ff; color: #00d9ff; } |
| |
| .timestamp { text-align: center; color: #666; margin-top: 30px; font-size: 0.9em; padding: 15px; background: rgba(255,255,255,0.02); border-radius: 10px; } |
| .footer { text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.1); color: #666; font-size: 0.85em; } |
| .empty-state { text-align: center; padding: 60px; color: #888; } |
| .empty-state h3 { margin-bottom: 15px; color: #00d9ff; } |
| |
| @media (max-width: 768px) { |
| body { padding: 10px; } |
| .container { padding: 20px; } |
| h1 { font-size: 1.8em; } |
| .tab { padding: 12px 15px; font-size: 0.85em; } |
| .screening-grid { grid-template-columns: 1fr; } |
| .stock-heatmap { grid-template-columns: repeat(3, 1fr); } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>๐ AI ์ฃผ์ ๋ฆฌ์์น ์ผํฐ v4.2</h1> |
| <p class="subtitle"> |
| TOP 30 ์ข
๋ชฉ ์ค์๊ฐ ๋ถ์ | ์คํ ํํธ๋งต | ์ฐจํธ ๋ถ์ | ์ฌ์ธต ๋ถ์ + PDF/DOCX<br> |
| <span style="color: #00d9ff;">AGI SOMA ๋ฉํฐ์์ด์ ํธ | ์กฐ์ฌ์โ๋นํ๊ฐโ๊ฐ๋
์ ํ์
์์คํ
</span> |
| </p> |
| |
| |
| <div class="indices-bar"> |
| {% for idx in indices %} |
| <div class="index-card"> |
| <div class="index-name">{{ idx.name }}</div> |
| <div class="index-value">{{ "{:,.2f}".format(idx.price) }}</div> |
| <div class="index-change {{ 'positive' if idx.change >= 0 else 'negative' }}"> |
| {{ "+" if idx.change >= 0 else "" }}{{ "{:.2f}".format(idx.change) }} ({{ "{:.2f}".format(idx.change_percent) }}%) |
| </div> |
| </div> |
| {% endfor %} |
| </div> |
| |
| |
| <div class="tabs"> |
| <button class="tab active" onclick="switchTab(event, 'market')">๐ ์์ฅ ๋ถ์</button> |
| <button class="tab" onclick="switchTab(event, 'breaking')">๐ฅ ์๋ณด ({{ breaking_news|length }})</button> |
| <button class="tab" onclick="switchTab(event, 'research')">๐ ๋ฆฌ์์น ({{ research_reports|length }})</button> |
| <button class="tab" onclick="switchTab(event, 'screening')">๐ ์คํฌ๋ฆฌ๋ ({{ screening_data|length }})</button> |
| </div> |
| |
| |
| <div id="market-content" class="tab-content active"> |
| |
| |
| <div class="ticker-tape-container"> |
| <div id="ticker-tape-widget"></div> |
| </div> |
| |
| <h2 style="color: #00d9ff; margin-bottom: 20px; margin-top: 20px;">๐ ์์ฅ ์ข
ํฉ ๋ถ์</h2> |
| |
| |
| <div class="market-overview-grid"> |
| |
| <div class="market-card"> |
| <div class="market-card-title">๐ ์์ฅ ์ผํฐ๋จผํธ</div> |
| <div class="gauge-container"> |
| <div class="gauge-wrapper" style="width: 200px; height: 120px;"> |
| <canvas id="sentimentGauge"></canvas> |
| </div> |
| <div class="gauge-labels" style="font-size: 0.6em;"> |
| <span>๋งค๋</span> |
| <span>์ค๋ฆฝ</span> |
| <span>๋งค์</span> |
| </div> |
| <div class="gauge-value" style="margin-top: 10px;"> |
| <div class="number" id="sentimentValue" style="font-size: 1.8em;">-</div> |
| <div class="label" id="sentimentLabel" style="color: #00d9ff; font-size: 0.95em;">๋ถ์ ์ค...</div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="market-card"> |
| <div class="market-card-title">๐ ์นํฐ๋ณ ํผํฌ๋จผ์ค</div> |
| <div class="chart-container"> |
| <canvas id="sectorChart"></canvas> |
| </div> |
| </div> |
| |
| |
| <div class="market-card"> |
| <div class="market-card-title">๐ฉ ์์น/ํ๋ฝ ๋น์จ</div> |
| <div class="chart-container"> |
| <canvas id="upDownChart"></canvas> |
| </div> |
| <div id="upDownLegend" class="chart-legend"></div> |
| </div> |
| </div> |
| |
| |
| <div class="widget-section"> |
| <h3 style="color: #00d9ff; margin-bottom: 15px;">๐ ๊ธ๋ก๋ฒ ์์ฅ ํํฉ</h3> |
| <div class="widget-card-full" id="market-overview-widget" style="height: 450px;"></div> |
| </div> |
| |
| |
| <h3 style="color: #00d9ff; margin-bottom: 15px; margin-top: 30px;">๐ฅ ์์ฅ ์ด์ ํค์๋ (24h)</h3> |
| <div class="keyword-cloud" id="keywordCloud"></div> |
| |
| |
| <div class="heatmap-section"> |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> |
| <h3 style="color: #00d9ff; margin: 0;">๐ ์์ฅ ํํธ๋งต</h3> |
| <div class="heatmap-tabs"> |
| <button class="heatmap-tab active" onclick="switchHeatmap('SPX500', this)">S&P 500</button> |
| <button class="heatmap-tab" onclick="switchHeatmap('NASDAQ100', this)">NASDAQ 100</button> |
| <button class="heatmap-tab" onclick="switchHeatmap('DJI', this)">DOW 30</button> |
| </div> |
| </div> |
| <div id="stock-heatmap-widget" style="height: 500px; border-radius: 15px; overflow: hidden;"></div> |
| </div> |
| </div> |
| |
| |
| <div id="breaking-content" class="tab-content"> |
| <h2 style="color: #00d9ff; margin-bottom: 20px;">๐ฅ 24์๊ฐ ์ด๋ด ์ฃผ์ ๋ด์ค</h2> |
| {% if breaking_news %} |
| {% for news in breaking_news %} |
| <div class="news-card"> |
| <div class="news-header"> |
| <div class="news-title">{{ news.title }}</div> |
| <div class="news-meta"> |
| {% if news.news_type == 'market' %} |
| <span class="badge market-badge">{{ news.ticker }}</span> |
| {% else %} |
| <span class="badge ticker-badge">{{ news.ticker }}</span> |
| {% endif %} |
| <span class="badge impact-{{ news.impact_level }}">{{ news.impact_text }}</span> |
| <span class="badge sentiment-{{ news.sentiment }}">{{ news.sentiment_text }}</span> |
| </div> |
| </div> |
| {% if news.title_original and news.title_original != news.title %} |
| <div style="color: #666; font-size: 0.85em; margin-bottom: 10px; font-style: italic;">๐ {{ news.title_original }}</div> |
| {% endif %} |
| <div class="news-summary">{{ news.summary }}</div> |
| {% if news.content %} |
| <div class="news-content">{{ news.content }}</div> |
| {% endif %} |
| <div class="analysis-box"> |
| <span class="analysis-label">๐ก ํฌ์ ํฌ์ธํธ</span> |
| <div style="color: #bbb; line-height: 1.7;">{{ news.investment_point }}</div> |
| </div> |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 15px; flex-wrap: wrap; gap: 10px;"> |
| <div> |
| <span style="color: #666; font-size: 0.85em;">๐
{{ news.published_at }}</span> |
| {% if news.source %} |
| <span style="color: #00d9ff; font-size: 0.85em; margin-left: 10px;">๐ฐ {{ news.source }}</span> |
| {% endif %} |
| </div> |
| <div> |
| {% if news.url %} |
| <a href="{{ news.url }}" target="_blank" class="news-link" style="margin-right: 5px;">๐ ์๋ฌธ ๋ณด๊ธฐ</a> |
| {% endif %} |
| <button class="news-link" onclick="searchNews('{{ news.title|e }}')">๐ ๊ด๋ จ ๊ฒ์</button> |
| </div> |
| </div> |
| </div> |
| {% endfor %} |
| {% else %} |
| <div class="empty-state"> |
| <h3>๐ญ 24์๊ฐ ์ด๋ด ๋ด์ค๊ฐ ์์ต๋๋ค</h3> |
| </div> |
| {% endif %} |
| </div> |
| |
| |
| <div id="research-content" class="tab-content"> |
| <h2 style="color: #00d9ff; margin-bottom: 20px;">๐ AGI SOMA ๋ฆฌ์์น + ์ฌ์ธต ๋ถ์</h2> |
| {% if research_reports %} |
| {% for report in research_reports %} |
| <div class="research-card"> |
| <div class="research-header"> |
| <div> |
| <span class="research-ticker">{{ report.ticker }}</span> |
| <span style="color: #888; margin-left: 10px;">{{ report.company_name }}</span> |
| </div> |
| <span class="rating-badge rating-{{ report.rating_class }}">{{ report.rating }}</span> |
| </div> |
| <div class="metrics-grid"> |
| <div class="metric-card"> |
| <div class="metric-label">ํ์ฌ๊ฐ</div> |
| <div class="metric-value">${{ "{:,.2f}".format(report.current_price) }}</div> |
| </div> |
| <div class="metric-card"> |
| <div class="metric-label">๋ชฉํ๊ฐ</div> |
| <div class="metric-value" style="color: #00ff88;">${{ "{:,.2f}".format(report.target_price) }}</div> |
| </div> |
| <div class="metric-card"> |
| <div class="metric-label">๐ 52์ฃผ ๊ณ ์ ๋๋น</div> |
| {% set stock = screening_data|selectattr('ticker', 'equalto', report.ticker)|first %} |
| {% set from_high = stock.from_high if stock else -10 %} |
| <div class="metric-value" style="color: {% if from_high > -5 %}#00ff88{% elif from_high > -15 %}#ffa502{% else %}#ff4757{% endif %};">{{ from_high }}%</div> |
| <div style="font-size: 0.7em; color: #888;">{% if from_high > -5 %}๊ณ ์ ๊ทผ์ฒ{% elif from_high > -15 %}์กฐ์ ๊ตฌ๊ฐ{% else %}๋ฐ๋ฑ์ฌ๋ ฅโ{% endif %}</div> |
| </div> |
| <div class="metric-card"> |
| <div class="metric-label">๐ฐ ์๊ฐ์ด์ก</div> |
| {% set mcap = stock.market_cap if stock else 0 %} |
| <div class="metric-value" style="color: #00d9ff;">${{ "{:,.0f}".format(mcap / 1e9) }}B</div> |
| <div style="font-size: 0.7em; color: #888;">{% if mcap > 500e9 %}๋ํ์ฃผ{% elif mcap > 100e9 %}์ค๋ํ{% else %}์คํ์ฃผ{% endif %}</div> |
| </div> |
| <div class="metric-card"> |
| <div class="metric-label">๐ ๊ฑฐ๋๋</div> |
| {% set vol = stock.volume if stock else 0 %} |
| <div class="metric-value" style="color: #a29bfe;">{{ "{:,.1f}".format(vol / 1e6) }}M</div> |
| <div style="font-size: 0.7em; color: #888;">์ผ ๊ฑฐ๋๋</div> |
| </div> |
| </div> |
| <div class="soma-agent soma-supervisor"> |
| <div class="soma-header"> |
| <div class="soma-icon" style="background: #9b59b6;">๐</div> |
| <span class="soma-name" style="color: #9b59b6;">๊ฐ๋
์ (Supervisor)</span> |
| </div> |
| <div class="soma-content">{{ report.supervisor_analysis }}</div> |
| </div> |
| <div class="soma-agent soma-researcher"> |
| <div class="soma-header"> |
| <div class="soma-icon" style="background: #3498db;">๐ฌ</div> |
| <span class="soma-name" style="color: #3498db;">์กฐ์ฌ์ (Researcher)</span> |
| </div> |
| <div class="soma-content">{{ report.researcher_analysis }}</div> |
| </div> |
| <div style="margin-top: 20px;"> |
| <button class="deep-analysis-btn" onclick="runDeepAnalysis('{{ report.ticker }}', '{{ report.company_name }}')">๐ฌ ์ฌ์ธต ๋ถ์</button> |
| <span id="deep-status-{{ report.ticker }}" style="margin-left: 15px; color: #888;"></span> |
| </div> |
| </div> |
| {% endfor %} |
| {% else %} |
| <div class="empty-state"><h3>๐ญ ๋ฆฌ์์น ๋ณด๊ณ ์๊ฐ ์์ต๋๋ค</h3></div> |
| {% endif %} |
| </div> |
| |
| |
| <div id="screening-content" class="tab-content"> |
| |
| |
| <div class="sub-tabs"> |
| <button class="sub-tab active" onclick="switchScreeningTab('dow30', this)">๐๏ธ DOW 30</button> |
| <button class="sub-tab" onclick="switchScreeningTab('nasdaq100', this)">๐ป NASDAQ 100</button> |
| </div> |
| |
| |
| <div id="dow30-content" class="screening-sub-content active"> |
| <h3 style="color: #00d9ff; margin: 20px 0;">๐๏ธ DOW 30 ์ข
๋ชฉ ์คํฌ๋ฆฌ๋</h3> |
| <div class="screening-grid"> |
| {% set dow30_tickers = ['UNH', 'GS', 'HD', 'CAT', 'CRM', 'MCD', 'V', 'TRV', 'AXP', 'JPM', 'IBM', 'BA', 'MMM', 'JNJ', 'PG', 'MRK', 'NKE', 'DIS', 'KO', 'WMT', 'VZ', 'DOW', 'SHW', 'AAPL', 'MSFT', 'AMZN', 'NVDA', 'HON', 'AMGN', 'CSCO'] %} |
| {% for stock in screening_data %} |
| {% if stock.ticker in dow30_tickers %} |
| <div class="stock-card"> |
| <div class="stock-card-header"> |
| <span class="stock-card-ticker">{{ stock.ticker }}</span> |
| <span class="stock-card-rank">#{{ stock.rank }}</span> |
| </div> |
| <div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div> |
| <div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div> |
| <div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}"> |
| {{ "{:+.2f}".format(stock.change_percent) }}% |
| </div> |
| |
| <div class="mini-chart" id="mini-chart-dow-{{ stock.ticker }}"></div> |
| <div class="stock-card-metrics"> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">RSI</span> |
| <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span> |
| </div> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">PER</span> |
| <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span> |
| </div> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">์์ด</span> |
| <span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span> |
| </div> |
| </div> |
| <button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NYSE') }}')">๐ ์ฐจํธ</button> |
| </div> |
| {% endif %} |
| {% endfor %} |
| </div> |
| </div> |
| |
| |
| <div id="nasdaq100-content" class="screening-sub-content"> |
| <div style="display: flex; justify-content: space-between; align-items: center; margin: 20px 0;"> |
| <h3 style="color: #00d9ff; margin: 0;">๐ป NASDAQ 100 ์ข
๋ชฉ ์คํฌ๋ฆฌ๋</h3> |
| <div class="pagination-tabs"> |
| <button class="page-tab active" onclick="showNasdaqPage(1, this)">1-35</button> |
| <button class="page-tab" onclick="showNasdaqPage(2, this)">36-70</button> |
| <button class="page-tab" onclick="showNasdaqPage(3, this)">71-101</button> |
| </div> |
| </div> |
| |
| |
| <div class="screening-grid nasdaq-page" id="nasdaq-page-1"> |
| {% for stock in screening_data[:35] %} |
| <div class="stock-card"> |
| <div class="stock-card-header"> |
| <span class="stock-card-ticker">{{ stock.ticker }}</span> |
| <span class="stock-card-rank">#{{ loop.index }}</span> |
| </div> |
| <div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div> |
| <div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div> |
| <div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}"> |
| {{ "{:+.2f}".format(stock.change_percent) }}% |
| </div> |
| |
| <div class="mini-chart" id="mini-chart-nq1-{{ stock.ticker }}"></div> |
| <div class="stock-card-metrics"> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">RSI</span> |
| <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span> |
| </div> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">PER</span> |
| <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span> |
| </div> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">์์ด</span> |
| <span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span> |
| </div> |
| </div> |
| <button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NASDAQ') }}')">๐ ์ฐจํธ</button> |
| </div> |
| {% endfor %} |
| </div> |
| |
| |
| <div class="screening-grid nasdaq-page" id="nasdaq-page-2" style="display: none;"> |
| {% for stock in screening_data[35:70] %} |
| <div class="stock-card"> |
| <div class="stock-card-header"> |
| <span class="stock-card-ticker">{{ stock.ticker }}</span> |
| <span class="stock-card-rank">#{{ loop.index + 35 }}</span> |
| </div> |
| <div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div> |
| <div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div> |
| <div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}"> |
| {{ "{:+.2f}".format(stock.change_percent) }}% |
| </div> |
| |
| <div class="mini-chart" id="mini-chart-nq2-{{ stock.ticker }}"></div> |
| <div class="stock-card-metrics"> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">RSI</span> |
| <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span> |
| </div> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">PER</span> |
| <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span> |
| </div> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">์์ด</span> |
| <span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span> |
| </div> |
| </div> |
| <button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NASDAQ') }}')">๐ ์ฐจํธ</button> |
| </div> |
| {% endfor %} |
| </div> |
| |
| |
| <div class="screening-grid nasdaq-page" id="nasdaq-page-3" style="display: none;"> |
| {% for stock in screening_data[70:101] %} |
| <div class="stock-card"> |
| <div class="stock-card-header"> |
| <span class="stock-card-ticker">{{ stock.ticker }}</span> |
| <span class="stock-card-rank">#{{ loop.index + 70 }}</span> |
| </div> |
| <div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div> |
| <div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div> |
| <div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}"> |
| {{ "{:+.2f}".format(stock.change_percent) }}% |
| </div> |
| |
| <div class="mini-chart" id="mini-chart-nq3-{{ stock.ticker }}"></div> |
| <div class="stock-card-metrics"> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">RSI</span> |
| <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span> |
| </div> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">PER</span> |
| <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span> |
| </div> |
| <div class="stock-card-metric"> |
| <span class="stock-card-metric-label">์์ด</span> |
| <span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span> |
| </div> |
| </div> |
| <button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NASDAQ') }}')">๐ ์ฐจํธ</button> |
| </div> |
| {% endfor %} |
| </div> |
| </div> |
| </div> |
| |
| <div class="button-group"> |
| <button class="refresh-btn" onclick="location.reload()">๐ ์๋ก๊ณ ์นจ</button> |
| <button class="refresh-btn secondary" onclick="location.href='/?refresh=true'">๐ฅ ๋ฐ์ดํฐ ๊ฐฑ์ </button> |
| <button class="refresh-btn" style="background: linear-gradient(135deg, #9b59b6, #8e44ad);" onclick="triggerAutoRefresh()">โฐ ์ฆ์ ์๋๊ฐฑ์ </button> |
| </div> |
| |
| |
| <div class="scheduler-status" id="schedulerStatus"> |
| <span class="scheduler-indicator">โฐ</span> |
| <span id="schedulerText">์๋ ๊ฐฑ์ ์ํ ํ์ธ ์ค...</span> |
| </div> |
| |
| <div class="timestamp">โฐ ๋ง์ง๋ง ์
๋ฐ์ดํธ: {{ timestamp }}</div> |
| <div class="footer"> |
| <p>๐ AI ์ฃผ์ ๋ฆฌ์์น ์ผํฐ v5.8 | NASDAQ 100 + DOW 30 | ์๋ ๊ฐฑ์ ์์คํ
| Ginigen AI</p> |
| <p style="margin-top: 10px;">โ ๏ธ ๋ณธ ์๋น์ค๋ ํฌ์ ์ฐธ๊ณ ์ฉ์ด๋ฉฐ, ํฌ์ ๊ฒฐ์ ์ ๋ณธ์ธ์ ํ๋จ๊ณผ ์ฑ
์ ํ์ ์ด๋ฃจ์ด์ ธ์ผ ํฉ๋๋ค.</p> |
| </div> |
| </div> |
| |
| |
| <div id="deepAnalysisModal" class="modal"> |
| <div class="modal-content" id="deepAnalysisContent"> |
| <div class="modal-header"> |
| <h2 style="color: #00d9ff;">๐ฌ <span id="modal-ticker"></span> ์ฌ์ธต ๋ถ์</h2> |
| <button class="modal-close" onclick="closeModal('deepAnalysisModal')">×</button> |
| </div> |
| <div id="loading-indicator" class="loading"> |
| <div class="spinner"></div> |
| <p>๐ค SOMA ์์ด์ ํธ๊ฐ ์ฌ์ธต ๋ถ์ ์ค์
๋๋ค...</p> |
| </div> |
| <div id="deep-analysis-result" style="display: none;"> |
| <div class="chart-grid"> |
| <div class="chart-box"><h4>๐ ํฌ์ ์งํ</h4><canvas id="metricsChart"></canvas></div> |
| <div class="chart-box"><h4>๐ฏ ๋ชฉํ๊ฐ ๋ถ์</h4><canvas id="priceChart"></canvas></div> |
| <div class="chart-box"><h4>โ๏ธ ๋ฆฌ์คํฌ/๊ธฐํ</h4><canvas id="riskChart"></canvas></div> |
| <div class="chart-box"><h4>๐ ์ฑ์ฅ ์ ๋ง</h4><canvas id="growthChart"></canvas></div> |
| </div> |
| <div class="metrics-grid" style="margin-bottom: 25px;"> |
| <div class="metric-card"><div class="metric-label">ํ์ฌ๊ฐ</div><div class="metric-value" id="result-current-price">-</div></div> |
| <div class="metric-card"><div class="metric-label">๋ชฉํ๊ฐ</div><div class="metric-value" style="color: #00ff88;" id="result-target-price">-</div></div> |
| <div class="metric-card"><div class="metric-label">ํฌ์์๊ฒฌ</div><div class="metric-value" style="color: #00d9ff;" id="result-rating">-</div></div> |
| </div> |
| |
| |
| <div class="elasticity-container" style="margin-bottom: 25px; padding: 20px; background: rgba(0,0,0,0.3); border-radius: 15px; border: 1px solid rgba(255,255,255,0.1);"> |
| <h4 style="color: #00d9ff; margin-bottom: 15px; text-align: center;">๐ ํ๋ ฅ์ฑ ์์ธก</h4> |
| |
| |
| <div class="elasticity-bar-container" style="position: relative; height: 50px; margin: 20px 0;"> |
| <div class="elasticity-scale" style="display: flex; justify-content: space-between; font-size: 11px; color: #888; margin-bottom: 5px;"> |
| <span id="elasticity-min">-30%</span> |
| <span>0%</span> |
| <span id="elasticity-max">+40%</span> |
| </div> |
| <div class="elasticity-track" style="position: relative; height: 20px; background: linear-gradient(to right, #ff4757 0%, #ff4757 40%, #888 40%, #888 45%, #00ff88 45%, #00ff88 100%); border-radius: 10px; overflow: hidden;"> |
| |
| <div id="elasticity-range" style="position: absolute; height: 100%; background: rgba(0,217,255,0.4); border: 2px solid #00d9ff; border-radius: 8px;"></div> |
| |
| <div id="elasticity-marker" style="position: absolute; top: -8px; width: 4px; height: 36px; background: #fff; border-radius: 2px; box-shadow: 0 0 10px rgba(255,255,255,0.8);"></div> |
| </div> |
| </div> |
| |
| |
| <div style="display: flex; justify-content: space-between; gap: 15px; margin-top: 20px;"> |
| <div style="flex: 1; text-align: center; padding: 15px; background: rgba(255,71,87,0.15); border-radius: 10px; border: 1px solid rgba(255,71,87,0.3);"> |
| <div style="font-size: 12px; color: #ff6b7a; margin-bottom: 5px;">๐ ํ๋ฝ ๋ฆฌ์คํฌ</div> |
| <div id="result-downside" style="font-size: 22px; font-weight: bold; color: #ff4757;">-</div> |
| <div style="font-size: 11px; color: #888; margin-top: 3px;"><span id="result-down-prob">-</span> ํ๋ฅ </div> |
| </div> |
| <div style="flex: 1; text-align: center; padding: 15px; background: rgba(0,217,255,0.15); border-radius: 10px; border: 1px solid rgba(0,217,255,0.3);"> |
| <div style="font-size: 12px; color: #00d9ff; margin-bottom: 5px;">๐ฏ ๊ธฐ๋ ์์ต</div> |
| <div id="result-prediction" style="font-size: 24px; font-weight: bold; color: #00d9ff;">-</div> |
| <div style="font-size: 11px; color: #888; margin-top: 3px;">R/R <span id="result-rr">-</span></div> |
| </div> |
| <div style="flex: 1; text-align: center; padding: 15px; background: rgba(0,255,136,0.15); border-radius: 10px; border: 1px solid rgba(0,255,136,0.3);"> |
| <div style="font-size: 12px; color: #00ff88; margin-bottom: 5px;">๐ ์์น ์ ์ฌ๋ ฅ</div> |
| <div id="result-upside" style="font-size: 22px; font-weight: bold; color: #00ff88;">-</div> |
| <div style="font-size: 11px; color: #888; margin-top: 3px;"><span id="result-up-prob">-</span> ํ๋ฅ </div> |
| </div> |
| </div> |
| |
| <div style="text-align: center; margin-top: 12px; font-size: 11px; color: #666;"> |
| โป AI ๊ธฐ๋ฐ ๋ถ์์ด๋ฉฐ ์ค์ ํฌ์ ์์ต์ ๋ณด์ฅํ์ง ์์ต๋๋ค |
| </div> |
| </div> |
| |
| <div class="deep-section" style="background: rgba(0,217,255,0.1); border: 1px solid rgba(0,217,255,0.3);"><h3>๐ ํต์ฌ ์์ฝ</h3><div class="content" id="result-executive-summary"></div></div> |
| <div class="deep-section"><h3>๐ข ํ์ฌ ๊ฐ์</h3><div class="content" id="result-company-overview"></div></div> |
| <div class="deep-section"><h3>๐ฐ ์ฌ๋ฌด ๋ถ์</h3><div class="content" id="result-financial-analysis"></div></div> |
| <div class="deep-section"><h3>๐ ๊ธฐ์ ์ ๋ถ์</h3><div class="content" id="result-technical-analysis"></div></div> |
| <div class="deep-section"><h3>๐ญ ์ฐ์
๋ถ์</h3><div class="content" id="result-industry-analysis"></div></div> |
| <div class="deep-section"><h3>โ ๏ธ ๋ฆฌ์คํฌ ํ๊ฐ</h3><div class="content" id="result-risk-assessment"></div></div> |
| <div class="deep-section"><h3>๐ก ํฌ์ ๋
ผ๋ฆฌ</h3><div class="content" id="result-investment-thesis"></div></div> |
| <div class="deep-section"><h3>๐ฏ ๋ชฉํ ์ฃผ๊ฐ</h3><div class="content" id="result-price-targets"></div></div> |
| <div class="deep-section"><h3>๐ ์นดํ๋ฆฌ์คํธ</h3><div class="content" id="result-catalysts"></div></div> |
| <div class="deep-section" style="background: rgba(0,255,136,0.1); border: 1px solid rgba(0,255,136,0.3);"><h3>โ
์ต์ข
๊ถ๊ณ </h3><div class="content" id="result-final-recommendation" style="font-weight: 600;"></div></div> |
| <div style="text-align: center; margin-top: 30px;"> |
| <a id="download-docx-btn" class="download-btn" style="display: none;">๐ DOCX</a> |
| <button id="download-pdf-btn" class="download-btn pdf" style="display: none;" onclick="downloadAsPDF()">๐ PDF</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="chartModal" class="modal"> |
| <div class="modal-content chart-modal-content"> |
| <div class="modal-header"> |
| <h2 style="color: #00d9ff;">๐ <span id="chart-modal-title"></span></h2> |
| <button class="modal-close" onclick="closeModal('chartModal')">×</button> |
| </div> |
| <div id="tradingview-chart"></div> |
| </div> |
| </div> |
| |
| <script> |
| |
| let currentTicker = ''; |
| let sentimentGaugeInstance = null; |
| let metricsChartInstance = null, priceChartInstance = null, riskChartInstance = null, growthChartInstance = null; |
| let tvWidget = null; |
| |
| const newsData = {{ breaking_news | tojson | safe }}; |
| const screeningData = {{ screening_data | tojson | safe }}; |
| |
| if (typeof marked !== 'undefined') { |
| marked.setOptions({ breaks: true, gfm: true, tables: true }); |
| } |
| |
| |
| async function updateSchedulerStatus() { |
| try { |
| const response = await fetch('/api/scheduler/status'); |
| const data = await response.json(); |
| |
| if (data.success) { |
| const scheduler = data.scheduler; |
| const indicator = document.querySelector('.scheduler-indicator'); |
| const text = document.getElementById('schedulerText'); |
| |
| if (scheduler.is_running) { |
| indicator.classList.add('active'); |
| indicator.classList.remove('inactive'); |
| indicator.textContent = '๐ข'; |
| |
| let statusText = `์๋ ๊ฐฑ์ ํ์ฑ (${scheduler.interval_hours}์๊ฐ ์ฃผ๊ธฐ)`; |
| if (scheduler.next_run) { |
| const nextRun = new Date(scheduler.next_run); |
| statusText += ` | ๋ค์: ${nextRun.toLocaleTimeString('ko-KR')}`; |
| } |
| if (scheduler.refresh_count > 0) { |
| statusText += ` | ๊ฐฑ์ ${scheduler.refresh_count}ํ`; |
| } |
| text.textContent = statusText; |
| } else { |
| indicator.classList.remove('active'); |
| indicator.classList.add('inactive'); |
| indicator.textContent = '๐ด'; |
| text.textContent = '์๋ ๊ฐฑ์ ๋นํ์ฑ'; |
| } |
| } |
| } catch (e) { |
| console.log('์ค์ผ์ค๋ฌ ์ํ ์กฐํ ์คํจ:', e); |
| } |
| } |
| |
| |
| async function triggerAutoRefresh() { |
| if (!confirm('์ง๊ธ ์ฆ์ ์ ์ฒด ๋ฐ์ดํฐ๋ฅผ ๊ฐฑ์ ํ์๊ฒ ์ต๋๊น?\n(๋ฐฑ๊ทธ๋ผ์ด๋์์ ์คํ๋๋ฉฐ ๋ช ๋ถ์ด ์์๋ฉ๋๋ค)')) { |
| return; |
| } |
| |
| try { |
| const response = await fetch('/api/scheduler/trigger'); |
| const data = await response.json(); |
| |
| if (data.success) { |
| alert('โ
์๋ ๊ฐฑ์ ์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์์๋์์ต๋๋ค.\n\n์๋ฃ๋๋ฉด ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ์ธ์.'); |
| } else { |
| alert('โ ๊ฐฑ์ ํธ๋ฆฌ๊ฑฐ ์คํจ: ' + (data.error || '์ ์ ์๋ ์ค๋ฅ')); |
| } |
| } catch (e) { |
| alert('โ ์๋ฒ ์ฐ๊ฒฐ ์ค๋ฅ: ' + e.message); |
| } |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| updateSchedulerStatus(); |
| |
| setInterval(updateSchedulerStatus, 60000); |
| }); |
| |
| function switchTab(event, tabName) { |
| document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); |
| document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); |
| event.currentTarget.classList.add('active'); |
| document.getElementById(tabName + '-content').classList.add('active'); |
| if (tabName === 'market') setTimeout(initMarketTab, 100); |
| if (tabName === 'screening') setTimeout(() => loadMiniChartsForPage('dow'), 500); |
| } |
| |
| function searchNews(title) { |
| window.open(`https://www.google.com/search?q=${encodeURIComponent(title)}&tbm=nws`, '_blank'); |
| } |
| |
| |
| function showNasdaqPage(pageNum, btn) { |
| |
| document.querySelectorAll('.nasdaq-page').forEach(p => p.style.display = 'none'); |
| |
| document.getElementById('nasdaq-page-' + pageNum).style.display = 'grid'; |
| |
| document.querySelectorAll('.page-tab').forEach(t => t.classList.remove('active')); |
| btn.classList.add('active'); |
| |
| setTimeout(() => loadMiniChartsForPage('nq' + pageNum), 300); |
| } |
| |
| |
| function switchScreeningTab(tabName, btn) { |
| |
| document.querySelectorAll('.screening-sub-content').forEach(c => c.classList.remove('active')); |
| |
| document.getElementById(tabName + '-content').classList.add('active'); |
| |
| document.querySelectorAll('.sub-tab').forEach(t => t.classList.remove('active')); |
| btn.classList.add('active'); |
| |
| if (tabName === 'dow30') { |
| setTimeout(() => loadMiniChartsForPage('dow'), 300); |
| } else { |
| setTimeout(() => loadMiniChartsForPage('nq1'), 300); |
| } |
| } |
| |
| |
| |
| |
| const NYSE_TICKERS = ['UNH', 'GS', 'HD', 'CAT', 'CRM', 'MCD', 'V', 'TRV', 'AXP', 'JPM', 'IBM', 'BA', 'MMM', 'JNJ', 'PG', 'MRK', 'NKE', 'DIS', 'KO', 'WMT', 'VZ', 'DOW', 'SHW', 'TRI', 'LIN', 'HON', 'AEP', 'CEG']; |
| |
| |
| const TRADINGVIEW_SYMBOLS = { |
| 'WMT': 'NASDAQ:WMT', |
| 'TRI': 'NYSE:TRI', |
| 'LIN': 'NYSE:LIN', |
| 'HON': 'NASDAQ:HON', |
| 'AEP': 'NASDAQ:AEP', |
| 'CEG': 'NASDAQ:CEG', |
| 'ASML': 'NASDAQ:ASML', |
| 'AZN': 'NASDAQ:AZN', |
| 'PDD': 'NASDAQ:PDD' |
| }; |
| |
| |
| function getTradingViewSymbol(ticker, defaultExchange) { |
| |
| if (TRADINGVIEW_SYMBOLS[ticker]) { |
| return TRADINGVIEW_SYMBOLS[ticker]; |
| } |
| |
| if (NYSE_TICKERS.includes(ticker)) { |
| return `NYSE:${ticker}`; |
| } |
| |
| return `${defaultExchange || 'NASDAQ'}:${ticker}`; |
| } |
| |
| function loadMiniChartsForPage(prefix) { |
| if (!screeningData) return; |
| |
| const containers = document.querySelectorAll(`[id^="mini-chart-${prefix}-"]`); |
| containers.forEach(container => { |
| if (container.dataset.loaded) return; |
| container.dataset.loaded = 'true'; |
| |
| const ticker = container.id.replace(`mini-chart-${prefix}-`, ''); |
| const stock = screeningData.find(s => s.ticker === ticker); |
| if (!stock) return; |
| |
| |
| const symbol = getTradingViewSymbol(ticker, stock.exchange); |
| |
| try { |
| new TradingView.MediumWidget({ |
| symbols: [[ticker, `${symbol}|3D`]], |
| chartOnly: true, |
| width: '100%', |
| height: 60, |
| locale: 'kr', |
| colorTheme: 'dark', |
| autosize: false, |
| showVolume: false, |
| hideDateRanges: true, |
| scalePosition: 'no', |
| scaleMode: 'Normal', |
| fontFamily: 'sans-serif', |
| noTimeScale: true, |
| chartType: 'area', |
| lineColor: stock.change_percent >= 0 ? '#00ff88' : '#ff4757', |
| bottomColor: stock.change_percent >= 0 ? 'rgba(0,255,136,0.1)' : 'rgba(255,71,87,0.1)', |
| lineWidth: 2, |
| container_id: container.id |
| }); |
| } catch (e) { |
| console.log('๋ฏธ๋์ฐจํธ ๋ก๋ ์คํจ:', ticker, e); |
| } |
| }); |
| } |
| |
| function initMarketTab() { |
| try { |
| initSentimentGauge(); |
| initSectorChart(); |
| initUpDownChart(); |
| initKeywordCloud(); |
| initTradingViewWidgets(); |
| } catch (e) { |
| console.error('initMarketTab ์ค๋ฅ:', e); |
| } |
| } |
| |
| |
| let sectorChartInstance = null; |
| function initSectorChart() { |
| try { |
| const canvas = document.getElementById('sectorChart'); |
| if (!canvas || !screeningData || screeningData.length === 0) return; |
| if (sectorChartInstance) sectorChartInstance.destroy(); |
| |
| |
| const sectorData = {}; |
| screeningData.forEach(s => { |
| const sector = s.sector || 'Other'; |
| if (!sectorData[sector]) sectorData[sector] = { sum: 0, count: 0 }; |
| sectorData[sector].sum += s.change_percent || 0; |
| sectorData[sector].count++; |
| }); |
| |
| const sectors = Object.keys(sectorData).map(k => ({ |
| name: k, |
| avg: sectorData[k].sum / sectorData[k].count |
| })).sort((a, b) => b.avg - a.avg); |
| |
| if (sectors.length === 0) return; |
| |
| const labels = sectors.map(s => s.name.replace('Consumer ', '').replace('Communication', 'Comm')); |
| const data = sectors.map(s => s.avg); |
| const colors = data.map(v => v >= 0 ? '#00ff88' : '#ff4757'); |
| |
| sectorChartInstance = new Chart(canvas, { |
| type: 'bar', |
| data: { |
| labels: labels, |
| datasets: [{ |
| data: data, |
| backgroundColor: colors, |
| borderRadius: 4, |
| barThickness: 18 |
| }] |
| }, |
| options: { |
| indexAxis: 'y', |
| responsive: true, |
| maintainAspectRatio: false, |
| plugins: { legend: { display: false } }, |
| scales: { |
| x: { |
| grid: { color: 'rgba(255,255,255,0.1)' }, |
| ticks: { color: '#888', callback: v => v + '%' } |
| }, |
| y: { |
| grid: { display: false }, |
| ticks: { color: '#ccc', font: { size: 11 } } |
| } |
| } |
| } |
| }); |
| } catch (e) { |
| console.error('initSectorChart ์ค๋ฅ:', e); |
| } |
| } |
| |
| |
| let upDownChartInstance = null; |
| function initUpDownChart() { |
| try { |
| const canvas = document.getElementById('upDownChart'); |
| if (!canvas || !screeningData || screeningData.length === 0) return; |
| if (upDownChartInstance) upDownChartInstance.destroy(); |
| |
| let up = 0, down = 0, flat = 0; |
| screeningData.forEach(s => { |
| const chg = s.change_percent || 0; |
| if (chg > 0.1) up++; |
| else if (chg < -0.1) down++; |
| else flat++; |
| }); |
| |
| upDownChartInstance = new Chart(canvas, { |
| type: 'doughnut', |
| data: { |
| labels: ['์์น', 'ํ๋ฝ', '๋ณดํฉ'], |
| datasets: [{ |
| data: [up, down, flat], |
| backgroundColor: ['#00ff88', '#ff4757', '#ffa502'], |
| borderWidth: 0 |
| }] |
| }, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| cutout: '60%', |
| plugins: { |
| legend: { display: false } |
| } |
| } |
| }); |
| |
| |
| const legendEl = document.getElementById('upDownLegend'); |
| if (legendEl) { |
| legendEl.innerHTML = ` |
| <div class="legend-item"><div class="legend-dot" style="background:#00ff88"></div>์์น ${up}๊ฐ</div> |
| <div class="legend-item"><div class="legend-dot" style="background:#ff4757"></div>ํ๋ฝ ${down}๊ฐ</div> |
| <div class="legend-item"><div class="legend-dot" style="background:#ffa502"></div>๋ณดํฉ ${flat}๊ฐ</div> |
| `; |
| } |
| } catch (e) { |
| console.error('initUpDownChart ์ค๋ฅ:', e); |
| } |
| } |
| |
| |
| function initTradingViewWidgets() { |
| try { |
| |
| const tickerTape = document.getElementById('ticker-tape-widget'); |
| if (tickerTape && !tickerTape.dataset.loaded) { |
| tickerTape.dataset.loaded = 'true'; |
| tickerTape.innerHTML = ` |
| <div class="tradingview-widget-container"> |
| <div class="tradingview-widget-container__widget"></div> |
| </div> |
| `; |
| const script = document.createElement('script'); |
| script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-ticker-tape.js'; |
| script.async = true; |
| script.innerHTML = JSON.stringify({ |
| "symbols": [ |
| {"proName": "FOREXCOM:SPXUSD", "title": "S&P 500"}, |
| {"proName": "FOREXCOM:NSXUSD", "title": "NASDAQ"}, |
| {"proName": "BITSTAMP:BTCUSD", "title": "Bitcoin"}, |
| {"proName": "NASDAQ:NVDA", "title": "NVIDIA"}, |
| {"proName": "NASDAQ:AAPL", "title": "Apple"}, |
| {"proName": "NASDAQ:MSFT", "title": "Microsoft"}, |
| {"proName": "NASDAQ:GOOGL", "title": "Alphabet"}, |
| {"proName": "NASDAQ:AMZN", "title": "Amazon"}, |
| {"proName": "NASDAQ:META", "title": "Meta"}, |
| {"proName": "NASDAQ:TSLA", "title": "Tesla"} |
| ], |
| "showSymbolLogo": true, |
| "isTransparent": true, |
| "displayMode": "adaptive", |
| "colorTheme": "dark", |
| "locale": "kr" |
| }); |
| tickerTape.querySelector('.tradingview-widget-container').appendChild(script); |
| } |
| |
| |
| const marketOverview = document.getElementById('market-overview-widget'); |
| if (marketOverview && !marketOverview.dataset.loaded) { |
| marketOverview.dataset.loaded = 'true'; |
| marketOverview.innerHTML = `<div class="tradingview-widget-container" style="height:100%;width:100%"><div class="tradingview-widget-container__widget" style="height:100%;width:100%"></div></div>`; |
| const script = document.createElement('script'); |
| script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-market-overview.js'; |
| script.async = true; |
| script.innerHTML = JSON.stringify({ |
| "colorTheme": "dark", |
| "dateRange": "1D", |
| "showChart": true, |
| "locale": "kr", |
| "largeChartUrl": "", |
| "isTransparent": true, |
| "showSymbolLogo": true, |
| "showFloatingTooltip": true, |
| "width": "100%", |
| "height": "100%", |
| "tabs": [ |
| { |
| "title": "์ง์", |
| "symbols": [ |
| {"s": "FOREXCOM:SPXUSD", "d": "S&P 500"}, |
| {"s": "FOREXCOM:NSXUSD", "d": "NASDAQ"}, |
| {"s": "FOREXCOM:DJI", "d": "Dow Jones"}, |
| {"s": "INDEX:VIX", "d": "VIX"} |
| ], |
| "originalTitle": "Indices" |
| }, |
| { |
| "title": "๋น
ํ
ํฌ", |
| "symbols": [ |
| {"s": "NASDAQ:NVDA", "d": "NVIDIA"}, |
| {"s": "NASDAQ:AAPL", "d": "Apple"}, |
| {"s": "NASDAQ:MSFT", "d": "Microsoft"}, |
| {"s": "NASDAQ:GOOGL", "d": "Alphabet"}, |
| {"s": "NASDAQ:META", "d": "Meta"} |
| ], |
| "originalTitle": "Big Tech" |
| } |
| ] |
| }); |
| marketOverview.querySelector('.tradingview-widget-container').appendChild(script); |
| } |
| |
| |
| const heatmap = document.getElementById('stock-heatmap-widget'); |
| if (heatmap && !heatmap.dataset.loaded) { |
| heatmap.dataset.loaded = 'true'; |
| heatmap.innerHTML = `<div class="tradingview-widget-container" style="height:100%;width:100%"><div class="tradingview-widget-container__widget" style="height:100%;width:100%"></div></div>`; |
| const script = document.createElement('script'); |
| script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-stock-heatmap.js'; |
| script.async = true; |
| script.innerHTML = JSON.stringify({ |
| "exchanges": [], |
| "dataSource": "SPX500", |
| "grouping": "sector", |
| "blockSize": "market_cap_basic", |
| "blockColor": "change", |
| "locale": "kr", |
| "symbolUrl": "", |
| "colorTheme": "dark", |
| "hasTopBar": false, |
| "isDataSet498or": true, |
| "isZoomEnabled": true, |
| "hasSymbolTooltip": true, |
| "isMonoSize": false, |
| "width": "100%", |
| "height": "100%" |
| }); |
| heatmap.querySelector('.tradingview-widget-container').appendChild(script); |
| } |
| } catch (e) { |
| console.error('initTradingViewWidgets ์ค๋ฅ:', e); |
| } |
| } |
| |
| |
| function switchHeatmap(dataSource, btn) { |
| |
| document.querySelectorAll('.heatmap-tab').forEach(t => t.classList.remove('active')); |
| btn.classList.add('active'); |
| |
| |
| const heatmap = document.getElementById('stock-heatmap-widget'); |
| heatmap.innerHTML = `<div class="tradingview-widget-container" style="height:100%;width:100%"><div class="tradingview-widget-container__widget" style="height:100%;width:100%"></div></div>`; |
| const script = document.createElement('script'); |
| script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-stock-heatmap.js'; |
| script.async = true; |
| script.innerHTML = JSON.stringify({ |
| "exchanges": [], |
| "dataSource": dataSource, |
| "grouping": "sector", |
| "blockSize": "market_cap_basic", |
| "blockColor": "change", |
| "locale": "kr", |
| "symbolUrl": "", |
| "colorTheme": "dark", |
| "hasTopBar": false, |
| "isDataSetEnabled": true, |
| "isZoomEnabled": true, |
| "hasSymbolTooltip": true, |
| "isMonoSize": false, |
| "width": "100%", |
| "height": "100%" |
| }); |
| heatmap.querySelector('.tradingview-widget-container').appendChild(script); |
| } |
| |
| function initSentimentGauge() { |
| const canvas = document.getElementById('sentimentGauge'); |
| if (!canvas) return; |
| if (sentimentGaugeInstance) sentimentGaugeInstance.destroy(); |
| |
| let score = 50; |
| let pos = 0, neg = 0; |
| newsData.forEach(n => { if (n.sentiment === 'positive') pos++; else if (n.sentiment === 'negative') neg++; }); |
| if (newsData.length > 0) score = ((pos * 100) + ((newsData.length - pos - neg) * 50)) / newsData.length; |
| |
| if (screeningData && screeningData.length > 0) { |
| const avgChange = screeningData.reduce((s, x) => s + (x.change_percent || 0), 0) / screeningData.length; |
| score += avgChange * 5; |
| } |
| score = Math.max(0, Math.min(100, score)); |
| |
| sentimentGaugeInstance = new Chart(canvas, { |
| type: 'doughnut', |
| data: { datasets: [{ data: [score, 100 - score], backgroundColor: [score < 20 ? '#ff4757' : score < 40 ? '#ffa502' : score < 60 ? '#78909c' : score < 80 ? '#2ed573' : '#00ff88', 'rgba(255,255,255,0.05)'], borderWidth: 0 }] }, |
| options: { circumference: 180, rotation: -90, cutout: '75%', responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { enabled: false } } } |
| }); |
| |
| document.getElementById('sentimentValue').textContent = score.toFixed(1); |
| let label = 'Neutral', color = '#78909c'; |
| if (score < 20) { label = 'Strong Sell'; color = '#ff4757'; } |
| else if (score < 40) { label = 'Sell'; color = '#ffa502'; } |
| else if (score < 60) { label = 'Neutral'; color = '#78909c'; } |
| else if (score < 80) { label = 'Buy'; color = '#2ed573'; } |
| else { label = 'Strong Buy'; color = '#00ff88'; } |
| document.getElementById('sentimentLabel').textContent = label; |
| document.getElementById('sentimentLabel').style.color = color; |
| } |
| |
| function initKeywordCloud() { |
| const container = document.getElementById('keywordCloud'); |
| if (!container) return; |
| container.innerHTML = ''; |
| const keywords = {}; |
| |
| |
| const kwList = [ |
| |
| '๊ธ๋ฆฌ', '์ธํ๋ ์ด์
', 'Fed', 'FOMC', '๊ธ๋ฆฌ์ธํ', '๊ธ๋ฆฌ๋๊ฒฐ', 'CPI', 'GDP', '๊ณ ์ฉ', '์ค์
๋ฅ ', |
| 'interest rate', 'inflation', 'rate cut', 'rate hike', 'economy', |
| |
| '๊ด์ธ', '๋ฌด์ญ', '์์ถ', '์์
', '์ ์ฌ', 'ํธ๋ผํ', 'Trump', '์ค๊ตญ', 'China', '๋ฌ์์', |
| '์ฐํฌ๋ผ์ด๋', '์ ์', 'tariff', 'trade war', |
| |
| '์ ๊ฐ', 'OPEC', '์์ ', '์ฒ์ฐ๊ฐ์ค', 'oil', 'crude', 'energy', |
| |
| 'AI', '์ธ๊ณต์ง๋ฅ', '๋ฐ๋์ฒด', 'GPU', 'chip', 'semiconductor', 'NVIDIA', 'AMD', 'Intel', |
| 'OpenAI', 'ChatGPT', 'Gemini', '๋ฐ์ดํฐ์ผํฐ', 'cloud', |
| |
| '๋ฐฐํฐ๋ฆฌ', 'EV', '์ ๊ธฐ์ฐจ', 'Tesla', '๋ฆฌํฌ', '์ถฉ์ ', 'electric vehicle', |
| |
| 'Bitcoin', 'BTC', 'crypto', '์ํธํํ', 'ETF', 'Ethereum', |
| |
| '์ค์ ', '๋งค์ถ', '์์ด์ต', 'earnings', 'revenue', 'profit', 'beat', 'miss', 'guidance', |
| |
| 'M&A', 'IPO', '์ธ์', 'ํฉ๋ณ', 'acquisition', 'merger', |
| |
| '๊ท์ ', 'SEC', 'FTC', 'antitrust', '๋ฐ๋
์ ', '์์ก', 'lawsuit', |
| |
| '๋ฐฐ๋น', 'dividend', '์์ฌ์ฃผ', 'buyback', '๋ฆฌ์ฝ', 'recall', 'ํ์
', 'strike', |
| |
| 'Apple', 'Microsoft', 'Google', 'Amazon', 'Meta', 'Netflix' |
| ]; |
| |
| |
| newsData.forEach(n => { |
| const text = ((n.title || '') + ' ' + (n.content || '') + ' ' + (n.title_original || '')).toLowerCase(); |
| kwList.forEach(k => { |
| const kLower = k.toLowerCase(); |
| if (text.includes(kLower)) { |
| |
| const displayKey = k.charAt(0).toUpperCase() + k.slice(1); |
| keywords[displayKey] = (keywords[displayKey] || 0) + 1; |
| } |
| }); |
| }); |
| |
| |
| if (screeningData) { |
| screeningData.forEach(s => { |
| if (s.sector) { |
| const sectorKey = s.sector.replace('Consumer ', ''); |
| keywords[sectorKey] = (keywords[sectorKey] || 0) + 1; |
| } |
| }); |
| } |
| |
| const sorted = Object.entries(keywords).sort((a, b) => b[1] - a[1]).slice(0, 20); |
| const max = sorted[0] ? sorted[0][1] : 1; |
| const colors = ['#ff4757', '#ffa502', '#2ed573', '#00d9ff', '#9b59b6', '#e74c3c', '#3498db', '#1abc9c', '#f39c12', '#e91e63', '#00ff88', '#ff6b81', '#7bed9f', '#70a1ff', '#5352ed']; |
| |
| sorted.forEach(([kw, cnt], i) => { |
| const tag = document.createElement('span'); |
| tag.className = 'keyword-tag'; |
| const ratio = cnt / max; |
| tag.classList.add(ratio > 0.7 ? 'size-xl' : ratio > 0.4 ? 'size-lg' : ratio > 0.2 ? 'size-md' : 'size-sm'); |
| tag.style.backgroundColor = colors[i % colors.length]; |
| tag.style.color = '#fff'; |
| tag.textContent = `${kw} (${cnt})`; |
| tag.onclick = () => window.open(`https://www.google.com/search?q=${encodeURIComponent(kw + ' stock market news')}&tbm=nws`, '_blank'); |
| container.appendChild(tag); |
| }); |
| |
| if (sorted.length === 0) { |
| |
| const defaultKeywords = ['AI', 'Fed', '๋ฐ๋์ฒด', 'Tesla', 'NVIDIA', 'Bitcoin', '์ค์ ', '๊ธ๋ฆฌ']; |
| defaultKeywords.forEach((kw, i) => { |
| const tag = document.createElement('span'); |
| tag.className = 'keyword-tag size-md'; |
| tag.style.backgroundColor = colors[i % colors.length]; |
| tag.style.color = '#fff'; |
| tag.textContent = kw; |
| tag.onclick = () => window.open(`https://www.google.com/search?q=${encodeURIComponent(kw + ' stock market news')}&tbm=nws`, '_blank'); |
| container.appendChild(tag); |
| }); |
| } |
| } |
| |
| function initStockHeatmap() { |
| const container = document.getElementById('stockHeatmap'); |
| container.innerHTML = ''; |
| if (!screeningData || screeningData.length === 0) { |
| container.innerHTML = '<div style="color:#888;padding:20px;">๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.</div>'; |
| return; |
| } |
| |
| |
| const maxMarketCap = Math.max(...screeningData.map(s => s.market_cap || 0)); |
| |
| screeningData.forEach(stock => { |
| const cell = document.createElement('div'); |
| cell.className = 'heatmap-cell'; |
| const chg = stock.change_percent || 0; |
| |
| |
| const mcapRatio = (stock.market_cap || 0) / maxMarketCap; |
| let cellSize = 80; |
| let fontSize = 0.85; |
| if (mcapRatio > 0.8) { cellSize = 140; fontSize = 1.2; } |
| else if (mcapRatio > 0.5) { cellSize = 120; fontSize = 1.05; } |
| else if (mcapRatio > 0.3) { cellSize = 100; fontSize = 0.95; } |
| else if (mcapRatio > 0.15) { cellSize = 90; fontSize = 0.9; } |
| |
| cell.style.minWidth = cellSize + 'px'; |
| cell.style.minHeight = (cellSize * 0.7) + 'px'; |
| cell.style.flex = `0 0 ${cellSize}px`; |
| |
| |
| if (chg > 3) cell.classList.add('strong-up'); |
| else if (chg > 1) cell.classList.add('up'); |
| else if (chg > 0) cell.classList.add('slight-up'); |
| else if (chg > -1) cell.classList.add('neutral'); |
| else if (chg > -3) cell.classList.add('slight-down'); |
| else if (chg > -5) cell.classList.add('down'); |
| else cell.classList.add('strong-down'); |
| |
| cell.innerHTML = `<div class="ticker" style="font-size:${fontSize}em">${stock.ticker}</div><div class="change">${chg >= 0 ? '+' : ''}${chg.toFixed(2)}%</div><div class="price">$${stock.price.toFixed(2)}</div>`; |
| cell.onclick = () => openChartModal(stock.ticker, stock.name, stock.exchange || 'NASDAQ'); |
| container.appendChild(cell); |
| }); |
| } |
| |
| function initMiniCharts() { |
| if (!screeningData) return; |
| screeningData.forEach((stock, i) => { |
| const container = document.getElementById(`mini-chart-${stock.ticker}`); |
| if (!container || container.dataset.loaded) return; |
| container.dataset.loaded = 'true'; |
| container.innerHTML = ''; |
| |
| |
| const exchange = stock.exchange || 'NASDAQ'; |
| const symbol = `${exchange}:${stock.ticker}`; |
| |
| new TradingView.MediumWidget({ |
| symbols: [[stock.ticker, `${symbol}|3D`]], |
| chartOnly: true, |
| width: '100%', |
| height: 60, |
| locale: 'kr', |
| colorTheme: 'dark', |
| autosize: false, |
| showVolume: false, |
| hideDateRanges: true, |
| scalePosition: 'no', |
| scaleMode: 'Normal', |
| fontFamily: 'sans-serif', |
| noTimeScale: true, |
| chartType: 'area', |
| lineColor: stock.change_percent >= 0 ? '#00ff88' : '#ff4757', |
| bottomColor: stock.change_percent >= 0 ? 'rgba(0,255,136,0.1)' : 'rgba(255,71,87,0.1)', |
| lineWidth: 2, |
| container_id: `mini-chart-${stock.ticker}` |
| }); |
| }); |
| } |
| |
| function openChartModal(ticker, name, exchange) { |
| |
| const symbol = getTradingViewSymbol(ticker, exchange); |
| |
| document.getElementById('chart-modal-title').textContent = `${ticker} - ${name}`; |
| document.getElementById('chartModal').classList.add('active'); |
| document.getElementById('tradingview-chart').innerHTML = ''; |
| new TradingView.widget({ |
| autosize: true, |
| symbol: symbol, |
| interval: 'D', |
| timezone: 'Asia/Seoul', |
| theme: 'dark', |
| style: '1', |
| locale: 'kr', |
| toolbar_bg: '#1a1a2e', |
| enable_publishing: false, |
| allow_symbol_change: true, |
| container_id: 'tradingview-chart', |
| hide_side_toolbar: false, |
| studies: ['RSI@tv-basicstudies', 'MASimple@tv-basicstudies'] |
| }); |
| } |
| |
| function closeModal(modalId) { |
| document.getElementById(modalId).classList.remove('active'); |
| } |
| |
| function renderMarkdown(text) { |
| if (!text) return '-'; |
| if (typeof marked !== 'undefined') return marked.parse(text); |
| return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\n/g, '<br>'); |
| } |
| |
| function updateElasticityUI(report) { |
| |
| const upside = report.expected_upside || 15; |
| const downside = report.expected_downside || -10; |
| const prediction = report.base_prediction || report.upside || 8; |
| const upProb = report.up_probability || 55; |
| const downProb = report.down_probability || 45; |
| const riskReward = report.risk_reward || 1.5; |
| |
| |
| document.getElementById('result-upside').textContent = `+${upside}%`; |
| document.getElementById('result-downside').textContent = `${downside}%`; |
| document.getElementById('result-prediction').textContent = prediction >= 0 ? `+${prediction}%` : `${prediction}%`; |
| document.getElementById('result-prediction').style.color = prediction >= 0 ? '#00d9ff' : '#ff4757'; |
| document.getElementById('result-up-prob').textContent = `${upProb}%`; |
| document.getElementById('result-down-prob').textContent = `${downProb}%`; |
| document.getElementById('result-rr').textContent = `${riskReward}:1`; |
| |
| |
| const minVal = Math.min(downside, -30); |
| const maxVal = Math.max(upside, 40); |
| const range = maxVal - minVal; |
| |
| |
| document.getElementById('elasticity-min').textContent = `${Math.round(minVal)}%`; |
| document.getElementById('elasticity-max').textContent = `+${Math.round(maxVal)}%`; |
| |
| |
| const rangeLeft = ((downside - minVal) / range) * 100; |
| const rangeRight = ((upside - minVal) / range) * 100; |
| const rangeEl = document.getElementById('elasticity-range'); |
| rangeEl.style.left = `${rangeLeft}%`; |
| rangeEl.style.width = `${rangeRight - rangeLeft}%`; |
| |
| |
| const markerPos = ((prediction - minVal) / range) * 100; |
| const markerEl = document.getElementById('elasticity-marker'); |
| markerEl.style.left = `calc(${markerPos}% - 2px)`; |
| } |
| |
| function initAnalysisCharts(report) { |
| [metricsChartInstance, priceChartInstance, riskChartInstance, growthChartInstance].forEach(c => c && c.destroy()); |
| const cp = report.current_price || 100, tp = report.target_price || 112; |
| const mc = document.getElementById('metricsChart'); |
| if (mc) metricsChartInstance = new Chart(mc, { type: 'radar', data: { labels: ['์์ต์ฑ', '์ฑ์ฅ์ฑ', '์์ ์ฑ', '๋ฐธ๋ฅ์์ด์
', '๋ชจ๋ฉํ
'], datasets: [{ data: [75+Math.random()*20, 70+Math.random()*25, 65+Math.random()*20, 60+Math.random()*25, 72+Math.random()*20], backgroundColor: 'rgba(0,217,255,0.2)', borderColor: '#00d9ff', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { r: { beginAtZero: true, max: 100, ticks: { color: '#888', backdropColor: 'transparent' }, grid: { color: 'rgba(255,255,255,0.1)' }, pointLabels: { color: '#ccc', font: { size: 10 } } } } } }); |
| const pc = document.getElementById('priceChart'); |
| if (pc) priceChartInstance = new Chart(pc, { type: 'bar', data: { labels: ['ํ์ฌ๊ฐ', '๋ชฉํ๊ฐ', '์ปจ์ผ์์ค'], datasets: [{ data: [cp, tp, tp*0.95], backgroundColor: ['#3498db', '#00ff88', '#9b59b6'], borderRadius: 5 }] }, options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y', plugins: { legend: { display: false } }, scales: { x: { grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#888' } }, y: { grid: { display: false }, ticks: { color: '#ccc' } } } } }); |
| const rc = document.getElementById('riskChart'); |
| if (rc) riskChartInstance = new Chart(rc, { type: 'doughnut', data: { labels: ['๊ธฐํ', '๋ฆฌ์คํฌ', '์ค๋ฆฝ'], datasets: [{ data: [55+Math.random()*15, 25+Math.random()*10, 20], backgroundColor: ['#00ff88', '#ff4757', '#ffa502'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#ccc', font: { size: 10 } } } } } }); |
| const gc = document.getElementById('growthChart'); |
| if (gc) growthChartInstance = new Chart(gc, { type: 'line', data: { labels: ['2024', '2025E', '2026E', '2027E'], datasets: [{ label: '๋งค์ถ', data: [8,12,15,18].map(v => v+Math.random()*5), borderColor: '#00d9ff', fill: true, backgroundColor: 'rgba(0,217,255,0.1)', tension: 0.4 }, { label: 'EPS', data: [10,15,20,25].map(v => v+Math.random()*5), borderColor: '#00ff88', fill: true, backgroundColor: 'rgba(0,255,136,0.1)', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#ccc', font: { size: 10 } } } }, scales: { y: { grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#888' } }, x: { grid: { display: false }, ticks: { color: '#ccc' } } } } }); |
| } |
| |
| async function runDeepAnalysis(ticker, companyName, forceRefresh = false) { |
| currentTicker = ticker; |
| const statusEl = document.getElementById(`deep-status-${ticker}`); |
| if (statusEl) statusEl.innerHTML = 'โณ ๋ถ์ ์ค...'; |
| |
| document.getElementById('modal-ticker').textContent = `${ticker} (${companyName})`; |
| document.getElementById('deepAnalysisModal').classList.add('active'); |
| document.getElementById('loading-indicator').classList.add('active'); |
| document.getElementById('deep-analysis-result').style.display = 'none'; |
| |
| |
| const regenerateBtn = document.getElementById('regenerate-btn'); |
| if (regenerateBtn) regenerateBtn.style.display = 'none'; |
| |
| try { |
| |
| const controller = new AbortController(); |
| const timeoutId = setTimeout(() => controller.abort(), 120000); |
| |
| const url = forceRefresh |
| ? `/api/deep-analysis/${ticker}?force=true` |
| : `/api/deep-analysis/${ticker}`; |
| |
| const res = await fetch(url, { signal: controller.signal }); |
| clearTimeout(timeoutId); |
| |
| const data = await res.json(); |
| |
| if (data.success) { |
| const report = data.report; |
| |
| |
| const requiredFields = ['executive_summary', 'company_overview', 'financial_analysis', 'final_recommendation']; |
| const missingFields = requiredFields.filter(f => !report[f] || report[f].length < 50); |
| |
| if (missingFields.length > 0) { |
| console.warn('๋ถ๋ถ ์์ฑ ๊ฐ์ง:', missingFields); |
| displayDeepAnalysisResult(report, ticker, true); |
| if (statusEl) statusEl.innerHTML = 'โ ๏ธ ์ผ๋ถ ๋ฏธ์์ฑ'; |
| } else { |
| displayDeepAnalysisResult(report, ticker, false); |
| if (statusEl) statusEl.innerHTML = data.from_cache ? '๐พ ์บ์' : 'โ
์๋ฃ'; |
| } |
| } else { |
| if (statusEl) statusEl.innerHTML = 'โ ์คํจ'; |
| showAnalysisError(data.error || '๋ถ์ ์คํจ'); |
| } |
| } catch (e) { |
| if (e.name === 'AbortError') { |
| if (statusEl) statusEl.innerHTML = 'โฑ๏ธ ํ์์์'; |
| showAnalysisError('๋ถ์ ์๊ฐ์ด ์ด๊ณผ๋์์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.'); |
| } else { |
| if (statusEl) statusEl.innerHTML = 'โ ์ค๋ฅ'; |
| showAnalysisError('๋คํธ์ํฌ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.'); |
| } |
| } |
| document.getElementById('loading-indicator').classList.remove('active'); |
| } |
| |
| function showAnalysisError(message) { |
| document.getElementById('deep-analysis-result').style.display = 'block'; |
| document.getElementById('deep-analysis-result').innerHTML = ` |
| <div style="text-align: center; padding: 40px;"> |
| <div style="font-size: 3em; margin-bottom: 20px;">โ</div> |
| <h3 style="color: #ff4757; margin-bottom: 15px;">${message}</h3> |
| <button class="deep-analysis-btn" onclick="runDeepAnalysis(currentTicker, '', true)" style="margin-top: 20px;"> |
| ๐ ๋ค์ ์๋ |
| </button> |
| </div> |
| `; |
| } |
| |
| function displayDeepAnalysisResult(report, ticker, isIncomplete = false) { |
| |
| const fields = ['executive_summary', 'company_overview', 'financial_analysis', 'technical_analysis', 'industry_analysis', 'risk_assessment', 'investment_thesis', 'price_targets', 'catalysts', 'final_recommendation']; |
| |
| fields.forEach(key => { |
| const elId = `result-${key.replace(/_/g, '-')}`; |
| const el = document.getElementById(elId); |
| if (el) { |
| const content = report[key]; |
| if (content && content.length > 20) { |
| el.innerHTML = renderMarkdown(content); |
| } else { |
| el.innerHTML = '<span style="color: #888;">โณ ์์ฑ ์ค ๋๋ ๋ฐ์ดํฐ ์์</span>'; |
| } |
| } |
| }); |
| |
| document.getElementById('result-current-price').textContent = report.current_price ? `$${report.current_price.toLocaleString()}` : '-'; |
| document.getElementById('result-target-price').textContent = report.target_price ? `$${report.target_price.toLocaleString()}` : '-'; |
| document.getElementById('result-rating').textContent = report.rating || 'Buy'; |
| |
| |
| updateElasticityUI(report); |
| |
| setTimeout(() => initAnalysisCharts(report), 100); |
| |
| document.getElementById('download-docx-btn').href = `/api/download-docx/${ticker}`; |
| document.getElementById('download-docx-btn').style.display = 'inline-flex'; |
| document.getElementById('download-pdf-btn').style.display = 'inline-flex'; |
| |
| |
| let regenerateBtn = document.getElementById('regenerate-btn'); |
| if (!regenerateBtn) { |
| regenerateBtn = document.createElement('button'); |
| regenerateBtn.id = 'regenerate-btn'; |
| regenerateBtn.className = 'download-btn'; |
| regenerateBtn.style.background = 'linear-gradient(135deg, #e74c3c, #c0392b)'; |
| regenerateBtn.innerHTML = '๐ ์ฌ์์ฑ'; |
| regenerateBtn.onclick = () => runDeepAnalysis(currentTicker, '', true); |
| document.getElementById('download-pdf-btn').parentNode.appendChild(regenerateBtn); |
| } |
| |
| if (isIncomplete) { |
| regenerateBtn.style.display = 'inline-flex'; |
| } else { |
| regenerateBtn.style.display = 'none'; |
| } |
| |
| document.getElementById('deep-analysis-result').style.display = 'block'; |
| } |
| |
| async function downloadAsPDF() { |
| const btn = document.getElementById('download-pdf-btn'); |
| const originalText = btn.innerHTML; |
| btn.innerHTML = 'โณ ์ค๋น ์ค...'; |
| btn.disabled = true; |
| |
| try { |
| |
| const resultArea = document.getElementById('deep-analysis-result'); |
| if (!resultArea) { |
| throw new Error('๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.'); |
| } |
| |
| |
| const printContent = ` |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <title>${currentTicker} ์ฌ์ธต ๋ถ์ ๋ฆฌํฌํธ</title> |
| <style> |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { |
| font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif; |
| background: white; |
| color: #333; |
| padding: 30px; |
| line-height: 1.6; |
| } |
| h1 { color: #0066cc; margin-bottom: 20px; font-size: 24px; border-bottom: 2px solid #0066cc; padding-bottom: 10px; } |
| h2 { color: #0066cc; margin: 25px 0 15px 0; font-size: 18px; } |
| h3 { color: #009966; margin: 20px 0 10px 0; font-size: 16px; } |
| p { margin-bottom: 10px; } |
| .header { text-align: center; margin-bottom: 30px; padding: 20px; background: #f5f5f5; border-radius: 10px; } |
| .header h1 { border: none; margin: 0; } |
| .header .date { color: #666; font-size: 14px; margin-top: 10px; } |
| .metrics { display: flex; justify-content: space-around; margin: 20px 0; padding: 15px; background: #f9f9f9; border-radius: 10px; } |
| .metric { text-align: center; } |
| .metric-label { color: #666; font-size: 12px; } |
| .metric-value { font-size: 20px; font-weight: bold; color: #009966; } |
| .section { margin-bottom: 25px; padding: 20px; background: #fafafa; border-radius: 10px; border-left: 3px solid #0066cc; } |
| .section-title { color: #0066cc; font-size: 16px; font-weight: bold; margin-bottom: 15px; } |
| .content { color: #333; } |
| ul, ol { margin-left: 20px; margin-bottom: 10px; } |
| li { margin-bottom: 5px; } |
| strong { color: #000; } |
| .footer { text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #999; font-size: 12px; } |
| @page { margin: 1cm; } |
| </style> |
| </head> |
| <body> |
| <div class="header"> |
| <h1>๐ ${currentTicker} ์ฌ์ธต ๋ถ์ ๋ฆฌํฌํธ</h1> |
| <div class="date">์์ฑ์ผ: ${new Date().toLocaleDateString('ko-KR')} | AI Stock Research Center</div> |
| </div> |
| |
| <div class="metrics"> |
| <div class="metric"> |
| <div class="metric-label">ํ์ฌ๊ฐ</div> |
| <div class="metric-value">${document.getElementById('result-current-price')?.textContent || '-'}</div> |
| </div> |
| <div class="metric"> |
| <div class="metric-label">๋ชฉํ๊ฐ</div> |
| <div class="metric-value">${document.getElementById('result-target-price')?.textContent || '-'}</div> |
| </div> |
| <div class="metric"> |
| <div class="metric-label">ํฌ์์๊ฒฌ</div> |
| <div class="metric-value">${document.getElementById('result-rating')?.textContent || '-'}</div> |
| </div> |
| </div> |
| |
| <div class="section" style="background: #f0f8ff; border-left-color: #0099cc;"> |
| <div class="section-title">๐ ์์ต๋ฅ ์ ๋ง</div> |
| <div style="display: flex; justify-content: space-around; text-align: center; margin: 15px 0;"> |
| <div> |
| <div style="color: #cc0000; font-size: 12px;">๐ ํ๋ฝ ๋ฆฌ์คํฌ</div> |
| <div style="font-size: 20px; font-weight: bold; color: #cc0000;">${document.getElementById('result-downside')?.textContent || '-'}</div> |
| <div style="font-size: 11px; color: #666;">${document.getElementById('result-down-prob')?.textContent || '-'} ํ๋ฅ </div> |
| </div> |
| <div> |
| <div style="color: #0066cc; font-size: 12px;">๐ฏ ๊ธฐ๋ ์์ต</div> |
| <div style="font-size: 24px; font-weight: bold; color: #0066cc;">${document.getElementById('result-prediction')?.textContent || '-'}</div> |
| <div style="font-size: 11px; color: #666;">R/R ${document.getElementById('result-rr')?.textContent || '-'}</div> |
| </div> |
| <div> |
| <div style="color: #009966; font-size: 12px;">๐ ์์น ์ ์ฌ๋ ฅ</div> |
| <div style="font-size: 20px; font-weight: bold; color: #009966;">${document.getElementById('result-upside')?.textContent || '-'}</div> |
| <div style="font-size: 11px; color: #666;">${document.getElementById('result-up-prob')?.textContent || '-'} ํ๋ฅ </div> |
| </div> |
| </div> |
| <div style="text-align: center; font-size: 10px; color: #999;">โป AI ๊ธฐ๋ฐ ๋ถ์์ด๋ฉฐ ์ค์ ํฌ์ ์์ต์ ๋ณด์ฅํ์ง ์์ต๋๋ค</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">๐ ํต์ฌ ์์ฝ</div> |
| <div class="content">${document.getElementById('result-executive-summary')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">๐ข ํ์ฌ ๊ฐ์</div> |
| <div class="content">${document.getElementById('result-company-overview')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">๐ฐ ์ฌ๋ฌด ๋ถ์</div> |
| <div class="content">${document.getElementById('result-financial-analysis')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">๐ ๊ธฐ์ ์ ๋ถ์</div> |
| <div class="content">${document.getElementById('result-technical-analysis')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">๐ญ ์ฐ์
๋ถ์</div> |
| <div class="content">${document.getElementById('result-industry-analysis')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">โ ๏ธ ๋ฆฌ์คํฌ ํ๊ฐ</div> |
| <div class="content">${document.getElementById('result-risk-assessment')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">๐ก ํฌ์ ๋
ผ๋ฆฌ</div> |
| <div class="content">${document.getElementById('result-investment-thesis')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">๐ฏ ๋ชฉํ ์ฃผ๊ฐ</div> |
| <div class="content">${document.getElementById('result-price-targets')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">๐ ์นดํ๋ฆฌ์คํธ</div> |
| <div class="content">${document.getElementById('result-catalysts')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="section"> |
| <div class="section-title">โ
์ต์ข
๊ถ๊ณ </div> |
| <div class="content">${document.getElementById('result-final-recommendation')?.innerHTML || '-'}</div> |
| </div> |
| |
| <div class="footer"> |
| <p>๋ณธ ๋ณด๊ณ ์๋ AI ๊ธฐ๋ฐ ์๋ ๋ถ์ ๊ฒฐ๊ณผ์ด๋ฉฐ, ํฌ์ ๊ฒฐ์ ์ ๋ณธ์ธ์ ํ๋จ๊ณผ ์ฑ
์ ํ์ ์ด๋ฃจ์ด์ ธ์ผ ํฉ๋๋ค.</p> |
| <p>ยฉ ${new Date().getFullYear()} AI Stock Research Center | Ginigen AI</p> |
| </div> |
| </body> |
| </html> |
| `; |
| |
| |
| let printFrame = document.getElementById('print-frame'); |
| if (!printFrame) { |
| printFrame = document.createElement('iframe'); |
| printFrame.id = 'print-frame'; |
| printFrame.style.position = 'absolute'; |
| printFrame.style.top = '-10000px'; |
| printFrame.style.left = '-10000px'; |
| printFrame.style.width = '1px'; |
| printFrame.style.height = '1px'; |
| document.body.appendChild(printFrame); |
| } |
| |
| const frameDoc = printFrame.contentWindow || printFrame.contentDocument; |
| const doc = frameDoc.document || frameDoc; |
| |
| doc.open(); |
| doc.write(printContent); |
| doc.close(); |
| |
| |
| setTimeout(() => { |
| try { |
| printFrame.contentWindow.focus(); |
| printFrame.contentWindow.print(); |
| btn.innerHTML = 'โ
์๋ฃ!'; |
| setTimeout(() => { btn.innerHTML = originalText; }, 2000); |
| } catch (printErr) { |
| console.error('ํ๋ฆฐํธ ์ค๋ฅ:', printErr); |
| btn.innerHTML = 'โ ์คํจ'; |
| setTimeout(() => { btn.innerHTML = originalText; }, 3000); |
| } |
| btn.disabled = false; |
| }, 500); |
| |
| } catch (e) { |
| console.error('PDF ์์ฑ ์ค๋ฅ:', e); |
| btn.innerHTML = 'โ ์คํจ'; |
| setTimeout(() => { btn.innerHTML = originalText; }, 3000); |
| alert('PDF ์์ฑ ์คํจ: ' + e.message); |
| btn.disabled = false; |
| } |
| } |
| |
| document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeModal('deepAnalysisModal'); closeModal('chartModal'); } }); |
| document.getElementById('deepAnalysisModal').addEventListener('click', e => { if (e.target.id === 'deepAnalysisModal') closeModal('deepAnalysisModal'); }); |
| document.getElementById('chartModal').addEventListener('click', e => { if (e.target.id === 'chartModal') closeModal('chartModal'); }); |
| |
| document.addEventListener('DOMContentLoaded', () => setTimeout(initMarketTab, 100)); |
| </script> |
| </body> |
| </html> |