|
import requests |
|
import json |
|
import time |
|
from datetime import datetime, timedelta |
|
import pandas as pd |
|
from tabulate import tabulate |
|
|
|
def fetch_financial_data(symbol="USDCAD=X", start_date=None, end_date=None, interval="1m", days_to_fetch=1): |
|
""" |
|
Fetch and process financial data from Yahoo Finance. |
|
|
|
Parameters: |
|
symbol (str): The ticker symbol (e.g., "USDCAD=X" for USD/CAD exchange rate) |
|
start_date (datetime): Start date for the data (optional, defaults to 1 day ago if not provided) |
|
end_date (datetime): End date for the data (optional, defaults to current time if not provided) |
|
interval (str): Data interval - Options include "1m", "5m", "15m", "30m", "60m", "1d", "1wk", "1mo" |
|
days_to_fetch (int): Number of days to fetch data for if end_date is not specified |
|
|
|
Returns: |
|
dict: A dictionary containing: |
|
- 'data': pandas DataFrame with the processed financial data |
|
- 'meta': dictionary with meta information |
|
- 'table': formatted table string |
|
- 'meta_info': formatted meta information string |
|
- 'stats': formatted summary statistics string |
|
- 'success': boolean indicating if the operation was successful |
|
- 'message': status message |
|
""" |
|
|
|
if start_date is None: |
|
start_date = datetime.now() - timedelta(days=days_to_fetch) |
|
if end_date is None: |
|
end_date = datetime.now() |
|
|
|
result = { |
|
'data': None, |
|
'meta': None, |
|
'table': None, |
|
'meta_info': None, |
|
'stats': None, |
|
'success': False, |
|
'message': "" |
|
} |
|
|
|
try: |
|
|
|
period1 = int(time.mktime(start_date.timetuple())) |
|
period2 = int(time.mktime(end_date.timetuple())) |
|
|
|
|
|
url = f"https://query2.finance.yahoo.com/v8/finance/chart/{symbol}" |
|
|
|
|
|
params = { |
|
"period1": period1, |
|
"period2": period2, |
|
"interval": interval, |
|
"includePrePost": "true", |
|
"events": "div|split|earn", |
|
"lang": "en-US", |
|
"region": "US", |
|
"source": "cosaic" |
|
} |
|
|
|
|
|
headers = { |
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" |
|
} |
|
|
|
|
|
response = requests.get(url, params=params, headers=headers) |
|
|
|
if response.status_code == 200: |
|
data = response.json() |
|
|
|
|
|
if not data or 'chart' not in data or 'result' not in data['chart'] or not data['chart']['result']: |
|
result['message'] = "No valid data to process" |
|
return result |
|
|
|
|
|
api_result = data['chart']['result'][0] |
|
meta = api_result['meta'] |
|
timestamps = api_result['timestamp'] |
|
quotes = api_result['indicators']['quote'][0] |
|
|
|
|
|
df = pd.DataFrame({ |
|
'timestamp': timestamps, |
|
'open': quotes['open'], |
|
'high': quotes['high'], |
|
'low': quotes['low'], |
|
'close': quotes['close'], |
|
'volume': quotes['volume'] |
|
}) |
|
|
|
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s') |
|
|
|
|
|
df['range'] = df['high'] - df['low'] |
|
df['change'] = df['close'] - df['open'] |
|
df['change_pct'] = (df['change'] / df['open'] * 100).round(3) |
|
|
|
|
|
df['trend'] = ['Bullish' if c > o else 'Bearish' if c < o else 'Neutral' |
|
for c, o in zip(df['close'], df['open'])] |
|
|
|
|
|
display_df = df.copy() |
|
|
|
|
|
display_df['Date'] = display_df['timestamp'].dt.strftime('%Y-%m-%d') |
|
display_df['Time'] = display_df['timestamp'].dt.strftime('%H:%M:%S') |
|
|
|
|
|
for col in ['open', 'high', 'low', 'close', 'range']: |
|
if col in display_df.columns: |
|
display_df[col] = display_df[col].round(4) |
|
|
|
|
|
display_df['Change'] = display_df['change'].round(4) |
|
display_df['% Change'] = display_df['change_pct'].round(2).astype(str) + '%' |
|
|
|
|
|
table_df = display_df[['Date', 'Time', 'open', 'high', 'low', 'close', |
|
'range', 'Change', '% Change', 'trend']] |
|
|
|
|
|
table_df.columns = ['Date', 'Time', 'Open', 'High', 'Low', 'Close', |
|
'Range', 'Change', '% Change', 'Trend'] |
|
|
|
|
|
table = tabulate(table_df, headers='keys', tablefmt='fancy_grid', showindex=False) |
|
|
|
|
|
title = f"{symbol} Price Data ({interval} interval)" |
|
title_line = "=" * len(title) |
|
formatted_table = f"\n{title_line}\n{title}\n{title_line}\n\n{table}" |
|
|
|
|
|
meta_str = "META INFORMATION\n===============\n" |
|
|
|
|
|
key_info = { |
|
'Currency': meta.get('currency'), |
|
'Symbol': meta.get('symbol'), |
|
'Exchange Name': meta.get('exchangeName'), |
|
'Full Exchange Name': meta.get('fullExchangeName'), |
|
'Instrument Type': meta.get('instrumentType'), |
|
'First Trade Date': datetime.fromtimestamp(meta.get('firstTradeDate', 0)).strftime('%Y-%m-%d') if 'firstTradeDate' in meta else 'N/A', |
|
'Regular Market Time': datetime.fromtimestamp(meta.get('regularMarketTime', 0)).strftime('%Y-%m-%d %H:%M:%S') if 'regularMarketTime' in meta else 'N/A', |
|
'Timezone': meta.get('timezone'), |
|
'Exchange Timezone': meta.get('exchangeTimezoneName'), |
|
'Regular Market Price': meta.get('regularMarketPrice'), |
|
'52 Week High': meta.get('fiftyTwoWeekHigh'), |
|
'52 Week Low': meta.get('fiftyTwoWeekLow'), |
|
'Day High': meta.get('regularMarketDayHigh'), |
|
'Day Low': meta.get('regularMarketDayLow'), |
|
'Volume': meta.get('regularMarketVolume'), |
|
'Long Name': meta.get('longName'), |
|
'Short Name': meta.get('shortName'), |
|
'Previous Close': meta.get('previousClose'), |
|
'Scale': meta.get('scale'), |
|
'Price Hint': meta.get('priceHint') |
|
} |
|
|
|
|
|
for key, value in key_info.items(): |
|
if isinstance(value, float): |
|
meta_str += f"{key}: {value:.4f}\n" |
|
else: |
|
meta_str += f"{key}: {value}\n" |
|
|
|
|
|
if 'currentTradingPeriod' in meta: |
|
meta_str += "\nCURRENT TRADING PERIOD\n=====================\n" |
|
for period_type, period_info in meta['currentTradingPeriod'].items(): |
|
start_time = datetime.fromtimestamp(period_info.get('start', 0)).strftime('%Y-%m-%d %H:%M:%S') |
|
end_time = datetime.fromtimestamp(period_info.get('end', 0)).strftime('%Y-%m-%d %H:%M:%S') |
|
meta_str += f"{period_type.capitalize()} Period: {start_time} to {end_time} ({period_info.get('timezone', 'Unknown')})\n" |
|
|
|
|
|
stats = { |
|
'Symbol': symbol, |
|
'Long Name': meta.get('longName', symbol), |
|
'Period Start': df['timestamp'].min().strftime("%Y-%m-%d %H:%M:%S"), |
|
'Period End': df['timestamp'].max().strftime("%Y-%m-%d %H:%M:%S"), |
|
'Data Points': len(df), |
|
'Opening Price': df['open'].iloc[0], |
|
'Closing Price': df['close'].iloc[-1], |
|
'Current Price': meta.get('regularMarketPrice', df['close'].iloc[-1]), |
|
'Overall Change': df['close'].iloc[-1] - df['open'].iloc[0], |
|
'Overall % Change': ((df['close'].iloc[-1] / df['open'].iloc[0]) - 1) * 100, |
|
'Highest Price': df['high'].max(), |
|
'Lowest Price': df['low'].min(), |
|
'Average Price': df[['open', 'high', 'low', 'close']].mean().mean(), |
|
'Price Range': df['high'].max() - df['low'].min(), |
|
'Day High (Meta)': meta.get('regularMarketDayHigh'), |
|
'Day Low (Meta)': meta.get('regularMarketDayLow'), |
|
'52 Week High': meta.get('fiftyTwoWeekHigh'), |
|
'52 Week Low': meta.get('fiftyTwoWeekLow'), |
|
'Bullish Candles': (df['trend'] == 'Bullish').sum(), |
|
'Bearish Candles': (df['trend'] == 'Bearish').sum(), |
|
'Neutral Candles': (df['trend'] == 'Neutral').sum() |
|
} |
|
|
|
|
|
stats_str = "SUMMARY STATISTICS\n==================\n" |
|
for key, value in stats.items(): |
|
if isinstance(value, float): |
|
stats_str += f"{key}: {value:.4f}\n" |
|
else: |
|
stats_str += f"{key}: {value}\n" |
|
|
|
|
|
result['data'] = df |
|
result['meta'] = meta |
|
result['table'] = formatted_table |
|
result['meta_info'] = meta_str |
|
result['stats'] = stats_str |
|
result['success'] = True |
|
result['message'] = f"Successfully processed {len(df)} data points." |
|
|
|
return result |
|
else: |
|
result['message'] = f"Failed to fetch data: Status code {response.status_code}" |
|
return result |
|
|
|
except Exception as e: |
|
result['message'] = f"Error: {str(e)}" |
|
return result |
|
|