feat: Add Trade Strategist node + SQLite checkpointing + .gitignore restore
- Add Trade Strategist agent (trade_strategist_node.py): generates 5 actionable trade setups (Entry, SL, TP, R:R, Win%) after Portfolio Manager - Wire Trade Strategist into LangGraph pipeline (setup.py) - Add 'trade_possibilities' field to AgentState schema - Initialize trade_possibilities in Propagator initial state - Integrate SqliteSaver (langgraph-checkpoint-sqlite) for node-by-node state persistence to trading_agents_state.sqlite - Add unique thread_id (ticker_date) to graph config for checkpoint isolation - Update CLI (main.py) to display Trade Strategist progress + save report section - Restore full standard Python .gitignore (200+ rules) stripped in prior PR - Fix: convert trade_strategist_node to factory function (create_trade_strategist) to resolve LangGraph TypeError on missing 'llm' argument - Fix: use sqlite3.connect() directly instead of SqliteSaver.from_conn_string() to avoid _GeneratorContextManager TypeError
This commit is contained in:
parent
0adad8d365
commit
39b8b6db84
|
|
@ -1,9 +1,195 @@
|
||||||
venv/
|
# ============================
|
||||||
|
# TradingAgents Custom Ignores
|
||||||
|
# ============================
|
||||||
|
|
||||||
|
# API Keys / Secrets
|
||||||
.env
|
.env
|
||||||
__pycache__/
|
.envrc
|
||||||
*.egg-info/
|
|
||||||
|
# Virtual environments
|
||||||
|
venv/
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Reports and results (generated output)
|
||||||
reports/
|
reports/
|
||||||
results/
|
results/
|
||||||
|
eval_results/
|
||||||
|
|
||||||
|
# SQLite state database
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# ============================
|
||||||
|
# Standard Python Ignores
|
||||||
|
# ============================
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
build/
|
build/
|
||||||
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# Pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
.pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# PEP 582
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# Ruff
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# PyPI configuration file
|
||||||
|
.pypirc
|
||||||
|
|
||||||
|
# ============================
|
||||||
|
# IDE / Editor Ignores
|
||||||
|
# ============================
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# ============================
|
||||||
|
# OS Ignores
|
||||||
|
# ============================
|
||||||
|
|
||||||
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# ============================
|
||||||
|
# Cache / Data
|
||||||
|
# ============================
|
||||||
|
|
||||||
|
**/data_cache/
|
||||||
|
.streamlit/secrets.toml
|
||||||
|
|
|
||||||
19
cli/main.py
19
cli/main.py
|
|
@ -46,7 +46,7 @@ class MessageBuffer:
|
||||||
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
"Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"],
|
||||||
"Trading Team": ["Trader"],
|
"Trading Team": ["Trader"],
|
||||||
"Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"],
|
"Risk Management": ["Aggressive Analyst", "Neutral Analyst", "Conservative Analyst"],
|
||||||
"Portfolio Management": ["Portfolio Manager"],
|
"Portfolio Management": ["Portfolio Manager", "Trade Strategist"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Analyst name mapping
|
# Analyst name mapping
|
||||||
|
|
@ -68,6 +68,7 @@ class MessageBuffer:
|
||||||
"investment_plan": (None, "Research Manager"),
|
"investment_plan": (None, "Research Manager"),
|
||||||
"trader_investment_plan": (None, "Trader"),
|
"trader_investment_plan": (None, "Trader"),
|
||||||
"final_trade_decision": (None, "Portfolio Manager"),
|
"final_trade_decision": (None, "Portfolio Manager"),
|
||||||
|
"trade_possibilities": (None, "Trade Strategist"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, max_length=100):
|
def __init__(self, max_length=100):
|
||||||
|
|
@ -1046,8 +1047,11 @@ def run_analysis():
|
||||||
selections["ticker"], selections["analysis_date"]
|
selections["ticker"], selections["analysis_date"]
|
||||||
)
|
)
|
||||||
# Pass callbacks to graph config for tool execution tracking
|
# Pass callbacks to graph config for tool execution tracking
|
||||||
# (LLM tracking is handled separately via LLM constructor)
|
args = graph.propagator.get_graph_args(
|
||||||
args = graph.propagator.get_graph_args(callbacks=[stats_handler])
|
selections["ticker"],
|
||||||
|
selections["analysis_date"],
|
||||||
|
callbacks=[stats_handler]
|
||||||
|
)
|
||||||
|
|
||||||
# Stream the analysis
|
# Stream the analysis
|
||||||
trace = []
|
trace = []
|
||||||
|
|
@ -1148,6 +1152,15 @@ def run_analysis():
|
||||||
message_buffer.update_agent_status("Conservative Analyst", "completed")
|
message_buffer.update_agent_status("Conservative Analyst", "completed")
|
||||||
message_buffer.update_agent_status("Neutral Analyst", "completed")
|
message_buffer.update_agent_status("Neutral Analyst", "completed")
|
||||||
message_buffer.update_agent_status("Portfolio Manager", "completed")
|
message_buffer.update_agent_status("Portfolio Manager", "completed")
|
||||||
|
message_buffer.update_agent_status("Trade Strategist", "in_progress")
|
||||||
|
|
||||||
|
# Trade Strategist
|
||||||
|
if chunk.get("trade_possibilities"):
|
||||||
|
message_buffer.update_report_section(
|
||||||
|
"trade_possibilities", chunk["trade_possibilities"]
|
||||||
|
)
|
||||||
|
if message_buffer.agent_status.get("Trade Strategist") != "completed":
|
||||||
|
message_buffer.update_agent_status("Trade Strategist", "completed")
|
||||||
|
|
||||||
# Update the display
|
# Update the display
|
||||||
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
update_display(layout, stats_handler=stats_handler, start_time=start_time)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
from langchain_core.prompts import ChatPromptTemplate
|
||||||
|
from tradingagents.agents.utils.agent_states import AgentState
|
||||||
|
|
||||||
|
def create_trade_strategist(llm):
|
||||||
|
def trade_strategist_node(state: AgentState):
|
||||||
|
"""
|
||||||
|
Agent that analyzes the final trade decision and outputs 5 distinct trade setups.
|
||||||
|
"""
|
||||||
|
|
||||||
|
prompt = ChatPromptTemplate.from_messages(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"system",
|
||||||
|
"""You are an elite Trade Strategist at a premier quantitative hedge fund.
|
||||||
|
Your job is to take the final consensus decision from the Portfolio Manager and the Trader's investment plan, and synthesize them into exactly 5 specific, actionable trade setups.
|
||||||
|
|
||||||
|
For the given asset, you must provide exactly 5 trade possibilities with the following parameters explicitly defined for each:
|
||||||
|
- Trade Direction (Long/Short, Options, etc.)
|
||||||
|
- Entry Price / Condition (e.g., Buy at market, Limit buy at $X, Wait for breakout above $X)
|
||||||
|
- Stop Loss (SL) (Specific price level)
|
||||||
|
- Take Profit (TP) (Specific price level)
|
||||||
|
- Risk/Reward Ratio
|
||||||
|
- Estimated Win Percentage (Probability of success based on current technicals/fundamentals, e.g., 65%)
|
||||||
|
- Brief Rationale (1-2 sentences explaining why this setup makes sense)
|
||||||
|
|
||||||
|
Format your output as a clean, highly readable Markdown document.
|
||||||
|
Do not output anything besides the 5 trades and a brief introductory/concluding sentence.
|
||||||
|
Use bullet points and bold text for the parameters so they are easily scannable."""
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"human",
|
||||||
|
"""Asset: {company}
|
||||||
|
|
||||||
|
Portfolio Manager's Final Decision:
|
||||||
|
{final_decision}
|
||||||
|
|
||||||
|
Trader's Investment Plan:
|
||||||
|
{trader_plan}
|
||||||
|
|
||||||
|
Please formulate the 5 Trade Possibilities based on the above data."""
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
chain = prompt | llm
|
||||||
|
|
||||||
|
result = chain.invoke({
|
||||||
|
"company": state.get("company_of_interest", ""),
|
||||||
|
"final_decision": state.get("final_trade_decision", ""),
|
||||||
|
"trader_plan": state.get("trader_investment_plan", "")
|
||||||
|
})
|
||||||
|
|
||||||
|
return {"trade_possibilities": result.content}
|
||||||
|
|
||||||
|
return trade_strategist_node
|
||||||
|
|
@ -74,3 +74,6 @@ class AgentState(MessagesState):
|
||||||
RiskDebateState, "Current state of the debate on evaluating risk"
|
RiskDebateState, "Current state of the debate on evaluating risk"
|
||||||
]
|
]
|
||||||
final_trade_decision: Annotated[str, "Final decision made by the Risk Analysts"]
|
final_trade_decision: Annotated[str, "Final decision made by the Risk Analysts"]
|
||||||
|
|
||||||
|
# final strategies
|
||||||
|
trade_possibilities: Annotated[str, "5 Trade Possibilities with percentages, SL/TP"]
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from tradingagents.agents.utils.agent_states import (
|
||||||
class Propagator:
|
class Propagator:
|
||||||
"""Handles state initialization and propagation through the graph."""
|
"""Handles state initialization and propagation through the graph."""
|
||||||
|
|
||||||
def __init__(self, max_recur_limit=100):
|
def __init__(self, max_recur_limit=1000):
|
||||||
"""Initialize with configuration parameters."""
|
"""Initialize with configuration parameters."""
|
||||||
self.max_recur_limit = max_recur_limit
|
self.max_recur_limit = max_recur_limit
|
||||||
|
|
||||||
|
|
@ -51,16 +51,23 @@ class Propagator:
|
||||||
"fundamentals_report": "",
|
"fundamentals_report": "",
|
||||||
"sentiment_report": "",
|
"sentiment_report": "",
|
||||||
"news_report": "",
|
"news_report": "",
|
||||||
|
"trade_possibilities": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_graph_args(self, callbacks: Optional[List] = None) -> Dict[str, Any]:
|
def get_graph_args(self, company_name: str, trade_date: str, callbacks: Optional[List] = None) -> Dict[str, Any]:
|
||||||
"""Get arguments for the graph invocation.
|
"""Get arguments for the graph invocation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
company_name: The ticker being analyzed
|
||||||
|
trade_date: The date of analysis
|
||||||
callbacks: Optional list of callback handlers for tool execution tracking.
|
callbacks: Optional list of callback handlers for tool execution tracking.
|
||||||
Note: LLM callbacks are handled separately via LLM constructor.
|
Note: LLM callbacks are handled separately via LLM constructor.
|
||||||
"""
|
"""
|
||||||
config = {"recursion_limit": self.max_recur_limit}
|
thread_id = f"{company_name}_{trade_date}"
|
||||||
|
config = {
|
||||||
|
"recursion_limit": self.max_recur_limit,
|
||||||
|
"configurable": {"thread_id": thread_id}
|
||||||
|
}
|
||||||
if callbacks:
|
if callbacks:
|
||||||
config["callbacks"] = callbacks
|
config["callbacks"] = callbacks
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,15 @@
|
||||||
|
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
|
|
||||||
from langgraph.graph import END, StateGraph, START
|
from langgraph.graph import END, StateGraph, START
|
||||||
from langgraph.prebuilt import ToolNode
|
from langgraph.prebuilt import ToolNode
|
||||||
|
import sqlite3
|
||||||
|
from langgraph.checkpoint.sqlite import SqliteSaver
|
||||||
|
|
||||||
from tradingagents.agents import *
|
from tradingagents.agents import *
|
||||||
from tradingagents.agents.utils.agent_states import AgentState
|
from tradingagents.agents.utils.agent_states import AgentState
|
||||||
|
from tradingagents.agents.trade_strategist_node import create_trade_strategist
|
||||||
|
|
||||||
from .conditional_logic import ConditionalLogic
|
from .conditional_logic import ConditionalLogic
|
||||||
|
|
||||||
|
|
@ -104,6 +108,7 @@ class GraphSetup:
|
||||||
portfolio_manager_node = create_portfolio_manager(
|
portfolio_manager_node = create_portfolio_manager(
|
||||||
self.deep_thinking_llm, self.portfolio_manager_memory
|
self.deep_thinking_llm, self.portfolio_manager_memory
|
||||||
)
|
)
|
||||||
|
trade_strategist_node = create_trade_strategist(self.quick_thinking_llm)
|
||||||
|
|
||||||
# Create workflow
|
# Create workflow
|
||||||
workflow = StateGraph(AgentState)
|
workflow = StateGraph(AgentState)
|
||||||
|
|
@ -125,6 +130,7 @@ class GraphSetup:
|
||||||
workflow.add_node("Neutral Analyst", neutral_analyst)
|
workflow.add_node("Neutral Analyst", neutral_analyst)
|
||||||
workflow.add_node("Conservative Analyst", conservative_analyst)
|
workflow.add_node("Conservative Analyst", conservative_analyst)
|
||||||
workflow.add_node("Portfolio Manager", portfolio_manager_node)
|
workflow.add_node("Portfolio Manager", portfolio_manager_node)
|
||||||
|
workflow.add_node("Trade Strategist", trade_strategist_node)
|
||||||
|
|
||||||
# Define edges
|
# Define edges
|
||||||
# Start with the first analyst
|
# Start with the first analyst
|
||||||
|
|
@ -196,7 +202,10 @@ class GraphSetup:
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
workflow.add_edge("Portfolio Manager", END)
|
workflow.add_edge("Portfolio Manager", "Trade Strategist")
|
||||||
|
workflow.add_edge("Trade Strategist", END)
|
||||||
|
|
||||||
# Compile and return
|
# Compile and return with SQLite memory
|
||||||
return workflow.compile()
|
conn = sqlite3.connect("trading_agents_state.sqlite", check_same_thread=False)
|
||||||
|
memory = SqliteSaver(conn)
|
||||||
|
return workflow.compile(checkpointer=memory)
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ class TradingAgentsGraph:
|
||||||
init_agent_state = self.propagator.create_initial_state(
|
init_agent_state = self.propagator.create_initial_state(
|
||||||
company_name, trade_date
|
company_name, trade_date
|
||||||
)
|
)
|
||||||
args = self.propagator.get_graph_args()
|
args = self.propagator.get_graph_args(company_name, trade_date)
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
# Debug mode with tracing
|
# Debug mode with tracing
|
||||||
|
|
@ -256,6 +256,7 @@ class TradingAgentsGraph:
|
||||||
},
|
},
|
||||||
"investment_plan": final_state["investment_plan"],
|
"investment_plan": final_state["investment_plan"],
|
||||||
"final_trade_decision": final_state["final_trade_decision"],
|
"final_trade_decision": final_state["final_trade_decision"],
|
||||||
|
"trade_possibilities": final_state.get("trade_possibilities", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Save to file
|
# Save to file
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue