Spaces:
Sleeping
Sleeping
| import re | |
| from collections.abc import Sequence | |
| from datetime import timedelta | |
| from typing import Any | |
| import numpy as np | |
| import pandas as pd | |
| from dateutil import parser # type: ignore | |
| def normalize_ticker_symbol(ticker_input: str) -> str: | |
| """Remove all non-alphanumeric characters from ticker input, return uppercase.""" | |
| return re.sub(r"[^A-Z0-9]", "", ticker_input.upper()) | |
| def get_available_tickers() -> list[str]: | |
| """This list is too limited, I opted for any input + validation in the app.""" | |
| nasdaq_url = "https://www.nasdaqtrader.com/dynamic/SymDir/nasdaqlisted.txt" | |
| nyse_url = "https://www.nasdaqtrader.com/dynamic/SymDir/otherlisted.txt" | |
| nasdaq_tickers = pd.read_csv(nasdaq_url, sep="|")["Symbol"].dropna().tolist()[:-1] | |
| nyse_tickers = pd.read_csv(nyse_url, sep="|")["ACT Symbol"].dropna().tolist()[:-1] | |
| all_tickers = sorted(set(nasdaq_tickers + nyse_tickers)) # type: ignore | |
| return all_tickers | |
| def date_to_idx_range(timestamps: pd.DatetimeIndex, date_range: Sequence[Any]) -> tuple[int, int]: | |
| if all(date_range): | |
| idx0, idx1 = timestamps.get_indexer(date_range, method="nearest") | |
| return (idx0, idx1) | |
| else: | |
| return (0, -1) | |
| def get_date_range(figure_layout: dict[str, Any]) -> Sequence[str | None]: | |
| date_range: Sequence[str | None] = [None, None] | |
| # check xaxis2 first | |
| if "xaxis2" in figure_layout and figure_layout["xaxis2"].get("range"): | |
| date_range = figure_layout["xaxis2"]["range"] | |
| # if not found, check xaxis1 | |
| elif "xaxis1" in figure_layout and figure_layout["xaxis1"].get("range"): | |
| date_range = figure_layout["xaxis1"]["range"] | |
| # else: | |
| # print(figure_layout) | |
| return date_range | |
| def adjust_date_range( | |
| timestamps: pd.DatetimeIndex, | |
| offset_days: int, | |
| triggered_id: str = "btn-1y", | |
| date_range: Sequence[str | None] | None = None, | |
| ) -> Sequence[str]: | |
| if not date_range or triggered_id == "btn-ytd": | |
| start_date = timestamps[0].strftime("%Y-%m-%d") | |
| end_date = timestamps[-1].strftime("%Y-%m-%d") | |
| else: | |
| start_date, end_date = date_range | |
| start_date = max( | |
| parser.parse(end_date) - timedelta(days=offset_days), | |
| timestamps[0], | |
| ).strftime("%Y-%m-%d") | |
| return [start_date, end_date] | |
| def normalize_prices(prices: pd.DataFrame, date_range: Sequence[Any]) -> pd.DataFrame: | |
| date0, date1 = date_range | |
| prices_normalized = np.nan * prices | |
| prices_normalized.loc[date0:date1] = prices[date0:date1] / prices.loc[date0] - 1 | |
| return prices_normalized | |