Spaces:
Sleeping
Sleeping
File size: 7,342 Bytes
3e48f76 | 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 | import datetime
import yfinance as yf
import pandas as pd
import logging
from yfinance.search import Search
import numpy as np
class SP500Sector:
MATERIALS = "Materials"
COMMUNICATION_SERVICES = "Communication Services"
CONSUMER_DISCRETIONARY = "Consumer Discretionary"
CONSUMER_STAPLES = "Consumer Staples"
ENERGY = "Energy"
FINANCIALS = "Financials"
HEALTH_CARE = "Health Care"
INDUSTRIALS = "Industrials"
OTHER = "Taiwan Other"
REAL_ESTATE = "Real Estate"
INFORMATION_TECHNOLOGY = "Information Technology"
UTILITIES = "Utilities"
@classmethod
def get_all_sectors(cls):
return sorted([v for k, v in vars(cls).items() if k.isupper() and isinstance(v, str)])
@classmethod
def get_sector_id(cls, sector_str):
sectors = cls.get_all_sectors()
if sector_str in sectors:
return sectors.index(sector_str)
return sectors.index(cls.OTHER)
def isMember(self, sector_str):
return sector_str in self.get_all_sectors()
def fromYfinanceSector(self, sector_str):
if sector_str == "Basic Materials":
return self.MATERIALS
elif sector_str == "Communication Services":
return self.COMMUNICATION_SERVICES
elif sector_str == "Consumer Cyclical":
return self.CONSUMER_DISCRETIONARY
elif sector_str == "Consumer Defensive":
return self.CONSUMER_STAPLES
elif sector_str == "Energy":
return self.ENERGY
elif sector_str == "Financial Services":
return self.FINANCIALS
elif sector_str == "Healthcare":
return self.HEALTH_CARE
elif sector_str == "Industrials":
return self.INDUSTRIALS
elif sector_str == "Real Estate":
return self.REAL_ESTATE
elif sector_str == "Technology":
return self.INFORMATION_TECHNOLOGY
elif sector_str == "Utilities":
return self.UTILITIES
else:
return self.OTHER
def get_yfinance_ticker_info(code) -> dict:
"""
Get ticker information using yfinance search method, which is faster than downloading history.
Returns a dictionary with keys
"""
if not hasattr(get_yfinance_ticker_info, "call_counter"):
get_yfinance_ticker_info.call_counter = 0
get_yfinance_ticker_info.call_counter += 1
# Suppress yfinance logging to avoid cluttering output
logger = logging.getLogger('yfinance')
original_level = logger.level
logger.setLevel(logging.CRITICAL)
try:
call_count = get_yfinance_ticker_info.call_counter
results = Search(code, max_results=10).quotes
if call_count == 1:
print(results[0])
print(f"{get_yfinance_ticker_info.call_counter}. Searched results for '{code}': {[x.get('symbol') for x in results]}")
for r in results:
if r.get("symbol") == code:
return {k: r.get(k, '') for k in ["exchange", "symbol", "longname", "sector"]}
for r in results:
if r.get("symbol", "").startswith(code):
return {k: r.get(k, '') for k in ["exchange", "symbol", "longname", "sector"]}
if results:
r = results[0]
return {k: r.get(k, '') for k in ["exchange", "symbol", "longname", "sector"]}
return None
except Exception as e:
print(f"Warning: Search method failed for '{code}' with error: {e}.")
finally:
logger.setLevel(original_level)
print(f"Warning: Could not find valid ticker for {code} using search.")
return None
def get_ticker_start_date(code):
"""
Get the start date (first trade date) of a stock without downloading all data.
"""
try:
ticker = yf.Ticker(code)
# get_history_metadata fetches lightweight metadata about the listing
meta = ticker.get_history_metadata()
return meta.get("firstTradeDate", None)
except Exception as e:
print(f"Warning: Could not fetch start date for '{code}': {e}")
return None
def calculate_return(df: pd.DataFrame) -> pd.Series:
return df['Close'].pct_change()
def calculate_log_return(df: pd.DataFrame) -> pd.Series:
return np.log(df['Close'] / df['Close'].shift(1))
def calculate_ma_5(df: pd.DataFrame) -> pd.Series:
return df['Close'].rolling(window=5).mean()
def calculate_ma_20(df: pd.DataFrame) -> pd.Series:
return df['Close'].rolling(window=20).mean()
def calculate_vol_20(df: pd.DataFrame) -> pd.Series:
return df['Return'].rolling(window=20).std()
def calculate_roc_10(df: pd.DataFrame) -> pd.Series:
return (df['Close'] - df['Close'].shift(10)) / df['Close'].shift(10)
def calculate_rsi_14(df: pd.DataFrame) -> pd.Series:
window = 14
delta = df['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
rs = gain / loss
return 100 - (100 / (1 + rs))
def calculate_macd(df: pd.DataFrame) -> pd.Series:
ema_12 = df['Close'].ewm(span=12, adjust=False).mean()
ema_26 = df['Close'].ewm(span=26, adjust=False).mean()
return ema_12 - ema_26
def calculate_macd_signal(df: pd.DataFrame) -> pd.Series:
macd = calculate_macd(df)
return macd.ewm(span=9, adjust=False).mean()
def calculate_kd_k(df: pd.DataFrame, n: int = 9) -> pd.Series:
if 'High' not in df.columns or 'Low' not in df.columns:
return pd.Series(50.0, index=df.index)
low_min = df['Low'].rolling(window=n).min()
high_max = df['High'].rolling(window=n).max()
rsv = 100 * (df['Close'] - low_min) / (high_max - low_min + 1e-8)
# com=2 is exponentially equivalent to alpha=1/3, matching standard KD smoothing
return rsv.ewm(com=2, adjust=False).mean()
def calculate_kd_d(df: pd.DataFrame) -> pd.Series:
k = calculate_kd_k(df)
return k.ewm(com=2, adjust=False).mean()
def calculate_bb_upper(df: pd.DataFrame) -> pd.Series:
ma_20 = calculate_ma_20(df)
std_20 = df['Close'].rolling(window=20).std()
return ma_20 + 2 * std_20
def calculate_bb_lower(df: pd.DataFrame) -> pd.Series:
ma_20 = calculate_ma_20(df)
std_20 = df['Close'].rolling(window=20).std()
return ma_20 - 2 * std_20
def calculate_bias_6(df: pd.DataFrame) -> pd.Series:
ma_6 = df['Close'].rolling(window=6).mean()
return (df['Close'] - ma_6) / ma_6 * 100
feature_map = {
'Return': calculate_return,
'Log_Return': calculate_log_return,
'MA_5': calculate_ma_5,
'MA_20': calculate_ma_20,
'Vol_20': calculate_vol_20,
'ROC_10': calculate_roc_10,
'RSI_14': calculate_rsi_14,
'MACD': calculate_macd,
'MACD_Signal': calculate_macd_signal,
'KD_K': calculate_kd_k,
'KD_D': calculate_kd_d,
'BB_Upper': calculate_bb_upper,
'BB_Lower': calculate_bb_lower,
'BIAS_6': calculate_bias_6
}
def generate_features(df: pd.DataFrame, sector_str: str = SP500Sector.OTHER, is_training: bool = True) -> pd.DataFrame:
for feature_name, calc_func in feature_map.items():
df[feature_name] = calc_func(df)
df['Sector_ID'] = SP500Sector.get_sector_id(sector_str)
if is_training:
df['Target'] = df['Close'].shift(-1) / df['Close'] - 1.0
return df
|