This commit is contained in:
Giulio De Pasquale 2025-10-12 14:33:37 +01:00
parent 7909484044
commit 6452802124
3 changed files with 248 additions and 45 deletions

View File

@ -3,7 +3,7 @@
import requests import requests
import math import math
from sys import exit from sys import exit
from datetime import datetime from datetime import datetime, timedelta
from dotenv import load_dotenv from dotenv import load_dotenv
from typing import NoReturn, List from typing import NoReturn, List
from os import environ from os import environ
@ -11,6 +11,7 @@ from enum import Enum
from dataclasses import dataclass from dataclasses import dataclass
from requests.adapters import HTTPAdapter from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry from urllib3.util.retry import Retry
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn
load_dotenv() load_dotenv()
@ -20,6 +21,7 @@ class Indicator:
endpoint: str endpoint: str
params: dict[str, int] params: dict[str, int]
@dataclass @dataclass
class QueryResult: class QueryResult:
datetime: datetime datetime: datetime
@ -51,19 +53,6 @@ class IndicatorEnum(Enum):
VOLUME = Indicator(endpoint="volume", params={}) VOLUME = Indicator(endpoint="volume", params={})
class Interval(Enum):
OneMinute = 60
FiveMinutes = 300
FifteenMinutes = 900
ThirtyMinutes = 1800
OneHour = 3600
TwoHours = 7200
FourHours = 14400
TwelveHours = 43200
OneDay = 86400
OneWeek = 604800
class TaapiClient: class TaapiClient:
def __init__(self, api_key: str) -> None: def __init__(self, api_key: str) -> None:
self._api_key: str = api_key self._api_key: str = api_key
@ -222,6 +211,24 @@ def parse_date_yyyymmdd(date_str: str) -> datetime:
return datetime.strptime(date_str, "%Y%m%d") return datetime.strptime(date_str, "%Y%m%d")
def days_range_from(date: datetime, n: int) -> List[datetime]:
"""
Generate a list of dates going back n days from the given date (inclusive).
Example: date=Jan 3, n=2 [Jan 1, Jan 2, Jan 3]
Args:
date: The target date
n: Number of days to backtrack
Returns:
List of datetime objects from (date - n) to date (inclusive)
"""
start_date = date - timedelta(days=n)
return [start_date + timedelta(days=i) for i in range(n + 1)]
def format_date_readable(date: datetime) -> str: def format_date_readable(date: datetime) -> str:
return date.strftime("%B %d, %Y") return date.strftime("%B %d, %Y")
@ -231,41 +238,96 @@ def main() -> NoReturn:
if not api_key: if not api_key:
print("API_KEY not set") print("API_KEY not set")
exit(0) exit(0)
date = parse_date_yyyymmdd("20250821") date = parse_date_yyyymmdd("20250821")
days_range = 14
dates_range = days_range_from(date, days_range)
tickers = ["AAPL", "NVDA", "AMD", "META", "MSFT", "GOOG"]
indicators = list(IndicatorEnum)
with TaapiClient(api_key) as client: with Progress(
# for t in ["AAPL", "NVDA", "AMD", "META", "MSFT", "GOOG"]: SpinnerColumn(),
for t in ["AAPL"]: TextColumn("[progress.description]{task.description}"),
print(f"TICKER: {t}\n") BarColumn(),
for i in IndicatorEnum: TaskProgressColumn(),
try: TimeRemainingColumn(),
indicator_results = client.query_indicator(t, i.value, date) ) as progress:
except Exception as e:
# print(f"Could not retrieve data: {e}") # Overall ticker progress
ticker_task = progress.add_task(
continue "[cyan]Processing tickers...", total=len(tickers)
)
if not indicator_results:
# print("Could not retrieve data") with TaapiClient(api_key) as client:
for ticker in tickers:
continue # Update ticker task
progress.update(
print(f"Indicator: {i}") ticker_task,
description=f"[cyan]Processing {ticker}..."
trading_day_values = [ )
x for x in indicator_results if is_trading_day(x.datetime)
] # Price loading subtask
price_task = progress.add_task(
for r in trading_day_values: f"[green] └─ Loading prices for {ticker}...",
price = client.query_price_on_day(t, r.datetime) total=len(dates_range)
print( )
f"{format_date_readable(r.datetime)} (${price.value:.2f}) - {i.name}: {r.value:.2f}"
prices = {}
for d in dates_range:
progress.update(
price_task,
description=f"[green] └─ Loading {ticker} price for {format_date_readable(d)}...",
advance=1
) )
result = client.query_price_on_day(ticker, d)
if result:
prices[d.day] = result.value
# Remove price task when done
progress.remove_task(price_task)
# Indicator loading subtask
indicator_task = progress.add_task(
f"[yellow] └─ Loading indicators for {ticker}...",
total=len(indicators)
)
for indicator_enum in indicators:
progress.update(
indicator_task,
description=f"[yellow] └─ Loading {ticker} indicator: {indicator_enum.name}...",
advance=1
)
try:
indicator_results = client.query_indicator(
ticker, indicator_enum.value, date, results=days_range
)
except Exception as e:
progress.console.print(f"[red]Error retrieving {indicator_enum.name}: {e}")
continue
print("---------------") if not indicator_results:
continue
progress.console.print(f"\n[bold]{ticker} - {indicator_enum.name}:[/bold]")
trading_day_values = [
x for x in indicator_results if is_trading_day(x.datetime)
]
for r in trading_day_values:
price_str = f"${prices[r.datetime.day]:.2f}" if r.datetime.day in prices else "N/A"
progress.console.print(
f" {format_date_readable(r.datetime)} ({price_str}) - {indicator_enum.name}: {r.value:.2f}"
)
# Remove indicator task when done
progress.remove_task(indicator_task)
# Advance overall ticker progress
progress.advance(ticker_task)
exit(0) exit(0)

