Spaces:
Runtime error
Runtime error
import os | |
import json | |
import requests | |
from bs4 import BeautifulSoup | |
from groq import Groq | |
from datetime import datetime | |
# Get the absolute path of the directory where the script is located | |
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
DATA_DIR = os.path.join(BASE_DIR, 'data') | |
# Ensure the data directory exists | |
os.makedirs(DATA_DIR, exist_ok=True) | |
# Configure Groq AI | |
client = Groq(api_key=os.environ.get("GROQ_API_KEY")) | |
GROQ_MODEL = "llama3-8b-8192" | |
def _get_soup(url): | |
"""Helper function to get BeautifulSoup object from a URL.""" | |
try: | |
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'} | |
response = requests.get(url, headers=headers, timeout=15) | |
response.raise_for_status() | |
return BeautifulSoup(response.content, 'html.parser') | |
except requests.RequestException as e: | |
print(f"Error fetching {url}: {e}") | |
return None | |
def fetch_vietnambiz_data(): | |
""" | |
Fetches various financial data from data.vietnambiz.vn, including: | |
- M2 Money Supply | |
- Central Exchange Rate | |
- Interbank Interest Rate | |
- Savings Interest Rate | |
Saves the data to a JSON file. | |
""" | |
url = "https://data.vietnambiz.vn/currency-interest-rate" | |
print(f"Fetching data from: {url}") | |
soup = _get_soup(url) | |
if not soup: | |
print("Failed to get soup for vietnambiz data.") | |
return None | |
all_data = {} | |
# Helper to parse tables | |
def parse_table(table): | |
if not table: | |
return None | |
headers = [th.text.strip() for th in table.find('thead').find_all('th')] | |
data = [] | |
rows = table.find('tbody').find_all('tr') | |
for row in rows: | |
cols = [td.text.strip() for td in row.find_all('td')] | |
if len(cols) == len(headers): | |
data.append(dict(zip(headers, cols))) | |
return data | |
# Find tables by the preceding h3 tag's text | |
h3_tags = soup.find_all('h3', class_='font-bold') | |
for h3 in h3_tags: | |
table = h3.find_next_sibling('div', class_='table-responsive').find('table') | |
if 'Cung tiền M2' in h3.text: | |
all_data['m2_supply'] = parse_table(table) | |
elif 'Tỷ giá trung tâm' in h3.text: | |
all_data['central_exchange_rate'] = parse_table(table) | |
elif 'Lãi suất liên ngân hàng' in h3.text: | |
all_data['interbank_interest_rate'] = parse_table(table) | |
elif 'Lãi suất huy động' in h3.text: # Savings interest rate | |
all_data['savings_interest_rate'] = parse_table(table) | |
if not all_data: | |
print("Could not find any data tables on the page.") | |
return None | |
output_path = os.path.join(DATA_DIR, 'vietnambiz_data.json') | |
with open(output_path, 'w', encoding='utf-8') as f: | |
json.dump(all_data, f, ensure_ascii=False, indent=4) | |
print(f"Successfully fetched data and saved to {output_path}") | |
return all_data | |
def fetch_usd_index(): | |
""" | |
Fetches the US Dollar Index from Investing.com and saves it to a JSON file. | |
""" | |
url = "https://vn.investing.com/indices/usdollar" | |
print(f"Fetching USD Index from: {url}") | |
soup = _get_soup(url) | |
if not soup: | |
print("Failed to get soup for USD Index.") | |
return None | |
try: | |
# The value is within a specific div with a data-test attribute | |
usd_index_element = soup.find('div', {'data-test': 'instrument-price-last'}) | |
if usd_index_element: | |
usd_index = usd_index_element.text.strip() | |
data = {'usd_index': usd_index, 'last_updated': str(datetime.now())} | |
output_path = os.path.join(DATA_DIR, 'usd_index.json') | |
with open(output_path, 'w', encoding='utf-8') as f: | |
json.dump(data, f, ensure_ascii=False, indent=4) | |
return data | |
else: | |
print("Could not find USD Index element with data-test 'instrument-price-last'.") | |
except Exception as e: | |
print(f"Error parsing USD Index: {e}") | |
return None | |
def fetch_market_news(limit=10): | |
""" | |
Fetches market news from CafeF and saves it to a JSON file. | |
""" | |
url = "https://cafef.vn/thi-truong-chung-khoan.chn" | |
print(f"Fetching market news from: {url}") | |
soup = _get_soup(url) | |
if not soup: | |
print("Failed to get soup for market news.") | |
return [] | |
news_items = [] | |
articles = soup.select('div.tlitem h3 a', limit=limit) | |
if not articles: | |
print("Could not find news articles with selector 'div.tlitem h3 a'.") | |
for article in articles: | |
title = article.text.strip() | |
link = "https://cafef.vn" + article['href'] | |
news_items.append({'title': title, 'link': link, 'source': 'CafeF'}) | |
output_path = os.path.join(DATA_DIR, 'market_news.json') | |
with open(output_path, 'w', encoding='utf-8') as f: | |
json.dump(news_items, f, ensure_ascii=False, indent=4) | |
return news_items | |
def fetch_foreign_trading_data(): | |
""" | |
Fetches foreign net trading data from Vietstock. | |
""" | |
url = "https://finance.vietstock.vn/giao-dich-nha-dau-tu-nuoc-ngoai" | |
print(f"Fetching foreign trading data from: {url}") | |
soup = _get_soup(url) | |
if not soup: | |
print("Failed to get soup for foreign trading data.") | |
return None | |
all_data = {} | |
tables = soup.find_all('table', class_='table') | |
def parse_trading_table(table): | |
if not table: | |
return None | |
headers = [th.text.strip() for th in table.find('thead').find_all('th')] | |
data = [] | |
rows = table.find('tbody').find_all('tr') | |
for row in rows: | |
cols = [td.text.strip() for td in row.find_all('td')] | |
if len(cols) == len(headers): | |
data.append(dict(zip(headers, cols))) | |
return data | |
if len(tables) >= 2: | |
all_data['hose_top_net_buy'] = parse_trading_table(tables[0]) | |
all_data['hose_top_net_sell'] = parse_trading_table(tables[1]) | |
if len(tables) >= 4: | |
all_data['hnx_top_net_buy'] = parse_trading_table(tables[2]) | |
all_data['hnx_top_net_sell'] = parse_trading_table(tables[3]) | |
if len(tables) >= 6: | |
all_data['upcom_top_net_buy'] = parse_trading_table(tables[4]) | |
all_data['upcom_top_net_sell'] = parse_trading_table(tables[5]) | |
if not all_data: | |
print("Could not find any foreign trading data tables on the page.") | |
return None | |
output_path = os.path.join(DATA_DIR, 'foreign_trading_data.json') | |
with open(output_path, 'w', encoding='utf-8') as f: | |
json.dump(all_data, f, ensure_ascii=False, indent=4) | |
print(f"Successfully fetched foreign trading data and saved to {output_path}") | |
return all_data | |
def analyze_market_data(): | |
""" | |
Reads all scraped data, sends it to Groq for analysis, and returns the result. | |
""" | |
# Fetch all data | |
fetch_vietnambiz_data() | |
fetch_usd_index() | |
fetch_market_news() | |
fetch_foreign_trading_data() | |
# Load data from JSON files | |
try: | |
with open(os.path.join(DATA_DIR, 'vietnambiz_data.json'), 'r', encoding='utf-8') as f: | |
vietnambiz_data = json.load(f) | |
except FileNotFoundError: | |
vietnambiz_data = "Không có dữ liệu." | |
try: | |
with open(os.path.join(DATA_DIR, 'usd_index.json'), 'r', encoding='utf-8') as f: | |
usd_data = json.load(f) | |
except FileNotFoundError: | |
usd_data = "Không có dữ liệu." | |
try: | |
with open(os.path.join(DATA_DIR, 'market_news.json'), 'r', encoding='utf-8') as f: | |
news_data = json.load(f) | |
except FileNotFoundError: | |
news_data = "Không có dữ liệu." | |
try: | |
with open(os.path.join(DATA_DIR, 'foreign_trading_data.json'), 'r', encoding='utf-8') as f: | |
foreign_data = json.load(f) | |
except FileNotFoundError: | |
foreign_data = "Không có dữ liệu." | |
prompt = ( | |
"Bạn là một chuyên gia phân tích thị trường tài chính Việt Nam chuyên sâu. " | |
"Dưới đây là dữ liệu thị trường tổng hợp được cập nhật gần đây:\n\n" | |
f"**1. Dữ liệu Vĩ mô từ Vietnambiz:**\n{json.dumps(vietnambiz_data, indent=2, ensure_ascii=False)}\n\n" | |
f"**2. Dữ liệu Chỉ số US Dollar Index (DXY):**\n{json.dumps(usd_data, indent=2, ensure_ascii=False)}\n\n" | |
f"**3. Giao dịch ròng của nhà đầu tư nước ngoài (từ Vietstock):**\n{json.dumps(foreign_data, indent=2, ensure_ascii=False)}\n\n" | |
f"**4. Tin tức thị trường chứng khoán (từ CafeF):**\n{json.dumps(news_data, indent=2, ensure_ascii=False)}\n\n" | |
"**Yêu cầu Phân Tích Chi Tiết:**\n" | |
"Dựa trên toàn bộ dữ liệu vĩ mô và tin tức được cung cấp, hãy thực hiện một bài phân tích chuyên sâu và đa chiều:\n" | |
"a. **Phân tích Dữ liệu Vĩ mô (Cung tiền M2, Tỷ giá, Lãi suất):**\n" | |
" - **Cung tiền M2:** Phân tích xu hướng, tốc độ tăng trưởng và ý nghĩa đối với thanh khoản thị trường. Chính sách tiền tệ của NHNN (nới lỏng/thắt chặt) đang được phản ánh ra sao?\n" | |
" - **Tỷ giá trung tâm:** Xu hướng tỷ giá ảnh hưởng thế nào đến các doanh nghiệp xuất nhập khẩu và nợ vay ngoại tệ?\n" | |
" - **Lãi suất (liên ngân hàng và tiết kiệm):** Phân tích xu hướng lãi suất và tác động của nó đến chi phí vốn của doanh nghiệp, sức hấp dẫn của kênh tiết kiệm so với chứng khoán.\n" | |
" - Mối tương quan giữa các yếu tố này và dự báo tác động tổng hợp đến thị trường chứng khoán.\n\n" | |
"b. **Phân tích Chỉ số US Dollar Index (DXY):**\n" | |
" - Phân tích xu hướng hiện tại của DXY và mối liên hệ với tỷ giá USD/VND.\n" | |
" - Tác động của biến động DXY đến dòng vốn đầu tư nước ngoài (FII) vào Việt Nam.\n\n" | |
"c. **Phân tích Giao dịch của Nhà đầu tư nước ngoài:**\n" | |
" - Nhận xét về xu hướng mua/bán ròng chung trên các sàn (HOSE, HNX, UPCOM).\n" | |
" - Xác định các cổ phiếu đang được khối ngoại mua ròng và bán ròng mạnh nhất.\n" | |
" - Đánh giá tác động của dòng vốn ngoại đến tâm lý thị trường và các nhóm ngành cụ thể.\n\n" | |
"d. **Tổng hợp và Phân tích Tin tức:**\n" | |
" - Xác định các chủ đề, sự kiện nổi bật nhất từ các tin tức.\n" | |
" - Phân loại tin tức theo mức độ ảnh hưởng (tích cực/tiêu cực) đến thị trường chung hoặc các nhóm ngành cụ thể.\n" | |
" - Tin tức nào có khả năng tác động mạnh nhất đến tâm lý nhà đầu tư trong ngắn hạn?\n\n" | |
"e. **Đánh giá và Chiến lược Tổng hợp:**\n" | |
" - Kết hợp tất cả các phân tích trên (Vĩ mô, DXY, Dòng vốn ngoại, Tin tức) để đưa ra một nhận định tổng quan về trạng thái thị trường chứng khoán Việt Nam hiện tại (tích lũy, tăng trưởng, phân phối, hay suy thoái?).\n" | |
" - Đánh giá tâm lý chung của thị trường (lạc quan, bi quan, hay thận trọng).\n" | |
" - Đưa ra các khuyến nghị và lưu ý cụ thể cho nhà đầu tư trong giai đoạn này (ví dụ: nên tập trung vào nhóm ngành nào, quản trị rủi ro ra sao, tỷ trọng cổ phiếu/tiền mặt đề xuất).\n\n" | |
"**Yêu cầu về định dạng:**\n" | |
" - Trình bày bài phân tích một cách logic, có cấu trúc rõ ràng theo từng mục.\n" | |
" - Sử dụng ngôn ngữ chuyên nghiệp, khách quan, và hoàn toàn bằng tiếng Việt." | |
) | |
try: | |
chat_completion = client.chat.completions.create( | |
messages=[{"role": "user", "content": prompt}], | |
model=GROQ_MODEL, | |
) | |
return chat_completion.choices[0].message.content | |
except Exception as e: | |
print(f"Error analyzing market data with Groq: {e}") | |
return "Lỗi khi phân tích dữ liệu với AI. Vui lòng kiểm tra lại API key và kết nối." | |