Contributing to TradingAgents
Guidelines for working on this codebase. Use the actual source under src/tradingagents/ as the canonical reference; this doc only covers operational commands and project-wide style invariants.
🧰 Commands
# Development
make help # List available make targets
make clean # Clean caches, artifacts and generated docs
make format # Run all pre-commit hooks
make test # Run pytest across the repository
make gen-docs # Generate docs from src/ and scripts/
# Dependencies (via uv)
make uv-install # Install uv on your system
uv add <pkg> # Add production dependency
uv add <pkg> --dev # Add development dependency
uv sync --group dev # Install dev-only deps (pre-commit, poe, notebook)
uv sync --group test # Install test-only deps
uv sync --group docs # Install docs-only deps
Optional tasks via poe (defined in [tool.poe.tasks]):
uv run poe cli # alias for `tradingagents cli`
uv run poe tui # alias for `tradingagents tui`
uv run poe docs # generate + serve docs
uv run poe gen # generate + deploy docs (gh-deploy)
📚 Docs
Built with MkDocs Material. Generate locally and serve at http://localhost:9987:
📦 Packaging
🔁 CI
All workflows under .github/workflows/:
test.yml— pytest on Python 3.12 / 3.13 / 3.14code-quality-check.yml— ruff + pre-commit hooksdeploy.yml— MkDocs → GitHub Pagesbuild_release.yml— build wheels + multi-platform executables on tag pushbuild_image.yml— build & push Docker image to GHCR (kept as backup artifact)release_drafter.yml— draft releases from Conventional Commits
📁 Project Layout
src/tradingagents/
├── agents/ # @tool definitions, agent node creators, prompts/*.md, state schemas
├── dataflows/ # yfinance-backed data fetchers (plain module-level functions)
├── graph/ # LangGraph wiring: setup, propagation, reflection, signal_processing, conditional_logic
├── interface/ # User-facing runners
│ ├── cli.py # fire-driven flag runner (run_cli)
│ ├── tui.py # questionary-driven interactive runner (run_tui)
│ ├── display.py # rich-based LangChain message renderer
│ └── help.py # rich-based help renderer (replaces fire's pager UI)
├── llm.py # build_chat_model wrapping init_chat_model + per-provider reasoning_effort mapping
├── config.py # TradingAgentsConfig schema + global singleton (set_config / get_config)
├── __init__.py # Top-level public API re-exports (intentionally lightweight)
└── __main__.py # Single dispatcher: backs both `tradingagents` console script and `python -m tradingagents`
The console script (defined under [project.scripts] in pyproject.toml) and python -m tradingagents both resolve to tradingagents.__main__:main. That function intercepts --help, -h, and the literal help token and renders interface/help.py directly, then hands the remaining args to fire.Fire({"cli": run_cli, "tui": run_tui}). There is no separate app.py layer.
Canonical examples (read these before writing similar code):
| Pattern | File | Symbol |
|---|---|---|
| Pure config model | config.py |
TradingAgentsConfig |
| Stateful service class | graph/trading_graph.py |
TradingAgentsGraph |
| LangGraph state schema | agents/utils/agent_states.py |
AgentState |
| Provider-agnostic LLM | llm.py |
build_chat_model |
@tool-wrapped function |
agents/utils/core_stock_tools.py |
get_stock_data |
| Agent node creator | agents/researchers/bull_researcher.py |
create_bull_* |
🎨 Code Style
Pydantic
- Every config / state / service class subclasses
pydantic.BaseModel. No bare__init__-based classes for stateful objects. - Every
Field()hasdefault(ordefault_factory),title, anddescription. - Mutable defaults always use
default_factory=(neverdefault={}ordefault=[]). - Nested models use
default_factory=NestedModel, notdefault=NestedModel(). - Add
model_config = ConfigDict(arbitrary_types_allowed=True)only when a field holds a non-Pydantic type (LLM client,ToolNode, dataclass, etc.). - Derived expensive objects:
@computed_fieldstacked directly above@cached_property, with a one-line docstring. - Side effects after construction go in
@model_validator(mode="after")returning"ClassName"(string forward ref, nottyping.Self). - For fields holding a
ChatModelUnion value, annotate asSkipValidation[ChatModel]to bypass Pydantic's cross-model coercion (LangChain's per-class validators fight each other otherwise).
Type Hints
- Use PEP 604 / lowercase generics:
X | None,list[X],dict[str, X],tuple[X, Y]. NeverOptional[X],List[X],Dict[str, X]. - Avoid bare
Anyin@computed_fieldreturn types — prefer the concrete type, theChatModelUnion, orobject. - Type LLM values as
ChatModel(the union exported fromtradingagents.llm), notBaseChatModel.
Paths
pathlib.Pathonly. Neveros.path.*,os.getcwd(), oros.path.join(...).- Anchor with
Path(__file__).resolve().parent. - Extract path defaults to module-level
_CONSTANTnames — never inline insideField(default=...).
LangGraph
- State schemas are Pydantic
BaseModel, notTypedDictordataclass. All fields have defaults so the schema can be instantiated empty. - Node function signatures:
(state: AgentState) -> dict[str, Any]. Access fields via attribute (state.market_report), notstate["market_report"]or.get(...). - The
messagesfield onAgentStatemust useAnnotated[list[AnyMessage], add_messages]so the LangGraph reducer fires. - Construct nested state updates as typed instances (
InvestDebateState(...)), not raw dicts. - Initial state via
Propagator.create_initial_state()returningAgentState. UseHumanMessage(content=...), not("human", ...)tuples.
Prompts
- All agent prompts live under
src/tradingagents/agents/prompts/<name>.md. - Load with
load_prompt(name)and.format(**kwargs). Never inline system / user prompts as Python string literals or module-level constants. - File name uses snake_case matching the agent role (e.g.
bull_researcher.md,reflector.md).
Misc
- Commit messages follow Conventional Commits.
- Open issues / PRs welcome. CI must pass; hooks should not be skipped.