142
poetry.lock generated
View File

@ -120,6 +120,34 @@ files = [
{file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"},
] ]
[[package]]
name = "click"
version = "8.3.0"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"},
{file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main"]
markers = "platform_system == \"Windows\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.10" version = "3.10"
@ -135,6 +163,57 @@ files = [
[package.extras] [package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
{file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"},
{file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"},
]
[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"]
linkify = ["linkify-it-py (>=1,<3)"]
plugins = ["mdit-py-plugins (>=0.5.0)"]
profiling = ["gprof2dot"]
rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"]
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "pygments"
version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]] [[package]]
name = "python-dotenv" name = "python-dotenv"
version = "1.1.1" version = "1.1.1"
@ -172,6 +251,67 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"] socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "rich"
version = "14.2.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.8.0"
groups = ["main"]
files = [
{file = "rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"},
{file = "rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"},
]
[package.dependencies]
markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "shellingham"
version = "1.5.4"
description = "Tool to Detect Surrounding Shell"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
]
[[package]]
name = "typer"
version = "0.19.2"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9"},
{file = "typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca"},
]
[package.dependencies]
click = ">=8.0.0"
rich = ">=10.11.0"
shellingham = ">=1.3.0"
typing-extensions = ">=3.7.4.3"
[[package]]
name = "typing-extensions"
version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.5.0" version = "2.5.0"
@ -193,4 +333,4 @@ zstd = ["zstandard (>=0.18.0)"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.13" python-versions = ">=3.13"
content-hash = "3f5fbe1c67324920f0a88200610bdf594bd4839fa97e478499b1e200f939a0a0" content-hash = "0e7245f4fa895043c8681791b01a961b7a5fa94bc3ff72cf63331fcc4e7de7c1"

View File

@ -10,7 +10,8 @@ requires-python = ">=3.13"
dependencies = [ dependencies = [
"requests (>=2.32.5,<3.0.0)", "requests (>=2.32.5,<3.0.0)",
"python-dotenv (>=1.1.1,<2.0.0)", "python-dotenv (>=1.1.1,<2.0.0)",
"beartype (>=0.22.2,<0.23.0)" "beartype (>=0.22.2,<0.23.0)",
"typer (>=0.19.2,<0.20.0)"
] ]