diff --git a/.env.example b/.env.example index 3c6f6dd4..f5dce8a3 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,20 @@ TAAPI_API_KEY=taapi_api_key_placeholder BYBIT_BASE_URL=https://api-demo.bybit.com BYBIT_API_KEY=bybit_api_key_placeholder BYBIT_API_SECRET=bybit_api_secret_placeholder +COIN_GECKO_API_BASE_URL=https://api.coingecko.com/api/v3 + +# Model settings +LLM_PROVIDER=openai +BACKEND_URL=https://api.openai.com/v1 +DEEP_THINK_LLM=gpt-4o-mini +QUICK_THINK_LLM=gpt-4o-mini + +# App settings +APP_HOST=localhost +APP_PORT=8000 + +# Redis +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD=default-password +REDIS_DB=0 diff --git a/.python-version b/.python-version deleted file mode 100644 index 56bb6605..00000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.12.7 diff --git a/README.md b/README.md index a18f0f01..9d0e0f75 100644 --- a/README.md +++ b/README.md @@ -110,23 +110,25 @@ conda activate tradingagents pyenv pyenv local 3.12.7 python -m venv .venv -source./venv/bin/activate -python --version -pip install --upgrade pip -pip install -r requirements.txt -pip list - -# daily use source .venv/bin/activate +python --version +python -m pip install --upgrade pip +python -m pip install -r requirements.txt +python -m pip list + deactivate if error mini-racer -source venv/bin/activate && pip install --no-deps -r requirements.txt #install without miniracer +source .venv/bin/activate && pip install --no-deps -r requirements.txt #install without miniracer + +Api +python webapp.py ``` -Install dependencies: -```bash -pip install -r requirements.txt +### Connect to Redis (Local) +``` +docker-compose up -d +redis-cli -h localhost -p 6379 -a {REDIS_PASSWORD} ``` ### Required APIs diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..14e2dbc7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + redis: + image: redis:7.4.7 + container_name: ${REDIS_CONTAINER_NAME:-trading_agents_redis} + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis-data:/data + environment: + REDIS_PASSWORD: ${REDIS_PASSWORD} + command: > + redis-server + --appendonly yes + --requirepass ${REDIS_PASSWORD} + +volumes: + redis-data: diff --git a/pyproject.toml b/pyproject.toml index 63af4721..664e7831 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,8 @@ dependencies = [ "rich>=14.0.0", "setuptools>=80.9.0", "stockstats>=0.6.5", + "fastapi>=0.115.0", + "uvicorn[standard]>=0.32.0", "tqdm>=4.67.1", "tushare>=1.4.21", "typing-extensions>=4.14.0", diff --git a/requirements.txt b/requirements.txt index 9f6dacd8..47523b29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,4 +26,6 @@ langchain_anthropic langchain-google-genai binance-sdk-spot telethon -pybit \ No newline at end of file +fastapi +uvicorn[standard] +redis[hiredis] diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index d1fdd3d8..2a21a457 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -1,15 +1,11 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json from tradingagents.agents.utils.agent_utils import get_fundamentals, get_whitepaper, get_market_cap -from tradingagents.dataflows.config import get_config def create_fundamentals_analyst(llm): def fundamentals_analyst_node(state): current_date = state["trade_date"] ticker = state["ticker_of_interest"] - company_name = state["ticker_of_interest"] tools = [ get_fundamentals, diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index 64ddef4c..d8137acc 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -1,9 +1,5 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json from tradingagents.agents.utils.agent_utils import get_news, get_global_news -from tradingagents.dataflows.config import get_config - def create_news_analyst(llm): def news_analyst_node(state): diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index cf2ccad0..269c6c10 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -1,15 +1,11 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json from tradingagents.agents.utils.agent_utils import get_news, get_fear_and_greed -from tradingagents.dataflows.config import get_config def create_social_media_analyst(llm): def social_media_analyst_node(state): current_date = state["trade_date"] ticker = state["ticker_of_interest"] - coin_name = state["ticker_of_interest"] tools = [ get_news, diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index fd59f611..0cb3db85 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -1,8 +1,6 @@ -import time -import json +from tradingagents.agents.utils.memory import FinancialSituationMemory - -def create_research_manager(llm, memory): +def create_research_manager(llm, memory: FinancialSituationMemory): def research_manager_node(state) -> dict: history = state["investment_debate_state"].get("history", "") market_research_report = state["market_report"] diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index 88f18328..4bccf7ef 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -1,12 +1,8 @@ -import time -import json +from tradingagents.agents.utils.memory import FinancialSituationMemory - -def create_risk_manager(llm, memory): +def create_risk_manager(llm, memory: FinancialSituationMemory): def risk_manager_node(state) -> dict: - company_name = state["company_of_interest"] - history = state["risk_debate_state"]["history"] risk_debate_state = state["risk_debate_state"] market_research_report = state["market_report"] diff --git a/tradingagents/agents/researchers/bear_researcher.py b/tradingagents/agents/researchers/bear_researcher.py index dcc227ff..18cc2e7c 100644 --- a/tradingagents/agents/researchers/bear_researcher.py +++ b/tradingagents/agents/researchers/bear_researcher.py @@ -1,9 +1,6 @@ -from langchain_core.messages import AIMessage -import time -import json +from tradingagents.agents.utils.memory import FinancialSituationMemory - -def create_bear_researcher(llm, memory): +def create_bear_researcher(llm, memory: FinancialSituationMemory): def bear_node(state) -> dict: investment_debate_state = state["investment_debate_state"] history = investment_debate_state.get("history", "") @@ -38,7 +35,7 @@ Resources available: Market research report: {market_research_report} Social media sentiment report: {sentiment_report} Latest world affairs news: {news_report} -Company fundamentals report: {fundamentals_report} +Coin fundamentals report: {fundamentals_report} Profile analysis report: {profile_report} Conversation history of the debate: {history} Last bull argument: {current_response} diff --git a/tradingagents/agents/researchers/bull_researcher.py b/tradingagents/agents/researchers/bull_researcher.py index 76dd1c42..88101886 100644 --- a/tradingagents/agents/researchers/bull_researcher.py +++ b/tradingagents/agents/researchers/bull_researcher.py @@ -1,9 +1,6 @@ -from langchain_core.messages import AIMessage -import time -import json +from tradingagents.agents.utils.memory import FinancialSituationMemory - -def create_bull_researcher(llm, memory): +def create_bull_researcher(llm, memory: FinancialSituationMemory): def bull_node(state) -> dict: investment_debate_state = state["investment_debate_state"] history = investment_debate_state.get("history", "") diff --git a/tradingagents/agents/risk_mgmt/aggresive_debator.py b/tradingagents/agents/risk_mgmt/aggresive_debator.py index bfaf7568..e852ed59 100644 --- a/tradingagents/agents/risk_mgmt/aggresive_debator.py +++ b/tradingagents/agents/risk_mgmt/aggresive_debator.py @@ -1,7 +1,3 @@ -import time -import json - - def create_risky_debator(llm): def risky_node(state) -> dict: risk_debate_state = state["risk_debate_state"] diff --git a/tradingagents/agents/risk_mgmt/conservative_debator.py b/tradingagents/agents/risk_mgmt/conservative_debator.py index 280284d9..5e09c8f8 100644 --- a/tradingagents/agents/risk_mgmt/conservative_debator.py +++ b/tradingagents/agents/risk_mgmt/conservative_debator.py @@ -1,8 +1,3 @@ -from langchain_core.messages import AIMessage -import time -import json - - def create_safe_debator(llm): def safe_node(state) -> dict: risk_debate_state = state["risk_debate_state"] diff --git a/tradingagents/agents/risk_mgmt/neutral_debator.py b/tradingagents/agents/risk_mgmt/neutral_debator.py index 347e9ba6..110e1ec0 100644 --- a/tradingagents/agents/risk_mgmt/neutral_debator.py +++ b/tradingagents/agents/risk_mgmt/neutral_debator.py @@ -1,7 +1,3 @@ -import time -import json - - def create_neutral_debator(llm): def neutral_node(state) -> dict: risk_debate_state = state["risk_debate_state"] diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index 5cdda5af..4bcdbc77 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -1,7 +1,4 @@ import functools -import time -import json - def create_trader(llm, memory): def trader_node(state, name): @@ -40,7 +37,9 @@ def create_trader(llm, memory): messages = [ { "role": "system", - "content": f"""You are a crypto trading agent analyzing cryptocurrency market data for a specific trading pair (e.g., BTC/USDT). Based on your analysis, provide a specific recommendation to BUY, SELL, or HOLD the base asset relative to the quote asset for the pair {pair_context}. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. Do not forget to utilize lessons from past decisions to learn from your mistakes. Here is some reflections from similar situations you traded in and the lessons learned: {past_memory_str}""", + "content": f"""You are a crypto trading agent analyzing cryptocurrency market data for a specific trading pair (e.g., BTC/USDT). Based on your analysis, provide a specific recommendation to BUY, SELL, or HOLD the base asset relative to the quote asset for the pair {pair_context}, along with the quantity for BUY and SELL \ + End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** **QUANTITY**' to confirm your recommendation. \ + Do not forget to utilize lessons from past decisions to learn from your mistakes. Here is some reflections from similar situations you traded in and the lessons learned: {past_memory_str}""", }, context, ] diff --git a/tradingagents/agents/utils/agent_states.py b/tradingagents/agents/utils/agent_states.py index fcae0985..c3bdefce 100644 --- a/tradingagents/agents/utils/agent_states.py +++ b/tradingagents/agents/utils/agent_states.py @@ -1,10 +1,7 @@ -from typing import Annotated, Sequence -from datetime import date, timedelta, datetime -from typing_extensions import TypedDict, Optional -from langchain_openai import ChatOpenAI +from typing import Annotated +from typing_extensions import TypedDict from tradingagents.agents import * -from langgraph.prebuilt import ToolNode -from langgraph.graph import END, StateGraph, START, MessagesState +from langgraph.graph import MessagesState # Researcher team state @@ -48,7 +45,6 @@ class RiskDebateState(TypedDict): class AgentState(MessagesState): - company_of_interest: Annotated[str, "Company that we are interested in trading"] ticker_of_interest: Annotated[str, "Ticker that we are interested in trading"] # e.g BTC/USDT trade_date: Annotated[str, "What date we are trading at"] diff --git a/tradingagents/agents/utils/memory.py b/tradingagents/agents/utils/memory.py index 69b8ab8c..f8e1da2e 100644 --- a/tradingagents/agents/utils/memory.py +++ b/tradingagents/agents/utils/memory.py @@ -11,7 +11,7 @@ class FinancialSituationMemory: self.embedding = "text-embedding-3-small" self.client = OpenAI(base_url=config["backend_url"]) self.chroma_client = chromadb.Client(Settings(allow_reset=True)) - self.situation_collection = self.chroma_client.create_collection(name=name) + self.situation_collection = self.chroma_client.get_or_create_collection(name=name) def get_embedding(self, text): """Get OpenAI embedding for a text""" diff --git a/tradingagents/dataflows/binance.py b/tradingagents/dataflows/binance.py index cb7435e2..c7458e1f 100644 --- a/tradingagents/dataflows/binance.py +++ b/tradingagents/dataflows/binance.py @@ -1,20 +1,33 @@ from binance_common.configuration import ConfigurationRestAPI from binance_common.constants import SPOT_REST_API_PROD_URL from binance_sdk_spot.spot import Spot -import os from datetime import datetime import csv import io +from tradingagents.dataflows.config import get_config -def get_api_key() -> str: - """Retrieve the API key for Binance from environment variables.""" - api_key = os.getenv("BINANCE_API_KEY") - if not api_key: - raise ValueError("BINANCE_API_KEY environment variable is not set.") - return api_key +_client = None -configuration = ConfigurationRestAPI(api_key=get_api_key(), base_path=SPOT_REST_API_PROD_URL) -client = Spot(config_rest_api=configuration) +def get_binance_client(): + """Get or create Binance client with lazy initialization.""" + global _client + if _client is None: + try: + config = get_config() + api_key = config["external"].get("BINANCE_API_KEY", "") + if not api_key: + raise ValueError("BINANCE_API_KEY not found in configuration") + + configuration = ConfigurationRestAPI( + api_key=api_key, + base_path=SPOT_REST_API_PROD_URL + ) + _client = Spot(config_rest_api=configuration) + except Exception as e: + print(f"ERROR: Failed to initialize Binance client: {e}") + raise + + return _client def get_market_data(symbol: str, start_date: str, end_date: str): """Fetch market data for a given symbol from Binance. Get OHLCV data. interval is 1 day. @@ -36,6 +49,7 @@ def get_market_data(symbol: str, start_date: str, end_date: str): # print(f"DEBUG: Fetching data for {formatted_symbol} from {start_date} to {end_date}") try: + client = get_binance_client() response = client.rest_api.klines( symbol=formatted_symbol, start_time=start_epoch, @@ -80,6 +94,8 @@ def get_market_data(symbol: str, start_date: str, end_date: str): csv_string = output.getvalue() output.close() + title = f"# Market Data for {symbol} from {start_date} to {end_date}\n\n" + csv_string = title + csv_string return csv_string except Exception as e: diff --git a/tradingagents/dataflows/coin_gecko_fundamentals.py b/tradingagents/dataflows/coin_gecko_fundamentals.py index 7e8f5759..15367a69 100644 --- a/tradingagents/dataflows/coin_gecko_fundamentals.py +++ b/tradingagents/dataflows/coin_gecko_fundamentals.py @@ -1,5 +1,6 @@ import requests from .alpha_vantage_common import API_BASE_URL +from tradingagents.dataflows.config import get_config def get_market_cap() -> str: """ @@ -8,8 +9,11 @@ def get_market_cap() -> str: Returns: str: Market capitalization percentage data for cryptocurrencies """ - endpoint = f"https://api.coingecko.com/api/v3/global" + config = get_config() + api_base_url = config["external"].get("COIN_GECKO_API_BASE_URL", "https://api.coingecko.com/api/v3") + endpoint = f"{api_base_url}/global" response = requests.get(endpoint) + print(f"DEBUG: CoinGecko API response status code: {response.status_code}") response.raise_for_status() data = response.json() market_cap_pct = data.get("data", {}).get("market_cap_percentage", {}) @@ -18,4 +22,4 @@ def get_market_cap() -> str: for coin, percentage in market_cap_pct.items(): # Format each line as "Coin: XX.XX%" result += f"- {coin.upper()}: {percentage:.2f}%\n" - return result \ No newline at end of file + return result diff --git a/tradingagents/dataflows/openai.py b/tradingagents/dataflows/openai.py index abc387f0..f643aee6 100644 --- a/tradingagents/dataflows/openai.py +++ b/tradingagents/dataflows/openai.py @@ -1,10 +1,27 @@ from openai import OpenAI from .config import get_config +_client = None + +def get_openai_client(): + """Get or create OpenAI client with lazy initialization.""" + global _client + if _client is None: + try: + config = get_config() + base_url = config.get("backend_url") + if not base_url: + raise ValueError("backend_url not found in configuration") + _client = OpenAI(base_url=base_url) + except Exception as e: + print(f"ERROR: Failed to initialize OpenAI client: {e}") + raise + + return _client def get_stock_news_openai(query, start_date, end_date): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -39,7 +56,7 @@ def get_stock_news_openai(query, start_date, end_date): def get_crypto_news_openai(query, start_date, end_date): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -74,7 +91,7 @@ def get_crypto_news_openai(query, start_date, end_date): def get_global_news_openai(curr_date, look_back_days=7, limit=5): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -110,7 +127,7 @@ def get_global_news_openai(curr_date, look_back_days=7, limit=5): def get_fundamentals_openai(ticker, curr_date): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -146,7 +163,7 @@ def get_fundamentals_openai(ticker, curr_date): def get_whitepaper_openai(symbol): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -177,4 +194,4 @@ def get_whitepaper_openai(symbol): store=True, ) - return response.output[1].content[0].text \ No newline at end of file + return response.output[1].content[0].text diff --git a/tradingagents/dataflows/taapi.py b/tradingagents/dataflows/taapi.py index c0d13777..456d28ec 100644 --- a/tradingagents/dataflows/taapi.py +++ b/tradingagents/dataflows/taapi.py @@ -1,16 +1,7 @@ import requests -from typing import Annotated, List, Dict -import os +from typing import Annotated, List from tradingagents.dataflows.config import get_config - -def get_api_key() -> str: - """Retrieve the API key for TAAPI from environment variables.""" - api_key = os.getenv("TAAPI_API_KEY") - if not api_key: - raise ValueError("TAAPI_API_KEY environment variable is not set.") - return api_key - # This is for single indicator, unused for now but kept for reference def get_crypto_stats_indicators_window( symbol: Annotated[str, "ticker symbol of the coin/asset"], @@ -58,8 +49,10 @@ def get_crypto_stats_indicators_window( return f"Error: Indicator '{indicator}' is not supported. Please choose from: {list(supported_indicators.keys())}" config = get_config() - base_url = config["tool_providers"].get("TAAPI_BASE_URL", "https://api.taapi.io") - api_key = get_api_key() + base_url = config["external"].get("TAAPI_BASE_URL", "https://api.taapi.io") + api_key = config["external"].get("TAAPI_API_KEY", "") + if not api_key: + return "Error: TAAPI_API_KEY is not set in the configuration." # Set backtrack as requested backtrack = look_back_days @@ -187,8 +180,10 @@ def get_crypto_stats_indicators( return f"Error: Indicators {invalid_indicators} are not supported. Please choose from: {list(supported_indicators.keys())}" config = get_config() - base_url = config["tool_providers"].get("TAAPI_BASE_URL", "https://api.taapi.io") - api_key = get_api_key() + base_url = config["external"].get("TAAPI_BASE_URL", "https://api.taapi.io") + api_key = config["external"].get("TAAPI_API_KEY", "") + if not api_key: + return "Error: TAAPI_API_KEY is not set in the configuration." # Construct the bulk API URL url = f"{base_url}/bulk" diff --git a/tradingagents/dataflows/telegram.py b/tradingagents/dataflows/telegram.py index 494d2c21..0d9b66ba 100644 --- a/tradingagents/dataflows/telegram.py +++ b/tradingagents/dataflows/telegram.py @@ -1,13 +1,19 @@ import asyncio from telethon import TelegramClient from datetime import datetime, timedelta, timezone -import os +from tradingagents.dataflows.config import get_config def get_api_credentials(): - api_id = int(os.getenv("TELEGRAM_API_ID", "")) - api_hash = os.getenv("TELEGRAM_API_HASH", "") - session_name = os.getenv("TELEGRAM_SESSION_NAME", "") - return api_id, api_hash, session_name + """Retrieve Telegram API credentials from environment variables.""" + config = get_config() + api_id = config["external"]["TELEGRAM_API_ID"] + api_hash = config["external"]["TELEGRAM_API_HASH"] + session_name = config["external"]["TELEGRAM_SESSION_NAME"] + + if not api_id or not api_hash or not session_name: + raise ValueError("Missing required Telegram credentials: TELEGRAM_API_ID, TELEGRAM_API_HASH, or TELEGRAM_SESSION_NAME") + + return int(api_id), api_hash, session_name async def _get_channel_history_async(start_date_str, end_date_str): """ @@ -55,4 +61,4 @@ def get_crypto_news_telegram(curr_date, look_back_days=7, limit=100): start_date_str = start_date.strftime('%Y-%m-%d') end_date_str = end_date.strftime('%Y-%m-%d') - return asyncio.run(_get_channel_history_async(start_date_str, end_date_str)) \ No newline at end of file + return asyncio.run(_get_channel_history_async(start_date_str, end_date_str)) diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 3c4387cd..585a5627 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -1,6 +1,10 @@ import os DEFAULT_CONFIG = { + # App config + "APP_HOST": "localhost", + "APP_PORT": 8000, + # Directory settings "project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")), "results_dir": os.getenv("TRADINGAGENTS_RESULTS_DIR", "./results"), "data_dir": "/Users/yluo/Documents/Code/ScAI/FR1-data", @@ -9,10 +13,10 @@ DEFAULT_CONFIG = { "dataflows/data_cache", ), # LLM settings - "llm_provider": "openai", - "deep_think_llm": "o4-mini", - "quick_think_llm": "gpt-4o-mini", - "backend_url": "https://api.openai.com/v1", + "llm_provider": os.getenv("LLM_PROVIDER", "openai"), + "deep_think_llm": os.getenv("DEEP_THINK_LLM", "gpt-4o-mini"), + "quick_think_llm": os.getenv("QUICK_THINK_LLM", "gpt-4o-mini"), + "backend_url": os.getenv("BACKEND_URL","https://api.openai.com/v1"), # Debate and discussion settings "max_debate_rounds": 1, "max_risk_discuss_rounds": 1, @@ -29,7 +33,6 @@ DEFAULT_CONFIG = { }, # Tool-level configuration (takes precedence over category-level) "tool_vendors": { - # Example: "get_stock_data": "alpha_vantage", # Override category default "get_global_news": "telegram" # Override category default }, # Tool provider settings @@ -37,8 +40,21 @@ DEFAULT_CONFIG = { "TAAPI_BASE_URL": os.getenv("TAAPI_BASE_URL", "https://api.taapi.io"), }, "external": { + "BINANCE_API_KEY": os.getenv("BINANCE_API_KEY", ""), + "TAAPI_BASE_URL": os.getenv("TAAPI_BASE_URL", "https://api.taapi.io"), + "TAAPI_API_KEY": os.getenv("TAAPI_API_KEY", ""), "BYBIT_BASE_URL": os.getenv("BYBIT_BASE_URL", "https://api-demo.bybit.com"), "BYBIT_API_KEY": os.getenv("BYBIT_API_KEY", ""), "BYBIT_API_SECRET": os.getenv("BYBIT_API_SECRET", ""), - } + "COIN_GECKO_API_BASE_URL": os.getenv("COIN_GECKO_API_BASE_URL", "https://api.coingecko.com/api/v3"), + "TELEGRAM_API_ID": os.getenv("TELEGRAM_API_ID", ""), + "TELEGRAM_API_HASH": os.getenv("TELEGRAM_API_HASH", ""), + "TELEGRAM_SESSION_NAME": os.getenv("TELEGRAM_SESSION_NAME", ""), + }, + "redis": { + "REDIS_HOST": os.getenv("REDIS_HOST", "localhost"), + "REDIS_PORT": int(os.getenv("REDIS_PORT", 6379)), + "REDIS_PASSWORD": os.getenv("REDIS_PASSWORD", "defaultpassword"), + "REDIS_DB": int(os.getenv("REDIS_DB", 0)), + }, } diff --git a/tradingagents/external/redis/client.py b/tradingagents/external/redis/client.py new file mode 100644 index 00000000..2d9c6299 --- /dev/null +++ b/tradingagents/external/redis/client.py @@ -0,0 +1,34 @@ +from redis import Redis, ConnectionPool +from redis.backoff import ExponentialBackoff +from redis.retry import Retry +from tradingagents.dataflows.config import get_config + +_client = None + +def get_redis_client() -> Redis: + """Get or create Redis client with lazy initialization.""" + global _client + if _client is None: + try: + config = get_config() + retry = Retry(ExponentialBackoff(), retries=5) + + pool = ConnectionPool( + host=config["redis"]["REDIS_HOST"], + port=config["redis"]["REDIS_PORT"], + password=config["redis"]["REDIS_PASSWORD"], + db=config["redis"]["REDIS_DB"], + decode_responses=True, + socket_connect_timeout=5, + socket_timeout=5, + health_check_interval=10, + retry=retry, + ) + print("INFO: Initializing Redis client") + _client = Redis(connection_pool=pool) + print("INFO: Redis client initialized successfully") + except Exception as e: + print(f"ERROR: Failed to initialize Redis client: {e}") + raise + + return _client diff --git a/tradingagents/external/redis/repo.py b/tradingagents/external/redis/repo.py new file mode 100644 index 00000000..754919bd --- /dev/null +++ b/tradingagents/external/redis/repo.py @@ -0,0 +1,18 @@ +from tradingagents.external.redis.client import get_redis_client + +redis = get_redis_client() + +class RedisRepo: + def get(self, key: str): + return redis.get(key) + + def set(self, key: str, value: str, ex: int | None = None): + return redis.set(key, value, ex=ex) + + def delete(self, key: str): + return redis.delete(key) + + def exists(self, key: str) -> bool: + return redis.exists(key) == 1 + +redis_repo = RedisRepo() diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index 98cf6578..5d2fda62 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -21,7 +21,6 @@ class Propagator: """Create the initial state for the agent graph.""" return { "messages": [("human", ticker)], - "company_of_interest": ticker, "ticker_of_interest": ticker, "trade_date": str(trade_date), "investment_debate_state": InvestDebateState( diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 7314084b..3a8ec920 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -215,7 +215,7 @@ class TradingAgentsGraph: def _log_state(self, trade_date, final_state): """Log the final state to a JSON file.""" self.log_states_dict[str(trade_date)] = { - "company_of_interest": final_state["company_of_interest"], + "ticker_of_interest": final_state["ticker_of_interest"], "trade_date": final_state["trade_date"], "market_report": final_state["market_report"], "sentiment_report": final_state["sentiment_report"], diff --git a/webapp.py b/webapp.py new file mode 100644 index 00000000..a583fac7 --- /dev/null +++ b/webapp.py @@ -0,0 +1,105 @@ +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +import uvicorn +from datetime import datetime +import asyncio + +# Import your trading agents +from tradingagents.graph.trading_graph import TradingAgentsGraph +from tradingagents.dataflows.config import get_config +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +config = get_config() + +# Create FastAPI app instance +app = FastAPI( + title="TradingAgents API", + description="API for TradingAgents financial trading framework", + version="0.1.0" +) + +# Pydantic models for request/response +class TradingRequest(BaseModel): + symbol: str + date: str + +class TradingResponse(BaseModel): + symbol: str + date: str + decision: dict + timestamp: str + status: str + +# Initialize trading agent once at startup +def create_trading_agent(): + """Create trading agent with fixed configuration""" + return TradingAgentsGraph(debug=True, config=config) + +# Create the trading agent instance once +trading_agent = create_trading_agent() + +@app.get("/") +async def root(): + """Root endpoint""" + return {"message": "Welcome to TradingAgents API"} + +@app.get("/ping") +async def ping(): + """Simple ping endpoint that returns pong""" + return {"message": "pong"} + +@app.get("/health") +async def health_check(): + """Health check endpoint""" + return { + "status": "healthy", + "timestamp": datetime.now().isoformat(), + "service": "tradingagents-api" + } + +@app.post("/trading/analyze", response_model=TradingResponse) +async def analyze_trading_decision(request: TradingRequest): + """ + Analyze trading decision for a given symbol and date + + Example usage: + POST /trading/analyze + { + "symbol": "NVDA", + "date": "2024-05-10" + } + """ + try: + # Run the analysis (this might take a while, so we run it in a thread pool) + def run_analysis(): + _, decision = trading_agent.propagate(request.symbol, request.date) + return decision + + # Run in thread pool to avoid blocking + loop = asyncio.get_event_loop() + decision = await loop.run_in_executor(None, run_analysis) + + return TradingResponse( + symbol=request.symbol, + date=request.date, + decision=decision, + timestamp=datetime.now().isoformat(), + status="success" + ) + + except Exception as e: + raise HTTPException(status_code=500, detail=f"Trading analysis failed: {str(e)}") + + +if __name__ == "__main__": + # Run the server + uvicorn.run( + "webapp:app", + host=config.get("APP_HOST", "localhost"), + port=config.get("APP_PORT", 8000), + reload=True, + log_level="info" + )