diff --git a/.env.sample b/.env.sample
new file mode 100644
index 000000000..b1810eaa0
--- /dev/null
+++ b/.env.sample
@@ -0,0 +1,4 @@
+# Copy this file to .env and fill in your API keys
+
+OPENAI_API_KEY=
+FINNHUB_API_KEY=
diff --git a/cli/main.py b/cli/main.py
index 2e06d50ce..96c07a108 100644
--- a/cli/main.py
+++ b/cli/main.py
@@ -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 = """
+
+
+
+{title}
+
+
+
+
+
+{content}
+
+
+"""
+
+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
@@ -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,
@@ -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,
}
@@ -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()
@@ -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
@@ -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
@@ -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")
@@ -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'{display_name}'
+ index_body = f"{decision_html}Other Reports
"
+ 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():
diff --git a/cli/utils.py b/cli/utils.py
index 7b9682a6b..3783756c0 100644
--- a/cli/utils.py
+++ b/cli/utils.py
@@ -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
@@ -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
diff --git a/requirements.txt b/requirements.txt
index a6154cd26..7c0ef1acb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,3 +24,4 @@ rich
questionary
langchain_anthropic
langchain-google-genai
+markdown
diff --git a/setup.py b/setup.py
index 793df3e69..bfc7880f1 100644
--- a/setup.py
+++ b/setup.py
@@ -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={
diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py
index 1f40a2a28..a12760343 100644
--- a/tradingagents/default_config.py
+++ b/tradingagents/default_config.py
@@ -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__), ".")),
@@ -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": {