| import yfinance as yf |
| import pandas as pd |
| import numpy as np |
| import matplotlib.pyplot as plt |
| import gradio as gr |
| import time |
|
|
| |
| try: |
| options_dates = stock.options |
| except: |
| time.sleep(5) |
| options_dates = stock.options |
|
|
| |
| def calculate_true_range_half(ticker_symbol): |
| stock = yf.Ticker(ticker_symbol) |
| data = stock.history(period="7d", interval="1d") |
| |
| if data.empty or len(data) < 5: |
| return None, None |
| |
| |
| data["True Range"] = data["High"] - data["Low"] |
| |
| |
| total_tr_5 = data["True Range"].tail(5).sum() |
| |
| |
| atr_5 = total_tr_5 / 5 |
| |
| |
| atr_5_half = atr_5 / 2 |
|
|
| return atr_5, atr_5_half |
|
|
| |
| def expected_move_analysis(ticker_symbol): |
| try: |
| |
| stock = yf.Ticker(ticker_symbol) |
| current_price = stock.fast_info["last_price"] |
| options_dates = stock.options |
|
|
| if not options_dates: |
| return f"❌ Không có dữ liệu quyền chọn cho {ticker_symbol}.", None |
|
|
| nearest_expiry = options_dates[0] |
| options_chain = stock.option_chain(nearest_expiry) |
| calls = options_chain.calls |
| puts = options_chain.puts |
|
|
| if calls.empty or puts.empty: |
| return f"❌ Không có dữ liệu quyền chọn tại ngày hết hạn {nearest_expiry}.", None |
|
|
| |
| atm_strike = min(calls["strike"], key=lambda x: abs(x - current_price)) |
|
|
| |
| atm_call_price = calls[calls["strike"] == atm_strike]["lastPrice"].values[0] |
| atm_put_price = puts[puts["strike"] == atm_strike]["lastPrice"].values[0] |
|
|
| |
| expected_move = (atm_call_price + atm_put_price) * 0.85 |
| upper_price = current_price + expected_move |
| lower_price = current_price - expected_move |
|
|
| |
| atm_call_iv = calls[calls["strike"] == atm_strike]["impliedVolatility"].values[0] |
| atm_put_iv = puts[puts["strike"] == atm_strike]["impliedVolatility"].values[0] |
| iv_skew = atm_call_iv - atm_put_iv |
|
|
| |
| total_call_volume = calls["volume"].sum() |
| total_put_volume = puts["volume"].sum() |
| pcr = total_put_volume / total_call_volume if total_call_volume > 0 else None |
| |
| |
| prob_up, prob_down = 50, 50 |
| if pcr is not None: |
| if pcr < 0.7: |
| prob_up += 20 |
| prob_down -= 20 |
| elif pcr > 1.0: |
| prob_up -= 20 |
| prob_down += 20 |
|
|
| if iv_skew > 0: |
| prob_up += 10 |
| prob_down -= 10 |
| else: |
| prob_up -= 10 |
| prob_down += 10 |
|
|
| prob_up = max(0, min(100, prob_up)) |
| prob_down = max(0, min(100, prob_down)) |
|
|
| |
| atr_5, atr_5_half = calculate_true_range_half(ticker_symbol) |
|
|
| |
| upper_atr5_2 = current_price + atr_5_half |
| lower_atr5_2 = current_price - atr_5_half |
|
|
| |
| fig, ax = plt.subplots(figsize=(8, 5)) |
| ax.plot(["Lower Bound", "Current Price", "Upper Bound"], |
| [lower_price, current_price, upper_price], 'ro-', label="Expected Move") |
| ax.axhline(y=current_price, color='g', linestyle='--', label="Current Price") |
|
|
| |
| if atr_5: |
| ax.axhline(y=current_price + atr_5, color='b', linestyle='-.', label="ATR 5 - Upper") |
| ax.axhline(y=current_price - atr_5, color='b', linestyle='-.', label="ATR 5 - Lower") |
| ax.axhline(y=upper_atr5_2, color='purple', linestyle='-.', label="ATR 5/2 - Upper") |
| ax.axhline(y=lower_atr5_2, color='purple', linestyle='-.', label="ATR 5/2 - Lower") |
|
|
| ax.set_title(f"Expected Move vs True Range for {ticker_symbol}") |
| ax.set_ylabel("Price") |
| ax.legend() |
| ax.grid() |
|
|
| |
| result_text = f""" |
| 📊 **Dự đoán giá cho {ticker_symbol}** |
| - **Giá hiện tại**: {current_price:.2f} USD |
| - **Biên độ**: {expected_move:.2f} USD |
| - **Giá dự đoán cao nhất**: {upper_price:.2f} USD |
| - **Giá dự đoán thấp nhất**: {lower_price:.2f} USD |
| - **ATR 5/2**: {atr_5_half:.2f} USD |
| - **(+ ATR 5/2)**: {upper_atr5_2:.2f} USD |
| - **(- ATR 5/2)**: {lower_atr5_2:.2f} USD |
| |
| 🔥 **Put/Call Ratio (PCR)**: {pcr:.2f} |
| - **IV Call ATM**: {atm_call_iv:.2%} |
| - **IV Put ATM**: {atm_put_iv:.2%} |
| - **IV Skew**: {iv_skew:.2%} |
| |
| 📈 **Xác suất giá tăng phiên kế**: {prob_up}% |
| 📉 **Xác suất giá giảm phiên kế**: {prob_down}% |
| """ |
|
|
| return result_text, fig |
|
|
| except Exception as e: |
| return f"❌ Lỗi: {str(e)}", None |
|
|
|
|
| |
| def compare_sector_performance(symbols_input, period): |
| sector_to_etf = { |
| 'Technology': 'XLK', |
| 'Health Care': 'XLV', |
| 'Financial Services': 'XLF', |
| 'Consumer Cyclical': 'XLY', |
| 'Energy': 'XLE', |
| 'Industrials': 'XLI', |
| 'Consumer Defensive': 'XLP', |
| 'Utilities': 'XLU', |
| 'Materials': 'XLB', |
| 'Real Estate': 'XLRE', |
| 'Communication Services': 'XLC' |
| } |
|
|
| def get_sector_etf(symbol): |
| try: |
| ticker = yf.Ticker(symbol) |
| info = ticker.info |
| sector = info.get("sector", "Không rõ") |
| etf = sector_to_etf.get(sector, "Không rõ") |
| return sector, etf |
| except Exception as e: |
| print(f"Lỗi khi lấy sector: {e}") |
| return "Không rõ", "Không rõ" |
|
|
| if not symbols_input: |
| return pd.DataFrame([{"Symbol": "N/A", "Sector ETF": "N/A", "Kết luận": "Vui lòng nhập mã cổ phiếu"}]) |
|
|
| symbols = [s.strip().upper() for s in symbols_input.split(",")] |
| results = [] |
|
|
| for symbol in symbols: |
| sector, etf = get_sector_etf(symbol) |
| if etf == "Không rõ": |
| results.append({"Symbol": symbol, "Sector ETF": "Không rõ", "Kết luận": "Không phân tích được"}) |
| continue |
|
|
| try: |
| stock_data = yf.download(symbol, period=period)['Close'] |
| etf_data = yf.download(etf, period=period)['Close'] |
|
|
| if stock_data.empty or etf_data.empty or len(stock_data) < 2 or len(etf_data) < 2: |
| results.append({"Symbol": symbol, "Sector ETF": etf, "Kết luận": "Dữ liệu không đủ"}) |
| continue |
|
|
| pct_stock = float((stock_data.iloc[-1] - stock_data.iloc[0]) / stock_data.iloc[0] * 100) |
| pct_sector = float((etf_data.iloc[-1] - etf_data.iloc[0]) / etf_data.iloc[0] * 100) |
| verdict = "Outperform" if pct_stock > pct_sector else "Underperform" |
|
|
| results.append({ |
| "Symbol": symbol, |
| "Sector ETF": etf, |
| "% Stock": f"{pct_stock:.2f}%", |
| "% Sector": f"{pct_sector:.2f}%", |
| "Kết luận": verdict |
| }) |
| except Exception as e: |
| results.append({"Symbol": symbol, "Sector ETF": etf, "Kết luận": f"Lỗi: {str(e)}"}) |
|
|
| return pd.DataFrame(results) |
|
|
| |
| import gradio as gr |
|
|
| |
| custom_css = """ |
| body { |
| background-color: #0f0f0f !important; |
| } |
| .gr-textbox, .gr-dropdown, .gr-button, .gr-dataframe, .gr-plot, .gr-markdown, .gr-tabs { |
| background-color: #1f1f1f !important; |
| color: #ffffff !important; |
| border: 1.5px solid #ff7f00 !important; |
| border-radius: 6px !important; |
| } |
| .gr-button { |
| background-color: #ff7f00 !important; |
| color: #000 !important; |
| font-weight: bold; |
| } |
| .gr-button:hover { |
| background-color: #e66e00 !important; |
| } |
| """ |
|
|
| with gr.Blocks(css=custom_css, title="Dashboard Phân Tích") as app: |
|
|
| with gr.Tabs(): |
| |
| with gr.Tab("Expected Move"): |
| gr.Markdown("### Dự đoán biến động (Expected Move)") |
| with gr.Row(): |
| ticker_input = gr.Textbox(label="Nhập mã cổ phiếu", value="NVDA") |
| expected_button = gr.Button("Phân Tích") |
| expected_output = gr.Textbox(label="Kết quả", lines=10) |
| expected_plot = gr.Plot(label="Biểu đồ") |
| expected_button.click( |
| fn=expected_move_analysis, |
| inputs=ticker_input, |
| outputs=[expected_output, expected_plot] |
| ) |
|
|
| |
| with gr.Tab("Sector Comparison"): |
| gr.Markdown("### So sánh hiệu suất giữa các cổ phiếu hoặc ngành") |
| symbols_input = gr.Textbox(label="Nhập mã cổ phiếu (cách nhau bằng dấu phẩy)", value="AAPL,TSLA") |
| period = gr.Dropdown( |
| label="Chọn khoảng thời gian", |
| choices=['5d', '1mo', '3mo', '6mo'], |
| value='1mo' |
| ) |
| sector_button = gr.Button("Phân Tích") |
| sector_output = gr.Dataframe(label="Kết quả") |
| sector_button.click( |
| fn=compare_sector_performance, |
| inputs=[symbols_input, period], |
| outputs=sector_output |
| ) |
|
|
| app.launch(share=True) |