|
import yfinance as yf |
|
import pandas as pd |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import gradio as gr |
|
|
|
|
|
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 |
|
|
|
|
|
with gr.Blocks(theme=gr.themes.Base(primary_hue="blue")) as app: |
|
gr.Markdown(f"📊 **NHAT TRAN - DỰ ĐOÁN GIÁ CỔ PHIẾU**") |
|
|
|
|
|
gr.Markdown(""" |
|
|
|
🔷 **Cách sử dụng:** |
|
|
|
1️⃣ Nhập **mã cổ phiếu** vào ô bên dưới. |
|
|
|
2️⃣ Nhấn **"Phân Tích"** để xem kết quả. |
|
|
|
🔆 Nếu bạn tra cứu tại thời điểm **Open Market** giá sẽ dự đoán trong phiên. |
|
|
|
🔆 Nếu bạn tra cứu tại thời điểm **After Market** giá sẽ dự đoán cho phiên kế tiếp. |
|
|
|
|
|
⚠️ **Lưu ý:** |
|
|
|
❗️Công cụ này chỉ mang tính chất tham khảo, |
|
thông số đôi lúc sẽ khác biệt so với thực tế. |
|
Anh chị em cân nhắc trước khi dùng để giao dịch. |
|
|
|
❗️ Công cụ này chỉ áp dụng cho cổ phiếu có **quyền chọn**, |
|
nếu cổ phiếu không có dữ liệu quyền chọn, **kết quả** sẽ không hiển thị.""") |
|
|
|
stock_input = gr.Textbox(label="Nhập mã cổ phiếu", value="NVDA") |
|
run_button = gr.Button("Phân Tích") |
|
|
|
output_text = gr.Textbox(label="Kết quả") |
|
output_plot = gr.Plot() |
|
|
|
|
|
|
|
run_button.click(expected_move_analysis, inputs=stock_input, outputs=[output_text, output_plot]) |
|
|
|
|
|
app.launch(share=True) |