feat: Enhance ticker resolution and data fetching for multiple exchanges
Browse files- Improved `resolve_ticker_symbol` function:
- Added support for multiple exchanges (NSE and BSE).
- Ensured the correct exchange suffix is appended to the ticker symbol.
- Used fuzzy matching to find the best match for the query among company names.
- Defaulted to the first result if no best match is found, with the appropriate exchange suffix.
- Enhanced `fetch_yfinance_data` function:
- Added support for fetching data from multiple exchanges (NSE and BSE).
- Implemented a fallback mechanism to try the next exchange if data is not found on the initial exchange.
- Improved error handling to log errors and continue to the next exchange if necessary.
- Returned detailed financial data, technical indicators, and price charts if data is successfully fetched from any exchange.
These changes ensure that the application can handle stocks listed on multiple exchanges and provide accurate data and analysis.
@@ -120,38 +120,48 @@ def generate_price_chart(history):
|
|
120 |
plt.tight_layout()
|
121 |
return fig
|
122 |
|
123 |
-
def resolve_ticker_symbol(query: str
|
124 |
"""
|
125 |
Convert company names/partial symbols to valid Yahoo Finance tickers.
|
126 |
Example: "Kalyan Jewellers" → "KALYANKJIL.NS"
|
127 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
url = "https://query2.finance.yahoo.com/v1/finance/search"
|
129 |
headers = {"User-Agent": "Mozilla/5.0"} # Avoid blocking
|
130 |
params = {"q": query, "quotesCount": 5, "country": "India"} # Adjust for regional markets
|
131 |
-
|
132 |
response = requests.get(url, headers=headers, params=params)
|
133 |
data = response.json()
|
134 |
-
|
135 |
if data.get("quotes"):
|
136 |
# Extract symbols and names
|
137 |
tickers = [quote["symbol"] for quote in data["quotes"]]
|
138 |
names = [quote["longname"] or quote["shortname"] for quote in data["quotes"]]
|
139 |
-
|
140 |
# Fuzzy match the query with company names
|
141 |
best_match = process.extractOne(query, names)
|
142 |
if best_match:
|
143 |
index = names.index(best_match[0])
|
144 |
resolved_ticker = tickers[index]
|
145 |
-
|
146 |
# Ensure the exchange suffix is only added if not already present
|
147 |
-
|
148 |
-
|
|
|
|
|
149 |
return resolved_ticker
|
150 |
else:
|
151 |
# Default to first result
|
152 |
resolved_ticker = tickers[0]
|
153 |
-
|
154 |
-
|
|
|
|
|
155 |
return resolved_ticker
|
156 |
else:
|
157 |
raise ValueError(f"No ticker found for: {query}")
|
@@ -180,38 +190,52 @@ def analyze_article_sentiment(article):
|
|
180 |
return article
|
181 |
|
182 |
def fetch_yfinance_data(ticker):
|
183 |
-
"""Enhanced Yahoo Finance data fetching with technical analysis"""
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
'
|
207 |
-
'
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
|
216 |
def time_weighted_sentiment(articles):
|
217 |
"""Apply time-based weighting to sentiment scores"""
|
|
|
120 |
plt.tight_layout()
|
121 |
return fig
|
122 |
|
123 |
+
def resolve_ticker_symbol(query: str) -> str:
|
124 |
"""
|
125 |
Convert company names/partial symbols to valid Yahoo Finance tickers.
|
126 |
Example: "Kalyan Jewellers" → "KALYANKJIL.NS"
|
127 |
"""
|
128 |
+
exchanges = ["NSE", "BSE"]
|
129 |
+
exchange_suffixes = {
|
130 |
+
"NSE": ".NS",
|
131 |
+
"BSE": ".BO",
|
132 |
+
}
|
133 |
+
|
134 |
url = "https://query2.finance.yahoo.com/v1/finance/search"
|
135 |
headers = {"User-Agent": "Mozilla/5.0"} # Avoid blocking
|
136 |
params = {"q": query, "quotesCount": 5, "country": "India"} # Adjust for regional markets
|
137 |
+
|
138 |
response = requests.get(url, headers=headers, params=params)
|
139 |
data = response.json()
|
140 |
+
|
141 |
if data.get("quotes"):
|
142 |
# Extract symbols and names
|
143 |
tickers = [quote["symbol"] for quote in data["quotes"]]
|
144 |
names = [quote["longname"] or quote["shortname"] for quote in data["quotes"]]
|
145 |
+
|
146 |
# Fuzzy match the query with company names
|
147 |
best_match = process.extractOne(query, names)
|
148 |
if best_match:
|
149 |
index = names.index(best_match[0])
|
150 |
resolved_ticker = tickers[index]
|
151 |
+
|
152 |
# Ensure the exchange suffix is only added if not already present
|
153 |
+
for exchange in exchanges:
|
154 |
+
if not resolved_ticker.endswith(exchange_suffixes[exchange]):
|
155 |
+
resolved_ticker += exchange_suffixes[exchange]
|
156 |
+
break
|
157 |
return resolved_ticker
|
158 |
else:
|
159 |
# Default to first result
|
160 |
resolved_ticker = tickers[0]
|
161 |
+
for exchange in exchanges:
|
162 |
+
if not resolved_ticker.endswith(exchange_suffixes[exchange]):
|
163 |
+
resolved_ticker += exchange_suffixes[exchange]
|
164 |
+
break
|
165 |
return resolved_ticker
|
166 |
else:
|
167 |
raise ValueError(f"No ticker found for: {query}")
|
|
|
190 |
return article
|
191 |
|
192 |
def fetch_yfinance_data(ticker):
|
193 |
+
"""Enhanced Yahoo Finance data fetching with technical analysis and fallback for multiple exchanges"""
|
194 |
+
exchanges = ["NSE", "BSE"]
|
195 |
+
exchange_suffixes = {
|
196 |
+
"NSE": ".NS",
|
197 |
+
"BSE": ".BO",
|
198 |
+
}
|
199 |
+
|
200 |
+
for exchange in exchanges:
|
201 |
+
try:
|
202 |
+
logging.info(f"Fetching Yahoo Finance data for: {ticker}{exchange_suffixes[exchange]}")
|
203 |
+
stock = yf.Ticker(f"{ticker}{exchange_suffixes[exchange]}")
|
204 |
+
|
205 |
+
# Get historical data for technical analysis
|
206 |
+
history = stock.history(period="1y", interval="1d")
|
207 |
+
|
208 |
+
if history.empty:
|
209 |
+
logging.error(f"No data found for {ticker}{exchange_suffixes[exchange]}, trying next exchange")
|
210 |
+
continue
|
211 |
+
|
212 |
+
# Calculate technical indicators
|
213 |
+
ta_data = calculate_technical_indicators(history)
|
214 |
+
|
215 |
+
# Current price data
|
216 |
+
current_price = history['Close'].iloc[-1]
|
217 |
+
prev_close = history['Close'].iloc[-2] if len(history) > 1 else 0
|
218 |
+
price_change = current_price - prev_close
|
219 |
+
percent_change = (price_change / prev_close) * 100 if prev_close != 0 else 0
|
220 |
+
|
221 |
+
# Generate price chart
|
222 |
+
chart = generate_price_chart(history[-120:]) # Last 120 days
|
223 |
+
|
224 |
+
return {
|
225 |
+
'current_price': current_price,
|
226 |
+
'price_change': price_change,
|
227 |
+
'percent_change': percent_change,
|
228 |
+
'chart': chart,
|
229 |
+
'technical_indicators': ta_data,
|
230 |
+
'fundamentals': stock.info
|
231 |
+
}
|
232 |
+
|
233 |
+
except Exception as e:
|
234 |
+
logging.error(f"Error fetching Yahoo Finance data for {ticker}{exchange_suffixes[exchange]}: {str(e)}")
|
235 |
+
continue
|
236 |
+
|
237 |
+
logging.error(f"Failed to fetch data for {ticker} from all exchanges")
|
238 |
+
return {"error": f"Failed to fetch data for {ticker} from all exchanges"}
|
239 |
|
240 |
def time_weighted_sentiment(articles):
|
241 |
"""Apply time-based weighting to sentiment scores"""
|