138 lines
4.8 KiB
Python
138 lines
4.8 KiB
Python
from typing import Any, Mapping
|
|
|
|
from langchain_core.messages import HumanMessage, RemoveMessage
|
|
|
|
# Import tools from separate utility files
|
|
from tradingagents.agents.utils.core_stock_tools import (
|
|
get_stock_data
|
|
)
|
|
from tradingagents.agents.utils.technical_indicators_tools import (
|
|
get_indicators
|
|
)
|
|
from tradingagents.agents.utils.fundamental_data_tools import (
|
|
get_fundamentals,
|
|
get_balance_sheet,
|
|
get_cashflow,
|
|
get_income_statement
|
|
)
|
|
from tradingagents.agents.utils.news_data_tools import (
|
|
get_news,
|
|
get_insider_transactions,
|
|
get_global_news
|
|
)
|
|
|
|
|
|
def get_language_instruction() -> str:
|
|
"""Return a prompt instruction for the configured output language.
|
|
|
|
Returns empty string when English (default), so no extra tokens are used.
|
|
Only applied to user-facing agents (analysts, portfolio manager).
|
|
Internal debate agents stay in English for reasoning quality.
|
|
"""
|
|
from tradingagents.dataflows.config import get_config
|
|
lang = get_config().get("output_language", "English")
|
|
if lang.strip().lower() == "english":
|
|
return ""
|
|
return f" Write your entire response in {lang}."
|
|
|
|
|
|
def use_compact_analysis_prompt() -> bool:
|
|
"""Return whether analysts should use shorter prompts/reports.
|
|
|
|
This is helpful for OpenAI-compatible or Anthropic-compatible backends
|
|
that support the API surface but struggle with the repository's original,
|
|
very verbose analyst instructions.
|
|
"""
|
|
from tradingagents.dataflows.config import get_config
|
|
|
|
mode = str(get_config().get("analysis_prompt_style", "standard")).strip().lower()
|
|
return mode in {"compact", "fast", "minimax"}
|
|
|
|
|
|
def truncate_prompt_text(text: str, max_chars: int = 1200) -> str:
|
|
"""Trim long reports/history before feeding them into compact prompts."""
|
|
text = (text or "").strip()
|
|
if len(text) <= max_chars:
|
|
return text
|
|
return text[:max_chars].rstrip() + "\n...[truncated]..."
|
|
|
|
|
|
def build_optional_decision_context(
|
|
portfolio_context: str | None,
|
|
peer_context: str | None,
|
|
*,
|
|
peer_context_mode: str = "UNSPECIFIED",
|
|
max_chars: int = 700,
|
|
) -> str:
|
|
sections: list[str] = []
|
|
if str(portfolio_context or "").strip():
|
|
sections.append(
|
|
f"Portfolio context: {truncate_prompt_text(str(portfolio_context), max_chars)}"
|
|
)
|
|
if str(peer_context or "").strip():
|
|
mode = str(peer_context_mode or "UNSPECIFIED").strip().upper()
|
|
if mode == "SAME_THEME_NORMALIZED":
|
|
sections.append(
|
|
"Peer context mode: SAME_THEME_NORMALIZED. "
|
|
"You may use this context when deciding SAME_THEME_RANK if the evidence is explicit."
|
|
)
|
|
sections.append(
|
|
f"Peer / same-theme context: {truncate_prompt_text(str(peer_context), max_chars)}"
|
|
)
|
|
else:
|
|
sections.append(
|
|
f"Peer context mode: {mode}. This context is not same-theme normalized. "
|
|
"Treat SAME_THEME_RANK as UNKNOWN unless the context itself contains explicit same-theme evidence."
|
|
)
|
|
sections.append(
|
|
f"Peer universe context: {truncate_prompt_text(str(peer_context), max_chars)}"
|
|
)
|
|
return "\n".join(sections)
|
|
|
|
|
|
def summarize_structured_signal(payload: Mapping[str, Any] | None) -> str:
|
|
if not payload:
|
|
return "rating=UNKNOWN"
|
|
|
|
parts = [f"rating={payload.get('rating', 'UNKNOWN')}"]
|
|
hold_subtype = payload.get("hold_subtype")
|
|
if hold_subtype and hold_subtype != "N/A":
|
|
parts.append(f"hold_subtype={hold_subtype}")
|
|
entry_style = payload.get("entry_style")
|
|
if entry_style and entry_style != "UNKNOWN":
|
|
parts.append(f"entry_style={entry_style}")
|
|
same_theme_rank = payload.get("same_theme_rank")
|
|
if same_theme_rank and same_theme_rank != "UNKNOWN":
|
|
parts.append(f"same_theme_rank={same_theme_rank}")
|
|
account_fit = payload.get("account_fit")
|
|
if account_fit and account_fit != "UNKNOWN":
|
|
parts.append(f"account_fit={account_fit}")
|
|
return ", ".join(parts)
|
|
|
|
|
|
def build_instrument_context(ticker: str) -> str:
|
|
"""Describe the exact instrument so agents preserve exchange-qualified tickers."""
|
|
return (
|
|
f"The instrument to analyze is `{ticker}`. "
|
|
"Use this exact ticker in every tool call, report, and recommendation, "
|
|
"preserving any exchange suffix (e.g. `.TO`, `.L`, `.HK`, `.T`)."
|
|
)
|
|
|
|
def create_msg_delete():
|
|
def delete_messages(state):
|
|
"""Clear messages and add placeholder for Anthropic compatibility"""
|
|
messages = state["messages"]
|
|
|
|
# Remove all messages
|
|
removal_operations = [RemoveMessage(id=m.id) for m in messages]
|
|
|
|
# Add a minimal placeholder message
|
|
placeholder = HumanMessage(content="Continue")
|
|
|
|
return {"messages": removal_operations + [placeholder]}
|
|
|
|
return delete_messages
|
|
|
|
|
|
|