Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files
app.py
CHANGED
|
@@ -724,26 +724,19 @@ class RiskAnalyzer:
|
|
| 724 |
return covariance / market_variance if market_variance != 0 else 0
|
| 725 |
# ========================== 風險管理模組 END ==========================
|
| 726 |
|
| 727 |
-
def get_stock_data(symbol, period):
|
| 728 |
-
"""
|
| 729 |
-
從 yfinance 獲取股票數據
|
| 730 |
-
|
| 731 |
-
Args:
|
| 732 |
-
symbol: 股票代碼
|
| 733 |
-
period: 數據時間段(例如 '1mo', '3mo', '1y')
|
| 734 |
-
|
| 735 |
-
Returns:
|
| 736 |
-
pd.DataFrame: 股票數據
|
| 737 |
-
"""
|
| 738 |
try:
|
| 739 |
-
|
| 740 |
-
data =
|
| 741 |
-
if data.empty:
|
| 742 |
-
|
| 743 |
-
|
|
|
|
|
|
|
|
|
|
| 744 |
return data
|
| 745 |
-
except
|
| 746 |
-
print(f"獲取 {symbol} 數據時發生錯誤:{e}")
|
| 747 |
return pd.DataFrame()
|
| 748 |
|
| 749 |
def get_us_market_data():
|
|
@@ -812,71 +805,49 @@ def simple_statistical_predict(data, predict_days=5):
|
|
| 812 |
'confidence': max(0.6, 1 - volatility * 2)
|
| 813 |
}
|
| 814 |
|
| 815 |
-
def
|
| 816 |
"""
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
Args:
|
| 820 |
-
stock_data: 來自 yfinance 的股票數據
|
| 821 |
-
dji_data: 道瓊指數數據
|
| 822 |
-
sox_data: 費半指數數據
|
| 823 |
-
news_score: 新聞情緒分數(預設 0.1)
|
| 824 |
-
|
| 825 |
-
Returns:
|
| 826 |
-
pd.DataFrame: 包含特徵的 DataFrame
|
| 827 |
"""
|
| 828 |
-
df
|
| 829 |
-
|
| 830 |
-
# 1. close - 前一日收盤價
|
| 831 |
-
df['close'] = df['Close']
|
| 832 |
|
| 833 |
-
#
|
| 834 |
df['return_t-1'] = df['Close'].pct_change()
|
| 835 |
|
| 836 |
-
#
|
| 837 |
df['return_t-5'] = (df['Close'] / df['Close'].shift(5) - 1)
|
| 838 |
|
| 839 |
-
#
|
| 840 |
df['MA5_close'] = df['Close'].rolling(window=5).mean()
|
| 841 |
|
| 842 |
-
#
|
|
|
|
|
|
|
|
|
|
| 843 |
df['volatility_5d'] = df['return_t-1'].rolling(window=5).std()
|
| 844 |
|
| 845 |
-
# 6. volume_ratio_5d
|
| 846 |
df['volume_5d_avg'] = df['Volume'].rolling(window=5).mean()
|
| 847 |
df['volume_ratio_5d'] = df['Volume'] / df['volume_5d_avg']
|
| 848 |
|
| 849 |
-
# 7.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 850 |
exp1 = df['Close'].ewm(span=12).mean()
|
| 851 |
exp2 = df['Close'].ewm(span=26).mean()
|
| 852 |
macd_line = exp1 - exp2
|
| 853 |
signal_line = macd_line.ewm(span=9).mean()
|
| 854 |
df['MACD_diff'] = macd_line - signal_line
|
| 855 |
|
| 856 |
-
#
|
| 857 |
-
if
|
| 858 |
-
df
|
| 859 |
-
else:
|
| 860 |
-
df['dji_return_t-1'] = 0.0
|
| 861 |
-
|
| 862 |
-
# 9. sox_return_t-1 - 前一日費半指數報酬率
|
| 863 |
-
if sox_data is not None and not sox_data.empty:
|
| 864 |
-
df['sox_return_t-1'] = sox_data['Close'].pct_change()
|
| 865 |
-
else:
|
| 866 |
-
df['sox_return_t-1'] = 0.0
|
| 867 |
-
|
| 868 |
-
# 10. NEWS - 新聞情緒分數
|
| 869 |
-
df['NEWS'] = news_score
|
| 870 |
-
|
| 871 |
-
# 選擇 XGBoostModel 所需的特徵
|
| 872 |
-
feature_columns = [
|
| 873 |
-
'close', 'return_t-1', 'return_t-5', 'MA5_close', 'volatility_5d',
|
| 874 |
-
'volume_ratio_5d', 'MACD_diff', 'dji_return_t-1', 'sox_return_t-1', 'NEWS'
|
| 875 |
-
]
|
| 876 |
-
df = df[feature_columns]
|
| 877 |
-
|
| 878 |
-
# 移除 NaN 值
|
| 879 |
-
df = df.fillna(method='ffill').fillna(0)
|
| 880 |
|
| 881 |
return df
|
| 882 |
|
|
@@ -1091,43 +1062,23 @@ def advanced_xgboost_predict(predict_days=5):
|
|
| 1091 |
traceback.print_exc()
|
| 1092 |
return None
|
| 1093 |
|
| 1094 |
-
def get_prediction(
|
| 1095 |
"""
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
Args:
|
| 1099 |
-
stock_data: 股價歷史資料
|
| 1100 |
-
days: 預測天數 (1, 5, 10, 20)
|
| 1101 |
-
|
| 1102 |
-
Returns:
|
| 1103 |
-
float: 預測的漲幅百分比
|
| 1104 |
"""
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
|
| 1111 |
-
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
| 1116 |
-
|
| 1117 |
-
|
| 1118 |
-
# 初始化 XGBoost 模型
|
| 1119 |
-
model = XGBoostModel()
|
| 1120 |
-
|
| 1121 |
-
# 進行預測
|
| 1122 |
-
predictions = model.predict('xgboost_model', latest_features)
|
| 1123 |
-
if predictions is None:
|
| 1124 |
-
return None
|
| 1125 |
-
|
| 1126 |
-
target_key = f'Change_pct_t{days}_pred'
|
| 1127 |
-
return predictions.get(target_key, 0.0)
|
| 1128 |
-
except Exception as e:
|
| 1129 |
-
print(f"預測失敗:{e}")
|
| 1130 |
-
return None
|
| 1131 |
|
| 1132 |
def calculate_technical_indicators(df):
|
| 1133 |
"""計算技術指標"""
|
|
|
|
| 724 |
return covariance / market_variance if market_variance != 0 else 0
|
| 725 |
# ========================== 風險管理模組 END ==========================
|
| 726 |
|
| 727 |
+
def get_stock_data(symbol, period='1y'):
|
| 728 |
+
"""獲取股票資料"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
try:
|
| 730 |
+
stock = yf.Ticker(symbol)
|
| 731 |
+
data = stock.history(period=period)
|
| 732 |
+
if data.empty and symbol == 'TXF=F':
|
| 733 |
+
stock = yf.Ticker('0050.TW')
|
| 734 |
+
data = stock.history(period=period)
|
| 735 |
+
if data.empty:
|
| 736 |
+
stock = yf.Ticker('^TWII')
|
| 737 |
+
data = stock.history(period=period)
|
| 738 |
return data
|
| 739 |
+
except:
|
|
|
|
| 740 |
return pd.DataFrame()
|
| 741 |
|
| 742 |
def get_us_market_data():
|
|
|
|
| 805 |
'confidence': max(0.6, 1 - volatility * 2)
|
| 806 |
}
|
| 807 |
|
| 808 |
+
def calculate_new_features(df):
|
| 809 |
"""
|
| 810 |
+
計算新的技術指標特徵 - 針對新特徵需求
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 811 |
"""
|
| 812 |
+
if df.empty:
|
| 813 |
+
return df
|
|
|
|
|
|
|
| 814 |
|
| 815 |
+
# 1. return_t-1 – 前一日報酬率
|
| 816 |
df['return_t-1'] = df['Close'].pct_change()
|
| 817 |
|
| 818 |
+
# 2. return_t-5 – 過去 5 日累積報酬率
|
| 819 |
df['return_t-5'] = (df['Close'] / df['Close'].shift(5) - 1)
|
| 820 |
|
| 821 |
+
# 3. MA5_close – 5 日移動平均價
|
| 822 |
df['MA5_close'] = df['Close'].rolling(window=5).mean()
|
| 823 |
|
| 824 |
+
# 4. MA20_close – 20 日移動平均價
|
| 825 |
+
df['MA20_close'] = df['Close'].rolling(window=20).mean()
|
| 826 |
+
|
| 827 |
+
# 5. volatility_5d – 5 日報酬標準差(短期波動)
|
| 828 |
df['volatility_5d'] = df['return_t-1'].rolling(window=5).std()
|
| 829 |
|
| 830 |
+
# 6. volume_ratio_5d – 今日成交量 ÷ 5 日均量
|
| 831 |
df['volume_5d_avg'] = df['Volume'].rolling(window=5).mean()
|
| 832 |
df['volume_ratio_5d'] = df['Volume'] / df['volume_5d_avg']
|
| 833 |
|
| 834 |
+
# 7. RSI_14 – 14 日 RSI 指標
|
| 835 |
+
delta = df['Close'].diff()
|
| 836 |
+
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
|
| 837 |
+
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
|
| 838 |
+
rs = gain / loss
|
| 839 |
+
df['RSI_14'] = 100 - (100 / (1 + rs))
|
| 840 |
+
|
| 841 |
+
# 8. MACD_diff – MACD - signal(趨勢強弱)
|
| 842 |
exp1 = df['Close'].ewm(span=12).mean()
|
| 843 |
exp2 = df['Close'].ewm(span=26).mean()
|
| 844 |
macd_line = exp1 - exp2
|
| 845 |
signal_line = macd_line.ewm(span=9).mean()
|
| 846 |
df['MACD_diff'] = macd_line - signal_line
|
| 847 |
|
| 848 |
+
# 移除輔助欄位
|
| 849 |
+
if 'volume_5d_avg' in df.columns:
|
| 850 |
+
df = df.drop('volume_5d_avg', axis=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 851 |
|
| 852 |
return df
|
| 853 |
|
|
|
|
| 1062 |
traceback.print_exc()
|
| 1063 |
return None
|
| 1064 |
|
| 1065 |
+
def get_prediction(data, predict_days=5):
|
| 1066 |
"""
|
| 1067 |
+
【【模型預測控制器】】
|
| 1068 |
+
根據 USE_ADVANCED_MODEL 的設定,呼叫對應的預測模型。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1069 |
"""
|
| 1070 |
+
if USE_ADVANCED_MODEL:
|
| 1071 |
+
print(f"模式: 進階XGBoost模型 | 預測天期: {predict_days}天")
|
| 1072 |
+
prediction = advanced_xgboost_predict(predict_days)
|
| 1073 |
+
# 如果進階模型預測失敗,則自動降級使用簡易模型
|
| 1074 |
+
if prediction is not None:
|
| 1075 |
+
return prediction
|
| 1076 |
+
else:
|
| 1077 |
+
print("進階模型預測失敗,自動降級為簡易統計模型。")
|
| 1078 |
+
|
| 1079 |
+
# 預設或降級時執行簡易模型
|
| 1080 |
+
print(f"模式: 簡易統計模型 | 預測天期: {predict_days}天")
|
| 1081 |
+
return simple_statistical_predict(data, predict_days)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1082 |
|
| 1083 |
def calculate_technical_indicators(df):
|
| 1084 |
"""計算技術指標"""
|