File size: 2,559 Bytes
f2e9588
533ab64
dbff4bd
533ab64
dbff4bd
 
1670a39
dbff4bd
 
 
533ab64
f2e9588
 
 
 
533ab64
f2e9588
1670a39
 
 
 
d926132
1670a39
 
 
533ab64
 
 
 
 
 
4f627a5
 
533ab64
 
4f627a5
 
 
 
 
 
 
 
 
dbff4bd
 
533ab64
 
 
 
 
 
ac73d75
5dabbc5
 
 
 
dbff4bd
5dabbc5
dbff4bd
 
 
 
 
533ab64
dbff4bd
 
 
 
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
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