File size: 15,172 Bytes
f63d7fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# pages/stock_report.py
import os
import asyncio
import streamlit as st
import pandas as pd
import altair as alt
from io import BytesIO
import base64
import tempfile
import weasyprint
import markdown
import json
from datetime import datetime
from modules.analysis_pipeline import run_analysis_pipeline, generate_html_report
from twelvedata_api import TwelveDataAPI

# Thiết lập trang
st.set_page_config(
    page_title="Stock Analysis Report",
    page_icon="📊",
    layout="wide"
)

# Tiêu đề ứng dụng
st.title("📄 In-depth Stock Analysis Report")
st.markdown("""
    This application generates a comprehensive analysis report for a stock symbol, combining data from multiple sources 
    and using AI to synthesize information, helping you make better investment decisions.
""")

# Hàm tạo biểu đồ giá
def create_price_chart(price_data, period):
    """Tạo biểu đồ giá từ dữ liệu"""
    if 'values' not in price_data:
        return None
    
    df = pd.DataFrame(price_data['values'])
    if df.empty:
        return None
    
    df['datetime'] = pd.to_datetime(df['datetime'])
    df['close'] = pd.to_numeric(df['close'])
    
    # Xác định tiêu đề biểu đồ dựa vào khoảng thời gian
    title_map = {
        '1_month': 'Stock price over the last month',
        '3_months': 'Stock price over the last 3 months',
        '1_year': 'Stock price over the last year'
    }
    
    # Tạo biểu đồ với Altair
    chart = alt.Chart(df).mark_line().encode(
        x=alt.X('datetime:T', title='Time'),
        y=alt.Y('close:Q', title='Closing Price', scale=alt.Scale(zero=False)),
        tooltip=[
            alt.Tooltip('datetime:T', title='Date', format='%d/%m/%Y'),
            alt.Tooltip('close:Q', title='Closing Price', format=',.2f'),
            alt.Tooltip('volume:Q', title='Volume', format=',.0f')
        ]
    ).properties(
        title=title_map.get(period, f'Stock price ({period})'),
        height=350
    ).interactive()
    
    return chart

# Hàm chuyển đổi kết quả phân tích thành PDF
def convert_html_to_pdf(html_content):
    """Chuyển đổi HTML thành file PDF"""
    with tempfile.NamedTemporaryFile(suffix='.html', delete=False) as f:
        f.write(html_content.encode())
        temp_html = f.name
    
    pdf_bytes = weasyprint.HTML(filename=temp_html).write_pdf()
    
    # Xóa file tạm sau khi sử dụng
    os.unlink(temp_html)
    
    return pdf_bytes

# Hàm tạo nút tải xuống file PDF
def get_download_link(pdf_bytes, filename):
    """Tạo link tải xuống cho file PDF"""
    b64 = base64.b64encode(pdf_bytes).decode()
    href = f'<a href="data:application/pdf;base64,{b64}" download="{filename}">Download Report (PDF)</a>'
    return href

# Danh sách các mã chứng khoán phổ biến và thông tin
def load_stock_symbols():
    """Load stock symbols from cache or create new cache"""
    cache_file = "static/stock_symbols_cache.json"
    
    # Check if cache exists
    if os.path.exists(cache_file):
        try:
            with open(cache_file, 'r') as f:
                return json.load(f)
        except Exception as e:
            print(f"Error loading cache: {e}")
    
    # Default list if cache doesn't exist or fails to load
    default_symbols = [
        {"symbol": "AAPL", "name": "Apple Inc."},
        {"symbol": "MSFT", "name": "Microsoft Corporation"},
        {"symbol": "GOOGL", "name": "Alphabet Inc."},
        {"symbol": "AMZN", "name": "Amazon.com Inc."},
        {"symbol": "TSLA", "name": "Tesla, Inc."},
        {"symbol": "META", "name": "Meta Platforms, Inc."},
        {"symbol": "NVDA", "name": "NVIDIA Corporation"},
        {"symbol": "JPM", "name": "JPMorgan Chase & Co."},
        {"symbol": "V", "name": "Visa Inc."},
        {"symbol": "JNJ", "name": "Johnson & Johnson"},
        {"symbol": "WMT", "name": "Walmart Inc."},
        {"symbol": "MA", "name": "Mastercard Incorporated"},
        {"symbol": "PG", "name": "Procter & Gamble Co."},
        {"symbol": "UNH", "name": "UnitedHealth Group Inc."},
        {"symbol": "HD", "name": "Home Depot Inc."},
        {"symbol": "BAC", "name": "Bank of America Corp."},
        {"symbol": "XOM", "name": "Exxon Mobil Corporation"},
        {"symbol": "DIS", "name": "Walt Disney Co."},
        {"symbol": "CSCO", "name": "Cisco Systems, Inc."},
        {"symbol": "VZ", "name": "Verizon Communications Inc."},
        {"symbol": "ADBE", "name": "Adobe Inc."},
        {"symbol": "NFLX", "name": "Netflix, Inc."},
        {"symbol": "CMCSA", "name": "Comcast Corporation"},
        {"symbol": "PFE", "name": "Pfizer Inc."},
        {"symbol": "KO", "name": "Coca-Cola Company"},
        {"symbol": "INTC", "name": "Intel Corporation"},
        {"symbol": "PYPL", "name": "PayPal Holdings, Inc."},
        {"symbol": "T", "name": "AT&T Inc."},
        {"symbol": "PEP", "name": "PepsiCo, Inc."},
        {"symbol": "MRK", "name": "Merck & Co., Inc."}
    ]
    
    # Try to fetch more comprehensive list if API key is available
    try:
        from dotenv import load_dotenv
        load_dotenv()
        api_key = os.getenv("TWELVEDATA_API_KEY")
        if api_key:
            td_api = TwelveDataAPI(api_key)
            stocks_data = td_api.get_all_stocks(exchange="NASDAQ")
            if stocks_data and 'data' in stocks_data:
                # Convert to format we need and take first 1000 stocks
                symbols = [{"symbol": stock["symbol"], "name": stock.get("name", "Unknown")} 
                          for stock in stocks_data['data']]
                
                # Save to cache
                os.makedirs(os.path.dirname(cache_file), exist_ok=True)
                with open(cache_file, 'w') as f:
                    json.dump(symbols, f)
                
                return symbols
    except Exception as e:
        print(f"Error fetching stock symbols from API: {e}")
    
    # If everything fails, return default list
    return default_symbols

