diff --git a/CHANGELOG.md b/CHANGELOG.md index e01d63e7..cc539b92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ All notable changes to the **TradingAgents** project will be documented in this - **Import Errors**: Fixed `NameError: name 'tool' is not defined` by restoring `langchain_core` imports in data tools that were accidentally removed during Blindfire integration. - **Payload Size Error**: Implemented input truncation (max 9000 chars) in `memory.py`. - **Display Layer De-Anonymization**: Added `deanonymize_text` to `TickerAnonymizer` and patched `cli/main.py` to reverse-map "ASSET_XXX" to real company names in the final report, effectively resolving "[Company Name]" placeholders for the user while keeping the internal system blind. +- **Alpaca Integration**: Added `tradingagents/dataflows/alpaca.py` to support `get_stock_data` via Alpaca Data API v2. Registered as a vendor option in `interface.py` and `default_config.py`. Requires `ALPACA_API_KEY` and `ALPACA_API_SECRET` in `.env`. + + ### Changed - **LLM Configuration**: Updated `tradingagents/default_config.py` and `cli/utils.py` to use valid Gemini model names (e.g., `gemini-1.5-flash`, `gemini-1.5-pro`) and `gemini-pro`. diff --git a/tradingagents/dataflows/alpaca.py b/tradingagents/dataflows/alpaca.py new file mode 100644 index 00000000..c8d6739d --- /dev/null +++ b/tradingagents/dataflows/alpaca.py @@ -0,0 +1,93 @@ +import os +import requests +import pandas as pd +from typing import Optional +from datetime import datetime, timedelta + +def get_stock_data(symbol: str, start_date: str = None, end_date: str = None) -> str: + """ + Fetch historical stock data (OHLCV) from Alpaca Data API v2. + + Args: + symbol: Ticker symbol (e.g., "AAPL") + start_date: Start date (YYYY-MM-DD), defaults to 1 year ago + end_date: End date (YYYY-MM-DD), defaults to today + + Returns: + String representation of the dataframe + """ + api_key = os.getenv("ALPACA_API_KEY") + secret_key = os.getenv("ALPACA_API_SECRET") + + if not api_key or not secret_key: + raise ValueError("Error: ALPACA_API_KEY and ALPACA_API_SECRET environment variables must be set.") + + # Default dates + if not end_date: + end_date = datetime.now().strftime("%Y-%m-%d") + if not start_date: + start_date = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d") + + # Alpaca API URL (Data API v2) + url = f"https://data.alpaca.markets/v2/stocks/{symbol}/bars" + + headers = { + "APCA-API-KEY-ID": api_key, + "APCA-API-SECRET-KEY": secret_key + } + + params = { + "start": start_date, + "end": end_date, + "timeframe": "1Day", + "limit": 10000, + "adjustment": "all", # Split and dividend adjusted + "feed": "sip" # Use SIP feed if available, or 'iex' for free tier + } + + try: + response = requests.get(url, headers=headers, params=params) + + if response.status_code != 200: + # Check for free tier specific error regarding feed (422 or 403) + # 422: Invalid value (explicit feed req) + # 403: Forbidden (subscription level) + if (response.status_code in [403, 422]) and ("feed" in response.text or "subscription" in response.text): + # Retry with IEX feed (Free tier) + print(f"INFO: Retrying {symbol} with IEX feed (Free Tier)...") + params["feed"] = "iex" + response = requests.get(url, headers=headers, params=params) + + if response.status_code != 200: + raise ValueError(f"Alpaca API Error: {response.status_code} - {response.text}") + + data = response.json() + + if "bars" not in data or not data["bars"]: + return f"No data found for {symbol} on Alpaca between {start_date} and {end_date}." + + # Parse data + # Alpaca returns: t (time), o, h, l, c, v, nw, n + bars = data["bars"] + + df_data = [] + for bar in bars: + df_data.append({ + "Date": bar["t"].split("T")[0], + "Open": bar["o"], + "High": bar["h"], + "Low": bar["l"], + "Close": bar["c"], + "Volume": bar["v"] + }) + + df = pd.DataFrame(df_data) + + # Format output string similar to yfinance output for consistency + result_str = f"Stock Data for {symbol} from {start_date} to {end_date}\n" + result_str += df.to_string(index=False) + + return result_str + + except Exception as e: + return f"Error fetching data from Alpaca for {symbol}: {str(e)}" diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index ee8d6063..101c78fd 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -16,6 +16,7 @@ from .alpha_vantage import ( get_news as get_alpha_vantage_news ) from .alpha_vantage_common import AlphaVantageRateLimitError +from .alpaca import get_stock_data as get_stock_alpaca # Configuration and routing logic from .config import get_config @@ -58,7 +59,8 @@ VENDOR_LIST = [ "local", "yfinance", "openai", - "google" + "google", + "alpaca" ] # Mapping of methods to their vendor-specific implementations @@ -68,6 +70,7 @@ VENDOR_METHODS = { "alpha_vantage": get_alpha_vantage_stock, "yfinance": get_YFin_data_online, "local": get_YFin_data, + "alpaca": get_stock_alpaca, }, # technical_indicators "get_indicators": { diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 1f7078c8..24093e52 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -20,7 +20,7 @@ DEFAULT_CONFIG = { # Data vendor configuration # Category-level configuration (default for all tools in category) "data_vendors": { - "core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local + "core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local, alpaca "technical_indicators": "yfinance", # Options: yfinance, alpha_vantage, local "fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local "news_data": "alpha_vantage", # Options: openai, alpha_vantage, google, local diff --git a/verify_alpaca.py b/verify_alpaca.py new file mode 100644 index 00000000..f1355b44 --- /dev/null +++ b/verify_alpaca.py @@ -0,0 +1,36 @@ +import os +from dotenv import load_dotenv +from tradingagents.dataflows.alpaca import get_stock_data + +# Load environment variables +load_dotenv() + +def verify_alpaca(): + print("Locked & Loaded: Verifying Alpaca Data Connection...") + + api_key = os.getenv("ALPACA_API_KEY") + secret_key = os.getenv("ALPACA_API_SECRET") + + if not api_key or not secret_key: + print("❌ SKIPPING: ALPACA_API_KEY or ALPACA_API_SECRET not found in environment.") + print("Please add them to your .env file to enable Alpaca.") + return + + try: + # Test with a known ticker + symbol = "AAPL" + print(f"Fetching data for {symbol}...") + data = get_stock_data(symbol) + + if "Error" in data and not "No data found" in data: + print(f"❌ FAIL: {data}") + else: + print("✅ SUCCESS: Data retrieved successfully!") + rows = data.splitlines()[:5] + print(f"Preview:\n" + "\n".join(rows) + "...") + + except Exception as e: + print(f"❌ FAIL: Exception occurred: {e}") + +if __name__ == "__main__": + verify_alpaca()