diff --git a/.gitignore b/.gitignore index 4ebf99e3..a5e353a5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ eval_results/ eval_data/ *.egg-info/ .env +TA +/.chainlit diff --git a/Trading_agent_p2p_server_fundamentals.py b/Trading_agent_p2p_server_fundamentals.py new file mode 100644 index 00000000..82fa184b --- /dev/null +++ b/Trading_agent_p2p_server_fundamentals.py @@ -0,0 +1,131 @@ +from tradingagents.graph.trading_graph import TradingAgentsGraph +from tradingagents.default_config import DEFAULT_CONFIG + +from isek.adapter.base import Adapter, AdapterCard +from isek.node.etcd_registry import EtcdRegistry +from isek.node.node_v2 import Node +import dotenv +from isek.utils.log import LoggerManager +from isek.utils.log import log +import json + +LoggerManager.plain_mode() +dotenv.load_dotenv() + +NODE_ID = "TA_Agent_Fundamentals" +# selected_analysts=["market", "social", "news", "fundamentals"] +selected_analysts=["fundamentals"] + +def json_to_markdown(data: dict) -> str: + """ + Convert trading analysis JSON to formatted markdown string, extracting only market analysis. + Assumes input is a dict with a single key (date), and the value is the trading data dict. + """ + # Extract the first value (trading data dict) + trading_data = next(iter(data.values())) + + # Start building markdown + markdown = [] + + # Header + markdown.append("# Analysis Report") + markdown.append("") + + # Basic Information + markdown.append("## Basic Information") + markdown.append("") + markdown.append(f"**Company:** {trading_data.get('company_of_interest', 'N/A')}") + markdown.append(f"**Trade Date:** {trading_data.get('trade_date', 'N/A')}") + markdown.append("") + + # Market Report (only section we want) + markdown.append(f"## {selected_analysts[0]} Analysis") + markdown.append("") + if "market_report" in trading_data: + market_report = trading_data.get("market_report", "") + if market_report: + markdown.append(market_report) + if "news_report" in trading_data: + news_report = trading_data.get("news_report", "") + if news_report: + markdown.append(news_report) + if "fundamentals_report" in trading_data: + fundamentals_report = trading_data.get("fundamentals_report", "") + if fundamentals_report: + markdown.append(fundamentals_report) + if "sentiment_report" in trading_data: + sentiment_report = trading_data.get("sentiment_report", "") + if sentiment_report: + markdown.append(sentiment_report) + else: + markdown.append("*No market analysis available*") + markdown.append("") + + return "\n".join(markdown) + + + +class TradingAgentAdapter(Adapter): + + def __init__(self): + + # Create a custom config + self.config = DEFAULT_CONFIG.copy() + self.config["deep_think_llm"] = "gpt-4.1-nano" # Use a different model + self.config["quick_think_llm"] = "gpt-4.1-nano" # Use a different model + self.config["max_debate_rounds"] = 1 # Increase debate rounds + self.config["online_tools"] = True # Use online tools or cached data + self.config["max_completion_tokens"] = 1000 + + # Initialize with custom config + # self.ta = TradingAgentsGraph(debug=True, config=self.config, debate=False, selected_analysts=["market", "social", "news", "fundamentals"]) + self.ta = TradingAgentsGraph(debug=True, config=self.config, debate=False, selected_analysts=selected_analysts) + + def run(self, prompt: str) -> str: + """Prompt format must be like this: Ticker,Date""" + try: + # Try to parse as JSON first + received = json.loads(prompt) + # Extract text from the structure + if isinstance(received, dict) and 'parts' in received and received['parts']: + result = received['parts'][0]['text'] + else: + result = str(received) + except (json.JSONDecodeError, KeyError, TypeError): + # If not JSON or structure doesn't match, use prompt as is + result = prompt + log.debug(f"prompt: {result}") + + Ticker = prompt.split(",")[0] + Date = prompt.split(",")[1] + print(f"Ticker: {Ticker}, Date: {Date}") + final_state, decision = self.ta.propagate(Ticker, Date) + + # path = f"eval_results/{Ticker}/TradingAgentsStrategy_logs/full_states_log_{Date}.json" + # # read the json file + # with open(path, 'r') as f: + # final_state = json.load(f) + + # final_state is already a dict, no need to parse it + markdown_content = json_to_markdown(final_state) + + return markdown_content + + def get_adapter_card(self) -> AdapterCard: + return AdapterCard( + name="Random Number Generator", + bio="", + lore="", + knowledge="", + routine="", + ) + +# Create the server node. +etcd_registry = EtcdRegistry(host="47.236.116.81", port=2379) +# Create the server node. +server_node = Node(node_id=NODE_ID, port=8866, p2p=True, p2p_server_port=9000, adapter=TradingAgentAdapter(), registry=etcd_registry) + +# Start the server in the foreground. +server_node.build_server(daemon=False) +# print(server_node.adapter.run("random a number 0-10")) + diff --git a/Trading_agent_p2p_server_market.py b/Trading_agent_p2p_server_market.py new file mode 100644 index 00000000..a3be431e --- /dev/null +++ b/Trading_agent_p2p_server_market.py @@ -0,0 +1,132 @@ +from tradingagents.graph.trading_graph import TradingAgentsGraph +from tradingagents.default_config import DEFAULT_CONFIG + +from isek.adapter.base import Adapter, AdapterCard +from isek.node.etcd_registry import EtcdRegistry +from isek.node.node_v2 import Node +import dotenv +from isek.utils.log import LoggerManager +from isek.utils.log import log +import json + +LoggerManager.plain_mode() +dotenv.load_dotenv() + +NODE_ID = "TA_Agent_Market" +# selected_analysts=["market", "social", "news", "fundamentals"] +selected_analysts=["market"] + +def json_to_markdown(data: dict) -> str: + """ + Convert trading analysis JSON to formatted markdown string, extracting only market analysis. + Assumes input is a dict with a single key (date), and the value is the trading data dict. + """ + # Extract the first value (trading data dict) + trading_data = next(iter(data.values())) + + # Start building markdown + markdown = [] + + # Header + markdown.append("# Market Analysis Report") + markdown.append("") + + # Basic Information + markdown.append("## Basic Information") + markdown.append("") + markdown.append(f"**Company:** {trading_data.get('company_of_interest', 'N/A')}") + markdown.append(f"**Trade Date:** {trading_data.get('trade_date', 'N/A')}") + markdown.append("") + + # Market Report (only section we want) + markdown.append(f"## {selected_analysts[0]} Analysis") + + markdown.append("") + if "market_report" in trading_data: + market_report = trading_data.get("market_report", "") + if market_report: + markdown.append(market_report) + if "news_report" in trading_data: + news_report = trading_data.get("news_report", "") + if news_report: + markdown.append(news_report) + if "fundamentals_report" in trading_data: + fundamentals_report = trading_data.get("fundamentals_report", "") + if fundamentals_report: + markdown.append(fundamentals_report) + if "sentiment_report" in trading_data: + sentiment_report = trading_data.get("sentiment_report", "") + if sentiment_report: + markdown.append(sentiment_report) + else: + markdown.append("*No market analysis available*") + markdown.append("") + + return "\n".join(markdown) + + + +class TradingAgentAdapter(Adapter): + + def __init__(self): + + # Create a custom config + self.config = DEFAULT_CONFIG.copy() + self.config["deep_think_llm"] = "gpt-4.1-nano" # Use a different model + self.config["quick_think_llm"] = "gpt-4.1-nano" # Use a different model + self.config["max_debate_rounds"] = 1 # Increase debate rounds + self.config["online_tools"] = True # Use online tools or cached data + self.config["max_completion_tokens"] = 1000 + + # Initialize with custom config + # self.ta = TradingAgentsGraph(debug=True, config=self.config, debate=False, selected_analysts=["market", "social", "news", "fundamentals"]) + self.ta = TradingAgentsGraph(debug=True, config=self.config, debate=False, selected_analysts=selected_analysts) + + def run(self, prompt: str) -> str: + """Prompt format must be like this: Ticker,Date""" + try: + # Try to parse as JSON first + received = json.loads(prompt) + # Extract text from the structure + if isinstance(received, dict) and 'parts' in received and received['parts']: + result = received['parts'][0]['text'] + else: + result = str(received) + except (json.JSONDecodeError, KeyError, TypeError): + # If not JSON or structure doesn't match, use prompt as is + result = prompt + log.debug(f"prompt: {result}") + + Ticker = prompt.split(",")[0] + Date = prompt.split(",")[1] + print(f"Ticker: {Ticker}, Date: {Date}") + final_state, decision = self.ta.propagate(Ticker, Date) + + # path = f"eval_results/{Ticker}/TradingAgentsStrategy_logs/full_states_log_{Date}.json" + # # read the json file + # with open(path, 'r') as f: + # final_state = json.load(f) + + # final_state is already a dict, no need to parse it + markdown_content = json_to_markdown(final_state) + + return markdown_content + + def get_adapter_card(self) -> AdapterCard: + return AdapterCard( + name="Random Number Generator", + bio="", + lore="", + knowledge="", + routine="", + ) + +# Create the server node. +etcd_registry = EtcdRegistry(host="47.236.116.81", port=2379) +# Create the server node. +server_node = Node(node_id=NODE_ID, port=8867, p2p=True, p2p_server_port=9000, adapter=TradingAgentAdapter(), registry=etcd_registry) + +# Start the server in the foreground. +server_node.build_server(daemon=False) +# print(server_node.adapter.run("random a number 0-10")) + diff --git a/Trading_agent_p2p_server_news.py b/Trading_agent_p2p_server_news.py new file mode 100644 index 00000000..f6f15497 --- /dev/null +++ b/Trading_agent_p2p_server_news.py @@ -0,0 +1,132 @@ +from tradingagents.graph.trading_graph import TradingAgentsGraph +from tradingagents.default_config import DEFAULT_CONFIG + +from isek.adapter.base import Adapter, AdapterCard +from isek.node.etcd_registry import EtcdRegistry +from isek.node.node_v2 import Node +import dotenv +from isek.utils.log import LoggerManager +from isek.utils.log import log +import json + +LoggerManager.plain_mode() +dotenv.load_dotenv() + +NODE_ID = "TA_Agent_News" +# selected_analysts=["market", "social", "news", "fundamentals"] +selected_analysts=["news"] + +def json_to_markdown(data: dict) -> str: + """ + Convert trading analysis JSON to formatted markdown string, extracting only market analysis. + Assumes input is a dict with a single key (date), and the value is the trading data dict. + """ + # Extract the first value (trading data dict) + trading_data = next(iter(data.values())) + + # Start building markdown + markdown = [] + + # Header + markdown.append("# Analysis Report") + markdown.append("") + + # Basic Information + markdown.append("## Basic Information") + markdown.append("") + markdown.append(f"**Company:** {trading_data.get('company_of_interest', 'N/A')}") + markdown.append(f"**Trade Date:** {trading_data.get('trade_date', 'N/A')}") + markdown.append("") + + # Market Report (only section we want) + markdown.append(f"## {selected_analysts[0]} Analysis") + + markdown.append("") + if "market_report" in trading_data: + market_report = trading_data.get("market_report", "") + if market_report: + markdown.append(market_report) + if "news_report" in trading_data: + news_report = trading_data.get("news_report", "") + if news_report: + markdown.append(news_report) + if "fundamentals_report" in trading_data: + fundamentals_report = trading_data.get("fundamentals_report", "") + if fundamentals_report: + markdown.append(fundamentals_report) + if "sentiment_report" in trading_data: + sentiment_report = trading_data.get("sentiment_report", "") + if sentiment_report: + markdown.append(sentiment_report) + else: + markdown.append("*No market analysis available*") + markdown.append("") + + return "\n".join(markdown) + + + +class TradingAgentAdapter(Adapter): + + def __init__(self): + + # Create a custom config + self.config = DEFAULT_CONFIG.copy() + self.config["deep_think_llm"] = "gpt-4.1-nano" # Use a different model + self.config["quick_think_llm"] = "gpt-4.1-nano" # Use a different model + self.config["max_debate_rounds"] = 1 # Increase debate rounds + self.config["online_tools"] = True # Use online tools or cached data + self.config["max_completion_tokens"] = 1000 + + # Initialize with custom config + # self.ta = TradingAgentsGraph(debug=True, config=self.config, debate=False, selected_analysts=["market", "social", "news", "fundamentals"]) + self.ta = TradingAgentsGraph(debug=True, config=self.config, debate=False, selected_analysts=selected_analysts) + + def run(self, prompt: str) -> str: + """Prompt format must be like this: Ticker,Date""" + try: + # Try to parse as JSON first + received = json.loads(prompt) + # Extract text from the structure + if isinstance(received, dict) and 'parts' in received and received['parts']: + result = received['parts'][0]['text'] + else: + result = str(received) + except (json.JSONDecodeError, KeyError, TypeError): + # If not JSON or structure doesn't match, use prompt as is + result = prompt + log.debug(f"prompt: {result}") + + Ticker = prompt.split(",")[0] + Date = prompt.split(",")[1] + print(f"Ticker: {Ticker}, Date: {Date}") + final_state, decision = self.ta.propagate(Ticker, Date) + + # path = f"eval_results/{Ticker}/TradingAgentsStrategy_logs/full_states_log_{Date}.json" + # # read the json file + # with open(path, 'r') as f: + # final_state = json.load(f) + + # final_state is already a dict, no need to parse it + markdown_content = json_to_markdown(final_state) + + return markdown_content + + def get_adapter_card(self) -> AdapterCard: + return AdapterCard( + name="Random Number Generator", + bio="", + lore="", + knowledge="", + routine="", + ) + +# Create the server node. +etcd_registry = EtcdRegistry(host="47.236.116.81", port=2379) +# Create the server node. +server_node = Node(node_id=NODE_ID, port=8868, p2p=True, p2p_server_port=9000, adapter=TradingAgentAdapter(), registry=etcd_registry) + +# Start the server in the foreground. +server_node.build_server(daemon=False) +# print(server_node.adapter.run("random a number 0-10")) + diff --git a/Trading_agent_p2p_server_social.py b/Trading_agent_p2p_server_social.py new file mode 100644 index 00000000..23bacd03 --- /dev/null +++ b/Trading_agent_p2p_server_social.py @@ -0,0 +1,132 @@ +from tradingagents.graph.trading_graph import TradingAgentsGraph +from tradingagents.default_config import DEFAULT_CONFIG + +from isek.adapter.base import Adapter, AdapterCard +from isek.node.etcd_registry import EtcdRegistry +from isek.node.node_v2 import Node +import dotenv +from isek.utils.log import LoggerManager +from isek.utils.log import log +import json + +LoggerManager.plain_mode() +dotenv.load_dotenv() + +NODE_ID = "TA_Agent_Social" +# selected_analysts=["market", "social", "news", "fundamentals"] +selected_analysts=["social"] + +def json_to_markdown(data: dict) -> str: + """ + Convert trading analysis JSON to formatted markdown string, extracting only market analysis. + Assumes input is a dict with a single key (date), and the value is the trading data dict. + """ + # Extract the first value (trading data dict) + trading_data = next(iter(data.values())) + + # Start building markdown + markdown = [] + + # Header + markdown.append("# Market Analysis Report") + markdown.append("") + + # Basic Information + markdown.append("## Basic Information") + markdown.append("") + markdown.append(f"**Company:** {trading_data.get('company_of_interest', 'N/A')}") + markdown.append(f"**Trade Date:** {trading_data.get('trade_date', 'N/A')}") + markdown.append("") + + # Market Report (only section we want) + markdown.append(f"## {selected_analysts[0]} Analysis") + + markdown.append("") + if "market_report" in trading_data: + market_report = trading_data.get("market_report", "") + if market_report: + markdown.append(market_report) + if "news_report" in trading_data: + news_report = trading_data.get("news_report", "") + if news_report: + markdown.append(news_report) + if "fundamentals_report" in trading_data: + fundamentals_report = trading_data.get("fundamentals_report", "") + if fundamentals_report: + markdown.append(fundamentals_report) + if "sentiment_report" in trading_data: + sentiment_report = trading_data.get("sentiment_report", "") + if sentiment_report: + markdown.append(sentiment_report) + else: + markdown.append("*No market analysis available*") + markdown.append("") + + return "\n".join(markdown) + + + +class TradingAgentAdapter(Adapter): + + def __init__(self): + + # Create a custom config + self.config = DEFAULT_CONFIG.copy() + self.config["deep_think_llm"] = "gpt-4.1-nano" # Use a different model + self.config["quick_think_llm"] = "gpt-4.1-nano" # Use a different model + self.config["max_debate_rounds"] = 1 # Increase debate rounds + self.config["online_tools"] = True # Use online tools or cached data + self.config["max_completion_tokens"] = 1000 + + # Initialize with custom config + # self.ta = TradingAgentsGraph(debug=True, config=self.config, debate=False, selected_analysts=["market", "social", "news", "fundamentals"]) + self.ta = TradingAgentsGraph(debug=True, config=self.config, debate=False, selected_analysts=selected_analysts) + + def run(self, prompt: str) -> str: + """Prompt format must be like this: Ticker,Date""" + try: + # Try to parse as JSON first + received = json.loads(prompt) + # Extract text from the structure + if isinstance(received, dict) and 'parts' in received and received['parts']: + result = received['parts'][0]['text'] + else: + result = str(received) + except (json.JSONDecodeError, KeyError, TypeError): + # If not JSON or structure doesn't match, use prompt as is + result = prompt + log.debug(f"prompt: {result}") + + Ticker = prompt.split(",")[0] + Date = prompt.split(",")[1] + print(f"Ticker: {Ticker}, Date: {Date}") + final_state, decision = self.ta.propagate(Ticker, Date) + + # path = f"eval_results/{Ticker}/TradingAgentsStrategy_logs/full_states_log_{Date}.json" + # # read the json file + # with open(path, 'r') as f: + # final_state = json.load(f) + + # final_state is already a dict, no need to parse it + markdown_content = json_to_markdown(final_state) + + return markdown_content + + def get_adapter_card(self) -> AdapterCard: + return AdapterCard( + name="Random Number Generator", + bio="", + lore="", + knowledge="", + routine="", + ) + +# Create the server node. +etcd_registry = EtcdRegistry(host="47.236.116.81", port=2379) +# Create the server node. +server_node = Node(node_id=NODE_ID, port=8869, p2p=True, p2p_server_port=9000, adapter=TradingAgentAdapter(), registry=etcd_registry) + +# Start the server in the foreground. +server_node.build_server(daemon=False) +# print(server_node.adapter.run("random a number 0-10")) + diff --git a/chainlit.md b/chainlit.md new file mode 100644 index 00000000..4507ac46 --- /dev/null +++ b/chainlit.md @@ -0,0 +1,14 @@ +# Welcome to Chainlit! 🚀🤖 + +Hi there, Developer! 👋 We're excited to have you on board. Chainlit is a powerful tool designed to help you prototype, debug and share applications built on top of LLMs. + +## Useful Links 🔗 + +- **Documentation:** Get started with our comprehensive [Chainlit Documentation](https://docs.chainlit.io) 📚 +- **Discord Community:** Join our friendly [Chainlit Discord](https://discord.gg/k73SQ3FyUh) to ask questions, share your projects, and connect with other developers! 💬 + +We can't wait to see what you create with Chainlit! Happy coding! 💻😊 + +## Welcome screen + +To modify the welcome screen, edit the `chainlit.md` file at the root of your project. If you do not want a welcome screen, just leave this file empty. diff --git a/chainlit_ui.py b/chainlit_ui.py new file mode 100644 index 00000000..ad95dcbb --- /dev/null +++ b/chainlit_ui.py @@ -0,0 +1,98 @@ +import chainlit as cl +import os +from dotenv import load_dotenv +from isek.node.node_v2 import Node +from isek.utils.log import log +from isek.node.etcd_registry import EtcdRegistry + +# Load environment variables +load_dotenv() + +# Server configuration +SERVER_NODE_ID = "TA_Agent" + +# Global client node instance +client_node = None + +@cl.on_chat_start +async def start(): + """Initialize the client connection to the ISEK server""" + global client_node + + try: + # Create a client node to send messages + EXAMPLE_REGISTRY_HOST = "47.236.116.81" + + # Create the server node. + etcd_registry = EtcdRegistry(host=EXAMPLE_REGISTRY_HOST, port=2379) + client_node = Node(node_id="Lyra_client", port=8889, p2p=True, p2p_server_port=9001, registry=etcd_registry) + # Start the server in the foreground. + client_node.build_server(daemon=True) + + # example of agent card + # • AdapterCard(name='SimpleAdapter', + # bio='A simple adapter for testing', + # lore='Created for testing purposes', + # knowledge='Basic testing knowledge', + # routine='Respond to messages') + # Send welcome message + await cl.Message( + content=f"🤖 Welcome to ISEK Agent Interface!\n\n" + f"I'm connected to your ISEK agent server. You can now interact with the agent ", + author="System" + ).send() + + log.info("Chainlit client connected to ISEK server") + + except Exception as e: + await cl.Message( + content=f"❌ Failed to connect to ISEK server: {str(e)}\n\n" + "Please make sure the agent server is running on localhost:9006", + author="System" + ).send() + log.error(f"Failed to connect to ISEK server: {e}") + +@cl.on_message +async def main(message: cl.Message): + """Handle incoming messages and forward them to the ISEK agent""" + global client_node + + if client_node is None: + await cl.Message( + content="❌ Client not initialized. Please refresh the page.", + author="System" + ).send() + return + + try: + # Send message to ISEK agent and get response + response = client_node.send_message(SERVER_NODE_ID, message.content) + + # Show agent response + if response is not None: + await cl.Message( + content=str(response), + author="ISEK Agent" + ).send() + else: + await cl.Message( + content="No response received from agent", + author="System" + ).send() + + except Exception as e: + error_msg = f"❌ Error communicating with agent: {str(e)}" + await cl.Message( + content=error_msg, + author="System" + ).send() + log.error(f"Error in message handling: {e}") + +@cl.on_chat_end +async def end(): + """Clean up when chat ends""" + global client_node + client_node = None + log.info("Chainlit client disconnected") + +# Note: Chat profile configuration has been removed as it's not supported in current Chainlit version \ No newline at end of file diff --git a/client_test.py b/client_test.py new file mode 100644 index 00000000..1e31d6a1 --- /dev/null +++ b/client_test.py @@ -0,0 +1,33 @@ +from isek.node.etcd_registry import EtcdRegistry +from isek.node.node_v2 import Node +from isek.utils.log import LoggerManager +from isek.utils.print_utils import print_send_message_result, print_panel + +LoggerManager.plain_mode() +EXAMPLE_REGISTRY_HOST = "47.236.116.81" +SERVER_NODE_ID = "TA_Agent_News" +SERVER_NODE_ID = "TA_Agent_Market" +SERVER_NODE_ID = "TA_Agent_Social" +# SERVER_NODE_ID = "TA_Agent_Fundamentals" + +# Create the server node. +etcd_registry = EtcdRegistry(host=EXAMPLE_REGISTRY_HOST, port=2379) +client_node = Node(node_id="RN_client", port=8889, p2p=True, p2p_server_port=9001, registry=etcd_registry) + +# Start the server in the foreground. +client_node.build_server(daemon=True) + +print_panel(title="LV10 P2P Node Client", + content="This Client accesses RN node through the p2p protocol." + "\nAnd demonstrate the autonomous discovery of nodes through the registration center", + color="bright_yellow") + +print_send_message_result( + lambda msg: client_node.send_message(SERVER_NODE_ID, msg), + source_node_id=client_node.node_id, + target_node_id=SERVER_NODE_ID, + message="NVDA,2024-01-03" + ) + +# reply = client_node.send_message("RN", "random a number 10-100") +# print(f"RN say:\n{reply}") diff --git a/display_json.py b/display_json.py new file mode 100755 index 00000000..21efbe91 --- /dev/null +++ b/display_json.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 +""" +JSON Pretty Display Script for Trading Analysis Data +Displays trading strategy logs with color coding and formatting +""" + +import json +import sys +import os +from typing import Dict, Any, Optional +from datetime import datetime +import argparse + +# Color codes for terminal output +class Colors: + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + END = '\033[0m' + +def print_header(text: str, color: str = Colors.HEADER): + """Print a formatted header""" + print(f"\n{color}{'='*80}") + print(f"{text.center(80)}") + print(f"{'='*80}{Colors.END}\n") + +def print_section(text: str, color: str = Colors.BLUE): + """Print a section header""" + print(f"\n{color}{'─'*60}") + print(f" {text}") + print(f"{'─'*60}{Colors.END}") + +def print_key_value(key: str, value: str, max_width: int = 100): + """Print a key-value pair with formatting""" + if len(value) > max_width: + # Truncate and add ellipsis + truncated = value[:max_width-3] + "..." + print(f"{Colors.CYAN}{key}:{Colors.END} {truncated}") + print(f"{Colors.YELLOW} (truncated - full text available in raw JSON){Colors.END}") + else: + print(f"{Colors.CYAN}{key}:{Colors.END} {value}") + +def format_text_block(text: str, indent: int = 2, max_width: int = 80) -> str: + """Format a long text block with proper wrapping""" + if not text: + return "" + + # Split by newlines and format each paragraph + paragraphs = text.split('\n\n') + formatted_paragraphs = [] + + for paragraph in paragraphs: + if not paragraph.strip(): + continue + + # Handle markdown headers + if paragraph.startswith('###'): + formatted_paragraphs.append(f"{Colors.GREEN}{paragraph}{Colors.END}") + elif paragraph.startswith('**') and paragraph.endswith('**'): + formatted_paragraphs.append(f"{Colors.BOLD}{paragraph}{Colors.END}") + else: + # Simple text wrapping + words = paragraph.split() + lines = [] + current_line = " " * indent + + for word in words: + if len(current_line + word) < max_width: + current_line += word + " " + else: + lines.append(current_line.strip()) + current_line = " " * indent + word + " " + + if current_line.strip(): + lines.append(current_line.strip()) + + formatted_paragraphs.append('\n'.join(lines)) + + return '\n\n'.join(formatted_paragraphs) + +def display_trading_data(data: Dict[str, Any]): + """Display the trading analysis data in a formatted way""" + + # Get the main date key (assuming it's the first key) + date_key = list(data.keys())[0] + trading_data = data[date_key] + + print_header(f"Trading Analysis Report - {date_key}", Colors.HEADER) + + # Basic information + print_section("Basic Information") + print_key_value("Company", trading_data.get("company_of_interest", "N/A")) + print_key_value("Trade Date", trading_data.get("trade_date", "N/A")) + + # Market Report + print_section("Market Analysis Report") + market_report = trading_data.get("market_report", "") + if market_report: + print(format_text_block(market_report)) + else: + print(f"{Colors.RED}No market report available{Colors.END}") + + # Sentiment Report + print_section("Sentiment Analysis") + sentiment_report = trading_data.get("sentiment_report", "") + if sentiment_report: + print(format_text_block(sentiment_report)) + else: + print(f"{Colors.RED}No sentiment report available{Colors.END}") + + # News Report + print_section("News Analysis") + news_report = trading_data.get("news_report", "") + if news_report: + print(format_text_block(news_report)) + else: + print(f"{Colors.RED}No news report available{Colors.END}") + + # Fundamentals Report + print_section("Fundamentals Analysis") + fundamentals_report = trading_data.get("fundamentals_report", "") + if fundamentals_report: + print(format_text_block(fundamentals_report)) + else: + print(f"{Colors.RED}No fundamentals report available{Colors.END}") + + # Investment Decision + print_section("Investment Decision") + investment_decision = trading_data.get("trader_investment_decision", "") + if investment_decision: + print(format_text_block(investment_decision)) + else: + print(f"{Colors.RED}No investment decision available{Colors.END}") + + # Investment Plan + print_section("Investment Plan") + investment_plan = trading_data.get("investment_plan", "") + if investment_plan: + print(format_text_block(investment_plan)) + else: + print(f"{Colors.RED}No investment plan available{Colors.END}") + + # Final Trade Decision + print_section("Final Trade Decision") + final_decision = trading_data.get("final_trade_decision", "") + if final_decision: + print(format_text_block(final_decision)) + else: + print(f"{Colors.RED}No final trade decision available{Colors.END}") + + # Debate States + if "investment_debate_state" in trading_data: + print_section("Investment Debate Analysis") + debate_state = trading_data["investment_debate_state"] + + if "judge_decision" in debate_state: + print(f"{Colors.BOLD}Judge Decision:{Colors.END}") + print(format_text_block(debate_state["judge_decision"])) + + if "risk_debate_state" in trading_data: + print_section("Risk Management Debate") + risk_state = trading_data["risk_debate_state"] + + if "judge_decision" in risk_state: + print(f"{Colors.BOLD}Risk Judge Decision:{Colors.END}") + print(format_text_block(risk_state["judge_decision"])) + +def display_raw_json(data: Dict[str, Any], indent: int = 2): + """Display the raw JSON with proper formatting""" + print_header("Raw JSON Data", Colors.YELLOW) + print(json.dumps(data, indent=indent, ensure_ascii=False)) + +def interactive_menu(data: Dict[str, Any]): + """Provide an interactive menu for exploring the data""" + while True: + print(f"\n{Colors.CYAN}Interactive Menu:{Colors.END}") + print("1. View formatted trading analysis") + print("2. View raw JSON data") + print("3. Search for specific content") + print("4. Export to formatted text file") + print("5. Exit") + + choice = input(f"\n{Colors.YELLOW}Enter your choice (1-5): {Colors.END}").strip() + + if choice == "1": + display_trading_data(data) + elif choice == "2": + display_raw_json(data) + elif choice == "3": + search_content(data) + elif choice == "4": + export_to_file(data) + elif choice == "5": + print(f"{Colors.GREEN}Goodbye!{Colors.END}") + break + else: + print(f"{Colors.RED}Invalid choice. Please try again.{Colors.END}") + +def search_content(data: Dict[str, Any]): + """Search for specific content in the data""" + search_term = input(f"{Colors.YELLOW}Enter search term: {Colors.END}").strip().lower() + + if not search_term: + return + + print(f"\n{Colors.CYAN}Searching for '{search_term}'...{Colors.END}\n") + + found = False + date_key = list(data.keys())[0] + trading_data = data[date_key] + + for key, value in trading_data.items(): + if isinstance(value, str) and search_term in value.lower(): + print_section(f"Found in: {key}") + # Find the context around the search term + words = value.split() + for i, word in enumerate(words): + if search_term in word.lower(): + start = max(0, i-5) + end = min(len(words), i+6) + context = " ".join(words[start:end]) + print(f"...{context}...") + found = True + break + + if not found: + print(f"{Colors.RED}No matches found for '{search_term}'{Colors.END}") + +def export_to_file(data: Dict[str, Any]): + """Export the formatted data to a text file""" + filename = input(f"{Colors.YELLOW}Enter filename (default: trading_analysis.txt): {Colors.END}").strip() + if not filename: + filename = "trading_analysis.txt" + + if not filename.endswith('.txt'): + filename += '.txt' + + try: + with open(filename, 'w', encoding='utf-8') as f: + # Redirect stdout to capture the formatted output + import io + import contextlib + + output = io.StringIO() + with contextlib.redirect_stdout(output): + display_trading_data(data) + + f.write(output.getvalue()) + + print(f"{Colors.GREEN}Data exported to {filename}{Colors.END}") + except Exception as e: + print(f"{Colors.RED}Error exporting file: {e}{Colors.END}") + +def main(): + parser = argparse.ArgumentParser(description="Pretty display JSON trading analysis data") + parser.add_argument("json_file", help="Path to the JSON file to display") + parser.add_argument("--raw", action="store_true", help="Display raw JSON only") + parser.add_argument("--interactive", action="store_true", help="Start interactive mode") + parser.add_argument("--export", help="Export formatted data to specified file") + + args = parser.parse_args() + + # Check if file exists + if not os.path.exists(args.json_file): + print(f"{Colors.RED}Error: File '{args.json_file}' not found{Colors.END}") + sys.exit(1) + + try: + with open(args.json_file, 'r', encoding='utf-8') as f: + data = json.load(f) + except json.JSONDecodeError as e: + print(f"{Colors.RED}Error: Invalid JSON file - {e}{Colors.END}") + sys.exit(1) + except Exception as e: + print(f"{Colors.RED}Error reading file: {e}{Colors.END}") + sys.exit(1) + + # Handle different display modes + if args.raw: + display_raw_json(data) + elif args.interactive: + interactive_menu(data) + elif args.export: + try: + with open(args.export, 'w', encoding='utf-8') as f: + import io + import contextlib + + output = io.StringIO() + with contextlib.redirect_stdout(output): + display_trading_data(data) + + f.write(output.getvalue()) + print(f"{Colors.GREEN}Data exported to {args.export}{Colors.END}") + except Exception as e: + print(f"{Colors.RED}Error exporting file: {e}{Colors.END}") + else: + # Default: display formatted data + display_trading_data(data) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/test.json b/test.json new file mode 100644 index 00000000..49b4b0b2 --- /dev/null +++ b/test.json @@ -0,0 +1,30 @@ +{ + "final_state": { + "messages": [ + { + "content": "Continue", + "additional_kwargs": {}, + "response_metadata": {}, + "id": "3a5825d2-ec8d-4560-9d08-1db61085ac2f" + } + ], + "company_of_interest": "eth", + "trade_date": "2024-01-01", + "market_report": "The recent ETH/USD data reveals a nuanced trend landscape:\n\n**Price Action & Moving Averages:** The price has been oscillating around the 50 SMA (~2178), which has steadily increased from about 1887 in early December to over 2178 at present, indicating a persistent medium-term upward trend. The 200 SMA (~1864) remains below the current price, reaffirming a bullish bias on a long-term horizon. The price hasn't crossed below the 50 SMA, supporting ongoing upward momentum.\n\n**MACD:** The MACD value stands at approximately 39.50, significantly above zero, suggesting strong bullish momentum. Although recent MACD values have fluctuated, they have remained in positive territory, and the recent readings from December are still indicative of upward acceleration, with no clear divergence signaling a reversal.\n\n**RSI:** The RSI is around 58.7, which is in a neutral zone but leaning slightly towards overbought. This suggests that while the momentum remains positive, the asset is nearing levels where a short-term correction could occur if the RSI approaches or surpasses 70.\n\n**Bollinger Bands:** The current Bollinger middle line (~2270) and the price near the upper band (~2270.7) indicate that ETH is approaching overbought levels relative to recent volatility. The bands are widening, reflecting increased volatility, typical during trend sustainment phases.\n\n### Insights:\n- The consistent upward move above the 50 and 200 SMAs indicates a resilient bullish trend.\n- The MACD confirms strong momentum, but its high level warrants caution for potential short-term pullbacks.\n- The RSI's neutral reading suggests room for continuation, but nearing overbought territory signals watchfulness.\n- Price near the upper Bollinger Band indicates potential for short-term retracement or consolidation before further gains.\n\n### Trading considerations:\n- For bullish continuation: recent bullish momentum is strong, but confirmation with a sustainable MACD crossover or a pullback to the 50 SMA before resuming upward could be prudent.\n- For caution: the proximity to the upper Bollinger Band and RSI nearing overbought suggest possible short-term pullbacks.\n\n**Overall, the trend remains bullish, supported by several key indicators, but short-term caution is advisable given overbought signals.**\n\n| Aspect | Key Point |\n| --- | --- |\n| Price Trend | Upward, near 50 SMA (~2178) and below upper Bollinger Band (~2270) |\n| Long-term", + "sentiment_report": "", + "news_report": "", + "fundamentals_report": "", + "investment_debate_state": { + "history": "", + "current_response": "", + "count": 0 + }, + "risk_debate_state": { + "history": "", + "current_risky_response": "", + "current_safe_response": "", + "current_neutral_response": "", + "count": 0 + } + } +} \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 00000000..4ae590b4 --- /dev/null +++ b/test.py @@ -0,0 +1,184 @@ +from tradingagents.graph.trading_graph import TradingAgentsGraph +from tradingagents.default_config import DEFAULT_CONFIG +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + + + +def json_to_markdown(json_file_path: str) -> str: + """ + Convert trading analysis JSON file to formatted markdown string. + + Args: + json_file_path (str): Path to the JSON file containing trading analysis data + + Returns: + str: Formatted markdown string + """ + import json + import os + + # Check if file exists + if not os.path.exists(json_file_path): + return f"# Error\nFile not found: {json_file_path}" + + try: + with open(json_file_path, 'r', encoding='utf-8') as f: + data = json.load(f) + except json.JSONDecodeError as e: + return f"# Error\nInvalid JSON file: {e}" + except Exception as e: + return f"# Error\nError reading file: {e}" + + # Get the main date key (assuming it's the first key) + date_key = list(data.keys())[0] + trading_data = data[date_key] + + # Start building markdown + markdown = [] + + # Header + markdown.append(f"# Trading Analysis Report - {date_key}") + markdown.append("") + + # Basic Information + markdown.append("## Basic Information") + markdown.append("") + markdown.append(f"**Company:** {trading_data.get('company_of_interest', 'N/A')}") + markdown.append(f"**Trade Date:** {trading_data.get('trade_date', 'N/A')}") + markdown.append("") + + # Market Report + markdown.append("## Market Analysis Report") + markdown.append("") + market_report = trading_data.get("market_report", "") + if market_report: + markdown.append(market_report) + else: + markdown.append("*No market report available*") + markdown.append("") + + # Sentiment Report + markdown.append("## Sentiment Analysis") + markdown.append("") + sentiment_report = trading_data.get("sentiment_report", "") + if sentiment_report: + markdown.append(sentiment_report) + else: + markdown.append("*No sentiment report available*") + markdown.append("") + + # News Report + markdown.append("## News Analysis") + markdown.append("") + news_report = trading_data.get("news_report", "") + if news_report: + markdown.append(news_report) + else: + markdown.append("*No news report available*") + markdown.append("") + + # Fundamentals Report + markdown.append("## Fundamentals Analysis") + markdown.append("") + fundamentals_report = trading_data.get("fundamentals_report", "") + if fundamentals_report: + markdown.append(fundamentals_report) + else: + markdown.append("*No fundamentals report available*") + markdown.append("") + + # Investment Decision + markdown.append("## Investment Decision") + markdown.append("") + investment_decision = trading_data.get("trader_investment_decision", "") + if investment_decision: + markdown.append(investment_decision) + else: + markdown.append("*No investment decision available*") + markdown.append("") + + # Investment Plan + markdown.append("## Investment Plan") + markdown.append("") + investment_plan = trading_data.get("investment_plan", "") + if investment_plan: + markdown.append(investment_plan) + else: + markdown.append("*No investment plan available*") + markdown.append("") + + # Final Trade Decision + markdown.append("## Final Trade Decision") + markdown.append("") + final_decision = trading_data.get("final_trade_decision", "") + if final_decision: + markdown.append(final_decision) + else: + markdown.append("*No final trade decision available*") + markdown.append("") + + # Debate States + if "investment_debate_state" in trading_data: + markdown.append("## Investment Debate Analysis") + markdown.append("") + debate_state = trading_data["investment_debate_state"] + + if "judge_decision" in debate_state: + markdown.append("### Judge Decision") + markdown.append("") + markdown.append(debate_state["judge_decision"]) + markdown.append("") + + if "risk_debate_state" in trading_data: + markdown.append("## Risk Management Debate") + markdown.append("") + risk_state = trading_data["risk_debate_state"] + + if "judge_decision" in risk_state: + markdown.append("### Risk Judge Decision") + markdown.append("") + markdown.append(risk_state["judge_decision"]) + markdown.append("") + + return "\n".join(markdown) + +# Example usage: +if __name__ == "__main__": + + # Create a custom config + config = DEFAULT_CONFIG.copy() + config["deep_think_llm"] = "gpt-4.1-nano" # Use a different model + config["quick_think_llm"] = "gpt-4.1-nano" # Use a different model + config["max_debate_rounds"] = 1 # Increase debate rounds + config["online_tools"] = True # Use online tools or cached data + + # Initialize with custom config + ta = TradingAgentsGraph(debug=False, config=config) + + # forward propagate + Ticker = "BTC" + Date = "2024-05-11" + _, decision = ta.propagate(Ticker, Date) + print("--------------------------------") + print(decision) + print("--------------------------------") + + path = f"eval_results/{Ticker}/TradingAgentsStrategy_logs/full_states_log_{Date}.json" + # Convert the JSON file to markdown + + markdown_content = json_to_markdown(path) + + # Print the markdown content + print("=" * 80) + print("MARKDOWN OUTPUT:") + print("=" * 80) + print(markdown_content) + + # Optionally save to file + markdown_file = f"eval_results/{Ticker}/TradingAgentsStrategy_logs/report_{Date}.md" + with open(markdown_file, 'w', encoding='utf-8') as f: + f.write(markdown_content) + # print(f"\nMarkdown report saved to: {markdown_file}") diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index 716d4de1..5e98dcee 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -22,7 +22,7 @@ def create_fundamentals_analyst(llm, toolkit): system_message = ( "You are a researcher tasked with analyzing fundamental information over the past week about a company. Please write a comprehensive report of the company's fundamental information such as financial documents, company profile, basic company financials, company financial history, insider sentiment and insider transactions to gain a full view of the company's fundamental information to inform traders. Make sure to include as much detail as possible. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions." - + " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.", + + " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read. ", ) prompt = ChatPromptTemplate.from_messages( @@ -36,7 +36,10 @@ def create_fundamentals_analyst(llm, toolkit): " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. The company we want to look at is {ticker}", + "For your reference, the current date is {current_date}. The company we want to look at is {ticker}" + "Make sure to generate less than 500 tokens." + " Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. " + ), MessagesPlaceholder(variable_name="messages"), ] diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index 41ee944b..c44919af 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -61,7 +61,9 @@ Volume-Based Indicators: " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. The company we want to look at is {ticker}", + "For your reference, the current date is {current_date}. The company we want to look at is {ticker}" + "Make sure to generate less than 500 tokens." + " Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. " ), MessagesPlaceholder(variable_name="messages"), ] diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index e1f03aa4..db449fc1 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -33,7 +33,11 @@ def create_news_analyst(llm, toolkit): " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. We are looking at the company {ticker}", + "For your reference, the current date is {current_date}. We are looking at the company {ticker}" + "Make sure to generate less than 500 tokens." + " Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. " + + ), MessagesPlaceholder(variable_name="messages"), ] diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index d556f73a..4ea9ad7a 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -32,7 +32,11 @@ def create_social_media_analyst(llm, toolkit): " If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable," " prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop." " You have access to the following tools: {tool_names}.\n{system_message}" - "For your reference, the current date is {current_date}. The current company we want to analyze is {ticker}", + "For your reference, the current date is {current_date}. The current company we want to analyze is {ticker}" + "Make sure to generate less than 500 tokens." + " Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. " + + ), MessagesPlaceholder(variable_name="messages"), ] diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index c537fa2f..242d1091 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -29,6 +29,7 @@ Your Recommendation: A decisive stance supported by the most convincing argument Rationale: An explanation of why these arguments lead to your conclusion. Strategic Actions: Concrete steps for implementing the recommendation. Take into account your past mistakes on similar situations. Use these insights to refine your decision-making and ensure you are learning and improving. Present your analysis conversationally, as if speaking naturally, without special formatting. +Make sure to generate less than 500 tokens. Here are your past reflections on mistakes: \"{past_memory_str}\" diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index fba763d6..39915879 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -33,7 +33,7 @@ Guidelines for Decision-Making: Deliverables: - A clear and actionable recommendation: Buy, Sell, or Hold. - Detailed reasoning anchored in the debate and past reflections. - +- Make sure to generate less than 500 tokens. --- **Analysts Debate History:** diff --git a/tradingagents/agents/researchers/bear_researcher.py b/tradingagents/agents/researchers/bear_researcher.py index 6634490a..e7e82624 100644 --- a/tradingagents/agents/researchers/bear_researcher.py +++ b/tradingagents/agents/researchers/bear_researcher.py @@ -42,6 +42,7 @@ Conversation history of the debate: {history} Last bull argument: {current_response} Reflections from similar situations and lessons learned: {past_memory_str} Use this information to deliver a compelling bear argument, refute the bull's claims, and engage in a dynamic debate that demonstrates the risks and weaknesses of investing in the stock. You must also address reflections and learn from lessons and mistakes you made in the past. +Make sure to generate less than 500 tokens. """ response = llm.invoke(prompt) diff --git a/tradingagents/agents/researchers/bull_researcher.py b/tradingagents/agents/researchers/bull_researcher.py index b03ef755..bd79a55e 100644 --- a/tradingagents/agents/researchers/bull_researcher.py +++ b/tradingagents/agents/researchers/bull_researcher.py @@ -40,6 +40,8 @@ Conversation history of the debate: {history} Last bear argument: {current_response} Reflections from similar situations and lessons learned: {past_memory_str} Use this information to deliver a compelling bull argument, refute the bear's concerns, and engage in a dynamic debate that demonstrates the strengths of the bull position. You must also address reflections and learn from lessons and mistakes you made in the past. +Make sure to generate less than 500 tokens. + """ response = llm.invoke(prompt) diff --git a/tradingagents/agents/risk_mgmt/aggresive_debator.py b/tradingagents/agents/risk_mgmt/aggresive_debator.py index 7e2b4937..d806d801 100644 --- a/tradingagents/agents/risk_mgmt/aggresive_debator.py +++ b/tradingagents/agents/risk_mgmt/aggresive_debator.py @@ -30,7 +30,10 @@ Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} Here is the current conversation history: {history} Here are the last arguments from the conservative analyst: {current_safe_response} Here are the last arguments from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point. -Engage actively by addressing any specific concerns raised, refuting the weaknesses in their logic, and asserting the benefits of risk-taking to outpace market norms. Maintain a focus on debating and persuading, not just presenting data. Challenge each counterpoint to underscore why a high-risk approach is optimal. Output conversationally as if you are speaking without any special formatting.""" +Engage actively by addressing any specific concerns raised, refuting the weaknesses in their logic, and asserting the benefits of risk-taking to outpace market norms. Maintain a focus on debating and persuading, not just presenting data. Challenge each counterpoint to underscore why a high-risk approach is optimal. Output conversationally as if you are speaking without any special formatting. +Make sure to generate less than 500 tokens. + +""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/risk_mgmt/conservative_debator.py b/tradingagents/agents/risk_mgmt/conservative_debator.py index c56e16ad..34080ae4 100644 --- a/tradingagents/agents/risk_mgmt/conservative_debator.py +++ b/tradingagents/agents/risk_mgmt/conservative_debator.py @@ -31,7 +31,10 @@ Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point. -Engage by questioning their optimism and emphasizing the potential downsides they may have overlooked. Address each of their counterpoints to showcase why a conservative stance is ultimately the safest path for the firm's assets. Focus on debating and critiquing their arguments to demonstrate the strength of a low-risk strategy over their approaches. Output conversationally as if you are speaking without any special formatting.""" +Engage by questioning their optimism and emphasizing the potential downsides they may have overlooked. Address each of their counterpoints to showcase why a conservative stance is ultimately the safest path for the firm's assets. Focus on debating and critiquing their arguments to demonstrate the strength of a low-risk strategy over their approaches. Output conversationally as if you are speaking without any special formatting. +Make sure to generate less than 500 tokens. + +""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/risk_mgmt/neutral_debator.py b/tradingagents/agents/risk_mgmt/neutral_debator.py index a6d2ef5c..7b0677e4 100644 --- a/tradingagents/agents/risk_mgmt/neutral_debator.py +++ b/tradingagents/agents/risk_mgmt/neutral_debator.py @@ -30,7 +30,10 @@ Latest World Affairs Report: {news_report} Company Fundamentals Report: {fundamentals_report} Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the safe analyst: {current_safe_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point. -Engage actively by analyzing both sides critically, addressing weaknesses in the risky and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting.""" +Engage actively by analyzing both sides critically, addressing weaknesses in the risky and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting. +Make sure to generate less than 500 tokens. + +""" response = llm.invoke(prompt) diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index 1b05c35d..c822baed 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -24,7 +24,7 @@ def create_trader(llm, memory): context = { "role": "user", - "content": f"Based on a comprehensive analysis by a team of analysts, here is an investment plan tailored for {company_name}. This plan incorporates insights from current technical market trends, macroeconomic indicators, and social media sentiment. Use this plan as a foundation for evaluating your next trading decision.\n\nProposed Investment Plan: {investment_plan}\n\nLeverage these insights to make an informed and strategic decision.", + "content": f"Based on a comprehensive analysis by a team of analysts, here is an investment plan tailored for {company_name}. This plan incorporates insights from current technical market trends, macroeconomic indicators, and social media sentiment. Use this plan as a foundation for evaluating your next trading decision.\n\nProposed Investment Plan: {investment_plan}\n\nLeverage these insights to make an informed and strategic decision. Make sure to generate less than 500 tokens.", } messages = [ diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 089e9c24..d771c79f 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -19,4 +19,5 @@ DEFAULT_CONFIG = { "max_recur_limit": 100, # Tool settings "online_tools": True, + "max_completion_tokens": 500, } diff --git a/tradingagents/graph/setup.py b/tradingagents/graph/setup.py index 847c429f..3283132f 100644 --- a/tradingagents/graph/setup.py +++ b/tradingagents/graph/setup.py @@ -27,6 +27,7 @@ class GraphSetup: invest_judge_memory, risk_manager_memory, conditional_logic: ConditionalLogic, + debate: bool = False ): """Initialize with required components.""" self.quick_thinking_llm = quick_thinking_llm @@ -39,7 +40,7 @@ class GraphSetup: self.invest_judge_memory = invest_judge_memory self.risk_manager_memory = risk_manager_memory self.conditional_logic = conditional_logic - + self.debate = debate def setup_graph( self, selected_analysts=["market", "social", "news", "fundamentals"] ): @@ -88,25 +89,26 @@ class GraphSetup: delete_nodes["fundamentals"] = create_msg_delete() tool_nodes["fundamentals"] = self.tool_nodes["fundamentals"] - # Create researcher and manager nodes - bull_researcher_node = create_bull_researcher( - self.quick_thinking_llm, self.bull_memory - ) - bear_researcher_node = create_bear_researcher( - self.quick_thinking_llm, self.bear_memory - ) - research_manager_node = create_research_manager( - self.deep_thinking_llm, self.invest_judge_memory - ) - trader_node = create_trader(self.quick_thinking_llm, self.trader_memory) + if self.debate: + # Create researcher and manager nodes + bull_researcher_node = create_bull_researcher( + self.quick_thinking_llm, self.bull_memory + ) + bear_researcher_node = create_bear_researcher( + self.quick_thinking_llm, self.bear_memory + ) + research_manager_node = create_research_manager( + self.deep_thinking_llm, self.invest_judge_memory + ) + trader_node = create_trader(self.quick_thinking_llm, self.trader_memory) - # Create risk analysis nodes - risky_analyst = create_risky_debator(self.quick_thinking_llm) - neutral_analyst = create_neutral_debator(self.quick_thinking_llm) - safe_analyst = create_safe_debator(self.quick_thinking_llm) - risk_manager_node = create_risk_manager( - self.deep_thinking_llm, self.risk_manager_memory - ) + # Create risk analysis nodes + risky_analyst = create_risky_debator(self.quick_thinking_llm) + neutral_analyst = create_neutral_debator(self.quick_thinking_llm) + safe_analyst = create_safe_debator(self.quick_thinking_llm) + risk_manager_node = create_risk_manager( + self.deep_thinking_llm, self.risk_manager_memory + ) # Create workflow workflow = StateGraph(AgentState) @@ -120,14 +122,15 @@ class GraphSetup: workflow.add_node(f"tools_{analyst_type}", tool_nodes[analyst_type]) # Add other nodes - workflow.add_node("Bull Researcher", bull_researcher_node) - workflow.add_node("Bear Researcher", bear_researcher_node) - workflow.add_node("Research Manager", research_manager_node) - workflow.add_node("Trader", trader_node) - workflow.add_node("Risky Analyst", risky_analyst) - workflow.add_node("Neutral Analyst", neutral_analyst) - workflow.add_node("Safe Analyst", safe_analyst) - workflow.add_node("Risk Judge", risk_manager_node) + if self.debate: + workflow.add_node("Bull Researcher", bull_researcher_node) + workflow.add_node("Bear Researcher", bear_researcher_node) + workflow.add_node("Research Manager", research_manager_node) + workflow.add_node("Trader", trader_node) + workflow.add_node("Risky Analyst", risky_analyst) + workflow.add_node("Neutral Analyst", neutral_analyst) + workflow.add_node("Safe Analyst", safe_analyst) + workflow.add_node("Risk Judge", risk_manager_node) # Define edges # Start with the first analyst @@ -153,53 +156,56 @@ class GraphSetup: next_analyst = f"{selected_analysts[i+1].capitalize()} Analyst" workflow.add_edge(current_clear, next_analyst) else: - workflow.add_edge(current_clear, "Bull Researcher") + if self.debate: + workflow.add_edge(current_clear, "Bull Researcher") + else: + workflow.add_edge(current_clear, END) # Add remaining edges - workflow.add_conditional_edges( - "Bull Researcher", - self.conditional_logic.should_continue_debate, - { - "Bear Researcher": "Bear Researcher", - "Research Manager": "Research Manager", - }, - ) - workflow.add_conditional_edges( - "Bear Researcher", - self.conditional_logic.should_continue_debate, - { - "Bull Researcher": "Bull Researcher", - "Research Manager": "Research Manager", - }, - ) - workflow.add_edge("Research Manager", "Trader") - workflow.add_edge("Trader", "Risky Analyst") - workflow.add_conditional_edges( - "Risky Analyst", - self.conditional_logic.should_continue_risk_analysis, - { - "Safe Analyst": "Safe Analyst", - "Risk Judge": "Risk Judge", - }, - ) - workflow.add_conditional_edges( - "Safe Analyst", - self.conditional_logic.should_continue_risk_analysis, - { - "Neutral Analyst": "Neutral Analyst", - "Risk Judge": "Risk Judge", - }, - ) - workflow.add_conditional_edges( - "Neutral Analyst", - self.conditional_logic.should_continue_risk_analysis, - { - "Risky Analyst": "Risky Analyst", - "Risk Judge": "Risk Judge", - }, - ) - - workflow.add_edge("Risk Judge", END) + if self.debate: + workflow.add_conditional_edges( + "Bull Researcher", + self.conditional_logic.should_continue_debate, + { + "Bear Researcher": "Bear Researcher", + "Research Manager": "Research Manager", + }, + ) + workflow.add_conditional_edges( + "Bear Researcher", + self.conditional_logic.should_continue_debate, + { + "Bull Researcher": "Bull Researcher", + "Research Manager": "Research Manager", + }, + ) + workflow.add_edge("Research Manager", "Trader") + workflow.add_edge("Trader", "Risky Analyst") + workflow.add_conditional_edges( + "Risky Analyst", + self.conditional_logic.should_continue_risk_analysis, + { + "Safe Analyst": "Safe Analyst", + "Risk Judge": "Risk Judge", + }, + ) + workflow.add_conditional_edges( + "Safe Analyst", + self.conditional_logic.should_continue_risk_analysis, + { + "Neutral Analyst": "Neutral Analyst", + "Risk Judge": "Risk Judge", + }, + ) + workflow.add_conditional_edges( + "Neutral Analyst", + self.conditional_logic.should_continue_risk_analysis, + { + "Risky Analyst": "Risky Analyst", + "Risk Judge": "Risk Judge", + }, + ) + workflow.add_edge("Risk Judge", END) # Compile and return return workflow.compile() diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 80a29e53..45ddd52a 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -37,6 +37,7 @@ class TradingAgentsGraph: selected_analysts=["market", "social", "news", "fundamentals"], debug=False, config: Dict[str, Any] = None, + debate=False ): """Initialize the trading agents graph and components. @@ -47,6 +48,7 @@ class TradingAgentsGraph: """ self.debug = debug self.config = config or DEFAULT_CONFIG + self.debate = debate # Update the interface's config set_config(self.config) @@ -59,8 +61,8 @@ class TradingAgentsGraph: # Initialize LLMs if self.config["llm_provider"].lower() == "openai" or self.config["llm_provider"] == "ollama" or self.config["llm_provider"] == "openrouter": - self.deep_thinking_llm = ChatOpenAI(model=self.config["deep_think_llm"], base_url=self.config["backend_url"]) - self.quick_thinking_llm = ChatOpenAI(model=self.config["quick_think_llm"], base_url=self.config["backend_url"]) + self.deep_thinking_llm = ChatOpenAI(model=self.config["deep_think_llm"], base_url=self.config["backend_url"],max_tokens=self.config["max_completion_tokens"]) + self.quick_thinking_llm = ChatOpenAI(model=self.config["quick_think_llm"], base_url=self.config["backend_url"],max_tokens=self.config["max_completion_tokens"]) elif self.config["llm_provider"].lower() == "anthropic": self.deep_thinking_llm = ChatAnthropic(model=self.config["deep_think_llm"], base_url=self.config["backend_url"]) self.quick_thinking_llm = ChatAnthropic(model=self.config["quick_think_llm"], base_url=self.config["backend_url"]) @@ -95,6 +97,7 @@ class TradingAgentsGraph: self.invest_judge_memory, self.risk_manager_memory, self.conditional_logic, + debate=debate ) self.propagator = Propagator() @@ -184,42 +187,56 @@ class TradingAgentsGraph: self.curr_state = final_state # Log state - self._log_state(trade_date, final_state) + log_state = self._log_state(trade_date, final_state) # Return decision and processed signal - return final_state, self.process_signal(final_state["final_trade_decision"]) + if self.debate: + return log_state, self.process_signal(final_state["final_trade_decision"]) + else: + return log_state, "" + 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"], - "trade_date": final_state["trade_date"], - "market_report": final_state["market_report"], - "sentiment_report": final_state["sentiment_report"], - "news_report": final_state["news_report"], - "fundamentals_report": final_state["fundamentals_report"], - "investment_debate_state": { - "bull_history": final_state["investment_debate_state"]["bull_history"], - "bear_history": final_state["investment_debate_state"]["bear_history"], - "history": final_state["investment_debate_state"]["history"], - "current_response": final_state["investment_debate_state"][ - "current_response" - ], - "judge_decision": final_state["investment_debate_state"][ - "judge_decision" - ], - }, - "trader_investment_decision": final_state["trader_investment_plan"], - "risk_debate_state": { - "risky_history": final_state["risk_debate_state"]["risky_history"], - "safe_history": final_state["risk_debate_state"]["safe_history"], - "neutral_history": final_state["risk_debate_state"]["neutral_history"], - "history": final_state["risk_debate_state"]["history"], - "judge_decision": final_state["risk_debate_state"]["judge_decision"], - }, - "investment_plan": final_state["investment_plan"], - "final_trade_decision": final_state["final_trade_decision"], - } + if self.debate: + self.log_states_dict[str(trade_date)] = { + "company_of_interest": final_state["company_of_interest"], + "trade_date": final_state["trade_date"], + "market_report": final_state["market_report"], + "sentiment_report": final_state["sentiment_report"], + "news_report": final_state["news_report"], + "fundamentals_report": final_state["fundamentals_report"], + "investment_debate_state": { + "bull_history": final_state["investment_debate_state"]["bull_history"], + "bear_history": final_state["investment_debate_state"]["bear_history"], + "history": final_state["investment_debate_state"]["history"], + "current_response": final_state["investment_debate_state"][ + "current_response" + ], + "judge_decision": final_state["investment_debate_state"][ + "judge_decision" + ], + }, + "trader_investment_decision": final_state["trader_investment_plan"], + "risk_debate_state": { + "risky_history": final_state["risk_debate_state"]["risky_history"], + "safe_history": final_state["risk_debate_state"]["safe_history"], + "neutral_history": final_state["risk_debate_state"]["neutral_history"], + "history": final_state["risk_debate_state"]["history"], + "judge_decision": final_state["risk_debate_state"]["judge_decision"], + }, + "investment_plan": final_state["investment_plan"], + "final_trade_decision": final_state["final_trade_decision"], + } + else: + self.log_states_dict[str(trade_date)] = { + "company_of_interest": final_state["company_of_interest"], + "trade_date": final_state["trade_date"], + "market_report": final_state["market_report"], + "sentiment_report": final_state["sentiment_report"], + "news_report": final_state["news_report"], + "fundamentals_report": final_state["fundamentals_report"], + } # Save to file directory = Path(f"eval_results/{self.ticker}/TradingAgentsStrategy_logs/") @@ -230,6 +247,7 @@ class TradingAgentsGraph: "w", ) as f: json.dump(self.log_states_dict, f, indent=4) + return self.log_states_dict def reflect_and_remember(self, returns_losses): """Reflect on decisions and update memory based on returns."""