Spaces:
Running
Running
Commit
·
4593632
1
Parent(s):
0255349
o3
Browse files
app.py
CHANGED
@@ -13,6 +13,7 @@ from ta.momentum import RSIIndicator, StochasticOscillator
|
|
13 |
from ta.trend import MACD
|
14 |
from ta.volume import volume_weighted_average_price
|
15 |
|
|
|
16 |
import dotenv
|
17 |
dotenv.load_dotenv()
|
18 |
|
@@ -75,6 +76,7 @@ You have access to the following tools:
|
|
75 |
- Output should be structured, easy to read, and in Traditional Chinese.
|
76 |
"""
|
77 |
|
|
|
78 |
def period_to_start_end(period_str: str):
|
79 |
"""
|
80 |
根據使用者選擇的時間區間,計算起始與結束日期。
|
@@ -91,15 +93,13 @@ def period_to_start_end(period_str: str):
|
|
91 |
start = now - dt.timedelta(weeks=13)
|
92 |
return start, now
|
93 |
|
|
|
94 |
def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
|
95 |
"""Fetches historical stock price data and technical indicators for a given ticker."""
|
96 |
try:
|
97 |
start, end = period_to_start_end(period)
|
98 |
-
|
99 |
-
# --- 除錯訊息開始 ---
|
100 |
print(f"[get_stock_prices] Downloading data for ticker={ticker}, period={period}")
|
101 |
print(f"[get_stock_prices] Start={start}, End={end}")
|
102 |
-
# --- 除錯訊息結束 ---
|
103 |
|
104 |
data = yf.download(
|
105 |
ticker,
|
@@ -108,24 +108,27 @@ def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
|
|
108 |
interval='1d'
|
109 |
)
|
110 |
|
111 |
-
# --- 除錯訊息:看資料長度與前幾筆 ---
|
112 |
print(f"[get_stock_prices] Fetched data shape={data.shape}")
|
113 |
if not data.empty:
|
114 |
print(f"[get_stock_prices] Head of data:\n{data.head()}")
|
115 |
else:
|
116 |
print("[get_stock_prices] No data returned from yfinance.")
|
117 |
-
# ---
|
118 |
|
119 |
if data.empty:
|
120 |
return {"error": f"No stock data found for {ticker}"}
|
121 |
|
122 |
-
#
|
123 |
if data.columns.nlevels > 1:
|
124 |
data.columns = [col[0] for col in data.columns]
|
125 |
|
126 |
-
|
127 |
-
data
|
128 |
|
|
|
|
|
|
|
|
|
|
|
129 |
df = data.copy()
|
130 |
|
131 |
# 指標只取最後 12 筆即可
|
@@ -136,24 +139,33 @@ def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
|
|
136 |
# RSI
|
137 |
rsi_series = RSIIndicator(df['Close'], window=14).rsi().iloc[-n:]
|
138 |
indicators["RSI"] = {
|
139 |
-
str(date.date()): float(value)
|
|
|
140 |
}
|
141 |
|
142 |
# Stochastic
|
143 |
-
sto_series = StochasticOscillator(
|
|
|
|
|
|
|
|
|
|
|
144 |
indicators["Stochastic_Oscillator"] = {
|
145 |
-
str(date.date()): float(value)
|
|
|
146 |
}
|
147 |
|
148 |
# MACD
|
149 |
macd = MACD(df['Close'])
|
150 |
macd_series = macd.macd().iloc[-n:]
|
151 |
indicators["MACD"] = {
|
152 |
-
str(date.date()): float(value)
|
|
|
153 |
}
|
154 |
macd_signal_series = macd.macd_signal().iloc[-n:]
|
155 |
indicators["MACD_Signal"] = {
|
156 |
-
str(date.date()): float(value)
|
|
|
157 |
}
|
158 |
|
159 |
# VWAP
|
@@ -164,17 +176,20 @@ def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
|
|
164 |
volume=df['Volume']
|
165 |
).iloc[-n:]
|
166 |
indicators["vwap"] = {
|
167 |
-
str(date.date()): float(value)
|
|
|
168 |
}
|
169 |
|
170 |
return {
|
171 |
-
"stock_price":
|
172 |
"indicators": indicators
|
173 |
}
|
|
|
174 |
except Exception as e:
|
175 |
print(f"[get_stock_prices] Exception: {str(e)}")
|
176 |
return f"Error fetching price data: {str(e)}"
|
177 |
|
|
|
178 |
def get_financial_news(ticker: str) -> Union[Dict, str]:
|
179 |
"""Fetches the latest financial news related to a given ticker."""
|
180 |
try:
|
@@ -202,13 +217,15 @@ def get_financial_news(ticker: str) -> Union[Dict, str]:
|
|
202 |
print(f"[get_financial_news] Exception: {str(e)}")
|
203 |
return f"Error fetching news: {str(e)}"
|
204 |
|
|
|
205 |
def get_financial_metrics(ticker: str) -> Union[Dict, str]:
|
206 |
"""Fetches key financial ratios for a given ticker."""
|
207 |
try:
|
208 |
print(f"[get_financial_metrics] Fetching financial metrics for ticker={ticker}")
|
209 |
stock = yf.Ticker(ticker)
|
210 |
info = stock.info
|
211 |
-
|
|
|
212 |
print(f"[get_financial_metrics] Info returned:\n{json.dumps(info, indent=2)}")
|
213 |
|
214 |
return {
|
@@ -221,6 +238,7 @@ def get_financial_metrics(ticker: str) -> Union[Dict, str]:
|
|
221 |
print(f"[get_financial_metrics] Exception: {str(e)}")
|
222 |
return f"Error fetching ratios: {str(e)}"
|
223 |
|
|
|
224 |
def analyze_stock(api_key: str, ticker: str, period: str) -> str:
|
225 |
"""
|
226 |
根據輸入的 LLM API key、股票代號與時間區間,抓取各項資料後,
|
@@ -257,7 +275,6 @@ def analyze_stock(api_key: str, ticker: str, period: str) -> str:
|
|
257 |
{prompt}
|
258 |
"""
|
259 |
|
260 |
-
# 呼叫 LLM 生成最終分析報告
|
261 |
print("[analyze_stock] Sending prompt to LLM...")
|
262 |
response = llm.invoke(analysis_prompt)
|
263 |
|
@@ -267,6 +284,7 @@ def analyze_stock(api_key: str, ticker: str, period: str) -> str:
|
|
267 |
print(f"[analyze_stock] Exception: {str(e)}")
|
268 |
return f"分析過程中發生錯誤: {str(e)}\n{traceback.format_exc()}"
|
269 |
|
|
|
270 |
# --- Gradio 介面 ---
|
271 |
iface = gr.Interface(
|
272 |
fn=analyze_stock,
|
|
|
13 |
from ta.trend import MACD
|
14 |
from ta.volume import volume_weighted_average_price
|
15 |
|
16 |
+
import pandas as pd
|
17 |
import dotenv
|
18 |
dotenv.load_dotenv()
|
19 |
|
|
|
76 |
- Output should be structured, easy to read, and in Traditional Chinese.
|
77 |
"""
|
78 |
|
79 |
+
|
80 |
def period_to_start_end(period_str: str):
|
81 |
"""
|
82 |
根據使用者選擇的時間區間,計算起始與結束日期。
|
|
|
93 |
start = now - dt.timedelta(weeks=13)
|
94 |
return start, now
|
95 |
|
96 |
+
|
97 |
def get_stock_prices(ticker: str, period: str = "3mo") -> Union[Dict, str]:
|
98 |
"""Fetches historical stock price data and technical indicators for a given ticker."""
|
99 |
try:
|
100 |
start, end = period_to_start_end(period)
|
|
|
|
|
101 |
print(f"[get_stock_prices] Downloading data for ticker={ticker}, period={period}")
|
102 |
print(f"[get_stock_prices] Start={start}, End={end}")
|
|
|
103 |
|
104 |
data = yf.download(
|
105 |
ticker,
|
|
|
108 |
interval='1d'
|
109 |
)
|
110 |
|
|
|
111 |
print(f"[get_stock_prices] Fetched data shape={data.shape}")
|
112 |
if not data.empty:
|
113 |
print(f"[get_stock_prices] Head of data:\n{data.head()}")
|
114 |
else:
|
115 |
print("[get_stock_prices] No data returned from yfinance.")
|
|
|
116 |
|
117 |
if data.empty:
|
118 |
return {"error": f"No stock data found for {ticker}"}
|
119 |
|
120 |
+
# 如果是多層欄位,就平坦化
|
121 |
if data.columns.nlevels > 1:
|
122 |
data.columns = [col[0] for col in data.columns]
|
123 |
|
124 |
+
# 設定日期為索引 (DatetimeIndex)
|
125 |
+
data.index = pd.to_datetime(data.index)
|
126 |
|
127 |
+
# 保留一份副本來輸出給 LLM 用
|
128 |
+
df_for_output = data.copy().reset_index()
|
129 |
+
df_for_output["Date"] = df_for_output["Date"].astype(str)
|
130 |
+
|
131 |
+
# 另存一份給技術指標用 (確保是 DatetimeIndex)
|
132 |
df = data.copy()
|
133 |
|
134 |
# 指標只取最後 12 筆即可
|
|
|
139 |
# RSI
|
140 |
rsi_series = RSIIndicator(df['Close'], window=14).rsi().iloc[-n:]
|
141 |
indicators["RSI"] = {
|
142 |
+
str(date.date()): float(value)
|
143 |
+
for date, value in rsi_series.dropna().items()
|
144 |
}
|
145 |
|
146 |
# Stochastic
|
147 |
+
sto_series = StochasticOscillator(
|
148 |
+
df['High'],
|
149 |
+
df['Low'],
|
150 |
+
df['Close'],
|
151 |
+
window=14
|
152 |
+
).stoch().iloc[-n:]
|
153 |
indicators["Stochastic_Oscillator"] = {
|
154 |
+
str(date.date()): float(value)
|
155 |
+
for date, value in sto_series.dropna().items()
|
156 |
}
|
157 |
|
158 |
# MACD
|
159 |
macd = MACD(df['Close'])
|
160 |
macd_series = macd.macd().iloc[-n:]
|
161 |
indicators["MACD"] = {
|
162 |
+
str(date.date()): float(value)
|
163 |
+
for date, value in macd_series.dropna().items()
|
164 |
}
|
165 |
macd_signal_series = macd.macd_signal().iloc[-n:]
|
166 |
indicators["MACD_Signal"] = {
|
167 |
+
str(date.date()): float(value)
|
168 |
+
for date, value in macd_signal_series.dropna().items()
|
169 |
}
|
170 |
|
171 |
# VWAP
|
|
|
176 |
volume=df['Volume']
|
177 |
).iloc[-n:]
|
178 |
indicators["vwap"] = {
|
179 |
+
str(date.date()): float(value)
|
180 |
+
for date, value in vwap_series.dropna().items()
|
181 |
}
|
182 |
|
183 |
return {
|
184 |
+
"stock_price": df_for_output.to_dict(orient="records"),
|
185 |
"indicators": indicators
|
186 |
}
|
187 |
+
|
188 |
except Exception as e:
|
189 |
print(f"[get_stock_prices] Exception: {str(e)}")
|
190 |
return f"Error fetching price data: {str(e)}"
|
191 |
|
192 |
+
|
193 |
def get_financial_news(ticker: str) -> Union[Dict, str]:
|
194 |
"""Fetches the latest financial news related to a given ticker."""
|
195 |
try:
|
|
|
217 |
print(f"[get_financial_news] Exception: {str(e)}")
|
218 |
return f"Error fetching news: {str(e)}"
|
219 |
|
220 |
+
|
221 |
def get_financial_metrics(ticker: str) -> Union[Dict, str]:
|
222 |
"""Fetches key financial ratios for a given ticker."""
|
223 |
try:
|
224 |
print(f"[get_financial_metrics] Fetching financial metrics for ticker={ticker}")
|
225 |
stock = yf.Ticker(ticker)
|
226 |
info = stock.info
|
227 |
+
|
228 |
+
# 顯示完整 info 以便除錯
|
229 |
print(f"[get_financial_metrics] Info returned:\n{json.dumps(info, indent=2)}")
|
230 |
|
231 |
return {
|
|
|
238 |
print(f"[get_financial_metrics] Exception: {str(e)}")
|
239 |
return f"Error fetching ratios: {str(e)}"
|
240 |
|
241 |
+
|
242 |
def analyze_stock(api_key: str, ticker: str, period: str) -> str:
|
243 |
"""
|
244 |
根據輸入的 LLM API key、股票代號與時間區間,抓取各項資料後,
|
|
|
275 |
{prompt}
|
276 |
"""
|
277 |
|
|
|
278 |
print("[analyze_stock] Sending prompt to LLM...")
|
279 |
response = llm.invoke(analysis_prompt)
|
280 |
|
|
|
284 |
print(f"[analyze_stock] Exception: {str(e)}")
|
285 |
return f"分析過程中發生錯誤: {str(e)}\n{traceback.format_exc()}"
|
286 |
|
287 |
+
|
288 |
# --- Gradio 介面 ---
|
289 |
iface = gr.Interface(
|
290 |
fn=analyze_stock,
|