170 lines
7.9 KiB
Python
170 lines
7.9 KiB
Python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
import time
|
|
import json
|
|
from tradingagents.agents.utils.agent_utils import get_stock_data, get_indicators, execute_text_tool_calls, needs_followup_call, execute_default_tools, generate_analysis_from_data
|
|
from tradingagents.dataflows.config import get_config
|
|
|
|
from tradingagents.log_utils import add_log, step_timer, symbol_progress
|
|
|
|
# Structured response format for explainability
|
|
ANALYST_RESPONSE_FORMAT = """
|
|
|
|
RESPONSE FORMAT (follow this structure exactly):
|
|
|
|
## EXECUTIVE SUMMARY
|
|
2-3 sentences: Key finding and directional bias (BULLISH / BEARISH / NEUTRAL).
|
|
|
|
## KEY DATA POINTS
|
|
- Bullet list of the 5 most significant data points with specific numbers
|
|
- Each point should include the metric name, value, and what it signals
|
|
|
|
## SIGNAL ASSESSMENT
|
|
Your overall reading: BULLISH / BEARISH / NEUTRAL
|
|
1-2 sentences explaining why, referencing specific data.
|
|
|
|
## RISK FACTORS
|
|
2-3 specific risks that could invalidate your assessment.
|
|
|
|
## CONFIDENCE: HIGH / MEDIUM / LOW
|
|
1 sentence justifying your confidence level.
|
|
|
|
| Metric | Value | Signal | Significance |
|
|
|--------|-------|--------|-------------|
|
|
| (fill with key metrics from your analysis) |
|
|
|
|
RULES:
|
|
- Maximum 3000 characters total
|
|
- Do NOT repeat raw data verbatim — summarize trends and insights
|
|
- Complete your ENTIRE analysis in a SINGLE response"""
|
|
|
|
|
|
def create_market_analyst(llm):
|
|
|
|
def market_analyst_node(state):
|
|
current_date = state["trade_date"]
|
|
ticker = state["company_of_interest"]
|
|
company_name = state["company_of_interest"]
|
|
|
|
tools = [
|
|
get_stock_data,
|
|
get_indicators,
|
|
]
|
|
|
|
system_message = (
|
|
"""You are a market/technical analyst tasked with analyzing financial markets. Select up to **8 of the most relevant indicators** for the current market condition. Available indicators by category:
|
|
|
|
Moving Averages:
|
|
- close_5_ema: 5 EMA — ultra-responsive short-term momentum
|
|
- close_10_ema: 10 EMA — responsive short-term average
|
|
- close_20_sma: 20 SMA — short-term trend (Bollinger baseline)
|
|
- close_50_sma: 50 SMA — medium-term trend direction
|
|
- close_200_sma: 200 SMA — long-term trend benchmark, golden/death cross
|
|
|
|
MACD Related:
|
|
- macd: MACD line — momentum via EMA differences
|
|
- macds: MACD Signal — smoothed MACD for crossover triggers
|
|
- macdh: MACD Histogram — momentum strength visualization
|
|
|
|
Momentum & Oscillators:
|
|
- rsi: RSI — overbought(>70)/oversold(<30) momentum
|
|
- kdjk: Stochastic %K — momentum oscillator, overbought(>80)/oversold(<20)
|
|
- cci: CCI — price deviation from mean, overbought(>100)/oversold(<-100)
|
|
|
|
Trend Strength:
|
|
- adx: ADX — trend strength regardless of direction (>25 = strong trend, <20 = ranging)
|
|
|
|
Volatility:
|
|
- boll: Bollinger Middle (20 SMA) — dynamic price benchmark
|
|
- boll_ub: Bollinger Upper — overbought/breakout zone
|
|
- boll_lb: Bollinger Lower — oversold/support zone
|
|
- atr: ATR — volatility for stop-loss and position sizing
|
|
|
|
Volume-Based:
|
|
- vwma: VWMA — volume-weighted moving average for trend confirmation
|
|
- mfi: MFI — money flow index combining price and volume
|
|
|
|
Strategy: Call `get_stock_data` first, then `get_indicators` with specific indicator names. Select indicators that provide diverse, complementary information — avoid redundancy. Provide specific numbers and quantitative reasoning, not generic statements."""
|
|
+ ANALYST_RESPONSE_FORMAT
|
|
)
|
|
|
|
prompt = ChatPromptTemplate.from_messages(
|
|
[
|
|
(
|
|
"system",
|
|
"You are a helpful AI assistant, collaborating with other assistants."
|
|
" Use the provided tools to progress towards answering the question."
|
|
" If you are unable to fully answer, that's OK; another assistant with different tools"
|
|
" will help where you left off. Execute what you can to make progress."
|
|
" 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}",
|
|
),
|
|
MessagesPlaceholder(variable_name="messages"),
|
|
]
|
|
)
|
|
|
|
prompt = prompt.partial(system_message=system_message)
|
|
prompt = prompt.partial(tool_names=", ".join([tool.name for tool in tools]))
|
|
prompt = prompt.partial(current_date=current_date)
|
|
prompt = prompt.partial(ticker=ticker)
|
|
|
|
chain = prompt | llm.bind_tools(tools)
|
|
|
|
step_timer.start_step("market_analyst")
|
|
add_log("agent", "market_analyst", f"📊 Market Analyst calling LLM for {ticker}...")
|
|
t0 = time.time()
|
|
result = chain.invoke(state["messages"])
|
|
elapsed = time.time() - t0
|
|
|
|
report = ""
|
|
|
|
if len(result.tool_calls) == 0:
|
|
report = result.content
|
|
add_log("llm", "market_analyst", f"LLM responded in {elapsed:.1f}s ({len(report)} chars)")
|
|
# Execute any text-based tool calls and capture results
|
|
tool_results = execute_text_tool_calls(report, tools)
|
|
if tool_results:
|
|
add_log("data", "market_analyst", f"Executed {len(tool_results)} tool calls: {', '.join(t['name'] for t in tool_results)}")
|
|
else:
|
|
# LLM didn't produce TOOL_CALL patterns — proactively fetch data
|
|
add_log("agent", "market_analyst", f"🔄 No tool calls found, proactively fetching data for {ticker}...")
|
|
tool_results = execute_default_tools(tools, ticker, current_date)
|
|
add_log("data", "market_analyst", f"Proactively fetched {len(tool_results)} data sources")
|
|
|
|
# If report is mostly tool calls / thin prose, make follow-up LLM call with actual data
|
|
if tool_results and needs_followup_call(report):
|
|
add_log("agent", "market_analyst", f"🔄 Generating analysis from {len(tool_results)} tool results...")
|
|
t1 = time.time()
|
|
followup = generate_analysis_from_data(llm, tool_results, system_message, ticker, current_date)
|
|
elapsed2 = time.time() - t1
|
|
if followup and len(followup) > 100:
|
|
report = followup
|
|
add_log("llm", "market_analyst", f"Follow-up analysis generated in {elapsed2:.1f}s ({len(report)} chars)")
|
|
|
|
add_log("agent", "market_analyst", f"✅ Market report ready: {report[:300]}...")
|
|
step_timer.end_step("market_analyst", "completed", report[:200])
|
|
symbol_progress.step_done(ticker, "market_analyst")
|
|
step_timer.update_details("market_analyst", {
|
|
"system_prompt": system_message[:2000],
|
|
"user_prompt": f"Analyze {ticker} on {current_date} using technical indicators",
|
|
"response": report[:3000],
|
|
"tool_calls": tool_results if tool_results else [],
|
|
})
|
|
else:
|
|
tool_call_info = [{"name": tc["name"], "args": str(tc.get("args", {}))[:200]} for tc in result.tool_calls]
|
|
step_timer.set_details("market_analyst", {
|
|
"system_prompt": system_message[:2000],
|
|
"user_prompt": f"Analyze {ticker} on {current_date} using technical indicators",
|
|
"response": "(Pending - tool calls in progress)",
|
|
"tool_calls": tool_call_info,
|
|
})
|
|
add_log("data", "market_analyst", f"LLM requested {len(result.tool_calls)} tool calls in {elapsed:.1f}s: {', '.join(tc['name'] for tc in result.tool_calls)}")
|
|
|
|
return {
|
|
"messages": [result],
|
|
"market_report": report,
|
|
}
|
|
|
|
return market_analyst_node
|