# Load stock symbols
STOCK_SYMBOLS = load_stock_symbols()

# Function to format stock options for display
def format_stock_option(stock):
    return f"{stock['symbol']} - {stock['name']}"

# Tạo giao diện
col1, col2 = st.columns([3, 1])

# Phần nhập thông tin
with col2:
    st.subheader("Enter Information")
    
    # Create a list of formatted options and a mapping back to symbols
    stock_options = [format_stock_option(stock) for stock in STOCK_SYMBOLS]
    
    # Use selectbox with search functionality
    selected_stock = st.selectbox(
        "Select a stock symbol",
        options=stock_options,
        index=0 if stock_options else None,
        placeholder="Search for a stock symbol...",
    )
    
    # Extract symbol from selection
    if selected_stock:
        stock_symbol = selected_stock.split(" - ")[0]
    else:
        stock_symbol = ""
    
    if st.button("Generate Report", use_container_width=True, type="primary"):
        if not stock_symbol:
            st.error("Please select a stock symbol to continue.")
        else:
            # Lưu mã cổ phiếu vào session state để duy trì giữa các lần chạy
            st.session_state.stock_symbol = stock_symbol
            st.session_state.analysis_requested = True
            st.rerun()
    
    # PDF report generation section - moved from tab1
    if "analysis_complete" in st.session_state and st.session_state.analysis_complete:
        st.divider()
        st.subheader("PDF Report")
        
        # Lấy kết quả từ session state
        analysis_results = st.session_state.analysis_results
        
        # Tạo thư mục static nếu chưa tồn tại
        os.makedirs("static", exist_ok=True)
        
        # Tạo tên file PDF và đường dẫn
        filename = f"Report_{analysis_results['symbol']}_{datetime.now().strftime('%d%m%Y')}.pdf"
        pdf_path = os.path.join("static", filename)
        
        # Hiển thị thông tin
        st.markdown("Get a complete PDF report with price charts:")
        
        # Import hàm tạo PDF
        from modules.analysis_pipeline import generate_pdf_report
        
        # Nút tạo và tải xuống PDF (gộp chung)
        if st.button("📊 Generate & Download PDF Report", use_container_width=True, key="pdf_btn", type="primary"):
            # Kiểm tra nếu file không tồn tại hoặc cần tạo lại
            if not os.path.exists(pdf_path):
                with st.spinner("Creating PDF report with charts..."):
                    generate_pdf_report(analysis_results, pdf_path)
                
                if not os.path.exists(pdf_path):
                    st.error("Failed to create PDF report.")
                    st.stop()
            
            # Đọc file PDF để tải xuống
            with open(pdf_path, "rb") as pdf_file:
                pdf_bytes = pdf_file.read()
            
            # Hiển thị thông báo thành công và widget tải xuống
            st.success("PDF report generated successfully!")
            
            st.download_button(
                label="⬇️ Download Report",
                data=pdf_bytes,
                file_name=filename,
                mime="application/pdf",
                use_container_width=True,
                key="download_pdf_btn"
            )

# Phần hiển thị báo cáo
with col1:
    # Kiểm tra xem có yêu cầu phân tích không
    if "analysis_requested" in st.session_state and st.session_state.analysis_requested:
        symbol = st.session_state.stock_symbol
        
        with st.spinner(f"🔍 Collecting data and analyzing {symbol} stock... (this may take a few minutes)"):
            try:
                # Chạy phân tích
                analysis_results = asyncio.run(run_analysis_pipeline(symbol))
                
                # Lưu kết quả vào session state
                st.session_state.analysis_results = analysis_results
                st.session_state.analysis_complete = True
                st.session_state.analysis_requested = False
                
                # Tự động rerun để hiển thị kết quả
                st.rerun()
            except Exception as e:
                st.error(f"An error occurred during analysis: {str(e)}")
                st.session_state.analysis_requested = False
    
    # Kiểm tra xem phân tích đã hoàn thành chưa
    if "analysis_complete" in st.session_state and st.session_state.analysis_complete:
        # Lấy kết quả từ session state
        analysis_results = st.session_state.analysis_results
        
        # Tạo các tab để hiển thị nội dung
        tab1, tab2, tab3, tab4, tab5 = st.tabs([
            "📋 Overview", 
            "💰 Financial Health", 
            "📰 News & Sentiment", 
            "👨‍💼 Market Analysis",
            "📊 Price Charts"
        ])
        
        with tab1:
            # Hiển thị thông tin cơ bản về công ty
            overview = analysis_results.get('overview', {})
            if overview:
                col1, col2 = st.columns([1, 1])
                with col1:
                    st.subheader(f"{analysis_results['symbol']} - {overview.get('Name', 'N/A')}")
                    st.write(f"**Industry:** {overview.get('Industry', 'N/A')}")
                    st.write(f"**Sector:** {overview.get('Sector', 'N/A')}")
                with col2:
                    st.write(f"**Market Cap:** {overview.get('MarketCapitalization', 'N/A')}")
                    st.write(f"**P/E Ratio:** {overview.get('PERatio', 'N/A')}")
                    st.write(f"**Dividend Yield:** {overview.get('DividendYield', 'N/A')}%")
            
            # Hiển thị tóm tắt
            st.markdown("### Summary & Recommendation")
            st.markdown(analysis_results['analysis']['summary'])
        
        with tab2:
            st.markdown("### Financial Health Analysis")
            st.markdown(analysis_results['analysis']['financial_health'])
        
        with tab3:
            st.markdown("### News & Market Sentiment Analysis")
            st.markdown(analysis_results['analysis']['news_sentiment'])
        
        with tab4:
            st.markdown("### Market Analysis")
            st.markdown(analysis_results['analysis']['expert_opinion'])
        
        with tab5:
            st.markdown("### Stock Price Charts")
            
            # Hiển thị biểu đồ từ dữ liệu giá
            price_data = analysis_results.get('price_data', {})
            if price_data:
                period_tabs = st.tabs(['1 Month', '3 Months', '1 Year'])
                
                periods = ['1_month', '3_months', '1_year']
                for i, period in enumerate(periods):
                    with period_tabs[i]:
                        if period in price_data:
                            chart = create_price_chart(price_data[period], period)
                            if chart:
                                st.altair_chart(chart, use_container_width=True)
                            else:
                                st.info(f"Insufficient data to display chart for {period} timeframe.")
                        else:
                            st.info(f"No chart data available for {period} timeframe.")
            else:
                st.info("No price chart data available for this stock.")
    else:
        # Hiển thị hướng dẫn khi không có phân tích
        st.info("👈 Enter a stock symbol and click 'Generate Report' to begin.")
        st.markdown("""
        ### About Stock Analysis Reports
        
        The stock analysis report includes the following information:
        
        1. **Overview & Investment Recommendation**: Summary of the company and general investment potential assessment.
        2. **Financial Health Analysis**: Evaluation of financial metrics, revenue growth, and profitability.
        3. **News & Market Sentiment Analysis**: Summary of notable news related to the company.
        4. **Market Analysis**: Analysis of current stock performance and market trends.
        5. **Price Charts**: Stock price charts for various timeframes.
        
        Reports are generated based on data from multiple sources and analyzed by AI.
        """)
        
        # Hiển thị các mã cổ phiếu phổ biến
        st.markdown("### Popular Stock Symbols")
        
        # Hiển thị danh sách các mã cổ phiếu phổ biến theo lưới
        # Chỉ lấy 12 mã đầu tiên để không làm rối giao diện
        display_stocks = STOCK_SYMBOLS[:12]
        
        # Tạo lưới với 4 cột
        cols = st.columns(4)
        for i, stock in enumerate(display_stocks):
            col = cols[i % 4]
            if col.button(f"{stock['symbol']} - {stock['name']}", key=f"pop_stock_{i}", use_container_width=True):
                st.session_state.stock_symbol = stock['symbol']
                st.session_state.analysis_requested = True
                st.rerun()