diff --git a/READ.MD b/READ.MD index b283772d..73d22a5d 100644 --- a/READ.MD +++ b/READ.MD @@ -54,6 +54,10 @@ The system supports multiple data vendors with flexible routing: - Uses sentiment scoring algorithms - Gauges short-term market mood and retail investor sentiment +- **Profile Analyst** (`nalysts/profile_abalyst.py`): + - Analyzez users balance including total equity and free margin + - Analyzez users open orders, locked assets, stop losses, and take profits + ##### **Research & Decision Making** - **Bull Researcher** (`researchers/bull_researcher.py`): Argues for positive investment opportunities - **Bear Researcher** (`researchers/bear_researcher.py`): Identifies risks and potential downsides diff --git a/README.md b/README.md index 56cde910..9d0e0f75 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ Our framework decomposes complex trading tasks into specialized roles. This ensu - Sentiment Analyst: Analyzes social media and public sentiment using sentiment scoring algorithms to gauge short-term market mood. - News Analyst: Monitors global news and macroeconomic indicators, interpreting the impact of events on market conditions. - Technical Analyst: Utilizes technical indicators (like MACD and RSI) to detect trading patterns and forecast price movements. +- Profile Analyst: Analyzes users portfolio, balance, and active open orders.

diff --git a/cli/main.py b/cli/main.py index 56508472..1a2b3c88 100644 --- a/cli/main.py +++ b/cli/main.py @@ -51,6 +51,7 @@ class MessageBuffer: "Social Analyst": "pending", "News Analyst": "pending", "Fundamentals Analyst": "pending", + "Profile Analyst": "pending", # Research Team "Bull Researcher": "pending", "Bear Researcher": "pending", @@ -70,6 +71,7 @@ class MessageBuffer: "sentiment_report": None, "news_report": None, "fundamentals_report": None, + "profile_report": None, "investment_plan": None, "trader_investment_plan": None, "final_trade_decision": None, @@ -111,6 +113,7 @@ class MessageBuffer: "sentiment_report": "Social Sentiment", "news_report": "News Analysis", "fundamentals_report": "Fundamentals Analysis", + "profile_report": "Profile Analysis", "investment_plan": "Research Team Decision", "trader_investment_plan": "Trading Team Plan", "final_trade_decision": "Portfolio Management Decision", @@ -133,6 +136,7 @@ class MessageBuffer: "sentiment_report", "news_report", "fundamentals_report", + "profile_report", ] ): report_parts.append("## Analyst Team Reports") @@ -152,6 +156,10 @@ class MessageBuffer: report_parts.append( f"### Fundamentals Analysis\n{self.report_sections['fundamentals_report']}" ) + if self.report_sections["profile_report"]: + report_parts.append( + f"### Profile Analysis\n{self.report_sections['profile_report']}" + ) # Research Team Reports if self.report_sections["investment_plan"]: @@ -224,6 +232,7 @@ def update_display(layout, spinner_text=None): "Social Analyst", "News Analyst", "Fundamentals Analyst", + "Profile Analyst", ], "Research Team": ["Bull Researcher", "Bear Researcher", "Research Manager"], "Trading Team": ["Trader"], @@ -571,6 +580,19 @@ def display_complete_report(final_state): ) ) + # Profile Analyst Report + if final_state.get("profile_report"): + analyst_reports.append( + Panel( + Markdown(final_state["profile_report"]), + title="Profile Analyst", + border_style="blue", + padding=(1, 2), + ) + ) + + # Display all analyst reports in a single panel + if analyst_reports: console.print( Panel( @@ -916,6 +938,17 @@ def run_analysis(): message_buffer.update_agent_status( "Fundamentals Analyst", "completed" ) + # Set next analyst to in_progress + if "profile" in selections["analysts"]: + message_buffer.update_agent_status( + "Profile Analyst", "in_progress" + ) + + if "profile_report" in chunk and chunk["profile_report"]: + message_buffer.update_report_section( + "profile_report", chunk["profile_report"] + ) + message_buffer.update_agent_status("Profile Analyst", "completed") # Set all research team members to in_progress update_research_team_status("in_progress") diff --git a/cli/models.py b/cli/models.py index f68c3da1..6f5fa18c 100644 --- a/cli/models.py +++ b/cli/models.py @@ -8,3 +8,4 @@ class AnalystType(str, Enum): SOCIAL = "social" NEWS = "news" FUNDAMENTALS = "fundamentals" + PROFILE = "profile" diff --git a/cli/utils.py b/cli/utils.py index 7b9682a6..cff98720 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -8,6 +8,7 @@ ANALYST_ORDER = [ ("Social Media Analyst", AnalystType.SOCIAL), ("News Analyst", AnalystType.NEWS), ("Fundamentals Analyst", AnalystType.FUNDAMENTALS), + ("Profile Analyst", AnalystType.PROFILE), ] diff --git a/playground.ipynb b/playground.ipynb index a8cb75ce..0ea0d9b1 100644 --- a/playground.ipynb +++ b/playground.ipynb @@ -2,273 +2,390 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "ab4c0304", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Crypto data for BTC/USDT from 2025-12-20 to 2025-12-25\n", + "# Total records: 7\n", + "# Data retrieved on: 2025-12-30 23:12:08 UTC\n", + "\n", + "Date,Open,High,Low,Close,Volume\n", + "2025-12-19,85506.9,89404.6,85104.9,88141.3,9324\n", + "2025-12-20,88141.3,88570.1,87797.9,88356,2252\n", + "2025-12-21,88356,89085.4,87587.8,88660.8,3438\n", + "2025-12-22,88660.8,90599.9,87888.9,88623.8,9026\n", + "2025-12-23,88623.8,88949.6,86595,87486.5,7168\n", + "2025-12-24,87486.5,88051.1,86412.6,87675,6033\n", + "2025-12-25,87675,88598.6,86953.4,87216.7,3872\n" + ] + } + ], "source": [ - "from tradingagents.dataflows.binance import get_market_data\n", + "from tradingagents.dataflows.bybit import *\n", "\n", "print(get_market_data('BTC/USDT', '2025-12-20', '2025-12-25'))" ] }, { "cell_type": "code", - "execution_count": null, - "id": "a58bb868", + "execution_count": 2, + "id": "c4d08861", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "from tradingagents.dataflows.taapi import get_crypto_stats_indicators_window\n", - "print(get_crypto_stats_indicators_window('BTC/USDT', 'macd', '2025-12-20', 3))" + "from dotenv import load_dotenv\n", + "load_dotenv()" ] }, { "cell_type": "code", - "execution_count": null, - "id": "60a3bc38", - "metadata": {}, - "outputs": [], - "source": [ - "from tradingagents.dataflows.taapi import get_crypto_stats_indicators\n", - "print(get_crypto_stats_indicators('BTC/USDT', ['rsi', 'macd'], '2025-12-25', 30))\n", - " # 08:42:55 [Tool Call] get_indicators_bulk(symbol=BTCUSDT, indicators=['sma', 'rsi', 'macd', 'bbands', 'atr'], curr_date=2025-12-26, look_back_days=30))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3f341582", - "metadata": {}, - "outputs": [], - "source": [ - "from tradingagents.dataflows.openai import get_whitepaper_openai\n", - "whitepaper = get_whitepaper_openai('BTC/USDT')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6db71e93", - "metadata": {}, - "outputs": [], - "source": [ - "print(whitepaper.output[0].content[0].text)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c00b90b8", - "metadata": {}, - "outputs": [], - "source": [ - "from tradingagents.dataflows.openai import get_fundamentals_openai\n", - "fundamentals = get_fundamentals_openai('BTC/USDT', '2025-12-25')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01b03899", - "metadata": {}, - "outputs": [], - "source": [ - "print(fundamentals)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "15fd34ce", - "metadata": {}, - "outputs": [], - "source": [ - "# Clean test of market_analyst only\n", - "print(\"=== CLEAN MARKET ANALYST TEST ===\")\n", - "print()\n", - "\n", - "# Import required modules\n", - "from langchain_openai import ChatOpenAI\n", - "from langchain_core.messages import HumanMessage\n", - "from tradingagents.agents.analysts.market_analyst import create_market_analyst\n", - "from datetime import date\n", - "import os\n", - "\n", - "# Create LLM client\n", - "llm = ChatOpenAI(\n", - " model=\"gpt-4o-mini\", \n", - " temperature=0.1,\n", - " api_key=os.getenv(\"OPENAI_API_KEY\")\n", - ")\n", - "\n", - "# Create market analyst\n", - "market_analyst = create_market_analyst(llm)\n", - "\n", - "# Correct test state with proper message format\n", - "test_state = {\n", - " \"trade_date\": \"2025-12-25\",\n", - " \"ticker_of_interest\": \"BTC/USDT\",\n", - " \"messages\": [HumanMessage(content=\"Analyze BTC/USDT market conditions\")]\n", - "}\n", - "\n", - "print(f\"Testing market analyst for: {test_state['ticker_of_interest']}\")\n", - "print(f\"Date: {test_state['trade_date']}\")\n", - "print(\"\\nRunning market analysis...\")\n", - "print(\"-\" * 40)\n", - "\n", - "try:\n", - " # Run the market analyst\n", - " result = market_analyst(test_state)\n", - " \n", - " print(\"āœ… SUCCESS! Market analysis completed.\")\n", - " \n", - " # Print the analysis result\n", - " if \"messages\" in result and result[\"messages\"]:\n", - " latest_message = result[\"messages\"][-1]\n", - " \n", - " # Check if it made tool calls\n", - " if hasattr(latest_message, 'tool_calls') and latest_message.tool_calls:\n", - " print(f\"\\nšŸ“Š Tools called: {len(latest_message.tool_calls)}\")\n", - " for i, call in enumerate(latest_message.tool_calls):\n", - " tool_name = call.get('name', 'unknown')\n", - " print(f\" {i+1}. {tool_name}\")\n", - " \n", - " # Print the content/analysis\n", - " if hasattr(latest_message, 'content') and latest_message.content:\n", - " print(f\"\\nšŸ“ Market Analysis:\")\n", - " print(\"=\" * 50)\n", - " print(latest_message.content)\n", - " \n", - " # Print market report if available\n", - " if \"market_report\" in result and result[\"market_report\"]:\n", - " print(f\"\\nšŸ“‹ Market Report:\")\n", - " print(\"=\" * 50)\n", - " print(result[\"market_report\"])\n", - " \n", - "except Exception as e:\n", - " print(f\"āŒ ERROR: {e}\")\n", - " import traceback\n", - " traceback.print_exc()" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "fcf9fe21", + "execution_count": 3, + "id": "3744edd7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "5000.62453368\n" + "# Account Balance Report for BTC/USDT\n", + "** Total Equity: $7965.89736959 **\n", + "## USDT (Quote) Details:\n", + "{\n", + " \"spotBorrow\": 0.0,\n", + " \"availableToBorrow\": 0.0,\n", + " \"bonus\": 0.0,\n", + " \"accruedInterest\": 0.0,\n", + " \"availableToWithdraw\": 0.0,\n", + " \"totalOrderIM\": 0.0,\n", + " \"equity\": 5002.44462368,\n", + " \"totalPositionMM\": 0.0,\n", + " \"usdValue\": 4997.1670446,\n", + " \"unrealisedPnl\": 0.0,\n", + " \"collateralSwitch\": 1.0,\n", + " \"spotHedgingQty\": 0.0,\n", + " \"borrowAmount\": 0.0,\n", + " \"totalPositionIM\": 0.0,\n", + " \"walletBalance\": 5002.44462368,\n", + " \"cumRealisedPnl\": -138.82418652,\n", + " \"locked\": 280.0,\n", + " \"marginCollateral\": 1.0\n", + "}\n", + "## BTC (Base) Details:\n", + "{\n", + " \"spotBorrow\": 0.0,\n", + " \"availableToBorrow\": 0.0,\n", + " \"bonus\": 0.0,\n", + " \"accruedInterest\": 0.0,\n", + " \"availableToWithdraw\": 0.0,\n", + " \"totalOrderIM\": 0.0,\n", + " \"equity\": 4e-07,\n", + " \"totalPositionMM\": 0.0,\n", + " \"usdValue\": 0.03615499,\n", + " \"unrealisedPnl\": 0.0,\n", + " \"collateralSwitch\": 0.0,\n", + " \"spotHedgingQty\": 0.0,\n", + " \"borrowAmount\": 0.0,\n", + " \"totalPositionIM\": 0.0,\n", + " \"walletBalance\": 4e-07,\n", + " \"cumRealisedPnl\": -0.00056459,\n", + " \"locked\": 0.0,\n", + " \"marginCollateral\": 1.0\n", + "}\n", + "\n" ] } ], "source": [ - "from tradingagents.dataflows.bybit import get_wallet_balance\n", - "balance = get_wallet_balance('USDT')\n", - "print(balance)" + "print(get_account_balance('BTC/USDT'))" ] }, { "cell_type": "code", - "execution_count": 3, - "id": "1dfa3165", - "metadata": {}, - "outputs": [ - { - "ename": "ValueError", - "evalue": "Failed to place order: Bybit API error: An unknown parameter was sent.", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mValueError\u001b[39m Traceback (most recent call last)", - "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/projects/halalquant/TradingAgents/tradingagents/dataflows/bybit.py:292\u001b[39m, in \u001b[36mplace_order\u001b[39m\u001b[34m(symbol, side, order_type, qty, price, stop_loss, take_profit, time_in_force, account_type, category, order_link_id, reduce_only, close_on_trigger)\u001b[39m\n\u001b[32m 291\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m292\u001b[39m data = \u001b[43mbybit_v5_request\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mPOST\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m/v5/order/create\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[43m=\u001b[49m\u001b[43mbody\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 293\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m data.get(\u001b[33m\"\u001b[39m\u001b[33mresult\u001b[39m\u001b[33m\"\u001b[39m, {})\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/projects/halalquant/TradingAgents/tradingagents/dataflows/bybit.py:61\u001b[39m, in \u001b[36mbybit_v5_request\u001b[39m\u001b[34m(method, path, params, body)\u001b[39m\n\u001b[32m 60\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m data.get(\u001b[33m\"\u001b[39m\u001b[33mretCode\u001b[39m\u001b[33m\"\u001b[39m) != \u001b[32m0\u001b[39m:\n\u001b[32m---> \u001b[39m\u001b[32m61\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mBybit API error: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mdata.get(\u001b[33m'\u001b[39m\u001b[33mretMsg\u001b[39m\u001b[33m'\u001b[39m)\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n\u001b[32m 63\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m data\n", - "\u001b[31mValueError\u001b[39m: Bybit API error: An unknown parameter was sent.", - "\nDuring handling of the above exception, another exception occurred:\n", - "\u001b[31mValueError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mtradingagents\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mdataflows\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mbybit\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m place_order, place_spot_order_with_sl_tp\n\u001b[32m 2\u001b[39m \u001b[38;5;66;03m# Place a spot limit order with stop loss and take profit\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m result = \u001b[43mplace_spot_order_with_sl_tp\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 4\u001b[39m \u001b[43m \u001b[49m\u001b[43msymbol\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mBTCUSDT\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 5\u001b[39m \u001b[43m \u001b[49m\u001b[43mside\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mBuy\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 6\u001b[39m \u001b[43m \u001b[49m\u001b[43mqty\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m0.001\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 7\u001b[39m \u001b[43m \u001b[49m\u001b[43mprice\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m88000.0\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 8\u001b[39m \u001b[43m \u001b[49m\u001b[43mstop_loss_price\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m85000.0\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[32m 9\u001b[39m \u001b[43m \u001b[49m\u001b[43mtake_profit_price\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m90000.0\u001b[39;49m\n\u001b[32m 10\u001b[39m \u001b[43m)\u001b[49m\n\u001b[32m 12\u001b[39m \u001b[38;5;66;03m# # Place a market order\u001b[39;00m\n\u001b[32m 13\u001b[39m \u001b[38;5;66;03m# result = place_order(\u001b[39;00m\n\u001b[32m 14\u001b[39m \u001b[38;5;66;03m# symbol=\"BTCUSDT\",\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 18\u001b[39m \u001b[38;5;66;03m# category=\"spot\"\u001b[39;00m\n\u001b[32m 19\u001b[39m \u001b[38;5;66;03m# )\u001b[39;00m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/projects/halalquant/TradingAgents/tradingagents/dataflows/bybit.py:322\u001b[39m, in \u001b[36mplace_spot_order_with_sl_tp\u001b[39m\u001b[34m(symbol, side, qty, price, stop_loss_price, take_profit_price, order_type)\u001b[39m\n\u001b[32m 298\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mplace_spot_order_with_sl_tp\u001b[39m(\n\u001b[32m 299\u001b[39m symbol: \u001b[38;5;28mstr\u001b[39m,\n\u001b[32m 300\u001b[39m side: \u001b[38;5;28mstr\u001b[39m,\n\u001b[32m (...)\u001b[39m\u001b[32m 305\u001b[39m order_type: \u001b[38;5;28mstr\u001b[39m = \u001b[33m\"\u001b[39m\u001b[33mLimit\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 306\u001b[39m ) -> Dict:\n\u001b[32m 307\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 308\u001b[39m \u001b[33;03m Convenience function to place a spot order with stop loss and take profit.\u001b[39;00m\n\u001b[32m 309\u001b[39m \u001b[33;03m \u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 320\u001b[39m \u001b[33;03m Dict containing order result\u001b[39;00m\n\u001b[32m 321\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m322\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mplace_order\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 323\u001b[39m \u001b[43m \u001b[49m\u001b[43msymbol\u001b[49m\u001b[43m=\u001b[49m\u001b[43msymbol\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 324\u001b[39m \u001b[43m \u001b[49m\u001b[43mside\u001b[49m\u001b[43m=\u001b[49m\u001b[43mside\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 325\u001b[39m \u001b[43m \u001b[49m\u001b[43morder_type\u001b[49m\u001b[43m=\u001b[49m\u001b[43morder_type\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 326\u001b[39m \u001b[43m \u001b[49m\u001b[43mqty\u001b[49m\u001b[43m=\u001b[49m\u001b[43mqty\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 327\u001b[39m \u001b[43m \u001b[49m\u001b[43mprice\u001b[49m\u001b[43m=\u001b[49m\u001b[43mprice\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 328\u001b[39m \u001b[43m \u001b[49m\u001b[43mstop_loss\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstop_loss_price\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 329\u001b[39m \u001b[43m \u001b[49m\u001b[43mtake_profit\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtake_profit_price\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 330\u001b[39m \u001b[43m \u001b[49m\u001b[43mcategory\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mspot\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\n\u001b[32m 331\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/Documents/projects/halalquant/TradingAgents/tradingagents/dataflows/bybit.py:295\u001b[39m, in \u001b[36mplace_order\u001b[39m\u001b[34m(symbol, side, order_type, qty, price, stop_loss, take_profit, time_in_force, account_type, category, order_link_id, reduce_only, close_on_trigger)\u001b[39m\n\u001b[32m 293\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m data.get(\u001b[33m\"\u001b[39m\u001b[33mresult\u001b[39m\u001b[33m\"\u001b[39m, {})\n\u001b[32m 294\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m--> \u001b[39m\u001b[32m295\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mFailed to place order: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mstr\u001b[39m(e)\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n", - "\u001b[31mValueError\u001b[39m: Failed to place order: Bybit API error: An unknown parameter was sent." - ] - } - ], - "source": [ - "from tradingagents.dataflows.bybit import place_order, place_spot_order_with_sl_tp\n", - "# Place a spot limit order with stop loss and take profit\n", - "result = place_spot_order_with_sl_tp(\n", - " symbol=\"BTCUSDT\",\n", - " side=\"Buy\",\n", - " qty=0.001,\n", - " price=88000.0,\n", - " stop_loss_price=85000.0,\n", - " take_profit_price=90000.0\n", - ")\n", - "\n", - "# # Place a market order\n", - "# result = place_order(\n", - "# symbol=\"BTCUSDT\",\n", - "# side=\"Buy\",\n", - "# order_type=\"Market\",\n", - "# qty=0.001,\n", - "# category=\"spot\"\n", - "# )" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "03bdc3c4", + "execution_count": 4, + "id": "4d338e6c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Order result: {'orderId': '2113891849709292800', 'orderLinkId': '2113891849709292801'}\n", - "Order status: {'symbol': 'BTCUSDT', 'orderType': 'Limit', 'orderLinkId': '2113891849709292801', 'slLimitPrice': '27500', 'orderId': '2113891849709292800', 'cancelType': 'UNKNOWN', 'avgPrice': '0.0', 'stopOrderType': '', 'lastPriceOnCreated': '', 'orderStatus': 'New', 'takeProfit': '35000', 'cumExecValue': '0.0000000', 'smpType': 'None', 'triggerDirection': 0, 'blockTradeId': '', 'cumFeeDetail': {}, 'isLeverage': '0', 'rejectReason': 'EC_NoError', 'price': '28000.0', 'orderIv': '', 'createdTime': '1766731545590', 'tpTriggerBy': '', 'positionIdx': 0, 'trailingPercentage': '0', 'timeInForce': 'PostOnly', 'leavesValue': '280.0000000', 'basePrice': '89223.5', 'updatedTime': '1766731545591', 'side': 'Buy', 'smpGroup': 0, 'triggerPrice': '0.0', 'tpLimitPrice': '36000', 'trailingValue': '0', 'cumExecFee': '0', 'leavesQty': '0.01', 'slTriggerBy': '', 'closeOnTrigger': False, 'placeType': '', 'cumExecQty': '0', 'reduceOnly': False, 'activationPrice': '0', 'qty': '0.010000', 'stopLoss': '27000', 'marketUnit': '', 'smpOrderId': '', 'triggerBy': ''}\n", - "Open orders: {'nextPageCursor': '2113891849709292800%3A1766731545590%2C2113891849709292800%3A1766731545590', 'category': 'spot', 'list': [{'symbol': 'BTCUSDT', 'orderType': 'Limit', 'orderLinkId': '2113891849709292801', 'slLimitPrice': '27500', 'orderId': '2113891849709292800', 'cancelType': 'UNKNOWN', 'avgPrice': '0.0', 'stopOrderType': '', 'lastPriceOnCreated': '', 'orderStatus': 'New', 'takeProfit': '35000', 'cumExecValue': '0.0000000', 'smpType': 'None', 'triggerDirection': 0, 'blockTradeId': '', 'cumFeeDetail': {}, 'isLeverage': '0', 'rejectReason': 'EC_NoError', 'price': '28000.0', 'orderIv': '', 'createdTime': '1766731545590', 'tpTriggerBy': '', 'positionIdx': 0, 'trailingPercentage': '0', 'timeInForce': 'PostOnly', 'leavesValue': '280.0000000', 'basePrice': '89223.5', 'updatedTime': '1766731545591', 'side': 'Buy', 'smpGroup': 0, 'triggerPrice': '0.0', 'tpLimitPrice': '36000', 'trailingValue': '0', 'cumExecFee': '0', 'leavesQty': '0.01', 'slTriggerBy': '', 'closeOnTrigger': False, 'placeType': '', 'cumExecQty': '0', 'reduceOnly': False, 'activationPrice': '0', 'qty': '0.010000', 'stopLoss': '27000', 'marketUnit': '', 'smpOrderId': '', 'triggerBy': ''}]}\n" + "# Open Orders for BTCUSDT\n", + "[\n", + " {\n", + " \"symbol\": \"BTCUSDT\",\n", + " \"orderType\": \"Limit\",\n", + " \"orderLinkId\": \"2113891849709292801\",\n", + " \"slLimitPrice\": 27500.0,\n", + " \"orderId\": \"2113891849709292800\",\n", + " \"cancelType\": \"UNKNOWN\",\n", + " \"avgPrice\": 0.0,\n", + " \"stopOrderType\": \"\",\n", + " \"lastPriceOnCreated\": \"\",\n", + " \"orderStatus\": \"New\",\n", + " \"takeProfit\": 35000.0,\n", + " \"cumExecValue\": 0.0,\n", + " \"smpType\": \"None\",\n", + " \"triggerDirection\": 0.0,\n", + " \"blockTradeId\": \"\",\n", + " \"cumFeeDetail\": {},\n", + " \"isLeverage\": 0.0,\n", + " \"rejectReason\": \"EC_NoError\",\n", + " \"price\": 28000.0,\n", + " \"orderIv\": \"\",\n", + " \"createdTime\": \"2025-12-26 06:45:45\",\n", + " \"tpTriggerBy\": \"\",\n", + " \"positionIdx\": 0.0,\n", + " \"trailingPercentage\": 0.0,\n", + " \"timeInForce\": \"PostOnly\",\n", + " \"leavesValue\": 280.0,\n", + " \"basePrice\": 89223.5,\n", + " \"updatedTime\": \"2025-12-26 06:45:45\",\n", + " \"side\": \"Buy\",\n", + " \"smpGroup\": 0.0,\n", + " \"triggerPrice\": 0.0,\n", + " \"tpLimitPrice\": 36000.0,\n", + " \"trailingValue\": 0.0,\n", + " \"cumExecFee\": 0.0,\n", + " \"leavesQty\": 0.01,\n", + " \"slTriggerBy\": \"\",\n", + " \"closeOnTrigger\": 0.0,\n", + " \"placeType\": \"\",\n", + " \"cumExecQty\": 0.0,\n", + " \"reduceOnly\": 0.0,\n", + " \"activationPrice\": 0.0,\n", + " \"qty\": 0.01,\n", + " \"stopLoss\": 27000.0,\n", + " \"marketUnit\": \"\",\n", + " \"smpOrderId\": \"\",\n", + " \"triggerBy\": \"\"\n", + " }\n", + "]\n" ] } ], "source": [ - "# Fixed version with correct Bybit parameters\n", - "from tradingagents.dataflows.bybit import place_order, place_spot_order_with_sl_tp, get_order_status, get_open_orders\n", - "\n", - "# Place a spot limit order with stop loss and take profit - like the example\n", - "# result = place_spot_order_with_sl_tp(\n", - "# symbol=\"BTCUSDT\",\n", - "# side=\"Buy\",\n", - "# qty=0.01,\n", - "# price=28000.0,\n", - "# stop_loss_price=27000.0,\n", - "# take_profit_price=35000.0,\n", - "# sl_limit_price=27500.0, # Stop loss limit price\n", - "# tp_limit_price=36000.0, # Take profit limit price\n", - "# sl_order_type=\"Limit\", # Stop loss as limit order\n", - "# tp_order_type=\"Limit\", # Take profit as limit order\n", - "# time_in_force=\"PostOnly\" # Post only for maker orders\n", - "# )\n", - "\n", - "print(\"Order result:\", result)\n", - "# Check order status\n", - "status = get_order_status(order_id=result.get(\"orderId\", \"2113891849709292800\"), category=\"spot\")\n", - "\n", - "print(\"Order status:\", status)\n", - "\n", - "# Get open orders\n", - "open_orders = get_open_orders(symbol=\"BTCUSDT\", category=\"spot\")\n", - "print(\"Open orders:\", open_orders)" + "print(get_open_orders('BTC/USDT'))" ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f2782eb4", + "metadata": {}, + "outputs": [], + "source": [ + "from tradingagents.dataflows.y_finance import *" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "fdd7515f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "## close_10_ema values from 2025-12-15 to 2025-12-25:\n", + "\n", + "2025-12-25: N/A: Not a trading day (weekend or holiday)\n", + "2025-12-24: 273.957107566544\n", + "2025-12-23: 273.9897986794218\n", + "2025-12-22: 274.35197941894614\n", + "2025-12-21: N/A: Not a trading day (weekend or holiday)\n", + "2025-12-20: N/A: Not a trading day (weekend or holiday)\n", + "2025-12-19: 275.1035301296668\n", + "2025-12-18: 275.42208939676294\n", + "2025-12-17: 276.1403309423977\n", + "2025-12-16: 277.0959608545104\n", + "2025-12-15: 277.64839985516545\n", + "\n", + "\n", + "10 EMA: A responsive short-term average. Usage: Capture quick shifts in momentum and potential entry points. Tips: Prone to noise in choppy markets; use alongside longer averages for filtering false signals.\n" + ] + } + ], + "source": [ + "print(get_stock_stats_indicators_window(symbol='AAPL', indicator='close_10_ema', curr_date='2025-12-25', look_back_days=10))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e469bf97", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "## close_200_sma values for BTCUSDT from 2025-12-15 to 2025-12-25:\n", + "\n", + "2025-12-25: 107547.3955\n", + "2025-12-24: 107639.8590\n", + "2025-12-23: 107729.1770\n", + "2025-12-22: 107813.0945\n", + "2025-12-21: 107877.4790\n", + "2025-12-20: 107957.4840\n", + "2025-12-19: 108042.5335\n", + "2025-12-18: 108131.1640\n", + "2025-12-17: 108231.8855\n", + "2025-12-16: 108323.7295\n", + "2025-12-15: 108404.5060\n", + "\n", + "200 SMA: A long-term trend benchmark. Usage: Confirm overall market trend and identify golden/death cross setups. Tips: It reacts slowly; best for strategic trend confirmation rather than frequent trading entries.\n" + ] + } + ], + "source": [ + "print(get_crypto_indicator_window('BTC/USDT', indicator='close_200_sma', curr_date='2025-12-25', look_back_days=10))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "2ec4396d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Technical Indicators Report for BTC/USDT\n", + "\n", + "**Current Date:** 2025-12-25\n", + "**Lookback Days:** 10\n", + "\n", + "## Simple Moving Average (SMA)\n", + "\n", + "**Value:** 89244.9917\n", + "\n", + "*SMA (Simple Moving Average): A basic trend-following indicator that smooths out price data. Usage: Identify trend direction and serve as dynamic support/resistance levels. Tips: Use multiple SMAs for crossover signals; combines well with volume analysis for confirmation.*\n", + "\n", + "---\n", + "\n", + "## Exponential Moving Average (EMA)\n", + "\n", + "**Value:** 90757.8592\n", + "\n", + "*EMA (Exponential Moving Average): A trend-following indicator that gives more weight to recent prices. Usage: More responsive than SMA for trend changes and crossover signals. Tips: Better for short-term trading; reacts faster to price changes than SMA.*\n", + "\n", + "---\n", + "\n", + "\n" + ] + } + ], + "source": [ + "from tradingagents.dataflows.taapi import *\n", + "\n", + "print(get_crypto_stats_indicators(symbol='BTC/USDT', indicators=['sma', 'ema'], curr_date='2025-12-25', look_back_days=10))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "71414822", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'BTCUSDT'" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "get_symbol(\"BTC\", \"USDT\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "3b52bac5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# Technical Indicators Report for BTCUSDT\n", + "\n", + "----------------------------------------\n", + "\n", + "## close_50_sma values for BTCUSDT from 2025-12-15 to 2025-12-25:\n", + "\n", + "2025-12-25: 91694.1440\n", + "2025-12-24: 92027.2860\n", + "2025-12-23: 92303.3980\n", + "2025-12-22: 92685.2460\n", + "2025-12-21: 93123.5500\n", + "2025-12-20: 93551.8900\n", + "2025-12-19: 93976.5280\n", + "2025-12-18: 94379.7400\n", + "2025-12-17: 94869.3140\n", + "2025-12-16: 95402.0880\n", + "2025-12-15: 95926.8880\n", + "\n", + "Description: 50 SMA: A medium-term trend indicator. Usage: Identify trend direction and serve as dynamic support/resistance. Tips: It lags price; combine with faster indicators for timely signals.\n", + "----------------------------------------\n", + "\n", + "## close_200_sma values for BTCUSDT from 2025-12-15 to 2025-12-25:\n", + "\n", + "2025-12-25: 107547.3955\n", + "2025-12-24: 107639.8590\n", + "2025-12-23: 107729.1770\n", + "2025-12-22: 107813.0945\n", + "2025-12-21: 107877.4790\n", + "2025-12-20: 107957.4840\n", + "2025-12-19: 108042.5335\n", + "2025-12-18: 108131.1640\n", + "2025-12-17: 108231.8855\n", + "2025-12-16: 108323.7295\n", + "2025-12-15: 108404.5060\n", + "\n", + "Description: 200 SMA: A long-term trend benchmark. Usage: Confirm overall market trend and identify golden/death cross setups. Tips: It reacts slowly; best for strategic trend confirmation rather than frequent trading entries.\n", + "----------------------------------------\n" + ] + } + ], + "source": [ + "print(get_crypto_indicators_bulk(symbol='BTC/USDT', indicators=['close_50_sma', 'close_200_sma'], curr_date='2025-12-25', look_back_days=10))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e9206ba", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "tradingagents", "language": "python", "name": "python3" }, @@ -282,7 +399,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.7" + "version": "3.13.11" } }, "nbformat": 4, diff --git a/requirements.txt b/requirements.txt index d5ca8e83..47523b29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,4 +28,4 @@ binance-sdk-spot telethon fastapi uvicorn[standard] -redis[hiredis] \ No newline at end of file +redis[hiredis] diff --git a/tradingagents/agents/__init__.py b/tradingagents/agents/__init__.py index d84d9eb1..bb3cb915 100644 --- a/tradingagents/agents/__init__.py +++ b/tradingagents/agents/__init__.py @@ -6,6 +6,7 @@ from .analysts.fundamentals_analyst import create_fundamentals_analyst from .analysts.market_analyst import create_market_analyst from .analysts.news_analyst import create_news_analyst from .analysts.social_media_analyst import create_social_media_analyst +from .analysts.profile_analyst import create_profile_analyst from .researchers.bear_researcher import create_bear_researcher from .researchers.bull_researcher import create_bull_researcher @@ -37,4 +38,5 @@ __all__ = [ "create_safe_debator", "create_social_media_analyst", "create_trader", + "create_profile_analyst" ] diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index 95f80581..4cc802e4 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -1,5 +1,5 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -from tradingagents.agents.utils.agent_utils import get_crypto_data, get_indicators_bulk +from tradingagents.agents.utils.agent_utils import get_crypto_data, get_indicators_bulk, get_account_balance, get_open_orders def create_market_analyst(llm): @@ -11,25 +11,36 @@ def create_market_analyst(llm): tools = [ get_crypto_data, get_indicators_bulk, + get_account_balance, + get_open_orders, ] system_message = ( """You are a crypto trading assistant tasked with analyzing cryptocurrency markets. Your role is to select the **most relevant indicators** for a given crypto market condition or trading strategy from the following list. The goal is to choose the **most effective indicators** that provide complementary insights without redundancy. Available indicators are: - Moving Averages: - - sma: Simple Moving Average: A basic trend-following indicator that smooths out price data. Usage: Identify trend direction and serve as dynamic support/resistance levels. Tips: Use multiple SMAs for crossover signals; combines well with volume analysis for confirmation. +Moving Averages: +- close_50_sma: 50 SMA: A medium-term trend indicator. Usage: Identify trend direction and serve as dynamic support/resistance. Tips: It lags price; combine with faster indicators for timely signals. +- close_200_sma: 200 SMA: A long-term trend benchmark. Usage: Confirm overall market trend and identify golden/death cross setups. Tips: It reacts slowly; best for strategic trend confirmation rather than frequent trading entries. +- close_10_ema: 10 EMA: A responsive short-term average. Usage: Capture quick shifts in momentum and potential entry points. Tips: Prone to noise in choppy markets; use alongside longer averages for filtering false signals. - MACD Related: - - macd: MACD (Moving Average Convergence Divergence): Measures momentum via differences between fast and slow EMAs. Usage: Look for signal line crossovers, centerline crossovers, and divergence patterns for trend changes. Tips: Most effective in trending markets; combine with RSI to avoid false signals in sideways markets. +MACD Related: +- macd: MACD: Computes momentum via differences of EMAs. Usage: Look for crossovers and divergence as signals of trend changes. Tips: Confirm with other indicators in low-volatility or sideways markets. +- macds: MACD Signal: An EMA smoothing of the MACD line. Usage: Use crossovers with the MACD line to trigger trades. Tips: Should be part of a broader strategy to avoid false positives. +- macdh: MACD Histogram: Shows the gap between the MACD line and its signal. Usage: Visualize momentum strength and spot divergence early. Tips: Can be volatile; complement with additional filters in fast-moving markets. - Momentum Indicators: - - rsi: RSI (Relative Strength Index): Oscillator measuring momentum to identify overbought (>70) and oversold (<30) conditions. Usage: Look for reversal signals at extreme levels and divergence with price action. Tips: In strong crypto trends, RSI can remain extreme for extended periods; always confirm with trend analysis. +Momentum Indicators: +- rsi: RSI: Measures momentum to flag overbought/oversold conditions. Usage: Apply 70/30 thresholds and watch for divergence to signal reversals. Tips: In strong trends, RSI may remain extreme; always cross-check with trend analysis. - Volatility Indicators: - - bbands: Bollinger Bands: Volatility indicator consisting of upper, middle (SMA), and lower bands based on standard deviations. Usage: Identify overbought/oversold conditions, volatility expansion/contraction, and potential breakout zones. Tips: Price touching bands doesn't guarantee reversal; use band squeeze for volatility breakout trades. - - atr: ATR (Average True Range): Measures market volatility by calculating the average of true ranges over a period. Usage: Set stop-loss levels, position sizing, and identify high/low volatility periods for strategy adjustment. Tips: Higher ATR indicates more volatile conditions; use for risk management rather than directional signals. +Volatility Indicators: +- boll: Bollinger Middle: A 20 SMA serving as the basis for Bollinger Bands. Usage: Acts as a dynamic benchmark for price movement. Tips: Combine with the upper and lower bands to effectively spot breakouts or reversals. +- boll_ub: Bollinger Upper Band: Typically 2 standard deviations above the middle line. Usage: Signals potential overbought conditions and breakout zones. Tips: Confirm signals with other tools; prices may ride the band in strong trends. +- boll_lb: Bollinger Lower Band: Typically 2 standard deviations below the middle line. Usage: Indicates potential oversold conditions. Tips: Use additional analysis to avoid false reversal signals. +- atr: ATR: Averages true range to measure volatility. Usage: Set stop-loss levels and adjust position sizes based on current market volatility. Tips: It's a reactive measure, so use it as part of a broader risk management strategy. - - Select indicators that provide diverse and complementary information. Avoid redundancy and focus on the most effective combination for crypto market analysis. Also briefly explain why they are suitable for the given crypto market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_crypto_data first to retrieve the cryptocurrency price data. Then use get_indicators_bulk with a list of the specific indicator names (e.g., ["sma", "rsi", "macd"]). Write a very detailed and nuanced report of the trends you observe. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help crypto traders make decisions.""" +Volume-Based Indicators: +- vwma: VWMA: A moving average weighted by volume. Usage: Confirm trends by integrating price action with volume data. Tips: Watch for skewed results from volume spikes; use in combination with other volume analyses. + +- Select indicators that provide diverse and complementary information. Avoid redundancy and focus on the most effective combination for crypto market analysis. Also briefly explain why they are suitable for the given crypto market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_crypto_data first to retrieve the cryptocurrency price data. Then use get_indicators_bulk with a list of the specific indicator names (e.g., ["close_50_sma", "rsi", "macd"]). Write a very detailed and nuanced report of the trends you observe. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help crypto traders make decisions.""" + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" ) diff --git a/tradingagents/agents/analysts/profile_analyst.py b/tradingagents/agents/analysts/profile_analyst.py new file mode 100644 index 00000000..74fda05d --- /dev/null +++ b/tradingagents/agents/analysts/profile_analyst.py @@ -0,0 +1,61 @@ +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +import time +import json +from tradingagents.agents.utils.agent_utils import get_account_balance, get_open_orders +from tradingagents.dataflows.config import get_config + + +def create_profile_analyst(llm): + def profile_analyst_node(state): + current_date = state["trade_date"] + ticker = state["ticker_of_interest"] + + tools = [ + get_account_balance, + get_open_orders, + ] + + system_message = ( + "You are a Profile and Portfolio Analyst tasked with providing a deep-dive assessment of the user's personal trading account and financial health. \ + You will be given access to the user's portfolio data, your objective is to write a comprehensive long report detailing your analysis, insights, and implications for the user's trading capacity after assessing their buying power, asset allocation, risk exposure, and active market participation. \ + Use the `get_account_balance(symbol)` tool (e.g., symbol='BTC/USDT') to determine total equity, free margin, and locked capital. Use the `get_open_orders(symbol)` tool (e.g., symbol='BTC/USDT') to identify capital tied up in pending limit orders or stop-losses. \ + Do not simply list the user's balances or holdings, provide detailed and finegrained analysis and insights. For instance, warn the user if they are overexposed to a single volatile asset, point out if they have too many 'stale' open orders locking up funds, or analyze if their current cash position allows for aggressive moves. Your report should serve as a risk management check before any new trades are executed." + + """ Make sure to append a Markdown table at the end of the report to organize key portfolio metrics (Total Equity, Free Margin, Top Holdings, Risk Level) and actionable recommendations, organized and easy to read.""", + ) + + 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}. We are looking at the coin {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) + result = chain.invoke(state["messages"]) + + report = "" + + if len(result.tool_calls) == 0: + report = result.content + + return { + "messages": [result], + "profile_report": report, + } + + return profile_analyst_node diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index d2cddd76..0cb3db85 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -7,10 +7,11 @@ def create_research_manager(llm, memory: FinancialSituationMemory): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + profile_report = state["profile_report"] investment_debate_state = state["investment_debate_state"] - curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" + curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}\n\n{profile_report}" past_memories = memory.get_memories(curr_situation, n_matches=2) past_memory_str = "" diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index e251f3c5..4bccf7ef 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -7,11 +7,12 @@ def create_risk_manager(llm, memory: FinancialSituationMemory): risk_debate_state = state["risk_debate_state"] market_research_report = state["market_report"] news_report = state["news_report"] - fundamentals_report = state["news_report"] + fundamentals_report = state["fundamentals_report"] sentiment_report = state["sentiment_report"] + profile_report = state["profile_report"] trader_plan = state["investment_plan"] - curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" + curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}\n\n{profile_report}" past_memories = memory.get_memories(curr_situation, n_matches=2) past_memory_str = "" diff --git a/tradingagents/agents/researchers/bear_researcher.py b/tradingagents/agents/researchers/bear_researcher.py index 6ee5d8e3..18cc2e7c 100644 --- a/tradingagents/agents/researchers/bear_researcher.py +++ b/tradingagents/agents/researchers/bear_researcher.py @@ -11,8 +11,9 @@ def create_bear_researcher(llm, memory: FinancialSituationMemory): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + profile_report = state["profile_report"] - curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" + curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}\n\n{profile_report}" past_memories = memory.get_memories(curr_situation, n_matches=2) past_memory_str = "" @@ -35,6 +36,7 @@ Market research report: {market_research_report} Social media sentiment report: {sentiment_report} Latest world affairs news: {news_report} Coin fundamentals report: {fundamentals_report} +Profile analysis report: {profile_report} Conversation history of the debate: {history} Last bull argument: {current_response} Reflections from similar situations and lessons learned: {past_memory_str} diff --git a/tradingagents/agents/researchers/bull_researcher.py b/tradingagents/agents/researchers/bull_researcher.py index e5be2337..88101886 100644 --- a/tradingagents/agents/researchers/bull_researcher.py +++ b/tradingagents/agents/researchers/bull_researcher.py @@ -11,8 +11,9 @@ def create_bull_researcher(llm, memory: FinancialSituationMemory): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + profile_report = state["profile_report"] - curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" + curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}\n\n{profile_report}" past_memories = memory.get_memories(curr_situation, n_matches=2) past_memory_str = "" @@ -33,6 +34,7 @@ Market research report: {market_research_report} Social media sentiment report: {sentiment_report} Latest world affairs news: {news_report} Company fundamentals report: {fundamentals_report} +Profile analysis report: {profile_report} Conversation history of the debate: {history} Last bear argument: {current_response} Reflections from similar situations and lessons learned: {past_memory_str} diff --git a/tradingagents/agents/risk_mgmt/aggresive_debator.py b/tradingagents/agents/risk_mgmt/aggresive_debator.py index 575da9f5..e852ed59 100644 --- a/tradingagents/agents/risk_mgmt/aggresive_debator.py +++ b/tradingagents/agents/risk_mgmt/aggresive_debator.py @@ -11,6 +11,7 @@ def create_risky_debator(llm): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + profile_report = state["profile_report"] trader_decision = state["trader_investment_plan"] @@ -24,6 +25,7 @@ Market Research Report: {market_research_report} Social Media Sentiment Report: {sentiment_report} Latest World Affairs Report: {news_report} Fundamentals Report: {fundamentals_report} +Profile Report: {profile_report} Here is the current conversation history: {history} Here are the last arguments from the conservative analyst: {current_safe_response} Here are the last arguments from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point. Engage actively by addressing any specific concerns raised, refuting the weaknesses in their logic, and asserting the benefits of risk-taking to outpace market norms. Maintain a focus on debating and persuading, not just presenting data. Challenge each counterpoint to underscore why a high-risk approach is optimal. Output conversationally as if you are speaking without any special formatting.""" diff --git a/tradingagents/agents/risk_mgmt/conservative_debator.py b/tradingagents/agents/risk_mgmt/conservative_debator.py index c863351b..5e09c8f8 100644 --- a/tradingagents/agents/risk_mgmt/conservative_debator.py +++ b/tradingagents/agents/risk_mgmt/conservative_debator.py @@ -11,6 +11,7 @@ def create_safe_debator(llm): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + profile_report = state["profile_report"] trader_decision = state["trader_investment_plan"] @@ -24,6 +25,7 @@ Market Research Report: {market_research_report} Social Media Sentiment Report: {sentiment_report} Latest World Affairs Report: {news_report} Fundamentals Report: {fundamentals_report} +Profile Report: {profile_report} Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point. Engage by questioning their optimism and emphasizing the potential downsides they may have overlooked. Address each of their counterpoints to showcase why a conservative stance is ultimately the safest path for the firm's assets. Focus on debating and critiquing their arguments to demonstrate the strength of a low-risk strategy over their approaches. Output conversationally as if you are speaking without any special formatting.""" diff --git a/tradingagents/agents/risk_mgmt/neutral_debator.py b/tradingagents/agents/risk_mgmt/neutral_debator.py index efc48c3f..110e1ec0 100644 --- a/tradingagents/agents/risk_mgmt/neutral_debator.py +++ b/tradingagents/agents/risk_mgmt/neutral_debator.py @@ -11,6 +11,7 @@ def create_neutral_debator(llm): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + profile_report = state["profile_report"] trader_decision = state["trader_investment_plan"] @@ -24,6 +25,7 @@ Market Research Report: {market_research_report} Social Media Sentiment Report: {sentiment_report} Latest World Affairs Report: {news_report} Fundamentals Report: {fundamentals_report} +Profile Report: {profile_report} Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the safe analyst: {current_safe_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point. Engage actively by analyzing both sides critically, addressing weaknesses in the risky and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting.""" diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index 167c0b6a..4bcdbc77 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -8,8 +8,9 @@ def create_trader(llm, memory): sentiment_report = state["sentiment_report"] news_report = state["news_report"] fundamentals_report = state["fundamentals_report"] + profile_report = state["profile_report"] - curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}" + curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}\n\n{profile_report}" past_memories = memory.get_memories(curr_situation, n_matches=2) past_memory_str = "" diff --git a/tradingagents/agents/utils/agent_states.py b/tradingagents/agents/utils/agent_states.py index b1541db3..c3bdefce 100644 --- a/tradingagents/agents/utils/agent_states.py +++ b/tradingagents/agents/utils/agent_states.py @@ -57,6 +57,7 @@ class AgentState(MessagesState): str, "Report from the News Researcher of current world affairs" ] fundamentals_report: Annotated[str, "Report from the Fundamentals Researcher"] + profile_report: Annotated[str, "Report from the Profile Analyst"] # researcher team discussion step investment_debate_state: Annotated[ diff --git a/tradingagents/agents/utils/agent_utils.py b/tradingagents/agents/utils/agent_utils.py index 814eb1a1..820d387f 100644 --- a/tradingagents/agents/utils/agent_utils.py +++ b/tradingagents/agents/utils/agent_utils.py @@ -28,6 +28,10 @@ from tradingagents.agents.utils.news_data_tools import ( from tradingagents.agents.utils.sentiment_tools import ( get_fear_and_greed, ) +from tradingagents.agents.utils.profile_tools import ( + get_account_balance, + get_open_orders, +) def create_msg_delete(): def delete_messages(state): diff --git a/tradingagents/agents/utils/profile_tools.py b/tradingagents/agents/utils/profile_tools.py new file mode 100644 index 00000000..3857e879 --- /dev/null +++ b/tradingagents/agents/utils/profile_tools.py @@ -0,0 +1,29 @@ +from langchain_core.tools import tool +from typing import Annotated +from tradingagents.dataflows.interface import route_to_vendor + +@tool +def get_account_balance( + symbol: Annotated[str, "The trading pair symbol, e.g., 'BTC/USDT'"], +) -> str: + """ + Fetches the account balance for a specific trading pair. + Args: + symbol (str): The trading pair symbol, e.g., 'BTC/USDT' + Returns: + str: A formatted string containing account balance details + """ + return route_to_vendor("get_account_balance", symbol) + +@tool +def get_open_orders( + symbol: Annotated[str, "The trading pair symbol, e.g., 'BTC/USDT'"], +) -> str: + """ + Fetches the list of open orders for a specific trading pair. + Args: + symbol (str): The trading pair symbol, e.g., 'BTC/USDT' + Returns: + str: A formatted string containing open orders details + """ + return route_to_vendor("get_open_orders", symbol) \ No newline at end of file diff --git a/tradingagents/dataflows/binance.py b/tradingagents/dataflows/binance.py index bdf4c160..c7458e1f 100644 --- a/tradingagents/dataflows/binance.py +++ b/tradingagents/dataflows/binance.py @@ -47,7 +47,7 @@ def get_market_data(symbol: str, start_date: str, end_date: str): start_epoch = int(datetime.strptime(start_date, "%Y-%m-%d").timestamp() * 1000) end_epoch = int(datetime.strptime(end_date, "%Y-%m-%d").timestamp() * 1000) - print(f"DEBUG: Fetching data for {formatted_symbol} from {start_date} to {end_date}") + # print(f"DEBUG: Fetching data for {formatted_symbol} from {start_date} to {end_date}") try: client = get_binance_client() response = client.rest_api.klines( @@ -58,7 +58,7 @@ def get_market_data(symbol: str, start_date: str, end_date: str): ) rate_limits = response.rate_limits - print(f"DEBUG: klines() rate limits: {rate_limits}") + # print(f"DEBUG: klines() rate limits: {rate_limits}") data = response.data() diff --git a/tradingagents/dataflows/bybit.py b/tradingagents/dataflows/bybit.py index 54997fdd..3cd52217 100644 --- a/tradingagents/dataflows/bybit.py +++ b/tradingagents/dataflows/bybit.py @@ -2,11 +2,16 @@ import hashlib import hmac import json import time -from typing import Dict, Optional +from typing import Dict, Optional, List from urllib.parse import urlencode import requests -from tradingagents.dataflows.config import get_config +from .config import get_config + +import json +from datetime import datetime, timedelta, timezone +import pandas as pd +from stockstats import StockDataFrame def bybit_v5_request(method: str, path: str, params: Optional[Dict] = None, body: Optional[Dict] = None) -> Dict: @@ -62,28 +67,566 @@ def bybit_v5_request(method: str, path: str, params: Optional[Dict] = None, body return data - -def get_wallet_balance(coin: str, account_type: str = "UNIFIED") -> float: - """Fetch wallet balance for a specific coin from Bybit.""" +def get_account_balance(symbol: str) -> dict: + """ + To determine total equity, available free margin for new trades, and locked capital. + + Args: + symbol: Format "BASE/QUOTE" (e.g., "BTC/USDT") + quote_coin: The currency used for buying (e.g., "USDT") + """ + if "/" not in symbol: + return f"Error: Symbol '{symbol}' is not in the correct format. Please use 'BASE/QUOTE' format, e.g., 'BTC/USDT'." + base_coin, quote_coin = symbol.split("/") + # 1. Fetch all assets from Bybit (omitting 'coin' gets everything) data = bybit_v5_request("GET", "/v5/account/wallet-balance", { - "accountType": account_type, - "coin": coin.upper() + "accountType": "UNIFIED" }) - # Parse response to get wallet balance + + # 2. Parse the raw response + try: + raw_list = data["result"]["list"][0]["coin"] + # Convert list to a dictionary for easy lookup: {'BTC': {...}, 'USDT': {...}} + result = { + item["coin"]: {k: float(v) if v else 0.0 for k, v in item.items() if k != "coin"} + for item in raw_list + } + except (IndexError, KeyError, TypeError): + result = {"error": "Could not retrieve wallet balance"} + + total_equity = sum(asset.get("usdValue", 0.0) for asset in result.values()) + + report = f"# Account Balance Report for {base_coin}/{quote_coin}\n" + report += f"** Total Equity: ${total_equity} **\n" + report += f"## {quote_coin} (Quote) Details:\n" + report += json.dumps(result.get(quote_coin, {}), indent=2) + "\n" + report += f"## {base_coin} (Base) Details:\n" + report += json.dumps(result.get(base_coin, {}), indent=2) + "\n" + return report + +def get_symbol(base_coin: str, quote_coin: str) -> str: + """ + Safely retrieves the correct Bybit symbol (e.g., "BTCUSDT") for a given base/quote pair. + + Args: + base_coin: The asset (e.g., "BTC") + quote_coin: The currency (e.g., "USDT") + category: "linear", "spot", or "inverse" + + Returns: + The valid symbol string (e.g., "BTCUSDT") or None if not found. + """ + # 1. Query the API specifically for this Base Coin + # This filters the search on the server side, which is much faster. + params = { + "category": "spot", + "baseCoin": base_coin.upper(), + "limit": 20 # We only expect a few matches (e.g., BTCUSDT, BTC-PERP) + } + + data = bybit_v5_request("GET", "/v5/market/instruments-info", params) + result = data.get("result", {}) - accounts = result.get("list", []) + instruments = result.get("list", []) - if not accounts: - return 0.0 + # 2. Find the exact match for the Quote Coin + # This handles cases where BTC might pair with USDT, USDC, or DAI + for item in instruments: + if item.get("quoteCoin") == quote_coin.upper() and item.get("baseCoin") == base_coin.upper(): + return item.get("symbol") - coins = accounts[0].get("coin", []) - for coin_data in coins: - if coin_data.get("coin") == coin.upper(): - return float(coin_data.get("walletBalance", 0)) + # 3. Fallback/Error handling + return None - return 0.0 +def get_open_orders(symbol: str) -> str: + """ + Fetches active orders and returns a text report analyzing capital lock-up and order age. + """ + if "/" not in symbol: + return f"Error: Symbol '{symbol}' is not in the correct format. Please use 'BASE/QUOTE' format, e.g., 'BTC/USDT'." + base_coin, quote_coin = symbol.split("/") + symbol = get_symbol(base_coin, quote_coin) + if not symbol: + return f"Error: No valid spot symbol found for {base_coin}/{quote_coin}" + # 1. Fetch Open Orders + data = bybit_v5_request("GET", "/v5/order/realtime", { + "category": "spot", + "symbol": symbol.upper(), + "openOnly": 0 # 0=Active orders (Pending) + }) + result = data.get("result", {}) + orders = result.get("list", []) + + for i in range(len(orders)): + # try to change to float if can + for k, v in orders[i].items(): + if k in ["orderLinkId", "orderId"]: + continue + try: + orders[i][k] = float(v) + except: + pass + # change createdTime and updatedTime to yyyy-mm-dd hh:mm:ss format (utc) + orders[i]["createdTime"] = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(orders[i]["createdTime"]/1000)) + orders[i]["updatedTime"] = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(orders[i]["updatedTime"]/1000)) + + report = f"# Open Orders for {symbol.upper()}\n" + report += json.dumps(orders, indent=2) + + return report + + +def get_market_data(symbol:str, start_date: str, end_date: str) -> str: + """ + Fetches historical Daily (1D) OHLCV data for a specific date range. + + Args: + symbol: Format "BASE/QUOTE" (e.g., "BTC/USDT"). + start_date: Format "YYYY-MM-DD" (Inclusive). + end_date: Format "YYYY-MM-DD" (Inclusive). + + Returns: + A formatted CSV-style string report. + """ + if "/" not in symbol: + return f"Error: Symbol '{symbol}' is not in the correct format. Please use 'BASE/QUOTE' format, e.g., 'BTC/USDT'." + # 1. Convert Date Strings to Timestamps (ms) + try: + # Start of the start_date (00:00:00) + dt_start = datetime.strptime(start_date, "%Y-%m-%d") + ts_start = int(dt_start.timestamp() * 1000) + + # End of the end_date (23:59:59) - Bybit 'end' parameter is inclusive if valid candle exists + dt_end = datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1) + ts_end = int(dt_end.timestamp() * 1000) + except ValueError: + return "Error: Invalid date format. Please use YYYY-MM-DD." + + # 2. Request Data from Bybit + # We use interval "D" for Daily. Limit 1000 covers ~2.7 years of daily data in one call. + base_coin, quote_coin = symbol.split("/") + symbol2 = get_symbol(base_coin, quote_coin) + if not symbol2: + return f"# Error: No valid spot symbol found for {base_coin}/{quote_coin}." + params = { + "category": "spot", + "symbol": symbol2.upper(), + "interval": "D", + "start": ts_start, + "end": ts_end, + "limit": 1000 + } + + data = bybit_v5_request("GET", "/v5/market/kline", params) + + result = data.get("result", {}) + raw_candles = result.get("list", []) # Returns [timestamp, open, high, low, close, volume, turnover] + + if not raw_candles: + return f"# No market data found for {symbol.upper()} from {start_date} to {end_date}." + + # 3. Format Data + # Bybit returns Newest -> Oldest. We reverse it to get chronological order (Oldest -> Newest). + raw_candles.reverse() + + csv_lines = [] + + for candle in raw_candles: + # Parse Timestamp + ts_ms = int(candle[0]) + date_str = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc).strftime('%Y-%m-%d') + + # Parse Values + open_p = candle[1] + high_p = candle[2] + low_p = candle[3] + close_p = candle[4] + volume = float(candle[5]) # Volume in Base Currency (e.g. BTC) + + # Format Line: Date,Open,High,Low,Close,Volume + # Note: We omit Dividends/Stock Splits as requested + line = f"{date_str},{open_p},{high_p},{low_p},{close_p},{int(volume)}" + csv_lines.append(line) + + # 4. Construct Final Report Header + current_time = datetime.now(tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC') + + header = [ + f"# Crypto data for {symbol.upper()} from {start_date} to {end_date}", + f"# Total records: {len(csv_lines)}", + f"# Data retrieved on: {current_time}", + "", + "Date,Open,High,Low,Close,Volume" + ] + + return "\n".join(header + csv_lines) + +def get_crypto_indicator_window( + symbol: str, + indicator: str, + curr_date: str, + look_back_days: int +) -> str: + """ + Calculates technical indicators for a crypto pair using Bybit data. + Fix: Uses index lookup for dates since stockstats sets 'date' as the index. + """ + if "/" not in symbol: + return f"Error: Symbol '{symbol}' is not in the correct format. Please use 'BASE/QUOTE' format, e.g., 'BTC/USDT'." + base_coin, quote_coin = symbol.split("/") + symbol2 = get_symbol(base_coin, quote_coin) + + # 1. Define Supported Indicators (Same as before) + best_ind_params = { + "close_50_sma": ( + "50 SMA: A medium-term trend indicator. " + "Usage: Identify trend direction and serve as dynamic support/resistance. " + "Tips: It lags price; combine with faster indicators for timely signals." + ), + "close_200_sma": ( + "200 SMA: A long-term trend benchmark. " + "Usage: Confirm overall market trend and identify golden/death cross setups. " + "Tips: It reacts slowly; best for strategic trend confirmation rather than frequent trading entries." + ), + "close_10_ema": ( + "10 EMA: A responsive short-term average. " + "Usage: Capture quick shifts in momentum and potential entry points. " + "Tips: Prone to noise in choppy markets; use alongside longer averages for filtering false signals." + ), + # MACD Related + "macd": ( + "MACD: Computes momentum via differences of EMAs. " + "Usage: Look for crossovers and divergence as signals of trend changes. " + "Tips: Confirm with other indicators in low-volatility or sideways markets." + ), + "macds": ( + "MACD Signal: An EMA smoothing of the MACD line. " + "Usage: Use crossovers with the MACD line to trigger trades. " + "Tips: Should be part of a broader strategy to avoid false positives." + ), + "macdh": ( + "MACD Histogram: Shows the gap between the MACD line and its signal. " + "Usage: Visualize momentum strength and spot divergence early. " + "Tips: Can be volatile; complement with additional filters in fast-moving markets." + ), + # Momentum Indicators + "rsi": ( + "RSI: Measures momentum to flag overbought/oversold conditions. " + "Usage: Apply 70/30 thresholds and watch for divergence to signal reversals. " + "Tips: In strong trends, RSI may remain extreme; always cross-check with trend analysis." + ), + # Volatility Indicators + "boll": ( + "Bollinger Middle: A 20 SMA serving as the basis for Bollinger Bands. " + "Usage: Acts as a dynamic benchmark for price movement. " + "Tips: Combine with the upper and lower bands to effectively spot breakouts or reversals." + ), + "boll_ub": ( + "Bollinger Upper Band: Typically 2 standard deviations above the middle line. " + "Usage: Signals potential overbought conditions and breakout zones. " + "Tips: Confirm signals with other tools; prices may ride the band in strong trends." + ), + "boll_lb": ( + "Bollinger Lower Band: Typically 2 standard deviations below the middle line. " + "Usage: Indicates potential oversold conditions. " + "Tips: Use additional analysis to avoid false reversal signals." + ), + "atr": ( + "ATR: Averages true range to measure volatility. " + "Usage: Set stop-loss levels and adjust position sizes based on current market volatility. " + "Tips: It's a reactive measure, so use it as part of a broader risk management strategy." + ), + # Volume-Based Indicators + "vwma": ( + "VWMA: A moving average weighted by volume. " + "Usage: Confirm trends by integrating price action with volume data. " + "Tips: Watch for skewed results from volume spikes; use in combination with other volume analyses." + ), + "mfi": ( + "MFI: The Money Flow Index is a momentum indicator that uses both price and volume to measure buying and selling pressure. " + "Usage: Identify overbought (>80) or oversold (<20) conditions and confirm the strength of trends or reversals. " + "Tips: Use alongside RSI or MACD to confirm signals; divergence between price and MFI can indicate potential reversals." + ), + } + + # 2. Calculate Date Range + target_date_dt = datetime.strptime(curr_date, "%Y-%m-%d") + start_window_dt = target_date_dt - timedelta(days=look_back_days) + + # Buffer: Fetch 250 extra days before start_window for lagging indicators (like 200 SMA) + buffer_days = 250 + fetch_start_dt = start_window_dt - timedelta(days=buffer_days) + + # Convert to timestamps for Bybit (ms) + ts_start = int(fetch_start_dt.timestamp() * 1000) + ts_end = int((target_date_dt + timedelta(days=1)).timestamp() * 1000) + + # 3. Fetch Data from Bybit + params = { + "category": "linear", + "symbol": symbol2.upper(), + "interval": "D", + "start": ts_start, + "end": ts_end, + "limit": 1000 + } + + # Ensure 'bybit_v5_request' is defined in your scope + data = bybit_v5_request("GET", "/v5/market/kline", params) + raw_list = data.get("result", {}).get("list", []) + + if not raw_list: + return f"Error: No data found for {symbol2}." + + # 4. Prepare DataFrame + parsed_data = [] + for row in raw_list: + parsed_data.append({ + # Standardize date format for the Index + "date": datetime.fromtimestamp(int(row[0])/1000).strftime('%Y-%m-%d'), + "open": float(row[1]), + "high": float(row[2]), + "low": float(row[3]), + "close": float(row[4]), + "volume": float(row[5]) + }) + + df = pd.DataFrame(parsed_data) + # Sort Ascending (Oldest -> Newest) is CRITICAL for stockstats + df = df.sort_values("date").reset_index(drop=True) + + # 5. Calculate Indicator + # retype(df) sets 'date' column as the Index! + stock = StockDataFrame.retype(df) + + try: + # Trigger calculation + _ = stock[indicator] + except KeyError: + return f"Error: Could not calculate {indicator}. Check if supported." + + # 6. Build Report + report_lines = [] + current_check_date = target_date_dt + + while current_check_date >= start_window_dt: + date_str = current_check_date.strftime('%Y-%m-%d') + + # --- FIX: Use .loc to find the row by Index (Date) --- + try: + # Locate the row using the date string index + val = stock.loc[date_str][indicator] + + # Format value + if isinstance(val, (float, int)): + val_str = f"{val:.4f}" + else: + val_str = str(val) + + report_lines.append(f"{date_str}: {val_str}") + + except KeyError: + # Date not found in index + report_lines.append(f"{date_str}: N/A (No Data)") + except Exception as e: + report_lines.append(f"{date_str}: Error ({str(e)})") + + current_check_date -= timedelta(days=1) + + # 7. Final Output + description = best_ind_params.get(indicator, "No description available.") + + result_str = ( + f"## {indicator} values for {symbol2} from {start_window_dt.strftime('%Y-%m-%d')} to {curr_date}:\n\n" + + "\n".join(report_lines) + + "\n\n" + + description + ) + + return result_str + +def get_crypto_indicators_bulk( + symbol: str, + indicators: List[str], + curr_date: str, + look_back_days: int +) -> str: + """ + Calculates multiple technical indicators for a crypto pair in one go. + + Args: + symbol: e.g., "BTC/USDT" + indicators: List of keys, e.g. ["rsi", "close_200_sma", "macd"] + curr_date: "YYYY-MM-DD" + look_back_days: Days of history to show in the report. + """ + if "/" not in symbol: + return f"Error: Symbol '{symbol}' is not in the correct format. Please use 'BASE/QUOTE' format, e.g., 'BTC/USDT'." + base_coin, quote_coin = symbol.split("/") + symbol2 = get_symbol(base_coin, quote_coin) + + # 1. Define Descriptions + best_ind_params = { + "close_50_sma": ( + "50 SMA: A medium-term trend indicator. " + "Usage: Identify trend direction and serve as dynamic support/resistance. " + "Tips: It lags price; combine with faster indicators for timely signals." + ), + "close_200_sma": ( + "200 SMA: A long-term trend benchmark. " + "Usage: Confirm overall market trend and identify golden/death cross setups. " + "Tips: It reacts slowly; best for strategic trend confirmation rather than frequent trading entries." + ), + "close_10_ema": ( + "10 EMA: A responsive short-term average. " + "Usage: Capture quick shifts in momentum and potential entry points. " + "Tips: Prone to noise in choppy markets; use alongside longer averages for filtering false signals." + ), + # MACD Related + "macd": ( + "MACD: Computes momentum via differences of EMAs. " + "Usage: Look for crossovers and divergence as signals of trend changes. " + "Tips: Confirm with other indicators in low-volatility or sideways markets." + ), + "macds": ( + "MACD Signal: An EMA smoothing of the MACD line. " + "Usage: Use crossovers with the MACD line to trigger trades. " + "Tips: Should be part of a broader strategy to avoid false positives." + ), + "macdh": ( + "MACD Histogram: Shows the gap between the MACD line and its signal. " + "Usage: Visualize momentum strength and spot divergence early. " + "Tips: Can be volatile; complement with additional filters in fast-moving markets." + ), + # Momentum Indicators + "rsi": ( + "RSI: Measures momentum to flag overbought/oversold conditions. " + "Usage: Apply 70/30 thresholds and watch for divergence to signal reversals. " + "Tips: In strong trends, RSI may remain extreme; always cross-check with trend analysis." + ), + # Volatility Indicators + "boll": ( + "Bollinger Middle: A 20 SMA serving as the basis for Bollinger Bands. " + "Usage: Acts as a dynamic benchmark for price movement. " + "Tips: Combine with the upper and lower bands to effectively spot breakouts or reversals." + ), + "boll_ub": ( + "Bollinger Upper Band: Typically 2 standard deviations above the middle line. " + "Usage: Signals potential overbought conditions and breakout zones. " + "Tips: Confirm signals with other tools; prices may ride the band in strong trends." + ), + "boll_lb": ( + "Bollinger Lower Band: Typically 2 standard deviations below the middle line. " + "Usage: Indicates potential oversold conditions. " + "Tips: Use additional analysis to avoid false reversal signals." + ), + "atr": ( + "ATR: Averages true range to measure volatility. " + "Usage: Set stop-loss levels and adjust position sizes based on current market volatility. " + "Tips: It's a reactive measure, so use it as part of a broader risk management strategy." + ), + # Volume-Based Indicators + "vwma": ( + "VWMA: A moving average weighted by volume. " + "Usage: Confirm trends by integrating price action with volume data. " + "Tips: Watch for skewed results from volume spikes; use in combination with other volume analyses." + ), + "mfi": ( + "MFI: The Money Flow Index is a momentum indicator that uses both price and volume to measure buying and selling pressure. " + "Usage: Identify overbought (>80) or oversold (<20) conditions and confirm the strength of trends or reversals. " + "Tips: Use alongside RSI or MACD to confirm signals; divergence between price and MFI can indicate potential reversals." + ), + } + + # 2. Fetch Data (ONLY ONCE) + # --------------------------------------------------------- + target_date_dt = datetime.strptime(curr_date, "%Y-%m-%d") + start_window_dt = target_date_dt - timedelta(days=look_back_days) + + # Buffer for lagging indicators + buffer_days = 250 + fetch_start_dt = start_window_dt - timedelta(days=buffer_days) + + ts_start = int(fetch_start_dt.timestamp() * 1000) + ts_end = int((target_date_dt + timedelta(days=1)).timestamp() * 1000) + + params = { + "category": "linear", + "symbol": symbol2.upper(), + "interval": "D", + "start": ts_start, + "end": ts_end, + "limit": 1000 + } + + data = bybit_v5_request("GET", "/v5/market/kline", params) + raw_list = data.get("result", {}).get("list", []) + + if not raw_list: + return f"Error: No data found for {symbol2}." + + parsed_data = [] + for row in raw_list: + parsed_data.append({ + "date": datetime.fromtimestamp(int(row[0])/1000).strftime('%Y-%m-%d'), + "open": float(row[1]), + "high": float(row[2]), + "low": float(row[3]), + "close": float(row[4]), + "volume": float(row[5]) + }) + + df = pd.DataFrame(parsed_data) + df = df.sort_values("date").reset_index(drop=True) + stock = StockDataFrame.retype(df) + # --------------------------------------------------------- + + # 3. Process Each Indicator + final_report = [f"# Technical Indicators Report for {symbol2}\n" + '-' * 40] + + for ind in indicators: + # Pre-calculation check + try: + # Trigger calculation of the specific column + _ = stock[ind] + except (KeyError, ValueError): + final_report.append(f"## Error: Indicator '{ind}' is not supported or failed to calculate.\n") + continue + + # Generate Block Report + report_lines = [] + current_check_date = target_date_dt + + while current_check_date >= start_window_dt: + date_str = current_check_date.strftime('%Y-%m-%d') + try: + # Use .loc because 'date' is the index + val = stock.loc[date_str][ind] + + if isinstance(val, (float, int)): + val_str = f"{val:.4f}" + else: + val_str = str(val) + report_lines.append(f"{date_str}: {val_str}") + except KeyError: + report_lines.append(f"{date_str}: N/A") + + current_check_date -= timedelta(days=1) + + description = best_ind_params.get(ind, "No description available.") + + block = ( + f"## {ind} values for {symbol2} from {start_window_dt.strftime('%Y-%m-%d')} to {curr_date}:\n\n" + + "\n".join(report_lines) + + "\n\n" + + f"Description: {description}\n" + + "-" * 40 # Separator line + ) + final_report.append(block) + + return "\n\n".join(final_report) def get_order_status(order_id: str, category: str = "spot") -> Dict: """ @@ -130,28 +673,6 @@ def cancel_order(order_id: str, symbol: str, category: str = "spot") -> Dict: return data.get("result", {}) -def get_open_orders(symbol: Optional[str] = None, category: str = "spot") -> Dict: - """ - Get all open orders. - - Args: - symbol: Optional trading pair to filter by - category: Trading category ("spot", "linear", "inverse") - - Returns: - Dict containing list of open orders - """ - params = { - "category": category.lower() - } - - if symbol: - params["symbol"] = symbol.upper() - - data = bybit_v5_request("GET", "/v5/order/realtime", params=params) - return data.get("result", {}) - - def get_order_history( symbol: Optional[str] = None, category: str = "spot", diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index 2c330da5..8ae875ff 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -23,6 +23,13 @@ from .taapi import get_crypto_stats_indicators_window, get_crypto_stats_indicato # Configuration and routing logic from .config import get_config +from .bybit import ( + get_account_balance, + get_open_orders, + get_market_data as get_bybit_crypto_data, + get_crypto_indicator_window as get_bybit_crypto_indicator_window, + get_crypto_indicators_bulk as get_bybit_crypto_indicators_bulk +) # Tools organized by category TOOLS_CATEGORIES = { @@ -65,6 +72,13 @@ TOOLS_CATEGORIES = { "get_insider_transactions", "get_fear_and_greed" ] + }, + "profile_data": { + "description": "User profile related data", + "tools": [ + "get_account_balance", + "get_open_orders", + ] } } @@ -77,7 +91,8 @@ VENDOR_LIST = [ "telegram", "coin_gecko", "alpha_vantage", - "taapi" + "taapi", + "bybit" ] # Mapping of methods to their vendor-specific implementations @@ -90,13 +105,19 @@ VENDOR_METHODS = { # core_crypto_apis "get_crypto_data": { "binance": get_binance_crypto_data, + "bybit": get_bybit_crypto_data, }, # technical_indicators "get_indicators": { - "taapi": get_crypto_stats_indicators_window, + # "taapi": get_crypto_stats_indicators_window, + "bybit" : get_bybit_crypto_indicator_window + # "alpha_vantage": get_alpha_vantage_indicator, + # "yfinance": get_stock_stats_indicators_window, + # "local": get_stock_stats_indicators_window }, "get_indicators_bulk": { - "taapi": get_crypto_stats_indicators, + # "taapi": get_crypto_stats_indicators, + "bybit": get_bybit_crypto_indicators_bulk }, # fundamental_data "get_fundamentals": { @@ -147,6 +168,12 @@ VENDOR_METHODS = { "get_fear_and_greed": { "local": get_fear_and_greed, }, + "get_account_balance": { + "bybit": get_account_balance, + }, + "get_open_orders": { + "bybit": get_open_orders, + }, } def get_category_for_method(method: str) -> str: diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 5151360d..585a5627 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -24,15 +24,21 @@ DEFAULT_CONFIG = { # Data vendor configuration # Category-level configuration (default for all tools in category) "data_vendors": { - "core_crypto_apis": "binance", # Options: binance - "technical_indicators": "taapi", # Options: taapi - "fundamental_data": "openai", # Options: openai - "news_data": "openai", # Options: local, openai, telegram + "core_crypto_apis": "bybit", # Options: binance, bybit + "core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local + "technical_indicators": "bybit", # Options: bybit + "fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local + "news_data": "openai", # Options: openai, alpha_vantage, google, local + "profile_data": "bybit", # Options: bybit, local }, # Tool-level configuration (takes precedence over category-level) "tool_vendors": { "get_global_news": "telegram" # Override category default }, + # Tool provider settings + "tool_providers": { + "TAAPI_BASE_URL": os.getenv("TAAPI_BASE_URL", "https://api.taapi.io"), + }, "external": { "BINANCE_API_KEY": os.getenv("BINANCE_API_KEY", ""), "TAAPI_BASE_URL": os.getenv("TAAPI_BASE_URL", "https://api.taapi.io"), @@ -51,4 +57,4 @@ DEFAULT_CONFIG = { "REDIS_PASSWORD": os.getenv("REDIS_PASSWORD", "defaultpassword"), "REDIS_DB": int(os.getenv("REDIS_DB", 0)), }, -} \ No newline at end of file +} diff --git a/tradingagents/graph/conditional_logic.py b/tradingagents/graph/conditional_logic.py index e7c87859..d82df442 100644 --- a/tradingagents/graph/conditional_logic.py +++ b/tradingagents/graph/conditional_logic.py @@ -42,6 +42,14 @@ class ConditionalLogic: if last_message.tool_calls: return "tools_fundamentals" return "Msg Clear Fundamentals" + + def should_continue_profile(self, state: AgentState): + """Determine if profile analysis should continue.""" + messages = state["messages"] + last_message = messages[-1] + if last_message.tool_calls: + return "tools_profile" + return "Msg Clear Profile" def should_continue_debate(self, state: AgentState) -> str: """Determine if debate should continue.""" diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index c5d119dc..5d2fda62 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -39,6 +39,7 @@ class Propagator: "fundamentals_report": "", "sentiment_report": "", "news_report": "", + "profile_report": "", } def get_graph_args(self) -> Dict[str, Any]: diff --git a/tradingagents/graph/reflection.py b/tradingagents/graph/reflection.py index 33303231..46015a15 100644 --- a/tradingagents/graph/reflection.py +++ b/tradingagents/graph/reflection.py @@ -27,6 +27,7 @@ Your goal is to deliver detailed insights into investment decisions and highligh - Price movement analysis. - Overall market data analysis - News analysis. + - Sentiment analysis. - Social media and sentiment analysis. - Fundamental data analysis. - Weight the importance of each factor in the decision-making process. @@ -52,8 +53,9 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur curr_sentiment_report = current_state["sentiment_report"] curr_news_report = current_state["news_report"] curr_fundamentals_report = current_state["fundamentals_report"] + curr_profile_report = current_state["profile_report"] - return f"{curr_market_report}\n\n{curr_sentiment_report}\n\n{curr_news_report}\n\n{curr_fundamentals_report}" + return f"{curr_market_report}\n\n{curr_sentiment_report}\n\n{curr_news_report}\n\n{curr_fundamentals_report}\n\n{curr_profile_report}" def _reflect_on_component( self, component_type: str, report: str, situation: str, returns_losses diff --git a/tradingagents/graph/setup.py b/tradingagents/graph/setup.py index b270ffc0..820af585 100644 --- a/tradingagents/graph/setup.py +++ b/tradingagents/graph/setup.py @@ -38,7 +38,7 @@ class GraphSetup: self.conditional_logic = conditional_logic def setup_graph( - self, selected_analysts=["market", "social", "news", "fundamentals"] + self, selected_analysts=["market", "social", "news", "fundamentals", "profile"] ): """Set up and compile the agent workflow graph. @@ -48,6 +48,7 @@ class GraphSetup: - "social": Social media analyst - "news": News analyst - "fundamentals": Fundamentals analyst + - "profile": Profile analyst """ if len(selected_analysts) == 0: raise ValueError("Trading Agents Graph Setup Error: no analysts selected!") @@ -84,6 +85,13 @@ class GraphSetup: ) delete_nodes["fundamentals"] = create_msg_delete() tool_nodes["fundamentals"] = self.tool_nodes["fundamentals"] + + if "profile" in selected_analysts: + analyst_nodes["profile"] = create_profile_analyst( + self.quick_thinking_llm + ) + delete_nodes["profile"] = create_msg_delete() + tool_nodes["profile"] = self.tool_nodes["profile"] # Create researcher and manager nodes bull_researcher_node = create_bull_researcher( diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 1307423e..3a8ec920 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -38,7 +38,9 @@ from tradingagents.agents.utils.agent_utils import ( get_insider_sentiment, get_insider_transactions, get_global_news, - get_fear_and_greed + get_fear_and_greed, + get_account_balance, + get_open_orders ) from .conditional_logic import ConditionalLogic @@ -53,7 +55,7 @@ class TradingAgentsGraph: def __init__( self, - selected_analysts=["market", "social", "news", "fundamentals"], + selected_analysts=["market", "social", "news", "fundamentals", "profile"], debug=False, config: Dict[str, Any] = None, ): @@ -133,7 +135,7 @@ class TradingAgentsGraph: # Crypto data tools get_crypto_data, # Core stock data tools - get_stock_data, + # get_stock_data, # Technical indicators get_indicators, get_indicators_bulk, @@ -166,6 +168,13 @@ class TradingAgentsGraph: # get_income_statement, ] ), + "profile": ToolNode( + [ + # Profile analysis tools can be added here + get_account_balance, + get_open_orders, + ] + ), } def propagate(self, company_name, trade_date): @@ -212,6 +221,7 @@ class TradingAgentsGraph: "sentiment_report": final_state["sentiment_report"], "news_report": final_state["news_report"], "fundamentals_report": final_state["fundamentals_report"], + "profile_report": final_state["profile_report"], "investment_debate_state": { "bull_history": final_state["investment_debate_state"]["bull_history"], "bear_history": final_state["investment_debate_state"]["bear_history"],