Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copy this file to .env and fill in your API keys

OPENAI_API_KEY=
FINNHUB_API_KEY=
89 changes: 84 additions & 5 deletions cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,52 @@
from rich import box
from rich.align import Align
from rich.rule import Rule
from dotenv import load_dotenv

# Load API keys from .env
load_dotenv()



# HTML output template and converter
HTML_TEMPLATE = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{title}</title>
<style>
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f4f5f7; margin: 0; padding: 0; }}
header {{ background-color: #2c3e50; padding: 10px 20px; }}
header a {{ color: #ffffff; text-decoration: none; font-weight: bold; }}
header a:hover {{ text-decoration: underline; }}
.container {{ max-width: 800px; margin: 40px auto; background: #ffffff; padding: 20px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }}
h1, h2, h3, h4 {{ color: #2c3e50; margin-top: 1.2em; }}
p {{ line-height: 1.6; color: #333333; }}
table {{ border-collapse: collapse; width: 100%; margin-bottom: 1.5em; }}
th, td {{ border: 1px solid #ddd; padding: 8px; }}
th {{ background-color: #f2f2f2; text-align: left; }}
a {{ color: #3498db; text-decoration: none; }}
a:hover {{ text-decoration: underline; }}
</style>
</head>
<body>
<header><a href="index.html">← Back to Index</a></header>
<div class="container">
{content}
</div>
</body>
</html>"""

def md_to_html(md_text: str) -> str:
"""Convert Markdown text to HTML, dynamically importing markdown."""
try:
import markdown
except ImportError:
raise RuntimeError(
"HTML output requires the 'markdown' package. Please install it with `pip install markdown`."
)
return markdown.markdown(md_text, extensions=["tables", "fenced_code"])


from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.default_config import DEFAULT_CONFIG
Expand Down Expand Up @@ -484,6 +530,15 @@ def create_question_box(title, prompt, default=None):
selected_shallow_thinker = select_shallow_thinking_agent(selected_llm_provider)
selected_deep_thinker = select_deep_thinking_agent(selected_llm_provider)

console.print(
create_question_box(
"Step 7: Output Format", "Choose output format (Markdown or HTML)", "Markdown"
)
)
selected_output_format = select_output_format()
console.print(f"[green]Selected output format:[/green] {selected_output_format.upper()}")


return {
"ticker": selected_ticker,
"analysis_date": analysis_date,
Expand All @@ -493,6 +548,7 @@ def create_question_box(title, prompt, default=None):
"backend_url": backend_url,
"shallow_thinker": selected_shallow_thinker,
"deep_thinker": selected_deep_thinker,
"output_format": selected_output_format,
}


Expand Down Expand Up @@ -738,6 +794,7 @@ def extract_content_string(content):
def run_analysis():
# First get all user selections
selections = get_user_selections()
output_format = selections["output_format"]

# Create config with selected research depth
config = DEFAULT_CONFIG.copy()
Expand Down Expand Up @@ -768,7 +825,7 @@ def wrapper(*args, **kwargs):
func(*args, **kwargs)
timestamp, message_type, content = obj.messages[-1]
content = content.replace("\n", " ") # Replace newlines with spaces
with open(log_file, "a") as f:
with open(log_file, "a", encoding="utf-8") as f:
f.write(f"{timestamp} [{message_type}] {content}\n")
return wrapper

Expand All @@ -779,7 +836,7 @@ def wrapper(*args, **kwargs):
func(*args, **kwargs)
timestamp, tool_name, args = obj.tool_calls[-1]
args_str = ", ".join(f"{k}={v}" for k, v in args.items())
with open(log_file, "a") as f:
with open(log_file, "a", encoding="utf-8") as f:
f.write(f"{timestamp} [Tool Call] {tool_name}({args_str})\n")
return wrapper

Expand All @@ -791,9 +848,15 @@ def wrapper(section_name, content):
if section_name in obj.report_sections and obj.report_sections[section_name] is not None:
content = obj.report_sections[section_name]
if content:
file_name = f"{section_name}.md"
with open(report_dir / file_name, "w") as f:
f.write(content)
if output_format == "md":
file_name = f"{section_name}.md"
with open(report_dir / file_name, "w", encoding="utf-8") as f:
f.write(content)
else:
file_name = f"{section_name}.html"
html_body = md_to_html(content)
with open(report_dir / file_name, "w", encoding="utf-8") as f:
f.write(HTML_TEMPLATE.format(title=section_name.replace('_',' ').title(), content=html_body))
return wrapper

message_buffer.add_message = save_message_decorator(message_buffer, "add_message")
Expand Down Expand Up @@ -1099,6 +1162,22 @@ def wrapper(section_name, content):

update_display(layout)

# Generate HTML index if HTML output selected
if output_format == "html":
# Convert final decision markdown to HTML
decision_md = message_buffer.report_sections.get("final_trade_decision", "")
decision_html = md_to_html(decision_md)
# Build links list
links = ""
for section, content in message_buffer.report_sections.items():
if section != "final_trade_decision" and content:
filename = f"{section}.html"
display_name = section.replace("_", " ").title()
links += f'<li><a href="{filename}">{display_name}</a></li>'
index_body = f"{decision_html}<h2>Other Reports</h2><ul>{links}</ul>"
with open(report_dir / "index.html", "w", encoding="utf-8") as f:
f.write(HTML_TEMPLATE.format(title="TradingAgents Report", content=index_body))


@app.command()
def analyze():
Expand Down
24 changes: 24 additions & 0 deletions cli/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import questionary
from rich.console import Console
console = Console()
from typing import List, Optional, Tuple, Dict

from cli.models import AnalystType
Expand Down Expand Up @@ -239,6 +241,28 @@ def select_deep_thinking_agent(provider) -> str:

return choice

def select_output_format() -> str:
"""Prompt the user to choose output format (Markdown or HTML)."""
choice = questionary.select(
"Select output format:",
choices=[
questionary.Choice("Markdown", "md"),
questionary.Choice("HTML", "html"),
],
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
style=questionary.Style(
[
("selected", "fg:cyan noinherit"),
("highlighted", "fg:cyan noinherit"),
("pointer", "noinherit"),
]
),
).ask()
if choice is None:
console.print("\n[red]No output format selected. Exiting...[/red]")
exit(1)
return choice

def select_llm_provider() -> tuple[str, str]:
"""Select the OpenAI api url using interactive selection."""
# Define OpenAI api options with their corresponding endpoints
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ rich
questionary
langchain_anthropic
langchain-google-genai
markdown
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"typer>=0.9.0",
"rich>=13.0.0",
"questionary>=2.0.1",
"markdown>=3.4.0",
],
python_requires=">=3.10",
entry_points={
Expand Down
9 changes: 9 additions & 0 deletions tradingagents/default_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import os
from dotenv import load_dotenv

# Load variables from .env
load_dotenv()

DEFAULT_CONFIG = {
"project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")),
Expand All @@ -17,6 +21,11 @@
"max_debate_rounds": 1,
"max_risk_discuss_rounds": 1,
"max_recur_limit": 100,
# Tool settings
"online_tools": True,
# API keys from environment
"openai_api_key": os.getenv("OPENAI_API_KEY", ""),
"finnhub_api_key": os.getenv("FINNHUB_API_KEY", ""),
# Data vendor configuration
# Category-level configuration (default for all tools in category)
"data_vendors": {
Expand Down