| | import re |
| | import logging |
| | import functools |
| | from typing import Dict, Any |
| | from crewai import Agent, Crew, Process, Task |
| | from dotenv import load_dotenv |
| | load_dotenv() |
| |
|
| | import os |
| | from src.crypto_analysis.tools.bitcoin_tools import YahooBitcoinDataTool, RealBitcoinNewsTool |
| | from src.crypto_analysis.tools.technical_tools import TechnicalAnalysisStrategy |
| | from src.crypto_analysis.tools.order_tools import AlpacaCryptoOrderTool |
| | from src.crypto_analysis.tools.yahoo_tools import YahooCryptoMarketTool |
| |
|
| | |
| | import openai |
| | from langchain_openai import ChatOpenAI |
| |
|
| | |
| | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
| | logger = logging.getLogger("bitcoin_crew") |
| |
|
| | |
| | openai.api_key = os.getenv("OPENAI_API_KEY") |
| | llm = ChatOpenAI(model="o3-mini") |
| |
|
| | class BitcoinAnalysisCrew: |
| | """A simplified Bitcoin analysis crew with reflection and synthesis""" |
| | |
| | def __init__(self, timeframe="5m", max_allocation=100): |
| | """ |
| | Initialize the Bitcoin analysis crew |
| | |
| | Args: |
| | timeframe (str): Timeframe for technical analysis (e.g., "1m", "5m", "15m", "1h", "4h", "1d") |
| | max_allocation (int): Maximum portfolio allocation percentage allowed (1-100) |
| | """ |
| | |
| | self.timeframe = timeframe |
| | self.max_allocation = min(max(1, max_allocation), 100) |
| | |
| | self.bitcoin_data_tool = YahooBitcoinDataTool( |
| | max_retries=3, |
| | backoff_factor=2.0, |
| | timeout=30, |
| | cache_duration_minutes=15, |
| | timeframe=self.timeframe |
| | ) |
| | self.bitcoin_news_tool = RealBitcoinNewsTool() |
| | self.technical_strategy_tool = TechnicalAnalysisStrategy( |
| | timeframe=self.timeframe |
| | ) |
| | self.order_tool = AlpacaCryptoOrderTool() |
| | self.crypto_market_tool = YahooCryptoMarketTool( |
| | max_retries=3, |
| | backoff_factor=2.0, |
| | timeout=30, |
| | cache_duration_minutes=30 |
| | ) |
| | |
| | |
| | self._technical_agent = None |
| | self._analyst_agent = None |
| | self._reflection_agent = None |
| | self._synthesis_agent = None |
| | |
| | |
| | self.tool_states = { |
| | "bitcoin_data": {"status": "not_started", "error": None}, |
| | "bitcoin_news": {"status": "not_started", "error": None}, |
| | "technical_strategy": {"status": "not_started", "error": None}, |
| | "crypto_market": {"status": "not_started", "error": None}, |
| | "order_tool": {"status": "not_started", "error": None} |
| | } |
| | |
| | |
| | self._patch_tools_for_monitoring() |
| | |
| | logger.info(f"Initialized Bitcoin analysis crew with timeframe={timeframe}, max_allocation={max_allocation}%") |
| | |
| | def create_technical_analyst_agent(self) -> Agent: |
| | """Creates a Technical Analysis agent for Bitcoin""" |
| | if self._technical_agent is not None: |
| | return self._technical_agent |
| | |
| | self._technical_agent = Agent( |
| | name="Bitcoin Technical Strategist", |
| | role="Bitcoin technical trading strategist", |
| | goal=f"Analyze Bitcoin technical indicators using {self.timeframe} timeframe data to generate accurate buy/sell signals", |
| | backstory=f"""You are an expert in technical analysis for Bitcoin trading. |
| | You specialize in analyzing {self.timeframe} price patterns using key indicators |
| | like RSI and Bollinger Bands to identify high-probability trading opportunities. |
| | You pride yourself on providing clear, data-driven trading signals |
| | with confidence levels and precise allocation recommendations. |
| | |
| | IMPORTANT: The user has set a maximum allocation of {self.max_allocation}% of their portfolio. |
| | You MUST NOT recommend allocations higher than this, regardless of how strong the signal is.""", |
| | verbose=True, |
| | llm=llm, |
| | tools=[self.technical_strategy_tool] |
| | ) |
| | return self._technical_agent |
| | |
| | def create_analyst_agent(self) -> Agent: |
| | """Creates a Bitcoin analyst agent for initial analysis""" |
| | if self._analyst_agent is not None: |
| | return self._analyst_agent |
| | |
| | self._analyst_agent = Agent( |
| | name="Bitcoin Initial Analyst", |
| | role="Bitcoin technical analyst", |
| | goal=f"Analyze Bitcoin technical data on {self.timeframe} timeframe, news, and technical strategy signals to provide a holistic assessment", |
| | backstory=f"""You are a technical analyst with expertise in Bitcoin price patterns |
| | and market indicators on the {self.timeframe} timeframe. You focus on data-driven analysis while also incorporating |
| | news sentiment and technical trading signals from specialized strategy models. |
| | |
| | IMPORTANT: The user has set a maximum allocation of {self.max_allocation}% of their portfolio. |
| | You MUST respect this limit in your analysis and recommendations. |
| | |
| | IMPORTANT: You must check for error reports in tool outputs. If a tool returns an 'error' field, |
| | you should acknowledge the error, explain its potential impact on your analysis, and adjust your |
| | approach accordingly. Do not simply proceed as if the tool succeeded. |
| | |
| | If multiple tools return errors, focus on what data is available and explicitly state the limitations |
| | of your analysis due to missing data.""", |
| | verbose=True, |
| | llm=llm, |
| | tools=[self.bitcoin_data_tool, self.bitcoin_news_tool, self.crypto_market_tool] |
| | ) |
| | return self._analyst_agent |
| | |
| | def create_reflection_agent(self) -> Agent: |
| | """Creates a reflection agent to evaluate the initial analysis""" |
| | if self._reflection_agent is not None: |
| | return self._reflection_agent |
| | |
| | self._reflection_agent = Agent( |
| | name="Bitcoin Reflection Analyst", |
| | role="Bitcoin market sentiment analyst", |
| | goal="Evaluate initial analysis and technical strategy signals to provide a market sentiment perspective", |
| | backstory=f"""You are a market sentiment specialist who analyzes news and social trends |
| | to determine broader market sentiment around Bitcoin. You also consider technical signals |
| | and initial analysis to form a comprehensive view of market conditions. |
| | |
| | IMPORTANT: The user has set a maximum allocation of {self.max_allocation}% of their portfolio. |
| | Any recommendation you make should respect this limit. |
| | |
| | IMPORTANT: If you encounter errors or limitations in earlier analyses due to tool failures, |
| | acknowledge these limitations and adapt your reflection accordingly. Be explicit about how |
| | missing data affects the reliability of your assessment.""", |
| | verbose=True, |
| | llm=llm, |
| | tools=[self.bitcoin_news_tool] |
| | ) |
| | return self._reflection_agent |
| | |
| | def create_synthesis_agent(self) -> Agent: |
| | """Creates a synthesis agent to provide final recommendation""" |
| | if self._synthesis_agent is not None: |
| | return self._synthesis_agent |
| | |
| | self._synthesis_agent = Agent( |
| | name="Bitcoin Synthesis Analyst", |
| | role="Senior cryptocurrency investment advisor", |
| | goal="Synthesize all analysis (technical strategy, initial, and reflection) to provide final recommendation and execute trades with comprehensive reasoning", |
| | backstory=f"""You are a seasoned investment advisor who specializes in cryptocurrency. |
| | You analyze technical signals, market sentiment, and fundamental factors to provide |
| | balanced investment advice with high confidence levels and specific allocation percentages. |
| | You can also execute trades on behalf of clients based on your final recommendations. |
| | |
| | You are known for providing highly detailed and comprehensive analysis reports that break down |
| | each factor that influenced your decision, quantifying the impact of each element on your final recommendation. |
| | |
| | IMPORTANT: The user has set a maximum allocation of {self.max_allocation}% of their portfolio. |
| | You MUST NOT exceed this limit in your allocation recommendation, regardless of signal strength. |
| | |
| | IMPORTANT: You must acknowledge and account for any data limitations or tool failures in your synthesis. |
| | If there were errors in previous analyses, explicitly state how these affect your final recommendation's |
| | reliability and adjust your confidence levels accordingly. |
| | |
| | IMPORTANT TOOL USAGE - When using the Alpaca Crypto Order Tool: |
| | 1. For BUY orders, use: |
| | {{"action": "buy", "symbol": "BTC/USD", "allocation_percentage": X, "confidence": Y}} |
| | where X is your recommended allocation percentage (NOT EXCEEDING {self.max_allocation}%) |
| | The tool will automatically convert the allocation_percentage into the appropriate BTC quantity. |
| | 2. For SELL orders, use: |
| | {{"action": "sell", "symbol": "BTC/USD", "allocation_percentage": X, "confidence": Y}} |
| | where X is your recommended allocation percentage (NOT EXCEEDING {self.max_allocation}%) |
| | The tool will automatically calculate how many BTC to sell based on your portfolio. |
| | 3. For checking account (HOLD), use: |
| | {{"action": "check"}} |
| | """, |
| | verbose=True, |
| | llm=llm, |
| | tools=[self.order_tool] |
| | ) |
| | return self._synthesis_agent |
| | |
| | def create_technical_strategy_task(self, strategy_text=None) -> Task: |
| | """Creates technical strategy analysis task""" |
| | |
| | if strategy_text: |
| | description = f"""Analyze Bitcoin's technical indicators on the {self.timeframe} timeframe to generate a buy/sell signal based on the following strategy: |
| | |
| | {strategy_text} |
| | |
| | Use the Technical Analysis Strategy Tool to get current indicator values and analyze them according to the strategy. |
| | |
| | IMPORTANT: The user has set a maximum allocation of {self.max_allocation}% of their portfolio. |
| | Do NOT recommend allocations above this limit, regardless of how strong the signal is. |
| | |
| | IMPORTANT: If the tool returns an error (look for an "error" field in the response), |
| | you must: |
| | 1. Acknowledge the error in your analysis |
| | 2. Explain how it impacts your ability to provide a reliable signal |
| | 3. If possible, make a recommendation based on any partial data available |
| | 4. Set confidence lower to reflect the uncertainty |
| | |
| | Your analysis should include: |
| | - Clear buy, sell, or hold signal |
| | - Confidence level as a percentage (0-100%) |
| | - Recommended portfolio allocation percentage (maximum {self.max_allocation}%) |
| | - Detailed reasoning explaining the signal |
| | - Current values for all relevant indicators |
| | - Any error messages or data limitations |
| | """ |
| | else: |
| | |
| | description = f"""Analyze Bitcoin's technical indicators on the {self.timeframe} timeframe to generate a buy/sell signal. |
| | |
| | 1. Use the Technical Analysis Strategy Tool to analyze RSI and Bollinger Bands on the {self.timeframe} timeframe |
| | 2. Evaluate if current conditions meet the criteria for a buy signal: |
| | - RSI below 40 |
| | - Price near or touching the lower Bollinger Band |
| | - Signs of a price bounce from the lower band |
| | 3. Evaluate if current conditions meet the criteria for a sell signal: |
| | - RSI above 60 |
| | - Price near or touching the upper Bollinger Band |
| | - Signs of price reversal from the upper band |
| | 4. If neither buy nor sell conditions are met, recommend hold |
| | |
| | IMPORTANT: The user has set a maximum allocation of {self.max_allocation}% of their portfolio. |
| | Do NOT recommend allocations above this limit. |
| | |
| | IMPORTANT: If the tool returns an error (look for an "error" field in the response), |
| | you must: |
| | 1. Acknowledge the error in your analysis |
| | 2. Explain how it impacts your ability to provide a reliable signal |
| | 3. If possible, make a recommendation based on any partial data available |
| | 4. Set confidence lower to reflect the uncertainty |
| | |
| | Your analysis should include: |
| | - Clear buy, sell, or hold signal |
| | - Confidence level as a percentage (0-100%) |
| | - Recommended portfolio allocation percentage (not exceeding {self.max_allocation}%) |
| | - Detailed reasoning explaining the signal |
| | - Current values for all relevant indicators |
| | - Any error messages or data limitations |
| | """ |
| | |
| | return Task( |
| | description=description, |
| | agent=self.create_technical_analyst_agent(), |
| | expected_output="Technical strategy analysis with clear signal, confidence level, and allocation recommendation" |
| | ) |
| | |
| | def create_initial_task(self, technical_task) -> Task: |
| | """Creates initial analysis task""" |
| | return Task( |
| | description="""Analyze Bitcoin's current market situation using price data, news, technical strategy signals, and broader crypto market context. |
| | |
| | 1. Get the latest Bitcoin price data and identify the trend |
| | 2. Check recent news that might impact Bitcoin price |
| | 3. Consider the technical strategy signals provided |
| | 4. Analyze the broader cryptocurrency market context using the Yahoo Finance Crypto Market Tool |
| | 5. Provide an initial holistic assessment |
| | |
| | IMPORTANT: For each tool you use, check if there's an "error" field in the response. |
| | If any tool returns an error: |
| | 1. Acknowledge the error explicitly |
| | 2. Explain how it affects your analysis |
| | 3. Adjust your conclusions accordingly |
| | 4. Use whatever partial data is available |
| | |
| | Your analysis should include: |
| | - Current Bitcoin Price (if available) |
| | - Recent Price Trend (bullish or bearish, if data available) |
| | - Technical Signals (including those from the strategy) |
| | - Broader Crypto Market Context (including BTC dominance and overall market trend, if available) |
| | - Key News Impact |
| | - Data Limitations (explain any missing or potentially unreliable data) |
| | - How the news sentiment and market context align or conflict with technical signals |
| | """, |
| | agent=self.create_analyst_agent(), |
| | context=[technical_task], |
| | expected_output="Initial holistic assessment of Bitcoin's current market situation" |
| | ) |
| | |
| | def create_reflection_task(self, technical_task, initial_task) -> Task: |
| | """Creates reflection task building on initial analysis""" |
| | return Task( |
| | description="""Review the technical strategy signals and initial Bitcoin analysis to add market sentiment perspective. |
| | |
| | 1. Evaluate the news sentiment around Bitcoin using the Bitcoin News Tool |
| | 2. Consider if technical signals and initial analysis align with market sentiment |
| | 3. Identify any potential gaps or oversights in the technical strategy or initial analysis |
| | 4. Evaluate if the technical strategy confidence level seems appropriate given broader market context |
| | |
| | IMPORTANT: If you encounter errors from tools or limitations noted in the previous analyses, |
| | acknowledge these explicitly and explain how they affect your assessment. |
| | |
| | Your reflection should include: |
| | - Sentiment Assessment (positive, negative, or neutral) |
| | - Agreement or Disagreement with Technical Strategy Signals |
| | - Agreement or Disagreement with Initial Analysis |
| | - Data Reliability Assessment (mention any tool errors or data limitations) |
| | - Additional Insights Not Covered in Previous Analyses |
| | """, |
| | agent=self.create_reflection_agent(), |
| | context=[technical_task, initial_task], |
| | expected_output="Reflection on technical strategy and initial analysis with sentiment perspective" |
| | ) |
| | |
| | def create_synthesis_task(self, technical_task, initial_task, reflection_task) -> Task: |
| | """Creates synthesis task for final recommendation""" |
| | return Task( |
| | description=f"""Synthesize all analyses (technical strategy, initial, and reflection) to provide a final investment recommendation and execute the trade. |
| | |
| | 1. Consider the technical strategy signals (buy/sell/hold) with HIGH PRIORITY |
| | - Use the technical strategy's confidence and allocation as a BASELINE |
| | - Adjust confidence based on any tool errors or data limitations noted |
| | 2. Evaluate the initial holistic assessment |
| | - Pay special attention to the broader crypto market context and BTC dominance |
| | - Consider how Bitcoin's performance compares to other cryptocurrencies |
| | 3. Incorporate the sentiment perspective from the reflection |
| | 4. Make a final recommendation that integrates all three perspectives |
| | - If technical and news analyses conflict, split the difference |
| | - NEVER output a confidence of 0% for a BUY or SELL signal |
| | - If recommending BUY or SELL, the minimum confidence should be 30% |
| | - If recommending BUY or SELL, the minimum allocation should be 5% |
| | - The MAXIMUM allocation must NOT exceed {self.max_allocation}% (user's set limit) |
| | - IMPORTANT: If there were significant tool errors, reduce confidence accordingly |
| | 5. Provide your own confidence level and allocation percentage |
| | 6. EXECUTE THE TRADE using the Alpaca Crypto Order Tool as the very last step: |
| | - IMPORTANT: Always use the symbol format "BTC/USD" with the slash, not "BTCUSD" |
| | - You MUST use allocation_percentage, which represents what percentage of the portfolio to allocate |
| | - For BUY orders: The tool will convert the allocation_percentage into the appropriate BTC quantity |
| | Example: {{"action": "buy", "symbol": "BTC/USD", "allocation_percentage": N, "confidence": M}} |
| | Where N is between 5 and {self.max_allocation}, and M is between 30 and 100 |
| | - For SELL orders: The tool will calculate how many BTC to sell based on your allocation_percentage |
| | Example: {{"action": "sell", "symbol": "BTC/USD", "allocation_percentage": N, "confidence": M}} |
| | Where N is between 5 and {self.max_allocation}, and M is between 30 and 100 |
| | - For HOLD recommendation: Check the account status only |
| | Example: {{"action": "check"}} |
| | - DO NOT try to calculate the exact BTC quantity yourself - the tool will do this automatically |
| | - DO NOT include additional parameters beyond what's shown in the examples |
| | - VERIFY that your input matches EXACTLY one of the example formats above |
| | |
| | EXAMPLES OF INCORRECT USAGE (DO NOT DO THESE): |
| | - DO NOT use: {{"action": "buy", "quantity": 0.001}} ← Don't specify quantity directly |
| | - DO NOT use: {{"action": "buy", "symbol...": 15, "confidence": 75}} ← Missing quotes around symbol |
| | - DO NOT use: {{"action": "buy", "symbol": "BTC/USD"}} ← Missing allocation_percentage |
| | - DO NOT use: {{"action": "buy", "symbol": "BTCUSD", "allocation_percentage": 15}} ← Wrong symbol format |
| | - DO NOT use: {{"action": "buy", "symbol": "BTC/USD", "quantity": 0.001, "allocation_percentage": 15}} ← Using both quantity and allocation |
| | |
| | Your final recommendation must be EXTREMELY DETAILED and should include: |
| | - Current Bitcoin Price: (latest price if available) |
| | - Technical Signal: (from strategy: buy, sell, or hold) |
| | - Technical Confidence: (from strategy: 0-100%) |
| | - Technical Allocation: (from strategy: percentage) |
| | - Tool Error Assessment: (explicitly describe any tool errors encountered in the analysis process and how they affected your recommendation) |
| | - Data Reliability: (assess the reliability of the data used for this recommendation) |
| | - Technical Analysis Summary: (detailed explanation of the technical indicators at {self.timeframe} timeframe) |
| | - Initial Analysis Summary: (detailed explanation of the broader market context) |
| | - Reflection Analysis Summary: (detailed explanation of market sentiment) |
| | - Importance Weighting: (explicitly state how you weighted each analysis: technical, initial, and reflection) |
| | - Impact of Technical Factors: (explicit percentage impact of technical factors on your decision, with detailed explanation) |
| | - Impact of Market Context: (explicit percentage impact of market context on your decision, with detailed explanation) |
| | - Impact of Sentiment: (explicit percentage impact of sentiment on your decision, with detailed explanation) |
| | - Final Recommendation: (buy, sell, or hold) |
| | - Final Confidence: (0-100%) |
| | - Allocation Percentage: (how much of portfolio to allocate, NOT EXCEEDING {self.max_allocation}%) |
| | - Detailed Reasoning: (comprehensive explanation of how you arrived at your final recommendation, including how you reconciled any conflicting signals) |
| | - Market Outlook: (your assessment of the likely short-term direction of Bitcoin price) |
| | - Risk Assessment: (evaluation of the risk associated with your recommendation) |
| | - Trade Execution: (details of the order executed or account status check) |
| | """, |
| | agent=self.create_synthesis_agent(), |
| | context=[technical_task, initial_task, reflection_task], |
| | expected_output="Final Bitcoin investment recommendation with detailed analysis breakdown and trade execution details" |
| | ) |
| | |
| | def run_analysis(self, strategy_text=None, timeframe=None, max_allocation=None) -> Dict[str, Any]: |
| | """ |
| | Run the Bitcoin analysis workflow with reflection and synthesis |
| | |
| | Args: |
| | strategy_text: Custom trading strategy text from UI |
| | timeframe: Optional override for analysis timeframe (e.g. "1m", "5m", "15m", "1h", "4h", "1d") |
| | max_allocation: Optional override for maximum portfolio allocation percentage (1-100) |
| | |
| | Returns: |
| | Dictionary with analysis results and trading recommendation |
| | """ |
| | try: |
| | |
| | if timeframe is not None: |
| | self.timeframe = timeframe |
| | |
| | self.bitcoin_data_tool.timeframe = timeframe |
| | self.technical_strategy_tool.timeframe = timeframe |
| | logger.info(f"Updated timeframe for analysis to {timeframe}") |
| | |
| | if max_allocation is not None: |
| | self.max_allocation = min(max(1, max_allocation), 100) |
| | logger.info(f"Updated maximum allocation to {self.max_allocation}%") |
| | |
| | logger.info(f"Starting Bitcoin analysis workflow with timeframe={self.timeframe}, max_allocation={self.max_allocation}%") |
| | |
| | |
| | for key in self.tool_states: |
| | self.tool_states[key] = {"status": "not_started", "error": None} |
| | |
| | |
| | logger.info("Creating tasks") |
| | technical_task = self.create_technical_strategy_task(strategy_text) |
| | initial_task = self.create_initial_task(technical_task) |
| | reflection_task = self.create_reflection_task(technical_task, initial_task) |
| | synthesis_task = self.create_synthesis_task(technical_task, initial_task, reflection_task) |
| | |
| | |
| | logger.info("Creating crew") |
| | crew = Crew( |
| | agents=[ |
| | self.create_technical_analyst_agent(), |
| | self.create_analyst_agent(), |
| | self.create_reflection_agent(), |
| | self.create_synthesis_agent() |
| | ], |
| | tasks=[technical_task, initial_task, reflection_task, synthesis_task], |
| | verbose=True, |
| | process=Process.sequential |
| | ) |
| | |
| | |
| | logger.info("Executing crew workflow") |
| | result = crew.kickoff() |
| | |
| | |
| | result_str = str(result) |
| | logger.info("Parsing analysis results") |
| | parsed_result = self._parse_result(result_str) |
| | |
| | |
| | parsed_result["tool_states"] = self.tool_states |
| | |
| | |
| | parsed_result["timeframe"] = self.timeframe |
| | parsed_result["max_allocation"] = self.max_allocation |
| | |
| | |
| | if parsed_result.get("allocation_percentage", 0) > self.max_allocation: |
| | logger.warning(f"Capping allocation from {parsed_result['allocation_percentage']}% to {self.max_allocation}%") |
| | parsed_result["allocation_percentage"] = self.max_allocation |
| | parsed_result["portfolio_allocation"] = self.max_allocation |
| | |
| | |
| | logger.info(f"Analysis complete - Signal: {parsed_result['signal']}, Confidence: {parsed_result['confidence']}%, Allocation: {parsed_result.get('allocation_percentage', 0)}%") |
| | |
| | |
| | tool_errors = [] |
| | for tool_name, state in self.tool_states.items(): |
| | if state.get("status") == "error" and state.get("error"): |
| | tool_errors.append(f"{tool_name}: {state['error']}") |
| | |
| | if tool_errors: |
| | error_summary = "; ".join(tool_errors) |
| | parsed_result["tool_error_summary"] = error_summary |
| | logger.warning(f"Tool errors occurred during analysis: {error_summary}") |
| | |
| | return parsed_result |
| | |
| | except Exception as e: |
| | logger.error(f"Error in Bitcoin analysis workflow: {str(e)}", exc_info=True) |
| | return { |
| | "error": str(e), |
| | "signal": "hold", |
| | "confidence": 0, |
| | "portfolio_allocation": 0, |
| | "allocation_percentage": 0, |
| | "reasoning": f"Error in analysis: {str(e)}", |
| | "tool_states": self.tool_states, |
| | "timeframe": self.timeframe, |
| | "max_allocation": self.max_allocation |
| | } |
| | |
| | def _parse_result(self, result: str) -> Dict[str, Any]: |
| | """ |
| | Parse the analysis result from text format |
| | |
| | Args: |
| | result: The text result from the crew |
| | |
| | Returns: |
| | Structured dictionary with recommendation details |
| | """ |
| | |
| | parsed = { |
| | "signal": "hold", |
| | "confidence": 0, |
| | "allocation_percentage": 0, |
| | "reasoning": "Analysis incomplete", |
| | "raw_result": result |
| | } |
| | |
| | try: |
| | logger.info("Parsing result from crew output") |
| | |
| | |
| | final_rec_match = re.search(r'final\s+recommendation:?\s*(buy|sell|hold)', result.lower()) |
| | if final_rec_match: |
| | parsed["signal"] = final_rec_match.group(1).lower() |
| | elif "buy" in result.lower(): |
| | parsed["signal"] = "buy" |
| | elif "sell" in result.lower(): |
| | parsed["signal"] = "sell" |
| | else: |
| | parsed["signal"] = "hold" |
| | |
| | |
| | |
| | confidence_patterns = [ |
| | r'final\s+confidence:?\s*(\d+)', |
| | r'confidence:?\s*(\d+)', |
| | r'confidence:?\s*(\d+)%', |
| | r'with\s+(\d+)%\s+confidence' |
| | ] |
| | |
| | for pattern in confidence_patterns: |
| | confidence_match = re.search(pattern, result.lower()) |
| | if confidence_match: |
| | parsed["confidence"] = int(confidence_match.group(1)) |
| | break |
| | |
| | |
| | if parsed["signal"] in ["buy", "sell"] and parsed["confidence"] < 30: |
| | parsed["confidence"] = 30 |
| | |
| | |
| | allocation_patterns = [ |
| | r'allocation\s+percentage:?\s*(\d+)', |
| | r'allocation:?\s*(\d+)', |
| | r'allocation:?\s*(\d+)%', |
| | r'allocate\s+(\d+)%' |
| | ] |
| | |
| | for pattern in allocation_patterns: |
| | allocation_match = re.search(pattern, result.lower()) |
| | if allocation_match: |
| | parsed["allocation_percentage"] = int(allocation_match.group(1)) |
| | break |
| | |
| | |
| | if parsed["signal"] in ["buy", "sell"] and parsed["allocation_percentage"] < 5: |
| | parsed["allocation_percentage"] = 5 |
| | |
| | |
| | data_reliability_match = re.search(r'data\s+reliability:?\s*(.+?)(?:\n\n|\Z|technical\s+analysis)', result, re.IGNORECASE | re.DOTALL) |
| | if data_reliability_match: |
| | parsed["data_reliability"] = data_reliability_match.group(1).strip() |
| | |
| | |
| | tool_error_match = re.search(r'tool\s+error\s+assessment:?\s*(.+?)(?:\n\n|\Z|data\s+reliability)', result, re.IGNORECASE | re.DOTALL) |
| | if tool_error_match: |
| | parsed["tool_error_assessment"] = tool_error_match.group(1).strip() |
| | |
| | |
| | detailed_reasoning_match = re.search(r'detailed\s+reasoning:?\s*(.+?)(?:\n\n|\Z|market\s+outlook)', result, re.IGNORECASE | re.DOTALL) |
| | if detailed_reasoning_match: |
| | parsed["reasoning"] = detailed_reasoning_match.group(1).strip() |
| | else: |
| | reasoning_match = re.search(r'reasoning:?\s*(.+?)(?:\n\n|\Z)', result, re.IGNORECASE | re.DOTALL) |
| | if reasoning_match: |
| | parsed["reasoning"] = reasoning_match.group(1).strip() |
| | else: |
| | |
| | paragraphs = result.split('\n\n') |
| | for paragraph in paragraphs: |
| | if len(paragraph) > 100 and not paragraph.startswith('-'): |
| | parsed["reasoning"] = paragraph.strip() |
| | break |
| | |
| | |
| | tech_analysis_match = re.search(r'technical\s+analysis\s+summary:?\s*(.+?)(?:\n\n|\Z|initial\s+analysis)', result, re.IGNORECASE | re.DOTALL) |
| | if tech_analysis_match: |
| | parsed["technical_analysis"] = tech_analysis_match.group(1).strip() |
| | |
| | initial_analysis_match = re.search(r'initial\s+analysis\s+summary:?\s*(.+?)(?:\n\n|\Z|reflection\s+analysis)', result, re.IGNORECASE | re.DOTALL) |
| | if initial_analysis_match: |
| | parsed["initial_analysis"] = initial_analysis_match.group(1).strip() |
| | |
| | reflection_analysis_match = re.search(r'reflection\s+analysis\s+summary:?\s*(.+?)(?:\n\n|\Z|importance\s+weighting)', result, re.IGNORECASE | re.DOTALL) |
| | if reflection_analysis_match: |
| | parsed["reflection_analysis"] = reflection_analysis_match.group(1).strip() |
| | |
| | impact_tech_match = re.search(r'impact\s+of\s+technical\s+factors:?\s*(.+?)(?:\n\n|\Z|impact\s+of\s+market)', result, re.IGNORECASE | re.DOTALL) |
| | if impact_tech_match: |
| | parsed["impact_technical"] = impact_tech_match.group(1).strip() |
| | |
| | impact_market_match = re.search(r'impact\s+of\s+market\s+context:?\s*(.+?)(?:\n\n|\Z|impact\s+of\s+sentiment)', result, re.IGNORECASE | re.DOTALL) |
| | if impact_market_match: |
| | parsed["impact_market"] = impact_market_match.group(1).strip() |
| | |
| | impact_sentiment_match = re.search(r'impact\s+of\s+sentiment:?\s*(.+?)(?:\n\n|\Z|final\s+recommendation)', result, re.IGNORECASE | re.DOTALL) |
| | if impact_sentiment_match: |
| | parsed["impact_sentiment"] = impact_sentiment_match.group(1).strip() |
| | |
| | market_outlook_match = re.search(r'market\s+outlook:?\s*(.+?)(?:\n\n|\Z|risk\s+assessment)', result, re.IGNORECASE | re.DOTALL) |
| | if market_outlook_match: |
| | parsed["market_outlook"] = market_outlook_match.group(1).strip() |
| | |
| | risk_assessment_match = re.search(r'risk\s+assessment:?\s*(.+?)(?:\n\n|\Z|trade\s+execution)', result, re.IGNORECASE | re.DOTALL) |
| | if risk_assessment_match: |
| | parsed["risk_assessment"] = risk_assessment_match.group(1).strip() |
| | |
| | |
| | |
| | execution_section_pattern = r'trade\s+execution:?\s*(.+?)(?:\n\n|\Z)' |
| | execution_match = re.search(execution_section_pattern, result, re.IGNORECASE | re.DOTALL) |
| | |
| | if execution_match: |
| | execution_text = execution_match.group(1).strip() |
| | parsed["order_execution_text"] = execution_text |
| | |
| | |
| | order_id_match = re.search(r'order\s+id:?\s*([a-f0-9-]+)', execution_text, re.IGNORECASE) |
| | if order_id_match: |
| | |
| | parsed["order_execution"] = { |
| | "order_id": order_id_match.group(1), |
| | "symbol": re.search(r'symbol:?\s*([A-Z/]+)', execution_text, re.IGNORECASE).group(1) if re.search(r'symbol:?\s*([A-Z/]+)', execution_text, re.IGNORECASE) else "BTC/USD", |
| | "side": re.search(r'side:?\s*(\w+)', execution_text, re.IGNORECASE).group(1) if re.search(r'side:?\s*(\w+)', execution_text, re.IGNORECASE) else parsed["signal"], |
| | "quantity": re.search(r'quantity:?\s*([\d.]+)', execution_text, re.IGNORECASE).group(1) if re.search(r'quantity:?\s*([\d.]+)', execution_text, re.IGNORECASE) else "unknown", |
| | "status": re.search(r'status:?\s*(\w+)', execution_text, re.IGNORECASE).group(1) if re.search(r'status:?\s*(\w+)', execution_text, re.IGNORECASE) else "unknown", |
| | "success": True |
| | } |
| | elif "check" in execution_text.lower() and "account" in execution_text.lower(): |
| | |
| | |
| | cash_match = re.search(r'cash:?\s*([\d.]+)', execution_text, re.IGNORECASE) |
| | equity_match = re.search(r'equity:?\s*([\d.]+)', execution_text, re.IGNORECASE) |
| | |
| | account_info = "Account check completed" |
| | if cash_match: |
| | account_info += f", Cash: ${cash_match.group(1)}" |
| | if equity_match: |
| | account_info += f", Equity: ${equity_match.group(1)}" |
| | |
| | parsed["order_execution"] = account_info |
| | elif "failed" in execution_text.lower() or "error" in execution_text.lower(): |
| | |
| | error_message = "Unknown error" |
| | error_match = re.search(r'error:?\s*(.+?)(?:\n|\Z)', execution_text, re.IGNORECASE) |
| | if error_match: |
| | error_message = error_match.group(1).strip() |
| | |
| | parsed["order_execution"] = { |
| | "success": False, |
| | "error": error_message |
| | } |
| | else: |
| | |
| | parsed["order_execution"] = execution_text |
| | |
| | |
| | parsed["portfolio_allocation"] = parsed["allocation_percentage"] |
| | |
| | logger.info(f"Successfully parsed result: signal={parsed['signal']}, confidence={parsed['confidence']}%, allocation={parsed['allocation_percentage']}%") |
| | return parsed |
| | |
| | except Exception as e: |
| | logger.error(f"Error parsing result: {str(e)}", exc_info=True) |
| | parsed["reasoning"] = f"Error parsing result: {str(e)}" |
| | return parsed |
| |
|
| | def track_tool_call(self, tool_name: str, response: Dict[str, Any]) -> None: |
| | """ |
| | Track a tool call and update its state |
| | |
| | Args: |
| | tool_name: Name of the tool being called |
| | response: Response from the tool call |
| | """ |
| | |
| | if tool_name not in self.tool_states: |
| | self.tool_states[tool_name] = {"status": "not_started", "error": None} |
| | |
| | |
| | if "error" in response: |
| | self.tool_states[tool_name]["status"] = "error" |
| | self.tool_states[tool_name]["error"] = response["error"] |
| | logger.warning(f"Tool '{tool_name}' reported an error: {response['error']}") |
| | else: |
| | self.tool_states[tool_name]["status"] = "success" |
| | self.tool_states[tool_name]["error"] = None |
| | logger.info(f"Tool '{tool_name}' executed successfully") |
| |
|
| | |
| | def _patch_tools_for_monitoring(self) -> None: |
| | """ |
| | Patch tool _run methods to track their execution status |
| | """ |
| | for tool_name, tool in [ |
| | ("bitcoin_data", self.bitcoin_data_tool), |
| | ("bitcoin_news", self.bitcoin_news_tool), |
| | ("technical_strategy", self.technical_strategy_tool), |
| | ("crypto_market", self.crypto_market_tool), |
| | ("order_tool", self.order_tool) |
| | ]: |
| | |
| | |
| | def patch_tool(current_tool_name, current_tool): |
| | original_run = current_tool._run |
| | |
| | @functools.wraps(original_run) |
| | def wrapped_run(*args, **kwargs): |
| | try: |
| | |
| | self.tool_states[current_tool_name]["status"] = "running" |
| | |
| | |
| | result = original_run(*args, **kwargs) |
| | |
| | |
| | self.track_tool_call(current_tool_name, result) |
| | |
| | return result |
| | except Exception as e: |
| | |
| | self.tool_states[current_tool_name]["status"] = "error" |
| | self.tool_states[current_tool_name]["error"] = str(e) |
| | logger.error(f"Exception in tool '{current_tool_name}': {str(e)}", exc_info=True) |
| | |
| | |
| | return {"error": f"Exception in tool execution: {str(e)}"} |
| | |
| | |
| | current_tool._run = wrapped_run |
| | |
| | |
| | patch_tool(tool_name, tool) |