File size: 10,409 Bytes
933e34e e9f7a8e 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e da58fa7 933e34e d3991b4 0b86578 7a17d26 f6a63fd 366eb47 7a17d26 366eb47 7a17d26 f6a63fd 7a17d26 f6a63fd 7a17d26 366eb47 a68b264 2c04fa0 a68b264 7a17d26 d3991b4 7a17d26 7d93a61 d3991b4 2c04fa0 f6a63fd 7a17d26 f6a63fd d3991b4 7a17d26 d3991b4 f6a63fd 7a17d26 f6a63fd 7a17d26 f6a63fd 3d661dc |
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 |
import yfinance as yf
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import gradio as gr
import time # Thêm dòng này ở đầu file nếu chưa có
# Thử lại nếu bị lỗi rate limit
try:
options_dates = stock.options
except:
time.sleep(5)
options_dates = stock.options
# 📌 Hàm tính True Range trung bình 5 ngày gần nhất và chia 2
def calculate_true_range_half(ticker_symbol):
stock = yf.Ticker(ticker_symbol)
data = stock.history(period="7d", interval="1d") # Lấy dữ liệu 7 ngày để đảm bảo có đủ 5 ngày giao dịch
if data.empty or len(data) < 5:
return None, None # Trả về None nếu không đủ dữ liệu
# 📌 Tính True Range của mỗi ngày
data["True Range"] = data["High"] - data["Low"]
# 📌 Tổng True Range của 5 ngày
total_tr_5 = data["True Range"].tail(5).sum()
# 📌 Tính ATR 5 ngày (TRUNG BÌNH của Tổng TR 5 ngày)
atr_5 = total_tr_5 / 5
# 📌 Chia đôi ATR 5
atr_5_half = atr_5 / 2
return atr_5, atr_5_half
# 📌 Hàm phân tích Expected Move và so sánh với True Range
def expected_move_analysis(ticker_symbol):
try:
# 🟢 Lấy dữ liệu cổ phiếu
stock = yf.Ticker(ticker_symbol)
current_price = stock.fast_info["last_price"] # Giá mới nhất từ phiên giao dịch chính + ngoài giờ
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] # Ngày hết hạn gần nhất
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
# 🟢 Xác định giá thực hiện ATM gần nhất
atm_strike = min(calls["strike"], key=lambda x: abs(x - current_price))
# 🟢 Lấy giá quyền chọn CALL và PUT tại ATM Strike
atm_call_price = calls[calls["strike"] == atm_strike]["lastPrice"].values[0]
atm_put_price = puts[puts["strike"] == atm_strike]["lastPrice"].values[0]
# 🟢 Tính Expected Move
expected_move = (atm_call_price + atm_put_price) * 0.85
upper_price = current_price + expected_move
lower_price = current_price - expected_move
# 🟢 Lấy IV của quyền chọn ATM
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 # Chênh lệch IV
# 🟢 Tính Put/Call Ratio (PCR)
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
# 🟢 Dự đoán xác suất giá tăng / giảm
prob_up, prob_down = 50, 50 # Mặc định 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))
# 📌 Tính True Range trung bình 5 ngày gần nhất và chia 2
atr_5, atr_5_half = calculate_true_range_half(ticker_symbol)
# 📌 Tính (Giá hiện tại + ATR 5/2) và (Giá hiện tại - ATR 5/2)
upper_atr5_2 = current_price + atr_5_half
lower_atr5_2 = current_price - atr_5_half
# 📈 Vẽ biểu đồ Expected Move & True Range
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")
# 🟢 Vẽ đường ATR 5 ngày và ATR 5/2
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()
# 📊 Kết quả hiển thị
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
# Hàm so sánh Outperform/Underperform theo Sector
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)
# Giao diện Gradio
import gradio as gr
# Custom CSS để làm nền đen, text trắng, border cam
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():
# Tab 1: Expected Move
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]
)
# Tab 2: So sánh Sector
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) |