From 64528021243e2b4a475ba608d763abec4a43451b Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Sun, 12 Oct 2025 14:33:37 +0100 Subject: [PATCH] prettify --- paperone.py | 148 +++++++++++++++++++++++++++++++++++-------------- poetry.lock | 142 ++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 3 +- 3 files changed, 248 insertions(+), 45 deletions(-) diff --git a/paperone.py b/paperone.py index ce2c227..3b8a6bf 100755 --- a/paperone.py +++ b/paperone.py @@ -3,7 +3,7 @@ import requests import math from sys import exit -from datetime import datetime +from datetime import datetime, timedelta from dotenv import load_dotenv from typing import NoReturn, List from os import environ @@ -11,6 +11,7 @@ from enum import Enum from dataclasses import dataclass from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry +from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeRemainingColumn load_dotenv() @@ -20,6 +21,7 @@ class Indicator: endpoint: str params: dict[str, int] + @dataclass class QueryResult: datetime: datetime @@ -51,19 +53,6 @@ class IndicatorEnum(Enum): 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: def __init__(self, api_key: str) -> None: 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") +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: return date.strftime("%B %d, %Y") @@ -231,41 +238,96 @@ def main() -> NoReturn: if not api_key: print("API_KEY not set") - exit(0) 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: - # for t in ["AAPL", "NVDA", "AMD", "META", "MSFT", "GOOG"]: - for t in ["AAPL"]: - print(f"TICKER: {t}\n") - for i in IndicatorEnum: - try: - indicator_results = client.query_indicator(t, i.value, date) - except Exception as e: - # print(f"Could not retrieve data: {e}") - - continue - - if not indicator_results: - # print("Could not retrieve data") - - continue - - print(f"Indicator: {i}") - - trading_day_values = [ - x for x in indicator_results if is_trading_day(x.datetime) - ] - - for r in trading_day_values: - price = client.query_price_on_day(t, r.datetime) - print( - f"{format_date_readable(r.datetime)} (${price.value:.2f}) - {i.name}: {r.value:.2f}" + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TaskProgressColumn(), + TimeRemainingColumn(), + ) as progress: + + # Overall ticker progress + ticker_task = progress.add_task( + "[cyan]Processing tickers...", total=len(tickers) + ) + + with TaapiClient(api_key) as client: + for ticker in tickers: + # Update ticker task + progress.update( + ticker_task, + description=f"[cyan]Processing {ticker}..." + ) + + # Price loading subtask + price_task = progress.add_task( + f"[green] └─ Loading prices for {ticker}...", + total=len(dates_range) + ) + + 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) diff --git a/poetry.lock b/poetry.lock index e1eed4b..3fc1aba 100644 --- a/poetry.lock +++ b/poetry.lock @@ -120,6 +120,34 @@ files = [ {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]] name = "idna" version = "3.10" @@ -135,6 +163,57 @@ files = [ [package.extras] 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]] name = "python-dotenv" version = "1.1.1" @@ -172,6 +251,67 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] 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]] name = "urllib3" version = "2.5.0" @@ -193,4 +333,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.1" python-versions = ">=3.13" -content-hash = "3f5fbe1c67324920f0a88200610bdf594bd4839fa97e478499b1e200f939a0a0" +content-hash = "0e7245f4fa895043c8681791b01a961b7a5fa94bc3ff72cf63331fcc4e7de7c1" diff --git a/pyproject.toml b/pyproject.toml index 7c9e05a..76a2bc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,8 @@ requires-python = ">=3.13" dependencies = [ "requests (>=2.32.5,<3.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)" ]