From 579509fe874f11b2aee1af21921bb350dd46a5c6 Mon Sep 17 00:00:00 2001 From: Matthew Grotke Date: Sat, 3 Jan 2026 21:43:01 -0500 Subject: [PATCH] The AlphaVantage stock script now uses a cache file that is built slowly while waiting 15 seconds per API call, because the free AlphaVantage API keys are rate limited and only the first stock symbol was being returned. --- stocks/get_stocks_alphavantage.py | 92 +++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/stocks/get_stocks_alphavantage.py b/stocks/get_stocks_alphavantage.py index af2fb49..bbb8b22 100755 --- a/stocks/get_stocks_alphavantage.py +++ b/stocks/get_stocks_alphavantage.py @@ -1,9 +1,16 @@ #!/usr/bin/env python3 +import os +import sys +import time import requests from datetime import datetime, timedelta import argparse +CACHE_FILE = os.path.expanduser("~/.cache/mgconky/stocks_alphavantage.txt") +API_DELAY_SECONDS = 15 +USE_CACHE = True + def fetch_intraday_data(api_key, symbol, interval="1min"): """Fetch current and historical intraday price using TIME_SERIES_INTRADAY.""" url = "https://www.alphavantage.co/query" @@ -99,6 +106,7 @@ def fetch_historical_data(api_key, symbol, range_in_days): print(f"Error: Failed to fetch historical data for {symbol} - {e}") return None + def main(): # Parse command-line arguments parser = argparse.ArgumentParser(description="Fetch stock data from Alpha Vantage.") @@ -126,9 +134,15 @@ def main(): line_tab3_offset = "${alignr}" # Could also replace this with goto 120 if you don't like the right alignment # Iterate symbols - for symbol in symbols: + had_success = False + for i, symbol in enumerate(symbols): fetched_data = fetch_intraday_data(args.api_key, symbol) if args.range_in_days < 1 else fetch_historical_data(args.api_key, symbol, args.range_in_days) - if fetched_data: + if ( + isinstance(fetched_data, dict) + and "current_price" in fetched_data + and isinstance(fetched_data["current_price"], (int, float)) + ): + had_success = True current_price = fetched_data["current_price"] compare_price = fetched_data["compare_price"] price_difference = current_price - compare_price @@ -146,11 +160,81 @@ def main(): else: output.append(f"{symbol}: Error fetching data") + # --- Rate limit protection for Alpha Vantage --- + if i < len(symbols) - 1: + time.sleep(API_DELAY_SECONDS) # wait 15 seconds between queries + # Join all parts of the output and print it header_label = f"Intraday" if args.range_in_days < 1 else f"{args.range_in_days} Day" header_line = f"{line_tab1_offset}{color_header}Ticker{line_tab2_offset}Price ($$){line_tab3_offset}{header_label}{color_label}" - return header_line + "\n" + f"{line_tab1_offset}{color_header}${{voffset -5}}${{hr 1}}" + "\n" + "\n".join(output) + final_output = ( + header_line + + "\n" + + f"{line_tab1_offset}{color_header}${{voffset -5}}${{hr 1}}" + + "\n" + + "\n".join(output) + ) + + if had_success: + if USE_CACHE: + # Save results to cache file + os.makedirs(os.path.dirname(CACHE_FILE), exist_ok=True) + tmp_file = CACHE_FILE + ".tmp" + with open(tmp_file, "w") as f: + f.write(final_output) + os.replace(tmp_file, CACHE_FILE) + else: + # Direct output mode (no cache) + return final_output + else: + # No valid data was returned + # Do NOT overwrite cache + # Do NOT return anything in cache mode + return None + if __name__ == "__main__": - print(main()) + + # The free alpha vantage plan may require you to rate limit. + # Conky will not wait for rate limited output. + # We must instead return the previous results + # (from when this script was last called) + # Set RATE_LIMIT to false if you have the paid plan. + if USE_CACHE: + + # Fork immediately so the process Conky launched can exit right away. + pid = os.fork() + if pid > 0: + # PARENT PROCESS (this is the process Conky launched) + + # Return contents of cache file (from early query to Conky) + if os.path.exists(CACHE_FILE): + try: + with open(CACHE_FILE, "r") as f: + print(f.read(), end="") + sys.stdout.flush() + except Exception: + pass + + # Parent exits immediately ---> Conky renders output + sys.exit(0) + + else: + # CHILD PROCESS (background updater, not waited on by Conky) + + # Detach from the parent session so Conky does not wait on this process + os.setsid() + + # Perform new query + main() + sys.exit(0) + + else: + + # Return the results to Conky + result = main() + if result is not None: + print(result) + +